@opencorestack/opengridx 3.3.0 → 3.5.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.
- package/CHANGELOG.md +64 -0
- package/README.md +10 -1
- package/dist/ai.cjs +2 -0
- package/dist/ai.cjs.map +1 -0
- package/dist/ai.d.ts +366 -0
- package/dist/ai.es.js +2 -0
- package/dist/ai.es.js.map +1 -0
- package/dist/index.d.ts +396 -6
- package/dist/opengridx.css +1 -1
- package/dist/opengridx.es.js +8029 -6090
- package/dist/opengridx.es.js.map +1 -1
- package/dist/opengridx.umd.js +16 -14
- package/dist/opengridx.umd.js.map +1 -1
- package/docs/API_REFERENCE.md +182 -4
- package/docs/README.md +5 -1
- package/docs/architecture/datagrid-orchestration.md +19 -5
- package/docs/components/filter-panel.md +1 -0
- package/docs/components/header.md +4 -1
- package/docs/components/toolbar.md +7 -0
- package/docs/customization/theming.md +12 -1
- package/docs/features/ai-toolkit.md +213 -0
- package/docs/features/cell-selection.md +8 -2
- package/docs/features/clipboard.md +88 -0
- package/docs/features/editing-reordering.md +50 -0
- package/docs/features/fill-handle.md +125 -0
- package/docs/features/filtering.md +3 -0
- package/docs/features/header-filters.md +138 -0
- package/docs/features/keyboard-navigation.md +31 -2
- package/docs/features/undo-redo.md +79 -0
- package/docs/roadmap.md +3 -4
- package/lib/ai/agentTools.ts +160 -0
- package/lib/ai/coerce.ts +90 -0
- package/lib/ai/columns.ts +90 -0
- package/lib/ai/index.ts +33 -0
- package/lib/ai/promptHandler.ts +109 -0
- package/lib/ai/schema.ts +214 -0
- package/lib/ai/types.ts +152 -0
- package/lib/ai/validate.ts +296 -0
- package/lib/components/AiAssistant/GridAiAssistantArea.tsx +26 -0
- package/lib/components/AiAssistant/GridAiAssistantPanel.css +171 -0
- package/lib/components/AiAssistant/GridAiAssistantPanel.tsx +145 -0
- package/lib/components/AiAssistant/useSpeechInput.ts +74 -0
- package/lib/components/Cell/Cell.css +33 -0
- package/lib/components/Cell/Cell.tsx +6 -2
- package/lib/components/Cell/GridEditInputCell.css +12 -4
- package/lib/components/Cell/GridEditInputCell.tsx +19 -12
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.css +14 -0
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +3 -2
- package/lib/components/DataGrid/DataGrid.tsx +138 -5
- package/lib/components/DataGrid/GridLiveRegion.tsx +5 -2
- package/lib/components/DataGrid/GridPinnedRows.tsx +5 -1
- package/lib/components/DataGrid/GridVirtualRows.tsx +5 -1
- package/lib/components/FilterPanel/FilterPanel.css +14 -0
- package/lib/components/FilterPanel/FilterPanel.tsx +5 -11
- package/lib/components/Header/Header.tsx +44 -23
- package/lib/components/Header/headerColumnStyle.ts +28 -0
- package/lib/components/HeaderFilter/HeaderFilter.css +160 -0
- package/lib/components/HeaderFilter/HeaderFilterCell.tsx +370 -0
- package/lib/components/HeaderFilter/HeaderFilterOperatorMenu.tsx +124 -0
- package/lib/components/HeaderFilter/HeaderFilterRow.tsx +157 -0
- package/lib/components/ListView/ListView.css +2 -4
- package/lib/components/ListView/ListViewRow.tsx +2 -2
- package/lib/components/Toolbar/GlobalSearch.css +16 -0
- package/lib/components/Toolbar/GlobalSearch.tsx +3 -2
- package/lib/components/Toolbar/GridToolbar.tsx +51 -0
- package/lib/components/ui/Checkbox.tsx +16 -5
- package/lib/components/ui/Input.css +43 -0
- package/lib/components/ui/Input.tsx +24 -5
- package/lib/hooks/core/gridApiPlaceholder.ts +16 -1
- package/lib/hooks/core/gridToolbarHostContext.ts +5 -0
- package/lib/hooks/core/useDataGrid.ts +27 -0
- package/lib/hooks/core/useGridApiMethods.ts +39 -0
- package/lib/hooks/core/useGridAriaRows.ts +5 -3
- package/lib/hooks/core/useGridCellSelection.ts +107 -13
- package/lib/hooks/core/useGridClipboardPaste.ts +283 -0
- package/lib/hooks/core/useGridColumnsPanel.ts +24 -1
- package/lib/hooks/core/useGridControlledState.ts +27 -0
- package/lib/hooks/core/useGridFillHandle.ts +323 -0
- package/lib/hooks/core/useGridKeyboardNavigation.ts +29 -10
- package/lib/hooks/core/useGridToolbarProps.ts +19 -0
- package/lib/hooks/core/useGridUndoRedo.ts +220 -0
- package/lib/hooks/features/useGridAiAssistant.ts +284 -0
- package/lib/hooks/features/useGridBatchEdit.ts +217 -0
- package/lib/hooks/features/useGridEditing.ts +24 -45
- package/lib/index.ts +10 -0
- package/lib/styles/opengridx.css +4 -1
- package/lib/theme/DataGridThemeProvider.tsx +2 -0
- package/lib/theme/types.ts +2 -0
- package/lib/types/index.ts +343 -1
- package/lib/utils/aiAssistant.ts +205 -0
- package/lib/utils/aiBrand.ts +17 -0
- package/lib/utils/cellSelection.ts +25 -5
- package/lib/utils/editing/commit.ts +69 -0
- package/lib/utils/editing/fill.ts +238 -0
- package/lib/utils/editing/targets.ts +109 -0
- package/lib/utils/filtering/index.ts +14 -0
- package/lib/utils/focus/index.ts +9 -3
- package/lib/utils/headerFilters.ts +189 -0
- package/lib/utils/parsing.ts +252 -0
- package/llms.txt +97 -3
- package/package.json +8 -2
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# AI Toolkit: Schema, Validator, Assistant & Agent Tools
|
|
2
|
+
|
|
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
|
+
|
|
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
|
+
|
|
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
|
+
- **`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.
|
|
11
|
+
|
|
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.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { getGridAiSchema, validateGridAiState } from '@opencorestack/opengridx/ai';
|
|
16
|
+
|
|
17
|
+
const schema = getGridAiSchema(columns, { include: ['filter', 'sort', 'grouping', 'aggregation'] });
|
|
18
|
+
|
|
19
|
+
async function ask(prompt: string) {
|
|
20
|
+
// Your own endpoint calls your own model, with `schema` as the structured-output format.
|
|
21
|
+
const res = await fetch('/api/grid-assistant', {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: { 'Content-Type': 'application/json' },
|
|
24
|
+
body: JSON.stringify({ prompt, schema, currentState: { filterModel, sortModel } }),
|
|
25
|
+
});
|
|
26
|
+
const { state, errors } = validateGridAiState(await res.text(), columns);
|
|
27
|
+
if (errors.length) console.info('Dropped from the reply:', errors);
|
|
28
|
+
if (state.filterModel) setFilterModel(state.filterModel);
|
|
29
|
+
if (state.sortModel) setSortModel(state.sortModel);
|
|
30
|
+
if (state.rowGroupingModel) setRowGroupingModel(state.rowGroupingModel);
|
|
31
|
+
if (state.aggregationModel) setAggregationModel(state.aggregationModel);
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
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.
|
|
36
|
+
|
|
37
|
+
## Privacy rules
|
|
38
|
+
|
|
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.
|
|
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.
|
|
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.
|
|
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`.
|
|
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.
|
|
45
|
+
|
|
46
|
+
## `getGridAiSchema(columns, options?)`
|
|
47
|
+
|
|
48
|
+
Returns a plain JSON object (no functions), deterministic for the same columns and options, so you can memoise it with the columns.
|
|
49
|
+
|
|
50
|
+
| Part (state key) | Option name | Shape | Taken from the columns |
|
|
51
|
+
| :--- | :--- | :--- | :--- |
|
|
52
|
+
| `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`) |
|
|
53
|
+
| `sortModel` | `'sort'` | `GridSortItem[]` | Fields with `sortable !== false` |
|
|
54
|
+
| `rowGroupingModel` | `'grouping'` | `string[]` | Fields with `groupable !== false` |
|
|
55
|
+
| `aggregationModel` | `'aggregation'` | field → function | `type: 'number'` (or `aggregable: true`) columns get every built-in function, other columns `count`; never `aggregable: false`; limited by `availableAggregationFunctions` |
|
|
56
|
+
| `pivotModel` | `'pivot'` | `GridPivotModel` | Groupable fields as row and column labels; value fields as for aggregation (`sum`, `avg`, `count`, `min`, `max`) |
|
|
57
|
+
| `columnVisibilityModel` | `'columnVisibility'` | field → boolean | Fields with `hideable !== false` |
|
|
58
|
+
|
|
59
|
+
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.
|
|
60
|
+
|
|
61
|
+
| Option | Default | Description |
|
|
62
|
+
| :--- | :--- | :--- |
|
|
63
|
+
| `include` | every part | Parts to offer: `'filter' \| 'sort' \| 'grouping' \| 'aggregation' \| 'pivot' \| 'columnVisibility'` |
|
|
64
|
+
| `exclude` | none | Parts to leave out; wins over `include` |
|
|
65
|
+
| `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`) |
|
|
66
|
+
| `examples` | `true` | Include the columns' `aiExamples`. Columns without `aiExamples` never send example values |
|
|
67
|
+
|
|
68
|
+
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.
|
|
69
|
+
|
|
70
|
+
Example for two columns:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
getGridAiSchema(
|
|
74
|
+
[
|
|
75
|
+
{ field: 'amt_usd', headerName: 'Revenue', type: 'number' },
|
|
76
|
+
{ field: 'status', headerName: 'Status', type: 'singleSelect', valueOptions: ['Open', 'Paid'], groupable: false },
|
|
77
|
+
],
|
|
78
|
+
{ include: ['filter', 'sort'], descriptions: false },
|
|
79
|
+
);
|
|
80
|
+
// {
|
|
81
|
+
// $schema: 'https://json-schema.org/draft/2020-12/schema', schemaVersion: 1, title: 'OpenGridX grid state',
|
|
82
|
+
// type: 'object', additionalProperties: false,
|
|
83
|
+
// properties: {
|
|
84
|
+
// filterModel: { type: 'object', additionalProperties: false, properties: {
|
|
85
|
+
// items: { type: 'array', items: { anyOf: [{ $ref: '#/$defs/filterItem' }, { $ref: '#/$defs/filterGroup1' }] } },
|
|
86
|
+
// logicOperator: { enum: ['and', 'or'] },
|
|
87
|
+
// quickFilterValues: { type: 'array', items: { type: 'string' } } } },
|
|
88
|
+
// sortModel: { type: 'array', items: { type: 'object', additionalProperties: false, required: ['field', 'sort'],
|
|
89
|
+
// properties: { field: { enum: ['amt_usd', 'status'] }, sort: { enum: ['asc', 'desc'] } } } },
|
|
90
|
+
// },
|
|
91
|
+
// $defs: {
|
|
92
|
+
// filterItem: { oneOf: [
|
|
93
|
+
// { type: 'object', required: ['field', 'operator'], additionalProperties: false, properties: {
|
|
94
|
+
// field: { const: 'amt_usd' }, operator: { enum: ['=', '!=', '>', '>=', '<', '<=', 'isEmpty', 'isNotEmpty'] },
|
|
95
|
+
// value: { type: 'number' } } },
|
|
96
|
+
// { type: 'object', required: ['field', 'operator'], additionalProperties: false, properties: {
|
|
97
|
+
// field: { const: 'status' }, operator: { enum: ['isAnyOf', 'is', 'not'] },
|
|
98
|
+
// value: { anyOf: [{ enum: ['Open', 'Paid'] }, { type: 'array', items: { enum: ['Open', 'Paid'] } }] } } },
|
|
99
|
+
// ] },
|
|
100
|
+
// filterGroup1: { … items: filterItem | filterGroup2 }, filterGroup2: { … }, filterGroup3: { … items: filterItem },
|
|
101
|
+
// },
|
|
102
|
+
// }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## `validateGridAiState(json, columns, options?)`
|
|
106
|
+
|
|
107
|
+
Returns `{ state, errors }`, where `state` is a `GridAiState` (`filterModel`, `sortModel`, `rowGroupingModel`, `aggregationModel`, `pivotModel`, `columnVisibilityModel`, each present only when the reply contained it) and `errors` is a list of `{ path, message }`, such as `{ path: 'filterModel.items[2].field', message: 'Unknown field "revenue"' }`.
|
|
108
|
+
|
|
109
|
+
- `json` may be an object or a JSON string. Bad JSON gives one error and an empty state.
|
|
110
|
+
- Unknown parts, unknown fields, fields a column does not allow (`filterable: false` …), operators that do not fit the column type, and functions or enum values outside the allowed list are **dropped**, each with an error. Parts outside `options.include` / `options.exclude` are dropped too.
|
|
111
|
+
- 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.
|
|
112
|
+
- `asc` / `desc`, `and` / `or`, operators and function names are matched case-insensitively and written in their canonical spelling.
|
|
113
|
+
- A filter group left empty is removed; groups nested deeper than 3 levels are dropped.
|
|
114
|
+
- Lists are read up to 200 entries and at most 100 errors are listed, so a runaway reply stays cheap.
|
|
115
|
+
- 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`.
|
|
116
|
+
|
|
117
|
+
Applying an empty part is meaningful: `{ sortModel: [] }` clears the sort. Only apply the parts present in `state`.
|
|
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
|
+
|
|
210
|
+
## Related
|
|
211
|
+
|
|
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.
|
|
213
|
+
- [API Reference: AI Toolkit](../API_REFERENCE.md)
|
|
@@ -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 (
|
|
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).
|
|
@@ -101,3 +101,91 @@ ID Name Email Status
|
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
When pasted into Excel or Google Sheets, this content will automatically be distributed into the correct cells.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 📥 Paste from Excel and Google Sheets (v3.4)
|
|
108
|
+
|
|
109
|
+
With `cellSelection` on, **Ctrl/Cmd+V** pastes tab-separated text into the grid's **editable** cells. Each value is parsed for its column and saved like an edit: `valueSetter`, then `processRowUpdate` **once per row**, then the grid stores every row that succeeded in one update. Demo: **Paste from Excel** (`/clipboard-paste`).
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
<DataGrid
|
|
113
|
+
rows={rows}
|
|
114
|
+
columns={columns} // columns with editable: true
|
|
115
|
+
cellSelection
|
|
116
|
+
processRowUpdate={saveRow}
|
|
117
|
+
onClipboardPaste={({ updated, skipped, failed, text }) =>
|
|
118
|
+
toast(`${updated.length} rows updated, ${skipped.length} cells skipped`)}
|
|
119
|
+
/>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- Paste works while focus is in the grid and no editor is open. In an open editor, Ctrl/Cmd+V is the input's own paste.
|
|
123
|
+
- The grid reads the `paste` event (`text/plain`), so the browser asks for no clipboard permission.
|
|
124
|
+
- `disableClipboardPaste` turns the shortcut off. `apiRef.current.pasteText(text, anchor?)` still works, for a toolbar "Paste" button that reads the clipboard itself.
|
|
125
|
+
|
|
126
|
+
### Where values land
|
|
127
|
+
|
|
128
|
+
| Clipboard | Selection | Result |
|
|
129
|
+
| :--- | :--- | :--- |
|
|
130
|
+
| One value | One cell | That cell |
|
|
131
|
+
| One value | A range | Every cell of the range (Excel's "fill selection") |
|
|
132
|
+
| A block | One cell | The block, from that cell to the right and down |
|
|
133
|
+
| A block | A range | The block from the range's top-left cell; cells past the range, the last row or the last column are dropped |
|
|
134
|
+
|
|
135
|
+
No rows are created. Only displayed rows are reached: rows on other pages and inside collapsed groups are not. After the paste the pasted area becomes the selection, and one Ctrl/Cmd+Z undoes all of it (with `undoRedo`).
|
|
136
|
+
|
|
137
|
+
### How text is parsed
|
|
138
|
+
|
|
139
|
+
The clipboard is read as Excel and Sheets write it: tabs between columns, `\r\n` or `\n` between rows, quoted fields that may hold tabs, line breaks and doubled quotes. A trailing empty line is ignored. Each cell is then parsed for its column:
|
|
140
|
+
|
|
141
|
+
| Column type | Accepted | Otherwise |
|
|
142
|
+
| :--- | :--- | :--- |
|
|
143
|
+
| `string` (default), `image` | The text as is (an `image` column takes the URL) | — |
|
|
144
|
+
| `number` | `1234.5`, `1,234.5`, `-12`, `(12)`, `12%` (→ 0.12), `1e3`; currency symbols and spaces stripped | skipped, `'invalidValue'` |
|
|
145
|
+
| `date` | ISO `2026-10-07` (that local day), and what `Date.parse` accepts. The value keeps the kind the cell holds: a `Date`, a timestamp, or a `YYYY-MM-DD` string | skipped |
|
|
146
|
+
| `boolean` | `true`/`false`, `yes`/`no` (the grid's `Yes`/`No` labels), `1`/`0`, any case | skipped |
|
|
147
|
+
| `singleSelect` | An option value or an option label, any case; the option's value is stored | skipped |
|
|
148
|
+
| An empty cell | `null` for number, date, boolean and singleSelect; `''` for string | — |
|
|
149
|
+
|
|
150
|
+
`1,5` is not read as a number: in a decimal-comma locale use a `valueParser`.
|
|
151
|
+
|
|
152
|
+
### `valueParser`
|
|
153
|
+
|
|
154
|
+
`GridColDef.valueParser(text, { row, field, colDef })` replaces the parser for one column. Throw (or return `undefined`) to reject the text: the cell is skipped with reason `'invalidValue'`.
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
{
|
|
158
|
+
field: 'price',
|
|
159
|
+
type: 'number',
|
|
160
|
+
editable: true,
|
|
161
|
+
// "1.234,5" → 1234.5
|
|
162
|
+
valueParser: (text) => {
|
|
163
|
+
const n = Number(text.replace(/[\s€]/g, '').split('.').join('').replace(',', '.'));
|
|
164
|
+
if (Number.isNaN(n)) throw new Error('not a number');
|
|
165
|
+
return n;
|
|
166
|
+
},
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### What is skipped
|
|
171
|
+
|
|
172
|
+
`result.skipped` lists every cell that was not written, with a reason:
|
|
173
|
+
|
|
174
|
+
| Reason | When |
|
|
175
|
+
| :--- | :--- |
|
|
176
|
+
| `'notEditable'` | The column is not `editable`, `isCellEditable` returned false, the row is a synthetic row (group header, subtotal, tree auto-parent, pivot Grand Total), or the cell is covered by a span |
|
|
177
|
+
| `'invalidValue'` | The text does not parse for the column type, or `valueParser` rejected it |
|
|
178
|
+
|
|
179
|
+
A row whose `valueSetter` or `processRowUpdate` throws or rejects is listed in `result.failed` and reported to `onProcessRowUpdateError`, as for a single edit; the other rows are still saved. Cells that already hold the pasted value are left alone.
|
|
180
|
+
|
|
181
|
+
### Events
|
|
182
|
+
|
|
183
|
+
- `onClipboardPaste(result)` fires after every paste, once every row has settled: `{ updated, failed, skipped, text }`.
|
|
184
|
+
- `onBeforeClipboardPaste({ text, anchor })` runs first. Return `false` to cancel the paste, or a string to paste instead (for example to strip a header line). A throw cancels the paste.
|
|
185
|
+
- Above 10,000 cells a paste logs a development warning.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 🧹 Clear a range with Delete (v3.4)
|
|
190
|
+
|
|
191
|
+
With `cellSelection` on and a range of **more than one cell**, **Delete** or **Backspace** empties every editable cell of the range: `''` in string and image columns, `null` elsewhere. It is one batch edit through `valueSetter` and `processRowUpdate`, so one undo. Non-editable cells and synthetic rows are left alone. On a single cell nothing changes (Delete is not bound). `disableRangeClear` turns it off; `disableClipboardPaste` does not affect it.
|
|
@@ -115,6 +115,56 @@ const columns: GridColDef[] = [
|
|
|
115
115
|
|
|
116
116
|
Enter and Escape keydowns that bubble out of a custom editor also commit and cancel. An error thrown by `renderEditCell` is contained to its cell (the cell shows `⚠`), like `renderCell`.
|
|
117
117
|
|
|
118
|
+
### Building custom editors with `Input`
|
|
119
|
+
The built-in text, number and date editors are the exported `Input` component with `variant="cell"` (since v3.3.1). Use it in `renderEditCell` and a custom editor looks like the built-in ones: it fills the cell, has no border, background or focus ring of its own (the editing cell's outline shows), and uses the cell's font.
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import { useEffect, useRef, useState } from 'react';
|
|
123
|
+
import { Input } from '@opencorestack/opengridx';
|
|
124
|
+
import type { GridColDef, GridRenderEditCellParams } from '@opencorestack/opengridx';
|
|
125
|
+
|
|
126
|
+
const isAmount = (t: string) => t.trim() !== '' && Number(t) >= 0;
|
|
127
|
+
|
|
128
|
+
function PriceEditor({ value, onValueChange, onCommit, onCancel }: GridRenderEditCellParams) {
|
|
129
|
+
const [text, setText] = useState(String(value ?? ''));
|
|
130
|
+
const ref = useRef<HTMLInputElement>(null);
|
|
131
|
+
useEffect(() => { ref.current?.focus(); ref.current?.select(); }, []);
|
|
132
|
+
const valid = isAmount(text);
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<Input
|
|
136
|
+
ref={ref} // reaches the <input>, on React 18 and 19
|
|
137
|
+
variant="cell"
|
|
138
|
+
startAdornment="$" // or endAdornment="kg"
|
|
139
|
+
error={!valid} // red inner edge + aria-invalid
|
|
140
|
+
inputMode="decimal"
|
|
141
|
+
value={text}
|
|
142
|
+
onChange={e => {
|
|
143
|
+
setText(e.target.value);
|
|
144
|
+
if (isAmount(e.target.value)) onValueChange(Number(e.target.value));
|
|
145
|
+
}}
|
|
146
|
+
onKeyDown={e => {
|
|
147
|
+
if (e.key === 'Enter') { e.stopPropagation(); if (valid) onCommit(); }
|
|
148
|
+
if (e.key === 'Escape') { e.stopPropagation(); onCancel(); }
|
|
149
|
+
}}
|
|
150
|
+
onBlur={() => (valid ? onCommit() : onCancel())}
|
|
151
|
+
/>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const columns: GridColDef[] = [
|
|
156
|
+
{ field: 'price', type: 'number', editable: true, renderEditCell: params => <PriceEditor {...params} /> },
|
|
157
|
+
];
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
- `ref` reaches the `<input>` (focus it and select its text when the editor opens, as the built-in editors do).
|
|
161
|
+
- `inputClassName` adds classes to the `<input>`; `className` goes to the `.ogx-input-wrapper` div around it.
|
|
162
|
+
- `error` adds `ogx-input-wrapper--error` (an inset `--ogx-color-error` edge in the cell variant, drawn over the cell outline) and `aria-invalid="true"`.
|
|
163
|
+
- Stop propagation of the Enter / Escape you handle yourself, or the grid also commits / cancels when they bubble out.
|
|
164
|
+
- The grid's boolean editor is the exported `Checkbox`; its `<input>` is visually hidden and `ref` / `inputClassName` reach it.
|
|
165
|
+
|
|
166
|
+
Live example: the *Custom Cell Editors* demo page (`/custom-editors`).
|
|
167
|
+
|
|
118
168
|
### Computed columns (`valueGetter` + `valueSetter`)
|
|
119
169
|
An editable column with a `valueGetter` edits the derived value. Give it a `valueSetter` so the commit can write the value back to the fields it comes from; otherwise the commit writes `row[field]`, which the `valueGetter` ignores, and a development warning is logged.
|
|
120
170
|
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Fill Handle
|
|
2
|
+
|
|
3
|
+
With [`cellSelection`](cell-selection.md) on, the selected range has a small square on its bottom-right corner: the **fill handle** (v3.5). Drag it down, up, right or left to fill the next cells from the range, as in Excel. **Ctrl/Cmd+D** and **Ctrl/Cmd+R** fill from the keyboard. Every fill is written like a paste, through `valueSetter` and `processRowUpdate`, and is one [undo](undo-redo.md) step. Demo: **Fill Handle** (`/fill-handle`).
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { DataGrid, type GridFillResult } from '@opencorestack/opengridx';
|
|
7
|
+
|
|
8
|
+
<DataGrid
|
|
9
|
+
rows={rows}
|
|
10
|
+
columns={columns} // fills only write editable columns
|
|
11
|
+
cellSelection // the fill handle comes with range selection
|
|
12
|
+
undoRedo // optional: one fill = one undo step
|
|
13
|
+
onFill={(result: GridFillResult) => console.log(result.direction, result.updated, result.skipped)}
|
|
14
|
+
processRowUpdate={saveRow} // called once per filled row
|
|
15
|
+
/>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Dragging the handle
|
|
19
|
+
|
|
20
|
+
- The handle sits on the bottom-right cell of the range, or on the only selected cell. It is shown while at least one column is `editable` and no editor is open.
|
|
21
|
+
- Drag it beyond the range. The range grows in **one direction**: the side of the range the cell under the pointer lies beyond. When that cell lies beyond both a row edge and a column edge (a diagonal drag), the larger pointer movement wins.
|
|
22
|
+
- Past the edge of the grid the body auto-scrolls, as in a range drag, also over rows and columns that are not rendered yet.
|
|
23
|
+
- Release to fill. Releasing over the original range, pressing Escape or losing the pointer writes nothing and restores the range.
|
|
24
|
+
- Hold **Alt** while releasing to **copy** instead of continuing a series (Excel uses Ctrl for this, which the grid keeps for range shortcuts).
|
|
25
|
+
- The handle has a 24 px hit area and `touch-action: none`, so a touch drag on it fills instead of scrolling the grid.
|
|
26
|
+
|
|
27
|
+
After the fill, the range covers the source and the filled cells.
|
|
28
|
+
|
|
29
|
+
## What is written
|
|
30
|
+
|
|
31
|
+
Each line along the fill (each column for a fill down or up, each row for a fill right or left) is filled from its source cells, read towards the new cells:
|
|
32
|
+
|
|
33
|
+
| Source cells of the line | Fill |
|
|
34
|
+
| :--- | :--- |
|
|
35
|
+
| One cell | Copies the value |
|
|
36
|
+
| Two or more numbers with an exactly equal step | Continues the series: 2, 4, 6 → 8, 10. Decimals are rounded to the source's decimal places, so 0.1, 0.2 → 0.3, not 0.30000000000000004 |
|
|
37
|
+
| Two or more dates on the same day of the month, an equal number of months apart | Continues by months: 15 Jan, 15 Feb → 15 Mar. The day is clamped to the month's end (31 Jan, 31 Mar → 31 May; 31 Dec, 31 Jan → 28 Feb) |
|
|
38
|
+
| Two or more dates at the same time of day, an equal number of days apart | Continues by days: Mon 5 Oct, Tue 6 Oct → Wed 7 Oct (calendar days, so a daylight-saving change does not break it) |
|
|
39
|
+
| Anything else (text, booleans, unequal steps, mixed types, empty cells) | Repeats the source pattern: A, B → A, B, A |
|
|
40
|
+
|
|
41
|
+
Filling up or left reads the source in that direction: 7, 9 filled up gives 5, 3.
|
|
42
|
+
|
|
43
|
+
Values are typed already, so `valueParser` is not involved: a number copied into a text column stays a number. Synthetic rows (group headers, subtotals, tree auto-parents) in the source are left out of the series.
|
|
44
|
+
|
|
45
|
+
### `GridColDef.fillValue`
|
|
46
|
+
|
|
47
|
+
A column can decide what a fill writes into its cells:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
{
|
|
51
|
+
field: 'amount', type: 'number', editable: true,
|
|
52
|
+
// params.value is what the grid would write; return it to keep it.
|
|
53
|
+
fillValue: ({ value, sourceValues, index, direction, copy, row, id }) =>
|
|
54
|
+
typeof value === 'number' ? Math.max(0, Math.round(value)) : value,
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
| Param | Description |
|
|
59
|
+
| :--- | :--- |
|
|
60
|
+
| `value` | The grid's choice: the next value of the series, the repeated pattern or the copy |
|
|
61
|
+
| `sourceValues` | The line's source values in fill order (the value next to the first filled cell is last) |
|
|
62
|
+
| `index` | Position of the cell among the filled cells of its line, from 0 |
|
|
63
|
+
| `direction` | `'down'`, `'up'`, `'right'` or `'left'` |
|
|
64
|
+
| `copy` | `true` when Alt was held or the fill came from Ctrl/Cmd+D or Ctrl/Cmd+R |
|
|
65
|
+
| `row`, `id`, `field`, `colDef` | The cell being filled |
|
|
66
|
+
|
|
67
|
+
A `fillValue` that throws skips that cell with reason `'invalidValue'` (and warns once in development).
|
|
68
|
+
|
|
69
|
+
## Keyboard
|
|
70
|
+
|
|
71
|
+
While focus is on a cell of the grid and no editor is open:
|
|
72
|
+
|
|
73
|
+
| Key | Action |
|
|
74
|
+
| :--- | :--- |
|
|
75
|
+
| **Ctrl+D** (Cmd+D) | Copies the range's top row into the rows below it |
|
|
76
|
+
| **Ctrl+R** (Cmd+R) | Copies the range's left column into the columns right of it |
|
|
77
|
+
|
|
78
|
+
The grid takes these keys (`preventDefault`, so no bookmark dialog or page reload) whenever a range or focused cell exists, `cellSelection` is on and `disableFillHandle` is not set. A range of one row (Ctrl+D) or one column (Ctrl+R) has nothing to fill. Inside an editor or a text field you render in a cell, the keys keep their native meaning.
|
|
79
|
+
|
|
80
|
+
## How it writes
|
|
81
|
+
|
|
82
|
+
A fill is a batch edit, the same path as [paste](clipboard.md):
|
|
83
|
+
|
|
84
|
+
- `valueSetter` (when the column has one) builds each row, and `processRowUpdate` runs **once per row**; rows run in parallel, and a row that throws or rejects is reported to `onProcessRowUpdateError` without stopping the others.
|
|
85
|
+
- All rows that succeeded are stored in one update.
|
|
86
|
+
- With `undoRedo`, the whole fill is **one undo step**.
|
|
87
|
+
- Cells that cannot be written are skipped with a reason:
|
|
88
|
+
|
|
89
|
+
| Reason | Cell |
|
|
90
|
+
| :--- | :--- |
|
|
91
|
+
| `'notEditable'` | The column is not `editable`, `isCellEditable` returned false, the row is synthetic, or a span covers the cell |
|
|
92
|
+
| `'invalidValue'` | The column's `fillValue` threw |
|
|
93
|
+
|
|
94
|
+
`onFill` receives the result plus the direction:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
interface GridFillResult extends GridBatchEditResult {
|
|
98
|
+
direction: 'down' | 'up' | 'right' | 'left';
|
|
99
|
+
}
|
|
100
|
+
// { updated: [3, 4, 5], failed: [], skipped: [{ id: 3, field: 'owner', reason: 'notEditable' }], direction: 'down' }
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Opting out
|
|
104
|
+
|
|
105
|
+
`disableFillHandle` hides the handle and leaves Ctrl/Cmd+D and Ctrl/Cmd+R to the browser. A grid without `cellSelection` has no fill handle.
|
|
106
|
+
|
|
107
|
+
## Styling
|
|
108
|
+
|
|
109
|
+
| Hook | Description |
|
|
110
|
+
| :--- | :--- |
|
|
111
|
+
| `.ogx__cell-fill-handle` | The handle: a 24 × 24 px hit area in the corner of the cell (`cursor: crosshair`, `touch-action: none`). The visible square is its `::after` |
|
|
112
|
+
| `--ogx-fill-handle-color` | The square's colour. Defaults to the range border (`--ogx-range-border`); theme key `grid.fillHandleColor` |
|
|
113
|
+
|
|
114
|
+
```css
|
|
115
|
+
.my-grid { --ogx-fill-handle-color: #16a34a; }
|
|
116
|
+
.my-grid .ogx__cell-fill-handle::after { width: 9px; height: 9px; }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## API
|
|
120
|
+
|
|
121
|
+
| Member | Description |
|
|
122
|
+
| :--- | :--- |
|
|
123
|
+
| `disableFillHandle?: boolean` | Hides the handle and turns off Ctrl/Cmd+D and Ctrl/Cmd+R. Default `false` |
|
|
124
|
+
| `onFill?(result: GridFillResult)` | Fired after every fill |
|
|
125
|
+
| `GridColDef.fillValue?(params: GridFillValueParams): unknown` | Decides the value written into a cell of the column |
|
|
@@ -7,6 +7,7 @@ OpenGridX provides a robust filtering system with support for both client-side a
|
|
|
7
7
|
- **Column Filters**: Specific operators for different data types (string, number, date, etc.).
|
|
8
8
|
- **Multi-Filter Groups**: Support for `AND`/`OR` logic operators.
|
|
9
9
|
- **Server-Side Filtering**: Offload complex queries to your backend.
|
|
10
|
+
- **Header Filters** (v3.5): a filter row under the column headers (`headerFilters`) that edits the same model. See [Header Filters](header-filters.md).
|
|
10
11
|
|
|
11
12
|
---
|
|
12
13
|
|
|
@@ -121,3 +122,5 @@ The filter panel is accessible through the built-in `GridToolbar`. To use your o
|
|
|
121
122
|
```
|
|
122
123
|
|
|
123
124
|
The `FilterPanel` component is also exported from `@opencorestack/opengridx` if you need to embed it inside a custom layout. See [`<FilterPanel />`](../components/filter-panel.md) for how it edits the model.
|
|
125
|
+
|
|
126
|
+
For a filter control on every column, set `headerFilters`: a row under the headers whose cells own the root items `id: 'header:<field>'` of the same `filterModel`, so the panel, the quick filter and the header row stay in sync. See [Header Filters](header-filters.md).
|