import { NodeApi } from "../interfaces/node-api"; import { TreeApi } from "../interfaces/tree-api"; export type ListResult = { /* The flattened, currently-visible rows. */ list: NodeApi[]; /* How many nodes match the search term across the whole tree (0 when not filtered). Counted here so consumers reading tree.filteredCount don't trigger a second full traversal. */ matchCount: number; }; export function createList(tree: TreeApi): ListResult { if (tree.isFiltered) { return flattenAndFilterTree(tree.root, tree.isMatch.bind(tree)); } else { return { list: flattenTree(tree.root), matchCount: 0 }; } } function flattenTree(root: NodeApi): NodeApi[] { const list: NodeApi[] = []; function collect(node: NodeApi) { if (node.level >= 0) { list.push(node); } if (node.isOpen) { node.children?.forEach(collect); } } collect(root); list.forEach(assignRowIndex); return list; } function flattenAndFilterTree( root: NodeApi, isMatch: (n: NodeApi) => boolean, ): ListResult { const matches: Record = {}; const list: NodeApi[] = []; let matchCount = 0; function markMatch(node: NodeApi) { const yes = !node.isRoot && isMatch(node); if (yes) { matchCount++; matches[node.id] = true; let parent = node.parent; while (parent) { matches[parent.id] = true; parent = parent.parent; } } if (node.children) { for (let child of node.children) markMatch(child); } } function collect(node: NodeApi) { if (node.level >= 0 && matches[node.id]) { list.push(node); } if (node.isOpen) { node.children?.forEach(collect); } } markMatch(root); collect(root); list.forEach(assignRowIndex); return { list, matchCount }; } function assignRowIndex(node: NodeApi, index: number) { node.rowIndex = index; }