@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.
Files changed (221) hide show
  1. package/CHANGELOG.md +408 -3
  2. package/README.md +93 -72
  3. package/dist/index.d.ts +611 -121
  4. package/dist/opengridx.css +1 -1
  5. package/dist/opengridx.es.js +7992 -5650
  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 -161
  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 +45 -22
  29. package/docs/components/datagrid.md +14 -12
  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 +100 -36
  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 +147 -41
  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 +56 -19
  54. package/docs/features/pinning.md +23 -5
  55. package/docs/features/selection.md +50 -10
  56. package/docs/features/sorting-pagination.md +66 -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 +44 -9
  60. package/docs/features/virtualization.md +64 -15
  61. package/docs/migration/v1-to-v2.md +115 -0
  62. package/docs/migration/v2-to-v3.md +1222 -0
  63. package/docs/performance.md +124 -0
  64. package/docs/roadmap.md +16 -10
  65. package/lib/components/Cell/Cell.css +13 -1
  66. package/lib/components/Cell/Cell.tsx +130 -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 +11 -0
  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 +84 -12
  76. package/lib/components/DataGrid/DataGrid.tsx +606 -872
  77. package/lib/components/DataGrid/GridAggregationFooter.tsx +53 -17
  78. package/lib/components/DataGrid/GridEmptyState.tsx +7 -1
  79. package/lib/components/DataGrid/GridErrorOverlay.tsx +15 -12
  80. package/lib/components/DataGrid/GridListView.tsx +166 -23
  81. package/lib/components/DataGrid/GridLiveRegion.tsx +31 -0
  82. package/lib/components/DataGrid/GridLoadingOverlay.tsx +27 -0
  83. package/lib/components/DataGrid/GridPaginationArea.tsx +44 -0
  84. package/lib/components/DataGrid/GridPinnedRows.tsx +65 -11
  85. package/lib/components/DataGrid/GridStandaloneColumnPanel.tsx +40 -38
  86. package/lib/components/DataGrid/GridToolbarSlot.tsx +51 -0
  87. package/lib/components/DataGrid/GridVirtualRows.tsx +48 -25
  88. package/lib/components/DetailPanel/DetailPanel.css +0 -6
  89. package/lib/components/DetailPanel/DetailPanel.tsx +34 -5
  90. package/lib/components/DetailPanel/DetailPanelContent.tsx +17 -0
  91. package/lib/components/FilterPanel/FilterPanel.css +13 -2
  92. package/lib/components/FilterPanel/FilterPanel.tsx +272 -99
  93. package/lib/components/Header/ColumnMenu.tsx +91 -39
  94. package/lib/components/Header/Header.css +3 -3
  95. package/lib/components/Header/Header.tsx +179 -160
  96. package/lib/components/ListView/ListView.css +25 -0
  97. package/lib/components/ListView/ListViewRow.tsx +79 -10
  98. package/lib/components/Pagination/Pagination.tsx +15 -7
  99. package/lib/components/Row/Row.tsx +185 -75
  100. package/lib/components/Toolbar/GlobalSearch.css +6 -3
  101. package/lib/components/Toolbar/GlobalSearch.tsx +26 -27
  102. package/lib/components/Toolbar/GridToolbar.css +12 -11
  103. package/lib/components/Toolbar/GridToolbar.tsx +133 -89
  104. package/lib/components/Toolbar/PivotPanel.tsx +89 -49
  105. package/lib/components/Tooltip/Tooltip.css +20 -0
  106. package/lib/components/Tooltip/Tooltip.tsx +68 -35
  107. package/lib/components/ui/Button.tsx +3 -0
  108. package/lib/components/ui/Checkbox.tsx +2 -1
  109. package/lib/components/ui/ExpandIcon.css +0 -9
  110. package/lib/components/ui/ExpandIcon.tsx +4 -1
  111. package/lib/hooks/core/gridApiPlaceholder.ts +34 -0
  112. package/lib/hooks/core/gridToolbarHostContext.ts +14 -0
  113. package/lib/hooks/core/useDataGrid.ts +115 -173
  114. package/lib/hooks/core/useGridAggregationApi.ts +72 -0
  115. package/lib/hooks/core/useGridApiMethods.ts +115 -0
  116. package/lib/hooks/core/useGridApiRef.ts +15 -3
  117. package/lib/hooks/core/useGridApiRefBinding.ts +18 -0
  118. package/lib/hooks/core/useGridAriaRows.ts +38 -0
  119. package/lib/hooks/core/useGridClipboardApi.ts +38 -0
  120. package/lib/hooks/core/useGridColumnLookup.ts +30 -0
  121. package/lib/hooks/core/useGridColumns.tsx +284 -76
  122. package/lib/hooks/core/useGridColumnsPanel.ts +77 -0
  123. package/lib/hooks/core/useGridControlledState.ts +99 -6
  124. package/lib/hooks/core/useGridDetailPanel.ts +51 -0
  125. package/lib/hooks/core/useGridDevWarnings.ts +123 -0
  126. package/lib/hooks/core/useGridFocusHandlers.ts +50 -0
  127. package/lib/hooks/core/useGridGroupingColumn.ts +62 -0
  128. package/lib/hooks/core/useGridHeaderHandlers.ts +92 -0
  129. package/lib/hooks/core/useGridHierarchy.ts +81 -0
  130. package/lib/hooks/core/useGridKeyboardNavigation.ts +452 -250
  131. package/lib/hooks/core/useGridLiveRowSelection.ts +67 -0
  132. package/lib/hooks/core/useGridPageCorrection.ts +55 -0
  133. package/lib/hooks/core/useGridRowIdOf.ts +32 -0
  134. package/lib/hooks/core/useGridRowInteractions.ts +81 -0
  135. package/lib/hooks/core/useGridRowPipeline.ts +69 -39
  136. package/lib/hooks/core/useGridRowSelection.ts +91 -0
  137. package/lib/hooks/core/useGridScrollSync.ts +80 -32
  138. package/lib/hooks/core/useGridScrollToIndexesApi.ts +45 -0
  139. package/lib/hooks/core/useGridStateSnapshot.ts +40 -5
  140. package/lib/hooks/core/useGridThemeDimensions.ts +49 -0
  141. package/lib/hooks/core/useGridToolbarProps.ts +97 -0
  142. package/lib/hooks/core/useGridViewport.ts +38 -0
  143. package/lib/hooks/core/useGridViewportSize.ts +26 -0
  144. package/lib/hooks/core/useGridVisibleRows.ts +20 -7
  145. package/lib/hooks/core/useLayout.ts +215 -195
  146. package/lib/hooks/core/useRowsScrollEndLatch.ts +41 -0
  147. package/lib/hooks/core/useStableColumns.ts +34 -0
  148. package/lib/hooks/features/useAggregation.ts +86 -90
  149. package/lib/hooks/features/useColumnGroupReorderGuard.ts +34 -0
  150. package/lib/hooks/features/useDetailPanelHeights.ts +29 -0
  151. package/lib/hooks/features/useGridClipboard.ts +133 -73
  152. package/lib/hooks/features/useGridDataSource.ts +354 -128
  153. package/lib/hooks/features/useGridEditing.ts +211 -51
  154. package/lib/hooks/features/useGridPivot.ts +92 -0
  155. package/lib/hooks/features/useGridSpanRenderWindow.ts +104 -0
  156. package/lib/hooks/features/useGridSpanning.ts +410 -306
  157. package/lib/hooks/features/usePivot.ts +0 -0
  158. package/lib/hooks/features/useServerTreeChildren.ts +50 -0
  159. package/lib/hooks/useColumnReorder.ts +38 -44
  160. package/lib/hooks/useRowGrouping.ts +335 -214
  161. package/lib/hooks/useRowReorder.ts +62 -31
  162. package/lib/hooks/useTreeData.ts +330 -262
  163. package/lib/index.ts +30 -2
  164. package/lib/state/index.ts +1 -0
  165. package/lib/state/types.ts +7 -1
  166. package/lib/state/useGridStateStorage.ts +88 -29
  167. package/lib/theme/DataGridThemeProvider.tsx +143 -38
  168. package/lib/theme/gridThemeContext.ts +5 -0
  169. package/lib/theme/index.ts +5 -0
  170. package/lib/theme/presets.ts +5 -4
  171. package/lib/theme/types.ts +40 -2
  172. package/lib/types/index.ts +311 -108
  173. package/lib/utils/aggregation/index.ts +157 -0
  174. package/lib/utils/aria/index.ts +40 -0
  175. package/lib/utils/columnGroups.ts +110 -0
  176. package/lib/utils/columnLookup.ts +33 -0
  177. package/lib/utils/columnWidth.ts +22 -0
  178. package/lib/utils/dataSource/index.ts +46 -0
  179. package/lib/utils/detailPanel.ts +66 -0
  180. package/lib/utils/dragData.ts +44 -0
  181. package/lib/utils/editing/index.ts +30 -0
  182. package/lib/utils/export/exportShared.ts +197 -0
  183. package/lib/utils/export/exportToExcelAdvanced.ts +344 -121
  184. package/lib/utils/export/exportToPdf.ts +143 -92
  185. package/lib/utils/export/groupLabel.ts +14 -0
  186. package/lib/utils/export/index.ts +278 -272
  187. package/lib/utils/export/pdfText.ts +50 -0
  188. package/lib/utils/filtering/index.ts +277 -148
  189. package/lib/utils/focus/index.ts +115 -0
  190. package/lib/utils/gridApiRows.ts +35 -0
  191. package/lib/utils/gridModes.ts +77 -0
  192. package/lib/utils/grouping/groupedExportRows.ts +91 -0
  193. package/lib/utils/pagination/index.ts +56 -0
  194. package/lib/utils/pinning/index.ts +77 -33
  195. package/lib/utils/pivot/index.ts +368 -0
  196. package/lib/utils/scroll/index.ts +78 -0
  197. package/lib/utils/scroll/scrollEnd.ts +18 -0
  198. package/lib/utils/sorting/index.ts +154 -30
  199. package/lib/utils/syntheticRows.ts +12 -0
  200. package/lib/utils/values.ts +206 -0
  201. package/lib/utils/viewport.ts +14 -0
  202. package/llms.txt +272 -166
  203. package/package.json +18 -6
  204. package/lib/components/ColumnGroupHeader/ColumnGroupHeader.tsx +0 -220
  205. package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.test.tsx +0 -85
  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/Header/Header.test.tsx +0 -135
  210. package/lib/components/Pagination/Pagination.test.tsx +0 -64
  211. package/lib/hooks/core/useDataGrid.regression.test.ts +0 -66
  212. package/lib/hooks/core/useGridControlledState.test.ts +0 -86
  213. package/lib/hooks/core/useGridKeyboardNavigation.test.ts +0 -176
  214. package/lib/hooks/core/useGridRowPipeline.test.ts +0 -117
  215. package/lib/hooks/features/useAggregation.test.ts +0 -175
  216. package/lib/hooks/features/usePivot.test.ts +0 -202
  217. package/lib/hooks/useRowGrouping.test.ts +0 -137
  218. package/lib/utils/export/exportToPdf.test.ts +0 -120
  219. package/lib/utils/export/groupedExport.test.ts +0 -144
  220. package/lib/utils/filtering/filtering.test.ts +0 -178
  221. package/lib/utils/sorting/sorting.test.ts +0 -88
