@opencorestack/opengridx 2.1.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/CHANGELOG.md +399 -0
  2. package/README.md +95 -72
  3. package/dist/index.d.ts +590 -131
  4. package/dist/opengridx.css +1 -1
  5. package/dist/opengridx.es.js +7891 -5631
  6. package/dist/opengridx.es.js.map +1 -1
  7. package/dist/opengridx.umd.js +17 -23
  8. package/dist/opengridx.umd.js.map +1 -1
  9. package/docs/API_REFERENCE.md +348 -166
  10. package/docs/README.md +6 -1
  11. package/docs/architecture/datagrid-orchestration.md +81 -0
  12. package/docs/architecture/grid-row-meta.md +102 -0
  13. package/docs/architecture/use-grid-columns.md +128 -0
  14. package/docs/architecture/use-grid-controlled-state.md +157 -0
  15. package/docs/architecture/use-grid-row-pipeline.md +120 -0
  16. package/docs/architecture/use-grid-scroll-sync.md +111 -0
  17. package/docs/architecture/use-grid-state-snapshot.md +90 -0
  18. package/docs/architecture/use-grid-virtualization.md +115 -0
  19. package/docs/architecture/use-grid-visible-rows.md +85 -0
  20. package/docs/assets/banner-alt.png +0 -0
  21. package/docs/assets/banner.png +0 -0
  22. package/docs/assets/hero.png +0 -0
  23. package/docs/assets/logo.png +0 -0
  24. package/docs/components/aggregation-footer.md +15 -10
  25. package/docs/components/cell.md +16 -6
  26. package/docs/components/column-group-header.md +55 -28
  27. package/docs/components/column-resize.md +17 -13
  28. package/docs/components/column-visibility.md +6 -7
  29. package/docs/components/datagrid.md +15 -13
  30. package/docs/components/empty-state.md +2 -1
  31. package/docs/components/error-overlay.md +12 -7
  32. package/docs/components/filter-panel.md +28 -8
  33. package/docs/components/header.md +28 -9
  34. package/docs/components/pagination.md +14 -8
  35. package/docs/components/row.md +21 -5
  36. package/docs/components/toolbar.md +27 -9
  37. package/docs/components/tooltip.md +16 -7
  38. package/docs/customization/slots-api.md +90 -38
  39. package/docs/customization/theming.md +66 -18
  40. package/docs/features/aggregation-pivot.md +75 -9
  41. package/docs/features/cell-spanning.md +80 -125
  42. package/docs/features/clipboard.md +41 -16
  43. package/docs/features/custom-pagination.md +38 -18
  44. package/docs/features/data-source.md +36 -13
  45. package/docs/features/editing-reordering.md +125 -16
  46. package/docs/features/export-guide.md +126 -39
  47. package/docs/features/filtering.md +44 -6
  48. package/docs/features/infinite-scroll.md +54 -46
  49. package/docs/features/keyboard-navigation.md +83 -0
  50. package/docs/features/list-view.md +58 -5
  51. package/docs/features/loading-states.md +18 -20
  52. package/docs/features/master-detail.md +18 -5
  53. package/docs/features/pdf-export.md +72 -19
  54. package/docs/features/pinning.md +24 -5
  55. package/docs/features/selection.md +50 -10
  56. package/docs/features/sorting-pagination.md +64 -14
  57. package/docs/features/state-persistence.md +78 -10
  58. package/docs/features/toolbar-customization.md +26 -17
  59. package/docs/features/tree-data-grouping.md +36 -8
  60. package/docs/features/virtualization.md +37 -14
  61. package/docs/migration/v1-to-v2.md +115 -0
  62. package/docs/migration/v2-to-v3.md +1233 -0
  63. package/docs/performance.md +124 -0
  64. package/docs/roadmap.md +5 -5
  65. package/lib/components/Cell/Cell.css +17 -1
  66. package/lib/components/Cell/Cell.tsx +129 -57
  67. package/lib/components/Cell/CellErrorBoundary.tsx +12 -6
  68. package/lib/components/Cell/GridEditInputCell.tsx +114 -41
  69. package/lib/components/ColumnGroupHeader/ColumnGroupHeader.css +7 -1
  70. package/lib/components/ColumnResizeHandle/ColumnResizeHandle.css +15 -2
  71. package/lib/components/ColumnResizeHandle/ColumnResizeHandle.tsx +104 -38
  72. package/lib/components/ColumnResizeHandle/clampResizeWidth.ts +13 -0
  73. package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.css +6 -0
  74. package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +21 -13
  75. package/lib/components/DataGrid/DataGrid.css +85 -12
  76. package/lib/components/DataGrid/DataGrid.tsx +582 -876
  77. package/lib/components/DataGrid/GridAggregationFooter.tsx +53 -17
  78. package/lib/components/DataGrid/GridErrorOverlay.tsx +15 -12
  79. package/lib/components/DataGrid/GridListView.tsx +165 -24
  80. package/lib/components/DataGrid/GridLiveRegion.tsx +31 -0
  81. package/lib/components/DataGrid/GridLoadingOverlay.tsx +27 -0
  82. package/lib/components/DataGrid/GridPaginationArea.tsx +44 -0
  83. package/lib/components/DataGrid/GridPinnedRows.tsx +62 -11
  84. package/lib/components/DataGrid/GridStandaloneColumnPanel.tsx +40 -38
  85. package/lib/components/DataGrid/GridToolbarSlot.tsx +51 -0
  86. package/lib/components/DataGrid/GridVirtualRows.tsx +40 -24
  87. package/lib/components/DetailPanel/DetailPanel.css +0 -6
  88. package/lib/components/DetailPanel/DetailPanel.tsx +34 -5
  89. package/lib/components/DetailPanel/DetailPanelContent.tsx +17 -0
  90. package/lib/components/FilterPanel/FilterPanel.css +13 -2
  91. package/lib/components/FilterPanel/FilterPanel.tsx +272 -99
  92. package/lib/components/Header/ColumnMenu.tsx +91 -39
  93. package/lib/components/Header/Header.css +6 -3
  94. package/lib/components/Header/Header.tsx +181 -160
  95. package/lib/components/ListView/ListView.css +25 -0
  96. package/lib/components/ListView/ListViewRow.tsx +76 -10
  97. package/lib/components/Pagination/Pagination.tsx +15 -7
  98. package/lib/components/Row/Row.tsx +177 -77
  99. package/lib/components/Toolbar/GlobalSearch.css +6 -3
  100. package/lib/components/Toolbar/GlobalSearch.tsx +26 -27
  101. package/lib/components/Toolbar/GridToolbar.css +12 -11
  102. package/lib/components/Toolbar/GridToolbar.tsx +133 -89
  103. package/lib/components/Toolbar/PivotPanel.tsx +89 -49
  104. package/lib/components/Tooltip/Tooltip.css +20 -0
  105. package/lib/components/Tooltip/Tooltip.tsx +68 -35
  106. package/lib/components/ui/Button.tsx +3 -0
  107. package/lib/components/ui/Checkbox.tsx +2 -1
  108. package/lib/components/ui/ExpandIcon.css +0 -9
  109. package/lib/components/ui/ExpandIcon.tsx +4 -1
  110. package/lib/hooks/core/gridApiPlaceholder.ts +34 -0
  111. package/lib/hooks/core/gridToolbarHostContext.ts +14 -0
  112. package/lib/hooks/core/useDataGrid.ts +115 -173
  113. package/lib/hooks/core/useGridAggregationApi.ts +72 -0
  114. package/lib/hooks/core/useGridApiMethods.ts +115 -0
  115. package/lib/hooks/core/useGridApiRef.ts +19 -4
  116. package/lib/hooks/core/useGridApiRefBinding.ts +18 -0
  117. package/lib/hooks/core/useGridAriaRows.ts +38 -0
  118. package/lib/hooks/core/useGridClipboardApi.ts +38 -0
  119. package/lib/hooks/core/useGridColumnLookup.ts +30 -0
  120. package/lib/hooks/core/useGridColumns.tsx +279 -76
  121. package/lib/hooks/core/useGridColumnsPanel.ts +77 -0
  122. package/lib/hooks/core/useGridControlledState.ts +99 -6
  123. package/lib/hooks/core/useGridDetailPanel.ts +51 -0
  124. package/lib/hooks/core/useGridDevWarnings.ts +104 -5
  125. package/lib/hooks/core/useGridFocusHandlers.ts +50 -0
  126. package/lib/hooks/core/useGridGroupingColumn.ts +62 -0
  127. package/lib/hooks/core/useGridHeaderHandlers.ts +92 -0
  128. package/lib/hooks/core/useGridHierarchy.ts +81 -0
  129. package/lib/hooks/core/useGridKeyboardNavigation.ts +447 -243
  130. package/lib/hooks/core/useGridLiveRowSelection.ts +67 -0
  131. package/lib/hooks/core/useGridPageCorrection.ts +55 -0
  132. package/lib/hooks/core/useGridRowIdOf.ts +32 -0
  133. package/lib/hooks/core/useGridRowInteractions.ts +81 -0
  134. package/lib/hooks/core/useGridRowPipeline.ts +69 -39
  135. package/lib/hooks/core/useGridRowSelection.ts +91 -0
  136. package/lib/hooks/core/useGridScrollSync.ts +80 -32
  137. package/lib/hooks/core/useGridScrollToIndexesApi.ts +45 -0
  138. package/lib/hooks/core/useGridStateSnapshot.ts +40 -5
  139. package/lib/hooks/core/useGridThemeDimensions.ts +49 -0
  140. package/lib/hooks/core/useGridToolbarProps.ts +97 -0
  141. package/lib/hooks/core/useGridViewport.ts +38 -0
  142. package/lib/hooks/core/useGridViewportSize.ts +26 -0
  143. package/lib/hooks/core/useGridVisibleRows.ts +17 -5
  144. package/lib/hooks/core/useLayout.ts +215 -195
  145. package/lib/hooks/core/useRowsScrollEndLatch.ts +41 -0
  146. package/lib/hooks/core/useStableColumns.ts +34 -0
  147. package/lib/hooks/features/useAggregation.ts +82 -51
  148. package/lib/hooks/features/useColumnGroupReorderGuard.ts +34 -0
  149. package/lib/hooks/features/useDetailPanelHeights.ts +29 -0
  150. package/lib/hooks/features/useGridClipboard.ts +133 -73
  151. package/lib/hooks/features/useGridDataSource.ts +354 -128
  152. package/lib/hooks/features/useGridEditing.ts +211 -51
  153. package/lib/hooks/features/useGridPivot.ts +92 -0
  154. package/lib/hooks/features/useGridSpanRenderWindow.ts +104 -0
  155. package/lib/hooks/features/useGridSpanning.ts +410 -306
  156. package/lib/hooks/features/usePivot.ts +0 -0
  157. package/lib/hooks/features/useServerTreeChildren.ts +50 -0
  158. package/lib/hooks/useColumnReorder.ts +38 -44
  159. package/lib/hooks/useRowGrouping.ts +298 -160
  160. package/lib/hooks/useRowReorder.ts +62 -31
  161. package/lib/hooks/useTreeData.ts +287 -220
  162. package/lib/index.ts +28 -2
  163. package/lib/state/index.ts +1 -0
  164. package/lib/state/types.ts +7 -1
  165. package/lib/state/useGridStateStorage.ts +88 -29
  166. package/lib/theme/DataGridThemeProvider.tsx +143 -38
  167. package/lib/theme/gridThemeContext.ts +5 -0
  168. package/lib/theme/index.ts +5 -0
  169. package/lib/theme/presets.ts +5 -4
  170. package/lib/theme/types.ts +40 -2
  171. package/lib/types/index.ts +313 -116
  172. package/lib/utils/aggregation/index.ts +119 -24
  173. package/lib/utils/aria/index.ts +40 -0
  174. package/lib/utils/columnGroups.ts +110 -0
  175. package/lib/utils/columnLookup.ts +33 -0
  176. package/lib/utils/columnWidth.ts +22 -0
  177. package/lib/utils/dataSource/index.ts +46 -0
  178. package/lib/utils/detailPanel.ts +66 -0
  179. package/lib/utils/dragData.ts +44 -0
  180. package/lib/utils/editing/index.ts +30 -0
  181. package/lib/utils/export/exportShared.ts +197 -0
  182. package/lib/utils/export/exportToExcelAdvanced.ts +278 -139
  183. package/lib/utils/export/exportToPdf.ts +143 -92
  184. package/lib/utils/export/groupLabel.ts +14 -0
  185. package/lib/utils/export/index.ts +278 -272
  186. package/lib/utils/export/pdfText.ts +50 -0
  187. package/lib/utils/filtering/index.ts +277 -148
  188. package/lib/utils/focus/index.ts +115 -0
  189. package/lib/utils/gridApiRows.ts +35 -0
  190. package/lib/utils/gridModes.ts +77 -0
  191. package/lib/utils/grouping/groupedExportRows.ts +91 -0
  192. package/lib/utils/pagination/index.ts +56 -0
  193. package/lib/utils/pinning/index.ts +77 -33
  194. package/lib/utils/pivot/index.ts +368 -0
  195. package/lib/utils/scroll/index.ts +55 -5
  196. package/lib/utils/scroll/scrollEnd.ts +18 -0
  197. package/lib/utils/sorting/index.ts +154 -30
  198. package/lib/utils/syntheticRows.ts +12 -0
  199. package/lib/utils/values.ts +206 -0
  200. package/lib/utils/viewport.ts +14 -0
  201. package/llms.txt +261 -164
  202. package/package.json +13 -5
  203. package/lib/components/ColumnGroupHeader/ColumnGroupHeader.tsx +0 -220
  204. package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.test.tsx +0 -85
  205. package/lib/components/DataGrid/DataGrid.defects.test.tsx +0 -103
  206. package/lib/components/DataGrid/DataGrid.regression.test.tsx +0 -132
  207. package/lib/components/DataGrid/DataGrid.smoke.test.tsx +0 -58
  208. package/lib/components/DataGrid/DataGrid.v2.test.tsx +0 -127
  209. package/lib/components/DataGrid/DataGrid.virtualization.browser.test.tsx +0 -183
  210. package/lib/components/Header/Header.test.tsx +0 -135
  211. package/lib/components/Pagination/Pagination.test.tsx +0 -64
  212. package/lib/hooks/core/useDataGrid.regression.test.ts +0 -66
  213. package/lib/hooks/core/useGridControlledState.test.ts +0 -86
  214. package/lib/hooks/core/useGridKeyboardNavigation.test.ts +0 -176
  215. package/lib/hooks/core/useGridRowPipeline.test.ts +0 -117
  216. package/lib/hooks/features/useAggregation.test.ts +0 -175
  217. package/lib/hooks/features/usePivot.test.ts +0 -202
  218. package/lib/hooks/useRowGrouping.test.ts +0 -298
  219. package/lib/hooks/useTreeData.test.ts +0 -83
  220. package/lib/utils/aggregation/aggregation.test.ts +0 -16
  221. package/lib/utils/export/exportToExcelAdvanced.test.ts +0 -109
  222. package/lib/utils/export/exportToPdf.test.ts +0 -120
  223. package/lib/utils/export/groupedExport.test.ts +0 -144
  224. package/lib/utils/filtering/filtering.test.ts +0 -178
  225. package/lib/utils/sorting/sorting.test.ts +0 -88
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # OpenGridX — AI Agent Context
2
2
  # Package: @opencorestack/opengridx
