Browse Source

fix: adding to selection via shift box-select (#6815)

David Luzar 2 years ago
parent
commit
8af9ea3cf3
2 changed files with 108 additions and 29 deletions
  1. 40 29
      src/components/App.tsx
  2. 68 0
      src/tests/selection.test.tsx

+ 40 - 29
src/components/App.tsx

@@ -6068,28 +6068,7 @@ class App extends React.Component<AppProps, AppState> {
         pointerDownState.boxSelection.hasOccurred = true;
 
         const elements = this.scene.getNonDeletedElements();
-        if (
-          !event.shiftKey &&
-          // allows for box-selecting points (without shift)
-          !this.state.editingLinearElement &&
-          isSomeElementSelected(elements, this.state)
-        ) {
-          if (pointerDownState.withCmdOrCtrl && pointerDownState.hit.element) {
-            this.setState((prevState) =>
-              selectGroupsForSelectedElements(
-                {
-                  ...prevState,
-                  selectedElementIds: {
-                    [pointerDownState.hit.element!.id]: true,
-                  },
-                },
-                this.scene.getNonDeletedElements(),
-                prevState,
-                this,
-              ),
-            );
-          }
-        }
+
         // box-select line editor points
         if (this.state.editingLinearElement) {
           LinearElementEditor.handleBoxSelection(
@@ -6099,18 +6078,46 @@ class App extends React.Component<AppProps, AppState> {
           );
           // regular box-select
         } else {
+          let shouldReuseSelection = true;
+
+          if (!event.shiftKey && isSomeElementSelected(elements, this.state)) {
+            if (
+              pointerDownState.withCmdOrCtrl &&
+              pointerDownState.hit.element
+            ) {
+              this.setState((prevState) =>
+                selectGroupsForSelectedElements(
+                  {
+                    ...prevState,
+                    selectedElementIds: {
+                      [pointerDownState.hit.element!.id]: true,
+                    },
+                  },
+                  this.scene.getNonDeletedElements(),
+                  prevState,
+                  this,
+                ),
+              );
+            } else {
+              shouldReuseSelection = false;
+            }
+          }
           const elementsWithinSelection = getElementsWithinSelection(
             elements,
             draggingElement,
           );
+
           this.setState((prevState) => {
-            const nextSelectedElementIds = elementsWithinSelection.reduce(
-              (acc: Record<ExcalidrawElement["id"], true>, element) => {
-                acc[element.id] = true;
-                return acc;
-              },
-              {},
-            );
+            const nextSelectedElementIds = {
+              ...(shouldReuseSelection && prevState.selectedElementIds),
+              ...elementsWithinSelection.reduce(
+                (acc: Record<ExcalidrawElement["id"], true>, element) => {
+                  acc[element.id] = true;
+                  return acc;
+                },
+                {},
+              ),
+            };
 
             if (pointerDownState.hit.element) {
               // if using ctrl/cmd, select the hitElement only if we
@@ -6125,6 +6132,10 @@ class App extends React.Component<AppProps, AppState> {
             return selectGroupsForSelectedElements(
               {
                 ...prevState,
+                ...(!shouldReuseSelection && {
+                  selectedGroupIds: {},
+                  editingGroupId: null,
+                }),
                 selectedElementIds: nextSelectedElementIds,
                 showHyperlinkPopup:
                   elementsWithinSelection.length === 1 &&

+ 68 - 0
src/tests/selection.test.tsx

@@ -28,6 +28,74 @@ const { h } = window;
 
 const mouse = new Pointer("mouse");
 
+describe("box-selection", () => {
+  beforeEach(async () => {
+    await render(<ExcalidrawApp />);
+  });
+
+  it("should allow adding to selection via box-select when holding shift", async () => {
+    const rect1 = API.createElement({
+      type: "rectangle",
+      x: 0,
+      y: 0,
+      width: 50,
+      height: 50,
+      backgroundColor: "red",
+      fillStyle: "solid",
+    });
+    const rect2 = API.createElement({
+      type: "rectangle",
+      x: 100,
+      y: 0,
+      width: 50,
+      height: 50,
+    });
+
+    h.elements = [rect1, rect2];
+
+    mouse.downAt(175, -20);
+    mouse.moveTo(85, 70);
+    mouse.up();
+
+    assertSelectedElements([rect2.id]);
+
+    Keyboard.withModifierKeys({ shift: true }, () => {
+      mouse.downAt(75, -20);
+      mouse.moveTo(-15, 70);
+      mouse.up();
+    });
+
+    assertSelectedElements([rect2.id, rect1.id]);
+  });
+
+  it("should (de)select element when box-selecting over and out while not holding shift", async () => {
+    const rect1 = API.createElement({
+      type: "rectangle",
+      x: 0,
+      y: 0,
+      width: 50,
+      height: 50,
+      backgroundColor: "red",
+      fillStyle: "solid",
+    });
+
+    h.elements = [rect1];
+
+    mouse.downAt(75, -20);
+    mouse.moveTo(-15, 70);
+
+    assertSelectedElements([rect1.id]);
+
+    mouse.moveTo(100, -100);
+
+    assertSelectedElements([]);
+
+    mouse.up();
+
+    assertSelectedElements([]);
+  });
+});
+
 describe("inner box-selection", () => {
   beforeEach(async () => {
     await render(<ExcalidrawApp />);