@dxos/react-ui-table 0.8.4-main.5ea62a8 → 0.8.4-main.66e292d

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 (115) hide show
  1. package/dist/lib/browser/chunk-5XWSB3JP.mjs +71 -0
  2. package/dist/lib/browser/chunk-5XWSB3JP.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-RQYXNWWN.mjs → chunk-KNC7XYT6.mjs} +529 -568
  4. package/dist/lib/browser/chunk-KNC7XYT6.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +187 -322
  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 +15 -25
  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-QNDUGIS7.mjs → chunk-CWSGS3Y2.mjs} +529 -568
  12. package/dist/lib/node-esm/chunk-CWSGS3Y2.mjs.map +7 -0
  13. package/dist/lib/node-esm/chunk-MKTMOY46.mjs +72 -0
  14. package/dist/lib/node-esm/chunk-MKTMOY46.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +187 -322
  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 +15 -25
  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 +5 -5
  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 +73 -9
  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 +5 -2
  29. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  30. package/dist/types/src/components/Table/Table.stories.d.ts +72 -10
  31. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  32. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +7 -5
  33. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  34. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +5 -3
  35. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  36. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +3 -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 -5
  43. package/dist/types/src/components/testing.d.ts.map +1 -1
  44. package/dist/types/src/hooks/index.d.ts +2 -1
  45. package/dist/types/src/hooks/index.d.ts.map +1 -1
  46. package/dist/types/src/hooks/useAddRow.d.ts +1 -1
  47. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  48. package/dist/types/src/hooks/useProjectionModel.d.ts +5 -0
  49. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
  50. package/dist/types/src/hooks/useTableModel.d.ts +4 -5
  51. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  52. package/dist/types/src/model/modal-controller.d.ts +0 -7
  53. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  54. package/dist/types/src/model/table-model.d.ts +8 -10
  55. package/dist/types/src/model/table-model.d.ts.map +1 -1
  56. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  57. package/dist/types/src/model/table-sorting.d.ts +8 -5
  58. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  59. package/dist/types/src/playwright/TableManager.d.ts +1 -1
  60. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  61. package/dist/types/src/testing/useTestTableModel.d.ts +21 -29
  62. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  63. package/dist/types/src/translations.d.ts +2 -2
  64. package/dist/types/src/types/Table.d.ts +28 -0
  65. package/dist/types/src/types/Table.d.ts.map +1 -0
  66. package/dist/types/src/types/index.d.ts +1 -1
  67. package/dist/types/src/types/index.d.ts.map +1 -1
  68. package/dist/types/src/util/dynamic-table.d.ts +9 -9
  69. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  70. package/dist/types/src/util/schema.d.ts +1 -1
  71. package/dist/types/src/util/schema.d.ts.map +1 -1
  72. package/dist/types/tsconfig.tsbuildinfo +1 -1
  73. package/package.json +53 -52
  74. package/src/components/Table/DynamicTable.stories.tsx +14 -13
  75. package/src/components/Table/DynamicTable.tsx +15 -10
  76. package/src/components/Table/Relations.stories.tsx +133 -29
  77. package/src/components/Table/RowActionsMenu.tsx +21 -13
  78. package/src/components/Table/Table.stories.tsx +85 -79
  79. package/src/components/Table/Table.tsx +13 -9
  80. package/src/components/TableCellEditor/FormCellEditor.tsx +51 -41
  81. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +21 -47
  82. package/src/components/TableCellEditor/TableCellEditor.tsx +20 -115
  83. package/src/components/TableCellEditor/index.ts +1 -0
  84. package/src/components/TableToolbar/TableToolbar.tsx +32 -22
  85. package/src/components/testing.ts +4 -4
  86. package/src/hooks/index.ts +2 -1
  87. package/src/hooks/useAddRow.ts +1 -1
  88. package/src/hooks/useProjectionModel.ts +19 -0
  89. package/src/hooks/useTableModel.ts +11 -13
  90. package/src/model/modal-controller.ts +0 -28
  91. package/src/model/table-model.test.ts +19 -9
  92. package/src/model/table-model.ts +42 -41
  93. package/src/model/table-presentation.test.ts +22 -20
  94. package/src/model/table-presentation.ts +12 -13
  95. package/src/model/table-sorting.ts +31 -13
  96. package/src/playwright/TableManager.ts +3 -3
  97. package/src/playwright/smoke.spec.ts +8 -6
  98. package/src/testing/useTestTableModel.ts +33 -22
  99. package/src/translations.ts +2 -2
  100. package/src/types/Table.ts +89 -0
  101. package/src/types/index.ts +1 -1
  102. package/src/util/dynamic-table.ts +17 -21
  103. package/src/util/schema.ts +4 -4
  104. package/dist/lib/browser/chunk-63UUF4IG.mjs +0 -66
  105. package/dist/lib/browser/chunk-63UUF4IG.mjs.map +0 -7
  106. package/dist/lib/browser/chunk-RQYXNWWN.mjs.map +0 -7
  107. package/dist/lib/node-esm/chunk-K734DJB7.mjs +0 -67
  108. package/dist/lib/node-esm/chunk-K734DJB7.mjs.map +0 -7
  109. package/dist/lib/node-esm/chunk-QNDUGIS7.mjs.map +0 -7
  110. package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
  111. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
  112. package/dist/types/src/types/schema.d.ts +0 -28
  113. package/dist/types/src/types/schema.d.ts.map +0 -1
  114. package/src/components/Table/CreateRefPanel.tsx +0 -103
  115. package/src/types/schema.ts +0 -73
