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

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 +816 -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 +816 -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 +169 -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
@@ -3,46 +3,47 @@
3
3
  //
4
4
 
5
5
  import { type Schema } from 'effect/Schema';
6
+ import { trim } from 'effect/String';
6
7
  import React, {
7
8
  type MouseEvent,
8
9
  type PropsWithChildren,
9
10
  type WheelEvent,
10
11
  forwardRef,
11
12
  useCallback,
13
+ useEffect,
12
14
  useImperativeHandle,
13
- useState,
14
15
  useMemo,
15
- useEffect,
16
+ useState,
16
17
  } from 'react';
17
18
 
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';
19
+ import { type Client } from '@dxos/react-client';
20
+ // TODO(wittjosiah): Remove dependency on react-client.
23
21
  import { useAttention } from '@dxos/react-ui-attention';
24
22
  import {
25
- closestCell,
26
23
  type DxGridElement,
27
- type DxGridPosition,
28
- type GridContentProps,
29
- Grid,
30
24
  type DxGridPlane,
31
25
  type DxGridPlaneRange,
32
- gridSeparatorInlineEnd,
26
+ type DxGridPosition,
27
+ Grid,
28
+ type GridContentProps,
29
+ closestCell,
33
30
  gridSeparatorBlockEnd,
31
+ gridSeparatorInlineEnd,
34
32
  } from '@dxos/react-ui-grid';
33
+ import { DxEditRequest } from '@dxos/react-ui-grid';
35
34
  import { mx } from '@dxos/react-ui-theme';
36
- import { isNotFalsy, safeParseInt } from '@dxos/util';
35
+
36
+ import { type InsertRowResult, ModalController, type TableModel, type TablePresentation } from '../../model';
37
+ import { tableButtons, tableControls } from '../../util';
38
+ import { type TableCellEditorProps, TableValueEditor } from '../TableCellEditor';
37
39
 
38
40
  import { ColumnActionsMenu } from './ColumnActionsMenu';
39
41
  import { ColumnSettings } from './ColumnSettings';
40
42
  import { CreateRefPanel } from './CreateRefPanel';
41
43
  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';
44
+
45
+ const columnDefault = { grid: { minSize: 80, maxSize: 640 } };
46
+ const rowDefault = { frozenRowsStart: { readonly: true, focusUnfurl: false } };
46
47
 
47
48
  //
48
49
  // Table.Root
@@ -50,15 +51,17 @@ import { createOption, TableValueEditor, type TableCellEditorProps } from '../Ta
50
51
 
51
52
  export type TableRootProps = PropsWithChildren<{ role?: string }>;
52
53
 
53
- const TableRoot = ({ children, role }: TableRootProps) => {
54
+ const TableRoot = ({ children, role = 'article' }: TableRootProps) => {
54
55
  return (
55
56
  <div
56
57
  role='none'
57
58
  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]',
59
+ 'relative !border-separator [&_.dx-grid]:max-is-[--dx-grid-content-inline-size] [&_.dx-grid]:max-bs-[--dx-grid-content-block-size]',
60
+ role === 'card--popover' && 'popover-card-height',
61
+ role === 'section' && 'attention-surface',
62
+ role === 'card--intrinsic' && '[&_.dx-grid]:bs-[--dx-grid-content-block-size]',
63
+ ['card--popover', 'section', 'card--extrinsic'].includes(role) && 'overflow-hidden',
64
+ ['article', 'slide'].includes(role) && 'flex flex-col [&_.dx-grid]:grow [&_.dx-grid]:bs-0',
62
65
  )}
63
66
  >
64
67
  {children}
