@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
@@ -1,15 +1,17 @@
1
1
  # 🌐 Server-Side Data Source
2
2
 
3
- Handle millions of rows by connecting OpenGridX directly to your backend API.
3
+ Connect OpenGridX to your backend API so the server does the sorting, filtering and paging.
4
4
 
5
5
  ---
6
6
 
7
7
  ## 📑 The `GridDataSource` Interface
8
8
 
9
- The `dataSource` prop is the gateway to server-side operations. When provided, the grid becomes a "thin client" that requests only the data it needs to render the current viewport.
9
+ The `dataSource` prop is the gateway to server-side operations. The grid calls `getRows` with a row range and the current sort, filter and aggregation models, and shows the rows it returns. Requests are page-based (see *Modes of Operation*), not driven by the scroll position.
10
10
 
11
11
  ### Implementation
12
12
  ```tsx
13
+ import { DataGrid, type GridDataSource } from '@opencorestack/opengridx';
14
+
13
15
  const myDataSource: GridDataSource = {
14
16
  getRows: async (params) => {
15
17
  const { startRow, endRow, sortModel, filterModel } = params;
@@ -23,12 +25,20 @@ const myDataSource: GridDataSource = {
23
25
 
24
26
  return {
25
27
  rows: data.items,
26
- rowCount: data.totalCount // Required for stable pagination
28
+ rowCount: data.totalCount // the total, used by the pager with paginationMode="server"
27
29
  };
28
30
  }
29
31
  };
30
32
 
31
- <DataGrid dataSource={myDataSource} />
33
+ <DataGrid
34
+ rows={[]}
35
+ columns={columns}
36
+ dataSource={myDataSource}
37
+ pagination
38
+ paginationMode="server"
39
+ sortingMode="server"
40
+ filterMode="server"
41
+ />
32
42
  ```
33
43
 
34
44
  ---
