@1771technologies/lytenyte-pro
Version:
Blazingly fast headless React data grid with 100s of features.
101 lines (100 loc) • 4.57 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import {} from "@1771technologies/lytenyte-shared";
import { useMemo, useState } from "react";
import { buildVirtualTreePartial } from "./get-virtual-tree-paths.js";
import { makeVirtualTree } from "./make-virtual-tree.js";
import { useFlattenedTree } from "./use-flattened-tree.js";
import { useRowStartAndEnd } from "./use-row-start-and-end.js";
import { getTreeNodeId } from "../utils/get-tree-node-id.js";
import { useEvent, useMeasure } from "@1771technologies/lytenyte-core/yinternal";
export function useVirtualizedTree({ itemHeight, expansions, expansionDefault = false, nonAdjacentPathTrees, paths, }) {
const [ref, bounds, _, panel] = useMeasure({ scroll: true });
const { flat, nodeToIndex, indexToId, allIds, idToNode, root } = useFlattenedTree(paths, expansions, expansionDefault, nonAdjacentPathTrees);
const size = useMemo(() => flat.length, [flat.length]);
const [rowStart, rowEnd] = useRowStartAndEnd(panel, itemHeight, bounds, size);
const [focused, setFocused] = useState(null);
const virtualTree = useMemo(() => {
const items = flat.slice(rowStart, rowEnd);
if (!items.length)
return [];
// Ensure that the parent nodes of the current tree in view are added as well. This ensure that
// the nodes are always visible.
while (items[0].parent.kind !== "root") {
items.unshift(items[0].parent);
}
// Ensure that the first node is always in the list of tree nodes so the home key works fine.
if (rowStart !== 0 && items[0] !== flat[0])
items.unshift(flat[0]);
// Ensure the last node is always in the list of nodes present.
if (rowEnd !== flat.length) {
const endItems = [flat.at(-1)];
while (endItems[0].parent.kind !== "root" && endItems[0].parent !== items.at(-1)) {
endItems.unshift(endItems[0].parent);
}
items.push(...endItems);
}
// Finally we need to ensure the focused node is present. We add the node before and after
// the current node - so that the next and previous node are selectable.
if (focused && idToNode.has(focused)) {
const node = idToNode.get(focused);
const index = nodeToIndex.get(node);
const nextId = indexToId.get(index + 1);
const prevId = indexToId.get(index - 1);
const next = idToNode.get(nextId);
const prev = idToNode.get(prevId);
if (!items.includes(node)) {
const firstIndexGreater = items.findIndex((c) => nodeToIndex.get(c) > index);
items.splice(firstIndexGreater, 0, node);
}
if (next && !items.includes(next)) {
const firstIndexGreater = items.findIndex((c) => nodeToIndex.get(c) > index + 1);
items.splice(firstIndexGreater, 0, next);
}
if (prev && !items.includes(prev)) {
const firstIndexGreater = items.findIndex((c) => nodeToIndex.get(c) > index - 1);
items.splice(firstIndexGreater, 0, prev);
}
}
const subtreePaths = buildVirtualTreePartial(items);
const subtree = makeVirtualTree(subtreePaths, nodeToIndex, itemHeight, nonAdjacentPathTrees ?? false);
return subtree;
}, [
flat,
focused,
idToNode,
indexToId,
itemHeight,
nodeToIndex,
nonAdjacentPathTrees,
rowEnd,
rowStart,
]);
const spacer = _jsx("div", { style: { height: size * itemHeight, width: 0 } });
const onFocusChange = useEvent((el) => {
setFocused(el ? getTreeNodeId(el) : null);
});
const getAllIds = useEvent(() => allIds);
const getIdsBetweenNodes = useEvent((left, right) => {
const leftIndex = Number.parseInt(left.getAttribute("data-ln-row-index"));
const rightIndex = Number.parseInt(right.getAttribute("data-ln-row-index"));
const [start, end] = leftIndex < rightIndex ? [leftIndex, rightIndex] : [rightIndex, leftIndex];
if (start === end)
return [indexToId.get(start)];
const ids = [];
for (let i = start; i <= end; i++) {
ids.push(indexToId.get(i));
}
return ids;
});
return {
ref,
virtualTree,
spacer,
rootProps: {
getAllIds,
getIdsBetweenNodes,
onFocusChange,
root,
},
};
}