@cdx-ui/data-grid 0.3.11 → 0.3.13

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.
Files changed (96) hide show
  1. package/README.md +3 -1
  2. package/coverage.svg +1 -1
  3. package/lib/commonjs/DataGrid.js +490 -81
  4. package/lib/commonjs/DataGrid.js.map +1 -1
  5. package/lib/commonjs/DataGridFilterPanel.js +547 -0
  6. package/lib/commonjs/DataGridFilterPanel.js.map +1 -0
  7. package/lib/commonjs/adapter/columnTypes.js +256 -0
  8. package/lib/commonjs/adapter/columnTypes.js.map +1 -0
  9. package/lib/commonjs/adapter/columns.js +140 -4
  10. package/lib/commonjs/adapter/columns.js.map +1 -1
  11. package/lib/commonjs/adapter/filterOperators.js +422 -0
  12. package/lib/commonjs/adapter/filterOperators.js.map +1 -0
  13. package/lib/commonjs/adapter/pinning.js +105 -0
  14. package/lib/commonjs/adapter/pinning.js.map +1 -0
  15. package/lib/commonjs/adapter/selectColumn.js +11 -0
  16. package/lib/commonjs/adapter/selectColumn.js.map +1 -1
  17. package/lib/commonjs/adapter/treeData.js +195 -0
  18. package/lib/commonjs/adapter/treeData.js.map +1 -0
  19. package/lib/commonjs/index.js +26 -0
  20. package/lib/commonjs/index.js.map +1 -1
  21. package/lib/commonjs/numberCells.js +280 -0
  22. package/lib/commonjs/numberCells.js.map +1 -0
  23. package/lib/commonjs/styles.js +107 -1
  24. package/lib/commonjs/styles.js.map +1 -1
  25. package/lib/commonjs/types.js +39 -1
  26. package/lib/commonjs/types.js.map +1 -1
  27. package/lib/commonjs/useDataGridTable.js +234 -18
  28. package/lib/commonjs/useDataGridTable.js.map +1 -1
  29. package/lib/module/DataGrid.js +497 -88
  30. package/lib/module/DataGrid.js.map +1 -1
  31. package/lib/module/DataGridFilterPanel.js +540 -0
  32. package/lib/module/DataGridFilterPanel.js.map +1 -0
  33. package/lib/module/adapter/columnTypes.js +240 -0
  34. package/lib/module/adapter/columnTypes.js.map +1 -0
  35. package/lib/module/adapter/columns.js +137 -4
  36. package/lib/module/adapter/columns.js.map +1 -1
  37. package/lib/module/adapter/filterOperators.js +406 -0
  38. package/lib/module/adapter/filterOperators.js.map +1 -0
  39. package/lib/module/adapter/pinning.js +94 -0
  40. package/lib/module/adapter/pinning.js.map +1 -0
  41. package/lib/module/adapter/selectColumn.js +11 -1
  42. package/lib/module/adapter/selectColumn.js.map +1 -1
  43. package/lib/module/adapter/treeData.js +187 -0
  44. package/lib/module/adapter/treeData.js.map +1 -0
  45. package/lib/module/index.js +2 -0
  46. package/lib/module/index.js.map +1 -1
  47. package/lib/module/numberCells.js +275 -0
  48. package/lib/module/numberCells.js.map +1 -0
  49. package/lib/module/styles.js +106 -0
  50. package/lib/module/styles.js.map +1 -1
  51. package/lib/module/types.js +38 -0
  52. package/lib/module/types.js.map +1 -1
  53. package/lib/module/useDataGridTable.js +238 -22
  54. package/lib/module/useDataGridTable.js.map +1 -1
  55. package/lib/typescript/DataGrid.d.ts.map +1 -1
  56. package/lib/typescript/DataGridFilterPanel.d.ts +52 -0
  57. package/lib/typescript/DataGridFilterPanel.d.ts.map +1 -0
  58. package/lib/typescript/adapter/columnTypes.d.ts +32 -0
  59. package/lib/typescript/adapter/columnTypes.d.ts.map +1 -0
  60. package/lib/typescript/adapter/columns.d.ts +14 -5
  61. package/lib/typescript/adapter/columns.d.ts.map +1 -1
  62. package/lib/typescript/adapter/filterOperators.d.ts +43 -0
  63. package/lib/typescript/adapter/filterOperators.d.ts.map +1 -0
  64. package/lib/typescript/adapter/pinning.d.ts +26 -0
  65. package/lib/typescript/adapter/pinning.d.ts.map +1 -0
  66. package/lib/typescript/adapter/selectColumn.d.ts +8 -0
  67. package/lib/typescript/adapter/selectColumn.d.ts.map +1 -1
  68. package/lib/typescript/adapter/treeData.d.ts +36 -0
  69. package/lib/typescript/adapter/treeData.d.ts.map +1 -0
  70. package/lib/typescript/index.d.ts +4 -1
  71. package/lib/typescript/index.d.ts.map +1 -1
  72. package/lib/typescript/numberCells.d.ts +46 -0
  73. package/lib/typescript/numberCells.d.ts.map +1 -0
  74. package/lib/typescript/styles.d.ts +32 -0
  75. package/lib/typescript/styles.d.ts.map +1 -1
  76. package/lib/typescript/types.d.ts +154 -7
  77. package/lib/typescript/types.d.ts.map +1 -1
  78. package/lib/typescript/useDataGridTable.d.ts +40 -4
  79. package/lib/typescript/useDataGridTable.d.ts.map +1 -1
  80. package/package.json +5 -5
  81. package/src/DataGrid.tsx +745 -95
  82. package/src/DataGridFilterPanel.tsx +648 -0
  83. package/src/adapter/columnTypes.ts +327 -0
  84. package/src/adapter/columns.test.ts +75 -1
  85. package/src/adapter/columns.ts +192 -3
  86. package/src/adapter/filterOperators.test.ts +292 -0
  87. package/src/adapter/filterOperators.ts +524 -0
  88. package/src/adapter/pinning.ts +134 -0
  89. package/src/adapter/selectColumn.ts +12 -1
  90. package/src/adapter/treeData.ts +228 -0
  91. package/src/index.ts +19 -0
  92. package/src/numberCells.tsx +379 -0
  93. package/src/styles.ts +131 -0
  94. package/src/types.ts +190 -7
  95. package/src/useDataGridTable.test.ts +69 -0
  96. package/src/useDataGridTable.ts +338 -17
