@opencorestack/opengridx 3.2.2 → 3.4.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 (88) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +16 -2
  3. package/dist/ai.cjs +1 -0
  4. package/dist/ai.cjs.map +1 -0
  5. package/dist/ai.d.ts +166 -0
  6. package/dist/ai.es.js +1 -0
  7. package/dist/ai.es.js.map +1 -0
  8. package/dist/index.d.ts +321 -3
  9. package/dist/opengridx.css +1 -1
  10. package/dist/opengridx.es.js +6483 -5291
  11. package/dist/opengridx.es.js.map +1 -1
  12. package/dist/opengridx.umd.js +16 -13
  13. package/dist/opengridx.umd.js.map +1 -1
  14. package/docs/API_REFERENCE.md +132 -3
  15. package/docs/README.md +4 -1
  16. package/docs/architecture/datagrid-orchestration.md +23 -6
  17. package/docs/components/header.md +25 -0
  18. package/docs/customization/theming.md +12 -1
  19. package/docs/features/ai-toolkit.md +119 -0
  20. package/docs/features/cell-selection.md +131 -0
  21. package/docs/features/clipboard.md +90 -0
  22. package/docs/features/editing-reordering.md +50 -0
  23. package/docs/features/keyboard-navigation.md +25 -2
  24. package/docs/features/undo-redo.md +79 -0
  25. package/docs/roadmap.md +64 -7
  26. package/lib/ai/coerce.ts +90 -0
  27. package/lib/ai/columns.ts +90 -0
  28. package/lib/ai/index.ts +16 -0
  29. package/lib/ai/schema.ts +214 -0
  30. package/lib/ai/types.ts +88 -0
  31. package/lib/ai/validate.ts +286 -0
  32. package/lib/components/Cell/Cell.css +35 -0
  33. package/lib/components/Cell/Cell.tsx +25 -13
  34. package/lib/components/Cell/CellErrorBoundary.tsx +3 -0
  35. package/lib/components/Cell/GridEditInputCell.css +12 -4
  36. package/lib/components/Cell/GridEditInputCell.tsx +19 -12
  37. package/lib/components/CellSelectionStats/CellSelectionStats.css +31 -0
  38. package/lib/components/CellSelectionStats/CellSelectionStats.tsx +32 -0
  39. package/lib/components/CellSelectionStats/GridCellSelectionStatsArea.tsx +24 -0
  40. package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.css +14 -0
  41. package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +3 -2
  42. package/lib/components/DataGrid/DataGrid.tsx +123 -3
  43. package/lib/components/DataGrid/GridLiveRegion.tsx +8 -2
  44. package/lib/components/DataGrid/GridPinnedRows.tsx +9 -0
  45. package/lib/components/DataGrid/GridToolbarSlot.tsx +5 -0
  46. package/lib/components/DataGrid/GridVirtualRows.tsx +9 -0
  47. package/lib/components/FilterPanel/FilterPanel.css +14 -0
  48. package/lib/components/FilterPanel/FilterPanel.tsx +5 -3
  49. package/lib/components/Header/Header.css +12 -0
  50. package/lib/components/Header/Header.tsx +17 -4
  51. package/lib/components/ListView/ListView.css +2 -4
  52. package/lib/components/ListView/ListViewRow.tsx +2 -2
  53. package/lib/components/Row/Row.tsx +38 -1
  54. package/lib/components/Toolbar/GlobalSearch.css +16 -0
  55. package/lib/components/Toolbar/GlobalSearch.tsx +3 -2
  56. package/lib/components/Toolbar/GridToolbar.tsx +10 -4
  57. package/lib/components/ui/Checkbox.tsx +26 -10
  58. package/lib/components/ui/Input.css +43 -0
  59. package/lib/components/ui/Input.tsx +24 -5
  60. package/lib/hooks/core/gridApiPlaceholder.ts +14 -1
  61. package/lib/hooks/core/useDataGrid.ts +27 -0
  62. package/lib/hooks/core/useGridCellSelection.ts +631 -0
  63. package/lib/hooks/core/useGridCellSelectionApi.ts +97 -0
  64. package/lib/hooks/core/useGridClipboardApi.ts +8 -2
  65. package/lib/hooks/core/useGridClipboardPaste.ts +283 -0
  66. package/lib/hooks/core/useGridControlledState.ts +38 -0
  67. package/lib/hooks/core/useGridKeyboardNavigation.ts +32 -0
  68. package/lib/hooks/core/useGridRowInteractions.ts +8 -4
  69. package/lib/hooks/core/useGridUndoRedo.ts +220 -0
  70. package/lib/hooks/features/useAggregation.ts +8 -6
  71. package/lib/hooks/features/useGridBatchEdit.ts +217 -0
  72. package/lib/hooks/features/useGridClipboard.ts +47 -2
  73. package/lib/hooks/features/useGridEditing.ts +39 -51
  74. package/lib/index.ts +16 -0
  75. package/lib/styles/base/variables.css +6 -1
  76. package/lib/styles/opengridx.css +3 -1
  77. package/lib/theme/DataGridThemeProvider.tsx +4 -0
  78. package/lib/theme/presets.ts +8 -0
  79. package/lib/theme/types.ts +4 -0
  80. package/lib/types/index.ts +254 -0
  81. package/lib/utils/attempt.ts +14 -0
  82. package/lib/utils/cellSelection.ts +238 -0
  83. package/lib/utils/editing/commit.ts +69 -0
  84. package/lib/utils/editing/targets.ts +109 -0
  85. package/lib/utils/headerWrap.ts +23 -0
  86. package/lib/utils/parsing.ts +252 -0
  87. package/llms.txt +71 -2
  88. package/package.json +14 -4
@@ -17,6 +17,7 @@ The main component for displaying and interacting with data.
17
17
  | `getRowId` | `(row: R) => GridRowId` | `row.id` | Returns a unique identifier for each row. It only keys the grid's internal store: rows reach `renderCell`, events, `apiRef` and `processRowUpdate` unchanged, and the id is never written to `row.id` (v3.0+). Pass it to export functions as `getRowId` when you export `selectedRows`. It must not throw: an id is a programming contract, so a throw is not contained (for a `dataSource` row it fails that request with the error overlay). |
18
18
  | `rowHeight` | `number` | `52` | Height of each row in pixels. Without it, an enclosing `DataGridThemeProvider`'s `grid.rowHeightStandard` applies (v3.0+). |
19
19
  | `headerHeight` | `number` | `56` | Height of the header row. Without it, the theme's `grid.headerHeight` applies (v3.0+). |
20
+ | `wrapHeaderText` | `boolean` | `false` | Wraps header titles onto several lines instead of cutting them with an ellipsis (v3.3.0+). The header keeps `headerHeight`: a title takes as many lines as fit (2 at 56px, 3 at 72px, 4 at 88px) and ends in an ellipsis if it is still longer; raise `headerHeight` to show more lines. Wrapped headers get `ogx__header-cell--wrap` and their full title as the `title` tooltip (unless `description` is set). `GridColDef.wrapHeaderText` overrides it per column. See [Header](components/header.md). |
20
21
  | `autoHeight` | `boolean` | `false` | Adjust grid height to match row total. |
21
22
  | `overscanRowCount` | `number` | `3` | Minimum rows rendered outside the visible viewport. The grid adapts this upward automatically based on scroll velocity — this prop sets the floor. |
22
23
  | `loading` | `boolean` | `false` | With no rows, the body shows skeleton rows (or `slots.loadingOverlay`). With rows already shown, they stay and a progress bar runs along the top of the grid (or `slots.loadingOverlay` is shown over them). Works in list view too. |
@@ -30,7 +31,7 @@ The main component for displaying and interacting with data.
30
31
  | `height` | `number \| string` | `undefined` | Total height of the grid. Without it the grid fills its container (`height: 100%`, or `flex: 1 1 auto; min-height: 0` as a flex item; v3.0.1+), and grows to fit every row in an auto-height container. |
