@dxos/react-ui-table 0.8.4-main.67995b8 → 0.8.4-main.c4373fc

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 (130) hide show
  1. package/dist/lib/browser/chunk-4K7JPALT.mjs +66 -0
  2. package/dist/lib/browser/chunk-4K7JPALT.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-5XLSHLOG.mjs → chunk-OZTXLZGM.mjs} +544 -491
  4. package/dist/lib/browser/chunk-OZTXLZGM.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +584 -673
  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 +13 -8
  9. package/dist/lib/browser/testing/index.mjs.map +3 -3
  10. package/dist/lib/browser/types/index.mjs +3 -3
  11. package/dist/lib/node-esm/chunk-MK6OVUF6.mjs +67 -0
  12. package/dist/lib/node-esm/chunk-MK6OVUF6.mjs.map +7 -0
  13. package/dist/lib/node-esm/{chunk-Y66YLSTC.mjs → chunk-S56Z4V4X.mjs} +544 -491
  14. package/dist/lib/node-esm/chunk-S56Z4V4X.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +584 -673
  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 +13 -8
  19. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  20. package/dist/lib/node-esm/types/index.mjs +3 -3
  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 +1 -1
  24. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  25. package/dist/types/src/components/Table/DynamicTable.d.ts +2 -2
  26. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +51 -3
  28. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/Relations.stories.d.ts +108 -0
  30. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -0
  31. package/dist/types/src/components/Table/RowActionsMenu.d.ts +1 -1
  32. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  33. package/dist/types/src/components/Table/Table.d.ts +8 -4
  34. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  35. package/dist/types/src/components/Table/Table.stories.d.ts +42 -4
  36. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  37. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  38. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +12 -6
  39. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  40. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +12 -17
  41. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  42. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -4
  43. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  44. package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
  45. package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
  46. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
  47. package/dist/types/src/components/testing.d.ts +3 -3
  48. package/dist/types/src/components/testing.d.ts.map +1 -1
  49. package/dist/types/src/hooks/useAddRow.d.ts +3 -2
  50. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  51. package/dist/types/src/hooks/useTableModel.d.ts +2 -2
  52. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  53. package/dist/types/src/model/modal-controller.d.ts +0 -7
  54. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  55. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  56. package/dist/types/src/model/table-model.d.ts +9 -7
  57. package/dist/types/src/model/table-model.d.ts.map +1 -1
  58. package/dist/types/src/model/table-presentation.d.ts +2 -1
  59. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  60. package/dist/types/src/model/table-sorting.d.ts +2 -3
  61. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  62. package/dist/types/src/testing/useTestTableModel.d.ts +3 -22
  63. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  64. package/dist/types/src/translations.d.ts +2 -2
  65. package/dist/types/src/types/index.d.ts +1 -1
  66. package/dist/types/src/types/index.d.ts.map +1 -1
  67. package/dist/types/src/types/schema.d.ts +25 -5
  68. package/dist/types/src/types/schema.d.ts.map +1 -1
  69. package/dist/types/src/util/dynamic-table.d.ts +4 -5
  70. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  71. package/dist/types/src/util/index.d.ts +0 -1
  72. package/dist/types/src/util/index.d.ts.map +1 -1
  73. package/dist/types/src/util/schema.d.ts +1 -1
  74. package/dist/types/src/util/schema.d.ts.map +1 -1
  75. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  76. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  77. package/dist/types/tsconfig.tsbuildinfo +1 -1
  78. package/package.json +59 -58
  79. package/src/components/Table/ColumnActionsMenu.tsx +1 -1
  80. package/src/components/Table/ColumnSettings.tsx +1 -1
  81. package/src/components/Table/DynamicTable.stories.tsx +15 -11
  82. package/src/components/Table/DynamicTable.tsx +8 -10
  83. package/src/components/Table/Relations.stories.tsx +248 -0
  84. package/src/components/Table/RowActionsMenu.tsx +3 -3
  85. package/src/components/Table/Table.stories.tsx +66 -54
  86. package/src/components/Table/Table.tsx +140 -115
  87. package/src/components/TableCellEditor/CellValidationMessage.tsx +1 -1
  88. package/src/components/TableCellEditor/FormCellEditor.tsx +153 -42
  89. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +24 -64
  90. package/src/components/TableCellEditor/TableCellEditor.tsx +74 -224
  91. package/src/components/TableCellEditor/index.ts +1 -0
  92. package/src/components/TableToolbar/TableToolbar.tsx +2 -2
  93. package/src/components/testing.ts +2 -2
  94. package/src/hooks/useAddRow.ts +7 -5
  95. package/src/hooks/useTableModel.ts +2 -2
  96. package/src/model/modal-controller.ts +1 -29
  97. package/src/model/selection-model.ts +3 -2
  98. package/src/model/table-model.test.ts +19 -10
  99. package/src/model/table-model.ts +37 -25
  100. package/src/model/table-presentation.test.ts +23 -21
  101. package/src/model/table-presentation.ts +74 -33
  102. package/src/model/table-sorting.ts +16 -7
  103. package/src/playwright/smoke.spec.ts +129 -37
  104. package/src/testing/useTestTableModel.ts +11 -7
  105. package/src/translations.ts +2 -2
  106. package/src/types/index.ts +1 -1
  107. package/src/types/schema.ts +63 -6
  108. package/src/util/dynamic-table.ts +13 -13
  109. package/src/util/index.ts +0 -1
  110. package/src/util/schema.ts +4 -4
  111. package/src/util/table-buttons.ts +4 -3
  112. package/src/util/table-controls.ts +4 -1
  113. package/dist/lib/browser/chunk-5XLSHLOG.mjs.map +0 -7
  114. package/dist/lib/browser/chunk-L2LSB6KV.mjs +0 -18
  115. package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +0 -7
  116. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +0 -20
  117. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +0 -7
  118. package/dist/lib/node-esm/chunk-Y66YLSTC.mjs.map +0 -7
  119. package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
  120. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
  121. package/dist/types/src/components/Table/relations.stories.d.ts +0 -9
  122. package/dist/types/src/components/Table/relations.stories.d.ts.map +0 -1
  123. package/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
  124. package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
  125. package/dist/types/src/util/table.d.ts +0 -11
  126. package/dist/types/src/util/table.d.ts.map +0 -1
  127. package/src/components/Table/CreateRefPanel.tsx +0 -86
  128. package/src/components/Table/relations.stories.tsx +0 -116
  129. package/src/components/TableCellEditor/extension.ts +0 -61
  130. package/src/util/table.ts +0 -49
