@dxos/react-ui-table 0.8.3 → 0.8.4-main.1f223c7

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 (148) hide show
  1. package/dist/lib/browser/chunk-63UUF4IG.mjs +66 -0
  2. package/dist/lib/browser/chunk-63UUF4IG.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-BBXDTWCX.mjs +1822 -0
  4. package/dist/lib/browser/chunk-BBXDTWCX.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +814 -2152
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/testing/index.mjs +124 -0
  9. package/dist/lib/browser/testing/index.mjs.map +7 -0
  10. package/dist/lib/browser/types/index.mjs +3 -5
  11. package/dist/lib/node-esm/chunk-K734DJB7.mjs +67 -0
  12. package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +7 -0
  13. package/dist/lib/node-esm/chunk-RNQDU7IQ.mjs +1823 -0
  14. package/dist/lib/node-esm/chunk-RNQDU7IQ.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +814 -2152
  16. package/dist/lib/node-esm/index.mjs.map +4 -4
  17. package/dist/lib/node-esm/meta.json +1 -1
  18. package/dist/lib/node-esm/testing/index.mjs +125 -0
  19. package/dist/lib/node-esm/testing/index.mjs.map +7 -0
  20. package/dist/lib/node-esm/types/index.mjs +3 -5
  21. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -1
  22. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  23. package/dist/types/src/components/Table/ColumnSettings.d.ts +4 -2
  24. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  25. package/dist/types/src/components/Table/CreateRefPanel.d.ts +1 -1
  26. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/DynamicTable.d.ts +1 -1
  28. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +50 -2
  30. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/Relations.stories.d.ts +44 -0
  32. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -0
  33. package/dist/types/src/components/Table/RowActionsMenu.d.ts +1 -1
  34. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  35. package/dist/types/src/components/Table/Table.d.ts +3 -2
  36. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  37. package/dist/types/src/components/Table/Table.stories.d.ts +42 -3
  38. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  39. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +2 -1
  40. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  41. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +8 -3
  42. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  43. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +9 -15
  44. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  45. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -3
  46. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  47. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
  48. package/dist/types/src/components/testing.d.ts +3 -40
  49. package/dist/types/src/components/testing.d.ts.map +1 -1
  50. package/dist/types/src/hooks/index.d.ts +1 -0
  51. package/dist/types/src/hooks/index.d.ts.map +1 -1
  52. package/dist/types/src/hooks/useAddRow.d.ts +14 -0
  53. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -0
  54. package/dist/types/src/hooks/useTableModel.d.ts +8 -7
  55. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  56. package/dist/types/src/index.d.ts +1 -2
  57. package/dist/types/src/index.d.ts.map +1 -1
  58. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  59. package/dist/types/src/model/selection-model.d.ts +2 -1
  60. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  61. package/dist/types/src/model/table-model.d.ts +59 -21
  62. package/dist/types/src/model/table-model.d.ts.map +1 -1
  63. package/dist/types/src/model/table-presentation.d.ts +10 -1
  64. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  65. package/dist/types/src/model/table-sorting.d.ts +3 -4
  66. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  67. package/dist/types/src/playwright/playwright.config.d.ts +3 -0
  68. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
  69. package/dist/types/src/testing/index.d.ts +2 -0
  70. package/dist/types/src/testing/index.d.ts.map +1 -0
  71. package/dist/types/src/testing/useTestTableModel.d.ts +22 -0
  72. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -0
  73. package/dist/types/src/translations.d.ts +5 -6
  74. package/dist/types/src/translations.d.ts.map +1 -1
  75. package/dist/types/src/types/index.d.ts +1 -1
  76. package/dist/types/src/types/index.d.ts.map +1 -1
  77. package/dist/types/src/types/schema.d.ts +28 -0
  78. package/dist/types/src/types/schema.d.ts.map +1 -0
  79. package/dist/types/src/util/dynamic-table.d.ts +4 -5
  80. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  81. package/dist/types/src/util/index.d.ts +0 -1
  82. package/dist/types/src/util/index.d.ts.map +1 -1
  83. package/dist/types/src/util/table-buttons.d.ts +15 -1
  84. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  85. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  86. package/dist/types/tsconfig.tsbuildinfo +1 -1
  87. package/package.json +57 -44
  88. package/src/components/Table/ColumnActionsMenu.tsx +1 -1
  89. package/src/components/Table/ColumnSettings.tsx +15 -11
  90. package/src/components/Table/CreateRefPanel.tsx +30 -11
  91. package/src/components/Table/DynamicTable.stories.tsx +8 -5
  92. package/src/components/Table/DynamicTable.tsx +17 -10
  93. package/src/components/Table/{relations.stories.tsx → Relations.stories.tsx} +56 -47
  94. package/src/components/Table/RowActionsMenu.tsx +3 -3
  95. package/src/components/Table/Table.stories.tsx +126 -159
  96. package/src/components/Table/Table.tsx +167 -123
  97. package/src/components/TableCellEditor/CellValidationMessage.tsx +7 -2
  98. package/src/components/TableCellEditor/FormCellEditor.tsx +158 -48
  99. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +31 -47
  100. package/src/components/TableCellEditor/TableCellEditor.tsx +106 -111
  101. package/src/components/TableToolbar/TableToolbar.tsx +2 -2
  102. package/src/components/testing.ts +7 -24
  103. package/src/hooks/index.ts +1 -0
  104. package/src/hooks/useAddRow.ts +38 -0
  105. package/src/hooks/useTableModel.ts +21 -17
  106. package/src/index.ts +1 -2
  107. package/src/model/modal-controller.ts +1 -1
  108. package/src/model/selection-model.ts +6 -3
  109. package/src/model/table-model.test.ts +12 -9
  110. package/src/model/table-model.ts +288 -88
  111. package/src/model/table-presentation.test.ts +12 -8
  112. package/src/model/table-presentation.ts +263 -138
  113. package/src/model/table-sorting.ts +22 -13
  114. package/src/playwright/playwright.config.ts +17 -0
  115. package/src/playwright/smoke.spec.ts +137 -44
  116. package/src/testing/index.ts +5 -0
  117. package/src/testing/useTestTableModel.ts +124 -0
  118. package/src/translations.ts +7 -5
  119. package/src/types/index.ts +2 -2
  120. package/src/types/schema.ts +73 -0
  121. package/src/util/dynamic-table.ts +24 -21
  122. package/src/util/index.ts +0 -1
  123. package/src/util/schema.ts +1 -1
  124. package/src/util/table-buttons.ts +28 -4
  125. package/src/util/table-controls.ts +4 -1
  126. package/dist/lib/browser/chunk-TL7CKQQV.mjs +0 -27
  127. package/dist/lib/browser/chunk-TL7CKQQV.mjs.map +0 -7
  128. package/dist/lib/node/chunk-O2VPBNS5.cjs +0 -50
  129. package/dist/lib/node/chunk-O2VPBNS5.cjs.map +0 -7
  130. package/dist/lib/node/index.cjs +0 -2739
  131. package/dist/lib/node/index.cjs.map +0 -7
  132. package/dist/lib/node/meta.json +0 -1
  133. package/dist/lib/node/types/index.cjs +0 -31
  134. package/dist/lib/node/types/index.cjs.map +0 -7
  135. package/dist/lib/node-esm/chunk-GBT6GD4G.mjs +0 -29
  136. package/dist/lib/node-esm/chunk-GBT6GD4G.mjs.map +0 -7
  137. package/dist/types/src/components/Table/relations.stories.d.ts +0 -9
  138. package/dist/types/src/components/Table/relations.stories.d.ts.map +0 -1
  139. package/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
  140. package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
  141. package/dist/types/src/types/table.d.ts +0 -56
  142. package/dist/types/src/types/table.d.ts.map +0 -1
  143. package/dist/types/src/util/table.d.ts +0 -14
  144. package/dist/types/src/util/table.d.ts.map +0 -1
  145. package/src/components/TableCellEditor/extension.ts +0 -61
  146. package/src/playwright/playwright.config.cts +0 -18
  147. package/src/types/table.ts +0 -27
  148. package/src/util/table.ts +0 -127
