@opencorestack/opengridx 2.1.0 → 3.0.1

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 (225) hide show
  1. package/CHANGELOG.md +399 -0
  2. package/README.md +95 -72
  3. package/dist/index.d.ts +590 -131
  4. package/dist/opengridx.css +1 -1
  5. package/dist/opengridx.es.js +7891 -5631
  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 -166
  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 +6 -7
  29. package/docs/components/datagrid.md +15 -13
  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 +90 -38
  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 +126 -39
  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 +72 -19
  54. package/docs/features/pinning.md +24 -5
  55. package/docs/features/selection.md +50 -10
  56. package/docs/features/sorting-pagination.md +64 -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 +36 -8
  60. package/docs/features/virtualization.md +37 -14
  61. package/docs/migration/v1-to-v2.md +115 -0
  62. package/docs/migration/v2-to-v3.md +1233 -0
  63. package/docs/performance.md +124 -0
  64. package/docs/roadmap.md +5 -5
  65. package/lib/components/Cell/Cell.css +17 -1
  66. package/lib/components/Cell/Cell.tsx +129 -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 +15 -2
  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 +85 -12
  76. package/lib/components/DataGrid/DataGrid.tsx +582 -876
  77. package/lib/components/DataGrid/GridAggregationFooter.tsx +53 -17
  78. package/lib/components/DataGrid/GridErrorOverlay.tsx +15 -12
  79. package/lib/components/DataGrid/GridListView.tsx +165 -24
  80. package/lib/components/DataGrid/GridLiveRegion.tsx +31 -0
  81. package/lib/components/DataGrid/GridLoadingOverlay.tsx +27 -0
  82. package/lib/components/DataGrid/GridPaginationArea.tsx +44 -0
  83. package/lib/components/DataGrid/GridPinnedRows.tsx +62 -11
  84. package/lib/components/DataGrid/GridStandaloneColumnPanel.tsx +40 -38
  85. package/lib/components/DataGrid/GridToolbarSlot.tsx +51 -0
  86. package/lib/components/DataGrid/GridVirtualRows.tsx +40 -24
  87. package/lib/components/DetailPanel/DetailPanel.css +0 -6
  88. package/lib/components/DetailPanel/DetailPanel.tsx +34 -5
  89. package/lib/components/DetailPanel/DetailPanelContent.tsx +17 -0
  90. package/lib/components/FilterPanel/FilterPanel.css +13 -2
  91. package/lib/components/FilterPanel/FilterPanel.tsx +272 -99
  92. package/lib/components/Header/ColumnMenu.tsx +91 -39
  93. package/lib/components/Header/Header.css +6 -3
  94. package/lib/components/Header/Header.tsx +181 -160
  95. package/lib/components/ListView/ListView.css +25 -0
  96. package/lib/components/ListView/ListViewRow.tsx +76 -10
  97. package/lib/components/Pagination/Pagination.tsx +15 -7
  98. package/lib/components/Row/Row.tsx +177 -77
  99. package/lib/components/Toolbar/GlobalSearch.css +6 -3
  100. package/lib/components/Toolbar/GlobalSearch.tsx +26 -27
  101. package/lib/components/Toolbar/GridToolbar.css +12 -11
  102. package/lib/components/Toolbar/GridToolbar.tsx +133 -89
  103. package/lib/components/Toolbar/PivotPanel.tsx +89 -49
  104. package/lib/components/Tooltip/Tooltip.css +20 -0
  105. package/lib/components/Tooltip/Tooltip.tsx +68 -35
  106. package/lib/components/ui/Button.tsx +3 -0
  107. package/lib/components/ui/Checkbox.tsx +2 -1
  108. package/lib/components/ui/ExpandIcon.css +0 -9
  109. package/lib/components/ui/ExpandIcon.tsx +4 -1
  110. package/lib/hooks/core/gridApiPlaceholder.ts +34 -0
  111. package/lib/hooks/core/gridToolbarHostContext.ts +14 -0
  112. package/lib/hooks/core/useDataGrid.ts +115 -173
  113. package/lib/hooks/core/useGridAggregationApi.ts +72 -0
  114. package/lib/hooks/core/useGridApiMethods.ts +115 -0
  115. package/lib/hooks/core/useGridApiRef.ts +19 -4
  116. package/lib/hooks/core/useGridApiRefBinding.ts +18 -0
  117. package/lib/hooks/core/useGridAriaRows.ts +38 -0
  118. package/lib/hooks/core/useGridClipboardApi.ts +38 -0
  119. package/lib/hooks/core/useGridColumnLookup.ts +30 -0
  120. package/lib/hooks/core/useGridColumns.tsx +279 -76
  121. package/lib/hooks/core/useGridColumnsPanel.ts +77 -0
  122. package/lib/hooks/core/useGridControlledState.ts +99 -6
  123. package/lib/hooks/core/useGridDetailPanel.ts +51 -0
  124. package/lib/hooks/core/useGridDevWarnings.ts +104 -5
  125. package/lib/hooks/core/useGridFocusHandlers.ts +50 -0
  126. package/lib/hooks/core/useGridGroupingColumn.ts +62 -0
  127. package/lib/hooks/core/useGridHeaderHandlers.ts +92 -0
  128. package/lib/hooks/core/useGridHierarchy.ts +81 -0
  129. package/lib/hooks/core/useGridKeyboardNavigation.ts +447 -243
  130. package/lib/hooks/core/useGridLiveRowSelection.ts +67 -0
  131. package/lib/hooks/core/useGridPageCorrection.ts +55 -0
  132. package/lib/hooks/core/useGridRowIdOf.ts +32 -0
  133. package/lib/hooks/core/useGridRowInteractions.ts +81 -0
  134. package/lib/hooks/core/useGridRowPipeline.ts +69 -39
  135. package/lib/hooks/core/useGridRowSelection.ts +91 -0
  136. package/lib/hooks/core/useGridScrollSync.ts +80 -32
  137. package/lib/hooks/core/useGridScrollToIndexesApi.ts +45 -0
  138. package/lib/hooks/core/useGridStateSnapshot.ts +40 -5
  139. package/lib/hooks/core/useGridThemeDimensions.ts +49 -0
  140. package/lib/hooks/core/useGridToolbarProps.ts +97 -0
  141. package/lib/hooks/core/useGridViewport.ts +38 -0
  142. package/lib/hooks/core/useGridViewportSize.ts +26 -0
  143. package/lib/hooks/core/useGridVisibleRows.ts +17 -5
  144. package/lib/hooks/core/useLayout.ts +215 -195
  145. package/lib/hooks/core/useRowsScrollEndLatch.ts +41 -0
  146. package/lib/hooks/core/useStableColumns.ts +34 -0
  147. package/lib/hooks/features/useAggregation.ts +82 -51
  148. package/lib/hooks/features/useColumnGroupReorderGuard.ts +34 -0
  149. package/lib/hooks/features/useDetailPanelHeights.ts +29 -0
  150. package/lib/hooks/features/useGridClipboard.ts +133 -73
  151. package/lib/hooks/features/useGridDataSource.ts +354 -128
  152. package/lib/hooks/features/useGridEditing.ts +211 -51
  153. package/lib/hooks/features/useGridPivot.ts +92 -0
  154. package/lib/hooks/features/useGridSpanRenderWindow.ts +104 -0
  155. package/lib/hooks/features/useGridSpanning.ts +410 -306
  156. package/lib/hooks/features/usePivot.ts +0 -0
  157. package/lib/hooks/features/useServerTreeChildren.ts +50 -0
  158. package/lib/hooks/useColumnReorder.ts +38 -44
  159. package/lib/hooks/useRowGrouping.ts +298 -160
  160. package/lib/hooks/useRowReorder.ts +62 -31
  161. package/lib/hooks/useTreeData.ts +287 -220
  162. package/lib/index.ts +28 -2
  163. package/lib/state/index.ts +1 -0
  164. package/lib/state/types.ts +7 -1
  165. package/lib/state/useGridStateStorage.ts +88 -29
  166. package/lib/theme/DataGridThemeProvider.tsx +143 -38
  167. package/lib/theme/gridThemeContext.ts +5 -0
  168. package/lib/theme/index.ts +5 -0
  169. package/lib/theme/presets.ts +5 -4
  170. package/lib/theme/types.ts +40 -2
  171. package/lib/types/index.ts +313 -116
  172. package/lib/utils/aggregation/index.ts +119 -24
  173. package/lib/utils/aria/index.ts +40 -0
  174. package/lib/utils/columnGroups.ts +110 -0
  175. package/lib/utils/columnLookup.ts +33 -0
  176. package/lib/utils/columnWidth.ts +22 -0
  177. package/lib/utils/dataSource/index.ts +46 -0
  178. package/lib/utils/detailPanel.ts +66 -0
  179. package/lib/utils/dragData.ts +44 -0
  180. package/lib/utils/editing/index.ts +30 -0
  181. package/lib/utils/export/exportShared.ts +197 -0
  182. package/lib/utils/export/exportToExcelAdvanced.ts +278 -139
  183. package/lib/utils/export/exportToPdf.ts +143 -92
  184. package/lib/utils/export/groupLabel.ts +14 -0
  185. package/lib/utils/export/index.ts +278 -272
  186. package/lib/utils/export/pdfText.ts +50 -0
  187. package/lib/utils/filtering/index.ts +277 -148
  188. package/lib/utils/focus/index.ts +115 -0
  189. package/lib/utils/gridApiRows.ts +35 -0
  190. package/lib/utils/gridModes.ts +77 -0
  191. package/lib/utils/grouping/groupedExportRows.ts +91 -0
  192. package/lib/utils/pagination/index.ts +56 -0
  193. package/lib/utils/pinning/index.ts +77 -33
  194. package/lib/utils/pivot/index.ts +368 -0
  195. package/lib/utils/scroll/index.ts +55 -5
  196. package/lib/utils/scroll/scrollEnd.ts +18 -0
  197. package/lib/utils/sorting/index.ts +154 -30
  198. package/lib/utils/syntheticRows.ts +12 -0
  199. package/lib/utils/values.ts +206 -0
  200. package/lib/utils/viewport.ts +14 -0
  201. package/llms.txt +261 -164
  202. package/package.json +13 -5
  203. package/lib/components/ColumnGroupHeader/ColumnGroupHeader.tsx +0 -220
  204. package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.test.tsx +0 -85
  205. package/lib/components/DataGrid/DataGrid.defects.test.tsx +0 -103
  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/DataGrid/DataGrid.virtualization.browser.test.tsx +0 -183
  210. package/lib/components/Header/Header.test.tsx +0 -135
  211. package/lib/components/Pagination/Pagination.test.tsx +0 -64
  212. package/lib/hooks/core/useDataGrid.regression.test.ts +0 -66
  213. package/lib/hooks/core/useGridControlledState.test.ts +0 -86
  214. package/lib/hooks/core/useGridKeyboardNavigation.test.ts +0 -176
  215. package/lib/hooks/core/useGridRowPipeline.test.ts +0 -117
  216. package/lib/hooks/features/useAggregation.test.ts +0 -175
  217. package/lib/hooks/features/usePivot.test.ts +0 -202
  218. package/lib/hooks/useRowGrouping.test.ts +0 -298
  219. package/lib/hooks/useTreeData.test.ts +0 -83
  220. package/lib/utils/aggregation/aggregation.test.ts +0 -16
  221. package/lib/utils/export/exportToExcelAdvanced.test.ts +0 -109
  222. package/lib/utils/export/exportToPdf.test.ts +0 -120
  223. package/lib/utils/export/groupedExport.test.ts +0 -144
  224. package/lib/utils/filtering/filtering.test.ts +0 -178
  225. 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.1.0",
