UNPKG

@1771technologies/lytenyte-pro

Version:

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

141 lines (140 loc) 6.84 kB
import { jsx as _jsx } from "react/jsx-runtime"; import { forwardRef, useCallback, useImperativeHandle, useMemo, useState, } from "react"; import { itemsWithIdToMap, moveRelative, } from "@1771technologies/lytenyte-shared"; import { TreeChildren } from "./tree-children.js"; import { SelectAll } from "./select-all.js"; import { useClientDataSource } from "../../data-source-client/use-client-data-source.js"; import { usePiece } from "@1771technologies/lytenyte-core/internal"; import { Root } from "../../root/root.js"; import { Grid } from "@1771technologies/lytenyte-core"; function TreeViewBase({ items, rowSelectAllShow: rowSelectAll = true, defaultExpansion, rowHeight = 28, children = TreeChildren, branchJoinSeparator = "/", rowSelectionEnabled = true, rowSelection, onRowSelectionChange, rowGroupExpansions, onRowGroupExpansionChange, selectAllSlot, draggable, onItemsReordered, }, forwarded) { const groupFn = useMemo(() => { return (x) => x.data.path; }, []); const finalItems = useMemo(() => { if (!rowSelectAll) return items; return [{ id: "__ln_select_all", path: [], name: "Select All" }, ...items]; }, [items, rowSelectAll]); const subtractions = useMemo(() => { if (rowSelectAll) return new Set(["__ln_select_all"]); return new Set(); }, [rowSelectAll]); const source = useClientDataSource({ group: groupFn, data: finalItems, rowGroupDefaultExpansion: defaultExpansion ?? false, sort: null, leafIdFn: useCallback((d) => d.id, []), groupIdFn: useCallback((p) => p.join(branchJoinSeparator), [branchJoinSeparator]), rowGroupExpansions, onRowGroupExpansionChange, rowSelectionIdUniverseSubtractions: subtractions, rowSelection, onRowSelectionChange: onRowSelectionChange, }); const [overId, setOverId] = useState(null); const [isBefore, setIsBefore] = useState(false); const itemLookup = useMemo(() => itemsWithIdToMap(items), [items]); const over$ = usePiece(overId); const isBefore$ = usePiece(isBefore); const [api, setApi] = useState(null); const groupColumn = useMemo(() => { return { widthFlex: 1, width: 20, widthMin: 20, cellRenderer: ({ row, rowIndex, api, indeterminate, selected }) => { const { props, isDragActive } = api.useRowDrag({ rowIndex, }); const over = over$.useValue(); const isBefore = isBefore$.useValue(); if (row.kind === "aggregated") return null; if (row.id === "__ln_select_all") { if (!api) return null; return _jsx(SelectAll, { api: api, render: selectAllSlot }); } const leafs = () => row.kind === "branch" ? api.rowLeafs(row.id).map((x) => itemLookup.get(x)) : [itemLookup.get(row.id)]; return children({ row, leafs, selectEnabled: rowSelectionEnabled, selected, indeterminate, select: (b) => api.rowSelect({ selected: row.id, deselect: !(b ?? selected) }), handleSelect: api.rowHandleSelect, toggle: (b) => api.rowGroupToggle(row.id, b), dragProps: draggable ? props : {}, isBefore: isBefore, isOver: !isDragActive && row.id === over, }); }, }; }, [children, draggable, isBefore$, itemLookup, over$, rowSelectionEnabled, selectAllSlot]); useImperativeHandle(forwarded, () => { return { rowsSelected: () => (api?.rowsSelected().rows ?? []).filter((x) => x.id !== "__ln_select_all"), }; }, [api]); return (_jsx(Root, { ref: setApi, rowSource: source, rowGroupColumn: groupColumn, rowAlternateAttr: false, headerHeight: 0, rowHeight: rowHeight, rowSelectionMode: rowSelectionEnabled ? "multiple" : "none", rowSelectionActivator: "none", onRowDragEnter: (p) => { if (p.over.kind === "viewport") return; setOverId(p.over.row.id); setIsBefore(p.over.rowIndex < p.source.rowIndex); }, onRowDragLeave: (p) => { if (p.over.kind === "viewport") return; setOverId(null); }, onRowDrop: (p) => { setOverId(null); if (p.over.kind === "viewport") return; const sourceRow = p.source.row; const rows = sourceRow.kind === "branch" ? p.source.api.rowLeafs(sourceRow.id) : [sourceRow.id]; const overRow = p.over.row; const overId = p.over.row.kind === "branch" ? p.over.api.rowLeafs(overRow.id)[0] : overRow.id; if (rows.includes(overId)) return; const moveItems = rows.map((r) => items.findIndex((x) => x.id === r)).sort((l, r) => l - r); const target = items.findIndex((x) => x.id === overId); const next = moveRelative(items, moveItems[0], target, moveItems.slice(1)); onItemsReordered?.(next); }, events: useMemo(() => { return { cell: { keyDown: ({ api, event, row }) => { if (event.key === " " && row.kind === "branch") { event.preventDefault(); api.rowGroupToggle(row.id); } if (event.key === "ArrowLeft" && row.kind === "branch") { event.preventDefault(); event.stopPropagation(); api.rowGroupToggle(row.id, false); } if (event.key === "ArrowRight" && row.kind === "branch") { event.preventDefault(); event.stopPropagation(); api.rowGroupToggle(row.id, true); } if (event.key === "Enter") { api.rowSelect({ selected: row.id, deselect: api.rowIsSelected(row.id) }); } }, }, }; }, []), styles: useMemo(() => { return { viewport: { className: "ln-tree-view", }, }; }, []), children: _jsx(Grid.Viewport, { children: _jsx(Grid.RowsContainer, { children: _jsx(Grid.RowsCenter, {}) }) }) })); } export const TreeView = forwardRef(TreeViewBase);