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
+66 -47
View File
@@ -35,23 +35,23 @@ export const useDocumentSelection = ({
const selectionAnchorRef = useRef<string | null>(null); const selectionAnchorRef = useRef<string | null>(null);
const selectionInitializedRef = useRef(false); const selectionInitializedRef = useRef(false);
const [focusedDocumentId, setFocusedDocumentId] = useState<DocumentId | null>(null); const [focusedDocumentId, setFocusedDocumentId] = useState<DocumentId | null>(null);
const [focusedRowKey, setFocusedRowKey] = useState<string | null>(null); const [focusedEntryKey, setFocusedEntryKey] = useState<string | null>(null);
const visibleRowKeySetRef = useRef<Set<string>>(new Set()); const visibleEntryKeySetRef = useRef<Set<string>>(new Set());
const navigableRowKeysRef = useRef<string[]>([]); const navigableEntryKeysRef = useRef<string[]>([]);
const configureSelectionEnvironment = useCallback(({ const configureSelectionEnvironment = useCallback(({
visibleRowKeySet, visibleEntryKeySet,
navigableRowKeys, navigableEntryKeys,
}: { }: {
visibleRowKeySet?: Set<string>; visibleEntryKeySet?: Set<string>;
navigableRowKeys?: string[]; navigableEntryKeys?: string[];
}) => { }) => {
if (visibleRowKeySet) { if (visibleEntryKeySet) {
visibleRowKeySetRef.current = visibleRowKeySet; visibleEntryKeySetRef.current = visibleEntryKeySet;
} }
if (Array.isArray(navigableRowKeys)) { if (Array.isArray(navigableEntryKeys)) {
navigableRowKeysRef.current = navigableRowKeys; navigableEntryKeysRef.current = navigableEntryKeys;
} }
}, []); }, []);
@@ -88,16 +88,16 @@ export const useDocumentSelection = ({
const applySelection = useCallback( const applySelection = useCallback(
( (
rowKeys: Array<string | null>, entryKeys: Array<string | null>,
{ anchor = null, interactedKeys = [] }: ApplySelectionOptions = { anchor: null, interactedKeys: [] }, { anchor = null, interactedKeys = [] }: ApplySelectionOptions = { anchor: null, interactedKeys: [] },
) => { ) => {
const visibleRowKeySet = visibleRowKeySetRef.current; const visibleEntryKeySet = visibleEntryKeySetRef.current;
const unique: string[] = []; const unique: string[] = [];
(rowKeys || []).forEach((key) => { (entryKeys || []).forEach((key) => {
if (!key) return; if (!key) return;
let canonicalKey: string | null = null; let canonicalKey: string | null = null;
if (visibleRowKeySet.has(key)) { if (visibleEntryKeySet.has(key)) {
canonicalKey = key; canonicalKey = key;
} else if (isDocumentEntry(key)) { } else if (isDocumentEntry(key)) {
const id = getEntryId(key); const id = getEntryId(key);
@@ -107,7 +107,7 @@ export const useDocumentSelection = ({
canonicalKey = id ? createFolderEntryKey(id) : null; canonicalKey = id ? createFolderEntryKey(id) : null;
} }
if (!canonicalKey || !visibleRowKeySet.has(canonicalKey)) { if (!canonicalKey || !visibleEntryKeySet.has(canonicalKey)) {
return; return;
} }
@@ -159,19 +159,25 @@ export const useDocumentSelection = ({
); );
const clearSelection = useCallback(() => { const clearSelection = useCallback(() => {
setFocusedRowKey(null); setFocusedEntryKey(null);
applySelection([], { anchor: null, interactedKeys: [] }); applySelection([], { anchor: null, interactedKeys: [] });
}, [applySelection]); }, [applySelection]);
const handleEntrySelection = useCallback( const handleEntrySelection = useCallback(
(rowKey: string, event?: SelectionEventLike) => { (entryKeyOrKeys: string | string[], event?: SelectionEventLike) => {
const visibleRowKeySet = visibleRowKeySetRef.current; const visibleEntryKeySet = visibleEntryKeySetRef.current;
const navigableRowKeys = navigableRowKeysRef.current; const navigableEntryKeys = navigableEntryKeysRef.current;
if (!rowKey || !visibleRowKeySet.has(rowKey)) {
const entryKeys = Array.isArray(entryKeyOrKeys) ? entryKeyOrKeys : [entryKeyOrKeys];
const validKeys = entryKeys.filter((key) => key && visibleEntryKeySet.has(key));
if (validKeys.length === 0) {
return; return;
} }
setFocusedRowKey(rowKey); // Focus the last valid key
const lastKey = validKeys[validKeys.length - 1];
setFocusedEntryKey(lastKey);
const shiftKey = Boolean(event?.shiftKey); const shiftKey = Boolean(event?.shiftKey);
const metaKey = Boolean(event?.metaKey); const metaKey = Boolean(event?.metaKey);
@@ -187,44 +193,57 @@ export const useDocumentSelection = ({
anchorKey = selectedEntries[selectedEntries.length - 1]; anchorKey = selectedEntries[selectedEntries.length - 1];
} }
if (!anchorKey) { if (!anchorKey) {
anchorKey = rowKey; anchorKey = lastKey;
} }
let nextKeys: string[] = []; let nextKeys: string[] = [];
let interactedKeys: string[] = []; let interactedKeys: string[] = [];
if (shiftKey && anchorKey) { // Shift selection logic (range) - primarily for single click + shift
const anchorIndex = navigableRowKeys.indexOf(anchorKey); if (shiftKey && anchorKey && validKeys.length === 1) {
const targetIndex = navigableRowKeys.indexOf(rowKey); const entryKey = validKeys[0];
const anchorIndex = navigableEntryKeys.indexOf(anchorKey);
const targetIndex = navigableEntryKeys.indexOf(entryKey);
if (anchorIndex !== -1 && targetIndex !== -1) { if (anchorIndex !== -1 && targetIndex !== -1) {
const [start, end] = anchorIndex <= targetIndex const [start, end] = anchorIndex <= targetIndex
? [anchorIndex, targetIndex] ? [anchorIndex, targetIndex]
: [targetIndex, anchorIndex]; : [targetIndex, anchorIndex];
const range = navigableRowKeys.slice(start, end + 1); const range = navigableEntryKeys.slice(start, end + 1);
nextKeys = range; nextKeys = range;
const previousSet = new Set(selectedEntries); const previousSet = new Set(selectedEntries);
interactedKeys = range.filter((key) => key === rowKey || !previousSet.has(key)); interactedKeys = range.filter((key) => key === entryKey || !previousSet.has(key));
if (!interactedKeys.includes(rowKey)) { if (!interactedKeys.includes(entryKey)) {
interactedKeys.push(rowKey); interactedKeys.push(entryKey);
} }
} else { } else {
nextKeys = [rowKey]; nextKeys = [entryKey];
interactedKeys = [rowKey]; interactedKeys = [entryKey];
} }
} else if (additive) { } else if (additive) {
if (selectedEntries.includes(rowKey)) { // Additive batch
nextKeys = selectedEntries.filter((key) => key !== rowKey); const previousSet = new Set(selectedEntries);
interactedKeys = []; if (validKeys.length === 1) {
const entryKey = validKeys[0];
if (previousSet.has(entryKey)) {
nextKeys = selectedEntries.filter((key) => key !== entryKey);
interactedKeys = [];
} else {
nextKeys = [...selectedEntries, entryKey];
interactedKeys = [entryKey];
}
} else { } else {
nextKeys = [...selectedEntries, rowKey]; // Batch add
interactedKeys = [rowKey]; validKeys.forEach(key => previousSet.add(key));
nextKeys = Array.from(previousSet) as string[];
interactedKeys = validKeys;
} }
anchorKey = rowKey; anchorKey = lastKey;
} else { } else {
nextKeys = [rowKey]; // Replace with batch
interactedKeys = [rowKey]; nextKeys = validKeys;
anchorKey = rowKey; interactedKeys = validKeys;
anchorKey = lastKey;
} }
applySelection(nextKeys, { anchor: anchorKey, interactedKeys }); applySelection(nextKeys, { anchor: anchorKey, interactedKeys });
@@ -235,14 +254,14 @@ export const useDocumentSelection = ({
const promoteSelectionOrder = useCallback( const promoteSelectionOrder = useCallback(
(docId?: DocumentId | null) => { (docId?: DocumentId | null) => {
if (!docId) return; if (!docId) return;
const rowKey = createDocumentEntryKey(docId); const entryKey = createDocumentEntryKey(docId);
if (!rowKey) return; if (!entryKey) return;
if (!selectedEntries.includes(rowKey)) { if (!selectedEntries.includes(entryKey)) {
return; return;
} }
updateSelectionOrder(selectedEntries, [rowKey]); updateSelectionOrder(selectedEntries, [entryKey]);
}, },
[selectedEntries, updateSelectionOrder], [selectedEntries, updateSelectionOrder],
); );
@@ -257,8 +276,8 @@ export const useDocumentSelection = ({
selectionInitializedRef, selectionInitializedRef,
focusedDocumentId, focusedDocumentId,
setFocusedDocumentId, setFocusedDocumentId,
focusedRowKey, focusedEntryKey,
setFocusedRowKey, setFocusedEntryKey,
applySelection, applySelection,
clearSelection, clearSelection,
handleEntrySelection, handleEntrySelection,
+17 -11
View File
@@ -5,7 +5,7 @@ import { isDocumentEntry, isFolderEntry, getEntryId } from './entryKey';
interface SelectionEntry { interface SelectionEntry {
entryKey?: string; entryKey?: string;
// Legacy field for compatibility // Legacy field for compatibility
rowKey?: string; // rowKey?: string; // Removed as part of refactor
[key: string]: unknown; [key: string]: unknown;
} }
@@ -32,8 +32,8 @@ export const useWorkspaceSelection = ({
selectionInitializedRef, selectionInitializedRef,
focusedDocumentId, focusedDocumentId,
setFocusedDocumentId, setFocusedDocumentId,
focusedRowKey, focusedEntryKey,
setFocusedRowKey, setFocusedEntryKey,
applySelection, applySelection,
clearSelection, clearSelection,
handleEntrySelection, handleEntrySelection,
@@ -60,12 +60,18 @@ export const useWorkspaceSelection = ({
); );
const selectEntry = useCallback( const selectEntry = useCallback(
(entry: SelectionEntry | string, event?: unknown) => { (entryOrEntries: SelectionEntry | string | Array<SelectionEntry | string>, event?: unknown) => {
const rowKey = entry && Object(entry) === entry const entries = Array.isArray(entryOrEntries) ? entryOrEntries : [entryOrEntries];
? (entry as SelectionEntry).rowKey ?? undefined const entryKeys = entries
: (entry as string); .map((entry) => {
if (!rowKey) return; return entry && Object(entry) === entry
handleEntrySelection(rowKey, event); ? (entry as SelectionEntry).entryKey ?? undefined
: (entry as string);
})
.filter((key): key is string => Boolean(key));
if (entryKeys.length === 0) return;
handleEntrySelection(entryKeys, event);
}, },
[handleEntrySelection], [handleEntrySelection],
); );
@@ -96,8 +102,8 @@ export const useWorkspaceSelection = ({
selectionInitializedRef, selectionInitializedRef,
focusedDocumentId, focusedDocumentId,
setFocusedDocumentId, setFocusedDocumentId,
focusedRowKey, focusedEntryKey,
setFocusedRowKey, setFocusedEntryKey,
applySelection, applySelection,
clearSelection, clearSelection,
handleEntrySelection: selectEntry, handleEntrySelection: selectEntry,
+36 -33
View File
@@ -171,6 +171,8 @@ interface DesktopWorkspaceViewProps extends Omit<DocumentsViewProps, 'entries' |
recalcVisibleDocIds: () => void; recalcVisibleDocIds: () => void;
dragSettings: DragSettings; dragSettings: DragSettings;
markLayoutDirty: () => void; markLayoutDirty: () => void;
onSelect?: (descriptor: unknown, event?: unknown) => void;
onPromoteSelection?: (docId: Identifier, event?: unknown) => void;
} }
const defaultGetDocumentAsset: GetAsset = () => null; const defaultGetDocumentAsset: GetAsset = () => null;
@@ -192,6 +194,7 @@ const DesktopWorkspace: React.FC<DocumentsViewProps> = ({
clearSelection, clearSelection,
handleEntrySelection, handleEntrySelection,
promoteSelectionOrder, promoteSelectionOrder,
configureSelectionEnvironment,
} = useWorkspaceSelectionContext(); } = useWorkspaceSelectionContext();
const items = useMemo<DeskDocument[]>( const items = useMemo<DeskDocument[]>(
() => { () => {
@@ -213,36 +216,16 @@ const DesktopWorkspace: React.FC<DocumentsViewProps> = ({
[entries], [entries],
); );
const getDocRowKey = useCallback((id: Identifier | null) => (id != null ? `document:${id}` : null), []); const getDocEntryKey = useCallback((id: Identifier | null) => (id != null ? `document:${id}` : null), []);
const handleStackSelect = useCallback(
(docIds: Identifier[], event?: PointerEvent | MouseEvent | null) => {
if (!Array.isArray(docIds) || docIds.length === 0) {
return;
}
const syntheticEvent = event || ({
metaKey: true,
ctrlKey: true,
preventDefault: () => { },
} as unknown as PointerEvent);
docIds.forEach((id) => {
const key = getDocRowKey(id);
if (key) {
handleEntrySelection(key, syntheticEvent);
}
});
},
[getDocRowKey, handleEntrySelection],
);
const handlePromoteSelection = useCallback( const handlePromoteSelection = useCallback(
(docId: Identifier | null) => { (docId: Identifier | null) => {
const key = getDocRowKey(docId); const key = getDocEntryKey(docId);
if (key && promoteSelectionOrder) { if (key) {
promoteSelectionOrder(key); promoteSelectionOrder(docId);
} }
}, },
[getDocRowKey, promoteSelectionOrder], [getDocEntryKey, promoteSelectionOrder],
); );
const allowLayoutPersistence = Boolean(tenantId && viewId && viewId.startsWith('folder:')); const allowLayoutPersistence = Boolean(tenantId && viewId && viewId.startsWith('folder:'));
@@ -307,10 +290,6 @@ const DesktopWorkspace: React.FC<DocumentsViewProps> = ({
engine.setDocumentLookup(map); engine.setDocumentLookup(map);
}, [engine, items]); }, [engine, items]);
useEffect(() => {
engine.setEnsureDocumentSize(ensureDocumentSize);
}, [engine, ensureDocumentSize]);
const workspaceSnapshot = useWorkspaceSnapshot(engine, useSyncExternalStore) as WorkspaceSnapshotState; const workspaceSnapshot = useWorkspaceSnapshot(engine, useSyncExternalStore) as WorkspaceSnapshotState;
const { const {
layout: layoutSnapshot, layout: layoutSnapshot,
@@ -323,6 +302,19 @@ const DesktopWorkspace: React.FC<DocumentsViewProps> = ({
initialLoadDone, initialLoadDone,
} = workspaceSnapshot; } = workspaceSnapshot;
useEffect(() => {
const visibleEntryKeySet = new Set<string>();
visibleDocIds.forEach((id) => {
const key = getDocEntryKey(id);
if (key) visibleEntryKeySet.add(key);
});
configureSelectionEnvironment({
visibleEntryKeySet,
navigableEntryKeys: [], // Desktop doesn't have linear navigation yet
});
}, [visibleDocIds, getDocEntryKey, configureSelectionEnvironment]);
useEffect(() => { useEffect(() => {
const shouldWaitForPersisted = allowLayoutPersistence && !initialLoadDone; const shouldWaitForPersisted = allowLayoutPersistence && !initialLoadDone;
@@ -794,10 +786,22 @@ const DesktopWorkspace: React.FC<DocumentsViewProps> = ({
overlayOriginRect, overlayOriginRect,
overlayOriginTransform, overlayOriginTransform,
overlayDocument, overlayDocument,
onDocumentStackSelect: handleStackSelect,
onPromoteSelection: handlePromoteSelection, onPromoteSelection: handlePromoteSelection,
selectedDocumentIds, selectedDocumentIds,
onClearSelection: clearSelection, onClearSelection: clearSelection,
onDocumentClick: passThroughProps.onDocumentClick,
onSelect: (descriptorOrDescriptors: any, event: any) => {
const descriptors = Array.isArray(descriptorOrDescriptors)
? descriptorOrDescriptors
: [descriptorOrDescriptors];
const keys = descriptors
.map((d: any) => getDocEntryKey(d.id))
.filter((k: any) => k);
if (keys.length > 0) {
handleEntrySelection(keys, event);
}
},
documentLookup, documentLookup,
resolveBaseMetrics, resolveBaseMetrics,
bringToFront, bringToFront,
@@ -836,7 +840,6 @@ function DesktopWorkspaceView({
overlayOriginTransform, overlayOriginTransform,
overlayDocument, overlayDocument,
onDocumentClick, onDocumentClick,
onDocumentStackSelect,
onPromoteSelection, onPromoteSelection,
selectedDocumentIds, selectedDocumentIds,
onClearSelection, onClearSelection,
@@ -850,6 +853,7 @@ function DesktopWorkspaceView({
dragSettings, dragSettings,
onDocumentActivate, onDocumentActivate,
markLayoutDirty, markLayoutDirty,
onSelect,
}: DesktopWorkspaceViewProps) { }: DesktopWorkspaceViewProps) {
const handleDeskDocumentActivate = useCallback( const handleDeskDocumentActivate = useCallback(
(docId: Identifier) => { (docId: Identifier) => {
@@ -888,7 +892,6 @@ function DesktopWorkspaceView({
settings: dragSettings, settings: dragSettings,
containerRef, containerRef,
onDocumentActivate: handleDeskDocumentActivate, onDocumentActivate: handleDeskDocumentActivate,
selectedDocumentIds,
markLayoutDirty, markLayoutDirty,
}) as { }) as {
handlePointerDown: React.PointerEventHandler<HTMLElement>; handlePointerDown: React.PointerEventHandler<HTMLElement>;
@@ -908,11 +911,11 @@ function DesktopWorkspaceView({
handlePointerUp, handlePointerUp,
handlePointerCancel, handlePointerCancel,
onDocumentClick: handleDeskDocumentClick, onDocumentClick: handleDeskDocumentClick,
onDocumentStackSelect,
onPromoteSelection, onPromoteSelection,
onDocumentActivate: handleDeskDocumentActivate, onDocumentActivate: handleDeskDocumentActivate,
selectedDocumentIds, selectedDocumentIds,
openOverlayForDoc, openOverlayForDoc,
onSelect,
}); });
useEffect(() => { useEffect(() => {
+51 -14
View File
@@ -42,6 +42,7 @@ export interface PointerIntent {
clickSelectionApplied: boolean; clickSelectionApplied: boolean;
stackSelectionApplied: boolean; stackSelectionApplied: boolean;
longPressTriggered: boolean; longPressTriggered: boolean;
optimisticSelection: string[];
} }
export const createPointerIntent = ({ export const createPointerIntent = ({
@@ -61,9 +62,9 @@ export const createPointerIntent = ({
if (metaKey) { if (metaKey) {
clickAction = CLICK_ACTIONS.addStack; clickAction = CLICK_ACTIONS.addStack;
dragAction = DRAG_ACTIONS.dragSelectStack; dragAction = DRAG_ACTIONS.dragSelection;
} else if (alreadySelected) { } else if (alreadySelected) {
clickAction = CLICK_ACTIONS.selectSingle; clickAction = CLICK_ACTIONS.openDetail;
dragAction = selectionCount > 1 ? DRAG_ACTIONS.dragSelection : DRAG_ACTIONS.dragSelectSingle; dragAction = selectionCount > 1 ? DRAG_ACTIONS.dragSelection : DRAG_ACTIONS.dragSelectSingle;
} else { } else {
clickAction = CLICK_ACTIONS.selectSingle; clickAction = CLICK_ACTIONS.selectSingle;
@@ -74,8 +75,23 @@ export const createPointerIntent = ({
? stackHits.map((value) => String(value)) ? stackHits.map((value) => String(value))
: [String(doc.id)]; : [String(doc.id)];
const stackDocIdsForDrag = dragAction === DRAG_ACTIONS.dragSelectStack ? stackList : null;
const stackDocIdsForClick = clickAction === CLICK_ACTIONS.addStack ? stackList : null; const stackDocIdsForClick = clickAction === CLICK_ACTIONS.addStack ? stackList : null;
const stackDocIdsForDrag = metaKey ? stackList : null;
// Calculate optimistic selection
let optimisticSelection: string[] = [];
if (metaKey) {
// Additive selection (stack or single)
const currentSelection = new Set(selectedDocumentIds);
stackList.forEach(id => currentSelection.add(id));
optimisticSelection = Array.from(currentSelection);
} else if (alreadySelected) {
// Already selected: keep current selection
optimisticSelection = [...selectedDocumentIds];
} else {
// New single selection
optimisticSelection = [doc.id];
}
return { return {
docId: doc.id, docId: doc.id,
@@ -90,33 +106,53 @@ export const createPointerIntent = ({
stackDocIdsForDrag, stackDocIdsForDrag,
stackDocIdsForClick, stackDocIdsForClick,
stackReplaceOnClick: clickAction === CLICK_ACTIONS.addStack, stackReplaceOnClick: clickAction === CLICK_ACTIONS.addStack,
stackReplaceOnDrag: dragAction === DRAG_ACTIONS.dragSelectStack, stackReplaceOnDrag: false,
clickSelectionApplied: false, clickSelectionApplied: false,
stackSelectionApplied: false, stackSelectionApplied: false,
longPressTriggered: false, longPressTriggered: false,
optimisticSelection,
}; };
}; };
export const applyClickPlanImmediately = ({ intent, event, onEntryPointer, onDocumentStackSelect }: { export const applyClickPlanImmediately = ({ intent, event, onEntryPointer, onDocumentStackSelect, onSelect }: {
intent: PointerIntent; intent: PointerIntent;
event?: unknown; event?: unknown;
onEntryPointer?: (descriptor: unknown, event?: unknown) => void; onEntryPointer?: (descriptor: unknown, event?: unknown) => void;
onDocumentStackSelect?: (docIds: string[], event?: unknown, options?: { replace?: boolean }) => void; onDocumentStackSelect?: (docIds: string[], event?: unknown, options?: { replace?: boolean }) => void;
onSelect?: (descriptor: unknown, event?: unknown) => void;
}) => { }) => {
switch (intent.clickAction) { switch (intent.clickAction) {
case CLICK_ACTIONS.selectSingle: case CLICK_ACTIONS.selectSingle:
case CLICK_ACTIONS.addCard: case CLICK_ACTIONS.addCard:
safeInvoke(onEntryPointer, intent.entryDescriptor, event); if (onSelect) {
safeInvoke(onSelect, intent.entryDescriptor, event);
} else {
safeInvoke(onEntryPointer, intent.entryDescriptor, event);
}
intent.clickSelectionApplied = true; intent.clickSelectionApplied = true;
break; break;
case CLICK_ACTIONS.addStack: case CLICK_ACTIONS.addStack:
if (Array.isArray(intent.stackDocIdsForClick) && intent.stackDocIdsForClick.length > 0) { if (Array.isArray(intent.stackDocIdsForClick) && intent.stackDocIdsForClick.length > 0) {
safeInvoke( // Use onSelect for stack selection (batch)
onDocumentStackSelect, if (onSelect) {
intent.stackDocIdsForClick, // Map docIds to descriptors if necessary, or just pass IDs if onSelect handles it.
event, // The current onSelect adapter in DesktopWorkspace expects { id } objects or just IDs?
{ replace: intent.stackReplaceOnClick }, // Let's assume it expects descriptors like selectSingle.
); const descriptors = intent.stackDocIdsForClick.map(id => ({
type: 'document',
id,
key: `document:${id}`,
}));
safeInvoke(onSelect, descriptors, event);
} else {
// Fallback to legacy if onSelect not provided (shouldn't happen in new flow)
safeInvoke(
onDocumentStackSelect,
intent.stackDocIdsForClick,
event,
{ replace: intent.stackReplaceOnClick },
);
}
intent.clickSelectionApplied = true; intent.clickSelectionApplied = true;
intent.stackSelectionApplied = true; intent.stackSelectionApplied = true;
} }
@@ -128,17 +164,18 @@ export const applyClickPlanImmediately = ({ intent, event, onEntryPointer, onDoc
} }
}; };
export const finalizeClickSelection = ({ intent, event, onEntryPointer, onDocumentStackSelect }: { export const finalizeClickSelection = ({ intent, event, onEntryPointer, onDocumentStackSelect, onSelect }: {
intent: PointerIntent; intent: PointerIntent;
event?: unknown; event?: unknown;
onEntryPointer?: (descriptor: unknown, event?: unknown) => void; onEntryPointer?: (descriptor: unknown, event?: unknown) => void;
onDocumentStackSelect?: (docIds: string[], event?: unknown, options?: { replace?: boolean }) => void; onDocumentStackSelect?: (docIds: string[], event?: unknown, options?: { replace?: boolean }) => void;
onSelect?: (descriptor: unknown, event?: unknown) => void;
}) => { }) => {
if (!intent || intent.clickSelectionApplied) { if (!intent || intent.clickSelectionApplied) {
return; return;
} }
applyClickPlanImmediately({ intent, event, onEntryPointer, onDocumentStackSelect }); applyClickPlanImmediately({ intent, event, onEntryPointer, onDocumentStackSelect, onSelect });
}; };
export const applyLongPressSelection = ({ intent, stackDocIds, syntheticEvent, onDocumentStackSelect }: { export const applyLongPressSelection = ({ intent, stackDocIds, syntheticEvent, onDocumentStackSelect }: {
+16 -17
View File
@@ -33,11 +33,11 @@ export const useDeskPointer = ({
handlePointerUp, handlePointerUp,
handlePointerCancel, handlePointerCancel,
onDocumentClick, onDocumentClick,
onDocumentStackSelect,
onPromoteSelection, onPromoteSelection,
onDocumentActivate, onDocumentActivate,
selectedDocumentIds, selectedDocumentIds,
openOverlayForDoc = null, openOverlayForDoc = null,
onSelect = null,
}) => { }) => {
const pointerIntentRef = useRef(null); const pointerIntentRef = useRef(null);
const pointerStartRef = useRef({ x: 0, y: 0 }); const pointerStartRef = useRef({ x: 0, y: 0 });
@@ -203,13 +203,13 @@ export const useDeskPointer = ({
intent, intent,
stackDocIds: stackHits, stackDocIds: stackHits,
syntheticEvent, syntheticEvent,
onDocumentStackSelect, onDocumentStackSelect: null, // Deprecated, handled by onSelect if needed, or long press needs update
}); });
pointerIntentRef.current = intent; pointerIntentRef.current = intent;
resetLongPressState(); resetLongPressState();
}, LONG_PRESS_DURATION_MS); }, LONG_PRESS_DURATION_MS);
}, },
[onDocumentStackSelect, resolveStackDocIds, resetLongPressState], [resolveStackDocIds, resetLongPressState],
); );
useEffect(() => () => resetLongPressState(), [resetLongPressState]); useEffect(() => () => resetLongPressState(), [resetLongPressState]);
@@ -242,25 +242,24 @@ export const useDeskPointer = ({
stackHits, stackHits,
}); });
if (intent.selectedAtDown) { if (intent.selectedAtDown) {
safeInvoke(onPromoteSelection, doc.id, event); safeInvoke(onPromoteSelection, doc.id, event);
} }
applyClickPlanImmediately({ applyClickPlanImmediately({
intent, intent,
event, event,
onEntryPointer: onDocumentClick, onEntryPointer: onDocumentClick,
onDocumentStackSelect, onSelect,
}); });
pointerIntentRef.current = intent; pointerIntentRef.current = intent;
handlePointerDown(event, doc.id, { handlePointerDown(event, doc.id, {
stackDocIds: intent.stackDocIdsForDrag, draggedDocIds: intent.optimisticSelection,
stackSelectionApplied: intent.stackSelectionApplied, stackSelectionApplied: intent.stackSelectionApplied,
wasSelected: intent.selectedAtDown, wasSelected: intent.selectedAtDown,
modifierActive, modifierActive,
stackReplace: intent.stackReplaceOnDrag,
}); });
scheduleLongPress({ scheduleLongPress({
@@ -273,7 +272,7 @@ export const useDeskPointer = ({
handlePointerDown, handlePointerDown,
onPromoteSelection, onPromoteSelection,
onDocumentClick, onDocumentClick,
onDocumentStackSelect, onSelect,
resolveStackDocIds, resolveStackDocIds,
resetLongPressState, resetLongPressState,
scheduleLongPress, scheduleLongPress,
@@ -309,7 +308,7 @@ export const useDeskPointer = ({
intent: pointerState, intent: pointerState,
event, event,
onEntryPointer: onDocumentClick, onEntryPointer: onDocumentClick,
onDocumentStackSelect, onSelect,
}); });
if ( if (
@@ -336,8 +335,8 @@ export const useDeskPointer = ({
[ [
handlePointerUp, handlePointerUp,
onDocumentActivate, onDocumentActivate,
onDocumentStackSelect,
onDocumentClick, onDocumentClick,
onSelect,
resetLongPressState, resetLongPressState,
selectedDocumentIds, selectedDocumentIds,
], ],
+11 -64
View File
@@ -6,7 +6,7 @@ import {
type RefObject, type RefObject,
} from 'react'; } from 'react';
import type { PointerEvent as ReactPointerEvent } from 'react'; import type { PointerEvent as ReactPointerEvent } from 'react';
import { preventAll, safeInvoke } from './events'; import { preventAll } from './events';
import { clamp } from '../utils/math'; import { clamp } from '../utils/math';
import usePointerTap from '../ui/usePointerTap'; import usePointerTap from '../ui/usePointerTap';
import { import {
@@ -83,11 +83,9 @@ interface DragSettings {
} }
interface PointerDownOptions { interface PointerDownOptions {
stackDocIds?: Array<Identifier | null>; draggedDocIds?: string[];
stackSelectionApplied?: boolean; stackSelectionApplied?: boolean;
wasSelected?: boolean;
modifierActive?: boolean; modifierActive?: boolean;
stackReplace?: boolean;
} }
interface UseDocumentDragOptions { interface UseDocumentDragOptions {
@@ -109,12 +107,6 @@ interface UseDocumentDragOptions {
settings?: DragSettings; settings?: DragSettings;
containerRef?: RefObject<HTMLElement>; containerRef?: RefObject<HTMLElement>;
onDocumentActivate?: (docId: Identifier | null, event?: PointerEvent | ReactPointerEvent) => void; onDocumentActivate?: (docId: Identifier | null, event?: PointerEvent | ReactPointerEvent) => void;
onDocumentStackSelect?: (
docIds: Identifier[],
event: PointerEvent | ReactPointerEvent,
options?: { replace?: boolean },
) => void;
selectedDocumentIds?: Array<Identifier | null>;
markLayoutDirty?: () => void; markLayoutDirty?: () => void;
} }
@@ -152,9 +144,7 @@ interface DragStateInternal extends EngineDragState {
activeDocIds: string[]; activeDocIds: string[];
groupItems: DragGroupItemInternal[]; groupItems: DragGroupItemInternal[];
groupElevated: boolean; groupElevated: boolean;
stackDocIds: string[] | null;
stackSelectionApplied: boolean; stackSelectionApplied: boolean;
stackReplace: boolean;
massGrams: number; massGrams: number;
pointerRadiusScale: number; pointerRadiusScale: number;
lastPointerCanvasX: number; lastPointerCanvasX: number;
@@ -209,8 +199,6 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
settings, settings,
containerRef: providedContainerRef, containerRef: providedContainerRef,
onDocumentActivate, onDocumentActivate,
onDocumentStackSelect,
selectedDocumentIds = [],
markLayoutDirty, markLayoutDirty,
} = options; } = options;
@@ -342,50 +330,20 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
return; return;
} }
const massGrams = computeDocumentMassGrams(doc); const massGrams = computeDocumentMassGrams(doc);
const draggedDocIds = options?.draggedDocIds;
const stackDocIdsOptionRaw = options?.stackDocIds; let selectionIds: string[] = [];
const stackDocIdsOption = Array.isArray(stackDocIdsOptionRaw)
? stackDocIdsOptionRaw
.map((value) => (value != null ? String(value) : null))
.filter((value): value is string => Boolean(value))
: null;
const stackSelectionAppliedInitial = Boolean(options?.stackSelectionApplied);
const wasSelectedAtPointerDown = Boolean(options?.wasSelected);
const pointerModifierActive =
options?.modifierActive ?? Boolean(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey);
const stackReplace = Boolean(options?.stackReplace);
let selectionIds: string[] = Array.isArray(selectedDocumentIds) if (Array.isArray(draggedDocIds) && draggedDocIds.length > 0) {
? selectedDocumentIds // Use explicitly provided IDs for the drag operation
.map((id) => (id != null ? String(id) : null)) selectionIds = draggedDocIds;
.filter((id): id is string => Boolean(id)) } else {
: []; // Fallback (should ideally not happen if useDeskPointer is correct)
if (!stackDocIdsOption && !pointerModifierActive && !wasSelectedAtPointerDown) {
selectionIds = [docKey]; selectionIds = [docKey];
} }
if (stackDocIdsOption && stackDocIdsOption.length) {
const selectionSet = new Set(selectionIds);
stackDocIdsOption.forEach((value) => {
if (value != null) {
selectionSet.add(String(value));
}
});
selectionIds = Array.from(selectionSet);
}
const metaOrCtrl = event.metaKey || event.ctrlKey;
if (!stackDocIdsOption && metaOrCtrl && !selectionIds.includes(docKey)) {
selectionIds = [...selectionIds, docKey];
}
selectionIds = selectionIds.filter((id, index, array) => array.indexOf(id) === index && documentLookup.has(id)); selectionIds = selectionIds.filter((id, index, array) => array.indexOf(id) === index && documentLookup.has(id));
if (!selectionIds.includes(docKey)) {
selectionIds.unshift(docKey);
}
if (!selectionIds.length) { if (!selectionIds.length) {
selectionIds = [docKey]; selectionIds = [docKey];
} }
@@ -413,7 +371,7 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
const centerX = Number.isFinite(entry?.centerX) ? entry.centerX : defaultCenterX; const centerX = Number.isFinite(entry?.centerX) ? entry.centerX : defaultCenterX;
const centerY = Number.isFinite(entry?.centerY) ? entry.centerY : defaultCenterY; const centerY = Number.isFinite(entry?.centerY) ? entry.centerY : defaultCenterY;
const modifierPressed = pointerModifierActive; const modifierPressed = Boolean(options?.modifierActive);
if (!modifierPressed) { if (!modifierPressed) {
if (isGroupDrag) { if (isGroupDrag) {
const layout = layoutRef.current; const layout = layoutRef.current;
@@ -498,8 +456,6 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
? performance.now() ? performance.now()
: Date.now(); : Date.now();
const hasStackSource = Array.isArray(stackDocIdsOption) && stackDocIdsOption.length > 1;
dragStateRef.current = { dragStateRef.current = {
docId: docKey, docId: docKey,
docKey, docKey,
@@ -532,9 +488,7 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
activeDocIds: selectionIds, activeDocIds: selectionIds,
groupItems, groupItems,
groupElevated: !isGroupDrag, groupElevated: !isGroupDrag,
stackDocIds: hasStackSource ? stackDocIdsOption : null, stackSelectionApplied: true,
stackSelectionApplied: stackSelectionAppliedInitial || !hasStackSource,
stackReplace,
massGrams, massGrams,
pointerRadiusScale: 1, pointerRadiusScale: 1,
lastPointerCanvasX: pointerCanvasX, lastPointerCanvasX: pointerCanvasX,
@@ -595,7 +549,6 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
ensureDocumentSize, ensureDocumentSize,
layoutRef, layoutRef,
resolveBaseMetrics, resolveBaseMetrics,
selectedDocumentIds,
setDraggingId, setDraggingId,
debugDrag, debugDrag,
itemRefs, itemRefs,
@@ -690,12 +643,7 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
state.moved = true; state.moved = true;
if ( if (
!state.stackSelectionApplied !state.stackSelectionApplied
&& Array.isArray(state.stackDocIds)
&& state.stackDocIds.length > 0
) { ) {
safeInvoke(onDocumentStackSelect, state.stackDocIds as Identifier[], event, {
replace: state.stackReplace,
});
state.stackSelectionApplied = true; state.stackSelectionApplied = true;
} }
if (!state.groupElevated) { if (!state.groupElevated) {
@@ -973,7 +921,6 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
layoutRef, layoutRef,
itemRefs, itemRefs,
debugDrag, debugDrag,
onDocumentStackSelect,
setDragTransform, setDragTransform,
], ],
); );
@@ -1,4 +1,5 @@
import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useCallback, useEffect, useMemo, useRef } from 'react';
import type { Dispatch, SetStateAction } from 'react';
import { createDocumentEntryKey, createFolderEntryKey, isFolderEntry } from '../../app/entryKey'; import { createDocumentEntryKey, createFolderEntryKey, isFolderEntry } from '../../app/entryKey';
import type { DocumentId, FolderId } from '../../types/identifiers'; import type { DocumentId, FolderId } from '../../types/identifiers';
@@ -22,8 +23,8 @@ interface UseDocumentsSelectionOptions {
showingSearchResults?: boolean; showingSearchResults?: boolean;
currentSubfolders?: FolderEntry[]; currentSubfolders?: FolderEntry[];
visibleDocuments?: DocumentEntry[]; visibleDocuments?: DocumentEntry[];
configureSelectionEnvironment: (config: { visibleRowKeySet: Set<string>; navigableRowKeys: string[] }) => void; configureSelectionEnvironment: (config: { visibleEntryKeySet: Set<string>; navigableEntryKeys: string[] }) => void;
visibleRowKeySet: Set<string>; visibleEntryKeySet: Set<string>;
selectedEntries: string[]; selectedEntries: string[];
selectionAnchorRef: { current: string | null }; selectionAnchorRef: { current: string | null };
promoteSelectionOrderRaw: (id: DocumentId) => void; promoteSelectionOrderRaw: (id: DocumentId) => void;
@@ -31,8 +32,8 @@ interface UseDocumentsSelectionOptions {
setActivePreviewId: (id: DocumentId | null) => void; setActivePreviewId: (id: DocumentId | null) => void;
clearSelection: () => void; clearSelection: () => void;
focusedDocumentId: DocumentId | null; focusedDocumentId: DocumentId | null;
setFocusedRowKey: (value: string | null | ((current: string | null) => string | null)) => void; setFocusedEntryKey: Dispatch<SetStateAction<string | null>>;
focusedRowKey: string | null; focusedEntryKey: string | null;
} }
const useDocumentsSelection = ({ const useDocumentsSelection = ({
@@ -40,7 +41,7 @@ const useDocumentsSelection = ({
currentSubfolders = [], currentSubfolders = [],
visibleDocuments = [], visibleDocuments = [],
configureSelectionEnvironment, configureSelectionEnvironment,
visibleRowKeySet, visibleEntryKeySet,
selectedEntries, selectedEntries,
selectionAnchorRef, selectionAnchorRef,
promoteSelectionOrderRaw, promoteSelectionOrderRaw,
@@ -48,8 +49,8 @@ const useDocumentsSelection = ({
setActivePreviewId, setActivePreviewId,
clearSelection, clearSelection,
focusedDocumentId, focusedDocumentId,
setFocusedRowKey, setFocusedEntryKey,
focusedRowKey, focusedEntryKey,
}: UseDocumentsSelectionOptions) => { }: UseDocumentsSelectionOptions) => {
const navigableRows = useMemo<NavigableRow[]>(() => { const navigableRows = useMemo<NavigableRow[]>(() => {
const entries: NavigableRow[] = []; const entries: NavigableRow[] = [];
@@ -77,10 +78,10 @@ const useDocumentsSelection = ({
useEffect(() => { useEffect(() => {
configureSelectionEnvironment({ configureSelectionEnvironment({
visibleRowKeySet, visibleEntryKeySet,
navigableRowKeys, navigableEntryKeys: navigableRowKeys,
}); });
}, [configureSelectionEnvironment, visibleRowKeySet, navigableRowKeys]); }, [configureSelectionEnvironment, visibleEntryKeySet, navigableRowKeys]);
const promoteSelectionOrder = useCallback( const promoteSelectionOrder = useCallback(
(docId: DocumentId | null) => { (docId: DocumentId | null) => {
@@ -108,40 +109,40 @@ const useDocumentsSelection = ({
} }
prevFocusedDocIdRef.current = focusedDocumentId; prevFocusedDocIdRef.current = focusedDocumentId;
if (focusedDocumentId) { if (focusedDocumentId) {
setFocusedRowKey(createDocumentEntryKey(focusedDocumentId)); setFocusedEntryKey(createDocumentEntryKey(focusedDocumentId));
} else { } else {
setFocusedRowKey((current) => (current && isFolderEntry(current) ? current : null)); setFocusedEntryKey((current) => (current && isFolderEntry(current) ? current : null));
} }
}, [focusedDocumentId, setFocusedRowKey]); }, [focusedDocumentId, setFocusedEntryKey]);
useEffect(() => { useEffect(() => {
if (!navigableRowKeys.length) { if (!navigableRowKeys.length) {
if (focusedRowKey) { if (focusedEntryKey) {
setFocusedRowKey(null); setFocusedEntryKey(null);
} }
return; return;
} }
if (focusedRowKey && navigableRowKeys.includes(focusedRowKey)) { if (focusedEntryKey && navigableRowKeys.includes(focusedEntryKey)) {
return; return;
} }
const docKey = focusedDocumentId ? createDocumentEntryKey(focusedDocumentId) : null; const docKey = focusedDocumentId ? createDocumentEntryKey(focusedDocumentId) : null;
if (docKey && navigableRowKeys.includes(docKey)) { if (docKey && navigableRowKeys.includes(docKey)) {
setFocusedRowKey(docKey); setFocusedEntryKey(docKey);
return; return;
} }
const selectedKey = selectedEntries.find((key) => navigableRowKeys.includes(key)); const selectedKey = selectedEntries.find((key) => navigableRowKeys.includes(key));
if (selectedKey) { if (selectedKey) {
setFocusedRowKey(selectedKey); setFocusedEntryKey(selectedKey);
return; return;
} }
if (focusedRowKey) { if (focusedEntryKey) {
setFocusedRowKey(null); setFocusedEntryKey(null);
} }
}, [focusedRowKey, focusedDocumentId, navigableRowKeys, selectedEntries, setFocusedRowKey]); }, [focusedEntryKey, focusedDocumentId, navigableRowKeys, selectedEntries, setFocusedEntryKey]);
return { return {
navigableRows, navigableRows,
+39 -39
View File
@@ -142,8 +142,8 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
}): ReactNode => { }): ReactNode => {
const { const {
selectedEntries, selectedEntries,
focusedRowKey, focusedEntryKey,
setFocusedRowKey, setFocusedEntryKey,
handleEntrySelection, handleEntrySelection,
clearSelection, clearSelection,
selectionAnchorRef, selectionAnchorRef,
@@ -177,28 +177,28 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
return; return;
} }
const rowKeys = docIds const entryKeys = docIds
.map((id) => createDocumentEntryKey(id as Identifier)) .map((id) => createDocumentEntryKey(id as Identifier))
.filter((value): value is string => typeof value === 'string'); .filter((value): value is string => typeof value === 'string');
if (!rowKeys.length) { if (!entryKeys.length) {
return; return;
} }
const nextKeys = [...selectedEntries]; const nextKeys = [...selectedEntries];
rowKeys.forEach((key) => { entryKeys.forEach((key) => {
if (!nextKeys.includes(key)) { if (!nextKeys.includes(key)) {
nextKeys.push(key); nextKeys.push(key);
} }
}); });
const anchor = (rowKeys[0] const anchor = (entryKeys[0]
|| selectionAnchorRef.current || selectionAnchorRef.current
|| nextKeys[nextKeys.length - 1]) as string | null; || nextKeys[nextKeys.length - 1]) as string | null;
applySelection(nextKeys, { applySelection(nextKeys, {
anchor, anchor,
interactedKeys: rowKeys, interactedKeys: entryKeys,
}); });
}, },
[applySelection, selectedEntries, selectionAnchorRef], [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.map((entry) => ({ key: entry.key, type: entry.type, id: entry.id })),
[entries], [entries],
); );
const navigableRowKeys = useMemo(() => navigableRows.map((row) => row.key), [navigableRows]); const navigableEntryKeys = useMemo(() => navigableRows.map((row) => row.key), [navigableRows]);
const getEntryByKey = useCallback( const getEntryByKey = useCallback(
(rowKey) => entries.find((entry) => entry.key === rowKey) || null, (entryKey) => entries.find((entry) => entry.key === entryKey) || null,
[entries], [entries],
); );
const handlePanelFocus = useCallback(() => { const handlePanelFocus = useCallback(() => {
let resolvedKey = null; let resolvedKey = null;
if (focusedRowKey && navigableRowKeys.includes(focusedRowKey)) { if (focusedEntryKey && navigableEntryKeys.includes(focusedEntryKey)) {
resolvedKey = focusedRowKey; resolvedKey = focusedEntryKey;
} }
if (!resolvedKey) { if (!resolvedKey) {
for (let index = selectedEntries.length - 1; index >= 0; index -= 1) { for (let index = selectedEntries.length - 1; index >= 0; index -= 1) {
const candidate = selectedEntries[index]; const candidate = selectedEntries[index];
if (navigableRowKeys.includes(candidate)) { if (navigableEntryKeys.includes(candidate)) {
resolvedKey = candidate; resolvedKey = candidate;
break; break;
} }
@@ -519,12 +519,12 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
return; return;
} }
setFocusedRowKey(resolvedKey); setFocusedEntryKey(resolvedKey);
}, [ }, [
focusedRowKey, focusedEntryKey,
navigableRowKeys, navigableEntryKeys,
navigableRows, navigableRows,
setFocusedRowKey, setFocusedEntryKey,
selectedEntries, selectedEntries,
]); ]);
@@ -543,15 +543,15 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
event.preventDefault(); event.preventDefault();
let activeKey = let activeKey =
focusedRowKey && navigableRowKeys.includes(focusedRowKey) focusedEntryKey && navigableEntryKeys.includes(focusedEntryKey)
? focusedRowKey ? focusedEntryKey
: null; : null;
if (!activeKey) { if (!activeKey) {
if (selectedEntries.length) { if (selectedEntries.length) {
for (let index = selectedEntries.length - 1; index >= 0; index -= 1) { for (let index = selectedEntries.length - 1; index >= 0; index -= 1) {
const candidate = selectedEntries[index]; const candidate = selectedEntries[index];
if (navigableRowKeys.includes(candidate)) { if (navigableEntryKeys.includes(candidate)) {
activeKey = candidate; activeKey = candidate;
break; break;
} }
@@ -559,11 +559,11 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
} }
if (!activeKey) { 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]; const activeRow = currentIndex === -1 ? null : navigableRows[currentIndex];
if (key === 'Enter' || key === ' ' || key === 'Space' || key === 'Spacebar') { if (key === 'Enter' || key === ' ' || key === 'Space' || key === 'Spacebar') {
@@ -601,22 +601,22 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
return; return;
} }
setFocusedRowKey(targetRow.key); setFocusedEntryKey(targetRow.key);
handleEntrySelection(targetRow.key, { handleEntrySelection(targetRow.key, {
shiftKey, shiftKey,
preventDefault: () => { }, preventDefault: () => { },
}); });
}, },
[ [
focusedRowKey, focusedEntryKey,
getEntryByKey, getEntryByKey,
navigableRowKeys, navigableEntryKeys,
navigableRows, navigableRows,
onFolderSelect, onFolderSelect,
selectedEntries, selectedEntries,
handleDocumentPreviewZoom, handleDocumentPreviewZoom,
handleEntrySelection, handleEntrySelection,
setFocusedRowKey, setFocusedEntryKey,
], ],
); );
@@ -648,14 +648,14 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
const isTagDragEvent = useCallback((event) => isTagTransferEvent(event), []); const isTagDragEvent = useCallback((event) => isTagTransferEvent(event), []);
const ensureFocusedRowVisible = useCallback(() => { const ensureFocusedRowVisible = useCallback(() => {
if (!focusedRowKey) return; if (!focusedEntryKey) return;
const container = scrollRef.current; const container = scrollRef.current;
if (!container) return; if (!container) return;
let selector = null; let selector = null;
if (focusedRowKey.startsWith('document:')) { if (focusedEntryKey.startsWith('document:')) {
selector = `#document-row-${focusedRowKey.slice('document:'.length)}`; selector = `#document-row-${focusedEntryKey.slice('document:'.length)}`;
} else if (focusedRowKey.startsWith('folder:')) { } else if (focusedEntryKey.startsWith('folder:')) {
selector = `#folder-row-${focusedRowKey.slice('folder:'.length)}`; selector = `#folder-row-${focusedEntryKey.slice('folder:'.length)}`;
} }
if (!selector) { if (!selector) {
return; return;
@@ -681,22 +681,22 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
const nextScrollTop = rowBottom - container.clientHeight; const nextScrollTop = rowBottom - container.clientHeight;
container.scrollTop = Math.max(nextScrollTop, 0); container.scrollTop = Math.max(nextScrollTop, 0);
} }
}, [focusedRowKey]); }, [focusedEntryKey]);
useEffect(() => { useEffect(() => {
ensureFocusedRowVisible(); ensureFocusedRowVisible();
}, [ensureFocusedRowVisible]); }, [ensureFocusedRowVisible]);
const activeDescendantId = useMemo(() => { const activeDescendantId = useMemo(() => {
if (!focusedRowKey) return undefined; if (!focusedEntryKey) return undefined;
if (focusedRowKey.startsWith('document:')) { if (focusedEntryKey.startsWith('document:')) {
return `document-row-${focusedRowKey.slice('document:'.length)}`; return `document-row-${focusedEntryKey.slice('document:'.length)}`;
} }
if (focusedRowKey.startsWith('folder:')) { if (focusedEntryKey.startsWith('folder:')) {
return `folder-row-${focusedRowKey.slice('folder:'.length)}`; return `folder-row-${focusedEntryKey.slice('folder:'.length)}`;
} }
return undefined; return undefined;
}, [focusedRowKey]); }, [focusedEntryKey]);
const handleDocumentTagDragOver = useCallback( const handleDocumentTagDragOver = useCallback(
(event) => { (event) => {
@@ -756,10 +756,10 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
&& scrollRef.current && scrollRef.current
) { ) {
scrollRef.current.focus({ preventScroll: true }); scrollRef.current.focus({ preventScroll: true });
setFocusedRowKey(`folder:${folder.id}`); setFocusedEntryKey(`folder:${folder.id}`);
} }
}, },
[onEntryPointer, setFocusedRowKey], [onEntryPointer, setFocusedEntryKey],
); );
const handleDocumentDragStartLocal = useCallback( const handleDocumentDragStartLocal = useCallback(
@@ -123,8 +123,8 @@ interface UseDocumentMutationsArgs {
selectionAnchorRef: MutableRefObject<string | null>; selectionAnchorRef: MutableRefObject<string | null>;
setFocusedDocumentId: Dispatch<SetStateAction<DocumentId | null>>; setFocusedDocumentId: Dispatch<SetStateAction<DocumentId | null>>;
focusedDocumentId: DocumentId | null; focusedDocumentId: DocumentId | null;
setFocusedRowKey: Dispatch<SetStateAction<string | null>>; setFocusedEntryKey: Dispatch<SetStateAction<string | null>>;
focusedRowKey: string | null; focusedEntryKey: string | null;
notifyApiError: NotifyApiError; notifyApiError: NotifyApiError;
setStatusMessage: SetStatusMessage; setStatusMessage: SetStatusMessage;
mapDocumentCaches: MapDocumentCaches; mapDocumentCaches: MapDocumentCaches;
@@ -197,8 +197,8 @@ const useDocumentMutations = ({
selectionAnchorRef, selectionAnchorRef,
setFocusedDocumentId, setFocusedDocumentId,
focusedDocumentId, focusedDocumentId,
setFocusedRowKey, setFocusedEntryKey,
focusedRowKey, focusedEntryKey,
notifyApiError, notifyApiError,
setStatusMessage, setStatusMessage,
mapDocumentCaches, mapDocumentCaches,
@@ -357,11 +357,11 @@ const useDocumentMutations = ({
setFocusedDocumentId(null); setFocusedDocumentId(null);
} }
if ( if (
focusedRowKey && focusedEntryKey &&
isDocumentEntry(focusedRowKey) && isDocumentEntry(focusedEntryKey) &&
uniqueIdSet.has(getEntryId(focusedRowKey) as DocumentId) uniqueIdSet.has(getEntryId(focusedEntryKey) as DocumentId)
) { ) {
setFocusedRowKey(null); setFocusedEntryKey(null);
} }
} }
@@ -387,8 +387,8 @@ const useDocumentMutations = ({
selectionAnchorRef, selectionAnchorRef,
setFocusedDocumentId, setFocusedDocumentId,
focusedDocumentId, focusedDocumentId,
setFocusedRowKey, setFocusedEntryKey,
focusedRowKey, focusedEntryKey,
notifyApiError, notifyApiError,
setStatusMessage, setStatusMessage,
mapDocumentCaches, mapDocumentCaches,
@@ -259,8 +259,8 @@ const useDocumentsWorkspace = ({
selectionInitializedRef, selectionInitializedRef,
focusedDocumentId, focusedDocumentId,
setFocusedDocumentId, setFocusedDocumentId,
focusedRowKey, focusedEntryKey,
setFocusedRowKey, setFocusedEntryKey,
applySelection, applySelection,
handleEntrySelection, handleEntrySelection,
clearSelection, clearSelection,
@@ -416,14 +416,14 @@ const useDocumentsWorkspace = ({
[showingSearchResults, currentSubfolders], [showingSearchResults, currentSubfolders],
); );
const visibleRowKeys = useMemo( const visibleEntryKeys = useMemo(
() => [...visibleFolderKeys, ...visibleDocumentKeys], () => [...visibleFolderKeys, ...visibleDocumentKeys],
[visibleFolderKeys, visibleDocumentKeys], [visibleFolderKeys, visibleDocumentKeys],
); );
const visibleRowKeySet = useMemo( const visibleEntryKeySet = useMemo(
() => new Set(visibleRowKeys), () => new Set(visibleEntryKeys),
[visibleRowKeys], [visibleEntryKeys],
); );
const { const {
@@ -750,8 +750,8 @@ const useDocumentsWorkspace = ({
selectionAnchorRef, selectionAnchorRef,
setFocusedDocumentId, setFocusedDocumentId,
focusedDocumentId, focusedDocumentId,
setFocusedRowKey, setFocusedEntryKey,
focusedRowKey, focusedEntryKey,
notifyApiError, notifyApiError,
setStatusMessage, setStatusMessage,
mapDocumentCaches, mapDocumentCaches,
@@ -812,7 +812,7 @@ const useDocumentsWorkspace = ({
currentSubfolders, currentSubfolders,
visibleDocuments: viewDocuments, visibleDocuments: viewDocuments,
configureSelectionEnvironment, configureSelectionEnvironment,
visibleRowKeySet, visibleEntryKeySet,
selectedEntries, selectedEntries,
selectionAnchorRef, selectionAnchorRef,
promoteSelectionOrderRaw, promoteSelectionOrderRaw,
@@ -820,8 +820,8 @@ const useDocumentsWorkspace = ({
setActivePreviewId, setActivePreviewId,
clearSelection, clearSelection,
focusedDocumentId, focusedDocumentId,
setFocusedRowKey, setFocusedEntryKey,
focusedRowKey, focusedEntryKey,
}); });
const initializeAfterLogin = useCallback(async () => { const initializeAfterLogin = useCallback(async () => {
await Promise.all([refreshTags(), refreshCorrespondents()]); await Promise.all([refreshTags(), refreshCorrespondents()]);