3
- # Version: 2.1.0
3
+ # Version: 3.0.1
4
4
  # Docs: https://opencorestack.github.io/OpenGridX/
5
5
  # This file is machine-readable. AI developers: start here.
6
6
 
@@ -11,10 +11,10 @@
11
11
  OpenGridX is a zero-dependency, high-performance React DataGrid library.
12
12
  It matches and extends the feature set of MUI X DataGrid Pro — offered completely free.
13
13
 
14
- - **React**: 18+ required
14
+ - **React**: 18+ required (react and react-dom are peer dependencies)
15
15
  - **TypeScript**: Full typings shipped in `dist/index.d.ts`
16
- - **Bundle**: 52 KB gzip (ES module). ExcelJS is optional peer dep (lazy-loaded).
17
- - **CSS**: Auto-imported via barrel. If unstyled, add: `import '@opencorestack/opengridx/styles'`
16
+ - **Optional peers**: `exceljs` (exportToExcelAdvanced), `jspdf` + `jspdf-autotable` (exportToPdf). Loaded lazily, never bundled.
17
+ - **CSS**: NOT loaded by the JS bundle. Always add `import '@opencorestack/opengridx/styles'` once in the app root.
18
18
 
19
19
  ---
20
20
 
@@ -22,22 +22,26 @@ It matches and extends the feature set of MUI X DataGrid Pro — offered complet
22
22
 
23
23
  ```bash
24
24
  npm install @opencorestack/opengridx
25
- # Optional — for advanced Excel export only:
25
+ # Optional — only for exportToExcelAdvanced:
26
26
  npm install exceljs
27
+ # Optional — only for exportToPdf:
28
+ npm install jspdf jspdf-autotable
27
29
  ```
