@opencorestack/opengridx 3.2.2 → 3.4.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.
Files changed (88) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +16 -2
  3. package/dist/ai.cjs +1 -0
  4. package/dist/ai.cjs.map +1 -0
  5. package/dist/ai.d.ts +166 -0
  6. package/dist/ai.es.js +1 -0
  7. package/dist/ai.es.js.map +1 -0
  8. package/dist/index.d.ts +321 -3
  9. package/dist/opengridx.css +1 -1
  10. package/dist/opengridx.es.js +6483 -5291
  11. package/dist/opengridx.es.js.map +1 -1
  12. package/dist/opengridx.umd.js +16 -13
  13. package/dist/opengridx.umd.js.map +1 -1
  14. package/docs/API_REFERENCE.md +132 -3
  15. package/docs/README.md +4 -1
  16. package/docs/architecture/datagrid-orchestration.md +23 -6
  17. package/docs/components/header.md +25 -0
  18. package/docs/customization/theming.md +12 -1
  19. package/docs/features/ai-toolkit.md +119 -0
  20. package/docs/features/cell-selection.md +131 -0
  21. package/docs/features/clipboard.md +90 -0
  22. package/docs/features/editing-reordering.md +50 -0
  23. package/docs/features/keyboard-navigation.md +25 -2
  24. package/docs/features/undo-redo.md +79 -0
  25. package/docs/roadmap.md +64 -7
  26. package/lib/ai/coerce.ts +90 -0
  27. package/lib/ai/columns.ts +90 -0
  28. package/lib/ai/index.ts +16 -0
  29. package/lib/ai/schema.ts +214 -0
  30. package/lib/ai/types.ts +88 -0
  31. package/lib/ai/validate.ts +286 -0
  32. package/lib/components/Cell/Cell.css +35 -0
  33. package/lib/components/Cell/Cell.tsx +25 -13
  34. package/lib/components/Cell/CellErrorBoundary.tsx +3 -0
  35. package/lib/components/Cell/GridEditInputCell.css +12 -4
  36. package/lib/components/Cell/GridEditInputCell.tsx +19 -12
  37. package/lib/components/CellSelectionStats/CellSelectionStats.css +31 -0
  38. package/lib/components/CellSelectionStats/CellSelectionStats.tsx +32 -0
  39. package/lib/components/CellSelectionStats/GridCellSelectionStatsArea.tsx +24 -0
  40. package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.css +14 -0
  41. package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +3 -2
  42. package/lib/components/DataGrid/DataGrid.tsx +123 -3
  43. package/lib/components/DataGrid/GridLiveRegion.tsx +8 -2
  44. package/lib/components/DataGrid/GridPinnedRows.tsx +9 -0
  45. package/lib/components/DataGrid/GridToolbarSlot.tsx +5 -0
  46. package/lib/components/DataGrid/GridVirtualRows.tsx +9 -0
  47. package/lib/components/FilterPanel/FilterPanel.css +14 -0
  48. package/lib/components/FilterPanel/FilterPanel.tsx +5 -3
  49. package/lib/components/Header/Header.css +12 -0
  50. package/lib/components/Header/Header.tsx +17 -4
  51. package/lib/components/ListView/ListView.css +2 -4
  52. package/lib/components/ListView/ListViewRow.tsx +2 -2
  53. package/lib/components/Row/Row.tsx +38 -1
  54. package/lib/components/Toolbar/GlobalSearch.css +16 -0
  55. package/lib/components/Toolbar/GlobalSearch.tsx +3 -2
  56. package/lib/components/Toolbar/GridToolbar.tsx +10 -4
  57. package/lib/components/ui/Checkbox.tsx +26 -10
  58. package/lib/components/ui/Input.css +43 -0
  59. package/lib/components/ui/Input.tsx +24 -5
  60. package/lib/hooks/core/gridApiPlaceholder.ts +14 -1
  61. package/lib/hooks/core/useDataGrid.ts +27 -0
  62. package/lib/hooks/core/useGridCellSelection.ts +631 -0
  63. package/lib/hooks/core/useGridCellSelectionApi.ts +97 -0
  64. package/lib/hooks/core/useGridClipboardApi.ts +8 -2
  65. package/lib/hooks/core/useGridClipboardPaste.ts +283 -0
  66. package/lib/hooks/core/useGridControlledState.ts +38 -0
  67. package/lib/hooks/core/useGridKeyboardNavigation.ts +32 -0
  68. package/lib/hooks/core/useGridRowInteractions.ts +8 -4
  69. package/lib/hooks/core/useGridUndoRedo.ts +220 -0
  70. package/lib/hooks/features/useAggregation.ts +8 -6
  71. package/lib/hooks/features/useGridBatchEdit.ts +217 -0
  72. package/lib/hooks/features/useGridClipboard.ts +47 -2
  73. package/lib/hooks/features/useGridEditing.ts +39 -51
  74. package/lib/index.ts +16 -0
  75. package/lib/styles/base/variables.css +6 -1
  76. package/lib/styles/opengridx.css +3 -1
  77. package/lib/theme/DataGridThemeProvider.tsx +4 -0
  78. package/lib/theme/presets.ts +8 -0
  79. package/lib/theme/types.ts +4 -0
  80. package/lib/types/index.ts +254 -0
  81. package/lib/utils/attempt.ts +14 -0
  82. package/lib/utils/cellSelection.ts +238 -0
  83. package/lib/utils/editing/commit.ts +69 -0
  84. package/lib/utils/editing/targets.ts +109 -0
  85. package/lib/utils/headerWrap.ts +23 -0
  86. package/lib/utils/parsing.ts +252 -0
  87. package/llms.txt +71 -2
  88. package/package.json +14 -4
