Przeglądaj źródła

feat: cycle through selected elements on cmd/ctrl-click

dwelle 4 lat temu
rodzic
commit
0e3a5b2042
2 zmienionych plików z 58 dodań i 1 usunięć
  1. 12 1
      src/components/App.tsx
  2. 46 0
      src/tests/selection.test.tsx

+ 12 - 1
src/components/App.tsx

@@ -1862,6 +1862,7 @@ class App extends React.Component<AppProps, AppState> {
       /** if true, returns the first selected element (with highest z-index)
       /** if true, returns the first selected element (with highest z-index)
         of all hit elements */
         of all hit elements */
       preferSelected?: boolean;
       preferSelected?: boolean;
+      cycleElementsUnderCursor?: boolean;
     },
     },
   ): NonDeleted<ExcalidrawElement> | null {
   ): NonDeleted<ExcalidrawElement> | null {
     const allHitElements = this.getElementsAtPosition(x, y);
     const allHitElements = this.getElementsAtPosition(x, y);
@@ -1872,6 +1873,13 @@ class App extends React.Component<AppProps, AppState> {
             return allHitElements[index];
             return allHitElements[index];
           }
           }
         }
         }
+      } else if (opts?.cycleElementsUnderCursor) {
+        const selectedIdx = allHitElements.findIndex(
+          (element) => this.state.selectedElementIds[element.id],
+        );
+        return selectedIdx > 0
+          ? allHitElements[selectedIdx - 1]
+          : allHitElements[allHitElements.length - 1];
       }
       }
       const elementWithHighestZIndex =
       const elementWithHighestZIndex =
         allHitElements[allHitElements.length - 1];
         allHitElements[allHitElements.length - 1];
@@ -2746,10 +2754,13 @@ class App extends React.Component<AppProps, AppState> {
 
 
         // hitElement may already be set above, so check first
         // hitElement may already be set above, so check first
         pointerDownState.hit.element =
         pointerDownState.hit.element =
-          pointerDownState.hit.element ??
+          (!event[KEYS.CTRL_OR_CMD] ? pointerDownState.hit.element : null) ??
           this.getElementAtPosition(
           this.getElementAtPosition(
             pointerDownState.origin.x,
             pointerDownState.origin.x,
             pointerDownState.origin.y,
             pointerDownState.origin.y,
+            {
+              cycleElementsUnderCursor: event[KEYS.CTRL_OR_CMD],
+            },
           );
           );
 
 
         // For overlapped elements one position may hit
         // For overlapped elements one position may hit

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

@@ -144,6 +144,52 @@ describe("inner box-selection", () => {
   });
   });
 });
 });
 
 
+describe("selecting with cmd/ctrl modifier", () => {
+  beforeEach(async () => {
+    await render(<ExcalidrawApp />);
+  });
+  it("cycling through elements under cursor", async () => {
+    const rect1 = API.createElement({
+      type: "rectangle",
+      x: 0,
+      y: 0,
+      width: 200,
+      height: 200,
+      backgroundColor: "red",
+      fillStyle: "solid",
+    });
+    const rect2 = API.createElement({
+      type: "rectangle",
+      x: 0,
+      y: 0,
+      width: 200,
+      height: 200,
+      backgroundColor: "red",
+      fillStyle: "solid",
+    });
+    const rect3 = API.createElement({
+      type: "rectangle",
+      x: 0,
+      y: 0,
+      width: 200,
+      height: 200,
+      backgroundColor: "red",
+      fillStyle: "solid",
+    });
+    h.elements = [rect1, rect2, rect3];
+    Keyboard.withModifierKeys({ ctrl: true }, () => {
+      mouse.clickAt(100, 100);
+      assertSelectedElements(rect3);
+      mouse.clickAt(100, 100);
+      assertSelectedElements(rect2);
+      mouse.clickAt(100, 100);
+      assertSelectedElements(rect1);
+      mouse.clickAt(100, 100);
+      assertSelectedElements(rect3);
+    });
+  });
+});
+
 describe("selection element", () => {
 describe("selection element", () => {
   it("create selection element on pointer down", async () => {
   it("create selection element on pointer down", async () => {
     const { getByToolName, container } = await render(<ExcalidrawApp />);
     const { getByToolName, container } = await render(<ExcalidrawApp />);