28
30
 
29
31
  ```tsx
30
32
  import { DataGrid } from '@opencorestack/opengridx';
31
- // If grid appears unstyled in your bundler:
32
- import '@opencorestack/opengridx/styles';
33
+ import '@opencorestack/opengridx/styles'; // required: the stylesheet is a separate file
33
34
  ```
34
35
 
36
+ Without the stylesheet the grid is unstyled AND the viewport does not scroll, so every row is rendered (virtualization is lost).
37
+
35
38
  ---
36
39
 
37
40
  ## MINIMAL EXAMPLE
38
41
 
39
42
  ```tsx
40
- import { DataGrid, GridColDef } from '@opencorestack/opengridx';
43
+ import { DataGrid, type GridColDef } from '@opencorestack/opengridx';
44
+ import '@opencorestack/opengridx/styles';
41
45
 
42
46
  const columns: GridColDef[] = [
43
47
  { field: 'id', headerName: 'ID', width: 70 },
@@ -56,37 +60,38 @@ export default function App() {
56
60
 
57
61
  ---
58
62
 
59
- ## DataGrid PROPS (DataGridProps<R extends GridRowModel>)
63
+ ## DataGrid PROPS (DataGridProps<R extends GridValidRowModel = GridRowModel>)
60
64
 
61
65
  ### Required
62
66
  | Prop | Type | Description |
63
67
  |-----------|------------------|----------------------------------|
64
- | rows | GridRowModel[] | Data array. Each row needs an id |
68
+ | rows | R[] | Data array. Each row needs an id (or pass getRowId). Rows are never copied or mutated |
65
69
  | columns | GridColDef<R>[] | Column definitions |
66
70
 
67
71
  ### Display
68
72
  | Prop | Type | Default | Description |
69
73
  |-------------------|-----------|---------|----------------------------------------|
70
- | height | number/string | — | Grid height. The grid must have a BOUNDED height or it renders every row (see GOTCHAS) |
71
- | autoHeight | boolean | false | Fit height to rows |
72
- | overscanRowCount | number | 3 | Minimum rows rendered outside the viewport (adaptive) |
74
+ | height | number/string | — | Grid height. No default: fills its container (class ogx--fill, 3.0.1+); the container must have a BOUNDED height or it renders every row (see GOTCHAS) |
75
+ | autoHeight | boolean | false | Fit height to rows (renders every row) |
76
+ | overscanRowCount | number | 3 | Minimum rows rendered outside the viewport (raised automatically while scrolling fast) |
73
77
  | ariaLabel | string | — | Accessible name for the grid |
74
- | localeText | GridLocaleText | — | Override pagination / no-rows strings |
78
+ | localeText | GridLocaleText | — | Override pager / no-rows strings: paginationRowsPerPage, paginationOf(from,to,count), paginationPage(page,count), noRowsLabel |
75
79
  | rowHeight | number | 52 | Row height in pixels |
76
80
  | headerHeight | number | 56 | Header height in pixels |
77
- | loading | boolean | false | Show skeleton loading overlay |
78
- | noRowsLabel | string | — | Empty state message |
79
- | density | 'compact'/'standard'/'comfortable' | 'standard' | Row density |
81
+ | loading | boolean | false | No rows: skeleton rows (or slots.loadingOverlay). Rows shown: they stay, a progress bar (.ogx__loading-bar) runs along the top, or slots.loadingOverlay is shown over them |
82
+ | noRowsLabel | string | 'No Data' | Empty state message |
83
+ | density | 'compact'/'standard'/'comfortable' | 'standard' | 32px / rowHeight / 72px rows |
80
84
 
81
85
  ### Selection
82
86
  | Prop | Type | Description |
83
87
  |----------------------------|-------------------------------|-------------------------------------|
84
- | checkboxSelection | boolean | Enable checkbox column |
85
- | disableMultipleRowSelection | boolean | Cap selection to one row |
88
+ | checkboxSelection | boolean | Enable checkbox column (header checkbox = select all filtered rows) |
89
+ | disableMultipleRowSelection | boolean | Cap selection to one row (checkboxes, Space, apiRef too); removes the select-all checkbox |
86
90
  | pinCheckboxColumn | boolean (default true) | Pin the checkbox column left |
87
91
  | rowSelectionModel | GridRowId[] | Controlled selection |
88
- | onRowSelectionModelChange | (model: GridRowId[]) => void | Selection change callback |
92
+ | onRowSelectionModelChange | (model: GridRowId[]) => void | Selection change callback. Also fires once with ids of removed rows pruned (client-owned rows) |
89
93
  | disableRowSelectionOnClick | boolean | Prevent click toggling selection |
94
+ | disableClipboardCopy | boolean | Turn off the grid's Ctrl/Cmd+C copy (copySelectedRows still works) |
90
95
 
91
96
  ### Sorting
92
97
  | Prop | Type | Description |
@@ -94,118 +99,125 @@ export default function App() {
94
99
  | sortModel | GridSortItem[] | Controlled sort state |
95
100
  | multiSort | boolean | Plain click appends to sortModel (Shift+click always does) |
96
101
  | onSortModelChange | (model: GridSortItem[]) => void | Sort change callback |
97
- | sortingMode | 'client' / 'server' | Where sorting is applied |
102
+ | sortingMode | 'client' / 'server' | 'server': rows are shown in the order given |
98
103
 
99
104
  ### Filtering
100
105
  | Prop | Type | Description |
101
106
  |---------------------|--------------------------------|-----------------------------------|
102
- | filterModel | GridFilterModel | Controlled filter state |
107
+ | filterModel | GridFilterModel | Controlled filter state (optional: the grid keeps its own filter model otherwise) |
103
108
  | onFilterModelChange | (model: GridFilterModel) => void | Filter change callback |
104
- | filterMode | 'client' / 'server' | Where filtering is applied |
109
+ | filterMode | 'client' / 'server' | 'server': rows are shown unfiltered as given |
105
110
 
106
111
  ### Pagination
107
112
  | Prop | Type | Description |
108
113
  |-------------------------|-----------------------------------------|----------------------------------|
109
- | pagination | boolean | Enable client-side pagination |
110
- | paginationModel | GridPaginationModel | {page, pageSize} controlled |
111
- | onPaginationModelChange | (model: GridPaginationModel) => void | Pagination change callback |
112
- | pageSizeOptions | number[] | Available page sizes |
113
- | paginationMode | 'client' / 'server' / 'infinite' | Pagination mode |
114
- | rowCount | number | Total rows for server pagination |
114
+ | pagination | boolean | Show the pager. Ignored under rowGroupingModel and with paginationMode='infinite' |
115
+ | paginationModel | GridPaginationModel | {page, pageSize} controlled (pass onPaginationModelChange too) |
116
+ | onPaginationModelChange | (model: GridPaginationModel) => void | Pagination change callback (also fires when the grid clamps a page past the end) |
117
+ | pageSizeOptions | number[] (default [10, 25, 50, 100]) | Available page sizes |
118
+ | paginationMode | 'client' / 'server' / 'infinite' | 'server': rows is the current page. 'infinite': needs dataSource |
119
+ | rowCount | number | Server total for paginationMode='server' without dataSource (dataSource response rowCount wins) |
115
120
 
116
121
  ### Column Features
117
122
  | Prop | Type | Description |
118
123
  |---------------------------|---------------------------|-------------------------------------------|
119
- | pinnedColumns | GridColumnPinning | {left: string[], right: string[]} |
124
+ | pinnedColumns | GridColumnPinning | {left: string[], right: string[]}; rendered in that order |
120
125
  | onPinnedColumnsChange | (model) => void | Pin change callback |
121
126
  | columnVisibilityModel | Record<string, boolean> | {field: false} to hide |
122
127
  | onColumnVisibilityModelChange | (model) => void | Visibility change callback |
123
128
  | columnOrder | string[] | Controlled column order |
124
- | onColumnOrderChange | (params) => void | Column reorder callback |
129
+ | onColumnOrderChange | ({column, oldIndex, targetIndex}) => void | One column moved; indices in the full current column order |
130
+ | onColumnOrderModelChange | (columnOrder: string[]) => void | Whole new order after every change, incl. Columns panel Reset (use this to control columnOrder) |
125
131
  | disableColumnReorder | boolean | Disable drag-to-reorder |
126
- | columnGroupingModel | GridColumnGroupingModel | Multi-level column header groups |
132
+ | columnGroupingModel | GridColumnGroupingModel | Multi-level column header groups; drag-reorder stays within a group |
127
133
 
128
134
  ### Row Features
129
135
  | Prop | Type | Description |
130
136
  |-------------------|---------------------------|-------------------------------------------|
131
- | pinnedRows | GridRowPinning | {top: id[], bottom: id[]} |
132
- | rowReordering | boolean | Enable drag-and-drop row reordering |
133
- | onRowOrderChange | (params) => void | Row reorder callback |
134
- | getRowId | (row: R) => GridRowId | Custom row ID accessor (default: row.id) |
137
+ | pinnedRows | GridRowPinning | {top: id[], bottom: id[]}. Ignored under treeData / rowGroupingModel (dev warning) |
138
+ | rowReordering | boolean | Enable drag-and-drop row reordering (no effect in pivot mode) |
139
+ | onRowOrderChange | ({row, oldIndex, targetIndex}) => void | Indices are positions in the `rows` prop |
140
+ | getRowId | (row: R) => GridRowId | Custom row ID accessor (default: row.id). Row objects are NOT given an `id` |
135
141
 
136
142
  ### Cell Editing
137
143
  | Prop | Type | Description |
138
144
  |-------------------------|----------------------------|-----------------------------------------|
139
- | processRowUpdate | (newRow, oldRow) => R | Commit edits. Must return updated row |
140
- | onProcessRowUpdateError | (error) => void | Handle commit errors |
141
- | isCellEditable | (params) => boolean | Fine-grained editability control |
145
+ | processRowUpdate | (newRow, oldRow) => R / Promise<R> | Called once per commit. Must return the updated row |
146
+ | onProcessRowUpdateError | (error) => void | processRowUpdate threw, rejected or returned no row (editor stays open) |
147
+ | isCellEditable | (params: GridCellParams) => boolean | Gates double-click, Enter and Tab editing; rejected cells get aria-readonly |
142
148
 
143
149
  ### Detail Panels (Master-Detail)
144
150
  | Prop | Type | Description |
145
151
  |-------------------------|----------------------------|-----------------------------------------|
146
- | getDetailPanelContent | (params) => ReactNode | Render expanded detail row |
152
+ | getDetailPanelContent | (params: {row, id, rowIndex}) => ReactNode | Called for expanded data rows only (never group rows) |
147
153
  | pinExpandColumn | boolean (default true) | Pin the detail-panel expand column |
148
- | getDetailPanelHeight | (params) => number / 'auto'| Height of detail panel |
154
+ | getDetailPanelHeight | (params) => number / 'auto'| px, or 'auto' = measured content height. Default 200 |
149
155
  | detailPanelExpandedRowIds | Set<GridRowId> | Controlled expanded rows |
150
- | onDetailPanelExpandedRowIdsChange | (ids) => void | Expand/collapse callback |
156
+ | onDetailPanelExpandedRowIdsChange | (ids: Set<GridRowId>) => void | Expand/collapse callback |
151
157
 
152
158
  ### Grouping & Aggregation
153
159
  | Prop | Type | Description |
154
160
  |------------------------|---------------------------|------------------------------------------|
155
- | rowGroupingModel | string[] | Fields to group by (one nesting level per field). Disables pagination |
156
- | aggregationModel | GridAggregationModel | {field: 'sum'/'avg'/'min'/'max'/'count'} |
157
- | onAggregationModelChange | (model) => void | Aggregation model changed |
158
- | groupingColDef | Partial<GridColDef> | Dedicated pinned __group__ column |
159
- | getAggregationPosition | (group) => 'footer'/'inline' | Where to render aggregation row |
160
- | defaultGroupingExpansionDepth | number | Auto-expand depth (default -1 = all) |
161
+ | rowGroupingModel | string[] | Fields to group by (one nesting level per field). Disables pagination. groupable:false fields are skipped |
162
+ | aggregationModel | GridAggregationModel | {field: 'sum'/'avg'/'min'/'max'/'count'/'unique'} |
163
+ | onAggregationModelChange | (model) => void | Aggregation model changed (toolbar Summaries panel) |
164
+ | groupingColDef | Partial<GridColDef<R>> | Adds a dedicated `__group__` column pinned left (rowGroupingModel or treeData); no `field` needed; never exported |
165
+ | getAggregationPosition | (groupNode: GridTreeNode / null) => 'inline'/'footer'/null | Per group: 'inline' (default) on the group row, 'footer' on a subtotal row, null hides. Called with null for the grand total: null hides the footer |
166
+ | defaultGroupingExpansionDepth | number | Initial expansion depth. Default 0 (all collapsed); -1 = expand all |
161
167
 
162
168
  ### Tree Data
163
169
  | Prop | Type | Description |
164
170
  |---------------------------|----------------------------|--------------------------------------|
165
171
  | treeData | boolean | Enable tree data mode |
166
- | getTreeDataPath | (row) => string[] | Return path array for each row |
167
- | defaultGroupingExpansionDepth | number | Same as grouping depth |
172
+ | getTreeDataPath | (row) => string[] | Required with treeData (without it rows show flat). Keep it stable (module scope / useCallback) |
173
+ | defaultGroupingExpansionDepth | number | Same as grouping (default 0) |
174
+
175
+ Missing path segments become auto-created parent rows `{ id }` (id like `auto-group-["a","b"]`); their label is `params.rowMeta.groupLabel`.
168
176
 
169
177
  ### Pivot
170
178
  | Prop | Type | Description |
171
179
  |----------------|----------------|-----------------------------------------------|
172
- | pivotMode | boolean | Enable pivot view |
173
- | pivotModel | GridPivotModel | {rowFields, columnFields, valueFields} |
174
- | onPivotModelChange | (model) => void | Pivot change callback |
180
+ | pivotMode | boolean | Enable pivot view (treeData / rowGrouping / getRowId / dataSource are ignored while pivoting) |
181
+ | pivotModel | GridPivotModel | {rowFields, columnFields, valueFields: {field, aggFn: 'sum'/'avg'/'count'/'min'/'max', headerName?}[]} |
182
+ | onPivotModelChange | (model) => void | Pivot change callback (toolbar Pivot panel) |
183
+
184
+ Pivot row ids look like `__pivot_row__:["North"]`; the Grand Total row is always last and cannot be selected.
175
185
 
176
186
  ### Server-Side
177
187
  | Prop | Type | Description |
178
188
  |------------|-----------------|----------------------------------------------------|
179
- | dataSource | GridDataSource | Adapter for server-side fetching (see below) |
189
+ | dataSource | GridDataSource | Adapter for server-side fetching (see SERVER-SIDE DATA SOURCE) |
180
190
 
181
191
  ### Styling & Customization
182
192
  | Prop | Type | Description |
183
193
  |--------------|-----------------------|------------------------------------------|
184
- | slots | GridSlots | Replace toolbar, pagination, overlays |
185
- | slotProps | GridSlotProps | Props for slot components |
194
+ | slots | { toolbar, pagination, noRowsOverlay, loadingOverlay, footer } | Replace built-in parts |
195
+ | slotProps | { toolbar, pagination, noRowsOverlay, loadingOverlay, footer } | Extra props for those slots |
186
196
  | className | string | Extra CSS class on the root element |
187
197
  | style | React.CSSProperties | Inline styles on the root element (use DataGridThemeProvider for theming) |
188
198
 
199
+ There are no getRowClassName / getCellClassName / theme props: use GridColDef.cellClassName (string or function) and DataGridThemeProvider.
200
+
189
201
  ### Callbacks
190
202
  | Prop | Type | Description |
191
203
  |-------------------|-----------------------|-------------------------------|
192
- | onRowClick | (params) => void | Row click |
193
- | onCellClick | (params) => void | Cell click |
194
- | onRowDoubleClick | (params) => void | Row double-click |
195
- | onRowsScrollEnd | (params) => void | Fired near bottom of scroll |
196
- | onStateChange | (state) => void | Fires on any state change |
204
+ | onRowClick | (params: {row, id, rowIndex}) => void | Row click, or Enter on a non-editable cell. Not fired for synthetic group rows (a click toggles them) |
205
+ | onCellClick | (params: GridCellParams) => void | Cell click (not inside an open editor) |
206
+ | onRowDoubleClick | (params) => void | Row double-click (also fires for group rows) |
207
+ | onRowsScrollEnd | (params) => void | Fires once each time the bottom comes into view (re-armed when rows are added or the user scrolls away) |
208
+ | onStateChange | (state: GridState) => void | Fires when sorting, filter, pagination, columns (widths/order/pinning/visibility) or density change |
197
209
 
198
210
  ### State Persistence
199
211
  | Prop | Type | Description |
200
212
  |--------------|------------------|----------------------------------------------|
201
- | initialState | GridInitialState | Restore column widths, sort, filter on mount |
202
- | apiRef | RefObject<GridApi> | Attach imperative API handle |
213
+ | initialState | GridInitialState | Starting sorting, filter, pagination, columns (widths, order, pinning, visibility) and density. Read at mount only |
214
+ | apiRef | MutableRefObject<GridApi<R>> or MutableRefObject<GridApi> | Imperative API handle from useGridApiRef<R>() (typed row getters) or useGridApiRef() |
203
215
 
204
216
  ### List View
205
217
  | Prop | Type | Description |
206
218
  |----------------|--------------------|---------------------------------------------|
207
- | listView | boolean | Render as card/list instead of table |
208
- | listViewColumn | GridListViewColDef | Defines the card renderer |
219
+ | listView | boolean | Render as cards instead of a table (requires listViewColumn; without it the grid view shows + dev warning). Honours loading, noRowsOverlay/loadingOverlay and slots.footer; rows are a roving Tab stop (arrows, Home/End, PageUp/Down, Enter/Space, Alt+Arrow expand); tree/group parents get a chevron. Not virtualized (dev warning above 2,000 items; paginate) |
220
+ | listViewColumn | { field, renderCell } | Defines the card renderer |
209
221
 
210
222
  ---
211
223
 
@@ -215,31 +227,37 @@ export default function App() {
215
227
  |-------------------|--------------------------------------------------------|--------------------------------------------|
216
228
  | field | string | **Required.** Key in row data |
217
229
  | headerName | string | Column label |
218
- | width | number / string (px or %) | Column width |
219
- | flex | number | Flex grow factor |
220
- | minWidth | number | Minimum width constraint |
221
- | maxWidth | number | Maximum width constraint |
222
- | type | 'string'/'number'/'boolean'/'date'/'singleSelect'/'image' | Data type |
223
- | editable | boolean | Enable double-click editing |
224
- | valueOptions | string[] / {value, label}[] | Options for singleSelect type |
225
- | renderCell | (params: GridRenderCellParams) => ReactNode | Custom cell renderer |
230
+ | description | string | Header tooltip |
231
+ | width | number / string (px or %) | Column width. Neither width nor flex = flex: 1 |
232
+ | flex | number | Flex grow factor; `width` is ignored when flex is set |
233
+ | minWidth | number | Minimum width (layout and resize; resize default 50) |
234
+ | maxWidth | number | Maximum width (layout and resize; no max by default) |
235
+ | type | 'string'/'number'/'boolean'/'date'/'singleSelect'/'image' | Drives filter operators, sorting, editors and, without a valueFormatter, the cell text (date: local calendar date, boolean: Yes/No, singleSelect: option label, image: <img>); formattedValue, list view and exports use the same text |
236
+ | editable | boolean | Enable double-click / Enter editing |
237
+ | valueOptions | (string / number / {value, label})[] | Options for singleSelect type (editor commits the original option value) |
238
+ | renderCell | (params: GridRenderCellParams<R>) => ReactNode | Custom cell renderer (errors contained per cell) |
226
239
  | renderHeader | (params: GridRenderHeaderParams) => ReactNode | Custom header renderer |
227
- | renderEditCell | (params: GridRenderCellParams) => ReactNode | Custom editor component |
228
- | valueGetter | ({row, field, value}) => any | Computed cell value |
229
- | valueFormatter | ({value, row, field}) => string | Format display value |
240
+ | renderEditCell | (params: GridRenderEditCellParams<R>) => ReactNode | Custom editor: call params.onValueChange(v), params.onCommit(), params.onCancel() |
241
+ | valueGetter | ({row, field, value}) => unknown | Computed value; used by sort, filter, quick search, aggregation, copy, export. Must be pure (quick filter caches per row object); a throw reads as undefined + dev warning |
242
+ | valueSetter | ({value, row, field}) => R | Write an edited value back onto the row (editable valueGetter columns) |
243
+ | valueFormatter | ({value, row, field}) => string | Format display value (value is `unknown`: cast before calling methods) |
230
244
  | cellClassName | string / (params) => string | CSS class on cells |
231
245
  | headerClassName | string | CSS class on header cell |
232
- | colSpan | number / (params) => number | Cell column span |
233
- | rowSpan | number / (params) => number | Cell row span |
234
- | align | 'left'/'center'/'right' | Cell content alignment |
235
- | headerAlign | 'left'/'center'/'right' | Header content alignment |
246
+ | colSpan | number / (params) => number | Cell column span (clamped to the pinned section; hidden columns skipped) |
247
+ | rowSpan | number / (params) => number | Cell row span (clamped to the row section) |
248
+ | align | 'left'/'center'/'right' | Cell content alignment |
249
+ | headerAlign | 'left'/'center'/'right' | Header content alignment |
236
250
  | sortable | boolean (default true) | Enable sorting |
237
- | filterable | boolean (default true) | Enable filtering |
238
- | resizable | boolean (default true) | Enable resize handle |
239
- | hideable | boolean (default true) | Allow hiding via toolbar |
240
- | pinnable | boolean (default true) | Allow pinning via column menu |
241
- | exportable | boolean (default true) | Include in CSV/Excel/JSON/Print exports |
251
+ | filterable | boolean (default true) | Enable filtering and quick search |
252
+ | resizable | boolean (default true) | Enable resize handle (mouse, touch, Alt+Arrow on header) |
253
+ | hideable | boolean (default true) | Allow hiding via toolbar / column menu |
254
+ | pinnable | boolean (default true) | Allow pinning via column menu (does not affect drag-reorder) |
255
+ | exportable | boolean (default true) | Include in CSV/Excel/JSON/Print/PDF exports and clipboard copy |
242
256
  | disableColumnMenu | boolean | Hide the ⋮ column menu button |
257
+ | groupable | boolean | false = skipped by rowGroupingModel and the pivot panel |
258
+ | groupingValueFormatter | ({field, value}) => string | Group row label (default "field: value") |
259
+ | aggregable | boolean | false = no aggregation. Summaries panel offers number columns + aggregable: true |
260
+ | availableAggregationFunctions | string[] | Restrict aggregation functions for this column |
243
261
 
244
262
  ---
245
263
 
@@ -248,37 +266,45 @@ export default function App() {
248
266
  ```tsx
