@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,10 +1,10 @@
1
1
  # `<GridTooltip />`
2
2
 
3
- The `GridTooltip` component provides a lightweight, accessible tooltip for any HTML or React element. It is commonly used within columns, actions, or toolbars to display extra information when a user hovers over a component.
3
+ The `GridTooltip` component provides a lightweight, accessible tooltip for any HTML or React element. It is commonly used within columns, actions, or toolbars to display extra information when a user hovers over or focuses a component.
4
4
 
5
5
  ## 📑 Overview
6
- - **Portal Rendering**: Tooltips are rendered at the end of `document.body` for unobstructed positioning.
7
- - **Dynamic Calculation**: Tooltips calculate their placement relative to the target element on hover.
6
+ - **Portal Rendering**: Tooltips are portalled into the closest `DataGridThemeProvider` (`.ogx-theme-provider`), so they use the theme's overlay colours like the grid's menus and panels, or into `document.body` outside a provider. They are `position: fixed` and follow the target when the page or a container scrolls.
7
+ - **Dynamic Calculation**: Tooltips calculate their placement relative to the target element when they open.
8
8
  - **Support for React Elements**: It wraps its child element to attach event listeners and calculate position.
9
9
  - **Customizable Delays**: Support for `enterDelay` and `leaveDelay`.
10
10
 
@@ -36,6 +36,14 @@ You can choose from four different placements: `top`, `bottom`, `left`, or `righ
36
36
  <GridTooltip title="Bottom Placement" placement="bottom">
37
37
  <span>Hover me</span>
38
38
  </GridTooltip>
39
+
40
+ <GridTooltip title="Left Placement" placement="left">
41
+ <span>Hover me</span>
42
+ </GridTooltip>
43
+
44
+ <GridTooltip title="Right Placement" placement="right">
45
+ <span>Hover me</span>
46
+ </GridTooltip>
39
47
  ```
40
48
 
41
49
  ---
@@ -47,8 +55,8 @@ You can choose from four different placements: `top`, `bottom`, `left`, or `righ
47
55
  | **`title`** | `React.ReactNode` | — | Content (text, JSX, etc.) to show inside the tooltip. |
48
56
  | **`children`** | `React.ReactElement` | — | The target element the tooltip is attached to. |
49
57
  | **`placement`** | `'top'`, `'bottom'`, `'left'`, `'right'` | `'top'` | Direction where the tooltip appears. |
50
- | **`enterDelay`** | `number` | `200` | Delay (ms) before showing the tooltip. |
51
- | **`leaveDelay`** | `number` | `0` | Delay (ms) before hiding the tooltip. |
58
+ | **`enterDelay`** | `number` | `200` | Delay (ms) before showing the tooltip on hover or focus. |
59
+ | **`leaveDelay`** | `number` | `0` | Delay (ms) before hiding the tooltip on mouse leave or blur. |
52
60
 
53
61
  ---
54
62
 
@@ -61,5 +69,6 @@ The internal tooltip component is not replaceable via the slots API. For column-
61
69
  ```
62
70
 
63
71
  ## 📝 Accessibility
64
- - Tooltips are triggered by mouse hover events (`onMouseEnter`/`onMouseLeave`).
65
- - It is recommended to use descriptive `aria-label` tags on the child element for screen readers, as tooltips are visually focused.
72
+ - The tooltip opens on hover and on keyboard focus of the child (focus events bubble to the wrapper), and closes on mouse leave, blur or Escape.
73
+ - The open tooltip has `role="tooltip"` and an id, and the child's `aria-describedby` references it (added to any `aria-describedby` the child already has) while it is open.
74
+ - The tooltip describes its child; it does not name it. Icon-only buttons still need an `aria-label`.
@@ -4,57 +4,85 @@ The Slots API allows you to replace any built-in component in the DataGrid with
4
4
 
5
5
  ## Available Slots
6
6
 
7
+ Every slot is typed `React.ComponentType<Record<string, unknown>>`, and every `slotProps` entry is `Record<string, unknown>`. A slot component therefore takes `props: Record<string, unknown>` and narrows the props it uses; the examples below cast to a type alias describing the props the grid passes. (A component declared with its own required props, such as `(props: { page: number }) => …`, is rejected by TypeScript.) The props from `slotProps.<slot>` are spread last, so they can override the grid's props.
8
+
7
9
  ### 1. `toolbar`
8
10
  Add a custom toolbar above the grid (commonly used for export buttons, filters, actions).
9
11
 
10
12
  **Props received:**
