@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
package/lib/types/index.ts
CHANGED
|
@@ -154,6 +154,18 @@ export interface GridColDef<R extends GridValidRowModel = GridRowModel> {
|
|
|
154
154
|
sortable?: boolean;
|
|
155
155
|
/** If false, filtering is disabled for this column. */
|
|
156
156
|
filterable?: boolean;
|
|
157
|
+
/**
|
|
158
|
+
* Set to `false` to leave this column's cell in the header filter row (`headerFilters`) empty.
|
|
159
|
+
* Columns with `filterable: false` or `type: 'image'` have no header filter either.
|
|
160
|
+
* @since v3.5
|
|
161
|
+
*/
|
|
162
|
+
headerFilter?: boolean;
|
|
163
|
+
/**
|
|
164
|
+
* The operator a new header filter (`headerFilters`) for this column starts with. Defaults by type:
|
|
165
|
+
* `contains` (string), `=` (number), `is` (date, boolean, singleSelect).
|
|
166
|
+
* @since v3.5
|
|
167
|
+
*/
|
|
168
|
+
headerFilterOperator?: GridFilterOperator;
|
|
157
169
|
/** If false, the user cannot manually resize this column. */
|
|
158
170
|
resizable?: boolean;
|
|
159
171
|
/** If false, the column cannot be hidden via the UI. */
|
|
@@ -178,6 +190,14 @@ export interface GridColDef<R extends GridValidRowModel = GridRowModel> {
|
|
|
178
190
|
* @since v3.0
|
|
179
191
|
*/
|
|
180
192
|
valueSetter?(params: GridValueSetterParams<R>): R;
|
|
193
|
+
/**
|
|
194
|
+
* Turns pasted text into this column's value (clipboard paste), instead of the default parser for
|
|
195
|
+
* the column `type` (see `docs/features/clipboard.md`). Use it for locales such as `1.234,5` or for
|
|
196
|
+
* custom codes. Throw (or return `undefined`) to reject the text: the cell is skipped with reason
|
|
197
|
+
* `'invalidValue'`. The value then goes through `valueSetter` and `processRowUpdate` like an edit.
|
|
198
|
+
* @since v3.4
|
|
199
|
+
*/
|
|
200
|
+
valueParser?(text: string, params: GridValueParserParams<R>): unknown;
|
|
181
201
|
/**
|
|
182
202
|
* Custom ascending comparator for client-side sorting by this column, used instead of the built-in
|
|
183
203
|
* type-aware comparison. `v1` / `v2` are the cell values (`valueGetter` applied) and **include
|
|
@@ -188,6 +208,13 @@ export interface GridColDef<R extends GridValidRowModel = GridRowModel> {
|
|
|
188
208
|
* @since v3.1
|
|
189
209
|
*/
|
|
190
210
|
sortComparator?(v1: unknown, v2: unknown, params1: GridSortCellParams<R>, params2: GridSortCellParams<R>): number;
|
|
211
|
+
/**
|
|
212
|
+
* Decides the value the fill handle (or Ctrl/Cmd+D, Ctrl/Cmd+R) writes into a cell of this column.
|
|
213
|
+
* `params.value` is what the grid would write (a continued series, the repeated pattern or a copy);
|
|
214
|
+
* return it to keep that, or any other value. A throw skips the cell with reason `'invalidValue'`.
|
|
215
|
+
* The value then goes through `valueSetter` and `processRowUpdate` like an edit. @since v3.5
|
|
216
|
+
*/
|
|
217
|
+
fillValue?(params: GridFillValueParams<R>): unknown;
|
|
191
218
|
/** Function to format a value into a human-readable string. */
|
|
192
219
|
valueFormatter?(params: GridValueFormatterParams<R>): string;
|
|
193
220
|
/** Custom component or element to render in the cell. */
|
|
@@ -240,6 +267,16 @@ export interface GridColDef<R extends GridValidRowModel = GridRowModel> {
|
|
|
240
267
|
* @default true
|
|
241
268
|
*/
|
|
242
269
|
exportable?: boolean;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Example values for this column, put into the schema from `getGridAiSchema`
|
|
273
|
+
* (`@opencorestack/opengridx/ai`) to help a language model write filters ("Acme" → `customer`).
|
|
274
|
+
* **These values are sent to the model.** The grid never reads row data for the schema; only what
|
|
275
|
+
* you list here leaves the app, so list nothing private. Strings, numbers and booleans are used
|
|
276
|
+
* (at most 10); other values are ignored. Not used by the grid itself.
|
|
277
|
+
* @since v3.4
|
|
278
|
+
*/
|
|
279
|
+
aiExamples?: unknown[];
|
|
243
280
|
}
|
|
244
281
|
|
|
245
282
|
/** Column definition used exclusively in List View mode. */
|
|
@@ -300,6 +337,40 @@ export interface GridValueGetterParams<R extends GridValidRowModel = GridRowMode
|
|
|
300
337
|
value: unknown;
|
|
301
338
|
}
|
|
302
339
|
|
|
340
|
+
/** The way a fill extends the cell range. @since v3.5 */
|
|
341
|
+
export type GridFillDirection = 'down' | 'up' | 'right' | 'left';
|
|
342
|
+
|
|
343
|
+
/** Parameters passed to `GridColDef.fillValue`. @since v3.5 */
|
|
344
|
+
export interface GridFillValueParams<R extends GridValidRowModel = GridRowModel> {
|
|
345
|
+
/** The row of the cell being filled, as stored now. */
|
|
346
|
+
row: R;
|
|
347
|
+
id: GridRowId;
|
|
348
|
+
field: string;
|
|
349
|
+
colDef: GridColDef<R>;
|
|
350
|
+
direction: GridFillDirection;
|
|
351
|
+
/** The source cells' values in fill order: the value next to the first filled cell is last. */
|
|
352
|
+
sourceValues: unknown[];
|
|
353
|
+
/** Position of this cell among the filled cells of its line, from 0 (next to the source). */
|
|
354
|
+
index: number;
|
|
355
|
+
/** What the grid would write: the continued series, the repeated pattern or a copy. */
|
|
356
|
+
value: unknown;
|
|
357
|
+
/** Whether a copy was asked for (Alt held at release, or the keyboard shortcuts), so no series. */
|
|
358
|
+
copy: boolean;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** `onFill` details: the batch edit result plus the direction of the fill. @since v3.5 */
|
|
362
|
+
export interface GridFillResult extends GridBatchEditResult {
|
|
363
|
+
direction: GridFillDirection;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** Parameters passed to `GridColDef.valueParser`. @since v3.4 */
|
|
367
|
+
export interface GridValueParserParams<R extends GridValidRowModel = GridRowModel> {
|
|
368
|
+
/** The row the text is pasted into, as stored now. */
|
|
369
|
+
row: R;
|
|
370
|
+
field: string;
|
|
371
|
+
colDef: GridColDef<R>;
|
|
372
|
+
}
|
|
373
|
+
|
|
303
374
|
/**
|
|
304
375
|
* Parameters passed to the `valueSetter` function.
|
|
305
376
|
* @since v3.0
|
|
@@ -803,6 +874,54 @@ export interface GridRowScrollEndParams {
|
|
|
803
874
|
viewportHeight: number;
|
|
804
875
|
}
|
|
805
876
|
|
|
877
|
+
/**
|
|
878
|
+
* Why a cell of a batch edit (paste, range clear, undo, redo) was not written:
|
|
879
|
+
* - `'notEditable'`: the column is not `editable`, `isCellEditable` returned false, or the row is a
|
|
880
|
+
* synthetic row (group header, subtotal, tree auto-parent, pivot Grand Total);
|
|
881
|
+
* - `'invalidValue'`: the pasted text does not parse for the column type (or `valueParser` rejected it);
|
|
882
|
+
* - `'changed'`: undo/redo only, the cell no longer holds the value the history recorded;
|
|
883
|
+
* - `'missing'`: undo/redo only, the row is gone.
|
|
884
|
+
* @since v3.4
|
|
885
|
+
*/
|
|
886
|
+
export type GridEditSkipReason = 'notEditable' | 'invalidValue' | 'changed' | 'missing';
|
|
887
|
+
|
|
888
|
+
/** What a batch edit (paste, range clear, undo, redo) did. @since v3.4 */
|
|
889
|
+
export interface GridBatchEditResult {
|
|
890
|
+
/** Rows whose `processRowUpdate` succeeded and that were stored, in the order they were first touched. */
|
|
891
|
+
updated: GridRowId[];
|
|
892
|
+
/** Rows whose `valueSetter` or `processRowUpdate` threw or rejected (also reported to `onProcessRowUpdateError`). */
|
|
893
|
+
failed: { id: GridRowId; error: unknown }[];
|
|
894
|
+
/** Cells that were not written, with the reason. */
|
|
895
|
+
skipped: { id: GridRowId; field: string; reason: GridEditSkipReason }[];
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/** `onClipboardPaste` details: the batch edit result plus the text that was pasted. @since v3.4 */
|
|
899
|
+
export interface GridClipboardPasteResult extends GridBatchEditResult {
|
|
900
|
+
text: string;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/** `onBeforeClipboardPaste` params. @since v3.4 */
|
|
904
|
+
export interface GridBeforeClipboardPasteParams {
|
|
905
|
+
/** The clipboard text (`text/plain`). */
|
|
906
|
+
text: string;
|
|
907
|
+
/** The cell the paste starts from (the range's top-left cell, or the focused cell). */
|
|
908
|
+
anchor: GridCellCoordinates | null;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/** `onHistoryChange` params. @since v3.4 */
|
|
912
|
+
export interface GridHistoryChangeParams {
|
|
913
|
+
canUndo: boolean;
|
|
914
|
+
canRedo: boolean;
|
|
915
|
+
/** Number of actions that can be undone. */
|
|
916
|
+
size: number;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/** `undoRedo` options. @since v3.4 */
|
|
920
|
+
export interface GridUndoRedoOptions {
|
|
921
|
+
/** How many actions are kept; the oldest is dropped beyond it. Default: 100. */
|
|
922
|
+
limit?: number;
|
|
923
|
+
}
|
|
924
|
+
|
|
806
925
|
/** Overrideable user-visible strings for internationalisation. All fields are optional; defaults match the built-in English strings. */
|
|
807
926
|
export interface GridLocaleText {
|
|
808
927
|
/** Label before the rows-per-page select. Default: `"Rows per page:"` */
|
|
@@ -819,6 +938,8 @@ export interface GridLocaleText {
|
|
|
819
938
|
* @since v3.3
|
|
820
939
|
*/
|
|
821
940
|
cellSelectionAnnouncement?: (cellCount: number, rowCount: number, columnCount: number) => string;
|
|
941
|
+
/** Label of the toolbar button that opens the `aiAssistant` panel. Default: `"Ask AI"` @since v3.5 */
|
|
942
|
+
aiAssistantButton?: string;
|
|
822
943
|
}
|
|
823
944
|
|
|
824
945
|
/**
|
|
@@ -844,6 +965,18 @@ export interface DataGridProps<R extends GridValidRowModel = GridRowModel> {
|
|
|
844
965
|
* Defaults to `false`.
|
|
845
966
|
*/
|
|
846
967
|
wrapHeaderText?: boolean;
|
|
968
|
+
/**
|
|
969
|
+
* Adds a filter row under the column headers: one control per column (text, number, date, a Yes / No
|
|
970
|
+
* select for booleans, the `valueOptions` for singleSelect) with an operator menu and a clear button.
|
|
971
|
+
* It reads and writes `filterModel`: each cell owns the root-level item with `id: 'header:<field>'`,
|
|
972
|
+
* so the filter panel, quick filter and `filterMode="server"` keep working. A model the row cannot
|
|
973
|
+
* show (root `logicOperator: 'or'`, other conditions on the column, nested groups) is shown as
|
|
974
|
+
* "Custom filter" and left untouched. See `docs/features/header-filters.md`. Defaults to `false`.
|
|
975
|
+
* @since v3.5
|
|
976
|
+
*/
|
|
977
|
+
headerFilters?: boolean;
|
|
978
|
+
/** Height of the header filter row (`headerFilters`) in pixels. Defaults to 40. @since v3.5 */
|
|
979
|
+
headerFilterHeight?: number;
|
|
847
980
|
/** If true, the grid height will adjust to match the total height of its rows. */
|
|
848
981
|
autoHeight?: boolean;
|
|
849
982
|
/**
|
|
@@ -939,6 +1072,49 @@ export interface DataGridProps<R extends GridValidRowModel = GridRowModel> {
|
|
|
939
1072
|
*/
|
|
940
1073
|
showCellSelectionStats?: boolean;
|
|
941
1074
|
|
|
1075
|
+
/**
|
|
1076
|
+
* With `cellSelection` on, Ctrl/Cmd+V pastes tab-separated text (Excel, Google Sheets) into the
|
|
1077
|
+
* editable cells from the range's top-left cell (or the focused cell), through `valueSetter` and
|
|
1078
|
+
* `processRowUpdate`. `true` turns the shortcut off; `apiRef.pasteText()` still works. Default: false.
|
|
1079
|
+
* @since v3.4
|
|
1080
|
+
*/
|
|
1081
|
+
disableClipboardPaste?: boolean;
|
|
1082
|
+
/**
|
|
1083
|
+
* Fired after every paste with the result (`updated` rows, `failed` rows, `skipped` cells and why)
|
|
1084
|
+
* and the pasted text. @since v3.4
|
|
1085
|
+
*/
|
|
1086
|
+
onClipboardPaste?: (result: GridClipboardPasteResult) => void;
|
|
1087
|
+
/**
|
|
1088
|
+
* Called before a paste is applied. Return `false` to cancel it, or a string to paste instead of the
|
|
1089
|
+
* clipboard text. A throw cancels the paste. @since v3.4
|
|
1090
|
+
*/
|
|
1091
|
+
onBeforeClipboardPaste?: (params: GridBeforeClipboardPasteParams) => boolean | string;
|
|
1092
|
+
/**
|
|
1093
|
+
* With `cellSelection` on, Delete / Backspace on a range of more than one cell empties its editable
|
|
1094
|
+
* cells (`null`, or `''` in string columns) as one edit. `true` turns that off. Default: false.
|
|
1095
|
+
* @since v3.4
|
|
1096
|
+
*/
|
|
1097
|
+
disableRangeClear?: boolean;
|
|
1098
|
+
/**
|
|
1099
|
+
* Undo and redo of cell edits, pastes and range clears: Ctrl/Cmd+Z, Ctrl/Cmd+Shift+Z and Ctrl+Y while
|
|
1100
|
+
* focus is in the grid and no editor is open, and `apiRef.undo()` / `redo()`. Undo and redo write
|
|
1101
|
+
* through `valueSetter` and `processRowUpdate`, like an edit. `{ limit }` sets how many actions are
|
|
1102
|
+
* kept (default 100). Default: false. @since v3.4
|
|
1103
|
+
*/
|
|
1104
|
+
undoRedo?: boolean | GridUndoRedoOptions;
|
|
1105
|
+
/** Fired when what can be undone or redone changes (`undoRedo`). @since v3.4 */
|
|
1106
|
+
onHistoryChange?: (params: GridHistoryChangeParams) => void;
|
|
1107
|
+
/**
|
|
1108
|
+
* With `cellSelection` on, a fill handle sits on the bottom-right corner of the range: dragging it
|
|
1109
|
+
* down, up, right or left fills the new cells from the range (a series of numbers or dates with an
|
|
1110
|
+
* equal step continues, anything else repeats; Alt at release copies), and Ctrl/Cmd+D / Ctrl/Cmd+R
|
|
1111
|
+
* fill down from the range's top row / right from its left column. `true` hides the handle and turns
|
|
1112
|
+
* the shortcuts off. Default: false. @since v3.5
|
|
1113
|
+
*/
|
|
1114
|
+
disableFillHandle?: boolean;
|
|
1115
|
+
/** Fired after every fill with the result and its direction. @since v3.5 */
|
|
1116
|
+
onFill?: (result: GridFillResult) => void;
|
|
1117
|
+
|
|
942
1118
|
/** Loading state. With no rows the body shows skeleton rows (or `slots.loadingOverlay`); with rows, they stay and a progress bar (or `slots.loadingOverlay`) is shown over them. */
|
|
943
1119
|
loading?: boolean;
|
|
944
1120
|
/** Visual density of the grid. */
|
|
@@ -1031,8 +1207,13 @@ export interface DataGridProps<R extends GridValidRowModel = GridRowModel> {
|
|
|
1031
1207
|
/** Initial expansion depth for Tree Data. */
|
|
1032
1208
|
defaultGroupingExpansionDepth?: number;
|
|
1033
1209
|
|
|
1034
|
-
/**
|
|
1210
|
+
/**
|
|
1211
|
+
* The row grouping model: the fields to group by, outermost first. Passing it controls grouping;
|
|
1212
|
+
* without it the grid keeps its own model, which `apiRef.setRowGroupingModel` and the AI assistant change.
|
|
1213
|
+
*/
|
|
1035
1214
|
rowGroupingModel?: GridRowGroupingModel;
|
|
1215
|
+
/** Fired when the row grouping model changes (`apiRef.setRowGroupingModel`, the AI assistant). @since v3.5 */
|
|
1216
|
+
onRowGroupingModelChange?: (model: GridRowGroupingModel) => void;
|
|
1036
1217
|
/** Controlled state for data aggregation (e.g., { salary: 'sum' }). */
|
|
1037
1218
|
aggregationModel?: GridAggregationModel;
|
|
1038
1219
|
/** Callback fired when aggregation model changes. */
|
|
@@ -1096,6 +1277,8 @@ export interface DataGridProps<R extends GridValidRowModel = GridRowModel> {
|
|
|
1096
1277
|
* (plus `slotProps.cellSelectionStats`). @since v3.3
|
|
1097
1278
|
*/
|
|
1098
1279
|
cellSelectionStats?: React.ComponentType<GridCellSelectionStatsSlotProps & Record<string, unknown>>;
|
|
1280
|
+
/** Replaces the `aiAssistant` panel. Receives `GridAiAssistantPanelProps` while the panel is open. @since v3.5 */
|
|
1281
|
+
aiAssistantPanel?: React.ComponentType<GridAiAssistantPanelProps & Record<string, unknown>>;
|
|
1099
1282
|
};
|
|
1100
1283
|
/** Properties passed directly to custom slots. */
|
|
1101
1284
|
slotProps?: {
|
|
@@ -1113,6 +1296,8 @@ export interface DataGridProps<R extends GridValidRowModel = GridRowModel> {
|
|
|
1113
1296
|
footer?: Partial<GridFooterSlotProps> & Record<string, unknown>;
|
|
1114
1297
|
/** Merged over the `GridCellSelectionStatsSlotProps` the grid passes to the status bar. */
|
|
1115
1298
|
cellSelectionStats?: Partial<GridCellSelectionStatsSlotProps> & Record<string, unknown>;
|
|
1299
|
+
/** Extra props for `slots.aiAssistantPanel`. */
|
|
1300
|
+
aiAssistantPanel?: Record<string, unknown>;
|
|
1116
1301
|
};
|
|
1117
1302
|
|
|
1118
1303
|
/** When true, renders the grid as a single-column list of cards. Perfect for mobile/responsive views. */
|
|
@@ -1125,6 +1310,18 @@ export interface DataGridProps<R extends GridValidRowModel = GridRowModel> {
|
|
|
1125
1310
|
* column headers, supporting multiple levels of nesting.
|
|
1126
1311
|
*/
|
|
1127
1312
|
columnGroupingModel?: GridColumnGroupingModel;
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* The "Ask AI" prompt panel: users describe the view they want and the grid applies the validated
|
|
1316
|
+
* reply as one undoable step, shown as removable chips. The built-in toolbar shows an **Ask AI**
|
|
1317
|
+
* button; `apiRef.openAiAssistant()` opens it without one. Build `onPrompt` with
|
|
1318
|
+
* `createGridAiPromptHandler` from `@opencorestack/opengridx/ai`. @since v3.5
|
|
1319
|
+
*/
|
|
1320
|
+
aiAssistant?: GridAiAssistantOptions;
|
|
1321
|
+
/** Fired after the assistant applied a reply. @since v3.5 */
|
|
1322
|
+
onAiAssistantApply?: (result: GridAiPromptResult) => void;
|
|
1323
|
+
/** Fired when `onPrompt` fails or returns a result that was not validated. @since v3.5 */
|
|
1324
|
+
onAiAssistantError?: (error: unknown) => void;
|
|
1128
1325
|
}
|
|
1129
1326
|
|
|
1130
1327
|
/**
|
|
@@ -1328,4 +1525,149 @@ export interface GridApi<R extends GridValidRowModel = GridRowModel> {
|
|
|
1328
1525
|
* copy; rejects when the clipboard write fails. @since v3.3
|
|
1329
1526
|
*/
|
|
1330
1527
|
copySelectedCells: () => Promise<void>;
|
|
1528
|
+
|
|
1529
|
+
/**
|
|
1530
|
+
* Pastes tab-separated text as Ctrl/Cmd+V does: from `anchor` (default: the range's top-left cell,
|
|
1531
|
+
* else the focused cell), with `onBeforeClipboardPaste` and `onClipboardPaste`. Works with
|
|
1532
|
+
* `disableClipboardPaste`. Resolves once every row's `processRowUpdate` has settled. @since v3.4
|
|
1533
|
+
*/
|
|
1534
|
+
pasteText: (text: string, anchor?: GridCellCoordinates) => Promise<GridBatchEditResult>;
|
|
1535
|
+
/** Undoes the last action (`undoRedo`). Resolves with what was written. @since v3.4 */
|
|
1536
|
+
undo: () => Promise<GridBatchEditResult>;
|
|
1537
|
+
/** Redoes the last undone action (`undoRedo`). @since v3.4 */
|
|
1538
|
+
redo: () => Promise<GridBatchEditResult>;
|
|
1539
|
+
/** Whether there is an action to undo. @since v3.4 */
|
|
1540
|
+
canUndo: () => boolean;
|
|
1541
|
+
/** Whether there is an undone action to redo. @since v3.4 */
|
|
1542
|
+
canRedo: () => boolean;
|
|
1543
|
+
/** Forgets every action (undo and redo). @since v3.4 */
|
|
1544
|
+
clearHistory: () => void;
|
|
1545
|
+
|
|
1546
|
+
/** Replaces the sort model (fires `onSortModelChange`). @since v3.5 */
|
|
1547
|
+
setSortModel: (model: GridSortItem[]) => void;
|
|
1548
|
+
/** Replaces the row grouping model (fires `onRowGroupingModelChange`). @since v3.5 */
|
|
1549
|
+
setRowGroupingModel: (model: GridRowGroupingModel) => void;
|
|
1550
|
+
/** Replaces the aggregation model (fires `onAggregationModelChange`). @since v3.5 */
|
|
1551
|
+
setAggregationModel: (model: GridAggregationModel) => void;
|
|
1552
|
+
/** Replaces the column visibility model (fires `onColumnVisibilityModelChange`). @since v3.5 */
|
|
1553
|
+
setColumnVisibilityModel: (model: GridColumnVisibilityModel) => void;
|
|
1554
|
+
/** Replaces the pivot model (fires `onPivotModelChange`). @since v3.5 */
|
|
1555
|
+
setPivotModel: (model: GridPivotModel) => void;
|
|
1556
|
+
/**
|
|
1557
|
+
* The current filter, sort, grouping, aggregation, pivot and column visibility models, in the shape
|
|
1558
|
+
* of the AI toolkit (`GridAiState`). @since v3.5
|
|
1559
|
+
*/
|
|
1560
|
+
getGridAiState: () => GridAiState;
|
|
1561
|
+
/** Opens the `aiAssistant` panel (does nothing without `aiAssistant`). @since v3.5 */
|
|
1562
|
+
openAiAssistant: () => void;
|
|
1563
|
+
/** Closes the `aiAssistant` panel. @since v3.5 */
|
|
1564
|
+
closeAiAssistant: () => void;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
// ── AI toolkit (v3.4 state types; v3.5 assistant) ──────────────────────────────────────────────────
|
|
1568
|
+
// Type-only and React-free: the `@opencorestack/opengridx/ai` entry point re-exports them.
|
|
1569
|
+
|
|
1570
|
+
/** A part of the grid state a model may change. @since v3.4 */
|
|
1571
|
+
export type GridAiPart = 'filter' | 'sort' | 'grouping' | 'aggregation' | 'pivot' | 'columnVisibility';
|
|
1572
|
+
|
|
1573
|
+
/** Grid state as the AI toolkit reads and writes it. Only the parts present are applied. @since v3.4 */
|
|
1574
|
+
export interface GridAiState {
|
|
1575
|
+
filterModel?: GridFilterModel;
|
|
1576
|
+
sortModel?: GridSortItem[];
|
|
1577
|
+
rowGroupingModel?: GridRowGroupingModel;
|
|
1578
|
+
aggregationModel?: GridAggregationModel;
|
|
1579
|
+
pivotModel?: GridPivotModel;
|
|
1580
|
+
columnVisibilityModel?: GridColumnVisibilityModel;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
/** One thing the validator dropped or could not use. `path` points into the model output. @since v3.4 */
|
|
1584
|
+
export interface GridAiValidationError {
|
|
1585
|
+
path: string;
|
|
1586
|
+
message: string;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
/** One earlier prompt of this session and the state the assistant applied for it. @since v3.5 */
|
|
1590
|
+
export interface GridAiHistoryEntry {
|
|
1591
|
+
prompt: string;
|
|
1592
|
+
applied: GridAiState;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
/** What `aiAssistant.onPrompt` receives next to the prompt. @since v3.5 */
|
|
1596
|
+
export interface GridAiPromptContext {
|
|
1597
|
+
/** The grid's current models (`apiRef.getGridAiState()`). */
|
|
1598
|
+
currentState: GridAiState;
|
|
1599
|
+
/** Earlier prompts of this session, oldest first. */
|
|
1600
|
+
history: GridAiHistoryEntry[];
|
|
1601
|
+
/** Aborted when the user presses Stop, sends another prompt or closes the panel. */
|
|
1602
|
+
signal: AbortSignal;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
/**
|
|
1606
|
+
* What `aiAssistant.onPrompt` resolves with. The grid applies it only when its `state` comes from
|
|
1607
|
+
* `validateGridAiState` (or `createGridAiPromptHandler`), which brands the result and its `state`.
|
|
1608
|
+
* @since v3.5
|
|
1609
|
+
*/
|
|
1610
|
+
export interface GridAiPromptResult {
|
|
1611
|
+
state: GridAiState;
|
|
1612
|
+
/** What the validator dropped; shown as "Ignored: …". */
|
|
1613
|
+
errors: GridAiValidationError[];
|
|
1614
|
+
/** The assistant's reply text, shown in the panel. */
|
|
1615
|
+
message?: string;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
/** The `aiAssistant` prop: the "Ask AI" prompt panel. @since v3.5 */
|
|
1619
|
+
export interface GridAiAssistantOptions {
|
|
1620
|
+
/**
|
|
1621
|
+
* Answers a prompt. Call your own model and return the validated result: `createGridAiPromptHandler`
|
|
1622
|
+
* from `@opencorestack/opengridx/ai` builds this function, or call `validateGridAiState` yourself.
|
|
1623
|
+
*/
|
|
1624
|
+
onPrompt: (prompt: string, context: GridAiPromptContext) => Promise<GridAiPromptResult>;
|
|
1625
|
+
/** Placeholder of the prompt input. Default: `"Ask about this table…"` */
|
|
1626
|
+
placeholder?: string;
|
|
1627
|
+
/** Example prompts shown as clickable chips. */
|
|
1628
|
+
suggestions?: string[];
|
|
1629
|
+
/** Shows a microphone button where the browser has speech recognition; the microphone is asked for only on click. Default: true. */
|
|
1630
|
+
voice?: boolean;
|
|
1631
|
+
/** The parts of the state the assistant may change. Default: every part. */
|
|
1632
|
+
parts?: GridAiPart[];
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
/** One change the assistant applied, shown as a removable chip. @since v3.5 */
|
|
1636
|
+
export interface GridAiChip {
|
|
1637
|
+
id: string;
|
|
1638
|
+
part: GridAiPart;
|
|
1639
|
+
label: string;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
/** The assistant panel's state. @since v3.5 */
|
|
1643
|
+
export type GridAiAssistantStatus = 'idle' | 'running' | 'applied' | 'stopped' | 'error';
|
|
1644
|
+
|
|
1645
|
+
/** Props of `slots.aiAssistantPanel` and the built-in panel, rendered while the panel is open. @since v3.5 */
|
|
1646
|
+
export interface GridAiAssistantPanelProps {
|
|
1647
|
+
status: GridAiAssistantStatus;
|
|
1648
|
+
/** The status line: "Thinking…", "Applied 3 changes", an error. */
|
|
1649
|
+
statusText: string;
|
|
1650
|
+
/** The assistant's reply text (`result.message`). */
|
|
1651
|
+
message: string | null;
|
|
1652
|
+
/** What the last reply asked for that was ignored. */
|
|
1653
|
+
errors: GridAiValidationError[];
|
|
1654
|
+
/** The changes the last reply applied. */
|
|
1655
|
+
chips: GridAiChip[];
|
|
1656
|
+
/** Earlier prompts of this session, oldest first. */
|
|
1657
|
+
history: GridAiHistoryEntry[];
|
|
1658
|
+
suggestions: string[];
|
|
1659
|
+
placeholder: string;
|
|
1660
|
+
/** `voice` is on and this browser has speech recognition. */
|
|
1661
|
+
voiceAvailable: boolean;
|
|
1662
|
+
/** Undo can restore the state from before the last reply. */
|
|
1663
|
+
canUndo: boolean;
|
|
1664
|
+
submit: (prompt: string) => void;
|
|
1665
|
+
/** Aborts the running request. */
|
|
1666
|
+
stop: () => void;
|
|
1667
|
+
/** Restores the state from before the last reply. */
|
|
1668
|
+
undo: () => void;
|
|
1669
|
+
/** Re-applies the last reply without this change. */
|
|
1670
|
+
removeChip: (id: string) => void;
|
|
1671
|
+
/** Closes the panel; focus returns to where it was before it opened. */
|
|
1672
|
+
close: () => void;
|
|
1331
1673
|
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// Pure helpers of the `aiAssistant` panel: which parts of a reply it may apply, what to restore on
|
|
2
|
+
// Undo, the removable chips and what a state looks like without one of them. No AI code: the reply
|
|
3
|
+
// was validated by the `/ai` entry point before it reaches the grid.
|
|
4
|
+
import type {
|
|
5
|
+
GridAiChip,
|
|
6
|
+
GridAiPart,
|
|
7
|
+
GridAiState,
|
|
8
|
+
GridAiValidationError,
|
|
9
|
+
GridApi,
|
|
10
|
+
GridFilterGroup,
|
|
11
|
+
GridFilterItem,
|
|
12
|
+
} from '../types';
|
|
13
|
+
|
|
14
|
+
export const AI_PARTS: readonly GridAiPart[] = ['filter', 'sort', 'grouping', 'aggregation', 'pivot', 'columnVisibility'];
|
|
15
|
+
|
|
16
|
+
const PART_KEYS: Record<GridAiPart, keyof GridAiState> = {
|
|
17
|
+
filter: 'filterModel',
|
|
18
|
+
sort: 'sortModel',
|
|
19
|
+
grouping: 'rowGroupingModel',
|
|
20
|
+
aggregation: 'aggregationModel',
|
|
21
|
+
pivot: 'pivotModel',
|
|
22
|
+
columnVisibility: 'columnVisibilityModel',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type StateKey = keyof GridAiState;
|
|
26
|
+
|
|
27
|
+
function keysOf(state: GridAiState): StateKey[] {
|
|
28
|
+
return AI_PARTS.map((part) => PART_KEYS[part]).filter((key) => state[key] !== undefined);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The parts of `state` that `parts` allows, and an error for each part it does not. */
|
|
32
|
+
export function pickAllowedParts(state: GridAiState, parts: readonly GridAiPart[]): { state: GridAiState; errors: GridAiValidationError[] } {
|
|
33
|
+
const picked: GridAiState = {};
|
|
34
|
+
const errors: GridAiValidationError[] = [];
|
|
35
|
+
for (const part of AI_PARTS) {
|
|
36
|
+
const key = PART_KEYS[part];
|
|
37
|
+
if (state[key] === undefined) continue;
|
|
38
|
+
if (parts.includes(part)) Object.assign(picked, { [key]: state[key] });
|
|
39
|
+
else errors.push({ path: key, message: 'Part not allowed' });
|
|
40
|
+
}
|
|
41
|
+
return { state: picked, errors };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isEmptyPart(state: GridAiState, key: StateKey): boolean {
|
|
45
|
+
const value = state[key];
|
|
46
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
47
|
+
if (key === 'filterModel') {
|
|
48
|
+
const model = state.filterModel;
|
|
49
|
+
return !model || ((model.items ?? []).length === 0 && (model.quickFilterValues ?? []).length === 0);
|
|
50
|
+
}
|
|
51
|
+
if (key === 'pivotModel') {
|
|
52
|
+
const model = state.pivotModel;
|
|
53
|
+
return !model || (model.rowFields.length === 0 && model.columnFields.length === 0 && model.valueFields.length === 0);
|
|
54
|
+
}
|
|
55
|
+
return typeof value === 'object' && value !== null && Object.keys(value).length === 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The state key an error path starts with (`'sortModel[0].field'` → `'sortModel'`). */
|
|
59
|
+
function errorKey(path: string): string {
|
|
60
|
+
let end = path.length;
|
|
61
|
+
for (const sep of ['.', '[']) {
|
|
62
|
+
const at = path.indexOf(sep);
|
|
63
|
+
if (at >= 0 && at < end) end = at;
|
|
64
|
+
}
|
|
65
|
+
return path.slice(0, end);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Leaves out a part that is empty only because the validator dropped everything in it (`sortModel`
|
|
70
|
+
* naming an unknown field comes back as `[]`): applying it would clear the grid's model, which the
|
|
71
|
+
* reply did not ask for. An empty part without errors ("clear the sort") is kept.
|
|
72
|
+
*/
|
|
73
|
+
export function dropEmptiedParts(state: GridAiState, errors: readonly GridAiValidationError[]): GridAiState {
|
|
74
|
+
const failed = new Set(errors.map((error) => errorKey(typeof error.path === 'string' ? error.path : '')));
|
|
75
|
+
const out: GridAiState = {};
|
|
76
|
+
for (const key of keysOf(state)) {
|
|
77
|
+
if (!(failed.has(key) && isEmptyPart(state, key))) Object.assign(out, { [key]: state[key] });
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The current values of the parts `state` changes: what Undo restores. */
|
|
83
|
+
export function snapshotParts(state: GridAiState, current: GridAiState): GridAiState {
|
|
84
|
+
const snapshot: GridAiState = {};
|
|
85
|
+
for (const key of keysOf(state)) Object.assign(snapshot, { [key]: current[key] });
|
|
86
|
+
return snapshot;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The models to apply for `applied` over `previous` (the snapshot taken before the reply): column
|
|
91
|
+
* visibility is merged over the previous model; a part removed from `applied` gets its previous value back.
|
|
92
|
+
*/
|
|
93
|
+
export function effectiveState(applied: GridAiState, previous: GridAiState): GridAiState {
|
|
94
|
+
const out: GridAiState = {};
|
|
95
|
+
for (const key of AI_PARTS.map((part) => PART_KEYS[part])) {
|
|
96
|
+
if (!(key in previous) && applied[key] === undefined) continue;
|
|
97
|
+
if (applied[key] === undefined) Object.assign(out, { [key]: previous[key] });
|
|
98
|
+
else if (key === 'columnVisibilityModel') out.columnVisibilityModel = { ...previous.columnVisibilityModel, ...applied.columnVisibilityModel };
|
|
99
|
+
else Object.assign(out, { [key]: applied[key] });
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
type GridAiStateSetters = Pick<GridApi, 'setFilterModel' | 'setSortModel' | 'setRowGroupingModel' | 'setAggregationModel' | 'setPivotModel' | 'setColumnVisibilityModel'>;
|
|
105
|
+
|
|
106
|
+
/** Applies every part present in `state` through the API (each fires its change callback). */
|
|
107
|
+
export function applyGridAiState(api: GridAiStateSetters, state: GridAiState): void {
|
|
108
|
+
if (state.filterModel) api.setFilterModel(state.filterModel);
|
|
109
|
+
if (state.sortModel) api.setSortModel(state.sortModel);
|
|
110
|
+
if (state.rowGroupingModel) api.setRowGroupingModel(state.rowGroupingModel);
|
|
111
|
+
if (state.aggregationModel) api.setAggregationModel(state.aggregationModel);
|
|
112
|
+
if (state.pivotModel) api.setPivotModel(state.pivotModel);
|
|
113
|
+
if (state.columnVisibilityModel) api.setColumnVisibilityModel(state.columnVisibilityModel);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function valueText(value: unknown): string {
|
|
117
|
+
if (Array.isArray(value)) return value.map(valueText).join(', ');
|
|
118
|
+
if (value instanceof Date) return value.toISOString().slice(0, 10);
|
|
119
|
+
return value === undefined || value === null ? '' : String(value);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function filterLabel(node: GridFilterItem | GridFilterGroup, label: (field: string) => string): string {
|
|
123
|
+
if ('items' in node) return `(${node.items.map((child) => filterLabel(child, label)).join(` ${node.logicOperator} `)})`;
|
|
124
|
+
return [label(node.field), node.operator, valueText(node.value)].filter(Boolean).join(' ');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const FN_LABELS: Record<string, string> = { sum: 'Sum', avg: 'Average', count: 'Count', min: 'Min', max: 'Max', unique: 'Unique' };
|
|
128
|
+
|
|
129
|
+
/** One chip per change: filter item, quick-filter words, sort key, grouping field, aggregation, visibility change, pivot. */
|
|
130
|
+
export function buildAiChips(applied: GridAiState, label: (field: string) => string): GridAiChip[] {
|
|
131
|
+
const chips: GridAiChip[] = [];
|
|
132
|
+
const { filterModel, sortModel, rowGroupingModel, aggregationModel, pivotModel, columnVisibilityModel } = applied;
|
|
133
|
+
if (filterModel) {
|
|
134
|
+
const items = filterModel.items ?? [];
|
|
135
|
+
items.forEach((item, i) => chips.push({ id: `filter:${i}`, part: 'filter', label: filterLabel(item, label) }));
|
|
136
|
+
const words = filterModel.quickFilterValues ?? [];
|
|
137
|
+
if (words.length > 0) chips.push({ id: 'filter:quick', part: 'filter', label: `Search: ${words.join(' ')}` });
|
|
138
|
+
if (items.length === 0 && words.length === 0) chips.push({ id: 'filter:clear', part: 'filter', label: 'No filters' });
|
|
139
|
+
}
|
|
140
|
+
if (sortModel) {
|
|
141
|
+
sortModel.forEach((s, i) => chips.push({ id: `sort:${i}`, part: 'sort', label: `Sort: ${label(s.field)}, ${s.sort === 'desc' ? 'descending' : 'ascending'}` }));
|
|
142
|
+
if (sortModel.length === 0) chips.push({ id: 'sort:clear', part: 'sort', label: 'No sorting' });
|
|
143
|
+
}
|
|
144
|
+
if (rowGroupingModel) {
|
|
145
|
+
rowGroupingModel.forEach((field, i) => chips.push({ id: `grouping:${i}`, part: 'grouping', label: `Group: ${label(field)}` }));
|
|
146
|
+
if (rowGroupingModel.length === 0) chips.push({ id: 'grouping:clear', part: 'grouping', label: 'No grouping' });
|
|
147
|
+
}
|
|
148
|
+
if (aggregationModel) {
|
|
149
|
+
const fields = Object.keys(aggregationModel);
|
|
150
|
+
fields.forEach((field) => {
|
|
151
|
+
const fn = aggregationModel[field];
|
|
152
|
+
chips.push({ id: `aggregation:${field}`, part: 'aggregation', label: `${FN_LABELS[fn] ?? fn}: ${label(field)}` });
|
|
153
|
+
});
|
|
154
|
+
if (fields.length === 0) chips.push({ id: 'aggregation:clear', part: 'aggregation', label: 'No summaries' });
|
|
155
|
+
}
|
|
156
|
+
if (pivotModel) {
|
|
157
|
+
const fields = [...pivotModel.rowFields, ...pivotModel.columnFields, ...pivotModel.valueFields.map((v) => v.field)];
|
|
158
|
+
chips.push({ id: 'pivot', part: 'pivot', label: fields.length > 0 ? `Pivot: ${fields.map(label).join(', ')}` : 'No pivot' });
|
|
159
|
+
}
|
|
160
|
+
if (columnVisibilityModel) {
|
|
161
|
+
for (const field of Object.keys(columnVisibilityModel)) {
|
|
162
|
+
chips.push({ id: `columnVisibility:${field}`, part: 'columnVisibility', label: `${columnVisibilityModel[field] ? 'Show' : 'Hide'}: ${label(field)}` });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return chips;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* `applied` without the change a chip stands for. A part left with no change is dropped, so
|
|
170
|
+
* `effectiveState` gives it its value from before the reply.
|
|
171
|
+
*/
|
|
172
|
+
export function removeAiChip(applied: GridAiState, id: string): GridAiState {
|
|
173
|
+
const next: GridAiState = { ...applied };
|
|
174
|
+
const sep = id.indexOf(':');
|
|
175
|
+
const part = sep < 0 ? id : id.slice(0, sep);
|
|
176
|
+
const rest = sep < 0 ? '' : id.slice(sep + 1);
|
|
177
|
+
const index = Number(rest);
|
|
178
|
+
if (part === 'filter' && next.filterModel) {
|
|
179
|
+
const model = { ...next.filterModel };
|
|
180
|
+
if (rest === 'quick') delete model.quickFilterValues;
|
|
181
|
+
else if (rest === 'clear') model.items = [];
|
|
182
|
+
else model.items = (model.items ?? []).filter((_, i) => i !== index);
|
|
183
|
+
if ((model.items ?? []).length === 0 && (model.quickFilterValues ?? []).length === 0) delete next.filterModel;
|
|
184
|
+
else next.filterModel = model;
|
|
185
|
+
} else if (part === 'sort' && next.sortModel) {
|
|
186
|
+
next.sortModel = next.sortModel.filter((_, i) => i !== index);
|
|
187
|
+
if (next.sortModel.length === 0 || rest === 'clear') delete next.sortModel;
|
|
188
|
+
} else if (part === 'grouping' && next.rowGroupingModel) {
|
|
189
|
+
next.rowGroupingModel = next.rowGroupingModel.filter((_, i) => i !== index);
|
|
190
|
+
if (next.rowGroupingModel.length === 0 || rest === 'clear') delete next.rowGroupingModel;
|
|
191
|
+
} else if (part === 'aggregation' && next.aggregationModel) {
|
|
192
|
+
const model = { ...next.aggregationModel };
|
|
193
|
+
delete model[rest];
|
|
194
|
+
if (Object.keys(model).length === 0) delete next.aggregationModel;
|
|
195
|
+
else next.aggregationModel = model;
|
|
196
|
+
} else if (part === 'columnVisibility' && next.columnVisibilityModel) {
|
|
197
|
+
const model = { ...next.columnVisibilityModel };
|
|
198
|
+
delete model[rest];
|
|
199
|
+
if (Object.keys(model).length === 0) delete next.columnVisibilityModel;
|
|
200
|
+
else next.columnVisibilityModel = model;
|
|
201
|
+
} else if (part === 'pivot') {
|
|
202
|
+
delete next.pivotModel;
|
|
203
|
+
}
|
|
204
|
+
return next;
|
|
205
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// The "validated" brand of the AI toolkit. `validateGridAiState` (in the `/ai` entry point) puts it on
|
|
2
|
+
// its result and on the result's `state`; the grid's `aiAssistant` applies a state only when it carries
|
|
3
|
+
// it. Shared by both bundles, so it holds no AI code: just the registered symbol and two helpers.
|
|
4
|
+
|
|
5
|
+
/** `Symbol.for('opengridx.ai.validated')`: the same symbol in every bundle and realm. */
|
|
6
|
+
export const GRID_AI_VALIDATED_BRAND: symbol = Symbol.for('opengridx.ai.validated');
|
|
7
|
+
|
|
8
|
+
/** Brands `value` (non-enumerable, so it does not change equality, spreading or JSON) and returns it. */
|
|
9
|
+
export function brandGridAiValidated<T extends object>(value: T): T {
|
|
10
|
+
Object.defineProperty(value, GRID_AI_VALIDATED_BRAND, { value: true, enumerable: false });
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Whether `value` carries the brand. */
|
|
15
|
+
export function isGridAiValidated(value: unknown): boolean {
|
|
16
|
+
return typeof value === 'object' && value !== null && (value as Record<symbol, unknown>)[GRID_AI_VALIDATED_BRAND] === true;
|
|
17
|
+
}
|