249
267
  import { useGridApiRef } from '@opencorestack/opengridx';
250
268
  const apiRef = useGridApiRef();
251
- <DataGrid apiRef={apiRef} ... />
269
+ <DataGrid apiRef={apiRef} rows={rows} columns={columns} />
252
270
 
253
271
  // Row access
254
- apiRef.current.getRow(id) // → GridRowModel | null
255
- apiRef.current.getAllRows() // → GridRowModel[]
256
- apiRef.current.getVisibleRows() // → GridRowModel[]
272
+ apiRef.current.getRow(id) // → R | null with useGridApiRef<R>(), else GridRowModel | null
273
+ apiRef.current.getAllRows() // → every loaded row
274
+ apiRef.current.getVisibleRows() // → rows on screen: current page + pinned rows
275
+ apiRef.current.getAllFilteredRows() // → every filtered + sorted row, all pages (use for exports); tree data + filter: matching rows only
276
+ apiRef.current.getGroupedExportRows() // → GridGroupedExportRow[] | null (row grouping only)
257
277
 
258
278
  // Column access
259
- apiRef.current.getColumn(field) // → GridColDef | null
260
- apiRef.current.getAllColumns() // → GridColDef[]
261
- apiRef.current.getVisibleColumns() // → GridColDef[]
279
+ apiRef.current.getColumn(field) // → GridColDef | null
280
+ apiRef.current.getAllColumns() // → GridColDef[]
281
+ apiRef.current.getVisibleColumns() // → visible columns in render order (left-pinned, unpinned, right-pinned)
262
282
 
