Pārlūkot izejas kodu

fix: support bidirectional shift+click selection in library items (#10034)

* fix: support bidirectional shift+click selection in library items

- Enable bottom-up multi-selection (previously only top-down worked)
- Use Math.min/max to handle selection range in both directions
- Maintains existing behavior for preserving non-contiguous selections
- Fixes issue where shift+clicking items above last selected item failed

* improve deselection behavior

---------

Co-authored-by: dwelle <[email protected]>
Omar Eltomy 3 nedēļas atpakaļ
vecāks
revīzija
f1b097ad06

+ 11 - 1
packages/excalidraw/components/LibraryMenu.tsx

@@ -281,19 +281,29 @@ export const LibraryMenu = memo(() => {
           if (target.closest(`.${CLASSES.SIDEBAR}`)) {
             // stop propagation so that we don't prevent it downstream
             // (default browser behavior is to clear search input on ESC)
-            event.stopPropagation();
             if (selectedItems.length > 0) {
+              event.stopPropagation();
               setSelectedItems([]);
             } else if (
               isWritableElement(target) &&
               target instanceof HTMLInputElement &&
               !target.value
             ) {
+              event.stopPropagation();
               // if search input empty -> close library
               // (maybe not a good idea?)
               setAppState({ openSidebar: null });
               app.focusContainer();
             }
+          } else if (selectedItems.length > 0) {
+            const { x, y } = app.lastViewportPosition;
+            const elementUnderCursor = document.elementFromPoint(x, y);
+            // also deselect elements if sidebar doesn't have focus but the
+            // cursor is over it
+            if (elementUnderCursor?.closest(`.${CLASSES.SIDEBAR}`)) {
+              event.stopPropagation();
+              setSelectedItems([]);
+            }
           }
         }
       },

+ 12 - 1
packages/excalidraw/components/LibraryMenuItems.tsx

@@ -138,10 +138,13 @@ export default function LibraryMenuItems({
           }
 
           const selectedItemsMap = arrayToMap(selectedItems);
+          // Support both top-down and bottom-up selection by using min/max
+          const minRange = Math.min(rangeStart, rangeEnd);
+          const maxRange = Math.max(rangeStart, rangeEnd);
           const nextSelectedIds = orderedItems.reduce(
             (acc: LibraryItem["id"][], item, idx) => {
               if (
-                (idx >= rangeStart && idx <= rangeEnd) ||
+                (idx >= minRange && idx <= maxRange) ||
                 selectedItemsMap.has(item.id)
               ) {
                 acc.push(item.id);
@@ -169,6 +172,14 @@ export default function LibraryMenuItems({
     ],
   );
 
+  useEffect(() => {
+    // if selection is removed (e.g. via esc), reset last selected item
+    // so that subsequent shift+clicks don't select a large range
+    if (!selectedItems.length) {
+      setLastSelectedItem(null);
+    }
+  }, [selectedItems]);
+
   const getInsertedElements = useCallback(
     (id: string) => {
       let targetElements;