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 <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
Omar Eltomy
2025-09-29 14:46:42 +03:00
committed by GitHub
parent 9fcbbe0d27
commit f1b097ad06
2 changed files with 23 additions and 2 deletions

View File

@@ -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([]);
}
}
}
},

View File

@@ -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;