@opencorestack/opengridx 2.0.4 → 3.0.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/CHANGELOG.md +408 -3
- package/README.md +93 -72
- package/dist/index.d.ts +611 -121
- package/dist/opengridx.css +1 -1
- package/dist/opengridx.es.js +7992 -5650
- package/dist/opengridx.es.js.map +1 -1
- package/dist/opengridx.umd.js +17 -23
- package/dist/opengridx.umd.js.map +1 -1
- package/docs/API_REFERENCE.md +348 -161
- package/docs/README.md +6 -1
- package/docs/architecture/datagrid-orchestration.md +81 -0
- package/docs/architecture/grid-row-meta.md +102 -0
- package/docs/architecture/use-grid-columns.md +128 -0
- package/docs/architecture/use-grid-controlled-state.md +157 -0
- package/docs/architecture/use-grid-row-pipeline.md +120 -0
- package/docs/architecture/use-grid-scroll-sync.md +111 -0
- package/docs/architecture/use-grid-state-snapshot.md +90 -0
- package/docs/architecture/use-grid-virtualization.md +115 -0
- package/docs/architecture/use-grid-visible-rows.md +85 -0
- package/docs/assets/banner-alt.png +0 -0
- package/docs/assets/banner.png +0 -0
- package/docs/assets/hero.png +0 -0
- package/docs/assets/logo.png +0 -0
- package/docs/components/aggregation-footer.md +15 -10
- package/docs/components/cell.md +16 -6
- package/docs/components/column-group-header.md +55 -28
- package/docs/components/column-resize.md +17 -13
- package/docs/components/column-visibility.md +45 -22
- package/docs/components/datagrid.md +14 -12
- package/docs/components/empty-state.md +2 -1
- package/docs/components/error-overlay.md +12 -7
- package/docs/components/filter-panel.md +28 -8
- package/docs/components/header.md +28 -9
- package/docs/components/pagination.md +14 -8
- package/docs/components/row.md +21 -5
- package/docs/components/toolbar.md +27 -9
- package/docs/components/tooltip.md +16 -7
- package/docs/customization/slots-api.md +100 -36
- package/docs/customization/theming.md +66 -18
- package/docs/features/aggregation-pivot.md +75 -9
- package/docs/features/cell-spanning.md +80 -125
- package/docs/features/clipboard.md +41 -16
- package/docs/features/custom-pagination.md +38 -18
- package/docs/features/data-source.md +36 -13
- package/docs/features/editing-reordering.md +125 -16
- package/docs/features/export-guide.md +147 -41
- package/docs/features/filtering.md +44 -6
- package/docs/features/infinite-scroll.md +54 -46
- package/docs/features/keyboard-navigation.md +83 -0
- package/docs/features/list-view.md +58 -5
- package/docs/features/loading-states.md +18 -20
- package/docs/features/master-detail.md +18 -5
- package/docs/features/pdf-export.md +56 -19
- package/docs/features/pinning.md +23 -5
- package/docs/features/selection.md +50 -10
- package/docs/features/sorting-pagination.md +66 -14
- package/docs/features/state-persistence.md +78 -10
- package/docs/features/toolbar-customization.md +26 -17
- package/docs/features/tree-data-grouping.md +44 -9
- package/docs/features/virtualization.md +64 -15
- package/docs/migration/v1-to-v2.md +115 -0
- package/docs/migration/v2-to-v3.md +1222 -0
- package/docs/performance.md +124 -0
- package/docs/roadmap.md +16 -10
- package/lib/components/Cell/Cell.css +13 -1
- package/lib/components/Cell/Cell.tsx +130 -57
- package/lib/components/Cell/CellErrorBoundary.tsx +12 -6
- package/lib/components/Cell/GridEditInputCell.tsx +114 -41
- package/lib/components/ColumnGroupHeader/ColumnGroupHeader.css +7 -1
- package/lib/components/ColumnResizeHandle/ColumnResizeHandle.css +11 -0
- package/lib/components/ColumnResizeHandle/ColumnResizeHandle.tsx +104 -38
- package/lib/components/ColumnResizeHandle/clampResizeWidth.ts +13 -0
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.css +6 -0
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +21 -13
- package/lib/components/DataGrid/DataGrid.css +84 -12
- package/lib/components/DataGrid/DataGrid.tsx +606 -872
- package/lib/components/DataGrid/GridAggregationFooter.tsx +53 -17
- package/lib/components/DataGrid/GridEmptyState.tsx +7 -1
- package/lib/components/DataGrid/GridErrorOverlay.tsx +15 -12
- package/lib/components/DataGrid/GridListView.tsx +166 -23
- package/lib/components/DataGrid/GridLiveRegion.tsx +31 -0
- package/lib/components/DataGrid/GridLoadingOverlay.tsx +27 -0
- package/lib/components/DataGrid/GridPaginationArea.tsx +44 -0
- package/lib/components/DataGrid/GridPinnedRows.tsx +65 -11
- package/lib/components/DataGrid/GridStandaloneColumnPanel.tsx +40 -38
- package/lib/components/DataGrid/GridToolbarSlot.tsx +51 -0
- package/lib/components/DataGrid/GridVirtualRows.tsx +48 -25
- package/lib/components/DetailPanel/DetailPanel.css +0 -6
- package/lib/components/DetailPanel/DetailPanel.tsx +34 -5
- package/lib/components/DetailPanel/DetailPanelContent.tsx +17 -0
- package/lib/components/FilterPanel/FilterPanel.css +13 -2
- package/lib/components/FilterPanel/FilterPanel.tsx +272 -99
- package/lib/components/Header/ColumnMenu.tsx +91 -39
- package/lib/components/Header/Header.css +3 -3
- package/lib/components/Header/Header.tsx +179 -160
- package/lib/components/ListView/ListView.css +25 -0
- package/lib/components/ListView/ListViewRow.tsx +79 -10
- package/lib/components/Pagination/Pagination.tsx +15 -7
- package/lib/components/Row/Row.tsx +185 -75
- package/lib/components/Toolbar/GlobalSearch.css +6 -3
- package/lib/components/Toolbar/GlobalSearch.tsx +26 -27
- package/lib/components/Toolbar/GridToolbar.css +12 -11
- package/lib/components/Toolbar/GridToolbar.tsx +133 -89
- package/lib/components/Toolbar/PivotPanel.tsx +89 -49
- package/lib/components/Tooltip/Tooltip.css +20 -0
- package/lib/components/Tooltip/Tooltip.tsx +68 -35
- package/lib/components/ui/Button.tsx +3 -0
- package/lib/components/ui/Checkbox.tsx +2 -1
- package/lib/components/ui/ExpandIcon.css +0 -9
- package/lib/components/ui/ExpandIcon.tsx +4 -1
- package/lib/hooks/core/gridApiPlaceholder.ts +34 -0
- package/lib/hooks/core/gridToolbarHostContext.ts +14 -0
- package/lib/hooks/core/useDataGrid.ts +115 -173
- package/lib/hooks/core/useGridAggregationApi.ts +72 -0
- package/lib/hooks/core/useGridApiMethods.ts +115 -0
- package/lib/hooks/core/useGridApiRef.ts +15 -3
- package/lib/hooks/core/useGridApiRefBinding.ts +18 -0
- package/lib/hooks/core/useGridAriaRows.ts +38 -0
- package/lib/hooks/core/useGridClipboardApi.ts +38 -0
- package/lib/hooks/core/useGridColumnLookup.ts +30 -0
- package/lib/hooks/core/useGridColumns.tsx +284 -76
- package/lib/hooks/core/useGridColumnsPanel.ts +77 -0
- package/lib/hooks/core/useGridControlledState.ts +99 -6
- package/lib/hooks/core/useGridDetailPanel.ts +51 -0
- package/lib/hooks/core/useGridDevWarnings.ts +123 -0
- package/lib/hooks/core/useGridFocusHandlers.ts +50 -0
- package/lib/hooks/core/useGridGroupingColumn.ts +62 -0
- package/lib/hooks/core/useGridHeaderHandlers.ts +92 -0
- package/lib/hooks/core/useGridHierarchy.ts +81 -0
- package/lib/hooks/core/useGridKeyboardNavigation.ts +452 -250
- package/lib/hooks/core/useGridLiveRowSelection.ts +67 -0
- package/lib/hooks/core/useGridPageCorrection.ts +55 -0
- package/lib/hooks/core/useGridRowIdOf.ts +32 -0
- package/lib/hooks/core/useGridRowInteractions.ts +81 -0
- package/lib/hooks/core/useGridRowPipeline.ts +69 -39
- package/lib/hooks/core/useGridRowSelection.ts +91 -0
- package/lib/hooks/core/useGridScrollSync.ts +80 -32
- package/lib/hooks/core/useGridScrollToIndexesApi.ts +45 -0
- package/lib/hooks/core/useGridStateSnapshot.ts +40 -5
- package/lib/hooks/core/useGridThemeDimensions.ts +49 -0
- package/lib/hooks/core/useGridToolbarProps.ts +97 -0
- package/lib/hooks/core/useGridViewport.ts +38 -0
- package/lib/hooks/core/useGridViewportSize.ts +26 -0
- package/lib/hooks/core/useGridVisibleRows.ts +20 -7
- package/lib/hooks/core/useLayout.ts +215 -195
- package/lib/hooks/core/useRowsScrollEndLatch.ts +41 -0
- package/lib/hooks/core/useStableColumns.ts +34 -0
- package/lib/hooks/features/useAggregation.ts +86 -90
- package/lib/hooks/features/useColumnGroupReorderGuard.ts +34 -0
- package/lib/hooks/features/useDetailPanelHeights.ts +29 -0
- package/lib/hooks/features/useGridClipboard.ts +133 -73
- package/lib/hooks/features/useGridDataSource.ts +354 -128
- package/lib/hooks/features/useGridEditing.ts +211 -51
- package/lib/hooks/features/useGridPivot.ts +92 -0
- package/lib/hooks/features/useGridSpanRenderWindow.ts +104 -0
- package/lib/hooks/features/useGridSpanning.ts +410 -306
- package/lib/hooks/features/usePivot.ts +0 -0
- package/lib/hooks/features/useServerTreeChildren.ts +50 -0
- package/lib/hooks/useColumnReorder.ts +38 -44
- package/lib/hooks/useRowGrouping.ts +335 -214
- package/lib/hooks/useRowReorder.ts +62 -31
- package/lib/hooks/useTreeData.ts +330 -262
- package/lib/index.ts +30 -2
- package/lib/state/index.ts +1 -0
- package/lib/state/types.ts +7 -1
- package/lib/state/useGridStateStorage.ts +88 -29
- package/lib/theme/DataGridThemeProvider.tsx +143 -38
- package/lib/theme/gridThemeContext.ts +5 -0
- package/lib/theme/index.ts +5 -0
- package/lib/theme/presets.ts +5 -4
- package/lib/theme/types.ts +40 -2
- package/lib/types/index.ts +311 -108
- package/lib/utils/aggregation/index.ts +157 -0
- package/lib/utils/aria/index.ts +40 -0
- package/lib/utils/columnGroups.ts +110 -0
- package/lib/utils/columnLookup.ts +33 -0
- package/lib/utils/columnWidth.ts +22 -0
- package/lib/utils/dataSource/index.ts +46 -0
- package/lib/utils/detailPanel.ts +66 -0
- package/lib/utils/dragData.ts +44 -0
- package/lib/utils/editing/index.ts +30 -0
- package/lib/utils/export/exportShared.ts +197 -0
- package/lib/utils/export/exportToExcelAdvanced.ts +344 -121
- package/lib/utils/export/exportToPdf.ts +143 -92
- package/lib/utils/export/groupLabel.ts +14 -0
- package/lib/utils/export/index.ts +278 -272
- package/lib/utils/export/pdfText.ts +50 -0
- package/lib/utils/filtering/index.ts +277 -148
- package/lib/utils/focus/index.ts +115 -0
- package/lib/utils/gridApiRows.ts +35 -0
- package/lib/utils/gridModes.ts +77 -0
- package/lib/utils/grouping/groupedExportRows.ts +91 -0
- package/lib/utils/pagination/index.ts +56 -0
- package/lib/utils/pinning/index.ts +77 -33
- package/lib/utils/pivot/index.ts +368 -0
- package/lib/utils/scroll/index.ts +78 -0
- package/lib/utils/scroll/scrollEnd.ts +18 -0
- package/lib/utils/sorting/index.ts +154 -30
- package/lib/utils/syntheticRows.ts +12 -0
- package/lib/utils/values.ts +206 -0
- package/lib/utils/viewport.ts +14 -0
- package/llms.txt +272 -166
- package/package.json +18 -6
- package/lib/components/ColumnGroupHeader/ColumnGroupHeader.tsx +0 -220
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.test.tsx +0 -85
- package/lib/components/DataGrid/DataGrid.regression.test.tsx +0 -132
- package/lib/components/DataGrid/DataGrid.smoke.test.tsx +0 -58
- package/lib/components/DataGrid/DataGrid.v2.test.tsx +0 -127
- package/lib/components/Header/Header.test.tsx +0 -135
- package/lib/components/Pagination/Pagination.test.tsx +0 -64
- package/lib/hooks/core/useDataGrid.regression.test.ts +0 -66
- package/lib/hooks/core/useGridControlledState.test.ts +0 -86
- package/lib/hooks/core/useGridKeyboardNavigation.test.ts +0 -176
- package/lib/hooks/core/useGridRowPipeline.test.ts +0 -117
- package/lib/hooks/features/useAggregation.test.ts +0 -175
- package/lib/hooks/features/usePivot.test.ts +0 -202
- package/lib/hooks/useRowGrouping.test.ts +0 -137
- package/lib/utils/export/exportToPdf.test.ts +0 -120
- package/lib/utils/export/groupedExport.test.ts +0 -144
- package/lib/utils/filtering/filtering.test.ts +0 -178
- package/lib/utils/sorting/sorting.test.ts +0 -88
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useMemo,
|
|
1
|
+
import { useState, useMemo, useCallback } from 'react';
|
|
2
2
|
import type {
|
|
3
3
|
GridRowModel,
|
|
4
4
|
GridRowId,
|
|
@@ -10,8 +10,14 @@ import type {
|
|
|
10
10
|
GridRowMeta,
|
|
11
11
|
GridColDef
|
|
12
12
|
} from '../types';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
13
|
+
import { createRowFilter } from '../utils/filtering';
|
|
14
|
+
import { sortItemsBySortModel, type GridSortValue } from '../utils/sorting';
|
|
15
|
+
import { callSafely, getCellValue } from '../utils/values';
|
|
16
|
+
import type { GridColumnLookup } from '../utils/columnLookup';
|
|
17
|
+
import { computeAggregations } from '../utils/aggregation';
|
|
18
|
+
|
|
19
|
+
/** Where a group's aggregates are shown: on the group row, on a subtotal row after its children, or not at all. */
|
|
20
|
+
export type GridGroupAggregationPosition = 'inline' | 'footer' | null;
|
|
15
21
|
|
|
16
22
|
export interface UseRowGroupingParams<R extends GridRowModel> {
|
|
17
23
|
rows: R[];
|
|
@@ -22,19 +28,118 @@ export interface UseRowGroupingParams<R extends GridRowModel> {
|
|
|
22
28
|
defaultGroupingExpansionDepth?: number;
|
|
23
29
|
filterModel?: GridFilterModel;
|
|
24
30
|
sortModel?: GridSortItem[];
|
|
25
|
-
getAggregationPosition?: (groupNode: GridTreeNode | null) =>
|
|
31
|
+
getAggregationPosition?: (groupNode: GridTreeNode | null) => GridGroupAggregationPosition;
|
|
32
|
+
/** Column definitions, so filter and sort read leaf cells through valueGetter and use the column type. */
|
|
33
|
+
columnLookup?: GridColumnLookup;
|
|
34
|
+
/** 'server': the rows arrive filtered, so the groups are built from them as they are. */
|
|
35
|
+
filterMode?: 'client' | 'server';
|
|
36
|
+
/** 'server': the rows arrive sorted, so groups and leaves keep the order the server sent them in. */
|
|
37
|
+
sortingMode?: 'client' | 'server';
|
|
26
38
|
}
|
|
27
39
|
|
|
28
|
-
|
|
40
|
+
// Group rows show their aggregates inline; the grand total (null) is the grid's footer row.
|
|
41
|
+
const defaultGetAggregationPosition = (groupNode: GridTreeNode | null): GridGroupAggregationPosition =>
|
|
42
|
+
groupNode === null ? 'footer' : 'inline';
|
|
43
|
+
|
|
44
|
+
// A getAggregationPosition that throws falls back to the default position for that node.
|
|
45
|
+
function callAggregationPosition(
|
|
46
|
+
getAggregationPosition: (groupNode: GridTreeNode | null) => GridGroupAggregationPosition,
|
|
47
|
+
groupNode: GridTreeNode | null,
|
|
48
|
+
): GridGroupAggregationPosition {
|
|
49
|
+
return callSafely(
|
|
50
|
+
() => getAggregationPosition(groupNode),
|
|
51
|
+
defaultGetAggregationPosition(groupNode),
|
|
52
|
+
'getAggregationPosition',
|
|
53
|
+
'getAggregationPosition threw; the default position is used',
|
|
54
|
+
);
|
|
55
|
+
}
|
|
29
56
|
|
|
30
|
-
|
|
31
|
-
|
|
57
|
+
// The group-row label: the column's groupingValueFormatter, or "field: value" (also when it throws).
|
|
58
|
+
function formatGroupLabel(colDef: GridColDef | undefined, field: string, value: unknown): string {
|
|
59
|
+
const fallback = `${field}: ${String(value)}`;
|
|
60
|
+
const formatter = colDef?.groupingValueFormatter;
|
|
61
|
+
if (!formatter) return fallback;
|
|
62
|
+
return callSafely(
|
|
63
|
+
() => formatter({ field, value }),
|
|
64
|
+
fallback,
|
|
65
|
+
`groupingValueFormatter:${field}`,
|
|
66
|
+
`groupingValueFormatter for column "${field}" threw; the group is labelled "${field}: value"`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
32
69
|
|
|
33
70
|
// Stable empty defaults — module-level constants prevent new object identity
|
|
34
71
|
// on every render, which would otherwise invalidate useMemo deps and cause
|
|
35
72
|
// infinite re-render loops when callers omit optional params.
|
|
36
73
|
const EMPTY_ROW_GROUPING_MODEL: GridRowGroupingModel = [];
|
|
37
74
|
const EMPTY_AGGREGATION_MODEL: GridAggregationModel = {};
|
|
75
|
+
const EMPTY_OVERRIDES: Map<GridRowId, boolean> = new Map();
|
|
76
|
+
|
|
77
|
+
const FIELD_SEPARATOR = '\u001f';
|
|
78
|
+
|
|
79
|
+
/** Options of the hierarchy hooks' getVisibleRows. */
|
|
80
|
+
export interface GridHierarchyVisibleRowsOptions {
|
|
81
|
+
/** Walk every group regardless of expansion (exports, getAllFilteredRows). */
|
|
82
|
+
expandAll?: boolean;
|
|
83
|
+
/** The column that shows the hierarchy: sorting by it orders group rows by their grouping value / label. */
|
|
84
|
+
labelField?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface ExpansionOverrides {
|
|
88
|
+
configKey: string;
|
|
89
|
+
overrides: Map<GridRowId, boolean>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The bucket key of a grouping value. Distinct values never share a key, so `null` and `'null'`,
|
|
94
|
+
* `1` and `'1'`, `true` and `'true'` are separate groups; equal dates share one. Strings keep their
|
|
95
|
+
* own text, so the group ids of string values stay readable.
|
|
96
|
+
*/
|
|
97
|
+
function groupKeyOf(value: unknown): string {
|
|
98
|
+
if (typeof value === 'string') return value;
|
|
99
|
+
if (value === null) return `${FIELD_SEPARATOR}null`;
|
|
100
|
+
if (value === undefined) return `${FIELD_SEPARATOR}undefined`;
|
|
101
|
+
if (value instanceof Date) return `${FIELD_SEPARATOR}date:${value.getTime()}`;
|
|
102
|
+
if (typeof value === 'object') {
|
|
103
|
+
try {
|
|
104
|
+
return `${FIELD_SEPARATOR}object:${JSON.stringify(value)}`;
|
|
105
|
+
} catch {
|
|
106
|
+
return `${FIELD_SEPARATOR}object:${String(value)}`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return `${FIELD_SEPARATOR}${typeof value}:${String(value)}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const groupIdOf = (field: string, key: string, parentId: GridRowId | null): string =>
|
|
113
|
+
`auto-group-${field}-${key}-${parentId ?? 'root'}`;
|
|
114
|
+
|
|
115
|
+
/** The id of the subtotal row shown after a group's children when its aggregates are in 'footer' position. */
|
|
116
|
+
const footerIdOf = (groupId: GridRowId): string => `${groupId}${FIELD_SEPARATOR}footer`;
|
|
117
|
+
|
|
118
|
+
interface GroupRowVariants<R> {
|
|
119
|
+
/** The group row carrying its aggregates. */
|
|
120
|
+
inline: R;
|
|
121
|
+
/** The group row without aggregates ('footer' and null positions). */
|
|
122
|
+
bare: R;
|
|
123
|
+
/** The subtotal row, when there is an aggregation model. */
|
|
124
|
+
footer: R | null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface GroupingBuild<R extends GridRowModel> {
|
|
128
|
+
treeNodes: Map<GridRowId, GridTreeNode>;
|
|
129
|
+
rootIds: GridRowId[];
|
|
130
|
+
groupIds: GridRowId[];
|
|
131
|
+
groupRowVariants: Map<GridRowId, GroupRowVariants<R>>;
|
|
132
|
+
/** Synthetic rows by id: every group row (with its aggregates) and every subtotal row. */
|
|
133
|
+
groupingRows: Map<GridRowId, R>;
|
|
134
|
+
/** The leaf rows in the tree, by id. */
|
|
135
|
+
rowLookup: Map<GridRowId, R>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const encodePosition = (position: GridGroupAggregationPosition): string =>
|
|
139
|
+
position === 'footer' ? 'f' : position === null ? 'n' : 'i';
|
|
140
|
+
|
|
141
|
+
const decodePosition = (code: string | undefined): GridGroupAggregationPosition =>
|
|
142
|
+
code === 'f' ? 'footer' : code === 'n' ? null : 'inline';
|
|
38
143
|
|
|
39
144
|
export function useRowGrouping<R extends GridRowModel>(params: UseRowGroupingParams<R>) {
|
|
40
145
|
const {
|
|
@@ -46,39 +151,80 @@ export function useRowGrouping<R extends GridRowModel>(params: UseRowGroupingPar
|
|
|
46
151
|
defaultGroupingExpansionDepth = 0,
|
|
47
152
|
filterModel,
|
|
48
153
|
sortModel,
|
|
49
|
-
getAggregationPosition = defaultGetAggregationPosition
|
|
154
|
+
getAggregationPosition = defaultGetAggregationPosition,
|
|
155
|
+
columnLookup,
|
|
156
|
+
filterMode = 'client',
|
|
157
|
+
sortingMode = 'client',
|
|
50
158
|
} = params;
|
|
51
159
|
|
|
160
|
+
// Keyed by value, so an inline `rowGroupingModel={['region']}` or aggregation model does not
|
|
161
|
+
// rebuild (and re-aggregate) every group on each parent render.
|
|
162
|
+
const requestedModelKey = rowGroupingModel.join(FIELD_SEPARATOR);
|
|
163
|
+
const requestedModel = useMemo<GridRowGroupingModel>(
|
|
164
|
+
() => (requestedModelKey === '' ? [] : requestedModelKey.split(FIELD_SEPARATOR)),
|
|
165
|
+
[requestedModelKey]
|
|
166
|
+
);
|
|
167
|
+
const aggregationModelKey = JSON.stringify(Object.entries(aggregationModel));
|
|
168
|
+
const stableAggregationModel = useMemo<GridAggregationModel>(
|
|
169
|
+
() => Object.fromEntries(JSON.parse(aggregationModelKey) as Array<[string, string]>),
|
|
170
|
+
[aggregationModelKey]
|
|
171
|
+
);
|
|
172
|
+
const hasAggregation = Object.keys(stableAggregationModel).length > 0;
|
|
173
|
+
|
|
52
174
|
const columnsLookup = useMemo(() => {
|
|
53
175
|
const map = new Map<string, GridColDef<R>>();
|
|
54
176
|
(columns ?? []).forEach(col => map.set(col.field, col));
|
|
55
177
|
return map;
|
|
56
178
|
}, [columns]);
|
|
57
179
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
180
|
+
const isActive = requestedModel.length > 0;
|
|
181
|
+
|
|
182
|
+
// Fields with `groupable: false` are dropped before building, so the remaining levels keep their
|
|
183
|
+
// depths (indent, defaultGroupingExpansionDepth and export depth) as if the field was never listed.
|
|
184
|
+
const groupingFields = useMemo(
|
|
185
|
+
() => requestedModel.filter(field => columnsLookup.get(field)?.groupable !== false),
|
|
186
|
+
[requestedModel, columnsLookup]
|
|
187
|
+
);
|
|
188
|
+
const groupingFieldsKey = groupingFields.join(FIELD_SEPARATOR);
|
|
189
|
+
|
|
190
|
+
// User expand/collapse choices are stored as overrides on top of the depth-based
|
|
191
|
+
// default, keyed by the grouping config. Rebuilding the tree (row edits, new rows
|
|
192
|
+
// arrays) keeps them because group ids are deterministic; changing the grouping
|
|
193
|
+
// config itself discards them.
|
|
194
|
+
const configKey = `${groupingFieldsKey}|${defaultGroupingExpansionDepth}`;
|
|
195
|
+
const [expansionState, setExpansionState] = useState<ExpansionOverrides>(() => ({
|
|
196
|
+
configKey,
|
|
197
|
+
overrides: new Map(),
|
|
198
|
+
}));
|
|
199
|
+
const overrides = expansionState.configKey === configKey ? expansionState.overrides : EMPTY_OVERRIDES;
|
|
200
|
+
|
|
201
|
+
// Groups hold only the rows that pass the filter, so group subtotals, the "(n)" counts and the set
|
|
202
|
+
// of groups all describe what is shown. With filterMode 'server' the rows already are the result.
|
|
203
|
+
// null = every row passes.
|
|
204
|
+
const matchingRows = useMemo<Set<R> | null>(() => {
|
|
205
|
+
if (!isActive || filterMode === 'server') return null;
|
|
206
|
+
const rowFilter = createRowFilter(filterModel, columnLookup);
|
|
207
|
+
return rowFilter ? new Set(rows.filter(row => rowFilter(row))) : null;
|
|
208
|
+
}, [isActive, rows, filterMode, filterModel, columnLookup]);
|
|
209
|
+
|
|
210
|
+
const build = useMemo<GroupingBuild<R>>(() => {
|
|
61
211
|
const treeNodes = new Map<GridRowId, GridTreeNode>();
|
|
62
|
-
const groupingRows = new Map<GridRowId, R>();
|
|
63
212
|
const rootIds: GridRowId[] = [];
|
|
213
|
+
const groupIds: GridRowId[] = [];
|
|
214
|
+
const groupRowVariants = new Map<GridRowId, GroupRowVariants<R>>();
|
|
215
|
+
const groupingRows = new Map<GridRowId, R>();
|
|
216
|
+
const rowLookup = new Map<GridRowId, R>();
|
|
64
217
|
|
|
65
|
-
if (
|
|
66
|
-
return { treeNodes, rootIds, groupingRows };
|
|
218
|
+
if (!isActive) {
|
|
219
|
+
return { treeNodes, rootIds, groupIds, groupRowVariants, groupingRows, rowLookup };
|
|
67
220
|
}
|
|
68
221
|
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const groupRows = (
|
|
74
|
-
currentRows: R[],
|
|
75
|
-
depth: number,
|
|
76
|
-
parentId: GridRowId | null
|
|
77
|
-
) => {
|
|
78
|
-
if (depth >= rowGroupingModel.length) {
|
|
79
|
-
|
|
80
|
-
return currentRows.map(row => {
|
|
222
|
+
const groupRows = (currentRows: R[], depth: number, parentId: GridRowId | null): GridRowId[] => {
|
|
223
|
+
if (depth >= groupingFields.length) {
|
|
224
|
+
const leaves = matchingRows ? currentRows.filter(row => matchingRows.has(row)) : currentRows;
|
|
225
|
+
return leaves.map(row => {
|
|
81
226
|
const id = getRowId(row);
|
|
227
|
+
rowLookup.set(id, row);
|
|
82
228
|
treeNodes.set(id, {
|
|
83
229
|
id,
|
|
84
230
|
parentId,
|
|
@@ -90,249 +236,222 @@ export function useRowGrouping<R extends GridRowModel>(params: UseRowGroupingPar
|
|
|
90
236
|
});
|
|
91
237
|
}
|
|
92
238
|
|
|
93
|
-
const field =
|
|
94
|
-
|
|
95
|
-
// Skip fields where the column has explicitly opted out of grouping.
|
|
239
|
+
const field = groupingFields[depth];
|
|
96
240
|
const colDef = columnsLookup.get(field);
|
|
97
|
-
if (colDef && colDef.groupable === false) {
|
|
98
|
-
return groupRows(currentRows, depth + 1, parentId);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const groups = new Map<string, R[]>();
|
|
102
241
|
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
242
|
+
// Bucket by the value the cell shows (through valueGetter), keyed so distinct values stay apart.
|
|
243
|
+
const buckets = new Map<string, { value: unknown; rows: R[] }>();
|
|
244
|
+
for (const row of currentRows) {
|
|
245
|
+
const value = getCellValue(row, field, colDef as GridColDef | undefined);
|
|
246
|
+
const key = groupKeyOf(value);
|
|
247
|
+
let bucket = buckets.get(key);
|
|
248
|
+
if (!bucket) {
|
|
249
|
+
bucket = { value, rows: [] };
|
|
250
|
+
buckets.set(key, bucket);
|
|
109
251
|
}
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const groupIds: GridRowId[] = [];
|
|
114
|
-
|
|
115
|
-
// Create group rows
|
|
116
|
-
groups.forEach((groupRowsList, key) => {
|
|
117
|
-
// Use the raw value from the first row of the group for the groupingValue
|
|
118
|
-
const firstRow = groupRowsList[0];
|
|
119
|
-
const rawValue = firstRow[field];
|
|
252
|
+
bucket.rows.push(row);
|
|
253
|
+
}
|
|
120
254
|
|
|
121
|
-
|
|
255
|
+
const ids: GridRowId[] = [];
|
|
256
|
+
buckets.forEach((bucket, key) => {
|
|
257
|
+
// Group order is the order of first appearance among all rows, so filtering never
|
|
258
|
+
// reorders the groups; a group with no matching row is left out.
|
|
259
|
+
const matching = matchingRows ? bucket.rows.filter(row => matchingRows.has(row)) : bucket.rows;
|
|
260
|
+
if (matching.length === 0) return;
|
|
261
|
+
const groupId = groupIdOf(field, key, parentId);
|
|
262
|
+
ids.push(groupId);
|
|
122
263
|
groupIds.push(groupId);
|
|
123
264
|
|
|
124
|
-
//
|
|
125
|
-
const aggregatedValues: Record<string, unknown> =
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const values = groupRowsList.map(r => r[aggField]);
|
|
129
|
-
if (aggType === 'sum') {
|
|
130
|
-
aggregatedValues[aggField] = values.reduce((a: number, b) => a + (Number(b) || 0), 0);
|
|
131
|
-
} else if (aggType === 'min') {
|
|
132
|
-
aggregatedValues[aggField] = Math.min(...values.map(v => Number(v) || 0));
|
|
133
|
-
} else if (aggType === 'max') {
|
|
134
|
-
aggregatedValues[aggField] = Math.max(...values.map(v => Number(v) || 0));
|
|
135
|
-
} else if (aggType === 'avg') {
|
|
136
|
-
const sum = values.reduce((a: number, b) => a + (Number(b) || 0), 0);
|
|
137
|
-
aggregatedValues[aggField] = values.length ? sum / values.length : 0;
|
|
138
|
-
} else if (aggType === 'count') {
|
|
139
|
-
aggregatedValues[aggField] = values.length;
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
}
|
|
265
|
+
// Aggregated over this group's (filtered) leaf rows.
|
|
266
|
+
const aggregatedValues: Record<string, unknown> = hasAggregation
|
|
267
|
+
? computeAggregations(matching, stableAggregationModel, columnsLookup, 'useRowGrouping')
|
|
268
|
+
: {};
|
|
143
269
|
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
id: groupId
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
groupingRows.set(groupId,
|
|
270
|
+
const inline = { [field]: bucket.value, ...aggregatedValues, id: groupId } as unknown as R;
|
|
271
|
+
const bare = { [field]: bucket.value, id: groupId } as unknown as R;
|
|
272
|
+
const footer = hasAggregation
|
|
273
|
+
? ({ ...aggregatedValues, id: footerIdOf(groupId) } as unknown as R)
|
|
274
|
+
: null;
|
|
275
|
+
groupRowVariants.set(groupId, { inline, bare, footer });
|
|
276
|
+
groupingRows.set(groupId, inline);
|
|
277
|
+
if (footer) groupingRows.set(footer.id, footer);
|
|
151
278
|
|
|
152
279
|
const treeNode: GridTreeNode = {
|
|
153
280
|
id: groupId,
|
|
154
281
|
parentId,
|
|
155
282
|
depth,
|
|
156
|
-
groupingKey:
|
|
283
|
+
groupingKey: String(bucket.value),
|
|
157
284
|
groupingField: field,
|
|
158
|
-
groupingValue:
|
|
285
|
+
groupingValue: bucket.value,
|
|
159
286
|
aggregatedValues,
|
|
160
287
|
isExpanded: false,
|
|
161
|
-
children: [],
|
|
162
|
-
label: colDef
|
|
163
|
-
|
|
164
|
-
: `${field}: ${String(rawValue)}`
|
|
288
|
+
children: [],
|
|
289
|
+
label: formatGroupLabel(colDef, field, bucket.value),
|
|
290
|
+
descendantCount: matching.length,
|
|
165
291
|
};
|
|
166
|
-
|
|
167
|
-
treeNode.aggregationPosition = getAggregationPosition(treeNode);
|
|
168
|
-
|
|
169
292
|
treeNodes.set(groupId, treeNode);
|
|
170
|
-
|
|
171
|
-
const childrenIds = groupRows(groupRowsList, depth + 1, groupId);
|
|
172
|
-
treeNodes.get(groupId)!.children = childrenIds;
|
|
173
|
-
|
|
174
|
-
const descendantCount = groupRowsList.length;
|
|
175
|
-
treeNodes.get(groupId)!.descendantCount = descendantCount;
|
|
293
|
+
treeNode.children = groupRows(bucket.rows, depth + 1, groupId);
|
|
176
294
|
});
|
|
177
|
-
|
|
178
|
-
return groupIds;
|
|
295
|
+
return ids;
|
|
179
296
|
};
|
|
180
297
|
|
|
181
|
-
|
|
182
|
-
rootIds
|
|
183
|
-
|
|
184
|
-
return { treeNodes, rootIds, groupingRows };
|
|
298
|
+
rootIds.push(...groupRows(rows, 0, null));
|
|
299
|
+
return { treeNodes, rootIds, groupIds, groupRowVariants, groupingRows, rowLookup };
|
|
300
|
+
}, [isActive, rows, matchingRows, groupingFields, hasAggregation, stableAggregationModel, getRowId, columnsLookup]);
|
|
185
301
|
|
|
186
|
-
|
|
302
|
+
const { treeNodes, rootIds, groupIds, groupRowVariants, groupingRows, rowLookup } = build;
|
|
187
303
|
|
|
188
|
-
|
|
189
|
-
|
|
304
|
+
const isDefaultExpanded = useCallback((node: GridTreeNode | undefined) => (
|
|
305
|
+
Boolean(node?.children && node.children.length > 0) &&
|
|
306
|
+
(defaultGroupingExpansionDepth === -1 || (node?.depth ?? 0) < defaultGroupingExpansionDepth)
|
|
307
|
+
), [defaultGroupingExpansionDepth]);
|
|
190
308
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const depthIds = new Set<GridRowId>();
|
|
200
|
-
treeNodes.forEach((node, id) => {
|
|
201
|
-
if (node.depth < defaultGroupingExpansionDepth && node.children && node.children.length > 0) {
|
|
202
|
-
depthIds.add(id);
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
setExpandedGroupIds(depthIds);
|
|
206
|
-
} else {
|
|
207
|
-
|
|
208
|
-
setExpandedGroupIds(new Set());
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
}, [rowGroupingModel, defaultGroupingExpansionDepth, treeNodes]);
|
|
309
|
+
const expandedGroupIds = useMemo<Set<GridRowId>>(() => {
|
|
310
|
+
const ids = new Set<GridRowId>();
|
|
311
|
+
treeNodes.forEach((node, id) => {
|
|
312
|
+
if (!node.children || node.children.length === 0) return;
|
|
313
|
+
if (overrides.get(id) ?? isDefaultExpanded(node)) ids.add(id);
|
|
314
|
+
});
|
|
315
|
+
return ids;
|
|
316
|
+
}, [treeNodes, overrides, isDefaultExpanded]);
|
|
212
317
|
|
|
213
318
|
const toggleExpansion = useCallback((id: GridRowId) => {
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
return next;
|
|
319
|
+
setExpansionState(prev => {
|
|
320
|
+
const base = prev.configKey === configKey ? prev.overrides : EMPTY_OVERRIDES;
|
|
321
|
+
const current = base.get(id) ?? isDefaultExpanded(treeNodes.get(id));
|
|
322
|
+
const next = new Map(base);
|
|
323
|
+
next.set(id, !current);
|
|
324
|
+
return { configKey, overrides: next };
|
|
222
325
|
});
|
|
223
|
-
}, []);
|
|
326
|
+
}, [configKey, treeNodes, isDefaultExpanded]);
|
|
224
327
|
|
|
225
328
|
const isGroupExpanded = useCallback((id: GridRowId) => {
|
|
226
329
|
return expandedGroupIds.has(id);
|
|
227
330
|
}, [expandedGroupIds]);
|
|
228
331
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
332
|
+
// getAggregationPosition runs once per group per render (it receives the group's current
|
|
333
|
+
// expansion state), but only its answers are memoized on, so an inline callback does not
|
|
334
|
+
// rebuild the rows or the row metadata.
|
|
335
|
+
const positionsKey = hasAggregation
|
|
336
|
+
? groupIds.map(id => {
|
|
337
|
+
const node = treeNodes.get(id);
|
|
338
|
+
return node ? encodePosition(callAggregationPosition(getAggregationPosition, { ...node, isExpanded: expandedGroupIds.has(id) })) : 'i';
|
|
339
|
+
}).join('')
|
|
340
|
+
: '';
|
|
341
|
+
const aggregationPositions = useMemo<Map<GridRowId, GridGroupAggregationPosition>>(() => {
|
|
342
|
+
const map = new Map<GridRowId, GridGroupAggregationPosition>();
|
|
343
|
+
groupIds.forEach((id, index) => map.set(id, decodePosition(positionsKey[index])));
|
|
344
|
+
return map;
|
|
345
|
+
}, [groupIds, positionsKey]);
|
|
346
|
+
|
|
347
|
+
/** Where the grand total goes: `null` hides the grid's aggregation footer. */
|
|
348
|
+
const rootAggregationPosition: GridGroupAggregationPosition = hasAggregation ? callAggregationPosition(getAggregationPosition, null) : 'footer';
|
|
349
|
+
|
|
350
|
+
const isClientSort = sortingMode !== 'server' && Boolean(sortModel && sortModel.length > 0);
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* The rows to render, in order. `labelField` is the column that shows the group labels: sorting
|
|
354
|
+
* by it orders the groups by their grouping value (a group row has no value of its own there).
|
|
355
|
+
*/
|
|
356
|
+
const getVisibleRows = useCallback((options?: GridHierarchyVisibleRowsOptions): R[] | null => {
|
|
357
|
+
if (!isActive) return null;
|
|
358
|
+
const labelField = options?.labelField;
|
|
359
|
+
const expandAll = options?.expandAll ?? false;
|
|
360
|
+
|
|
361
|
+
const activeSortModel = isClientSort && sortModel ? sortModel : [];
|
|
362
|
+
|
|
363
|
+
const readSortValue = (id: GridRowId, sortItem: GridSortItem): GridSortValue => {
|
|
364
|
+
const node = treeNodes.get(id);
|
|
365
|
+
if (node && groupRowVariants.has(id)) {
|
|
366
|
+
const groupingField = node.groupingField ?? '';
|
|
367
|
+
if (sortItem.field === groupingField || sortItem.field === labelField) {
|
|
368
|
+
return { value: node.groupingValue, type: columnLookup?.byField.get(groupingField)?.type };
|
|
369
|
+
}
|
|
370
|
+
if (node.aggregatedValues && Object.prototype.hasOwnProperty.call(node.aggregatedValues, sortItem.field)) {
|
|
371
|
+
return { value: node.aggregatedValues[sortItem.field], type: columnLookup?.byField.get(sortItem.field)?.type };
|
|
249
372
|
}
|
|
250
|
-
return
|
|
373
|
+
return { value: undefined };
|
|
251
374
|
}
|
|
252
|
-
|
|
253
|
-
|
|
375
|
+
const row = rowLookup.get(id);
|
|
376
|
+
const colDef = columnLookup?.byField.get(sortItem.field);
|
|
377
|
+
return { value: row ? getCellValue(row, sortItem.field, colDef) : undefined, type: colDef?.type };
|
|
254
378
|
};
|
|
255
379
|
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
let visibleIds = ids;
|
|
259
|
-
if (filterModel) {
|
|
260
|
-
visibleIds = ids.filter(doesNodeMatchFilter);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (sortModel && sortModel.length > 0) {
|
|
264
|
-
visibleIds.sort((aId, bId) => {
|
|
265
|
-
const rowA = groupingRows.get(aId) || rowLookup.get(aId);
|
|
266
|
-
const rowB = groupingRows.get(bId) || rowLookup.get(bId);
|
|
267
|
-
|
|
268
|
-
if (!rowA || !rowB) return 0;
|
|
269
|
-
|
|
270
|
-
for (const sortItem of sortModel) {
|
|
271
|
-
const valA = rowA[sortItem.field];
|
|
272
|
-
const valB = rowB[sortItem.field];
|
|
273
|
-
const compareResult = compareValues(valA, valB, sortItem.sort);
|
|
274
|
-
if (compareResult !== 0) return compareResult;
|
|
275
|
-
}
|
|
276
|
-
return 0;
|
|
277
|
-
});
|
|
278
|
-
}
|
|
380
|
+
const result: R[] = [];
|
|
381
|
+
const seenIds = new Set<GridRowId>();
|
|
279
382
|
|
|
280
|
-
|
|
281
|
-
|
|
383
|
+
const traverse = (ids: readonly GridRowId[]) => {
|
|
384
|
+
const ordered = activeSortModel.length > 0 ? sortItemsBySortModel(ids, activeSortModel, readSortValue) : ids;
|
|
385
|
+
for (const id of ordered) {
|
|
386
|
+
if (seenIds.has(id)) continue;
|
|
387
|
+
seenIds.add(id);
|
|
388
|
+
|
|
389
|
+
const variants = groupRowVariants.get(id);
|
|
390
|
+
if (!variants) {
|
|
391
|
+
// Hierarchy info travels in rowMetaMap; the consumer's row object is passed through unchanged.
|
|
392
|
+
const row = rowLookup.get(id);
|
|
393
|
+
if (row) result.push(row);
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
282
396
|
|
|
283
397
|
const node = treeNodes.get(id);
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
_groupingField: node.groupingField,
|
|
297
|
-
_groupingValue: node.groupingValue,
|
|
298
|
-
_descendantCount: node.descendantCount
|
|
299
|
-
};
|
|
300
|
-
result.push(enhancedRow);
|
|
301
|
-
|
|
302
|
-
if (isExpandedGroupIds.has(id) && isGroup) {
|
|
303
|
-
traverse(node.children!);
|
|
304
|
-
}
|
|
398
|
+
const isExpanded = expandedGroupIds.has(id);
|
|
399
|
+
// null is a position ("hidden"), so no `??` default here.
|
|
400
|
+
const position = aggregationPositions.has(id) ? aggregationPositions.get(id) ?? null : 'inline';
|
|
401
|
+
// 'footer' moves the aggregates below the children; while collapsed there is no
|
|
402
|
+
// subtotal row, so they stay on the group row instead of disappearing.
|
|
403
|
+
const showInline = position === 'inline' || (position === 'footer' && !isExpanded);
|
|
404
|
+
result.push(showInline ? variants.inline : variants.bare);
|
|
405
|
+
|
|
406
|
+
if ((isExpanded || expandAll) && node?.children) {
|
|
407
|
+
traverse(node.children);
|
|
408
|
+
// expandAll (exports) walks the data only; exporters write their own subtotals.
|
|
409
|
+
if (isExpanded && !expandAll && position === 'footer' && variants.footer) result.push(variants.footer);
|
|
305
410
|
}
|
|
306
|
-
}
|
|
411
|
+
}
|
|
307
412
|
};
|
|
308
413
|
|
|
309
|
-
const isExpandedGroupIds = expandedGroupIds;
|
|
310
|
-
|
|
311
414
|
traverse(rootIds);
|
|
312
|
-
|
|
313
415
|
return result;
|
|
314
|
-
|
|
315
|
-
}, [rows, getRowId, treeNodes, rootIds, groupingRows, filterModel, sortModel, expandedGroupIds, rowGroupingModel]);
|
|
416
|
+
}, [isActive, isClientSort, sortModel, treeNodes, groupRowVariants, rowLookup, columnLookup, expandedGroupIds, aggregationPositions, rootIds]);
|
|
316
417
|
|
|
317
418
|
const getNode = useCallback((id: GridRowId) => treeNodes.get(id), [treeNodes]);
|
|
318
419
|
|
|
319
420
|
const rowMetaMap = useMemo<Map<GridRowId, GridRowMeta>>(() => {
|
|
320
421
|
const map = new Map<GridRowId, GridRowMeta>();
|
|
321
422
|
treeNodes.forEach((node, id) => {
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
423
|
+
const variants = groupRowVariants.get(id);
|
|
424
|
+
if (!variants) {
|
|
425
|
+
map.set(id, {
|
|
426
|
+
hasChildren: false,
|
|
427
|
+
treeDepth: node.depth,
|
|
428
|
+
isGroupRow: false,
|
|
429
|
+
});
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const groupMeta: GridRowMeta = {
|
|
433
|
+
hasChildren: true,
|
|
325
434
|
treeDepth: node.depth,
|
|
326
435
|
groupingField: node.groupingField,
|
|
327
436
|
groupingValue: node.groupingValue,
|
|
328
437
|
groupLabel: node.label,
|
|
329
438
|
descendantCount: node.descendantCount,
|
|
330
|
-
isGroupRow:
|
|
331
|
-
isExpanded:
|
|
332
|
-
}
|
|
439
|
+
isGroupRow: true,
|
|
440
|
+
isExpanded: expandedGroupIds.has(id),
|
|
441
|
+
};
|
|
442
|
+
map.set(id, groupMeta);
|
|
443
|
+
if (variants.footer && aggregationPositions.get(id) === 'footer') {
|
|
444
|
+
map.set(variants.footer.id, {
|
|
445
|
+
...groupMeta,
|
|
446
|
+
hasChildren: false,
|
|
447
|
+
treeDepth: node.depth + 1,
|
|
448
|
+
isExpanded: undefined,
|
|
449
|
+
isGroupFooter: true,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
333
452
|
});
|
|
334
453
|
return map;
|
|
335
|
-
}, [treeNodes, expandedGroupIds]);
|
|
454
|
+
}, [treeNodes, groupRowVariants, expandedGroupIds, aggregationPositions]);
|
|
336
455
|
|
|
337
456
|
return useMemo(() => ({
|
|
338
457
|
treeNodes,
|
|
@@ -342,5 +461,7 @@ export function useRowGrouping<R extends GridRowModel>(params: UseRowGroupingPar
|
|
|
342
461
|
getVisibleRows,
|
|
343
462
|
getNode,
|
|
344
463
|
rowMetaMap,
|
|
345
|
-
|
|
464
|
+
aggregationPositions,
|
|
465
|
+
rootAggregationPosition,
|
|
466
|
+
}), [treeNodes, groupingRows, toggleExpansion, isGroupExpanded, getVisibleRows, getNode, rowMetaMap, aggregationPositions, rootAggregationPosition]);
|
|
346
467
|
}
|