@opencorestack/opengridx 3.5.1 → 3.7.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 (114) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +2 -0
  3. package/dist/ai.cjs +4 -2
  4. package/dist/ai.cjs.map +1 -1
  5. package/dist/ai.d.ts +2 -2
  6. package/dist/ai.es.js +4 -2
  7. package/dist/ai.es.js.map +1 -1
  8. package/dist/index.d.ts +62 -0
  9. package/dist/opengridx.css +1 -1
  10. package/dist/opengridx.es.js +9643 -9024
  11. package/dist/opengridx.es.js.map +1 -1
  12. package/dist/opengridx.umd.js +16 -16
  13. package/dist/opengridx.umd.js.map +1 -1
  14. package/docs/API_REFERENCE.md +17 -3
  15. package/docs/README.md +2 -0
  16. package/docs/architecture/datagrid-orchestration.md +46 -29
  17. package/docs/architecture/grid-row-meta.md +1 -1
  18. package/docs/customization/slots-api.md +1 -1
  19. package/docs/customization/theming.md +43 -0
  20. package/docs/features/ai-toolkit.md +2 -2
  21. package/docs/features/export-guide.md +2 -0
  22. package/docs/features/header-filters.md +34 -0
  23. package/docs/features/keyboard-navigation.md +2 -0
  24. package/docs/features/nextjs.md +241 -0
  25. package/docs/features/pdf-export.md +2 -0
  26. package/docs/features/pinning.md +2 -0
  27. package/docs/features/rtl.md +58 -0
  28. package/docs/features/virtualization.md +16 -2
  29. package/docs/performance.md +89 -119
  30. package/docs/roadmap.md +4 -2
  31. package/lib/ai/promptHandler.ts +12 -8
  32. package/lib/ai/validate.ts +5 -2
  33. package/lib/components/AiAssistant/GridAiAssistantPanel.css +4 -2
  34. package/lib/components/Cell/Cell.css +31 -9
  35. package/lib/components/Cell/Cell.tsx +2 -2
  36. package/lib/components/ColumnGroupHeader/ColumnGroupHeader.css +4 -4
  37. package/lib/components/ColumnResizeHandle/ColumnResizeHandle.css +3 -3
  38. package/lib/components/ColumnResizeHandle/ColumnResizeHandle.tsx +18 -2
  39. package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.css +3 -1
  40. package/lib/components/DataGrid/DataGrid.css +19 -13
  41. package/lib/components/DataGrid/DataGrid.tsx +163 -670
  42. package/lib/components/DataGrid/GridAggregationFooter.tsx +3 -3
  43. package/lib/components/DataGrid/GridListView.tsx +3 -1
  44. package/lib/components/DataGrid/GridRoot.tsx +239 -0
  45. package/lib/components/DataGrid/GridViewport.tsx +314 -0
  46. package/lib/components/FilterPanel/FilterPanel.css +9 -8
  47. package/lib/components/Header/ColumnMenu.css +5 -4
  48. package/lib/components/Header/ColumnMenu.tsx +14 -6
  49. package/lib/components/Header/Header.css +23 -14
  50. package/lib/components/Header/Header.tsx +16 -9
  51. package/lib/components/Header/headerColumnStyle.ts +3 -2
  52. package/lib/components/HeaderFilter/HeaderFilter.css +9 -3
  53. package/lib/components/HeaderFilter/HeaderFilterCell.tsx +21 -18
  54. package/lib/components/HeaderFilter/HeaderFilterOperatorMenu.tsx +5 -4
  55. package/lib/components/HeaderFilter/HeaderFilterRow.tsx +9 -3
  56. package/lib/components/ListView/ListViewRow.tsx +1 -1
  57. package/lib/components/Pagination/Pagination.css +5 -1
  58. package/lib/components/Pagination/Pagination.tsx +4 -4
  59. package/lib/components/Row/Row.css +2 -2
  60. package/lib/components/Row/Row.tsx +3 -3
  61. package/lib/components/SkeletonRow/SkeletonRow.tsx +2 -2
  62. package/lib/components/Toolbar/GridToolbar.css +8 -7
  63. package/lib/components/Toolbar/GridToolbar.tsx +8 -28
  64. package/lib/components/Toolbar/PivotPanel.css +3 -3
  65. package/lib/components/Toolbar/PivotPanel.tsx +4 -7
  66. package/lib/components/ui/ExpandIcon.css +13 -0
  67. package/lib/components/ui/ExpandIcon.tsx +2 -4
  68. package/lib/hooks/core/useGridCellSelection.ts +20 -5
  69. package/lib/hooks/core/useGridColumns.tsx +2 -2
  70. package/lib/hooks/core/useGridDevWarnings.ts +3 -1
  71. package/lib/hooks/core/useGridEditPipeline.ts +55 -0
  72. package/lib/hooks/core/useGridKeyboardNavigation.ts +7 -4
  73. package/lib/hooks/core/useGridRangeEditing.ts +129 -0
  74. package/lib/hooks/core/useGridRenderWindow.ts +66 -0
  75. package/lib/hooks/core/useGridScrollSync.ts +28 -5
  76. package/lib/hooks/core/useGridScrollViewport.ts +42 -0
  77. package/lib/hooks/core/useGridSelectionAndStateApi.ts +61 -0
  78. package/lib/hooks/core/useGridToolbarStack.ts +49 -0
  79. package/lib/hooks/core/useGridViewApis.ts +46 -0
  80. package/lib/hooks/core/useLayout.ts +13 -6
  81. package/lib/hooks/useRowGrouping.ts +9 -9
  82. package/lib/styles/base/variables.css +12 -0
  83. package/lib/theme/DataGridThemeProvider.tsx +15 -0
  84. package/lib/theme/__baseline__/default-theme.darwin.chromium.json +1503 -0
  85. package/lib/theme/__baseline__/default-theme.darwin.firefox.json +1503 -0
  86. package/lib/theme/__baseline__/default-theme.darwin.webkit.json +1503 -0
  87. package/lib/theme/types.ts +31 -0
  88. package/lib/types/ai.ts +109 -0
  89. package/lib/types/api.ts +169 -0
  90. package/lib/types/columns.ts +341 -0
  91. package/lib/types/dataSource.ts +40 -0
  92. package/lib/types/editing.ts +77 -0
  93. package/lib/types/export.ts +164 -0
  94. package/lib/types/filtering.ts +52 -0
  95. package/lib/types/grouping.ts +27 -0
  96. package/lib/types/index.ts +18 -1672
  97. package/lib/types/locale.ts +51 -0
  98. package/lib/types/props.ts +405 -0
  99. package/lib/types/rows.ts +132 -0
  100. package/lib/types/selection.ts +66 -0
  101. package/lib/types/slots.ts +56 -0
  102. package/lib/types/sorting.ts +25 -0
  103. package/lib/types/state.ts +35 -0
  104. package/lib/utils/aggregation/index.ts +56 -1
  105. package/lib/utils/direction.ts +52 -0
  106. package/lib/utils/export/exportToExcelAdvanced.ts +9 -2
  107. package/lib/utils/export/exportToPdf.ts +2 -1
  108. package/lib/utils/filtering/index.ts +58 -6
  109. package/lib/utils/headerFilterLocale.ts +37 -0
  110. package/lib/utils/scroll/index.ts +5 -3
  111. package/lib/utils/sorting/index.ts +141 -26
  112. package/lib/utils/values.ts +10 -6
  113. package/llms.txt +4 -2
  114. package/package.json +4 -2
