Repository: ProcessMaker/modeler
Pull request #1488 (2023-04-11 00:01:57)

Canvas Usability Improvements v1


Patch:
diff --git a/src/components/controls/controls.scss b/src/components/controls/controls.scss
index 3efd559f..e8a1b412 100644
--- a/src/components/controls/controls.scss
+++ b/src/components/controls/controls.scss
@@ -3,7 +3,7 @@ $controls-column-compressed-max-width: 95px;
 $controls-transition: 0.3s;
 
 .controls {
-  z-index: 1;
+  z-index: 2;
   padding-bottom: $toolbar-height;
 
   &-column {
@@ -27,8 +27,13 @@ $controls-transition: 0.3s;
   background: #f7f7f7;
 }
 
-.list-group-item:first-child {
-  border-top: 0;
+.list-group-item {
+  &:focus-visible {
+    outline: none;
+  }
+  &:first-child {
+    border-top: 0;
+  }
 }
 
 .tool {
diff --git a/src/components/crown/crownConfig/crownConfig.scss b/src/components/crown/crownConfig/crownConfig.scss
index c4e3ce2c..fee403ca 100644
--- a/src/components/crown/crownConfig/crownConfig.scss
+++ b/src/components/crown/crownConfig/crownConfig.scss
@@ -4,7 +4,7 @@ $crown-left-chevron: 0.3rem;
 .crown-config {
   background-color: $primary-color;
   position: absolute;
-  z-index: 0;
+  z-index: 1;
   display: flex;
   align-items: center;
   justify-content: center;
diff --git a/src/components/crown/crownConfig/crownConfig.vue b/src/components/crown/crownConfig/crownConfig.vue
index 02be0dc0..9d1de519 100644
--- a/src/components/crown/crownConfig/crownConfig.vue
+++ b/src/components/crown/crownConfig/crownConfig.vue
@@ -1,5 +1,5 @@
 <template>
-  <div class="crown-config" :style="style" v-if="showCrown" role="menu">
+  <div class="crown-config" :style="style" v-if="showCrown && !isMultiselect" role="menu">
     <slot />
 
     <association-flow-button
@@ -168,6 +168,10 @@ export default {
     this.$t = this.$t.bind(this);
   },
   computed: {
+    isMultiselect() {
+      const countSelected = store.getters.highlightedShapes.length;
+      return countSelected > 1;
+    },
     isFlow() {
       return [
         'processmaker-modeler-sequence-flow',
diff --git a/src/components/crown/crownMultiselect/crownMultiselect.vue b/src/components/crown/crownMultiselect/crownMultiselect.vue
new file mode 100644
index 00000000..802d1711
--- /dev/null
+++ b/src/components/crown/crownMultiselect/crownMultiselect.vue
@@ -0,0 +1,140 @@
+<template>
+  <div
+    class="crown-config crown-multiselect"
+    :style="style"
+    v-if="isMultiSelect"
+    role="menu"
+  >
+    <slot />
+
+    <button
+      v-for="button in buttons"
+      :key="button.label"
+      :aria-label="button.label"
+      class="btn"
+      :title="button.label"
+      :data-test="button.testId"
+      :role="button.role"
+      @mousedown.stop.prevent
+      @click.stop.prevent="button.action"
+    >
+      <i :class="`fa fa-${button.icon} text-white`" />
+    </button>
+  </div>
+</template>
+
+<script>
+import store from '@/store';
+import runningInCypressTest from '@/runningInCypressTest';
+
+export default {
+  props: {
+    paper: Object,
+  },
+  data() {
+    return {
+      runningInCypressTest: runningInCypressTest(),
+      showCrown: false,
+      savePositionOnPointerupEventSet: false,
+      style: null,
+      taskDropdownInitiallyOpen: this.paperNotRendered(),
+      showReplaceModal: false,
+      nodeToReplace: null,
+      buttons: [
+        {
+          label: 'Copy Element',
+          icon: 'copy',
+          testId: 'copy-button',
+          role: 'menuitem',
+          action: this.copyElement,
+        },
+        {
+          label: 'Delete Element',
+          icon: 'trash-alt',
+          testId: 'delete-button',
+          role: 'menuitem',
+          action: this.deleteElement,
+        },
+        // add more buttons as necessary
+      ],
+    };
+  },
+  created() {
+    this.$t = this.$t.bind(this);
+  },
+  computed: {
+    isMultiSelect() {
+      const countSelected = store.getters.highlightedShapes.length;
+      return countSelected > 1;
+    },
+    highlightedShapes: () => store.getters.highlightedShapes,
+  },
+  methods: {
+    copyElement() {
+      // @todo: Implement copyElement
+      this.$emit('copy-nodes');
+    },
+    deleteElement() {
+      this.$emit('remove-nodes');
+    },
+    paperNotRendered() {
+      return !this.isRendering;
+    },
+    setNodePosition() {
+      this.shape.stopListening(
+        this.paper,
+        'element:pointerup',
+        this.setNodePosition
+      );
+      this.savePositionOnPointerupEventSet = false;
+
+      if (!store.getters.allowSavingElementPosition) {
+        return;
+      }
+
+      this.$emit('save-state');
+    },
+    paperDoneRendering() {
+      if (!this.isRendering) {
+        return;
+      }
+
+      return new Promise((resolve) => this.paper.once('render:done', resolve));
+    },
+    setUpCrownConfig() {
+      this.paper.on(
+        'render:done scale:changed translate:changed',
+        this.repositionCrown
+      );
+    },
+    setUpPositionHandling() {},
+  },
+  async mounted() {
+    await this.$nextTick();
+    await this.paperDoneRendering();
+    this.setUpCrownConfig();
+  },
+};
+</script>
+
+<style scoped>
+.crown-multiselect {
+  top: -38px;
+  left: 50%;
+  pointer-events: auto;
+}
+.btn {
+  border: none;
+  padding: 0;
+  margin-top: 0;
+  display: flex;
+}
+img {
+  margin: 0px 10px;
+  height: 15px;
+}
+i {
+  margin: 0px 10px;
+  font-size: 15px;
+}
+</style>
diff --git a/src/components/hotkeys/main.js b/src/components/hotkeys/main.js
new file mode 100644
index 00000000..1a4cde0f
--- /dev/null
+++ b/src/components/hotkeys/main.js
@@ -0,0 +1,62 @@
+import ZoomInOut from './zoomInOut';
+import store from '@/store';
+import moveShapeByKeypress from './moveWithArrowKeys';
+
+export default {
+  mixins: [ZoomInOut],
+  mounted() {
+    document.addEventListener('keydown', this.keydownListener);
+    document.addEventListener('keyup', this.keyupListener);
+  },
+  methods: {
+    handleHotkeys(event, options) {
+      // Pass event to all handlers
+      this.zoomInOutHandler(event, options);
+    },
+    keyupListener(event) {
+      if (event.code === 'Space') {
+        this.isGrabbing = false;
+        this.paperManager.removeEventHandler('blank:pointermove');
+      }
+    },
+    keydownListener(event) {
+      // Check if either the Control key (Windows/Linux) or the Meta key (macOS) is pressed
+      const isAppleOS = this.isAppleOS();
+      const mod = isAppleOS ? event.metaKey : event.ctrlKey;
+      let options = { mod, isAppleOS, modeler: this };
+      try {
+        this.handleHotkeys(event, options);
+      } catch (error) {
+        // eslint-disable-next-line no-console
+        console.error(error);
+      }
+
+      const focusIsOutsideDiagram = !event.target.toString().toLowerCase().includes('body');
+      if (focusIsOutsideDiagram) {
+        return;
+      }
+
+      if (event.code === 'Space') {
+        this.isGrabbing = true;
+        this.paperManager.addEventHandler('blank:pointermove', (event, x, y) => {
+          if (!this.canvasDragPosition) {
+            const scale = this.paperManager.scale;
+            this.canvasDragPosition = { x: x * scale.sx, y: y * scale.sy };
+          }
+          if (this.canvasDragPosition) {
+            this.paperManager.translate(
+              event.offsetX - this.canvasDragPosition.x,
+              event.offsetY - this.canvasDragPosition.y
+            );
+          }
+        });
+      }
+
+      moveShapeByKeypress(
+        event.key,
+        store.getters.highlightedShapes,
+        this.pushToUndoStack,
+      );
+    },
+  },
+};
diff --git a/src/components/modeler/moveWithArrowKeys.js b/src/components/hotkeys/moveWithArrowKeys.js
similarity index 100%
rename from src/components/modeler/moveWithArrowKeys.js
rename to src/components/hotkeys/moveWithArrowKeys.js
diff --git a/src/components/hotkeys/zoomInOut.js b/src/components/hotkeys/zoomInOut.js
new file mode 100644
index 00000000..7c5c3b5c
--- /dev/null
+++ b/src/components/hotkeys/zoomInOut.js
@@ -0,0 +1,29 @@
+export default {
+  methods: {
+    zoomInOutHandler(event, options) {
+      const isPlus = 
+        (event.key === '+' || event.key === 'NumpadAdd')
+        || (event.keyCode === 107 || event.keyCode === 187);
+
+      const isMinus = 
+        (event.key === '-' || event.key === 'NumpadSubtract')
+        || (event.keyCode === 109 || event.keyCode === 189);
+
+
+      if (isPlus && options.mod) {
+        this.zoomIn(event);
+      }
+      if (isMinus && options.mod) {
+        this.zoomOut(event);
+      }
+    },
+    zoomIn(event) {
+      event.preventDefault();
+      this.canvasScale = this.paperManager.scale.sx + this.scaleStep;
+    },
+    zoomOut(event) {
+      event.preventDefault();
+      this.canvasScale = Math.max(this.minimumScale, this.paperManager.scale.sx -= this.scaleStep);
+    },
+  },
+};
\ No newline at end of file
diff --git a/src/components/inspectors/InspectorPanel.vue b/src/components/inspectors/InspectorPanel.vue
index 928e53fa..2e135ea5 100644
--- a/src/components/inspectors/InspectorPanel.vue
+++ b/src/components/inspectors/InspectorPanel.vue
@@ -212,6 +212,7 @@ export default {
       return this.defaultInspectorHandler(omit(value, ['artifacts', 'flowElements', 'laneSets']));
     },
     setNodeProp(node, key, value) {
+      this.$emit('shape-resize');
       store.commit('updateNodeProp', { node, key, value });
     },
     defaultInspectorHandler(value) {
diff --git a/src/components/inspectors/inspector.scss b/src/components/inspectors/inspector.scss
index af309012..4ad8a873 100644
--- a/src/components/inspectors/inspector.scss
+++ b/src/components/inspectors/inspector.scss
@@ -5,7 +5,7 @@ $inspector-column-max-width: 265px;
 }
 
 .inspector {
-  z-index: 1;
+  z-index: 2;
 
   &-container {
     text-align: left;
diff --git a/src/components/modeler/Modeler.vue b/src/components/modeler/Modeler.vue
index da91d52b..7a480b82 100644
--- a/src/components/modeler/Modeler.vue
+++ b/src/components/modeler/Modeler.vue
@@ -12,6 +12,7 @@
       @toggle-mini-map-open="miniMapOpen = $event"
       @saveBpmn="saveBpmn"
       @save-state="pushToUndoStack"
+      @clearSelection="clearSelection"
     />
     <b-row class="modeler h-100">
       <b-tooltip
@@ -50,6 +51,7 @@
 
       <InspectorPanel
         ref="inspector-panel"
+        v-show="!(highlightedNodes.length > 1)"
         :style="{ height: parentHeight }"
         :nodeRegistry="nodeRegistry"
         :moddle="moddle"
@@ -60,12 +62,14 @@
         :parent-height="parentHeight"
         :canvas-drag-position="canvasDragPosition"
         :compressed="panelsCompressed && noElementsSelected"
+        @shape-resize="shapeResize"
       />
 
       <component
         v-for="node in nodes"
         :is="node.type"
         :key="node._modelerId"
+        ref="nodeComponent"
         :graph="graph"
         :paper="paper"
         :node="node"
@@ -89,8 +93,8 @@
         @remove-node="removeNode"
         @set-cursor="cursor = $event"
         @set-pool-target="poolTarget = $event"
-        @click="highlightNode(node, $event)"
         @unset-pools="unsetPools"
+        @clearSelection="clearSelection"
         @set-pools="setPools"
         @save-state="pushToUndoStack"
         @set-shape-stacking="setShapeStacking"
@@ -99,6 +103,17 @@
         @replace-generic-flow="replaceGenericFlow"
         @copy-element="copyElement"
         @default-flow="toggleDefaultFlow"
+        @shape-resize="shapeResize"
+      />
+      <selection
+        v-if="paper"
+        ref="selector"
+        :graph="graph"
+        :paperManager="paperManager"
+        :useModelGeometry="false"
+        @remove-nodes="removeNodes"
+        :processNode="processNode"
+        @save-state="pushToUndoStack"
       />
     </b-row>
   </span>
@@ -138,8 +153,7 @@ import ensureShapeIsNotCovered from '@/components/shapeStackUtils';
 import ToolBar from '@/components/toolbar/ToolBar';
 import Node from '@/components/nodes/node';
 import { addNodeToProcess } from '@/components/nodeManager';
-import moveShapeByKeypress from '@/components/modeler/moveWithArrowKeys';
-import setUpSelectionBox from '@/components/modeler/setUpSelectionBox';
+import hotkeys from '@/components/hotkeys/main';
 import TimerEventNode from '@/components/nodes/timerEventNode';
 import focusNameInputAndHighlightLabel from '@/components/modeler/focusNameInputAndHighlightLabel';
 import XMLManager from '@/components/modeler/XMLManager';
@@ -150,6 +164,8 @@ import addLoopCharacteristics from '@/setup/addLoopCharacteristics';
 
 import ProcessmakerModelerGenericFlow from '@/components/nodes/genericFlow/genericFlow';
 
+import Selection from './Selection';
+
 export default {
   components: {
     ToolBar,
@@ -157,6 +173,7 @@ export default {
     InspectorPanel,
     MiniPaper,
     ProcessmakerModelerGenericFlow,
+    Selection,
   },
   props: {
     owner: Object,
@@ -167,6 +184,7 @@ export default {
       },
     },
   },
+  mixins: [hotkeys],
   data() {
     return {
       tooltipTarget: null,
@@ -209,6 +227,15 @@ export default {
       activeNode: null,
       xmlManager: null,
       previouslyStackedShape: null,
+      keyMod: this.isAppleOS() ? 'Command' : 'Control',
+      canvasScale: 1,
+      initialScale: 1,
+      minimumScale: 0.2,
+      scaleStep: 0.1,
+      isDragging: false,
+      isSelecting: false,
+      isIntoTheSelection: false,
+      dragStart: null,
     };
   },
   watch: {
@@ -237,6 +264,10 @@ export default {
     autoValidate() {
       this.validateIfAutoValidateIsOn();
     },
+    canvasScale(canvasScale) {
+      this.paperManager.scale = canvasScale;
+    },
+
   },
   computed: {
     noElementsSelected() {
@@ -261,6 +292,14 @@ export default {
     },
   },
   methods: {
+    isAppleOS() {
+      return typeof navigator !== 'undefined' && /Mac|iPad|iPhone/.test(navigator.platform);
+    },
+    async shapeResize() {
+      await this.$nextTick();
+      await this.paperManager.awaitScheduledUpdates();
+      this.$refs.selector.updateSelectionBox(true);
+    },
     toggleDefaultFlow(flow) {
       const source = flow.definition.sourceRef;
       if (source.default && source.default.id === flow.id) {
@@ -330,7 +369,6 @@ export default {
       try {
         const xml = await this.getXmlFromDiagram();
         undoRedoStore.dispatch('pushState', xml);
-
         window.ProcessMaker.EventBus.$emit('modeler-change');
       } catch (invalidXml) {
         // eslint-disable-next-line no-console
@@ -403,7 +441,7 @@ export default {
       let isSameHighlightedNode = _.isEqual(node.id, this.highlightedNode.id);
 
       if (!isSameHighlightedNode) {
-        store.commit('highlightNode', node);  
+        store.commit('highlightNode', node);
       }
 
       return;
@@ -746,7 +784,6 @@ export default {
 
       this.highlightNode(newNode);
       await this.addNode(newNode);
-
       if (!nodeThatWillBeReplaced) {
         return;
       }
@@ -767,7 +804,14 @@ export default {
       diagram.bounds.x -= (diagram.bounds.width / 2);
       diagram.bounds.y -= (diagram.bounds.height / 2);
     },
-    addNode(node) {
+    async selectNewNode(node) {
+      await this.$nextTick();
+      await this.paperManager.awaitScheduledUpdates();
+      const newNodeComponent = this.$refs.nodeComponent.find(component => component.node === node);
+      const view = newNodeComponent.shapeView;
+      await this.$refs.selector.selectElement(view);
+    },
+    async addNode(node) {
       if (!node.pool) {
         node.pool = this.poolTarget;
       }
@@ -793,6 +837,9 @@ export default {
         return;
       }
 
+      // Select the node after it has been added to the store (does not apply to flows)
+      this.selectNewNode(node);
+
       return new Promise(resolve => {
         setTimeout(() => {
           this.pushToUndoStack();
@@ -814,9 +861,22 @@ export default {
       this.removeNodesFromPool(node);
       store.commit('removeNode', node);
       store.commit('highlightNode', this.processNode);
+      this.$refs.selector.clearSelection();
       await this.$nextTick();
       this.pushToUndoStack();
     },
+    async removeNodes() {
+      this.performSingleUndoRedoTransaction(async() => {
+        await this.paperManager.performAtomicAction(async() => {
+          const waitPromises = [];
+          this.highlightedNodes.forEach((node) =>
+            waitPromises.push(this.removeNode(node, { removeRelationships: true }))
+          );
+          await Promise.all(waitPromises);
+          store.commit('highlightNode');
+        });
+      });
+    },
     replaceNode({ node, typeToReplaceWith }) {
       this.performSingleUndoRedoTransaction(async() => {
         await this.paperManager.performAtomicAction(async() => {
@@ -829,6 +889,7 @@ export default {
 
           await this.removeNode(node, { removeRelationships: false });
           this.highlightNode(newNode);
+          this.selectNewNode(newNode);
         });
       });
     },
@@ -898,18 +959,6 @@ export default {
 
       this.paperManager.setPaperDimensions(clientWidth, clientHeight);
     },
-    keydownListener(event) {
-      const focusIsOutsideDiagram = !event.target.toString().toLowerCase().includes('body');
-      if (focusIsOutsideDiagram) {
-        return;
-      }
-
-      moveShapeByKeypress(
-        event.key,
-        store.getters.highlightedShapes,
-        this.pushToUndoStack,
-      );
-    },
     validateDropTarget({ clientX, clientY, control }) {
       const { allowDrop, poolTarget } = getValidationProperties(clientX, clientY, control, this.paperManager.paper, this.graph, this.collaboration, this.$refs['paper-container']);
       this.allowDrop = allowDrop;
@@ -926,6 +975,94 @@ export default {
       this.previouslyStackedShape = shape;
       this.paperManager.performAtomicAction(() => ensureShapeIsNotCovered(shape, this.graph));
     },
+    clearSelection(){
+      this.$refs.selector.clearSelection();
+    },
+    isPointInSelection(event) {
+      const selector = this.$refs.selector.$el;
+      if (typeof selector.getBoundingClientRect === 'function') {
+        // check if mouse was clicked inside the selector
+        const { x: sx, y: sy, width:swidth, height: sheight } = selector.getBoundingClientRect();
+        if (event.clientX >= sx && event.clientX <= sx + swidth && event.clientY >= sy && event.clientY <= sy + sheight) {
+          return true;
+        }
+      }
+      return false;
+    },
+    async pointerDowInShape(event, element) {
+      const { clientX: x, clientY: y } = event;
+      const shapeView = this.paper.findViewByModel(element);
+      this.isDragging = false;
+      this.isSelecting = false;
+      this.isIntoTheSelection = false;
+      this.dragStart = { x, y };
+      // Verify if is in the selection box
+      if (this.isPointInSelection(event)) {
+        this.isIntoTheSelection = true;
+      } else {
+        if (!event.shiftKey) {
+          await this.$refs.selector.selectElement(shapeView);
+          await this.$nextTick();
+        }
+      }
+      this.$refs.selector.startDrag({
+        clientX: event.clientX,
+        clientY: event.clientY,
+      });
+    },
+    pointerDownHandler(event) {
+      const { clientX: x, clientY: y } = event;
+      this.isDragging = false;
+      this.isSelecting = false;
+      this.isIntoTheSelection = false;
+      this.dragStart = { x, y };
+      // Verify if is in the selection box
+      if (this.isPointInSelection(event)) {
+        this.isIntoTheSelection = true;
+        this.$refs.selector.startDrag({
+          clientX: event.clientX,
+          clientY: event.clientY,
+        });
+      } else {
+        this.isSelecting = true;
+        this.$refs.selector.startSelection(event);
+      }
+    },
+    pointerMoveHandler(event) {
+      const { clientX: x, clientY: y } = event;
+      if (this.dragStart && (Math.abs(x - this.dragStart.x) > 5 || Math.abs(y - this.dragStart.y) > 5)) {
+        this.isDragging = true;
+        this.dragStart = null;
+      } else {
+        if (this.isSelecting) {
+          this.$refs.selector.updateSelection(event);
+        } else {
+          if (this.isDragging) {
+            this.$refs.selector.drag(event);
+          }
+        }
+      }
+    },
+    pointerUpHandler(event, cellView) {
+      if (!this.isDragging && this.dragStart) {
+        // is clicked over the shape
+        if (cellView) {
+          this.$refs.selector.stopDrag(event);
+          this.$refs.selector.selectElement(cellView, event.shiftKey);
+        } else {
+          this.clearSelection();
+        }
+      } else {
+        if (this.isSelecting) {
+          this.$refs.selector.endSelection(this.paperManager.paper);
+        } else {
+          this.$refs.selector.stopDrag(event);
+        }
+      }
+      this.isDragging = false;
+      this.dragStart = null;
+      this.isSelecting = false;
+    },
   },
   created() {
     if (runningInCypressTest()) {
@@ -959,13 +1096,12 @@ export default {
     this.$emit('set-xml-manager', this.xmlManager);
   },
   mounted() {
-    document.addEventListener('keydown', this.keydownListener);
-
     this.graph = new dia.Graph();
     store.commit('setGraph', this.graph);
     this.graph.set('interactiveFunc', cellView => {
+      const isPoolEdge = cellView.model.get('type') === 'standard.EmbeddedImage';
       return {
-        elementMove: cellView.model.get('elementMove'),
+        elementMove: isPoolEdge,
         labelMove: false,
       };
     });
@@ -980,31 +1116,29 @@ export default {
 
     store.commit('setPaper', this.paperManager.paper);
 
-    this.paperManager.addEventHandler('blank:pointerclick', () => {
-      store.commit('highlightNode', this.processNode);
-    }, this);
-
     this.paperManager.addEventHandler('element:pointerclick', this.blurFocusedScreenBuilderElement, this);
 
     this.paperManager.addEventHandler('blank:pointerdown', (event, x, y) => {
+      if (this.isGrabbing) return;
       const scale = this.paperManager.scale;
       this.canvasDragPosition = { x: x * scale.sx, y: y * scale.sy };
-      this.isGrabbing = true;
+      this.isOverShape = false;
+      this.pointerDownHandler(event);
+    }, this);
+    this.paperManager.addEventHandler('blank:pointerup', (event) => {
+      this.canvasDragPosition = null;
+      this.activeNode = null;
+      this.pointerUpHandler(event);
     }, this);
-    this.paperManager.addEventHandler('cell:pointerup blank:pointerup', () => {
+    this.paperManager.addEventHandler('cell:pointerup', (cellView, event) => {
       this.canvasDragPosition = null;
-      this.isGrabbing = false;
       this.activeNode = null;
+      this.pointerUpHandler(event, cellView);
     }, this);
 
     this.$el.addEventListener('mousemove', event => {
-      if (this.canvasDragPosition) {
-        this.paperManager.translate(
-          event.offsetX - this.canvasDragPosition.x,
-          event.offsetY - this.canvasDragPosition.y,
-        );
-      }
-    }, this);
+      this.pointerMoveHandler(event);
+    });
 
     this.paperManager.addEventHandler('cell:pointerclick', (cellView, evt, x, y) => {
       const clickHandler = cellView.model.get('onClick');
@@ -1021,23 +1155,16 @@ export default {
       shape.component.$emit('click', event);
     });
 
-    this.paperManager.addEventHandler('cell:pointerdown', ({ model: shape }) => {
+    this.paperManager.addEventHandler('cell:pointerdown', ({ model: shape }, event) => {
       if (!this.isBpmnNode(shape)) {
         return;
       }
-
       this.setShapeStacking(shape);
       this.activeNode = shape.component.node;
+      this.isOverShape = true;
+      this.pointerDowInShape(event, shape);
     });
 
-    let cursor;
-    const setCursor = () => {
-      cursor = this.cursor;
-      this.cursor = 'crosshair';
-    };
-    const resetCursor = () => this.cursor = cursor;
-    setUpSelectionBox(setCursor, resetCursor, this.paperManager, this.graph);
-
     /* Register custom nodes */
     window.ProcessMaker.EventBus.$emit('modeler-start', {
       loadXML: xml => {
diff --git a/src/components/modeler/Selection.vue b/src/components/modeler/Selection.vue
new file mode 100644
index 00000000..c7ad6411
--- /dev/null
+++ b/src/components/modeler/Selection.vue
@@ -0,0 +1,705 @@
+<template>
+  <div
+    ref="drag"
+    v-if="showLasso && style"
+    class="box"
+    data-cy="selection-box"
+    :data-length="selected.length"
+    :style="style"
+  >
+    <crown-multiselect
+      :paper="paperManager.paper"
+      :graph="$parent.graph"
+      :moddle="$parent.moddle"
+      :collaboration="$parent.collaboration"
+      :process-node="$parent.processNode"
+      :plane-elements="$parent.planeElements"
+      :is-rendering="$parent.isRendering"
+      :dropdown-data="[]"
+      v-on="$listeners"
+    />
+  </div>
+</template>
+
+<script>
+import { util, g, V } from 'jointjs';
+import store from '@/store';
+import CrownMultiselect from '@/components/crown/crownMultiselect/crownMultiselect';
+import { id as poolId } from '@/components/nodes/pool/config';
+import { id as laneId } from '@/components/nodes/poolLane/config';
+import { id as genericFlowId } from '@/components/nodes/genericFlow/config';
+import { labelWidth, poolPadding } from '../nodes/pool/poolSizes';
+export default {
+  name: 'Selection',
+  components: {
+    CrownMultiselect,
+  },
+  props: {
+    graph: Object,
+    paperManager: Object,
+    useModelGeometry: Boolean,
+    processNode: Object,
+  },
+  data() {
+    return {
+      start: null,
+      isSelecting: false,
+      isSelected: false,
+      selected: [],
+      dragging: false,
+      style:  {
+        left: '0px',
+        top: '0px',
+        width: '0px',
+        height: '0px',
+      },
+      mouseX: 0,
+      mouseY: 0,
+      top: 0,
+      left: 0,
+      initialPosition: null,
+      hasMouseDown: false,
+      hasMouseMoved: false,
+      showLasso: false,
+      isOutOfThePool: false,
+      stopForceMove: false,
+      draggableBlackList: [
+        laneId,
+      ],
+      selectionBlackList:[
+        genericFlowId,
+      ],
+      selectableBlackList:[
+        genericFlowId,
+      ],
+    };
+  },
+  mounted(){
+    this.paperManager.paper.on('scale:changed ', this.updateSelectionBox);
+    this.paperManager.paper.on('translate:changed ', this.translateChanged);
+  },
+  watch: {
+    // whenever selected changes
+    selected(newSelected) {
+      this.addToHighlightedNodes(newSelected);
+    },
+  },
+  methods: {
+    /**
+     * Select an element dinamically.
+     * Shift key will manage the condition to push to selection
+     * @param {Object} view
+     * @param {Boolean} shiftKey
+     */
+    async selectElement(view, shiftKey = false) {
+      if (view.model.component && this.selectableBlackList.includes(view.model.component.node.type)) {
+        return;
+      }
+      this.showLasso = true;
+      this.isSelected = true;
+      this.isSelecting = true;
+      this.start = null;
+      if (shiftKey) {
+        this.shiftKeySelectionHandler(view);
+      } else {
+        this.selected = [view];
+      }
+      this.filterSelected();
+      await this.$nextTick();
+      this.updateSelectionBox();
+    },
+    /**
+     * Select or unselect an element with shift key pressed
+     * @param {Object} view
+     */
+    shiftKeySelectionHandler(view){
+      // validate if current shape is black listed
+      if (view && view.model && view.model.component &&
+        this.draggableBlackList.includes(view.model.component.node.type)) {
+        return;
+      }
+      // validate if there is a lane previously selected
+      let lane = this.selected.find(view => {
+        return this.draggableBlackList.includes(view.model.component.node.type);
+      });
+      if (lane) {
+        this.selected = [view];
+        return;
+      } 
+      // validate if the current selection is a pool
+      if (view.model.component && view.model.component.node.type === poolId) {
+        //validate if previous selection are all pools
+        if (this.hasOnlyPools(this.selected)) {
+          this.selectOrUnselectShape(view);
+        } else {
+          this.selected = [view];
+        }
+        return;
+      }
+      this.selectOrUnselectShape(view);
+    },
+    /**
+     * Select an shape if it is not in the collection
+     * Unselect an shape if it is in the collection
+     */
+    selectOrUnselectShape(view){
+      const element = this.selected.find( item => item.id === view.id);
+      if (element) {
+        this.selected = this.selected.filter(item => item.id !== view.id);
+      } else {
+        this.selected.push(view);
+      }     
+    },
+    clearSelection() {
+      this.initSelection();
+    },
+    initSelection(){
+      this.isSelecting = false;
+      this.start = null;
+      this.selected = [];
+      this.showLasso = false;
+      this.isSelected = false;
+      this.style = {
+        left: '0px',
+        top: '0px',
+        width: '0px',
+        height: '0px',
+      };
+    },
+    /**
+     * Starts the selection box
+     * @param {Object} event
+     */
+    startSelection(event) {
+      const { paper } = this.paperManager;
+      const { clientX, clientY, offsetX, offsetY } = util.normalizeEvent(event);
+      const paperOffset = paper.$el.offset();
+      this.clearSelection();
+      this.isSelecting = true;
+      this.showLasso = true;
+      this.start = {
+        x: clientX - paperOffset.left + window.pageXOffset + paper.$el.scrollLeft(),
+        y: clientY - paperOffset.top + window.pageYOffset + paper.$el.scrollTop(),
+      };
+      this._offset = { x: offsetX, y: offsetY };
+    },
+    /**
+     * Updates the selection box
+     * @param {Object} event
+     */
+    updateSelection(event) {
+      const { paper } = this.paperManager;
+      if (this.isSelecting && !this.isSelected && this.start) {
+        const nEvent= util.normalizeEvent(event);
+        const paperOffset = paper.$el.offset();
+        const end = {
+          x: nEvent.clientX - paperOffset.left + window.pageXOffset + paper.$el.scrollLeft(),
+          y: nEvent.clientY - paperOffset.top + window.pageYOffset + paper.$el.scrollTop(),
+        };
+
+        const size = {
+          height: end.y - this.start.y,
+          width: end.x - this.start.x,
+        };
+        // Set the position of the element
+        this.style.left =`${size.width < 0 ? this._offset.x + size.width: this.start.x}px`;
+        this.style.top = `${size.height < 0 ? this._offset.y + size.height: this.start.y}px`;
+        // Set the dimensions of the element
+        this.style.width = `${Math.abs(size.width)}px`;
+        this.style.height = `${Math.abs(size.height)}px`;
+      }
+    },
+    /**
+     * End the selection box
+     */
+    endSelection() {
+      const { paper } = this.paperManager;
+      if (!this.isSelecting || this.isSelected) {
+        return;
+      }
+      const paperOffset = paper.$el.offset();
+
+      const selectorOffset = {
+        left: this.$el.offsetLeft + paperOffset.left,
+        top: this.$el.offsetTop + paperOffset.top,
+      };
+      let width = this.$el.clientWidth;
+      let height = this.$el.clientHeight;
+
+      const f = V(paper.viewport).toLocalPoint(selectorOffset.left, selectorOffset.top);
+      f.x -= window.pageXOffset;
+      f.y -= window.pageYOffset;
+      const scale = paper.scale();
+      width /= scale.sx;
+      height /= scale.sy;
+
+      let selectedArea = g.rect(f.x, f.y, width, height);
+      this.selected = this.getElementsInSelectedArea(selectedArea, { strict: false });
+      this.filterSelected();
+      if (this.selected && this.selected.length > 0) {
+        this.updateSelectionBox();
+        this.isSelected = true;
+      } else {
+        this.clearSelection();
+        this.isSelected = false;
+        this.start = null;
+      }
+      this.start = null;
+    },
+    /**
+     * Get elements into a selected area
+     * @param {Object} area
+     */
+    getElementsInSelectedArea(area, options, addLinks=true) {
+      const { paper } = this.paperManager;
+      const elements =  paper.findViewsInArea(area, options);
+
+      if (!addLinks) {
+        return elements;
+      }
+      // get flows
+      this.graph.getLinks().forEach(function(link) {
+        // Check if the link is within the selected area
+        if (area.intersect(link.getBBox())) {
+          // The link is within the selected area
+          // Do something with the link, such as highlighting it
+          elements.push(paper.findViewByModel(link));
+        }
+      });
+      return elements;
+    },
+    /**
+     * Return the bounding box of the selected elements,
+     * @param {Array} selected
+     */
+    getSelectionVertex(selected, byModel = false, includeAll = false) {
+      const point = { x : 1 / 0, y: 1 / 0 };
+      const size = { width: 0, height: 0 };
+      const useModelGeometry = this.useModelGeometry;
+      const shapesToNotTranslate = [
+        'PoolLane',
+        'standard.Link',
+      ];
+      selected.filter(shape => includeAll || !shapesToNotTranslate.includes(shape.model.get('type')))
+        .map(function(view) {
+          const box = byModel ?
+            view.model.getBBox({ useModelGeometry }) :
+            view.getBBox({ useModelGeometry }).inflate(7);
+          point.x = Math.min(point.x, box.x);
+          point.y = Math.min(point.y, box.y);
+          size.width = Math.max(size.width, box.x + box.width);
+          size.height= Math.max(size.height, box.y + box.height);
+        });
+      return {
+        minX: point.x,
+        minY: point.y,
+        maxX: size.width,
+        maxY: size.height,
+      };
+    },
+    /**
+     * Update the selection Box
+     */
+    updateSelectionBox(force=false) {
+      if (force || this.isSelecting && this.style) {
+        if (this.selected.length > 0) {
+          const box = this.getSelectionVertex(this.selected, false, true);
+          // Set the position of the element
+          this.style.left = `${box.minX}px`;
+          this.style.top = `${box.minY}px`;
+          this.left = parseInt(this.style.left);
+          this.top = parseInt(this.style.top);
+          // Set the dimensions of the element
+          this.style.width = `${box.maxX - box.minX}px`;
+          this.style.height = `${box.maxY - box.minY}px`;
+        } else {
+          this.clearSelection();
+        }
+      }
+    },
+    /**
+     * Filter the selected elements
+     */
+    filterSelected() {
+      // remove from selection the selected child nodes in the pool
+      const selectedPoolsIds = this.selected
+        .filter(shape => shape.model.component)
+        .filter(shape => shape.model.component.node.type === 'processmaker-modeler-pool')
+        .map(shape => shape.model.component.node.id);
+      this.selected = this.selected.filter(shape => {
+        if (shape.model.component && shape.model.component.node.pool) {
+          return shape.model.component.node.pool && !selectedPoolsIds.includes(shape.model.component.node.pool.component.node.id);
+        }
+        return true;
+      }).filter(shape => {
+        return !(shape.model.getParentCell() && shape.model.getParentCell().get('parent'));
+      });
+    },
+    /**
+     * Pan paper handler
+     */
+    translateChanged() {
+      if (this.isSelecting) {
+        this.updateSelectionBox();
+      }
+    },
+    /**
+     * Verify if has selected lanes
+     */
+    hasLanes(selected) {
+      const shapesToNotTranslate = [
+        'processmaker-modeler-lane',
+      ];
+      const shapes = selected.find(shape => {
+        return shapesToNotTranslate.includes(shape.model.component.node.type);
+      });
+      if (shapes) {
+        return true;
+      } 
+      return false;
+    },
+    /**
+     * Verify if has only flows
+     */
+    hasOnlyLinks(selected) {
+      let shapes = this.selected.filter(shape => {
+        return shape.model.get('type') === 'standard.Link';
+      });
+      return shapes && selected.length === shapes.length;
+    },
+    /**
+     * Verify if has selected one Pools
+     */
+    hasOnlyPools(selected) {
+      let shapes = this.selected.filter(shape => {
+        return shape.model.component && shape.model.component.node.type === poolId;
+      });
+      return shapes && selected.length === shapes.length;
+    },
+    /**
+     * Start the drag procedure for the selext box
+     * @param {Object} event
+     */
+    startDrag(event) {
+      if (!this.$refs.drag){
+        return;
+      }
+      this.stopForceMove = false;
+      this.dragging = true;
+      this.hasMouseMoved = false;
+      const nEvent= util.normalizeEvent(event);
+      this.mouseX = nEvent.clientX;
+      this.mouseY = nEvent.clientY;
+      this.top = this.$refs.drag.offsetTop;
+      this.left = this.$refs.drag.offsetLeft;
+      this.initialPosition = {
+        top: this.top,
+        left: this.left,
+      };
+      if (this.hasLanes(this.selected)) {
+        this.stopForceMove = true;
+        return;
+      }
+      if (this.hasOnlyLinks(this.selected)) {
+        this.stopForceMove = true;
+        return;
+      }
+      if (this.selected && this.selected.length === 1 &&
+        this.selected[0].model.get('type') === 'processmaker.components.nodes.boundaryEvent.Shape') {
+        this.selected[0].model.component.attachToValidTarget(this.selected[0]);
+      }
+    },
+    /**
+     * on Drag procedure
+     * @param {*} event
+     */
+    drag(event) {
+      if (this.stopForceMove) return;
+      if (!this.dragging) return;
+      this.hasMouseMoved = true;
+      const nEvent= util.normalizeEvent(event);
+      const deltaX = nEvent.clientX - this.mouseX;
+      const deltaY = nEvent.clientY - this.mouseY;
+      this.top += deltaY;
+      this.left += deltaX;
+      this.mouseX = nEvent.clientX;
+      this.mouseY = nEvent.clientY;
+      // Set the position of the element
+      const scale = this.paperManager.paper.scale();
+      this.style.left =`${this.left}px`;
+      this.style.top = `${this.top}px`;
+      this.translateSelectedShapes(deltaX/scale.sx, deltaY/scale.sy);
+      this.overPoolDrag(event);
+    },
+    /**
+     * Stop drag procedure
+     * @param {Object} event
+     */
+    stopDrag() {
+      this.overPoolStopDrag();
+      this.$emit('save-state');
+      this.dragging = false;
+      this.stopForceMove = false;
+    },
+    /**
+     * Translate the Selected shapes adding some custom validations
+     */
+    translateSelectedShapes(x, y, drafRef) {
+      const shapesToNotTranslate = [
+        'PoolLane',
+        'standard.Link',
+        'processmaker.components.nodes.boundaryEvent.Shape',
+      ];
+      let shapes = this.selected.filter(shape => {
+        return !shapesToNotTranslate.includes(shape.model.get('type'));
+      });
+      if (drafRef) {
+        shapes.filter(shape =>{
+          return drafRef.model.get('id') !== shape.model.get('id');
+        });
+      }
+      // allow movement only if one lane boundary event is selected;
+      if (this.selected && this.selected.length === 1 && 
+        this.selected[0].model.get('type') === 'processmaker.components.nodes.boundaryEvent.Shape') {
+        this.selected[0].model.translate(x, y);
+        return;
+      }
+      shapes.forEach((shape)=> shape.model.translate(x, y));
+    },
+    /**
+     * Gets shape from a point object
+     * @param {Object} event
+     */
+    getShapesFromPoint(event){
+      const nEvent= util.normalizeEvent(event);
+      const mouseX = nEvent.clientX;
+      const mouseY = nEvent.clientY;
+      const point = V(this.paperManager.paper.viewport).toLocalPoint(mouseX, mouseY);
+      return this.paperManager.paper.findViewsFromPoint(point);
+    },
+    /**
+     * Add an element into the highlighted nodes
+     */
+    addToHighlightedNodes(selected){
+      store.commit('highlightNode');
+      if (selected.length > 0) {
+        const selectedNodes = selected.filter(shape => shape.model.component)
+          .map(shape => shape.model.component.node);
+        store.commit('addToHighlightedNodes', selectedNodes);
+      } else {
+        store.commit('highlightNode', this.processNode);
+      }
+    },
+    /**
+     * Gets the child shape
+     * @param {object} point
+     */
+    getChildShape(point) {
+      let result = null;
+      const views = this.getShapesFromPoint(point);
+      if (views.length === 1 ) {
+        return views[0];
+      }
+      views.forEach(shape => {
+        if (shape.model.get('parent') && shape.model.component.node.type !== laneId) {
+          result = shape;
+        }
+      });
+      return result;
+    },
+    /**
+     * Mark a shape as selected
+     * @param {object} point
+     */
+    markSelectedByPoint(point) {
+      const element = this.getChildShape(point);
+      if (element) {
+        this.selected = [element];
+      }
+      if (this.selected.length > 0) {
+        this.isSelected = true;
+        this.isSelecting = true;
+        this.showLasso = true;
+        this.updateSelectionBox();
+      }
+    },
+    /**
+     * Get the elements that are inside the selector box
+     */
+    getElementsInsideSelector() {
+      const { paper } = this.paperManager;
+      const paperOffset = paper.$el.offset();
+
+      const selectorOffset = {
+        left: this.$el.offsetLeft + paperOffset.left,
+        top: this.$el.offsetTop + paperOffset.top,
+      };
+      let width = this.$el.clientWidth;
+      let height = this.$el.clientHeight;
+
+      const f = V(paper.viewport).toLocalPoint(selectorOffset.left, selectorOffset.top);
+      f.x -= window.pageXOffset;
+      f.y -= window.pageYOffset;
+      const scale = paper.scale();
+      width /= scale.sx;
+      height /= scale.sy;
+
+      let selectedArea = g.rect(f.x, f.y, width, height);
+      return this.getElementsInSelectedArea(selectedArea, { strict: false });
+    },
+    /**
+     * Check that they are not in a pool
+     * @param {Array} elements 
+     * @return true if there is a pool in the selection or if none of the selected elements are in a pool
+     */
+    isNotPoolChilds(elements) {
+      if (elements.length > 0) {
+        const poolInSelection = elements.find(({ model }) => {
+          return model.component && model.component.node.type === poolId;
+        });
+        const elementInAPool = elements.find(({ model }) => {
+          return (model.getParentCell() && model.getParentCell().component.node.type === poolId);
+        });
+        return !!poolInSelection || !elementInAPool;
+      }
+      return false;
+    },
+    /**
+     * Movement controller of the elements that are inside a pool
+     */
+    overPoolDrag(event) {
+      if (this.isNotPoolChilds(this.selected)) {
+        return;
+      }
+      const elementsUnderDivArea = this.getShapesFromPoint(event);
+      const pool = elementsUnderDivArea.find(item => {
+        return item.model.component && item.model.component.node.type === poolId;
+      });
+      if (!pool) {
+        this.isOutOfThePool = true;
+        store.commit('preventSavingElementPosition');
+        this.paperManager.setStateInvalid();
+      } else {
+        this.isOutOfThePool = false;
+        store.commit('preventSavingElementPosition');
+        this.paperManager.setStateValid();
+      }
+    },
+    /**
+     * Stop dragging elements that are in a pool
+     */
+    overPoolStopDrag(){
+      if (this.isNotPoolChilds(this.selected)) {
+        return;
+      }
+      if (this.isOutOfThePool) {
+        this.rollbackSelection();
+      } else {
+        this.expandToFitElement(this.selected);
+      }
+    },
+    /**
+     * Rollback drag an element outside it's pool parent
+     */
+    rollbackSelection(){
+      const deltaX = this.initialPosition.left  - this.left;
+      const deltaY = this.initialPosition.top - this.top;
+      this.style.left = `${this.initialPosition.left}px`;
+      this.style.top = `${this.initialPosition.top}px`;
+      const scale = this.paperManager.paper.scale();
+      const shapesToNotTranslate = [
+        'PoolLane',
+        'standard.Link',
+      ];
+      this.selected.filter(shape => !shapesToNotTranslate.includes(shape.model.get('type')))
+        .forEach(shape => {
+          shape.model.translate(deltaX/scale.sx, deltaY/scale.sy);
+        });
+      this.isOutOfThePool = false;
+      store.commit('allowSavingElementPosition');
+      this.paperManager.setStateValid();
+    },
+    /**
+     * Expand and fit the pool container
+     */
+    expandToFitElement(selected) {
+      if (selected.length > 0) {
+        const pool = selected.find(({ model }) => {
+          if (model.getParentCell()) {
+            return model.getParentCell().component.node.type === poolId;
+          }
+          return false;
+        }).model.getParentCell();
+        const selectionBBox = this.getSelectionVertex(this.selected, true);
+        const { x: poolX, y: poolY, width, height } = pool.getBBox();
+
+        const elementWidth = selectionBBox.maxX - selectionBBox.minX;
+        const elementHeight = selectionBBox.maxY - selectionBBox.minY;
+
+        const relativeX = selectionBBox.minX - poolX;
+        const relativeY = selectionBBox.minY  - poolY;
+
+        const rightEdge = relativeX + elementWidth;
+        const leftEdge = relativeX;
+        const topEdge = relativeY;
+        const bottomEdge = relativeY + elementHeight;
+
+        let newWidth = 0;
+        let newHeight = 0;
+        let directionHeight = 'bottom';
+        let directionWidth = 'right';
+
+        if (rightEdge > width - poolPadding) {
+          newWidth = rightEdge + poolPadding;
+        }
+
+        if (leftEdge < labelWidth + poolPadding) {
+          newWidth = width + ((labelWidth + poolPadding) - leftEdge);
+          directionWidth = 'left';
+        }
+
+        if (topEdge < poolPadding) {
+          newHeight = (poolPadding - topEdge) + height;
+          directionHeight = 'top';
+        }
+
+        if (bottomEdge > height) {
+          newHeight = bottomEdge + poolPadding;
+        }
+        if (newWidth || newHeight) {
+          pool.resize(Math.max(newWidth, width), Math.max(newHeight, height), {
+            direction: `${directionHeight}-${directionWidth}`,
+          });
+
+          pool.component.fixResizeRounding();
+          if (pool.component.laneSet) {
+            /* Expand any lanes within the pool */
+            pool.component.resizeLanes();
+
+            pool.component.sortedLanes().forEach(laneShape => {
+              store.commit('updateNodeBounds', {
+                node: laneShape.component.node,
+                bounds: laneShape.getBBox(),
+              });
+            });
+          }
+          pool.component.updateAnchorPointPosition();
+          store.commit('updateNodeBounds', {
+            node: pool.component.node,
+            bounds: pool.getBBox(),
+          });
+          this.$emit('save-state');
+        }
+      }
+    },
+  },
+};
+</script>
+
+<style scoped>
+.box {
+  border: 1px solid #5faaee;
+  position: absolute;
+  pointer-events: none;
+}
+</style>
diff --git a/src/components/modeler/modeler.scss b/src/components/modeler/modeler.scss
index d98467d9..46258326 100644
--- a/src/components/modeler/modeler.scss
+++ b/src/components/modeler/modeler.scss
@@ -18,7 +18,6 @@
 
   .paper-container {
     position: initial !important;
-    cursor: grab;
     user-select: none;
   }
 
diff --git a/src/components/nodes/boundaryEvent/boundaryEvent.vue b/src/components/nodes/boundaryEvent/boundaryEvent.vue
index 9cb74615..b37748d6 100644
--- a/src/components/nodes/boundaryEvent/boundaryEvent.vue
+++ b/src/components/nodes/boundaryEvent/boundaryEvent.vue
@@ -143,6 +143,7 @@ export default {
     resetToInitialPosition() {
       this.shape.position(this.validPosition.x, this.validPosition.y);
       store.commit('allowSavingElementPosition');
+      this.$emit('shape-resize');
     },
     moveBoundaryEventIfOverTask() {
       const task = this.getTaskUnderShape();
@@ -154,11 +155,13 @@ export default {
 
       this.attachBoundaryEventToTask(task);
       this.updateShapePosition(task);
+      this.$emit('shape-resize');
     },
     resetInvalidTarget() {
       if (this.invalidTargetElement) {
         resetShapeColor(this.invalidTargetElement);
         this.invalidTargetElement = null;
+        this.$emit('shape-resize');
       }
     },
     attachToValidTarget(cellView) {
diff --git a/src/components/nodes/pool/poolEventHandlers.js b/src/components/nodes/pool/poolEventHandlers.js
index a6404ee2..30ec229a 100644
--- a/src/components/nodes/pool/poolEventHandlers.js
+++ b/src/components/nodes/pool/poolEventHandlers.js
@@ -25,6 +25,7 @@ export default class PoolEventHandlers {
 
   onPointerDown(cellView) {
     if (this.isNotPoolChild(cellView.model)) {
+      this.component.$emit('select-pool');
       return;
     }
 
diff --git a/src/components/paperManager.js b/src/components/paperManager.js
index 7b91f851..7b54a2f9 100644
--- a/src/components/paperManager.js
+++ b/src/components/paperManager.js
@@ -21,6 +21,8 @@ export default class PaperManager {
       sorting: 'sorting-approximate',
       gridSize: PaperManager.gridSize,
       drawGrid: true,
+      snapLinks: false, // disable snapping of links
+      snapElements: false, // disable snapping of elements
       clickThreshold: 10,
       perpendicularLinks: true,
       interactive: interactiveFunc,
diff --git a/src/components/toolbar/ToolBar.vue b/src/components/toolbar/ToolBar.vue
index c4aa931e..acbbdc82 100644
--- a/src/components/toolbar/ToolBar.vue
+++ b/src/components/toolbar/ToolBar.vue
@@ -34,7 +34,7 @@
         <div class="btn-group btn-group-sm mr-2" role="group" aria-label="Zoom controls">
           <b-button
             class="btn btn-sm btn-secondary"
-            @click="scale += scaleStep"
+            @click="paperManager.scale = paperManager.scale.sx + scaleStep"
             data-test="zoom-in"
             v-b-tooltip.hover
             :title="$t('Zoom In')"
@@ -43,7 +43,7 @@
           </b-button>
           <b-button
             class="btn btn-sm btn-secondary"
-            @click="scale = Math.max(minimumScale, scale -= scaleStep)"
+            @click="paperManager.scale = Math.max(minimumScale, paperManager.scale.sx -= scaleStep)"
             data-test="zoom-out"
             v-b-tooltip.hover
             :title="$t('Zoom Out')"
@@ -51,16 +51,17 @@
             <font-awesome-icon :icon="minusIcon" />
           </b-button>
           <b-button
+            v-if="paperManager"
             class="btn btn-sm btn-secondary"
-            @click="scale = initialScale"
-            :disabled="scale === initialScale"
+            @click="paperManager.scale = initialScale"
+            :disabled="paperManager.scale.sx === initialScale"
             data-test="zoom-reset"
             v-b-tooltip.hover
             :title="$t('Reset to initial scale')"
           >
             {{ $t('Reset') }}
           </b-button>
-          <span class="btn btn-sm btn-secondary scale-value">{{ Math.round(scale*100) }}%</span>
+          <span v-if="paperManager" class="btn btn-sm btn-secondary scale-value">{{ Math.round(paperManager.scale.sx*100) }}%</span>
         </div>
 
         <div class="btn-group btn-group-sm mr-2" role="group" aria-label="Additional controls">
@@ -121,12 +122,6 @@ export default {
     panelsCompressed: Boolean,
   },
   watch: {
-    scale(scale) {
-      this.paperManager.scale = scale;
-      if (scale === this.initialScale) {
-        this.$root.$emit('bv::hide::tooltip');
-      }
-    },
     miniMapOpen(isOpen) {
       this.$emit('toggle-mini-map-open', isOpen);
     },
@@ -168,6 +163,7 @@ export default {
   },
   methods: {
     undo() {
+      this.$emit('clearSelection');
       if (this.isRendering) {
         return;
       }
@@ -177,6 +173,7 @@ export default {
         .then(() => window.ProcessMaker.EventBus.$emit('modeler-change'));
     },
     redo() {
+      this.$emit('clearSelection');
       if (this.isRendering) {
         return;
       }
diff --git a/src/components/toolbar/toolbar.scss b/src/components/toolbar/toolbar.scss
index 0f3ca733..62d4dabd 100644
--- a/src/components/toolbar/toolbar.scss
+++ b/src/components/toolbar/toolbar.scss
@@ -1,7 +1,7 @@
 $toolbar-background-color: #FFF;
 
 .toolbar {
-  z-index: 1;
+  z-index: 2;
   height: $toolbar-height;
   cursor: auto;
   width: 100%;
diff --git a/src/mixins/highlightConfig.js b/src/mixins/highlightConfig.js
index e7e09203..9c09cdf3 100644
--- a/src/mixins/highlightConfig.js
+++ b/src/mixins/highlightConfig.js
@@ -21,6 +21,7 @@ const defaultHighlighter = {
       attrs: {
         stroke: '#5096db',
         'stroke-width': 3,
+        'data-cy': 'selected',
       },
     },
   },
@@ -96,6 +97,7 @@ export default {
           this.setShapeHighlight();
           this.shape.on('change:size', () => {
             this.paperManager.awaitScheduledUpdates().then(this.setShapeHighlight);
+            this.$emit('shape-resize', this.shape);
           });
         });
     });
diff --git a/src/mixins/linkConfig.js b/src/mixins/linkConfig.js
index 07842c5f..41b18439 100644
--- a/src/mixins/linkConfig.js
+++ b/src/mixins/linkConfig.js
@@ -302,6 +302,9 @@ export default {
     }
 
     this.updateRouter();
+    this.shape.on('change:vertices', function() {
+      this.component.$emit('shape-resize');
+    });
   },
   beforeDestroy() {
     document.removeEventListener('mouseup', this.emitSave);
diff --git a/src/store.js b/src/store.js
index 065952c3..d9f37017 100644
--- a/src/store.js
+++ b/src/store.js
@@ -94,7 +94,18 @@ export default new Vuex.Store({
       state.highlightedNodes = node ? [node] : [];
     },
     addToHighlightedNodes(state, nodes) {
-      state.highlightedNodes = uniq([...state.highlightedNodes, ...nodes]);
+      const highlightedNodes = uniq([...state.highlightedNodes, ...nodes]);
+      const selectedPoolsIds = highlightedNodes
+        .filter(node => node.type === 'processmaker-modeler-pool')
+        .map(node => node.id);
+      state.highlightedNodes = highlightedNodes
+        // remove from selection the selected child nodes in the pool
+        .filter(node => {
+          if (node.pool && node.pool.component.node.id) {
+            return !selectedPoolsIds.includes(node.pool.component.node.id);
+          }
+          return true;
+        });
     },
     addNode(state, node) {
       /* Add an unchanging ID that Vue can use to track the component


Test patch:
diff --git a/tests/e2e/specs/canvasUsability/canvasSelection/DeselectWhenCanvasIsClicked.spec.js b/tests/e2e/specs/canvasUsability/canvasSelection/DeselectWhenCanvasIsClicked.spec.js
new file mode 100644
index 00000000..fcaa6c67
--- /dev/null
+++ b/tests/e2e/specs/canvasUsability/canvasSelection/DeselectWhenCanvasIsClicked.spec.js
@@ -0,0 +1,56 @@
+import {
+  dragFromSourceToDest,
+  connectNodesWithFlow,
+  waitToRenderAllShapes,
+} from '../../../support/utils';
+import { nodeTypes } from '../../../support/constants';
+
+describe('Canvas Selection', () => {
+  it('Verify that controls are deselected when the user click in canvas', () => {
+    const startEventPosition = { x: 150, y: 150 };
+    const taskFormPosition = {
+      x: startEventPosition.x + 200,
+      y: startEventPosition.y,
+    };
+    const endEventPosition = {
+      x: taskFormPosition.x + 200,
+      y: taskFormPosition.y,
+    };
+
+    // Drag a Task Form
+    dragFromSourceToDest(nodeTypes.task, taskFormPosition);
+
+    // Connect the Start Event with Task Form
+    connectNodesWithFlow('generic-flow-button', startEventPosition, taskFormPosition);
+
+    // Drag an End Event
+    dragFromSourceToDest(nodeTypes.endEvent, endEventPosition);
+
+    // Connect the Task Form with End Event
+    connectNodesWithFlow('generic-flow-button', taskFormPosition, endEventPosition);
+
+    // Select all elements
+    cy.get('.paper-container').as('paperContainer').click();
+    cy.get('.paper-container').trigger('mousedown', 'topLeft');
+    cy.get('.paper-container').trigger('mousemove', 'bottomRight');
+    waitToRenderAllShapes();
+    cy.get('.paper-container').trigger('mouseup', 'bottomRight');
+
+    
+    // Validation 1: Verify that controls are selected inside a selection box
+    cy.get('[data-cy="selection-box"]').should('exist');
+    cy.get('[data-type="processmaker.components.nodes.startEvent.Shape"] [data-cy="selected"]').should('exist');
+    cy.get('[data-type="processmaker.components.nodes.task.Shape"] [data-cy="selected"]').should('exist');
+    cy.get('[data-type="processmaker.components.nodes.endEvent.Shape"] [data-cy="selected"]').should('exist');
+    // Check the selection is large enough to contain all elements
+    cy.get('[data-cy="selection-box"]').then($selectionBox => {
+      const selectionBoxWidth = $selectionBox.width();
+      expect(selectionBoxWidth).to.be.greaterThan(400);
+    });
+
+    // Validation 2: Verify that the previous selection was lost after click in the canvas
+    cy.get('.paper-container').trigger('mousedown', 'topLeft');
+    cy.get('.paper-container').trigger('mouseup', 'topLeft');
+    cy.get('[data-cy="selection-box"]').should('not.exist');
+  });
+});
diff --git a/tests/e2e/specs/canvasUsability/canvasSelection/DeselectWhenNewElementAdded.spec.js b/tests/e2e/specs/canvasUsability/canvasSelection/DeselectWhenNewElementAdded.spec.js
new file mode 100644
index 00000000..b2b5e2e4
--- /dev/null
+++ b/tests/e2e/specs/canvasUsability/canvasSelection/DeselectWhenNewElementAdded.spec.js
@@ -0,0 +1,67 @@
+import {
+  dragFromSourceToDest,
+  connectNodesWithFlow,
+  waitToRenderAllShapes,
+} from '../../../support/utils';
+import { nodeTypes } from '../../../support/constants';
+
+describe('Canvas Selection', () => {
+  it('Verify that controls are deselected if a new element is added', () => {
+    const startEventPosition = { x: 150, y: 150 };
+    const taskFormPosition = {
+      x: startEventPosition.x + 200,
+      y: startEventPosition.y,
+    };
+    const endEventPosition = {
+      x: taskFormPosition.x + 200,
+      y: taskFormPosition.y,
+    };
+
+    // Drag a Task Form
+    dragFromSourceToDest(nodeTypes.task, taskFormPosition);
+
+    // Connect the Start Event with Task Form
+    connectNodesWithFlow('generic-flow-button', startEventPosition, taskFormPosition);
+
+    // Drag an End Event
+    dragFromSourceToDest(nodeTypes.endEvent, endEventPosition);
+
+    // Connect the Task Form with End Event
+    connectNodesWithFlow('generic-flow-button', taskFormPosition, endEventPosition);
+
+    // Select all elements
+    cy.get('.paper-container').as('paperContainer').click();
+    cy.get('.paper-container').trigger('mousedown', 'topLeft');
+    cy.get('.paper-container').trigger('mousemove', 'bottomRight');
+    waitToRenderAllShapes();
+    cy.get('.paper-container').trigger('mouseup', 'bottomRight');
+
+    // Validation 1: Verify that controls are selected inside a selection box
+    cy.get('[data-cy="selection-box"]').should('exist');
+    cy.get('[data-type="processmaker.components.nodes.startEvent.Shape"] [data-cy="selected"]').should('exist');
+    cy.get('[data-type="processmaker.components.nodes.task.Shape"] [data-cy="selected"]').should('exist');
+    cy.get('[data-type="processmaker.components.nodes.endEvent.Shape"] [data-cy="selected"]').should('exist');
+    // Check the selection is large enough to contain all elements
+    cy.get('[data-cy="selection-box"]').then($selectionBox => {
+      const selectionBoxWidth = $selectionBox.width();
+      expect(selectionBoxWidth).to.be.greaterThan(400);
+    });
+
+    // Drag another element in the modeler
+    const newTaskPosition = {
+      x: endEventPosition.x,
+      y: endEventPosition.y + 200,
+    };
+    dragFromSourceToDest(nodeTypes.task, newTaskPosition);
+
+    // Validation 2: Verify that the previous selection was lost after dragging another element
+    cy.get('[data-type="processmaker.components.nodes.startEvent.Shape"] [data-cy="selected"]').should('not.exist');
+    cy.get('[data-type="processmaker.components.nodes.task.Shape"] [data-cy="selected"]').should('have.length', 1); // the new added task
+    cy.get('[data-type="processmaker.components.nodes.endEvent.Shape"] [data-cy="selected"]').should('not.exist');
+    // Check the selection is smaller than the previous one since it only contains the new element
+    cy.get('[data-cy="selection-box"]').then($selectionBox => {
+      const selectionBoxWidth = $selectionBox.width();
+      expect(selectionBoxWidth).to.be.lessThan(400);
+    });
+  });
+});
diff --git a/tests/e2e/specs/canvasUsability/canvasSelection/SelectingAnElementByClickingOnIt.spec.js b/tests/e2e/specs/canvasUsability/canvasSelection/SelectingAnElementByClickingOnIt.spec.js
new file mode 100644
index 00000000..3a7e93d2
--- /dev/null
+++ b/tests/e2e/specs/canvasUsability/canvasSelection/SelectingAnElementByClickingOnIt.spec.js
@@ -0,0 +1,46 @@
+import {
+  dragFromSourceToDest,
+  connectNodesWithFlow,
+  waitToRenderAllShapes,
+  getElementAtPosition,
+} from '../../../support/utils';
+import { nodeTypes } from '../../../support/constants';
+
+describe('Canvas Selection', () => {
+  it('Verify that controls are selected when the user click on it', () => {
+    const startEventPosition = { x: 150, y: 150 };
+    const taskFormPosition = {
+      x: startEventPosition.x + 200,
+      y: startEventPosition.y,
+    };
+    const endEventPosition = {
+      x: taskFormPosition.x + 200,
+      y: taskFormPosition.y,
+    };
+
+    // Drag a Task Form
+    dragFromSourceToDest(nodeTypes.task, taskFormPosition);
+
+    // Connect the Start Event with Task Form
+    connectNodesWithFlow('generic-flow-button', startEventPosition, taskFormPosition);
+
+    // Drag an End Event
+    dragFromSourceToDest(nodeTypes.endEvent, endEventPosition);
+
+    // Connect the Task Form with End Event
+    connectNodesWithFlow('generic-flow-button', taskFormPosition, endEventPosition);
+
+    waitToRenderAllShapes();
+    
+    // Validation 1: Click in the task the task must be selected
+    getElementAtPosition(taskFormPosition).click();
+
+    cy.get('[data-cy="selection-box"]').then($selectionBox => {
+      const { top: newTop, left: newLeft } = $selectionBox.position();
+      expect(newTop).to.be.greaterThan(100);
+      expect(newTop).to.be.lessThan(200);
+      expect(newLeft).to.be.greaterThan(480);
+      expect(newLeft).to.be.lessThan(530);
+    });
+  });
+});
diff --git a/tests/e2e/specs/canvasUsability/canvasSelection/SelectingAnElementIntoTheSelectedBox.spec.js b/tests/e2e/specs/canvasUsability/canvasSelection/SelectingAnElementIntoTheSelectedBox.spec.js
new file mode 100644
index 00000000..46d2547a
--- /dev/null
+++ b/tests/e2e/specs/canvasUsability/canvasSelection/SelectingAnElementIntoTheSelectedBox.spec.js
@@ -0,0 +1,61 @@
+import {
+  dragFromSourceToDest,
+  connectNodesWithFlow,
+  waitToRenderAllShapes,
+  getElementAtPosition,
+} from '../../../support/utils';
+import { nodeTypes } from '../../../support/constants';
+
+describe('Canvas Selection', () => {
+  it('Verify that controls are selected when the user click on it', () => {
+    const startEventPosition = { x: 150, y: 150 };
+    const taskFormPosition = {
+      x: startEventPosition.x + 200,
+      y: startEventPosition.y,
+    };
+    const endEventPosition = {
+      x: taskFormPosition.x + 200,
+      y: taskFormPosition.y,
+    };
+
+    // Drag a Task Form
+    dragFromSourceToDest(nodeTypes.task, taskFormPosition);
+
+    // Connect the Start Event with Task Form
+    connectNodesWithFlow('generic-flow-button', startEventPosition, taskFormPosition);
+
+    // Drag an End Event
+    dragFromSourceToDest(nodeTypes.endEvent, endEventPosition);
+
+    // Connect the Task Form with End Event
+    connectNodesWithFlow('generic-flow-button', taskFormPosition, endEventPosition);
+
+    // Select all elements
+    cy.get('.paper-container').as('paperContainer').click();
+    cy.get('.paper-container').trigger('mousedown', 'topLeft');
+    cy.get('.paper-container').trigger('mousemove', 'bottomRight');
+    waitToRenderAllShapes();
+    cy.get('.paper-container').trigger('mouseup', 'bottomRight');
+
+    // Validation 1: Verify that controls are selected inside a selection box
+    cy.get('[data-cy="selection-box"]').should('exist');
+    cy.get('[data-type="processmaker.components.nodes.startEvent.Shape"] [data-cy="selected"]').should('exist');
+    cy.get('[data-type="processmaker.components.nodes.task.Shape"] [data-cy="selected"]').should('exist');
+    cy.get('[data-type="processmaker.components.nodes.endEvent.Shape"] [data-cy="selected"]').should('exist');
+    // Check the selection is large enough to contain all elements
+    cy.get('[data-cy="selection-box"]').then($selectionBox => {
+      const selectionBoxWidth = $selectionBox.width();
+      expect(selectionBoxWidth).to.be.greaterThan(400);
+    });
+
+    //Validation 2: Click in the task the task must be selected
+    getElementAtPosition(taskFormPosition).click();
+    cy.get('[data-cy="selection-box"]').then($selectionBox => {
+      const { top: newTop, left: newLeft } = $selectionBox.position();
+      expect(newTop).to.be.greaterThan(100);
+      expect(newTop).to.be.lessThan(200);
+      expect(newLeft).to.be.greaterThan(480);
+      expect(newLeft).to.be.lessThan(530);
+    });
+  });
+});
diff --git a/tests/e2e/specs/canvasUsability/canvasSelection/SelectionOfPoolsWithShiftClick.spec.js b/tests/e2e/specs/canvasUsability/canvasSelection/SelectionOfPoolsWithShiftClick.spec.js
new file mode 100644
index 00000000..24bfc6a9
--- /dev/null
+++ b/tests/e2e/specs/canvasUsability/canvasSelection/SelectionOfPoolsWithShiftClick.spec.js
@@ -0,0 +1,85 @@
+import {
+  dragFromSourceToDest,
+  getElementAtPosition,
+  waitToRenderAllShapes,
+} from '../../../support/utils';
+import { nodeTypes } from '../../../support/constants';
+
+describe('Selection of pool with shift click', () => {
+  it('should add to selection a pool with shift key', () => {
+    // Drag pool 1 and elements inside
+    const pool1Position = { x: 100, y: 50 };
+    dragFromSourceToDest(nodeTypes.pool, pool1Position);
+
+    const taskPosition1 = { x: 280, y: 180 };
+    const endEventPosition1 = { x: 440, y: 240 };
+    dragFromSourceToDest(nodeTypes.task, taskPosition1);
+    dragFromSourceToDest(nodeTypes.endEvent, endEventPosition1);
+
+    // Drag pool 2 and elements inside
+    const pool2Position = { x: 100, y: 450 };
+    dragFromSourceToDest(nodeTypes.pool, pool2Position);
+
+    const startEventPosition2 = { x: 180, y: 520 };
+    const taskPosition2 = { x: 280, y: 500 };
+    const endEventPosition2 = { x: 440, y: 520 };
+    dragFromSourceToDest(nodeTypes.startEvent, startEventPosition2);
+    dragFromSourceToDest(nodeTypes.task, taskPosition2);
+    dragFromSourceToDest(nodeTypes.endEvent, endEventPosition2);
+
+    // Select both pools with shift + click
+    cy.get('body').type('{shift}', { release: false });
+    getElementAtPosition(pool1Position, nodeTypes.pool, 12, 100).click();
+
+    getElementAtPosition(pool2Position, nodeTypes.pool, 12, 100).click();
+    cy.get('body').type('{shift}', { release: true });
+
+    // Validate that two pools were selected ([data-length=2])
+    cy.get('[data-cy="selection-box"]').should('have.attr', 'data-length', '2');
+
+    // get current position of pool 1
+    // eslint-disable-next-line no-unused-vars
+    let pool1;
+    getElementAtPosition(pool1Position, nodeTypes.pool, 12, 100).then(($pool1) => {
+      pool1 = $pool1[0].getBoundingClientRect();
+    });
+
+    // get current position of pool 1
+    // eslint-disable-next-line no-unused-vars
+    let pool2;
+    getElementAtPosition(pool2Position, nodeTypes.pool, 12, 100).then(($pool2) => {
+      pool2 = $pool2[0].getBoundingClientRect();
+    });
+
+    // Move the selected pools to another position (upper)
+    const fromPosition = { x: 200, y: 200 };
+    const translateAmount = { x: 0, y: 100 };
+    // click and drag fromPosition to fromPosition + translateAmount
+    cy.get('.paper-container .joint-paper').trigger('mousedown', { which: 1, pageX: fromPosition.x, pageY: fromPosition.y, force: true});
+    cy.get('.paper-container').trigger('mousemove', { which: 1, pageX: fromPosition.x + translateAmount.x, pageY: fromPosition.y + translateAmount.y, force: true});
+    waitToRenderAllShapes();
+    cy.get('.paper-container .joint-paper').trigger('mouseup',{force: true});
+
+    waitToRenderAllShapes();
+
+    // Validate that the selected pools were moved
+    const newPool1Position = {
+      x: pool1Position.x + translateAmount.x,
+      y: pool1Position.y + translateAmount.y,
+    };
+    const newPool2Position = {
+      x: pool2Position.x + translateAmount.x,
+      y: pool2Position.y + translateAmount.y,
+    };
+
+    getElementAtPosition(newPool1Position, nodeTypes.pool, 12, 100).then(($pool1) => {
+      const { y } = $pool1[0].getBoundingClientRect();
+      expect(pool1.y).to.be.lessThan(y);
+    });
+
+    getElementAtPosition(newPool2Position, nodeTypes.pool, 12, 100).then(($pool2) => {
+      const { y } = $pool2[0].getBoundingClientRect();
+      expect(pool1.y).to.be.lessThan(y);
+    });
+  });
+});
diff --git a/tests/e2e/specs/canvasUsability/canvasSelection/VerifySelectionWithControlsWithLargeName.spec.js b/tests/e2e/specs/canvasUsability/canvasSelection/VerifySelectionWithControlsWithLargeName.spec.js
new file mode 100644
index 00000000..365a89cf
--- /dev/null
+++ b/tests/e2e/specs/canvasUsability/canvasSelection/VerifySelectionWithControlsWithLargeName.spec.js
@@ -0,0 +1,43 @@
+import {
+  waitToRenderAllShapes, getGraphElements,
+} from '../../../support/utils';
+
+describe('Canvas Selection', () => {
+  it('TCP4-2667: Verify selection with controls with large name', () => {
+    const initialNumberOfElements = 1;
+
+    //Step 1: Drag Start Event
+    waitToRenderAllShapes();
+    getGraphElements().should('have.length', initialNumberOfElements);
+    cy.get('.paper-container').click('bottomRight');
+
+    //Step 2: Clicks and drags the mouse over the elements
+    cy.get('.paper-container').trigger('mousedown', 'topLeft');
+    cy.get('.paper-container').trigger('mousemove', 'bottomRight');
+    cy.get('.paper-container').trigger('mouseup',{force: true});
+
+    //Step 3: Get with of rectangle of selection
+    cy.get('[data-cy="selection-box"]').should('be.visible')
+      .invoke('width').then((val) => {
+        const value = val;
+
+        //Step 4: Set a large name in the Start Event
+        cy.get('[data-type="processmaker.components.nodes.startEvent.Shape"]').first().click({force: true});
+        cy.get('[name="name"]').clear().type('New Large Name to Start event');
+
+        //Step 5: Clicks and drags the mouse over the elements
+        cy.get('.paper-container').click('topRight');
+        cy.get('.paper-container').trigger('mousedown', 'topLeft');
+        cy.get('.paper-container').trigger('mousemove', 'bottomRight');
+        cy.get('.paper-container').trigger('mouseup',{force: true});
+
+        //Validation 1: Verify the rectangle of the selection cover the element with the large name
+        cy.get('[data-cy="selection-box"]').should('exist')
+          .invoke('width').then((val) => {
+            cy.log('with initial of rectangle', value);
+            cy.log('with with of rectangle with large name ', val);
+            expect(val).to.be.greaterThan(value);
+          });
+      });
+  });
+});
diff --git a/tests/e2e/specs/canvasUsability/canvasSelection/VerifySelectionWithProcess.spec.js b/tests/e2e/specs/canvasUsability/canvasSelection/VerifySelectionWithProcess.spec.js
new file mode 100644
index 00000000..7f56210c
--- /dev/null
+++ b/tests/e2e/specs/canvasUsability/canvasSelection/VerifySelectionWithProcess.spec.js
@@ -0,0 +1,40 @@
+import {
+  dragFromSourceToDest,
+  connectNodesWithFlow,
+  waitToRenderAllShapes, getGraphElements,
+} from '../../../support/utils';
+import { nodeTypes } from '../../../support/constants';
+
+describe('Canvas Selection', () => {
+  it('TCP4-2666: Verify selection with process', () => {
+    const initialNumberOfElements = 1;
+
+    //Step 1: Drag Start Event
+    waitToRenderAllShapes();
+    const startEventPosition = { x: 150, y: 150 };
+    getGraphElements().should('have.length', initialNumberOfElements);
+
+    //Step 2: Drag Task component
+    const taskPosition = { x: 300, y: 130 };
+    dragFromSourceToDest(nodeTypes.task, taskPosition);
+
+    //Step 3: Drag End Event
+    const endEventPosition = { x: 500, y: 150 };
+    dragFromSourceToDest(nodeTypes.endEvent, endEventPosition);
+
+    //Step 4: Connect the Start Event with Task
+    connectNodesWithFlow('generic-flow-button', startEventPosition, taskPosition);
+
+    //Step 4: Connect the Task with End Event
+    connectNodesWithFlow('generic-flow-button', taskPosition, endEventPosition);
+
+    //Step 5: Clicks and drags the mouse over the elements
+    cy.get('.paper-container').trigger('mousedown', 'topLeft');
+    cy.get('.paper-container').trigger('mousemove', 'bottomRight');
+    cy.get('.paper-container').trigger('mouseup',{force: true});
+
+    //Validation 1: Verify that all element are selected and a rectangle highlights the selected elements
+    cy.get('[data-cy="selection-box"]').should('exist');
+
+  });
+});
diff --git a/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/Hotkeys.spec.js b/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/Hotkeys.spec.js
new file mode 100644
index 00000000..10688202
--- /dev/null
+++ b/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/Hotkeys.spec.js
@@ -0,0 +1,43 @@
+it('Zoom percentage increases when Ctrl + "+" is pressed', () => {
+  cy.get('[aria-label="Toolbar"]').click();
+  cy.get('.scale-value').should('have.text', '100%');
+  cy.get('body').type('{ctrl}+');
+  cy.get('.scale-value').should('have.text', '110%');
+  cy.get('body').type('{ctrl}+');
+  cy.get('.scale-value').should('have.text', '120%');
+  cy.get('body').type('{ctrl}+');
+  cy.get('.scale-value').should('have.text', '130%');
+});
+
+it('Zoom percentage increases when Ctrl + "NumpadAdd" is pressed', () => {
+  cy.get('[aria-label="Toolbar"]').click();
+  cy.get('.scale-value').should('have.text', '100%');
+  cy.get('body').trigger('keydown', { key:'NumpadAdd', keyCode: 107, which: 107, ctrlKey: true });
+  cy.get('.scale-value').should('have.text', '110%');
+  cy.get('body').trigger('keydown', { key:'NumpadAdd', keyCode: 107, which: 107, ctrlKey: true });
+  cy.get('.scale-value').should('have.text', '120%');
+  cy.get('body').trigger('keydown', { key:'NumpadAdd', keyCode: 107, which: 107, ctrlKey: true });
+  cy.get('.scale-value').should('have.text', '130%');
+});
+
+it('Zoom percentage decreases when Ctrl + "-" is pressed', () => {
+  cy.get('[aria-label="Toolbar"]').click();
+  cy.get('.scale-value').should('have.text', '100%');
+  cy.get('body').type('{ctrl}-');
+  cy.get('.scale-value').should('have.text', '90%');
+  cy.get('body').type('{ctrl}-');
+  cy.get('.scale-value').should('have.text', '80%');
+  cy.get('body').type('{ctrl}-');
+  cy.get('.scale-value').should('have.text', '70%');
+});
+
+it('Zoom percentage increases when Ctrl + "NumpadSubstract" is pressed', () => {
+  cy.get('[aria-label="Toolbar"]').click();
+  cy.get('.scale-value').should('have.text', '100%');
+  cy.get('body').trigger('keydown', { key:'NumpadSubstract', keyCode: 109, which: 109, ctrlKey: true });
+  cy.get('.scale-value').should('have.text', '90%');
+  cy.get('body').trigger('keydown', { key:'NumpadSubstract', keyCode: 109, which: 109, ctrlKey: true });
+  cy.get('.scale-value').should('have.text', '80%');
+  cy.get('body').trigger('keydown', { key:'NumpadSubstract', keyCode: 109, which: 109, ctrlKey: true });
+  cy.get('.scale-value').should('have.text', '70%');
+});
\ No newline at end of file
diff --git a/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyBottomRailIsNotAffectedByControl++.spec.js b/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyBottomRailIsNotAffectedByControl++.spec.js
new file mode 100644
index 00000000..d9a1ab8d
--- /dev/null
+++ b/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyBottomRailIsNotAffectedByControl++.spec.js
@@ -0,0 +1,53 @@
+import {
+  dragFromSourceToDest,
+  connectNodesWithFlow,
+  waitToRenderAllShapes,
+  getGraphElements,
+} from '../../../support/utils';
+import { nodeTypes } from '../../../support/constants';
+
+describe('Zoom In/Out Hot keys', () => {
+  it('TCP4-2657: Verify that "BOTTOM RAIL" is not affected by Control ++', () => {
+    const initialNumberOfElements = 1;
+
+    //Step 1: Drag Start Event
+    waitToRenderAllShapes();
+    const startEventPosition = { x: 150, y: 150 };
+    getGraphElements().should('have.length', initialNumberOfElements);
+
+    //Step 2: Drag Task component
+    const taskPosition = { x: 300, y: 130 };
+    dragFromSourceToDest(nodeTypes.task, taskPosition);
+
+    //Step 3: Drag End Event
+    const endEventPosition = { x: 500, y: 150 };
+    dragFromSourceToDest(nodeTypes.endEvent, endEventPosition);
+
+    //Step 4: Connect the Start Event with Task
+    connectNodesWithFlow('generic-flow-button', startEventPosition, taskPosition);
+
+    //Step 4: Connect the Task with End Event
+    connectNodesWithFlow('generic-flow-button', taskPosition, endEventPosition);
+
+    //Step 5: Click on Form Task
+    cy.get('[data-type="processmaker.components.nodes.task.Shape"]').first().click();
+
+    //Step 6: Get width of "BOTTOM RAIL" menu
+    cy.get('div[class*="card-footer"]').should('be.visible')
+      .invoke('width').then((val) => {
+        const width = val;
+        //Step 7: Press CONTROL +++++
+        cy.get('body').type('{ctrl}+++++');
+        cy.get('.scale-value').should('have.text', '150%');
+       
+        //Validation 1: Verify that heigth "BOTTOM RAIL" does not change
+        cy.get('div[class*="card-footer"]').should('exist')
+          .invoke('width').then((val) => {
+            cy.log('width initial of BOTTOM RAIL', width);
+            cy.log('width after Ctrl +++++', val);
+            expect(val).to.be.equal(width);
+          });
+      });
+  });
+
+});
diff --git a/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyBottomRailIsNotAffectedByControl--.spec.js b/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyBottomRailIsNotAffectedByControl--.spec.js
new file mode 100644
index 00000000..83860473
--- /dev/null
+++ b/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyBottomRailIsNotAffectedByControl--.spec.js
@@ -0,0 +1,53 @@
+import {
+  dragFromSourceToDest,
+  connectNodesWithFlow,
+  waitToRenderAllShapes,
+  getGraphElements,
+} from '../../../support/utils';
+import { nodeTypes } from '../../../support/constants';
+
+describe('Zoom In/Out Hot keys', () => {
+  it('TCP4-2656: Verify that "BOTTOM RAIL" is not affected by Control --', () => {
+    const initialNumberOfElements = 1;
+
+    //Step 1: Drag Start Event
+    waitToRenderAllShapes();
+    const startEventPosition = { x: 150, y: 150 };
+    getGraphElements().should('have.length', initialNumberOfElements);
+
+    //Step 2: Drag Task component
+    const taskPosition = { x: 300, y: 130 };
+    dragFromSourceToDest(nodeTypes.task, taskPosition);
+
+    //Step 3: Drag End Event
+    const endEventPosition = { x: 500, y: 150 };
+    dragFromSourceToDest(nodeTypes.endEvent, endEventPosition);
+
+    //Step 4: Connect the Start Event with Task
+    connectNodesWithFlow('generic-flow-button', startEventPosition, taskPosition);
+
+    //Step 4: Connect the Task with End Event
+    connectNodesWithFlow('generic-flow-button', taskPosition, endEventPosition);
+
+    //Step 5: Click on Form Task
+    cy.get('[data-type="processmaker.components.nodes.task.Shape"]').first().click();
+
+    //Step 6: Get width of "BOTTOM RAIL" menu
+    cy.get('div[class*="card-footer"]').should('be.visible')
+      .invoke('width').then((val) => {
+        const width = val;
+        //Step 7: Press CONTROL ---
+        cy.get('body').type('{ctrl}-----');
+        cy.get('.scale-value').should('have.text', '50%');
+       
+        //Validation 1: Verify that heigth "BOTTOM RAIL" does not change
+        cy.get('div[class*="card-footer"]').should('exist')
+          .invoke('width').then((val) => {
+            cy.log('width initial of BOTTOM RAIL', width);
+            cy.log('width after Ctrl -----', val);
+            expect(val).to.be.equal(width);
+          });
+      });
+  });
+
+});
diff --git a/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyControlsRailIsNotAffectedByControl++.spec.js b/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyControlsRailIsNotAffectedByControl++.spec.js
new file mode 100644
index 00000000..ce0e036b
--- /dev/null
+++ b/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyControlsRailIsNotAffectedByControl++.spec.js
@@ -0,0 +1,53 @@
+import {
+  dragFromSourceToDest,
+  connectNodesWithFlow,
+  waitToRenderAllShapes,
+  getGraphElements,
+} from '../../../support/utils';
+import { nodeTypes } from '../../../support/constants';
+
+describe('Zoom In/Out Hot keys', () => {
+  it('TCP4-2650: Verify that "CONTROLS RAIL" is not affected by Control ++', () => {
+    const initialNumberOfElements = 1;
+
+    //Step 1: Drag Start Event
+    waitToRenderAllShapes();
+    const startEventPosition = { x: 150, y: 150 };
+    getGraphElements().should('have.length', initialNumberOfElements);
+
+    //Step 2: Drag Task component
+    const taskPosition = { x: 300, y: 130 };
+    dragFromSourceToDest(nodeTypes.task, taskPosition);
+
+    //Step 3: Drag End Event
+    const endEventPosition = { x: 500, y: 150 };
+    dragFromSourceToDest(nodeTypes.endEvent, endEventPosition);
+
+    //Step 4: Connect the Start Event with Task
+    connectNodesWithFlow('generic-flow-button', startEventPosition, taskPosition);
+
+    //Step 4: Connect the Task with End Event
+    connectNodesWithFlow('generic-flow-button', taskPosition, endEventPosition);
+
+    //Step 5: Click on Form Task
+    cy.get('[data-type="processmaker.components.nodes.task.Shape"]').first().click();
+
+    //Step 6: Get heigth of "CONTROLS RAIL" menu
+    cy.get('[data-test="controls-column"]> div').should('be.visible')
+      .invoke('height').then((val) => {
+        const heigth = val;
+        //Step 7: Press CONTROL + plus
+        cy.get('body').type('{ctrl}+');
+        cy.get('.scale-value').should('have.text', '110%');
+       
+        //Validation 1: Verify that heigth "CONTROL RAIL" does not change
+        cy.get('[data-test="controls-column"]> div').should('exist')
+          .invoke('height').then((val) => {
+            cy.log('heigth initial of CONTROLS RAIL', heigth);
+            cy.log('heigth after Ctrl +', val);
+            expect(val).to.be.equal(heigth);
+          });
+      });
+  });
+
+});
diff --git a/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyControlsRailIsNotAffectedByControl--.spec.js b/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyControlsRailIsNotAffectedByControl--.spec.js
new file mode 100644
index 00000000..8d21b544
--- /dev/null
+++ b/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyControlsRailIsNotAffectedByControl--.spec.js
@@ -0,0 +1,53 @@
+import {
+  dragFromSourceToDest,
+  connectNodesWithFlow,
+  waitToRenderAllShapes,
+  getGraphElements,
+} from '../../../support/utils';
+import { nodeTypes } from '../../../support/constants';
+
+describe('Zoom In/Out Hot keys', () => {
+  it('TCP4-2651: Verify that "CONTROLS RAIL" is not affected by Control --', () => {
+    const initialNumberOfElements = 1;
+
+    //Step 1: Drag Start Event
+    waitToRenderAllShapes();
+    const startEventPosition = { x: 150, y: 150 };
+    getGraphElements().should('have.length', initialNumberOfElements);
+
+    //Step 2: Drag Task component
+    const taskPosition = { x: 300, y: 130 };
+    dragFromSourceToDest(nodeTypes.task, taskPosition);
+
+    //Step 3: Drag End Event
+    const endEventPosition = { x: 500, y: 150 };
+    dragFromSourceToDest(nodeTypes.endEvent, endEventPosition);
+
+    //Step 4: Connect the Start Event with Task
+    connectNodesWithFlow('generic-flow-button', startEventPosition, taskPosition);
+
+    //Step 4: Connect the Task with End Event
+    connectNodesWithFlow('generic-flow-button', taskPosition, endEventPosition);
+
+    //Step 5: Click on Form Task
+    cy.get('[data-type="processmaker.components.nodes.task.Shape"]').first().click();
+
+    //Step 6: Get heigth of "CONTROLS RAIL" menu
+    cy.get('[data-test="controls-column"]> div').should('be.visible')
+      .invoke('height').then((val) => {
+        const heigth = val;
+        //Step 7: Press CONTROL -
+        cy.get('body').type('{ctrl}-');
+        cy.get('.scale-value').should('have.text', '90%');
+       
+        //Validation 1: Verify that heigth "CONTROL RAIL" does not change
+        cy.get('[data-test="controls-column"]> div').should('exist')
+          .invoke('height').then((val) => {
+            cy.log('heigth initial of CONTROLS RAIL', heigth);
+            cy.log('heigth after Ctrl -', val);
+            expect(val).to.be.equal(heigth);
+          });
+      });
+  });
+
+});
diff --git a/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyInspectorRailIsNotAffectedByControl++.spec.js b/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyInspectorRailIsNotAffectedByControl++.spec.js
new file mode 100644
index 00000000..e44de1aa
--- /dev/null
+++ b/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyInspectorRailIsNotAffectedByControl++.spec.js
@@ -0,0 +1,53 @@
+import {
+  dragFromSourceToDest,
+  connectNodesWithFlow,
+  waitToRenderAllShapes,
+  getGraphElements,
+} from '../../../support/utils';
+import { nodeTypes } from '../../../support/constants';
+
+describe('Zoom In/Out Hot keys', () => {
+  it('TCP4-2654: Verify that "INSPECTOR RAIL" is not affected by Control ++', () => {
+    const initialNumberOfElements = 1;
+
+    //Step 1: Drag Start Event
+    waitToRenderAllShapes();
+    const startEventPosition = { x: 150, y: 150 };
+    getGraphElements().should('have.length', initialNumberOfElements);
+
+    //Step 2: Drag Task component
+    const taskPosition = { x: 300, y: 130 };
+    dragFromSourceToDest(nodeTypes.task, taskPosition);
+
+    //Step 3: Drag End Event
+    const endEventPosition = { x: 500, y: 150 };
+    dragFromSourceToDest(nodeTypes.endEvent, endEventPosition);
+
+    //Step 4: Connect the Start Event with Task
+    connectNodesWithFlow('generic-flow-button', startEventPosition, taskPosition);
+
+    //Step 4: Connect the Task with End Event
+    connectNodesWithFlow('generic-flow-button', taskPosition, endEventPosition);
+
+    //Step 5: Click on Form Task
+    cy.get('[data-type="processmaker.components.nodes.task.Shape"]').first().click();
+
+    //Step 6: Get heigth of "INSPECTOR RAIL" menu
+    cy.get('[data-test="inspector-column"]').should('be.visible')
+      .invoke('height').then((val) => {
+        const heigth = val;
+        //Step 7: Press CONTROL + plus
+        cy.get('body').type('{ctrl}++++');
+        cy.get('.scale-value').should('have.text', '140%');
+       
+        //Validation 1: Verify that heigth "INSPECTOR RAIL" does not change
+        cy.get('[data-test="inspector-column"]').should('exist')
+          .invoke('height').then((val) => {
+            cy.log('heigth initial of INSPECTOR RAIL', heigth);
+            cy.log('heigth after Ctrl +', val);
+            expect(val).to.be.equal(heigth);
+          });
+      });
+  });
+
+});
diff --git a/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyInspectorRailIsNotAffectedByControl--.spec.js b/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyInspectorRailIsNotAffectedByControl--.spec.js
new file mode 100644
index 00000000..b80cd862
--- /dev/null
+++ b/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyInspectorRailIsNotAffectedByControl--.spec.js
@@ -0,0 +1,53 @@
+import {
+  dragFromSourceToDest,
+  connectNodesWithFlow,
+  waitToRenderAllShapes,
+  getGraphElements,
+} from '../../../support/utils';
+import { nodeTypes } from '../../../support/constants';
+
+describe('Zoom In/Out Hot keys', () => {
+  it('TCP4-2655: Verify that "INSPECTOR RAIL" is not affected by Control --', () => {
+    const initialNumberOfElements = 1;
+
+    //Step 1: Drag Start Event
+    waitToRenderAllShapes();
+    const startEventPosition = { x: 150, y: 150 };
+    getGraphElements().should('have.length', initialNumberOfElements);
+
+    //Step 2: Drag Task component
+    const taskPosition = { x: 300, y: 130 };
+    dragFromSourceToDest(nodeTypes.task, taskPosition);
+
+    //Step 3: Drag End Event
+    const endEventPosition = { x: 500, y: 150 };
+    dragFromSourceToDest(nodeTypes.endEvent, endEventPosition);
+
+    //Step 4: Connect the Start Event with Task
+    connectNodesWithFlow('generic-flow-button', startEventPosition, taskPosition);
+
+    //Step 4: Connect the Task with End Event
+    connectNodesWithFlow('generic-flow-button', taskPosition, endEventPosition);
+
+    //Step 5: Click on Form Task
+    cy.get('[data-type="processmaker.components.nodes.task.Shape"]').first().click();
+
+    //Step 6: Get heigth of "INSPECTOR RAIL" menu
+    cy.get('[data-test="inspector-column"]').should('be.visible')
+      .invoke('height').then((val) => {
+        const heigth = val;
+        //Step 7: Press CONTROL ---
+        cy.get('body').type('{ctrl}----');
+        cy.get('.scale-value').should('have.text', '60%');
+       
+        //Validation 1: Verify that heigth "INSPECTOR RAIL" does not change
+        cy.get('[data-test="inspector-column"]').should('exist')
+          .invoke('height').then((val) => {
+            cy.log('heigth initial of INSPECTOR RAIL', heigth);
+            cy.log('heigth after Ctrl ----', val);
+            expect(val).to.be.equal(heigth);
+          });
+      });
+  });
+
+});
diff --git a/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyTopRailIsNotAffectedByControl++.spec.js b/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyTopRailIsNotAffectedByControl++.spec.js
new file mode 100644
index 00000000..9f3a35e7
--- /dev/null
+++ b/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyTopRailIsNotAffectedByControl++.spec.js
@@ -0,0 +1,53 @@
+import {
+  dragFromSourceToDest,
+  connectNodesWithFlow,
+  waitToRenderAllShapes,
+  getGraphElements,
+} from '../../../support/utils';
+import { nodeTypes } from '../../../support/constants';
+
+describe('Zoom In/Out Hot keys', () => {
+  it('TCP4-2652: Verify that "TOP RAIL" is not affected by Control ++', () => {
+    const initialNumberOfElements = 1;
+
+    //Step 1: Drag Start Event
+    waitToRenderAllShapes();
+    const startEventPosition = { x: 150, y: 150 };
+    getGraphElements().should('have.length', initialNumberOfElements);
+
+    //Step 2: Drag Task component
+    const taskPosition = { x: 300, y: 130 };
+    dragFromSourceToDest(nodeTypes.task, taskPosition);
+
+    //Step 3: Drag End Event
+    const endEventPosition = { x: 500, y: 150 };
+    dragFromSourceToDest(nodeTypes.endEvent, endEventPosition);
+
+    //Step 4: Connect the Start Event with Task
+    connectNodesWithFlow('generic-flow-button', startEventPosition, taskPosition);
+
+    //Step 4: Connect the Task with End Event
+    connectNodesWithFlow('generic-flow-button', taskPosition, endEventPosition);
+
+    //Step 5: Click on Form Task
+    cy.get('[data-type="processmaker.components.nodes.task.Shape"]').first().click();
+
+    //Step 6: Get width of "TOP RAIL" menu
+    cy.get('[role="toolbar"][aria-label="Toolbar"]').should('be.visible')
+      .invoke('width').then((val) => {
+        const width = val;
+        //Step 7: Press CONTROL ++
+        cy.get('body').type('{ctrl}++');
+        cy.get('.scale-value').should('have.text', '120%');
+       
+        //Validation 1: Verify that heigth "TOP RAIL" does not change
+        cy.get('[role="toolbar"][aria-label="Toolbar"]').should('exist')
+          .invoke('width').then((val) => {
+            cy.log('width initial of TOP RAIL', width);
+            cy.log('width after Ctrl ++', val);
+            expect(val).to.be.equal(width);
+          });
+      });
+  });
+
+});
diff --git a/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyTopRailIsNotAffectedByControl--.spec.js b/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyTopRailIsNotAffectedByControl--.spec.js
new file mode 100644
index 00000000..7304f549
--- /dev/null
+++ b/tests/e2e/specs/canvasUsability/zoomInOutHotKeys/VerifyTopRailIsNotAffectedByControl--.spec.js
@@ -0,0 +1,53 @@
+import {
+  dragFromSourceToDest,
+  connectNodesWithFlow,
+  waitToRenderAllShapes,
+  getGraphElements,
+} from '../../../support/utils';
+import { nodeTypes } from '../../../support/constants';
+
+describe('Zoom In/Out Hot keys', () => {
+  it('TCP4-2653: Verify that "TOP RAIL" is not affected by Control --', () => {
+    const initialNumberOfElements = 1;
+
+    //Step 1: Drag Start Event
+    waitToRenderAllShapes();
+    const startEventPosition = { x: 150, y: 150 };
+    getGraphElements().should('have.length', initialNumberOfElements);
+
+    //Step 2: Drag Task component
+    const taskPosition = { x: 300, y: 130 };
+    dragFromSourceToDest(nodeTypes.task, taskPosition);
+
+    //Step 3: Drag End Event
+    const endEventPosition = { x: 500, y: 150 };
+    dragFromSourceToDest(nodeTypes.endEvent, endEventPosition);
+
+    //Step 4: Connect the Start Event with Task
+    connectNodesWithFlow('generic-flow-button', startEventPosition, taskPosition);
+
+    //Step 4: Connect the Task with End Event
+    connectNodesWithFlow('generic-flow-button', taskPosition, endEventPosition);
+
+    //Step 5: Click on Form Task
+    cy.get('[data-type="processmaker.components.nodes.task.Shape"]').first().click();
+
+    //Step 6: Get width of "TOP RAIL" menu
+    cy.get('[role="toolbar"][aria-label="Toolbar"]').should('be.visible')
+      .invoke('width').then((val) => {
+        const width = val;
+        //Step 7: Press CONTROL --
+        cy.get('body').type('{ctrl}--');
+        cy.get('.scale-value').should('have.text', '80%');
+       
+        //Validation 1: Verify that heigth "TOP RAIL" does not change
+        cy.get('[role="toolbar"][aria-label="Toolbar"]').should('exist')
+          .invoke('width').then((val) => {
+            cy.log('width initial of TOP RAIL', width);
+            cy.log('width after Ctrl --', val);
+            expect(val).to.be.equal(width);
+          });
+      });
+  });
+
+});
diff --git a/tests/e2e/support/utils.js b/tests/e2e/support/utils.js
index ac922397..ab1b4ead 100644
--- a/tests/e2e/support/utils.js
+++ b/tests/e2e/support/utils.js
@@ -39,13 +39,13 @@ export function getGraphElements() {
     .then(cells => cells.filter(cell => cell.component));
 }
 
-export function getElementAtPosition(position, componentType) {
+export function getElementAtPosition(position, componentType, offsetX = 0, offsetY = 0) {
   const paperGridSize = gridSize;
   const searchRectangle = {
     width: paperGridSize,
     height: paperGridSize,
-    x: position.x - paperGridSize / 2,
-    y: position.y - paperGridSize / 2,
+    x: position.x - paperGridSize / 2 + offsetX,
+    y: position.y - paperGridSize / 2 + offsetY,
   };
 
   return cy.window()
@@ -148,11 +148,13 @@ export function waitToRenderNodeUpdates() {
 }
 
 export function connectNodesWithFlow(flowType, startPosition, endPosition, clickPosition = 'center') {
+  const mouseEvent = { clientX: startPosition.x , clientY: startPosition.y };
   return getElementAtPosition(startPosition)
-    .click({ force: true })
+    .trigger('mousedown', mouseEvent, { force: true })
+    .trigger('mouseup', mouseEvent,  { force: true })
     .then($element => {
       return getCrownButtonForElement($element, flowType)
-        .click({ force: true });
+        .click();
     })
     .then(() => {
       getElementAtPosition(endPosition).then($endElement => {
diff --git a/tests/unit/moveWithArrowKeys.spec.js b/tests/unit/moveWithArrowKeys.spec.js
index f383d6ff..af3b361f 100644
--- a/tests/unit/moveWithArrowKeys.spec.js
+++ b/tests/unit/moveWithArrowKeys.spec.js
@@ -1,4 +1,4 @@
-import moveShapeByKeypress, { moveAmount } from '@/components/modeler/moveWithArrowKeys';
+import moveShapeByKeypress, { moveAmount } from '@/components/hotkeys/moveWithArrowKeys';
 
 const shapeFactory = (type = 'foo') => ({
   translate: jest.fn(),