@@ -0,0 +1,83 @@
1
+ # Keyboard Navigation & Accessibility
2
+
3
+ The grid follows the [WAI-ARIA data grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/): it is a single Tab stop, and the arrow keys move a focused cell inside it. This page lists the keys, explains how focus behaves, and documents the ARIA structure the grid exposes (v3.0).
4
+
5
+ ## Focus model
6
+
7
+ - **One Tab stop.** The grid viewport (`role="grid"`) is in the page Tab order. Tabbing in puts focus on the last focused cell, or on the first cell (the select-all header checkbox with `checkboxSelection`, the first column header when there are no rows). While focus is inside the grid the viewport leaves the Tab order, so **Tab** and **Shift+Tab** leave the grid in one press.
8
+ - **Tab is not captured** outside edit mode. It moves to the next focusable element in the page, which may be a control you render in a cell (`renderCell`) or inside an expanded detail panel.
9
+ - **Focus is remembered.** Leaving the grid (Tab, clicking elsewhere, switching window) keeps the focused cell; coming back restores it. The focus ring is only drawn while the grid has focus and the keyboard is in use.
10
+ - **Focus survives virtualization.** When the focused row scrolls out of the render window, focus stays on the grid so keys keep working; the cell gets DOM focus back when it is rendered again. The grid never takes focus back from an element outside it.
11
+ - **Rows and columns that disappear.** If the focused row is removed or filtered out, focus moves to the row now at the same position; if the focused column is hidden, to the first visible data column; with no rows left, to the header.
12
+ - **Your own controls keep their keys.** Keys pressed in text inputs, textareas, selects and editable content rendered in cells, and anything inside a detail panel, are left to that element. On a button, link or checkbox rendered in a cell, **Enter** and **Space** activate it and the arrow keys still move grid focus.
13
+
14
+ ## Keys
15
+
16
+ ### Cells
17
+
18
+ | Key | Action |
19
+ | :--- | :--- |
20
+ | **Arrow keys** | Move one cell. Left/Right wrap to the previous/next row. Up from the first row moves to the header. Merged (spanned) cells count as one cell. |
21
+ | **Home** / **End** | First / last column of the row. |
22
+ | **Ctrl+Home** / **Ctrl+End** (Cmd on macOS) | First column of the header row / last column of the last row. |
23
+ | **PageUp** / **PageDown** | Up / down one page (`pageSize` with pagination, otherwise 10 rows). |
24
+ | **Enter** | Editable cell: open the editor. Other cells of a row with children (group row or tree-data parent): expand or collapse it. Other cells: same as clicking the row — fires `onRowClick` and toggles selection unless `disableRowSelectionOnClick`. |
25
+ | **Alt+ArrowRight** / **Alt+ArrowLeft** | Tree data / row grouping: expand / collapse the focused row. |
26
+ | **Shift+Space** | Select or deselect the focused row (when rows can be selected: `checkboxSelection`, or click selection not disabled). Synthetic group rows are not selectable. |
27
+ | **Space** | On the row checkbox: toggle the row. On the detail-panel toggle: expand or collapse the panel. Elsewhere it does nothing (it never scrolls the grid). |
28
+ | **Ctrl+A** (Cmd+A) | Select every row, when several rows can be selected. |
29
+ | **Ctrl+C** (Cmd+C) | Copy the selected rows (see [Clipboard](clipboard.md)). |
30
+
31
+ Arrow, Home/End, PageUp/PageDown and Space are always consumed, so the viewport does not scroll natively when focus cannot move further.
32
+
33
+ ### While editing
34
+
35
+ | Key | Action |
36
+ | :--- | :--- |
37
+ | **Enter** | Commit. |
38
+ | **Escape** | Cancel. |
39
+ | **Tab** / **Shift+Tab** | Commit and open the next / previous editable cell (checkbox, expand and reorder columns are skipped). With no editable cell left, focus leaves the grid and the edit commits. |
40
+
41
+ When an edit ends, focus returns to the edited cell unless you moved it elsewhere.
42
+
43
+ ### Column headers
44
+
45
+ | Key | Action |
46
+ | :--- | :--- |
47
+ | **Enter** / **Space** | Sort the column (asc → desc → none), replacing the sort model; with `multiSort`, or with **Shift** held, the column is added to (or updated or removed from) the sort model instead, as a header click or shift-click does. On the select-all header: select or clear all rows. |
48
+ | **Alt+ArrowDown** or **Ctrl+Enter** (Cmd+Enter) | Open the column menu. |
49
+ | **Alt+ArrowRight** / **Alt+ArrowLeft** | Widen / narrow the column by 10px (with **Shift**: 50px). Not for `resizable: false` columns. |
50
+ | **ArrowDown** | Move to the first row. |
51
+
52
+ In the column menu, **ArrowUp** / **ArrowDown** / **Home** / **End** move between items, **Enter** / **Space** choose one, and **Escape** or **Tab** closes the menu. Focus returns to the column header.
53
+
54
+ ### List view
55
+
56
+ In `listView`, focus moves between whole rows instead of cells:
57
+
58
+ | Key | Action |
59
+ | :--- | :--- |
60
+ | **Tab** | The list is one Tab stop: it lands on the last focused row (the first row at first). Row checkboxes are not separate Tab stops. |
61
+ | **ArrowDown** / **ArrowUp** | Next / previous row. |
62
+ | **Home** / **End** | First / last row. |
63
+ | **PageDown** / **PageUp** | Ten rows down / up. |
64
+ | **Enter** / **Space** | A row with children (tree-data parent, group row): expand or collapse it. Any other row: same as clicking it (`onRowClick`, and click selection unless `disableRowSelectionOnClick`). |
65
+ | **Alt+ArrowRight** / **Alt+ArrowLeft** | Expand / collapse the focused parent row. |
66
+ | **Shift+Space** | Select or deselect the focused row, when rows can be selected. |
67
+
68
+ Keys pressed in content your `renderCell` puts in a row (inputs, buttons) are left to that content.
69
+
70
+ ## ARIA structure
71
+
72
+ | Element | Attributes |
73
+ | :--- | :--- |
74
+ | Viewport | `role="grid"`, `aria-rowcount`, `aria-colcount`, `aria-multiselectable` (true when several rows can be selected), `aria-busy`. |
75
+ | Header rows | `role="row"` with `aria-rowindex`: column-group rows first, then the column header row. |
76
+ | Data rows | `role="row"`, `aria-rowindex` counted across the whole dataset (page 2 continues after page 1; bottom-pinned rows come after every page), `aria-selected`. Tree-data and row-grouping rows add `aria-level`; rows with children add `aria-expanded`. |
77
+ | Cells and headers | `aria-colindex`, identical for a header and the body cells below it. The system columns (reorder handle, detail-panel toggle, checkbox) come first. |
78
+ | Sorted headers | Only the primary sort column has `aria-sort="ascending"` / `"descending"`; with several sort keys every sorted header also gets an `aria-description` such as "Sorted descending, sort priority 2 of 2". |
79
+ | Detail panel | A `role="row"` with one `role="gridcell"` spanning every column (`aria-colspan`). The expand cell has `aria-expanded` and, while open, `aria-controls` pointing at the panel. |
80
+
81
+ `aria-rowcount` counts the header rows plus top-pinned, centre and bottom-pinned rows; under server-side pagination it uses `rowCount`. `aria-colcount` counts the rendered columns: visible data columns (including the `groupingColDef` column and generated pivot columns) plus the system columns.
82
+
83
+ `GridCellParams.colIndex` / `GridRenderCellParams.colIndex` (and `renderHeader`'s `colIndex`) are the zero-based position of the column among the **visible data columns in render order** (left-pinned, unpinned, right-pinned). They do not change with horizontal scrolling. `aria-colindex` is `colIndex + 1 +` the number of system columns.
@@ -6,9 +6,16 @@ Switch from a multi-column grid to a single-column detailed list. Perfect for mo
6
6
 
7
7
  ## 🏗️ Usage
8
8
 
9
- Enable List View by passing the `listView` prop. You must also provide a `listViewColumn` object which defines how each "Card" should be rendered.
9
+ Enable List View by passing the `listView` prop. You must also provide a `listViewColumn` object which defines how each "Card" should be rendered. Without `listViewColumn` the grid view is shown instead and a dev warning is logged.
10
+
11
+ List view honours `loading`, `slots.loadingOverlay`, `slots.noRowsOverlay` and `slots.footer` like the grid view: while loading it shows a progress bar (or your loading overlay) rather than the empty state, and `slots.footer` replaces its pagination controls.
10
12
 
11
13
  ```tsx
14
+ import { DataGrid, type GridListViewColDef } from '@opencorestack/opengridx';
15
+
16
+ // Your own row type: an interface or a type alias both work (v3+).
17
+ interface Employee { id: number; name: string; role: string; status: string; avatar: string }
18
+
12
19
  const listColDef: GridListViewColDef<Employee> = {
13
20
  field: 'card',
14
21
  renderCell: (params) => <MyEmployeeCard row={params.row} />
@@ -32,10 +39,24 @@ When `listView` is active:
32
39
  - **Card Containers**: Each row maintains its standard selection and hover behaviors, but visually acts as a card.
33
40
 
34
41
  ### Custom Card Rendering
35
- The `renderCell` function in `listViewColumn` receives all standard `GridRenderCellParams`, allowing you to build rich, complex UIs for each item.
42
+ The `renderCell` function in `listViewColumn` receives all standard `GridRenderCellParams`, allowing you to build rich, complex UIs for each item:
43
+
44
+ | Param | Value |
45
+ | :--- | :--- |
46
+ | `row` | The consumer's row object. |
47
+ | `field` | `listViewColumn.field`. |
48
+ | `value` | The cell value for that field: the grid column with the same `field` reads it through its `valueGetter`; otherwise `row[field]` (so a synthetic field such as `'card'` gives `undefined`). |
49
+ | `formattedValue` | That column's `valueFormatter` result, or `String(value)` (`''` for null/undefined). |
50
+ | `colDef` | The grid column with the same `field`, or `listViewColumn` itself. |
51
+ | `rowMeta` | Tree-data / row-grouping metadata (`treeDepth`, `hasChildren`, …), `undefined` for flat rows. |
52
+ | `rowIndex` | Index of the row among the rendered rows (pinned rows first). |
53
+
54
+ `renderCell` only ever receives real rows: while an infinite-scroll page loads, no placeholder rows are passed to it.
55
+
56
+ Under row grouping (and for auto-created tree parents) `renderCell` is also called for group rows (`rowMeta.isGroupRow`). Returning `undefined` for one shows its `rowMeta.groupLabel`. Group rows have no selection checkbox, and the `N items` summary counts data rows only, as in the grid view (v3.0+). A `renderCell` that throws shows an error in that row only, and a throwing `valueGetter` / `valueFormatter` of the grid column reads as `undefined` / the unformatted value (v3.0+).
36
57
 
37
58
  ```tsx
38
- function MyEmployeeCard({ row }) {
59
+ function MyEmployeeCard({ row }: { row: Employee }) {
39
60
  return (
40
61
  <div className="employee-card">
41
62
  <Avatar src={row.avatar} />
@@ -51,15 +72,47 @@ function MyEmployeeCard({ row }) {
51
72
 
52
73
  ---
53
74
 
75
+ ## 📄 Pagination and infinite scroll
76
+
77
+ List view pages exactly like the grid view: with `pagination`, one page of the (unpinned) rows is shown between any pinned rows, also under tree data. The summary line above the list reads `N items · page X of Y`, where under server pagination (`paginationMode="server"` with a `dataSource`) `N` and the page count come from the server's `rowCount`.
78
+
79
+ `onRowsScrollEnd` fires when the list is scrolled to within 100px of its bottom, so the [infinite scroll](./infinite-scroll.md) pattern works in list view too. As in the grid view, it fires once per arrival: it fires again only after the list leaves that zone or the row count changes (for example, when the next page has loaded).
80
+
81
+ The list view is **not virtualized**: it renders every row it shows (one page with `pagination`, otherwise all rows). Use pagination for large datasets. `slots.footer` is rendered below the list (in place of its pagination controls); the aggregation footer row is not shown in list view.
82
+
83
+ ## ♿ Accessibility
84
+
85
+ The list is a `role="grid"` whose rows carry an `aria-rowindex` over the whole data set (row 11 on the second page of 10) and whose `aria-rowcount` is the total number of rows. There is no header row in list view. Tree-data and grouped rows also carry `aria-level` and, for parents, `aria-expanded`; they are indented by depth, and parents show a chevron that expands or collapses them on click (the chevron is not a Tab stop).
86
+
87
+ ### Keyboard (v3.0)
88
+
89
+ Rows are focusable with a roving tab stop:
90
+
91
+ | Key | Action |
92
+ | :--- | :--- |
93
+ | **Tab** | The list is one Tab stop: it lands on the last focused row (the first row at first). Row checkboxes are not separate Tab stops. |
94
+ | **ArrowDown** / **ArrowUp** | Next / previous row. |
95
+ | **Home** / **End** | First / last row. |
96
+ | **PageDown** / **PageUp** | Ten rows down / up. |
97
+ | **Enter** / **Space** | A row with children (tree-data parent, group row): expand or collapse it. Any other row: same as clicking it (`onRowClick`, and click selection unless `disableRowSelectionOnClick`). |
98
+ | **Alt+ArrowRight** / **Alt+ArrowLeft** | Expand / collapse the focused parent row. |
99
+ | **Shift+Space** | Select or deselect the focused row, when rows can be selected. |
100
+
101
+ Keys pressed in content your `renderCell` puts in a row (inputs, buttons) are left to that content.
102
+
103
+ ---
104
+
54
105
  ## 📱 Responsive Toggle
55
106
 
56
107
  A common pattern is to toggle List View based on screen width:
57
108
 
58
109
  ```tsx
59
- const isMobile = window.innerWidth < 768;
110
+ const isMobile = window.innerWidth < 768; // read once; use a resize listener or matchMedia to follow changes
60
111
 
61
112
  <DataGrid
113
+ rows={rows}
114
+ columns={columns}
62
115
  listView={isMobile}
63
- // ...
116
+ listViewColumn={listColDef}
64
117
  />
65
118
  ```
@@ -6,17 +6,22 @@ OpenGridX provides multiple ways to handle data loading, from initial shimmers t
6
6
 
7
7
  ## 🦴 Skeleton Loading
8
8
 
9
- The grid features a smart skeleton loader that automatically calculates the number of columns and rows needed to fill the viewport.
9
+ While `loading` is true and there are no rows to show, the grid body shows ten animated skeleton rows laid out with the current columns.
10
10
 
11
11
  ### Usage
12
12
  ```tsx
13
- <DataGrid loading={true} />
13
+ <DataGrid rows={[]} columns={columns} loading />
14
14
  ```
15
15
 
16
16
  ### Features
17
- - **Dynamic Calculation**: Skeletons match the current column widths and configuration.
17
+ - **Column-aware**: Skeleton cells use the current column widths, plus the checkbox, detail-panel and drag-handle columns when those are enabled. With no columns yet, placeholder columns fill the width.
18
18
  - **Shimmer Animation**: Uses a CSS-optimized animation for a premium feel.
19
- - **Auto-Fill**: Fills the entire visible height if `rows` are empty.
19
+ - **Custom overlay**: Pass `slots.loadingOverlay` to render your own component instead of the skeleton rows.
20
+
21
+ ### Loading with rows already shown
22
+ When `loading` is true (or a data source request is running) and the grid already has rows, for example during a reload or a server-side sort, the rows stay visible and usable and a thin progress bar (`role="progressbar"`, class `ogx__loading-bar`) runs along the top of the grid. Pass `slots.loadingOverlay` to show your own indicator centred over the rows instead. Infinite scroll keeps its own indicator (skeleton rows at the bottom).
23
+
24
+ List view follows the same rules: while loading it shows a progress bar or `slots.loadingOverlay`, never the "No rows" state.
20
25
 
21
26
  ---
22
27
 
@@ -24,45 +29,38 @@ The grid features a smart skeleton loader that automatically calculates the numb
24
29
 
25
30
  For large datasets, use the `paginationMode="infinite"` to load data as the user scrolls.
26
31
 
27
- - **Trigger**: Fired when the user scrolls near the bottom of the grid.
32
+ - **Trigger**: `onRowsScrollEnd` fires when the user scrolls near the bottom; advance `paginationModel.page` there and the `dataSource` loads the next rows.
28
33
  - **Feedback**: Displays skeleton rows at the bottom while new data is being fetched.
29
34
  - **Guide**: See [Infinite Scroll Guide](./infinite-scroll.md) for full implementation details.
30
35
 
31
36
  ---
32
37
 
33
- ## 💤 Lazy Loading (Viewport)
34
-
35
- Unlike infinite scroll which appends data to the end, Lazy Loading fetches specific chunks based on the user's current scroll position (viewport).
36
-
37
- - **Benefit**: Best for datasets with millions of records where users might "jump" to the middle of the scrollbar.
38
- - **Implementation**: Managed via the `GridDataSource` and `rowCount` props.
39
-
40
- ---
41
-
42
38
  ## 🔍 Global Search (Quick Filter)
43
39
 
44
- Quickly filter the entire dataset across all visible columns. This is managed via the `GridToolbar`.
40
+ Quickly filter the rows across all visible, filterable columns. The search box is part of `GridToolbar`.
45
41
 
46
42
  ### Basic Setup
47
43
 
48
- Add the toolbar and wire a filter model — the toolbar's search bar activates automatically when `onFilterModelChange` is wired:
44
+ Add the toolbar. The search box filters the grid on its own; pass `filterModel` / `onFilterModelChange` only when you want to control or observe the filter:
49
45
 
50
46
  ```tsx
51
47
  <DataGrid
48
+ rows={rows}
49
+ columns={columns}
52
50
  slots={{ toolbar: GridToolbar }}
53
- filterModel={filterModel}
54
- onFilterModelChange={setFilterModel}
55
51
  />
56
52
  ```
57
53
 
58
54
  ### Controlled Search
59
- You can also control the search value externally via the `filterModel`.
55
+ You can also control the search value externally via the `filterModel`. Each entry is one term, and a row matches when every term is found in some visible column (the toolbar box splits typed text on whitespace the same way).
60
56
 
61
57
  ```tsx
62
58
  <DataGrid
59
+ rows={rows}
60
+ columns={columns}
63
61
  filterModel={{
64
62
  items: [],
65
- quickFilterValues: ['search term']
63
+ quickFilterValues: ['john', 'london']
66
64
  }}
67
65
  />
68
66
  ```
@@ -6,7 +6,7 @@ Display supplementary information for a row in an expandable panel without leavi
6
6
 
7
7
  ## 🛠️ Basic Implementation
8
8
 
9
- To enable detail panels, provided two main props: `getDetailPanelContent` and `getDetailPanelHeight`.
9
+ To enable detail panels, provide two main props: `getDetailPanelContent` and `getDetailPanelHeight`.
10
10
 
11
11
  ```tsx
12
12
  <DataGrid
@@ -14,14 +14,16 @@ To enable detail panels, provided two main props: `getDetailPanelContent` and `g
14
14
  columns={columns}
15
15
  getDetailPanelContent={(params) => (
16
16
  <div style={{ padding: '20px' }}>
17
- <h3>Details for {params.row.name}</h3>
18
- <p>{params.row.description}</p>
17
+ <h3>Details for {String(params.row.name)}</h3>
18
+ <p>{String(params.row.description)}</p>
19
19
  </div>
20
20
  )}
21
21
  getDetailPanelHeight={() => 'auto'} // or a fixed number
22
22
  />
23
23
  ```
24
24
 
25
+ `getDetailPanelContent` and `getDetailPanelHeight` are called only for **expanded** rows, while they render (v3.0+). A collapsed row costs nothing, and the callbacks may read fields that only data rows have. Synthetic hierarchy rows (row-grouping group rows and generated tree-data parents) have no detail panel: they keep an empty cell in the expand column, are never passed to the callbacks, and their ids in `detailPanelExpandedRowIds` are ignored. Tree-data parents that are real rows do get a panel. If `getDetailPanelContent` throws, that panel shows an error marker and the rest of the grid keeps working.
26
+
25
27
  ---
26
28
 
27
29
  ## 🕹️ Controlled Expansion
@@ -29,7 +31,9 @@ To enable detail panels, provided two main props: `getDetailPanelContent` and `g
29
31
  You can manage which rows are expanded by using the `detailPanelExpandedRowIds` and `onDetailPanelExpandedRowIdsChange` props.
30
32
 
31
33
  ```tsx
32
- const [expandedIds, setExpandedIds] = useState(new Set());
34
+ import type { GridRowId } from '@opencorestack/opengridx';
35
+
36
+ const [expandedIds, setExpandedIds] = useState<Set<GridRowId>>(new Set());
33
37
 
34
38
  <DataGrid
35
39
  detailPanelExpandedRowIds={expandedIds}
@@ -43,4 +47,13 @@ const [expandedIds, setExpandedIds] = useState(new Set());
43
47
 
44
48
  - **Expansion Column**: A `+` icon is automatically added to the start of the row.
45
49
  - **Pinning**: The expansion column can be pinned using `pinExpandColumn={true}`. This ensures the expansion trigger is always visible even when scrolling horizontally.
46
- - **Virtualization**: The grid engine correctly accounts for variable row heights when detail panels are open.
50
+ - **Height**: `getDetailPanelHeight` returns a pixel height (a fixed-height scroll box; `0` renders a 0px panel) or `'auto'`. Without the prop every panel is 200px. An `'auto'` panel is measured when it renders and whenever its content resizes, and the rows below are laid out at the measured height. Until a panel has rendered once, 200px is reserved for it.
51
+ - **Virtualization**: The grid engine accounts for variable row heights when detail panels are open, including panels on pinned rows (keyboard navigation and `scrollToIndexes` keep rows clear of an expanded bottom-pinned panel).
52
+
53
+ ## ⌨️ Keyboard & Accessibility
54
+
55
+ - **Space** or **Enter** on the expand cell toggles the panel (v3.0: Enter too).
56
+ - Keys typed into content you render in the panel (inputs, selects, buttons) are left to that content: the grid does not react to them, so typing a space no longer collapses the panel and arrow keys move the caret.
57
+ - **Tab** is not captured by the grid, so it reaches focusable content inside an open panel.
58
+ - Keyboard scrolling brings the row itself into view, not the row plus its panel, so moving onto a row with a panel taller than the viewport keeps the row visible.
59
+ - The panel is exposed as a `role="row"` containing one `role="gridcell"` that spans every column; the expand cell has `aria-expanded` and, while open, `aria-controls` pointing at the panel. The `+`/`−` button inside the cell is not a separate Tab stop.
@@ -13,15 +13,18 @@ npm install jspdf jspdf-autotable
13
13
  ## Basic Usage
14
14
 
15
15
  ```tsx
16
- import { exportToPdf, useGridApiRef } from '@opencorestack/opengridx';
16
+ import {
17
+ DataGrid, GridToolbar, exportToPdf, useGridApiRef,
18
+ type GridColDef, type GridRowModel,
19
+ } from '@opencorestack/opengridx';
17
20
 
18
- function MyGrid() {
21
+ function MyGrid({ rows, columns }: { rows: GridRowModel[]; columns: GridColDef[] }) {
19
22
  const apiRef = useGridApiRef();
20
23
 
21
24
  const handleExport = async () => {
22
25
  await exportToPdf(
23
- apiRef.current.getVisibleRows(),
24
- apiRef.current.getVisibleColumns(),
26
+ apiRef.current.getAllFilteredRows(), // every row passing the filter, all pages
27
+ apiRef.current.getVisibleColumns(), // on-screen columns, in display order
25
28
  { fileName: 'my-report', title: 'Sales Report' }
26
29
  );
27
30
  };
@@ -31,14 +34,13 @@ function MyGrid() {
31
34
  apiRef={apiRef}
32
35
  rows={rows}
33
36
  columns={columns}
34
- slots={{
35
- toolbar: () => (
36
- <GridToolbar
37
- renderExportButton={() => (
38
- <button onClick={handleExport}>Export PDF</button>
39
- )}
40
- />
41
- ),
37
+ slots={{ toolbar: GridToolbar }}
38
+ slotProps={{
39
+ toolbar: {
40
+ renderExportButton: () => (
41
+ <button onClick={handleExport}>Export PDF</button>
42
+ ),
43
+ },
42
44
  }}
43
45
  />
44
46
  );
@@ -49,7 +51,7 @@ function MyGrid() {
49
51
 
50
52
  ```tsx
51
53
  await exportToPdf(
52
- apiRef.current.getVisibleRows(),
54
+ apiRef.current.getAllFilteredRows(),
53
55
  apiRef.current.getVisibleColumns(),
54
56
  {
55
57
  fileName: 'employee-report',
@@ -76,14 +78,49 @@ await exportToPdf(
76
78
  | `title` | `string` | — | Adds a branded header block above the table |
77
79
  | `logoUrl` | `string` | — | Data URI or URL for a logo image (requires `title`) |
78
80
  | `orientation` | `'portrait' \| 'landscape'` | `'landscape'` | Page orientation |
79
- | `selectedRows` | `(string \| number)[]` | — | Export only rows with these IDs |
81
+ | `selectedRows` | `(string \| number)[]` | — | Export only rows with these IDs. Takes precedence over `groupedRows`; the footer totals are recomputed over the selected rows (v3.0+) |
82
+ | `getRowId` | `(row) => GridRowId` | `row.id` | The grid's `getRowId`, when it has one, so `selectedRows` match rows that keep their id in another field (v3.0+) |
80
83
  | `aggregationResult` | `Record<string, unknown>` | — | From `apiRef.current.getAggregationResult()` |
81
84
  | `aggregationModel` | `GridAggregationModel` | — | From `apiRef.current.getAggregationModel()` |
82
- | `filterModel` | `GridFilterModel` | — | From `apiRef.current.getFilterModel()` |
85
+ | `filterModel` | `GridFilterModel` | — | From `apiRef.current.getFilterModel()`. Summarised under the title (see below) |
86
+ | `groupedRows` | `GridGroupedExportRow[]` | — | From `apiRef.current.getGroupedExportRows()`: group headers, subtotals and a grand-total footer |
83
87
  | `alternateRowColor` | `boolean` | `true` | Alternating row background shading |
84
- | `headerBackgroundColor` | `string` | `'#4f46e5'` | Column header cell background (hex) |
85
- | `headerTextColor` | `string` | `'#ffffff'` | Column header cell text color (hex) |
88
+ | `headerBackgroundColor` | `string` | `'#4f46e5'` | Column header cell background (`#rrggbb` or `#rgb`). Invalid values fall back to the default |
89
+ | `headerTextColor` | `string` | `'#ffffff'` | Column header cell text color (`#rrggbb` or `#rgb`). Invalid values fall back to the default |
86
90
  | `fontSize` | `number` | `9` | Body cell font size in points |
91
+ | `font` | `{ name: string; data: string; boldData?: string }` | — | A TrueType font (base64 `.ttf`) for text outside Latin-1 (v3.0+). See [Non-Latin text](#non-latin-text-and-unicode-fonts) |
92
+
93
+ ## Totals and filter summary
94
+
95
+ - The footer shows `TOTAL` in the first column; when the first column has its own aggregate, the value is kept next to it (`TOTAL: 30`). Grouped `Subtotal` and `Grand Total` rows do the same.
96
+ - `count` and `unique` are plain counts, not run through the column's `valueFormatter`. `sum`, `avg`, `min` and `max` use the formatter; if it throws (e.g. it reads a data-row field), the value is formatted like the grid footer instead of aborting the export. See [Aggregate values](export-guide.md#aggregate-values).
97
+ - The filter line under the title lists the applied filter as the grid evaluates it: `A equals "1" OR (B contains "x" AND C isEmpty) • Search: "acme"` — nested groups in parentheses, the model's logic operator, and the quick-search terms. Rules without a value (an unfinished row in the filter panel) are left out. Long titles and filter lines wrap within the page margins.
98
+
99
+ ## Non-latin text and Unicode fonts
100
+
101
+ jsPDF's built-in Helvetica font only covers Latin-1 and the WinAnsi punctuation (`é`, `ü`, `€`, `“ ”`, `•`). A string with any other character used to come out entirely garbled; since v3.0 only the undrawable characters are replaced with `?` (special spaces and the minus sign become their ASCII forms, so `fr-FR` totals such as `1 234 567` print correctly) and a console warning is logged.
102
+
103
+ To print `₹`, Greek, Cyrillic, CJK or Devanagari text, pass a Unicode TrueType font such as [Noto Sans](https://fonts.google.com/noto) as base64:
104
+
105
+ ```tsx
106
+ const toBase64 = async (url: string) => {
107
+ const bytes = new Uint8Array(await (await fetch(url)).arrayBuffer());
108
+ let binary = '';
109
+ bytes.forEach(b => { binary += String.fromCharCode(b); });
110
+ return btoa(binary);
111
+ };
112
+
113
+ await exportToPdf(rows, columns, {
114
+ title: 'बिक्री रिपोर्ट',
115
+ font: {
116
+ name: 'NotoSans',
117
+ data: await toBase64('/fonts/NotoSans-Regular.ttf'),
118
+ boldData: await toBase64('/fonts/NotoSans-Bold.ttf'), // optional; the regular face is used for bold otherwise
119
+ },
120
+ });
121
+ ```
122
+
123
+ jsPDF does not shape complex scripts, so Devanagari or Arabic conjuncts may not join.
87
124
 
88
125
  ## Column Exclusion
89
126
 
@@ -93,7 +130,7 @@ Columns with `exportable: false` are excluded automatically:
93
130
  { field: 'internalNotes', headerName: 'Notes', exportable: false }
94
131
  ```
95
132
 
96
- System columns (`__check__`, `__actions__`) are also always excluded.
133
+ System columns (`__check__`, `__actions__`) and spacer columns (`isSpacer: true`) are also always excluded.
97
134
 
98
135
  ## Troubleshooting
99
136
 
@@ -107,4 +144,4 @@ Ensure `logoUrl` is a data URI (base64-encoded) or an absolute URL that is CORS-
107
144
 
108
145
  ## Exporting unfiltered data
109
146
 
110
- `getVisibleRows()` returns only rows that pass the current filter. Use `getAllRows()` if you want to export all data regardless of active filters.
147
+ `getVisibleRows()` returns the rows on screen: those that pass the current filter, only the current page when pagination is on, plus pinned rows. `getAllFilteredRows()` returns every filtered row regardless of pagination (and, under grouping or tree data, of expansion), in the grid's sort order with top- and bottom-pinned rows first and last; it is the right source for a full export. Use `getAllRows()` if you want to export all data regardless of active filters. `getVisibleColumns()` returns the columns on screen (hidden columns omitted), in display order. `getFilterModel()` returns the live filter model, including filters applied from the toolbar or filter panel.
@@ -18,12 +18,20 @@ Columns can be pinned to the left or right edges of the grid. Pinned columns rem
18
18
  />
19
19
  ```
20
20
 
21
+ ### Order and widths
22
+ - Pinned columns render in the order of `pinnedColumns.left` / `pinnedColumns.right`, not in column order. Pinning from the column menu appends the column to the end of its side, so it lands next to the scrolling columns.
23
+ - A pinned column is sized exactly as it would be unpinned: numbers, percentages, `flex` and `'auto'` all apply, clamped to `minWidth` / `maxWidth`. The sticky offsets use those widths, so pinned columns never overlap.
24
+
21
25
  ### Column Configuration
22
- Individual columns can be marked as non-pinnable:
26
+ Individual columns can be marked as non-pinnable. Their column menu has no pin actions:
23
27
  ```typescript
24
28
  { field: 'id', pinnable: false }
25
29
  ```
26
30
 
31
+ `pinnable: false` only affects the UI; a column listed in `pinnedColumns` is still pinned. It does not block drag-reordering (v3.0+).
32
+
33
+ Pinned column headers are not draggable and are not drop targets for header drag-reorder: to move a pinned column, change the order in `pinnedColumns`.
34
+
27
35
  ---
28
36
 
29
37
  ## ⬆️ Row Pinning
@@ -40,8 +48,13 @@ Rows can be pinned to the top or bottom of the grid. Pinned rows remain visible
40
48
  />
41
49
  ```
42
50
 
51
+ ### Behaviour
52
+ - Pinned rows are split out after filtering: a pinned row that does not match the active filter or quick filter is hidden. They are never sorted; they keep the order of `pinnedRows.top` / `.bottom`.
53
+ - `pinnedRows` is **ignored while `treeData` or `rowGroupingModel` is active**: the rows stay in their place in the hierarchy, and a development warning is logged.
54
+ - Top-pinned rows stick directly below the whole header, including column group header rows. Bottom-pinned rows stick directly above the aggregation footer; both stay visible.
55
+
43
56
  ### Use Cases
44
- - **Header Summary**: Keep a "Totals" or "Averages" row at the top.
57
+ - **Header Summary**: Keep a summary row at the top (for grid-wide totals that follow the filter, prefer `aggregationModel`, whose footer never scrolls away).
45
58
  - **Comparison**: Pin a specific row to compare it against other data.
46
59
  - **Actions footer**: Pin a specialized row for bulk actions at the bottom.
47
60
 
@@ -59,8 +72,13 @@ Rows can be pinned to the top or bottom of the grid. Pinned rows remain visible
59
72
  ---
60
73
 
61
74
  ## 🎨 Styling Pinning
62
- Pinned elements have specific CSS shadow classes applied to indicate depth:
63
- - `.ogx-column--pinned-left--last`
64
- - `.ogx-column--pinned-right--first`
75
+ The last left-pinned cell and the first right-pinned cell of each row carry section-edge classes (the body cells draw an edge shadow):
76
+ - body cells: `.ogx__cell--pinned-left-last`, `.ogx__cell--pinned-right-first`
77
+ - header cells: `.ogx__header-cell--pinned-left-last`, `.ogx__header-cell--pinned-right-first`
78
+ - aggregation footer: `.ogx__aggregation-cell--pinned-left-last`, `.ogx__aggregation-cell--pinned-right-first`
79
+
80
+ The edge is the last / first pinned column that is actually shown, so hiding a pinned column moves it. All pinned cells also carry `.ogx__cell--pinned-left` / `--pinned-right` (`.ogx__header-cell--pinned-left` / `--pinned-right`).
81
+
82
+ Sticky row areas are wrapped (v3.0+): the header and the top-pinned rows sit in `div.ogx__sticky-top`, and the bottom-pinned rows and the aggregation footer sit in `div.ogx__sticky-bottom`. The `.ogx__pinned-rows--top` / `.ogx__pinned-rows--bottom` containers inside them are no longer sticky themselves, so custom sticky CSS belongs on the wrappers.
65
83
 
66
84
  These can be customized via your theme or global CSS.
@@ -19,6 +19,8 @@ OpenGridX provides various ways for users to select one or multiple rows.
19
19
  ### Enable Checkbox Selection
20
20
  ```tsx
21
21
  <DataGrid
22
+ rows={rows}
23
+ columns={columns}
22
24
  checkboxSelection
23
25
  pinCheckboxColumn // Optional: keeps checkbox on the left during horizontal scroll
24
26
  />
@@ -31,6 +33,8 @@ Use the `rowSelectionModel` to control the selection state from your parent comp
31
33
  const [selection, setSelection] = useState<GridRowId[]>([]);
32
34
 
33
35
  <DataGrid
36
+ rows={rows}
37
+ columns={columns}
34
38
  rowSelectionModel={selection}
35
39
  onRowSelectionModelChange={(newSelection) => setSelection(newSelection)}
36
40
  />
@@ -42,6 +46,8 @@ Use `disableRowSelectionOnClick` to prevent clicking a row from changing the sel
42
46
 
43
47
  ```tsx
44
48
  <DataGrid
49
+ rows={rows}
50
+ columns={columns}
45
51
  checkboxSelection
46
52
  disableRowSelectionOnClick
47
53
  />
@@ -49,15 +55,41 @@ Use `disableRowSelectionOnClick` to prevent clicking a row from changing the sel
49
55
 
50
56
  ### Single-Row Selection Only
51
57
 
52
- Use `disableMultipleRowSelection` to cap selection to one row at a time. Clicking a second row deselects the first; clicking an already-selected row deselects it.
58
+ Use `disableMultipleRowSelection` to cap selection to one row at a time. Clicking a second row deselects the first; clicking an already-selected row deselects it. The cap applies to row clicks, row checkboxes, the Space key and `apiRef.selectRow` / `selectRows` alike, and the header select-all checkbox is not shown.
53
59
 
54
60
  ```tsx
55
61
  <DataGrid
62
+ rows={rows}
63
+ columns={columns}
56
64
  disableMultipleRowSelection
57
65
  onRowSelectionModelChange={(model) => console.log('selected:', model)}
58
66
  />
59
67
  ```
60
68
 
69
+ ### Select All
70
+
71
+ With `checkboxSelection`, the header checkbox selects the rows that pass the current filter (including pinned rows) and adds them to the selection; unchecking it removes those rows again. Rows hidden by the filter keep their selection state, and under row grouping group rows are not part of select-all. Synthetic rows (row-grouping group and subtotal rows, auto-created tree-data parents, the pivot Grand Total) have no checkbox and are never selected, by click, checkbox, Space key or `apiRef.selectRow` / `selectRows` (v3.0+ for the Grand Total and `apiRef`); tree-data parents that are your own rows are selectable like any row. A click on the Grand Total still fires `onRowClick`. `apiRef.selectRow` on a row that is already selected reports nothing, as a click on the checkbox would not. The header shows checked when every such row is selected and indeterminate when only some are, so ids of rows that are no longer in `rows` do not affect it.
72
+
73
+ ### Removed Rows
74
+
75
+ When rows leave `rows`, their ids leave the selection (v3.0+): `apiRef.current.getSelectedRows()`, the header checkbox and the next model a click produces no longer contain them, and the pruned model is reported once through `onRowSelectionModelChange`. An uncontrolled selection forgets the ids, so a row that comes back is not selected again; a controlled model should adopt the reported value. This applies when the grid holds every row: without a `dataSource`, with client pagination and filtering, and outside pivot mode. With `paginationMode="server"`, `filterMode="server"` or a `dataSource`, ids of rows that are not loaded are kept, so a selection survives paging.
76
+
77
+ In pivot mode the pivot rows have their own ids (`'__pivot_row__:["North"]'`, derived from the row-field values), so a selection made on the source rows never marks a pivot row, and leaving pivot mode gives the source selection back.
78
+
79
+ ### Keyboard Selection
80
+
81
+ With focus in the grid (see [Keyboard & Accessibility](keyboard-navigation.md)):
82
+
83
+ | Key | Action |
84
+ | :--- | :--- |
85
+ | **Shift+Space** | Select or deselect the focused row. Available when rows can be selected at all (`checkboxSelection`, or click selection not disabled with `disableRowSelectionOnClick`). |
86
+ | **Space** / **Enter** on a row checkbox | Toggle that row. |
87
+ | **Space** / **Enter** on the select-all header | Select or clear all rows, like clicking it. |
88
+ | **Enter** on a non-editable cell | Same as clicking the row: `onRowClick`, then click-to-select unless `disableRowSelectionOnClick`. |
89
+ | **Ctrl+A** / **Cmd+A** | Select every row, unless `disableMultipleRowSelection` (or selection is not possible). |
90
+
91
+ Synthetic rows (row-grouping groups and subtotals, auto-created tree parents, the pivot Grand Total) are never selected from the keyboard. The grid sets `aria-multiselectable="true"` when several rows can be selected.
92
+
61
93
  ---
62
94
 
63
95
  ## ⚙️ API Reference
@@ -77,17 +109,25 @@ Use `disableMultipleRowSelection` to cap selection to one row at a time. Clickin
77
109
  ## 🖱️ Interaction Callbacks
78
110
 
79
111
  ### `onRowClick`
80
- Fired when a row is clicked (even if selection is disabled on click).
81
- ```typescript
82
- onRowClick: (params: GridRowParams) => {
83
- console.log('Row clicked:', params.id, params.row);
84
- }
112
+ Fired when a row is clicked, or Enter is pressed on one of its non-editable cells, even if selection is disabled on click. It is not fired for synthetic row-grouping group rows (a click toggles them instead).
113
+ ```tsx
114
+ <DataGrid
115
+ rows={rows}
116
+ columns={columns}
117
+ onRowClick={(params: GridRowParams) => {
118
+ console.log('Row clicked:', params.id, params.row);
119
+ }}
120
+ />
85
121
  ```
86
122
 
87
123
  ### `onCellClick`
88
124
  Fired when a specific cell is clicked.
89
- ```typescript
90
- onCellClick: (params: GridCellParams) => {
91
- console.log('Cell clicked:', params.field, params.value);
92
- }
125
+ ```tsx
126
+ <DataGrid
127
+ rows={rows}
128
+ columns={columns}
129
+ onCellClick={(params: GridCellParams) => {
130
+ console.log('Cell clicked:', params.field, params.value);
131
+ }}
132
+ />
93
133
  ```