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