3
+ "version": "3.0.1",
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,8 +45,9 @@
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
52
  "test": "vitest run --project unit",
45
53
  "test:browser": "vitest run --project browser",
@@ -105,8 +113,8 @@
105
113
  "@types/react": "^19.2.5",
106
114
  "@types/react-dom": "^19.2.3",
107
115
  "@vitejs/plugin-react": "^5.1.1",
108
- "@vitest/browser-playwright": "^4.0.18",
109
- "@vitest/coverage-v8": "^4.0.18",
116
+ "@vitest/browser-playwright": "^4.1.11",
117
+ "@vitest/coverage-v8": "^4.1.11",
110
118
  "eslint": "^9.39.1",
111
119
  "eslint-plugin-react-hooks": "^7.0.1",
112
120
  "eslint-plugin-react-refresh": "^0.4.24",
@@ -122,6 +130,6 @@
122
130
  "typescript-eslint": "^8.65.0",
123
131
  "vite": "^7.2.4",
124
132
  "vite-plugin-dts": "^4.5.4",
125
- "vitest": "^4.0.18"
133
+ "vitest": "^4.1.11"
126
134
  }
127
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,103 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { render, screen, fireEvent } from '@testing-library/react';
3
- import { DataGrid } from './DataGrid';
4
- import type { GridColDef } from '../../types';
5
-
6
- // Regression tests for the OpenGridX 2.0.4 consumer defect report.
7
-
8
- type Row = { id: number; region: string; amount: number };
9
-
10
- const ROWS: Row[] = [
11
- { id: 1, region: 'North', amount: 1200 },
12
- { id: 2, region: 'North', amount: 800 },
13
- { id: 3, region: 'South', amount: 450 },
14
- ];
15
-
16
- const COLUMNS: GridColDef<Row>[] = [
17
- { field: 'region', headerName: 'Region', width: 150 },
18
- { field: 'amount', headerName: 'Amount', width: 150, valueFormatter: ({ value }) => `$${String(value)}` },
19
- ];
20
-
21
- describe('D3 — valueFormatter survives row grouping', () => {
22
- it('renders formatted values without grouping', () => {
23
- render(<DataGrid rows={ROWS} columns={COLUMNS} />);
24
- expect(screen.getByText('$1200')).toBeInTheDocument();
25
- });
26
-
27
- it('renders formatted values for leaf rows when rowGroupingModel is active', () => {
28
- render(<DataGrid rows={ROWS} columns={COLUMNS} rowGroupingModel={['region']} defaultGroupingExpansionDepth={-1} />);
29
- expect(screen.getByText('$1200')).toBeInTheDocument();
30
- expect(screen.queryByText('1200')).not.toBeInTheDocument();
31
- });
32
-
33
- it('passes formattedValue to a consumer renderCell', () => {
34
- const renderCell = vi.fn(() => null);
35
- const cols: GridColDef<Row>[] = [COLUMNS[0], { ...COLUMNS[1], renderCell }];
36
- render(<DataGrid rows={ROWS} columns={cols} />);
37
- expect(renderCell).toHaveBeenCalledWith(expect.objectContaining({ value: 1200, formattedValue: '$1200' }));
38
- });
39
- });
40
-
41
- describe('D2 — slots that were typed but never rendered', () => {
42
- it('renders slots.footer with grid state, replacing the default pagination', () => {
43
- const Footer = vi.fn((props: Record<string, unknown>) => <div data-testid="footer">rows:{String(props.rowCount)} note:{String(props.note)}</div>);
44
- render(
45
- <DataGrid
46
- rows={ROWS}
47
- columns={COLUMNS}
48
- pagination
49
- aggregationModel={{ amount: 'sum' }}
50
- slots={{ footer: Footer }}
51
- slotProps={{ footer: { note: 'hi' } }}
52
- />
53
- );
54
- expect(screen.getByTestId('footer')).toHaveTextContent('rows:3 note:hi');
55
- expect(Footer).toHaveBeenCalledWith(expect.objectContaining({ aggregationResult: { amount: 2450 } }), undefined);
56
- expect(document.querySelector('.ogx-pagination, .ogx__pagination')).toBeNull();
57
- });
58
-
59
- it('renders slots.noRowsOverlay when there are no rows', () => {
60
- render(<DataGrid rows={[]} columns={COLUMNS} slots={{ noRowsOverlay: () => <div>Nothing here</div> }} />);
61
- expect(screen.getByText('Nothing here')).toBeInTheDocument();
62
- });
63
-
64
- it('renders slots.loadingOverlay while loading with no rows', () => {
65
- render(<DataGrid rows={[]} columns={COLUMNS} loading slots={{ loadingOverlay: () => <div>Fetching…</div> }} />);
66
- expect(screen.getByText('Fetching…')).toBeInTheDocument();
67
- });
68
- });
69
-
70
- describe('D6 — onRowDoubleClick', () => {
71
- it('fires with the row params on double-click', () => {
72
- const onRowDoubleClick = vi.fn();
73
- render(<DataGrid rows={ROWS} columns={COLUMNS} onRowDoubleClick={onRowDoubleClick} />);
74
- fireEvent.doubleClick(screen.getByText('$800'));
75
- expect(onRowDoubleClick).toHaveBeenCalledWith(expect.objectContaining({ id: 2, row: ROWS[1] }));
76
- });
77
-
78
- it('fires in list view as well', () => {
79
- const onRowDoubleClick = vi.fn();
80
- render(
81
- <DataGrid rows={ROWS} columns={COLUMNS} listView
82
- listViewColumn={{ field: 'card', renderCell: ({ row }) => <span>card-{row.id}</span> }}
83
- onRowDoubleClick={onRowDoubleClick} />
84
- );
85
- fireEvent.doubleClick(screen.getByText('card-3'));
86
- expect(onRowDoubleClick).toHaveBeenCalledWith(expect.objectContaining({ id: 3, row: ROWS[2] }));
87
- });
88
- });
89
-
90
- describe('Row grouping — expansion survives row edits', () => {
91
- it('keeps a user-expanded group open when the rows prop changes', () => {
92
- const { rerender, container } = render(<DataGrid rows={ROWS} columns={COLUMNS} rowGroupingModel={['region']} />);
93
- const rowCount = () => container.querySelectorAll('.ogx__rows [role="row"]').length;
94
- expect(rowCount()).toBe(2);
95
-
96
- fireEvent.click(container.querySelector('.ogx__rows [role="row"]')!);
97
- expect(rowCount()).toBe(4);
98
-
99
- rerender(<DataGrid rows={ROWS.map(r => (r.id === 1 ? { ...r, amount: 1300 } : r))} columns={COLUMNS} rowGroupingModel={['region']} />);
100
- expect(rowCount()).toBe(4);
101
- expect(screen.getByText('$1300')).toBeInTheDocument();
102
- });
103
- });
@@ -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
- });