11
13
  ```typescript
12
14
  {
13
- // Any props from slotProps.toolbar
15
+ apiRef: React.MutableRefObject<GridApi>;
16
+ columns: GridColDef[]; // every column in display order, hidden ones included
17
+ baseColumns: GridColDef[]; // the `columns` prop as passed
18
+ aggregationModel: GridAggregationModel;
19
+ onAggregationModelChange: (model: GridAggregationModel) => void;
20
+ pivotModel?: GridPivotModel; // only when pivotMode, pivotModel or onPivotModelChange is set
21
+ onPivotModelChange?: (model: GridPivotModel) => void;
22
+ filterModel: GridFilterModel;
23
+ onFilterModelChange: (model: GridFilterModel) => void;
24
+ columnVisibilityModel: GridColumnVisibilityModel;
25
+ onColumnVisibilityModelChange: (model: GridColumnVisibilityModel) => void;
26
+ onColumnReorder?: (fromField: string, toField: string) => void; // undefined with disableColumnReorder
27
+ onColumnOrderReset?: () => void; // undefined with disableColumnReorder
28
+ forceColumnsOpen: boolean; // the column menu's "Manage columns" asked for the Columns panel
29
+ onColumnsPanelClose: () => void;
30
+ // ...plus any props from slotProps.toolbar
14
31
  }
15
32
  ```
16
33
 
34
+ These match `GridToolbarProps`, so `slots={{ toolbar: GridToolbar }}` gives the built-in toolbar.
35
+
17
36
  **Example:**
18
37
  ```tsx
19
- import { exportToCsv, exportToExcel } from '@opencorestack/opengridx';
38
+ import { DataGrid, exportToCsv, exportToExcel, type GridApi, type GridColDef } from '@opencorestack/opengridx';
39
+ import type { MutableRefObject } from 'react';
40
+
41
+ type ToolbarSlotProps = { apiRef: MutableRefObject<GridApi>; columns: GridColDef[] };
20
42
 
21
- function CustomToolbar({ rows, columns }) {
43
+ function CustomToolbar(props: Record<string, unknown>) {
44
+ const { apiRef, columns } = props as ToolbarSlotProps;
45
+ const exportRows = () => apiRef.current.getAllFilteredRows();
22
46
  return (
23
47
  <div style={{ padding: '12px', display: 'flex', gap: '8px' }}>
24
- <button onClick={() => exportToCsv(rows, columns)}>
48
+ <button type="button" onClick={() => exportToCsv(exportRows(), columns)}>
25
49
  Export CSV
26
50
  </button>
27
- <button onClick={() => exportToExcel(rows, columns)}>
51
+ <button type="button" onClick={() => exportToExcel(exportRows(), columns)}>
28
52
  Export Excel
29
53
  </button>
30
54
  </div>
31
55
  );
32
56
  }
33
57
 
34
- <DataGrid
35
- slots={{ toolbar: CustomToolbar }}
36
- slotProps={{ toolbar: { rows, columns } }}
37
- />
58
+ <DataGrid rows={rows} columns={columns} slots={{ toolbar: CustomToolbar }} />
38
59
  ```
39
60
 
61
+ `toolbar` accepts any React component type: function, class, `React.memo`, `forwardRef` or `lazy`. A plain function toolbar may be defined inline inside your own component: the grid keys it on its source code, so it keeps its state (open panels, typed search) when your component re-renders and remounts only when you pass a toolbar with different code. A class, memo or forwardRef toolbar is rendered as a normal element, so define it outside render to keep its identity stable.
62
+
40
63
  ### 2. `pagination`
41
- Replace the default pagination component.
64
+ Replace the default pagination component. It is rendered only while pagination is in effect (`pagination` set, no row grouping, `paginationMode` not `"infinite"`), is not rendered when a `footer` slot is set, and is used by list view too.
42
65
 
43
66
  **Props received:**
44
67
  ```typescript
45
68
  {
46
- page: number; // Current page (0-based)
69
+ page: number; // Current page (0-based), clamped to the last page client-side
47
70
  pageSize: number; // Current page size
48
- rowCount: number; // Total number of rows
49
- pageSizeOptions: number[]; // Available page sizes
71
+ rowCount: number; // Rows being paged: filtered, unpinned rows; the server total in paginationMode="server"
72
+ pageSizeOptions: number[]; // Available page sizes (default [10, 25, 50, 100])
50
73
  onPageChange: (page: number) => void;
51
- onPageSizeChange: (size: number) => void;
74
+ onPageSizeChange: (size: number) => void; // also resets to page 0
75
+ localeText?: Pick<GridLocaleText, 'paginationRowsPerPage' | 'paginationOf' | 'paginationPage'>; // only when the localeText prop is set
76
+ // ...plus any props from slotProps.pagination
52
77
  }
53
78
  ```
54
79
 
55
80
  **Example:**
56
81
  ```tsx
57
82
  <DataGrid
83
+ rows={rows}
84
+ columns={columns}
85
+ pagination
58
86
  slots={{ pagination: CustomPagination }}
59
87
  slotProps={{ pagination: { customProp: 'value' } }}
60
88
  />
@@ -66,7 +94,7 @@ Replace the empty state shown when there are no rows.
66
94
  **Props received:**
67
95
  ```typescript
68
96
  {
69
- // Any props from slotProps.noRowsOverlay
97
+ // Only the props from slotProps.noRowsOverlay
70
98
  }
71
99
  ```
72
100
 
@@ -82,6 +110,8 @@ function CustomNoRows() {
82
110
  }
83
111
 
84
112
  <DataGrid
113
+ rows={rows}
114
+ columns={columns}
85
115
  slots={{ noRowsOverlay: CustomNoRows }}
86
116
  />
87
117
  ```
