@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
@@ -1,41 +1,78 @@
1
1
  import React from 'react';
2
- import type { GridColDef, GridRowModel, GridRowId, GridListViewColDef, GridRenderCellParams } from '../../types';
2
+ import { formatValueByType, getCellValue, getFormattedValue } from '../../utils/values';
3
+ import { ExpandIcon } from '../ui/ExpandIcon';
4
+ import { CellErrorBoundary } from '../Cell/CellErrorBoundary';
5
+ import { isSyntheticRowId } from '../../utils/syntheticRows';
6
+ import type { GridColDef, GridRowModel, GridRowId, GridRowMeta, GridListViewColDef, GridRenderCellParams } from '../../types';
3
7
 
4
8
  export interface ListViewRowProps<R extends GridRowModel = GridRowModel> {
5
9
  row: R;
10
+ /** The row's id (from getRowId). Defaults to `row.id`. */
11
+ rowId?: GridRowId;
12
+ /** Index of the row among the rendered rows; passed to renderCell and the row callbacks. */
6
13
  rowIndex: number;
14
+ /** 1-based position of the row in the whole data set. Defaults to rowIndex + 1. */
15
+ ariaRowIndex?: number;
7
16
  listViewColumn: GridListViewColDef<R>;
17
+ /** The grid column with the list field, if any: its valueGetter / valueFormatter produce value and formattedValue. */
18
+ gridColumn?: GridColDef<R>;
19
+ /** Tree-data / row-grouping metadata for the row. */
20
+ rowMeta?: GridRowMeta;
8
21
  isSelected?: boolean;
9
22
  checkboxSelection?: boolean;
10
23
  rowHeight?: number;
11
24
  onRowClick?: (row: R) => void;
12
25
  onRowDoubleClick?: (row: R) => void;
13
26
  onSelectionChange?: (rowId: GridRowId, isSelected: boolean) => void;
27
+ /** Roving tab stop: 0 for the one row the list's Tab stop lands on, -1 for the others. */
28
+ tabIndex?: number;
29
+ /** Called when the row element itself receives focus. */
30
+ onFocus?: (rowIndex: number) => void;
31
+ /** Expands / collapses a row with children. When set, hierarchy rows get an expand chevron. */
32
+ onToggleExpansion?: (rowId: GridRowId) => void;
14
33
  }
15
34
 
