@dxos/react-ui-table 0.8.4-main.28f8d3d → 0.8.4-main.2c6827d

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 (114) hide show
  1. package/dist/lib/browser/chunk-2PFAPQL4.mjs +66 -0
  2. package/dist/lib/browser/chunk-2PFAPQL4.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-ZAOSIG3L.mjs → chunk-GS2UNGJK.mjs} +174 -213
  4. package/dist/lib/browser/chunk-GS2UNGJK.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +411 -515
  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 +12 -9
  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-6POOFFYF.mjs +67 -0
  12. package/dist/lib/node-esm/chunk-6POOFFYF.mjs.map +7 -0
  13. package/dist/lib/node-esm/{chunk-ZOFFI3JJ.mjs → chunk-TRR3C5NX.mjs} +174 -213
  14. package/dist/lib/node-esm/chunk-TRR3C5NX.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +411 -515
  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 +12 -9
  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/DynamicTable.d.ts +2 -2
  22. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  23. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +51 -3
  24. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  25. package/dist/types/src/components/Table/Relations.stories.d.ts +103 -5
  26. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  28. package/dist/types/src/components/Table/Table.d.ts +8 -5
  29. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  30. package/dist/types/src/components/Table/Table.stories.d.ts +41 -3
  31. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  32. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +12 -6
  33. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  34. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +8 -13
  35. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  36. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +39 -4
  37. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  38. package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
  39. package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
  40. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +2 -2
  41. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
  42. package/dist/types/src/components/testing.d.ts +3 -3
  43. package/dist/types/src/components/testing.d.ts.map +1 -1
  44. package/dist/types/src/hooks/useAddRow.d.ts +3 -2
  45. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  46. package/dist/types/src/hooks/useTableModel.d.ts +3 -3
  47. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  48. package/dist/types/src/model/modal-controller.d.ts +0 -7
  49. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  50. package/dist/types/src/model/table-model.d.ts +9 -8
  51. package/dist/types/src/model/table-model.d.ts.map +1 -1
  52. package/dist/types/src/model/table-presentation.d.ts +1 -1
  53. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  54. package/dist/types/src/model/table-sorting.d.ts +2 -3
  55. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  56. package/dist/types/src/testing/useTestTableModel.d.ts +3 -22
  57. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  58. package/dist/types/src/types/index.d.ts +1 -1
  59. package/dist/types/src/types/index.d.ts.map +1 -1
  60. package/dist/types/src/types/schema.d.ts +25 -5
  61. package/dist/types/src/types/schema.d.ts.map +1 -1
  62. package/dist/types/src/util/dynamic-table.d.ts +5 -6
  63. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  64. package/dist/types/src/util/index.d.ts +0 -1
  65. package/dist/types/src/util/index.d.ts.map +1 -1
  66. package/dist/types/src/util/schema.d.ts +1 -1
  67. package/dist/types/src/util/schema.d.ts.map +1 -1
  68. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  69. package/dist/types/tsconfig.tsbuildinfo +1 -1
  70. package/package.json +53 -51
  71. package/src/components/Table/DynamicTable.stories.tsx +15 -12
  72. package/src/components/Table/DynamicTable.tsx +4 -7
  73. package/src/components/Table/Relations.stories.tsx +160 -42
  74. package/src/components/Table/RowActionsMenu.tsx +21 -13
  75. package/src/components/Table/Table.stories.tsx +63 -56
  76. package/src/components/Table/Table.tsx +109 -90
  77. package/src/components/TableCellEditor/FormCellEditor.tsx +153 -42
  78. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +24 -64
  79. package/src/components/TableCellEditor/TableCellEditor.tsx +62 -208
  80. package/src/components/TableCellEditor/index.ts +1 -0
  81. package/src/components/TableToolbar/TableToolbar.tsx +32 -22
  82. package/src/components/testing.ts +2 -2
  83. package/src/hooks/useAddRow.ts +7 -5
  84. package/src/hooks/useTableModel.ts +5 -5
  85. package/src/model/modal-controller.ts +0 -28
  86. package/src/model/table-model.test.ts +19 -11
  87. package/src/model/table-model.ts +36 -24
  88. package/src/model/table-presentation.test.ts +22 -21
  89. package/src/model/table-presentation.ts +58 -39
  90. package/src/model/table-sorting.ts +16 -7
  91. package/src/playwright/TableManager.ts +1 -1
  92. package/src/playwright/smoke.spec.ts +51 -49
  93. package/src/testing/useTestTableModel.ts +10 -7
  94. package/src/types/index.ts +1 -1
  95. package/src/types/schema.ts +63 -6
  96. package/src/util/dynamic-table.ts +15 -16
  97. package/src/util/index.ts +0 -1
  98. package/src/util/schema.ts +4 -4
  99. package/src/util/table-buttons.ts +3 -3
  100. package/dist/lib/browser/chunk-L2LSB6KV.mjs +0 -18
  101. package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +0 -7
  102. package/dist/lib/browser/chunk-ZAOSIG3L.mjs.map +0 -7
  103. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +0 -20
  104. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +0 -7
  105. package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs.map +0 -7
  106. package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
  107. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
  108. package/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
  109. package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
  110. package/dist/types/src/util/table.d.ts +0 -12
  111. package/dist/types/src/util/table.d.ts.map +0 -1
  112. package/src/components/Table/CreateRefPanel.tsx +0 -86
  113. package/src/components/TableCellEditor/extension.ts +0 -61
  114. package/src/util/table.ts +0 -53
