@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
@@ -10,70 +10,42 @@ Cell spanning allows cells in the DataGrid to span across multiple columns (colu
10
10
  - **Row Spanning**: Cells can span vertically across multiple rows
11
11
  - **Dynamic Spanning**: Span values can be calculated dynamically based on cell data
12
12
  - **Static Spanning**: Span values can be set as fixed numbers
13
- - **Accessibility**: Automatic ARIA attributes (`aria-colspan`, `aria-rowspan`)
14
- - **CSS Grid Integration**: Uses CSS Grid for proper layout
13
+ - **Accessibility**: Automatic ARIA attributes (`aria-colspan`, `aria-rowspan`) and keyboard navigation that treats a merged area as one cell
14
+ - **Virtualization-aware**: A span stays rendered while any part of it is in view, horizontally and vertically
15
+ - **Zero cost when unused**: Nothing is computed unless a column declares `colSpan` or `rowSpan`
15
16
 
16
17
  ---
17
18
 
18
- ## ⚠️ Important: Feature Conflicts
19
+ ## How spans interact with other features
19
20
 
20
- When using `colSpan` or `rowSpan`, some other DataGrid features may not work as expected or may create a confusing grid layout. **To avoid layout issues and unexpected behavior, consider disabling the following features for any columns that use cell spanning:**
21
+ Spans are computed from the grid **as it is rendered**, so they follow the current column order, visibility, pinning, sorting, filtering and page:
21
22
 
22
- ### Features to Disable
23
+ | Feature | Behaviour |
24
+ | :--- | :--- |
25
+ | **Column visibility** | Hidden columns are skipped. `colSpan: 2` merges the origin with the next *visible* column, and the merged width and `aria-colspan` count visible columns only. |
26
+ | **Column order / reordering** | A span covers the columns that follow the origin in the current (rendered) order. |
27
+ | **Column pinning** | A span never crosses a pinned-section boundary: it is clamped to the end of the left-pinned, unpinned or right-pinned section its origin is in. |
28
+ | **Row pinning** | Spans are computed separately for the top-pinned rows, the scrolling rows and the bottom-pinned rows, and never reach from one into another. |
29
+ | **Sorting / filtering / pagination** | Spans are recomputed against the new row order, the filtered rows and the current page. `rowIndex` in the callback is the row's position in the rendered rows (top-pinned rows first). |
30
+ | **Master-detail** | A row span ends at a row whose detail panel is expanded, so the merged cell never covers the panel. |
31
+ | **Virtualization** | The render window is widened so a span is never cut: the row whose cell starts a visible row span, and the column that starts a visible column span, are always rendered. |
32
+ | **Column resizing, flex and percentage widths** | A merged cell is exactly as wide as the columns it covers, using their resolved widths. The origin column's own `minWidth` / `maxWidth` do not apply to the merged cell. Resizing never recomputes the spans. |
33
+ | **Keyboard navigation** | Arrow keys move over a merged area in one step and land on its origin cell; covered cells are never focused. While editing, Tab skips covered cells (outside edit mode Tab leaves the grid). |
34
+ | **Infinite scrolling** | The loading placeholder rows are never passed to `colSpan` / `rowSpan`. |
35
+ | **Row grouping, tree data, pivot** | Rows the grid made (group headers, subtotals, auto-created tree parents, the pivot Grand Total) are never a span origin, and a `rowSpan` ends before the next such row, so a span never merges data across a group boundary (v3.0+). |
23
36
 
24
- 1. **Sorting** (`sortable: false`)
25
- - When rows are reordered by sorting, spanned cells may break visual continuity
26
- - Subtotal/summary rows with spanning should remain in fixed positions
37
+ Spans on columns whose values change with sorting still work, but the merged areas move with the rows. For summary rows that must stay in place, disable sorting on the spanning column (`sortable: false`) or use [Row Pinning](./pinning.md).
27
38
 
28
- 2. **Filtering** (when implemented)
29
- - Hidden rows can cause spanned cells to appear disconnected or incomplete
30
- - Filtering may hide parts of a spanned cell group
39
+ ### Span values
31
40
 
32
- 3. **Column Reordering** (`disableReorder: true`)
33
- - Moving columns can break the spanning logic
34
- - Spanned cells depend on specific column order
41
+ The value returned (or set) for `colSpan` / `rowSpan` is normalised before it is used:
35
42
 
36
- 4. **Column Hiding** (`hideable: false`)
37
- - Hiding columns within a span creates layout gaps and visual artifacts
38
- - The grid cannot properly render partial spans
43
+ - Values larger than what is left are clamped: `colSpan` to the end of the origin's pinned section, `rowSpan` to the end of the row section (or to the first row with an expanded detail panel). `aria-colspan` / `aria-rowspan` show the clamped value.
44
+ - Fractions are rounded down (`1.7` → `1`).
45
+ - `Infinity` means "to the end" and is clamped like any other large value.
46
+ - `NaN`, `0`, negative numbers and non-numbers mean no span (`1`).
39
47
 
40
- 5. **Column Pinning** (avoid pinning spanned columns)
41
- - Pinned columns with spanning can cause rendering conflicts
42
- - Spanning across pinned and non-pinned columns is not supported
43
-
44
- ### Recommended Configuration
45
-
46
- ```tsx
47
- const columns: GridColDef<Row>[] = [
48
- {
49
- field: 'summary',
50
- headerName: 'Summary',
51
- width: 200,
52
- colSpan: (params) => params.row.isTotal ? 3 : 1,
53
- // Disable conflicting features
54
- sortable: false, // ✅ Disable sorting
55
- hideable: false, // ✅ Disable column hiding
56
- // Column reorder is disabled grid-wide via disableColumnReorder on <DataGrid>
57
- // Note: Avoid pinning this column
58
- renderCell: (params) => {
59
- if (params.row.isTotal) {
60
- return <strong>Total: {params.value}</strong>;
61
- }
62
- return params.value;
63
- }
64
- },
65
- // Other columns...
66
- ];
67
- ```
68
-
69
- ### Why These Limitations Exist
70
-
71
- Cell spanning fundamentally changes the grid's layout model:
72
- - **Sorting/Filtering** change row order/visibility, breaking span relationships
73
- - **Column operations** (reorder/hide/pin) modify the column structure that spans depend on
74
- - **CSS Grid** requires stable column positions for proper spanning
75
-
76
- **Best Practice**: Use cell spanning primarily for static summary rows, headers, or fixed layout sections where these features are not needed.
48
+ A `colSpan` or `rowSpan` function (or the `valueGetter` whose value it receives) that **throws** is treated as `1` for that cell, and a warning is logged in development. The rest of the grid keeps rendering.
77
49
 
78
50
  ---
79
51
 
@@ -127,7 +99,7 @@ const columns: GridColDef<Row>[] = [
127
99
  if (params.row.isTotal) {
128
100
  return <strong>Total: {params.row.name}</strong>;
129
101
  }
130
- return params.value;
102
+ return params.formattedValue;
131
103
  }
132
104
  },
