UNPKG

@1771technologies/lytenyte-pro

Version:

Blazingly fast headless React data grid with 100s of features.

153 lines (152 loc) 6.96 kB
import { useMemo, useRef } from "react"; import { collapse } from "./collapse.js"; import { collapseLast } from "./collapse-last.js"; import { evaluateLabelFilter } from "../use-pivot/auxiliary-functions/evaluate-label-filter.js"; import { getValidLeafs } from "../get-valid-leafs.js"; export function useGroupTree(leafs, workingSet, group, groupIdFn, rowGroupCollapseBehavior, labelFilter, having, agg) { const groupNodeCacheRef = useRef(new Map()); const prevCollapse = useRef(rowGroupCollapseBehavior); if (prevCollapse.current !== rowGroupCollapseBehavior) { groupNodeCacheRef.current = new Map(); prevCollapse.current = rowGroupCollapseBehavior; } return useMemo(() => { const groupIdToGroupNode = new Map(); if (!group) return null; const root = { kind: "root", children: new Map(), groupLookup: groupIdToGroupNode, maxDepth: 0, }; for (let i = 0; i < workingSet.length; i++) { const n = leafs[workingSet[i]]; const paths = group(n); // Skip this row if its path is filtered out. if (!evaluateLabelFilter(labelFilter, paths)) continue; let current = root.children; let currentGroup = root; // This has been marked a non-terminal node if (!paths?.length) { current.set(current.size, { kind: "leaf", row: n, parent: root, key: current.size }); continue; } root.maxDepth = Math.max(paths.length, root.maxDepth); for (let j = 0; j < paths.length; j++) { const p = paths[j]; if (!current.has(p)) { const partial = paths.slice(0, j + 1); const groupId = groupIdFn(partial); const children = new Map(); const isLast = j === paths.length - 1; if (!groupNodeCacheRef.current.get(groupId)) groupNodeCacheRef.current.set(groupId, { __children: children, __invalidate: true, last: isLast, kind: "branch", id: groupId, data: null, depth: j, key: p, parentId: currentGroup.kind === "root" ? null : currentGroup.id, expandable: false, expanded: false, }); const node = groupNodeCacheRef.current.get(groupId); node.__children = children; node.__invalidate = true; node.last = isLast; node.parentId = currentGroup.kind === "root" ? null : currentGroup.id; current.set(p, { id: groupId, row: node, kind: "branch", children, leafs: [], leafIds: new Set(), path: partial, last: j === paths.length - 1, parent: currentGroup, key: p, }); groupIdToGroupNode.set(groupId, current.get(p)); } const node = current.get(p); if (node.kind !== "branch") { console.error(`Invalid grouping path. Expected a group node for path: ${paths}, but found a leaf along the way.`, node); continue; } node.leafs.push(i); node.leafIds.add(n.id); currentGroup = node; current = node.children; } const n_writable = n; if (currentGroup.kind === "root") { n_writable.depth = 0; n_writable.parentId = null; } else { n_writable.depth = currentGroup.row.depth + 1; n_writable.parentId = currentGroup.id; } current.set(current.size, { kind: "leaf", row: n, parent: currentGroup, key: current.size }); } // Doing filtering here. Then I can collapse afterwards // TODO: @lee verify this logic is sound and test thoroughly if (having) { const traverse = (node, depth = 0) => { if (node.kind === "leaf") return; if (node.kind === "branch") { node.children.forEach((x) => { if (x.kind === "leaf") return; traverse(x, depth + 1); }); if (node.leafIds.size === 0) { // Delete self node.parent.children.delete(node.key); return; } // Now we have traversed the group. const filterFn = Array.isArray(having) ? (having[depth] ?? null) : having; // We aren't filtering at this depth, so return early. if (!filterFn) return; const row = node.row; // We need to aggregate this row. if (row.__invalidate) { const data = agg ? agg(getValidLeafs(node, leafs, workingSet)) : {}; row.data = data; row.__invalidate = false; } const shouldKeep = filterFn(row); if (shouldKeep) return; // We need to remove this row, remove the leafs from the parents going upwards. root.groupLookup.delete(node.id); node.parent.children.delete(node.key); if (node.parent.kind !== "root") { let current = node.parent; while (current) { current.leafIds = current.leafIds.difference(node.leafIds); if (current.parent.kind === "root") break; current = current.parent; } } } }; root.children.forEach((x) => traverse(x, 0)); } if (rowGroupCollapseBehavior === "full-tree") root.children.forEach(collapse); if (rowGroupCollapseBehavior === "last-only") root.children.forEach(collapseLast); return root; }, [agg, group, groupIdFn, having, labelFilter, leafs, rowGroupCollapseBehavior, workingSet]); }