@@ -3,14 +3,15 @@
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
14
  import { type DxGridAxis, type DxGridPosition } from '@dxos/lit-grid';
12
- import { useThemeContext } from '@dxos/react-ui';
13
- import { createMarkdownExtensions } from '@dxos/react-ui-editor';
14
15
  import {
15
16
  type EditorBlurHandler,
16
17
  type EditorKeyEvent,
@@ -18,49 +19,43 @@ import {
18
19
  GridCellEditor,
19
20
  type GridCellEditorProps,
20
21
  type GridScopedProps,
21
- cellQuery,
22
22
  editorKeys,
23
23
  parseCellIndex,
24
24
  useGridContext,
25
25
  } from '@dxos/react-ui-grid';
26
- import { createLinks, tagPicker } from '@dxos/react-ui-tag-picker';
27
26
  import { type FieldProjection } from '@dxos/schema';
28
27
 
29
28
  import { type ModalController, type TableModel } from '../../model';
30
29
 
31
30
  import { CellValidationMessage } from './CellValidationMessage';
32
- import { completion } from './extension';
33
- import { FormCellEditor } from './FormCellEditor';
34
-
35
- const newValue = Symbol.for('newValue');
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: DxGridPosition) => void;
41
+ schema?: Schema.Schema.AnyNoContext;
51
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,38 +85,28 @@ 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
110
  const [validationError, setValidationError] = useState<string | null>(null);
125
111
  const [validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
126
112
 
@@ -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,17 +173,16 @@ 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
187
  onFocus(determineNavigationAxis(event), determineNavigationDelta(event), cell);
229
188
  }