@@ -2,23 +2,18 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
- import React, { useEffect, useMemo, useState } from 'react';
6
+ import React from 'react';
9
7
 
10
8
  import { Obj } from '@dxos/echo';
11
- import { type EchoSchema, isMutable } from '@dxos/echo-schema';
12
- import { invariant } from '@dxos/invariant';
13
9
  import { faker } from '@dxos/random';
14
- import { Filter, useQuery } from '@dxos/react-client/echo';
15
- import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
10
+ import { withClientProvider } from '@dxos/react-client/testing';
11
+ import { withTheme } from '@dxos/react-ui/testing';
16
12
  import { Grid, type GridEditing, defaultRowSize } from '@dxos/react-ui-grid';
17
- import { DataType, createDefaultSchema } from '@dxos/schema';
18
- import { withLayout, withTheme } from '@dxos/storybook-utils';
13
+ import { View } from '@dxos/schema';
14
+ import { Task } from '@dxos/types';
19
15
 
20
- import { useTableModel } from '../../hooks';
21
- import { type TableFeatures } from '../../model';
16
+ import { useTestTableModel } from '../../testing';
22
17
  import { translations } from '../../translations';
23
18
  import { Table } from '../../types';
24
19
 
@@ -29,39 +24,16 @@ type StoryProps = {
29
24
  };
30
25
 
