@opencorestack/opengridx 3.4.0 → 3.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +10 -3
  3. package/dist/ai.cjs +2 -1
  4. package/dist/ai.cjs.map +1 -1
  5. package/dist/ai.d.ts +203 -3
  6. package/dist/ai.es.js +2 -1
  7. package/dist/ai.es.js.map +1 -1
  8. package/dist/index.d.ts +248 -4
  9. package/dist/opengridx.css +1 -1
  10. package/dist/opengridx.es.js +7334 -5906
  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 +88 -7
  15. package/docs/README.md +3 -1
  16. package/docs/architecture/datagrid-orchestration.md +11 -5
  17. package/docs/components/filter-panel.md +1 -0
  18. package/docs/components/header.md +4 -1
  19. package/docs/components/toolbar.md +7 -0
  20. package/docs/customization/theming.md +3 -1
  21. package/docs/features/ai-toolkit.md +99 -5
  22. package/docs/features/cell-selection.md +8 -2
  23. package/docs/features/fill-handle.md +125 -0
  24. package/docs/features/filtering.md +3 -0
  25. package/docs/features/header-filters.md +138 -0
  26. package/docs/features/keyboard-navigation.md +20 -2
  27. package/docs/features/virtualization.md +5 -5
  28. package/docs/performance.md +212 -1
  29. package/docs/roadmap.md +3 -4
  30. package/lib/ai/agentTools.ts +160 -0
  31. package/lib/ai/index.ts +19 -2
  32. package/lib/ai/promptHandler.ts +109 -0
  33. package/lib/ai/types.ts +89 -25
  34. package/lib/ai/validate.ts +10 -0
  35. package/lib/components/AiAssistant/GridAiAssistantArea.tsx +26 -0
  36. package/lib/components/AiAssistant/GridAiAssistantPanel.css +171 -0
  37. package/lib/components/AiAssistant/GridAiAssistantPanel.tsx +145 -0
  38. package/lib/components/AiAssistant/useSpeechInput.ts +74 -0
  39. package/lib/components/Cell/Cell.css +28 -0
  40. package/lib/components/Cell/Cell.tsx +6 -2
  41. package/lib/components/DataGrid/DataGrid.tsx +84 -4
  42. package/lib/components/DataGrid/GridLiveRegion.tsx +5 -2
  43. package/lib/components/DataGrid/GridPinnedRows.tsx +5 -1
  44. package/lib/components/DataGrid/GridVirtualRows.tsx +5 -1
  45. package/lib/components/FilterPanel/FilterPanel.tsx +2 -9
  46. package/lib/components/Header/Header.tsx +44 -23
  47. package/lib/components/Header/headerColumnStyle.ts +28 -0
  48. package/lib/components/HeaderFilter/HeaderFilter.css +160 -0
  49. package/lib/components/HeaderFilter/HeaderFilterCell.tsx +370 -0
  50. package/lib/components/HeaderFilter/HeaderFilterOperatorMenu.tsx +124 -0
  51. package/lib/components/HeaderFilter/HeaderFilterRow.tsx +157 -0
  52. package/lib/components/Toolbar/GridToolbar.tsx +51 -0
  53. package/lib/hooks/core/gridApiPlaceholder.ts +8 -0
  54. package/lib/hooks/core/gridToolbarHostContext.ts +5 -0
  55. package/lib/hooks/core/useGridApiMethods.ts +39 -0
  56. package/lib/hooks/core/useGridAriaRows.ts +5 -3
  57. package/lib/hooks/core/useGridCellSelection.ts +77 -11
  58. package/lib/hooks/core/useGridColumnsPanel.ts +24 -1
  59. package/lib/hooks/core/useGridControlledState.ts +27 -0
  60. package/lib/hooks/core/useGridDevWarnings.ts +21 -6
  61. package/lib/hooks/core/useGridFillHandle.ts +323 -0
  62. package/lib/hooks/core/useGridKeyboardNavigation.ts +29 -10
  63. package/lib/hooks/core/useGridMaxContentHeight.ts +16 -0
  64. package/lib/hooks/core/useGridToolbarProps.ts +19 -0
  65. package/lib/hooks/core/useGridVirtualization.ts +19 -0
  66. package/lib/hooks/features/useGridAiAssistant.ts +284 -0
  67. package/lib/hooks/features/useGridBatchEdit.ts +2 -2
  68. package/lib/index.ts +3 -0
  69. package/lib/styles/opengridx.css +2 -0
  70. package/lib/theme/DataGridThemeProvider.tsx +2 -0
  71. package/lib/theme/types.ts +2 -0
  72. package/lib/types/index.ts +219 -1
  73. package/lib/utils/aiAssistant.ts +205 -0
  74. package/lib/utils/aiBrand.ts +17 -0
  75. package/lib/utils/cellSelection.ts +25 -5
  76. package/lib/utils/editing/fill.ts +238 -0
  77. package/lib/utils/filtering/index.ts +14 -0
  78. package/lib/utils/focus/index.ts +9 -3
  79. package/lib/utils/headerFilters.ts +189 -0
  80. package/lib/utils/maxElementHeight.ts +53 -0
  81. package/llms.txt +44 -4
  82. package/package.json +3 -2
@@ -34,6 +34,8 @@ The main component for displaying and interacting with data.
34
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). |
35
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+). |
36
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+). |
37
+ | `headerFilters` | `boolean` | `false` | A filter row under the column headers (v3.5): text, number and date boxes (the shared `Input`), a Yes / No select for `boolean`, a `valueOptions` select for `singleSelect`, an operator menu and a clear button per column. Each cell owns the root `filterModel` item with `id: 'header:<field>'` and leaves every other entry alone; a model it cannot show (root `logicOperator: 'or'`, the field in a group, another root item on the field) is shown as a read-only "Custom filter" that opens the toolbar's filter panel. Typing is debounced by 300 ms. Not rendered in pivot mode. Classes `ogx__header-filter-row`, `ogx__header-filter-cell` (`--focused`, `--active`, `--custom`, `--empty`, `--system`, `--pinned*`), `ogx__header-filter-operator`, `ogx__header-filter-menu`, `ogx__header-filter-input-wrapper`, `ogx__header-filter-input`, `ogx__header-filter-select`, `ogx__header-filter-operator-label`, `ogx__header-filter-clear`, `ogx__header-filter-custom`. See [Header Filters](features/header-filters.md). |
38
+ | `headerFilterHeight` | `number` | `40` | Height of the header filter row in pixels; sets `--ogx-header-filter-height` (v3.5). |
37
39
  | `sortModel` | `GridSortItem[]` | `undefined` | Active sorting. |
38
40
  | `onRowClick` | `(params: GridRowParams) => void` | — | Fired when a row is clicked. Tree-data parent rows are real rows: a click fires this and selects them (their chevron expands them). Not fired for synthetic rows (row-grouping group rows, subtotal rows, auto-created tree parents): clicking a group row toggles it. |
39
41
  | `onRowDoubleClick` | `(params: GridRowParams) => void` | — | Fired when a row is double-clicked (v2.1+). Also fires for group rows, and (v3.0+) for the double-click that opens an editor on an editable cell; not fired on the checkbox, expand icon, drag handle or inside an open editor. |