@@ -3,64 +3,59 @@
3
3
  //
4
4
 
5
5
  import { type Completion } from '@codemirror/autocomplete';
6
- import { type Schema } from 'effect/Schema';
7
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
6
+ import { EditorView } from '@codemirror/view';
7
+ import type * as Schema from 'effect/Schema';
8
+ import React, { useCallback, useMemo, useRef, useState } from 'react';
8
9
 
9
- import { FormatEnum, TypeEnum } from '@dxos/echo-schema';
10
+ import { debounce } from '@dxos/async';
11
+ import type { Client } from '@dxos/client';
12
+ import { FormatEnum, TypeEnum } from '@dxos/echo/internal';
10
13
  import { invariant } from '@dxos/invariant';
11
- import { type DxGrid } from '@dxos/lit-grid';
12
- import { useThemeContext } from '@dxos/react-ui';
13
- import { createMarkdownExtensions } from '@dxos/react-ui-editor';
14
+ import { type DxGridAxis, type DxGridPosition } from '@dxos/lit-grid';
14
15
  import {
15
- cellQuery,
16
- editorKeys,
17
- parseCellIndex,
18
- useGridContext,
19
- type DxGridPlanePosition,
16
+ type EditorBlurHandler,
20
17
  type EditorKeyEvent,
21
18
  type EditorKeyOrBlurHandler,
22
- type EditorBlurHandler,
23
19
  GridCellEditor,
24
20
  type GridCellEditorProps,
25
21
  type GridScopedProps,
22
+ editorKeys,
23
+ parseCellIndex,
24
+ useGridContext,
26
25
  } from '@dxos/react-ui-grid';