@@ -92,7 +122,7 @@ Replace the loading state overlay.
92
122
  **Props received:**
93
123
  ```typescript
94
124
  {
95
- // Any props from slotProps.loadingOverlay
125
+ // Only the props from slotProps.loadingOverlay
96
126
  }
97
127
  ```
98
128
 
@@ -108,44 +138,79 @@ function CustomLoader() {
108
138
  }
109
139
 
110
140
  <DataGrid
141
+ rows={rows}
142
+ columns={columns}
143
+ loading={isLoading}
111
144
  slots={{ loadingOverlay: CustomLoader }}
112
145
  />
113
146
  ```
114
147
 
115
148
  ### 5. `footer`
116
- Replace the entire footer section (including pagination).
149
+ Replace the entire footer section below the grid, including the default pagination. Rendered whenever the slot is set, including while row grouping is active, except in list view.
117
150
 
118
- **Props received:**
151
+ **Props received** (`GridFooterSlotProps`, v3.0+):
119
152
  ```typescript
120
153
  {
121
- // Any props from slotProps.footer
154
+ apiRef: React.MutableRefObject<GridApi>;
155
+ aggregationModel: GridAggregationModel;
156
+ aggregationResult: GridAggregationResult | null; // null when aggregationModel is empty, and in pivot mode
157
+ rowCount: number; // see below
158
+ pagination: boolean; // effective: false while row grouping is active or with paginationMode="infinite"
159
+ paginationModel: GridPaginationModel; // the model as held (not clamped to the last page)
160
+ pageSizeOptions: number[];
161
+ onPaginationModelChange: (model: GridPaginationModel) => void;
162
+ // ...plus any props from slotProps.footer
122
163
  }
123
164
  ```
124
165
 
125
- **Example:**
166
+ `rowCount` is:
167
+ - with `paginationMode="server"`: the server total (the `dataSource` response's `rowCount`, else the `rowCount` prop, else the number of loaded rows);
168
+ - otherwise, in a flat grid: the pager's `rowCount`, the filtered rows **without** pinned rows (v3.0+; before, pinned rows were counted). With `filterMode="server"` it is every row the grid holds; with `paginationMode="infinite"` the rows loaded so far; in pivot mode the pivot rows including the Grand Total row;
169
+ - under row grouping and tree data: the data rows that pass the client filter (never group rows), whatever is expanded.
170
+
171
+ **Example — a persistent grand-total bar that stays in sync with filtering:**
126
172
  ```tsx