@@ -9,40 +9,40 @@ import React, {
9
9
  type WheelEvent,
10
10
  forwardRef,
11
11
  useCallback,
12
+ useEffect,
12
13
  useImperativeHandle,
13
- useState,
14
14
  useMemo,
15
- useEffect,
15
+ useState,
16
16
  } from 'react';
17
17
 
18
- import { type Client } from '@dxos/client';
19
- import { getValue } from '@dxos/echo-schema';
20
- import { invariant } from '@dxos/invariant';
21
- import { Filter } from '@dxos/react-client/echo';
22
- import { useTranslation } from '@dxos/react-ui';
18
+ import { type Client } from '@dxos/react-client';
19
+ // TODO(wittjosiah): Remove dependency on react-client.
23
20
  import { useAttention } from '@dxos/react-ui-attention';
24
21
  import {
25
- closestCell,
26
22
  type DxGridElement,
27
- type DxGridPosition,
28
- type GridContentProps,
29
- Grid,
30
23
  type DxGridPlane,
31
24
  type DxGridPlaneRange,
32
- gridSeparatorInlineEnd,
25
+ type DxGridPosition,
26
+ Grid,
27
+ type GridContentProps,
28
+ closestCell,
33
29
  gridSeparatorBlockEnd,
30
+ gridSeparatorInlineEnd,
34
31
  } from '@dxos/react-ui-grid';