@@ -38,28 +48,41 @@ const myDataSource: GridDataSource = {
38
48
  | Parameter | Type | Description |
39
49
  | :--- | :--- | :--- |
40
50
  | `startRow` | `number` | The starting index of the requested range. |
41
- | `endRow` | `number` | The ending index (exclusive). |
51
+ | `endRow` | `number` | The ending index (exclusive): return `rows.slice(startRow, endRow)`. `Number.MAX_SAFE_INTEGER` means "every row". |
42
52
  | `sortModel` | `GridSortItem[]` | Current sorting configuration. |
43
53
  | `filterModel` | `GridFilterModel` | Current filtering configuration. |
44
- | `groupKeys` | `string[]` | Active expansion path (used for server-side tree data). |
54
+ | `groupKeys` | `string[]` | Path of the parent whose children are requested (server-side tree data); empty for top-level rows. |
55
+ | `aggregationModel` | `GridAggregationModel \| undefined` | Current aggregation configuration, so the server can return `aggregationResults`. |
45
56
 
46
57
  ---
47
58
 
48
59
  ## 🔄 Modes of Operation
49
60
 
50
- ### 1. Server-Side Pagination
51
- Set `paginationMode="server"`. The grid will request fresh data every time the page changes.
61
+ The grid calls `getRows` whenever a `dataSource` is set. Rapid changes are collapsed into one request (300 ms debounce); the loading state shows from the moment a request is scheduled. Rows are keyed with `getRowId`.
62
+
63
+ ### 1. Client Pagination (default)
64
+ With `paginationMode="client"` (the default) the grid requests every row once (`startRow: 0`, `endRow: Number.MAX_SAFE_INTEGER`) and pages them itself. With `sortingMode="server"` or `filterMode="server"` a sort or filter change requests every row again with the new model; client-side sorts and filters, and page changes, never refetch.
65
+
66
+ ### 2. Server-Side Pagination
67
+ Set `paginationMode="server"`. Each page change requests `[page * pageSize, (page + 1) * pageSize)` and the response replaces the rows. Return `rowCount` so the pager knows the total.
52
68
 
53
- ### 2. Infinite Loading
54
- Set `paginationMode="infinite"`. The grid will request the next "chunk" of data as the user scrolls near the bottom of the list.
69
+ Row grouping turns pagination off (every group renders in one scrollable view). With `rowGroupingModel` set, a `paginationMode="server"` source is therefore asked for every row (`startRow: 0`, `endRow: Number.MAX_SAFE_INTEGER`) instead of the first page, and a development warning says so (v3.0+; before, only the first page was requested and the rest were unreachable).
55
70
 
56
- ### 3. Server-Side Tree Data
57
- When `treeData` is enabled with a `dataSource`, OpenGridX will call `getRows` with `groupKeys`. You should return the children for that specific parent path.
71
+ ### 3. Infinite Loading
72
+ Set `paginationMode="infinite"` and advance `paginationModel.page` (for example from `onRowsScrollEnd`). The grid requests the rows from the end of what is loaded up to the current page and appends them, so a page that moves on while a request is in flight never leaves a gap. See [Infinite Scroll](infinite-scroll.md).
73
+
74
+ ### 4. Server-Side Tree Data
75
+ When `treeData` is enabled with a `dataSource`, top-level rows come from the normal request. A row with `serverChildrenCount > 0` shows an expand toggle; expanding it calls `getRows` with that row's path in `groupKeys` (and `startRow: 0`, `endRow: Number.MAX_SAFE_INTEGER`). Return that parent's children. With `filterMode="server"` / `sortingMode="server"` the returned rows are neither re-filtered nor re-sorted in the tree. When a refetch replaces the rows (page, sort, filter), the children of nodes that are still expanded are requested again. If a children request fails, the node collapses again and the error is logged; expanding it retries. (It does not show the grid-level error overlay.) `defaultGroupingExpansionDepth` counts a node with `serverChildrenCount > 0` as expandable, so the nodes within that depth (all of them with `-1`) start expanded and their children are requested right away (v3.0+; before, lazy nodes were never expanded by the default depth).
76
+
77
+ ### When a refetch happens
78
+ Models are compared by content, so an inline `filterModel={{ items: [] }}` or a `dataSource={{ getRows }}` object recreated with the same `getRows` function does not refetch. A new `getRows` function does. A response for parameters that have changed in the meantime is discarded.
58
79
 
59
80
  ---
60
81
 
61
82
  ## 🛠️ Error Handling
62
- If your API call fails, you should throw an error or return a rejected promise. OpenGridX will automatically display the **Error Overlay** with a "Retry" button.
83
+ If your API call fails, throw an error or return a rejected promise. OpenGridX shows the **Error Overlay** with the rejection's `message` (an `Error`, a string, or any object with a string `message`) and a **Retry** button that requests the rows again.
84
+
85
+ A response the grid cannot use fails the same way (v3.0+): a response without a `rows` array, or a `getRowId` that throws for a fetched row, shows the error overlay and ends the loading state, instead of an unhandled promise rejection and an endless "Loading". For a children request (server-side tree data) it collapses the node as a failed request does.
63
86
 
64
87
  ```typescript
65
88
  getRows: async () => {
@@ -10,12 +10,33 @@ OpenGridX supports inline cell editing. For a column to be editable, you must se
10
10
 
11
11
  ### Basic Setup
12
12
  ```tsx
13
- const columns = [
13
+ import type { GridColDef } from '@opencorestack/opengridx';
14
+
15
+ const columns: GridColDef[] = [
14
16
  { field: 'name', editable: true },
15
17
  { field: 'role', editable: true, type: 'singleSelect', valueOptions: ['Admin', 'Editor'] }
16
18
  ];
17
19
  ```
18
20
 
21
+ Double-click a cell or press **Enter** on a focused cell to open its editor.
22
+
23
+ ### Which cells can be edited
24
+ A cell is editable when its column has `editable: true` **and**, if you pass it, `isCellEditable` returns `true`. The same rule drives double-click, Enter, the cells **Tab** stops on while editing, and `aria-readonly`.
25
+
26
+ ```tsx
27
+ <DataGrid
28
+ columns={columns}
29
+ rows={rows}
30
+ // Lock archived rows, whatever the column says.
31
+ isCellEditable={({ row }) => row.status !== 'archived'}
32
+ />
33
+ ```
34
+
35
+ - `isCellEditable` can only restrict. It cannot make a column without `editable: true` editable. If it throws, the cell is treated as read-only.
36
+ - Row-grouping group rows and the ancestors tree data generates for missing path segments are never editable. Tree-data **parent rows are your own rows** and are editable like any other row.
37
+ - Pinned rows (`pinnedRows`) are edited like any other row, with double-click or Enter (v3.0; they used to show no editor).
38
+ - **Enter** on a cell that is not editable does what clicking its row does (`onRowClick`, click-to-select, expanding a group). See [Keyboard & Accessibility](keyboard-navigation.md).
39
+
19
40
  ### Persistence
20
41
  When a cell edit is committed, the `processRowUpdate` callback is triggered.
21
42
 
@@ -33,6 +54,82 @@ When a cell edit is committed, the `processRowUpdate` callback is triggered.
33
54
  />
34
55
  ```
35
56
 
57
+ `processRowUpdate` must return the row to store (or a Promise of it). The grid stores that row under the id of the row you edited, so a server response that lacks the key field (for example the `uid` read by `getRowId={r => r.uid}`) is still applied. The row is stored as returned: the grid never adds an `id` field to it (v3.0+). Returning nothing is a mistake the grid reports through `onProcessRowUpdateError` (or `console.error` in development when no handler is set); the editor stays open.
58
+
59
+ ### Commit and cancel
60
+
61
+ | Action | Result |
62
+ | :--- | :--- |
63
+ | **Enter** | Commits and closes the editor. |
64
+ | **Tab** / **Shift+Tab** | Commits and moves to the next / previous editable cell. With no editable cell left, focus moves on out of the grid and the edit commits. Outside edit mode Tab is not captured: it leaves the grid. |
65
+ | Moving focus away (clicking another cell or any control outside the grid) | Commits. Focus stays where you put it. |
66
+ | The edited cell leaving the grid (scrolled out of the render window, filtered out, another page) | Commits, as if the editor had lost focus. |
67
+ | Starting an edit on another cell | Commits the current edit first. |
68
+ | **Escape** | Discards the pending value and closes the editor. |
69
+
70
+ Clicks and double-clicks inside an open editor stay in the editor: you can place the caret, select a word or open a dropdown. They do not fire `onCellClick`, `onRowClick` or `onRowDoubleClick`, and do not change row selection. While an IME is composing (Japanese, Chinese, Korean input), **Enter** confirms the candidate and does not commit.
71
+
72
+ `processRowUpdate` runs **once per commit**. Actions that overlap, such as pressing Enter and then clicking elsewhere while an async save is in flight, share that one call.
73
+
74
+ With an async `processRowUpdate`, the editor stays open until the Promise settles:
75
+ - **Resolved:** the returned row is stored and the editor closes. If the user kept typing meanwhile, the editor stays open with the newer text, which the next commit saves.
76
+ - **Rejected:** `onProcessRowUpdateError` receives the error and the editor stays open with the pending value, so the user can fix it or press Escape.
77
+ - **Escape while pending** closes the editor at once, but cannot recall the save: when it resolves, the returned row is still stored, because your `processRowUpdate` has already run.
78
+ - A late result never closes an edit the user has started on another cell.
79
+
80
+ ### Built-in editors
81
+ The editor follows the column `type`, and commits values of the same type the cell held.
82
+
83
+ | `type` | Editor | Committed value |
84
+ | :--- | :--- | :--- |
85
+ | `'string'` (default) | Text input | String. |
86
+ | `'number'` | Number input | Number, or `null` when cleared. |
87
+ | `'boolean'` | Checkbox | Toggling commits immediately. |
88
+ | `'singleSelect'` | `<select>` over `valueOptions` | The option's own value: `{ value: 2, label: 'Closed' }` commits the number `2`, not `'2'`. When the current value is empty or not among the options, an empty choice is shown, so any option can be picked. |
89
+ | `'date'` | Date input | Same kind as before: a `Date` stays a `Date` (keeping its time of day), a timestamp stays a timestamp, an ISO date-time string keeps its time part. `null` when cleared. |
90
+
91
+ Every editor is labelled with the column's `headerName` (or `field`) for screen readers.
92
+
93
+ ### Custom editors (`renderEditCell`)
94
+ `renderEditCell` receives the usual cell params, with `value` set to the pending value, plus three callbacks:
95
+
96
+ ```tsx
97
+ import type { GridColDef, GridRenderEditCellParams } from '@opencorestack/opengridx';
98
+
99
+ function RatingEditor({ value, onValueChange, onCommit, onCancel }: GridRenderEditCellParams) {
100
+ return (
101
+ <input
102
+ type="range" min={0} max={5} autoFocus
103
+ value={Number(value ?? 0)}
104
+ onChange={e => onValueChange(Number(e.target.value))}
105
+ onBlur={onCommit}
106
+ onKeyDown={e => { if (e.key === 'Escape') onCancel(); }}
107
+ />
108
+ );
109
+ }
110
+
111
+ const columns: GridColDef[] = [
112
+ { field: 'rating', type: 'number', editable: true, renderEditCell: params => <RatingEditor {...params} /> },
113
+ ];
114
+ ```
115
+
116
+ Enter and Escape keydowns that bubble out of a custom editor also commit and cancel. An error thrown by `renderEditCell` is contained to its cell (the cell shows `⚠`), like `renderCell`.
117
+
118
+ ### Computed columns (`valueGetter` + `valueSetter`)
119
+ An editable column with a `valueGetter` edits the derived value. Give it a `valueSetter` so the commit can write the value back to the fields it comes from; otherwise the commit writes `row[field]`, which the `valueGetter` ignores, and a development warning is logged.
120
+
121
+ ```tsx
122
+ {
123
+ field: 'fullName',
124
+ editable: true,
125
+ valueGetter: ({ row }) => `${row.firstName} ${row.lastName}`,
126
+ valueSetter: ({ value, row }) => {
127
+ const [firstName, ...rest] = String(value).split(' ');
128
+ return { ...row, firstName, lastName: rest.join(' ') };
129
+ },
130
+ }
131
+ ```
132
+
36
133
  ---
37
134
 
38
135
  ## ↕️ Row Reordering
@@ -45,10 +142,9 @@ Allows users to rearrange rows by dragging a handle.
45
142
 
46
143
  ```tsx
47
144
  <DataGrid
145
+ rows={rows}
48
146
  rowReordering={true}
49
- onRowOrderChange={(params) => {
50
- const { oldIndex, targetIndex } = params;
51
- // Move logic
147
+ onRowOrderChange={({ oldIndex, targetIndex }) => {
52
148
  const newRows = [...rows];
53
149
  const [moved] = newRows.splice(oldIndex, 1);
54
150
  newRows.splice(targetIndex, 0, moved);
@@ -57,8 +153,19 @@ Allows users to rearrange rows by dragging a handle.
57
153
  />
58
154
  ```
59
155
 
60
- > [!IMPORTANT]
61
- > Row reordering is usually disabled when sorting or filtering is active to prevent index confusion.
156
+ ### What the indices mean
157
+ `oldIndex` and `targetIndex` are positions in **your `rows` prop** (v3.0+), so `rows[oldIndex] === params.row` and the splice above always moves the row that was dragged. This holds on every page, with pinned rows, and while the grid is sorted or filtered. (Before v3.0 they were positions in the visible page, which moved the wrong row in all of those cases.)
158
+
159
+ `params.row` is your own row object, also with `getRowId`.
160
+
161
+ While the grid is sorted, the new position in `rows` does not change where the row shows up: the sort decides that. Turn sorting off if the drag should visibly move the row.
162
+
163
+ ### Which rows can be dragged
164
+ - Every data row you passed in `rows` can be dragged and dropped on.
165
+ - Pinned rows (`pinnedRows`) keep their place. They show an empty handle cell, so their cells line up with the header, but cannot be dragged or dropped on.
166
+ - Rows the grid makes itself cannot be dragged or dropped on: row-grouping group rows and the tree-data parents generated for missing path segments.
167
+ - While pivot mode is active, row reordering is off (no handle column): pivot rows are generated, so their positions mean nothing in `rows`. It comes back when pivoting ends.
168
+ - Only the grid's own row drags are accepted: dropping a file, a text selection or a header on a row does nothing.
62
169
 
63
170
  ---
64
171
 
@@ -73,26 +180,26 @@ Use the `disableColumnReorder` prop to control this feature globally.
73
180
  <DataGrid disableColumnReorder={true} />
74
181
  ```
75
182
 
183
+ `disableColumnReorder` only removes the user's ways to reorder (header drag, Columns-panel reorder). A controlled `columnOrder` or `initialState.columns.columnOrder` still sets the order, so you can fix a custom order and stop users from changing it.
184
+
76
185
  ### Controlled Column Order
77
- You can manage the column order state explicitly using `columnOrder` and `onColumnOrderChange`.
186
+ Manage the order yourself with `columnOrder` and `onColumnOrderModelChange`, which receives the whole new order after every change: a header drag, a drag in the Columns panel, and the panel's **Reset**.
78
187
 
79
188
  ```tsx
80
- const [colOrder, setColOrder] = useState(['id', 'name', 'status']);
189
+ const [colOrder, setColOrder] = useState<string[]>(['id', 'name', 'status']);
81
190
 
82
191
  <DataGrid
83
192
  columnOrder={colOrder}
84
- onColumnOrderChange={(params) => {
85
- // params: { column: GridColDef, oldIndex: number, targetIndex: number }
86
- const newOrder = [...colOrder];
87
- const [moved] = newOrder.splice(params.oldIndex, 1);
88
- newOrder.splice(params.targetIndex, 0, moved);
89
- setColOrder(newOrder);
90
- }}
193
+ onColumnOrderModelChange={setColOrder}
91
194
  />
92
195
  ```
93
196
 
197
+ `onColumnOrderChange` still fires for every single move, with `{ column, oldIndex, targetIndex }`. The indices are positions in the grid's **full** column order: every current column (hidden columns and the row-grouping `__group__` column included), in its current order. Splicing your `columnOrder` with them is only right when it lists every column; prefer `onColumnOrderModelChange`. The panel's Reset does not fire `onColumnOrderChange`.
198
+
199
+ Columns missing from `columnOrder` (added later, for example) are still shown, placed by their position in `columns`.
200
+
94
201
  ### Non-Reorderable Columns
95
- To prevent all columns from being reordered, use `disableColumnReorder` on the grid. For individual column control, pin the column using `pinnedColumns` at the grid level — pinned columns do not participate in drag-reorder.
202
+ To prevent all columns from being reordered, use `disableColumnReorder` on the grid. Pinned columns (`pinnedColumns`, and the row-grouping column) keep their place: their headers cannot be dragged, and a header dropped on them does nothing. Unpin a column to move it. `pinnable: false` only stops a column from being pinned; it can still be reordered (v3.0+).
96
203
 
97
204
  ```tsx
98
205
  // Disable reordering globally
@@ -101,3 +208,5 @@ To prevent all columns from being reordered, use `disableColumnReorder` on the g
101
208
  // Keep a column fixed by pinning it
102
209
  <DataGrid pinnedColumns={{ left: ['id'] }} />
103
210
  ```
211
+
212
+ Under `columnGroupingModel`, a column can only be dropped on a column of the same group.