133
105
  {
@@ -145,7 +117,13 @@ const columns: GridColDef<Row>[] = [
145
117
  ### Basic Usage
146
118
 
147
119
  ```tsx
148
- const columns: GridColDef<Row>[] = [
120
+ interface ItemRow {
121
+ id: number;
122
+ category: string;
123
+ item: string;
124
+ }
125
+
126
+ const columns: GridColDef<ItemRow>[] = [
149
127
  {
150
128
  field: 'category',
151
129
  headerName: 'Category',
@@ -164,18 +142,18 @@ const columns: GridColDef<Row>[] = [
164
142
  ### Dynamic Row Spanning
165
143
 
166
144
  ```tsx
167
- const columns: GridColDef<Row>[] = [
145
+ // `rows: ItemRow[]` is the array passed to the grid, sorted by category.
146
+ const columns: GridColDef<ItemRow>[] = [
168
147
  {
169
148
  field: 'category',
170
149
  headerName: 'Category',
171
150
  width: 150,
172
151
  // Dynamic row span based on data
173
152
  rowSpan: (params) => {
174
- // Calculate how many rows this category spans
175
- const categoryRows = rows.filter(
176
- r => r.category === params.row.category
177
- );
178
- return categoryRows.length;
153
+ // Start a span at the first row of each category; the rest are covered by it
154
+ const index = rows.findIndex(r => r.category === params.row.category);
155
+ if (rows[index].id !== params.row.id) return 1;
156
+ return rows.filter(r => r.category === params.row.category).length;
179
157
  }
180
158
  },
181
159
  {
@@ -190,10 +168,20 @@ const columns: GridColDef<Row>[] = [
190
168
 
191
169
  ## Combined Column and Row Spanning
192
170
 
193
- You can use both `colSpan` and `rowSpan` together:
171
+ You can use both `colSpan` and `rowSpan` together. The origin cell then covers the whole rectangle: with `colSpan: 3` and `rowSpan: 2`, the three cells in the next row are covered too.
172
+
173
+ - A cell covered by another cell's `colSpan` is not evaluated as a `rowSpan` origin in that row.
174
+ - A cell covered by a `rowSpan` from above is not evaluated as a `colSpan` origin.
175
+ - A `colSpan` stops before a cell that a `rowSpan` from a row above already covers.
194
176
 
195
177
  ```tsx
196
- const columns: GridColDef<Row>[] = [
178
+ interface SectionRow {
179
+ id: number;
180
+ header: string;
181
+ isHeader?: boolean;
182
+ }
183
+
184
+ const columns: GridColDef<SectionRow>[] = [
197
185
  {
198
186
  field: 'header',
199
187
  headerName: 'Header',
@@ -209,11 +197,11 @@ const columns: GridColDef<Row>[] = [
209
197
  textAlign: 'center',
210
198
  padding: '20px'
211
199
  }}>
212
- {params.value}
200
+ {params.formattedValue}
213
201
  </div>
214
202
  );
215
203
  }
216
- return params.value;
204
+ return params.formattedValue;
217
205
  }
218
206
  }
219
207
  ];
@@ -292,7 +280,7 @@ export default function SalesReport() {
292
280
  if (params.row.isSubtotal) {
293
281
  return <em>{params.row.region} Subtotal</em>;
294
282
  }
295
- return params.value;
283
+ return params.formattedValue;
296
284
  }
297
285
  },
298
286
  {
@@ -305,35 +293,35 @@ export default function SalesReport() {
305
293
  headerName: 'Q1',
306
294
  width: 120,
307
295
  type: 'number',
308
- valueFormatter: ({ value }) => `$${value.toLocaleString()}`
296
+ valueFormatter: ({ value }) => `$${(value as number).toLocaleString()}`
309
297
  },
310
298
  {
311
299
  field: 'q2',
312
300
  headerName: 'Q2',
313
301
  width: 120,
314
302
  type: 'number',
315
- valueFormatter: ({ value }) => `$${value.toLocaleString()}`
303
+ valueFormatter: ({ value }) => `$${(value as number).toLocaleString()}`
316
304
  },
317
305
  {
318
306
  field: 'q3',
319
307
  headerName: 'Q3',
320
308
  width: 120,
321
309
  type: 'number',
322
- valueFormatter: ({ value }) => `$${value.toLocaleString()}`
310
+ valueFormatter: ({ value }) => `$${(value as number).toLocaleString()}`
323
311
  },
324
312
  {
325
313
  field: 'q4',
326
314
  headerName: 'Q4',
327
315
  width: 120,
328
316
  type: 'number',
329
- valueFormatter: ({ value }) => `$${value.toLocaleString()}`
317
+ valueFormatter: ({ value }) => `$${(value as number).toLocaleString()}`
330
318
  },
331
319
  {
332
320
  field: 'total',
333
321
  headerName: 'Total',
334
322
  width: 140,
335
323
  type: 'number',
336
- valueFormatter: ({ value }) => value ? `$${value.toLocaleString()}` : ''
324
+ valueFormatter: ({ value }) => (value == null ? '' : `$${(value as number).toLocaleString()}`)
337
325
  }
338
326
  ];
339
327
 
@@ -387,7 +375,7 @@ Defines how many rows a cell should span.
387
375
  rowSpan: 2
388
376
 
389
377
  // Dynamic spanning
390
- rowSpan: (params) => params.row.categorySize || 1
378
+ rowSpan: (params) => (params.row.categorySize as number | undefined) ?? 1
391
379
  ```
392
380
 
393
381
  ### GridRenderCellParams
@@ -396,12 +384,14 @@ Parameters passed to the `colSpan` and `rowSpan` functions:
396
384
 
397
385
  ```tsx
398
386
  interface GridRenderCellParams<R> {
399
- value: any; // Cell value
400
- row: R; // Complete row data
401
- field: string; // Column field name
387
+ value: unknown; // Cell value: the valueGetter result when the column has one
388
+ formattedValue?: string; // The valueFormatter result (or String(value)); render this, not `value`
389
+ row: R; // Complete row data
390
+ field: string; // Column field name
402
391
  colDef: GridColDef<R>; // Column definition
403
- rowIndex: number; // Row index
404
- colIndex: number; // Column index
392
+ rowIndex: number; // Position among the rendered rows (top-pinned rows, then the current page, then bottom-pinned rows)
393
+ colIndex: number; // Position among the rendered data columns (left-pinned first); checkbox, detail-panel and reorder columns are not counted
394
+ rowMeta?: GridRowMeta; // Hierarchy metadata under tree data / row grouping
405
395
  }
406
396
  ```
407
397
 
@@ -430,11 +420,11 @@ Spanned cells can be styled using custom `renderCell`:
430
420
  fontSize: '1.2em',
431
421
  borderBottom: '2px solid #1976d2'
432
422
  }}>
433
- {params.value}
423
+ {params.formattedValue}
434
424
  </div>
435
425
  );
436
426
  }
437
- return params.value;
427
+ return params.formattedValue;
438
428
  }
439
429
  }
