@opencorestack/opengridx 3.3.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/README.md +10 -1
- package/dist/ai.cjs +2 -0
- package/dist/ai.cjs.map +1 -0
- package/dist/ai.d.ts +366 -0
- package/dist/ai.es.js +2 -0
- package/dist/ai.es.js.map +1 -0
- package/dist/index.d.ts +396 -6
- package/dist/opengridx.css +1 -1
- package/dist/opengridx.es.js +8029 -6090
- package/dist/opengridx.es.js.map +1 -1
- package/dist/opengridx.umd.js +16 -14
- package/dist/opengridx.umd.js.map +1 -1
- package/docs/API_REFERENCE.md +182 -4
- package/docs/README.md +5 -1
- package/docs/architecture/datagrid-orchestration.md +19 -5
- package/docs/components/filter-panel.md +1 -0
- package/docs/components/header.md +4 -1
- package/docs/components/toolbar.md +7 -0
- package/docs/customization/theming.md +12 -1
- package/docs/features/ai-toolkit.md +213 -0
- package/docs/features/cell-selection.md +8 -2
- package/docs/features/clipboard.md +88 -0
- package/docs/features/editing-reordering.md +50 -0
- package/docs/features/fill-handle.md +125 -0
- package/docs/features/filtering.md +3 -0
- package/docs/features/header-filters.md +138 -0
- package/docs/features/keyboard-navigation.md +31 -2
- package/docs/features/undo-redo.md +79 -0
- package/docs/roadmap.md +3 -4
- package/lib/ai/agentTools.ts +160 -0
- package/lib/ai/coerce.ts +90 -0
- package/lib/ai/columns.ts +90 -0
- package/lib/ai/index.ts +33 -0
- package/lib/ai/promptHandler.ts +109 -0
- package/lib/ai/schema.ts +214 -0
- package/lib/ai/types.ts +152 -0
- package/lib/ai/validate.ts +296 -0
- package/lib/components/AiAssistant/GridAiAssistantArea.tsx +26 -0
- package/lib/components/AiAssistant/GridAiAssistantPanel.css +171 -0
- package/lib/components/AiAssistant/GridAiAssistantPanel.tsx +145 -0
- package/lib/components/AiAssistant/useSpeechInput.ts +74 -0
- package/lib/components/Cell/Cell.css +33 -0
- package/lib/components/Cell/Cell.tsx +6 -2
- package/lib/components/Cell/GridEditInputCell.css +12 -4
- package/lib/components/Cell/GridEditInputCell.tsx +19 -12
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.css +14 -0
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +3 -2
- package/lib/components/DataGrid/DataGrid.tsx +138 -5
- package/lib/components/DataGrid/GridLiveRegion.tsx +5 -2
- package/lib/components/DataGrid/GridPinnedRows.tsx +5 -1
- package/lib/components/DataGrid/GridVirtualRows.tsx +5 -1
- package/lib/components/FilterPanel/FilterPanel.css +14 -0
- package/lib/components/FilterPanel/FilterPanel.tsx +5 -11
- package/lib/components/Header/Header.tsx +44 -23
- package/lib/components/Header/headerColumnStyle.ts +28 -0
- package/lib/components/HeaderFilter/HeaderFilter.css +160 -0
- package/lib/components/HeaderFilter/HeaderFilterCell.tsx +370 -0
- package/lib/components/HeaderFilter/HeaderFilterOperatorMenu.tsx +124 -0
- package/lib/components/HeaderFilter/HeaderFilterRow.tsx +157 -0
- package/lib/components/ListView/ListView.css +2 -4
- package/lib/components/ListView/ListViewRow.tsx +2 -2
- package/lib/components/Toolbar/GlobalSearch.css +16 -0
- package/lib/components/Toolbar/GlobalSearch.tsx +3 -2
- package/lib/components/Toolbar/GridToolbar.tsx +51 -0
- package/lib/components/ui/Checkbox.tsx +16 -5
- package/lib/components/ui/Input.css +43 -0
- package/lib/components/ui/Input.tsx +24 -5
- package/lib/hooks/core/gridApiPlaceholder.ts +16 -1
- package/lib/hooks/core/gridToolbarHostContext.ts +5 -0
- package/lib/hooks/core/useDataGrid.ts +27 -0
- package/lib/hooks/core/useGridApiMethods.ts +39 -0
- package/lib/hooks/core/useGridAriaRows.ts +5 -3
- package/lib/hooks/core/useGridCellSelection.ts +107 -13
- package/lib/hooks/core/useGridClipboardPaste.ts +283 -0
- package/lib/hooks/core/useGridColumnsPanel.ts +24 -1
- package/lib/hooks/core/useGridControlledState.ts +27 -0
- package/lib/hooks/core/useGridFillHandle.ts +323 -0
- package/lib/hooks/core/useGridKeyboardNavigation.ts +29 -10
- package/lib/hooks/core/useGridToolbarProps.ts +19 -0
- package/lib/hooks/core/useGridUndoRedo.ts +220 -0
- package/lib/hooks/features/useGridAiAssistant.ts +284 -0
- package/lib/hooks/features/useGridBatchEdit.ts +217 -0
- package/lib/hooks/features/useGridEditing.ts +24 -45
- package/lib/index.ts +10 -0
- package/lib/styles/opengridx.css +4 -1
- package/lib/theme/DataGridThemeProvider.tsx +2 -0
- package/lib/theme/types.ts +2 -0
- package/lib/types/index.ts +343 -1
- package/lib/utils/aiAssistant.ts +205 -0
- package/lib/utils/aiBrand.ts +17 -0
- package/lib/utils/cellSelection.ts +25 -5
- package/lib/utils/editing/commit.ts +69 -0
- package/lib/utils/editing/fill.ts +238 -0
- package/lib/utils/editing/targets.ts +109 -0
- package/lib/utils/filtering/index.ts +14 -0
- package/lib/utils/focus/index.ts +9 -3
- package/lib/utils/headerFilters.ts +189 -0
- package/lib/utils/parsing.ts +252 -0
- package/llms.txt +97 -3
- package/package.json +8 -2
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { useCallback, useLayoutEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import type { GridBatchEditResult, GridColDef, GridEditSkipReason, GridRowId, GridRowModel } from '../../types';
|
|
3
|
+
import { attempt } from '../../utils/attempt';
|
|
4
|
+
import { getCellValue } from '../../utils/values';
|
|
5
|
+
import {
|
|
6
|
+
buildEditedRow,
|
|
7
|
+
invalidProcessedRowError,
|
|
8
|
+
isPromiseLike,
|
|
9
|
+
isSameValue,
|
|
10
|
+
reportEditError,
|
|
11
|
+
} from '../../utils/editing/commit';
|
|
12
|
+
import type { GridCellChange } from '../../utils/editing/commit';
|
|
13
|
+
|
|
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
|
+
|
|
17
|
+
/** One cell to write in a batch edit. */
|
|
18
|
+
export interface GridBatchCellEdit {
|
|
19
|
+
id: GridRowId;
|
|
20
|
+
field: string;
|
|
21
|
+
value: unknown;
|
|
22
|
+
/**
|
|
23
|
+
* Undo/redo: the value the cell must still hold. When it holds something else the cell is
|
|
24
|
+
* skipped with reason `'changed'` (values are compared, not row objects).
|
|
25
|
+
*/
|
|
26
|
+
expect?: { value: unknown };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A batch edit's result, plus the cell changes that were stored (what history records). */
|
|
30
|
+
export interface GridBatchEditOutcome {
|
|
31
|
+
result: GridBatchEditResult;
|
|
32
|
+
committed: GridCellChange[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface UseGridBatchEditParams<R extends GridRowModel> {
|
|
36
|
+
/** The rendered row store (`state.rows.idRowsLookup`). */
|
|
37
|
+
rowsLookup: ReadonlyMap<GridRowId, R>;
|
|
38
|
+
columns: GridColDef<R>[];
|
|
39
|
+
processRowUpdate?: (newRow: R, oldRow: R) => R | Promise<R>;
|
|
40
|
+
onProcessRowUpdateError?: (error: unknown) => void;
|
|
41
|
+
/** Stores rows in one update (`replaceRows` of the row store). */
|
|
42
|
+
replaceRows: (rows: ReadonlyMap<GridRowId, R>) => void;
|
|
43
|
+
/** Receives the stored changes of every commit that is not an undo or redo (history). */
|
|
44
|
+
onCommitted?: (changes: GridCellChange[], source: GridEditSource) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface UseGridBatchEditReturn<R extends GridRowModel> {
|
|
48
|
+
/**
|
|
49
|
+
* Writes cells through `valueSetter` → `processRowUpdate` (once per row, rows in parallel) →
|
|
50
|
+
* the store (one update for every row that succeeded). A row that throws or rejects is reported
|
|
51
|
+
* to `onProcessRowUpdateError` and does not stop the others. Cells whose value does not change
|
|
52
|
+
* are left alone. Resolves when every row has settled.
|
|
53
|
+
*/
|
|
54
|
+
applyEdits: (edits: readonly GridBatchCellEdit[], source: GridEditSource) => Promise<GridBatchEditOutcome>;
|
|
55
|
+
/** The latest row under `id`, including rows stored but not rendered yet. */
|
|
56
|
+
getRow: (id: GridRowId) => R | undefined;
|
|
57
|
+
/** Stores one committed row (a single-cell edit), so `getRow` sees it before the next render. */
|
|
58
|
+
storeRow: (id: GridRowId, row: R) => void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface RowCell<R extends GridRowModel> {
|
|
62
|
+
field: string;
|
|
63
|
+
value: unknown;
|
|
64
|
+
colDef: GridColDef<R> | undefined;
|
|
65
|
+
order: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface RowGroup<R extends GridRowModel> {
|
|
69
|
+
id: GridRowId;
|
|
70
|
+
row: R;
|
|
71
|
+
cells: RowCell<R>[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
type Settled = { id: GridRowId; ok: true; processed: unknown } | { id: GridRowId; ok: false; error: unknown };
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Batch edits: several cells committed together through the single-edit pipeline (paste, range clear,
|
|
78
|
+
* undo, redo). One `processRowUpdate` per row, one store update for the batch, failures isolated per
|
|
79
|
+
* row and reported through the same path as single edits. The returned functions are stable.
|
|
80
|
+
*/
|
|
81
|
+
export function useGridBatchEdit<R extends GridRowModel>(params: UseGridBatchEditParams<R>): UseGridBatchEditReturn<R> {
|
|
82
|
+
const latestRef = useRef(params);
|
|
83
|
+
// Rows stored since the last render. The rendered lookup catches up on the next render, when
|
|
84
|
+
// this is emptied; until then an undo right after a paste still reads the pasted rows.
|
|
85
|
+
const overlayRef = useRef(new Map<GridRowId, R>());
|
|
86
|
+
const warnedFieldsRef = useRef(new Set<string>());
|
|
87
|
+
useLayoutEffect(() => {
|
|
88
|
+
latestRef.current = params;
|
|
89
|
+
overlayRef.current.clear();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const getRow = useCallback((id: GridRowId): R | undefined => {
|
|
93
|
+
const pending = overlayRef.current.get(id);
|
|
94
|
+
return pending === undefined ? latestRef.current.rowsLookup.get(id) : pending;
|
|
95
|
+
}, []);
|
|
96
|
+
|
|
97
|
+
const store = useCallback((rows: Map<GridRowId, R>) => {
|
|
98
|
+
if (rows.size === 0) return;
|
|
99
|
+
rows.forEach((row, id) => { overlayRef.current.set(id, row); });
|
|
100
|
+
latestRef.current.replaceRows(rows);
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
const storeRow = useCallback((id: GridRowId, row: R) => {
|
|
104
|
+
store(new Map([[id, row]]));
|
|
105
|
+
}, [store]);
|
|
106
|
+
|
|
107
|
+
const applyEdits = useCallback((edits: readonly GridBatchCellEdit[], source: GridEditSource): Promise<GridBatchEditOutcome> => {
|
|
108
|
+
const { columns, processRowUpdate } = latestRef.current;
|
|
109
|
+
const report = (error: unknown) => reportEditError(error, latestRef.current.onProcessRowUpdateError);
|
|
110
|
+
const columnOrder = new Map<string, number>();
|
|
111
|
+
const columnByField = new Map<string, GridColDef<R>>();
|
|
112
|
+
columns.forEach((col, index) => {
|
|
113
|
+
columnOrder.set(col.field, index);
|
|
114
|
+
columnByField.set(col.field, col);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const skipped: { id: GridRowId; field: string; reason: GridEditSkipReason }[] = [];
|
|
118
|
+
const groups = new Map<GridRowId, RowGroup<R>>();
|
|
119
|
+
for (const edit of edits) {
|
|
120
|
+
const row = getRow(edit.id);
|
|
121
|
+
if (row === undefined) {
|
|
122
|
+
skipped.push({ id: edit.id, field: edit.field, reason: 'missing' });
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const colDef = columnByField.get(edit.field);
|
|
126
|
+
if (edit.expect && !isSameValue(getCellValue(row, edit.field, colDef), edit.expect.value)) {
|
|
127
|
+
skipped.push({ id: edit.id, field: edit.field, reason: 'changed' });
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
let group = groups.get(edit.id);
|
|
131
|
+
if (!group) {
|
|
132
|
+
group = { id: edit.id, row, cells: [] };
|
|
133
|
+
groups.set(edit.id, group);
|
|
134
|
+
}
|
|
135
|
+
const order = columnOrder.get(edit.field) ?? Number.MAX_SAFE_INTEGER;
|
|
136
|
+
const existing = group.cells.findIndex(cell => cell.field === edit.field);
|
|
137
|
+
const cell = { field: edit.field, value: edit.value, colDef, order };
|
|
138
|
+
// The same cell twice: the last value wins.
|
|
139
|
+
if (existing === -1) group.cells.push(cell);
|
|
140
|
+
else group.cells[existing] = cell;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const settled: Settled[] = [];
|
|
144
|
+
const pending: Promise<Settled>[] = [];
|
|
145
|
+
const warned = warnedFieldsRef.current;
|
|
146
|
+
groups.forEach(group => {
|
|
147
|
+
const { id, row } = group;
|
|
148
|
+
// Cells that already hold the value are not written; a row with none left is left alone.
|
|
149
|
+
const cells = group.cells
|
|
150
|
+
.filter(cell => !isSameValue(getCellValue(row, cell.field, cell.colDef), cell.value))
|
|
151
|
+
.sort((a, b) => a.order - b.order);
|
|
152
|
+
group.cells = cells;
|
|
153
|
+
if (cells.length === 0) return;
|
|
154
|
+
const outcome = attempt<unknown>(() => {
|
|
155
|
+
let newRow = row;
|
|
156
|
+
for (const cell of cells) newRow = buildEditedRow(newRow, cell, cell.colDef, warned);
|
|
157
|
+
return processRowUpdate ? processRowUpdate(newRow, row) : newRow;
|
|
158
|
+
});
|
|
159
|
+
if (!outcome.ok) {
|
|
160
|
+
settled.push({ id, ok: false, error: outcome.error });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const value = outcome.value;
|
|
164
|
+
if (isPromiseLike(value)) {
|
|
165
|
+
pending.push(Promise.resolve(value).then(
|
|
166
|
+
(processed): Settled => ({ id, ok: true, processed }),
|
|
167
|
+
(error: unknown): Settled => ({ id, ok: false, error }),
|
|
168
|
+
));
|
|
169
|
+
} else {
|
|
170
|
+
settled.push({ id, ok: true, processed: value });
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const finish = (all: Settled[]): GridBatchEditOutcome => {
|
|
175
|
+
const byId = new Map<GridRowId, Settled>();
|
|
176
|
+
all.forEach(entry => { byId.set(entry.id, entry); });
|
|
177
|
+
const updated: GridRowId[] = [];
|
|
178
|
+
const failed: { id: GridRowId; error: unknown }[] = [];
|
|
179
|
+
const committed: GridCellChange[] = [];
|
|
180
|
+
const stored = new Map<GridRowId, R>();
|
|
181
|
+
groups.forEach(group => {
|
|
182
|
+
const entry = byId.get(group.id);
|
|
183
|
+
if (!entry) return;
|
|
184
|
+
if (!entry.ok) {
|
|
185
|
+
failed.push({ id: group.id, error: entry.error });
|
|
186
|
+
report(entry.error);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const processed = entry.processed;
|
|
190
|
+
if (typeof processed !== 'object' || processed === null) {
|
|
191
|
+
const error = invalidProcessedRowError(processed, 'The row was not saved.');
|
|
192
|
+
failed.push({ id: group.id, error });
|
|
193
|
+
report(error);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const storedRow = processed as R;
|
|
197
|
+
stored.set(group.id, storedRow);
|
|
198
|
+
updated.push(group.id);
|
|
199
|
+
for (const cell of group.cells) {
|
|
200
|
+
const before = getCellValue(group.row, cell.field, cell.colDef);
|
|
201
|
+
const after = getCellValue(storedRow, cell.field, cell.colDef);
|
|
202
|
+
if (!isSameValue(before, after)) committed.push({ id: group.id, field: cell.field, before, after });
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
store(stored);
|
|
206
|
+
if (committed.length > 0 && source !== 'undo' && source !== 'redo') {
|
|
207
|
+
latestRef.current.onCommitted?.(committed, source);
|
|
208
|
+
}
|
|
209
|
+
return { result: { updated, failed, skipped }, committed };
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
if (pending.length === 0) return Promise.resolve(finish(settled));
|
|
213
|
+
return Promise.all(pending).then(results => finish([...settled, ...results]));
|
|
214
|
+
}, [getRow, store]);
|
|
215
|
+
|
|
216
|
+
return useMemo(() => ({ applyEdits, getRow, storeRow }), [applyEdits, getRow, storeRow]);
|
|
217
|
+
}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { useState, useCallback, useRef, useEffect, useLayoutEffect, useMemo } from 'react';
|
|
2
2
|
import type { GridColDef, GridRowId, GridRowModel } from '../../types';
|
|
3
3
|
import { attempt } from '../../utils/attempt';
|
|
4
|
+
import { getCellValue } from '../../utils/values';
|
|
5
|
+
import {
|
|
6
|
+
buildEditedRow,
|
|
7
|
+
invalidProcessedRowError,
|
|
8
|
+
isPromiseLike,
|
|
9
|
+
isSameValue,
|
|
10
|
+
reportEditError,
|
|
11
|
+
} from '../../utils/editing/commit';
|
|
12
|
+
import type { GridCellChange } from '../../utils/editing/commit';
|
|
4
13
|
|
|
5
14
|
export interface GridEditingState {
|
|
6
15
|
editingCell: {
|
|
@@ -23,6 +32,12 @@ export interface UseGridEditingParams<R extends GridRowModel> {
|
|
|
23
32
|
* grid resolved with getRowId), not on a field of the returned row.
|
|
24
33
|
*/
|
|
25
34
|
onRowChange?: (newRow: R, id: GridRowId) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Receives the cell change once `processRowUpdate` succeeded and the row was stored (undo/redo
|
|
37
|
+
* history): the value before and the stored value, both read through `getCellValue`. Not called
|
|
38
|
+
* when the stored value equals the previous one.
|
|
39
|
+
*/
|
|
40
|
+
onCommitted?: (changes: GridCellChange[]) => void;
|
|
26
41
|
}
|
|
27
42
|
|
|
28
43
|
export interface StopCellEditParams {
|
|
@@ -46,18 +61,6 @@ interface PendingCommit {
|
|
|
46
61
|
|
|
47
62
|
const RESOLVED: Promise<void> = Promise.resolve();
|
|
48
63
|
|
|
49
|
-
/** A misuse of the editing contract (e.g. processRowUpdate returned nothing), as opposed to a consumer rejection. */
|
|
50
|
-
class OpenGridXEditError extends Error {}
|
|
51
|
-
|
|
52
|
-
function isSameValue(a: unknown, b: unknown): boolean {
|
|
53
|
-
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
|
|
54
|
-
return Object.is(a, b);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
58
|
-
return typeof value === 'object' && value !== null && typeof (value as { then?: unknown }).then === 'function';
|
|
59
|
-
}
|
|
60
|
-
|
|
61
64
|
/**
|
|
62
65
|
* Cell edit sessions and their commits.
|
|
63
66
|
*
|
|
@@ -126,24 +129,21 @@ export function useGridEditing<R extends GridRowModel>(params: UseGridEditingPar
|
|
|
126
129
|
return RESOLVED;
|
|
127
130
|
}
|
|
128
131
|
|
|
129
|
-
const report = (error: unknown) =>
|
|
130
|
-
|
|
131
|
-
if (onError) {
|
|
132
|
-
onError(error);
|
|
133
|
-
} else if (process.env.NODE_ENV !== 'production' && error instanceof OpenGridXEditError) {
|
|
134
|
-
console.error(error.message);
|
|
135
|
-
}
|
|
136
|
-
};
|
|
132
|
+
const report = (error: unknown) => reportEditError(error, latestRef.current.onProcessRowUpdateError);
|
|
133
|
+
const colDef = columns?.find(c => c.field === cell.field);
|
|
137
134
|
|
|
138
135
|
const apply = (processed: unknown) => {
|
|
139
136
|
if (typeof processed !== 'object' || processed === null) {
|
|
140
|
-
report(
|
|
141
|
-
`[OpenGridX] processRowUpdate must return the updated row object (or a Promise of it); ` +
|
|
142
|
-
`it returned ${processed === null ? 'null' : typeof processed}. The edit was not saved and the editor stays open.`
|
|
143
|
-
));
|
|
137
|
+
report(invalidProcessedRowError(processed, 'The edit was not saved and the editor stays open.'));
|
|
144
138
|
return;
|
|
145
139
|
}
|
|
146
140
|
latestRef.current.onRowChange?.(processed as R, cell.id);
|
|
141
|
+
const { onCommitted } = latestRef.current;
|
|
142
|
+
if (onCommitted) {
|
|
143
|
+
const before = getCellValue(existingRow, cell.field, colDef);
|
|
144
|
+
const after = getCellValue(processed as R, cell.field, colDef);
|
|
145
|
+
if (!isSameValue(before, after)) onCommitted([{ id: cell.id, field: cell.field, before, after }]);
|
|
146
|
+
}
|
|
147
147
|
const now = editingCellRef.current;
|
|
148
148
|
if (sessionRef.current !== token || !now) return;
|
|
149
149
|
if (isSameValue(now.value, cell.value)) {
|
|
@@ -164,7 +164,6 @@ export function useGridEditing<R extends GridRowModel>(params: UseGridEditingPar
|
|
|
164
164
|
if (pendingRef.current.get(token) === entry) pendingRef.current.delete(token);
|
|
165
165
|
};
|
|
166
166
|
|
|
167
|
-
const colDef = columns?.find(c => c.field === cell.field);
|
|
168
167
|
const outcome = attempt<unknown>(() => {
|
|
169
168
|
const newRow = buildEditedRow(existingRow, cell, colDef, warnedFieldsRef.current);
|
|
170
169
|
return processRowUpdate ? processRowUpdate(newRow, existingRow) : newRow;
|
|
@@ -246,23 +245,3 @@ export function useGridEditing<R extends GridRowModel>(params: UseGridEditingPar
|
|
|
246
245
|
setEditCellValue
|
|
247
246
|
}), [editingCell, startCellEdit, stopCellEdit, setEditCellValue]);
|
|
248
247
|
}
|
|
249
|
-
|
|
250
|
-
function buildEditedRow<R extends GridRowModel>(
|
|
251
|
-
row: R,
|
|
252
|
-
cell: EditingCell,
|
|
253
|
-
colDef: GridColDef<R> | undefined,
|
|
254
|
-
warnedFields: Set<string>
|
|
255
|
-
): R {
|
|
256
|
-
if (colDef?.valueSetter) {
|
|
257
|
-
return colDef.valueSetter({ value: cell.value, row, field: cell.field });
|
|
258
|
-
}
|
|
259
|
-
if (colDef?.valueGetter && process.env.NODE_ENV !== 'production' && !warnedFields.has(cell.field)) {
|
|
260
|
-
warnedFields.add(cell.field);
|
|
261
|
-
console.warn(
|
|
262
|
-
`[OpenGridX] Column "${cell.field}" is editable and has a valueGetter but no valueSetter. ` +
|
|
263
|
-
`The edited value was written to row["${cell.field}"], which the valueGetter may ignore, so the cell ` +
|
|
264
|
-
'can keep showing the old value. Add a valueSetter that maps the value back onto the row.'
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
return { ...row, [cell.field]: cell.value };
|
|
268
|
-
}
|
package/lib/index.ts
CHANGED
|
@@ -93,6 +93,7 @@ export type {
|
|
|
93
93
|
GridValueGetterParams,
|
|
94
94
|
GridSortCellParams,
|
|
95
95
|
GridValueSetterParams,
|
|
96
|
+
GridValueParserParams,
|
|
96
97
|
GridValueFormatterParams,
|
|
97
98
|
GridRowParams,
|
|
98
99
|
GridCellParams,
|
|
@@ -108,6 +109,15 @@ export type {
|
|
|
108
109
|
GridCellSelectionChangeDetails,
|
|
109
110
|
GridSelectedCell,
|
|
110
111
|
GridCellSelectionStatsSlotProps,
|
|
112
|
+
GridEditSkipReason,
|
|
113
|
+
GridBatchEditResult,
|
|
114
|
+
GridClipboardPasteResult,
|
|
115
|
+
GridFillDirection,
|
|
116
|
+
GridFillResult,
|
|
117
|
+
GridFillValueParams,
|
|
118
|
+
GridBeforeClipboardPasteParams,
|
|
119
|
+
GridHistoryChangeParams,
|
|
120
|
+
GridUndoRedoOptions,
|
|
111
121
|
GridColumnOrder,
|
|
112
122
|
GridColumnOrderChangeParams,
|
|
113
123
|
GridRowOrderChangeParams,
|
package/lib/styles/opengridx.css
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
@import './base/variables.css';
|
|
2
2
|
@import './base/reset.css';
|
|
3
|
+
/* Shared primitives first, so component styles that restyle them win on equal specificity. */
|
|
4
|
+
@import '../components/ui/Input.css';
|
|
3
5
|
@import '../components/DataGrid/DataGrid.css';
|
|
4
6
|
@import '../components/Header/Header.css';
|
|
7
|
+
@import '../components/HeaderFilter/HeaderFilter.css';
|
|
5
8
|
@import '../components/Row/Row.css';
|
|
6
9
|
@import '../components/Cell/Cell.css';
|
|
7
10
|
@import '../components/Cell/GridEditInputCell.css';
|
|
@@ -16,7 +19,6 @@
|
|
|
16
19
|
@import '../components/ui/Checkbox.css';
|
|
17
20
|
@import '../components/ui/DragHandleIcon.css';
|
|
18
21
|
@import '../components/ui/ExpandIcon.css';
|
|
19
|
-
@import '../components/ui/Input.css';
|
|
20
22
|
@import '../components/Toolbar/GridToolbar.css';
|
|
21
23
|
@import '../components/Toolbar/PivotPanel.css';
|
|
22
24
|
@import '../components/FilterPanel/FilterPanel.css';
|
|
@@ -26,3 +28,4 @@
|
|
|
26
28
|
@import '../components/ColumnGroupHeader/ColumnGroupHeader.css';
|
|
27
29
|
@import '../components/Tooltip/Tooltip.css';
|
|
28
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;
|