@@ -72,6 +75,7 @@ const TableRoot = ({ children, role }: TableRootProps) => {
72
75
 
73
76
  export type TableController = {
74
77
  update?: (cell?: DxGridPosition) => void;
78
+ handleInsertRowResult?: (insertRowResult?: InsertRowResult) => void;
75
79
  };
76
80
 
77
81
  export type TableMainProps = {
@@ -88,9 +92,15 @@ const TableMain = forwardRef<TableController, TableMainProps>(
88
92
  ({ model, presentation, ignoreAttention, schema, client, onRowClick }, forwardedRef) => {
89
93
  const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
90
94
  const { hasAttention } = useAttention(model?.id ?? 'table');
91
- const { t } = useTranslation(translationKey);
92
95
  const modals = useMemo(() => new ModalController(), []);
93
96
 
97
+ const draftRowCount = model?.getDraftRowCount() ?? 0;
98
+
99
+ const handleSave = useCallback(() => {
100
+ dxGrid?.updateCells(true);
101
+ dxGrid?.requestUpdate();
102
+ }, [dxGrid]);
103
+
94
104
  const frozen = useMemo(() => {
95
105
  const noActionColumn =
96
106
  model?.features.dataEditable === false &&
@@ -99,10 +109,11 @@ const TableMain = forwardRef<TableController, TableMainProps>(
99
109
 
100
110
  return {
101
111
  frozenRowsStart: 1,
112
+ frozenRowsEnd: Math.max(1, draftRowCount),
102
113
  frozenColsStart: model?.features.selection.enabled ? 1 : 0,
103
114
  frozenColsEnd: noActionColumn ? 0 : 1,
104
115
  };
105
- }, [model]);
116
+ }, [model, draftRowCount]);
106
117
 
107
118
  const getCells = useCallback<NonNullable<GridContentProps['getCells']>>(
108
119
  (range: DxGridPlaneRange, plane: DxGridPlane) => presentation?.getCells(range, plane) ?? {},
@@ -116,37 +127,70 @@ const TableMain = forwardRef<TableController, TableMainProps>(
116
127
  dxGrid.getCells = getCells;
117
128
  }, [presentation, dxGrid, getCells]);
118
129
 
130
+ const handleInsertRowResult = useCallback(
131
+ (insertResult?: InsertRowResult) => {
132
+ if (insertResult === 'draft') {
133
+ requestAnimationFrame(() => {
134
+ dxGrid?.setFocus({ plane: 'frozenRowsEnd', col: 0, row: 0 });
135
+ dxGrid?.refocus();
136
+ });
137
+ } else {
138
+ requestAnimationFrame(() => {
139
+ dxGrid?.setFocus({ plane: 'grid', col: 0, row: model ? model.getRowCount() - 1 : 0 });
140
+ dxGrid?.refocus();
141
+ });
142
+ }
143
+ },
144
+ [model, dxGrid],
145
+ );
146
+
119
147
  /**
120
148
  * Provides an external controller that can be called to repaint the table.
121
149
  */
122
- useImperativeHandle<TableController, TableController>(
123
- forwardedRef,
124
- () => {
125
- if (!presentation || !dxGrid) {
126
- return {};
127
- }
150
+ useImperativeHandle<TableController, TableController>(forwardedRef, () => {
151
+ if (!presentation || !dxGrid) {
152
+ return {};
153
+ }
128
154
 
129
- return {
130
- update: (cell) => {
131
- if (cell) {
132
- dxGrid.updateIfWithinBounds(cell);
133
- } else {
134
- dxGrid.updateCells(true);
135
- dxGrid.requestUpdate();
136
- }
137
- },
138
- };
155
+ return {
156
+ update: (cell) => {
157
+ if (cell) {
158
+ dxGrid.updateIfWithinBounds(cell, true);
159
+ } else {
160
+ dxGrid.updateCells(true);
161
+ dxGrid.requestUpdate();
162
+ }
163
+ },
164
+ handleInsertRowResult,
165
+ };
166
+ }, [presentation, dxGrid, model]);
167
+
168
+ const handleSaveDraftRow = useCallback(
169
+ (rowIndex = 0) => {
170
+ if (model && dxGrid) {
171
+ const didCommitSuccessfully = model.commitDraftRow(rowIndex);
172
+ if (didCommitSuccessfully) {
173
+ requestAnimationFrame(() => {
174
+ dxGrid.scrollToEndRow();
175
+ });
176
+ }
177
+ }
139
178
  },
140
- [presentation, dxGrid],
179
+ [model, dxGrid],
141
180
  );
142
181
 
143
182
  const handleGridClick = useCallback(
144
183
  (event: MouseEvent) => {
145
- const rowIndex = safeParseInt((event.target as HTMLElement).ariaRowIndex ?? '');
146
- if (rowIndex != null) {
184
+ const cell = closestCell(event.target as HTMLElement);
185
+ if (cell) {
186
+ const { row: rowIndex, plane } = cell;
147
187
  if (onRowClick) {
148
- const row = model?.getRowAt(rowIndex);
149
- row && onRowClick(row);
188
+ if (plane === 'grid') {
189
+ const row = model?.getRowAt(rowIndex);
190
+ row && onRowClick(row);
191
+ } else {
192
+ onRowClick(cell);
193
+ }
150
194
  }
151
195
 
152
196
  if (model?.features.selection.enabled && model?.selection.selectionMode === 'single') {
@@ -188,6 +232,10 @@ const TableMain = forwardRef<TableController, TableMainProps>(
188
232
  model?.sorting?.toggleSort(data.fieldId);
189
233
  break;
190
234
  }
235
+ case 'saveDraftRow': {
236
+ handleSaveDraftRow();
237
+ break;
238
+ }
191
239
  }
192
240
  return;
193
241
  }
@@ -222,36 +270,23 @@ const TableMain = forwardRef<TableController, TableMainProps>(
222
270
  }
223
271
  }
224
272
  },
225
- [model, modals],
273
+ [model, modals, dxGrid],
226
274
  );
227
275
 
228
276
  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
- });
277
+ (increment, delta, cell) => {
278
+ if (dxGrid && model) {
279
+ if (cell?.plane === 'grid' && cell?.row >= model.getRowCount() - 1) {
280
+ handleInsertRowResult(draftRowCount < 1 ? model.insertRow() : 'final');
281
+ } else if (cell?.plane === 'frozenRowsEnd' && increment === 'row') {
282
+ handleSaveDraftRow(cell.row);
283
+ handleInsertRowResult(model.insertRow());
284
+ } else {
285
+ dxGrid.refocus(increment, delta);
251
286
  }
252
287
  }
253
288
  },
254
- [model, dxGrid],
289
+ [dxGrid, model],
255
290
  );
256
291
 
257
292
  const handleKeyDown = useCallback<NonNullable<GridContentProps['onKeyDown']>>(
@@ -265,15 +300,72 @@ const TableMain = forwardRef<TableController, TableMainProps>(
265
300
  return;
266
301
  }
267
302
 
303
+ // Handle Meta+C (Copy) and Meta+V (Paste) commands
304
+ if (event.metaKey || event.ctrlKey) {
305
+ switch (event.key) {
306
+ case 'c': {
307
+ // Copy focused cell's text content to clipboard
308
+ try {
309
+ const cellData = model.getCellData(cell);
310
+ const textContent = cellData?.toString() ?? '';
311
+ void navigator.clipboard.writeText(textContent);
312
+ event.preventDefault();
313
+ } catch (error) {
314
+ console.warn('Failed to copy cell content:', error);
315
+ }
316
+ break;
317
+ }
318
+ case 'v': {
319
+ // Paste clipboard content to focused cell
320
+ event.preventDefault();
321
+ void navigator.clipboard.readText().then((clipboardText) => {
322
+ try {
323
+ // Attempt to set the cell's content to clipboard content
324
+ model.setCellData(cell, trim(clipboardText));
325
+ handleSave();
326
+ } catch {
327
+ // If validation fails, emit a DxEditRequest event with initialContent from clipboard
328
+ // TODO(thure): Should `dx-grid` expose a method like this?
329
+ const cellElement = (event.target as HTMLElement).closest(
330
+ '[data-dx-grid-action="cell"]',
331
+ ) as HTMLElement;
332
+ if (cellElement) {
333
+ const rect = cellElement.getBoundingClientRect();
334
+ const editRequest = new DxEditRequest({
335
+ cellIndex: `${cell.plane},${cell.col},${cell.row}`,
336
+ cellBox: {
337
+ insetInlineStart: rect.left,
338
+ insetBlockStart: rect.top,
339
+ inlineSize: rect.width,
340
+ blockSize: rect.height,
341
+ },
342
+ cellElement,
343
+ initialContent: clipboardText,
344
+ });
345
+ cellElement.dispatchEvent(editRequest);
346
+ }
347
+ }
348
+ });
349
+ break;
350
+ }
351
+ }
352
+ }
353
+
268
354
  switch (event.key) {
269
355
  case 'Backspace':
270
356
  case 'Delete': {
271
- model.setCellData(cell, undefined);
357
+ try {
358
+ model.setCellData(cell, undefined);
359
+ event.preventDefault();
360
+ handleSave();
361
+ } catch {
362
+ // Delete results in a validation error; don’t prevent default so dx-grid can emit an edit request.
363
+ }
272
364
  break;
273
365
  }
274
366
  }
275
367
  },
276
- [model],
368
+ [model, dxGrid, handleSave],
277
369
  );
278
370
 
279
371
  const handleAxisResize = useCallback<NonNullable<GridContentProps['onAxisResize']>>(
@@ -302,53 +394,6 @@ const TableMain = forwardRef<TableController, TableMainProps>(
302
394
  }
303
395
  }, [model, dxGrid]);
