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
+51 -14
View File
@@ -42,6 +42,7 @@ export interface PointerIntent {
clickSelectionApplied: boolean;
stackSelectionApplied: boolean;
longPressTriggered: boolean;
optimisticSelection: string[];
}
export const createPointerIntent = ({
@@ -61,9 +62,9 @@ export const createPointerIntent = ({
if (metaKey) {
clickAction = CLICK_ACTIONS.addStack;
dragAction = DRAG_ACTIONS.dragSelectStack;
dragAction = DRAG_ACTIONS.dragSelection;
} else if (alreadySelected) {
clickAction = CLICK_ACTIONS.selectSingle;
clickAction = CLICK_ACTIONS.openDetail;
dragAction = selectionCount > 1 ? DRAG_ACTIONS.dragSelection : DRAG_ACTIONS.dragSelectSingle;
} else {
clickAction = CLICK_ACTIONS.selectSingle;
@@ -74,8 +75,23 @@ export const createPointerIntent = ({
? stackHits.map((value) => String(value))
: [String(doc.id)];
const stackDocIdsForDrag = dragAction === DRAG_ACTIONS.dragSelectStack ? 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 {
docId: doc.id,
@@ -90,33 +106,53 @@ export const createPointerIntent = ({
stackDocIdsForDrag,
stackDocIdsForClick,
stackReplaceOnClick: clickAction === CLICK_ACTIONS.addStack,
stackReplaceOnDrag: dragAction === DRAG_ACTIONS.dragSelectStack,
stackReplaceOnDrag: false,
clickSelectionApplied: false,
stackSelectionApplied: false,
longPressTriggered: false,
optimisticSelection,
};
};
export const applyClickPlanImmediately = ({ intent, event, onEntryPointer, onDocumentStackSelect }: {
export const applyClickPlanImmediately = ({ intent, event, onEntryPointer, onDocumentStackSelect, onSelect }: {
intent: PointerIntent;
event?: unknown;
onEntryPointer?: (descriptor: unknown, event?: unknown) => void;
onDocumentStackSelect?: (docIds: string[], event?: unknown, options?: { replace?: boolean }) => void;
onSelect?: (descriptor: unknown, event?: unknown) => void;
}) => {
switch (intent.clickAction) {
case CLICK_ACTIONS.selectSingle:
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;
break;
case CLICK_ACTIONS.addStack:
if (Array.isArray(intent.stackDocIdsForClick) && intent.stackDocIdsForClick.length > 0) {
safeInvoke(
onDocumentStackSelect,
intent.stackDocIdsForClick,
event,
{ replace: intent.stackReplaceOnClick },
);
// Use onSelect for stack selection (batch)
if (onSelect) {
// Map docIds to descriptors if necessary, or just pass IDs if onSelect handles it.
// The current onSelect adapter in DesktopWorkspace expects { id } objects or just IDs?
// 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.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;
event?: unknown;
onEntryPointer?: (descriptor: unknown, event?: unknown) => void;
onDocumentStackSelect?: (docIds: string[], event?: unknown, options?: { replace?: boolean }) => void;
onSelect?: (descriptor: unknown, event?: unknown) => void;
}) => {
if (!intent || intent.clickSelectionApplied) {
return;
}
applyClickPlanImmediately({ intent, event, onEntryPointer, onDocumentStackSelect });
applyClickPlanImmediately({ intent, event, onEntryPointer, onDocumentStackSelect, onSelect });
};
export const applyLongPressSelection = ({ intent, stackDocIds, syntheticEvent, onDocumentStackSelect }: {