@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
@@ -243,7 +243,7 @@ Public CSS: `ogx__cell-fill-handle` (24 × 24 px hit area in the corner cell; th
243
243
 
244
244
  | Prop | Type | Default | Description |
245
245
  | :--- | :--- | :--- | :--- |
246
- | `localeText` | `GridLocaleText` | — | Override user-visible strings: the Pagination labels, the empty-state label (`noRowsLabel`, which wins over the `noRowsLabel` prop), the cell-range announcement and the Ask AI button (`aiAssistantButton`). See [`GridLocaleText`](#gridlocaletext). |
246
+ | `localeText` | `GridLocaleText` | — | Override user-visible strings: the Pagination labels, the empty-state label (`noRowsLabel`, which wins over the `noRowsLabel` prop), the cell-range announcement, the Ask AI button (`aiAssistantButton`) and the header filter row (`headerFilter*`, v3.6). See [`GridLocaleText`](#gridlocaletext). |
247
247
 
248
248
  #### Imperative Ref
249
249
 
@@ -648,8 +648,9 @@ Context provider for overriding the grid's visual system.
648
648
  - **`colors`**: `primary`, `primaryDark`, `primaryLight`, `primaryFocus`, `secondary*`, `success`, `warning`, `error`, `info`, `white`, `black`, `gray` (`{ 50 … 900 }`). Toolbar, menu, selection and focus accents derive from `primary*` unless set directly.
649
649
  - **`typography`**: `fontFamily`, `fontFamilyMono`, `fontSizeXs` … `fontSizeXl`.
650
650
  - **`spacing`**: `xs` … `xxl`. **`borders`**: `widthThin/Medium/Thick`, `radiusSm` … `radiusXl`, `color`, `colorHover`. **`shadows`**: `sm` … `xl`.
651
- - **`grid`**: surfaces (`background`, `borderColor`, `headerBackground`, `headerText`, `headerHoverBackground`, `headerSortedBackground`, `rowText`, `rowHoverBackground`, `rowAlternateBackground`, `rowSelectedBackground`, `rowSelectedHoverBackground`, `cellFocusBorder`, `rangeBackground`, `rangeBorder` (v3.3), `pinnedLeftShadow`, `pinnedRightShadow`, `checkboxBg`, `checkboxBorder`), sizing (`rowHeightCompact`, `rowHeightStandard`, `rowHeightComfortable`, `headerHeight` in px, read by the grid's layout; the `rowHeight` / `headerHeight` props win), `cellPaddingX`, `cellPaddingY`, `cellFontSize`, `headerFontSize`.
652
- - **`toolbar`**, **`overlays`**, **`scrollbar`** (`thumbColor`, `trackColor`, `size`), **`skeleton`** (`baseColor`, `highlightColor`), **`transitions`**.
651
+ - **`grid`**: surfaces (`background`, `borderColor`, `headerBackground`, `headerText`, `headerHoverBackground`, `headerSortedBackground`, `rowText`, `rowHoverBackground`, `rowAlternateBackground`, `rowSelectedBackground`, `rowSelectedHoverBackground`, `cellFocusBorder`, `rangeBackground`, `rangeBorder` (v3.3), `pinnedLeftShadow`, `pinnedRightShadow`, `checkboxBg`, `checkboxBorder`), sizing (`rowHeightCompact`, `rowHeightStandard`, `rowHeightComfortable`, `headerHeight` in px, read by the grid's layout; the `rowHeight` / `headerHeight` props win), `cellPaddingX`, `cellPaddingY`, `cellFontSize`, `headerFontSize`; v3.7: fonts (`headerFontFamily`, `headerFontWeight`, `cellFontFamily`, `cellFontWeight`), `headerPaddingX`, outer `borderWidth` and `borderRadius`, dividers (`columnDividerWidth`, `headerColumnDividerWidth`, `rowDividerWidth`, `columnDividerColor`, `rowDividerColor`; `0` hides a divider).
652
+ - **`overlays`**: colours (`background`, `text`, `border`, `shadow`, `item*`) and, v3.7, `fontFamily`, `fontWeight`, `fontSize` for menus and panels.
653
+ - **`toolbar`**, **`scrollbar`** (`thumbColor`, `trackColor`, `size`), **`skeleton`** (`baseColor`, `highlightColor`), **`transitions`**.
653
654
 
654
655
  Presets: `darkTheme`, `roseTheme`, `emeraldTheme`, `amberTheme`, `compactTheme`. See [Theming](customization/theming.md).
655
656
 
@@ -889,6 +890,19 @@ interface GridLocaleText {
889
890
  cellSelectionAnnouncement?: (cellCount: number, rowCount: number, columnCount: number) => string;
890
891
  /** v3.5: label of the toolbar's Ask AI button. Default "Ask AI". */
891
892
  aiAssistantButton?: string;
893
+ /** v3.6: header filter row strings (defaults in English). See features/header-filters.md#translating-the-row. */
894
+ headerFilterCustom?: string; // "Custom filter"
895
+ headerFilterCustomTooltip?: string; // "Set in the filter panel"
896
+ headerFilterCellLabel?: (header: string) => string; // "Filter Qty"
897
+ headerFilterValueLabel?: (header: string) => string; // "Filter value for Qty"
898
+ headerFilterOperatorButtonLabel?: (header: string, operatorLabel: string) => string; // "Filter operator for Qty: Equals"
899
+ headerFilterOperatorMenuLabel?: (header: string) => string; // "Filter operator for Qty"
900
+ headerFilterClearLabel?: (header: string) => string; // "Clear filter for Qty"
901
+ headerFilterClearTooltip?: string; // "Clear"
902
+ headerFilterAny?: string; // "Any"
903
+ headerFilterTrue?: string; // "Yes"
904
+ headerFilterFalse?: string; // "No"
905
+ headerFilterOperators?: Partial<Record<GridFilterOperator, string>>; // operator names
892
906
  }
893
907
  ```
894
908
 
package/docs/README.md CHANGED
@@ -48,9 +48,11 @@ In-depth guides on the grid's functional capabilities.
48
48
  - **[Cell Spanning](features/cell-spanning.md)** - Row and Col span spanning
49
49
  - **[Master-Detail](features/master-detail.md)** - Expandable Detail Panels
50
50
  - **[Keyboard & Accessibility](features/keyboard-navigation.md)** - Keys, focus behaviour and ARIA structure
51
+ - **[Right-to-Left (RTL)](features/rtl.md)** - What works in `dir="rtl"`, what was fixed, and the known limitations (pinned columns)
51
52
  - **[List View](features/list-view.md)** - Card-based responsive layout
52
53
  - **[Infinite Scroll](features/infinite-scroll.md)** - Advanced performance loading
53
54
  - **[Data Source](features/data-source.md)** - Server-side fetching and SSR
55
+ - **[Next.js (App Router)](features/nextjs.md)** - Client components, server-fetched rows, Route Handlers, the AI toolkit on server and client, common errors
54
56
  - **[Export Guide](features/export-guide.md)** - Excel, CSV, JSON, and Print
55
57
  - **[AI Toolkit](features/ai-toolkit.md)** - JSON Schema of the grid state for your own model, a validator for its replies, the **Ask AI** panel (`aiAssistant`) and tools for AI agents (`@opencorestack/opengridx/ai`)
56
58
 
@@ -1,8 +1,23 @@
1
1
  # DataGrid orchestration
2
2
 
3
3
  `lib/components/DataGrid/DataGrid.tsx` is the orchestration layer: it destructures the props, calls
4
- hooks and pure utils in a fixed order, and renders JSX. Feature logic lives in the hooks and utils
5
- listed here; add new logic there, not in the component.
4
+ hooks and pure utils in a fixed order, and renders `GridRoot` with one props object. Feature logic lives
5
+ in the hooks and utils listed here; add new logic there, not in the component.
6
+
7
+ Some contiguous runs of hooks are wrapped in a **composite hook** (`hooks/core/`) that calls its members in
8
+ the same order, so `DataGrid.tsx` calls one hook where the table below lists several (marked with the
9
+ composite's name). A composite only ever wraps consecutive steps: a hook that has to run between two of its
10
+ members goes inside it at that position, never before or after the composite.
11
+
12
+ | Composite | Steps |
13
+ | :--- | :--- |
14
+ | `useGridEditPipeline` | 12a–13 |
15
+ | `useGridSelectionAndStateApi` | 23–24 |
16
+ | `useGridViewApis` | 30–31a |
17
+ | `useGridScrollViewport` | 32–33 |
18
+ | `useGridRangeEditing` | 36a–36d |
19
+ | `useGridRenderWindow` | 38–41 |
20
+ | `useGridToolbarStack` | 44–46 |
6
21
 
7
22
  ## Why the order matters
8
23
 
@@ -47,9 +62,9 @@ in an effect, so **keep the call order when you move or add a hook**. In particu
47
62
  | 10 | `useGridRowIdOf` | `hooks/core/useGridRowIdOf.ts` | `getRowIdOf` and its stable event-handler twin |
48
63
  | 11 | `useGridApiRefBinding` | `hooks/core/useGridApiRefBinding.ts` | Binds the `apiRef` prop (layout effect) |
49
64
  | 12 | `useGridHierarchy` | `hooks/core/useGridHierarchy.ts` | `useTreeData` + `useRowGrouping`, the active handlers and `rowMetaMap` |
50
- | 12a | `useGridEditCommitChannel` | `hooks/core/useGridUndoRedo.ts` | Stable `emit` / `setListener` pair: the commit paths below report stored changes through `emit`, and `useGridUndoRedo` (36c) registers the listener (v3.4) |
51
- | 12b | `useGridBatchEdit` | `hooks/features/useGridBatchEdit.ts` | Batch edits (v3.4): `applyEdits` runs `valueSetter` → `processRowUpdate` once per row (rows in parallel, failures isolated and sent to `onProcessRowUpdateError`) and stores every successful row in one `replaceRows` update; `getRow` sees rows stored since the last render; `storeRow` stores a single edit |
52
- | 13 | `useGridEditing` | `hooks/features/useGridEditing.ts` | Cell editing state and handlers; stores through `useGridBatchEdit.storeRow` and reports the committed change to the channel |
65
+ | 12a | `useGridEditCommitChannel` (in `useGridEditPipeline`) | `hooks/core/useGridUndoRedo.ts` | Stable `emit` / `setListener` pair: the commit paths below report stored changes through `emit`, and `useGridUndoRedo` (36c) registers the listener (v3.4) |
66
+ | 12b | `useGridBatchEdit` (in `useGridEditPipeline`) | `hooks/features/useGridBatchEdit.ts` | Batch edits (v3.4): `applyEdits` runs `valueSetter` → `processRowUpdate` once per row (rows in parallel, failures isolated and sent to `onProcessRowUpdateError`) and stores every successful row in one `replaceRows` update; `getRow` sees rows stored since the last render; `storeRow` stores a single edit |
67
+ | 13 | `useGridEditing` (in `useGridEditPipeline`) | `hooks/features/useGridEditing.ts` | Cell editing state and handlers; stores through `useGridBatchEdit.storeRow` and reports the committed change to the channel |
53
68
  | 14 | `useGridLiveRowSelection` | `hooks/core/useGridLiveRowSelection.ts` | Selection without removed rows |
54
69
  | 15 | `useGridDataSource` | `hooks/features/useGridDataSource.ts` | Server fetching |
55
70
  | 16 | `useServerTreeChildren` | `hooks/features/useServerTreeChildren.ts` | Lazy server tree children |
@@ -59,46 +74,48 @@ in an effect, so **keep the call order when you move or add a hook**. In particu
59
74
  | 20 | `useGridRowPipeline` | `hooks/core/useGridRowPipeline.ts` | Filter → pin → sort → paginate |
60
75
  | 21 | `useGridPageCorrection` | `hooks/core/useGridPageCorrection.ts` | Moves a page left past the end back to the last page |
61
76
  | 22 | `getPaginationRowCount` (pure) | `utils/pagination/index.ts` | What the pager pages through |
62
- | 23 | `useGridRowInteractions` | `hooks/core/useGridRowInteractions.ts` | Row selection, `handleRowClick`, `handleSelectionChange` |
63
- | 24 | `useGridApiMethods` | `hooks/core/useGridApiMethods.ts` | State API methods (`getAllFilteredRows` via `utils/gridApiRows.ts`); since v3.5 also `setSortModel`, `setRowGroupingModel`, `setAggregationModel`, `setColumnVisibilityModel`, `setPivotModel` and `getGridAiState` |
77
+ | 23 | `useGridRowInteractions` (in `useGridSelectionAndStateApi`) | `hooks/core/useGridRowInteractions.ts` | Row selection, `handleRowClick`, `handleSelectionChange` |
78
+ | 24 | `useGridApiMethods` (in `useGridSelectionAndStateApi`) | `hooks/core/useGridApiMethods.ts` | State API methods (`getAllFilteredRows` via `utils/gridApiRows.ts`); since v3.5 also `setSortModel`, `setRowGroupingModel`, `setAggregationModel`, `setColumnVisibilityModel`, `setPivotModel` and `getGridAiState` |
64
79
  | 25 | `useRowReorder` | `hooks/useRowReorder.ts` | Row drag-reorder |
65
80
  | 26 | `useAggregation` | `hooks/features/useAggregation.ts` | Footer aggregation result |
66
81
  | 27 | `useGridAggregationApi` | `hooks/core/useGridAggregationApi.ts` | `getAggregationResult`, `getAggregationModel`, `getGroupedExportRows` |
67
82
  | 28 | `useLayout` | `hooks/core/useLayout.ts` | Row heights, pinned column split, widths |
68
83
  | 29 | `useGridSpanning` | `hooks/features/useGridSpanning.ts` | Row/column spans |
69
84
  | 29a | `useGridCellSelection` | `hooks/core/useGridCellSelection.ts` | Cell range (v3.3): the model's range resolved against `allRenderableRows` and the data columns of `navigationColumns` (grown over spans), the render rectangle, pointer drag with auto-scroll and Shift+click, the Shift-key handlers for the keyboard hook, copy text, status-bar totals and the debounced live-region text. Inert while `cellSelection` is off |
70
- | 30 | `useGridClipboardApi` | `hooks/core/useGridClipboardApi.ts` | Ctrl/Cmd+C and `copySelectedRows`; with `cellSelection` the shortcut copies the range (`getCellRangeCopyText`) |
71
- | 31 | `useGridScrollToIndexesApi` | `hooks/core/useGridScrollToIndexesApi.ts` | `scrollToIndexes` |
72
- | 31a | `useGridColumnAutosize` | `hooks/core/useGridColumnAutosize.ts` | `autosizeColumn` / `autosizeColumns` on the API, and the resize handle's auto-size handler (v3.1) |
73
- | 32 | `useGridViewport` | `hooks/core/useGridViewport.ts` | Scroll sync, viewport measurement, viewport callback ref |
74
- | 32a | `useGridMaxContentHeight` | `hooks/core/useGridMaxContentHeight.ts` | The tallest element this browser lays out (`utils/maxElementHeight.ts`, measured once per document after mount; `undefined` until then, `Infinity` without a layout engine) |
75
- | 33 | `useGridVirtualization` | `hooks/core/useGridVirtualization.ts` | Render window; `virtualContainerHeight` caps the rows' container to `maxContentHeight` (Firefox collapses taller elements) |
85
+ | 30 | `useGridClipboardApi` (in `useGridViewApis`) | `hooks/core/useGridClipboardApi.ts` | Ctrl/Cmd+C and `copySelectedRows`; with `cellSelection` the shortcut copies the range (`getCellRangeCopyText`) |
86
+ | 31 | `useGridScrollToIndexesApi` (in `useGridViewApis`) | `hooks/core/useGridScrollToIndexesApi.ts` | `scrollToIndexes` |
87
+ | 31a | `useGridColumnAutosize` (in `useGridViewApis`) | `hooks/core/useGridColumnAutosize.ts` | `autosizeColumn` / `autosizeColumns` on the API, and the resize handle's auto-size handler (v3.1) |
88
+ | 32 | `useGridViewport` (in `useGridScrollViewport`) | `hooks/core/useGridViewport.ts` | Scroll sync, viewport measurement, viewport callback ref |
89
+ | 32a | `useGridMaxContentHeight` (in `useGridScrollViewport`) | `hooks/core/useGridMaxContentHeight.ts` | The tallest element this browser lays out (`utils/maxElementHeight.ts`, measured once per document after mount; `undefined` until then, `Infinity` without a layout engine) |
90
+ | 33 | `useGridVirtualization` (in `useGridScrollViewport`) | `hooks/core/useGridVirtualization.ts` | Render window; `virtualContainerHeight` caps the rows' container to `maxContentHeight` (Firefox collapses taller elements) |
76
91
  | 34 | `useGridSortHandlers` | `hooks/core/useGridHeaderHandlers.ts` | Header sort / shift-sort |
77
92
  | 35 | `useGridKeyboardNavigation` | `hooks/core/useGridKeyboardNavigation.ts` | Focus and keyboard handling; takes `cellSelection.keyboard` (Shift+navigation, Escape, Ctrl/Cmd+A) |
78
93
  | 36 | `useGridPointerFocusHandlers` | `hooks/core/useGridFocusHandlers.ts` | Cell and header click focus |
79
- | 36a | `useGridCellSelectionApi` | `hooks/core/useGridCellSelectionApi.ts` | Keeps the range anchor and the focused cell together (layout effect: a new anchor moves focus; focus moved to another data cell collapses the range), clears a range whose corner is gone (`'dataChange'`), and installs `getCellSelectionModel`, `setCellSelectionModel`, `selectCellRange`, `clearCellSelection`, `getSelectedCells`, `copySelectedCells` (once per `apiRef`, reading the latest values from a ref) |
80
- | 36b | `useGridClipboardPaste` | `hooks/core/useGridClipboardPaste.ts` | Paste and range clear (v3.4): `paste` and Delete/Backspace listeners on the grid root (only for cell targets, not editors or nested grids), TSV parsing (`utils/parsing.ts`), placement against the renderable rows and range columns (`utils/editing/targets.ts`), the batch edit, the new selection, `onBeforeClipboardPaste` / `onClipboardPaste`, and `apiRef.pasteText`. Inert without `cellSelection` except for `pasteText` |
81
- | 36c | `useGridUndoRedo` | `hooks/core/useGridUndoRedo.ts` | History (v3.4, `undoRedo`): records the channel's changes, replays them as batch edits with an `expect` value per cell (stale cells skipped), queues undo/redo, selects and scrolls to the affected cells, Ctrl/Cmd+Z / Shift+Z / Ctrl+Y on the grid root, `onHistoryChange`, and `apiRef.undo` / `redo` / `canUndo` / `canRedo` / `clearHistory` |
82
- | 36d | `useGridFillHandle` | `hooks/core/useGridFillHandle.ts` | Fill handle (v3.5, `cellSelection` without `disableFillHandle`): where the handle is drawn (`rowRenderProps.fillHandle`, the range's bottom-right cell or its span origin), a `pointerdown` listener on the viewport for `.ogx__cell-fill-handle` that runs `cellSelection.startTrackedDrag` (the range drag's hit testing, auto-scroll and slow-machine fallback, on pointer events so touch works), the fill itself (`utils/editing/fill.ts`: series detection, lines, `GridColDef.fillValue`) as one batch edit with source `'fill'`, the new selection, `onFill`, and Ctrl/Cmd+D / Ctrl/Cmd+R on the grid root |
94
+ | 36a | `useGridCellSelectionApi` (in `useGridRangeEditing`) | `hooks/core/useGridCellSelectionApi.ts` | Keeps the range anchor and the focused cell together (layout effect: a new anchor moves focus; focus moved to another data cell collapses the range), clears a range whose corner is gone (`'dataChange'`), and installs `getCellSelectionModel`, `setCellSelectionModel`, `selectCellRange`, `clearCellSelection`, `getSelectedCells`, `copySelectedCells` (once per `apiRef`, reading the latest values from a ref) |
95
+ | 36b | `useGridClipboardPaste` (in `useGridRangeEditing`) | `hooks/core/useGridClipboardPaste.ts` | Paste and range clear (v3.4): `paste` and Delete/Backspace listeners on the grid root (only for cell targets, not editors or nested grids), TSV parsing (`utils/parsing.ts`), placement against the renderable rows and range columns (`utils/editing/targets.ts`), the batch edit, the new selection, `onBeforeClipboardPaste` / `onClipboardPaste`, and `apiRef.pasteText`. Inert without `cellSelection` except for `pasteText` |
96
+ | 36c | `useGridUndoRedo` (in `useGridRangeEditing`) | `hooks/core/useGridUndoRedo.ts` | History (v3.4, `undoRedo`): records the channel's changes, replays them as batch edits with an `expect` value per cell (stale cells skipped), queues undo/redo, selects and scrolls to the affected cells, Ctrl/Cmd+Z / Shift+Z / Ctrl+Y on the grid root, `onHistoryChange`, and `apiRef.undo` / `redo` / `canUndo` / `canRedo` / `clearHistory` |
97
+ | 36d | `useGridFillHandle` (in `useGridRangeEditing`) | `hooks/core/useGridFillHandle.ts` | Fill handle (v3.5, `cellSelection` without `disableFillHandle`): where the handle is drawn (`rowRenderProps.fillHandle`, the range's bottom-right cell or its span origin), a `pointerdown` listener on the viewport for `.ogx__cell-fill-handle` that runs `cellSelection.startTrackedDrag` (the range drag's hit testing, auto-scroll and slow-machine fallback, on pointer events so touch works), the fill itself (`utils/editing/fill.ts`: series detection, lines, `GridColDef.fillValue`) as one batch edit with source `'fill'`, the new selection, `onFill`, and Ctrl/Cmd+D / Ctrl/Cmd+R on the grid root |
83
98
  | 37 | `useGridColumnMenuHandlers` | `hooks/core/useGridHeaderHandlers.ts` | Column menu Hide / Pin (`pinColumnTo` in `utils/pinning`) |
84
- | 38 | `useGridAriaRows` | `hooks/core/useGridAriaRows.ts` | `aria-rowcount` and `aria-rowindex` bases |
85
- | 39 | `useGridSpanRowWindow` | `hooks/features/useGridSpanRenderWindow.ts` | Render window widened to whole row spans |
86
- | 40 | `useGridVisibleRows` | `hooks/core/useGridVisibleRows.ts` | Pinned + virtual centre rows to render |
87
- | 41 | `useGridSpanColumnWindow` | `hooks/features/useGridSpanRenderWindow.ts` | Columns to render, widened to whole column spans |
99
+ | 38 | `useGridAriaRows` (in `useGridRenderWindow`) | `hooks/core/useGridAriaRows.ts` | `aria-rowcount` and `aria-rowindex` bases |
100
+ | 39 | `useGridSpanRowWindow` (in `useGridRenderWindow`) | `hooks/features/useGridSpanRenderWindow.ts` | Render window widened to whole row spans |
101
+ | 40 | `useGridVisibleRows` (in `useGridRenderWindow`) | `hooks/core/useGridVisibleRows.ts` | Pinned + virtual centre rows to render |
102
+ | 41 | `useGridSpanColumnWindow` (in `useGridRenderWindow`) | `hooks/features/useGridSpanRenderWindow.ts` | Columns to render, widened to whole column spans |
88
103
  | 42 | `useGridDevWarnings` | `hooks/core/useGridDevWarnings.ts` | Dev-only warnings |
89
104
  | 43 | `useColumnGroupReorderGuard` | `hooks/features/useColumnGroupReorderGuard.ts` | Header drag-reorder kept inside column groups |
90
- | 44 | `useGridColumnsPanel` | `hooks/core/useGridColumnsPanel.ts` | Columns panel (toolbar or standalone) |
91
- | 45 | `useGridStylesheetWarning` | `hooks/core/useGridDevWarnings.ts` | Warns when the stylesheet is missing |
92
- | 45a | `useGridAiAssistant` | `hooks/features/useGridAiAssistant.ts` | The `aiAssistant` panel (v3.5): open state, the request and its `AbortController` (Stop), applying a branded reply through the `apiRef` setters of step 24 (`utils/aiAssistant.ts`: allowed parts, the Undo snapshot, chips, chip removal), the live-region status, and `apiRef.openAiAssistant` / `closeAiAssistant`. Inert without `aiAssistant`; it runs before step 46 because the toolbar gets its Ask AI button from it |
93
- | 46 | `useGridToolbarProps` | `hooks/core/useGridToolbarProps.ts` | Props for `slots.toolbar` |
105
+ | 44 | `useGridColumnsPanel` (in `useGridToolbarStack`) | `hooks/core/useGridColumnsPanel.ts` | Columns panel (toolbar or standalone) |
106
+ | 45 | `useGridStylesheetWarning` (in `useGridToolbarStack`) | `hooks/core/useGridDevWarnings.ts` | Warns when the stylesheet is missing |
107
+ | 45a | `useGridAiAssistant` (in `useGridToolbarStack`) | `hooks/features/useGridAiAssistant.ts` | The `aiAssistant` panel (v3.5): open state, the request and its `AbortController` (Stop), applying a branded reply through the `apiRef` setters of step 24 (`utils/aiAssistant.ts`: allowed parts, the Undo snapshot, chips, chip removal), the live-region status, and `apiRef.openAiAssistant` / `closeAiAssistant`. Inert without `aiAssistant`; it runs before step 46 because the toolbar gets its Ask AI button from it |
108
+ | 46 | `useGridToolbarProps` (in `useGridToolbarStack`) | `hooks/core/useGridToolbarProps.ts` | Props for `slots.toolbar` |
94
109
 
95
110
  ## Rendered components
96
111
 
97
- `GridToolbarSlot`, `GridAiAssistantArea` (the `aiAssistant` panel or `slots.aiAssistantPanel` while open, anchored under the toolbar), `GridStandaloneColumnPanel`, `GridListView` (list view) or the viewport
98
- (`Header`, `GridPinnedRows` top, `GridEmptyState`, `GridVirtualRows`, `GridPinnedRows` bottom,
112
+ `DataGrid.tsx` renders `GridRoot` (`components/DataGrid/GridRoot.tsx`): the root element with, in this order,
113
+ `GridToolbarSlot`, `GridAiAssistantArea` (the `aiAssistant` panel or `slots.aiAssistantPanel` while open, anchored under the toolbar), `GridStandaloneColumnPanel`, `GridListView` (list view) or `GridViewport` (`components/DataGrid/GridViewport.tsx`, the `role="grid"` viewport:
114
+ `Header`, `GridPinnedRows` top, `GridEmptyState`, `GridVirtualRows`, `GridPinnedRows` bottom,
99
115
  `GridAggregationFooter`), `GridCellSelectionStatsArea` (`showCellSelectionStats` with `cellSelection`), then `slots.footer` or `GridPaginationArea`, `GridLiveRegion`,
100
- `GridLoadingOverlay` and `GridErrorOverlay`. The props all three row renderers take alike are built
101
- once in `DataGrid.tsx` (`rowRenderProps`) and spread into each. `rowRenderProps.cellRange` is the cell range's
116
+ `GridLoadingOverlay` and `GridErrorOverlay`. `GridRoot` builds the overlay slot elements and passes the
117
+ rest of its props on to `GridViewport`. The props all three row renderers take alike are built once in
118
+ `GridViewport` (`rowRenderProps`) and spread into each. `rowRenderProps.cellRange` is the cell range's
102
119
  render rectangle; each row renderer turns it into a per-row `cellRange` (`getRowCellRange`), `null` for rows
103
120
  outside it, and `Row` is memoised with a value comparison of that prop, so a drag only re-renders the rows the
104
121
  range enters or leaves. `rowRenderProps.fillHandle` (v3.5) adds `fillHandleCol` to the one row that draws the fill
@@ -2,7 +2,7 @@
2
2
 
3
3
  Hierarchy metadata for a grid row, available via `params.rowMeta` in `renderCell` since v1.1.
4
4
 
5
- **File:** `lib/types/index.ts`
5
+ **File:** `lib/types/rows.ts` (re-exported from `lib/types/index.ts`)
6
6
 
7
7
  ---
8
8
 
@@ -288,4 +288,4 @@ function CustomPagination(props: Record<string, unknown>) {
288
288
  ## See Also
289
289
 
290
290
  - [Custom Pagination Guide](../features/custom-pagination.md) - Detailed examples with Ant Design and MUI
291
- - [DataGrid Props](../../lib/types/index.ts) - Full TypeScript definitions
291
+ - [DataGrid Props](../../lib/types/props.ts) - Full TypeScript definitions
@@ -5,6 +5,8 @@ OpenGridX ships with a complete design-token system built on CSS custom properti
5
5
  1. **`DataGridThemeProvider`** — the recommended React API (maps a typed `GridTheme` object → CSS variables)
6
6
  2. **Raw CSS override** — set any `--ogx-*` variable yourself on a wrapper element or `:root`
7
7
 
8
+ To design a theme without writing code, use the **[Theme Builder](https://opencorestack.github.io/OpenGridX/theme-builder)** demo page: controls for colours, fonts, sizes, borders and dividers, buttons and menus, edited separately for light and dark mode on a live grid, with WCAG AA contrast hints. It copies or downloads the result as a `GridTheme` (only the values that differ from the defaults) and can share it as a link (`#theme=…`).
9
+
8
10
  ---
9
11
 
10
12
  ## Quick Start — `DataGridThemeProvider`
@@ -198,6 +200,47 @@ All sub-theme types (`GridThemeColors`, `GridThemeGrayScale`, `GridThemeTypograp
198
200
  | `--ogx-grid-header-font-size` | `13px` | Header cell font size (`grid.headerFontSize`) |
199
201
  | `--ogx-grid-line-height` | `var(--ogx-line-height-normal)` | Row line height |
200
202
 
203
+ ### Fonts, dividers and corners (v3.7)
204
+
205
+ These keys were added for the [Theme Builder](https://opencorestack.github.io/OpenGridX/theme-builder). Each defaults to exactly what the grid drew before, so a theme that leaves them out looks unchanged. The variables without a stylesheet value are read with a fallback, so they follow the token in the Default column when only that one is set.
206
+
207
+ | Theme key | CSS variable | Default | Styles |
208
+ |---|---|---|---|
209
+ | `grid.headerFontFamily` | `--ogx-grid-header-font-family` | the grid font | Header cells |
210
+ | `grid.headerFontWeight` | `--ogx-grid-header-font-weight` | `600` | Header cells |
211
+ | `grid.cellFontFamily` | `--ogx-grid-cell-font-family` | the grid font | Body cells |
212
+ | `grid.cellFontWeight` | `--ogx-grid-cell-font-weight` | inherited (`400`) | Body cells |
213
+ | `grid.headerPaddingX` | `--ogx-grid-header-padding-x` | `--ogx-grid-cell-padding-x` | Horizontal padding of header cells |
214
+ | `grid.borderWidth` | `--ogx-grid-border-width` | `1px` | The grid's outer border |
215
+ | `grid.borderRadius` | `--ogx-grid-border-radius` | `--ogx-border-radius-lg` (`12px`) | The grid's corners (inner corners follow: radius minus border width) |
216
+ | `grid.columnDividerWidth` | `--ogx-grid-column-divider-width` | `1px` | Lines between body cells and aggregation footer cells, and the edge of the right-pinned section |
217
+ | `grid.headerColumnDividerWidth` | `--ogx-grid-header-column-divider-width` | `1px` | The short separators between header cells and column-group cells |
218
+ | `grid.rowDividerWidth` | `--ogx-grid-row-divider-width` | `1px` | Lines under rows, the header row, column-group rows and the header filter row |
219
+ | `grid.columnDividerColor` | `--ogx-grid-column-divider-color` | `--ogx-grid-border-color` | Colour of the column dividers |
220
+ | `grid.rowDividerColor` | `--ogx-grid-row-divider-color` | `--ogx-grid-border-color` | Colour of the row dividers |
221
+ | `overlays.fontFamily` | `--ogx-overlay-font-family` | the grid font | Column menu, header filter menu, toolbar panels (columns, filters, summaries, pivot), AI panel |
222
+ | `overlays.fontWeight` | `--ogx-overlay-font-weight` | each item's own (`400`, labels `500`) | Menu items and panel labels; section titles and active items stay bold |
223
+ | `overlays.fontSize` | `--ogx-overlay-font-size` | `--ogx-font-size-xs` (AI panel `--ogx-font-size-sm`) | Menu items and panel labels |
224
+
225
+ The header-cell separators keep their own colour (`--ogx-color-gray-300` at 50% opacity); only their width is themable.
226
+
227
+ #### Recipe: hide the dividers
228
+
229
+ A width of `0` removes a divider without changing any cell's size or position:
230
+
231
+ ```tsx
232
+ const borderless: GridTheme = {
233
+ grid: {
234
+ columnDividerWidth: '0', // body and footer cells
235
+ headerColumnDividerWidth: '0', // header and column-group cells
236
+ rowDividerWidth: '0', // under rows and header rows
237
+ rowAlternateBackground: '#f8fafc', // optional: zebra rows instead of lines
238
+ },
239
+ };
240
+ ```
241
+
242
+ For visible gaps between cells instead of lines, set the divider colour to the grid background (`columnDividerColor: '#ffffff'`) and widen it. Cells have no margin setting: they are positioned to the pixel for virtualization, pinning and spanning, so use padding for room inside them.
243
+
201
244
  ### Grid Colors
202
245
 
203
246
  | Token | Default (light) | Dark override | Description |
@@ -104,7 +104,7 @@ getGridAiSchema(
104
104
 
105
105
  ## `validateGridAiState(json, columns, options?)`
106
106
 
107
- Returns `{ state, errors }`, where `state` is a `GridAiState` (`filterModel`, `sortModel`, `rowGroupingModel`, `aggregationModel`, `pivotModel`, `columnVisibilityModel`, each present only when the reply contained it) and `errors` is a list of `{ path, message }`, such as `{ path: 'filterModel.items[2].field', message: 'Unknown field "revenue"' }`.
107
+ Returns `{ state, errors }`, where `state` is a `GridAiState` (`filterModel`, `sortModel`, `rowGroupingModel`, `aggregationModel`, `pivotModel`, `columnVisibilityModel`, each present only when the reply contained it) and `errors` is a list of `{ path, message }`, such as `{ path: 'filterModel.items[2].field', message: 'Unknown field "revenue"' }`. A top-level string `message` key (the model's reply text, which `createGridAiPromptHandler` shows) is skipped in both object and JSON string input, so you can validate a whole reply without removing it first; a `message` that is not a string is reported.
108
108
 
109
109
  - `json` may be an object or a JSON string. Bad JSON gives one error and an empty state.
110
110
  - Unknown parts, unknown fields, fields a column does not allow (`filterable: false` …), operators that do not fit the column type, and functions or enum values outside the allowed list are **dropped**, each with an error. Parts outside `options.include` / `options.exclude` are dropped too.
@@ -172,7 +172,7 @@ A copied state (`{ ...result.state }`) or a hand-built one is not branded.
172
172
  Returns the `onPrompt` function. It builds the schema once (from `columns`, limited to `parts` and `schemaOptions`), then for each prompt:
173
173
 
174
174
  1. calls `callModel({ prompt, schema, currentState, history }, { signal })`;
175
- 2. reads the reply: a state object, a JSON string, or text with a fenced `json` block (the text around the block becomes `result.message`). An object's string `message` key is the message, not state. Plain text with no JSON becomes the message with nothing to apply;
175
+ 2. reads the reply: a state object, a JSON string, or text with a fenced `json` block (the text around the block becomes `result.message`). A string `message` key in the state object (or in the JSON of a string reply) is the message, not state; when a fenced block has text around it and a `message` key inside, both are kept, joined by a blank line. Plain text with no JSON becomes the message with nothing to apply;
176
176
  3. validates it with `validateGridAiState` (same `parts`) and returns the branded `{ state, errors, message? }`.
177
177
 
178
178
  A `callModel` throw or rejection becomes `{ state: {}, errors: [{ path: '', message: 'The model call failed: …' }] }`. An abort (Stop, a new prompt, closing the panel) rejects with an `AbortError`, even when `callModel` ignores the signal.
@@ -2,6 +2,8 @@
2
2
 
3
3
  The DataGrid provides built-in export utilities for multiple formats. The standard exports (CSV, JSON, Print, basic Excel) have **zero dependencies**. The advanced Excel export uses [ExcelJS](https://github.com/exceljs/exceljs) loaded lazily.
4
4
 
5
+ `exceljs` is an optional peer dependency: install it (`npm install exceljs`) only if you call `exportToExcelAdvanced`. Apps without it still build in Vite, webpack 5 and Next.js (Turbopack and webpack; webpack prints a `Module not found` warning for the optional import), and `exportToExcelAdvanced` then rejects with `exportToExcelAdvanced requires 'exceljs'. Run: npm install exceljs`. Before v3.6, webpack and Next.js builds failed without it.
6
+
5
7
  ## Available Export Formats
6
8
 
7
9
  | Format | Function | Deps | Description |
@@ -107,6 +107,40 @@ The filter row is a header row (`role="row"`) between the column headers and the
107
107
 
108
108
  Inside a control, arrow keys edit the text (or move through a select) and do not move the grid's focus, as in a cell editor. **Escape** returns focus to the filter cell. **Tab** leaves the grid: the grid stays a single Tab stop and every control in the row has `tabIndex={-1}`.
109
109
 
110
+ ## Translating the row
111
+
112
+ Every string the row shows or announces comes from `localeText` (v3.6). Each key defaults to the English text below.
113
+
114
+ | `GridLocaleText` key | Type | Default |
115
+ | :--- | :--- | :--- |
116
+ | `headerFilterCustom` | `string` | `"Custom filter"` |
117
+ | `headerFilterCustomTooltip` | `string` | `"Set in the filter panel"` |
118
+ | `headerFilterCellLabel` | `(header) => string` | `` `Filter ${header}` `` (the cell's `aria-label`) |
119
+ | `headerFilterValueLabel` | `(header) => string` | `` `Filter value for ${header}` `` |
120
+ | `headerFilterOperatorButtonLabel` | `(header, operatorLabel) => string` | `` `Filter operator for ${header}: ${operatorLabel}` `` |
121
+ | `headerFilterOperatorMenuLabel` | `(header) => string` | `` `Filter operator for ${header}` `` |
122
+ | `headerFilterClearLabel` | `(header) => string` | `` `Clear filter for ${header}` `` |
123
+ | `headerFilterClearTooltip` | `string` | `"Clear"` |
124
+ | `headerFilterAny` | `string` | `"Any"` (boolean and `singleSelect` selects) |
125
+ | `headerFilterTrue` / `headerFilterFalse` | `string` | `"Yes"` / `"No"` |
126
+ | `headerFilterOperators` | `Partial<Record<GridFilterOperator, string>>` | English names ("Contains", "Is empty", ...); operators left out keep them |
127
+
128
+ ```tsx
129
+ <DataGrid
130
+ headerFilters
131
+ localeText={{
132
+ headerFilterAny: 'Tous',
133
+ headerFilterTrue: 'Oui',
134
+ headerFilterFalse: 'Non',
135
+ headerFilterCellLabel: (h) => `Filtrer ${h}`,
136
+ headerFilterOperators: { contains: 'Contient', isEmpty: 'Est vide' },
137
+ }}
138
+ /* ... */
139
+ />
140
+ ```
141
+
142
+ The operator symbols on the buttons (`=`, `≠`, `>`) are not translated. Demo: *Locale Text* (`/locale-text`).
143
+
110
144
  ## Layout
111
145
 
112
146
  - The row sits inside the sticky header block (`.ogx__header-wrap`), under any column group rows and the column header row, so it stays at the top while rows scroll.
@@ -108,6 +108,8 @@ In `listView`, focus moves between whole rows instead of cells:
108
108
 
109
109
  Keys pressed in content your `renderCell` puts in a row (inputs, buttons) are left to that content.
110
110
 
111
+ In a right-to-left grid (`dir="rtl"`, v3.6) ArrowLeft and ArrowRight follow the screen: ArrowLeft moves to the next column, and the Alt+Arrow shortcuts (expand / collapse, header resize) are mirrored the same way. See [Right-to-Left](rtl.md).
112
+
111
113
  ## ARIA structure
112
114
 
113
115
  | Element | Attributes |
@@ -0,0 +1,241 @@
1
+ # Next.js (App Router)
2
+
3
+ OpenGridX is a client component: it uses state, effects, context and the DOM. In the App Router you render it from a file marked `'use client'`, and fetch the data in Server Components or Route Handlers.
4
+
5
+ Everything on this page was built with `next build`, served with `next start` and loaded in Chromium, Firefox and WebKit without console errors, on Next.js 16.4 (Turbopack; `next build --webpack` also builds) and 15.5 (webpack), React 19.3.
6
+
7
+ ## 1. Install and import the stylesheet
8
+
9
+ ```bash
10
+ npm install @opencorestack/opengridx
11
+ ```
12
+
13
+ Import the stylesheet once, in the root layout:
14
+
15
+ ```tsx
16
+ // app/layout.tsx
17
+ import type { ReactNode } from 'react';
18
+ import '@opencorestack/opengridx/styles';
19
+
20
+ export default function RootLayout({ children }: { children: ReactNode }) {
21
+ return (
22
+ <html lang="en">
23
+ <body>{children}</body>
24
+ </html>
25
+ );
26
+ }
27
+ ```
28
+
29
+ ### The optional export peers
30
+
31
+ `exportToExcelAdvanced` needs `exceljs`, and `exportToPdf` needs `jspdf` + `jspdf-autotable`. The grid loads them with `import()` only when you call those functions. Install them if you use those exports:
32
+
33
+ ```bash
34
+ npm install exceljs jspdf jspdf-autotable
35
+ ```
36
+
37
+ If you do not install them, the app still builds (Turbopack and webpack treat these imports as optional; webpack prints a `Module not found` **warning**, not an error) and every other export works. Calling `exportToExcelAdvanced` or `exportToPdf` then rejects with an install hint such as `exportToExcelAdvanced requires 'exceljs'. Run: npm install exceljs`. No `next.config` alias is needed from v3.6. In 3.5.1 and earlier `next build` failed with `Module not found: Can't resolve 'exceljs'` without the peers.
38
+
39
+ ## 2. Render the grid from a client component
40
+
41
+ Column definitions usually hold functions (`valueFormatter`, `renderCell`, `valueGetter`). Functions cannot cross from a Server Component to a Client Component, so **define the columns in the client file**:
42
+
43
+ ```tsx
44
+ // app/OrdersGrid.tsx
45
+ 'use client';
46
+
47
+ import { DataGrid, GridToolbar, type GridColDef } from '@opencorestack/opengridx';
48
+ import type { Order } from '../lib/orders';
49
+
50
+ const columns: GridColDef<Order>[] = [
51
+ { field: 'id', headerName: 'ID', type: 'number', width: 80 },
52
+ { field: 'customer', headerName: 'Customer', flex: 1 },
53
+ {
54
+ field: 'amount',
55
+ headerName: 'Amount',
56
+ type: 'number',
57
+ width: 140,
58
+ valueFormatter: ({ value }) => `$${Number(value).toFixed(2)}`,
59
+ },
60
+ { field: 'shipped', headerName: 'Shipped', type: 'boolean', width: 110 },
61
+ ];
62
+
63
+ export function OrdersGrid({ rows }: { rows: Order[] }) {
64
+ return (
65
+ <DataGrid
66
+ rows={rows}
67
+ columns={columns}
68
+ height={480}
69
+ checkboxSelection
70
+ headerFilters
71
+ slots={{ toolbar: GridToolbar }}
72
+ />
73
+ );
74
+ }
75
+ ```
76
+
77
+ Give the grid a `height` (or a parent with a bounded height): without one it fills its container, and in an auto-height page that means every row renders. See [Virtualization](virtualization.md).
78
+
79
+ ## 3. Fetch rows in a Server Component
80
+
81
+ The page stays a Server Component. It loads the rows and passes them as props. Props must be serialisable: plain objects, arrays, strings, numbers, booleans, `null`. `Date` objects survive; class instances, `Map`s with functions and functions do not.
82
+
83
+ ```tsx
84
+ // app/page.tsx (Server Component, no 'use client')
85
+ import { getOrders } from '../lib/orders';
86
+ import { OrdersGrid } from './OrdersGrid';
87
+
88
+ export default async function Page() {
89
+ const rows = await getOrders(); // a database query, fetch() to your API, ...
90
+ return <OrdersGrid rows={rows} />;
91
+ }
92
+ ```
93
+
94
+ The grid renders on the server (the first screen of rows is in the HTML) and hydrates in the browser.
95
+
96
+ **Rendering `<DataGrid>` straight from a Server Component** works from v3.6, whose bundle starts with `'use client'`, as long as every prop is serialisable (`columns={[{ field: 'name' }]}`, no functions). In 3.5.1 and earlier it fails with `TypeError: createContext is not a function`. Wrapping the grid in your own client file, as above, works in every version and is where your column functions belong anyway.
97
+
98
+ ## 4. Server-side data with `dataSource` and a Route Handler
99
+
100
+ For large tables, let the server sort, filter and page. The `dataSource` lives in the client file and calls a Route Handler:
101
+
102
+ ```tsx
103
+ // app/orders/ServerOrdersGrid.tsx
104
+ 'use client';
105
+
106
+ import { DataGrid, GridToolbar, type GridColDef, type GridDataSource } from '@opencorestack/opengridx';
107
+ import type { Order } from '../../lib/orders';
108
+
109
+ const columns: GridColDef<Order>[] = [
110
+ { field: 'id', headerName: 'ID', type: 'number', width: 80 },
111
+ { field: 'customer', headerName: 'Customer', flex: 1 },
112
+ { field: 'amount', headerName: 'Amount', type: 'number', width: 140 },
113
+ ];
114
+
115
+ // Module scope (or useMemo): a new dataSource object on every render refetches.
116
+ const dataSource: GridDataSource<Order> = {
117
+ getRows: async (params) => {
118
+ const res = await fetch('/api/orders', {
119
+ method: 'POST',
120
+ headers: { 'Content-Type': 'application/json' },
121
+ body: JSON.stringify(params),
122
+ });
123
+ if (!res.ok) throw new Error(`Orders request failed: ${res.status}`);
124
+ return res.json();
125
+ },
126
+ };
127
+
128
+ export function ServerOrdersGrid() {
129
+ return (
130
+ <DataGrid
131
+ rows={[]}
132
+ columns={columns}
133
+ dataSource={dataSource}
134
+ sortingMode="server"
135
+ filterMode="server"
136
+ paginationMode="server"
137
+ pagination
138
+ initialState={{ pagination: { paginationModel: { page: 0, pageSize: 25 } } }}
139
+ height={480}
140
+ slots={{ toolbar: GridToolbar }}
141
+ />
142
+ );
143
+ }
144
+ ```
145
+
146
+ ```ts
147
+ // app/api/orders/route.ts
148
+ import type { GridGetRowsParams, GridGetRowsResponse } from '@opencorestack/opengridx';
149
+ import { queryOrders } from '../../../lib/orders';
150
+
151
+ export async function POST(request: Request) {
152
+ const params = (await request.json()) as GridGetRowsParams;
153
+ // params: startRow, endRow, sortModel, filterModel (items, quickFilterValues), groupKeys
154
+ const { rows, total } = await queryOrders(params);
155
+ const body: GridGetRowsResponse = { rows, rowCount: total };
156
+ return Response.json(body);
157
+ }
158
+ ```
159
+
160
+ Importing types from the package in a Route Handler is free: they are erased at build time. See [Data Source](data-source.md) for the request and response shapes.
161
+
162
+ ## 5. The AI toolkit (`@opencorestack/opengridx/ai`)
163
+
164
+ `@opencorestack/opengridx/ai` has no React code and no imports, so it runs anywhere: Server Components, Route Handlers, middleware and the browser.
165
+
166
+ - **On the server**: build the schema (`getGridAiSchema`) for your model call, and keep your API key there. You may also run `validateGridAiState` on the reply to log or reject bad answers.
167
+ - **In the browser**: the grid applies a reply only if `validateGridAiState` branded it, and the brand is a `Symbol` that does not survive JSON. So the reply must be validated again on the client. `createGridAiPromptHandler` does that for you:
168
+
169
+ ```tsx
170
+ // app/ai/AiOrdersGrid.tsx
171
+ 'use client';
172
+
173
+ import { DataGrid, GridToolbar } from '@opencorestack/opengridx';
174
+ import { createGridAiPromptHandler } from '@opencorestack/opengridx/ai';
175
+ import { aiColumns } from '../../lib/aiColumns'; // plain column data, shared with the route
176
+ import type { Order } from '../../lib/orders';
177
+
178
+ const onPrompt = createGridAiPromptHandler({
179
+ columns: aiColumns,
180
+ callModel: async (request, { signal }) => {
181
+ // request: { prompt, schema, currentState, history }
182
+ const res = await fetch('/api/grid-ai', { method: 'POST', body: JSON.stringify(request), signal });
183
+ return res.json(); // e.g. { filterModel, sortModel, message }
184
+ },
185
+ });
186
+
187
+ export function AiOrdersGrid({ rows }: { rows: Order[] }) {
188
+ return <DataGrid rows={rows} columns={aiColumns} height={480} slots={{ toolbar: GridToolbar }} aiAssistant={{ onPrompt }} />;
189
+ }
190
+ ```
191
+
192
+ ```ts
193
+ // app/api/grid-ai/route.ts
194
+ import { validateGridAiState } from '@opencorestack/opengridx/ai';
195
+ import { aiColumns } from '../../../lib/aiColumns';
196
+
197
+ export async function POST(request: Request) {
198
+ const { prompt, schema, currentState } = await request.json();
199
+ const reply = await callYourModel({ prompt, schema, currentState }); // your SDK, with `schema` as structured output
200
+ // Optional server-side check. A string `message` key is skipped, not reported as an error.
201
+ const { errors } = validateGridAiState(reply, aiColumns);
202
+ if (errors.length) console.info('Model reply had invalid parts:', errors);
203
+ return Response.json(reply);
204
+ }
205
+ ```
206
+
207
+ Columns shared between the route and the client must be plain data (no functions), or the server gets a client reference instead of the array. Keep a function-free list such as `aiColumns` in a module without `'use client'`, and spread extra function fields onto it in the client file if the grid needs them.
208
+
209
+ ## Pages Router
210
+
211
+ The Pages Router has no Server Components: every page is a client component that also renders on the server. Import the grid directly in the page, and the stylesheet in `pages/_app.tsx`:
212
+
213
+ ```tsx
214
+ // pages/_app.tsx
215
+ import type { AppProps } from 'next/app';
216
+ import '@opencorestack/opengridx/styles';
217
+
218
+ export default function App({ Component, pageProps }: AppProps) {
219
+ return <Component {...pageProps} />;
220
+ }
221
+ ```
222
+
223
+ Column functions can stay in the page. Rows from `getServerSideProps` / `getStaticProps` must be JSON-serialisable (no `Date`; send ISO strings and use `valueGetter`).
224
+
225
+ ## Common errors
226
+
227
+ | Error | Cause and fix |
228
+ | :--- | :--- |
229
+ | `You're importing a module that depends on useState into a React Server Component module` (webpack: `…needs useState. This React hook only works in a Client Component`) | A file of yours that calls hooks (or wraps the grid) has no `'use client'`. Add it at the top of the file that renders `<DataGrid>`. |
230
+ | `TypeError: createContext is not a function` (build: "Failed to collect page data") | 3.5.1 or earlier, imported from a Server Component. Render the grid from a `'use client'` file, or upgrade. |
231
+ | `Functions cannot be passed directly to Client Components` … `{field: "name", valueFormatter: function}` | Columns with functions built in a Server Component. Define them in the client file. |
232
+ | `Module not found: Can't resolve 'exceljs'` (or `jspdf`) | 3.5.1 or earlier without the optional export peers. Upgrade, or install them (section 1). From v3.6 webpack shows this as a warning only. |
233
+ | Unstyled grid, every row rendered, dev warning `[OpenGridX] The grid stylesheet is not loaded` | The stylesheet is not imported. Add `import '@opencorestack/opengridx/styles'` to `app/layout.tsx` (or `pages/_app.tsx`). |
234
+ | `Hydration failed because the server rendered HTML didn't match the client` | Usually rows or cell text that differ between server and client: `Math.random()`, `Date.now()`, `toLocaleString()` without a fixed locale and time zone in a `valueFormatter`. Fetch rows on the server and pass them as props, and format with an explicit locale (`toLocaleString('en-US', { timeZone: 'UTC' })`). Rows created in an effect or with `typeof window` branches render differently on the server; build them once on the server instead. |
235
+ | The grid refetches in a loop with `dataSource` | The `dataSource` object is recreated on every render. Define it at module scope or with `useMemo`. |
236
+
237
+ ## See also
238
+
239
+ - [Data Source](data-source.md)
240
+ - [AI Toolkit](ai-toolkit.md)
241
+ - [Export Guide](export-guide.md)
@@ -10,6 +10,8 @@ Generate a styled PDF report from grid data using `exportToPdf()`.
10
10
  npm install jspdf jspdf-autotable
11
11
  ```
12
12
 
13
+ They are optional: an app that never calls `exportToPdf` need not install them, and still builds in Vite, webpack 5 and Next.js (webpack prints a `Module not found` warning for the optional import, not an error). Without them `exportToPdf` rejects with the install hint below.
14
+
13
15
  > **Currency symbols such as `₹` print as `?` by default.** The built-in Helvetica font only covers Latin-1. Pass the [`font` option](#non-latin-text-and-unicode-fonts) with a Unicode `.ttf`:
14
16
  >
15
17
  > ```ts
@@ -35,6 +35,8 @@ Pinned column headers are not draggable and are not drop targets for header drag
35
35
 
36
36
  ---
37
37
 
38
+ > **Right-to-left:** `left` and `right` are the inline **start** and **end** sides. In a `dir="rtl"` grid `pinnedColumns.left` (and the pinned checkbox, expand and reorder columns) stick to the right edge, `pinnedColumns.right` to the left edge, and the shadows fall towards the scrolling columns. The column menu's *Pin to Left* / *Pin to Right* items pin by screen side, so in RTL *Pin to Left* adds the column to `pinnedColumns.right`. See [Right-to-Left](rtl.md#pinned-columns).
39
+
38
40
  ## ⬆️ Row Pinning
39
41
 
40
42
  Rows can be pinned to the top or bottom of the grid. Pinned rows remain visible while the user scrolls vertically through the grid body.