263
283
  // Selection
264
- apiRef.current.selectRow(id, isSelected?) // select/deselect a row
265
- apiRef.current.selectRows(ids, isSelected?) // bulk select
266
- apiRef.current.getSelectedRows() // → GridRowId[]
284
+ apiRef.current.selectRow(id, isSelected?) // select/deselect a row (synthetic group/total ids ignored; no-op fires nothing)
285
+ apiRef.current.selectRows(ids, isSelected?) // bulk select (same rules)
286
+ apiRef.current.getSelectedRows() // → GridRowId[]
267
287
 
268
- // Sort / Filter
288
+ // Sort / Filter (setters update the grid and fire the change callbacks)
269
289
  apiRef.current.sortColumn(field, 'asc' | 'desc' | null)
270
- apiRef.current.getSortModel() // → GridSortItem[]
290
+ apiRef.current.getSortModel() // → GridSortItem[]
271
291
  apiRef.current.setFilterModel(model)
292
+ apiRef.current.getFilterModel() // → GridFilterModel
272
293
 
273
294
  // Pagination
274
295
  apiRef.current.setPage(page)
275
296
  apiRef.current.setPageSize(size)
276
297
 
298
+ // Scroll
299
+ apiRef.current.scrollToIndexes({ rowIndex, colIndex })
300
+
277
301
  // Clipboard