@@ -111,6 +113,28 @@ interface GridHistoryChangeParams { canUndo: boolean; canRedo: boolean; size: nu
111
113
  interface GridUndoRedoOptions { limit?: number }
112
114
  ```
113
115
 
116
+ #### Fill Handle (v3.5)
117
+
118
+ | Prop | Type | Default | Description |
119
+ | :--- | :--- | :--- | :--- |
120
+ | `disableFillHandle` | `boolean` | `false` | With `cellSelection` on, the range's bottom-right cell carries a fill handle: dragging it down, up, right or left (one direction; auto-scrolls; touch works) fills the new cells from the range, continuing numbers and dates with an exactly equal step and repeating anything else; Alt at release copies. Ctrl/Cmd+D fills down from the range's top row and Ctrl/Cmd+R right from its left column. Writes through `valueSetter` and `processRowUpdate` once per row, as one undo step; non-editable and synthetic cells are skipped (`'notEditable'`). `true` hides the handle and leaves the keys to the browser. See [Fill Handle](features/fill-handle.md). |
121
+ | `onFill` | `(result: GridFillResult) => void` | — | Fired after every fill, once every row has settled: `{ updated, failed, skipped, direction }`. |
122
+
123
+ ```ts
124
+ type GridFillDirection = 'down' | 'up' | 'right' | 'left';
125
+ interface GridFillResult extends GridBatchEditResult { direction: GridFillDirection }
126
+ interface GridFillValueParams<R> {
127
+ row: R; id: GridRowId; field: string; colDef: GridColDef<R>;
128
+ direction: GridFillDirection;
129
+ sourceValues: unknown[]; // the line's source values in fill order
130
+ index: number; // 0 for the cell next to the source
131
+ value: unknown; // what the grid would write
132
+ copy: boolean; // Alt held, or Ctrl/Cmd+D / Ctrl/Cmd+R
133
+ }
134
+ ```
135
+
136
+ Public CSS: `ogx__cell-fill-handle` (24 × 24 px hit area in the corner cell; the visible square is its `::after`), `--ogx-fill-handle-color` (defaults to `--ogx-range-border`; theme key `grid.fillHandleColor`).
137
+
114
138
  #### Column Visibility
115
139
 
116
140
  | Prop | Type | Default | Description |
@@ -168,7 +192,8 @@ interface GridUndoRedoOptions { limit?: number }
168
192
 
169
193
  | Prop | Type | Default | Description |
170
194
  | :--- | :--- | :--- | :--- |
171
- | `rowGroupingModel` | `GridRowGroupingModel` | `[]` | Array of field names to group rows by (e.g. `['department', 'team']`). See [Row Grouping](#️-row-grouping). |
195
+ | `rowGroupingModel` | `GridRowGroupingModel` | — | Array of field names to group rows by (e.g. `['department', 'team']`). Passing it controls grouping; without it the grid keeps its own model (initially `[]`), which `apiRef.setRowGroupingModel()` and the AI assistant change (v3.5.0+). See [Row Grouping](#️-row-grouping). |
196
+ | `onRowGroupingModelChange` | `(model: GridRowGroupingModel) => void` | — | Fired when the grouping model changes through `apiRef.setRowGroupingModel()` or the AI assistant. With a controlled `rowGroupingModel`, update the prop from it (v3.5.0+). |
172
197
  | `aggregationModel` | `GridAggregationModel` | — | Map of `field → aggFn` (e.g. `{ salary: 'sum', age: 'avg' }`). See [Aggregation Reference](#-aggregation-reference). |
173
198
  | `onAggregationModelChange` | `(model: GridAggregationModel) => void` | — | Fired when the aggregation model changes. |
174
199
  | `getAggregationPosition` | `(groupNode: GridTreeNode \| null) => 'inline' \| 'footer' \| null` | — | Controls where aggregation results appear. Called for every group node (with its current `isExpanded`) and once with `null` for the grand total. `'inline'` (group default) = on the group row, `'footer'` = on a subtotal row after the group's children while it is expanded (on the group row while collapsed), `null` = hidden. For the grand total, `null` hides the footer row. A callback that throws gets the default (`'inline'` for a group, `'footer'` for the grand total; v3.0+). See [the callback](#getaggregationposition-callback). |
@@ -181,6 +206,24 @@ interface GridUndoRedoOptions { limit?: number }
181
206
  | `pivotModel` | `GridPivotModel` | — | Controlled pivot configuration (row fields, column fields, value fields). |
182
207
  | `onPivotModelChange` | `(model: GridPivotModel) => void` | — | Fired when the pivot model changes. |
183
208
 
209
+ #### AI Assistant (v3.5)
210
+
211
+ | Prop | Type | Default | Description |
212
+ | :--- | :--- | :--- | :--- |
213
+ | `aiAssistant` | `GridAiAssistantOptions` | — | The "Ask AI" prompt panel: `{ onPrompt, placeholder?, suggestions?, voice?, parts? }`. The built-in toolbar shows an **Ask AI** button (`localeText.aiAssistantButton`); `apiRef.openAiAssistant()` opens the panel without one; `slots.aiAssistantPanel` replaces it. A reply is applied only when its `state` carries the brand `validateGridAiState` sets (build `onPrompt` with `createGridAiPromptHandler`); it is applied at once as one undoable step, shown as removable chips. See [AI Toolkit](features/ai-toolkit.md#ai-assistant-panel). Grids without it are unchanged. |
214
+ | `onAiAssistantApply` | `(result: GridAiPromptResult) => void` | — | Fired after the assistant applied a reply. |
215
+ | `onAiAssistantError` | `(error: unknown) => void` | — | Fired when `onPrompt` rejects (not on Stop) or returns a result that was not validated. |
216
+
217
+ `GridAiAssistantOptions`:
218
+
219
+ | Option | Type | Default | Description |
220
+ | :--- | :--- | :--- | :--- |
221
+ | `onPrompt` | `(prompt: string, context: GridAiPromptContext) => Promise<GridAiPromptResult>` | — | Answers a prompt. `context` is `{ currentState, history, signal }`: the grid's models (`GridAiState`), earlier prompts of the session (`GridAiHistoryEntry[]`: `{ prompt, applied }`) and an `AbortSignal` aborted by Stop, a new prompt or closing the panel. Resolve with `{ state, errors, message? }` from `validateGridAiState` or `createGridAiPromptHandler`. |
222
+ | `placeholder` | `string` | `'Ask about this table…'` | Placeholder of the prompt input. |
223
+ | `suggestions` | `string[]` | — | Example prompts shown as clickable chips. |
224
+ | `voice` | `boolean` | `true` | Shows a microphone button where the browser has `SpeechRecognition` / `webkitSpeechRecognition`. The microphone is asked for only when the button is clicked; elsewhere the button is hidden. |
225
+ | `parts` | `GridAiPart[]` | every part | The parts of the state the assistant may change. Other parts of a reply are ignored ("Part not allowed"). |
226
+
184
227
  #### Scroll & Viewport
185
228
 
186
229
  | Prop | Type | Default | Description |
@@ -200,7 +243,7 @@ interface GridUndoRedoOptions { limit?: number }
200
243
 
201
244
  | Prop | Type | Default | Description |
202
245
  | :--- | :--- | :--- | :--- |
203
- | `localeText` | `GridLocaleText` | — | Override user-visible strings: the Pagination labels and the empty-state label (`noRowsLabel`, which wins over the `noRowsLabel` prop). 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 and the Ask AI button (`aiAssistantButton`). See [`GridLocaleText`](#gridlocaletext). |
204
247
 
205
248
  #### Imperative Ref
206
249
 
@@ -261,6 +304,7 @@ The types of the `slots` and `slotProps` props (v3.0+ exports). Each slot is typ
261
304
  | `noRowsOverlay` | The empty-state icon and label (shown when there are no rows and the grid is not loading). | `slotProps.noRowsOverlay` only (`GridOverlaySlotProps`). |
262
305
  | `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
306
  | `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`. |
307
+ | `aiAssistantPanel` | The `aiAssistant` panel (v3.5). Rendered while the panel is open, under the toolbar; Escape inside it closes it. | `GridAiAssistantPanelProps`: `status`, `statusText`, `message`, `errors`, `chips` (`GridAiChip[]`: `{ id, part, label }`), `history`, `suggestions`, `placeholder`, `voiceAvailable`, `canUndo`, and the actions `submit(prompt)`, `stop()`, `undo()`, `removeChip(id)`, `close()`; then `slotProps.aiAssistantPanel`. |
264
308
  | `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. |
265
309
 
266
310
  ```ts
@@ -293,6 +337,8 @@ The built-in toolbar component. Mount it via `slots={{ toolbar: GridToolbar }}`.
293
337
  | `onColumnOrderReset` | `() => void` | — | Called when the user clicks "Reset order" in the Columns panel. |
294
338
  | `forceColumnsOpen` | `boolean` | — | Opens the Columns panel when it becomes `true` (set by `DataGrid` for the column menu's **Manage columns**). If the toolbar slot does not render a `GridToolbar` that receives it, the grid opens its standalone Columns panel instead. |
295
339
  | `onColumnsPanelClose` | `() => void` | — | Called whenever the Columns panel closes (its button, a custom button, another panel opening, click-outside or Escape). |
340
+ | `forceFiltersOpen` | `boolean` | — | Opens the filter panel when it becomes `true` (v3.5; set by `DataGrid` for a header filter cell's "Custom filter"). Only a `GridToolbar` that receives it lets that label open the panel. |
341
+ | `onFiltersPanelClose` | `() => void` | — | Called whenever the filter panel closes (v3.5). |
296
342
  | `children` | `ReactNode` | — | Content rendered on the **left** side of the toolbar, before the spacer. |
297
343
  | `rightContent` | `ReactNode` | — | Content rendered on the **right** side, after all built-in buttons. |
298
344
  | `className` | `string` | — | Additional CSS class on the toolbar root `<div>`. Use for visual theme overrides. |
@@ -302,6 +348,9 @@ The built-in toolbar component. Mount it via `slots={{ toolbar: GridToolbar }}`.
302
348
  | `renderAggregationButton` | `(props: ToolbarButtonRenderProps) => ReactNode` | — | Replace the built-in Summaries toggle button. The Aggregation panel still functions normally. |
303
349
  | `renderExportButton` | `() => ReactNode` | — | Inject an Export button after the Aggregation button. No built-in export button exists. |
304
350
  | `renderQuickFilter` | `(props: ToolbarQuickFilterRenderProps) => ReactNode` | — | Replace the built-in `GlobalSearch` input with your own component. |
351
+ | `onAiAssistantToggle` | `(trigger: HTMLElement \| null) => void` | — | Opens or closes the `aiAssistant` panel. The grid injects it when `aiAssistant` is set; its presence renders the **Ask AI** button (sparkle icon), first in the button row. Focus returns to `trigger` when the panel closes (v3.5). |
352
+ | `aiAssistantOpen` | `boolean` | `false` | Whether the panel is open (`aria-expanded` of the button; injected by the grid) (v3.5). |
353
+ | `aiAssistantLabel` | `string` | `'Ask AI'` | The button's label; the grid passes `localeText.aiAssistantButton` (v3.5). |
305
354
 
306
355
  ### `ToolbarButtonRenderProps`
307
356
 
@@ -363,6 +412,13 @@ Access these methods via the `apiRef` prop.
363
412
  | `redo()` | `Promise<GridBatchEditResult>` | Redo the last undone action (v3.4). |
364
413
  | `canUndo()` / `canRedo()` | `boolean` | Whether there is an action to undo / redo (v3.4). |
365
414
  | `clearHistory()` | `void` | Forget every undo and redo action (v3.4). |
415
+ | `setSortModel(model)` | `void` | Replace the sort model. Fires `onSortModelChange` (v3.5). |
416
+ | `setRowGroupingModel(model)` | `void` | Replace the row grouping model. Fires `onRowGroupingModelChange`; with a controlled `rowGroupingModel`, update the prop from it (v3.5). |
417
+ | `setAggregationModel(model)` | `void` | Replace the aggregation model. Fires `onAggregationModelChange` (v3.5). |
418
+ | `setColumnVisibilityModel(model)` | `void` | Replace the column visibility model. Fires `onColumnVisibilityModelChange` (v3.5). |
419
+ | `setPivotModel(model)` | `void` | Replace the pivot model. Fires `onPivotModelChange` (v3.5). |
420
+ | `getGridAiState()` | `GridAiState` | The current `filterModel`, `sortModel`, `rowGroupingModel`, `aggregationModel`, `pivotModel` and `columnVisibilityModel`, as the AI toolkit reads them. Like the other getters it sees a setter's value in the same tick (v3.5). |
421
+ | `openAiAssistant()` / `closeAiAssistant()` | `void` | Open or close the `aiAssistant` panel; `openAiAssistant` does nothing without `aiAssistant` (v3.5). |
366
422
  | `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. |
367
423
 
368
424
  ---
@@ -419,6 +475,7 @@ Defines the behavior and appearance of a single column.
419
475
  | `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. |
420
476
  | `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. |
421
477
  | `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. |
478
+ | `fillValue` | `(params: GridFillValueParams) => unknown` | — | v3.5+. Decides what a fill (handle drag, Ctrl/Cmd+D, Ctrl/Cmd+R) writes into a cell of this column. `params.value` is the grid's choice (series, pattern or copy); return it to keep it. A throw skips the cell (`'invalidValue'`). See [Fill Handle](features/fill-handle.md#gridcoldeffillvalue). |
422
479
  | `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). |
423
480
  | `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+). |
424
481
 
@@ -458,7 +515,9 @@ Span values are floored; `Infinity` means "to the end"; `NaN`, `0` and negative
458
515
  | :--- | :--- | :--- | :--- |
459
516
  | `sortable` | `boolean` | `true` | Enable/disable sorting for this column. |
460
517
  | `sortComparator` | `(v1, v2, params1, params2) => number` | — | Custom ascending comparator for client-side sorting (v3.1.0+). `v1`/`v2` are `valueGetter` values **including `null`/`undefined`**; `params` are `GridSortCellParams` `{ id, field, row, value }`. Negated for `desc`, chained in multi-sort, used for group ordering under row grouping, tree-data siblings and pivot row labels. Throw / `NaN` reads as 0 (dev warning once). Ignored with `sortingMode="server"`. |
461
- | `filterable` | `boolean` | `true` | Enable/disable filtering for this column. `false` also removes it from the quick-filter search. |
518
+ | `filterable` | `boolean` | `true` | Enable/disable filtering for this column. `false` also removes it from the quick-filter search and the header filter row. |
519
+ | `headerFilter` | `boolean` | `true` | `false` leaves this column's cell in the header filter row (`headerFilters`) empty (v3.5). `image` columns never get a control. |
520
+ | `headerFilterOperator` | `GridFilterOperator` | by type | The operator a new header filter starts with (v3.5). Defaults: `contains` (string), `=` (number), `is` (date, boolean, singleSelect). |
462
521
  | `resizable` | `boolean` | `true` | Allow the user to drag-resize this column; double-click the handle (or Enter on it) to auto-size to content (v3.1.0+). |
463
522
  | `hideable` | `boolean` | `true` | Allow the user to hide this column from the UI. `false` removes it from the Columns panel (unless `showNonHideableColumns`) and removes **Hide Column** from its column menu. |
464
523
  | `pinnable` | `boolean` | `true` | Allow this column to be pinned via the UI. `false` removes the pin actions from its column menu. Does not affect drag-reordering (v3.0+). |
@@ -494,14 +553,16 @@ Every exporter takes `columns` typed for your rows (`GridColDef<R>[]`) or untype
494
553
  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
554
 
496
555
  ```ts
497
- import { getGridAiSchema, validateGridAiState } from '@opencorestack/opengridx/ai';
498
- import type { GridAiState, GridAiValidationError } from '@opencorestack/opengridx/ai';
556
+ import { getGridAiSchema, validateGridAiState, createGridAiPromptHandler, createGridAgentTools } from '@opencorestack/opengridx/ai';
557
+ import type { GridAiState, GridAiValidationError, GridAgentTool } from '@opencorestack/opengridx/ai';
499
558
  ```
500
559
 
501
560
  | Function | Return | Description |
502
561
  | :--- | :--- | :--- |
503
562
  | `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. |
563
+ | `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. Since v3.5 the result and its `state` carry the non-enumerable brand `Symbol.for('opengridx.ai.validated')`, which `aiAssistant` requires. |
564
+ | `createGridAiPromptHandler({ columns, callModel, parts?, schemaOptions? })` | `(prompt, context) => Promise<GridAiPromptResult>` | Builds `aiAssistant.onPrompt` (v3.5). Builds the schema once; per prompt calls `callModel({ prompt, schema, currentState, history }, { signal })`, reads the reply (a state object, a JSON string, or a fenced JSON block inside text; a `message` key or the text around the JSON becomes `result.message`; plain text becomes the message with nothing to apply), validates it and returns the branded result. A `callModel` throw or rejection gives `{ state: {}, errors: [{ path: '', message }] }`; an abort rejects with an `AbortError`, also when `callModel` ignores the signal. |
565
+ | `createGridAgentTools(apiRef, columns, options?)` | `GridAgentTool[]` | Plain tools for AI agents (v3.5): `get_grid_state`, `set_filter`, `set_sort`, `set_grouping`, `set_aggregation`, `set_column_visibility` (merged over the current model), `clear_filters`, and `get_row_summary` only with `options.allowRowAccess: true`. Each is `{ name, description, inputSchema, execute(input) }`; inputs are validated with `validateGridAiState` and applied through `apiRef` only when they have no errors. `execute` never throws. A `set_*` tool whose part no column allows is left out. |
505
566
 
506
567
  `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
568
 
@@ -511,7 +572,23 @@ import type { GridAiState, GridAiValidationError } from '@opencorestack/opengrid
511
572
  | `GridAiSchema` | The returned JSON Schema object: `$schema`, `schemaVersion: 1`, `title`, `type: 'object'`, `properties` (one per offered part) and `$defs`. |
512
573
  | `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
574
  | `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[] }` |
575
+ | `GridAiValidationResult` | `{ state: GridAiState; errors: GridAiValidationError[] }` (branded since v3.5). |
576
+ | `GridAiPart` | `'filter' \| 'sort' \| 'grouping' \| 'aggregation' \| 'pivot' \| 'columnVisibility'`. |
577
+ | `GridAiPromptHandlerOptions` | `{ columns, callModel, parts?, schemaOptions? }` of `createGridAiPromptHandler` (v3.5). |
578
+ | `GridAiModelRequest` | What `callModel` receives: `{ prompt, schema, currentState, history }` (v3.5). |
579
+ | `GridAiPromptContext` | `onPrompt`'s second argument: `{ currentState: GridAiState; history: GridAiHistoryEntry[]; signal: AbortSignal }` (v3.5). |
580
+ | `GridAiPromptResult` | `{ state: GridAiState; errors: GridAiValidationError[]; message?: string }` (v3.5). |
581
+ | `GridAiHistoryEntry` | `{ prompt: string; applied: GridAiState }` (v3.5). |
582
+ | `GridAiAssistantOptions` | The `aiAssistant` prop (v3.5). |
583
+ | `GridAiAssistantPanelProps` | Props of `slots.aiAssistantPanel` (v3.5). |
584
+ | `GridAiAssistantStatus` | `'idle' \| 'running' \| 'applied' \| 'stopped' \| 'error'` (v3.5). |
585
+ | `GridAiChip` | `{ id: string; part: GridAiPart; label: string }`: one applied change (v3.5). |
586
+ | `GridAgentTool` | `{ name: string; description: string; inputSchema: GridAiJsonSchema; execute: (input: unknown) => Promise<GridAgentToolResult> }` (v3.5). |
587
+ | `GridAgentToolResult` | `{ ok: boolean; applied?; errors?; state?; columns?; rowCount?; rows? }` (v3.5). |
588
+ | `GridAgentToolsOptions` | `{ allowRowAccess?: boolean; fields?: string[]; maxRows?: number }`: `get_row_summary` returns the row count and at most `maxRows` (default 20) rows, limited to `fields` (default: every column given), values read through `valueGetter` (v3.5). |
589
+ | `GridAgentApi` | The part of `GridApi` the tools use; `apiRef.current` fits it (v3.5). |
590
+
591
+ The state and assistant types (`GridAiState`, `GridAiPart`, `GridAiValidationError`, `GridAiPrompt*`, `GridAiAssistant*`, `GridAiChip`) are declared with the grid's own types since v3.5 and exported from `@opencorestack/opengridx/ai`; they are type-only, so importing them adds nothing to a bundle.
515
592
 
516
593
  ---
517
594
 
@@ -810,6 +887,8 @@ interface GridLocaleText {
810
887
  noRowsLabel?: string;
811
888
  /** v3.3: live-region text after a cell range change. Default "12 cells selected, 3 rows by 4 columns". */
812
889
  cellSelectionAnnouncement?: (cellCount: number, rowCount: number, columnCount: number) => string;
890
+ /** v3.5: label of the toolbar's Ask AI button. Default "Ask AI". */
891
+ aiAssistantButton?: string;
813
892
  }
814
893
  ```
815
894
 
@@ -1183,6 +1262,8 @@ The filter panel shows only the operators relevant to each column's `type`; an o
1183
1262
  | `boolean` | `is` | `is` |
1184
1263
  | `singleSelect` | `isAnyOf` | `isAnyOf`, `is`, `not` |
1185
1264
 
1265
+ The header filter row (`headerFilters`) offers the same operators; it starts `singleSelect` columns with `is` (one value from a select) and any column with its `headerFilterOperator`.
1266
+
1186
1267
  ### Nested Group Example
1187
1268
 
1188
1269
  ```tsx
package/docs/README.md CHANGED
@@ -32,6 +32,7 @@ In-depth guides on the grid's functional capabilities.
32
32
 
33
33
  - **[Virtualization](features/virtualization.md)** - Handling massive datasets
34
34
  - **[Filtering & Search](features/filtering.md)** - Column filters and Global Quick Search
35
+ - **[Header Filters](features/header-filters.md)** - A filter row under the column headers, sharing the filter model
35
36
  - **[Sorting & Pagination](features/sorting-pagination.md)** - Organizing and navigating data
36
37
  - **[Editing & Reordering](features/editing-reordering.md)** - Inline edits and Drag-and-Drop
37
38
  - **[Loading & Performance](features/loading-states.md)** - Skeletons, Lazy, and Infinite loading
@@ -39,6 +40,7 @@ In-depth guides on the grid's functional capabilities.
39
40
  - **[Clipboard](features/clipboard.md)** - Copy rows and ranges, paste from Excel / Sheets, clear a range with Delete
40
41
  - **[Cell Range Selection](features/cell-selection.md)** - Spreadsheet-style cell ranges, copy and a sum / average status bar
41
42
  - **[Undo & Redo](features/undo-redo.md)** - Ctrl+Z / Ctrl+Shift+Z for edits, pastes and range clears
43
+ - **[Fill Handle](features/fill-handle.md)** - Drag the range corner to continue a series or copy, Ctrl+D / Ctrl+R
42
44
  - **[Pinning](features/pinning.md)** - Sticky columns and rows
43
45
  - **[State Persistence](features/state-persistence.md)** - Save/Restore grid configuration
44
46
  - **[Aggregation & Pivot](features/aggregation-pivot.md)** - Data summarization and analytics
@@ -50,7 +52,7 @@ In-depth guides on the grid's functional capabilities.
50
52
  - **[Infinite Scroll](features/infinite-scroll.md)** - Advanced performance loading
51
53
  - **[Data Source](features/data-source.md)** - Server-side fetching and SSR
52
54
  - **[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`)
55
+ - **[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`)
54
56
 
55
57
  ---
56
58
 
@@ -25,6 +25,8 @@ in an effect, so **keep the call order when you move or add a hook**. In particu
25
25
  late (36c); commits reach it through `useGridEditCommitChannel` (12a), whose listener it sets in a layout effect.
26
26
  `useGridClipboardPaste` (36b) and `useGridUndoRedo` listen for keys on the grid root with native listeners, which
27
27
  fire before React's delegated `onKeyDown` of the viewport; they act only on cell targets with no editor open.
28
+ `useGridFillHandle` (36d) does the same for Ctrl/Cmd+D and Ctrl/Cmd+R; its fills commit through the batch edit
29
+ with source `'fill'`, so the history records them like a paste.
28
30
  - `useGridLiveRowSelection` prunes the selection, and `useGridPageCorrection` corrects a page past the
29
31
  end, in effects that run before the later hooks' effects.
30
32
 
@@ -33,7 +35,7 @@ in an effect, so **keep the call order when you move or add a hook**. In particu
33
35
  | # | Hook / util | File | Produces |
34
36
  | :- | :--- | :--- | :--- |
35
37
  | 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) |
36
- | 1 | `useGridControlledState` | `hooks/core/useGridControlledState.ts` | Controlled/uncontrolled sort, filter, aggregation, visibility, pinning, pivot, pagination, selection, density, cell selection (v3.3) |
38
+ | 1 | `useGridControlledState` | `hooks/core/useGridControlledState.ts` | Controlled/uncontrolled sort, filter, aggregation, row grouping (v3.5), visibility, pinning, pivot, pagination, selection, density, cell selection (v3.3) |
37
39
  | 2 | `useGridPivot` | `hooks/features/useGridPivot.ts` | Pivot rows/columns (or the source ones) and the pipeline filter/sort models |
38
40
  | 3 | `resolveGridModes` (pure) | `utils/gridModes.ts` | Which features are in effect: tree data, row grouping, pagination, row reordering, list view, grouping column |
39
41
  | 4 | `useGridThemeDimensions` | `hooks/core/useGridThemeDimensions.ts` | Row and header heights (props → theme provider → defaults) |
@@ -58,7 +60,7 @@ in an effect, so **keep the call order when you move or add a hook**. In particu
58
60
  | 21 | `useGridPageCorrection` | `hooks/core/useGridPageCorrection.ts` | Moves a page left past the end back to the last page |
59
61
  | 22 | `getPaginationRowCount` (pure) | `utils/pagination/index.ts` | What the pager pages through |
60
62
  | 23 | `useGridRowInteractions` | `hooks/core/useGridRowInteractions.ts` | Row selection, `handleRowClick`, `handleSelectionChange` |
61
- | 24 | `useGridApiMethods` | `hooks/core/useGridApiMethods.ts` | State API methods (`getAllFilteredRows` via `utils/gridApiRows.ts`) |
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` |
62
64
  | 25 | `useRowReorder` | `hooks/useRowReorder.ts` | Row drag-reorder |
63
65
  | 26 | `useAggregation` | `hooks/features/useAggregation.ts` | Footer aggregation result |
64
66
  | 27 | `useGridAggregationApi` | `hooks/core/useGridAggregationApi.ts` | `getAggregationResult`, `getAggregationModel`, `getGroupedExportRows` |
@@ -69,13 +71,15 @@ in an effect, so **keep the call order when you move or add a hook**. In particu
69
71
  | 31 | `useGridScrollToIndexesApi` | `hooks/core/useGridScrollToIndexesApi.ts` | `scrollToIndexes` |
70
72
  | 31a | `useGridColumnAutosize` | `hooks/core/useGridColumnAutosize.ts` | `autosizeColumn` / `autosizeColumns` on the API, and the resize handle's auto-size handler (v3.1) |
71
73
  | 32 | `useGridViewport` | `hooks/core/useGridViewport.ts` | Scroll sync, viewport measurement, viewport callback ref |
72
- | 33 | `useGridVirtualization` | `hooks/core/useGridVirtualization.ts` | Render window |
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) |
73
76
  | 34 | `useGridSortHandlers` | `hooks/core/useGridHeaderHandlers.ts` | Header sort / shift-sort |
74
77
  | 35 | `useGridKeyboardNavigation` | `hooks/core/useGridKeyboardNavigation.ts` | Focus and keyboard handling; takes `cellSelection.keyboard` (Shift+navigation, Escape, Ctrl/Cmd+A) |
75
78
  | 36 | `useGridPointerFocusHandlers` | `hooks/core/useGridFocusHandlers.ts` | Cell and header click focus |
76
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) |
77
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` |
78
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 |
79
83
  | 37 | `useGridColumnMenuHandlers` | `hooks/core/useGridHeaderHandlers.ts` | Column menu Hide / Pin (`pinColumnTo` in `utils/pinning`) |
80
84
  | 38 | `useGridAriaRows` | `hooks/core/useGridAriaRows.ts` | `aria-rowcount` and `aria-rowindex` bases |
81
85
  | 39 | `useGridSpanRowWindow` | `hooks/features/useGridSpanRenderWindow.ts` | Render window widened to whole row spans |
@@ -85,15 +89,17 @@ in an effect, so **keep the call order when you move or add a hook**. In particu
85
89
  | 43 | `useColumnGroupReorderGuard` | `hooks/features/useColumnGroupReorderGuard.ts` | Header drag-reorder kept inside column groups |
86
90
  | 44 | `useGridColumnsPanel` | `hooks/core/useGridColumnsPanel.ts` | Columns panel (toolbar or standalone) |
87
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 |
88
93
  | 46 | `useGridToolbarProps` | `hooks/core/useGridToolbarProps.ts` | Props for `slots.toolbar` |
89
94
 
90
95
  ## Rendered components
91
96
 
92
- `GridToolbarSlot`, `GridStandaloneColumnPanel`, `GridListView` (list view) or the viewport
97
+ `GridToolbarSlot`, `GridAiAssistantArea` (the `aiAssistant` panel or `slots.aiAssistantPanel` while open, anchored under the toolbar), `GridStandaloneColumnPanel`, `GridListView` (list view) or the viewport
93
98
  (`Header`, `GridPinnedRows` top, `GridEmptyState`, `GridVirtualRows`, `GridPinnedRows` bottom,
94
99
  `GridAggregationFooter`), `GridCellSelectionStatsArea` (`showCellSelectionStats` with `cellSelection`), then `slots.footer` or `GridPaginationArea`, `GridLiveRegion`,
95
100
  `GridLoadingOverlay` and `GridErrorOverlay`. The props all three row renderers take alike are built
96
101
  once in `DataGrid.tsx` (`rowRenderProps`) and spread into each. `rowRenderProps.cellRange` is the cell range's
97
102
  render rectangle; each row renderer turns it into a per-row `cellRange` (`getRowCellRange`), `null` for rows
98
103
  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.
104
+ range enters or leaves. `rowRenderProps.fillHandle` (v3.5) adds `fillHandleCol` to the one row that draws the fill
105
+ handle (a single selected cell gets an empty column interval, so it draws the handle but no range).
@@ -51,6 +51,7 @@ The panel shows one row per column with `filterable !== false` (the synthetic ro
51
51
  - Typing is debounced by 300 ms before `onFilterModelChange` fires. Opening the panel never emits anything and never rewrites existing values (arrays, numbers and dates stay as they are).
52
52
  - Changing the operator right after typing keeps the typed text.
53
53
  - Each row edits the **first** root-level item for its column and replaces it in place. Filter groups (`GridFilterGroup`) and further items for the same column are passed through untouched; the panel notes how many such conditions it does not show.
54
+ - With `headerFilters` (v3.5) that item is often the header filter row's `id: 'header:<field>'` item. The panel keeps the item's `id` when it edits it, so the header row goes on showing it; see [Header Filters](../features/header-filters.md).
54
55
  - Picking an operator without typing, or clearing the input, leaves an item with an empty value, which does not filter. The row's **×** button removes the item.
55
56
  - If the model is changed from outside the panel (the toolbar's **Clear all**, or your own code) while typing is still pending, the pending text is dropped instead of being re-applied.
56
57
 
@@ -21,8 +21,11 @@ Manages the column headers, sorting triggers, column resizing, and column groupi
21
21
  | `checkboxSelection` | `boolean` | Reserves the checkbox column. The "Select All" checkbox is rendered only when `onSelectAll` is also passed (the grid omits it with `disableMultipleRowSelection`). |
22
22
  | `onSelectAll` | `(isSelected: boolean) => void` | Called by the "Select All" checkbox. |
23
23
  | `allSelected` / `someSelected` | `boolean` | Checked / indeterminate state of the "Select All" checkbox. |
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 />`. |
24
+ | `focusedCell` | `{ id: GridRowId \| null; field: string; headerFilter?: boolean } \| 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); with `headerFilter: true`, its cell in the header filter row (v3.5). 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
+ | `headerFilters` | `boolean` | Renders the header filter row under the column header row (v3.5, see [Header Filters](../features/header-filters.md)). |
27
+ | `filterModel` / `onFilterModelChange` | `GridFilterModel` / `(model) => void` | The model the filter row reads and the callback it reports changes to. |
28
+ | `onOpenFilterPanel` | `() => void` | Called by a filter cell's "Custom filter" button; without it the label is plain text. |
26
29
 
27
30
  ## ↩️ Wrapped Header Text
28
31
 
@@ -56,6 +56,13 @@ The Summaries panel lists each aggregable column with the functions it allows: i
56
56
  | `renderAggregationButton` | `(props: ToolbarButtonRenderProps) => ReactNode` | — | Replace the built-in Summaries button. The Aggregation panel still opens and closes normally. |
57
57
  | `renderExportButton` | `() => ReactNode` | — | Inject an Export button after the Aggregation button. No built-in export button exists — this is the slot for it. |
58
58
  | `renderQuickFilter` | `(props: ToolbarQuickFilterRenderProps) => ReactNode` | — | Replace the built-in quick-filter search input with your own component. |
59
+ | `onAiAssistantToggle` | `(trigger: HTMLElement \| null) => void` | — | Opens or closes the `aiAssistant` panel. The grid injects it when the `aiAssistant` prop is set; its presence renders the **Ask AI** button (sparkle icon), first in the button row. Focus returns to `trigger` when the panel closes (v3.5). |
60
+ | `aiAssistantOpen` | `boolean` | `false` | Whether the AI panel is open: the button's `aria-expanded` (injected by the grid, v3.5). |
61
+ | `aiAssistantLabel` | `string` | `'Ask AI'` | The Ask AI button's label; the grid passes `localeText.aiAssistantButton` (v3.5). |
62
+
63
+ ### Ask AI button (v3.5)
64
+
65
+ With the grid's `aiAssistant` prop set, `GridToolbar` shows an **Ask AI** button that opens the prompt panel under the toolbar (a `role="dialog"` labelled "Ask AI"; Escape closes it and focus returns to the button). Opening it closes any open toolbar panel. A custom toolbar can render its own button: call `onAiAssistantToggle(buttonElement)` from the props the grid passes, or `apiRef.current.openAiAssistant()`. See [AI Toolkit](../features/ai-toolkit.md#ai-assistant-panel).
59
66
 
60
67
  ---
61
68
 
@@ -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, rangeBackground, rangeBorder, …
86
+ grid?: GridThemeGrid; // surfaces, heights (px), cellPaddingX/Y, cellFontSize, headerFontSize, rangeBackground, rangeBorder, fillHandleColor, …
87
87
  toolbar?: GridThemeToolbar;
88
88
  overlays?: GridThemeOverlays;
89
89
  scrollbar?: GridThemeScrollbar;
@@ -187,6 +187,7 @@ All sub-theme types (`GridThemeColors`, `GridThemeGrayScale`, `GridThemeTypograp
187
187
  |---|---|---|
188
188
  | `--ogx-row-height` | `52px` | Row height. The grid sets it inline on `.ogx` from the `rowHeight` / `density` props (or the provider's `grid.rowHeight*`); read it in custom CSS, don't set it. |
189
189
  | `--ogx-header-height` | `56px` | Column header row height; set inline by the grid like `--ogx-row-height`. |
190
+ | `--ogx-header-filter-height` | `40px` | Header filter row height (`headerFilters`, v3.5); set inline by the grid from `headerFilterHeight`. The row's colours come from the header variables, so themes need no new keys. |
190
191
  | `--ogx-grid-row-height-compact` | `32px` | Informational copy of the theme's `grid.rowHeightCompact`; the grid reads the theme value, not this variable |
191
192
  | `--ogx-grid-row-height-standard` | `52px` | Informational copy of `grid.rowHeightStandard` |
192
193
  | `--ogx-grid-row-height-comfortable` | `72px` | Informational copy of `grid.rowHeightComfortable` |
@@ -215,6 +216,7 @@ All sub-theme types (`GridThemeColors`, `GridThemeGrayScale`, `GridThemeTypograp
215
216
  | `--ogx-grid-cell-focus-border` | `#3b82f6` | `#60a5fa` | Keyboard focus ring |
216
217
  | `--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
218
  | `--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` |
219
+ | `--ogx-fill-handle-color` | `var(--ogx-range-border)` | `var(--ogx-range-border)` | The fill handle square (`ogx__cell-fill-handle`, v3.5). Unset in the stylesheet, where it falls back to `--ogx-range-border`; every theme of `DataGridThemeProvider` sets it to `var(--ogx-range-border)`. Theme key `grid.fillHandleColor` |
218
220
  | `--ogx-grid-pinned-left-shadow` | `4px 0 24px...` | (darker) | Shadow on left-pinned columns |
219
221
  | `--ogx-grid-pinned-right-shadow` | `-4px 0 24px...` | (darker) | Shadow on right-pinned columns |
220
222
  | `--ogx-checkbox-bg` | — | `#1e293b` | Checkbox background (dark only) |
@@ -1,11 +1,13 @@
1
- # AI Toolkit: Grid Schema & Validator
1
+ # AI Toolkit: Schema, Validator, Assistant & Agent Tools
2
2
 
3
- *Since v3.4.0* · Demo: [AI: Grid Schema & Validator](https://opencorestack.github.io/OpenGridX/ai-schema)
3
+ *Since v3.4.0 (schema and validator); v3.5.0 (assistant panel, prompt handler, agent tools)* · Demos: [AI: Grid Schema & Validator](https://opencorestack.github.io/OpenGridX/ai-schema), [AI Assistant](https://opencorestack.github.io/OpenGridX/ai-assistant)
4
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:
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 pieces the grid can own:
6
6
 
7
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
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
+ - **`createGridAiPromptHandler(...)`** (v3.5) connects those two to your model for the grid's [**Ask AI** panel](#ai-assistant-panel) (`aiAssistant` prop).
10
+ - **`createGridAgentTools(apiRef, columns)`** (v3.5) gives [AI agents](#agent-tools) plain tools to read and change the grid.
9
11
 
10
12
  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
13
 
@@ -30,14 +32,15 @@ async function ask(prompt: string) {
30
32
  }
31
33
  ```
32
34
 
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.
35
+ The entry point has no React import and no dependencies (about 6.7 kB gzipped with the v3.5 additions; the budget is 8 kB). 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
36
 
35
37
  ## Privacy rules
36
38
 
37
39
  - **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
40
  - **`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
41
  - **`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.
42
+ - **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. The `aiAssistant` panel enforces this: it applies only a validated (branded) state, shows every change as a chip and offers Undo.
43
+ - **Rows stay in the app unless you opt in.** The assistant sends the prompt, the schema, the current models and the session's earlier prompts, never rows. Of the agent tools, only `get_row_summary` reads rows, and it exists only with `allowRowAccess: true`.
41
44
  - **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
45
 
43
46
  ## `getGridAiSchema(columns, options?)`
@@ -113,6 +116,97 @@ Returns `{ state, errors }`, where `state` is a `GridAiState` (`filterModel`, `s
113
116
 
114
117
  Applying an empty part is meaningful: `{ sortModel: [] }` clears the sort. Only apply the parts present in `state`.
115
118
 
119
+ ## AI assistant panel
120
+
121
+ *Since v3.5.0.* The `aiAssistant` prop adds an **Ask AI** panel: users type (or dictate) what they want to see, your model answers, and the grid applies the validated reply at once, as one step they can undo.
122
+
123
+ ```tsx
124
+ import { DataGrid, GridToolbar } from '@opencorestack/opengridx';
125
+ import { createGridAiPromptHandler } from '@opencorestack/opengridx/ai';
126
+
127
+ const onPrompt = createGridAiPromptHandler({
128
+ columns,
129
+ // Your endpoint calls your model with request.schema as the structured-output format.
130
+ callModel: async (request, { signal }) => {
131
+ const res = await fetch('/api/grid-ai', { method: 'POST', body: JSON.stringify(request), signal });
132
+ return res.json();
133
+ },
134
+ });
135
+
136
+ <DataGrid
137
+ rows={rows}
138
+ columns={columns}
139
+ slots={{ toolbar: GridToolbar }}
140
+ aiAssistant={{ onPrompt, suggestions: ['Top 10 by revenue', 'Group by region'] }}
141
+ />;
142
+ ```
143
+
144
+ - **Opening it.** The built-in toolbar shows an **Ask AI** button (sparkle icon; label `localeText.aiAssistantButton`). Without the toolbar, call `apiRef.current.openAiAssistant()`. `slots.aiAssistantPanel` replaces the panel; it receives `GridAiAssistantPanelProps`.
145
+ - **The panel.** A prompt input (Enter sends), the `suggestions` as chips, a microphone button where the browser has `SpeechRecognition` (hidden elsewhere; the microphone is asked for only on click; `voice: false` turns it off), **Stop** while a request runs (aborts `context.signal`), the reply's `message`, and the session's earlier prompts.
146
+ - **Applying.** The reply is applied immediately as one undoable step. Each change shows as a removable chip: a filter item, the quick-filter words, a sort key, a grouping field, an aggregation, a column shown or hidden, the pivot. Removing a chip re-applies the reply without it (a part with no chip left returns to its value from before the reply). **Undo** restores the state from before the reply. This view-state undo is separate from the cell-edit undo history (`undoRedo`).
147
+ - **What is applied.** Each part in `result.state` replaces the grid's model, except column visibility, which is merged over the current model (`{ tax: false }` hides one column and leaves the others). Parts outside `aiAssistant.parts` are ignored. A part the validator emptied (every entry dropped, such as a sort on an unknown field) is not applied, so it cannot clear the grid's model by accident. Changes go through the same handlers as the UI, so `onSortModelChange`, `onRowGroupingModelChange` and the rest fire; with a controlled model, update your state from them.
148
+ - **What was dropped** is listed under the chips, such as `Ignored: Unknown field "revnue"`.
149
+ - **Accessibility.** The panel is a `role="dialog"` labelled "Ask AI". Focus moves to the input when it opens and back to the toolbar button (or wherever it was) when it closes. Escape closes it. The status ("Thinking…", "Applied 3 changes", errors) is announced through the grid's live region.
150
+ - **Events.** `onAiAssistantApply(result)` after a reply was applied; `onAiAssistantError(error)` when `onPrompt` rejects (Stop does not count) or its result was not validated.
151
+
152
+ ### The brand rule
153
+
154
+ The grid applies a result only when its `state` carries the brand `Symbol.for('opengridx.ai.validated')`. `validateGridAiState` sets it (non-enumerable, on the result and on its `state`), and `createGridAiPromptHandler` returns such a result. An unbranded result is not applied: the panel shows "The reply was not validated" and a development warning says why. This makes "validate before applying" a rule the grid enforces rather than advice.
155
+
156
+ Writing `onPrompt` yourself:
157
+
158
+ ```ts
159
+ import { validateGridAiState } from '@opencorestack/opengridx/ai';
160
+
161
+ const onPrompt = async (prompt, { currentState, history, signal }) => {
162
+ const reply = await myModel({ prompt, currentState, history, signal });
163
+ const result = validateGridAiState(reply.json, columns);
164
+ return { ...result, message: reply.text }; // spreading keeps the branded state
165
+ };
166
+ ```
167
+
168
+ A copied state (`{ ...result.state }`) or a hand-built one is not branded.
169
+
170
+ ### `createGridAiPromptHandler({ columns, callModel, parts?, schemaOptions? })`
171
+
172
+ Returns the `onPrompt` function. It builds the schema once (from `columns`, limited to `parts` and `schemaOptions`), then for each prompt:
173
+
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;
176
+ 3. validates it with `validateGridAiState` (same `parts`) and returns the branded `{ state, errors, message? }`.
177
+
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.
179
+
180
+ ## Agent tools
181
+
182
+ *Since v3.5.0.* `createGridAgentTools(apiRef, columns, options?)` returns plain tool objects, `{ name, description, inputSchema, execute }`, for an AI agent to read and change the grid. They have no SDK dependency, so they work with the Vercel AI SDK, CopilotKit, OpenAI or Anthropic tool calling, and WebMCP (`navigator.modelContext.registerTool`).
183
+
184
+ ```ts
185
+ import { createGridAgentTools } from '@opencorestack/opengridx/ai';
186
+
187
+ const tools = createGridAgentTools(apiRef, columns);
188
+ // Anthropic / OpenAI style: describe them to the model …
189
+ const toolSpecs = tools.map(({ name, description, inputSchema }) => ({ name, description, input_schema: inputSchema }));
190
+ // … and run the call the model asks for.
191
+ const result = await tools.find((t) => t.name === call.name)?.execute(call.input);
192
+ ```
193
+
194
+ | Tool | Input | Does |
195
+ | :--- | :--- | :--- |
196
+ | `get_grid_state` | `{}` | Returns `{ ok, state, columns }`: the current models (`apiRef.getGridAiState()`) and `{ field, headerName, type }` per column |
197
+ | `set_filter` | `{ filterModel }` | Replaces the filter model |
198
+ | `set_sort` | `{ sortModel }` | Replaces the sort model |
199
+ | `set_grouping` | `{ rowGroupingModel }` | Replaces the row grouping |
200
+ | `set_aggregation` | `{ aggregationModel }` | Replaces the summaries |
201
+ | `set_column_visibility` | `{ columnVisibilityModel }` | Shows or hides the listed columns; the others keep their visibility |
202
+ | `clear_filters` | `{}` | Removes every filter and the quick-filter words |
203
+ | `get_row_summary` | `{ limit? }` | **Only with `allowRowAccess: true`.** Returns `{ ok, rowCount, rows }`: the number of rows that pass the filter and the first `maxRows` (default 20) of them, limited to `options.fields`, values read through `valueGetter` (JSON-safe: dates as ISO strings, objects as `null`) |
204
+
205
+ - Each `set_*` tool's `inputSchema` is the part's schema from `getGridAiSchema`; a tool whose part no column allows is left out.
206
+ - Inputs are checked with `validateGridAiState`. A call with any error applies **nothing** and returns `{ ok: false, errors }`, so the agent can correct it; a valid call returns `{ ok: true, applied }`.
207
+ - `execute` never throws or rejects: a grid that is not mounted, a throwing change callback or any input gives `{ ok: false, errors }`.
208
+ - The tools read `apiRef.current` only when they run, so they can be created before the grid mounts.
209
+
116
210
  ## Related
117
211
 
118
212
  - [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.
@@ -115,6 +115,8 @@ function MyStats({ count, sum }: GridCellSelectionStatsSlotProps) {
115
115
  | `ogx--range-dragging` | On the grid root during a pointer drag (`user-select: none`). |
116
116
  | `--ogx-range-background` | Range tint. Theme key `grid.rangeBackground`. |
117
117
  | `--ogx-range-border` | Range outline. Theme key `grid.rangeBorder`. |
118
+ | `ogx__cell-fill-handle` | The fill handle on the range's bottom-right cell (v3.5, see [Fill Handle](fill-handle.md)). |
119
+ | `--ogx-fill-handle-color` | Fill handle colour; defaults to the range outline. Theme key `grid.fillHandleColor` (v3.5). |
118
120
 
119
121
  All five built-in themes define both variables. A custom `DataGridThemeProvider` theme derives them from `colors.primary` unless it sets them.
120
122
 
@@ -124,8 +126,12 @@ All five built-in themes define both variables. A custom `DataGridThemeProvider`
124
126
  - DOM focus stays on the anchor. The grid stays a single Tab stop.
125
127
  - After each change the polite live region announces the size, for example "12 cells selected, 3 rows by 4 columns", debounced to 300 ms. Translate it with `localeText.cellSelectionAnnouncement(cells, rows, columns)`.
126
128
 
129
+ ## Fill handle (v3.5)
130
+
131
+ The range's bottom-right cell carries a fill handle: drag it to continue a series or repeat the range into the next cells, or press Ctrl/Cmd+D / Ctrl/Cmd+R. See [Fill Handle](fill-handle.md); `disableFillHandle` turns it off.
132
+
127
133
  ## Not in 3.3
128
134
 
129
- Paste, Delete-to-clear and undo (planned for 3.4), the fill handle, several ranges at once (Ctrl+drag), touch selection (on touch a drag scrolls) and ranges across pages.
135
+ Paste, Delete-to-clear and undo (added in 3.4), the fill handle (added in 3.5), several ranges at once (Ctrl+drag), touch selection (on touch a drag scrolls; the fill handle accepts touch drags) and ranges across pages.
130
136
 
131
- See also: [Clipboard](clipboard.md), [Keyboard Navigation](keyboard-navigation.md), [Theming](../customization/theming.md).
137
+ See also: [Fill Handle](fill-handle.md), [Clipboard](clipboard.md), [Undo & Redo](undo-redo.md), [Keyboard Navigation](keyboard-navigation.md), [Theming](../customization/theming.md).