@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencorestack/opengridx",
3
- "version": "2.0.4",
3
+ "version": "3.0.0",
4
4
  "description": "OpenGridX: High-performance React data infrastructure. Unlock advanced Row Grouping, Excel Export, and Column Pinning without the usual \"Pro\" gatekeeping. Built for speed, scale, and complete architectural freedom. Fully open, virtualization-ready, and feature-complete.",
5
5
  "type": "module",
6
6
  "main": "./dist/opengridx.umd.js",
@@ -20,11 +20,18 @@
20
20
  "files": [
21
21
  "dist",
22
22
  "lib",
23
+ "!lib/**/*.test.ts",
24
+ "!lib/**/*.test.tsx",
25
+ "!lib/**/__screenshots__",
26
+ "docs/architecture",
27
+ "docs/assets",
23
28
  "docs/components",
24
29
  "docs/customization",
25
30
  "docs/features",
31
+ "docs/migration",
26
32
  "docs/API_REFERENCE.md",
27
33
  "docs/README.md",
34
+ "docs/performance.md",
28
35
  "docs/roadmap.md",
29
36
  "README.md",
30
37
  "LICENSE",
@@ -38,11 +45,14 @@
38
45
  "scripts": {
39
46
  "dev": "vite",
40
47
  "build": "tsc --project tsconfig.build.json && vite build",
41
- "build:lib": "tsc --project tsconfig.build.json && vite build --config vite.config.lib.js && cp lib/styles/opengridx.css.d.ts dist/opengridx.css.d.ts",
48
+ "build:lib": "tsc --project tsconfig.build.json && vite build --config vite.config.lib.js && cp lib/styles/opengridx.css.d.ts dist/opengridx.css.d.ts && node scripts/check-bundle.mjs",
42
49
  "lint": "eslint .",
50
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
43
51
  "preview": "vite preview",
44
- "test": "vitest run",
45
- "test:watch": "vitest",
52
+ "test": "vitest run --project unit",
53
+ "test:browser": "vitest run --project browser",
54
+ "test:all": "vitest run",
55
+ "test:watch": "vitest --project unit",
46
56
  "coverage": "vitest run --coverage",
47
57
  "prepublishOnly": "npm run build:lib"
48
58
  },
@@ -103,7 +113,8 @@
103
113
  "@types/react": "^19.2.5",
104
114
  "@types/react-dom": "^19.2.3",
105
115
  "@vitejs/plugin-react": "^5.1.1",
106
- "@vitest/coverage-v8": "^4.0.18",
116
+ "@vitest/browser-playwright": "^4.1.11",
117
+ "@vitest/coverage-v8": "^4.1.11",
107
118
  "eslint": "^9.39.1",
108
119
  "eslint-plugin-react-hooks": "^7.0.1",
109
120
  "eslint-plugin-react-refresh": "^0.4.24",
@@ -111,6 +122,7 @@
111
122
  "jsdom": "^29.1.1",
112
123
  "jspdf": "^4.2.1",
113
124
  "jspdf-autotable": "^5.0.8",
125
+ "playwright": "^1.58.2",
114
126
  "react": "^19.2.0",
115
127
  "react-dom": "^19.2.0",
116
128
  "react-router-dom": "^7.13.1",
@@ -118,6 +130,6 @@
118
130
  "typescript-eslint": "^8.65.0",
119
131
  "vite": "^7.2.4",
120
132
  "vite-plugin-dts": "^4.5.4",
121
- "vitest": "^4.0.18"
133
+ "vitest": "^4.1.11"
122
134
  }
123
135
  }