@@ -1,5 +1,15 @@
1
1
  import { useState, useCallback, useRef, useEffect, useLayoutEffect, useMemo } from 'react';
2
2
  import type { GridColDef, 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';
3
13
 
4
14
  export interface GridEditingState {
5
15
  editingCell: {
@@ -22,6 +32,12 @@ export interface UseGridEditingParams<R extends GridRowModel> {
22
32
  * grid resolved with getRowId), not on a field of the returned row.
23
33
  */
24
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;
25
41
  }
26
42
 
27
43
  export interface StopCellEditParams {
@@ -45,18 +61,6 @@ interface PendingCommit {
45
61
 
46
62
  const RESOLVED: Promise<void> = Promise.resolve();
47
63
 
48
- /** A misuse of the editing contract (e.g. processRowUpdate returned nothing), as opposed to a consumer rejection. */
49
- class OpenGridXEditError extends Error {}
50
-
51
- function isSameValue(a: unknown, b: unknown): boolean {
52
- if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
53
- return Object.is(a, b);
54
- }
55
-
56
- function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
57
- return typeof value === 'object' && value !== null && typeof (value as { then?: unknown }).then === 'function';
58
- }
59
-
60
64
  /**
61
65
  * Cell edit sessions and their commits.
62
66
  *
@@ -95,6 +99,10 @@ export function useGridEditing<R extends GridRowModel>(params: UseGridEditingPar
95
99
  setEditingCellState(next);
96
100
  }, []);
97
101
 
102
+ // The in-flight branch below re-runs the commit through this ref: a callback that refers to
103
+ // itself is read before its declaration, which React Compiler rejects.
104
+ const commitRef = useRef<((token: number, cell: EditingCell) => Promise<void>) | null>(null);
105
+
98
106
  const commit = useCallback((token: number, cell: EditingCell): Promise<void> => {
99
107
  const inFlight = pendingRef.current.get(token);
100
108
  if (inFlight) {
@@ -103,7 +111,7 @@ export function useGridEditing<R extends GridRowModel>(params: UseGridEditingPar
103
111
  return inFlight.promise.then(() => {
104
112
  const now = editingCellRef.current;
105
113
  if (sessionRef.current !== token || !now || isSameValue(now.value, inFlight.value)) return;
106
- return commit(token, now);
114
+ return commitRef.current?.(token, now);
107
115
  });
108
116
  }
109
117
 
@@ -121,24 +129,21 @@ export function useGridEditing<R extends GridRowModel>(params: UseGridEditingPar
121
129
  return RESOLVED;
122
130
  }
123
131
 
124
- const report = (error: unknown) => {
125
- const onError = latestRef.current.onProcessRowUpdateError;
126
- if (onError) {
127
- onError(error);
128
- } else if (process.env.NODE_ENV !== 'production' && error instanceof OpenGridXEditError) {
129
- console.error(error.message);
130
- }
131
- };
132
+ const report = (error: unknown) => reportEditError(error, latestRef.current.onProcessRowUpdateError);
133
+ const colDef = columns?.find(c => c.field === cell.field);
132
134
 
133
135
  const apply = (processed: unknown) => {
134
136
  if (typeof processed !== 'object' || processed === null) {
135
- report(new OpenGridXEditError(
136
- `[OpenGridX] processRowUpdate must return the updated row object (or a Promise of it); ` +
137
- `it returned ${processed === null ? 'null' : typeof processed}. The edit was not saved and the editor stays open.`
138
- ));
137
+ report(invalidProcessedRowError(processed, 'The edit was not saved and the editor stays open.'));
139
138
  return;
140
139
  }
141
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
+ }
142
147
  const now = editingCellRef.current;
143
148
  if (sessionRef.current !== token || !now) return;
144
149
  if (isSameValue(now.value, cell.value)) {
@@ -159,18 +164,18 @@ export function useGridEditing<R extends GridRowModel>(params: UseGridEditingPar
159
164
  if (pendingRef.current.get(token) === entry) pendingRef.current.delete(token);
160
165
  };
161
166
 
162
- let result: unknown;
163
- try {
164
- const colDef = columns?.find(c => c.field === cell.field);
167
+ const outcome = attempt<unknown>(() => {
165
168
  const newRow = buildEditedRow(existingRow, cell, colDef, warnedFieldsRef.current);
166
- result = processRowUpdate ? processRowUpdate(newRow, existingRow) : newRow;
167
- } catch (error) {
169
+ return processRowUpdate ? processRowUpdate(newRow, existingRow) : newRow;
170
+ });
171
+ if (!outcome.ok) {
168
172
  // Consumer code (valueSetter / processRowUpdate) threw: report it, keep the editor open.
169
173
  finish();
170
- report(error);
174
+ report(outcome.error);
171
175
  settle();
172
176
  return promise;
173
177
  }
178
+ const result = outcome.value;
174
179
 
175
180
  const complete = (processed: unknown) => {
176
181
  finish();
@@ -194,6 +199,9 @@ export function useGridEditing<R extends GridRowModel>(params: UseGridEditingPar
194
199
  }
195
200
  return promise;
196
201
  }, [setEditingCell]);
202
+ useLayoutEffect(() => {
203
+ commitRef.current = commit;
204
+ }, [commit]);
197
205
 
198
206
  const startCellEdit = useCallback((editParams: { id: GridRowId; field: string; value: unknown }) => {
199
207
  const current = editingCellRef.current;
@@ -237,23 +245,3 @@ export function useGridEditing<R extends GridRowModel>(params: UseGridEditingPar
237
245
  setEditCellValue
238
246
  }), [editingCell, startCellEdit, stopCellEdit, setEditCellValue]);
239
247
  }
240
-
241
- function buildEditedRow<R extends GridRowModel>(
242
- row: R,
243
- cell: EditingCell,
244
- colDef: GridColDef<R> | undefined,
245
- warnedFields: Set<string>
246
- ): R {
247
- if (colDef?.valueSetter) {
248
- return colDef.valueSetter({ value: cell.value, row, field: cell.field });
249
- }
250
- if (colDef?.valueGetter && process.env.NODE_ENV !== 'production' && !warnedFields.has(cell.field)) {
251
- warnedFields.add(cell.field);
252
- console.warn(
253
- `[OpenGridX] Column "${cell.field}" is editable and has a valueGetter but no valueSetter. ` +
254
- `The edited value was written to row["${cell.field}"], which the valueGetter may ignore, so the cell ` +
255
- 'can keep showing the old value. Add a valueSetter that maps the value back onto the row.'
256
- );
257
- }
258
- return { ...row, [cell.field]: cell.value };
259
- }
package/lib/index.ts CHANGED
@@ -21,6 +21,8 @@ export type { GridToolbarProps, ToolbarButtonRenderProps, ToolbarQuickFilterRend
21
21
  export { GridTooltip } from './components/Tooltip/Tooltip';
22
22
  export type { GridTooltipProps } from './components/Tooltip/Tooltip';
23
23
  export { ColumnVisibilityPanel } from './components/ColumnVisibilityPanel/ColumnVisibilityPanel';
24
+ export { CellSelectionStats } from './components/CellSelectionStats/CellSelectionStats';
25
+ export type { CellSelectionStatsProps } from './components/CellSelectionStats/CellSelectionStats';
24
26
  export type { ColumnVisibilityPanelProps } from './components/ColumnVisibilityPanel/ColumnVisibilityPanel';
25
27
 
26
28
  export { useGridApiRef } from './hooks/core/useGridApiRef';
@@ -91,6 +93,7 @@ export type {
91
93
  GridValueGetterParams,
92
94
  GridSortCellParams,
93
95
  GridValueSetterParams,
96
+ GridValueParserParams,
94
97
  GridValueFormatterParams,
95
98
  GridRowParams,
96
99
  GridCellParams,
@@ -99,6 +102,19 @@ export type {
99
102
  GridPinnedPosition,
100
103
  GridColumnVisibilityModel,
101
104
  GridRowSelectionModel,
105
+ GridCellCoordinates,
106
+ GridCellRange,
107
+ GridCellSelectionModel,
108
+ GridCellSelectionReason,
109
+ GridCellSelectionChangeDetails,
110
+ GridSelectedCell,
111
+ GridCellSelectionStatsSlotProps,
112
+ GridEditSkipReason,
113
+ GridBatchEditResult,
114
+ GridClipboardPasteResult,
115
+ GridBeforeClipboardPasteParams,
116
+ GridHistoryChangeParams,
117
+ GridUndoRedoOptions,
102
118
  GridColumnOrder,
103
119
  GridColumnOrderChangeParams,
104
120
  GridRowOrderChangeParams,
@@ -93,7 +93,10 @@
93
93
  --ogx-grid-row-selected-background: var(--ogx-color-primary-light);
94
94
  --ogx-grid-row-selected-hover-background: #dbeafe;
95
95
  --ogx-grid-cell-focus-border: var(--ogx-color-primary);
96
- --ogx-grid-pinned-left-shadow: 4px 0 24px -4px rgba(0, 0, 0, 0.05);
96
+ /* Cell range selection (cellSelection, v3.3): tint over the range and its outline. */
97
+ --ogx-range-background: rgba(59, 130, 246, 0.12);
98
+ --ogx-range-border: var(--ogx-color-primary);
99
+ --ogx-grid-pinned-left-shadow: 4px 0 24px -4px rgba(0, 0, 0, 0.05);
97
100
  --ogx-grid-pinned-right-shadow: -4px 0 24px -4px rgba(0, 0, 0, 0.05);
98
101
  --ogx-skeleton-base: #f0f0f0;
99
102
  --ogx-skeleton-highlight: #f8f8f8;
@@ -142,6 +145,8 @@
142
145
  --ogx-grid-row-selected-background: #1e3a5f;
143
146
  --ogx-grid-row-selected-hover-background: #1d4ed8;
144
147
  --ogx-grid-cell-focus-border: #60a5fa;
148
+ --ogx-range-background: rgba(96, 165, 250, 0.18);
149
+ --ogx-range-border: #60a5fa;
145
150
  --ogx-checkbox-bg: #1e293b;
146
151
  --ogx-checkbox-border: #64748b;
147
152
  --ogx-grid-pinned-left-shadow: 4px 0 24px -4px rgba(0, 0, 0, 0.3);
@@ -1,5 +1,7 @@
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';
5
7
  @import '../components/Row/Row.css';
@@ -16,7 +18,6 @@
16
18
  @import '../components/ui/Checkbox.css';
17
19
  @import '../components/ui/DragHandleIcon.css';
18
20
  @import '../components/ui/ExpandIcon.css';
19
- @import '../components/ui/Input.css';
20
21
  @import '../components/Toolbar/GridToolbar.css';
21
22
  @import '../components/Toolbar/PivotPanel.css';
22
23
  @import '../components/FilterPanel/FilterPanel.css';
@@ -25,3 +26,4 @@
25
26
  @import '../components/ListView/ListView.css';
26
27
  @import '../components/ColumnGroupHeader/ColumnGroupHeader.css';
27
28
  @import '../components/Tooltip/Tooltip.css';
29
+ @import '../components/CellSelectionStats/CellSelectionStats.css';
@@ -9,6 +9,8 @@ const primaryDerived: Record<string, string> = {
9
9
  '--ogx-grid-row-selected-background': 'var(--ogx-color-primary-light)',
10
10
  '--ogx-grid-row-selected-hover-background': 'color-mix(in srgb, var(--ogx-color-primary) 14%, var(--ogx-color-primary-light))',
11
11
  '--ogx-grid-cell-focus-border': 'var(--ogx-color-primary)',
12
+ '--ogx-range-background': 'color-mix(in srgb, var(--ogx-color-primary) 12%, transparent)',
13
+ '--ogx-range-border': 'var(--ogx-color-primary)',
12
14
  '--ogx-toolbar-btn-primary-bg': 'var(--ogx-color-primary)',
13
15
  '--ogx-toolbar-btn-primary-hover': 'var(--ogx-color-primary-dark)',
14
16
  '--ogx-toolbar-input-focus-border': 'var(--ogx-color-primary)',
@@ -281,6 +283,8 @@ function themeToCSS(theme: GridTheme): Record<string, string> {
281
283
  set('--ogx-grid-row-selected-background', g.rowSelectedBackground);
282
284
  set('--ogx-grid-row-selected-hover-background', g.rowSelectedHoverBackground);
283
285
  set('--ogx-grid-cell-focus-border', g.cellFocusBorder);
286
+ set('--ogx-range-background', g.rangeBackground);
287
+ set('--ogx-range-border', g.rangeBorder);
284
288
  set('--ogx-grid-pinned-left-shadow', g.pinnedLeftShadow);
285
289
  set('--ogx-grid-pinned-right-shadow', g.pinnedRightShadow);
286
290
  set('--ogx-checkbox-bg', g.checkboxBg);
@@ -21,6 +21,8 @@ export const darkTheme: GridTheme = {
21
21
  rowSelectedBackground: '#1e3a5f',
22
22
  rowSelectedHoverBackground: '#1d4ed8',
23
23
  cellFocusBorder: '#60a5fa',
24
+ rangeBackground: 'rgba(96, 165, 250, 0.18)',
25
+ rangeBorder: '#60a5fa',
24
26
  checkboxBg: '#1e293b',
25
27
  checkboxBorder: '#64748b',
26
28
  },
@@ -92,6 +94,8 @@ export const roseTheme: GridTheme = {
92
94
  rowSelectedBackground: '#ffe4e6',
93
95
  rowSelectedHoverBackground: '#fecdd3',
94
96
  cellFocusBorder: '#e11d48',
97
+ rangeBackground: 'rgba(225, 29, 72, 0.10)',
98
+ rangeBorder: '#e11d48',
95
99
  },
96
100
  };
97
101
 
@@ -110,6 +114,8 @@ export const emeraldTheme: GridTheme = {
110
114
  rowSelectedBackground: '#d1fae5',
111
115
  rowSelectedHoverBackground: '#a7f3d0',
112
116
  cellFocusBorder: '#059669',
117
+ rangeBackground: 'rgba(5, 150, 105, 0.12)',
118
+ rangeBorder: '#059669',
113
119
  },
114
120
  };
115
121
 
@@ -128,6 +134,8 @@ export const amberTheme: GridTheme = {
128
134
  rowSelectedBackground: '#fef3c7',
129
135
  rowSelectedHoverBackground: '#fde68a',
130
136
  cellFocusBorder: '#d97706',
137
+ rangeBackground: 'rgba(217, 119, 6, 0.12)',
138
+ rangeBorder: '#d97706',
131
139
  },
132
140
  };
133
141
 
@@ -159,6 +159,10 @@ export interface GridThemeGrid {
159
159
  rowSelectedBackground?: string;
160
160
  rowSelectedHoverBackground?: string;
161
161
  cellFocusBorder?: string;
162
+ /** Tint over a selected cell range (`--ogx-range-background`). Default: the primary colour at 12%. @since v3.3 */
163
+ rangeBackground?: string;
164
+ /** Outline of a selected cell range (`--ogx-range-border`). Default: the primary colour. @since v3.3 */
165
+ rangeBorder?: string;
162
166
  pinnedLeftShadow?: string;
163
167
  pinnedRightShadow?: string;
164
168
  checkboxBg?: string;
@@ -144,6 +144,12 @@ export interface GridColDef<R extends GridValidRowModel = GridRowModel> {
144
144
  align?: GridAlignment;
145
145
  /** Content alignment within the header cell. */
146
146
  headerAlign?: GridAlignment;
147
+ /**
148
+ * Wraps this column's header title onto several lines instead of cutting it with an ellipsis.
149
+ * Overrides the grid's `wrapHeaderText`. The header keeps `headerHeight`: the title shows as many
150
+ * lines as fit and ends in an ellipsis if it is still longer. Raise `headerHeight` for more lines.
151
+ */
152
+ wrapHeaderText?: boolean;
147
153
  /** If false, sorting is disabled for this column. */
148
154
  sortable?: boolean;
149
155
  /** If false, filtering is disabled for this column. */
@@ -172,6 +178,14 @@ export interface GridColDef<R extends GridValidRowModel = GridRowModel> {
172
178
  * @since v3.0
173
179
  */
174
180
  valueSetter?(params: GridValueSetterParams<R>): R;
181
+ /**
182
+ * Turns pasted text into this column's value (clipboard paste), instead of the default parser for
183
+ * the column `type` (see `docs/features/clipboard.md`). Use it for locales such as `1.234,5` or for
184
+ * custom codes. Throw (or return `undefined`) to reject the text: the cell is skipped with reason
185
+ * `'invalidValue'`. The value then goes through `valueSetter` and `processRowUpdate` like an edit.
186
+ * @since v3.4
187
+ */
188
+ valueParser?(text: string, params: GridValueParserParams<R>): unknown;
175
189
  /**
176
190
  * Custom ascending comparator for client-side sorting by this column, used instead of the built-in
177
191
  * type-aware comparison. `v1` / `v2` are the cell values (`valueGetter` applied) and **include
@@ -234,6 +248,16 @@ export interface GridColDef<R extends GridValidRowModel = GridRowModel> {
234
248
  * @default true
235
249
  */
236
250
  exportable?: boolean;
251
+
252
+ /**
253
+ * Example values for this column, put into the schema from `getGridAiSchema`
254
+ * (`@opencorestack/opengridx/ai`) to help a language model write filters ("Acme" → `customer`).
255
+ * **These values are sent to the model.** The grid never reads row data for the schema; only what
256
+ * you list here leaves the app, so list nothing private. Strings, numbers and booleans are used
257
+ * (at most 10); other values are ignored. Not used by the grid itself.
258
+ * @since v3.4
259
+ */
260
+ aiExamples?: unknown[];
237
261
  }
238
262
 
239
263
  /** Column definition used exclusively in List View mode. */
@@ -294,6 +318,14 @@ export interface GridValueGetterParams<R extends GridValidRowModel = GridRowMode
294
318
  value: unknown;
295
319
  }
296
320
 
321
+ /** Parameters passed to `GridColDef.valueParser`. @since v3.4 */
322
+ export interface GridValueParserParams<R extends GridValidRowModel = GridRowModel> {
323
+ /** The row the text is pasted into, as stored now. */
324
+ row: R;
325
+ field: string;
326
+ colDef: GridColDef<R>;
327
+ }
328
+
297
329
  /**
298
330
  * Parameters passed to the `valueSetter` function.
299
331
  * @since v3.0
@@ -658,6 +690,68 @@ export interface GridPaginationModel {
658
690
 
659
691
  export type GridRowSelectionModel = GridRowId[];
660
692
 
693
+ /** One cell, addressed by its row id and column field. @since v3.3 */
694
+ export interface GridCellCoordinates {
695
+ id: GridRowId;
696
+ field: string;
697
+ }
698
+
699
+ /**
700
+ * A rectangle of cells between two corners. `anchor` is the active cell: it keeps focus, is the cell
701
+ * an edit starts in, and stays put while the range is extended. `head` is the corner that moves with
702
+ * Shift+arrows or the pointer, and the one scrolled into view. A single cell has `anchor` equal to `head`.
703
+ * @since v3.3
704
+ */
705
+ export interface GridCellRange {
706
+ anchor: GridCellCoordinates;
707
+ head: GridCellCoordinates;
708
+ }
709
+
710
+ /**
711
+ * The cell selection: zero or one range in 3.3 (the array form leaves room for several ranges later).
712
+ * Corners are ids and fields, so the rectangle follows its corner rows through sorting and filtering.
713
+ * @since v3.3
714
+ */
715
+ export type GridCellSelectionModel = readonly GridCellRange[];
716
+
717
+ /** What changed the cell selection. @since v3.3 */
718
+ export type GridCellSelectionReason = 'pointer' | 'keyboard' | 'selectAll' | 'api' | 'clear' | 'dataChange';
719
+
720
+ /** Second argument of `onCellSelectionModelChange`. @since v3.3 */
721
+ export interface GridCellSelectionChangeDetails {
722
+ reason: GridCellSelectionReason;
723
+ }
724
+
725
+ /** One cell of the selected range, as returned by `apiRef.getSelectedCells()`. @since v3.3 */
726
+ export interface GridSelectedCell {
727
+ id: GridRowId;
728
+ field: string;
729
+ /** The cell's value, read through the column's `valueGetter`. */
730
+ value: unknown;
731
+ }
732
+
733
+ /**
734
+ * Props the grid passes to `slots.cellSelectionStats` (the status bar of `showCellSelectionStats`),
735
+ * before `slotProps.cellSelectionStats` is merged over them. Rendered only while more than one cell
736
+ * is selected.
737
+ * @since v3.3
738
+ */
739
+ export interface GridCellSelectionStatsSlotProps {
740
+ apiRef: React.MutableRefObject<GridApi>;
741
+ /** Cells in the rectangle (rows × columns), synthetic rows and span-covered positions included. */
742
+ cellCount: number;
743
+ rowCount: number;
744
+ columnCount: number;
745
+ /** Non-empty cells of data rows. */
746
+ count: number;
747
+ /** Cells holding a number. */
748
+ numericCount: number;
749
+ /** Sum of the numeric cells, or `null` when there are none. */
750
+ sum: number | null;
751
+ /** Average of the numeric cells, or `null` when there are none. */
752
+ average: number | null;
753
+ }
754
+
661
755
  export interface GridPinnedColumns {
662
756
  left?: string[];
663
757
  right?: string[];
@@ -735,6 +829,54 @@ export interface GridRowScrollEndParams {
735
829
  viewportHeight: number;
736
830
  }
737
831
 
832
+ /**
833
+ * Why a cell of a batch edit (paste, range clear, undo, redo) was not written:
834
+ * - `'notEditable'`: the column is not `editable`, `isCellEditable` returned false, or the row is a
835
+ * synthetic row (group header, subtotal, tree auto-parent, pivot Grand Total);
836
+ * - `'invalidValue'`: the pasted text does not parse for the column type (or `valueParser` rejected it);
837
+ * - `'changed'`: undo/redo only, the cell no longer holds the value the history recorded;
838
+ * - `'missing'`: undo/redo only, the row is gone.
839
+ * @since v3.4
840
+ */
841
+ export type GridEditSkipReason = 'notEditable' | 'invalidValue' | 'changed' | 'missing';
842
+
843
+ /** What a batch edit (paste, range clear, undo, redo) did. @since v3.4 */
844
+ export interface GridBatchEditResult {
845
+ /** Rows whose `processRowUpdate` succeeded and that were stored, in the order they were first touched. */
846
+ updated: GridRowId[];
847
+ /** Rows whose `valueSetter` or `processRowUpdate` threw or rejected (also reported to `onProcessRowUpdateError`). */
848
+ failed: { id: GridRowId; error: unknown }[];
849
+ /** Cells that were not written, with the reason. */
850
+ skipped: { id: GridRowId; field: string; reason: GridEditSkipReason }[];
851
+ }
852
+
853
+ /** `onClipboardPaste` details: the batch edit result plus the text that was pasted. @since v3.4 */
854
+ export interface GridClipboardPasteResult extends GridBatchEditResult {
855
+ text: string;
856
+ }
857
+
858
+ /** `onBeforeClipboardPaste` params. @since v3.4 */
859
+ export interface GridBeforeClipboardPasteParams {
860
+ /** The clipboard text (`text/plain`). */
861
+ text: string;
862
+ /** The cell the paste starts from (the range's top-left cell, or the focused cell). */
863
+ anchor: GridCellCoordinates | null;
864
+ }
865
+
866
+ /** `onHistoryChange` params. @since v3.4 */
867
+ export interface GridHistoryChangeParams {
868
+ canUndo: boolean;
869
+ canRedo: boolean;
870
+ /** Number of actions that can be undone. */
871
+ size: number;
872
+ }
873
+
874
+ /** `undoRedo` options. @since v3.4 */
875
+ export interface GridUndoRedoOptions {
876
+ /** How many actions are kept; the oldest is dropped beyond it. Default: 100. */
877
+ limit?: number;
878
+ }
879
+
738
880
  /** Overrideable user-visible strings for internationalisation. All fields are optional; defaults match the built-in English strings. */
739
881
  export interface GridLocaleText {
740
882
  /** Label before the rows-per-page select. Default: `"Rows per page:"` */
@@ -745,6 +887,12 @@ export interface GridLocaleText {
745
887
  paginationPage?: (page: number, pageCount: number) => string;
746
888
  /** Empty-state label. When set, overrides the `noRowsLabel` prop on DataGrid. Default: `"No Data"` */
747
889
  noRowsLabel?: string;
890
+ /**
891
+ * Screen-reader announcement after the cell range changes (`cellSelection`). Receives (cells, rows,
892
+ * columns). Default: `` (c, r, col) => `${c} cells selected, ${r} rows by ${col} columns` `` (singular for 1).
893
+ * @since v3.3
894
+ */
895
+ cellSelectionAnnouncement?: (cellCount: number, rowCount: number, columnCount: number) => string;
748
896
  }
749
897
 
750
898
  /**
@@ -763,6 +911,13 @@ export interface DataGridProps<R extends GridValidRowModel = GridRowModel> {
763
911
  rowHeight?: number;
764
912
  /** Height of the header row in pixels. Defaults to 56. */
765
913
  headerHeight?: number;
914
+ /**
915
+ * Wraps header titles onto several lines instead of cutting them with an ellipsis. The header
916
+ * keeps `headerHeight` (2 lines at the default 56px, 3 at 72px, 4 at 88px); a title that still
917
+ * does not fit ends in an ellipsis. `GridColDef.wrapHeaderText` overrides it per column.
918
+ * Defaults to `false`.
919
+ */
920
+ wrapHeaderText?: boolean;
766
921
  /** If true, the grid height will adjust to match the total height of its rows. */
767
922
  autoHeight?: boolean;
768
923
  /**
@@ -840,6 +995,57 @@ export interface DataGridProps<R extends GridValidRowModel = GridRowModel> {
840
995
  */
841
996
  disableClipboardCopy?: boolean;
842
997
 
998
+ /**
999
+ * Lets users select a rectangle of cells (drag, Shift+click, Shift+arrows, Ctrl/Cmd+A) and copy it
1000
+ * with Ctrl/Cmd+C as tab-separated text. While on, Ctrl/Cmd+C copies the cell range (the focused
1001
+ * cell at minimum) instead of the selected rows; `apiRef.copySelectedRows()` still copies rows.
1002
+ * Default: false. @since v3.3
1003
+ */
1004
+ cellSelection?: boolean;
1005
+ /** Controlled cell selection (zero or one range). Needs `cellSelection`. @since v3.3 */
1006
+ cellSelectionModel?: GridCellSelectionModel;
1007
+ /** Fired when the cell selection changes, with what changed it. @since v3.3 */
1008
+ onCellSelectionModelChange?: (model: GridCellSelectionModel, details: GridCellSelectionChangeDetails) => void;
1009
+ /**
1010
+ * Shows a status bar under the grid with the count of non-empty cells and the sum and average of the
1011
+ * numeric cells in the range, while more than one cell is selected. Replaceable through
1012
+ * `slots.cellSelectionStats`. Needs `cellSelection`. Default: false. @since v3.3
1013
+ */
1014
+ showCellSelectionStats?: boolean;
1015
+
1016
+ /**
1017
+ * With `cellSelection` on, Ctrl/Cmd+V pastes tab-separated text (Excel, Google Sheets) into the
1018
+ * editable cells from the range's top-left cell (or the focused cell), through `valueSetter` and
1019
+ * `processRowUpdate`. `true` turns the shortcut off; `apiRef.pasteText()` still works. Default: false.
1020
+ * @since v3.4
1021
+ */
1022
+ disableClipboardPaste?: boolean;
1023
+ /**
1024
+ * Fired after every paste with the result (`updated` rows, `failed` rows, `skipped` cells and why)
1025
+ * and the pasted text. @since v3.4
1026
+ */
1027
+ onClipboardPaste?: (result: GridClipboardPasteResult) => void;
1028
+ /**
1029
+ * Called before a paste is applied. Return `false` to cancel it, or a string to paste instead of the
1030
+ * clipboard text. A throw cancels the paste. @since v3.4
1031
+ */
1032
+ onBeforeClipboardPaste?: (params: GridBeforeClipboardPasteParams) => boolean | string;
1033
+ /**
1034
+ * With `cellSelection` on, Delete / Backspace on a range of more than one cell empties its editable
1035
+ * cells (`null`, or `''` in string columns) as one edit. `true` turns that off. Default: false.
1036
+ * @since v3.4
1037
+ */
1038
+ disableRangeClear?: boolean;
1039
+ /**
1040
+ * Undo and redo of cell edits, pastes and range clears: Ctrl/Cmd+Z, Ctrl/Cmd+Shift+Z and Ctrl+Y while
1041
+ * focus is in the grid and no editor is open, and `apiRef.undo()` / `redo()`. Undo and redo write
1042
+ * through `valueSetter` and `processRowUpdate`, like an edit. `{ limit }` sets how many actions are
1043
+ * kept (default 100). Default: false. @since v3.4
1044
+ */
1045
+ undoRedo?: boolean | GridUndoRedoOptions;
1046
+ /** Fired when what can be undone or redone changes (`undoRedo`). @since v3.4 */
1047
+ onHistoryChange?: (params: GridHistoryChangeParams) => void;
1048
+
843
1049
  /** 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. */
844
1050
  loading?: boolean;
845
1051
  /** Visual density of the grid. */
@@ -992,6 +1198,11 @@ export interface DataGridProps<R extends GridValidRowModel = GridRowModel> {
992
1198
  * Receives `GridFooterSlotProps` (plus `slotProps.footer`).
993
1199
  */
994
1200
  footer?: React.ComponentType<GridFooterSlotProps & Record<string, unknown>>;
1201
+ /**
1202
+ * Replaces the status bar of `showCellSelectionStats`. Receives `GridCellSelectionStatsSlotProps`
1203
+ * (plus `slotProps.cellSelectionStats`). @since v3.3
1204
+ */
1205
+ cellSelectionStats?: React.ComponentType<GridCellSelectionStatsSlotProps & Record<string, unknown>>;
995
1206
  };
996
1207
  /** Properties passed directly to custom slots. */
997
1208
  slotProps?: {
@@ -1007,6 +1218,8 @@ export interface DataGridProps<R extends GridValidRowModel = GridRowModel> {
1007
1218
  loadingOverlay?: GridOverlaySlotProps;
1008
1219
  /** Merged over the `GridFooterSlotProps` the grid passes to the footer slot. */
1009
1220
  footer?: Partial<GridFooterSlotProps> & Record<string, unknown>;
1221
+ /** Merged over the `GridCellSelectionStatsSlotProps` the grid passes to the status bar. */
1222
+ cellSelectionStats?: Partial<GridCellSelectionStatsSlotProps> & Record<string, unknown>;
1010
1223
  };
1011
1224
 
1012
1225
  /** When true, renders the grid as a single-column list of cards. Perfect for mobile/responsive views. */
@@ -1198,4 +1411,45 @@ export interface GridApi<R extends GridValidRowModel = GridRowModel> {
1198
1411
  * in which case nothing is written) and rejects when the clipboard write fails.
1199
1412
  */
1200
1413
  copySelectedRows: () => Promise<void>;
1414
+
1415
+ /** The cell selection (`[]` when `cellSelection` is off or nothing is selected). @since v3.3 */
1416
+ getCellSelectionModel: () => GridCellSelectionModel;
1417
+ /**
1418
+ * Replaces the cell selection (only the first range is kept) and moves focus to its anchor.
1419
+ * Does nothing while `cellSelection` is off. @since v3.3
1420
+ */
1421
+ setCellSelectionModel: (model: GridCellSelectionModel) => void;
1422
+ /** Selects the rectangle between two cells; `anchor` becomes the active cell. @since v3.3 */
1423
+ selectCellRange: (anchor: GridCellCoordinates, head: GridCellCoordinates) => void;
1424
+ /** Empties the cell selection. Focus stays where it is. @since v3.3 */
1425
+ clearCellSelection: () => void;
1426
+ /**
1427
+ * Cells of the current range in display order (row by row, columns in screen order), values read
1428
+ * through the column's `valueGetter`. Synthetic rows (group headers, subtotals, tree auto-parents, the
1429
+ * pivot Grand Total) and positions covered by a span are left out. @since v3.3
1430
+ */
1431
+ getSelectedCells: () => GridSelectedCell[];
1432
+ /**
1433
+ * Copies the cell range (the focused cell when nothing is selected) as tab-separated text, formatted
1434
+ * the way the cells show it, with no header line. Resolves without writing when there is nothing to
1435
+ * copy; rejects when the clipboard write fails. @since v3.3
1436
+ */
1437
+ copySelectedCells: () => Promise<void>;
1438
+
1439
+ /**
1440
+ * Pastes tab-separated text as Ctrl/Cmd+V does: from `anchor` (default: the range's top-left cell,
1441
+ * else the focused cell), with `onBeforeClipboardPaste` and `onClipboardPaste`. Works with
1442
+ * `disableClipboardPaste`. Resolves once every row's `processRowUpdate` has settled. @since v3.4
1443
+ */
1444
+ pasteText: (text: string, anchor?: GridCellCoordinates) => Promise<GridBatchEditResult>;
1445
+ /** Undoes the last action (`undoRedo`). Resolves with what was written. @since v3.4 */
1446
+ undo: () => Promise<GridBatchEditResult>;
1447
+ /** Redoes the last undone action (`undoRedo`). @since v3.4 */
1448
+ redo: () => Promise<GridBatchEditResult>;
1449
+ /** Whether there is an action to undo. @since v3.4 */
1450
+ canUndo: () => boolean;
1451
+ /** Whether there is an undone action to redo. @since v3.4 */
1452
+ canRedo: () => boolean;
1453
+ /** Forgets every action (undo and redo). @since v3.4 */
1454
+ clearHistory: () => void;
1201
1455
  }
@@ -0,0 +1,14 @@
1
+ export type AttemptResult<T> = { ok: true; value: T } | { ok: false; error: unknown };
2
+
3
+ /**
4
+ * Calls `fn` and contains a throw. Components and hooks use it instead of an inline `try` / `catch`
5
+ * around expressions with `?.`, `??`, `||` or `? :`: React Compiler cannot compile a function that has
6
+ * those inside a `try` statement yet and skips the whole component or hook.
7
+ */
8
+ export function attempt<T>(fn: () => T): AttemptResult<T> {
9
+ try {
10
+ return { ok: true, value: fn() };
11
+ } catch (error) {
12
+ return { ok: false, error };
13
+ }
14
+ }