@bmsuisse/datagrid 0.23.4 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +75 -2
- package/dist/index.js +272 -51
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -762,6 +762,37 @@ interface DataGridProps<TRow> {
|
|
|
762
762
|
* a page with more than one.
|
|
763
763
|
*/
|
|
764
764
|
testId?: string;
|
|
765
|
+
/**
|
|
766
|
+
* Buckets the grid's already-filtered/sorted/paginated `rows` into groups,
|
|
767
|
+
* rendering one full-width `colSpan`'d group-header row before each
|
|
768
|
+
* bucket's own rows, in first-seen bucket order (rows are never re-sorted
|
|
769
|
+
* to cluster a group together — the caller's own sort already determines
|
|
770
|
+
* which key appears first). Single level only — no nested grouping, no
|
|
771
|
+
* aggregate/summary calculation; a caller wanting a subtotal computes it
|
|
772
|
+
* themselves in `renderGroupHeader` off that bucket's own row array.
|
|
773
|
+
* Operates on whatever `rows` already resolved to — i.e. the current page,
|
|
774
|
+
* if paginated — with no special-casing; pair with a large `pageSize`/
|
|
775
|
+
* `showPagination: false` if a group's full membership should never be
|
|
776
|
+
* split across pages. Omit entirely to disable — no grouping, no header
|
|
777
|
+
* rows, unchanged default rendering.
|
|
778
|
+
*
|
|
779
|
+
* Not supported together with `virtualize` yet — when both are set,
|
|
780
|
+
* virtualization is silently disabled and the grid renders as a plain,
|
|
781
|
+
* fully-rendered (but still grouped) `<table>` instead. See AGENTS.md's
|
|
782
|
+
* "Known limitations."
|
|
783
|
+
*/
|
|
784
|
+
groupBy?: (row: TRow) => string;
|
|
785
|
+
/** Customizes a group-header row's content. Defaults to `` `${key} (${rows.length})` ``. */
|
|
786
|
+
renderGroupHeader?: (key: string, rows: TRow[], expanded: boolean) => ReactNode;
|
|
787
|
+
/** Whether a newly-seen group starts expanded. Defaults to true. */
|
|
788
|
+
defaultGroupsExpanded?: boolean;
|
|
789
|
+
/**
|
|
790
|
+
* Controlled per-group expand/collapse state, keyed by the `groupBy` key.
|
|
791
|
+
* Both must be supplied together to control it — omit both for
|
|
792
|
+
* `<DataGrid>` to own this state internally.
|
|
793
|
+
*/
|
|
794
|
+
expandedGroups?: Record<string, boolean>;
|
|
795
|
+
onExpandedGroupsChange?: (expanded: Record<string, boolean>) => void;
|
|
765
796
|
}
|
|
766
797
|
|
|
767
798
|
/**
|
|
@@ -772,7 +803,7 @@ interface DataGridProps<TRow> {
|
|
|
772
803
|
* (`"server"`). Column filter UI defaults to the type-appropriate widget
|
|
773
804
|
* from `src/filter` unless a column supplies its own `cell` renderer.
|
|
774
805
|
*/
|
|
775
|
-
declare function DataGrid<TRow extends RowData>({ columns, dataSource, getRowId, rowActions, headerActions, enableMultiSort, initialState, columnVisibility, renderDetail, enableColumnResizing, columnResizeMode, columnSizing: controlledColumnSizing, onColumnSizingChange, virtualize, showPagination, getRowProps, getRowTestId, gridState, selectedIds: controlledSelectedIds, onSelectedIdsChange, testId, loading: loadingProp, }: DataGridProps<TRow>): ReactElement;
|
|
806
|
+
declare function DataGrid<TRow extends RowData>({ columns, dataSource, getRowId, rowActions, headerActions, enableMultiSort, initialState, columnVisibility, renderDetail, enableColumnResizing, columnResizeMode, columnSizing: controlledColumnSizing, onColumnSizingChange, virtualize, showPagination, getRowProps, getRowTestId, gridState, selectedIds: controlledSelectedIds, onSelectedIdsChange, testId, loading: loadingProp, groupBy, renderGroupHeader, defaultGroupsExpanded, expandedGroups: controlledExpandedGroups, onExpandedGroupsChange, }: DataGridProps<TRow>): ReactElement;
|
|
776
807
|
|
|
777
808
|
interface GridStateController {
|
|
778
809
|
state: GridState;
|
|
@@ -954,6 +985,48 @@ interface TreeDataGridProps<TRow> extends TreeAccessors<TRow> {
|
|
|
954
985
|
estimatedRowHeight?: number;
|
|
955
986
|
/** Max height of the scrollable table body in pixels. Required for virtualization to have a viewport to scroll within. Defaults to 480. */
|
|
956
987
|
maxBodyHeight?: number;
|
|
988
|
+
/**
|
|
989
|
+
* Controlled row selection — same convention as `<DataGrid>`'s own
|
|
990
|
+
* `selectedIds`/`onSelectedIdsChange`. Both must be supplied together to
|
|
991
|
+
* render a checkbox column at all; there is no internal/uncontrolled
|
|
992
|
+
* fallback here, unlike `<DataGrid>` — `<TreeDataGrid>` has no
|
|
993
|
+
* `headerActions` bulk-toolbar concept that would need selection state
|
|
994
|
+
* even without external control, so there's nothing for an uncontrolled
|
|
995
|
+
* default to serve.
|
|
996
|
+
*
|
|
997
|
+
* Clicking a row's checkbox always toggles exactly that row's own id in
|
|
998
|
+
* this set — never its children or parent. Any cascade ("select whole
|
|
999
|
+
* group" / "select all descendants") is the caller's job, done by
|
|
1000
|
+
* recomputing the desired final set inside `onSelectedIdsChange` before
|
|
1001
|
+
* committing it to their own state.
|
|
1002
|
+
*/
|
|
1003
|
+
selectedIds?: ReadonlySet<string>;
|
|
1004
|
+
onSelectedIdsChange?: (ids: ReadonlySet<string>) => void;
|
|
1005
|
+
/**
|
|
1006
|
+
* Fully overrides a row's checked/indeterminate display, replacing the
|
|
1007
|
+
* default derivation below. Needed for selection semantics this component
|
|
1008
|
+
* won't build in — e.g. a "select whole group vs. split into individual
|
|
1009
|
+
* children once loaded" cascade, where a parent should read as checked
|
|
1010
|
+
* even though not every descendant id is literally in `selectedIds`.
|
|
1011
|
+
* Return `undefined` for a given row to fall back to the default
|
|
1012
|
+
* derivation for just that row.
|
|
1013
|
+
*
|
|
1014
|
+
* Omitted (the default): `checked` is exactly `selectedIds.has(getRowId(row))`
|
|
1015
|
+
* — `selectedIds` always stays the single source of truth for "is this id
|
|
1016
|
+
* selected." `indeterminate` is true only when the row itself isn't
|
|
1017
|
+
* checked but at least one *currently loaded* descendant (recursively, via
|
|
1018
|
+
* the same `getChildren`/`onLoadChildren` cache this component already
|
|
1019
|
+
* maintains) is checked or itself indeterminate. A selected descendant
|
|
1020
|
+
* hidden behind a node that's never been expanded (so its children were
|
|
1021
|
+
* never fetched) can't be inspected — that ancestor reads as unchecked,
|
|
1022
|
+
* not indeterminate, until the fetch happens.
|
|
1023
|
+
*/
|
|
1024
|
+
getRowSelectionState?: (row: TRow) => {
|
|
1025
|
+
checked: boolean;
|
|
1026
|
+
indeterminate?: boolean;
|
|
1027
|
+
} | undefined;
|
|
1028
|
+
/** Disables just the checkbox for a specific row. Defaults to `() => false`. */
|
|
1029
|
+
isRowSelectionDisabled?: (row: TRow) => boolean;
|
|
957
1030
|
}
|
|
958
1031
|
|
|
959
1032
|
/**
|
|
@@ -970,7 +1043,7 @@ interface TreeDataGridProps<TRow> extends TreeAccessors<TRow> {
|
|
|
970
1043
|
* column's cell matches `treeColumnId` (defaults to `columns[0]`); every
|
|
971
1044
|
* other column renders exactly like it would in `<DataGrid>`.
|
|
972
1045
|
*/
|
|
973
|
-
declare function TreeDataGrid<TRow>({ columns, data, treeColumnId, getRowId, getChildren, hasChildren, onLoadChildren, initialExpandedLevel, expanded: controlledExpanded, onExpandedChange, indentSize, getRowProps, getRowTestId, rowActions, virtualizeThreshold, estimatedRowHeight, maxBodyHeight, }: TreeDataGridProps<TRow>): ReactElement;
|
|
1046
|
+
declare function TreeDataGrid<TRow>({ columns, data, treeColumnId, getRowId, getChildren, hasChildren, onLoadChildren, initialExpandedLevel, expanded: controlledExpanded, onExpandedChange, indentSize, getRowProps, getRowTestId, rowActions, virtualizeThreshold, estimatedRowHeight, maxBodyHeight, selectedIds: controlledSelectedIds, onSelectedIdsChange, getRowSelectionState, isRowSelectionDisabled, }: TreeDataGridProps<TRow>): ReactElement;
|
|
974
1047
|
|
|
975
1048
|
interface FlatTreeRow<TRow> {
|
|
976
1049
|
row: TRow;
|
package/dist/index.js
CHANGED
|
@@ -338,7 +338,13 @@ function BooleanFilter({
|
|
|
338
338
|
"aria-label": `Filter ${column.header}`,
|
|
339
339
|
"data-testid": `filter-${column.id}`,
|
|
340
340
|
children: [
|
|
341
|
-
/* @__PURE__ */ jsx4(
|
|
341
|
+
/* @__PURE__ */ jsx4(
|
|
342
|
+
FunnelIcon,
|
|
343
|
+
{
|
|
344
|
+
className: cn("h-3.5 w-3.5 shrink-0", isFiltered ? "opacity-100 text-primary" : "opacity-40"),
|
|
345
|
+
"aria-hidden": true
|
|
346
|
+
}
|
|
347
|
+
),
|
|
342
348
|
isFiltered && /* @__PURE__ */ jsx4("span", { className: "truncate", children: summary })
|
|
343
349
|
]
|
|
344
350
|
}
|
|
@@ -592,7 +598,13 @@ function EnumFilter({
|
|
|
592
598
|
"aria-label": `Filter ${column.header}`,
|
|
593
599
|
"data-testid": `filter-${column.id}`,
|
|
594
600
|
children: [
|
|
595
|
-
/* @__PURE__ */ jsx9(
|
|
601
|
+
/* @__PURE__ */ jsx9(
|
|
602
|
+
FunnelIcon2,
|
|
603
|
+
{
|
|
604
|
+
className: cn("h-3.5 w-3.5 shrink-0", isFiltered ? "opacity-100 text-primary" : "opacity-40"),
|
|
605
|
+
"aria-hidden": true
|
|
606
|
+
}
|
|
607
|
+
),
|
|
596
608
|
isFiltered && /* @__PURE__ */ jsx9("span", { className: "truncate", children: summary })
|
|
597
609
|
]
|
|
598
610
|
}
|
|
@@ -877,7 +889,13 @@ function NumberComparisonFilter({
|
|
|
877
889
|
"aria-label": `Filter ${column.header}`,
|
|
878
890
|
"data-testid": `filter-${column.id}`,
|
|
879
891
|
children: [
|
|
880
|
-
/* @__PURE__ */ jsx10(
|
|
892
|
+
/* @__PURE__ */ jsx10(
|
|
893
|
+
FunnelIcon3,
|
|
894
|
+
{
|
|
895
|
+
className: cn("h-3.5 w-3.5 shrink-0", isFiltered ? "opacity-100 text-primary" : "opacity-40"),
|
|
896
|
+
"aria-hidden": true
|
|
897
|
+
}
|
|
898
|
+
),
|
|
881
899
|
isFiltered && /* @__PURE__ */ jsx10("span", { className: "truncate", children: summary })
|
|
882
900
|
]
|
|
883
901
|
}
|
|
@@ -971,7 +989,13 @@ function NumberRangeFilter({
|
|
|
971
989
|
"aria-label": `Filter ${column.header}`,
|
|
972
990
|
"data-testid": `filter-${column.id}`,
|
|
973
991
|
children: [
|
|
974
|
-
/* @__PURE__ */ jsx11(
|
|
992
|
+
/* @__PURE__ */ jsx11(
|
|
993
|
+
FunnelIcon4,
|
|
994
|
+
{
|
|
995
|
+
className: cn("h-3.5 w-3.5 shrink-0", isFiltered ? "opacity-100 text-primary" : "opacity-40"),
|
|
996
|
+
"aria-hidden": true
|
|
997
|
+
}
|
|
998
|
+
),
|
|
975
999
|
isFiltered && /* @__PURE__ */ jsx11("span", { className: "truncate", children: summary })
|
|
976
1000
|
]
|
|
977
1001
|
}
|
|
@@ -1184,7 +1208,13 @@ function NumberHistogramFilter({
|
|
|
1184
1208
|
"aria-label": `Filter ${column.header}`,
|
|
1185
1209
|
"data-testid": `filter-${column.id}`,
|
|
1186
1210
|
children: [
|
|
1187
|
-
/* @__PURE__ */ jsx12(
|
|
1211
|
+
/* @__PURE__ */ jsx12(
|
|
1212
|
+
FunnelIcon5,
|
|
1213
|
+
{
|
|
1214
|
+
className: cn("h-3.5 w-3.5 shrink-0", isFiltered ? "opacity-100 text-primary" : "opacity-40"),
|
|
1215
|
+
"aria-hidden": true
|
|
1216
|
+
}
|
|
1217
|
+
),
|
|
1188
1218
|
isFiltered && /* @__PURE__ */ jsx12("span", { className: "truncate", children: summary })
|
|
1189
1219
|
]
|
|
1190
1220
|
}
|
|
@@ -1300,7 +1330,13 @@ function StringFilter({
|
|
|
1300
1330
|
"aria-label": `Filter ${column.header}`,
|
|
1301
1331
|
"data-testid": `filter-${column.id}`,
|
|
1302
1332
|
children: [
|
|
1303
|
-
/* @__PURE__ */ jsx13(
|
|
1333
|
+
/* @__PURE__ */ jsx13(
|
|
1334
|
+
FunnelIcon6,
|
|
1335
|
+
{
|
|
1336
|
+
className: cn("h-3.5 w-3.5 shrink-0", isFiltered ? "opacity-100 text-primary" : "opacity-40"),
|
|
1337
|
+
"aria-hidden": true
|
|
1338
|
+
}
|
|
1339
|
+
),
|
|
1304
1340
|
isFiltered && /* @__PURE__ */ jsx13("span", { className: "truncate", children: text })
|
|
1305
1341
|
]
|
|
1306
1342
|
}
|
|
@@ -1598,6 +1634,21 @@ import { useVirtualizer } from "@tanstack/react-virtual";
|
|
|
1598
1634
|
import { ChevronRight, Loader2 } from "lucide-react";
|
|
1599
1635
|
import { Fragment as Fragment2, useEffect as useEffect7, useMemo as useMemo6, useRef as useRef5, useState as useState6 } from "react";
|
|
1600
1636
|
|
|
1637
|
+
// src/lib/structuralColumns.ts
|
|
1638
|
+
var SELECTION_COLUMN_WIDTH = 44;
|
|
1639
|
+
|
|
1640
|
+
// src/grid/groupRows.ts
|
|
1641
|
+
function groupRows(items, groupBy) {
|
|
1642
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
1643
|
+
for (const item of items) {
|
|
1644
|
+
const key = groupBy(item);
|
|
1645
|
+
const bucket = buckets.get(key);
|
|
1646
|
+
if (bucket) bucket.push(item);
|
|
1647
|
+
else buckets.set(key, [item]);
|
|
1648
|
+
}
|
|
1649
|
+
return Array.from(buckets, ([key, groupItems]) => ({ key, items: groupItems }));
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1601
1652
|
// src/grid/processClientData.ts
|
|
1602
1653
|
function makeComparator(sort) {
|
|
1603
1654
|
return (a, b) => {
|
|
@@ -1755,8 +1806,8 @@ import { Fragment as Fragment3, jsx as jsx19, jsxs as jsxs12 } from "react/jsx-r
|
|
|
1755
1806
|
var gridTableFeatures = tableFeatures({ columnSizingFeature, columnResizingFeature });
|
|
1756
1807
|
var DEFAULT_COLUMN_SIZE = 150;
|
|
1757
1808
|
var DETAIL_COLUMN_WIDTH = 44;
|
|
1758
|
-
var SELECTION_COLUMN_WIDTH = 44;
|
|
1759
1809
|
var ROW_ACTIONS_COLUMN_WIDTH = 44;
|
|
1810
|
+
var STRUCTURAL_ZEBRA_BG_CLASS = "bg-[color-mix(in_srgb,var(--color-foreground)_5%,var(--color-background))]";
|
|
1760
1811
|
function pinnedOffsets(visibleColumns, side, getSize, leadingOffset) {
|
|
1761
1812
|
const offsets = /* @__PURE__ */ new Map();
|
|
1762
1813
|
let cursor = leadingOffset;
|
|
@@ -1813,7 +1864,12 @@ function DataGrid({
|
|
|
1813
1864
|
selectedIds: controlledSelectedIds,
|
|
1814
1865
|
onSelectedIdsChange,
|
|
1815
1866
|
testId,
|
|
1816
|
-
loading: loadingProp = false
|
|
1867
|
+
loading: loadingProp = false,
|
|
1868
|
+
groupBy,
|
|
1869
|
+
renderGroupHeader,
|
|
1870
|
+
defaultGroupsExpanded = true,
|
|
1871
|
+
expandedGroups: controlledExpandedGroups,
|
|
1872
|
+
onExpandedGroupsChange
|
|
1817
1873
|
}) {
|
|
1818
1874
|
const { state, filtersByColumn, setColumnFilter, toggleSort, setPage } = useGridState(
|
|
1819
1875
|
dataSource,
|
|
@@ -1841,6 +1897,18 @@ function DataGrid({
|
|
|
1841
1897
|
return next;
|
|
1842
1898
|
});
|
|
1843
1899
|
}
|
|
1900
|
+
const [internalExpandedGroups, setInternalExpandedGroups] = useState6({});
|
|
1901
|
+
const expandedGroupsState = controlledExpandedGroups ?? internalExpandedGroups;
|
|
1902
|
+
function updateExpandedGroups(next) {
|
|
1903
|
+
setInternalExpandedGroups(next);
|
|
1904
|
+
onExpandedGroupsChange?.(next);
|
|
1905
|
+
}
|
|
1906
|
+
function isGroupExpanded(key) {
|
|
1907
|
+
return expandedGroupsState[key] ?? defaultGroupsExpanded;
|
|
1908
|
+
}
|
|
1909
|
+
function toggleGroupExpanded(key) {
|
|
1910
|
+
updateExpandedGroups({ ...expandedGroupsState, [key]: !isGroupExpanded(key) });
|
|
1911
|
+
}
|
|
1844
1912
|
const visibleColumns = useMemo6(
|
|
1845
1913
|
() => columnVisibility ? columns.filter((column) => isColumnVisible(columnVisibility, column.id)) : columns,
|
|
1846
1914
|
[columns, columnVisibility]
|
|
@@ -1925,8 +1993,14 @@ function DataGrid({
|
|
|
1925
1993
|
return { style: column.width ? { width: column.width } : void 0 };
|
|
1926
1994
|
}
|
|
1927
1995
|
function structuralCellProps(side, offset, width, area = "body") {
|
|
1996
|
+
const base = "border-b border-border p-1 sticky z-10";
|
|
1928
1997
|
const bg = area === "header" ? "bg-muted" : "bg-background";
|
|
1929
|
-
|
|
1998
|
+
const bgOdd = area === "header" ? "bg-muted" : STRUCTURAL_ZEBRA_BG_CLASS;
|
|
1999
|
+
return {
|
|
2000
|
+
className: `${base} ${bg}`,
|
|
2001
|
+
classNameOdd: `${base} ${bgOdd}`,
|
|
2002
|
+
style: { [side]: offset, width }
|
|
2003
|
+
};
|
|
1930
2004
|
}
|
|
1931
2005
|
const detailCellProps = structuralCellProps("left", 0, DETAIL_COLUMN_WIDTH);
|
|
1932
2006
|
const selectionCellProps = structuralCellProps(
|
|
@@ -1943,7 +2017,7 @@ function DataGrid({
|
|
|
1943
2017
|
"header"
|
|
1944
2018
|
);
|
|
1945
2019
|
const rowActionsHeaderCellProps = structuralCellProps("right", 0, ROW_ACTIONS_COLUMN_WIDTH, "header");
|
|
1946
|
-
const BODY_TD_BASE_CLASS = "border-b p-2";
|
|
2020
|
+
const BODY_TD_BASE_CLASS = "border-b border-border p-2";
|
|
1947
2021
|
function bodyCellClassAndStyle(column) {
|
|
1948
2022
|
const pinnedProps = column.pinned || enableColumnResizing ? pinnedCellProps(column) : void 0;
|
|
1949
2023
|
return {
|
|
@@ -1965,19 +2039,23 @@ function DataGrid({
|
|
|
1965
2039
|
const leafHeaderCellPropsByColumn = useMemo6(() => {
|
|
1966
2040
|
const map = /* @__PURE__ */ new Map();
|
|
1967
2041
|
for (const column of visibleColumns) {
|
|
1968
|
-
map.set(column.id, headerCellClassAndStyle(column, "relative border-b p-2 font-medium"));
|
|
2042
|
+
map.set(column.id, headerCellClassAndStyle(column, "relative border-b border-border p-2 font-medium"));
|
|
1969
2043
|
}
|
|
1970
2044
|
return map;
|
|
1971
2045
|
}, [visibleColumns, enableColumnResizing, leftPinnedOffsets, rightPinnedOffsets, columnSizing]);
|
|
1972
2046
|
const filterHeaderCellPropsByColumn = useMemo6(() => {
|
|
1973
2047
|
const map = /* @__PURE__ */ new Map();
|
|
1974
2048
|
for (const column of visibleColumns) {
|
|
1975
|
-
map.set(column.id, headerCellClassAndStyle(column, "border-b p-2 align-top font-normal"));
|
|
2049
|
+
map.set(column.id, headerCellClassAndStyle(column, "border-b border-border p-2 align-top font-normal"));
|
|
1976
2050
|
}
|
|
1977
2051
|
return map;
|
|
1978
2052
|
}, [visibleColumns, enableColumnResizing, leftPinnedOffsets, rightPinnedOffsets, columnSizing]);
|
|
1979
2053
|
const tableRows = table.getRowModel().rows;
|
|
1980
|
-
const shouldVirtualize = Boolean(virtualize) && tableRows.length > (virtualize?.threshold ?? 100);
|
|
2054
|
+
const shouldVirtualize = Boolean(virtualize) && !groupBy && tableRows.length > (virtualize?.threshold ?? 100);
|
|
2055
|
+
const groupedBuckets = useMemo6(
|
|
2056
|
+
() => groupBy ? groupRows(tableRows, (row) => groupBy(row.original)) : void 0,
|
|
2057
|
+
[tableRows, groupBy]
|
|
2058
|
+
);
|
|
1981
2059
|
const scrollRef = useRef5(null);
|
|
1982
2060
|
const virtualizer = useVirtualizer({
|
|
1983
2061
|
count: tableRows.length,
|
|
@@ -2009,50 +2087,97 @@ function DataGrid({
|
|
|
2009
2087
|
{
|
|
2010
2088
|
...rowProps,
|
|
2011
2089
|
"data-testid": rowTestId,
|
|
2012
|
-
className: rowProps?.className ? cn(rowProps.className, row.index % 2 === 1 && "bg-foreground/5") : row.index % 2 === 1 ? "bg-foreground/5" :
|
|
2090
|
+
className: rowProps?.className ? cn("divide-x divide-border", rowProps.className, row.index % 2 === 1 && "bg-foreground/5") : `divide-x divide-border${row.index % 2 === 1 ? " bg-foreground/5" : ""}`,
|
|
2013
2091
|
children: [
|
|
2014
|
-
showDetailColumn && /* @__PURE__ */ jsx19(
|
|
2015
|
-
|
|
2092
|
+
showDetailColumn && /* @__PURE__ */ jsx19(
|
|
2093
|
+
"td",
|
|
2016
2094
|
{
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
size: "icon",
|
|
2020
|
-
"aria-label": `${isExpanded ? "Collapse" : "Expand"} row ${row.id}`,
|
|
2021
|
-
"aria-expanded": isExpanded,
|
|
2022
|
-
onClick: (event) => {
|
|
2023
|
-
stopRowClick(event);
|
|
2024
|
-
toggleRowExpanded(row.id);
|
|
2025
|
-
},
|
|
2095
|
+
className: row.index % 2 === 1 ? detailCellProps.classNameOdd : detailCellProps.className,
|
|
2096
|
+
style: detailCellProps.style,
|
|
2026
2097
|
children: /* @__PURE__ */ jsx19(
|
|
2027
|
-
|
|
2098
|
+
Button,
|
|
2028
2099
|
{
|
|
2029
|
-
|
|
2030
|
-
"
|
|
2100
|
+
type: "button",
|
|
2101
|
+
variant: "ghost",
|
|
2102
|
+
size: "icon",
|
|
2103
|
+
"aria-label": `${isExpanded ? "Collapse" : "Expand"} row ${row.id}`,
|
|
2104
|
+
"aria-expanded": isExpanded,
|
|
2105
|
+
onClick: (event) => {
|
|
2106
|
+
stopRowClick(event);
|
|
2107
|
+
toggleRowExpanded(row.id);
|
|
2108
|
+
},
|
|
2109
|
+
children: /* @__PURE__ */ jsx19(
|
|
2110
|
+
ChevronRight,
|
|
2111
|
+
{
|
|
2112
|
+
className: `h-4 w-4 transition-transform${isExpanded ? " rotate-90" : ""}`,
|
|
2113
|
+
"aria-hidden": true
|
|
2114
|
+
}
|
|
2115
|
+
)
|
|
2031
2116
|
}
|
|
2032
2117
|
)
|
|
2033
2118
|
}
|
|
2034
|
-
)
|
|
2035
|
-
showSelectionColumn && /* @__PURE__ */ jsx19(
|
|
2036
|
-
"
|
|
2119
|
+
),
|
|
2120
|
+
showSelectionColumn && /* @__PURE__ */ jsx19(
|
|
2121
|
+
"td",
|
|
2037
2122
|
{
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2123
|
+
className: row.index % 2 === 1 ? selectionCellProps.classNameOdd : selectionCellProps.className,
|
|
2124
|
+
style: selectionCellProps.style,
|
|
2125
|
+
children: /* @__PURE__ */ jsx19(
|
|
2126
|
+
"input",
|
|
2127
|
+
{
|
|
2128
|
+
type: "checkbox",
|
|
2129
|
+
"aria-label": `Select row ${row.id}`,
|
|
2130
|
+
checked: selectedIds.has(row.id),
|
|
2131
|
+
onClick: stopRowClick,
|
|
2132
|
+
onChange: () => toggleRowSelected(row.id)
|
|
2133
|
+
}
|
|
2134
|
+
)
|
|
2043
2135
|
}
|
|
2044
|
-
)
|
|
2136
|
+
),
|
|
2045
2137
|
row.getAllCells().map((cell) => {
|
|
2046
2138
|
const cellProps = bodyCellPropsByColumn.get(cell.column.id);
|
|
2047
2139
|
return /* @__PURE__ */ jsx19("td", { style: cellProps?.style, className: cellProps?.className ?? BODY_TD_BASE_CLASS, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id);
|
|
2048
2140
|
}),
|
|
2049
|
-
showRowActionsColumn && rowActions && /* @__PURE__ */ jsx19(
|
|
2141
|
+
showRowActionsColumn && rowActions && /* @__PURE__ */ jsx19(
|
|
2142
|
+
"td",
|
|
2143
|
+
{
|
|
2144
|
+
className: row.index % 2 === 1 ? rowActionsCellProps.classNameOdd : rowActionsCellProps.className,
|
|
2145
|
+
style: rowActionsCellProps.style,
|
|
2146
|
+
children: /* @__PURE__ */ jsx19(ActionsMenu, { items: rowActions, ctx: { row: row.original }, triggerLabel: `Row actions for ${row.id}` })
|
|
2147
|
+
}
|
|
2148
|
+
)
|
|
2050
2149
|
]
|
|
2051
2150
|
}
|
|
2052
2151
|
),
|
|
2053
|
-
showDetailColumn && isExpanded && renderDetail && /* @__PURE__ */ jsx19("tr", { "data-testid": `${rowTestId}-detail`, children: /* @__PURE__ */ jsx19("td", { colSpan: totalColumnCount, className: "border-b p-2", children: renderDetail(row.original) }) })
|
|
2152
|
+
showDetailColumn && isExpanded && renderDetail && /* @__PURE__ */ jsx19("tr", { "data-testid": `${rowTestId}-detail`, children: /* @__PURE__ */ jsx19("td", { colSpan: totalColumnCount, className: "border-b border-border p-2", children: renderDetail(row.original) }) })
|
|
2054
2153
|
] }, row.id);
|
|
2055
2154
|
}
|
|
2155
|
+
function renderGroupedBucket(bucket) {
|
|
2156
|
+
const expanded = isGroupExpanded(bucket.key);
|
|
2157
|
+
const originalRows = bucket.items.map((row) => row.original);
|
|
2158
|
+
return /* @__PURE__ */ jsxs12(Fragment2, { children: [
|
|
2159
|
+
/* @__PURE__ */ jsx19("tbody", { children: /* @__PURE__ */ jsx19("tr", { "data-testid": `group-header-${bucket.key}`, children: /* @__PURE__ */ jsx19("td", { colSpan: totalColumnCount, className: "border-b bg-muted/50 p-2 font-medium", children: /* @__PURE__ */ jsxs12(
|
|
2160
|
+
"button",
|
|
2161
|
+
{
|
|
2162
|
+
type: "button",
|
|
2163
|
+
className: "flex w-full items-center gap-1 text-left",
|
|
2164
|
+
onClick: () => toggleGroupExpanded(bucket.key),
|
|
2165
|
+
"aria-expanded": expanded,
|
|
2166
|
+
children: [
|
|
2167
|
+
/* @__PURE__ */ jsx19(
|
|
2168
|
+
ChevronRight,
|
|
2169
|
+
{
|
|
2170
|
+
className: `h-4 w-4 shrink-0 transition-transform${expanded ? " rotate-90" : ""}`,
|
|
2171
|
+
"aria-hidden": true
|
|
2172
|
+
}
|
|
2173
|
+
),
|
|
2174
|
+
renderGroupHeader ? renderGroupHeader(bucket.key, originalRows, expanded) : `${bucket.key} (${originalRows.length})`
|
|
2175
|
+
]
|
|
2176
|
+
}
|
|
2177
|
+
) }) }) }),
|
|
2178
|
+
expanded && bucket.items.map((row) => renderRow(row))
|
|
2179
|
+
] }, `group-${bucket.key}`);
|
|
2180
|
+
}
|
|
2056
2181
|
return /* @__PURE__ */ jsxs12("div", { className: "flex flex-col gap-2", "data-testid": "datagrid", children: [
|
|
2057
2182
|
showSelectionColumn && headerActions && /* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-between text-sm", children: [
|
|
2058
2183
|
/* @__PURE__ */ jsxs12("span", { children: [
|
|
@@ -2108,7 +2233,16 @@ function DataGrid({
|
|
|
2108
2233
|
}
|
|
2109
2234
|
),
|
|
2110
2235
|
filterable && /* @__PURE__ */ jsxs12(Popover, { children: [
|
|
2111
|
-
/* @__PURE__ */ jsx19(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx19(Button, { variant: "ghost", size: "icon", "aria-label": `Filter ${column.header}`, children: /* @__PURE__ */ jsx19(
|
|
2236
|
+
/* @__PURE__ */ jsx19(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx19(Button, { variant: "ghost", size: "icon", "aria-label": `Filter ${column.header}`, children: /* @__PURE__ */ jsx19(
|
|
2237
|
+
FunnelIcon7,
|
|
2238
|
+
{
|
|
2239
|
+
className: cn(
|
|
2240
|
+
"h-3 w-3",
|
|
2241
|
+
filtersByColumn.get(column.id) !== void 0 ? "opacity-100 text-primary" : "opacity-40"
|
|
2242
|
+
),
|
|
2243
|
+
"aria-hidden": true
|
|
2244
|
+
}
|
|
2245
|
+
) }) }),
|
|
2112
2246
|
/* @__PURE__ */ jsx19(PopoverContent, { children: renderFilterWidget(column) })
|
|
2113
2247
|
] })
|
|
2114
2248
|
] }),
|
|
@@ -2124,7 +2258,7 @@ function DataGrid({
|
|
|
2124
2258
|
] }, header.id);
|
|
2125
2259
|
}
|
|
2126
2260
|
return /* @__PURE__ */ jsxs12(Fragment2, { children: [
|
|
2127
|
-
hasHeaderGroups && /* @__PURE__ */ jsxs12("tr", { "data-testid": "header-group-row", children: [
|
|
2261
|
+
hasHeaderGroups && /* @__PURE__ */ jsxs12("tr", { "data-testid": "header-group-row", className: "divide-x divide-border", children: [
|
|
2128
2262
|
showDetailColumn && /* @__PURE__ */ jsx19(
|
|
2129
2263
|
"th",
|
|
2130
2264
|
{
|
|
@@ -2156,7 +2290,7 @@ function DataGrid({
|
|
|
2156
2290
|
"th",
|
|
2157
2291
|
{
|
|
2158
2292
|
colSpan: run.columns.length,
|
|
2159
|
-
className: "border-b p-2 text-center font-medium",
|
|
2293
|
+
className: "border-b border-border p-2 text-center font-medium",
|
|
2160
2294
|
children: run.label
|
|
2161
2295
|
},
|
|
2162
2296
|
`group-${run.label}-${run.columns[0].id}`
|
|
@@ -2172,7 +2306,7 @@ function DataGrid({
|
|
|
2172
2306
|
}
|
|
2173
2307
|
)
|
|
2174
2308
|
] }, `${headerGroup.id}-groups`),
|
|
2175
|
-
/* @__PURE__ */ jsxs12("tr", { children: [
|
|
2309
|
+
/* @__PURE__ */ jsxs12("tr", { className: "divide-x divide-border", children: [
|
|
2176
2310
|
!hasHeaderGroups && showDetailColumn && /* @__PURE__ */ jsx19(
|
|
2177
2311
|
"th",
|
|
2178
2312
|
{
|
|
@@ -2213,7 +2347,7 @@ function DataGrid({
|
|
|
2213
2347
|
] }, headerGroup.id)
|
|
2214
2348
|
] }, headerGroup.id);
|
|
2215
2349
|
}),
|
|
2216
|
-
hasFilterRow && /* @__PURE__ */ jsxs12("tr", { "data-testid": "filter-row", children: [
|
|
2350
|
+
hasFilterRow && /* @__PURE__ */ jsxs12("tr", { "data-testid": "filter-row", className: "divide-x divide-border", children: [
|
|
2217
2351
|
showDetailColumn && /* @__PURE__ */ jsx19("th", { className: detailHeaderCellProps.className, style: detailHeaderCellProps.style }),
|
|
2218
2352
|
showSelectionColumn && /* @__PURE__ */ jsx19(
|
|
2219
2353
|
"th",
|
|
@@ -2241,7 +2375,7 @@ function DataGrid({
|
|
|
2241
2375
|
(virtualItem) => renderRow(tableRows[virtualItem.index], (el) => virtualizer.measureElement(el))
|
|
2242
2376
|
),
|
|
2243
2377
|
paddingBottom > 0 && /* @__PURE__ */ jsx19("tbody", { children: /* @__PURE__ */ jsx19("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx19("td", { colSpan: totalColumnCount, style: { padding: 0, border: "none", height: paddingBottom } }) }) })
|
|
2244
|
-
] }) : tableRows.map((row) => renderRow(row))
|
|
2378
|
+
] }) : groupedBuckets ? groupedBuckets.map((bucket) => renderGroupedBucket(bucket)) : tableRows.map((row) => renderRow(row))
|
|
2245
2379
|
] })
|
|
2246
2380
|
}
|
|
2247
2381
|
)
|
|
@@ -2306,6 +2440,31 @@ function flattenTree(roots, accessors, expanded, childrenOverride) {
|
|
|
2306
2440
|
return out;
|
|
2307
2441
|
}
|
|
2308
2442
|
|
|
2443
|
+
// src/tree/selectionState.ts
|
|
2444
|
+
function computeSelectionStates(roots, accessors, childrenOverride, selectedIds) {
|
|
2445
|
+
const { getRowId, getChildren } = accessors;
|
|
2446
|
+
const out = /* @__PURE__ */ new Map();
|
|
2447
|
+
function visit(row) {
|
|
2448
|
+
const id = getRowId(row);
|
|
2449
|
+
if (selectedIds.has(id)) {
|
|
2450
|
+
const state2 = { checked: true, indeterminate: false };
|
|
2451
|
+
out.set(id, state2);
|
|
2452
|
+
return state2;
|
|
2453
|
+
}
|
|
2454
|
+
const children = childrenOverride.get(id) ?? getChildren(row) ?? [];
|
|
2455
|
+
let anySelectedOrIndeterminate = false;
|
|
2456
|
+
for (const child of children) {
|
|
2457
|
+
const childState = visit(child);
|
|
2458
|
+
if (childState.checked || childState.indeterminate) anySelectedOrIndeterminate = true;
|
|
2459
|
+
}
|
|
2460
|
+
const state = { checked: false, indeterminate: anySelectedOrIndeterminate };
|
|
2461
|
+
out.set(id, state);
|
|
2462
|
+
return state;
|
|
2463
|
+
}
|
|
2464
|
+
for (const root of roots) visit(root);
|
|
2465
|
+
return out;
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2309
2468
|
// src/tree/useTreeState.ts
|
|
2310
2469
|
import { useCallback as useCallback3, useMemo as useMemo7, useState as useState7 } from "react";
|
|
2311
2470
|
function useTreeState({
|
|
@@ -2412,6 +2571,8 @@ function useTreeState({
|
|
|
2412
2571
|
|
|
2413
2572
|
// src/tree/TreeDataGrid.tsx
|
|
2414
2573
|
import { Fragment as Fragment4, jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2574
|
+
var EMPTY_SELECTED_IDS = /* @__PURE__ */ new Set();
|
|
2575
|
+
var UNCHECKED_STATE = { checked: false, indeterminate: false };
|
|
2415
2576
|
function renderCell(column, row) {
|
|
2416
2577
|
const value = getColumnValue(column, row);
|
|
2417
2578
|
return column.cell ? column.cell(value, row) : defaultFormat(column, value);
|
|
@@ -2466,7 +2627,11 @@ function TreeDataGrid({
|
|
|
2466
2627
|
rowActions,
|
|
2467
2628
|
virtualizeThreshold = 100,
|
|
2468
2629
|
estimatedRowHeight = 40,
|
|
2469
|
-
maxBodyHeight = 480
|
|
2630
|
+
maxBodyHeight = 480,
|
|
2631
|
+
selectedIds: controlledSelectedIds,
|
|
2632
|
+
onSelectedIdsChange,
|
|
2633
|
+
getRowSelectionState,
|
|
2634
|
+
isRowSelectionDisabled
|
|
2470
2635
|
}) {
|
|
2471
2636
|
const accessors = useMemo8(
|
|
2472
2637
|
() => ({ getRowId, getChildren, hasChildren }),
|
|
@@ -2493,7 +2658,38 @@ function TreeDataGrid({
|
|
|
2493
2658
|
);
|
|
2494
2659
|
const treeColId = treeColumnId ?? columns[0]?.id;
|
|
2495
2660
|
const showRowActionsColumn = Boolean(rowActions?.length);
|
|
2496
|
-
const
|
|
2661
|
+
const showSelectionColumn = controlledSelectedIds !== void 0;
|
|
2662
|
+
const totalColumnCount = columns.length + (showRowActionsColumn ? 1 : 0) + (showSelectionColumn ? 1 : 0);
|
|
2663
|
+
const selectionStates = useMemo8(
|
|
2664
|
+
() => showSelectionColumn ? computeSelectionStates(data, accessors, childrenMap, controlledSelectedIds ?? EMPTY_SELECTED_IDS) : void 0,
|
|
2665
|
+
[showSelectionColumn, data, accessors, childrenMap, controlledSelectedIds]
|
|
2666
|
+
);
|
|
2667
|
+
function resolveSelectionState(row, id) {
|
|
2668
|
+
const override = getRowSelectionState?.(row);
|
|
2669
|
+
if (override) return { checked: override.checked, indeterminate: override.indeterminate ?? false };
|
|
2670
|
+
return selectionStates?.get(id) ?? UNCHECKED_STATE;
|
|
2671
|
+
}
|
|
2672
|
+
function toggleRowSelected(id) {
|
|
2673
|
+
if (!controlledSelectedIds) return;
|
|
2674
|
+
const next = new Set(controlledSelectedIds);
|
|
2675
|
+
if (next.has(id)) next.delete(id);
|
|
2676
|
+
else next.add(id);
|
|
2677
|
+
onSelectedIdsChange?.(next);
|
|
2678
|
+
}
|
|
2679
|
+
const selectableFlatRows = useMemo8(
|
|
2680
|
+
() => flatRows.filter((flatRow) => !(isRowSelectionDisabled?.(flatRow.row) ?? false)),
|
|
2681
|
+
[flatRows, isRowSelectionDisabled]
|
|
2682
|
+
);
|
|
2683
|
+
const allVisibleSelected = showSelectionColumn && selectableFlatRows.length > 0 && selectableFlatRows.every((flatRow) => controlledSelectedIds.has(flatRow.id));
|
|
2684
|
+
function toggleAllVisibleSelected() {
|
|
2685
|
+
if (!controlledSelectedIds) return;
|
|
2686
|
+
const next = new Set(controlledSelectedIds);
|
|
2687
|
+
for (const flatRow of selectableFlatRows) {
|
|
2688
|
+
if (allVisibleSelected) next.delete(flatRow.id);
|
|
2689
|
+
else next.add(flatRow.id);
|
|
2690
|
+
}
|
|
2691
|
+
onSelectedIdsChange?.(next);
|
|
2692
|
+
}
|
|
2497
2693
|
const bodyTdClassByColumn = useMemo8(() => {
|
|
2498
2694
|
const map = /* @__PURE__ */ new Map();
|
|
2499
2695
|
for (const column of columns) map.set(column.id, `p-2 ${alignClassName(column)}`);
|
|
@@ -2517,8 +2713,25 @@ function TreeDataGrid({
|
|
|
2517
2713
|
ref: measureRef,
|
|
2518
2714
|
...rowProps,
|
|
2519
2715
|
"data-testid": rowTestId,
|
|
2520
|
-
className: rowProps?.className ? cn(
|
|
2716
|
+
className: rowProps?.className ? cn(
|
|
2717
|
+
"border-b border-border divide-x divide-border",
|
|
2718
|
+
rowProps.className,
|
|
2719
|
+
index % 2 === 1 && "bg-foreground/5"
|
|
2720
|
+
) : `border-b border-border divide-x divide-border${index % 2 === 1 ? " bg-foreground/5" : ""}`,
|
|
2521
2721
|
children: [
|
|
2722
|
+
showSelectionColumn && /* @__PURE__ */ jsx20("td", { className: "p-1", style: { width: SELECTION_COLUMN_WIDTH }, children: (() => {
|
|
2723
|
+
const state = resolveSelectionState(flatRow.row, flatRow.id);
|
|
2724
|
+
return /* @__PURE__ */ jsx20(
|
|
2725
|
+
Checkbox,
|
|
2726
|
+
{
|
|
2727
|
+
"aria-label": `Select row ${flatRow.id}`,
|
|
2728
|
+
checked: state.indeterminate ? "indeterminate" : state.checked,
|
|
2729
|
+
disabled: isRowSelectionDisabled?.(flatRow.row) ?? false,
|
|
2730
|
+
onCheckedChange: () => toggleRowSelected(flatRow.id),
|
|
2731
|
+
onClick: stopRowClick
|
|
2732
|
+
}
|
|
2733
|
+
);
|
|
2734
|
+
})() }),
|
|
2522
2735
|
columns.map((column) => /* @__PURE__ */ jsx20("td", { className: bodyTdClassByColumn.get(column.id), children: column.id === treeColId ? /* @__PURE__ */ jsx20(
|
|
2523
2736
|
TreeCell,
|
|
2524
2737
|
{
|
|
@@ -2554,17 +2767,25 @@ function TreeDataGrid({
|
|
|
2554
2767
|
className: shouldVirtualize ? "overflow-y-auto" : void 0,
|
|
2555
2768
|
style: shouldVirtualize ? { maxHeight: maxBodyHeight } : void 0,
|
|
2556
2769
|
children: /* @__PURE__ */ jsxs13("table", { className: "w-full border-collapse text-sm", children: [
|
|
2557
|
-
/* @__PURE__ */ jsx20("thead", { children: /* @__PURE__ */ jsxs13("tr", { children: [
|
|
2770
|
+
/* @__PURE__ */ jsx20("thead", { children: /* @__PURE__ */ jsxs13("tr", { className: "divide-x divide-border", children: [
|
|
2771
|
+
showSelectionColumn && /* @__PURE__ */ jsx20("th", { className: "border-b border-border p-1", style: { width: SELECTION_COLUMN_WIDTH }, children: /* @__PURE__ */ jsx20(
|
|
2772
|
+
Checkbox,
|
|
2773
|
+
{
|
|
2774
|
+
"aria-label": "Select all visible rows",
|
|
2775
|
+
checked: allVisibleSelected,
|
|
2776
|
+
onCheckedChange: toggleAllVisibleSelected
|
|
2777
|
+
}
|
|
2778
|
+
) }),
|
|
2558
2779
|
columns.map((column) => /* @__PURE__ */ jsx20(
|
|
2559
2780
|
"th",
|
|
2560
2781
|
{
|
|
2561
2782
|
style: column.width ? { width: column.width } : void 0,
|
|
2562
|
-
className: `border-b p-2 font-medium ${alignClassName(column)}`,
|
|
2783
|
+
className: `border-b border-border p-2 font-medium ${alignClassName(column)}`,
|
|
2563
2784
|
children: column.header
|
|
2564
2785
|
},
|
|
2565
2786
|
column.id
|
|
2566
2787
|
)),
|
|
2567
|
-
showRowActionsColumn && /* @__PURE__ */ jsx20("th", { className: "border-b p-2", "aria-label": "Row actions" })
|
|
2788
|
+
showRowActionsColumn && /* @__PURE__ */ jsx20("th", { className: "border-b border-border p-2", "aria-label": "Row actions" })
|
|
2568
2789
|
] }) }),
|
|
2569
2790
|
/* @__PURE__ */ jsx20("tbody", { children: flatRows.length === 0 ? /* @__PURE__ */ jsx20("tr", { children: /* @__PURE__ */ jsx20("td", { colSpan: totalColumnCount, className: "p-4 text-center text-muted-foreground", children: "No results." }) }) : shouldVirtualize ? /* @__PURE__ */ jsxs13(Fragment4, { children: [
|
|
2570
2791
|
paddingTop > 0 && /* @__PURE__ */ jsx20("tr", { "aria-hidden": "true", children: /* @__PURE__ */ jsx20("td", { colSpan: totalColumnCount, style: { padding: 0, border: "none", height: paddingTop } }) }),
|