import type { Virtualizer } from "@tanstack/react-virtual"; import { useVirtualizer } from "@tanstack/react-virtual"; import type { CSSProperties } from "react"; import { Fragment, useLayoutEffect, useRef, useState } from "react"; import type { SelectableTreeNode } from "./common"; import type { TreeProps } from "./Tree"; import { TreeDropMarker } from "./TreeDropMarker"; import type { TreeItemHandle, TreeItemProps } from "./TreeItem"; import { TreeItem } from "./TreeItem"; export type TreeItemListProps = Pick< TreeProps, | "ItemInner" | "ItemLeftSlotInner" | "ItemRightSlot" | "treeId" | "getItemKey" | "getEditOptions" | "renderContextMenu" > & Pick, "onClick" | "getContextMenu"> & { nodes: SelectableTreeNode[]; style?: CSSProperties; className?: string; forceDepth?: number; addTreeItemRef?: (item: T, n: TreeItemHandle | null) => void; /** * Enable virtualization by providing the scroll container. Rows are then * windowed with @tanstack/react-virtual and only visible rows mount. */ getScrollElement?: () => HTMLElement | null; onVirtualizerReady?: (v: Virtualizer) => void; }; export function TreeItemList(props: TreeItemListProps) { if (props.getScrollElement != null) { return ; } return ; } function StaticTreeItemList({ className, getItemKey, nodes, style, treeId, forceDepth, addTreeItemRef, getScrollElement: _getScrollElement, onVirtualizerReady: _onVirtualizerReady, ...props }: TreeItemListProps) { return (
    {nodes.map((child, i) => ( ))}
); } // Rows are --height-sm (2rem). Derive the pixel estimate from the actual root // font size so scroll math stays accurate under interface scaling. function estimateRowHeightPx() { const rem = Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16; return 2 * rem; } function VirtualTreeItemList({ className, getItemKey, nodes, style, treeId, forceDepth, addTreeItemRef, getScrollElement, onVirtualizerReady, ...props }: TreeItemListProps & { getScrollElement: () => HTMLElement | null }) { const listRef = useRef(null); // Offset of the list within the scroll container (eg. container padding), // so windowing and scrollToIndex targets aren't shifted by it const [scrollMargin, setScrollMargin] = useState(0); useLayoutEffect(() => { const list = listRef.current; const scroller = getScrollElement(); if (list == null || scroller == null) return; const offset = list.getBoundingClientRect().top - scroller.getBoundingClientRect().top + scroller.scrollTop; setScrollMargin(offset); }, [getScrollElement]); const virtualizer = useVirtualizer({ count: nodes.length, getScrollElement, estimateSize: estimateRowHeightPx, overscan: 10, scrollMargin, }); useLayoutEffect(() => { onVirtualizerReady?.(virtualizer); }, [virtualizer, onVirtualizerReady]); return (
    {virtualizer.getVirtualItems().map((virtualItem) => { const child = nodes[virtualItem.index]; if (child == null) return null; return (
    ); })}
); }