440
430
  ```
@@ -445,10 +435,10 @@ Spanned cells can be styled using custom `renderCell`:
445
435
 
446
436
  The DataGrid automatically adds ARIA attributes for spanned cells:
447
437
 
448
- - `aria-colspan`: Added when `colSpan > 1`
449
- - `aria-rowspan`: Added when `rowSpan > 1`
438
+ - `aria-colspan`: Added when the (clamped) `colSpan > 1`
439
+ - `aria-rowspan`: Added when the (clamped) `rowSpan > 1`
450
440
 
451
- This ensures screen readers properly announce the spanning behavior.
441
+ Cells covered by a column span are not rendered. Cells covered by a row span render as empty `role="presentation"` placeholders that keep the column's slot. Keyboard focus always lands on the origin cell of a merged area.
452
442
 
453
443
  ---
454
444
 
@@ -456,43 +446,18 @@ This ensures screen readers properly announce the spanning behavior.
456
446
 
457
447
  1. **Use Dynamic Spanning**: Prefer dynamic spanning functions over static values for flexibility
458
448
  2. **Combine with renderCell**: Always provide custom rendering for spanned cells to improve visual clarity
459
- 3. **Consider Performance**: Spanning calculations run on every render, so keep them lightweight
460
- 4. **Test with Virtualization**: Ensure spanning works correctly with virtual scrolling
449
+ 3. **Consider Performance**: Span functions run once per cell of a spanning column whenever the rows or the column structure change (not on scroll or resize), so keep them lightweight
450
+ 4. **Keep Row Spans Short**: While a long row span is on screen, every row from its origin down is rendered, so a 1,000-row span renders up to 1,000 rows
461
451
  5. **Accessibility**: Provide meaningful content in spanned cells for screen readers
462
452
 
463
453
  ---
464
454
 
465
455
  ## Limitations
466
456
 
467
- ### Feature Conflicts
468
-
469
- **⚠️ See the [Feature Conflicts](#️-important-feature-conflicts) section above for important information about which features should be disabled when using cell spanning.**
470
-
471
- ### Technical Limitations
472
-
473
- 1. **Virtualization**:
474
- - Row spanning may have visual artifacts with virtualization if spans cross virtual boundaries
475
- - Large row spans (>10 rows) may cause performance issues with virtual scrolling
476
- - Consider using row pinning for summary rows instead of row spanning
477
-
478
- 2. **Pinned Columns**:
479
- - Column spanning with pinned columns requires careful consideration
480
- - Spanning across pinned and non-pinned columns is not supported
481
- - Use spanning only within pinned or non-pinned column groups
482
-
483
- 3. **Sorting/Filtering**:
484
- - Spanned cells may break visual continuity when rows are reordered
485
- - Always disable sorting on columns with dynamic spanning
486
- - Filtering can hide parts of spanned cell groups
487
-
488
- 4. **Column Operations**:
489
- - Column reordering can break spanning logic
490
- - Hiding columns within a span creates layout gaps
491
- - Column resizing may not work optimally with spanned cells
492
-
493
- 5. **Editing**:
494
- - Cell editing on spanned cells may have unexpected behavior
495
- - Consider disabling editing for columns with spanning
457
+ 1. **Pinned columns**: A span cannot cover columns in two pinned sections. Put the columns a span should merge in the same section.
458
+ 2. **Pinned rows**: A span cannot reach from a pinned row into the scrolling rows (or the other way).
459
+ 3. **Long row spans**: The rows between a visible row span's origin and the viewport are rendered, so very long row spans cost DOM nodes. Consider [Row Grouping](./tree-data-grouping.md) for large vertical groups.
460
+ 4. **Editing**: Double-click editing on a merged cell edits the origin cell's field.
496
461
 
497
462
  ### Workarounds
498
463
 
@@ -502,19 +467,9 @@ This ensures screen readers properly announce the spanning behavior.
502
467
 
503
468
  ---
504
469
 
505
- ## Browser Support
506
-
507
- Cell spanning uses CSS Grid, which is supported in:
508
- - Chrome 57+
509
- - Firefox 52+
510
- - Safari 10.1+
511
- - Edge 16+
512
-
513
- ---
514
-
515
470
  ## See Also
516
471
 
517
472
  - [Row Pinning](./pinning.md)
518
473
  - [Column Pinning](./pinning.md)
519
- - [Custom Cell Rendering](./cell.md)
474
+ - [Custom Cell Rendering](../components/cell.md)
520
475
  - [Aggregation](./aggregation-pivot.md)
@@ -4,9 +4,10 @@ OpenGridX supports copying grid data directly to the clipboard. This feature all
4
4
 
5
5
  ## 📑 Overview
6
6
  - **Zero Configuration**: Enabled by default with the standard `DataGrid`.
7
- - **Selected Rows**: Only selected rows are copied when selection is active.
8
- - **TSV Format**: Data is copied in Tab-Separated Values (TSV) format for maximum compatibility.
9
- - **Value Formatters**: Respects `valueFormatter` for accurate data representation.
7
+ - **Selected Rows**: Every selected row that passes the filter is copied — rows on other pages, rows inside collapsed groups or tree nodes, and pinned rows included — in the order the grid shows them. Rows the filter removes are not copied.
8
+ - **What you see**: Only the visible columns are copied, in screen order (column order, drag reorder and pinning are followed). Columns marked `exportable: false` (action/button columns) are left out, as in every export.
9
+ - **TSV Format**: Data is copied in Tab-Separated Values (TSV) format, quoted the way Excel and Google Sheets read it.
10
+ - **Cell values**: `valueGetter` and then `valueFormatter` are applied, as in the cells.
10
11
 
11
12
  ---
12
13
 
@@ -16,7 +17,17 @@ Selection and copying follow standard spreadsheet metaphors:
16
17
  | :--- | :--- |
17
18
  | `Ctrl + C` / `Cmd + C` | Copy currently selected rows to the clipboard. |
18
19
 
19
- > **Note**: The copy action is ignored if an input, textarea, or another editable element currently has focus.
20
+ > **Note**: The shortcut only copies while focus is inside the grid (a cell, a header, the toolbar), so with several grids on a page only the one in use copies, and Ctrl+C elsewhere on the page is never taken over (v3.0). It is also ignored while an input, textarea, or another editable element has focus, and while the page has a text selection, which the browser copies as usual. `apiRef.current.copySelectedRows()` is not affected by focus.
21
+ >
22
+ > The shortcut works with Caps Lock on and on non-Latin keyboard layouts (the key in the "C" position). Ctrl+Shift+C and Ctrl+Alt+C are not copy shortcuts and are left alone. Focus stays where it was after the copy.
23
+
24
+ ### Taking over the shortcut
25
+ - Call `event.preventDefault()` in your own `keydown` handler (on the element, `document`, or in the capture phase) and the grid leaves that Ctrl+C alone.
26
+ - Or pass `disableClipboardCopy` to turn the grid's shortcut off entirely. `apiRef.current.copySelectedRows()` still works.
27
+
28
+ ```tsx
29
+ <DataGrid rows={rows} columns={columns} disableClipboardCopy />
30
+ ```
20
31
 
21
32
  ---
22
33
 
@@ -34,32 +45,46 @@ If `checkboxSelection` is enabled and rows are selected, pressing `Ctrl + C` wil
34
45
  ```