304
396
 
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
397
  if (!model || !modals) {
353
398
  return <span role='none' className='attention-surface' />;
354
399
  }
@@ -359,21 +404,22 @@ const TableMain = forwardRef<TableController, TableMainProps>(
359
404
  model={model}
360
405
  modals={modals}
361
406
  schema={schema}
362
- onEnter={handleEnter}
363
407
  onFocus={handleFocus}
364
- onQuery={handleQuery}
408
+ onSave={handleSave}
409
+ client={client}
365
410
  />
366
411
  <Grid.Content
367
412
  className={mx('[--dx-grid-base:var(--baseSurface)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
368
413
  frozen={frozen}
369
- // getCells={getCells}
370
414
  columns={model.columnMeta.value}
415
+ columnDefault={columnDefault}
416
+ rowDefault={rowDefault}
371
417
  limitRows={model.getRowCount() ?? 0}
372
- limitColumns={model.view?.fields?.length ?? 0}
418
+ limitColumns={model.projection.fields.length}
373
419
  overscroll='trap'
374
420
  onAxisResize={handleAxisResize}
375
421
  onClick={handleGridClick}
376
- onKeyDown={handleKeyDown}
422
+ onKeyDownCapture={handleKeyDown}
377
423
  onWheelCapture={handleWheel}
378
424
  ref={setDxGrid}
379
425
  />
@@ -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,