import { jotaiStore } from '../../../lib/jotai'; import { collapsedFamily, selectedIdsFamily } from './atoms'; export interface TreeNode { children?: TreeNode[]; item: T; hidden?: boolean; parent: TreeNode | null; depth: number; draggable?: boolean; localDrag?: boolean; } export interface SelectableTreeNode { node: TreeNode; depth: number; index: number; } export function getSelectedItems( treeId: string, selectableItems: SelectableTreeNode[], ) { const selectedItemIds = jotaiStore.get(selectedIdsFamily(treeId)); return selectableItems .filter((i) => selectedItemIds.includes(i.node.item.id)) .map((i) => i.node.item); } export function equalSubtree( a: TreeNode, b: TreeNode, getItemKey: (t: T) => string, ): boolean { if (getNodeKey(a, getItemKey) !== getNodeKey(b, getItemKey)) return false; const ak = a.children ?? []; const bk = b.children ?? []; if (ak.length !== bk.length) { return false; } for (let i = 0; i < ak.length; i++) { // biome-ignore lint/style/noNonNullAssertion: none if (!equalSubtree(ak[i]!, bk[i]!, getItemKey)) return false; } return true; } export function getNodeKey(a: TreeNode, getItemKey: (i: T) => string) { return getItemKey(a.item) + a.hidden; } export function hasAncestor(node: TreeNode, ancestorId: string) { if (node.parent == null) return false; if (node.parent.item.id === ancestorId) return true; // Check parents recursively return hasAncestor(node.parent, ancestorId); } export function isVisibleNode(treeId: string, node: TreeNode) { const collapsed = jotaiStore.get(collapsedFamily(treeId)); let p = node.parent; while (p) { if (collapsed[p.item.id]) return false; // any collapsed ancestor hides this node p = p.parent; } return true; } export function closestVisibleNode( treeId: string, node: TreeNode, ): TreeNode | null { let n: TreeNode | null = node; while (n) { if (isVisibleNode(treeId, n) && !n.hidden) return n; if (n.parent == null) return null; n = n.parent; } return null; }