35
46
 
36
47
  ### Programmatic Copy
37
- You can trigger a copy action programmatically using the exported hook if building a custom toolbar.
48
+ Call `apiRef.current.copySelectedRows()`, for example from a custom toolbar button. The promise resolves once the text is written (or straight away, writing nothing, when no selected row is found) and **rejects when the browser refuses the write**, so you can tell the user.
38
49
 
39
50
  ```tsx
40
- import { useGridClipboard } from '@opencorestack/opengridx';
51
+ import { DataGrid, useGridApiRef } from '@opencorestack/opengridx';
41
52
 
42
- function MyCustomToolbar({ state, columns, getVisibleRows }) {
43
- const { copySelectedRows } = useGridClipboard({ state, columns, getVisibleRows });
53
+ function MyGrid({ rows, columns }) {
54
+ const apiRef = useGridApiRef();
55
+
56
+ const copy = async () => {
57
+ try {
58
+ await apiRef.current.copySelectedRows();
59
+ showToast('Copied');
60
+ } catch {
61
+ showToast('The browser blocked the clipboard');
62
+ }
63
+ };
44
64
 
45
65
  return (
46
- <button onClick={copySelectedRows}>
47
- Copy to Clipboard
48
- </button>
66
+ <>
67
+ <button onClick={copy}>Copy to Clipboard</button>
68
+ <DataGrid rows={rows} columns={columns} apiRef={apiRef} checkboxSelection />
69
+ </>
49
70
  );
50
71
  }
51
72
  ```
