@opencorestack/opengridx 3.8.0 → 3.9.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 +47 -0
- package/dist/ai.cjs.map +1 -1
- package/dist/ai.es.js.map +1 -1
- package/dist/index.d.ts +342 -3
- package/dist/opengridx.css +1 -1
- package/dist/opengridx.es.js +6952 -6697
- package/dist/opengridx.es.js.map +1 -1
- package/dist/opengridx.umd.js +17 -17
- package/dist/opengridx.umd.js.map +1 -1
- package/docs/API_REFERENCE.md +121 -4
- package/docs/customization/theming.md +11 -2
- package/docs/features/ai-toolkit.md +1 -1
- package/lib/components/AiAssistant/GridAiAssistantPanel.css +2 -2
- package/lib/components/AiAssistant/GridAiAssistantPanel.tsx +16 -14
- package/lib/components/Cell/Cell.css +1 -3
- package/lib/components/Cell/CellErrorBoundary.tsx +17 -10
- package/lib/components/CellSelectionStats/CellSelectionStats.tsx +5 -3
- package/lib/components/ColumnGroupHeader/ColumnGroupHeader.css +1 -2
- package/lib/components/ColumnResizeHandle/ColumnResizeHandle.tsx +3 -1
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +6 -4
- package/lib/components/DataGrid/DataGrid.css +1 -28
- package/lib/components/DataGrid/GridAggregationFooter.tsx +3 -1
- package/lib/components/DataGrid/GridErrorOverlay.tsx +4 -2
- package/lib/components/DataGrid/GridListView.tsx +7 -5
- package/lib/components/DataGrid/GridLiveRegion.tsx +5 -3
- package/lib/components/DataGrid/GridLoadingOverlay.tsx +3 -1
- package/lib/components/DataGrid/GridPinnedRows.tsx +1 -1
- package/lib/components/DataGrid/GridRoot.tsx +8 -1
- package/lib/components/DataGrid/GridStandaloneColumnPanel.tsx +3 -1
- package/lib/components/DataGrid/GridViewport.tsx +3 -1
- package/lib/components/DataGrid/GridVirtualRows.tsx +3 -2
- package/lib/components/DataGrid/gridLocaleContext.ts +13 -0
- package/lib/components/FilterPanel/FilterPanel.tsx +25 -56
- package/lib/components/Header/ColumnMenu.css +5 -5
- package/lib/components/Header/ColumnMenu.tsx +17 -15
- package/lib/components/Header/Header.css +2 -11
- package/lib/components/Header/Header.tsx +8 -6
- package/lib/components/HeaderFilter/HeaderFilterCell.tsx +6 -37
- package/lib/components/HeaderFilter/HeaderFilterRow.tsx +3 -1
- package/lib/components/ListView/ListView.css +0 -16
- package/lib/components/ListView/ListViewRow.tsx +3 -1
- package/lib/components/Pagination/Pagination.tsx +15 -18
- package/lib/components/QuickFilter/QuickFilter.tsx +5 -2
- package/lib/components/Row/Row.tsx +9 -4
- package/lib/components/Toolbar/GlobalSearch.tsx +6 -3
- package/lib/components/Toolbar/GridToolbar.tsx +35 -29
- package/lib/components/Toolbar/PivotPanel.tsx +26 -24
- package/lib/components/ui/Checkbox.css +5 -5
- package/lib/components/ui/DragHandleIcon.tsx +3 -1
- package/lib/components/ui/ExpandIcon.css +3 -3
- package/lib/components/ui/ExpandIcon.tsx +3 -1
- package/lib/hooks/core/useGridRowInteractions.ts +13 -3
- package/lib/hooks/core/useGridToolbarStack.ts +2 -2
- package/lib/hooks/core/useGridVirtualization.ts +40 -25
- package/lib/hooks/core/useShallowStableValue.ts +29 -0
- package/lib/hooks/features/useGridAiAssistant.ts +52 -22
- package/lib/hooks/features/useGridSpanning.ts +1 -1
- package/lib/styles/base/reset.css +6 -7
- package/lib/theme/DataGridThemeProvider.tsx +9 -0
- package/lib/theme/__baseline__/default-theme.darwin.chromium.json +1 -1503
- package/lib/theme/__baseline__/default-theme.darwin.firefox.json +1 -1503
- package/lib/theme/__baseline__/default-theme.darwin.webkit.json +1 -1503
- package/lib/theme/types.ts +18 -0
- package/lib/types/locale.ts +344 -0
- package/lib/types/rows.ts +0 -2
- package/lib/utils/cellSelection.ts +0 -2
- package/lib/utils/editing/index.ts +1 -1
- package/lib/utils/filtering/filterInput.ts +45 -0
- package/lib/utils/filtering/index.ts +0 -28
- package/lib/utils/gridLocale.ts +241 -0
- package/lib/utils/pinning/index.ts +0 -107
- package/lib/utils/sorting/index.ts +42 -33
- package/lib/utils/values.ts +5 -1
- package/llms.txt +9 -3
- package/package.json +1 -1
- package/lib/theme/defaultStyles.ts +0 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
/** Equal when both hold the same keys with `Object.is`-equal values, one level of plain records deep. */
|
|
4
|
+
function isShallowEqual(a: unknown, b: unknown, depth = 1): boolean {
|
|
5
|
+
if (Object.is(a, b)) return true;
|
|
6
|
+
if (!a || !b || typeof a !== 'object' || typeof b !== 'object' || Array.isArray(a) || Array.isArray(b)) return false;
|
|
7
|
+
const ra = a as Record<string, unknown>;
|
|
8
|
+
const rb = b as Record<string, unknown>;
|
|
9
|
+
const keys = Object.keys(ra);
|
|
10
|
+
if (keys.length !== Object.keys(rb).length) return false;
|
|
11
|
+
return keys.every(key => Object.prototype.hasOwnProperty.call(rb, key)
|
|
12
|
+
&& (Object.is(ra[key], rb[key]) || (depth > 0 && isShallowEqual(ra[key], rb[key], depth - 1))));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* `value`, or the previous one while the new one holds the same entries. A consumer passing an
|
|
17
|
+
* inline object (`localeText={{ … }}`) then keeps one identity across renders, so what is memoised
|
|
18
|
+
* on it (the resolved locale, and every row that reads it) does not update needlessly. Functions
|
|
19
|
+
* inside are compared by identity: an inline function still counts as a change.
|
|
20
|
+
*/
|
|
21
|
+
export function useShallowStableValue<T>(value: T): T {
|
|
22
|
+
const [stable, setStable] = useState(value);
|
|
23
|
+
if (stable !== value && !isShallowEqual(stable, value)) {
|
|
24
|
+
// Adjusting state while rendering (React's documented pattern for derived state).
|
|
25
|
+
setStable(value);
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
return stable;
|
|
29
|
+
}
|
|
@@ -10,7 +10,9 @@ import type {
|
|
|
10
10
|
GridAiValidationError,
|
|
11
11
|
GridApi,
|
|
12
12
|
GridColDef,
|
|
13
|
+
GridLocaleText,
|
|
13
14
|
} from '../../types';
|
|
15
|
+
import { resolveGridLocale, type ResolvedGridLocale } from '../../utils/gridLocale';
|
|
14
16
|
import { attempt } from '../../utils/attempt';
|
|
15
17
|
import { isGridAiValidated } from '../../utils/aiBrand';
|
|
16
18
|
import {
|
|
@@ -32,6 +34,8 @@ export interface UseGridAiAssistantParams {
|
|
|
32
34
|
columns: readonly GridColDef[];
|
|
33
35
|
onApply?: (result: GridAiPromptResult) => void;
|
|
34
36
|
onError?: (error: unknown) => void;
|
|
37
|
+
/** The grid's `localeText`, for the status line and the placeholder. */
|
|
38
|
+
localeText?: GridLocaleText;
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
export interface UseGridAiAssistantResult {
|
|
@@ -48,7 +52,6 @@ export interface UseGridAiAssistantResult {
|
|
|
48
52
|
announcement: string;
|
|
49
53
|
}
|
|
50
54
|
|
|
51
|
-
const DEFAULT_PLACEHOLDER = 'Ask about this table…';
|
|
52
55
|
const NO_SUGGESTIONS: string[] = [];
|
|
53
56
|
const NO_ERRORS: GridAiValidationError[] = [];
|
|
54
57
|
const NOT_VALIDATED = 'The reply was not validated';
|
|
@@ -69,13 +72,38 @@ function isAbortError(error: unknown): boolean {
|
|
|
69
72
|
return typeof error === 'object' && error !== null && (error as { name?: unknown }).name === 'AbortError';
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
+
/** What the status line says, turned into text with the grid's locale at render time. */
|
|
76
|
+
type StatusLine =
|
|
77
|
+
| { kind: 'none' }
|
|
78
|
+
| { kind: 'thinking' }
|
|
79
|
+
| { kind: 'stopped' }
|
|
80
|
+
| { kind: 'undone' }
|
|
81
|
+
| { kind: 'removed' }
|
|
82
|
+
| { kind: 'applied'; count: number; ignored: number }
|
|
83
|
+
| { kind: 'notValidated' }
|
|
84
|
+
| { kind: 'failed'; error: unknown };
|
|
85
|
+
|
|
86
|
+
const NO_STATUS: StatusLine = { kind: 'none' };
|
|
87
|
+
const THINKING: StatusLine = { kind: 'thinking' };
|
|
88
|
+
const STOPPED: StatusLine = { kind: 'stopped' };
|
|
89
|
+
const UNDONE: StatusLine = { kind: 'undone' };
|
|
90
|
+
const REMOVED: StatusLine = { kind: 'removed' };
|
|
91
|
+
const NOT_VALIDATED_STATUS: StatusLine = { kind: 'notValidated' };
|
|
75
92
|
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
|
|
93
|
+
function statusLineText(line: StatusLine, locale: ResolvedGridLocale): string {
|
|
94
|
+
switch (line.kind) {
|
|
95
|
+
case 'none': return '';
|
|
96
|
+
case 'thinking': return locale.aiAssistantThinking;
|
|
97
|
+
case 'stopped': return locale.aiAssistantStopped;
|
|
98
|
+
case 'undone': return locale.aiAssistantUndone;
|
|
99
|
+
case 'removed': return locale.aiAssistantChangeRemoved;
|
|
100
|
+
case 'applied': return locale.aiAssistantApplied(line.count, line.ignored);
|
|
101
|
+
case 'notValidated': return locale.aiAssistantNotValidated;
|
|
102
|
+
case 'failed': {
|
|
103
|
+
const { error } = line;
|
|
104
|
+
return locale.aiAssistantFailed(error instanceof Error && error.message ? error.message : locale.aiAssistantUnknownError);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
79
107
|
}
|
|
80
108
|
|
|
81
109
|
/** Calls a consumer callback; a throw is warned about (development) and otherwise ignored. */
|
|
@@ -92,11 +120,13 @@ function notify<T>(callback: ((value: T) => void) | undefined, value: T, name: s
|
|
|
92
120
|
* `closeAiAssistant`.
|
|
93
121
|
*/
|
|
94
122
|
export function useGridAiAssistant(params: UseGridAiAssistantParams): UseGridAiAssistantResult {
|
|
95
|
-
const { apiRef, options, columns } = params;
|
|
123
|
+
const { apiRef, options, columns, localeText } = params;
|
|
124
|
+
const locale = useMemo(() => resolveGridLocale(localeText), [localeText]);
|
|
96
125
|
const enabled = Boolean(options);
|
|
97
126
|
const [open, setOpen] = useState(false);
|
|
98
127
|
const [status, setStatus] = useState<GridAiAssistantStatus>('idle');
|
|
99
|
-
const [
|
|
128
|
+
const [statusLine, setStatusLine] = useState<StatusLine>(NO_STATUS);
|
|
129
|
+
const statusText = statusLineText(statusLine, locale);
|
|
100
130
|
const [message, setMessage] = useState<string | null>(null);
|
|
101
131
|
const [errors, setErrors] = useState<GridAiValidationError[]>(NO_ERRORS);
|
|
102
132
|
const [applied, setApplied] = useState<Applied | null>(null);
|
|
@@ -135,7 +165,7 @@ export function useGridAiAssistant(params: UseGridAiAssistantParams): UseGridAiA
|
|
|
135
165
|
if (controllerRef.current) {
|
|
136
166
|
abortRequest();
|
|
137
167
|
setStatus('stopped');
|
|
138
|
-
|
|
168
|
+
setStatusLine(STOPPED);
|
|
139
169
|
}
|
|
140
170
|
setOpen(false);
|
|
141
171
|
const target = returnFocusRef.current;
|
|
@@ -148,9 +178,9 @@ export function useGridAiAssistant(params: UseGridAiAssistantParams): UseGridAiA
|
|
|
148
178
|
else openPanel(trigger);
|
|
149
179
|
}, [openPanel, closePanel]);
|
|
150
180
|
|
|
151
|
-
const fail = useCallback((
|
|
181
|
+
const fail = useCallback((line: StatusLine, error: unknown) => {
|
|
152
182
|
setStatus('error');
|
|
153
|
-
|
|
183
|
+
setStatusLine(line);
|
|
154
184
|
setMessage(null);
|
|
155
185
|
setErrors(NO_ERRORS);
|
|
156
186
|
notify(latestRef.current.params.onError, error, 'onAiAssistantError');
|
|
@@ -163,7 +193,7 @@ export function useGridAiAssistant(params: UseGridAiAssistantParams): UseGridAiA
|
|
|
163
193
|
if (process.env.NODE_ENV !== 'production') {
|
|
164
194
|
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
195
|
}
|
|
166
|
-
fail(
|
|
196
|
+
fail(NOT_VALIDATED_STATUS, new Error(NOT_VALIDATED));
|
|
167
197
|
return;
|
|
168
198
|
}
|
|
169
199
|
const allowed = pickAllowedParts(result.state, current.options?.parts ?? AI_PARTS);
|
|
@@ -177,7 +207,7 @@ export function useGridAiAssistant(params: UseGridAiAssistantParams): UseGridAiA
|
|
|
177
207
|
setMessage(typeof result.message === 'string' && result.message ? result.message : null);
|
|
178
208
|
setHistory((list) => [...list, { prompt, applied: state }]);
|
|
179
209
|
setStatus('applied');
|
|
180
|
-
|
|
210
|
+
setStatusLine({ kind: 'applied', count: buildAiChips(state, (f) => f).length, ignored: ignored.length });
|
|
181
211
|
notify(current.onApply, result, 'onAiAssistantApply');
|
|
182
212
|
}, [apiRef, fail]);
|
|
183
213
|
|
|
@@ -190,7 +220,7 @@ export function useGridAiAssistant(params: UseGridAiAssistantParams): UseGridAiA
|
|
|
190
220
|
const controller = new AbortController();
|
|
191
221
|
controllerRef.current = controller;
|
|
192
222
|
setStatus('running');
|
|
193
|
-
|
|
223
|
+
setStatusLine(THINKING);
|
|
194
224
|
setMessage(null);
|
|
195
225
|
setErrors(NO_ERRORS);
|
|
196
226
|
const context = { currentState: apiRef.current.getGridAiState(), history: earlier, signal: controller.signal };
|
|
@@ -207,10 +237,10 @@ export function useGridAiAssistant(params: UseGridAiAssistantParams): UseGridAiA
|
|
|
207
237
|
controllerRef.current = null;
|
|
208
238
|
if (controller.signal.aborted || isAbortError(error)) {
|
|
209
239
|
setStatus('stopped');
|
|
210
|
-
|
|
240
|
+
setStatusLine(STOPPED);
|
|
211
241
|
return;
|
|
212
242
|
}
|
|
213
|
-
fail(
|
|
243
|
+
fail({ kind: 'failed', error }, error);
|
|
214
244
|
},
|
|
215
245
|
);
|
|
216
246
|
}, [apiRef, abortRequest, applyResult, fail]);
|
|
@@ -219,7 +249,7 @@ export function useGridAiAssistant(params: UseGridAiAssistantParams): UseGridAiA
|
|
|
219
249
|
if (!controllerRef.current) return;
|
|
220
250
|
abortRequest();
|
|
221
251
|
setStatus('stopped');
|
|
222
|
-
|
|
252
|
+
setStatusLine(STOPPED);
|
|
223
253
|
}, [abortRequest]);
|
|
224
254
|
|
|
225
255
|
const undo = useCallback(() => {
|
|
@@ -230,7 +260,7 @@ export function useGridAiAssistant(params: UseGridAiAssistantParams): UseGridAiA
|
|
|
230
260
|
setErrors(NO_ERRORS);
|
|
231
261
|
setMessage(null);
|
|
232
262
|
setStatus('idle');
|
|
233
|
-
|
|
263
|
+
setStatusLine(UNDONE);
|
|
234
264
|
}, [apiRef]);
|
|
235
265
|
|
|
236
266
|
const removeChip = useCallback((id: string) => {
|
|
@@ -239,7 +269,7 @@ export function useGridAiAssistant(params: UseGridAiAssistantParams): UseGridAiA
|
|
|
239
269
|
const state = removeAiChip(last.state, id);
|
|
240
270
|
applyGridAiState(apiRef.current, effectiveState(state, last.previous));
|
|
241
271
|
setApplied({ state, previous: last.previous });
|
|
242
|
-
|
|
272
|
+
setStatusLine(REMOVED);
|
|
243
273
|
}, [apiRef]);
|
|
244
274
|
|
|
245
275
|
useLayoutEffect(() => {
|
|
@@ -261,7 +291,7 @@ export function useGridAiAssistant(params: UseGridAiAssistantParams): UseGridAiA
|
|
|
261
291
|
chips,
|
|
262
292
|
history,
|
|
263
293
|
suggestions: options.suggestions ?? NO_SUGGESTIONS,
|
|
264
|
-
placeholder: options.placeholder ??
|
|
294
|
+
placeholder: options.placeholder ?? locale.aiAssistantPlaceholder,
|
|
265
295
|
voiceAvailable: options.voice !== false && hasSpeechRecognition(),
|
|
266
296
|
canUndo: applied !== null,
|
|
267
297
|
submit,
|
|
@@ -270,7 +300,7 @@ export function useGridAiAssistant(params: UseGridAiAssistantParams): UseGridAiA
|
|
|
270
300
|
removeChip,
|
|
271
301
|
close: closePanel,
|
|
272
302
|
};
|
|
273
|
-
}, [isOpen, options, status, statusText, message, errors, chips, history, applied, submit, stop, undo, removeChip, closePanel]);
|
|
303
|
+
}, [isOpen, options, status, statusText, message, errors, chips, history, applied, submit, stop, undo, removeChip, closePanel, locale]);
|
|
274
304
|
|
|
275
305
|
return {
|
|
276
306
|
enabled,
|
|
@@ -356,7 +356,7 @@ function computeSection<R extends GridRowModel>(
|
|
|
356
356
|
|
|
357
357
|
for (let r = 0; r < rows.length; r++) {
|
|
358
358
|
const row = rows[r];
|
|
359
|
-
if (!row
|
|
359
|
+
if (!row) continue;
|
|
360
360
|
// Synthetic rows hold no data of their own: they are never a span origin (spans from above
|
|
361
361
|
// already end before them).
|
|
362
362
|
if (isSyntheticRowId(getRowId(row), rowMetaMap)) continue;
|
|
@@ -5,18 +5,17 @@
|
|
|
5
5
|
:where([class*="ogx"], [class*="ogx"] *)::after {
|
|
6
6
|
box-sizing: border-box;
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
user-select: none;
|
|
10
|
-
-webkit-user-select: none;
|
|
11
|
-
-moz-user-select: none;
|
|
12
|
-
-ms-user-select: none;
|
|
13
|
-
}
|
|
8
|
+
|
|
14
9
|
.ogx *:focus {
|
|
15
10
|
outline: none;
|
|
16
11
|
}
|
|
17
|
-
|
|
12
|
+
/* Background and border at zero specificity: a component's own button class (.ogx-button--contained,
|
|
13
|
+
the AI panel's suggestions, …) must win over this reset. Font and cursor keep their old weight. */
|
|
14
|
+
:where(.ogx button:not(.ogx-menu-item)) {
|
|
18
15
|
background: none;
|
|
19
16
|
border: none;
|
|
17
|
+
}
|
|
18
|
+
.ogx button:not(.ogx-menu-item) {
|
|
20
19
|
cursor: pointer;
|
|
21
20
|
font-family: inherit;
|
|
22
21
|
font-size: inherit;
|
|
@@ -303,6 +303,10 @@ function themeToCSS(theme: GridTheme): Record<string, string> {
|
|
|
303
303
|
set('--ogx-grid-pinned-right-shadow', g.pinnedRightShadow);
|
|
304
304
|
set('--ogx-checkbox-bg', g.checkboxBg);
|
|
305
305
|
set('--ogx-checkbox-border', g.checkboxBorder);
|
|
306
|
+
set('--ogx-checkbox-checked-bg', g.checkboxCheckedBackground);
|
|
307
|
+
set('--ogx-checkbox-checkmark', g.checkboxCheckmark);
|
|
308
|
+
set('--ogx-expand-icon-color', g.expandIconColor);
|
|
309
|
+
set('--ogx-expand-icon-hover-color', g.expandIconHoverColor);
|
|
306
310
|
}
|
|
307
311
|
|
|
308
312
|
if (theme.transitions) {
|
|
@@ -350,6 +354,11 @@ function themeToCSS(theme: GridTheme): Record<string, string> {
|
|
|
350
354
|
set('--ogx-overlay-item-selected-text', o.itemSelectedText);
|
|
351
355
|
set('--ogx-overlay-item-danger-bg', o.itemDangerBackground);
|
|
352
356
|
set('--ogx-overlay-item-danger-text', o.itemDangerText);
|
|
357
|
+
set('--ogx-overlay-item-indicator', o.itemIndicatorColor);
|
|
358
|
+
set('--ogx-overlay-item-selected-icon', o.itemSelectedIcon);
|
|
359
|
+
set('--ogx-overlay-icon', o.iconColor);
|
|
360
|
+
set('--ogx-overlay-section-label-text', o.sectionLabelText);
|
|
361
|
+
set('--ogx-overlay-divider', o.dividerColor);
|
|
353
362
|
set('--ogx-overlay-font-family', o.fontFamily);
|
|
354
363
|
set('--ogx-overlay-font-weight', o.fontWeight);
|
|
355
364
|
set('--ogx-overlay-font-size', o.fontSize);
|