278
- apiRef.current.copySelectedRows() // → Promise<void> (TSV to clipboard)
302
+ apiRef.current.copySelectedRows() // → Promise<void>; rejects if the write fails. Copies every selected row that passes
303
+ // the filter (all pages, collapsed groups, pinned rows), visible columns in screen order
279
304
 
280
- // Aggregation (when aggregationModel is set)
281
- apiRef.current.getAggregationResult() // → GridAggregationResult | null
305
+ // Aggregation
306
+ apiRef.current.getAggregationResult() // → Record<string, unknown> | null (null in pivot mode)
307
+ apiRef.current.getAggregationModel() // → GridAggregationModel | null
282
308
  ```
283
309
 
284
310
  ---
@@ -286,10 +312,13 @@ apiRef.current.getAggregationResult() // → GridAggregationResult | null
286
312
  ## CELL EDITING — FULL EXAMPLE
287
313
 
288
314
  ```tsx
289
- const [rows, setRows] = useState(initialRows);
315
+ // Your own row type: an interface or a type alias, no index signature needed (v3+)
316
+ interface Person { id: number; name: string; age: number; role: string }
317
+
318
+ const [rows, setRows] = useState<Person[]>(initialRows);
290
319
 
291
- const processRowUpdate = (newRow, oldRow) => {
292
- setRows(prev => prev.map(r => r.id === newRow.id ? newRow : r));
320
+ const processRowUpdate = (newRow: Person) => {
321
+ setRows(prev => prev.map(r => (r.id === newRow.id ? newRow : r)));
293
322
  return newRow; // Must return the committed row
294
323
  };
295
324
 
@@ -306,41 +335,57 @@ const processRowUpdate = (newRow, oldRow) => {
306
335
  />
307
336
  ```
308
337
 
338
+ Double-click or Enter opens the editor; Enter / Tab / blur commits; Escape cancels. An edit whose row scrolls, pages or filters away is committed, not dropped.
339
+
309
340
  ---
310
341
 
311
342
  ## SERVER-SIDE DATA SOURCE
312
343
 
344
+ Pass a `dataSource` object to DataGrid; there is no separate data-source hook to import.
345
+
313
346
  ```tsx
314
- import { useGridDataSource } from '@opencorestack/opengridx';
347
+ import { DataGrid, type GridDataSource } from '@opencorestack/opengridx';
315
348
 
316
349
  const dataSource: GridDataSource = {
317
- getRows: async ({ page, pageSize, sortModel, filterModel }) => {
318
- const data = await fetch(`/api/rows?page=${page}&size=${pageSize}`).then(r => r.json());
319
- return { rows: data.items, rowCount: data.total };
350
+ // params: { startRow, endRow, sortModel, filterModel, groupKeys, aggregationModel? }
351
+ getRows: async ({ startRow, endRow, sortModel, filterModel, groupKeys }) => {
352
+ const res = await fetch('/api/rows', {
353
+ method: 'POST',
354
+ body: JSON.stringify({ startRow, endRow, sortModel, filterModel, groupKeys }),
355
+ });
356
+ const data = await res.json();
357
+ return { rows: data.items, rowCount: data.total }; // rowCount optional, needed for server paging
358
+ },
359
+ // Optional: totals for the aggregation footer
360
+ getAggregations: async ({ sortModel, filterModel, aggregationModel }) => {
361
+ return fetch('/api/totals').then(r => r.json());
320
362
  },
321
- // Optional: lazy-load children for server-side tree data
322
- getChildren: async ({ row }) => {
323
- return fetch(`/api/rows/${row.id}/children`).then(r => r.json());
324
- }
325
363
  };
326
364
 
327
365
  <DataGrid
328
366
  columns={columns}
329
- rows={[]} // empty — data comes from dataSource
367
+ rows={[]} // data comes from dataSource
330
368
  dataSource={dataSource}
369
+ pagination
331
370
  paginationMode="server"
332
371
  sortingMode="server"
333
372
  filterMode="server"
334
- rowCount={1000}
335
373
  />
336
374
  ```
337
375
 
376
+ - Requests are page-based: server pagination asks for `[page*pageSize, (page+1)*pageSize)`; client pagination (default modes) asks once for every row (`endRow: Number.MAX_SAFE_INTEGER`); `paginationMode="infinite"` asks for the rows after those loaded, up to `(page+1)*pageSize`, and appends them (advance `paginationModel.page` from `onRowsScrollEnd`).
377
+ - `endRow` is exclusive: return `allRows.slice(startRow, endRow)`.
378
+ - Server tree data: rows with `serverChildrenCount > 0` get an expand toggle; expanding calls `getRows` with the row's path in `groupKeys`. There is no `getChildren`.
379
+ - Refetch happens only when a model's content or the `getRows` function changes; stale responses are discarded; 300 ms debounce.
380
+ - A rejected `getRows` (or a response that cannot be processed) shows the error overlay; its Retry button calls `getRows` again.
381
+ - Row grouping with `paginationMode="server"` fetches every row in one request (`[0, Number.MAX_SAFE_INTEGER)`, dev warning). Server tree data with `defaultGroupingExpansionDepth` auto-expands and loads lazy nodes (one request per node).
382
+
338
383
  ---
339
384
 
340
385
  ## THEMING
341
386
 
342
387
  ```tsx
343
- import { DataGridThemeProvider, darkTheme } from '@opencorestack/opengridx';
388
+ import { DataGridThemeProvider, darkTheme, type GridTheme } from '@opencorestack/opengridx';
344
389
 
345
390
  // Built-in themes: darkTheme, roseTheme, emeraldTheme, amberTheme, compactTheme
346
391
  <DataGridThemeProvider theme={darkTheme}>
@@ -348,25 +393,27 @@ import { DataGridThemeProvider, darkTheme } from '@opencorestack/opengridx';
348
393
  </DataGridThemeProvider>
349
394
 
350
395
  // Custom theme override:
351
- const myTheme = {
396
+ const myTheme: GridTheme = {
397
+ mode: 'light', // base palette the provider pins ('light' default, or 'dark')
352
398
  colors: {
353
399
  primary: '#6366f1',
354
400
  },
355
401
  grid: {
356
- headerBackground: '#1e1e2e',
357
- rowHoverBackground: '#2a2a3e',
402
+ headerBackground: '#eef2ff',
403
+ rowHoverBackground: '#f5f3ff',
404
+ rowHeightStandard: '40px', // theme heights size rows/header; rowHeight/headerHeight props win
358
405
  },
359
406
  overlays: {
360
- background: '#1e1e2e',
361
- border: '#2a2a3e',
362
- }
407
+ background: '#ffffff',
408
+ border: '#e0e7ff',
409
+ },
363
410
  };
364
411
  <DataGridThemeProvider theme={myTheme}>
365
- <DataGrid ... />
412
+ <DataGrid rows={rows} columns={columns} />
366
413
  </DataGridThemeProvider>
367
414
 
368
- // Note: Panels and Dropdowns (Column Menu, Pivot, etc.) intelligently hunt for
369
- // `.ogx-theme-provider` to inherit these variables organically through portals.
415
+ // The provider pins a complete light or dark palette regardless of the OS colour scheme.
416
+ // Panels, menus and tooltips render into `.ogx-theme-provider`, so they inherit the theme.
370
417
  ```
371
418
 
372
419
  ---
@@ -376,21 +423,24 @@ const myTheme = {
376
423
  ```tsx
377
424
  import { GridToolbar } from '@opencorestack/opengridx';
378
425
 
379
- // Replace built-in components:
380
426
  <DataGrid
427
+ rows={rows}
428
+ columns={columns}
381
429
  slots={{
382
- toolbar: MyCustomToolbar,
383
- pagination: MyPagination,
430
+ toolbar: GridToolbar, // or your own component
431
+ pagination: MyPagination, // props: page, pageSize, rowCount, pageSizeOptions, onPageChange, onPageSizeChange, localeText
384
432
  noRowsOverlay: EmptyState,
385
433
  loadingOverlay: Spinner,
386
- footer: CustomFooter,
434
+ footer: CustomFooter, // replaces the pager; see note 14
387
435
  }}
388
436
  slotProps={{
389
- toolbar: { onExport: () => apiRef.current.copySelectedRows() },
437
+ toolbar: { showNonHideableColumns: true },
390
438
  }}
391
439
  />
392
440
  ```
393
441
 
442
+ `GridToolbar` in `slots.toolbar` gets search, Columns, Filters and Summaries (plus Pivot when pivot is configured); they work without any filter/visibility props. Render props: `renderQuickFilter`, `renderColumnsButton`, `renderFilterButton`, `renderAggregationButton`, `renderExportButton`.
443
+
394
444
  ---
395
445
 
396
446
  ## STATE PERSISTENCE
@@ -399,11 +449,11 @@ import { GridToolbar } from '@opencorestack/opengridx';
399
449
  import { useGridStateStorage } from '@opencorestack/opengridx';
400
450
 
401
451
  function MyGrid() {
402
- const { initialState, handleStateChange } = useGridStateStorage('my-grid-v1');
452
+ const { initialState, onStateChange, clearState } = useGridStateStorage('my-grid-v1');
403
453
  return (
404
454
  <DataGrid
405
455
  initialState={initialState}
406
- onStateChange={handleStateChange}
456
+ onStateChange={onStateChange}
407
457
  rows={rows}
408
458
  columns={columns}
409
459
  />
@@ -411,6 +461,8 @@ function MyGrid() {
411
461
  }
412
462
  ```
413
463
 
464
+ Also accepts `{ key, debounceMs?, include?: (keyof GridState)[], storage? }`. `initialState` is read at mount: when the key changes, remount with `<DataGrid key={key} />`. If storage is blocked, the grid works without persistence.
465
+
414
466
  ---
415
467
 
416
468
  ## EXPORT UTILITIES
@@ -418,60 +470,85 @@ function MyGrid() {
418
470
  ```tsx
419
471
  import {
420
472
  exportToCsv,
421
- exportToExcel, // HTML-based .xlsx — zero deps
422
- exportToExcelAdvanced, // Rich .xlsx binary — requires: npm install exceljs
473
+ exportToExcel, // HTML table saved as .xls — zero deps
474
+ exportToExcelAdvanced, // real .xlsx — requires: npm install exceljs
423
475
  exportToJson,
476
+ exportToPdf, // requires: npm install jspdf jspdf-autotable
424
477
  printGrid,
425
478
  } from '@opencorestack/opengridx';
426
479
 
427
- exportToCsv(rows, columns, { fileName: 'export.csv' });
428
- exportToExcel(rows, columns, { fileName: 'export.xlsx' });
429
- exportToExcelAdvanced(rows, columns, {
480
+ const rows = apiRef.current.getAllFilteredRows(); // what the grid shows, all pages
481
+ const columns = apiRef.current.getVisibleColumns(); // visible columns, display order
482
+
483
+ exportToCsv(rows, columns, { fileName: 'export.csv' }); // UTF-8 BOM (bom: false to omit)
484
+ exportToExcel(rows, columns, { fileName: 'export.xls', sheetName: 'Data' });
485
+ await exportToExcelAdvanced(rows, columns, {
430
486
  fileName: 'export.xlsx',
431
- sheetName: 'Data',
487
+ sheets: [{ name: 'Data' }, { name: 'Selected', rows: 'selected' }],
488
+ selectedRows: apiRef.current.getSelectedRows(),
432
489
  columnStyles: {
433
490
  avatar: { embedImage: true, imageWidth: 32, imageHeight: 32 }
434
491
  }
435
492
  });
436
493
  exportToJson(rows, columns, { fileName: 'export.json' });
437
- printGrid(rows, columns, 'My Report Title');
494
+ await exportToPdf(rows, columns, { fileName: 'export', title: 'My Report' });
495
+ await printGrid(rows, columns, 'My Report Title');
438
496
  ```
439
497
 
498
+ - Every export accepts `selectedRows`, `getRowId` (pass the grid's getRowId so ids match), `aggregationResult` + `aggregationModel` (totals row) and `groupedRows` from `apiRef.current.getGroupedExportRows()`.
499
+ - CSV and exportToExcel neutralise formulas: text starting with = + - @ tab or CR gets a leading `'` (`escapeFormulas: false` opts out).
500
+ - exportToExcel writes HTML: a `.xlsx` fileName is saved as `.xls` with a console warning.
501
+ - Columns with `exportable: false` are left out everywhere; `valueGetter` and `valueFormatter` are applied.
502
+
440
503
  ---
441
504
 
442
505
  ## CSS CUSTOMIZATION
443
506
 
444
- All CSS variables use the `--ogx-` prefix. Set them on the grid wrapper:
507
+ All CSS variables use the `--ogx-` prefix and are declared on `:root` by the stylesheet. Override them on a wrapper (or use DataGridThemeProvider, which sets the same variables):
445
508
 
446
509
  ```css
447
510
  .my-grid-wrapper {
448
511
  --ogx-color-primary: #6366f1;
449
- --ogx-color-header-bg: #1e1e2e;
450
- --ogx-color-row-hover: rgba(99, 102, 241, 0.08);
451
- --ogx-color-border: #2a2a3e;
452
- --ogx-font-size-cell: 13px;
453
- --ogx-border-radius: 8px;
512
+ --ogx-grid-header-background: #eef2ff;
513
+ --ogx-grid-row-hover-background: rgba(99, 102, 241, 0.08);
514
+ --ogx-grid-border-color: #e0e7ff;
515
+ --ogx-grid-cell-font-size: 13px;
516
+ --ogx-border-radius-md: 8px;
454
517
  }
455
518
  ```
456
519
 
457
- All class names use the `ogx__` BEM prefix, e.g.:
458
- - `ogx__row`, `ogx__row--selected`, `ogx__row--even`
520
+ Derived tokens (e.g. `--ogx-grid-cell-focus-border`, which defaults to the primary colour) are resolved on `:root`, so override them directly when you change `--ogx-color-primary` on a wrapper.
521
+
522
+ Class names use the `ogx__` BEM prefix, e.g.:
523
+ - `ogx__row`, `ogx__row--selected`, `ogx__row--even`, `ogx__row--odd`
459
524
  - `ogx__cell`, `ogx__cell--focused`, `ogx__cell--editing`
460
525
  - `ogx__header-cell`, `ogx__header-cell--sorted`
526
+ - Sticky wrappers: `ogx__sticky-top` (header + top-pinned rows), `ogx__sticky-bottom` (bottom-pinned rows + aggregation footer)
461
527
 
462
528
  ---
463
529
 
464
530
  ## TYPE REFERENCE
465
531
 
466
532
  ```ts
467
- type GridRowModel = Record<string, any> & { id: GridRowId };
468
533
  type GridRowId = string | number;
534
+ interface GridRowModel { id: GridRowId; [key: string]: unknown; }
469
535
  type GridSortDirection = 'asc' | 'desc' | null;
470
- type GridPinnedPosition = 'left' | 'right' | null;
536
+ type GridPinnedPosition = 'left' | 'right';
537
+
538
+ type GridFilterOperator =
539
+ | 'contains' | 'equals' | 'startsWith' | 'endsWith' // string
540
+ | 'isEmpty' | 'isNotEmpty' | 'isAnyOf'
541
+ | '=' | '!=' | '>' | '>=' | '<' | '<=' // number
542
+ | 'is' | 'not' | 'after' | 'onOrAfter' | 'before' | 'onOrBefore'; // date / boolean / singleSelect
471
543
 
472
544
  interface GridSortItem { field: string; sort: 'asc' | 'desc'; }
473
- interface GridFilterItem { field: string; operator: string; value: any; }
474
- interface GridFilterModel { items: GridFilterItem[]; logicOperator?: 'and' | 'or'; }
545
+ interface GridFilterItem { id?: string | number; field: string; operator: GridFilterOperator; value?: unknown; }
546
+ interface GridFilterGroup { id?: string | number; logicOperator: 'and' | 'or'; items: (GridFilterItem | GridFilterGroup)[]; }
547
+ interface GridFilterModel {
548
+ items?: (GridFilterItem | GridFilterGroup)[];
549
+ logicOperator?: 'and' | 'or';
550
+ quickFilterValues?: string[]; // every term must match some visible, filterable column
551
+ }
475
552
  interface GridPaginationModel { page: number; pageSize: number; }
476
553
  interface GridColumnPinning { left?: string[]; right?: string[]; }
477
554
  interface GridRowPinning { top?: GridRowId[]; bottom?: GridRowId[]; }
@@ -481,33 +558,53 @@ interface GridRenderCellParams<R = GridRowModel> {
481
558
  colDef: GridColDef<R>; rowIndex: number; colIndex: number;
482
559
  rowMeta?: GridRowMeta; // hierarchy info for tree-data / grouping rows
483
560
  }
561
+ interface GridRowMeta {
562
+ hasChildren?: boolean; treeDepth?: number; groupingField?: string; groupingValue?: unknown;
563
+ groupLabel?: string; descendantCount?: number; isExpanded?: boolean;
564
+ isGroupRow?: boolean; isGroupFooter?: boolean;
565
+ }
484
566
 
485
- interface GridDataSource {
486
- getRows: (params: GridGetRowsParams) => Promise<GridGetRowsResponse>;
487
- getChildren?: (params: { row: GridRowModel }) => Promise<GridRowModel[]>;
567
+ interface GridDataSource<R = GridRowModel> {
568
+ getRows: (params: GridGetRowsParams) => Promise<GridGetRowsResponse<R>>;
569
+ getAggregations?: (params: Omit<GridGetRowsParams, 'startRow' | 'endRow'>) => Promise<Record<string, unknown>>;
488
570
  }
489
571
  interface GridGetRowsParams {
490
- page: number; pageSize: number;
572
+ startRow: number; endRow: number; // endRow exclusive; Number.MAX_SAFE_INTEGER = all rows
491
573
  sortModel: GridSortItem[]; filterModel: GridFilterModel;
574
+ groupKeys: string[]; // server tree data: path of the parent being expanded
575
+ aggregationModel?: GridAggregationModel;
576
+ }
577
+ interface GridGetRowsResponse<R = GridRowModel> {
578
+ rows: R[]; rowCount?: number; aggregationResults?: Record<string, unknown>;
492
579
  }
493
- interface GridGetRowsResponse { rows: GridRowModel[]; rowCount: number; }
494
580
  ```
495
581
 
496
582
  ---
497
583
 
584
+ ## KEYBOARD
585
+
586
+ Arrows move between cells (skipping hidden columns); Home/End, Ctrl+Home/End, PageUp/PageDown; Enter edits an editable cell, otherwise acts like a row click (toggles group/tree rows); Tab leaves the grid (while editing it moves to the next editable cell); Shift+Space selects the row (Space toggles the checkbox or detail panel when those cells are focused); Ctrl/Cmd+A selects all; Ctrl/Cmd+C copies the selection; Alt+ArrowRight/Left expands/collapses; Enter/Space on a header sorts (Shift adds to the sort); Alt+ArrowDown or Ctrl+Enter on a header opens the column menu; Alt+ArrowLeft/Right on a header resizes it.
587
+
588
+ ---
589
+
498
590
  ## KEY NOTES FOR AI AGENTS
499
591
 
500
- 1. **Always `return newRow`** from `processRowUpdate` — failing to do so breaks the internal state update.
501
- 2. **`getRowId` default is `row.id`** — if your data uses a different key, always pass `getRowId`.
502
- 3. **CSS must be imported**: if grid is unstyled, add `import '@opencorestack/opengridx/styles'` to app root.
503
- 4. **ExcelJS is optional**: `exportToExcelAdvanced` requires `npm install exceljs` separately (peer dep).
504
- 5. **`dataSource` + `rows={[]}`**: when using server-side mode, pass empty `rows` array — the grid fills via `dataSource.getRows`.
505
- 6. **`flex` columns**: columns with `flex` expand to fill available space. Setting `width` on a flex column sets its base size but it will still flex.
592
+ 1. **Always `return newRow`** from `processRowUpdate` — returning nothing calls `onProcessRowUpdateError` and keeps the editor open.
593
+ 2. **`getRowId` default is `row.id`** — if your data uses a different key, always pass `getRowId`. The grid does not write an `id` onto your rows, and exports need `getRowId` in their options to match `selectedRows`.
594
+ 3. **CSS must be imported**: always add `import '@opencorestack/opengridx/styles'` to the app root. The JS bundle does not load it; dev builds warn once ("The grid stylesheet is not loaded") when it is missing.
595
+ 4. **Optional peers**: `exportToExcelAdvanced` needs `exceljs`; `exportToPdf` needs `jspdf` + `jspdf-autotable`.
596
+ 5. **`dataSource` + `rows={[]}`**: with a dataSource pass an empty `rows` array — the grid fills it via `dataSource.getRows`.
597
+ 6. **`flex` columns**: columns with `flex` share the free space; `width` is ignored on them (use `minWidth` / `maxWidth`). A column with neither `width` nor `flex` acts like `flex: 1`.
506
598
  7. **`singleSelect` editing**: for dropdown editors in cells, set `type: 'singleSelect'` and provide `valueOptions`.
507
- 8. **`initialState.columns.columnWidths`**: accepts `Record<string, number>` to restore saved column widths.
599
+ 8. **`initialState.columns.columnWidths`**: accepts `Record<string, number>` to restore saved column widths. Every part of `initialState` is optional.
508
600
  9. **`pinnedColumns`** and **`columnWidths`** are independent state — pinning does not reset widths.
509
- 10. **Aggregation**: setting `aggregationModel` alone renders a sticky footer with the totals; with `rowGroupingModel` each group row also shows its own aggregates. `null`/`undefined` values are ignored by every function.
510
- 11. **GOTCHAS — bounded height**: virtualization only works if the grid's container has a bounded height. A flex child without `min-height: 0` grows to fit every row, so every row is rendered. Pagination hides this, and row grouping turns pagination off. Use `<div style={{ flex: 1, minHeight: 0 }}>` around `height="100%"`, or pass a fixed pixel `height`. Dev builds log a warning when this happens.
601
+ 10. **Aggregation**: setting `aggregationModel` alone renders a sticky footer with the totals; with `rowGroupingModel` each group row also shows its own aggregates. `null`/`undefined`, blank strings, booleans and arrays are ignored by sum/avg/min/max; min/max of dates return a `Date`; `valueGetter` columns aggregate the computed value; footer totals go through the column's `valueFormatter` (not for count/unique).
602
+ 11. **GOTCHAS — bounded height**: virtualization only works if the grid's container has a bounded height. A flex child without `min-height: 0` grows to fit every row, so every row is rendered. Pagination hides this, and row grouping turns pagination off. Without a `height` prop the grid fills its container (3.0.1+), so use `<div style={{ flex: 1, minHeight: 0 }}>` around the grid, or pass a fixed pixel `height`. The list view never virtualizes: paginate it. Dev builds log a warning when this happens.
511
603
  12. **Pagination is ignored under row grouping** — a grouped grid is one scrollable, virtualized list.
512
604
  13. **`valueFormatter` + `renderCell`**: `renderCell` receives `params.formattedValue` (v2.1+), so a custom renderer can reuse the formatter instead of duplicating it.
513
- 14. **`slots.footer`** replaces the pagination area and receives `aggregationResult`, `rowCount`, `paginationModel`, `onPaginationModelChange` and `apiRef` (v2.1+).
605
+ 14. **`slots.footer`** replaces the pagination area and receives `apiRef`, `aggregationModel`, `aggregationResult`, `rowCount` (filtered data rows excluding pinned rows in flat grids — data rows under tree data / grouping; the server total with `paginationMode="server"`), `pagination`, `paginationModel`, `pageSizeOptions` and `onPaginationModelChange`.
606
+ 15. **v3: no underscore row fields** — `params.row._hasChildren` / `_treeDepth` / … are `undefined` since 3.0.0; use `params.rowMeta?.hasChildren` etc. Under grouping and tree data `params.row` is the consumer's own object; never mutate it in render callbacks. Migration guide: `docs/migration/v2-to-v3.md`.
607
+ 16. **Row limit**: the scroll height is `rows × rowHeight` px and browsers cap element height (Chromium 33,554,432 px), so one scrolling grid reaches ~645,000 rows at 52 px. Page or stream beyond that.
608
+ 17. **Server modes without dataSource**: with `paginationMode` / `sortingMode` / `filterMode` = `'server'` the grid shows `rows` as given (no slicing, sorting or filtering). Fetch from the change callbacks and control the models.
609
+ 18. **`valueFormatter` / `valueGetter` params are `unknown`-typed**: cast (`(value as number).toFixed(2)`) before calling methods, or TypeScript strict mode rejects it.
610
+ 19. **Inline `columns` are reused while shallow-equal**: a new array with the same column objects does not re-run filtering/sorting, but mutating a column object in place is not detected — pass new objects when a definition changes.