@@ -1,220 +0,0 @@
1
- import type { GridColumnGroup, GridColumnGroupingModel, GridColDef, GridRowModel } from '../../types';
2
- import './ColumnGroupHeader.css';
3
-
4
- // ─── Helpers ──────────────────────────────────────────────────────────────────
5
-
6
- /** All leaf field names reachable from a group node (recursive). */
7
- function collectFields(group: GridColumnGroup): string[] {
8
- const fields: string[] = [];
9
- for (const child of group.children) {
10
- if (typeof child === 'string') {
11
- fields.push(child);
12
- } else {
13
- fields.push(...collectFields(child));
14
- }
15
- }
16
- return fields;
17
- }
18
-
19
- /** Maximum nesting depth of any group tree. */
20
- function maxDepth(groups: GridColumnGroupingModel): number {
21
- let d = 0;
22
- function walk(g: GridColumnGroup, depth: number) {
23
- d = Math.max(d, depth);
24
- for (const child of g.children) {
25
- if (typeof child !== 'string') walk(child, depth + 1);
26
- }
27
- }
28
- groups.forEach(g => walk(g, 1));
29
- return d;
30
- }
31
-
32
- /**
33
- * Flattened cell descriptor for one row of group headers.
34
- * `span` is the pixel width, `level` is 0-indexed depth.
35
- */
36
- interface GroupCell {
37
- key: string;
38
- label: string;
39
- width: number; // total pixel width spanned
40
- isGroup: boolean; // false → empty "filler" cell for ungrouped columns
41
- groupId?: string;
42
- headerClassName?: string;
43
- }
44
-
45
- /** Build one row of GroupCell descriptors for a given depth level. */
46
- function buildRow(
47
- groups: GridColumnGroupingModel,
48
- leafColumns: string[],
49
- columnWidths: Record<string, number>,
50
- level: number,
51
- ): GroupCell[] {
52
- const cells: GroupCell[] = [];
53
-
54
- // Assign each leaf column to the group (at `level`) that contains it, if any.
55
- // Consecutive columns in the same group merge into one cell.
56
-
57
- // Build a map: field → group-at-this-level
58
- const fieldToGroup = new Map<string, GridColumnGroup | null>();
59
-
60
- function findGroupAtLevel(
61
- groups: GridColumnGroupingModel,
62
- field: string,
63
- currentLevel: number,
64
- ): GridColumnGroup | null {
65
- for (const g of groups) {
66
- const leafFields = collectFields(g);
67
- if (!leafFields.includes(field)) continue;
68
- if (currentLevel === level) return g;
69
- // Go deeper
70
- for (const child of g.children) {
71
- if (typeof child !== 'string') {
72
- const found = findGroupAtLevel([child], field, currentLevel + 1);
73
- if (found) return found;
74
- }
75
- }
76
- return null;
77
- }
78
- return null;
79
- }
80
-
81
- for (const f of leafColumns) {
82
- fieldToGroup.set(f, findGroupAtLevel(groups, f, 0));
83
- }
84
-
85
- // Merge consecutive columns belonging to the same group
86
- let i = 0;
87
- while (i < leafColumns.length) {
88
- const field = leafColumns[i];
89
- const group = fieldToGroup.get(field) ?? null;
90
-
91
- if (!group) {
92
- // Ungrouped column — filler cell spanning just this column
93
- cells.push({
94
- key: `filler-${field}-${level}`,
95
- label: '',
96
- width: columnWidths[field] ?? 100,
97
- isGroup: false,
98
- });
99
- i++;
100
- } else {
101
- // Collect all consecutive columns that belong to the same group instance
102
- let totalWidth = 0;
103
- let j = i;
104
- while (j < leafColumns.length && fieldToGroup.get(leafColumns[j]) === group) {
105
- totalWidth += columnWidths[leafColumns[j]] ?? 100;
106
- j++;
107
- }
108
- cells.push({
109
- key: `${group.groupId}-${level}`,
110
- label: group.headerName,
111
- width: totalWidth,
112
- isGroup: true,
113
- groupId: group.groupId,
114
- headerClassName: group.headerClassName,
115
- });
116
- i = j;
117
- }
118
- }
119
-
120
- return cells;
121
- }
122
-
123
- // ─── Component ────────────────────────────────────────────────────────────────
124
-
125
- export interface ColumnGroupHeaderProps<R extends GridRowModel = GridRowModel> {
126
- columnGroupingModel: GridColumnGroupingModel;
127
- /** The ordered, effective columns (already filtered for visibility). */
128
- columns: GridColDef<R>[];
129
- /** Resolved pixel widths for every column. */
130
- columnWidths: Record<string, number>;
131
- /** Whether a checkbox column is prepended. */
132
- checkboxSelection?: boolean;
133
- /** Whether a row-reorder handle column is prepended. */
134
- rowReordering?: boolean;
135
- /** Whether a detail-panel expand column is prepended. */
136
- hasDetailPanel?: boolean;
137
- /** height (px) of each group header row */
138
- groupHeaderHeight?: number;
139
- }
140
-
141
- export function ColumnGroupHeader<R extends GridRowModel = GridRowModel>({
142
- columnGroupingModel,
143
- columns,
144
- columnWidths,
145
- checkboxSelection = false,
146
- rowReordering = false,
147
- hasDetailPanel = false,
148
- groupHeaderHeight = 36,
149
- }: ColumnGroupHeaderProps<R>) {
150
-
151
- const levels = maxDepth(columnGroupingModel);
152
- if (levels === 0 || columns.length === 0) return null;
153
-
154
- // Ordered leaf fields (visible columns only)
155
- const leafFields = columns.map(c => c.field);
156
-
157
- // Width of any prepended utility columns
158
- const CHECKBOX_W = 52;
159
- const REORDER_W = 40;
160
- const EXPAND_W = 52;
161
- const prefixWidth =
162
- (checkboxSelection ? CHECKBOX_W : 0) +
163
- (rowReordering ? REORDER_W : 0) +
164
- (hasDetailPanel ? EXPAND_W : 0);
165
-
166
- return (
167
- <>
168
- {Array.from({ length: levels }, (_, level) => {
169
- const row = buildRow(
170
- columnGroupingModel,
171
- leafFields,
172
- columnWidths,
173
- level,
174
- );
175
-
176
- return (
177
- <div
178
- key={`group-row-${level}`}
179
- className="ogx-col-group-row"
180
- style={{ height: groupHeaderHeight }}
181
- role="row"
182
- aria-rowindex={level + 1}
183
- >
184
- {/* Spacer for checkbox / reorder / expand columns */}
185
- {prefixWidth > 0 && (
186
- <div
187
- className="ogx-col-group-cell ogx-col-group-cell--filler"
188
- style={{ width: prefixWidth, minWidth: prefixWidth }}
189
- role="columnheader"
190
- />
191
- )}
192
-
193
- {row.map((cell) => (
194
- <div
195
- key={cell.key}
196
- role="columnheader"
197
- aria-colspan={undefined /* pixel-based layout */}
198
- className={[
199
- 'ogx-col-group-cell',
200
- cell.isGroup
201
- ? 'ogx-col-group-cell--group'
202
- : 'ogx-col-group-cell--filler',
203
- cell.headerClassName ?? '',
204
- ].join(' ').trim()}
205
- style={{ width: cell.width, minWidth: cell.width }}
206
- title={cell.isGroup ? cell.label : undefined}
207
- >
208
- {cell.isGroup && (
209
- <span className="ogx-col-group-cell__label">
210
- {cell.label}
211
- </span>
212
- )}
213
- </div>
214
- ))}
215
- </div>
216
- );
217
- })}
218
- </>
219
- );
220
- }
@@ -1,85 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { render, screen } from '@testing-library/react';
3
- import { ColumnVisibilityPanel } from './ColumnVisibilityPanel';
4
- import type { GridColDef } from '../../types';
5
-
6
- const columns: GridColDef[] = [
7
- { field: 'name', headerName: 'Name' },
8
- { field: 'department', headerName: 'Department' },
9
- { field: 'groupKey', headerName: 'Group Key', hideable: false },
10
- ];
11
-
12
- const visibleColumns = new Set(['name', 'department', 'groupKey']);
13
-
14
- const noop = () => {};
15
-
16
- describe('ColumnVisibilityPanel', () => {
17
- it('excludes hideable:false columns from the list by default', () => {
18
- render(
19
- <ColumnVisibilityPanel
20
- columns={columns}
21
- visibleColumns={visibleColumns}
22
- onVisibilityChange={noop}
23
- onShowAll={noop}
24
- onHideAll={noop}
25
- />
26
- );
27
- expect(screen.getByText('Name')).toBeInTheDocument();
28
- expect(screen.getByText('Department')).toBeInTheDocument();
29
- expect(screen.queryByText('Group Key')).not.toBeInTheDocument();
30
- });
31
-
32
- it('shows hideable:false columns as disabled when showNonHideableColumns is true', () => {
33
- render(
34
- <ColumnVisibilityPanel
35
- columns={columns}
36
- visibleColumns={visibleColumns}
37
- onVisibilityChange={noop}
38
- onShowAll={noop}
39
- onHideAll={noop}
40
- showNonHideableColumns
41
- />
42
- );
43
- expect(screen.getByText('Name')).toBeInTheDocument();
44
- expect(screen.getByText('Department')).toBeInTheDocument();
45
- expect(screen.getByText('Group Key')).toBeInTheDocument();
46
-
47
- // 3 columns shown + 1 "Show/Hide All" checkbox in footer = 4 checkboxes
48
- // The 3rd column (Group Key, hideable:false) must be the disabled one
49
- const checkboxes = screen.getAllByRole('checkbox');
50
- const disabledCheckboxes = checkboxes.filter(cb => (cb as HTMLInputElement).disabled);
51
- expect(disabledCheckboxes).toHaveLength(1);
52
- });
53
-
54
- it('does not render a disabled checkbox for hideable:false columns by default', () => {
55
- render(
56
- <ColumnVisibilityPanel
57
- columns={columns}
58
- visibleColumns={visibleColumns}
59
- onVisibilityChange={noop}
60
- onShowAll={noop}
61
- onHideAll={noop}
62
- />
63
- );
64
- const checkboxes = screen.getAllByRole('checkbox');
65
- const disabledCheckboxes = checkboxes.filter(cb => (cb as HTMLInputElement).disabled);
66
- expect(disabledCheckboxes).toHaveLength(0);
67
- });
68
-
69
- it('never shows hideable:false columns even when search would match them', () => {
70
- // Without showNonHideableColumns, hideable:false columns are not in the base list
71
- // so they cannot appear regardless of search. This test verifies the base list,
72
- // not a dynamic search interaction.
73
- render(
74
- <ColumnVisibilityPanel
75
- columns={columns}
76
- visibleColumns={visibleColumns}
77
- onVisibilityChange={noop}
78
- onShowAll={noop}
79
- onHideAll={noop}
80
- />
81
- );
82
- // "Group Key" is hideable:false — must not appear in the default panel
83
- expect(screen.queryByText('Group Key')).not.toBeInTheDocument();
84
- });
85
- });
@@ -1,132 +0,0 @@
1
- /**
2
- * Regression tests for bugs fixed in the DataGrid render layer.
3
- *
4
- * Tests cover:
5
- * - getVisibleRows() respects pagination (not returning all rows)
6
- * - Row DOM nodes are not remounted when column widths change
7
- *
8
- * Not covered here (require a real browser / ResizeObserver):
9
- * - translateY offset — depends on computed cumulativeHeights, which requires
10
- * a real viewport; jsdom reports all dimensions as 0
11
- * - scroll-ref RAF batching — requires scroll events that jsdom fires but
12
- * requestAnimationFrame does not execute by default
13
- */
14
-
15
- import React from 'react';
16
- import { describe, it, expect } from 'vitest';
17
- import { render, act } from '@testing-library/react';
18
- import { DataGrid } from './DataGrid';
19
- import { useGridApiRef } from '../../hooks/core/useGridApiRef';
20
- import type { GridColDef } from '../../types';
21
-
22
- const COLS: GridColDef[] = [
23
- { field: 'id', headerName: 'ID', width: 80 },
24
- { field: 'name', headerName: 'Name', width: 150 },
25
- { field: 'age', headerName: 'Age', width: 100 },
26
- ];
27
-
28
- // 10 rows so pagination actually clips the visible set
29
- const ROWS = Array.from({ length: 10 }, (_, i) => ({
30
- id: i + 1,
31
- name: `User ${i + 1}`,
32
- age: 20 + i,
33
- }));
34
-
35
- // ─────────────────────────────────────────────────────────────────────────────
36
- // Bug: getVisibleRows() was returning sortedUnpinnedRows (all rows) regardless
37
- // of whether pagination was active. Fixed: now returns paginatedUnpinnedRows
38
- // when a paginationModel is set.
39
- // ─────────────────────────────────────────────────────────────────────────────
40
-
41
- describe('getVisibleRows respects pagination', () => {
42
- it('returns only the current page rows, not all rows', () => {
43
- const assigningRef = React.createRef<import('../../types').GridApi>();
44
-
45
- function GridWithRef() {
46
- const ref = useGridApiRef();
47
- // expose ref to test scope after render
48
- React.useEffect(() => {
49
- (assigningRef as React.MutableRefObject<import('../../types').GridApi>).current = ref.current;
50
- });
51
- return (
52
- <DataGrid
53
- rows={ROWS}
54
- columns={COLS}
55
- apiRef={ref}
56
- pagination
57
- paginationModel={{ page: 0, pageSize: 3 }}
58
- />
59
- );
60
- }
61
-
62
- act(() => {
63
- render(<GridWithRef />);
64
- });
65
-
66
- // pageSize=3, total=10 → page 0 should expose 3 rows, not 10.
67
- const visible = assigningRef.current?.getVisibleRows() ?? [];
68
- expect(visible.length).toBe(3);
69
- expect(visible.map(r => r.id)).toEqual([1, 2, 3]);
70
- });
71
-
72
- it('returns all rows when no pagination is provided', () => {
73
- const assigningRef = React.createRef<import('../../types').GridApi>();
74
-
75
- function GridNoPagination() {
76
- const ref = useGridApiRef();
77
- React.useEffect(() => {
78
- (assigningRef as React.MutableRefObject<import('../../types').GridApi>).current = ref.current;
79
- });
80
- return <DataGrid rows={ROWS} columns={COLS} apiRef={ref} />;
81
- }
82
-
83
- act(() => {
84
- render(<GridNoPagination />);
85
- });
86
-
87
- const visible = assigningRef.current?.getVisibleRows() ?? [];
88
- // Without pagination the full sorted set is returned.
89
- expect(visible.length).toBe(ROWS.length);
90
- });
91
- });
92
-
93
- // ─────────────────────────────────────────────────────────────────────────────
94
- // Bug: row keys were `${row.id}-${virtualization.totalWidth}`, so every column
95
- // resize unmounted and remounted every DOM row node.
96
- // Fixed: key is now simply `row.id`.
97
- //
98
- // Test strategy: capture DOM element identity before and after a column prop
99
- // change. If the key includes totalWidth the elements will be new nodes; if
100
- // the key is stable they will be the exact same DOM nodes.
101
- // ─────────────────────────────────────────────────────────────────────────────
102
-
103
- describe('row DOM nodes are stable across column width changes', () => {
104
- it('does not remount rows when column widths change', () => {
105
- const { rerender, container } = render(
106
- <DataGrid rows={ROWS.slice(0, 3)} columns={COLS} />
107
- );
108
-
109
- // Collect the row cell elements before the update.
110
- const cellsBefore = Array.from(
111
- container.querySelectorAll('[role="gridcell"]')
112
- );
113
- expect(cellsBefore.length).toBeGreaterThan(0);
114
-
115
- // Update the Name column width — simulates a resize.
116
- const updatedCols: GridColDef[] = COLS.map(c =>
117
- c.field === 'name' ? { ...c, width: 250 } : c
118
- );
119
-
120
- rerender(<DataGrid rows={ROWS.slice(0, 3)} columns={updatedCols} />);
121
-
122
- const cellsAfter = Array.from(
123
- container.querySelectorAll('[role="gridcell"]')
124
- );
125
-
126
- // Same number of cells, and critically the same DOM node instances.
127
- expect(cellsAfter.length).toBe(cellsBefore.length);
128
- cellsBefore.forEach((node, i) => {
129
- expect(cellsAfter[i]).toBe(node);
130
- });
131
- });
132
- });
@@ -1,58 +0,0 @@
1
- import React from 'react';
2
- import { describe, it, expect } from 'vitest';
3
- import { render, screen } from '@testing-library/react';
4
- import { DataGrid } from './DataGrid';
5
- import type { GridColDef } from '../../types';
6
-
7
- const COLUMNS: GridColDef[] = [
8
- { field: 'id', headerName: 'ID', width: 80 },
9
- { field: 'name', headerName: 'Name', width: 150 },
10
- { field: 'age', headerName: 'Age', width: 100, type: 'number' },
11
- ];
12
-
13
- const ROWS = [
14
- { id: 1, name: 'Alice', age: 30 },
15
- { id: 2, name: 'Bob', age: 25 },
16
- { id: 3, name: 'Charlie', age: 35 },
17
- ];
18
-
19
- describe('DataGrid — smoke tests', () => {
20
- it('renders without throwing', () => {
21
- expect(() =>
22
- render(<DataGrid rows={ROWS} columns={COLUMNS} />)
23
- ).not.toThrow();
24
- });
25
-
26
- it('renders column headers', () => {
27
- render(<DataGrid rows={ROWS} columns={COLUMNS} />);
28
- expect(screen.getByText('Name')).toBeInTheDocument();
29
- expect(screen.getByText('Age')).toBeInTheDocument();
30
- });
31
-
32
- it('renders row data', () => {
33
- render(<DataGrid rows={ROWS} columns={COLUMNS} />);
34
- expect(screen.getByText('Alice')).toBeInTheDocument();
35
- expect(screen.getByText('Bob')).toBeInTheDocument();
36
- expect(screen.getByText('Charlie')).toBeInTheDocument();
37
- });
38
-
39
- it('renders with empty rows without throwing', () => {
40
- expect(() =>
41
- render(<DataGrid rows={[]} columns={COLUMNS} />)
42
- ).not.toThrow();
43
- });
44
-
45
- it('renders with empty columns without throwing', () => {
46
- expect(() =>
47
- render(<DataGrid rows={ROWS} columns={[]} />)
48
- ).not.toThrow();
49
- });
50
-
51
- it('renders with checkboxSelection enabled', () => {
52
- const { container } = render(
53
- <DataGrid rows={ROWS} columns={COLUMNS} checkboxSelection />
54
- );
55
- const checkboxes = container.querySelectorAll('input[type="checkbox"]');
56
- expect(checkboxes.length).toBeGreaterThan(0);
57
- });
58
- });