32
+ import { DxEditRequest } from '@dxos/react-ui-grid';
35
33
  import { mx } from '@dxos/react-ui-theme';
36
- import { isNotFalsy, safeParseInt } from '@dxos/util';
34
+
35
+ import { type InsertRowResult, ModalController, type TableModel, type TablePresentation } from '../../model';
36
+ import { tableButtons, tableControls } from '../../util';
37
+ import { type TableCellEditorProps, TableValueEditor } from '../TableCellEditor';
37
38
 
38
39
  import { ColumnActionsMenu } from './ColumnActionsMenu';
39
40
  import { ColumnSettings } from './ColumnSettings';
40
41
  import { CreateRefPanel } from './CreateRefPanel';
41
42
  import { RowActionsMenu } from './RowActionsMenu';
42
- import { ModalController, type TableModel, type TablePresentation } from '../../model';
43
- import { translationKey } from '../../translations';
44
- import { tableButtons, tableControls } from '../../util';
45
- import { createOption, TableValueEditor, type TableCellEditorProps } from '../TableCellEditor';
43
+
44
+ const columnDefault = { grid: { minSize: 80, maxSize: 640 } };
45
+ const rowDefault = { frozenRowsStart: { readonly: true, focusUnfurl: false } };
46
46
 
47
47
  //
48
48
  // Table.Root
@@ -50,15 +50,17 @@ import { createOption, TableValueEditor, type TableCellEditorProps } from '../Ta
50
50
 
51
51
  export type TableRootProps = PropsWithChildren<{ role?: string }>;
52
52
 
