@1771technologies/lytenyte-pro
Version:
Blazingly fast headless React data grid with 100s of features.
153 lines (152 loc) • 6.96 kB
JavaScript
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]);
}