import type { BoxSelectionChange, SelectionState, } from "@/selection/types"; function dedupeIds({ ids }: { ids: string[] }) { return [...new Set(ids)]; } function getRangeIds({ orderedIds, anchorId, targetId, }: { orderedIds: string[]; anchorId: string; targetId: string; }) { const anchorIndex = orderedIds.indexOf(anchorId); const targetIndex = orderedIds.indexOf(targetId); if (anchorIndex === -1 || targetIndex === -1) { return [targetId]; } const rangeStart = Math.min(anchorIndex, targetIndex); const rangeEnd = Math.max(anchorIndex, targetIndex); return orderedIds.slice(rangeStart, rangeEnd + 1); } export function replaceSelection({ ids, anchorId, }: { ids: string[]; anchorId?: string | null; }): SelectionState { const selectedIds = dedupeIds({ ids }); return { selectedIds, anchorId: anchorId ?? selectedIds[selectedIds.length - 1] ?? null, }; } export function clearSelection(): SelectionState { return { selectedIds: [], anchorId: null, }; } export function pruneSelection({ state, orderedIds, }: { state: SelectionState; orderedIds: string[]; }): SelectionState { const validIds = new Set(orderedIds); const selectedIds = state.selectedIds.filter((id) => validIds.has(id)); const anchorId = state.anchorId && validIds.has(state.anchorId) ? state.anchorId : selectedIds[selectedIds.length - 1] ?? null; const isUnchanged = selectedIds.length === state.selectedIds.length && anchorId === state.anchorId; if (isUnchanged) { return state; } return { selectedIds, anchorId, }; } export function isSelected({ state, id, }: { state: SelectionState; id: string; }) { return state.selectedIds.includes(id); } export function toggleSelection({ state, id, }: { state: SelectionState; id: string; }): SelectionState { if (isSelected({ state, id })) { const selectedIds = state.selectedIds.filter((selectedId) => selectedId !== id); const anchorId = state.anchorId === id ? selectedIds[selectedIds.length - 1] ?? null : state.anchorId; return replaceSelection({ ids: selectedIds, anchorId, }); } return replaceSelection({ ids: [...state.selectedIds, id], anchorId: id, }); } export function selectRange({ state, orderedIds, targetId, isAdditive, }: { state: SelectionState; orderedIds: string[]; targetId: string; isAdditive: boolean; }): SelectionState { const anchorId = state.anchorId ?? state.selectedIds[state.selectedIds.length - 1] ?? targetId; const rangeIds = getRangeIds({ orderedIds, anchorId, targetId, }); const selectedIds = isAdditive ? dedupeIds({ ids: [...state.selectedIds, ...rangeIds], }) : rangeIds; return replaceSelection({ ids: selectedIds, anchorId, }); } export function applyBoxSelection({ intersectedIds, initialSelectedIds, initialAnchorId, isAdditive, }: BoxSelectionChange): SelectionState { const selectedIds = isAdditive ? dedupeIds({ ids: [...initialSelectedIds, ...intersectedIds], }) : intersectedIds; const anchorId = isAdditive ? initialAnchorId ?? intersectedIds[intersectedIds.length - 1] ?? null : intersectedIds[intersectedIds.length - 1] ?? null; return replaceSelection({ ids: selectedIds, anchorId, }); }