@@ -1,4 +1,5 @@
1
1
  import { useRef, useState, useCallback, useEffect } from 'react';
2
+ import { getScrollStart, setScrollStart } from '../../utils/direction';
2
3
  import type { GridRowScrollEndParams } from '../../types';
3
4
  import { getRowsScrollEndParams } from '../../utils/scroll/scrollEnd';
4
5
 
@@ -82,16 +83,18 @@ export function useGridScrollSync(params: UseGridScrollSyncParams): UseGridScrol
82
83
  const handleScroll = useCallback((event: React.UIEvent<HTMLDivElement>) => {
83
84
  const target = event.currentTarget;
84
85
  const now = performance.now();
86
+ // Counted from the inline start, so the column window is the same in a right-to-left grid.
87
+ const scrollLeft = getScrollStart(target);
85
88
 
86
89
  // Velocity: total positional delta / elapsed time (px/ms)
87
90
  const deltaPos = Math.abs(target.scrollTop - prevRef.current.scrollTop)
88
- + Math.abs(target.scrollLeft - prevRef.current.scrollLeft);
91
+ + Math.abs(scrollLeft - prevRef.current.scrollLeft);
89
92
  const deltaTime = now - prevRef.current.time;
90
93
  const velocity = deltaTime > 0 ? deltaPos / deltaTime : 0;
91
94
  const movedVertically = target.scrollTop !== prevRef.current.scrollTop;
92
95
 
93
- prevRef.current = { scrollTop: target.scrollTop, scrollLeft: target.scrollLeft, time: now };
94
- pendingRef.current = { scrollTop: target.scrollTop, scrollLeft: target.scrollLeft, velocity };
96
+ prevRef.current = { scrollTop: target.scrollTop, scrollLeft, time: now };
97
+ pendingRef.current = { scrollTop: target.scrollTop, scrollLeft, velocity };
95
98
 
96
99
  if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
97
100
  rafRef.current = requestAnimationFrame(() => {
@@ -120,16 +123,36 @@ export function useGridScrollSync(params: UseGridScrollSyncParams): UseGridScrol
120
123
  checkRowsScrollEnd(el);
121
124
  }, [rowCount, autoHeight, checkRowsScrollEnd]);
122
125
 
126
+ // Switching `dir` on the grid or an ancestor moves the content to the other side, and browsers
127
+ // reset (Firefox without a scroll event) or keep the scroll position as they please. Re-read it
128
+ // so the column window matches what the viewport shows.
129
+ useEffect(() => {
130
+ const doc = elementRef.current?.ownerDocument ?? (typeof document === 'undefined' ? null : document);
131
+ if (!doc || typeof MutationObserver === 'undefined') return;
132
+ const observer = new MutationObserver(() => {
133
+ const target = elementRef.current;
134
+ if (!target) return;
135
+ const actual = { scrollTop: target.scrollTop, scrollLeft: getScrollStart(target) };
136
+ const pending = pendingRef.current;
137
+ if (actual.scrollTop === pending.scrollTop && actual.scrollLeft === pending.scrollLeft) return;
138
+ pendingRef.current = { ...pending, ...actual };
139
+ prevRef.current = { ...prevRef.current, ...actual };
140
+ setScrollState(prev => ({ ...prev, ...actual }));
141
+ });
142
+ observer.observe(doc.documentElement, { attributes: true, attributeFilter: ['dir'], subtree: true });
143
+ return () => observer.disconnect();
144
+ }, []);
145
+
123
146
  const attachViewport = useCallback((element: HTMLDivElement | null) => {
124
147
  elementRef.current = element;
125
148
  if (!element) return;
126
149
  const { scrollTop, scrollLeft } = pendingRef.current;
127
150
  if (scrollTop === 0 && scrollLeft === 0) return;
128
151
  element.scrollTop = scrollTop;
129
- element.scrollLeft = scrollLeft;
152
+ setScrollStart(element, scrollLeft);
130
153
  // The browser clamps to the new content. If it did, sync the state to what the element
131
154
  // shows (no scroll event fires when the position ends up unchanged).
132
- const actual = { scrollTop: element.scrollTop, scrollLeft: element.scrollLeft };
155
+ const actual = { scrollTop: element.scrollTop, scrollLeft: getScrollStart(element) };
133
156
  if (actual.scrollTop !== scrollTop || actual.scrollLeft !== scrollLeft) {
134
157
  pendingRef.current = { ...pendingRef.current, ...actual };
135
158
  prevRef.current = { ...prevRef.current, ...actual };
@@ -0,0 +1,42 @@
1
+ import type { MutableRefObject, RefObject } from 'react';
2
+ import type { GridRowModel } from '../../types';
3
+ import { useGridViewport, type UseGridViewportParams } from './useGridViewport';
4
+ import { useGridMaxContentHeight } from './useGridMaxContentHeight';
5
+ import { useGridVirtualization, type UseGridVirtualizationParams } from './useGridVirtualization';
6
+ import type { LayoutResult } from './useLayout';
7
+
8
+ export interface UseGridScrollViewportParams<R extends GridRowModel> {
9
+ viewport: Omit<UseGridViewportParams, 'viewportRef' | 'rowCount'>;
10
+ viewportRef: MutableRefObject<HTMLDivElement | null>;
11
+ containerRef: RefObject<HTMLDivElement | null>;
12
+ layout: LayoutResult<R>;
13
+ viewportWidth: number;
14
+ viewportHeight: number;
15
+ /** The virtualization inputs that do not come from the viewport. */
16
+ virtualization: Pick<UseGridVirtualizationParams<R>,
17
+ 'autoHeight' | 'rowReordering' | 'hasDetailPanel' | 'checkboxSelection' | 'pinCheckboxColumn' | 'pinExpandColumn'>;
18
+ }
19
+
20
+ /**
21
+ * Steps 32–33 of `DataGrid` (docs/architecture/datagrid-orchestration.md), in their call order: scroll sync
22
+ * and viewport measurement, the tallest element this browser lays out (32a), and the render window.
23
+ */
24
+ export function useGridScrollViewport<R extends GridRowModel>(params: UseGridScrollViewportParams<R>) {
25
+ const { viewport, viewportRef, containerRef, layout, viewportWidth, viewportHeight, virtualization: options } = params;
26
+
27
+ const { scrollTop, scrollLeft, overscanRows, handleScroll, setViewportElement } = useGridViewport({
28
+ ...viewport, rowCount: layout.unpinnedRowsLength, viewportRef,
29
+ });
30
+
31
+ const maxContentHeight = useGridMaxContentHeight(containerRef);
32
+ const virtualization = useGridVirtualization({
33
+ layout, scrollTop, scrollLeft,
34
+ viewportWidth,
35
+ viewportHeight,
36
+ ...options,
37
+ overscanRows,
38
+ maxContentHeight: maxContentHeight ?? Infinity,
39
+ });
40
+
41
+ return { handleScroll, setViewportElement, maxContentHeight, virtualization };
42
+ }
@@ -0,0 +1,61 @@
1
+ import type { GridRowModel } from '../../types';
2
+ import { useGridRowInteractions, type UseGridRowInteractionsParams, type UseGridRowInteractionsResult } from './useGridRowInteractions';
3
+ import { useGridApiMethods, type UseGridApiMethodsParams } from './useGridApiMethods';
4
+ import type { UseGridControlledStateReturn } from './useGridControlledState';
5
+
6
+ /** The state API inputs that do not come from `useGridControlledState`. */
7
+ export type GridStateApiParams = Pick<UseGridApiMethodsParams,
8
+ 'apiRef' | 'multiSort' | 'selectedRowIds' | 'disableMultipleRowSelection' | 'isSyntheticRowId' | 'getVisibleRows' |
9
+ 'getAllFilteredRows' | 'visibleColumns'>;
10
+
11
+ export interface UseGridSelectionAndStateApiParams<R extends GridRowModel> {
12
+ controlledState: UseGridControlledStateReturn;
13
+ interactions: Omit<UseGridRowInteractionsParams<R>, 'selectedRowIds' | 'onSelectionModelChange' | 'disableMultipleRowSelection'>;
14
+ api: GridStateApiParams;
15
+ }
16
+
17
+ /**
18
+ * Steps 23–24 of `DataGrid` (docs/architecture/datagrid-orchestration.md), in their call order: row
19
+ * selection and the row click / checkbox handlers, then the state API methods (sort, filter, pagination,
20
+ * selection, row grouping, aggregation, visibility and pivot setters, `getGridAiState`) installed on `apiRef`.
21
+ */
22
+ export function useGridSelectionAndStateApi<R extends GridRowModel>(
23
+ params: UseGridSelectionAndStateApiParams<R>
24
+ ): UseGridRowInteractionsResult<R> {
25
+ const { controlledState, interactions, api } = params;
26
+ const {
27
+ sortModel, handleSortModelChange, filterModel, handleFilterModelChange, effectivePaginationModel,
28
+ handlePaginationModelChange, handleRowSelectionModelChange, rowGroupingModel, handleRowGroupingModelChange,
29
+ aggregationModel, handleAggregationModelChange, columnVisibilityModel, handleColumnVisibilityModelChange,
30
+ currentPivotModel, handlePivotModelChange,
31
+ } = controlledState;
32
+
33
+ const result = useGridRowInteractions<R>({
34
+ ...interactions,
35
+ selectedRowIds: api.selectedRowIds,
36
+ onSelectionModelChange: handleRowSelectionModelChange,
37
+ disableMultipleRowSelection: api.disableMultipleRowSelection,
38
+ });
39
+
40
+ useGridApiMethods({
41
+ ...api,
42
+ onSortModelChange: handleSortModelChange,
43
+ sortModel,
44
+ filterModel,
45
+ onFilterModelChange: handleFilterModelChange,
46
+ paginationModel: effectivePaginationModel,
47
+ onPaginationModelChange: handlePaginationModelChange,
48
+ getPendingRowSelectionModel: controlledState.getPendingRowSelectionModel,
49
+ onRowSelectionModelChange: handleRowSelectionModelChange,
50
+ rowGroupingModel,
51
+ onRowGroupingModelChange: handleRowGroupingModelChange,
52
+ aggregationModel,
53
+ onAggregationModelChange: handleAggregationModelChange,
54
+ columnVisibilityModel,
55
+ onColumnVisibilityModelChange: handleColumnVisibilityModelChange,
56
+ pivotModel: currentPivotModel,
57
+ onPivotModelChange: handlePivotModelChange,
58
+ });
59
+
60
+ return result;
61
+ }
@@ -0,0 +1,49 @@
1
+ import type { MutableRefObject, RefObject } from 'react';
2
+ import type { GridApi, GridLocaleText, GridRowModel } from '../../types';
3
+ import { useGridColumnsPanel } from './useGridColumnsPanel';
4
+ import { useGridStylesheetWarning } from './useGridDevWarnings';
5
+ import { useGridAiAssistant, type UseGridAiAssistantParams } from '../features/useGridAiAssistant';
6
+ import { useGridToolbarProps, type UseGridToolbarPropsParams } from './useGridToolbarProps';
7
+
8
+ /** What `useGridToolbarProps` needs besides the columns panel and the AI assistant built here. */
9
+ export type GridToolbarStackToolbarParams<R extends GridRowModel> = Omit<UseGridToolbarPropsParams<R>,
10
+ 'forceColumnsOpen' | 'onColumnsPanelClose' | 'aiAssistantEnabled' | 'aiAssistantOpen' | 'onAiAssistantToggle' |
11
+ 'aiAssistantLabel' | 'forceFiltersOpen' | 'onFiltersPanelClose' | 'apiRef'>;
12
+
13
+ export interface UseGridToolbarStackParams<R extends GridRowModel> {
14
+ apiRef: MutableRefObject<GridApi>;
15
+ containerRef: RefObject<HTMLDivElement | null>;
16
+ aiAssistant: Omit<UseGridAiAssistantParams, 'apiRef'>;
17
+ toolbar: GridToolbarStackToolbarParams<R>;
18
+ localeText: GridLocaleText | undefined;
19
+ }
20
+
21
+ /**
22
+ * Steps 44–46 of `DataGrid` (docs/architecture/datagrid-orchestration.md), in their call order: the columns
23
+ * panel (toolbar or standalone), the missing-stylesheet warning, the `aiAssistant` panel, and the props for
24
+ * `slots.toolbar` (which gets its Ask AI button from the assistant).
25
+ */
26
+ export function useGridToolbarStack<R extends GridRowModel>(params: UseGridToolbarStackParams<R>) {
27
+ const { apiRef, containerRef, aiAssistant, toolbar, localeText } = params;
28
+
29
+ const columnsPanel = useGridColumnsPanel();
30
+ useGridStylesheetWarning(containerRef);
31
+
32
+ // The aiAssistant prompt panel (v3.5): applies validated replies through apiRef; apiRef.openAiAssistant.
33
+ const aiAssistantPanel = useGridAiAssistant({ apiRef, ...aiAssistant });
34
+
35
+ const toolbarProps = useGridToolbarProps<R>({
36
+ ...toolbar,
37
+ apiRef,
38
+ forceColumnsOpen: columnsPanel.toolbarPanelRequested,
39
+ onColumnsPanelClose: columnsPanel.closeToolbarPanel,
40
+ aiAssistantEnabled: aiAssistantPanel.enabled,
41
+ aiAssistantOpen: aiAssistantPanel.open,
42
+ onAiAssistantToggle: aiAssistantPanel.togglePanel,
43
+ aiAssistantLabel: localeText?.aiAssistantButton,
44
+ forceFiltersOpen: columnsPanel.toolbarFiltersRequested,
45
+ onFiltersPanelClose: columnsPanel.closeToolbarFiltersPanel,
46
+ });
47
+
48
+ return { columnsPanel, aiAssistantPanel, toolbarProps };
49
+ }
@@ -0,0 +1,46 @@
1
+ import type { MutableRefObject, RefObject } from 'react';
2
+ import type { GridApi, GridColDef, GridRowId, GridRowModel } from '../../types';
3
+ import { useGridClipboardApi } from './useGridClipboardApi';
4
+ import { useGridScrollToIndexesApi } from './useGridScrollToIndexesApi';
5
+ import { useGridColumnAutosize } from './useGridColumnAutosize';
6
+ import type { LayoutResult } from './useLayout';
7
+
8
+ export interface UseGridViewApisParams<R extends GridRowModel> {
9
+ apiRef: MutableRefObject<GridApi>;
10
+ containerRef: RefObject<HTMLDivElement | null>;
11
+ viewportRef: RefObject<HTMLDivElement | null>;
12
+ /** Rendered data columns with their layout-resolved widths, in render order (what Ctrl/Cmd+C copies). */
13
+ copyColumns: GridColDef[];
14
+ getRowId: (row: GridRowModel) => GridRowId;
15
+ disableClipboardCopy: boolean;
16
+ /** The cell range's copy text while `cellSelection` is on. */
17
+ getCellRangeCopyText: (() => string | null) | undefined;
18
+ layout: LayoutResult<R>;
19
+ rowHeight: number;
20
+ /** Every column in display order (what auto-size measures). */
21
+ orderedColumns: GridColDef<R>[];
22
+ onColumnResize: (field: string, width: number) => void;
23
+ }
24
+
25
+ /**
26
+ * Steps 30–31a of `DataGrid` (docs/architecture/datagrid-orchestration.md), in their call order: the
27
+ * `apiRef` installers that need the laid-out view — copy, `scrollToIndexes` and column auto-size.
28
+ * Returns the resize handle's auto-size handler.
29
+ */
30
+ export function useGridViewApis<R extends GridRowModel>(params: UseGridViewApisParams<R>): (field: string) => void {
31
+ const {
32
+ apiRef, containerRef, viewportRef, copyColumns, getRowId, disableClipboardCopy, getCellRangeCopyText, layout, rowHeight,
33
+ orderedColumns, onColumnResize,
34
+ } = params;
35
+
36
+ useGridClipboardApi({
37
+ apiRef,
38
+ columns: copyColumns,
39
+ getRowId,
40
+ containerRef,
41
+ disableKeyboardShortcut: disableClipboardCopy,
42
+ getCellRangeCopyText,
43
+ });
44
+ useGridScrollToIndexesApi<R>({ apiRef, viewportRef, layout, rowHeight });
45
+ return useGridColumnAutosize<R>({ apiRef, containerRef, columns: orderedColumns, onColumnResize });
46
+ }
@@ -255,12 +255,19 @@ export function useLayout<R extends GridRowModel>(params: UseLayoutParams<R>): L
255
255
  return rowHeight + getDetailPanelLayoutHeight(panelHeight, detailPanelHeights?.get(id));
256
256
  };
257
257
 
258
- const rowHeights = unpinnedRows.map((row, index) => heightOf(row, pinnedTopRowsLength + index));
259
-
260
- const cumulativeHeights = rowHeights.reduce((acc, height, index) => {
261
- acc.push((acc[index - 1] || 0) + height);
262
- return acc;
263
- }, [] as number[]);
258
+ // One pass with preallocated arrays: this runs for every row after each sort or filter. With
259
+ // no detail panel open every row has the plain height, so row ids are not looked up.
260
+ const rowCount = unpinnedRows.length;
261
+ const rowHeights = new Array<number>(rowCount);
262
+ const cumulativeHeights = new Array<number>(rowCount);
263
+ const anyPanelOpen = expandedRowIds.size > 0;
264
+ let runningHeight = 0;
265
+ for (let index = 0; index < rowCount; index++) {
266
+ const height = anyPanelOpen ? heightOf(unpinnedRows[index], pinnedTopRowsLength + index) : rowHeight;
267
+ rowHeights[index] = height;
268
+ runningHeight = (runningHeight || 0) + height; // as before: a NaN total restarts from 0
269
+ cumulativeHeights[index] = runningHeight;
270
+ }
264
271
 
265
272
  const skeletonCount = (paginationMode === 'infinite' && isLoading && unpinnedRows.length > 0)
266
273
  ? Math.min(pageSize, 20)
@@ -307,13 +307,15 @@ export function useRowGrouping<R extends GridRowModel>(params: UseRowGroupingPar
307
307
  ), [defaultGroupingExpansionDepth]);
308
308
 
309
309
  const expandedGroupIds = useMemo<Set<GridRowId>>(() => {
310
+ // Only group nodes have children, so walk the groups rather than every leaf, in tree order.
310
311
  const ids = new Set<GridRowId>();
311
- treeNodes.forEach((node, id) => {
312
- if (!node.children || node.children.length === 0) return;
312
+ for (const id of groupIds) {
313
+ const node = treeNodes.get(id);
314
+ if (!node?.children || node.children.length === 0) continue;
313
315
  if (overrides.get(id) ?? isDefaultExpanded(node)) ids.add(id);
314
- });
316
+ }
315
317
  return ids;
316
- }, [treeNodes, overrides, isDefaultExpanded]);
318
+ }, [groupIds, treeNodes, overrides, isDefaultExpanded]);
317
319
 
318
320
  const toggleExpansion = useCallback((id: GridRowId) => {
319
321
  setExpansionState(prev => {
@@ -439,11 +441,9 @@ export function useRowGrouping<R extends GridRowModel>(params: UseRowGroupingPar
439
441
  treeNodes.forEach((node, id) => {
440
442
  const variants = groupRowVariants.get(id);
441
443
  if (!variants) {
442
- map.set(id, {
443
- hasChildren: false,
444
- treeDepth: node.depth,
445
- isGroupRow: false,
446
- });
444
+ // One object per row (not shared per depth): `params.rowMeta` is public, and a consumer that
445
+ // writes to it must not change other rows.
446
+ map.set(id, { hasChildren: false, treeDepth: node.depth, isGroupRow: false });
447
447
  return;
448
448
  }
449
449
  const groupMeta: GridRowMeta = {
@@ -81,6 +81,18 @@
81
81
  --ogx-grid-cell-font-size: 13px;
82
82
  --ogx-grid-header-font-size: 13px;
83
83
  --ogx-grid-line-height: var(--ogx-line-height-normal);
84
+ /* Outer border and divider widths (v3.7). 0 hides a divider. Their colours,
85
+ --ogx-grid-column-divider-color and --ogx-grid-row-divider-color, are unset here and fall back
86
+ to --ogx-grid-border-color where they are used. So do the other theme-builder variables:
87
+ --ogx-grid-border-radius (--ogx-border-radius-lg), --ogx-grid-header-padding-x
88
+ (--ogx-grid-cell-padding-x), --ogx-grid-header-font-family / -font-weight and
89
+ --ogx-grid-cell-font-family / -font-weight (inherited; header weight 600), and
90
+ --ogx-overlay-font-family / -font-weight / -font-size (the grid font, each item's own weight,
91
+ --ogx-font-size-xs for menu and panel items). */
92
+ --ogx-grid-border-width: 1px;
93
+ --ogx-grid-column-divider-width: 1px;
94
+ --ogx-grid-header-column-divider-width: 1px;
95
+ --ogx-grid-row-divider-width: 1px;
84
96
  --ogx-grid-background: var(--ogx-color-white);
85
97
  --ogx-grid-border-color: var(--ogx-color-gray-200);
86
98
  --ogx-grid-header-background: var(--ogx-color-gray-50);
@@ -272,6 +272,18 @@ function themeToCSS(theme: GridTheme): Record<string, string> {
272
272
  set('--ogx-grid-cell-padding-y', g.cellPaddingY);
273
273
  set('--ogx-grid-cell-font-size', g.cellFontSize);
274
274
  set('--ogx-grid-header-font-size', g.headerFontSize);
275
+ set('--ogx-grid-header-font-family', g.headerFontFamily);
276
+ set('--ogx-grid-header-font-weight', g.headerFontWeight);
277
+ set('--ogx-grid-cell-font-family', g.cellFontFamily);
278
+ set('--ogx-grid-cell-font-weight', g.cellFontWeight);
279
+ set('--ogx-grid-header-padding-x', g.headerPaddingX);
280
+ set('--ogx-grid-border-width', g.borderWidth);
281
+ set('--ogx-grid-border-radius', g.borderRadius);
282
+ set('--ogx-grid-column-divider-width', g.columnDividerWidth);
283
+ set('--ogx-grid-header-column-divider-width', g.headerColumnDividerWidth);
284
+ set('--ogx-grid-row-divider-width', g.rowDividerWidth);
285
+ set('--ogx-grid-column-divider-color', g.columnDividerColor);
286
+ set('--ogx-grid-row-divider-color', g.rowDividerColor);
275
287
  set('--ogx-grid-background', g.background);
276
288
  set('--ogx-grid-border-color', g.borderColor);
277
289
  set('--ogx-grid-header-background', g.headerBackground);
@@ -338,6 +350,9 @@ function themeToCSS(theme: GridTheme): Record<string, string> {
338
350
  set('--ogx-overlay-item-selected-text', o.itemSelectedText);
339
351
  set('--ogx-overlay-item-danger-bg', o.itemDangerBackground);
340
352
  set('--ogx-overlay-item-danger-text', o.itemDangerText);
353
+ set('--ogx-overlay-font-family', o.fontFamily);
354
+ set('--ogx-overlay-font-weight', o.fontWeight);
355
+ set('--ogx-overlay-font-size', o.fontSize);
341
356
  }
342
357
 
343
358
  if (theme.scrollbar) {