@opencorestack/opengridx 3.4.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 +33 -0
- package/README.md +6 -0
- package/dist/ai.cjs +2 -1
- package/dist/ai.cjs.map +1 -1
- package/dist/ai.d.ts +203 -3
- package/dist/ai.es.js +2 -1
- package/dist/ai.es.js.map +1 -1
- package/dist/index.d.ts +248 -4
- package/dist/opengridx.css +1 -1
- package/dist/opengridx.es.js +7104 -5726
- package/dist/opengridx.es.js.map +1 -1
- package/dist/opengridx.umd.js +16 -16
- package/dist/opengridx.umd.js.map +1 -1
- package/docs/API_REFERENCE.md +88 -7
- package/docs/README.md +3 -1
- package/docs/architecture/datagrid-orchestration.md +9 -4
- 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 +3 -1
- package/docs/features/ai-toolkit.md +99 -5
- package/docs/features/cell-selection.md +8 -2
- 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 +20 -2
- package/docs/roadmap.md +2 -3
- package/lib/ai/agentTools.ts +160 -0
- package/lib/ai/index.ts +19 -2
- package/lib/ai/promptHandler.ts +109 -0
- package/lib/ai/types.ts +89 -25
- package/lib/ai/validate.ts +10 -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 +28 -0
- package/lib/components/Cell/Cell.tsx +6 -2
- package/lib/components/DataGrid/DataGrid.tsx +76 -3
- 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.tsx +2 -9
- 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/Toolbar/GridToolbar.tsx +51 -0
- package/lib/hooks/core/gridApiPlaceholder.ts +8 -0
- package/lib/hooks/core/gridToolbarHostContext.ts +5 -0
- package/lib/hooks/core/useGridApiMethods.ts +39 -0
- package/lib/hooks/core/useGridAriaRows.ts +5 -3
- package/lib/hooks/core/useGridCellSelection.ts +77 -11
- 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/features/useGridAiAssistant.ts +284 -0
- package/lib/hooks/features/useGridBatchEdit.ts +2 -2
- package/lib/index.ts +3 -0
- package/lib/styles/opengridx.css +2 -0
- package/lib/theme/DataGridThemeProvider.tsx +2 -0
- package/lib/theme/types.ts +2 -0
- package/lib/types/index.ts +219 -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/fill.ts +238 -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/llms.txt +44 -4
- package/package.json +1 -1
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import type { MutableRefObject } from 'react';
|
|
3
|
+
import type {
|
|
4
|
+
GridAiAssistantOptions,
|
|
5
|
+
GridAiAssistantPanelProps,
|
|
6
|
+
GridAiAssistantStatus,
|
|
7
|
+
GridAiHistoryEntry,
|
|
8
|
+
GridAiPromptResult,
|
|
9
|
+
GridAiState,
|
|
10
|
+
GridAiValidationError,
|
|
11
|
+
GridApi,
|
|
12
|
+
GridColDef,
|
|
13
|
+
} from '../../types';
|
|
14
|
+
import { attempt } from '../../utils/attempt';
|
|
15
|
+
import { isGridAiValidated } from '../../utils/aiBrand';
|
|
16
|
+
import {
|
|
17
|
+
AI_PARTS,
|
|
18
|
+
applyGridAiState,
|
|
19
|
+
buildAiChips,
|
|
20
|
+
dropEmptiedParts,
|
|
21
|
+
effectiveState,
|
|
22
|
+
pickAllowedParts,
|
|
23
|
+
removeAiChip,
|
|
24
|
+
snapshotParts,
|
|
25
|
+
} from '../../utils/aiAssistant';
|
|
26
|
+
|
|
27
|
+
export interface UseGridAiAssistantParams {
|
|
28
|
+
apiRef: MutableRefObject<GridApi>;
|
|
29
|
+
/** The `aiAssistant` prop. Without it the hook is inert. */
|
|
30
|
+
options: GridAiAssistantOptions | undefined;
|
|
31
|
+
/** Columns, for the chip labels (header names). */
|
|
32
|
+
columns: readonly GridColDef[];
|
|
33
|
+
onApply?: (result: GridAiPromptResult) => void;
|
|
34
|
+
onError?: (error: unknown) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface UseGridAiAssistantResult {
|
|
38
|
+
enabled: boolean;
|
|
39
|
+
open: boolean;
|
|
40
|
+
/** Opens the panel; focus returns to `trigger` (default: the focused element) when it closes. */
|
|
41
|
+
openPanel: (trigger?: HTMLElement | null) => void;
|
|
42
|
+
/** Opens the panel, or closes it when it is open. */
|
|
43
|
+
togglePanel: (trigger?: HTMLElement | null) => void;
|
|
44
|
+
closePanel: () => void;
|
|
45
|
+
/** Props of the panel while it is open, else null. */
|
|
46
|
+
panelProps: GridAiAssistantPanelProps | null;
|
|
47
|
+
/** The status line for the grid's live region. */
|
|
48
|
+
announcement: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const DEFAULT_PLACEHOLDER = 'Ask about this table…';
|
|
52
|
+
const NO_SUGGESTIONS: string[] = [];
|
|
53
|
+
const NO_ERRORS: GridAiValidationError[] = [];
|
|
54
|
+
const NOT_VALIDATED = 'The reply was not validated';
|
|
55
|
+
|
|
56
|
+
interface Applied {
|
|
57
|
+
/** The reply's parts the grid applied (chips are built from it). */
|
|
58
|
+
state: GridAiState;
|
|
59
|
+
/** The same parts as they were before: what Undo restores. */
|
|
60
|
+
previous: GridAiState;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Whether this browser has speech recognition. The microphone is never asked for here. */
|
|
64
|
+
export function hasSpeechRecognition(): boolean {
|
|
65
|
+
return typeof window !== 'undefined' && ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isAbortError(error: unknown): boolean {
|
|
69
|
+
return typeof error === 'object' && error !== null && (error as { name?: unknown }).name === 'AbortError';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function errorText(error: unknown): string {
|
|
73
|
+
return error instanceof Error && error.message ? error.message : 'Unknown error';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function changesText(count: number, ignored: number): string {
|
|
77
|
+
const applied = count === 0 ? 'No changes' : `Applied ${count} ${count === 1 ? 'change' : 'changes'}`;
|
|
78
|
+
return ignored > 0 ? `${applied}, ${ignored} ignored` : applied;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Calls a consumer callback; a throw is warned about (development) and otherwise ignored. */
|
|
82
|
+
function notify<T>(callback: ((value: T) => void) | undefined, value: T, name: string): void {
|
|
83
|
+
if (!callback) return;
|
|
84
|
+
const result = attempt(() => callback(value));
|
|
85
|
+
if (!result.ok && process.env.NODE_ENV !== 'production') console.warn(`[OpenGridX] ${name} threw.`, result.error);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The `aiAssistant` prompt panel: open state, the request (with Stop), applying a validated reply as
|
|
90
|
+
* one undoable step, chips, Undo, the session's prompts and the status line. Results are applied only
|
|
91
|
+
* when their `state` carries the brand of `validateGridAiState`. Installs `apiRef.openAiAssistant` /
|
|
92
|
+
* `closeAiAssistant`.
|
|
93
|
+
*/
|
|
94
|
+
export function useGridAiAssistant(params: UseGridAiAssistantParams): UseGridAiAssistantResult {
|
|
95
|
+
const { apiRef, options, columns } = params;
|
|
96
|
+
const enabled = Boolean(options);
|
|
97
|
+
const [open, setOpen] = useState(false);
|
|
98
|
+
const [status, setStatus] = useState<GridAiAssistantStatus>('idle');
|
|
99
|
+
const [statusText, setStatusText] = useState('');
|
|
100
|
+
const [message, setMessage] = useState<string | null>(null);
|
|
101
|
+
const [errors, setErrors] = useState<GridAiValidationError[]>(NO_ERRORS);
|
|
102
|
+
const [applied, setApplied] = useState<Applied | null>(null);
|
|
103
|
+
const [history, setHistory] = useState<GridAiHistoryEntry[]>([]);
|
|
104
|
+
|
|
105
|
+
const controllerRef = useRef<AbortController | null>(null);
|
|
106
|
+
const returnFocusRef = useRef<HTMLElement | null>(null);
|
|
107
|
+
const latestRef = useRef({ params, applied, history, open });
|
|
108
|
+
useLayoutEffect(() => {
|
|
109
|
+
latestRef.current = { params, applied, history, open };
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const labelOf = useMemo(() => {
|
|
113
|
+
const byField = new Map(columns.map((col) => [col.field, col.headerName || col.field]));
|
|
114
|
+
return (field: string) => byField.get(field) ?? field;
|
|
115
|
+
}, [columns]);
|
|
116
|
+
const chips = useMemo(() => (applied ? buildAiChips(applied.state, labelOf) : []), [applied, labelOf]);
|
|
117
|
+
|
|
118
|
+
const abortRequest = useCallback(() => {
|
|
119
|
+
const controller = controllerRef.current;
|
|
120
|
+
controllerRef.current = null;
|
|
121
|
+
if (controller) controller.abort();
|
|
122
|
+
}, []);
|
|
123
|
+
|
|
124
|
+
// Abort a running request when the grid unmounts.
|
|
125
|
+
useEffect(() => abortRequest, [abortRequest]);
|
|
126
|
+
|
|
127
|
+
const openPanel = useCallback((trigger?: HTMLElement | null) => {
|
|
128
|
+
if (!latestRef.current.params.options) return;
|
|
129
|
+
const active = typeof document === 'undefined' ? null : document.activeElement;
|
|
130
|
+
returnFocusRef.current = trigger ?? (active instanceof HTMLElement ? active : null);
|
|
131
|
+
setOpen(true);
|
|
132
|
+
}, []);
|
|
133
|
+
|
|
134
|
+
const closePanel = useCallback(() => {
|
|
135
|
+
if (controllerRef.current) {
|
|
136
|
+
abortRequest();
|
|
137
|
+
setStatus('stopped');
|
|
138
|
+
setStatusText('Stopped');
|
|
139
|
+
}
|
|
140
|
+
setOpen(false);
|
|
141
|
+
const target = returnFocusRef.current;
|
|
142
|
+
returnFocusRef.current = null;
|
|
143
|
+
if (target && target.isConnected) target.focus();
|
|
144
|
+
}, [abortRequest]);
|
|
145
|
+
|
|
146
|
+
const togglePanel = useCallback((trigger?: HTMLElement | null) => {
|
|
147
|
+
if (latestRef.current.open) closePanel();
|
|
148
|
+
else openPanel(trigger);
|
|
149
|
+
}, [openPanel, closePanel]);
|
|
150
|
+
|
|
151
|
+
const fail = useCallback((text: string, error: unknown) => {
|
|
152
|
+
setStatus('error');
|
|
153
|
+
setStatusText(text);
|
|
154
|
+
setMessage(null);
|
|
155
|
+
setErrors(NO_ERRORS);
|
|
156
|
+
notify(latestRef.current.params.onError, error, 'onAiAssistantError');
|
|
157
|
+
}, []);
|
|
158
|
+
|
|
159
|
+
const applyResult = useCallback((prompt: string, result: GridAiPromptResult) => {
|
|
160
|
+
const { params: current } = latestRef.current;
|
|
161
|
+
const isObject = typeof result === 'object' && result !== null;
|
|
162
|
+
if (!isObject || !isGridAiValidated(result.state)) {
|
|
163
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
164
|
+
console.warn('[OpenGridX] aiAssistant: the onPrompt result was not applied because its state was not validated. Return the result of createGridAiPromptHandler, or pass the model reply through validateGridAiState (from @opencorestack/opengridx/ai) and return its state.');
|
|
165
|
+
}
|
|
166
|
+
fail(NOT_VALIDATED, new Error(NOT_VALIDATED));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const allowed = pickAllowedParts(result.state, current.options?.parts ?? AI_PARTS);
|
|
170
|
+
const ignored = [...(Array.isArray(result.errors) ? result.errors : []), ...allowed.errors];
|
|
171
|
+
const state = dropEmptiedParts(allowed.state, ignored);
|
|
172
|
+
const api = apiRef.current;
|
|
173
|
+
const previous = snapshotParts(state, api.getGridAiState());
|
|
174
|
+
applyGridAiState(api, effectiveState(state, previous));
|
|
175
|
+
setApplied({ state, previous });
|
|
176
|
+
setErrors(ignored);
|
|
177
|
+
setMessage(typeof result.message === 'string' && result.message ? result.message : null);
|
|
178
|
+
setHistory((list) => [...list, { prompt, applied: state }]);
|
|
179
|
+
setStatus('applied');
|
|
180
|
+
setStatusText(changesText(buildAiChips(state, (f) => f).length, ignored.length));
|
|
181
|
+
notify(current.onApply, result, 'onAiAssistantApply');
|
|
182
|
+
}, [apiRef, fail]);
|
|
183
|
+
|
|
184
|
+
const submit = useCallback((text: string) => {
|
|
185
|
+
const prompt = text.trim();
|
|
186
|
+
const { params: current, history: earlier } = latestRef.current;
|
|
187
|
+
const onPrompt = current.options?.onPrompt;
|
|
188
|
+
if (!prompt || !onPrompt) return;
|
|
189
|
+
abortRequest();
|
|
190
|
+
const controller = new AbortController();
|
|
191
|
+
controllerRef.current = controller;
|
|
192
|
+
setStatus('running');
|
|
193
|
+
setStatusText('Thinking…');
|
|
194
|
+
setMessage(null);
|
|
195
|
+
setErrors(NO_ERRORS);
|
|
196
|
+
const context = { currentState: apiRef.current.getGridAiState(), history: earlier, signal: controller.signal };
|
|
197
|
+
const call = attempt(() => onPrompt(prompt, context));
|
|
198
|
+
const pending = call.ok ? Promise.resolve(call.value) : Promise.reject(call.error);
|
|
199
|
+
pending.then(
|
|
200
|
+
(result) => {
|
|
201
|
+
if (controllerRef.current !== controller) return;
|
|
202
|
+
controllerRef.current = null;
|
|
203
|
+
applyResult(prompt, result);
|
|
204
|
+
},
|
|
205
|
+
(error: unknown) => {
|
|
206
|
+
if (controllerRef.current !== controller) return;
|
|
207
|
+
controllerRef.current = null;
|
|
208
|
+
if (controller.signal.aborted || isAbortError(error)) {
|
|
209
|
+
setStatus('stopped');
|
|
210
|
+
setStatusText('Stopped');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
fail(`The assistant could not answer: ${errorText(error)}`, error);
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
}, [apiRef, abortRequest, applyResult, fail]);
|
|
217
|
+
|
|
218
|
+
const stop = useCallback(() => {
|
|
219
|
+
if (!controllerRef.current) return;
|
|
220
|
+
abortRequest();
|
|
221
|
+
setStatus('stopped');
|
|
222
|
+
setStatusText('Stopped');
|
|
223
|
+
}, [abortRequest]);
|
|
224
|
+
|
|
225
|
+
const undo = useCallback(() => {
|
|
226
|
+
const last = latestRef.current.applied;
|
|
227
|
+
if (!last) return;
|
|
228
|
+
applyGridAiState(apiRef.current, last.previous);
|
|
229
|
+
setApplied(null);
|
|
230
|
+
setErrors(NO_ERRORS);
|
|
231
|
+
setMessage(null);
|
|
232
|
+
setStatus('idle');
|
|
233
|
+
setStatusText('Undone');
|
|
234
|
+
}, [apiRef]);
|
|
235
|
+
|
|
236
|
+
const removeChip = useCallback((id: string) => {
|
|
237
|
+
const last = latestRef.current.applied;
|
|
238
|
+
if (!last) return;
|
|
239
|
+
const state = removeAiChip(last.state, id);
|
|
240
|
+
applyGridAiState(apiRef.current, effectiveState(state, last.previous));
|
|
241
|
+
setApplied({ state, previous: last.previous });
|
|
242
|
+
setStatusText('Change removed');
|
|
243
|
+
}, [apiRef]);
|
|
244
|
+
|
|
245
|
+
useLayoutEffect(() => {
|
|
246
|
+
const api = apiRef.current;
|
|
247
|
+
api.openAiAssistant = () => openPanel();
|
|
248
|
+
api.closeAiAssistant = () => {
|
|
249
|
+
if (latestRef.current.open) closePanel();
|
|
250
|
+
};
|
|
251
|
+
}, [apiRef, openPanel, closePanel]);
|
|
252
|
+
|
|
253
|
+
const isOpen = enabled && open;
|
|
254
|
+
const panelProps = useMemo<GridAiAssistantPanelProps | null>(() => {
|
|
255
|
+
if (!isOpen || !options) return null;
|
|
256
|
+
return {
|
|
257
|
+
status,
|
|
258
|
+
statusText,
|
|
259
|
+
message,
|
|
260
|
+
errors,
|
|
261
|
+
chips,
|
|
262
|
+
history,
|
|
263
|
+
suggestions: options.suggestions ?? NO_SUGGESTIONS,
|
|
264
|
+
placeholder: options.placeholder ?? DEFAULT_PLACEHOLDER,
|
|
265
|
+
voiceAvailable: options.voice !== false && hasSpeechRecognition(),
|
|
266
|
+
canUndo: applied !== null,
|
|
267
|
+
submit,
|
|
268
|
+
stop,
|
|
269
|
+
undo,
|
|
270
|
+
removeChip,
|
|
271
|
+
close: closePanel,
|
|
272
|
+
};
|
|
273
|
+
}, [isOpen, options, status, statusText, message, errors, chips, history, applied, submit, stop, undo, removeChip, closePanel]);
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
enabled,
|
|
277
|
+
open: isOpen,
|
|
278
|
+
openPanel,
|
|
279
|
+
togglePanel,
|
|
280
|
+
closePanel,
|
|
281
|
+
panelProps,
|
|
282
|
+
announcement: enabled ? statusText : '',
|
|
283
|
+
};
|
|
284
|
+
}
|
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
} from '../../utils/editing/commit';
|
|
12
12
|
import type { GridCellChange } from '../../utils/editing/commit';
|
|
13
13
|
|
|
14
|
-
/** What made a commit: history records `edit`, `paste` and `
|
|
15
|
-
export type GridEditSource = 'edit' | 'paste' | 'clear' | 'undo' | 'redo';
|
|
14
|
+
/** What made a commit: history records `edit`, `paste`, `clear` and `fill`; `undo` / `redo` replay it. */
|
|
15
|
+
export type GridEditSource = 'edit' | 'paste' | 'clear' | 'fill' | 'undo' | 'redo';
|
|
16
16
|
|
|
17
17
|
/** One cell to write in a batch edit. */
|
|
18
18
|
export interface GridBatchCellEdit {
|
package/lib/index.ts
CHANGED
|
@@ -112,6 +112,9 @@ export type {
|
|
|
112
112
|
GridEditSkipReason,
|
|
113
113
|
GridBatchEditResult,
|
|
114
114
|
GridClipboardPasteResult,
|
|
115
|
+
GridFillDirection,
|
|
116
|
+
GridFillResult,
|
|
117
|
+
GridFillValueParams,
|
|
115
118
|
GridBeforeClipboardPasteParams,
|
|
116
119
|
GridHistoryChangeParams,
|
|
117
120
|
GridUndoRedoOptions,
|
package/lib/styles/opengridx.css
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
@import '../components/ui/Input.css';
|
|
5
5
|
@import '../components/DataGrid/DataGrid.css';
|
|
6
6
|
@import '../components/Header/Header.css';
|
|
7
|
+
@import '../components/HeaderFilter/HeaderFilter.css';
|
|
7
8
|
@import '../components/Row/Row.css';
|
|
8
9
|
@import '../components/Cell/Cell.css';
|
|
9
10
|
@import '../components/Cell/GridEditInputCell.css';
|
|
@@ -27,3 +28,4 @@
|
|
|
27
28
|
@import '../components/ColumnGroupHeader/ColumnGroupHeader.css';
|
|
28
29
|
@import '../components/Tooltip/Tooltip.css';
|
|
29
30
|
@import '../components/CellSelectionStats/CellSelectionStats.css';
|
|
31
|
+
@import '../components/AiAssistant/GridAiAssistantPanel.css';
|
|
@@ -11,6 +11,7 @@ const primaryDerived: Record<string, string> = {
|
|
|
11
11
|
'--ogx-grid-cell-focus-border': 'var(--ogx-color-primary)',
|
|
12
12
|
'--ogx-range-background': 'color-mix(in srgb, var(--ogx-color-primary) 12%, transparent)',
|
|
13
13
|
'--ogx-range-border': 'var(--ogx-color-primary)',
|
|
14
|
+
'--ogx-fill-handle-color': 'var(--ogx-range-border)',
|
|
14
15
|
'--ogx-toolbar-btn-primary-bg': 'var(--ogx-color-primary)',
|
|
15
16
|
'--ogx-toolbar-btn-primary-hover': 'var(--ogx-color-primary-dark)',
|
|
16
17
|
'--ogx-toolbar-input-focus-border': 'var(--ogx-color-primary)',
|
|
@@ -285,6 +286,7 @@ function themeToCSS(theme: GridTheme): Record<string, string> {
|
|
|
285
286
|
set('--ogx-grid-cell-focus-border', g.cellFocusBorder);
|
|
286
287
|
set('--ogx-range-background', g.rangeBackground);
|
|
287
288
|
set('--ogx-range-border', g.rangeBorder);
|
|
289
|
+
set('--ogx-fill-handle-color', g.fillHandleColor);
|
|
288
290
|
set('--ogx-grid-pinned-left-shadow', g.pinnedLeftShadow);
|
|
289
291
|
set('--ogx-grid-pinned-right-shadow', g.pinnedRightShadow);
|
|
290
292
|
set('--ogx-checkbox-bg', g.checkboxBg);
|
package/lib/theme/types.ts
CHANGED
|
@@ -163,6 +163,8 @@ export interface GridThemeGrid {
|
|
|
163
163
|
rangeBackground?: string;
|
|
164
164
|
/** Outline of a selected cell range (`--ogx-range-border`). Default: the primary colour. @since v3.3 */
|
|
165
165
|
rangeBorder?: string;
|
|
166
|
+
/** The fill handle on the range's corner (`--ogx-fill-handle-color`). Default: the range border colour. @since v3.5 */
|
|
167
|
+
fillHandleColor?: string;
|
|
166
168
|
pinnedLeftShadow?: string;
|
|
167
169
|
pinnedRightShadow?: string;
|
|
168
170
|
checkboxBg?: string;
|
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. */
|
|
@@ -196,6 +208,13 @@ export interface GridColDef<R extends GridValidRowModel = GridRowModel> {
|
|
|
196
208
|
* @since v3.1
|
|
197
209
|
*/
|
|
198
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;
|
|
199
218
|
/** Function to format a value into a human-readable string. */
|
|
200
219
|
valueFormatter?(params: GridValueFormatterParams<R>): string;
|
|
201
220
|
/** Custom component or element to render in the cell. */
|
|
@@ -318,6 +337,32 @@ export interface GridValueGetterParams<R extends GridValidRowModel = GridRowMode
|
|
|
318
337
|
value: unknown;
|
|
319
338
|
}
|
|
320
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
|
+
|
|
321
366
|
/** Parameters passed to `GridColDef.valueParser`. @since v3.4 */
|
|
322
367
|
export interface GridValueParserParams<R extends GridValidRowModel = GridRowModel> {
|
|
323
368
|
/** The row the text is pasted into, as stored now. */
|
|
@@ -893,6 +938,8 @@ export interface GridLocaleText {
|
|
|
893
938
|
* @since v3.3
|
|
894
939
|
*/
|
|
895
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;
|
|
896
943
|
}
|
|
897
944
|
|
|
898
945
|
/**
|
|
@@ -918,6 +965,18 @@ export interface DataGridProps<R extends GridValidRowModel = GridRowModel> {
|
|
|
918
965
|
* Defaults to `false`.
|
|
919
966
|
*/
|
|
920
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;
|
|
921
980
|
/** If true, the grid height will adjust to match the total height of its rows. */
|
|
922
981
|
autoHeight?: boolean;
|
|
923
982
|
/**
|
|
@@ -1045,6 +1104,16 @@ export interface DataGridProps<R extends GridValidRowModel = GridRowModel> {
|
|
|
1045
1104
|
undoRedo?: boolean | GridUndoRedoOptions;
|
|
1046
1105
|
/** Fired when what can be undone or redone changes (`undoRedo`). @since v3.4 */
|
|
1047
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;
|
|
1048
1117
|
|
|
1049
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. */
|
|
1050
1119
|
loading?: boolean;
|
|
@@ -1138,8 +1207,13 @@ export interface DataGridProps<R extends GridValidRowModel = GridRowModel> {
|
|
|
1138
1207
|
/** Initial expansion depth for Tree Data. */
|
|
1139
1208
|
defaultGroupingExpansionDepth?: number;
|
|
1140
1209
|
|
|
1141
|
-
/**
|
|
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
|
+
*/
|
|
1142
1214
|
rowGroupingModel?: GridRowGroupingModel;
|
|
1215
|
+
/** Fired when the row grouping model changes (`apiRef.setRowGroupingModel`, the AI assistant). @since v3.5 */
|
|
1216
|
+
onRowGroupingModelChange?: (model: GridRowGroupingModel) => void;
|
|
1143
1217
|
/** Controlled state for data aggregation (e.g., { salary: 'sum' }). */
|
|
1144
1218
|
aggregationModel?: GridAggregationModel;
|
|
1145
1219
|
/** Callback fired when aggregation model changes. */
|
|
@@ -1203,6 +1277,8 @@ export interface DataGridProps<R extends GridValidRowModel = GridRowModel> {
|
|
|
1203
1277
|
* (plus `slotProps.cellSelectionStats`). @since v3.3
|
|
1204
1278
|
*/
|
|
1205
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>>;
|
|
1206
1282
|
};
|
|
1207
1283
|
/** Properties passed directly to custom slots. */
|
|
1208
1284
|
slotProps?: {
|
|
@@ -1220,6 +1296,8 @@ export interface DataGridProps<R extends GridValidRowModel = GridRowModel> {
|
|
|
1220
1296
|
footer?: Partial<GridFooterSlotProps> & Record<string, unknown>;
|
|
1221
1297
|
/** Merged over the `GridCellSelectionStatsSlotProps` the grid passes to the status bar. */
|
|
1222
1298
|
cellSelectionStats?: Partial<GridCellSelectionStatsSlotProps> & Record<string, unknown>;
|
|
1299
|
+
/** Extra props for `slots.aiAssistantPanel`. */
|
|
1300
|
+
aiAssistantPanel?: Record<string, unknown>;
|
|
1223
1301
|
};
|
|
1224
1302
|
|
|
1225
1303
|
/** When true, renders the grid as a single-column list of cards. Perfect for mobile/responsive views. */
|
|
@@ -1232,6 +1310,18 @@ export interface DataGridProps<R extends GridValidRowModel = GridRowModel> {
|
|
|
1232
1310
|
* column headers, supporting multiple levels of nesting.
|
|
1233
1311
|
*/
|
|
1234
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;
|
|
1235
1325
|
}
|
|
1236
1326
|
|
|
1237
1327
|
/**
|
|
@@ -1452,4 +1542,132 @@ export interface GridApi<R extends GridValidRowModel = GridRowModel> {
|
|
|
1452
1542
|
canRedo: () => boolean;
|
|
1453
1543
|
/** Forgets every action (undo and redo). @since v3.4 */
|
|
1454
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;
|
|
1455
1673
|
}
|