@cdx-ui/data-grid 0.3.12 → 0.3.14
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/coverage.svg +1 -1
- package/lib/commonjs/DataGrid.js +279 -40
- package/lib/commonjs/DataGrid.js.map +1 -1
- package/lib/commonjs/DataGridFilterPanel.js +136 -9
- package/lib/commonjs/DataGridFilterPanel.js.map +1 -1
- package/lib/commonjs/adapter/columns.js +16 -8
- package/lib/commonjs/adapter/columns.js.map +1 -1
- package/lib/commonjs/adapter/pinning.js +5 -1
- package/lib/commonjs/adapter/pinning.js.map +1 -1
- package/lib/commonjs/adapter/selectColumn.js +11 -0
- package/lib/commonjs/adapter/selectColumn.js.map +1 -1
- package/lib/commonjs/adapter/treeData.js +195 -0
- package/lib/commonjs/adapter/treeData.js.map +1 -0
- package/lib/commonjs/styles.js +48 -2
- package/lib/commonjs/styles.js.map +1 -1
- package/lib/commonjs/types.js +11 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/useDataGridTable.js +143 -12
- package/lib/commonjs/useDataGridTable.js.map +1 -1
- package/lib/module/DataGrid.js +285 -46
- package/lib/module/DataGrid.js.map +1 -1
- package/lib/module/DataGridFilterPanel.js +139 -13
- package/lib/module/DataGridFilterPanel.js.map +1 -1
- package/lib/module/adapter/columns.js +17 -9
- package/lib/module/adapter/columns.js.map +1 -1
- package/lib/module/adapter/pinning.js +5 -1
- package/lib/module/adapter/pinning.js.map +1 -1
- package/lib/module/adapter/selectColumn.js +11 -1
- package/lib/module/adapter/selectColumn.js.map +1 -1
- package/lib/module/adapter/treeData.js +187 -0
- package/lib/module/adapter/treeData.js.map +1 -0
- package/lib/module/styles.js +47 -1
- package/lib/module/styles.js.map +1 -1
- package/lib/module/types.js +10 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/useDataGridTable.js +147 -16
- package/lib/module/useDataGridTable.js.map +1 -1
- package/lib/typescript/DataGrid.d.ts.map +1 -1
- package/lib/typescript/DataGridFilterPanel.d.ts +39 -4
- package/lib/typescript/DataGridFilterPanel.d.ts.map +1 -1
- package/lib/typescript/adapter/columns.d.ts +1 -1
- package/lib/typescript/adapter/columns.d.ts.map +1 -1
- package/lib/typescript/adapter/pinning.d.ts +1 -0
- package/lib/typescript/adapter/pinning.d.ts.map +1 -1
- package/lib/typescript/adapter/selectColumn.d.ts +8 -0
- package/lib/typescript/adapter/selectColumn.d.ts.map +1 -1
- package/lib/typescript/adapter/treeData.d.ts +36 -0
- package/lib/typescript/adapter/treeData.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/styles.d.ts +12 -1
- package/lib/typescript/styles.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +68 -0
- package/lib/typescript/types.d.ts.map +1 -1
- package/lib/typescript/useDataGridTable.d.ts +25 -3
- package/lib/typescript/useDataGridTable.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/DataGrid.tsx +399 -59
- package/src/DataGridFilterPanel.tsx +188 -17
- package/src/adapter/columns.ts +21 -7
- package/src/adapter/pinning.ts +10 -1
- package/src/adapter/selectColumn.ts +12 -1
- package/src/adapter/treeData.ts +228 -0
- package/src/index.ts +4 -0
- package/src/styles.ts +57 -1
- package/src/types.ts +76 -0
- package/src/useDataGridTable.ts +217 -15
package/src/styles.ts
CHANGED
|
@@ -180,8 +180,21 @@ export const dataGridBodyVariants = cva(
|
|
|
180
180
|
|
|
181
181
|
export const dataGridFilterInputVariants = cva('min-w-24');
|
|
182
182
|
|
|
183
|
+
// The toolbar panel sits at a fixed spot in the grid root. A panel opened from a column is anchored
|
|
184
|
+
// to its opener in a portal, so its position and width cap come from the anchored overlay instead.
|
|
183
185
|
export const dataGridFilterPanelVariants = cva(
|
|
184
|
-
'
|
|
186
|
+
'w-max gap-3 rounded-lg border border-stroke-secondary bg-surface-primary px-3 pb-2 pt-3 shadow-lg',
|
|
187
|
+
{
|
|
188
|
+
variants: {
|
|
189
|
+
anchored: {
|
|
190
|
+
true: '',
|
|
191
|
+
false: 'absolute start-4 top-16 z-30 max-w-[calc(100%-2rem)]',
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
defaultVariants: {
|
|
195
|
+
anchored: false,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
185
198
|
);
|
|
186
199
|
|
|
187
200
|
// The panel is as wide as one unwrapped row, capped at the grid's width. Below that, fields wrap
|
|
@@ -234,6 +247,49 @@ export const dataGridCellVariants = cva([
|
|
|
234
247
|
}),
|
|
235
248
|
]);
|
|
236
249
|
|
|
250
|
+
export const dataGridDetailToggleCellVariants = cva([
|
|
251
|
+
'w-12 px-2',
|
|
252
|
+
Platform.select({
|
|
253
|
+
web: 'border-b border-stroke-secondary',
|
|
254
|
+
default: '',
|
|
255
|
+
}),
|
|
256
|
+
]);
|
|
257
|
+
|
|
258
|
+
// Header labels and depth-0 content line up because both reserve the toggle slot.
|
|
259
|
+
export const dataGridTreeCellInnerVariants = cva('min-w-0 flex-1 flex-row items-center gap-2');
|
|
260
|
+
|
|
261
|
+
export const dataGridTreeToggleSlotVariants = cva('size-8 shrink-0 items-center justify-center');
|
|
262
|
+
|
|
263
|
+
export const dataGridTreeContentVariants = cva('min-w-0 flex-1 flex-row items-center');
|
|
264
|
+
|
|
265
|
+
export const dataGridDetailPanelCellVariants = cva([
|
|
266
|
+
'px-4 py-3',
|
|
267
|
+
Platform.select({
|
|
268
|
+
web: 'border-b border-stroke-secondary bg-surface-primary',
|
|
269
|
+
default: '',
|
|
270
|
+
}),
|
|
271
|
+
]);
|
|
272
|
+
|
|
273
|
+
// Same `w-0 min-w-full` trick as the header label: a declared column width truncates the value
|
|
274
|
+
// with an ellipsis instead of wrapping it onto a second line and growing the fixed-height row.
|
|
275
|
+
export const dataGridCellTextVariants = cva('min-w-0', {
|
|
276
|
+
variants: {
|
|
277
|
+
align: {
|
|
278
|
+
start: 'text-start',
|
|
279
|
+
center: 'text-center',
|
|
280
|
+
end: 'text-end',
|
|
281
|
+
},
|
|
282
|
+
fitToColumn: {
|
|
283
|
+
true: 'w-0 min-w-full',
|
|
284
|
+
false: '',
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
defaultVariants: {
|
|
288
|
+
align: 'start',
|
|
289
|
+
fitToColumn: false,
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
237
293
|
export const dataGridSelectCellVariants = cva([
|
|
238
294
|
'w-14 px-4',
|
|
239
295
|
Platform.select({
|
package/src/types.ts
CHANGED
|
@@ -166,6 +166,31 @@ export type GridFeatureMode = 'client' | 'server';
|
|
|
166
166
|
|
|
167
167
|
export type GridPaginationMode = GridFeatureMode;
|
|
168
168
|
|
|
169
|
+
/** Maps a column `field` to `false` to hide it. Fields that are missing or `true` stay visible. */
|
|
170
|
+
export type GridColumnVisibilityModel = Record<string, boolean>;
|
|
171
|
+
|
|
172
|
+
export interface GridRowParams<R extends GridValidRowModel = GridValidRowModel> {
|
|
173
|
+
id: GridRowId;
|
|
174
|
+
row: R;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface GridTreeNodeParams<
|
|
178
|
+
R extends GridValidRowModel = GridValidRowModel,
|
|
179
|
+
> extends GridRowParams<R> {
|
|
180
|
+
/** `0` for top-level rows. */
|
|
181
|
+
depth: number;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* How a selection change spreads through `treeData`. Both default to `true`, as in MUI.
|
|
186
|
+
*/
|
|
187
|
+
export interface GridRowSelectionPropagation {
|
|
188
|
+
/** Selecting or deselecting a parent applies to its descendants that pass the current filter. */
|
|
189
|
+
descendants?: boolean;
|
|
190
|
+
/** A parent is selected exactly when all of its children are selected. */
|
|
191
|
+
parents?: boolean;
|
|
192
|
+
}
|
|
193
|
+
|
|
169
194
|
export interface DataGridProps<R extends GridValidRowModel = GridValidRowModel> {
|
|
170
195
|
rows: GridRowsProp<R>;
|
|
171
196
|
columns: readonly GridColDef<R>[];
|
|
@@ -225,7 +250,53 @@ export interface DataGridProps<R extends GridValidRowModel = GridValidRowModel>
|
|
|
225
250
|
* MUI X Community ships no column pinning; this is a Pro feature.
|
|
226
251
|
*/
|
|
227
252
|
pinnedColumns?: GridPinnedColumnFields;
|
|
253
|
+
/**
|
|
254
|
+
* Hides columns without removing them from `columns`. Hidden columns still sort, filter, and
|
|
255
|
+
* match the quick filter, and the filter panel still lists them. The grid has no built-in
|
|
256
|
+
* control that changes visibility, so keep the model in your own state.
|
|
257
|
+
*/
|
|
258
|
+
columnVisibilityModel?: GridColumnVisibilityModel;
|
|
228
259
|
headerFilters?: boolean;
|
|
260
|
+
/**
|
|
261
|
+
* Renders `rows` as a tree. Each row's position comes from `getTreeDataPath`. The first column
|
|
262
|
+
* becomes the tree column: it indents by depth and holds the expand toggle and, with
|
|
263
|
+
* `checkboxSelection`, the row checkbox. Search and filters keep the ancestors of every match
|
|
264
|
+
* and expand them while the filter is active.
|
|
265
|
+
*
|
|
266
|
+
* Client mode only: server pagination, sorting, and filtering are not supported with tree data.
|
|
267
|
+
* Pagination counts top-level rows, so a group never splits across pages.
|
|
268
|
+
* @default false
|
|
269
|
+
*/
|
|
270
|
+
treeData?: boolean;
|
|
271
|
+
/**
|
|
272
|
+
* Path from the root to the row, for example `['Accounts', 'OAuth V1', 'List accounts']`.
|
|
273
|
+
* Paths must be unique. A row whose parent path has no row attaches to its nearest existing
|
|
274
|
+
* ancestor. Keep the function reference stable.
|
|
275
|
+
*/
|
|
276
|
+
getTreeDataPath?: (row: R) => readonly string[];
|
|
277
|
+
/**
|
|
278
|
+
* Overrides the tree column (the first column) when `treeData` is on: header name, width,
|
|
279
|
+
* `renderCell`, and so on. `field` stays the first column's field.
|
|
280
|
+
*/
|
|
281
|
+
groupingColDef?: Partial<Omit<GridBaseColDef<R>, 'field'>>;
|
|
282
|
+
/** Depth expanded on first render. `0` collapses every group; `-1` expands all. @default 0 */
|
|
283
|
+
defaultGroupingExpansionDepth?: number;
|
|
284
|
+
/** Takes priority over `defaultGroupingExpansionDepth` for the initial state of a group. */
|
|
285
|
+
isGroupExpandedByDefault?: (node: GridTreeNodeParams<R>) => boolean;
|
|
286
|
+
/**
|
|
287
|
+
* How selection spreads between parents and children with `treeData`. Unlike MUI, rows hidden
|
|
288
|
+
* by a filter stay selected, so a search-then-pick form keeps earlier picks.
|
|
289
|
+
*/
|
|
290
|
+
rowSelectionPropagation?: GridRowSelectionPropagation;
|
|
291
|
+
/**
|
|
292
|
+
* Content of the expandable panel under a row (master-detail). Adds a toggle column at the
|
|
293
|
+
* start. Rows for which this returns `null` or `undefined` get no toggle.
|
|
294
|
+
*/
|
|
295
|
+
getDetailPanelContent?: (params: GridRowParams<R>) => ReactNode;
|
|
296
|
+
/** Ids of rows whose detail panel is open (controlled). */
|
|
297
|
+
detailPanelExpandedRowIds?: ReadonlySet<GridRowId>;
|
|
298
|
+
defaultDetailPanelExpandedRowIds?: ReadonlySet<GridRowId>;
|
|
299
|
+
onDetailPanelExpandedRowIdsChange?: (ids: Set<GridRowId>) => void;
|
|
229
300
|
/** Replaces the default centered "No rows" message. Shown when `rows` is empty and not loading. */
|
|
230
301
|
noRowsOverlay?: ReactNode;
|
|
231
302
|
/** Replaces the default skeleton rows shown when `loading` is true. */
|
|
@@ -239,6 +310,11 @@ export interface DataGridProps<R extends GridValidRowModel = GridValidRowModel>
|
|
|
239
310
|
|
|
240
311
|
export const SELECT_COLUMN_ID = '__select__';
|
|
241
312
|
|
|
313
|
+
/** Same id as MUI X's master-detail toggle column. */
|
|
314
|
+
export const DETAIL_PANEL_TOGGLE_ID = '__detail_panel_toggle__';
|
|
315
|
+
|
|
316
|
+
export const EMPTY_ROW_ID_SET: ReadonlySet<GridRowId> = new Set();
|
|
317
|
+
|
|
242
318
|
/** MUI X Data Grid checkbox-column field; accepted as an alias for {@link SELECT_COLUMN_ID}. */
|
|
243
319
|
export const MUI_CHECKBOX_FIELD = '__check__';
|
|
244
320
|
|
package/src/useDataGridTable.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
1
|
+
import { useMemo, useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
columnFilteringFeature,
|
|
4
4
|
columnPinningFeature,
|
|
5
|
+
columnVisibilityFeature,
|
|
5
6
|
createFilteredRowModel,
|
|
6
7
|
createPaginatedRowModel,
|
|
7
8
|
createSortedRowModel,
|
|
8
9
|
filterFn_includesString,
|
|
9
10
|
globalFilteringFeature,
|
|
11
|
+
rowExpandingFeature,
|
|
10
12
|
rowPaginationFeature,
|
|
11
13
|
rowSelectionFeature,
|
|
12
14
|
rowSortingFeature,
|
|
@@ -20,7 +22,16 @@ import {
|
|
|
20
22
|
type RowSelectionState,
|
|
21
23
|
type SortingState,
|
|
22
24
|
} from '@tanstack/react-table';
|
|
23
|
-
import { createSelectColumn } from './adapter/selectColumn';
|
|
25
|
+
import { createDetailPanelToggleColumn, createSelectColumn } from './adapter/selectColumn';
|
|
26
|
+
import {
|
|
27
|
+
applyTreeSelection,
|
|
28
|
+
buildTreeIndex,
|
|
29
|
+
computeTreeSelection,
|
|
30
|
+
keepMatchesWithAncestors,
|
|
31
|
+
selectionScope,
|
|
32
|
+
type GridTreeRowLike,
|
|
33
|
+
type GridTreeSelectionStatus,
|
|
34
|
+
} from './adapter/treeData';
|
|
24
35
|
import { applyUpdater, gridColDefToColumnDef } from './adapter/columns';
|
|
25
36
|
import {
|
|
26
37
|
filterFn_displayText,
|
|
@@ -42,6 +53,7 @@ import {
|
|
|
42
53
|
} from './adapter/filterOperators';
|
|
43
54
|
import type {
|
|
44
55
|
GridColDef,
|
|
56
|
+
GridColumnVisibilityModel,
|
|
45
57
|
GridFeatureMode,
|
|
46
58
|
GridFilterItem,
|
|
47
59
|
GridFilterModel,
|
|
@@ -49,18 +61,20 @@ import type {
|
|
|
49
61
|
GridPaginationModel,
|
|
50
62
|
GridRowId,
|
|
51
63
|
GridRowSelectionModel,
|
|
64
|
+
GridRowSelectionPropagation,
|
|
52
65
|
GridRowsProp,
|
|
53
66
|
GridSortModel,
|
|
67
|
+
GridTreeNodeParams,
|
|
54
68
|
GridValidRowModel,
|
|
55
69
|
} from './types';
|
|
56
|
-
import { SELECT_COLUMN_ID } from './types';
|
|
70
|
+
import { DETAIL_PANEL_TOGGLE_ID, SELECT_COLUMN_ID } from './types';
|
|
57
71
|
|
|
58
72
|
function gridGlobalFilterFn(
|
|
59
73
|
row: Parameters<typeof filterFn_displayText>[0],
|
|
60
74
|
columnId: string,
|
|
61
75
|
filterValue: unknown,
|
|
62
76
|
): boolean {
|
|
63
|
-
if (columnId === SELECT_COLUMN_ID) {
|
|
77
|
+
if (columnId === SELECT_COLUMN_ID || columnId === DETAIL_PANEL_TOGGLE_ID) {
|
|
64
78
|
return false;
|
|
65
79
|
}
|
|
66
80
|
const meta = row.getAllCells().find((cell) => cell.column.id === columnId)?.column.columnDef.meta;
|
|
@@ -73,7 +87,9 @@ function gridGlobalFilterFn(
|
|
|
73
87
|
const features = tableFeatures({
|
|
74
88
|
columnFilteringFeature,
|
|
75
89
|
columnPinningFeature,
|
|
90
|
+
columnVisibilityFeature,
|
|
76
91
|
globalFilteringFeature,
|
|
92
|
+
rowExpandingFeature,
|
|
77
93
|
rowSortingFeature,
|
|
78
94
|
rowPaginationFeature,
|
|
79
95
|
rowSelectionFeature,
|
|
@@ -275,8 +291,30 @@ interface UseDataGridTableParams<R extends GridValidRowModel> {
|
|
|
275
291
|
rowSelectionModel: GridRowSelectionModel;
|
|
276
292
|
onRowSelectionModelChange: (model: GridRowSelectionModel) => void;
|
|
277
293
|
columnPinning: ColumnPinningState;
|
|
294
|
+
columnVisibility?: GridColumnVisibilityModel;
|
|
295
|
+
detailPanel?: boolean;
|
|
296
|
+
treeData?: boolean;
|
|
297
|
+
getTreeDataPath?: (row: R) => readonly string[];
|
|
298
|
+
defaultGroupingExpansionDepth?: number;
|
|
299
|
+
isGroupExpandedByDefault?: (node: GridTreeNodeParams<R>) => boolean;
|
|
300
|
+
rowSelectionPropagation?: GridRowSelectionPropagation;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export interface DataGridTreeState {
|
|
304
|
+
isRowExpanded: (key: string) => boolean;
|
|
305
|
+
toggleRowExpanded: (key: string) => void;
|
|
306
|
+
selectionStatus: (key: string) => GridTreeSelectionStatus;
|
|
307
|
+
toggleRowSelected: (row: GridTreeRowLike) => void;
|
|
308
|
+
toggleAllRowsSelected: () => void;
|
|
309
|
+
headerSelection: { checked: boolean; indeterminate: boolean };
|
|
278
310
|
}
|
|
279
311
|
|
|
312
|
+
const EMPTY_EXPANSION: Readonly<Record<string, boolean>> = {};
|
|
313
|
+
const EMPTY_FILTER_EXPANSION: Readonly<Partial<Record<string, boolean>>> = {};
|
|
314
|
+
const EMPTY_VISIBILITY: GridColumnVisibilityModel = {};
|
|
315
|
+
|
|
316
|
+
let warnedMissingTreeDataPath = false;
|
|
317
|
+
|
|
280
318
|
export function useDataGridTable<R extends GridValidRowModel>({
|
|
281
319
|
rows,
|
|
282
320
|
columns,
|
|
@@ -299,7 +337,22 @@ export function useDataGridTable<R extends GridValidRowModel>({
|
|
|
299
337
|
rowSelectionModel,
|
|
300
338
|
onRowSelectionModelChange,
|
|
301
339
|
columnPinning,
|
|
340
|
+
columnVisibility = EMPTY_VISIBILITY,
|
|
341
|
+
detailPanel = false,
|
|
342
|
+
treeData: treeDataProp = false,
|
|
343
|
+
getTreeDataPath,
|
|
344
|
+
defaultGroupingExpansionDepth = 0,
|
|
345
|
+
isGroupExpandedByDefault,
|
|
346
|
+
rowSelectionPropagation,
|
|
302
347
|
}: UseDataGridTableParams<R>) {
|
|
348
|
+
const treeData = treeDataProp && getTreeDataPath !== undefined;
|
|
349
|
+
if (__DEV__ && treeDataProp && getTreeDataPath === undefined && !warnedMissingTreeDataPath) {
|
|
350
|
+
warnedMissingTreeDataPath = true;
|
|
351
|
+
console.warn('[DataGrid] `treeData` needs `getTreeDataPath`. Rendering flat rows.');
|
|
352
|
+
}
|
|
353
|
+
const propagateDescendants = rowSelectionPropagation?.descendants ?? true;
|
|
354
|
+
const propagateParents = rowSelectionPropagation?.parents ?? true;
|
|
355
|
+
|
|
303
356
|
const orFilterItems = useMemo(() => {
|
|
304
357
|
if (filterLogicOperator !== 'or' || filterMode === 'server') {
|
|
305
358
|
return undefined;
|
|
@@ -308,6 +361,13 @@ export function useDataGridTable<R extends GridValidRowModel>({
|
|
|
308
361
|
return active.length > 1 ? active : undefined;
|
|
309
362
|
}, [filterLogicOperator, filterMode, filterModel]);
|
|
310
363
|
|
|
364
|
+
const keyOf = useMemo(() => (row: R) => selectionIdKey(resolveRowId(row, getRowId)), [getRowId]);
|
|
365
|
+
|
|
366
|
+
const fullTree = useMemo(
|
|
367
|
+
() => (treeData ? buildTreeIndex(rows, getTreeDataPath, keyOf) : undefined),
|
|
368
|
+
[getTreeDataPath, keyOf, rows, treeData],
|
|
369
|
+
);
|
|
370
|
+
|
|
311
371
|
const data = useMemo(() => {
|
|
312
372
|
if (!orFilterItems) {
|
|
313
373
|
return [...rows];
|
|
@@ -315,14 +375,24 @@ export function useDataGridTable<R extends GridValidRowModel>({
|
|
|
315
375
|
const filterableColumns = new Map(
|
|
316
376
|
columns.filter((col) => col.filterable !== false).map((col) => [col.field, col]),
|
|
317
377
|
);
|
|
318
|
-
|
|
378
|
+
const matches = (row: R) =>
|
|
319
379
|
orFilterItems.some((item) => {
|
|
320
380
|
const col = filterableColumns.get(item.field);
|
|
321
381
|
return col !== undefined && rowMatchesFilterItem(col, row, item);
|
|
322
|
-
})
|
|
323
|
-
|
|
324
|
-
|
|
382
|
+
});
|
|
383
|
+
return fullTree
|
|
384
|
+
? keepMatchesWithAncestors(rows, fullTree, keyOf, matches)
|
|
385
|
+
: rows.filter(matches);
|
|
386
|
+
}, [columns, fullTree, keyOf, orFilterItems, rows]);
|
|
325
387
|
const allIds = useMemo(() => data.map((row) => resolveRowId(row, getRowId)), [data, getRowId]);
|
|
388
|
+
const allRowIds = useMemo(() => rows.map((row) => resolveRowId(row, getRowId)), [rows, getRowId]);
|
|
389
|
+
|
|
390
|
+
const tree = useMemo(() => {
|
|
391
|
+
if (!fullTree || !getTreeDataPath) {
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
return data.length === rows.length ? fullTree : buildTreeIndex(data, getTreeDataPath, keyOf);
|
|
395
|
+
}, [data, fullTree, getTreeDataPath, keyOf, rows.length]);
|
|
326
396
|
|
|
327
397
|
const columnDefs = useMemo(() => {
|
|
328
398
|
const mapped = columns.map((col) => {
|
|
@@ -332,8 +402,12 @@ export function useDataGridTable<R extends GridValidRowModel>({
|
|
|
332
402
|
}
|
|
333
403
|
return def;
|
|
334
404
|
});
|
|
335
|
-
return
|
|
336
|
-
|
|
405
|
+
return [
|
|
406
|
+
...(checkboxSelection && !treeData ? [createSelectColumn()] : []),
|
|
407
|
+
...(detailPanel ? [createDetailPanelToggleColumn()] : []),
|
|
408
|
+
...mapped,
|
|
409
|
+
];
|
|
410
|
+
}, [checkboxSelection, columns, detailPanel, disableColumnSorting, treeData]);
|
|
337
411
|
|
|
338
412
|
const sortingState = useMemo(() => sortModelToSortingState(sortModel), [sortModel]);
|
|
339
413
|
const paginationState = useMemo(() => paginationModelToState(paginationModel), [paginationModel]);
|
|
@@ -342,10 +416,79 @@ export function useDataGridTable<R extends GridValidRowModel>({
|
|
|
342
416
|
[filterModel, orFilterItems],
|
|
343
417
|
);
|
|
344
418
|
const rowSelectionState = useMemo(
|
|
345
|
-
() => selectionModelToRowSelection(rowSelectionModel, allIds),
|
|
346
|
-
[rowSelectionModel, allIds],
|
|
419
|
+
() => selectionModelToRowSelection(rowSelectionModel, treeData ? allRowIds : allIds),
|
|
420
|
+
[rowSelectionModel, treeData, allRowIds, allIds],
|
|
347
421
|
);
|
|
348
422
|
|
|
423
|
+
const filterSignature =
|
|
424
|
+
treeData &&
|
|
425
|
+
filterMode === 'client' &&
|
|
426
|
+
(quickFilter.trim().length > 0 || filterModel.some(isActiveFilterItem))
|
|
427
|
+
? JSON.stringify([quickFilter, filterModel, filterLogicOperator])
|
|
428
|
+
: '';
|
|
429
|
+
const [userExpanded, setUserExpanded] = useState<Partial<Record<string, boolean>>>({});
|
|
430
|
+
const [filterExpansion, setFilterExpansion] = useState<{
|
|
431
|
+
signature: string;
|
|
432
|
+
expanded: Partial<Record<string, boolean>>;
|
|
433
|
+
}>({ signature: '', expanded: {} });
|
|
434
|
+
const filterExpanded =
|
|
435
|
+
filterSignature !== '' && filterExpansion.signature === filterSignature
|
|
436
|
+
? filterExpansion.expanded
|
|
437
|
+
: EMPTY_FILTER_EXPANSION;
|
|
438
|
+
|
|
439
|
+
const expandedState = useMemo(() => {
|
|
440
|
+
if (!tree) {
|
|
441
|
+
return EMPTY_EXPANSION;
|
|
442
|
+
}
|
|
443
|
+
const isExpanded = (key: string): boolean => {
|
|
444
|
+
if (filterSignature !== '') {
|
|
445
|
+
return filterExpanded[key] ?? true;
|
|
446
|
+
}
|
|
447
|
+
const own = userExpanded[key];
|
|
448
|
+
if (own !== undefined) {
|
|
449
|
+
return own;
|
|
450
|
+
}
|
|
451
|
+
const row = tree.rowOf.get(key);
|
|
452
|
+
const depth = fullTree?.depthOf.get(key) ?? tree.depthOf.get(key) ?? 0;
|
|
453
|
+
if (row !== undefined && isGroupExpandedByDefault) {
|
|
454
|
+
return isGroupExpandedByDefault({ id: resolveRowId(row, getRowId), row, depth });
|
|
455
|
+
}
|
|
456
|
+
return defaultGroupingExpansionDepth === -1 || depth < defaultGroupingExpansionDepth;
|
|
457
|
+
};
|
|
458
|
+
const state: Record<string, boolean> = {};
|
|
459
|
+
for (const key of tree.childKeysOf.keys()) {
|
|
460
|
+
if (isExpanded(key)) {
|
|
461
|
+
state[key] = true;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return state;
|
|
465
|
+
}, [
|
|
466
|
+
tree,
|
|
467
|
+
fullTree,
|
|
468
|
+
filterSignature,
|
|
469
|
+
filterExpanded,
|
|
470
|
+
userExpanded,
|
|
471
|
+
isGroupExpandedByDefault,
|
|
472
|
+
defaultGroupingExpansionDepth,
|
|
473
|
+
getRowId,
|
|
474
|
+
]);
|
|
475
|
+
const isRowExpanded = (key: string) => key in expandedState;
|
|
476
|
+
|
|
477
|
+
const toggleRowExpanded = (key: string) => {
|
|
478
|
+
const next = !isRowExpanded(key);
|
|
479
|
+
if (filterSignature !== '') {
|
|
480
|
+
setFilterExpansion((previous) => ({
|
|
481
|
+
signature: filterSignature,
|
|
482
|
+
expanded: {
|
|
483
|
+
...(previous.signature === filterSignature ? previous.expanded : {}),
|
|
484
|
+
[key]: next,
|
|
485
|
+
},
|
|
486
|
+
}));
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
setUserExpanded((previous) => ({ ...previous, [key]: next }));
|
|
490
|
+
};
|
|
491
|
+
|
|
349
492
|
const resetPaginationPage = () => {
|
|
350
493
|
if (paginationModel.page !== 0) {
|
|
351
494
|
onPaginationModelChange({ ...paginationModel, page: 0 });
|
|
@@ -369,8 +512,11 @@ export function useDataGridTable<R extends GridValidRowModel>({
|
|
|
369
512
|
const table = useTable({
|
|
370
513
|
features,
|
|
371
514
|
columns: columnDefs,
|
|
372
|
-
data,
|
|
373
|
-
getRowId: (row) =>
|
|
515
|
+
data: tree ? tree.roots : data,
|
|
516
|
+
getRowId: (row) => keyOf(row),
|
|
517
|
+
getSubRows: tree ? (row) => tree.childrenOf.get(row) : undefined,
|
|
518
|
+
filterFromLeafRows: treeData,
|
|
519
|
+
paginateExpandedRows: false,
|
|
374
520
|
enableMultiSort: false,
|
|
375
521
|
enableMultiRowSelection: true,
|
|
376
522
|
globalFilterFn: gridGlobalFilterFn,
|
|
@@ -386,6 +532,8 @@ export function useDataGridTable<R extends GridValidRowModel>({
|
|
|
386
532
|
globalFilter: quickFilter,
|
|
387
533
|
rowSelection: rowSelectionState,
|
|
388
534
|
columnPinning,
|
|
535
|
+
columnVisibility,
|
|
536
|
+
expanded: expandedState,
|
|
389
537
|
},
|
|
390
538
|
onSortingChange: (updater) => {
|
|
391
539
|
const next = applyUpdater(updater, sortingState);
|
|
@@ -416,5 +564,59 @@ export function useDataGridTable<R extends GridValidRowModel>({
|
|
|
416
564
|
resetPaginationPage();
|
|
417
565
|
};
|
|
418
566
|
|
|
419
|
-
|
|
567
|
+
const treeSelection = useMemo(() => {
|
|
568
|
+
if (!fullTree) {
|
|
569
|
+
return undefined;
|
|
570
|
+
}
|
|
571
|
+
const selectedKeys = new Set(Object.keys(rowSelectionState));
|
|
572
|
+
return {
|
|
573
|
+
selectedKeys,
|
|
574
|
+
statuses: computeTreeSelection(fullTree, selectedKeys, propagateParents),
|
|
575
|
+
};
|
|
576
|
+
}, [fullTree, propagateParents, rowSelectionState]);
|
|
577
|
+
|
|
578
|
+
let treeState: DataGridTreeState | undefined;
|
|
579
|
+
if (fullTree && treeSelection) {
|
|
580
|
+
const { selectedKeys, statuses } = treeSelection;
|
|
581
|
+
const selectionStatus = (key: string) => statuses.get(key) ?? 'none';
|
|
582
|
+
const commit = (scope: { targets: string[]; units: string[] }) => {
|
|
583
|
+
const value = !scope.units.every((key) => selectionStatus(key) === 'all');
|
|
584
|
+
const next = applyTreeSelection(
|
|
585
|
+
fullTree,
|
|
586
|
+
selectedKeys,
|
|
587
|
+
scope.targets,
|
|
588
|
+
value,
|
|
589
|
+
propagateParents,
|
|
590
|
+
);
|
|
591
|
+
onRowSelectionModelChange(
|
|
592
|
+
rowSelectionToModel(
|
|
593
|
+
Object.fromEntries([...next].map((key) => [key, true])),
|
|
594
|
+
rowSelectionModel,
|
|
595
|
+
allRowIds,
|
|
596
|
+
),
|
|
597
|
+
);
|
|
598
|
+
};
|
|
599
|
+
const filtered = table.getFilteredRowModel();
|
|
600
|
+
const headerScope = propagateDescendants
|
|
601
|
+
? selectionScope(filtered.rows, fullTree, true)
|
|
602
|
+
: selectionScope(filtered.flatRows, fullTree, false);
|
|
603
|
+
const selectedUnits = headerScope.units.filter((key) => selectionStatus(key) !== 'none');
|
|
604
|
+
const allUnitsSelected =
|
|
605
|
+
headerScope.units.length > 0 &&
|
|
606
|
+
headerScope.units.every((key) => selectionStatus(key) === 'all');
|
|
607
|
+
|
|
608
|
+
treeState = {
|
|
609
|
+
isRowExpanded,
|
|
610
|
+
toggleRowExpanded,
|
|
611
|
+
selectionStatus,
|
|
612
|
+
toggleRowSelected: (row) => commit(selectionScope([row], fullTree, propagateDescendants)),
|
|
613
|
+
toggleAllRowsSelected: () => commit(headerScope),
|
|
614
|
+
headerSelection: {
|
|
615
|
+
checked: allUnitsSelected,
|
|
616
|
+
indeterminate: !allUnitsSelected && selectedUnits.length > 0,
|
|
617
|
+
},
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return { table, setQuickFilter, setColumnFilter, setFilterModel, tree: treeState };
|
|
420
622
|
}
|