53
- const TableRoot = ({ children, role }: TableRootProps) => {
53
+ const TableRoot = ({ children, role = 'article' }: TableRootProps) => {
54
54
  return (
55
55
  <div
56
56
  role='none'
57
57
  className={mx(
58
- 'relative !border-separator',
59
- role === 'section' // TODO(burdon): This leaks composer plugin concepts? Standardize for react-ui?
60
- ? 'attention-surface overflow-hidden [&_.dx-grid]:max-is-[--dx-grid-content-inline-size]'
61
- : 'flex flex-col [&_.dx-grid]:grow [&_.dx-grid]:max-is-[--dx-grid-content-inline-size] [&_.dx-grid]:bs-0 [&_.dx-grid]:max-bs-[--dx-grid-content-block-size]',
58
+ 'relative !border-separator [&_.dx-grid]:max-is-[--dx-grid-content-inline-size] [&_.dx-grid]:max-bs-[--dx-grid-content-block-size]',
59
+ role === 'card--popover' && 'popover-card-height',
60
+ role === 'section' && 'attention-surface',
61
+ role === 'card--intrinsic' && '[&_.dx-grid]:bs-[--dx-grid-content-block-size]',
62
+ ['card--popover', 'section', 'card--extrinsic'].includes(role) && 'overflow-hidden',
63
+ ['article', 'slide'].includes(role) && 'flex flex-col [&_.dx-grid]:grow [&_.dx-grid]:bs-0',
62
64
  )}
63
65
  >
64
66
  {children}
@@ -72,6 +74,7 @@ const TableRoot = ({ children, role }: TableRootProps) => {
72
74
 
73
75
  export type TableController = {
74
76
  update?: (cell?: DxGridPosition) => void;
77
+ handleInsertRowResult?: (insertRowResult?: InsertRowResult) => void;
75
78
  };
76
79
 
77
80
  export type TableMainProps = {
@@ -88,9 +91,15 @@ const TableMain = forwardRef<TableController, TableMainProps>(
88
91
  ({ model, presentation, ignoreAttention, schema, client, onRowClick }, forwardedRef) => {
89
92
  const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
90
93
  const { hasAttention } = useAttention(model?.id ?? 'table');
91
- const { t } = useTranslation(translationKey);
92
94
  const modals = useMemo(() => new ModalController(), []);
93
95
 
96
+ const draftRowCount = model?.getDraftRowCount() ?? 0;
97
+
98
+ const handleSave = useCallback(() => {
99
+ dxGrid?.updateCells(true);
100
+ dxGrid?.requestUpdate();
101
+ }, [dxGrid]);
102
+
94
103
  const frozen = useMemo(() => {
95
104
  const noActionColumn =
96
105
  model?.features.dataEditable === false &&
@@ -99,10 +108,11 @@ const TableMain = forwardRef<TableController, TableMainProps>(
99
108
 
100
109
  return {
101
110
  frozenRowsStart: 1,
111
+ frozenRowsEnd: Math.max(1, draftRowCount),
102
112
  frozenColsStart: model?.features.selection.enabled ? 1 : 0,
103
113
  frozenColsEnd: noActionColumn ? 0 : 1,
104
114
  };
105
- }, [model]);
115
+ }, [model, draftRowCount]);
106
116
 
107
117
  const getCells = useCallback<NonNullable<GridContentProps['getCells']>>(
108
118
  (range: DxGridPlaneRange, plane: DxGridPlane) => presentation?.getCells(range, plane) ?? {},
@@ -116,37 +126,70 @@ const TableMain = forwardRef<TableController, TableMainProps>(
116
126
  dxGrid.getCells = getCells;
117
127
  }, [presentation, dxGrid, getCells]);
118
128
 
129
+ const handleInsertRowResult = useCallback(
130
+ (insertResult?: InsertRowResult) => {
131
+ if (insertResult === 'draft') {
132
+ requestAnimationFrame(() => {
133
+ dxGrid?.setFocus({ plane: 'frozenRowsEnd', col: 0, row: 0 });
134
+ dxGrid?.refocus();
135
+ });
136
+ } else {
137
+ requestAnimationFrame(() => {
138
+ dxGrid?.setFocus({ plane: 'grid', col: 0, row: model ? model.getRowCount() - 1 : 0 });
139
+ dxGrid?.refocus();
140
+ });
141
+ }
142
+ },
143
+ [model, dxGrid],
144
+ );
145
+
119
146
  /**
120
147
  * Provides an external controller that can be called to repaint the table.
121
148
  */
122
- useImperativeHandle<TableController, TableController>(
123
- forwardedRef,
124
- () => {
125
- if (!presentation || !dxGrid) {
126
- return {};
127
- }
149
+ useImperativeHandle<TableController, TableController>(forwardedRef, () => {
150
+ if (!presentation || !dxGrid) {
151
+ return {};
152
+ }
128
153
 
129
- return {
130
- update: (cell) => {
131
- if (cell) {
132
- dxGrid.updateIfWithinBounds(cell);
133
- } else {
134
- dxGrid.updateCells(true);
135
- dxGrid.requestUpdate();
136
- }
137
- },
138
- };
154
+ return {
155
+ update: (cell) => {
156
+ if (cell) {
157
+ dxGrid.updateIfWithinBounds(cell, true);
158
+ } else {
159
+ dxGrid.updateCells(true);
160
+ dxGrid.requestUpdate();
161
+ }
162
+ },
163
+ handleInsertRowResult,
164
+ };
165
+ }, [presentation, dxGrid, model]);
166
+
167
+ const handleSaveDraftRow = useCallback(
168
+ (rowIndex = 0) => {
169
+ if (model && dxGrid) {
170
+ const didCommitSuccessfully = model.commitDraftRow(rowIndex);
171
+ if (didCommitSuccessfully) {
172
+ requestAnimationFrame(() => {
173
+ dxGrid.scrollToEndRow();
174
+ });
175
+ }
176
+ }
139
177
  },
140
- [presentation, dxGrid],
178
+ [model, dxGrid],
141
179
  );
142
180
 
143
181
  const handleGridClick = useCallback(
144
182
  (event: MouseEvent) => {
145
- const rowIndex = safeParseInt((event.target as HTMLElement).ariaRowIndex ?? '');
146
- if (rowIndex != null) {
183
+ const cell = closestCell(event.target as HTMLElement);
184
+ if (cell) {
185
+ const { row: rowIndex, plane } = cell;
147
186
  if (onRowClick) {
148
- const row = model?.getRowAt(rowIndex);
149
- row && onRowClick(row);
187
+ if (plane === 'grid') {
188
+ const row = model?.getRowAt(rowIndex);
189
+ row && onRowClick(row);
190
+ } else {
191
+ onRowClick(cell);
192
+ }
150
193
  }
151
194
 
152
195
  if (model?.features.selection.enabled && model?.selection.selectionMode === 'single') {
@@ -188,6 +231,10 @@ const TableMain = forwardRef<TableController, TableMainProps>(
188
231
  model?.sorting?.toggleSort(data.fieldId);
189
232
  break;
190
233
  }
234
+ case 'saveDraftRow': {
235
+ handleSaveDraftRow();
236
+ break;
237
+ }
191
238
  }
192
239
  return;
193
240
  }
@@ -222,36 +269,23 @@ const TableMain = forwardRef<TableController, TableMainProps>(
222
269
  }
223
270
  }
224
271
  },
225
- [model, modals],
272
+ [model, modals, dxGrid],
226
273
  );
227
274
 
228
275
  const handleFocus = useCallback<NonNullable<TableCellEditorProps['onFocus']>>(
229
- (increment, delta) => {
230
- if (dxGrid) {
231
- dxGrid.refocus(increment, delta);
232
- }
233
- },
234
- [dxGrid],
235
- );
236
-
237
- const handleEnter = useCallback<NonNullable<TableCellEditorProps['onEnter']>>(
238
- (cell) => {
239
- if (!model?.features.dataEditable) {
240
- return;
241
- }
242
-
243
- // TODO(burdon): Insert row only if bottom row isn't completely blank already.
244
- if (model && cell.row === model.getRowCount() - 1) {
245
- model.insertRow(cell.row);
246
- if (dxGrid) {
247
- requestAnimationFrame(() => {
248
- dxGrid?.scrollToRow(cell.row + 1);
249
- dxGrid?.refocus('row', 1);
250
- });
276
+ (increment, delta, cell) => {
277
+ if (dxGrid && model) {
278
+ if (cell?.plane === 'grid' && cell?.row >= model.getRowCount() - 1) {
279
+ handleInsertRowResult(draftRowCount < 1 ? model.insertRow() : 'final');
280
+ } else if (cell?.plane === 'frozenRowsEnd' && increment === 'row') {
281
+ handleSaveDraftRow(cell.row);
282
+ handleInsertRowResult(model.insertRow());
283
+ } else {
284
+ dxGrid.refocus(increment, delta);
251
285
  }
252
286
  }
253
287
  },
254
- [model, dxGrid],
288
+ [dxGrid, model],
255
289
  );
256
290
 
257
291
  const handleKeyDown = useCallback<NonNullable<GridContentProps['onKeyDown']>>(
@@ -265,15 +299,71 @@ const TableMain = forwardRef<TableController, TableMainProps>(
265
299
  return;
266
300
  }
267
301
 
302
+ // Handle Meta+C (Copy) and Meta+V (Paste) commands
303
+ if (event.metaKey || event.ctrlKey) {
304
+ switch (event.key) {
305
+ case 'c': {
306
+ // Copy focused cell's text content to clipboard
307
+ try {
308
+ const cellData = model.getCellData(cell);
309
+ const textContent = cellData?.toString() ?? '';
310
+ void navigator.clipboard.writeText(textContent);
311
+ event.preventDefault();
312
+ } catch (error) {
313
+ console.warn('Failed to copy cell content:', error);
314
+ }
315
+ break;
316
+ }
317
+ case 'v': {
318
+ // Paste clipboard content to focused cell
319
+ event.preventDefault();
320
+ void navigator.clipboard.readText().then((clipboardText) => {
321
+ try {
322
+ // Attempt to set the cell's content to clipboard content
323
+ model.setCellData(cell, clipboardText);
324
+ handleSave();
325
+ } catch {
326
+ // If validation fails, emit a DxEditRequest event with initialContent from clipboard
327
+ // TODO(thure): Should `dx-grid` expose a method like this?
328
+ const cellElement = (event.target as HTMLElement).closest(
329
+ '[data-dx-grid-action="cell"]',
330
+ ) as HTMLElement;
331
+ if (cellElement) {
332
+ const rect = cellElement.getBoundingClientRect();
333
+ const editRequest = new DxEditRequest({
334
+ cellIndex: `${cell.plane},${cell.col},${cell.row}`,
335
+ cellBox: {
336
+ insetInlineStart: rect.left,
337
+ insetBlockStart: rect.top,
338
+ inlineSize: rect.width,
339
+ blockSize: rect.height,
340
+ },
341
+ cellElement,
342
+ initialContent: clipboardText,
343
+ });
344
+ cellElement.dispatchEvent(editRequest);
345
+ }
346
+ }
347
+ });
348
+ break;
349
+ }
350
+ }
351
+ }
352
+
268
353
  switch (event.key) {
269
354
  case 'Backspace':
270
355
  case 'Delete': {
271
- model.setCellData(cell, undefined);
356
+ try {
357
+ model.setCellData(cell, undefined);
358
+ event.preventDefault();
359
+ } catch {
360
+ // Delete results in a validation error; don’t prevent default so dx-grid can emit an edit request.
361
+ }
272
362
  break;
273
363
  }
274
364
  }
275
365
  },
276
- [model],
366
+ [model, dxGrid, handleSave],
277
367
  );
278
368
 
279
369
  const handleAxisResize = useCallback<NonNullable<GridContentProps['onAxisResize']>>(
@@ -302,53 +392,6 @@ const TableMain = forwardRef<TableController, TableMainProps>(
302
392
  }
303
393
  }, [model, dxGrid]);
304
394
 
305
- // TODO(burdon): Factor out?
306
- // TODO(burdon): Generalize to handle other value types (e.g., enums).
307
- const handleQuery = useCallback<NonNullable<TableCellEditorProps['onQuery']>>(
308
- async ({ field, props }, text) => {
309
- if (model && props.referenceSchema && field.referencePath) {
310
- const space = model.space;
311
- invariant(space);
312
-
313
- let schema;
314
- if (client) {
315
- schema = client.graph.schemaRegistry.getSchema(props.referenceSchema);
316
- }
317
- if (!schema) {
318
- schema = space.db.schemaRegistry.getSchema(props.referenceSchema);
319
- }
320
-
321
- if (schema) {
322
- const { objects } = await space.db.query(Filter.type(schema)).run();
323
- const options = objects
324
- .map((obj) => {
325
- const value = getValue(obj, field.referencePath!);
326
- if (!value || typeof value !== 'string') {
327
- return undefined;
328
- }
329
-
330
- return {
331
- label: value,
332
- data: obj,
333
- };
334
- })
335
- .filter(isNotFalsy);
336
-
337
- return [
338
- ...options,
339
- {
340
- label: t('create new object label', { text }),
341
- data: createOption(text),
342
- },
343
- ];
344
- }
345
- }
346
-
347
- return [];
348
- },
349
- [model, client, t],
350
- );
351
-
352
395
  if (!model || !modals) {
353
396
  return <span role='none' className='attention-surface' />;
354
397
  }
@@ -359,21 +402,22 @@ const TableMain = forwardRef<TableController, TableMainProps>(
359
402
  model={model}
360
403
  modals={modals}
361
404
  schema={schema}
362
- onEnter={handleEnter}
363
405
  onFocus={handleFocus}
364
- onQuery={handleQuery}
406
+ onSave={handleSave}
407
+ client={client}
365
408
  />
366
409
  <Grid.Content
367
410
  className={mx('[--dx-grid-base:var(--baseSurface)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
368
411
  frozen={frozen}
369
- // getCells={getCells}
370
412
  columns={model.columnMeta.value}
413
+ columnDefault={columnDefault}
414
+ rowDefault={rowDefault}
371
415
  limitRows={model.getRowCount() ?? 0}
372
- limitColumns={model.view?.fields?.length ?? 0}
416
+ limitColumns={model.projection.fields.length}
373
417
  overscroll='trap'
374
418
  onAxisResize={handleAxisResize}
375
419
  onClick={handleGridClick}
376
- onKeyDown={handleKeyDown}
420
+ onKeyDownCapture={handleKeyDown}
377
421
  onWheelCapture={handleWheel}
378
422
  ref={setDxGrid}
379
423
  />
@@ -4,14 +4,16 @@
4
4
 
5
5
  import React from 'react';
6
6
 
7
- import { useGridContext, type GridScopedProps } from '@dxos/react-ui-grid';
7
+ import { type GridScopedProps, useGridContext } from '@dxos/react-ui-grid';
8
8
 
9
9
  export type CellValidationMessageProps = {
10
10
  validationError: string | null;
11
+ variant?: 'error' | 'warning';
11
12
  };
12
13
 
13
14
  export const CellValidationMessage = ({
14
15
  validationError,
16
+ variant = 'error',
15
17
  __gridScope,
16
18
  }: GridScopedProps<CellValidationMessageProps>) => {
17
19
  const { editing, editBox: box } = useGridContext('GridSheetCellEditor', __gridScope);
@@ -20,10 +22,13 @@ export const CellValidationMessage = ({
20
22
  return null;
21
23
  }
22
24
 
25
+ const bgClass = variant === 'error' ? 'bg-errorSurface' : 'bg-warningSurface';
26
+ const textClass = variant === 'error' ? 'text-errorSurfaceText' : 'text-warningSurfaceText';
27
+
23
28
  return (
24
29
  <div
25
30
  role='none'
26
- className='absolute bg-errorSurface text-errorSurfaceText rounded-bs-sm text-xs p-1'
31
+ className={`absolute ${bgClass} ${textClass} rounded-bs-sm text-xs p-1`}
27
32
  style={{
28
33
  ...{ '--dx-gridCellWidth': `${box?.inlineSize ?? 200}px` },
29
34
  zIndex: 10,