52
73
 
74
+ `copySelectedRows()` reads the latest selection, so it can be called right after `apiRef.current.selectRows(ids)` or from `onRowSelectionModelChange`.
75
+
53
76
  ---
54
77
 
55
78
  ## ⚙️ How it Works
56
79
 
57
- 1. **Header Row**: The first line of the copied content includes the column header names.
80
+ 1. **Header Row**: The first line of the copied content includes the column header names (`headerName`, else `field`).
58
81
  2. **Data Transformation**: The grid iterates through the selected rows and columns:
59
- - **Exclusions**: Special internal columns (checkboxes, expanders, etc.) are excluded.
60
- - **Formatting**: If a column has a `valueFormatter`, it is applied to the raw value before copying.
61
- 3. **TSV Generation**: Fields are joined by tabs (`\t`), and rows are joined by newlines (`\n`).
62
- 4. **Clipboard API**: Uses the modern `navigator.clipboard.writeText` API.
82
+ - **Exclusions**: Hidden columns, grid system columns (checkbox, expand, reorder, the `groupingColDef` column) and `exportable: false` columns are excluded. Synthetic group rows are never copied.
83
+ - **Values**: `valueGetter` runs first, then `valueFormatter` (also for empty values, as in the cells). Empty values are written as empty cells.
84
+ 3. **TSV Generation**: Fields are joined by tabs (`\t`), and rows are joined by newlines (`\n`). A field that holds a tab, a line break or a double quote is wrapped in double quotes, with inner quotes doubled (`say "hi"` → `"say ""hi"""`), so the pasted table keeps its rows and columns.
85
+ 4. **Clipboard API**: Uses `navigator.clipboard.writeText`. When it is missing (plain `http:` pages) or rejects, a hidden-textarea `document.execCommand('copy')` fallback runs; focus and the page selection are restored afterwards.
86
+
87
+ > **Formulas**: copy reproduces what the grid shows and does **not** neutralise formulas. A copied value that starts with `=`, `+`, `-` or `@` may be evaluated as a formula when pasted into Excel, Google Sheets or LibreOffice. The file exports (CSV, Excel, and the others) do neutralise such text by prefixing `'`; use an export when the data comes from untrusted input.
63
88
 