127
- function CustomFooter() {
173
+ import type { GridFooterSlotProps } from '@opencorestack/opengridx';
174
+
175
+ // Type the props you read; the grid checks them against GridFooterSlotProps.
176
+ function TotalsFooter({ aggregationResult, rowCount }: Pick<GridFooterSlotProps, 'aggregationResult' | 'rowCount'>) {
177
+ const total = aggregationResult?.amount;
128
178
  return (
129
179
  <div style={{ padding: '12px', borderTop: '1px solid #e0e0e0' }}>
130
- <span>Custom footer content</span>
180
+ {rowCount} rows · Total: {typeof total === 'number' ? total.toLocaleString() : '—'}
131
181
  </div>
132
182
  );
133
183
  }
134
184
 
135
185
  <DataGrid
136
- slots={{ footer: CustomFooter }}
186
+ rows={rows}
187
+ columns={columns}
188
+ aggregationModel={{ amount: 'sum' }}
189
+ slots={{ footer: TotalsFooter }}
137
190
  />
138
191
  ```
139
192
 
193
+ > `noRowsOverlay`, `loadingOverlay` and `footer` were declared in the types before v2.1 but never rendered. They are wired from v2.1.
194
+
140
195
  ## Complete Example
141
196
 
142
197
  ```tsx
143
198
  import { Select, Pagination, Spin, Empty } from 'antd';
144
199
  import { DataGrid } from '@opencorestack/opengridx';
145
200
 
146
- function AntdPagination(props) {
147
- const { page, pageSize, rowCount, pageSizeOptions, onPageChange, onPageSizeChange } = props;
148
-
201
+ type PaginationSlotProps = {
202
+ page: number;
203
+ pageSize: number;
204
+ rowCount: number;
205
+ pageSizeOptions: number[];
206
+ onPageChange: (page: number) => void;
207
+ onPageSizeChange: (pageSize: number) => void;
208
+ };
209
+
210
+ function AntdPagination(props: Record<string, unknown>) {
211
+ const { page, pageSize, rowCount, pageSizeOptions, onPageChange, onPageSizeChange } =
212
+ props as PaginationSlotProps;
213
+
149
214
  return (
150
215
  <div style={{ display: 'flex', gap: '16px', padding: '12px 16px', justifyContent: 'flex-end' }}>
151
216
  <Select
@@ -199,18 +264,17 @@ function MyApp() {
199
264
 
200
265
  ## TypeScript Support
201
266
 
202
- For full type safety, define your component props:
267
+ The library exports no props type for the pagination, overlay or footer slots (the toolbar's props are `GridToolbarProps`). Declare a type alias for the props you read and narrow `Record<string, unknown>` to it, as above. Use a `type` alias rather than an `interface`: an interface has no index signature, so `props as MyInterface` needs `as unknown as`.
203
268
 
204
269
  ```typescript
205
- import type { PaginationProps } from '@opencorestack/opengridx';
206
-
207
- interface CustomPaginationProps extends PaginationProps {
208
- customProp?: string;
209
- }
270
+ type CustomPaginationProps = PaginationSlotProps & {
271
+ customProp?: string; // from slotProps.pagination
272
+ };
210
273
 
211
- const CustomPagination: React.FC<CustomPaginationProps> = (props) => {
274
+ function CustomPagination(props: Record<string, unknown>) {
275
+ const { page, customProp } = props as CustomPaginationProps;
212
276
  // Your implementation
213
- };
277
+ }
214
278
  ```
215
279
 
216
280
  ## Best Practices
@@ -223,5 +287,5 @@ const CustomPagination: React.FC<CustomPaginationProps> = (props) => {
223
287
 
224
288
  ## See Also
225
289
 
226
- - [Custom Pagination Guide](./CUSTOM_PAGINATION.md) - Detailed examples with Ant Design and MUI
227
- - [DataGrid Props](../src/OpenGridX/types/index.ts) - Full TypeScript definitions
290
+ - [Custom Pagination Guide](../features/custom-pagination.md) - Detailed examples with Ant Design and MUI
291
+ - [DataGrid Props](../../lib/types/index.ts) - Full TypeScript definitions
@@ -11,6 +11,7 @@ OpenGridX ships with a complete design-token system built on CSS custom properti
11
11
 
12
12
  ```tsx
13
13
  import { DataGrid, DataGridThemeProvider } from '@opencorestack/opengridx';
14
+ import '@opencorestack/opengridx/styles'; // required: the tokens only take effect through the grid's stylesheet
14
15
 
15
16
  const myTheme = {
16
17
  colors: {
@@ -38,11 +39,27 @@ export default function App() {
38
39
 
39
40
  The `DataGridThemeProvider` resolves each key in the `GridTheme` object to its corresponding `--ogx-*` CSS variable and applies it as an inline style on a wrapper `div`. **No CSS-in-JS, no runtime overhead.**
40
41
 
42
+ How the provider builds the variables (v3.0+):
43
+
44
+ - **It pins a complete palette.** It starts from a full light palette (or a full dark one with `mode: 'dark'`) that sets every colour token, neutral scale included, and then applies your keys on top. A light theme therefore stays light, and readable, when the operating system is in dark mode, and a dark theme needs no other CSS.
45
+ - **Accents follow `colors.primary`.** The toolbar's primary button, active chips, search focus ring, selected menu items, selected rows and the keyboard focus ring are all derived from the `colors.primary*` tokens (with `var()` / `color-mix()`), so a theme that only sets `colors.primary` recolours all of them. Set the specific `toolbar.*`, `overlays.*` or `grid.*` key to override one.
46
+ - **Toolbar tokens follow the grid surface.** Unless you set `toolbar.*`, the toolbar uses the header background, header text and grid border colours.
47
+ - **Row and header heights reach the layout.** `grid.rowHeightStandard` / `grid.headerHeight` (and `rowHeightCompact` / `rowHeightComfortable` with `density`) are read by the grid itself, which virtualizes from them. They must be pixel values (`'36px'` or `'36'`); other units are ignored. The `rowHeight` / `headerHeight` props win over the theme.
48
+
49
+ > **Sizing:** the wrapper `div` (`.ogx-theme-provider`) is a normal auto-height block. If the grid uses `height="100%"`, pass `style={{ height: '100%' }}` (or a `className` that sets a height) to the provider too. Otherwise the wrapper grows to fit every row and row virtualization is effectively off. See [Virtualization → The grid needs a bounded height](../features/virtualization.md#the-grid-needs-a-bounded-height).
50
+
41
51
  ---
42
52
 
43
53
  ## Dark Mode
44
54
 
45
- The grid automatically switches to its dark palette via `@media (prefers-color-scheme: dark)`. If your app manages dark mode manually (e.g. via a `data-theme` attribute), override the dark tokens with:
55
+ Without a provider, the grid automatically switches to its dark palette via `@media (prefers-color-scheme: dark)`. Inside a `DataGridThemeProvider` the theme decides instead: `darkTheme` (or any theme with `mode: 'dark'`) is dark and every other theme is light, whatever the operating-system setting. To follow the OS with the provider, pick the theme in your app:
56
+
57
+ ```tsx
58
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
59
+ <DataGridThemeProvider theme={prefersDark ? darkTheme : roseTheme}>…</DataGridThemeProvider>
60
+ ```
61
+
62
+ If your app manages dark mode manually without the provider (e.g. via a `data-theme` attribute), override the dark tokens with:
46
63
 
47
64
  ```css
48
65
  [data-theme="dark"] {
@@ -60,20 +77,23 @@ The grid automatically switches to its dark palette via `@media (prefers-color-s
60
77
 
61
78
  ```ts
62
79
  interface GridTheme {
63
- colors?: GridThemeColors;
80
+ mode?: 'light' | 'dark'; // base palette, default 'light'
81
+ colors?: GridThemeColors; // primary*, secondary*, success/warning/error/info, white, black, gray: { 50…900 }
64
82
  typography?: GridThemeTypography;
65
83
  spacing?: GridThemeSpacing;
66
84
  borders?: GridThemeBorders;
67
85
  shadows?: GridThemeShadows;
68
- grid?: GridThemeGrid;
86
+ grid?: GridThemeGrid; // surfaces, heights (px), cellPaddingX/Y, cellFontSize, headerFontSize, …
69
87
  toolbar?: GridThemeToolbar;
70
88
  overlays?: GridThemeOverlays;
71
89
  scrollbar?: GridThemeScrollbar;
72
- skeleton?: GridThemeSkeleton;
90
+ skeleton?: GridThemeSkeleton; // baseColor, highlightColor
73
91
  transitions?: GridThemeTransitions;
74
92
  }
75
93
  ```
76
94
 
95
+ All sub-theme types (`GridThemeColors`, `GridThemeGrayScale`, `GridThemeTypography`, `GridThemeSpacing`, `GridThemeBorders`, `GridThemeShadows`, `GridThemeGrid`, `GridThemeToolbar`, `GridThemeOverlays`, `GridThemeScrollbar`, `GridThemeSkeleton`, `GridThemeTransitions`) are exported from the package.
96
+
77
97
  ---
78
98
 
79
99
  ## All CSS Tokens
@@ -110,8 +130,8 @@ interface GridTheme {
110
130
 
111
131
  | Token | Default | Description |
112
132
  |---|---|---|
113
- | `--ogx-font-family` | `'Inter', system-ui, sans-serif` | Primary UI font |
114
- | `--ogx-font-family-mono` | `'JetBrains Mono', 'Fira Code', monospace` | Monospace (code cells, IDs) |
133
+ | `--ogx-font-family` | `'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', …, sans-serif` | Primary UI font |
134
+ | `--ogx-font-family-mono` | `'JetBrains Mono', 'Fira Code', 'Courier New', Courier, monospace` | Monospace (code cells, IDs) |
115
135
  | `--ogx-font-size-xs` | `0.75rem` (12px) | Extra-small |
116
136
  | `--ogx-font-size-sm` | `0.875rem` (14px) | Small |
117
137
  | `--ogx-font-size-md` | `1rem` (16px) | Medium |
@@ -165,16 +185,16 @@ interface GridTheme {
165
185
 
166
186
  | Token | Default | Description |
167
187
  |---|---|---|
168
- | `--ogx-row-height` | `52px` | Default row height |
169
- | `--ogx-header-height` | `56px` | Column header row height |
170
- | `--ogx-grid-row-height-compact` | `32px` | Compact density preset |
171
- | `--ogx-grid-row-height-standard` | `52px` | Standard density preset |
172
- | `--ogx-grid-row-height-comfortable` | `72px` | Comfortable density preset |
173
- | `--ogx-grid-header-height` | `56px` | Header height (grid-specific) |
188
+ | `--ogx-row-height` | `52px` | Row height. The grid sets it inline on `.ogx` from the `rowHeight` / `density` props (or the provider's `grid.rowHeight*`); read it in custom CSS, don't set it. |
189
+ | `--ogx-header-height` | `56px` | Column header row height; set inline by the grid like `--ogx-row-height`. |
190
+ | `--ogx-grid-row-height-compact` | `32px` | Informational copy of the theme's `grid.rowHeightCompact`; the grid reads the theme value, not this variable |
191
+ | `--ogx-grid-row-height-standard` | `52px` | Informational copy of `grid.rowHeightStandard` |
192
+ | `--ogx-grid-row-height-comfortable` | `72px` | Informational copy of `grid.rowHeightComfortable` |
193
+ | `--ogx-grid-header-height` | `56px` | Informational copy of `grid.headerHeight` |
174
194
  | `--ogx-grid-cell-padding-x` | `10px` | Horizontal cell padding |
175
195
  | `--ogx-grid-cell-padding-y` | `8px` | Vertical cell padding |
176
- | `--ogx-grid-cell-font-size` | `13px` | Body cell font size |
177
- | `--ogx-grid-header-font-size` | `13px` | Header cell font size |
196
+ | `--ogx-grid-cell-font-size` | `13px` | Body cell font size (`grid.cellFontSize`) |
197
+ | `--ogx-grid-header-font-size` | `13px` | Header cell font size (`grid.headerFontSize`) |
178
198
  | `--ogx-grid-line-height` | `var(--ogx-line-height-normal)` | Row line height |
179
199
 
180
200
  ### Grid Colors
@@ -198,6 +218,26 @@ interface GridTheme {
198
218
  | `--ogx-checkbox-bg` | — | `#1e293b` | Checkbox background (dark only) |
199
219
  | `--ogx-checkbox-border` | — | `#64748b` | Checkbox border (dark only) |
200
220
 
221
+ ### Toolbar, Overlay and Scrollbar Colors
222
+
223
+ Read with a fallback, so a grid without a provider looks the same as before. Inside a provider they come from `toolbar.*`, `overlays.*` and `scrollbar.*`, or from the derivations described above.
224
+
225
+ | Token | Theme key | Default | Used by |
226
+ |---|---|---|---|
227
+ | `--ogx-toolbar-background` / `-text` / `-border` | `toolbar.background` / `text` / `border` | header background / header text / grid border | Toolbar bar |
228
+ | `--ogx-toolbar-btn-bg` / `-btn-hover` / `-btn-text` | `toolbar.buttonBackground` / `buttonHoverBackground` / `buttonText` | transparent / header hover / header text | Toolbar icon buttons |
229
+ | `--ogx-toolbar-btn-primary-bg` / `-hover` / `-text` | `toolbar.buttonPrimary*` | primary / primary-dark / white | Active summary pills |
230
+ | `--ogx-toolbar-btn-danger-bg` / `-hover` / `-text` | `toolbar.buttonDanger*` | red tints | Clear buttons |
231
+ | `--ogx-toolbar-input-bg` / `-text` / `-border` | `toolbar.inputBackground` / `inputText` / `inputBorder` | grid background / row text / gray-300 | Quick search |
232
+ | `--ogx-toolbar-input-focus-border` / `-focus-shadow` | `toolbar.inputFocus*` | primary / 12% primary | Focused quick search |
233
+ | `--ogx-toolbar-chip-bg` / `-text` | `toolbar.chipBackground` / `chipText` | transparent / row text | Summary pills |
234
+ | `--ogx-toolbar-chip-active-bg` / `-text` | `toolbar.chipActive*` | 12% primary / primary-dark | Active pills, filter chips, pivot chips |
235
+ | `--ogx-overlay-*` | `overlays.*` | white surface, gray border | Menus, panels, tooltips |
236
+ | `--ogx-overlay-item-selected-bg` / `-text` | `overlays.itemSelected*` | 8% primary / primary-dark | Selected menu item |
237
+ | `--ogx-overlay-item-danger-bg` / `-text` | `overlays.itemDanger*` | red tints | Remove-condition button in the filter panel |
238
+ | `--ogx-scrollbar-thumb` / `-track` / `-size` | `scrollbar.thumbColor` / `trackColor` / `size` | gray-300 / transparent / 8px | Grid viewport scrollbars |
239
+ | `--ogx-skeleton-base` / `-highlight` | `skeleton.baseColor` / `highlightColor` | light grays | Loading skeleton rows |
240
+
201
241
  ### Transitions
202
242
 
203
243
  | Token | Default | Description |
@@ -247,17 +287,25 @@ You can theme the grid without the `DataGridThemeProvider` by overriding variabl
247
287
 
248
288
  ## Built-in Presets
249
289
 
250
- OpenGridX ships with two presets accessible from `'opengridx'`:
290
+ OpenGridX exports five presets as named exports:
251
291
 
252
292
  ```tsx
253
- import { DataGridThemeProvider, themes } from '@opencorestack/opengridx';
293
+ import { DataGridThemeProvider, darkTheme } from '@opencorestack/opengridx';
294
+ // Also: roseTheme, emeraldTheme, amberTheme, compactTheme
254
295
 
255
- // Available: themes.light, themes.dark, themes.compact, themes.comfortable
256
- <DataGridThemeProvider theme={themes.dark}>
296
+ <DataGridThemeProvider theme={darkTheme}>
257
297
  <DataGrid ... />
258
298
  </DataGridThemeProvider>
259
299
  ```
260
300
 
301
+ | Preset | What it sets |
302
+ |---|---|
303
+ | `darkTheme` | `mode: 'dark'` plus a dark grid, toolbar and overlay palette |
304
+ | `roseTheme`, `emeraldTheme`, `amberTheme` | A brand primary colour and tinted header / selection backgrounds on the light palette |
305
+ | `compactTheme` | 36px rows, 40px header (28px rows with `density="compact"`), tighter cell padding and 12px cell / header text |
306
+
307
+ Presets are plain objects, so you can combine them: `theme={{ ...compactTheme, ...roseTheme, grid: { ...compactTheme.grid, ...roseTheme.grid } }}`.
308
+
261
309
  ---
262
310
 
263
311
  ## Example: Custom Purple Theme
@@ -21,19 +21,45 @@ Aggregation allows you to calculate summary values (Sum, Avg, etc.) for groups o
21
21
  />
22
22
  ```
23
23
 
24
+ ### What the footer totals
25
+
26
+ The footer (and `apiRef.current.getAggregationResult()`, and the `aggregationResult` passed to `slots.footer`) always aggregates **every data row that passes the current filter**. That holds with or without row grouping or tree data, and however many groups are expanded. Group rows show their own subtotals inline; the grand total is computed from the underlying rows, never from those subtotals. (Before v3.0 the footer aggregated the *visible* rows under grouping, so expanding a group double-counted it, and `count` / `avg` were wrong even when collapsed.)
27
+
28
+ Cells are read the way the grid shows them: a column with a `valueGetter` (for example `total = price × qty`) totals the computed values, not `row[field]`.
29
+
30
+ ### Footer layout
31
+
32
+ The footer is laid out exactly like the rows: a hidden column has no footer cell, pinned columns keep their totals under the pinned column (left-pinned first, right-pinned last), and the totals stay aligned while scrolling horizontally, including when column virtualization renders only part of the columns and when the checkbox, detail-panel or row-reorder column is (or is not) pinned.
33
+
34
+ ### Server-side totals
35
+
36
+ When a `dataSource` drives the rows (server or infinite pagination, server sorting, or server filtering), the grid only holds the rows the server returned, so it never totals them itself. The footer shows the `aggregationResults` returned by `getRows`, or the result of `dataSource.getAggregations`.
37
+
38
+ - A result is only shown for the request it answered: after changing the aggregation model, filter or sort, the footer shows `—` until the new totals arrive, and keeps showing `—` if that request fails (the error is logged). It never shows the previous model's or filter's totals under the new label.
39
+ - While a `getAggregations` request is pending the footer has `aria-busy="true"` and the class `ogx__aggregation-footer--loading`.
40
+ - An inline `aggregationModel={{ salary: 'sum' }}` is a new object on every render of your component. The grid compares the model by content, so re-rendering with an equal model neither refetches nor clears the totals.
41
+
42
+ If the server provides neither `aggregationResults` nor `getAggregations`, the footer shows `—`.
43
+
24
44
  ### Configuration
25
- You can control where the aggregation results appear:
26
- - **`footer`**: A sticky row at the bottom of the grid.
27
- - **`inline`**: Values displayed within group headers (when Row Grouping is active).
28
- - **`null`**: Skip aggregation for this group.
45
+ `getAggregationPosition(groupNode)` controls where aggregation results appear. It is called for every group node (with its current `isExpanded`) and once with `null` for the grand total:
46
+ - **`inline`** (default for groups): values on the group row.
47
+ - **`footer`**: values on a subtotal row after the group's children while the group is expanded; the group row shows none. While the group is collapsed there is no subtotal row, so the values stay on the group row. For the grand total (`null`), `'footer'` (the default) shows the footer row.
48
+ - **`null`**: no aggregation for that group (on screen and in `getGroupedExportRows()`); for the grand total, the footer row is hidden.
29
49
 
30
50
  ```tsx
31
51
  <DataGrid
52
+ rows={rows}
53
+ columns={columns}
32
54
  aggregationModel={model}
33
- getAggregationPosition={(groupNode) => groupNode ? 'inline' : 'footer'}
55
+ rowGroupingModel={['department']}
56
+ // Subtotal rows below expanded groups, inline values on collapsed ones, and the grand total.
57
+ getAggregationPosition={() => 'footer'}
34
58
  />
35
59
  ```
36
60
 
61
+ Group subtotals and the `(n)` count next to a group label cover only the rows that pass the filter, like the grand total; a group with no matching row is not shown.
62
+
37
63
  ---
38
64
 
39
65
  ## 🔄 Pivot Mode
@@ -51,29 +77,69 @@ const pivotModel: GridPivotModel = {
51
77
  };
52
78
 
53
79
  <DataGrid
80
+ rows={rows}
81
+ columns={columns}
54
82
  pivotMode={true}
55
83
  pivotModel={pivotModel}
56
84
  />
57
85
  ```
58
86
 
87
+ `aggFn` is a `GridPivotAggFn`: `'sum' | 'avg' | 'count' | 'min' | 'max'`. `unique` is available to `aggregationModel`, but not to pivot value fields.
88
+
59
89
  ### Key Concepts
60
90
  - **Row Fields**: These become the static vertical axis of your pivot table.
61
91
  - **Column Fields**: Unique values in these fields become new columns.
62
92
  - **Value Fields**: The data that gets summarized at the intersection of row and column fields.
63
93
 
94
+ The generated value columns are named `<field>␟<aggFn>` (for example `'salary\u001fsum'`) without column fields, and `<column key>␟<field>␟<aggFn>` with them.
95
+
96
+ ### Rows, filtering and sorting
97
+
98
+ - **Filtering.** Filters on source fields (including the row fields) and the quick filter are applied to the **source rows before pivoting**, so every pivot value and the Grand Total reflect them. In pivot mode the quick filter searches every filterable source column. A condition on a generated value column (for example `'salary\u001fsum' > 1000`) is applied to the pivot rows. If a filter model mixes both kinds with `logicOperator: 'or'`, each kind is applied in its own stage, so the result is as if they were joined with `and`.
99
+ - **Sorting** orders the pivot data rows. The Grand Total row always stays last.
100
+ - **Pivot row ids** are derived from the row-field values (`'__pivot_row__:["Eng"]'`, `'__pivot_row__:["North","Q1"]'` for two row fields), so they never collide with source row ids and stay the same for the same group when the data changes (v3.0+; they were `0, 1, …`).
101
+ - **Grand Total** is the last row (id `'__pivot_grand_total__'`). It totals the pivot rows shown, recomputed from the raw values (an average is the average of all underlying values, not of the row averages). It is not selected by the header select-all checkbox. When no source row is left, there is no Grand Total row and the no-rows overlay is shown; the value columns stay.
102
+ - **Row ids.** Pivot rows carry their own ids (the `'__pivot_row__:…'` ids above and the Grand Total id). Your `getRowId` is not applied to them.
103
+ - **Groups** are formed by each value's displayed string, as in row grouping, so `1` and `'1'` are one group. `null`, `undefined` and `''` form one blank group, labelled `null`.
104
+
105
+ ### Generated columns
106
+
107
+ - **Row-label columns** keep the source column's `valueFormatter`, `renderCell`, `type`, alignment and `valueOptions` (the formatter and renderer receive a source row of that group). They always show, even if `columnVisibilityModel` hides the source column of the same field, and are `hideable: false`.
108
+ - **Value columns** are formatted like every other aggregate (see [Formatting Totals](#-formatting-totals)).
109
+ - **Column keys** are ordered by value: numbers and dates numerically, strings naturally (`'9'` before `'10'`), blanks last. Column-field header labels use the source column's `valueFormatter` (a boolean column formatted as Yes / No reads `Active: Yes — Salary (sum)`).
110
+ - **Column rules.** A row or column field on a `groupable: false` column is skipped (as in row grouping), and a value field on an `aggregable: false` column, or whose function the column's `availableAggregationFunctions` does not allow, is skipped. Listing the same field and function twice produces one column. The toolbar's pivot panel offers only what these rules allow and never offers an `aggregable: false` column as a value.
111
+ - **Column order.** The generated columns keep an order of their own: reordering them (in the header or the Columns panel) does not change your column order, and a controlled `columnOrder` (which names source columns) does not apply to them. When pivot mode is turned off, the previous column order is back.
112
+
113
+ ### Not combined with pivot mode
114
+
115
+ - **Tree data and row grouping** are turned off while pivoting (the pivot rows are already grouped by the row fields). A development warning says so.
116
+ - **`dataSource`**: pivoting is client-side, and a server-driven grid only holds one page of rows, so `pivotMode` is ignored when a `dataSource` is set (development warning). Pivot on the server, or pass the full dataset as `rows`.
117
+ - **`aggregationModel`**: the footer is not shown, `apiRef.current.getAggregationResult()` returns `null` and `slots.footer` receives `aggregationResult: null`; the Grand Total row is the pivot's aggregate. The toolbar's Summaries panel does not list generated pivot columns.
118
+
64
119
  ---
65
120
 
66
121
  ## ⚙️ Aggregation Functions
67
122
 
68
- Built-in functions available:
123
+ Built-in functions available (the same functions drive the footer, group rows, pivot and exports):
69
124
  - `sum`: Total of numeric values.
70
125
  - `avg`: Arithmetic mean.
71
126
  - `min`: Smallest value.
72
127
  - `max`: Largest value.
73
- - `count`: Number of items.
74
- - `unique`: Number of unique items.
128
+ - `count`: Number of non-empty values.
129
+ - `unique`: Number of distinct non-empty values.
130
+
131
+ `sum`, `avg`, `min` and `max` use numbers, numeric strings and dates. Empty values (`null`, `undefined`, and strings that are empty or only whitespace), booleans and other values are ignored, so a blank cell never counts as `0`. `count` and `unique` ignore empty values too. `min` and `max` of dates return the date itself.
132
+
133
+ The toolbar's Summaries panel offers these for each aggregable column, or only the ones listed in the column's `availableAggregationFunctions`. The same rules bind `aggregationModel` itself: an entry for an `aggregable: false` column, or with a function the column does not allow, is ignored by the footer, group rows and exports.
75
134
 
76
135
  ---
77
136
 
78
137
  ## 🎨 Formatting Totals
79
- Aggregation results automatically inherit the `valueFormatter` from their respective column definitions. If you need a specific format for aggregates only, use the `formatAggregationValue` utility.
138
+
139
+ The footer, group rows, pivot value cells and every export format aggregates the same way:
140
+
141
+ - `sum`, `avg`, `min` and `max` go through the column's `valueFormatter`, so totals keep the column's currency, unit or percent format. The formatter's `row` is the record of aggregated values: the footer totals, the group row, or the pivot row. A formatter that throws (typically because it reads row data an aggregate does not have) falls back to the default format instead of breaking the grid.
142
+ - `count` and `unique` are plain counts: the column's `valueFormatter` is not applied (a count of a currency column is not an amount).
143
+ - Without a formatter, numbers are locale-formatted (`avg` to at most two decimals) and dates show as locale dates.
144
+
145
+ Exports follow the same rules; see [Aggregate values](export-guide.md#aggregate-values). Use `formatAggregationValue(value, fnName)` for the default number formatting elsewhere.