import { create, type StoreApi, type UseBoundStore } from "zustand"; import type { SelectionPropagation, TreeNode } from "../types/treeView.types"; import type { DropPosition } from "../types/dragDrop.types"; export type TreeViewState = { // Store ids of checked tree nodes checked: Set; updateChecked: (checked: Set) => void; // Store ids of indeterminate state nodes indeterminate: Set; updateIndeterminate: (indeterminate: Set) => void; // Store ids of expanded parent nodes expanded: Set; updateExpanded: (expanded: Set) => void; // Store initial tree view data exactly as passed by the consumer initialTreeViewData: TreeNode[]; updateInitialTreeViewData: (initialTreeViewData: TreeNode[]) => void; // Map to store the id to the tree node map nodeMap: Map>; updateNodeMap: (nodeMap: Map>) => void; // Map to store child id to parent id map childToParentMap: Map; updateChildToParentMap: (childToParentMap: Map) => void; // Search text state searchText: string; updateSearchText: (searchText: string) => void; // Search keys state searchKeys: string[]; updateSearchKeys: (searchKeys: string[]) => void; // To store inner most children ids - required to un/select all filtered-only nodes innerMostChildrenIds: ID[]; updateInnerMostChildrenIds: (innerMostChildrenIds: ID[]) => void; selectionPropagation: SelectionPropagation; setSelectionPropagation: ( selectionPropagation: SelectionPropagation ) => void; // Drag-and-drop state draggedNodeId: ID | null; updateDraggedNodeId: (draggedNodeId: ID | null) => void; invalidDragTargetIds: Set; updateInvalidDragTargetIds: (invalidDragTargetIds: Set) => void; // Drop target state (used by nodes to render their own indicator) dropTargetNodeId: ID | null; dropPosition: DropPosition | null; dropLevel: number | null; updateDropTarget: (nodeId: ID | null, position: DropPosition | null, level?: number | null) => void; // Cleanup all states in this store cleanUpTreeViewStore: () => void; }; // Map to store individual tree view stores by id const treeViewStores = new Map>>>(); // a function that returns a strongly typed version of `treeViewStores` const typedStore: () => Map>>> = () => treeViewStores as Map>>>; export function getTreeViewStore(id: string): UseBoundStore>> { if (!typedStore().has(id)) { const store = create>((set) => ({ checked: new Set(), updateChecked: (checked: Set) => set({ checked }), indeterminate: new Set(), updateIndeterminate: (indeterminate: Set) => set({ indeterminate }), expanded: new Set(), updateExpanded: (expanded: Set) => set({ expanded }), initialTreeViewData: [], updateInitialTreeViewData: (initialTreeViewData: TreeNode[]) => set({ initialTreeViewData }), nodeMap: new Map>(), updateNodeMap: (nodeMap: Map>) => set({ nodeMap }), childToParentMap: new Map(), updateChildToParentMap: (childToParentMap: Map) => set({ childToParentMap }), searchText: "", updateSearchText: (searchText: string) => set({ searchText }), searchKeys: [""], updateSearchKeys: (searchKeys: string[]) => set({ searchKeys }), innerMostChildrenIds: [], updateInnerMostChildrenIds: (innerMostChildrenIds: ID[]) => set({ innerMostChildrenIds }), selectionPropagation: { toChildren: true, toParents: true }, setSelectionPropagation: (selectionPropagation) => set({ selectionPropagation: { // Default selection propagation for parent and children to true if not specified toChildren: selectionPropagation.toChildren ?? true, toParents: selectionPropagation.toParents ?? true } }), draggedNodeId: null, updateDraggedNodeId: (draggedNodeId) => set({ draggedNodeId }), invalidDragTargetIds: new Set(), updateInvalidDragTargetIds: (invalidDragTargetIds) => set({ invalidDragTargetIds }), dropTargetNodeId: null, dropPosition: null, dropLevel: null, updateDropTarget: (nodeId, position, level) => set({ dropTargetNodeId: nodeId, dropPosition: position, dropLevel: level ?? null, }), cleanUpTreeViewStore: () => set({ checked: new Set(), indeterminate: new Set(), expanded: new Set(), initialTreeViewData: [], nodeMap: new Map>(), childToParentMap: new Map(), searchText: "", searchKeys: [""], innerMostChildrenIds: [], selectionPropagation: { toChildren: true, toParents: true }, draggedNodeId: null, invalidDragTargetIds: new Set(), dropTargetNodeId: null, dropPosition: null, dropLevel: null, }), })); typedStore().set(id, store); } return typedStore().get(id)!; } export function deleteTreeViewStore(id: string) { treeViewStores.delete(id); } /** * Returns the per-instance bound Zustand store for the given id. The returned * store is itself callable as a hook with a selector - e.g. * `useTreeViewStore(id)(useShallow(state => ...))` - which is why it keeps the * `use` prefix. Internal helper; not part of the public API. */ export function useTreeViewStore(id: string) { return getTreeViewStore(id); }