@@ -1,11 +1,14 @@
1
- import { useMemo } from 'react';
1
+ import { useMemo, useState } from 'react';
2
2
  import {
3
3
  columnFilteringFeature,
4
+ columnPinningFeature,
5
+ columnVisibilityFeature,
4
6
  createFilteredRowModel,
5
7
  createPaginatedRowModel,
6
8
  createSortedRowModel,
7
9
  filterFn_includesString,
8
10
  globalFilteringFeature,
11
+ rowExpandingFeature,
9
12
  rowPaginationFeature,
10
13
  rowSelectionFeature,
11
14
  rowSortingFeature,
@@ -14,28 +17,79 @@ import {
14
17
  tableFeatures,
15
18
  useTable,
16
19
  type ColumnFiltersState,
20
+ type ColumnPinningState,
17
21
  type PaginationState,
18
22
  type RowSelectionState,
19
23
  type SortingState,
20
24
  } from '@tanstack/react-table';
21
- 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';
22
35
  import { applyUpdater, gridColDefToColumnDef } from './adapter/columns';
36
+ import {
37
+ filterFn_displayText,
38
+ metaUsesDisplayFilter,
39
+ normalizeFilterQuery,
40
+ sortFn_boolean,
41
+ sortFn_datetime,
42
+ sortFn_number,
43
+ sortFn_selectLabel,
44
+ untypedCellIncludesQuery,
45
+ } from './adapter/columnTypes';
46
+ import {
47
+ filterOperatorNeedsValue,
48
+ isActiveFilterItem,
49
+ isBlankFilterValue,
50
+ isFilterValueGroup,
51
+ isOperatorFilterValue,
52
+ rowMatchesFilterItem,
53
+ } from './adapter/filterOperators';
23
54
  import type {
24
55
  GridColDef,
56
+ GridColumnVisibilityModel,
25
57
  GridFeatureMode,
58
+ GridFilterItem,
26
59
  GridFilterModel,
60
+ GridLogicOperator,
27
61
  GridPaginationModel,
28
62
  GridRowId,
29
63
  GridRowSelectionModel,
64
+ GridRowSelectionPropagation,
30
65
  GridRowsProp,
31
66
  GridSortModel,
67
+ GridTreeNodeParams,
32
68
  GridValidRowModel,
33
69
  } from './types';
34
- import { SELECT_COLUMN_ID } from './types';
70
+ import { DETAIL_PANEL_TOGGLE_ID, SELECT_COLUMN_ID } from './types';
71
+
72
+ function gridGlobalFilterFn(
73
+ row: Parameters<typeof filterFn_displayText>[0],
74
+ columnId: string,
75
+ filterValue: unknown,
76
+ ): boolean {
77
+ if (columnId === SELECT_COLUMN_ID || columnId === DETAIL_PANEL_TOGGLE_ID) {
78
+ return false;
79
+ }
80
+ const meta = row.getAllCells().find((cell) => cell.column.id === columnId)?.column.columnDef.meta;
81
+ if (metaUsesDisplayFilter(meta)) {
82
+ return filterFn_displayText(row, columnId, filterValue);
83
+ }
84
+ return untypedCellIncludesQuery(row.getValue(columnId), normalizeFilterQuery(filterValue));
85
+ }
35
86
 
36
87
  const features = tableFeatures({
37
88
  columnFilteringFeature,
89
+ columnPinningFeature,
90
+ columnVisibilityFeature,
38
91
  globalFilteringFeature,
92
+ rowExpandingFeature,
39
93
  rowSortingFeature,
40
94
  rowPaginationFeature,
41
95
  rowSelectionFeature,
@@ -43,7 +97,14 @@ const features = tableFeatures({
43
97
  sortedRowModel: createSortedRowModel(),
44
98
  paginatedRowModel: createPaginatedRowModel(),
45
99
  filterFns: { includesString: filterFn_includesString },
46
- sortFns: { alphanumeric: sortFn_alphanumeric, text: sortFn_text },
100
+ sortFns: {
101
+ alphanumeric: sortFn_alphanumeric,
102
+ text: sortFn_text,
103
+ number: sortFn_number,
104
+ datetime: sortFn_datetime,
105
+ boolean: sortFn_boolean,
106
+ selectLabel: sortFn_selectLabel,
107
+ },
47
108
  });
48
109
 
49
110
  export function sortModelToSortingState(model: GridSortModel): SortingState {
@@ -81,16 +142,55 @@ function columnFilterValueToString(value: unknown): string {
81
142
  return '';
82
143
  }
83
144
 
145
+ /**
146
+ * Items without an `operator` keep today's text filter. Items with one carry the operator and
147
+ * typed operand into the column filter. An item whose operator needs a value but has none is
148
+ * dropped, so `isEmpty` / `isNotEmpty` survive a blank `value` and nothing else does.
149
+ */
84
150
  export function filterModelToColumnFilters(model: GridFilterModel): ColumnFiltersState {
85
- return model
86
- .filter((item) => item.value.trim().length > 0)
87
- .map((item) => ({ id: item.field, value: item.value }));
151
+ const valuesByField = new Map<string, unknown[]>();
152
+ for (const item of model) {
153
+ let value: unknown;
154
+ if (item.operator !== undefined) {
155
+ if (filterOperatorNeedsValue(item.operator) && isBlankFilterValue(item.value)) {
156
+ continue;
157
+ }
158
+ value = { operator: item.operator, value: item.value };
159
+ } else {
160
+ const text = columnFilterValueToString(item.value);
161
+ if (text.trim().length === 0) {
162
+ continue;
163
+ }
164
+ value = text;
165
+ }
166
+ const values = valuesByField.get(item.field);
167
+ if (values) {
168
+ values.push(value);
169
+ } else {
170
+ valuesByField.set(item.field, [value]);
171
+ }
172
+ }
173
+ return [...valuesByField].map(([id, values]) => ({
174
+ id,
175
+ value: values.length === 1 ? values[0] : { items: values },
176
+ }));
177
+ }
178
+
179
+ function columnFilterValueToItem(field: string, value: unknown): GridFilterItem {
180
+ if (isOperatorFilterValue(value)) {
181
+ return { field, operator: value.operator, value: value.value };
182
+ }
183
+ return { field, value: columnFilterValueToString(value) };
88
184
  }
89
185
 
90
186
  export function columnFiltersToFilterModel(state: ColumnFiltersState): GridFilterModel {
91
187
  return state
92
188
  .filter((item) => item.id !== SELECT_COLUMN_ID)
93
- .map((item) => ({ field: item.id, value: columnFilterValueToString(item.value) }));
189
+ .flatMap((item) =>
190
+ isFilterValueGroup(item.value)
191
+ ? item.value.items.map((value) => columnFilterValueToItem(item.id, value))
192
+ : [columnFilterValueToItem(item.id, item.value)],
193
+ );
94
194
  }
95
195
 
96
196
  function selectionIdKey(id: GridRowId): string {
@@ -185,12 +285,36 @@ interface UseDataGridTableParams<R extends GridValidRowModel> {
185
285
  onPaginationModelChange: (model: GridPaginationModel) => void;
186
286
  filterModel: GridFilterModel;
187
287
  onFilterModelChange: (model: GridFilterModel) => void;
288
+ filterLogicOperator: GridLogicOperator;
188
289
  quickFilter: string;
189
290
  onQuickFilterChange: (value: string) => void;
190
291
  rowSelectionModel: GridRowSelectionModel;
191
292
  onRowSelectionModelChange: (model: GridRowSelectionModel) => void;
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;
192
301
  }
193
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 };
310
+ }
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
+
194
318
  export function useDataGridTable<R extends GridValidRowModel>({
195
319
  rows,
196
320
  columns,
@@ -207,13 +331,68 @@ export function useDataGridTable<R extends GridValidRowModel>({
207
331
  onPaginationModelChange,
208
332
  filterModel,
209
333
  onFilterModelChange,
334
+ filterLogicOperator,
210
335
  quickFilter,
211
336
  onQuickFilterChange,
212
337
  rowSelectionModel,
213
338
  onRowSelectionModelChange,
339
+ columnPinning,
340
+ columnVisibility = EMPTY_VISIBILITY,
341
+ detailPanel = false,
342
+ treeData: treeDataProp = false,
343
+ getTreeDataPath,
344
+ defaultGroupingExpansionDepth = 0,
345
+ isGroupExpandedByDefault,
346
+ rowSelectionPropagation,
214
347
  }: UseDataGridTableParams<R>) {
215
- const data = useMemo(() => [...rows], [rows]);
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
+
356
+ const orFilterItems = useMemo(() => {
357
+ if (filterLogicOperator !== 'or' || filterMode === 'server') {
358
+ return undefined;
359
+ }
360
+ const active = filterModel.filter(isActiveFilterItem);
361
+ return active.length > 1 ? active : undefined;
362
+ }, [filterLogicOperator, filterMode, filterModel]);
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
+
371
+ const data = useMemo(() => {
372
+ if (!orFilterItems) {
373
+ return [...rows];
374
+ }
375
+ const filterableColumns = new Map(
376
+ columns.filter((col) => col.filterable !== false).map((col) => [col.field, col]),
377
+ );
378
+ const matches = (row: R) =>
379
+ orFilterItems.some((item) => {
380
+ const col = filterableColumns.get(item.field);
381
+ return col !== undefined && rowMatchesFilterItem(col, row, item);
382
+ });
383
+ return fullTree
384
+ ? keepMatchesWithAncestors(rows, fullTree, keyOf, matches)
385
+ : rows.filter(matches);
386
+ }, [columns, fullTree, keyOf, orFilterItems, rows]);
216
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]);
217
396
 
218
397
  const columnDefs = useMemo(() => {
219
398
  const mapped = columns.map((col) => {
@@ -223,17 +402,93 @@ export function useDataGridTable<R extends GridValidRowModel>({
223
402
  }
224
403
  return def;
225
404
  });
226
- return checkboxSelection ? [createSelectColumn(), ...mapped] : mapped;
227
- }, [checkboxSelection, columns, disableColumnSorting]);
405
+ return [
406
+ ...(checkboxSelection && !treeData ? [createSelectColumn()] : []),
407
+ ...(detailPanel ? [createDetailPanelToggleColumn()] : []),
408
+ ...mapped,
409
+ ];
410
+ }, [checkboxSelection, columns, detailPanel, disableColumnSorting, treeData]);
228
411
 
229
412
  const sortingState = useMemo(() => sortModelToSortingState(sortModel), [sortModel]);
230
413
  const paginationState = useMemo(() => paginationModelToState(paginationModel), [paginationModel]);
231
- const columnFilterState = useMemo(() => filterModelToColumnFilters(filterModel), [filterModel]);
414
+ const columnFilterState = useMemo(
415
+ () => (orFilterItems ? [] : filterModelToColumnFilters(filterModel)),
416
+ [filterModel, orFilterItems],
417
+ );
232
418
  const rowSelectionState = useMemo(
233
- () => selectionModelToRowSelection(rowSelectionModel, allIds),
234
- [rowSelectionModel, allIds],
419
+ () => selectionModelToRowSelection(rowSelectionModel, treeData ? allRowIds : allIds),
420
+ [rowSelectionModel, treeData, allRowIds, allIds],
235
421
  );
236
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
+
237
492
  const resetPaginationPage = () => {
238
493
  if (paginationModel.page !== 0) {
239
494
  onPaginationModelChange({ ...paginationModel, page: 0 });
@@ -257,10 +512,14 @@ export function useDataGridTable<R extends GridValidRowModel>({
257
512
  const table = useTable({
258
513
  features,
259
514
  columns: columnDefs,
260
- data,
261
- getRowId: (row) => selectionIdKey(resolveRowId(row, getRowId)),
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,
262
520
  enableMultiSort: false,
263
521
  enableMultiRowSelection: true,
522
+ globalFilterFn: gridGlobalFilterFn,
264
523
  manualPagination: paginationMode === 'server',
265
524
  manualSorting: sortingMode === 'server',
266
525
  manualFiltering: filterMode === 'server',
@@ -272,6 +531,9 @@ export function useDataGridTable<R extends GridValidRowModel>({
272
531
  columnFilters: columnFilterState,
273
532
  globalFilter: quickFilter,
274
533
  rowSelection: rowSelectionState,
534
+ columnPinning,
535
+ columnVisibility,
536
+ expanded: expandedState,
275
537
  },
276
538
  onSortingChange: (updater) => {
277
539
  const next = applyUpdater(updater, sortingState);
@@ -297,5 +559,64 @@ export function useDataGridTable<R extends GridValidRowModel>({
297
559
  },
298
560
  });
299
561
 
300
- return { table, setQuickFilter, setColumnFilter };
562
+ const setFilterModel = (model: GridFilterModel) => {
563
+ onFilterModelChange(model);
564
+ resetPaginationPage();
565
+ };
566
+
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 };
301
622
  }