27
- import { tagPicker, createLinks } from '@dxos/react-ui-tag-picker';
28
26
  import { type FieldProjection } from '@dxos/schema';
29
27
 
30
- import { CellValidationMessage } from './CellValidationMessage';
31
- import { FormCellEditor } from './FormCellEditor';
32
- import { completion } from './extension';
33
- import { type TableModel, type ModalController } from '../../model';
28
+ import { type ModalController, type TableModel } from '../../model';
34
29
 
35
- const newValue = Symbol.for('newValue');
30
+ import { CellValidationMessage } from './CellValidationMessage';
31
+ import { FormCellEditor, type OnCreateHandler } from './FormCellEditor';
36
32
 
37
33
  /**
38
34
  * Option to create new object/value.
39
35
  */
40
- export const createOption = (text: string) => ({ [newValue]: true, text });
41
-
42
- const isCreateOption = (data: any) => typeof data === 'object' && data[newValue];
43
-
44
36
  export type QueryResult = Pick<Completion, 'label'> & { data: any };
45
37
 
46
38
  export type TableCellEditorProps = {
47
39
  model?: TableModel;
48
40
  modals?: ModalController;
49
- schema?: Schema.AnyNoContext;
50
- onEnter?: (cell: DxGridPlanePosition) => void;
51
- onFocus?: DxGrid['refocus'];
41
+ schema?: Schema.Schema.AnyNoContext;
42
+ onFocus?: (axis?: DxGridAxis, delta?: -1 | 0 | 1, cell?: DxGridPosition) => void;
43
+ onCreate?: OnCreateHandler;
52
44
  onSave?: () => void;
53
- onQuery?: (field: FieldProjection, text: string) => Promise<QueryResult[]>;
45
+ client?: Client;
54
46
  };
55
47
 