64
89
  ---
65
90
 
@@ -4,11 +4,22 @@ This guide shows how to use Ant Design (or any other UI library) components with
4
4
 
5
5
  ## Using Ant Design Select for Page Size
6
6
 
7
+ Slots are typed `React.ComponentType<Record<string, unknown>>`, so the component takes `Record<string, unknown>` and narrows it to the props it reads (see [Slots API](../customization/slots-api.md#available-slots)).
8
+
7
9
  ```tsx
8
- import { Select } from 'antd';
10
+ import { Pagination, Select } from 'antd';
9
11
  import { DataGrid } from '@opencorestack/opengridx';
10
12
 
11
- function AntdPaginationComponent(props) {
13
+ type PaginationSlotProps = {
14
+ page: number;
15
+ pageSize: number;
16
+ rowCount: number;
17
+ pageSizeOptions: number[];
18
+ onPageChange: (page: number) => void;
19
+ onPageSizeChange: (pageSize: number) => void;
20
+ };
21
+
22
+ function AntdPaginationComponent(props: Record<string, unknown>) {
12
23
  const {
13
24
  page,
14
25
  pageSize,
@@ -16,12 +27,15 @@ function AntdPaginationComponent(props) {
16
27
  pageSizeOptions,
17
28
  onPageChange,
18
29
  onPageSizeChange
19
- } = props;
30
+ } = props as PaginationSlotProps;
20
31
 
21
- const pageCount = Math.max(1, Math.ceil(rowCount / pageSize));
22
- const currentPage = Math.min(page, pageCount - 1);
23
- const firstRowIndex = currentPage * pageSize;
32
+ // Client-side, `page` is already clamped to the last page.
33
+ const firstRowIndex = page * pageSize;
24
34
  const lastRowIndex = Math.min(firstRowIndex + pageSize, rowCount);
35
+ // Show the page size in use even when it is not one of pageSizeOptions, as the built-in pager does.
36
+ const sizes = pageSizeOptions.includes(pageSize)
37
+ ? pageSizeOptions
38
+ : [...pageSizeOptions, pageSize].sort((a, b) => a - b);
25
39
 
26
40
  return (
27
41
  <div style={{
@@ -38,7 +52,7 @@ function AntdPaginationComponent(props) {
38
52
  <Select
39
53
  value={pageSize}
40
54
  onChange={onPageSizeChange}
41
- options={pageSizeOptions.map(size => ({
55
+ options={sizes.map(size => ({
42
56
  value: size,
43
57
  label: size
44
58
  }))}
@@ -47,11 +61,11 @@ function AntdPaginationComponent(props) {
47
61
  </div>
48
62
 
49
63
  {/* Row count */}
50
- <span>{firstRowIndex + 1}–{lastRowIndex} of {rowCount}</span>
64
+ <span>{rowCount === 0 ? 0 : firstRowIndex + 1}–{lastRowIndex} of {rowCount}</span>
51
65
 
52
66
  {/* Ant Design Pagination */}
53
67
  <Pagination
54
- current={currentPage + 1}
68
+ current={page + 1}
55
69
  total={rowCount}
56
70
  pageSize={pageSize}
57
71
  onChange={(page) => onPageChange(page - 1)}
@@ -86,15 +100,16 @@ function MyApp() {
86
100
 
87
101
  ```tsx
88
102
  import { Select, MenuItem } from '@mui/material';
103
+ // PaginationSlotProps: the type alias from the example above
89
104
 
90
- function MuiPaginationComponent(props) {
91
- const { pageSize, pageSizeOptions, onPageSizeChange } = props;
105
+ function MuiPaginationComponent(props: Record<string, unknown>) {
106
+ const { pageSize, pageSizeOptions, onPageSizeChange } = props as PaginationSlotProps;
92
107
 
93
108
  return (
94
109
  <div>
95
110
  <Select
96
111
  value={pageSize}
97
- onChange={(e) => onPageSizeChange(e.target.value)}
112
+ onChange={(e) => onPageSizeChange(Number(e.target.value))}
98
113
  >
99
114
  {pageSizeOptions.map(size => (
100
115
  <MenuItem key={size} value={size}>
@@ -111,18 +126,23 @@ function MuiPaginationComponent(props) {
111
126
 
112
127
  Your custom pagination component will receive these props:
113
128
 
114
- - `page`: Current page index (0-based)
115
- - `pageSize`: Current page size
116
- - `rowCount`: Total number of rows
117
- - `pageSizeOptions`: Array of available page sizes
129
+ - `page`: Current page index (0-based). With client pagination it is clamped to the last page: when the rows shrink below the current page, the grid shows the last page and reports the corrected page once through `onPaginationModelChange`.
130
+ - `pageSize`: Current page size. It may be a value missing from `pageSizeOptions` (the built-in pager then lists it as an extra option).
131
+ - `rowCount`: Number of rows being paged: the filtered, unpinned rows (pinned rows show on every page), or the server total with `paginationMode="server"`.
132
+ - `pageSizeOptions`: Array of available page sizes (default `[10, 25, 50, 100]`). The default page size is 100, or the first option when 100 is not offered.
118
133
  - `onPageChange(newPage: number)`: Callback to change page
119
- - `onPageSizeChange(newPageSize: number)`: Callback to change page size
134
+ - `onPageSizeChange(newPageSize: number)`: Callback to change page size (also goes back to page 0)
135
+ - `localeText`: `{ paginationRowsPerPage, paginationOf, paginationPage }`, passed only when the grid's `localeText` prop is set. The built-in pager uses `paginationRowsPerPage` as the select's label and accessible name.
120
136
  - Any additional props from `slotProps.pagination`
121
137
 
138
+ The slot is rendered only while pagination is in effect: `pagination` is set, row grouping is off and `paginationMode` is not `"infinite"`. A `slots.footer` replaces it. List view uses it too.
139
+
122
140
  ## Other Customizable Slots
123
141
 
124
142
  ```tsx
125
143
  <DataGrid
144
+ rows={rows}
145
+ columns={columns}
126
146
  slots={{
127
147
  pagination: CustomPaginationComponent,
128
148
  noRowsOverlay: CustomNoRowsComponent,
@@ -140,4 +160,4 @@ Your custom pagination component will receive these props:
140
160
 
141
161
  ## Complete Example
142
162
 
143
- See `demo/examples/CustomPagination.tsx` for a complete working example.
163
+ See `demo/examples/CustomPagination/CustomPagination.tsx` for a complete working example.