16
35
  export function ListViewRow<R extends GridRowModel = GridRowModel>({
17
36
  row,
37
+ rowId,
18
38
  rowIndex,
39
+ ariaRowIndex,
19
40
  listViewColumn,
41
+ gridColumn,
42
+ rowMeta,
20
43
  isSelected = false,
21
44
  checkboxSelection = false,
22
45
  rowHeight,
23
46
  onRowClick,
24
47
  onRowDoubleClick,
25
48
  onSelectionChange,
49
+ tabIndex = -1,
50
+ onFocus,
51
+ onToggleExpansion,
26
52
  }: ListViewRowProps<R>) {
53
+ const id = rowId ?? row.id;
54
+ const field = listViewColumn.field;
55
+ // Same value and formatting as a grid cell for this field (Cell.tsx). A valueGetter or
56
+ // valueFormatter that throws reads as undefined / the unformatted value instead of crashing.
57
+ const value = getCellValue(row, field, gridColumn);
58
+ const formattedValue = getFormattedValue(row, field, value, gridColumn) ?? formatValueByType(value, gridColumn);
59
+ // Row-grouping headers, subtotals and auto-created tree parents are not data rows.
60
+ const isSyntheticRow = rowMeta?.isGroupRow === true || isSyntheticRowId(id);
61
+
27
62
  const params: GridRenderCellParams<R> = {
28
63
  row,
29
- value: undefined,
30
- field: listViewColumn.field,
31
- colDef: { field: listViewColumn.field } as unknown as GridColDef<R>,
64
+ value,
65
+ formattedValue,
66
+ field,
67
+ colDef: gridColumn ?? listViewColumn,
32
68
  rowIndex,
33
69
  colIndex: 0,
70
+ rowMeta,
34
71
  };
35
72
 
36
73
  const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {
37
74
  e.stopPropagation();
38
- onSelectionChange?.(row.id, e.target.checked);
75
+ onSelectionChange?.(id, e.target.checked);
39
76
  };
40
77
 
41
78
  return (
@@ -45,21 +82,50 @@ export function ListViewRow<R extends GridRowModel = GridRowModel>({
45
82
  onClick={() => onRowClick?.(row)}
46
83
  onDoubleClick={onRowDoubleClick ? () => onRowDoubleClick(row) : undefined}
47
84
  role="row"
48
- aria-rowindex={rowIndex + 2}
85
+ aria-rowindex={ariaRowIndex ?? rowIndex + 1}
49
86
  aria-selected={isSelected}
87
+ aria-level={rowMeta?.treeDepth !== undefined ? rowMeta.treeDepth + 1 : undefined}
88
+ aria-expanded={rowMeta?.hasChildren ? rowMeta.isExpanded === true : undefined}
89
+ data-rowindex={rowIndex}
90
+ tabIndex={tabIndex}
91
+ onFocus={onFocus ? (e) => { if (e.target === e.currentTarget) onFocus(rowIndex); } : undefined}
50
92
  >
51
93
  {checkboxSelection && (
52
94
  <div className="ogx-list-view__checkbox" onClick={(e) => e.stopPropagation()}>
53
- <input
95
+ {!isSyntheticRow && <input
54
96
  type="checkbox"
55
97
  checked={isSelected}
56
98
  onChange={handleCheckboxChange}
57
- aria-label={`Select row ${row.id}`}
58
- />
99
+ aria-label={`Select row ${id}`}
100
+ // Not a Tab stop of its own: the list is one Tab stop, Shift+Space selects the focused row.
101
+ tabIndex={-1}
102
+ />}
103
+ </div>
104
+ )}
105
+ {rowMeta && onToggleExpansion && (
106
+ // Tree data / row grouping: indentation by depth, and a chevron on rows with children
107
+ // (not a Tab stop: Enter or Alt+Arrow toggle the focused row).
108
+ <div
109
+ className="ogx-list-view__expand"
110
+ style={{ paddingLeft: (rowMeta.treeDepth ?? 0) * 16 }}
111
+ onClick={(e) => e.stopPropagation()}
112
+ onDoubleClick={(e) => e.stopPropagation()}
113
+ >
114
+ {rowMeta.hasChildren && (
115
+ <ExpandIcon isExpanded={rowMeta.isExpanded === true} onClick={() => onToggleExpansion(id)} tabIndex={-1} />
116
+ )}
59
117
  </div>
60
118
  )}
61
119
  <div className="ogx-list-view__cell" role="gridcell">
62
- {listViewColumn.renderCell(params)}
120
+ <CellErrorBoundary
121
+ field={field}
122
+ resetKey={row}
123
+ renderFn={() => {
124
+ const content = listViewColumn.renderCell(params);
125
+ // A group row whose renderCell has nothing for it still shows its label.
126
+ return content === undefined && isSyntheticRow ? rowMeta?.groupLabel : content;
127
+ }}
128
+ />
63
129
  </div>
64
130
  </div>
65
131
  );
@@ -1,6 +1,7 @@
1
1
 
2
2
  import React, { useId } from 'react';
3
3
  import { Button } from '../ui/Button';
4
+ import { normalizePageSize, getPageCount, clampPage } from '../../utils/pagination';
4
5
  import type { GridLocaleText } from '../../types';
5
6
 
6
7
  export interface PaginationProps {
@@ -29,11 +30,19 @@ export function Pagination(props: PaginationProps) {
29
30
  localeText,
30
31
  } = props;
31
32
 
32
- const pageCount = Math.max(1, Math.ceil(rowCount / pageSize));
33
- const currentPage = Math.min(page, pageCount - 1);
33
+ const safePageSize = normalizePageSize(pageSize);
34
+ const pageCount = getPageCount(rowCount, safePageSize);
35
+ const currentPage = clampPage(page, pageCount);
34
36
 
35
- const firstRowIndex = currentPage * pageSize;
36
- const lastRowIndex = Math.min(firstRowIndex + pageSize, rowCount);
37
+ // The select must be able to show the size in use, even when it is not one of the options
38
+ // (e.g. the default 100 with pageSizeOptions={[10, 25, 50]}); otherwise it displays the first
39
+ // option and choosing that option fires no change event.
40
+ const selectOptions = pageSizeOptions.includes(safePageSize)
41
+ ? pageSizeOptions
42
+ : [...pageSizeOptions, safePageSize].sort((a, b) => a - b);
43
+
44
+ const firstRowIndex = currentPage * safePageSize;
45
+ const lastRowIndex = Math.min(firstRowIndex + safePageSize, rowCount);
37
46
  const displayedRows = rowCount === 0 ? 0 : `${firstRowIndex + 1}–${lastRowIndex}`;
38
47
 
39
48
  const rowsPerPageLabel = localeText?.paginationRowsPerPage ?? 'Rows per page:';
@@ -79,11 +88,10 @@ export function Pagination(props: PaginationProps) {
79
88
  <select
80
89
  id={pageSizeId}
81
90
  className="ogx-pagination__select"
82
- value={pageSize}
91
+ value={safePageSize}
83
92
  onChange={handlePageSizeChange}
84
- aria-label="Rows per page"
85
93
  >
86
- {pageSizeOptions.map(option => (
94
+ {selectOptions.map(option => (
87
95
  <option key={option} value={option}>
88
96
  {option}
89
97
  </option>
@@ -7,11 +7,45 @@ import { DragHandleIcon } from '../ui/DragHandleIcon';
7
7
  import { DetailPanel } from '../DetailPanel/DetailPanel';
8
8
  import type { GridRowModel, GridColDef, GridRowId, GridColumnPinning, GridRowPinning, GridRowParams, GridCellParams, GridDetailPanelHeight, GridRowMeta } from '../../types';
9
9
  import type { CellColSpanInfo, RowSpanningCaches } from '../../hooks/features/useGridSpanning';
10
- import { isColumnPinned, calculatePinnedPositions, isRowPinned } from '../../utils/pinning';
11
-
10
+ import { isColumnPinned, calculatePinnedPositions, getPinnedEdgeFields, isRowPinned } from '../../utils/pinning';
11
+ import { resolveCellEditable } from '../../utils/editing';
12
+ import { tryGetCellValue } from '../../utils/values';
13
+ import { isSyntheticRowId } from '../../utils/syntheticRows';
14
+ import { getRenderedColumnWidth } from '../../utils/columnWidth';
15
+ import { DEFAULT_DETAIL_PANEL_HEIGHT } from '../../utils/detailPanel';
16
+
17
+ /** Sticky system cells (drag handle, detail toggle, checkbox) sit above scrolled, spanned and focused cells. */
18
+ const SYSTEM_CELL_Z_INDEX = 12;
19
+
20
+
21
+ /**
22
+ * Builds the Cell span info for a colSpan origin: the merged width and flex-grow are the sums over
23
+ * the columns it covers, taken from the rendered (layout-resolved) columns. The span never crosses a
24
+ * spacer, so a span cut by the column window only covers what is rendered.
25
+ */
26
+ function mergeColSpan<R extends GridRowModel>(
27
+ columns: GridColDef<R>[],
28
+ originIndex: number,
29
+ colSpan: number,
30
+ columnWidths: Record<string, number>,
31
+ ): CellColSpanInfo {
32
+ let width = 0;
33
+ let flexGrow = 0;
34
+ let covered = 0;
35
+ for (let i = originIndex; i < columns.length && covered < colSpan; i++) {
36
+ const col = columns[i];
37
+ if (col.isSpacer) break;
38
+ width += getRenderedColumnWidth(col, columnWidths);
39
+ flexGrow += col.flex ?? 0;
40
+ covered++;
41
+ }
42
+ return { spannedByColSpan: false, cellProps: { colSpan: covered, width, flexGrow } };
43
+ }
12
44
 
13
45
  export interface RowProps<R extends GridRowModel = GridRowModel> {
14
46
  row: R;
47
+ /** The row's id (from getRowId). Defaults to `row.id`; the grid always passes it. */
48
+ rowId?: GridRowId;
15
49
  columns: GridColDef<R>[];
16
50
  rowIndex: number;
17
51
  isSelected?: boolean;
@@ -29,6 +63,8 @@ export interface RowProps<R extends GridRowModel = GridRowModel> {
29
63
  onDetailPanelToggle?: (rowId: GridRowId) => void;
30
64
  detailPanelContent?: React.ReactNode;
31
65
  detailPanelHeight?: GridDetailPanelHeight;
66
+ /** Called with the rendered height of an `'auto'` detail panel whenever it changes. */
67
+ onDetailPanelHeightChange?: (rowId: GridRowId, height: number) => void;
32
68
 
33
69
  pinCheckboxColumn?: boolean;
34
70
  pinExpandColumn?: boolean;
@@ -43,8 +79,11 @@ export interface RowProps<R extends GridRowModel = GridRowModel> {
43
79
 
44
80
  editingCell?: { id: GridRowId; field: string; value: unknown; } | null;
45
81
  onEditStart?: (params: { id: GridRowId, field: string, value: unknown }) => void;
46
- onEditStop?: (params?: { cancel?: boolean }) => void;
82
+ /** `id` / `field` name the cell whose editor asked to stop. */
83
+ onEditStop?: (params?: { cancel?: boolean; id?: GridRowId; field?: string }) => void;
47
84
  onEditCellValueChange?: (params: { id: GridRowId, field: string, value: unknown }) => void;
85
+ /** Per-cell editability predicate (`DataGrid.isCellEditable`). */
86
+ isCellEditable?: (params: GridCellParams<R>) => boolean;
48
87
 
49
88
  focusedCellField?: string | null;
50
89
  isFocusVisible?: boolean;
@@ -53,11 +92,16 @@ export interface RowProps<R extends GridRowModel = GridRowModel> {
53
92
  rowSpanningCaches?: RowSpanningCaches;
54
93
  rowHeight?: number;
55
94
  rowMeta?: GridRowMeta;
95
+ /** Position of each visible data column in render order, for `colIndex` and `aria-colindex`. */
96
+ columnIndexMap?: Map<string, number>;
97
+ /** 1-based `aria-rowindex` of this row in the whole grid (header rows included). Defaults to `rowIndex + 2`. */
98
+ ariaRowIndex?: number;
56
99
  }
57
100
 
58
101
  export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
59
102
  const {
60
103
  row,
104
+ rowId: rowIdProp,
61
105
  columns,
62
106
  rowIndex,
63
107
  isSelected = false,
@@ -73,7 +117,8 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
73
117
  isDetailPanelExpanded = false,
74
118
  onDetailPanelToggle,
75
119
  detailPanelContent,
76
- detailPanelHeight = 200,
120
+ detailPanelHeight = DEFAULT_DETAIL_PANEL_HEIGHT,
121
+ onDetailPanelHeightChange,
77
122
  pinCheckboxColumn = true,
78
123
  pinExpandColumn = true,
79
124
  rowReordering = false,
@@ -88,6 +133,7 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
88
133
  onEditStart,
89
134
  onEditStop,
90
135
  onEditCellValueChange,
136
+ isCellEditable,
91
137
 
92
138
  focusedCellField,
93
139
  isFocusVisible,
@@ -95,22 +141,16 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
95
141
  colspanMap,
96
142
  rowSpanningCaches,
97
143
  rowHeight = 52,
98
- rowMeta
144
+ rowMeta,
145
+ columnIndexMap,
146
+ ariaRowIndex,
99
147
  } = props;
100
148
 
149
+ const id = rowIdProp ?? row.id;
150
+ // All hooks must come before any early returns (Rules of Hooks).
151
+ // DOM focus is moved onto the focused cell by useGridKeyboardNavigation, not by the row.
101
152
  const checkboxId = useId();
102
- const expandCellRef = React.useRef<HTMLDivElement>(null);
103
- const checkboxCellRef = React.useRef<HTMLDivElement>(null);
104
-
105
- // All hooks must come before any early returns (Rules of Hooks)
106
- React.useLayoutEffect(() => {
107
- if (focusedCellField === '__expand_col__' && expandCellRef.current) {
108
- expandCellRef.current.focus({ preventScroll: true });
109
- }
110
- if (focusedCellField === '__checkbox_col__' && checkboxCellRef.current) {
111
- checkboxCellRef.current.focus({ preventScroll: true });
112
- }
113
- }, [focusedCellField]);
153
+ const detailPanelId = useId();
114
154
 
115
155
  const pinnedPositions = React.useMemo(() => {
116
156
  return calculatePinnedPositions(
@@ -125,21 +165,28 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
125
165
  );
126
166
  }, [columns, columnWidths, pinnedColumns, checkboxSelection, pinCheckboxColumn, hasDetailPanel, pinExpandColumn, rowReordering]);
127
167
 
128
- const isGroupRow = rowMeta?.hasChildren === true;
168
+ const pinnedEdges = React.useMemo(() => getPinnedEdgeFields(columns, pinnedColumns), [columns, pinnedColumns]);
129
169
 
170
+ const isGroupRow = rowMeta?.hasChildren === true;
171
+ // Group rows, subtotal rows and auto-created tree parents: not data, so no selection or detail panel.
172
+ const isSyntheticRow = rowMeta?.isGroupRow === true;
173
+ const showDetailPanel = hasDetailPanel && !isSyntheticRow;
174
+ // The pivot Grand Total is synthetic too, but its cells show values like any row.
175
+ const isSelectableRow = !isSyntheticRow && !isSyntheticRowId(id);
176
+
177
+ // Which cells may be edited is decided per cell below (resolveCellEditable), not per row:
178
+ // tree-data parents are real rows and stay editable; synthetic group rows never are.
130
179
  const handleCellEditStart = React.useCallback((field: string, value: unknown) => {
131
- if (!isGroupRow) {
132
- onEditStart?.({ id: row.id, field, value });
133
- }
134
- }, [isGroupRow, row.id, onEditStart]);
180
+ onEditStart?.({ id, field, value });
181
+ }, [id, onEditStart]);
135
182
 
136
183
  const handleCellValueChange = React.useCallback((field: string, newValue: unknown) => {
137
- onEditCellValueChange?.({ id: row.id, field, value: newValue });
138
- }, [onEditCellValueChange, row.id]);
184
+ onEditCellValueChange?.({ id, field, value: newValue });
185
+ }, [onEditCellValueChange, id]);
139
186
 
140
- const handleEditStopWrapper = React.useCallback((cancel?: boolean) => {
141
- onEditStop?.({ cancel });
142
- }, [onEditStop]);
187
+ const handleEditStopWrapper = React.useCallback((cancel?: boolean, field?: string) => {
188
+ onEditStop?.({ cancel, id, field });
189
+ }, [onEditStop, id]);
143
190
 
144
191
  // ── Skeleton row (shown during infinite-scroll fetch) ─────────────────────
145
192
  if (row._isSkeleton) {
@@ -161,32 +208,42 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
161
208
  );
162
209
  }
163
210
 
211
+ // `.ogx__cell--editing` covers custom renderEditCell editors, which have no .ogx__edit-cell wrapper.
164
212
  const handleRowClick = (event: React.MouseEvent) => {
165
213
 
166
- if ((event.target as HTMLElement).closest('.ogx-checkbox-wrapper, .ogx-expand-icon, .ogx-drag-handle, .ogx__edit-cell')) {
214
+ if ((event.target as HTMLElement).closest('.ogx-checkbox-wrapper, .ogx-expand-icon, .ogx-drag-handle, .ogx__edit-cell, .ogx__cell--editing')) {
167
215
  return;
168
216
  }
169
- onRowClick?.({ row, id: row.id, rowIndex });
217
+ onRowClick?.({ row, id, rowIndex });
170
218
  };
171
219
 
172
220
  const handleRowDoubleClick = (event: React.MouseEvent) => {
173
- if ((event.target as HTMLElement).closest('.ogx-checkbox-wrapper, .ogx-expand-icon, .ogx-drag-handle, .ogx__edit-cell')) {
221
+ if ((event.target as HTMLElement).closest('.ogx-checkbox-wrapper, .ogx-expand-icon, .ogx-drag-handle, .ogx__edit-cell, .ogx__cell--editing')) {
174
222
  return;
175
223
  }
176
- onRowDoubleClick?.({ row, id: row.id, rowIndex });
224
+ onRowDoubleClick?.({ row, id, rowIndex });
177
225
  };
178
226
 
179
227
  const handleCheckboxChange = (event: React.ChangeEvent<HTMLInputElement>) => {
180
228
  event.stopPropagation();
181
- onSelectionChange?.(row.id, event.target.checked);
229
+ onSelectionChange?.(id, event.target.checked);
182
230
  };
183
231
 
184
232
  const handleDetailPanelToggle = (event: React.MouseEvent) => {
185
233
  event.stopPropagation();
186
- onDetailPanelToggle?.(row.id);
234
+ onDetailPanelToggle?.(id);
187
235
  };
188
236
 
189
- const rowPinnedPosition = isRowPinned(row.id, pinnedRows);
237
+ // aria-colindex: system columns first (reorder, expand, checkbox), then the data columns.
238
+ const reorderColIndex = rowReordering ? 1 : 0;
239
+ const expandColIndex = hasDetailPanel ? reorderColIndex + 1 : reorderColIndex;
240
+ const checkboxColIndex = checkboxSelection ? expandColIndex + 1 : expandColIndex;
241
+ const systemColumnCount = checkboxColIndex;
242
+ const dataColumnCount = columnIndexMap?.size ?? columns.filter(c => !c.isSpacer).length;
243
+ const isHierarchyRow = rowMeta !== undefined;
244
+ const hasChildren = rowMeta?.hasChildren === true;
245
+
246
+ const rowPinnedPosition = isRowPinned(id, pinnedRows);
190
247
  const isRowPinnedTop = rowPinnedPosition === 'top';
191
248
  const isRowPinnedBottom = rowPinnedPosition === 'bottom';
192
249
 
@@ -199,7 +256,8 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
199
256
  isRowPinnedBottom && 'ogx__row--pinned-bottom',
200
257
  isDragging && 'ogx__row--dragging',
201
258
  isDragOver && 'ogx__row--drag-over',
202
- isGroupRow && 'ogx__row--group'
259
+ isGroupRow && 'ogx__row--group',
260
+ rowMeta?.isGroupFooter && 'ogx__row--group-footer'
203
261
  ].filter(Boolean).join(' ');
204
262
 
205
263
  return (
@@ -209,11 +267,13 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
209
267
  onClick={handleRowClick}
210
268
  onDoubleClick={onRowDoubleClick ? handleRowDoubleClick : undefined}
211
269
  role="row"
212
- aria-rowindex={rowIndex + 1}
270
+ aria-rowindex={ariaRowIndex ?? rowIndex + 2}
213
271
  aria-selected={isSelected}
272
+ aria-level={isHierarchyRow ? (rowMeta?.treeDepth ?? 0) + 1 : undefined}
273
+ aria-expanded={hasChildren ? rowMeta?.isExpanded === true : undefined}
214
274
  data-rowindex={rowIndex}
215
- onDragOver={onDragOver ? onDragOver(row.id) : undefined}
216
- onDrop={onDrop ? onDrop(row.id) : undefined}
275
+ onDragOver={onDragOver ? onDragOver(id) : undefined}
276
+ onDrop={onDrop ? onDrop(id) : undefined}
217
277
  style={{
218
278
  maxHeight: `${rowHeight}px`,
219
279
  minHeight: `${rowHeight}px`,
@@ -224,20 +284,23 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
224
284
  { }
225
285
  {rowReordering && (
226
286
  <div
227
- className="ogx__cell ogx__cell--drag-handle"
287
+ className={`ogx__cell ogx__cell--drag-handle ${focusedCellField === '__reorder_col__' ? 'ogx__cell--focused' : ''} ${(focusedCellField === '__reorder_col__' && isFocusVisible) ? 'ogx__cell--focus-visible' : ''}`}
228
288
  role="gridcell"
229
- aria-label="Drag to reorder row"
289
+ aria-label={onDragStart ? 'Drag to reorder row' : undefined}
290
+ aria-colindex={reorderColIndex}
291
+ data-field="__reorder_col__"
292
+ tabIndex={-1}
230
293
  style={{
231
294
  position: 'sticky',
232
295
  left: 0,
233
- zIndex: 4
296
+ zIndex: SYSTEM_CELL_Z_INDEX
234
297
  }}
235
- draggable={true}
236
- onDragStart={onDragStart ? onDragStart(row.id) : undefined}
298
+ draggable={!!onDragStart}
299
+ onDragStart={onDragStart ? onDragStart(id) : undefined}
237
300
  onDragEnd={onDragEnd}
238
301
  onClick={(e) => e.stopPropagation()}
239
302
  >
240
- <DragHandleIcon />
303
+ {onDragStart && <DragHandleIcon />}
241
304
  </div>
242
305
  )}
243
306
 
@@ -248,14 +311,16 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
248
311
  } ${focusedCellField === '__expand_col__' ? 'ogx__cell--focused' : ''} ${(focusedCellField === '__expand_col__' && isFocusVisible) ? 'ogx__cell--focus-visible' : ''
249
312
  }`}
250
313
  role="gridcell"
251
- aria-label={isDetailPanelExpanded ? 'Collapse row details' : 'Expand row details'}
252
- aria-expanded={isDetailPanelExpanded}
314
+ aria-label={showDetailPanel ? (isDetailPanelExpanded ? 'Collapse row details' : 'Expand row details') : undefined}
315
+ aria-expanded={showDetailPanel ? isDetailPanelExpanded : undefined}
316
+ aria-controls={showDetailPanel && isDetailPanelExpanded ? detailPanelId : undefined}
317
+ aria-colindex={expandColIndex}
318
+ data-field="__expand_col__"
253
319
  style={{
254
320
  position: pinExpandColumn ? 'sticky' : undefined,
255
321
  left: pinExpandColumn ? (rowReordering ? 48 : 0) : undefined,
256
- zIndex: pinExpandColumn ? 5 : undefined
322
+ zIndex: pinExpandColumn ? SYSTEM_CELL_Z_INDEX : undefined
257
323
  }}
258
- ref={expandCellRef}
259
324
  tabIndex={-1}
260
325
  onClick={(e) => {
261
326
  e.stopPropagation();
@@ -269,11 +334,14 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
269
334
  });
270
335
  }}
271
336
  >
272
- <ExpandIcon
273
- isExpanded={isDetailPanelExpanded}
274
- onClick={handleDetailPanelToggle}
275
- variant="plus-minus"
276
- />
337
+ {showDetailPanel && (
338
+ <ExpandIcon
339
+ isExpanded={isDetailPanelExpanded}
340
+ onClick={handleDetailPanelToggle}
341
+ variant="plus-minus"
342
+ tabIndex={-1}
343
+ />
344
+ )}
277
345
  </div>
278
346
  )}
279
347
 
@@ -284,12 +352,13 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
284
352
  } ${focusedCellField === '__checkbox_col__' ? 'ogx__cell--focused' : ''} ${(focusedCellField === '__checkbox_col__' && isFocusVisible) ? 'ogx__cell--focus-visible' : ''
285
353
  }`}
286
354
  role="gridcell"
355
+ aria-colindex={checkboxColIndex}
356
+ data-field="__checkbox_col__"
287
357
  style={{
288
358
  position: pinCheckboxColumn ? 'sticky' : undefined,
289
359
  left: pinCheckboxColumn ? ((rowReordering ? 48 : 0) + (hasDetailPanel && pinExpandColumn ? 48 : 0)) : undefined,
290
- zIndex: pinCheckboxColumn ? 11 : undefined
360
+ zIndex: pinCheckboxColumn ? SYSTEM_CELL_Z_INDEX : undefined
291
361
  }}
292
- ref={checkboxCellRef}
293
362
  tabIndex={-1}
294
363
  onClick={(e) => {
295
364
  e.stopPropagation();
@@ -303,21 +372,23 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
303
372
  });
304
373
  }}
305
374
  >
306
- <Checkbox
307
- id={checkboxId}
308
- name={`ogx-select-row-${row.id}`}
309
- checked={isSelected}
310
- onChange={handleCheckboxChange}
311
- onClick={(e) => e.stopPropagation()}
312
- aria-label={isSelected ? `Deselect row ${row.id}` : `Select row ${row.id}`}
313
- tabIndex={-1}
314
- onMouseDown={(e) => e.preventDefault()}
315
- />
375
+ {isSelectableRow && (
376
+ <Checkbox
377
+ id={checkboxId}
378
+ name={`ogx-select-row-${id}`}
379
+ checked={isSelected}
380
+ onChange={handleCheckboxChange}
381
+ onClick={(e) => e.stopPropagation()}
382
+ aria-label={isSelected ? `Deselect row ${id}` : `Select row ${id}`}
383
+ tabIndex={-1}
384
+ onMouseDown={(e) => e.preventDefault()}
385
+ />
386
+ )}
316
387
  </div>
317
388
  )}
318
389
 
319
390
  { }
320
- {columns.map((colDef, colIndex) => {
391
+ {columns.map((colDef, localIndex) => {
321
392
  if (colDef.isSpacer) {
322
393
  return (
323
394
  <div
@@ -327,22 +398,45 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
327
398
  />
328
399
  );
329
400
  }
330
- const value = colDef.valueGetter
331
- ? colDef.valueGetter({ row, field: colDef.field, value: row[colDef.field] })
332
- : row[colDef.field];
401
+ // A synthetic row holds its grouping value and aggregates under the column fields; a
402
+ // valueGetter written for data rows would recompute (or crash) from fields it lacks.
403
+ // A valueGetter that throws is contained to its cell.
404
+ let value: unknown;
405
+ let valueError: unknown;
406
+ if (isSyntheticRow) {
407
+ value = row[colDef.field];
408
+ } else {
409
+ ({ value, error: valueError } = tryGetCellValue(row, colDef.field, colDef));
410
+ }
411
+ const colIndex = columnIndexMap?.get(colDef.field) ?? localIndex;
333
412
 
334
413
  const effectiveWidth = columnWidths[colDef.field] ?? colDef.width;
335
414
 
336
415
  const pinnedPosition = isColumnPinned(colDef.field, pinnedColumns);
337
- const pinnedOffset = pinnedPosition ? pinnedPositions[colDef.field] : undefined;
338
416
 
339
- const isEditing = editingCell?.id === row.id && editingCell?.field === colDef.field;
417
+ const isEditable = Boolean(onEditStart) && resolveCellEditable(
418
+ { row, field: colDef.field, value, colDef, rowIndex, colIndex, rowMeta },
419
+ isCellEditable
420
+ );
421
+ const isEditing = editingCell?.id === id && editingCell?.field === colDef.field;
340
422
 
341
423
  const cellValue = isEditing ? editingCell?.value : value;
342
424
 
343
- const colSpanInfo = colspanMap?.get(row.id)?.[colDef.field];
344
- const rowSpan = rowSpanningCaches?.spannedCells[row.id]?.[colDef.field];
345
- const isHiddenByRowSpan = rowSpanningCaches?.hiddenCells[row.id]?.[colDef.field] || false;
425
+ const storedColSpanInfo = colspanMap?.get(id)?.[colDef.field];
426
+ const colSpanInfo = storedColSpanInfo && !storedColSpanInfo.spannedByColSpan && storedColSpanInfo.cellProps.colSpan > 1
427
+ ? mergeColSpan(columns, colIndex, storedColSpanInfo.cellProps.colSpan, columnWidths)
428
+ : storedColSpanInfo;
429
+ // A merged cell ends at the last column it covers: a right-pinned one is anchored by
430
+ // that column's right edge, and it is the left-pinned edge cell if that column is.
431
+ const lastCoveredField = colSpanInfo && !colSpanInfo.spannedByColSpan
432
+ ? columns[colIndex + colSpanInfo.cellProps.colSpan - 1]?.field ?? colDef.field
433
+ : colDef.field;
434
+ const offsetField = pinnedPosition === 'right' ? lastCoveredField : colDef.field;
435
+ const pinnedOffset = pinnedPosition ? pinnedPositions[offsetField] : undefined;
436
+ const isPinnedEdge = (pinnedPosition === 'left' && lastCoveredField === pinnedEdges.lastLeft)
437
+ || (pinnedPosition === 'right' && colDef.field === pinnedEdges.firstRight);
438
+ const rowSpan = rowSpanningCaches?.spannedCells[id]?.[colDef.field];
439
+ const isHiddenByRowSpan = rowSpanningCaches?.hiddenCells[id]?.[colDef.field] || false;
346
440
 
347
441
  return (
348
442
  <Cell
@@ -352,16 +446,18 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
352
446
  colDef={colDef}
353
447
  rowIndex={rowIndex}
354
448
  colIndex={colIndex}
449
+ ariaColIndex={systemColumnCount + colIndex + 1}
355
450
  isSelected={isSelected}
356
451
  onCellClick={onCellClick}
357
452
  width={effectiveWidth}
358
453
  pinnedPosition={pinnedPosition}
359
454
  pinnedOffset={pinnedOffset}
455
+ isPinnedEdge={isPinnedEdge}
360
456
 
361
457
  isFocused={focusedCellField === colDef.field}
362
458
  isFocusVisible={isFocusVisible}
363
459
 
364
- isEditable={colDef.editable}
460
+ isEditable={isEditable}
365
461
  isEditing={isEditing}
366
462
  onCellEditStart={handleCellEditStart}
367
463
  onEditStop={handleEditStopWrapper}
@@ -371,20 +467,24 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
371
467
  rowSpan={rowSpan}
372
468
  isHiddenByRowSpan={isHiddenByRowSpan}
373
469
  rowMeta={rowMeta}
470
+ valueError={valueError}
374
471
  />
375
472
  );
376
473
  })}
377
474
  </div>
378
475
 
379
476
  { }
380
- {hasDetailPanel && (
477
+ {showDetailPanel && (
381
478
  <DetailPanel
479
+ id={detailPanelId}
480
+ colSpan={systemColumnCount + dataColumnCount}
382
481
  row={row}
383
- rowId={row.id}
482
+ rowId={id}
384
483
  rowIndex={rowIndex}
385
484
  content={detailPanelContent}
386
485
  height={detailPanelHeight}
387
486
  isExpanded={isDetailPanelExpanded}
487
+ onHeightChange={onDetailPanelHeightChange}
388
488
  />
389
489
  )}
390
490
  </>