refactor: Unify document selection and drag interactions by replacing stack-specific callbacks and simplifying drag state.

This commit is contained in:
2025-11-25 20:54:53 +01:00
parent 13c87f4b07
commit e0fce40bc3
10 changed files with 279 additions and 267 deletions
+39 -39
View File
@@ -142,8 +142,8 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
}): ReactNode => {
const {
selectedEntries,
focusedRowKey,
setFocusedRowKey,
focusedEntryKey,
setFocusedEntryKey,
handleEntrySelection,
clearSelection,
selectionAnchorRef,
@@ -177,28 +177,28 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
return;
}
const rowKeys = docIds
const entryKeys = docIds
.map((id) => createDocumentEntryKey(id as Identifier))
.filter((value): value is string => typeof value === 'string');
if (!rowKeys.length) {
if (!entryKeys.length) {
return;
}
const nextKeys = [...selectedEntries];
rowKeys.forEach((key) => {
entryKeys.forEach((key) => {
if (!nextKeys.includes(key)) {
nextKeys.push(key);
}
});
const anchor = (rowKeys[0]
const anchor = (entryKeys[0]
|| selectionAnchorRef.current
|| nextKeys[nextKeys.length - 1]) as string | null;
applySelection(nextKeys, {
anchor,
interactedKeys: rowKeys,
interactedKeys: entryKeys,
});
},
[applySelection, selectedEntries, selectionAnchorRef],
@@ -482,24 +482,24 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
() => entries.map((entry) => ({ key: entry.key, type: entry.type, id: entry.id })),
[entries],
);
const navigableRowKeys = useMemo(() => navigableRows.map((row) => row.key), [navigableRows]);
const navigableEntryKeys = useMemo(() => navigableRows.map((row) => row.key), [navigableRows]);
const getEntryByKey = useCallback(
(rowKey) => entries.find((entry) => entry.key === rowKey) || null,
(entryKey) => entries.find((entry) => entry.key === entryKey) || null,
[entries],
);
const handlePanelFocus = useCallback(() => {
let resolvedKey = null;
if (focusedRowKey && navigableRowKeys.includes(focusedRowKey)) {
resolvedKey = focusedRowKey;
if (focusedEntryKey && navigableEntryKeys.includes(focusedEntryKey)) {
resolvedKey = focusedEntryKey;
}
if (!resolvedKey) {
for (let index = selectedEntries.length - 1; index >= 0; index -= 1) {
const candidate = selectedEntries[index];
if (navigableRowKeys.includes(candidate)) {
if (navigableEntryKeys.includes(candidate)) {
resolvedKey = candidate;
break;
}
@@ -519,12 +519,12 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
return;
}
setFocusedRowKey(resolvedKey);
setFocusedEntryKey(resolvedKey);
}, [
focusedRowKey,
navigableRowKeys,
focusedEntryKey,
navigableEntryKeys,
navigableRows,
setFocusedRowKey,
setFocusedEntryKey,
selectedEntries,
]);
@@ -543,15 +543,15 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
event.preventDefault();
let activeKey =
focusedRowKey && navigableRowKeys.includes(focusedRowKey)
? focusedRowKey
focusedEntryKey && navigableEntryKeys.includes(focusedEntryKey)
? focusedEntryKey
: null;
if (!activeKey) {
if (selectedEntries.length) {
for (let index = selectedEntries.length - 1; index >= 0; index -= 1) {
const candidate = selectedEntries[index];
if (navigableRowKeys.includes(candidate)) {
if (navigableEntryKeys.includes(candidate)) {
activeKey = candidate;
break;
}
@@ -559,11 +559,11 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
}
if (!activeKey) {
activeKey = key === 'ArrowUp' ? navigableRowKeys[navigableRowKeys.length - 1] : navigableRowKeys[0];
activeKey = key === 'ArrowUp' ? navigableEntryKeys[navigableEntryKeys.length - 1] : navigableEntryKeys[0];
}
}
const currentIndex = navigableRowKeys.indexOf(activeKey);
const currentIndex = navigableEntryKeys.indexOf(activeKey);
const activeRow = currentIndex === -1 ? null : navigableRows[currentIndex];
if (key === 'Enter' || key === ' ' || key === 'Space' || key === 'Spacebar') {
@@ -601,22 +601,22 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
return;
}
setFocusedRowKey(targetRow.key);
setFocusedEntryKey(targetRow.key);
handleEntrySelection(targetRow.key, {
shiftKey,
preventDefault: () => { },
});
},
[
focusedRowKey,
focusedEntryKey,
getEntryByKey,
navigableRowKeys,
navigableEntryKeys,
navigableRows,
onFolderSelect,
selectedEntries,
handleDocumentPreviewZoom,
handleEntrySelection,
setFocusedRowKey,
setFocusedEntryKey,
],
);
@@ -648,14 +648,14 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
const isTagDragEvent = useCallback((event) => isTagTransferEvent(event), []);
const ensureFocusedRowVisible = useCallback(() => {
if (!focusedRowKey) return;
if (!focusedEntryKey) return;
const container = scrollRef.current;
if (!container) return;
let selector = null;
if (focusedRowKey.startsWith('document:')) {
selector = `#document-row-${focusedRowKey.slice('document:'.length)}`;
} else if (focusedRowKey.startsWith('folder:')) {
selector = `#folder-row-${focusedRowKey.slice('folder:'.length)}`;
if (focusedEntryKey.startsWith('document:')) {
selector = `#document-row-${focusedEntryKey.slice('document:'.length)}`;
} else if (focusedEntryKey.startsWith('folder:')) {
selector = `#folder-row-${focusedEntryKey.slice('folder:'.length)}`;
}
if (!selector) {
return;
@@ -681,22 +681,22 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
const nextScrollTop = rowBottom - container.clientHeight;
container.scrollTop = Math.max(nextScrollTop, 0);
}
}, [focusedRowKey]);
}, [focusedEntryKey]);
useEffect(() => {
ensureFocusedRowVisible();
}, [ensureFocusedRowVisible]);
const activeDescendantId = useMemo(() => {
if (!focusedRowKey) return undefined;
if (focusedRowKey.startsWith('document:')) {
return `document-row-${focusedRowKey.slice('document:'.length)}`;
if (!focusedEntryKey) return undefined;
if (focusedEntryKey.startsWith('document:')) {
return `document-row-${focusedEntryKey.slice('document:'.length)}`;
}
if (focusedRowKey.startsWith('folder:')) {
return `folder-row-${focusedRowKey.slice('folder:'.length)}`;
if (focusedEntryKey.startsWith('folder:')) {
return `folder-row-${focusedEntryKey.slice('folder:'.length)}`;
}
return undefined;
}, [focusedRowKey]);
}, [focusedEntryKey]);
const handleDocumentTagDragOver = useCallback(
(event) => {
@@ -756,10 +756,10 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
&& scrollRef.current
) {
scrollRef.current.focus({ preventScroll: true });
setFocusedRowKey(`folder:${folder.id}`);
setFocusedEntryKey(`folder:${folder.id}`);
}
},
[onEntryPointer, setFocusedRowKey],
[onEntryPointer, setFocusedEntryKey],
);
const handleDocumentDragStartLocal = useCallback(