31
32
  | `density` | `'compact' \| 'standard' \| 'comfortable'` | `'standard'` | Visual row density: compact = the theme's `grid.rowHeightCompact` or 32 px, standard = `rowHeight`, comfortable = the theme's `grid.rowHeightComfortable` or 72 px. |
32
33
  | `initialState` | `GridInitialState` | `undefined` | Starting state (sort, filter, pagination, columns, density), read on mount. Seeds the grid's uncontrolled state; see [State Persistence](features/state-persistence.md). |
33
- | `slots` | `GridSlots` | — | Custom component overrides: `toolbar`, `pagination`, `noRowsOverlay`, `loadingOverlay`, `footer`. See [`GridSlots`](#gridslots-and-gridslotprops) and the [Slots API](customization/slots-api.md). |
34
+ | `slots` | `GridSlots` | — | Custom component overrides: `toolbar`, `pagination`, `noRowsOverlay`, `loadingOverlay`, `footer`, `cellSelectionStats`. See [`GridSlots`](#gridslots-and-gridslotprops) and the [Slots API](customization/slots-api.md). |
34
35
  | `slotProps` | `GridSlotProps` | — | Props passed to the slots, keyed like `slots`. `slotProps.toolbar` is typed as `Partial<GridToolbarProps>` (plus any extra keys your own toolbar reads), so `GridToolbar` render props are type-checked (v3.0+). |
35
36
  | `filterModel` | `GridFilterModel` | `undefined` | Active filters (controlled). Omit it to let the grid keep its own filter state, seeded from `initialState.filter` and changed by the toolbar or `apiRef.setFilterModel` (v3.0+). |
36
37
  | `sortModel` | `GridSortItem[]` | `undefined` | Active sorting. |
@@ -61,6 +62,55 @@ The main component for displaying and interacting with data.
61
62
  | `disableMultipleRowSelection` | `boolean` | `false` | When `true`, at most one row can be selected at a time — by click, checkbox, Space or `apiRef` — and the header select-all checkbox is not shown. |
62
63
  | `disableClipboardCopy` | `boolean` | `false` | When `true`, Ctrl+C / Cmd+C does not copy the selected rows, so the page or your own handler owns the shortcut. `apiRef.current.copySelectedRows()` still works. See [Clipboard](features/clipboard.md). |
63
64
 
65
+ #### Cell Range Selection (v3.3)
66
+
67
+ | Prop | Type | Default | Description |
68
+ | :--- | :--- | :--- | :--- |
69
+ | `cellSelection` | `boolean` | `false` | Spreadsheet-style cell ranges: drag (with auto-scroll), Shift+click, Shift+arrow / Home / End / PageUp / PageDown, Ctrl/Cmd+A (every data cell of the page) and Escape (collapse). While on, Ctrl/Cmd+C copies the range (the focused cell at minimum) as TSV without a header line; `apiRef.current.copySelectedRows()` still copies rows. Row selection is not changed by ranges. See [Cell Range Selection](features/cell-selection.md). |
70
+ | `cellSelectionModel` | `GridCellSelectionModel` | — | Controlled cell selection (zero or one range). Ignored while `cellSelection` is off. |
71
+ | `onCellSelectionModelChange` | `(model: GridCellSelectionModel, details: { reason: GridCellSelectionReason }) => void` | — | Fired when the cell selection changes. `reason`: `'pointer'`, `'keyboard'`, `'selectAll'`, `'api'`, `'clear'` or `'dataChange'` (a corner's row or column is no longer displayed, so the range was cleared). |
72
+ | `showCellSelectionStats` | `boolean` | `false` | Status bar under the grid (above the pagination area) with Count of non-empty cells and Sum / Average of the numeric cells, while more than one cell is selected. Replaceable through `slots.cellSelectionStats`. Needs `cellSelection`. |
73
+
74
+ ```ts
75
+ interface GridCellCoordinates { id: GridRowId; field: string }
76
+ interface GridCellRange { anchor: GridCellCoordinates; head: GridCellCoordinates } // anchor = active cell
77
+ type GridCellSelectionModel = readonly GridCellRange[];
78
+ type GridCellSelectionReason = 'pointer' | 'keyboard' | 'selectAll' | 'api' | 'clear' | 'dataChange';
79
+ interface GridCellSelectionChangeDetails { reason: GridCellSelectionReason }
80
+ interface GridSelectedCell { id: GridRowId; field: string; value: unknown }
81
+ ```
82
+
83
+ Public CSS: `ogx__cell--range`, `ogx__cell--range-top` / `-bottom` / `-left` / `-right`, `ogx--range-dragging` (root, during a drag), `--ogx-range-background`, `--ogx-range-border`. Range cells carry `aria-selected="true"`.
84
+
85
+ #### Paste and Range Clear (v3.4)
86
+
87
+ | Prop | Type | Default | Description |
88
+ | :--- | :--- | :--- | :--- |
89
+ | `disableClipboardPaste` | `boolean` | `false` | With `cellSelection` on, Ctrl/Cmd+V pastes tab-separated text into the editable cells (through `valueSetter` and `processRowUpdate`, once per row). `true` turns the shortcut off; `apiRef.current.pasteText()` still works. See [Clipboard](features/clipboard.md#-paste-from-excel-and-google-sheets-v34). |
90
+ | `onClipboardPaste` | `(result: GridClipboardPasteResult) => void` | — | Fired after every paste, once every row has settled: `{ updated, failed, skipped, text }`. |
91
+ | `onBeforeClipboardPaste` | `(params: GridBeforeClipboardPasteParams) => boolean \| string` | — | Runs before a paste with `{ text, anchor }`. Return `false` to cancel, a string to paste instead. A throw cancels the paste. |
92
+ | `disableRangeClear` | `boolean` | `false` | With `cellSelection`, Delete / Backspace on a range of more than one cell empties its editable cells (`''` for strings, `null` otherwise) as one edit. `true` turns that off. |
93
+
94
+ #### Undo / Redo (v3.4)
95
+
96
+ | Prop | Type | Default | Description |
97
+ | :--- | :--- | :--- | :--- |
98
+ | `undoRedo` | `boolean \| GridUndoRedoOptions` | `false` | History of committed edits, pastes and range clears: Ctrl/Cmd+Z, Ctrl/Cmd+Shift+Z, Ctrl+Y (focus on a cell, no editor open) and `apiRef.undo()` / `redo()`. Undo and redo write through `valueSetter` and `processRowUpdate`; cells changed since are skipped (`'changed'`). `{ limit }`: actions kept, default 100. See [Undo & Redo](features/undo-redo.md). |
99
+ | `onHistoryChange` | `(params: GridHistoryChangeParams) => void` | — | Fired when `canUndo`, `canRedo` or `size` (actions that can be undone) changes. |
100
+
101
+ ```ts
102
+ type GridEditSkipReason = 'notEditable' | 'invalidValue' | 'changed' | 'missing';
103
+ interface GridBatchEditResult {
104
+ updated: GridRowId[]; // rows stored (processRowUpdate succeeded)
105
+ failed: { id: GridRowId; error: unknown }[]; // also sent to onProcessRowUpdateError
106
+ skipped: { id: GridRowId; field: string; reason: GridEditSkipReason }[];
107
+ }
108
+ interface GridClipboardPasteResult extends GridBatchEditResult { text: string }
109
+ interface GridBeforeClipboardPasteParams { text: string; anchor: GridCellCoordinates | null }
110
+ interface GridHistoryChangeParams { canUndo: boolean; canRedo: boolean; size: number }
111
+ interface GridUndoRedoOptions { limit?: number }
112
+ ```
113
+
64
114
  #### Column Visibility
65
115
 
66
116
  | Prop | Type | Default | Description |
@@ -210,6 +260,7 @@ The types of the `slots` and `slotProps` props (v3.0+ exports). Each slot is typ
210
260
  | `pagination` | The built-in `Pagination` bar (shown with `pagination`). | `GridPaginationSlotProps` (= `PaginationProps`: `page`, `pageSize`, `rowCount`, `pageSizeOptions`, `onPageChange`, `onPageSizeChange`, `localeText`), then `slotProps.pagination`. |
211
261
  | `noRowsOverlay` | The empty-state icon and label (shown when there are no rows and the grid is not loading). | `slotProps.noRowsOverlay` only (`GridOverlaySlotProps`). |
212
262
  | `loadingOverlay` | The loading indicator: the skeleton rows while loading with no rows, the progress bar while loading with rows shown (the slot is then shown over them). | `slotProps.loadingOverlay` only (`GridOverlaySlotProps`). |
263
+ | `cellSelectionStats` | The status bar of `showCellSelectionStats` (v3.3). Rendered while more than one cell is selected. | `GridCellSelectionStatsSlotProps`: `apiRef`, `cellCount`, `rowCount`, `columnCount`, `count`, `numericCount`, `sum`, `average` (`null` without numeric cells), then `slotProps.cellSelectionStats`. |
213
264
  | `footer` | The pagination area. | `GridFooterSlotProps`: `apiRef`, `aggregationModel`, `aggregationResult`, `rowCount`, `pagination`, `paginationModel`, `pageSizeOptions`, `onPaginationModelChange`, then `slotProps.footer`. `rowCount` is the built-in pager's count (pinned rows excluded, v3.0+); under tree data or row grouping, the filtered data rows. |
214
265
 
215
266
  ```ts
@@ -301,6 +352,17 @@ Access these methods via the `apiRef` prop.
301
352
  | `getAggregationModel()` | `GridAggregationModel \| null` | Get the active aggregation configuration. |
302
353
  | `getAllFilteredRows()` | `R[]` | Get every row that passes the filter, sorted, regardless of pagination, including pinned rows. Under row grouping and tree data it returns the data rows (no group rows) in hierarchy order with every group expanded, ordered like the screen (also when sorting by the hierarchy column; v3.0+). With tree data and a filter, ancestors shown only to give a match its context are left out: the result is the set select-all and the aggregation footer act on (v3.0+). Use for full-dataset exports. |
303
354
  | `getGroupedExportRows()` | `GridGroupedExportRow[] \| null` | Get a flat ordered list reflecting the active row-grouping tree (group-header, leaf, subtotal, grand-total), sorted and filtered like the screen. Collapsed groups are included with their rows, and subtotals are computed over each group's exported rows. Group-header and subtotal entries carry `groupLabel`, the label the grid shows (v3.0+). Returns `null` when row grouping is not active. |
355
+ | `getCellSelectionModel()` | `GridCellSelectionModel` | The cell selection (`[]` while `cellSelection` is off). Includes a change made earlier in the same tick (v3.3). |
356
+ | `setCellSelectionModel(model)` | `void` | Replace the cell selection (only the first range is kept); focus moves to its anchor. `reason: 'api'` (v3.3). |
357
+ | `selectCellRange(anchor, head)` | `void` | Select the rectangle between two `GridCellCoordinates`; `anchor` becomes the active cell (v3.3). |
358
+ | `clearCellSelection()` | `void` | Empty the cell selection (`reason: 'clear'`) (v3.3). |
359
+ | `getSelectedCells()` | `GridSelectedCell[]` | Cells of the range in display order with values read through `valueGetter`. Synthetic rows and span-covered positions are left out (v3.3). |
360
+ | `copySelectedCells()` | `Promise<void>` | Copy the range (the focused cell when nothing else is selected) as TSV without a header line. Resolves without writing when there is nothing to copy; rejects when the clipboard write fails (v3.3). |
361
+ | `pasteText(text, anchor?)` | `Promise<GridBatchEditResult>` | Paste tab-separated text as Ctrl/Cmd+V does, from `anchor` (default: the range's top-left cell, else the focused cell), with `onBeforeClipboardPaste` / `onClipboardPaste`. Works with `disableClipboardPaste` and without `cellSelection` (then pass `anchor` or focus a cell) (v3.4). |
362
+ | `undo()` | `Promise<GridBatchEditResult>` | Undo the last action (`undoRedo`); resolves once every row has settled (v3.4). |
363
+ | `redo()` | `Promise<GridBatchEditResult>` | Redo the last undone action (v3.4). |
364
+ | `canUndo()` / `canRedo()` | `boolean` | Whether there is an action to undo / redo (v3.4). |
365
+ | `clearHistory()` | `void` | Forget every undo and redo action (v3.4). |
304
366
  | `copySelectedRows()` | `Promise<void>` | Copy every selected row that passes the filter (other pages, collapsed groups and pinned rows included) as TSV, with the visible columns in screen order. Resolves without writing when no selected row is found; rejects when the clipboard write fails. |
305
367
 
306
368
  ---
@@ -346,6 +408,7 @@ Defines the behavior and appearance of a single column.
346
408
  | `flex` | `number` | — | Flex grow factor — distributes remaining space proportionally. When both are set, `flex` wins over `width` until the user resizes the column. |
347
409
  | `align` | `'left' \| 'center' \| 'right'` | `'left'` | Horizontal alignment of cell content. |
348
410
  | `headerAlign` | `'left' \| 'center' \| 'right'` | `align` | Horizontal alignment of the header cell content. Falls back to the column's `align`, then `'left'`. |
411
+ | `wrapHeaderText` | `boolean` | grid's `wrapHeaderText` | Wraps this column's header title (v3.3.0+). Overrides the grid prop either way: `false` keeps one line with an ellipsis in a wrapping grid. |
349
412
  | `zIndex` | `number` | — | CSS `z-index` for the column's cells and header cell (pinned cells default to 3). |
350
413
 
351
414
  #### Data & Type
@@ -356,6 +419,7 @@ Defines the behavior and appearance of a single column.
356
419
  | `valueOptions` | `Array<string \| number \| { value: unknown; label: string }>` | — | Allowed values for `type: 'singleSelect'` — the filter panel offers them as a (multi-)select, and the edit cell uses them; cells and exports show the option's label. |
357
420
  | `valueGetter` | `(params: GridValueGetterParams) => unknown` | — | Derive a computed value from the row object. Runs before `valueFormatter` and `renderCell`. Client-side sorting, column filters and the quick filter use this value, and editors start from it (double-click and Enter alike). Keep it pure: the quick filter caches each row's search text per row object and column set. A getter that throws for a row shows an error in that cell and reads as `undefined` for sorting, filtering, aggregation, grouping, pivot and list view (v3.0+, with a one-time development warning per column); before, it crashed the grid once the column was sorted, filtered or aggregated. |
358
421
  | `valueSetter` | `(params: GridValueSetterParams) => R` | — | v3.0+. Maps an edited value back onto the row (`{ value, row, field }` → updated row) when an edit is committed. Needed for editable `valueGetter` columns; without it the commit writes `row[field]` and a development warning is logged. |
422
+ | `valueParser` | `(text: string, params: GridValueParserParams) => unknown` | — | v3.4+. Turns pasted text into this column's value instead of the default parser for its `type` (`{ row, field, colDef }`). Throw or return `undefined` to skip the cell (`'invalidValue'`). See [Clipboard](features/clipboard.md#valueparser). |
359
423
  | `valueFormatter` | `(params: GridValueFormatterParams) => string` | — | Format the value into a display string (e.g. currency, dates). Does not affect editing, sorting or column filters; the quick filter also searches the formatted text. A formatter that throws leaves the value unformatted (the quick filter searches the raw value; v3.0+). |
360
424
 
361
425
  #### Rendering
@@ -404,6 +468,7 @@ Span values are floored; `Infinity` means "to the end"; `NaN`, `0` and negative
404
468
  | `groupingValueFormatter` | `(params: { field: string; value: unknown }) => string` | — | Custom formatter for group-header labels when this column is the active grouping field. Falls back to `"field: value"` when omitted or when it throws (v3.0+). |
405
469
  | `aggregable` | `boolean` | — | Whether this column can be aggregated. Unset, the toolbar's Summaries panel and the pivot panel's value fields offer it only for `type: 'number'` columns; `true` offers it for any type. `false` keeps it out of both panels, and an `aggregationModel` or `pivotModel` entry naming it is ignored (no footer total, group aggregate, pivot value or export total). |
406
470
  | `availableAggregationFunctions` | `string[]` | all built-ins | Restrict which aggregation functions are computed and available for this column (e.g. `['sum', 'avg']`). Functions outside this list are skipped even if set in `aggregationModel` or a pivot value field, the toolbar's Summaries panel and the pivot panel offer only these. |
471
+ | `aiExamples` | `unknown[]` | — | Example values put into the schema from `getGridAiSchema` (`@opencorestack/opengridx/ai`) to help a model write filters. **These values are sent to the model**: the schema never reads row data, so only what you list here leaves the app. Strings, numbers and booleans, at most 10. Not used by the grid itself (v3.4.0+). |
407
472
 
408
473
  ---
409
474
 
@@ -424,6 +489,32 @@ Every exporter takes `columns` typed for your rows (`GridColDef<R>[]`) or untype
424
489
 
425
490
  ---
426
491
 
492
+ ## 🤖 AI Toolkit (`@opencorestack/opengridx/ai`)
493
+
494
+ A separate entry point (v3.4.0+) with no React import and no dependencies; the core package never loads it. It does not call any AI service: the app sends the schema to its own model and validates the reply. Guide: [AI Toolkit](features/ai-toolkit.md).
495
+
496
+ ```ts
497
+ import { getGridAiSchema, validateGridAiState } from '@opencorestack/opengridx/ai';
498
+ import type { GridAiState, GridAiValidationError } from '@opencorestack/opengridx/ai';
499
+ ```
500
+
501
+ | Function | Return | Description |
502
+ | :--- | :--- | :--- |
503
+ | `getGridAiSchema(columns, options?)` | `GridAiSchema` | JSON Schema (draft 2020-12, `schemaVersion: 1`) of the `filterModel` (nested AND/OR groups up to 3 levels, `quickFilterValues`), `sortModel`, `rowGroupingModel`, `aggregationModel`, `pivotModel` and `columnVisibilityModel` the columns allow, honouring `filterable`, `sortable`, `groupable`, `hideable`, `aggregable`, `availableAggregationFunctions` and `valueOptions`. Operators per column come from the column type. Reads column definitions only, never rows; deterministic for the same input. |
504
+ | `validateGridAiState(json, columns, options?)` | `GridAiValidationResult` | `{ state, errors }`. `json` is an object or a JSON string. Drops unknown parts, fields, operators and enum values (each with an `{ path, message }` error), coerces values to the column type (`"1,200"` → `1200`, dates → `"YYYY-MM-DD"`), removes empty filter groups. Never throws. |
505
+
506
+ `GridAiSchemaOptions`: `include` / `exclude` (`GridAiPart[]`: `'filter' \| 'sort' \| 'grouping' \| 'aggregation' \| 'pivot' \| 'columnVisibility'`; `exclude` wins), `descriptions` (default `true`: each column's `headerName` and `description` go into the schema), `examples` (default `true`: the columns' `aiExamples` go into the schema). `GridAiValidateOptions` takes `include` / `exclude`; parts outside them are dropped with an error.
507
+
508
+ | Type | Description |
509
+ | :--- | :--- |
510
+ | `GridAiColumn` | The column properties the toolkit reads. A `GridColDef` (typed or untyped) is accepted as is. Fields starting with `__` (system columns) are skipped. |
511
+ | `GridAiSchema` | The returned JSON Schema object: `$schema`, `schemaVersion: 1`, `title`, `type: 'object'`, `properties` (one per offered part) and `$defs`. |
512
+ | `GridAiState` | The validated state: `filterModel?`, `sortModel?`, `rowGroupingModel?`, `aggregationModel?`, `pivotModel?`, `columnVisibilityModel?`, the same types the grid's props take. Only parts present in the reply are set. |
513
+ | `GridAiValidationError` | `{ path: string; message: string }`, e.g. `{ path: 'filterModel.items[2].field', message: 'Unknown field "revenue"' }`. At most 100 are listed. |
514
+ | `GridAiValidationResult` | `{ state: GridAiState; errors: GridAiValidationError[] }` |
515
+
516
+ ---
517
+
427
518
  ## 🧩 Standalone Components
428
519
 
429
520
  The grid's building blocks are exported with their props types (the props types since v3.0). `DataGrid` renders all of them itself; import them to build your own layout or slot.
@@ -438,8 +529,35 @@ The grid's building blocks are exported with their props types (the props types
438
529
  | `Header` | `HeaderProps` | The column header rows. See [Header](components/header.md). |
439
530
  | `Row` | `RowProps` | One body row. See [Row](components/row.md). |
440
531
  | `Cell` | `CellProps` | One body cell. See [Cell](components/cell.md). |
532
+ | `CellSelectionStats` | `CellSelectionStatsProps` | The default status bar of `showCellSelectionStats` (v3.3), e.g. to wrap in a `cellSelectionStats` slot. |
441
533
  | `Skeleton` | `SkeletonProps` | Loading placeholder (`rows`, `columns`, both default 5). |
442
- | `Button`, `Input`, `Checkbox` | `ButtonProps`, `InputProps`, `CheckboxProps` | The grid's form controls. `Button` defaults to `type="button"`. |
534
+ | `Button`, `Input`, `Checkbox` | `ButtonProps`, `InputProps`, `CheckboxProps` | The grid's form controls. `Button` defaults to `type="button"`. See [`Input`](#input) and [`Checkbox`](#checkbox) below. |
535
+
536
+ ### `Input`
537
+ A text field: an `<input class="ogx-input">` inside a `<div class="ogx-input-wrapper">` that also holds the adornments. The grid's search boxes, filter value box and text / number / date cell editors render through it. Every other `<input>` attribute (`value`, `onChange`, `type`, `id`, `name`, `aria-*`, …) goes to the `<input>`.
538
+
539
+ | Prop | Type | Default | Description |
540
+ | :--- | :--- | :--- | :--- |
541
+ | `ref` | `Ref<HTMLInputElement>` | — | Reaches the `<input>` (`forwardRef`, React 18 and 19). Since v3.3.1. |
542
+ | `variant` | `'field' \| 'cell'` | `'field'` | `'field'`: bordered form field. `'cell'`: fills its container (a grid cell), no border, radius, background or focus shadow, cell font and the editor padding; adds `ogx-input-wrapper--cell`. Since v3.3.1. |
543
+ | `inputClassName` | `string` | — | Classes added to the `<input>` next to `ogx-input`. Since v3.3.1. |
544
+ | `className` | `string` | — | Classes added to the wrapper `<div>`. |
545
+ | `fullWidth` | `boolean` | `false` | Wrapper takes 100% width (`ogx-input-wrapper--full-width`). |
546
+ | `error` | `boolean` | `false` | `ogx-input-wrapper--error` (error border; inset error edge in the cell variant) and `aria-invalid="true"` on the `<input>`. |
547
+ | `startAdornment` / `endAdornment` | `ReactNode` | — | Content before / after the `<input>` (`ogx-input__adornment--start` / `--end`), e.g. a currency or a unit. |
548
+ | `disabled` | `boolean` | — | Disables the `<input>` and adds `ogx-input-wrapper--disabled`. |
549
+
550
+ ### `Checkbox`
551
+ A styled checkbox: a visually hidden `<input type="checkbox" class="ogx-checkbox__input">` and a drawn `.ogx-checkbox__box`, inside a `<label class="ogx-checkbox-wrapper">`. Other props (`checked`, `onChange`, `onMouseDown`, `tabIndex`, `aria-*`, …) go to the `<input>`. The grid's row checkboxes, column panel, list view and boolean cell editor use it.
552
+
553
+ | Prop | Type | Default | Description |
554
+ | :--- | :--- | :--- | :--- |
555
+ | `ref` | `Ref<HTMLInputElement>` | — | Reaches the `<input>` (`forwardRef`). Since v3.3.1. |
556
+ | `inputRef` | `Ref<HTMLInputElement>` | — | Also reaches the `<input>` (kept for compatibility). |
557
+ | `inputClassName` | `string` | — | Classes added to the `<input>` next to `ogx-checkbox__input`. Since v3.3.1. |
558
+ | `className` | `string` | — | Classes added to the wrapper `<label>`. |
559
+ | `indeterminate` | `boolean` | `false` | Shows the indeterminate mark and sets the input's `indeterminate`. |
560
+ | `label` | `string` | — | Visible label text after the box; also the accessible name. |
443
561
 
444
562
  ---
445
563
 
@@ -453,7 +571,7 @@ Context provider for overriding the grid's visual system.
453
571
  - **`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.
454
572
  - **`typography`**: `fontFamily`, `fontFamilyMono`, `fontSizeXs` … `fontSizeXl`.
455
573
  - **`spacing`**: `xs` … `xxl`. **`borders`**: `widthThin/Medium/Thick`, `radiusSm` … `radiusXl`, `color`, `colorHover`. **`shadows`**: `sm` … `xl`.
456
- - **`grid`**: surfaces (`background`, `borderColor`, `headerBackground`, `headerText`, `headerHoverBackground`, `headerSortedBackground`, `rowText`, `rowHoverBackground`, `rowAlternateBackground`, `rowSelectedBackground`, `rowSelectedHoverBackground`, `cellFocusBorder`, `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`.
574
+ - **`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`.
457
575
  - **`toolbar`**, **`overlays`**, **`scrollbar`** (`thumbColor`, `trackColor`, `size`), **`skeleton`** (`baseColor`, `highlightColor`), **`transitions`**.
458
576
 
459
577
  Presets: `darkTheme`, `roseTheme`, `emeraldTheme`, `amberTheme`, `compactTheme`. See [Theming](customization/theming.md).
@@ -690,6 +808,8 @@ interface GridLocaleText {
690
808
  paginationOf?: (from: number, to: number, count: number) => string;
691
809
  paginationPage?: (page: number, pageCount: number) => string;
692
810
  noRowsLabel?: string;
811
+ /** v3.3: live-region text after a cell range change. Default "12 cells selected, 3 rows by 4 columns". */
812
+ cellSelectionAnnouncement?: (cellCount: number, rowCount: number, columnCount: number) => string;
693
813
  }
694
814
  ```
695
815
 
@@ -782,6 +902,15 @@ Passed to `valueSetter` (v3.0+).
782
902
  | `row` | `R` | The row as it was before the edit. |
783
903
  | `field` | `string` | The column field name. |
784
904
 
905
+ ### `GridValueParserParams<R>`
906
+ Passed to `valueParser` (v3.4+).
907
+
908
+ | Property | Type | Description |
909
+ | :--- | :--- | :--- |
910
+ | `row` | `R` | The row the text is pasted into, as stored now. |
911
+ | `field` | `string` | The column field name. |
912
+ | `colDef` | `GridColDef<R>` | The column. |
913
+
785
914
  ### `GridColumnOrderChangeParams`
786
915
  Passed to `onColumnOrderChange`.
787
916
 
package/docs/README.md CHANGED
@@ -36,7 +36,9 @@ In-depth guides on the grid's functional capabilities.
36
36
  - **[Editing & Reordering](features/editing-reordering.md)** - Inline edits and Drag-and-Drop
37
37
  - **[Loading & Performance](features/loading-states.md)** - Skeletons, Lazy, and Infinite loading
38
38
  - **[Row Selection](features/selection.md)** - Multi & single choice selection
39
- - **[Clipboard](features/clipboard.md)** - Copy and Paste grid data
39
+ - **[Clipboard](features/clipboard.md)** - Copy rows and ranges, paste from Excel / Sheets, clear a range with Delete
40
+ - **[Cell Range Selection](features/cell-selection.md)** - Spreadsheet-style cell ranges, copy and a sum / average status bar
41
+ - **[Undo & Redo](features/undo-redo.md)** - Ctrl+Z / Ctrl+Shift+Z for edits, pastes and range clears
40
42
  - **[Pinning](features/pinning.md)** - Sticky columns and rows
41
43
  - **[State Persistence](features/state-persistence.md)** - Save/Restore grid configuration
42
44
  - **[Aggregation & Pivot](features/aggregation-pivot.md)** - Data summarization and analytics
@@ -48,6 +50,7 @@ In-depth guides on the grid's functional capabilities.
48
50
  - **[Infinite Scroll](features/infinite-scroll.md)** - Advanced performance loading
49
51
  - **[Data Source](features/data-source.md)** - Server-side fetching and SSR
50
52
  - **[Export Guide](features/export-guide.md)** - Excel, CSV, JSON, and Print
53
+ - **[AI Toolkit](features/ai-toolkit.md)** - JSON Schema of the grid state for your own model, and a validator for its replies (`@opencorestack/opengridx/ai`)
51
54
 
52
55
  ---
53
56
 
@@ -17,6 +17,14 @@ in an effect, so **keep the call order when you move or add a hook**. In particu
17
17
  `useGridAggregationApi` and `useGridScrollToIndexesApi` install once per `apiRef` in a layout
18
18
  effect and read the latest values from a ref refreshed in a layout effect, so the methods are
19
19
  live in a parent's layout effect and never answer from a previous render.
20
+ - `useGridCellSelection` runs after `useGridSpanning` (it needs `getSpanOrigin` and the span caches) and
21
+ before `useGridKeyboardNavigation`, which receives its Shift-key handlers. `useGridCellSelectionApi` runs
22
+ after the keyboard hook because it reads and sets `focusedCell`.
23
+ - `useGridBatchEdit` is created before `useGridEditing` (single edits store through it, so an undo right after an
24
+ edit reads the stored row before the next render). The history hook needs the focus and selection hooks, so it runs
25
+ late (36c); commits reach it through `useGridEditCommitChannel` (12a), whose listener it sets in a layout effect.
26
+ `useGridClipboardPaste` (36b) and `useGridUndoRedo` listen for keys on the grid root with native listeners, which
27
+ fire before React's delegated `onKeyDown` of the viewport; they act only on cell targets with no editor open.
20
28
  - `useGridLiveRowSelection` prunes the selection, and `useGridPageCorrection` corrects a page past the
21
29
  end, in effects that run before the later hooks' effects.
22
30
 
@@ -25,7 +33,7 @@ in an effect, so **keep the call order when you move or add a hook**. In particu
25
33
  | # | Hook / util | File | Produces |
26
34
  | :- | :--- | :--- | :--- |
27
35
  | 0 | `useStableColumns` | `hooks/core/useStableColumns.ts` | The `columns` prop with a stable identity while every column is shallowly equal (inline columns do not re-run the row passes) |
28
- | 1 | `useGridControlledState` | `hooks/core/useGridControlledState.ts` | Controlled/uncontrolled sort, filter, aggregation, visibility, pinning, pivot, pagination, selection, density |
36
+ | 1 | `useGridControlledState` | `hooks/core/useGridControlledState.ts` | Controlled/uncontrolled sort, filter, aggregation, visibility, pinning, pivot, pagination, selection, density, cell selection (v3.3) |
29
37
  | 2 | `useGridPivot` | `hooks/features/useGridPivot.ts` | Pivot rows/columns (or the source ones) and the pipeline filter/sort models |
30
38
  | 3 | `resolveGridModes` (pure) | `utils/gridModes.ts` | Which features are in effect: tree data, row grouping, pagination, row reordering, list view, grouping column |
31
39
  | 4 | `useGridThemeDimensions` | `hooks/core/useGridThemeDimensions.ts` | Row and header heights (props → theme provider → defaults) |
@@ -37,7 +45,9 @@ in an effect, so **keep the call order when you move or add a hook**. In particu
37
45
  | 10 | `useGridRowIdOf` | `hooks/core/useGridRowIdOf.ts` | `getRowIdOf` and its stable event-handler twin |
38
46
  | 11 | `useGridApiRefBinding` | `hooks/core/useGridApiRefBinding.ts` | Binds the `apiRef` prop (layout effect) |
39
47
  | 12 | `useGridHierarchy` | `hooks/core/useGridHierarchy.ts` | `useTreeData` + `useRowGrouping`, the active handlers and `rowMetaMap` |
40
- | 13 | `useGridEditing` | `hooks/features/useGridEditing.ts` | Cell editing state and handlers |
48
+ | 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) |
49
+ | 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 |
50
+ | 13 | `useGridEditing` | `hooks/features/useGridEditing.ts` | Cell editing state and handlers; stores through `useGridBatchEdit.storeRow` and reports the committed change to the channel |
41
51
  | 14 | `useGridLiveRowSelection` | `hooks/core/useGridLiveRowSelection.ts` | Selection without removed rows |
42
52
  | 15 | `useGridDataSource` | `hooks/features/useGridDataSource.ts` | Server fetching |
43
53
  | 16 | `useServerTreeChildren` | `hooks/features/useServerTreeChildren.ts` | Lazy server tree children |
@@ -54,14 +64,18 @@ in an effect, so **keep the call order when you move or add a hook**. In particu
54
64
  | 27 | `useGridAggregationApi` | `hooks/core/useGridAggregationApi.ts` | `getAggregationResult`, `getAggregationModel`, `getGroupedExportRows` |
55
65
  | 28 | `useLayout` | `hooks/core/useLayout.ts` | Row heights, pinned column split, widths |
56
66
  | 29 | `useGridSpanning` | `hooks/features/useGridSpanning.ts` | Row/column spans |
57
- | 30 | `useGridClipboardApi` | `hooks/core/useGridClipboardApi.ts` | Ctrl/Cmd+C and `copySelectedRows` |
67
+ | 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 |
68
+ | 30 | `useGridClipboardApi` | `hooks/core/useGridClipboardApi.ts` | Ctrl/Cmd+C and `copySelectedRows`; with `cellSelection` the shortcut copies the range (`getCellRangeCopyText`) |
58
69
  | 31 | `useGridScrollToIndexesApi` | `hooks/core/useGridScrollToIndexesApi.ts` | `scrollToIndexes` |
59
70
  | 31a | `useGridColumnAutosize` | `hooks/core/useGridColumnAutosize.ts` | `autosizeColumn` / `autosizeColumns` on the API, and the resize handle's auto-size handler (v3.1) |
60
71
  | 32 | `useGridViewport` | `hooks/core/useGridViewport.ts` | Scroll sync, viewport measurement, viewport callback ref |
61
72
  | 33 | `useGridVirtualization` | `hooks/core/useGridVirtualization.ts` | Render window |
62
73
  | 34 | `useGridSortHandlers` | `hooks/core/useGridHeaderHandlers.ts` | Header sort / shift-sort |
63
- | 35 | `useGridKeyboardNavigation` | `hooks/core/useGridKeyboardNavigation.ts` | Focus and keyboard handling |
74
+ | 35 | `useGridKeyboardNavigation` | `hooks/core/useGridKeyboardNavigation.ts` | Focus and keyboard handling; takes `cellSelection.keyboard` (Shift+navigation, Escape, Ctrl/Cmd+A) |
64
75
  | 36 | `useGridPointerFocusHandlers` | `hooks/core/useGridFocusHandlers.ts` | Cell and header click focus |
76
+ | 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) |
77
+ | 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` |
78
+ | 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` |
65
79
  | 37 | `useGridColumnMenuHandlers` | `hooks/core/useGridHeaderHandlers.ts` | Column menu Hide / Pin (`pinColumnTo` in `utils/pinning`) |
66
80
  | 38 | `useGridAriaRows` | `hooks/core/useGridAriaRows.ts` | `aria-rowcount` and `aria-rowindex` bases |
67
81
  | 39 | `useGridSpanRowWindow` | `hooks/features/useGridSpanRenderWindow.ts` | Render window widened to whole row spans |
@@ -77,6 +91,9 @@ in an effect, so **keep the call order when you move or add a hook**. In particu
77
91
 
78
92
  `GridToolbarSlot`, `GridStandaloneColumnPanel`, `GridListView` (list view) or the viewport
79
93
  (`Header`, `GridPinnedRows` top, `GridEmptyState`, `GridVirtualRows`, `GridPinnedRows` bottom,
80
- `GridAggregationFooter`), then `slots.footer` or `GridPaginationArea`, `GridLiveRegion`,
94
+ `GridAggregationFooter`), `GridCellSelectionStatsArea` (`showCellSelectionStats` with `cellSelection`), then `slots.footer` or `GridPaginationArea`, `GridLiveRegion`,
81
95
  `GridLoadingOverlay` and `GridErrorOverlay`. The props all three row renderers take alike are built
82
- once in `DataGrid.tsx` (`rowRenderProps`) and spread into each.
96
+ once in `DataGrid.tsx` (`rowRenderProps`) and spread into each. `rowRenderProps.cellRange` is the cell range's
97
+ render rectangle; each row renderer turns it into a per-row `cellRange` (`getRowCellRange`), `null` for rows
98
+ outside it, and `Row` is memoised with a value comparison of that prop, so a drag only re-renders the rows the
99
+ range enters or leaves.
@@ -24,6 +24,31 @@ Manages the column headers, sorting triggers, column resizing, and column groupi
24
24
  | `focusedCell` | `{ id: GridRowId \| null; field: string } \| null` | The grid's focus position. `id: null` means the header cell of `field` is focused (v3.0; it used to be the string `'HEADER'`, which a real row id could collide with). DOM focus is moved by the grid, not by `<Header />`. |
25
25
  | `columnIndexMap` | `Map<string, number>` | Position of each visible data column in render order. Used for `aria-colindex` and the `colIndex` passed to `renderHeader`, so they match the body cells and do not change with horizontal scrolling. |
26
26
 
27
+ ## ↩️ Wrapped Header Text
28
+
29
+ By default a header title stays on one line and is cut with an ellipsis. Pass `wrapHeaderText` to `<DataGrid>` (or set `GridColDef.wrapHeaderText` on a column) and long titles wrap instead (v3.3.0+):
30
+
31
+ ```tsx
32
+ const columns: GridColDef[] = [
33
+ // Keeps one line with an ellipsis even though the grid wraps
34
+ { field: 'fund', headerName: 'Fund name and share class', wrapHeaderText: false },
35
+ { field: 'nav', headerName: 'Net asset value per share (USD)', width: 120 },
36
+ ];
37
+
38
+ <DataGrid rows={rows} columns={columns} wrapHeaderText headerHeight={72} />
39
+ ```
40
+
41
+ - **The header keeps `headerHeight`.** It does not grow to fit the title (sticky offsets and virtualization rely on a fixed header height). A wrapped title takes as many lines as fit — 2 at the default 56px, 3 at 72px, 4 at 88px (16px lines inside 8px vertical padding) — and ends in an ellipsis on the last line if it is still longer. Raise `headerHeight` to show more lines. When the column shows an aggregation label under its title, the title gets one line less.
42
+ - **Precedence:** a column's `wrapHeaderText` wins over the grid prop, in both directions.
43
+ - **Tooltip:** a wrapped header shows its full title as the `title` tooltip, unless the column has a `description`, which is shown instead. Only string titles are used; a `renderHeader` result gets no automatic tooltip.
44
+ - **Layout:** the sort icon, the menu button and the resize handle stay where they are and stay clickable; only the title wraps.
45
+ - **Auto-size:** double-clicking the resize handle (or `apiRef.current.autosizeColumn`) still measures the title on one line, so the column grows until the title no longer wraps.
46
+ - **Class:** wrapped header cells get `ogx__header-cell--wrap`; the line count is set inline as `-webkit-line-clamp` on `.ogx__header-cell-title`.
47
+ - **Themes:** the line count assumes the default header font (13px) and cell padding (8px). With a theme that changes `headerFontSize` or `cellPaddingY`, the title is still capped to the header height, but the last visible line may be cut without an ellipsis.
48
+ - Column group header rows are not affected.
49
+
50
+ `Header` takes the same `wrapHeaderText` prop and a `headerHeight` (default 56) when used on its own.
51
+
27
52
  ## 📐 Column Grouping
28
53
 
29
54
  The `<Header />` dynamically calculates the nesting depth of your `columnGroupingModel` and renders one group row per level above the main column headers. Group cells are sized from `allColumns`, so they match their member columns' resolved widths; cells over pinned columns are sticky. See [Column Group Headers](../API_REFERENCE.md#️-column-group-headers) for the full behaviour.
@@ -83,7 +83,7 @@ interface GridTheme {
83
83
  spacing?: GridThemeSpacing;
84
84
  borders?: GridThemeBorders;
85
85
  shadows?: GridThemeShadows;
86
- grid?: GridThemeGrid; // surfaces, heights (px), cellPaddingX/Y, cellFontSize, headerFontSize, …
86
+ grid?: GridThemeGrid; // surfaces, heights (px), cellPaddingX/Y, cellFontSize, headerFontSize, rangeBackground, rangeBorder, …
87
87
  toolbar?: GridThemeToolbar;
88
88
  overlays?: GridThemeOverlays;
89
89
  scrollbar?: GridThemeScrollbar;
@@ -213,6 +213,8 @@ All sub-theme types (`GridThemeColors`, `GridThemeGrayScale`, `GridThemeTypograp
213
213
  | `--ogx-grid-row-selected-background` | `#eff6ff` | `#1e3a5f` | Selected row fill |
214
214
  | `--ogx-grid-row-selected-hover-background` | `#dbeafe` | `#1d4ed8` | Selected row hover |
215
215
  | `--ogx-grid-cell-focus-border` | `#3b82f6` | `#60a5fa` | Keyboard focus ring |
216
+ | `--ogx-range-background` | `rgba(59, 130, 246, 0.12)` | `rgba(96, 165, 250, 0.18)` | Tint over a selected cell range (`cellSelection`, v3.3). Theme key `grid.rangeBackground` |
217
+ | `--ogx-range-border` | `var(--ogx-color-primary)` | `#60a5fa` | Outline of a selected cell range, drawn as an inset box-shadow on `ogx__cell--range-top/-bottom/-left/-right` (v3.3). Theme key `grid.rangeBorder` |
216
218
  | `--ogx-grid-pinned-left-shadow` | `4px 0 24px...` | (darker) | Shadow on left-pinned columns |
217
219
  | `--ogx-grid-pinned-right-shadow` | `-4px 0 24px...` | (darker) | Shadow on right-pinned columns |
218
220
  | `--ogx-checkbox-bg` | — | `#1e293b` | Checkbox background (dark only) |
@@ -338,4 +340,13 @@ const purpleTheme: GridTheme = {
338
340
  - The dark mode overrides are in the same file under `@media (prefers-color-scheme: dark)`
339
341
  - The CSS entry point for the entire library is `lib/styles/opengridx.css` (single barrel import)
340
342
  - BEM class prefix: `ogx__` (e.g. `ogx__cell`, `ogx__header-cell`)
343
+ - **Form controls** — the toolbar search, column-panel search, filter value box and the text / number / date cell
344
+ editors render through the shared `Input` (v3.3.1+): their `<input>` keeps its own class (`ogx-global-search__input`,
345
+ `ogx-column-visibility-panel__search-input`, `ogx-filter__value-input`, `ogx__edit-input`) and also has `ogx-input`,
346
+ and sits inside a `div.ogx-input-wrapper`. Target it with a descendant selector (`.ogx-global-search input`), not a
347
+ child one (`.ogx-global-search > input`). Cell editors' wrappers also have `ogx-input-wrapper--cell` (fills the cell,
348
+ no border, background or focus shadow; `ogx-input-wrapper--error` adds an inset `--ogx-color-error` edge). The boolean
349
+ editor and list-view row checkbox are the shared `Checkbox` (`ogx-checkbox-wrapper`, `ogx-checkbox__box`; the `<input>`
350
+ keeps `ogx__edit-checkbox` and is visually hidden). `Input.css` loads first in `opengridx.css`, so component rules
351
+ override it at equal specificity.
341
352
  - Variable prefix: `--ogx-` (e.g. `--ogx-color-primary`)
@@ -0,0 +1,119 @@
1
+ # AI Toolkit: Grid Schema & Validator
2
+
3
+ *Since v3.4.0* · Demo: [AI: Grid Schema & Validator](https://opencorestack.github.io/OpenGridX/ai-schema)
4
+
5
+ Let users drive the grid in plain language ("paid orders over $10k in the North, biggest first") with the model your app already uses. The `@opencorestack/opengridx/ai` entry point gives you the two pieces the grid can own:
6
+
7
+ - **`getGridAiSchema(columns, options?)`** turns your column definitions into a JSON Schema (draft 2020-12) of the grid state a model may return: filters, sort, row grouping, aggregation, pivot and column visibility, each limited to what the columns allow.
8
+ - **`validateGridAiState(json, columns, options?)`** checks the model's reply against the columns and returns the state that is safe to apply, plus a list of what it dropped.
9
+
10
+ OpenGridX never calls an AI service and bundles no AI SDK. Your app sends the prompt, the schema and the current state to its own model (with structured output), and passes the reply to the validator.
11
+
12
+ ```ts
13
+ import { getGridAiSchema, validateGridAiState } from '@opencorestack/opengridx/ai';
14
+
15
+ const schema = getGridAiSchema(columns, { include: ['filter', 'sort', 'grouping', 'aggregation'] });
16
+
17
+ async function ask(prompt: string) {
18
+ // Your own endpoint calls your own model, with `schema` as the structured-output format.
19
+ const res = await fetch('/api/grid-assistant', {
20
+ method: 'POST',
21
+ headers: { 'Content-Type': 'application/json' },
22
+ body: JSON.stringify({ prompt, schema, currentState: { filterModel, sortModel } }),
23
+ });
24
+ const { state, errors } = validateGridAiState(await res.text(), columns);
25
+ if (errors.length) console.info('Dropped from the reply:', errors);
26
+ if (state.filterModel) setFilterModel(state.filterModel);
27
+ if (state.sortModel) setSortModel(state.sortModel);
28
+ if (state.rowGroupingModel) setRowGroupingModel(state.rowGroupingModel);
29
+ if (state.aggregationModel) setAggregationModel(state.aggregationModel);
30
+ }
31
+ ```
32
+
33
+ The entry point has no React import and no dependencies (about 4.5 kB gzipped). Importing `@opencorestack/opengridx` never loads it, and it works on a server too, so you can validate the reply in the same endpoint that calls the model.
34
+
35
+ ## Privacy rules
36
+
37
+ - **No row data by default.** The schema is built from column definitions only: `field`, `headerName`, `description`, `type`, the operators of the type, `valueOptions` and the allowed aggregation functions. The grid never reads a row for it.
38
+ - **`aiExamples` is real data.** `GridColDef.aiExamples` puts example values into the schema to help the model ("Acme" → `customer`). Those values are sent to the model. List only what you are happy to share; leave it out for anything personal. `options.examples: false` drops all of them.
39
+ - **`valueOptions` are sent.** The options of a `singleSelect` column become an `enum` in the schema. If an option list is itself sensitive, mark the column `filterable: false` or pass a copy of the columns without it.
40
+ - **Always validate before applying.** Model output is untrusted input. Apply `state`, never the raw reply; show `errors` (or log them) so users can see what was ignored. Show the change to the user and keep it easy to undo.
41
+ - **Check `schemaVersion`.** The schema carries `schemaVersion: 1`. It changes only when the shape of the schema changes, so you can cache prompts or fine-tuning data per version. The validator accepts a `schemaVersion` key in the reply and reports any value other than 1.
42
+
43
+ ## `getGridAiSchema(columns, options?)`
44
+
45
+ Returns a plain JSON object (no functions), deterministic for the same columns and options, so you can memoise it with the columns.
46
+
47
+ | Part (state key) | Option name | Shape | Taken from the columns |
48
+ | :--- | :--- | :--- | :--- |
49
+ | `filterModel` | `'filter'` | `GridFilterModel`: `items`, `logicOperator`, nested AND/OR groups up to 3 levels, `quickFilterValues` | Fields with `filterable !== false`; per field the operators of `getOperatorsForType(type)`; `value` typed per column type (number, `YYYY-MM-DD` string, boolean, `valueOptions` enum or a list of them for `isAnyOf`) |
50
+ | `sortModel` | `'sort'` | `GridSortItem[]` | Fields with `sortable !== false` |
51
+ | `rowGroupingModel` | `'grouping'` | `string[]` | Fields with `groupable !== false` |
52
+ | `aggregationModel` | `'aggregation'` | field → function | `type: 'number'` (or `aggregable: true`) columns get every built-in function, other columns `count`; never `aggregable: false`; limited by `availableAggregationFunctions` |
53
+ | `pivotModel` | `'pivot'` | `GridPivotModel` | Groupable fields as row and column labels; value fields as for aggregation (`sum`, `avg`, `count`, `min`, `max`) |
54
+ | `columnVisibilityModel` | `'columnVisibility'` | field → boolean | Fields with `hideable !== false` |
55
+
56
+ Each filter item is a `oneOf` per field (`field` is a `const`, `operator` an `enum`, `value` typed by the column), which the main structured-output APIs accept. A part that no column allows is left out of the schema.
57
+
58
+ | Option | Default | Description |
59
+ | :--- | :--- | :--- |
60
+ | `include` | every part | Parts to offer: `'filter' \| 'sort' \| 'grouping' \| 'aggregation' \| 'pivot' \| 'columnVisibility'` |
61
+ | `exclude` | none | Parts to leave out; wins over `include` |
62
+ | `descriptions` | `true` | Put each column's `headerName` and `description` into the schema, so the model can map the user's words ("revenue") to a field (`amt_usd`) |
63
+ | `examples` | `true` | Include the columns' `aiExamples`. Columns without `aiExamples` never send example values |
64
+
65
+ Columns whose field starts with `__` (the grid's own `__check__`, `__group__` …) are skipped, so `apiRef.current.getAllColumns()` can be passed as is. The `columns` parameter takes any object with the `GridColDef` properties above (`GridAiColumn`); renderers, getters and formatters are ignored.
66
+
67
+ Example for two columns:
68
+
69
+ ```ts
70
+ getGridAiSchema(
71
+ [
72
+ { field: 'amt_usd', headerName: 'Revenue', type: 'number' },
73
+ { field: 'status', headerName: 'Status', type: 'singleSelect', valueOptions: ['Open', 'Paid'], groupable: false },
74
+ ],
75
+ { include: ['filter', 'sort'], descriptions: false },
76
+ );
77
+ // {
78
+ // $schema: 'https://json-schema.org/draft/2020-12/schema', schemaVersion: 1, title: 'OpenGridX grid state',
79
+ // type: 'object', additionalProperties: false,
80
+ // properties: {
81
+ // filterModel: { type: 'object', additionalProperties: false, properties: {
82
+ // items: { type: 'array', items: { anyOf: [{ $ref: '#/$defs/filterItem' }, { $ref: '#/$defs/filterGroup1' }] } },
83
+ // logicOperator: { enum: ['and', 'or'] },
84
+ // quickFilterValues: { type: 'array', items: { type: 'string' } } } },
85
+ // sortModel: { type: 'array', items: { type: 'object', additionalProperties: false, required: ['field', 'sort'],
86
+ // properties: { field: { enum: ['amt_usd', 'status'] }, sort: { enum: ['asc', 'desc'] } } } },
87
+ // },
88
+ // $defs: {
89
+ // filterItem: { oneOf: [
90
+ // { type: 'object', required: ['field', 'operator'], additionalProperties: false, properties: {
91
+ // field: { const: 'amt_usd' }, operator: { enum: ['=', '!=', '>', '>=', '<', '<=', 'isEmpty', 'isNotEmpty'] },
92
+ // value: { type: 'number' } } },
93
+ // { type: 'object', required: ['field', 'operator'], additionalProperties: false, properties: {
94
+ // field: { const: 'status' }, operator: { enum: ['isAnyOf', 'is', 'not'] },
95
+ // value: { anyOf: [{ enum: ['Open', 'Paid'] }, { type: 'array', items: { enum: ['Open', 'Paid'] } }] } } },
96
+ // ] },
97
+ // filterGroup1: { … items: filterItem | filterGroup2 }, filterGroup2: { … }, filterGroup3: { … items: filterItem },
98
+ // },
99
+ // }
100
+ ```
101
+
102
+ ## `validateGridAiState(json, columns, options?)`
103
+
104
+ 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"' }`.
105
+
106
+ - `json` may be an object or a JSON string. Bad JSON gives one error and an empty state.
107
+ - 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.
108
+ - Values are coerced to the column type with the same rules as clipboard paste: `"1,200"`, `"$1,200"` → `1200`, `"12%"` → `0.12`; `"yes"` / `"no"` / `"1"` / `"0"` → booleans; dates (ISO or anything `Date.parse` reads) → the local day as `"YYYY-MM-DD"`, as the filter panel writes it; `singleSelect` values match an option value or label, case-insensitively, and become the option's value. A filter without a usable value is dropped.
109
+ - `asc` / `desc`, `and` / `or`, operators and function names are matched case-insensitively and written in their canonical spelling.
110
+ - A filter group left empty is removed; groups nested deeper than 3 levels are dropped.
111
+ - Lists are read up to 200 entries and at most 100 errors are listed, so a runaway reply stays cheap.
112
+ - It never throws, never returns a field the columns do not allow, and reads only own keys of the reply (never `__proto__`), so a reply cannot reach `Object.prototype`.
113
+
114
+ Applying an empty part is meaningful: `{ sortModel: [] }` clears the sort. Only apply the parts present in `state`.
115
+
116
+ ## Related
117
+
118
+ - [Filtering](filtering.md), [Sorting & Pagination](sorting-pagination.md), [Aggregation & Pivot](aggregation-pivot.md), [Tree Data & Grouping](tree-data-grouping.md): the models the schema describes.
119
+ - [API Reference: AI Toolkit](../API_REFERENCE.md)