@@ -232,6 +191,7 @@ export const TableCellEditor = ({
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
  }
@@ -396,7 +250,7 @@ export const TableCellEditor = ({
396
250
  return (
397
251
  <>
398
252
  <CellValidationMessage validationError={validationError} variant={validationVariant} __gridScope={__gridScope} />
399
- <GridCellEditor extension={extension} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
253
+ <GridCellEditor extensions={extensions} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
400
254
  </>
401
255
  );
402
256
  };
@@ -3,3 +3,4 @@
3
3
  //
4
4
 
5
5
  export * from './TableCellEditor';
6
+ export { type OnCreateHandler } from './FormCellEditor';
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Rx } from '@effect-rx/rx-react';
5
+ import { Atom } from '@effect-atom/atom-react';
6
6
  import React, { useMemo } from 'react';
7
7
 
8
8
  import { live } from '@dxos/live-object';
@@ -18,7 +18,7 @@ import {
18
18
  createMenuAction,
19
19
  useMenuActions,
20
20
  } from '@dxos/react-ui-menu';
21
- import { rxFromSignal } from '@dxos/react-ui-menu';
21
+ import { atomFromSignal } from '@dxos/react-ui-menu';
22
22
 
23
23
  import { translationKey } from '../../translations';
24
24
 
@@ -30,12 +30,14 @@ export type TableToolbarActionType = 'add-row' | 'comment' | 'save-view';
30
30
 
31
31
  type TableToolbarState = Partial<{ viewDirty: boolean }>;
32
32
 
33
+ // TODO(burdon): Radix style layout.
34
+
33
35
  export type TableToolbarProps = ThemedClassName<
34
36
  TableToolbarState & {
35
37
  onAdd: () => void;
36
38
  onSave: () => void;
37
39
  attendableId?: string;
38
- customActions?: Rx.Rx<ActionGraphProps>;
40
+ customActions?: Atom.Atom<ActionGraphProps>;
39
41
  }
40
42
  >;
41
43
 
@@ -46,33 +48,41 @@ const createTableToolbarActions = ({
46
48
  customActions,
47
49
  }: {
48
50
  state: TableToolbarState;
49
- onAdd: () => void;
50
- onSave: () => void;
51
- customActions?: Rx.Rx<ActionGraphProps>;
51
+ onAdd?: () => void;
52
+ onSave?: () => void;
53
+ customActions?: Atom.Atom<ActionGraphProps>;
52
54
  }) =>
53
- Rx.make((get) => {
54
- const add = createMenuAction<TableToolbarActionProperties>('add-row', onAdd, {
55
- type: 'add-row' as const,
56
- icon: 'ph--plus--regular',
57
- label: ['add row', { ns: translationKey }],
58
- testId: 'table.toolbar.add-row',
59
- });
60
- const save = createMenuAction<TableToolbarActionProperties>('save-view', onSave, {
61
- type: 'save-view' as const,
62
- icon: 'ph--floppy-disk--regular',
63
- label: ['save view label', { ns: translationKey }],
64
- testId: 'table.toolbar.save-view',
65
- iconOnly: false,
66
- hidden: get(rxFromSignal(() => !state.viewDirty)),
67
- });
55
+ Atom.make((get) => {
56
+ const nodes: ActionGraphNodes = [];
57
+ if (onAdd) {
58
+ const add = createMenuAction<TableToolbarActionProperties>('add-row', onAdd, {
59
+ type: 'add-row' as const,
60
+ icon: 'ph--plus--regular',
61
+ label: ['add row', { ns: translationKey }],
62
+ testId: 'table.toolbar.add-row',
63
+ });
64
+ nodes.push(add);
65
+ }
66
+ if (onSave) {
67
+ const save = createMenuAction<TableToolbarActionProperties>('save-view', onSave, {
68
+ type: 'save-view' as const,
69
+ icon: 'ph--floppy-disk--regular',
70
+ label: ['save view label', { ns: translationKey }],
71
+ testId: 'table.toolbar.save-view',
72
+ iconOnly: false,
73
+ hidden: get(atomFromSignal(() => !state.viewDirty)),
74
+ });
75
+ nodes.push(save);
76
+ }
68
77
  const gap = createGapSeparator();
69
- const nodes: ActionGraphNodes = [add, save, ...gap.nodes];
78
+ nodes.push(...gap.nodes);
70
79
  const edges: ActionGraphEdges = nodes.map(({ id: target }) => ({ source: 'root', target }));
71
80
  if (customActions) {
72
81
  const custom = get(customActions);
73
82
  nodes.push(...custom.nodes);
74
83
  edges.push(...custom.edges);
75
84
  }
85
+
76
86
  return {
77
87
  nodes,
78
88
  edges,
@@ -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, TypeEnum, TypedObject, setValue } 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,17 +6,17 @@ 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 { Obj } from '@dxos/echo';
10
+ import { type JsonSchemaType } from '@dxos/echo/internal';
10
11
  import { type Live } from '@dxos/live-object';
11
- import { fullyQualifiedId } from '@dxos/react-client/echo';
12
12
  import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';
13
- import { type DataType, type ProjectionModel } from '@dxos/schema';
13
+ import { type ProjectionModel, type View } from '@dxos/schema';
14
14
  import { isNonNullable } from '@dxos/util';
15
15
 
16
16
  import { TableModel, type TableModelProps, type TableRow, type TableRowAction } from '../model';
17
17
 
18
18
  export type UseTableModelParams<T extends TableRow = TableRow> = {
19
- view?: DataType.View;
19
+ view?: View.View;
20
20
  schema?: JsonSchemaType;
21
21
  projection?: ProjectionModel;
22
22
  rows?: Live<T>[];
@@ -39,7 +39,7 @@ export const useTableModel = <T extends TableRow = TableRow>({
39
39
  onRowAction,
40
40
  ...props
41
41
  }: UseTableModelParams<T>): TableModel<T> | undefined => {
42
- const selected = useSelected(view && fullyQualifiedId(view), 'multi');
42
+ const selected = useSelected(view && Obj.getDXN(view).toString(), 'multi');
43
43
  const initialSelection = useMemo(() => selected, [view]);
44
44
 
45
45
  const [model, setModel] = useState<TableModel<T>>();
@@ -5,21 +5,12 @@
5
5
  import { signal } from '@preact/signals-core';
6
6
  import { type MutableRefObject, type RefObject } from 'react';
7
7
 
8
- import { type Live } from '@dxos/live-object';
9
- import { log } from '@dxos/log';
10
-
11
8
  export type ColumnSettingsMode = { type: 'create' } | { type: 'edit'; fieldId: string };
12
9
 
13
10
  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
  };