@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,6 +1,6 @@
|
|
|
1
1
|
# 🔢 Sorting & Pagination
|
|
2
2
|
|
|
3
|
-
Efficiently
|
|
3
|
+
Efficiently navigate and organize through large datasets using OpenGridX's sorting and pagination engines.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -34,18 +34,32 @@ OpenGridX supports two modes of multi-column sorting:
|
|
|
34
34
|
|
|
35
35
|
Each active sort column shows a numbered priority badge (`1`, `2`, `3`…) next to its arrow.
|
|
36
36
|
|
|
37
|
-
- Clicking (or shift-clicking) an already-sorted column cycles its direction: `asc → desc → removed`.
|
|
37
|
+
- Clicking (or shift-clicking) an already-sorted column cycles its direction: `asc → desc → removed`. The column keeps its priority while its direction changes; it is not moved to the end of the sort model.
|
|
38
38
|
- A plain click *without* `multiSort` and without Shift always resets to a single-key sort on that column.
|
|
39
|
+
- The column menu acts on that column only: **Unsort** removes just that key, and **Sort Ascending / Descending** on a column that is already sorted changes its direction in place. On a column that is not sorted yet it appends with `multiSort` and replaces the model without it, like a plain click.
|
|
39
40
|
- Set `sortable: false` on a column to exclude it entirely.
|
|
40
41
|
|
|
42
|
+
### How values are compared (client-side)
|
|
43
|
+
|
|
44
|
+
- Cells are read through the column's `valueGetter`, so computed columns sort by the value they display.
|
|
45
|
+
- `type: 'number'` columns compare numerically even when the data holds numeric strings (`'9'` sorts before `'10'`). `type: 'date'` columns parse date strings (`'YYYY-MM-DD'` is read as a local date) and compare chronologically.
|
|
46
|
+
- Strings use language-aware ordering (`Intl.Collator`): case is ignored, accented letters sort next to their base letter (`'Émile'` between `'Adam'` and `'Zoe'`), and digit runs compare by value (`'item9'` before `'item10'`).
|
|
47
|
+
- `null`, `undefined`, `NaN`, an Invalid Date and, in number / date columns, values that cannot be parsed are "empty": they sort last in ascending order and first in descending order.
|
|
48
|
+
- A column that mixes kinds of value (numbers and strings, say) is ordered by kind first — numbers, dates, booleans, strings — so the order does not depend on the input order.
|
|
49
|
+
- Ties keep the rows' original order.
|
|
50
|
+
|
|
41
51
|
```tsx
|
|
42
52
|
// Controlled multi-sort — set programmatically or drive from UI with multiSort:
|
|
53
|
+
const [sortModel, setSortModel] = useState<GridSortItem[]>([
|
|
54
|
+
{ field: 'department', sort: 'asc' },
|
|
55
|
+
{ field: 'salary', sort: 'desc' },
|
|
56
|
+
]);
|
|
57
|
+
|
|
43
58
|
<DataGrid
|
|
59
|
+
rows={rows}
|
|
60
|
+
columns={columns}
|
|
44
61
|
multiSort
|
|
45
|
-
sortModel={
|
|
46
|
-
{ field: 'department', sort: 'asc' },
|
|
47
|
-
{ field: 'salary', sort: 'desc' },
|
|
48
|
-
]}
|
|
62
|
+
sortModel={sortModel}
|
|
49
63
|
onSortModelChange={setSortModel}
|
|
50
64
|
/>
|
|
51
65
|
```
|
|
@@ -62,27 +76,64 @@ Disable sorting for specific columns in `GridColDef`:
|
|
|
62
76
|
|
|
63
77
|
OpenGridX supports standard page-based pagination and infinite scrolling.
|
|
64
78
|
|
|
79
|
+
> **Pagination is ignored while row grouping is active.** When `rowGroupingModel` is non-empty the grid renders all groups in one scrollable, virtualized view and does not show the pager, even if `pagination` is set. A development-mode `console.warn` flags this combination. Give a grouped grid a bounded height so virtualization limits what renders; see [Virtualization](./virtualization.md#the-grid-needs-a-bounded-height).
|
|
80
|
+
|
|
65
81
|
### Usage
|
|
66
82
|
```tsx
|
|
83
|
+
// Uncontrolled: the grid keeps the page; initialState sets where it starts
|
|
67
84
|
<DataGrid
|
|
85
|
+
rows={rows}
|
|
86
|
+
columns={columns}
|
|
68
87
|
pagination
|
|
69
|
-
paginationModel={{ page: 0, pageSize: 10 }}
|
|
70
88
|
pageSizeOptions={[5, 10, 20, 50]}
|
|
89
|
+
initialState={{ pagination: { paginationModel: { page: 0, pageSize: 10 } } }}
|
|
90
|
+
/>
|
|
91
|
+
|
|
92
|
+
// Controlled: pass onPaginationModelChange too, or the pager cannot change page
|
|
93
|
+
const [paginationModel, setPaginationModel] = useState({ page: 0, pageSize: 10 });
|
|
94
|
+
<DataGrid
|
|
95
|
+
rows={rows}
|
|
96
|
+
columns={columns}
|
|
97
|
+
pagination
|
|
98
|
+
paginationModel={paginationModel}
|
|
99
|
+
onPaginationModelChange={setPaginationModel}
|
|
71
100
|
/>
|
|
72
101
|
```
|
|
73
102
|
|
|
103
|
+
The pager counts the rows being paged: the rows that pass the filter, excluding pinned rows (pinned rows show on every page).
|
|
104
|
+
|
|
105
|
+
Without `paginationModel` or `initialState.pagination`, the page size is 100 when `pageSizeOptions` offers it and otherwise the first option, so the rows-per-page select always matches the rows shown.
|
|
106
|
+
|
|
107
|
+
### When the rows shrink
|
|
108
|
+
If the current page ends up past the last page (new `rows`, a filter, collapsed tree nodes), the grid shows the last page and reports the corrected model through `onPaginationModelChange`, so Previous and Next continue from the page on screen. The correction waits until rows are present and nothing is loading, so a restored page (for example from `initialState.pagination`) survives an initially empty `rows` array. Under server pagination the server's `rowCount` decides; the grid does not clamp.
|
|
109
|
+
|
|
110
|
+
### Page sizes outside `pageSizeOptions`
|
|
111
|
+
When the page size in use is not one of `pageSizeOptions` (for example a controlled `paginationModel={{ page: 0, pageSize: 20 }}` with `pageSizeOptions={[10, 25, 50]}`), the rows-per-page select adds it as an extra option, so it shows the size actually used. A page size below 1 is treated as 1.
|
|
112
|
+
|
|
74
113
|
### Server-Side Pagination
|
|
75
|
-
|
|
114
|
+
With `paginationMode="server"` the grid does not slice rows: `rows` is the current page. There are two ways to supply it.
|
|
115
|
+
|
|
116
|
+
**Fetch the page yourself** — control `paginationModel`, pass the page's rows and the server total as `rowCount`. `rowCount` is read on every render, so update it when the total changes.
|
|
76
117
|
|
|
77
118
|
```tsx
|
|
119
|
+
const [paginationModel, setPaginationModel] = useState({ page: 0, pageSize: 10 });
|
|
120
|
+
const { rows, total } = usePageQuery(paginationModel); // your data fetching
|
|
121
|
+
|
|
78
122
|
<DataGrid
|
|
123
|
+
rows={rows} // only the current page
|
|
124
|
+
columns={columns}
|
|
79
125
|
pagination
|
|
80
126
|
paginationMode="server"
|
|
81
|
-
rowCount={
|
|
82
|
-
|
|
127
|
+
rowCount={total} // total rows on the server
|
|
128
|
+
paginationModel={paginationModel}
|
|
129
|
+
onPaginationModelChange={setPaginationModel}
|
|
83
130
|
/>
|
|
84
131
|
```
|
|
85
132
|
|
|
133
|
+
**Or use a `dataSource`** — the grid calls `getRows` with `startRow` / `endRow` and uses the response's `rowCount` as the total (the `rowCount` prop is only a fallback until the first response). See [Server-Side Data](./data-source.md).
|
|
134
|
+
|
|
135
|
+
`sortingMode="server"` and `filterMode="server"` work the same way: the grid fires `onSortModelChange` / `onFilterModelChange` and shows `rows` as given, without re-sorting or re-filtering them, with or without a `dataSource`.
|
|
136
|
+
|
|
86
137
|
### Props
|
|
87
138
|
| Prop | Type | Default | Description |
|
|
88
139
|
| :--- | :--- | :--- | :--- |
|
|
@@ -90,14 +141,15 @@ When using `paginationMode="server"`, you must provide the `rowCount` and handle
|
|
|
90
141
|
| `paginationMode` | `'client' \| 'server' \| 'infinite'` | `'client'` | Location of the pagination logic. |
|
|
91
142
|
| `paginationModel` | `GridPaginationModel` | — | Controlled pagination state (`{ page, pageSize }`). |
|
|
92
143
|
| `onPaginationModelChange` | `(model: GridPaginationModel) => void` | — | Fired when page or page size changes. |
|
|
93
|
-
| `pageSizeOptions` | `number[]` | `[10, 25, 50, 100]` | Options for the rows-per-page selector. |
|
|
144
|
+
| `pageSizeOptions` | `number[]` | `[10, 25, 50, 100]` | Options for the rows-per-page selector. The uncontrolled default page size is 100 when offered, else the first option. |
|
|
145
|
+
| `rowCount` | `number` | — | Server total for `paginationMode="server"` without a `dataSource` (with one, the response's `rowCount` is used). |
|
|
94
146
|
|
|
95
147
|
---
|
|
96
148
|
|
|
97
149
|
## ⚡ Infinite Scrolling
|
|
98
150
|
For a more modern experience, use infinite scroll combined with virtualization.
|
|
99
151
|
|
|
100
|
-
1. Set `paginationMode="infinite"`.
|
|
101
|
-
2.
|
|
152
|
+
1. Set `paginationMode="infinite"` and pass a `dataSource`.
|
|
153
|
+
2. Advance `paginationModel.page` from `onRowsScrollEnd`; the grid requests the next rows from `dataSource.getRows` and appends them. The pager is not shown and rows are not sliced.
|
|
102
154
|
|
|
103
|
-
📖 **[Full Infinite Scroll Guide](
|
|
155
|
+
📖 **[Full Infinite Scroll Guide](./infinite-scroll.md)**
|
|
@@ -6,18 +6,29 @@ Save and restore the grid's configuration (sorting, filtering, column order, etc
|
|
|
6
6
|
|
|
7
7
|
## 🏗️ The `initialState` Prop
|
|
8
8
|
|
|
9
|
-
You can pre-configure the grid status on mount using the `initialState` prop.
|
|
9
|
+
You can pre-configure the grid status on mount using the `initialState` prop. Every part is optional, including each field of `columns`.
|
|
10
10
|
|
|
11
11
|
```tsx
|
|
12
12
|
<DataGrid
|
|
13
|
+
rows={rows}
|
|
14
|
+
columns={columns}
|
|
13
15
|
initialState={{
|
|
14
16
|
sorting: {
|
|
15
17
|
sortModel: [{ field: 'name', sort: 'asc' }]
|
|
16
|
-
}
|
|
18
|
+
},
|
|
19
|
+
filter: {
|
|
20
|
+
filterModel: { items: [{ field: 'age', operator: '>', value: 30 }] }
|
|
21
|
+
},
|
|
22
|
+
columns: {
|
|
23
|
+
columnVisibilityModel: { age: false }
|
|
24
|
+
},
|
|
25
|
+
density: { density: 'compact' }
|
|
17
26
|
}}
|
|
18
27
|
/>
|
|
19
28
|
```
|
|
20
29
|
|
|
30
|
+
`initialState` is read once, when the grid mounts. It seeds the grid's own (uncontrolled) state, so it has no effect on a model you also pass as a prop (`sortModel`, `filterModel`, `paginationModel`, …), and the `density` prop wins over `initialState.density`.
|
|
31
|
+
|
|
21
32
|
---
|
|
22
33
|
|
|
23
34
|
## ⚓ The `useGridStateStorage` Hook
|
|
@@ -28,26 +39,81 @@ OpenGridX provides a built-in hook to simplify `localStorage` persistence.
|
|
|
28
39
|
import { DataGrid, useGridStateStorage } from '@opencorestack/opengridx';
|
|
29
40
|
|
|
30
41
|
export default function MyGrid() {
|
|
31
|
-
const { initialState, onStateChange } = useGridStateStorage('my-app-storage-key');
|
|
42
|
+
const { initialState, onStateChange, clearState } = useGridStateStorage('my-app-storage-key');
|
|
32
43
|
|
|
33
44
|
return (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
45
|
+
<>
|
|
46
|
+
<button type="button" onClick={clearState}>Forget saved layout</button>
|
|
47
|
+
<DataGrid
|
|
48
|
+
rows={rows}
|
|
49
|
+
columns={columns}
|
|
50
|
+
initialState={initialState}
|
|
51
|
+
onStateChange={onStateChange}
|
|
52
|
+
/>
|
|
53
|
+
</>
|
|
39
54
|
);
|
|
40
55
|
}
|
|
41
56
|
```
|
|
42
57
|
|
|
58
|
+
Writes are debounced (`debounceMs`, default 300 ms), and a pending write is flushed when the component unmounts. When the browser blocks storage (cookie blocking, sandboxed iframes), the hook falls back to no persistence instead of throwing.
|
|
59
|
+
|
|
60
|
+
Options (pass an object instead of the key string):
|
|
61
|
+
|
|
62
|
+
| Option | Type | Default | Description |
|
|
63
|
+
| :--- | :--- | :--- | :--- |
|
|
64
|
+
| `key` | `string` | — | Storage key. |
|
|
65
|
+
| `debounceMs` | `number` | `300` | Delay before a state change is written. |
|
|
66
|
+
| `include` | `(keyof GridState)[]` | all | Only persist these parts, e.g. `['sorting', 'columns']`. |
|
|
67
|
+
| `storage` | `{ getItem, setItem, removeItem }` | `window.localStorage` | Any Storage-like object (e.g. `sessionStorage`). |
|
|
68
|
+
|
|
69
|
+
`clearState()` removes the saved state and cancels any pending write, so the old state is not written back afterwards. It does not reset the mounted grid, and the grid's next state change is saved again; remount the grid (for example with a new `key`) to start from defaults.
|
|
70
|
+
|
|
71
|
+
### Server-side rendering (Next.js, Remix)
|
|
72
|
+
|
|
73
|
+
`useGridStateStorage` is client-only. It reads storage in its initial render, so on the server `initialState` is empty while the browser's first render already has the saved state: when saved state exists, React reports a hydration mismatch (the sort arrows, column order or page differ). Render the persisted grid only after mount, for example:
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
function PersistedGrid() {
|
|
77
|
+
const { initialState, onStateChange } = useGridStateStorage('my-app-storage-key');
|
|
78
|
+
return <DataGrid rows={rows} columns={columns} initialState={initialState} onStateChange={onStateChange} />;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default function Page() {
|
|
82
|
+
const [mounted, setMounted] = useState(false);
|
|
83
|
+
useEffect(() => setMounted(true), []);
|
|
84
|
+
return mounted ? <PersistedGrid /> : <GridPlaceholder />;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
In Next.js, `dynamic(() => import('./PersistedGrid'), { ssr: false })` does the same.
|
|
89
|
+
|
|
90
|
+
### Changing the key
|
|
91
|
+
|
|
92
|
+
The grid reads `initialState` only when it mounts. If the key can change while the grid stays on screen (a per-user or per-view key), remount the grid with the key so it starts from the new key's saved state:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
const storageKey = `grid-${userId}`;
|
|
96
|
+
const { initialState, onStateChange } = useGridStateStorage(storageKey);
|
|
97
|
+
|
|
98
|
+
<DataGrid key={storageKey} rows={rows} columns={columns} initialState={initialState} onStateChange={onStateChange} />
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Without the remount the grid keeps its current state, and its next change is saved under the new key.
|
|
102
|
+
|
|
103
|
+
### Server-side rendering
|
|
104
|
+
|
|
105
|
+
The hook reads storage during the first render. On the server there is no storage, so the server HTML uses the default state while the client's first render uses the saved one, and React reports a hydration mismatch. If users can have saved state, render the persisted grid on the client only (for example after mount, or with Next.js `dynamic(..., { ssr: false })`). The [migration guide, §26](../migration/v2-to-v3.md#26-state-persistence) has a complete example.
|
|
106
|
+
|
|
43
107
|
---
|
|
44
108
|
|
|
45
109
|
## 📡 Tracking Changes
|
|
46
110
|
|
|
47
|
-
Use the `onStateChange` callback to listen for
|
|
111
|
+
Use the `onStateChange` callback to listen for modifications to the grid state and save them to `localStorage` or a database. It fires once on mount and then whenever the state's *value* changes — re-rendering the grid with equal (even inline) props does not fire it, so storing the state in parent React state is safe.
|
|
48
112
|
|
|
49
113
|
```tsx
|
|
50
114
|
<DataGrid
|
|
115
|
+
rows={rows}
|
|
116
|
+
columns={columns}
|
|
51
117
|
onStateChange={(state) => {
|
|
52
118
|
localStorage.setItem('grid-state', JSON.stringify(state));
|
|
53
119
|
}}
|
|
@@ -58,9 +124,11 @@ Use the `onStateChange` callback to listen for any modifications to the grid sta
|
|
|
58
124
|
|
|
59
125
|
## 🧩 Supported State Objects
|
|
60
126
|
|
|
61
|
-
The following features support state persistence:
|
|
127
|
+
The following features support state persistence (they appear in the `onStateChange` payload and are restored from `initialState`):
|
|
62
128
|
- **Sorting**: `sortModel`
|
|
63
129
|
- **Filtering**: `filterModel`
|
|
64
130
|
- **Pagination**: `paginationModel`
|
|
65
131
|
- **Columns**: `columnVisibilityModel`, `columnOrder`, `pinnedColumns`, `columnWidths`
|
|
66
132
|
- **Density**: `density`
|
|
133
|
+
|
|
134
|
+
Only your own columns appear in the column state: the grid's synthetic grouping column (added by `groupingColDef`) is never included.
|
|
@@ -19,10 +19,6 @@ import {
|
|
|
19
19
|
<DataGrid
|
|
20
20
|
rows={rows}
|
|
21
21
|
columns={columns}
|
|
22
|
-
filterModel={filterModel}
|
|
23
|
-
onFilterModelChange={setFilterModel}
|
|
24
|
-
columnVisibilityModel={columnVisibilityModel}
|
|
25
|
-
onColumnVisibilityModelChange={setColumnVisibilityModel}
|
|
26
22
|
slots={{ toolbar: GridToolbar }}
|
|
27
23
|
slotProps={{
|
|
28
24
|
toolbar: {
|
|
@@ -39,6 +35,19 @@ import {
|
|
|
39
35
|
/>
|
|
40
36
|
```
|
|
41
37
|
|
|
38
|
+
When the toolbar is mounted through `slots.toolbar`, the grid passes it the filter, column-visibility, aggregation and column-order state and handlers itself, so search, Filters, Columns and Summaries work without any controlled props (v3.0+: the search box and Filters button no longer need `filterModel` / `onFilterModelChange`). Pass `filterModel` + `onFilterModelChange` (and the other controlled pairs) only when you want to own that state.
|
|
39
|
+
|
|
40
|
+
### Which buttons appear
|
|
41
|
+
|
|
42
|
+
`GridToolbar` renders a control only when it has the handler it needs. Under `slots.toolbar` the grid supplies these, except for pivot:
|
|
43
|
+
|
|
44
|
+
| Control | Shown when |
|
|
45
|
+
| :--- | :--- |
|
|
46
|
+
| Search + Filters | `onFilterModelChange` is present (always under `slots.toolbar`) |
|
|
47
|
+
| Columns | `onColumnVisibilityModelChange` is present (always under `slots.toolbar`) |
|
|
48
|
+
| Pivot | `onPivotModelChange` is present (under `slots.toolbar`: when `pivotMode`, `pivotModel` or `onPivotModelChange` is set on the grid) |
|
|
49
|
+
| Summaries | `onAggregationModelChange` is present (always under `slots.toolbar`; a standalone `<GridToolbar>` without it shows no Summaries button, v3.0+) |
|
|
50
|
+
|
|
42
51
|
---
|
|
43
52
|
|
|
44
53
|
## New props on `GridToolbarProps`
|
|
@@ -70,12 +79,12 @@ interface ToolbarButtonRenderProps {
|
|
|
70
79
|
|
|
71
80
|
```ts
|
|
72
81
|
interface ToolbarQuickFilterRenderProps {
|
|
73
|
-
value: string;
|
|
74
|
-
onChange: (value: string) => void;
|
|
82
|
+
value: string; // quickFilterValues joined with spaces
|
|
83
|
+
onChange: (value: string) => void; // split on whitespace into quickFilterValues terms
|
|
75
84
|
}
|
|
76
85
|
```
|
|
77
86
|
|
|
78
|
-
Both types are exported from the package:
|
|
87
|
+
Both types are exported from the package. `slotProps.toolbar` is typed as `Record<string, unknown>`, so annotate render-prop parameters with these types (an unannotated `(props) => …` is an implicit `any` under `strict`):
|
|
79
88
|
|
|
80
89
|
```ts
|
|
81
90
|
import type { ToolbarButtonRenderProps, ToolbarQuickFilterRenderProps } from '@opencorestack/opengridx';
|
|
@@ -104,7 +113,7 @@ function MySearchBar({ value, onChange }: ToolbarQuickFilterRenderProps) {
|
|
|
104
113
|
|
|
105
114
|
slotProps={{
|
|
106
115
|
toolbar: {
|
|
107
|
-
renderQuickFilter: (props) => <MySearchBar {...props} />,
|
|
116
|
+
renderQuickFilter: (props: ToolbarQuickFilterRenderProps) => <MySearchBar {...props} />,
|
|
108
117
|
},
|
|
109
118
|
}}
|
|
110
119
|
```
|
|
@@ -126,7 +135,7 @@ function MyFilterBtn({ onClick, isOpen, activeCount }: ToolbarButtonRenderProps)
|
|
|
126
135
|
|
|
127
136
|
slotProps={{
|
|
128
137
|
toolbar: {
|
|
129
|
-
renderFilterButton: (props) => <MyFilterBtn {...props} />,
|
|
138
|
+
renderFilterButton: (props: ToolbarButtonRenderProps) => <MyFilterBtn {...props} />,
|
|
130
139
|
},
|
|
131
140
|
}}
|
|
132
141
|
```
|
|
@@ -144,7 +153,7 @@ const apiRef = useGridApiRef();
|
|
|
144
153
|
slotProps={{
|
|
145
154
|
toolbar: {
|
|
146
155
|
renderExportButton: () => (
|
|
147
|
-
<button onClick={() => exportToCsv(apiRef.current.
|
|
156
|
+
<button onClick={() => exportToCsv(apiRef.current.getAllFilteredRows(), apiRef.current.getVisibleColumns(), { fileName: 'export.csv' })}>
|
|
148
157
|
Export CSV
|
|
149
158
|
</button>
|
|
150
159
|
),
|
|
@@ -158,11 +167,11 @@ const apiRef = useGridApiRef();
|
|
|
158
167
|
```tsx
|
|
159
168
|
slotProps={{
|
|
160
169
|
toolbar: {
|
|
161
|
-
renderQuickFilter: (props) => <MySearch {...props} />,
|
|
162
|
-
renderColumnsButton: (props)
|
|
163
|
-
renderFilterButton: (props)
|
|
164
|
-
renderAggregationButton: (props)
|
|
165
|
-
renderExportButton: ()
|
|
170
|
+
renderQuickFilter: (props: ToolbarQuickFilterRenderProps) => <MySearch {...props} />,
|
|
171
|
+
renderColumnsButton: (props: ToolbarButtonRenderProps) => <MyColumnsBtn {...props} />,
|
|
172
|
+
renderFilterButton: (props: ToolbarButtonRenderProps) => <MyFilterBtn {...props} />,
|
|
173
|
+
renderAggregationButton: (props: ToolbarButtonRenderProps) => <MyAggBtn {...props} />,
|
|
174
|
+
renderExportButton: () => <MyExportBtn />,
|
|
166
175
|
},
|
|
167
176
|
}}
|
|
168
177
|
```
|
|
@@ -190,7 +199,7 @@ slotProps={{
|
|
|
190
199
|
|
|
191
200
|
When you provide a custom button, the toolbar renders your element as the trigger but keeps its own open/close state. The panel (Columns, Filters, or Aggregation) is positioned relative to the wrapper `<div>` surrounding your custom button, so placement is automatic — you don't need to pass any refs.
|
|
192
201
|
|
|
193
|
-
Panel close behaviour is unchanged: Escape
|
|
202
|
+
Panel close behaviour is unchanged: Escape closes every panel, and click-outside closes every panel except the Filter panel, which has an explicit Close button so users can type without it dismissing. However the Columns panel closes, `onColumnsPanelClose` is called.
|
|
194
203
|
|
|
195
204
|
---
|
|
196
205
|
|
|
@@ -201,5 +210,5 @@ These built-in features are unaffected regardless of which render props you prov
|
|
|
201
210
|
- Column panel content (drag-to-reorder, show/hide toggles)
|
|
202
211
|
- Filter panel content (operators, values, logic operator)
|
|
203
212
|
- Aggregation panel content (per-column function pills)
|
|
204
|
-
- Pivot panel (
|
|
213
|
+
- Pivot panel (when the Pivot button is shown, see above)
|
|
205
214
|
- `children` / `rightContent` slot behaviour
|
|
@@ -12,14 +12,32 @@ Row grouping allows you to categorize rows based on common column values.
|
|
|
12
12
|
```tsx
|
|
13
13
|
<DataGrid
|
|
14
14
|
rows={rows}
|
|
15
|
+
columns={columns}
|
|
15
16
|
rowGroupingModel={['department', 'role']}
|
|
16
17
|
/>
|
|
17
18
|
```
|
|
18
19
|
|
|
19
20
|
### Features
|
|
20
|
-
- **Multi-Level Groups**: Nest data as deeply as needed.
|
|
21
|
+
- **Multi-Level Groups**: Nest data as deeply as needed — one level per field in `rowGroupingModel`, in order.
|
|
21
22
|
- **Aggregation Integration**: Summarize values automatically for each group level.
|
|
22
|
-
- **Expansion Control**:
|
|
23
|
+
- **Expansion Control**: `defaultGroupingExpansionDepth` sets how many levels start expanded (default `0`: all collapsed; `-1`: everything expanded).
|
|
24
|
+
- **Aggregate placement**: `getAggregationPosition(node)` returns `'inline'` (on the group row, the default), `'footer'` (a subtotal row after the group's children while it is expanded) or `null` (hidden). It is also called once with `null` for the grand-total footer row; returning `null` there hides the footer.
|
|
25
|
+
|
|
26
|
+
### Behaviour to know about
|
|
27
|
+
|
|
28
|
+
- **Pagination is ignored** while `rowGroupingModel` is active. All groups render in one scrollable, virtualized view, so the grid needs a bounded height ([Virtualization](./virtualization.md#the-grid-needs-a-bounded-height)). A development-mode warning is logged if you pass `pagination` too.
|
|
29
|
+
- **Expansion state survives data updates** (row grouping and tree data). Groups and nodes the user expanded or collapsed stay that way when `rows` changes: inline edits, live refreshes, new array identities, and lazily loaded server-side children. The state resets to `defaultGroupingExpansionDepth` only when `rowGroupingModel` or `defaultGroupingExpansionDepth` changes value.
|
|
30
|
+
- **Group aggregation uses the same functions as the footer.** `sum`, `avg`, `count`, `min`, `max` and `unique` ignore `null`/`undefined`, so a group of `[10, null, 20]` gives `min` 10, `avg` 15 and `count` 2. `availableAggregationFunctions` on a column is honoured for group rows too.
|
|
31
|
+
- **The expand toggle, indentation and group label sit in the leftmost column on screen**, after column order, visibility and pinning are applied — not in whichever column is first in `columns`. Hiding or moving that column no longer leaves group rows without a label or toggle (fixed in v3.0).
|
|
32
|
+
- **Keyboard and screen readers** (v3.0): **Enter** on a non-editable cell of a group row or a tree-data parent expands or collapses it, and **Alt+ArrowRight** / **Alt+ArrowLeft** expand / collapse it. Rows expose `aria-level` (depth + 1), and rows with children expose `aria-expanded`. The expand buttons inside cells are not separate Tab stops. See [Keyboard & Accessibility](keyboard-navigation.md).
|
|
33
|
+
- **`valueFormatter` applies to grouped rows** the same as flat rows (fixed in v2.1; earlier versions rendered raw values for every column once grouping was on).
|
|
34
|
+
- **Group subtotals and the `(n)` count follow the filter.** They cover only the leaf rows that pass the current filter, and groups with no matching row are left out (v3.0; before, they counted hidden rows too). Groups keep their order of first appearance while filtering.
|
|
35
|
+
- **Grouping values are compared by value, not by text**: `null` and `'null'`, `1` and `'1'`, `true` and `'true'` are separate groups; equal dates share one. A column with a `valueGetter` is grouped by the value it computes.
|
|
36
|
+
- **`groupable: false`** fields are dropped from the model before grouping, so the other levels keep their depth, indentation, `defaultGroupingExpansionDepth` and export depth.
|
|
37
|
+
- **Sorting the column that shows the group labels orders the groups by their grouping value**; sorting an aggregated column orders them by the aggregate.
|
|
38
|
+
- **Group rows are synthetic.** They have no selection checkbox and no detail panel, their ids never appear in the selection model, clicking them toggles them (without `onRowClick`), and a column's `valueGetter` is not called for them. `renderCell` *is* called for them: see [GridRowMeta](../architecture/grid-row-meta.md#accessing-metadata-in-rendercell).
|
|
39
|
+
- **Server modes**: with `filterMode="server"` / `sortingMode="server"` the grid does not filter or sort the rows again on the client, in row grouping and tree data as in a flat grid.
|
|
40
|
+
- **Pass stable callbacks.** `rowGroupingModel` and `aggregationModel` are compared by value, so inline arrays and objects are fine, but a new `getTreeDataPath` or `columns` identity on every render rebuilds the whole hierarchy. Define them at module scope or memoize them.
|
|
23
41
|
|
|
24
42
|
---
|
|
25
43
|
|
|
@@ -29,15 +47,29 @@ Tree data is used for data that has a natural parent-child relationship (e.g., a
|
|
|
29
47
|
|
|
30
48
|
### Implementation
|
|
31
49
|
1. Enable `treeData={true}`.
|
|
32
|
-
2. Provide a `getTreeDataPath` function to define the hierarchy.
|
|
50
|
+
2. Provide a `getTreeDataPath` function to define the hierarchy. It is required: without it the rows are shown flat. Define it at module scope or with `useCallback`.
|
|
33
51
|
|
|
34
52
|
```tsx
|
|
35
53
|
<DataGrid
|
|
54
|
+
rows={rows}
|
|
55
|
+
columns={columns}
|
|
36
56
|
treeData
|
|
37
|
-
getTreeDataPath={(row) => row.hierarchyPath
|
|
57
|
+
getTreeDataPath={getPath} // e.g. (row) => row.hierarchyPath: ['CEO', 'VP Engineering', 'Manager']
|
|
38
58
|
/>
|
|
39
59
|
```
|
|
40
60
|
|
|
61
|
+
### Behaviour to know about
|
|
62
|
+
|
|
63
|
+
- **Path segments can contain any character**, including `/`: `['a/b']` and `['a', 'b']` are different paths.
|
|
64
|
+
- **Missing parents are created for you.** A path segment with no row of its own gets a synthetic parent row (`rowMeta.isGroupRow`, label in `rowMeta.groupLabel`, row object `{ id }`) shown in the toggle column as `label (n)`, whatever that column is called. It is shown only when a row under it passes the filter, and sorting the toggle column orders it by its label.
|
|
65
|
+
- **Parents that are your own rows behave like any row**: clicking one fires `onRowClick` and selects it; its chevron expands it. (Before v3.0 a click toggled it and never fired `onRowClick`.) From the keyboard it expands with Enter or Alt+ArrowRight (see below) and **Shift+Space** selects it.
|
|
66
|
+
- **`descendantCount`** (the `(n)`) counts the rows below a node at any depth that pass the filter. A parent whose children are all filtered out is shown without a toggle.
|
|
67
|
+
- **Invalid paths are reported in development**: a row whose path is empty is shown as a top-level row, rows sharing a path attach their children to the first of them, and `treeData` without `getTreeDataPath` shows the rows flat. Each logs a `console.warn`.
|
|
68
|
+
- **Pagination** pages the flattened visible tree; keyboard navigation stays on the current page.
|
|
69
|
+
- **Lazy (server) trees**: the children of an expanded node with `serverChildrenCount` are fetched when it is expanded, and fetched again after a server re-fetch (a sort or filter change) replaces the rows. `defaultGroupingExpansionDepth` counts such a node as expandable (v3.0+), so `-1` expands every lazy node and loads its children.
|
|
70
|
+
- **`apiRef.getAllFilteredRows()` with a filter** returns the rows that match the filter, the same set select-all and the aggregation footer use. Ancestors shown only to give a match its context are not in it (v3.0+; before, they were).
|
|
71
|
+
- **Row grouping with `paginationMode="server"`**: grouping turns pagination off, so a `dataSource` is asked for every row (see [Data Source](data-source.md#2-server-side-pagination)).
|
|
72
|
+
|
|
41
73
|
---
|
|
42
74
|
|
|
43
75
|
## ⚙️ API Reference
|
|
@@ -47,15 +79,18 @@ Tree data is used for data that has a natural parent-child relationship (e.g., a
|
|
|
47
79
|
| :--- | :--- | :--- | :--- |
|
|
48
80
|
| `rowGroupingModel` | `string[]` | `[]` | Fields to group by (in order). |
|
|
49
81
|
| `treeData` | `boolean` | `false` | Enable tree data mode. |
|
|
50
|
-
| `getTreeDataPath` | `(row) => string[]` | `undefined` |
|
|
51
|
-
| `defaultGroupingExpansionDepth` | `number` | `0` | How many levels
|
|
52
|
-
| `groupingColDef` | `GridColDef
|
|
82
|
+
| `getTreeDataPath` | `(row) => string[]` | `undefined` | Path of a row. Required with `treeData`. |
|
|
83
|
+
| `defaultGroupingExpansionDepth` | `number` | `0` | How many levels start expanded (`0` = collapsed, `-1` = all). |
|
|
84
|
+
| `groupingColDef` | `Partial<GridColDef<R>>` | `undefined` | Adds a dedicated group column (row grouping or tree data). |
|
|
85
|
+
| `getAggregationPosition` | `(node: GridTreeNode \| null) => 'inline' \| 'footer' \| null` | `undefined` | Where group aggregates appear; called with `null` for the grand total. |
|
|
86
|
+
|
|
87
|
+
Hierarchy information is never written onto your row objects: read it from `params.rowMeta` (`isGroupRow`, `hasChildren`, `treeDepth`, `groupLabel`, …) in `renderCell`. See [GridRowMeta](../architecture/grid-row-meta.md).
|
|
53
88
|
|
|
54
89
|
---
|
|
55
90
|
|
|
56
91
|
## 🎨 Customizing the Group Column
|
|
57
92
|
|
|
58
|
-
When `rowGroupingModel` is active, pass `groupingColDef` to configure a dedicated `__group__` column that is prepended at position 0 and auto-pinned left, separate from your data columns.
|
|
93
|
+
When `rowGroupingModel` or `treeData` is active, pass `groupingColDef` to configure a dedicated `__group__` column that is prepended at position 0 and auto-pinned left, separate from your data columns. Its type is `Partial<GridColDef<R>>`: every key is optional, and a `field` you pass is ignored (it is always `'__group__'`). It also forces `hideable`, `sortable`, `filterable`, `pinnable` and `exportable` to `false`. Defaults: `headerName: 'Group'`, `width: 220`. With tree data (v3.0) the column shows the last segment of each row's path, and a `valueGetter` in `groupingColDef` can show something else (for example the whole path).
|
|
59
94
|
|
|
60
95
|
```tsx
|
|
61
96
|
<DataGrid
|
|
@@ -69,7 +104,7 @@ When `rowGroupingModel` is active, pass `groupingColDef` to configure a dedicate
|
|
|
69
104
|
/>
|
|
70
105
|
```
|
|
71
106
|
|
|
72
|
-
Without `groupingColDef`, the group toggle is overlaid on the
|
|
107
|
+
Without `groupingColDef`, the group toggle is overlaid on the leftmost column on screen. With it, the group indicator always appears in its own fixed column regardless of your `columns` order. A `renderCell` in `groupingColDef` renders the group rows' label cell (return `undefined` to keep the default label).
|
|
73
108
|
|
|
74
109
|
---
|
|
75
110
|
|
|
@@ -17,8 +17,8 @@ Virtualization is a technique that dramatically improves performance when workin
|
|
|
17
17
|
The grid calculates which rows are visible based on:
|
|
18
18
|
- Current scroll position
|
|
19
19
|
- Viewport height
|
|
20
|
-
- Row height (fixed
|
|
21
|
-
- Overscan buffer (default
|
|
20
|
+
- Row height (the fixed `rowHeight`, plus the height of any expanded detail panels)
|
|
21
|
+
- Overscan buffer (adaptive, minimum `overscanRowCount`, default 3 rows)
|
|
22
22
|
|
|
23
23
|
```tsx
|
|
24
24
|
// Example: 1000 rows, but only ~15 rendered at a time
|
|
@@ -29,21 +29,69 @@ The grid calculates which rows are visible based on:
|
|
|
29
29
|
/>
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
**Performance characteristics
|
|
32
|
+
**Performance characteristics** (when the grid has a bounded height — see below):
|
|
33
33
|
- ✅ Renders ~15-25 rows regardless of total dataset size
|
|
34
34
|
- ✅ Constant memory usage
|
|
35
35
|
- ✅ Smooth 60fps scrolling
|
|
36
|
-
-
|
|
36
|
+
- ⚠️ Not millions of rows. Browsers cap an element's height (33,554,432px in Chromium, about 17.9M px in Firefox) and the grid does no scroll scaling: it lays every row out at its real height, so rows past the cap cannot be scrolled to. The limit is roughly `33,554,432 / rowHeight` rows in Chromium (about 645,000 at the default 52px), and about half that in Firefox (about 340,000). Beyond that, use pagination, or server-side paging / infinite scroll. In development the grid warns when the content passes 17.8M px.
|
|
37
37
|
|
|
38
38
|
### Column Virtualization
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
Unpinned columns are virtualized horizontally: only the columns in view plus 6 on each side are rendered, with spacer cells standing in for the rest. Pinned columns are always rendered.
|
|
41
|
+
|
|
42
|
+
### Import the stylesheet
|
|
43
|
+
|
|
44
|
+
Virtualization depends on the grid's CSS. Import it once in your app:
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import '@opencorestack/opengridx/styles';
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Without the stylesheet the viewport does not scroll, so the grid renders every row. In development the grid logs a one-time `[OpenGridX] The grid stylesheet is not loaded` warning.
|
|
51
|
+
|
|
52
|
+
### The grid needs a bounded height
|
|
53
|
+
|
|
54
|
+
Virtualization renders as many rows as fit in the grid's viewport. If the grid's container has no bounded height, the viewport grows to fit **every** row, so every row is rendered. Nothing errors; the grid just stops virtualizing.
|
|
55
|
+
|
|
56
|
+
The most common cause is a flex child that is missing `min-height: 0`. A flex item defaults to `min-height: auto`, which lets it grow to its content:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// ❌ Grows to fit all rows — virtualization is effectively off
|
|
60
|
+
<div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
|
|
61
|
+
<div style={{ flex: 1 }}>
|
|
62
|
+
<DataGrid rows={rows} columns={columns} height="100%" />
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
// ✅ Bounded — only visible rows render
|
|
67
|
+
<div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
|
|
68
|
+
<div style={{ flex: 1, minHeight: 0 }}>
|
|
69
|
+
<DataGrid rows={rows} columns={columns} height="100%" />
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`height="100%"` only helps if every ancestor up to a fixed-height element also has a definite height.
|
|
75
|
+
|
|
76
|
+
**`DataGridThemeProvider` counts as an ancestor.** It renders a plain `<div class="ogx-theme-provider">` around the grid, and that wrapper is auto-height like any other `div`. When you use `height="100%"` inside it, give the wrapper a height as well:
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
<div style={{ flex: 1, minHeight: 0 }}>
|
|
80
|
+
<DataGridThemeProvider theme={theme} style={{ height: '100%' }}>
|
|
81
|
+
<DataGrid rows={rows} columns={columns} height="100%" />
|
|
82
|
+
</DataGridThemeProvider>
|
|
83
|
+
</div>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
In development builds the grid logs a `console.warn` when it detects that it is rendering every row of a dataset larger than 200 rows because its viewport is unbounded.
|
|
87
|
+
|
|
88
|
+
Pagination can hide this problem, because a paginated grid only has one page of rows to render. Row grouping switches pagination off (see below), so an unbounded container that looked fine with pagination can freeze the tab once grouping is enabled.
|
|
41
89
|
|
|
42
90
|
## Configuration
|
|
43
91
|
|
|
44
92
|
### Row Height
|
|
45
93
|
|
|
46
|
-
|
|
94
|
+
Every data row has the same height. There is no per-row height callback; the only rows of a different height are expanded detail panels (see below).
|
|
47
95
|
|
|
48
96
|
```tsx
|
|
49
97
|
// Fixed row height (default: 52px)
|
|
@@ -157,7 +205,7 @@ When using detail panels, row heights become variable. The grid automatically:
|
|
|
157
205
|
|
|
158
206
|
## Virtualization with Grouping/Tree Data
|
|
159
207
|
|
|
160
|
-
|
|
208
|
+
Grouped and tree rows go through the same virtualizer as flat rows. Expanding a group of any size adds its rows to the virtual row list, not to the DOM: in a bounded container, expanding a 25,000-row group renders ~20 DOM rows (verified in Chromium by `DataGrid.virtualization.browser.test.tsx`).
|
|
161
209
|
|
|
162
210
|
```tsx
|
|
163
211
|
// Tree data with virtualization
|
|
@@ -170,7 +218,7 @@ Row grouping and tree data work seamlessly with virtualization:
|
|
|
170
218
|
/>
|
|
171
219
|
```
|
|
172
220
|
|
|
173
|
-
**Note:**
|
|
221
|
+
**Note:** `pagination` is ignored while `rowGroupingModel` is active, so a grouped grid relies entirely on virtualization to bound what it renders. Make sure its container has a bounded height. A development-mode warning is logged if `pagination` is passed together with `rowGroupingModel`.
|
|
174
222
|
|
|
175
223
|
## Debugging Virtualization
|
|
176
224
|
|
|
@@ -192,13 +240,14 @@ You should see console logs only for visible rows + overscan buffer.
|
|
|
192
240
|
|
|
193
241
|
## Known Limitations
|
|
194
242
|
|
|
195
|
-
1. **
|
|
196
|
-
2. **
|
|
197
|
-
3. **
|
|
243
|
+
1. **Unbounded containers** - Virtualization needs a bounded viewport height (see above)
|
|
244
|
+
2. **Browser height cap** - Rows past roughly `33,554,432 / rowHeight` (Chromium; less in Firefox) cannot be scrolled to (see above)
|
|
245
|
+
3. **Uniform row heights** - Only detail panels vary the height; cumulative heights are recomputed over all rows when panels change
|
|
246
|
+
4. **Horizontal scrolling** - May show brief flicker with very wide grids
|
|
198
247
|
|
|
199
248
|
## Future Improvements
|
|
200
249
|
|
|
201
|
-
- [
|
|
250
|
+
- [x] Column virtualization (unpinned columns, 6-column overscan)
|
|
202
251
|
- [x] Adaptive overscan based on scroll velocity (`overscanRowCount` prop, v2.0.4)
|
|
203
252
|
- [ ] Virtual scrollbar for extremely large datasets (millions of rows)
|
|
204
253
|
- [ ] Intersection Observer API for better scroll detection
|
|
@@ -208,10 +257,10 @@ You should see console logs only for visible rows + overscan buffer.
|
|
|
208
257
|
| Feature | OpenGridX | MUI X DataGrid |
|
|
209
258
|
|---------|--------------|----------------|
|
|
210
259
|
| Row virtualization | ✅ Yes | ✅ Yes |
|
|
211
|
-
| Column virtualization |
|
|
212
|
-
| Variable row heights |
|
|
260
|
+
| Column virtualization | ✅ Yes (unpinned columns) | ✅ Yes (Pro) |
|
|
261
|
+
| Variable row heights | Detail panels only | ✅ Yes |
|
|
213
262
|
| Overscan buffer | 3–30 rows (adaptive, velocity-based) | 3-8 rows (adaptive) |
|
|
214
|
-
| Max
|
|
263
|
+
| Max scrollable rows | ~645,000 at 52px in Chromium (browser height cap) | — |
|
|
215
264
|
|
|
216
265
|
## Related Documentation
|
|
217
266
|
|