/** * Connection multi-selection state for the sidebar tree. * * The transitions live here rather than in the components because the checkbox * that grows the selection and the bulk actions that release it sit in two * different files, and only their combination decides what the next bulk action * operates on. */ export type ConnectionMultiSelection = { connectionIds: string[]; activeConnectionId: string | null; anchorConnectionId: string | null; active: boolean; }; /** The tree selection fields a multi-selection is written back to. */ export type ConnectionMultiSelectionTarget = { selectedTreeNodeIds: string[]; selectedTreeNodeId: string | null; treeSelectionAnchorId: string | null; connectionMultiSelectActive: boolean; }; export function emptyConnectionMultiSelection(): ConnectionMultiSelection { return { connectionIds: [], activeConnectionId: null, anchorConnectionId: null, active: false }; } /** * Ticking a checkbox adds to the current selection while multi-select is on, and * starts a new one when it is off. A bulk action that forgets to release the * selection therefore hands its connections to the next one (issue #5758). */ export function connectionMultiSelectionAfterToggle(selection: Pick, connectionId: string): ConnectionMultiSelection { const next = new Set(selection.active ? selection.connectionIds : []); if (next.has(connectionId)) next.delete(connectionId); else next.add(connectionId); const connectionIds = [...next]; return { connectionIds, activeConnectionId: next.has(connectionId) ? connectionId : (connectionIds[0] ?? null), anchorConnectionId: connectionId, active: connectionIds.length > 0, }; } export function applyConnectionMultiSelection(target: ConnectionMultiSelectionTarget, selection: ConnectionMultiSelection): void { target.selectedTreeNodeIds = selection.connectionIds; target.selectedTreeNodeId = selection.activeConnectionId; target.treeSelectionAnchorId = selection.anchorConnectionId; target.connectionMultiSelectActive = selection.active; } export function releaseConnectionFromMultiSelection(target: ConnectionMultiSelectionTarget, connectionId: string): void { if (!target.connectionMultiSelectActive || !target.selectedTreeNodeIds.includes(connectionId)) return; const connectionIds = target.selectedTreeNodeIds.filter((id) => id !== connectionId); const activeConnectionId = target.selectedTreeNodeId && connectionIds.includes(target.selectedTreeNodeId) ? target.selectedTreeNodeId : (connectionIds[0] ?? null); const anchorConnectionId = target.treeSelectionAnchorId && connectionIds.includes(target.treeSelectionAnchorId) ? target.treeSelectionAnchorId : activeConnectionId; applyConnectionMultiSelection(target, { connectionIds, activeConnectionId, anchorConnectionId, active: connectionIds.length > 0, }); }