48
+ const adaptValidationMessage = (message: string | null) =>
49
+ message ? (message.endsWith('is missing') ? 'Can’t be blank' : message) : null;
50
+
56
51
  export const TableValueEditor = ({
57
52
  model,
58
53
  modals,
59
54
  schema,
60
- onEnter,
61
55
  onFocus,
62
56
  onSave,
63
- onQuery,
57
+ onCreate,
58
+ client,
64
59
  __gridScope,
65
60
  }: GridScopedProps<TableCellEditorProps>) => {
66
61
  const { editing } = useGridContext('TableValueEditor', __gridScope);
@@ -79,7 +74,8 @@ export const TableValueEditor = ({
79
74
 
80
75
  if (
81
76
  fieldProjection?.props.type === TypeEnum.Array ||
82
- fieldProjection?.props.format === FormatEnum.SingleSelect
77
+ fieldProjection?.props.format === FormatEnum.SingleSelect ||
78
+ fieldProjection?.props.format === FormatEnum.Ref
83
79
  // TODO(thure): Support `FormatEnum.MultiSelect`
84
80
  ) {
85
81
  return (
@@ -89,40 +85,30 @@ export const TableValueEditor = ({
89
85
  schema={schema}
90
86
  __gridScope={__gridScope}
91
87
  onSave={onSave}
88
+ onCreate={onCreate}
89
+ client={client}
90
+ modals={modals}
92
91
  />
93
92
  );
94
93
  }
95
94
 
96
95
  // For all other types, use the existing cell editor
97
- return (
98
- <TableCellEditor
99
- model={model}
100
- modals={modals}
101
- onEnter={onEnter}
102
- onFocus={onFocus}
103
- onQuery={onQuery}
104
- onSave={onSave}
105
- __gridScope={__gridScope}
106
- />
107
- );
96
+ return <TableCellEditor model={model} modals={modals} onFocus={onFocus} onSave={onSave} __gridScope={__gridScope} />;
108
97
  };
109
98
 
110
- const editorSlots = { scroller: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
99
+ const editorSlots = { scroll: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
111
100
 
112
101
  export const TableCellEditor = ({
113
102
  model,
114
103
  modals,
115
- onEnter,
116
104
  onFocus,
117
- onQuery,
118
105
  onSave,
119
106
  __gridScope,
120
107
  }: GridScopedProps<TableCellEditorProps>) => {
121
- const { id: gridId, editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
108
+ const { editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
122
109
  const suppressNextBlur = useRef(false);
123
- const { themeMode } = useThemeContext();
124
- const [_validationError, setValidationError] = useState<string | null>(null);
125
- const [_validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
110
+ const [validationError, setValidationError] = useState<string | null>(null);
111
+ const [validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
126
112
 
127
113
  const fieldProjection = useMemo<FieldProjection | undefined>(() => {
128
114
  if (!model || !editing) {
@@ -136,38 +122,6 @@ export const TableCellEditor = ({
136
122
  return fieldProjection;
137
123
  }, [model, editing]);
138
124
 
139
- useEffect(() => {
140
- // Check for existing validation errors when editing starts (for draft rows).
141
- if (!model || !editing || !fieldProjection) {
142
- setValidationError(null);
143
- return;
144
- }
145
-
146
- const cell = parseCellIndex(editing.index);
147
- const { row, col } = cell;
148
-
149
- if (model.isDraftCell(cell)) {
150
- const field = model.projection.fields[col];
151
- const hasValidationError = model.hasDraftRowValidationError(row, field.path);
152
-
153
- if (hasValidationError) {
154
- const draftRows = model.draftRows.value;
155
- if (row >= 0 && row < draftRows.length) {
156
- const draftRow = draftRows[row];
157
- const validationError = draftRow.validationErrors?.find((error) => error.path === field.path);
158
- if (validationError) {
159
- setValidationError(validationError.message);
160
- setValidationVariant('warning');
161
- }
162
- }
163
- } else {
164
- setValidationError(null);
165
- }
166
- } else {
167
- setValidationError(null);
168
- }
169
- }, [model, editing, fieldProjection]);
170
-
171
125
  const handleEnter = useCallback(
172
126
  async (value: any) => {
173
127
  if (!model || !editing) {
@@ -180,28 +134,34 @@ export const TableCellEditor = ({
180
134
  if (validationResult.valid) {
181
135
  setValidationError(null);
182
136
  model.setCellData(cell, value);
183
- onEnter?.(cell);
184
137
  onFocus?.();
185
138
  setEditing(null);
186
139
  onSave?.();
187
140
  } else {
188
- setValidationError(validationResult.error);
141
+ setValidationError(adaptValidationMessage(validationResult.error));
189
142
  setValidationVariant('error');
190
143
  }
191
144
  },
192
- [model, editing, onEnter, onFocus, setEditing],
145
+ [model, editing, onFocus, setEditing],
193
146
  );
194
147
 
195
148
  const handleBlur = useCallback<EditorBlurHandler>(
196
149
  async (value) => {
197
- if (!model || !editing) {
198
- return;
199
- }
200
150
  if (suppressNextBlur.current) {
201
151
  suppressNextBlur.current = false;
152
+ } else if (model && editing) {
153
+ // Save silently if validation passes
154
+ const cell = parseCellIndex(editing.index);
155
+ if (value !== undefined) {
156
+ const result = await model.validateCellData(cell, value);
157
+ if (result.valid) {
158
+ setValidationError(null);
159
+ model.setCellData(cell, value);
160
+ setEditing(null);
161
+ onSave?.();
162
+ }
163
+ }
202
164
  }
203
-
204
- // Don't save on blur - let handleClose handle validation and saving
205
165
  },
206
166
  [model, editing],
207
167
  );
@@ -213,25 +173,25 @@ export const TableCellEditor = ({
213
173
  }
214
174
 
215
175
  const cell = parseCellIndex(editing.index);
216
-
217
176
  if (value !== undefined) {
218
177
  // Pre-commit validation check.
219
178
  const result = await model.validateCellData(cell, value);
220
179
 
221
180
  if (result.valid) {
181
+ suppressNextBlur.current = true;
222
182
  setValidationError(null);
223
183
  model.setCellData(cell, value);
224
184
  setEditing(null);
225
185
  onSave?.();
226
- onEnter?.(cell);
227
186
  if (event && onFocus) {
228
- onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
187
+ onFocus(determineNavigationAxis(event), determineNavigationDelta(event), cell);
229
188
  }
230
189
  } else {
231
190
  setValidationError(result.error);
232
191
  setValidationVariant('error');
233
192
  }
234
193
  } else {
194
+ suppressNextBlur.current = true;
235
195
  setValidationError(null);
236
196
  setEditing(null);
237
197
  onSave?.();
@@ -240,154 +200,48 @@ export const TableCellEditor = ({
240
200
  }
241
201
  }
242
202
  },
243
- [model, editing, onFocus, onEnter, fieldProjection, setEditing, onSave],
203
+ [model, editing, onFocus, fieldProjection, setEditing, onSave],
244
204
  );
245
205
 
246
- const extension = useMemo(() => {
206
+ const extensions = useMemo(() => {
247
207
  if (!fieldProjection) {
248
208
  return [];
249
209
  }
250
210
 
251
- const extension = [
211
+ const extensions = [
252
212
  editorKeys({
253
213
  onClose: handleClose,
254
214
  ...(editing?.initialContent && { onNav: handleClose }),
255
215
  }),
256
216
  ];
257
-
258
- const format = fieldProjection.props.format;
259
-
260
- if (format === FormatEnum.SingleSelect || format === FormatEnum.MultiSelect) {
261
- // TODO(ZaymonFC): Reconcile this with the TagPicker component?
262
- // Add markdown extensions needed by tag picker.
263
- extension.push(createMarkdownExtensions({ themeMode }));
264
-
265
- const options = fieldProjection.props.options || [];
266
-
267
- const mode = format === FormatEnum.SingleSelect ? ('single-select' as const) : ('multi-select' as const);
268
-
269
- extension.push(
270
- tagPicker({
271
- mode,
272
- keymap: false,
273
- onSearch: (text, selectedIds) => {
274
- return options
275
- .filter(
276
- (option) =>
277
- selectedIds.indexOf(option.id) === -1 &&
278
- (text.length === 0 || option.title.toLowerCase().includes(text.toLowerCase())),
279
- )
280
- .map((option) => ({
281
- id: option.id,
282
- label: option.title,
283
- hue: option.color as any,
284
- }));
285
- },
286
- onUpdate: (ids) => {
287
- if (model && editing) {
288
- if (ids.length === 0) {
289
- return;
290
- }
291
- if (mode === 'single-select') {
292
- void handleEnter(ids[0]);
217
+ // Add validation extension to handle content changes.
218
+ if (model && editing) {
219
+ extensions.push(
220
+ EditorView.updateListener.of(
221
+ debounce((update) => {
222
+ const content = update.state.doc.toString();
223
+ const cell = parseCellIndex(editing.index);
224
+
225
+ // Perform validation on content change.
226
+ void model.validateCellData(cell, content).then((result) => {
227
+ if (result.valid) {
228
+ setValidationError(null);
293
229
  } else {
294
- void handleEnter(ids);
230
+ setValidationError(result.error);
231
+ setValidationVariant('error');
295
232
  }
296
- }
297
- },
298
- }),
233
+ });
234
+ }, 10),
235
+ ),
299
236
  );
300
237
  }
301
238
 
302
- if (onQuery) {
303
- switch (fieldProjection.props.format) {
304
- case FormatEnum.Ref: {
305
- extension.push([
306
- completion({
307
- onQuery: (text) => onQuery(fieldProjection, text),
308
- onMatch: (data) => {
309
- if (model && editing && modals) {
310
- if (isCreateOption(data)) {
311
- const { field, props } = fieldProjection;
312
- if (props.referenceSchema) {
313
- suppressNextBlur.current = true;
314
- modals.openCreateRef(
315
- props.referenceSchema,
316
- document.querySelector(cellQuery(editing.index, gridId)),
317
- {
318
- [field.referencePath!]: data.text,
319
- },
320
- (data) => {
321
- void handleEnter(data);
322
- },
323
- );
324
- }
325
- } else {
326
- void handleEnter(data);
327
- }
328
- }
329
- },
330
- }),
331
- ]);
332
- break;
333
- }
334
- }
335
- }
336
-
337
- return extension;
338
- }, [model, modals, editing, fieldProjection, handleClose, themeMode]);
239
+ return extensions;
240
+ }, [model, modals, editing, fieldProjection, handleClose]);
339
241
 
340
242
  const getCellContent = useCallback<GridCellEditorProps['getCellContent']>(() => {
341
243
  if (model && editing) {
342
244
  const cell = parseCellIndex(editing.index);
343
- const { col } = cell;
344
- const field = model.projection.fields[col];
345
- const fieldProjection = model.projection.getFieldProjection(field.id);
346
-
347
- if (
348
- fieldProjection?.props.format === FormatEnum.SingleSelect ||
349
- fieldProjection?.props.format === FormatEnum.MultiSelect
350
- ) {
351
- const value = model.getCellData(cell);
352
-
353
- if (value !== undefined) {
354
- const options = fieldProjection.props.options || [];
355
-
356
- if (fieldProjection.props.format === FormatEnum.MultiSelect) {
357
- const tagItems = value
358
- .split(',')
359
- .map((id: string) => {
360
- const option = options.find((o) => o.id === id);
361
- if (option) {
362
- return {
363
- id,
364
- label: option.title,
365
- hue: option.color as any,
366
- };
367
- }
368
- return undefined;
369
- })
370
- .filter((item: any): item is { id: any; label: string; hue: any } => item !== undefined);
371
-
372
- return createLinks(tagItems);
373
- } else {
374
- const option = options.find((o) => o.id === value);
375
-
376
- if (option) {
377
- const tagItem = {
378
- id: value,
379
- label: option.title,
380
- hue: option.color as any,
381
- };
382
-
383
- return createLinks([tagItem]);
384
- }
385
- }
386
- }
387
-
388
- return '';
389
- }
390
-
391
245
  const value = model.getCellData(cell);
392
246
  return value !== undefined ? String(value) : '';
393
247
  }
@@ -395,12 +249,8 @@ export const TableCellEditor = ({
395
249
 
396
250
  return (
397
251
  <>
398
- <CellValidationMessage
399
- validationError={_validationError}
400
- variant={_validationVariant}
401
- __gridScope={__gridScope}
402
- />
403
- <GridCellEditor extension={extension} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
252
+ <CellValidationMessage validationError={validationError} variant={validationVariant} __gridScope={__gridScope} />
253
+ <GridCellEditor extensions={extensions} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
404
254
  </>
405
255
  );
406
256
  };
@@ -3,3 +3,4 @@
3
3
  //
4
4
 
5
5
  export * from './TableCellEditor';
6
+ export { type OnCreateHandler } from './FormCellEditor';
@@ -11,11 +11,11 @@ import {
11
11
  type ActionGraphEdges,
12
12
  type ActionGraphNodes,
13
13
  type ActionGraphProps,
14
- createGapSeparator,
15
- createMenuAction,
16
14
  type MenuAction,
17
15
  MenuProvider,
18
16
  ToolbarMenu,
17
+ createGapSeparator,
18
+ createMenuAction,
19
19
  useMenuActions,
20
20
  } from '@dxos/react-ui-menu';
21
21
  import { rxFromSignal } from '@dxos/react-ui-menu';
@@ -2,10 +2,10 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
5
+ import * as Schema from 'effect/Schema';
6
6
  import { useEffect } from 'react';
7
7
 
8
- import { FormatEnum, ObjectId, setValue, TypedObject, TypeEnum } from '@dxos/echo-schema';
8
+ import { FormatEnum, ObjectId, TypeEnum, TypedObject, setValue } from '@dxos/echo/internal';
9
9
  import { live } from '@dxos/live-object';
10
10
  import { faker } from '@dxos/random';
11
11
  import { type ProjectionModel } from '@dxos/schema';
@@ -2,12 +2,14 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type Schema } from 'effect';
5
+ import type * as Schema from 'effect/Schema';
6
6
  import { useCallback } from 'react';
7
7
 
8
8
  import { type Space } from '@dxos/client/echo';
9
9
  import { Obj } from '@dxos/echo';
10
10
 
11
+ import { type InsertRowResult } from '../model';
12
+
11
13
  export type UseAddRowParams = {
12
14
  space?: Space;
13
15
  schema?: Schema.Schema.AnyNoContext;
@@ -20,16 +22,16 @@ export type UseAddRowParams = {
20
22
  */
21
23
  export const useAddRow = ({ space, schema }: UseAddRowParams) => {
22
24
  return useCallback(
23
- (data?: any) => {
25
+ (data?: any): InsertRowResult => {
24
26
  if (space && schema) {
25
27
  try {
26
28
  space.db.add(Obj.make(schema, data ?? {}));
27
- return true;
29
+ return 'final';
28
30
  } catch (error) {
29
- return false;
31
+ return 'draft';
30
32
  }
31
33
  }
32
- return false;
34
+ return 'final';
33
35
  },
34
36
  [space, schema],
35
37
  );
@@ -6,14 +6,14 @@ import { effect } from '@preact/signals-core';
6
6
  import orderBy from 'lodash.orderby';
7
7
  import { useEffect, useMemo, useState } from 'react';
8
8
 
9
- import { type JsonSchemaType } from '@dxos/echo-schema';
9
+ import { type JsonSchemaType } from '@dxos/echo/internal';
10
10
  import { type Live } from '@dxos/live-object';
11
11
  import { fullyQualifiedId } from '@dxos/react-client/echo';
12
12
  import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';
13
13
  import { type DataType, type ProjectionModel } from '@dxos/schema';
14
14
  import { isNonNullable } from '@dxos/util';
15
15
 
16
- import { type TableRow, TableModel, type TableModelProps, type TableRowAction } from '../model';
16
+ import { TableModel, type TableModelProps, type TableRow, type TableRowAction } from '../model';
17
17
 
18
18
  export type UseTableModelParams<T extends TableRow = TableRow> = {
19
19
  view?: DataType.View;
@@ -3,10 +3,7 @@
3
3
  //
4
4
 
5
5
  import { signal } from '@preact/signals-core';
6
- import { type RefObject, type MutableRefObject } from 'react';
7
-
8
- import { type Live } from '@dxos/live-object';
9
- import { log } from '@dxos/log';
6
+ import { type MutableRefObject, type RefObject } from 'react';
10
7
 
11
8
  export type ColumnSettingsMode = { type: 'create' } | { type: 'edit'; fieldId: string };
12
9
 
@@ -14,12 +11,6 @@ export type ModalState =
14
11
  | { type: 'row'; rowIndex: number }
15
12
  | { type: 'column'; fieldId: string }
16
13
  | { type: 'refPanel'; targetId: string; typename: string }
17
- | {
18
- type: 'createRefPanel';
19
- typename: string;
20
- initialValues?: Record<string, string>;
21
- onCreate?: (obj: Live<any>) => void;
22
- }
23
14
  | { type: 'columnSettings'; mode: ColumnSettingsMode }
24
15
  | { type: 'closed' };
25
16
 
@@ -68,25 +59,6 @@ export class ModalController {
68
59
  }
69
60
  };
70
61
 
71
- public openCreateRef = (
72
- typename: string,
73
- anchorCell: Element | null,
74
- initialValues?: Record<string, string>,
75
- onCreate?: (obj: Live<any>) => void,
76
- ) => {
77
- if (anchorCell) {
78
- this._triggerRef.current = anchorCell as HTMLElement;
79
- this._state.value = {
80
- type: 'createRefPanel',
81
- typename,
82
- initialValues,
83
- onCreate,
84
- };
85
- } else {
86
- log.warn('anchor cell not found while creating new ref');
87
- }
88
- };
89
-
90
62
  public close = () => {
91
63
  this._state.value = { type: 'closed' };
92
64
  };
@@ -2,13 +2,14 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { computed, effect, type Signal, signal, type ReadonlySignal } from '@preact/signals-core';
5
+ import { type ReadonlySignal, type Signal, computed, effect, signal } from '@preact/signals-core';
6
6
 
7
7
  import { Resource } from '@dxos/context';
8
8
 
9
- import { type TableRow } from './table-model';
10
9
  import { touch } from '../util';
11
10
 
11
+ import { type TableRow } from './table-model';
12
+
12
13
  export type SelectionMode = 'single' | 'multiple';
13
14
 
14
15
  export class SelectionModel<T extends TableRow> extends Resource {
@@ -3,19 +3,20 @@
3
3
  //
4
4
 
5
5
  import { computed } from '@preact/signals-core';
6
- import { Schema } from 'effect';
6
+ import * as Schema from 'effect/Schema';
7
7
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
8
8
 
9
- import { Obj } from '@dxos/echo';
10
- import { TypedObject } from '@dxos/echo-schema';
11
- import { updateCounter } from '@dxos/echo-schema/testing';
9
+ import { Filter, Query } from '@dxos/echo';
10
+ import { TypedObject } from '@dxos/echo/internal';
11
+ import { updateCounter } from '@dxos/echo/testing';
12
+ import { createEchoSchema } from '@dxos/echo/testing';
12
13
  import { registerSignalsRuntime } from '@dxos/echo-signals';
13
14
  import { live } from '@dxos/live-object';
14
- import { createEchoSchema } from '@dxos/live-object/testing';
15
15
  import { createView } from '@dxos/schema';
16
16
 
17
+ import { Table } from '../types';
18
+
17
19
  import { TableModel, type TableModelProps } from './table-model';
18
- import { TableView } from '../types';
19
20
 
20
21
  // TODO(burdon): Tests are disabled in project.json since they bring in plugin deps.
21
22
  // Restore once factored out into react-ui-table.
@@ -24,7 +25,7 @@ registerSignalsRuntime();
24
25
 
25
26
  describe('TableModel', () => {
26
27
  let updateCount = 0;
27
- let model: TableModel;
28
+ let model: any;
28
29
 
29
30
  beforeEach(async () => {
30
31
  updateCount = 0;
@@ -32,6 +33,11 @@ describe('TableModel', () => {
32
33
  onCellUpdate: () => updateCount++,
33
34
  });
34
35
  await model.open();
36
+ model.setRows([
37
+ { id: '1', title: 'Test', completed: false },
38
+ { id: '2', title: 'Test 2', completed: true },
39
+ { id: '3', title: 'Test 3', completed: false },
40
+ ]);
35
41
  });
36
42
 
37
43
  afterEach(async () => {
@@ -72,7 +78,6 @@ describe('TableModel', () => {
72
78
  describe('reactivity', () => {
73
79
  it('pure signals should nest', () => {
74
80
  const signal$ = live({ arr: [{ thingInside: 1 }, { thingInside: 2 }] });
75
-
76
81
  const computed$ = computed(() => {
77
82
  return signal$.arr.map((row) =>
78
83
  computed(() => {
@@ -107,7 +112,11 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
107
112
 
108
113
  const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
109
114
  const schema = createEchoSchema(Test);
110
- const table = Obj.make(TableView, { sizes: {} });
111
- const view = createView({ typename: schema.typename, jsonSchema: schema.jsonSchema, presentation: table });
115
+ const table = Table.make();
116
+ const view = createView({
117
+ query: Query.select(Filter.type(schema)),
118
+ jsonSchema: schema.jsonSchema,
119
+ presentation: table,
120
+ });
112
121
  return new TableModel({ view, schema: schema.jsonSchema, ...props });
113
122
  };