31
26
  const DefaultStory = ({ editing }: StoryProps) => {
32
- const { space } = useClientProvider();
33
- invariant(space);
34
-
35
- const views = useQuery(space, Filter.type(DataType.View));
36
- const [view, setView] = useState<DataType.View>();
37
- const [schema, setSchema] = useState<EchoSchema>();
38
- useEffect(() => {
39
- if (space && views.length && !view) {
40
- const view = views[0];
41
- setView(view);
42
- setSchema(space.db.schemaRegistry.getSchema(view.query.typename!));
43
- }
44
- }, [space, views, view]);
45
-
46
- const features: Partial<TableFeatures> = useMemo(
47
- () => ({
48
- selection: { enabled: true, mode: 'multiple' },
49
- dataEditable: true,
50
- schemaEditable: schema && isMutable(schema),
51
- }),
52
- [schema],
53
- );
27
+ const { model, table } = useTestTableModel();
54
28
 
55
- const model = useTableModel({ view, schema: schema?.jsonSchema, features });
56
-
57
- if (!model || !schema || !view) {
29
+ if (!model || !table) {
58
30
  return <div />;
59
31
  }
60
32
 
61
33
  return (
62
- <div className='flex w-[300px] border border-separator' style={{ height: defaultRowSize }}>
34
+ <div className='flex is-[300px] border border-separator' style={{ height: defaultRowSize }}>
63
35
  <Grid.Root id='test' editing={editing}>
64
- <TableCellEditor model={model} schema={schema} />
36
+ <TableCellEditor model={model} schema={Task.Task} />
65
37
  </Grid.Root>
66
38
  </div>
67
39
  );
@@ -71,19 +43,19 @@ const meta = {
71
43
  title: 'ui/react-ui-table/TableCellEditor',
72
44
  component: DefaultStory,
73
45
  render: DefaultStory,
74
- parameters: { translations, layout: 'centered' },
75
46
  decorators: [
47
+ withTheme,
76
48
  withClientProvider({
77
- types: [DataType.View, Table.Table],
49
+ types: [View.View, Task.Task, Table.Table],
78
50
  createIdentity: true,
79
51
  createSpace: true,
80
- onSpaceCreated: async ({ client, space }) => {
81
- const schema = createDefaultSchema();
82
- const { view } = await Table.makeView({ client, space, typename: schema.typename });
83
- space.db.add(view);
52
+ onCreateSpace: async ({ client, space }) => {
53
+ const { view, jsonSchema } = await View.makeFromSpace({ client, space, typename: Task.Task.typename });
54
+ const table = Table.make({ view, jsonSchema });
55
+ space.db.add(table);
84
56
  Array.from({ length: 10 }).forEach(() => {
85
57
  space.db.add(
86
- Obj.make(schema, {
58
+ Obj.make(Task.Task, {
87
59
  title: faker.person.fullName(),
88
60
  status: faker.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
89
61
  description: faker.lorem.sentence(),
@@ -92,9 +64,11 @@ const meta = {
92
64
  });
93
65
  },
94
66
  }),
95
- withTheme,
96
- withLayout(),
97
67
  ],
68
+ parameters: {
69
+ translations,
70
+ layout: 'centered',
71
+ },
98
72
  } satisfies Meta<typeof DefaultStory>;
99
73
 
100
74
  export default meta;
@@ -4,16 +4,14 @@
4
4
 
5
5
  import { type Completion } from '@codemirror/autocomplete';
6
6
  import { EditorView } from '@codemirror/view';
7
- import { type Schema } from 'effect/Schema';
7
+ import type * as Schema from 'effect/Schema';
8
8
  import React, { useCallback, useMemo, useRef, useState } from 'react';
9
9
 
10
10
  import { debounce } from '@dxos/async';
11
11
  import type { Client } from '@dxos/client';
12
- import { FormatEnum, TypeEnum } from '@dxos/echo-schema';
12
+ import { Format, TypeEnum } from '@dxos/echo/internal';
13
13
  import { invariant } from '@dxos/invariant';
14
14
  import { type DxGridAxis, type DxGridPosition } from '@dxos/lit-grid';
15
- import { useThemeContext } from '@dxos/react-ui';
16
- import { createMarkdownExtensions } from '@dxos/react-ui-editor';
17
15
  import {
18
16
  type EditorBlurHandler,
19
17
  type EditorKeyEvent,
@@ -25,13 +23,12 @@ import {
25
23
  parseCellIndex,
26
24
  useGridContext,
27
25
  } from '@dxos/react-ui-grid';
28
- import { createLinks, tagPicker } from '@dxos/react-ui-tag-picker';
29
26
  import { type FieldProjection } from '@dxos/schema';
30
27
 
31
28
  import { type ModalController, type TableModel } from '../../model';
32
29
 
33
30
  import { CellValidationMessage } from './CellValidationMessage';
34
- import { FormCellEditor } from './FormCellEditor';
31
+ import { FormCellEditor, type OnCreateHandler } from './FormCellEditor';
35
32
 
36
33
  /**
37
34
  * Option to create new object/value.
@@ -41,8 +38,9 @@ export type QueryResult = Pick<Completion, 'label'> & { data: any };
41
38
  export type TableCellEditorProps = {
42
39
  model?: TableModel;
43
40
  modals?: ModalController;
44
- schema?: Schema.AnyNoContext;
41
+ schema?: Schema.Schema.AnyNoContext;
45
42
  onFocus?: (axis?: DxGridAxis, delta?: -1 | 0 | 1, cell?: DxGridPosition) => void;
43
+ onCreate?: OnCreateHandler;
46
44
  onSave?: () => void;
47
45
  client?: Client;
48
46
  };
@@ -56,6 +54,7 @@ export const TableValueEditor = ({
56
54
  schema,
57
55
  onFocus,
58
56
  onSave,
57
+ onCreate,
59
58
  client,
60
59
  __gridScope,
61
60
  }: GridScopedProps<TableCellEditorProps>) => {
@@ -75,9 +74,9 @@ export const TableValueEditor = ({
75
74
 
76
75
  if (
77
76
  fieldProjection?.props.type === TypeEnum.Array ||
78
- fieldProjection?.props.format === FormatEnum.SingleSelect ||
79
- fieldProjection?.props.format === FormatEnum.Ref
80
- // TODO(thure): Support `FormatEnum.MultiSelect`
77
+ fieldProjection?.props.format === Format.TypeFormat.SingleSelect ||
78
+ fieldProjection?.props.format === Format.TypeFormat.Ref
79
+ // TODO(thure): Support `Format.TypeFormat.MultiSelect`
81
80
  ) {
82
81
  return (
83
82
  <FormCellEditor
@@ -86,6 +85,7 @@ export const TableValueEditor = ({
86
85
  schema={schema}
87
86
  __gridScope={__gridScope}
88
87
  onSave={onSave}
88
+ onCreate={onCreate}
89
89
  client={client}
90
90
  modals={modals}
91
91
  />
@@ -96,7 +96,7 @@ export const TableValueEditor = ({
96
96
  return <TableCellEditor model={model} modals={modals} onFocus={onFocus} onSave={onSave} __gridScope={__gridScope} />;
97
97
  };
98
98
 
99
- const editorSlots = { scroller: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
99
+ const editorSlots = { scroll: { className: '!plb-[--dx-grid-cell-editor-padding-block]' } };
100
100
 
101
101
  export const TableCellEditor = ({
102
102
  model,
@@ -105,9 +105,8 @@ export const TableCellEditor = ({
105
105
  onSave,
106
106
  __gridScope,
107
107
  }: GridScopedProps<TableCellEditorProps>) => {
108
- const { id: gridId, editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
108
+ const { editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
109
109
  const suppressNextBlur = useRef(false);
110
- const { themeMode } = useThemeContext();
111
110
  const [validationError, setValidationError] = useState<string | null>(null);
112
111
  const [validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
113
112
 
@@ -174,7 +173,6 @@ export const TableCellEditor = ({
174
173
  }
175
174
 
176
175
  const cell = parseCellIndex(editing.index);
177
-
178
176
  if (value !== undefined) {
179
177
  // Pre-commit validation check.
180
178
  const result = await model.validateCellData(cell, value);
@@ -205,71 +203,26 @@ export const TableCellEditor = ({
205
203
  [model, editing, onFocus, fieldProjection, setEditing, onSave],
206
204
  );
207
205
 
208
- const extension = useMemo(() => {
206
+ const extensions = useMemo(() => {
209
207
  if (!fieldProjection) {
210
208
  return [];
211
209
  }
212
210
 
213
- const extension = [
211
+ const extensions = [
214
212
  editorKeys({
215
213
  onClose: handleClose,
216
214
  ...(editing?.initialContent && { onNav: handleClose }),
217
215
  }),
218
216
  ];
219
-
220
- const format = fieldProjection.props.format;
221
-
222
- if (format === FormatEnum.SingleSelect || format === FormatEnum.MultiSelect) {
223
- // TODO(ZaymonFC): Reconcile this with the TagPicker component?
224
- // Add markdown extensions needed by tag picker.
225
- extension.push(createMarkdownExtensions({ themeMode }));
226
-
227
- const options = fieldProjection.props.options || [];
228
-
229
- const mode = format === FormatEnum.SingleSelect ? ('single-select' as const) : ('multi-select' as const);
230
-
231
- extension.push(
232
- tagPicker({
233
- mode,
234
- keymap: false,
235
- onSearch: (text, selectedIds) => {
236
- return options
237
- .filter(
238
- (option) =>
239
- selectedIds.indexOf(option.id) === -1 &&
240
- (text.length === 0 || option.title.toLowerCase().includes(text.toLowerCase())),
241
- )
242
- .map((option) => ({
243
- id: option.id,
244
- label: option.title,
245
- hue: option.color as any,
246
- }));
247
- },
248
- onUpdate: (ids) => {
249
- if (model && editing) {
250
- if (ids.length === 0) {
251
- return;
252
- }
253
- if (mode === 'single-select') {
254
- void handleEnter(ids[0]);
255
- } else {
256
- void handleEnter(ids);
257
- }
258
- }
259
- },
260
- }),
261
- );
262
- }
263
-
264
- // Add validation extension to handle content changes
217
+ // Add validation extension to handle content changes.
265
218
  if (model && editing) {
266
- extension.push(
219
+ extensions.push(
267
220
  EditorView.updateListener.of(
268
221
  debounce((update) => {
269
222
  const content = update.state.doc.toString();
270
223
  const cell = parseCellIndex(editing.index);
271
224
 
272
- // Perform validation on content change
225
+ // Perform validation on content change.
273
226
  void model.validateCellData(cell, content).then((result) => {
274
227
  if (result.valid) {
275
228
  setValidationError(null);
@@ -283,60 +236,12 @@ export const TableCellEditor = ({
283
236
  );
284
237
  }
285
238
 
286
- return extension;
287
- }, [model, modals, editing, fieldProjection, handleClose, themeMode]);
239
+ return extensions;
240
+ }, [model, modals, editing, fieldProjection, handleClose]);
288
241
 
289
242
  const getCellContent = useCallback<GridCellEditorProps['getCellContent']>(() => {
290
243
  if (model && editing) {
291
244
  const cell = parseCellIndex(editing.index);
292
- const { col } = cell;
293
- const field = model.projection.fields[col];
294
- const fieldProjection = model.projection.getFieldProjection(field.id);
295
-
296
- if (
297
- fieldProjection?.props.format === FormatEnum.SingleSelect ||
298
- fieldProjection?.props.format === FormatEnum.MultiSelect
299
- ) {
300
- const value = model.getCellData(cell);
301
-
302
- if (value !== undefined) {
303
- const options = fieldProjection.props.options || [];
304
-
305
- if (fieldProjection.props.format === FormatEnum.MultiSelect) {
306
- const tagItems = value
307
- .split(',')
308
- .map((id: string) => {
309
- const option = options.find((o) => o.id === id);
310
- if (option) {
311
- return {
312
- id,
313
- label: option.title,
314
- hue: option.color as any,
315
- };
316
- }
317
- return undefined;
318
- })
319
- .filter((item: any): item is { id: any; label: string; hue: any } => item !== undefined);
320
-
321
- return createLinks(tagItems);
322
- } else {
323
- const option = options.find((o) => o.id === value);
324
-
325
- if (option) {
326
- const tagItem = {
327
- id: value,
328
- label: option.title,
329
- hue: option.color as any,
330
- };
331
-
332
- return createLinks([tagItem]);
333
- }
334
- }
335
- }
336
-
337
- return '';
338
- }
339
-
340
245
  const value = model.getCellData(cell);
341
246
  return value !== undefined ? String(value) : '';
342
247
  }
@@ -345,7 +250,7 @@ export const TableCellEditor = ({
345
250
  return (
346
251
  <>
347
252
  <CellValidationMessage validationError={validationError} variant={validationVariant} __gridScope={__gridScope} />
348
- <GridCellEditor extension={extension} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
253
+ <GridCellEditor extensions={extensions} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
349
254
  </>
350
255
  );
351
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 label', { 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,16 +2,16 @@
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 { Format } from '@dxos/echo';
9
+ import { TypeEnum, TypedObject, setValue } from '@dxos/echo/internal';
9
10
  import { live } from '@dxos/live-object';
10
11
  import { faker } from '@dxos/random';
11
12
  import { type ProjectionModel } from '@dxos/schema';
12
13
 
13
14
  export const TestSchema = TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
14
- id: ObjectId,
15
15
  name: Schema.optional(Schema.String),
16
16
  age: Schema.optional(Schema.Number),
17
17
  active: Schema.optional(Schema.Boolean),
@@ -88,7 +88,7 @@ export const useSimulator = ({ items, projection, insertInterval, updateInterval
88
88
 
89
89
  if (format) {
90
90
  switch (format) {
91
- case FormatEnum.Currency: {
91
+ case Format.TypeFormat.Currency: {
92
92
  item[path] = Math.floor(Math.random() * 1000);
93
93
  break;
94
94
  }
@@ -2,5 +2,6 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- export * from './useTableModel';
6
5
  export * from './useAddRow';
6
+ export * from './useProjectionModel';
7
+ export * from './useTableModel';
@@ -2,7 +2,7 @@
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';
@@ -0,0 +1,19 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { useMemo } from 'react';
6
+
7
+ import { Type } from '@dxos/echo';
8
+ import { ProjectionModel } from '@dxos/schema';
9
+
10
+ import { type Table } from '../types';
11
+
12
+ export const useProjectionModel = <S extends Type.Entity.Any>(schema: S | undefined, table: Table.Table | undefined) =>
13
+ useMemo(() => {
14
+ if (schema && table?.view.target?.projection) {
15
+ const projection = new ProjectionModel(Type.toJsonSchema(schema), table.view.target.projection);
16
+ projection.normalizeView();
17
+ return projection;
18
+ }
19
+ }, [schema, table?.view.target?.projection]);
@@ -6,18 +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
10
  import { type Live } from '@dxos/live-object';
11
- import { fullyQualifiedId } from '@dxos/react-client/echo';
12
11
  import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';
13
- import { type DataType, type ProjectionModel } from '@dxos/schema';
12
+ import { type ProjectionModel } from '@dxos/schema';
14
13
  import { isNonNullable } from '@dxos/util';
15
14
 
16
15
  import { TableModel, type TableModelProps, type TableRow, type TableRowAction } from '../model';
16
+ import { type Table } from '../types';
17
17
 
18
18
  export type UseTableModelParams<T extends TableRow = TableRow> = {
19
- view?: DataType.View;
20
- schema?: JsonSchemaType;
19
+ object?: Table.Table;
21
20
  projection?: ProjectionModel;
22
21
  rows?: Live<T>[];
23
22
  rowActions?: TableRowAction[];
@@ -29,8 +28,7 @@ export type UseTableModelParams<T extends TableRow = TableRow> = {
29
28
  >;
30
29
 
31
30
  export const useTableModel = <T extends TableRow = TableRow>({
32
- view,
33
- schema,
31
+ object,
34
32
  projection,
35
33
  rows,
36
34
  rowActions,
@@ -39,20 +37,19 @@ export const useTableModel = <T extends TableRow = TableRow>({
39
37
  onRowAction,
40
38
  ...props
41
39
  }: UseTableModelParams<T>): TableModel<T> | undefined => {
42
- const selected = useSelected(view && fullyQualifiedId(view), 'multi');
43
- const initialSelection = useMemo(() => selected, [view]);
40
+ const selected = useSelected(object && Obj.getDXN(object).toString(), 'multi');
41
+ const initialSelection = useMemo(() => selected, [object]);
44
42
 
45
43
  const [model, setModel] = useState<TableModel<T>>();
46
44
  useEffect(() => {
47
- if (!view || !schema) {
45
+ if (!object || !projection) {
48
46
  return;
49
47
  }
50
48
 
51
49
  let model: TableModel<T> | undefined;
52
50
  const t = setTimeout(async () => {
53
51
  model = new TableModel<T>({
54
- view,
55
- schema,
52
+ object,
56
53
  projection,
57
54
  features,
58
55
  rowActions,
@@ -68,7 +65,8 @@ export const useTableModel = <T extends TableRow = TableRow>({
68
65
  clearTimeout(t);
69
66
  void model?.close();
70
67
  };
71
- }, [view, schema, projection, features, rowActions, initialSelection]); // TODO(burdon): Trigger if callbacks change?
68
+ // TODO(burdon): Trigger if callbacks change?
69
+ }, [object, projection, features, rowActions, initialSelection]);
72
70
 
73
71
  // Update data.
74
72
  useEffect(() => {
@@ -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
  };