@dxos/react-ui-table 0.8.4-main.5ad4a44 → 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 (92) 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-OZTXLZGM.mjs → chunk-KNC7XYT6.mjs} +521 -542
  4. package/dist/lib/browser/chunk-KNC7XYT6.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +75 -59
  6. package/dist/lib/browser/index.mjs.map +3 -3
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/testing/index.mjs +14 -27
  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-S56Z4V4X.mjs → chunk-CWSGS3Y2.mjs} +521 -542
  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 +75 -59
  16. package/dist/lib/node-esm/index.mjs.map +3 -3
  17. package/dist/lib/node-esm/meta.json +1 -1
  18. package/dist/lib/node-esm/testing/index.mjs +14 -27
  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.map +1 -1
  22. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +2 -2
  23. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  24. package/dist/types/src/components/Table/Relations.stories.d.ts +7 -7
  25. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  26. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/Table.stories.d.ts +67 -4
  28. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  29. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  30. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +2 -2
  31. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  32. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +2 -2
  33. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
  34. package/dist/types/src/components/testing.d.ts +0 -2
  35. package/dist/types/src/components/testing.d.ts.map +1 -1
  36. package/dist/types/src/hooks/index.d.ts +2 -1
  37. package/dist/types/src/hooks/index.d.ts.map +1 -1
  38. package/dist/types/src/hooks/useProjectionModel.d.ts +5 -0
  39. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
  40. package/dist/types/src/hooks/useTableModel.d.ts +4 -5
  41. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  42. package/dist/types/src/model/table-model.d.ts +7 -9
  43. package/dist/types/src/model/table-model.d.ts.map +1 -1
  44. package/dist/types/src/model/table-sorting.d.ts +8 -4
  45. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  46. package/dist/types/src/playwright/TableManager.d.ts +1 -1
  47. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  48. package/dist/types/src/testing/useTestTableModel.d.ts +21 -15
  49. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  50. package/dist/types/src/translations.d.ts +2 -2
  51. package/dist/types/src/types/Table.d.ts +28 -0
  52. package/dist/types/src/types/Table.d.ts.map +1 -0
  53. package/dist/types/src/types/index.d.ts +1 -1
  54. package/dist/types/src/types/index.d.ts.map +1 -1
  55. package/dist/types/src/util/dynamic-table.d.ts +7 -6
  56. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  57. package/dist/types/tsconfig.tsbuildinfo +1 -1
  58. package/package.json +41 -40
  59. package/src/components/Table/DynamicTable.stories.tsx +7 -7
  60. package/src/components/Table/DynamicTable.tsx +12 -4
  61. package/src/components/Table/Relations.stories.tsx +24 -35
  62. package/src/components/Table/RowActionsMenu.tsx +21 -13
  63. package/src/components/Table/Table.stories.tsx +58 -53
  64. package/src/components/TableCellEditor/FormCellEditor.tsx +11 -11
  65. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +11 -9
  66. package/src/components/TableCellEditor/TableCellEditor.tsx +4 -4
  67. package/src/components/TableToolbar/TableToolbar.tsx +32 -22
  68. package/src/components/testing.ts +3 -3
  69. package/src/hooks/index.ts +2 -1
  70. package/src/hooks/useProjectionModel.ts +19 -0
  71. package/src/hooks/useTableModel.ts +11 -13
  72. package/src/model/table-model.test.ts +5 -5
  73. package/src/model/table-model.ts +34 -40
  74. package/src/model/table-presentation.test.ts +5 -5
  75. package/src/model/table-presentation.ts +9 -9
  76. package/src/model/table-sorting.ts +28 -19
  77. package/src/playwright/TableManager.ts +3 -3
  78. package/src/playwright/smoke.spec.ts +8 -6
  79. package/src/testing/useTestTableModel.ts +32 -24
  80. package/src/translations.ts +2 -2
  81. package/src/types/Table.ts +89 -0
  82. package/src/types/index.ts +1 -1
  83. package/src/util/dynamic-table.ts +12 -16
  84. package/dist/lib/browser/chunk-4K7JPALT.mjs +0 -66
  85. package/dist/lib/browser/chunk-4K7JPALT.mjs.map +0 -7
  86. package/dist/lib/browser/chunk-OZTXLZGM.mjs.map +0 -7
  87. package/dist/lib/node-esm/chunk-MK6OVUF6.mjs +0 -67
  88. package/dist/lib/node-esm/chunk-MK6OVUF6.mjs.map +0 -7
  89. package/dist/lib/node-esm/chunk-S56Z4V4X.mjs.map +0 -7
  90. package/dist/types/src/types/schema.d.ts +0 -28
  91. package/dist/types/src/types/schema.d.ts.map +0 -1
  92. package/src/types/schema.ts +0 -73
@@ -6,26 +6,18 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import * as Schema from 'effect/Schema';
7
7
  import React, { useCallback } from 'react';
8
8
 
9
- import { Filter, Obj, Query, Type } from '@dxos/echo';
10
- import {
11
- EchoObject,
12
- FormatAnnotation,
13
- FormatEnum,
14
- GeneratorAnnotation,
15
- LabelAnnotation,
16
- PropertyMetaAnnotationId,
17
- } from '@dxos/echo/internal';
18
- import { live } from '@dxos/echo/internal';
9
+ import { Annotation, Format, Obj, type QueryAST, Type } from '@dxos/echo';
10
+ import { PropertyMetaAnnotationId } from '@dxos/echo/internal';
19
11
  import { invariant } from '@dxos/invariant';
20
12
  import { faker } from '@dxos/random';
21
13
  import { PublicKey } from '@dxos/react-client';
22
14
  import { type Space } from '@dxos/react-client/echo';
23
15
  import { withClientProvider } from '@dxos/react-client/testing';
24
16
  import { withTheme } from '@dxos/react-ui/testing';
25
- import { ViewEditor } from '@dxos/react-ui-form';
17
+ import { ViewEditor, translations as formTranslations } from '@dxos/react-ui-form';
26
18
  import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
27
- import { DataType, getSchemaFromPropertyDefinitions } from '@dxos/schema';
28
- import { Testing, createObjectFactory } from '@dxos/schema/testing';
19
+ import { View, getSchemaFromPropertyDefinitions, getTypenameFromQuery } from '@dxos/schema';
20
+ import { TestSchema, createObjectFactory } from '@dxos/schema/testing';
29
21
 
30
22
  import { useTestTableModel } from '../../testing';
31
23
  import { translations } from '../../translations';
@@ -34,13 +26,13 @@ import { TableToolbar } from '../TableToolbar';
34
26
 
35
27
  import { Table as TableComponent } from './Table';
36
28
 
37
- const TestSchema = Schema.Struct({
29
+ const Example = Schema.Struct({
38
30
  // TODO(wittjosiah): Should be title. Currently name to work with default label.
39
31
  name: Schema.optional(Schema.String).annotations({ title: 'Title' }),
40
32
  urgent: Schema.optional(Schema.Boolean).annotations({ title: 'Urgent' }),
41
33
  status: Schema.optional(
42
34
  Schema.Literal('todo', 'in-progress', 'done')
43
- .pipe(FormatAnnotation.set(FormatEnum.SingleSelect))
35
+ .pipe(Format.FormatAnnotation.set(Format.TypeFormat.SingleSelect))
44
36
  .annotations({
45
37
  title: 'Status',
46
38
  [PropertyMetaAnnotationId]: {
@@ -55,14 +47,14 @@ const TestSchema = Schema.Struct({
55
47
  }),
56
48
  ),
57
49
  description: Schema.optional(Schema.String).annotations({ title: 'Description' }),
58
- parent: Schema.optional(Schema.suspend((): Type.Ref<TestSchema> => Type.Ref(TestSchema))).annotations({
50
+ parent: Schema.optional(Schema.suspend((): Type.Ref<Example> => Type.Ref(Example))).annotations({
59
51
  title: 'Parent',
60
52
  }),
61
53
  }).pipe(
62
54
  Type.Obj({ typename: `example.com/type/${PublicKey.random().truncate()}`, version: '0.1.0' }),
63
- LabelAnnotation.set(['name']),
55
+ Annotation.LabelAnnotation.set(['name']),
64
56
  );
65
- interface TestSchema extends Schema.Schema.Type<typeof TestSchema> {}
57
+ interface Example extends Schema.Schema.Type<typeof Example> {}
66
58
 
67
59
  const StoryViewEditor = ({
68
60
  view,
@@ -70,18 +62,18 @@ const StoryViewEditor = ({
70
62
  space,
71
63
  handleDeleteColumn,
72
64
  }: {
73
- view?: DataType.View;
65
+ view?: View.View;
74
66
  schema?: Schema.Schema.AnyNoContext;
75
67
  space?: Space;
76
68
  handleDeleteColumn: (fieldId: string) => void;
77
69
  }) => {
78
70
  const handleQueryChanged = useCallback(
79
- (typename: string) => {
71
+ (newQuery: QueryAST.Query) => {
80
72
  invariant(schema);
81
73
  invariant(Type.isMutable(schema));
82
- schema.updateTypename(typename);
74
+ schema.updateTypename(getTypenameFromQuery(newQuery));
83
75
  invariant(view);
84
- view.query.ast = Query.select(Filter.typename(typename)).ast;
76
+ view.query.ast = newQuery;
85
77
  },
86
78
  [schema, view],
87
79
  );
@@ -109,7 +101,7 @@ const DefaultStory = () => {
109
101
  const {
110
102
  space,
111
103
  schema,
112
- view,
104
+ table,
113
105
  tableRef,
114
106
  model,
115
107
  presentation,
@@ -119,7 +111,7 @@ const DefaultStory = () => {
119
111
  handleDeleteColumn,
120
112
  } = useTestTableModel();
121
113
 
122
- if (!schema || !view) {
114
+ if (!schema || !table?.view.target) {
123
115
  return <div />;
124
116
  }
125
117
 
@@ -138,10 +130,15 @@ const DefaultStory = () => {
138
130
  />
139
131
  </TableComponent.Root>
140
132
  </div>
141
- <div className='flex flex-col h-full border-l border-separator overflow-y-auto'>
142
- <StoryViewEditor view={view} schema={schema} space={space} handleDeleteColumn={handleDeleteColumn} />
133
+ <div className='flex flex-col bs-full border-l border-separator overflow-y-auto'>
134
+ <StoryViewEditor
135
+ view={table.view.target}
136
+ schema={schema}
137
+ space={space}
138
+ handleDeleteColumn={handleDeleteColumn}
139
+ />
143
140
  <SyntaxHighlighter language='json' className='text-xs'>
144
- {JSON.stringify({ view, schema }, null, 2)}
141
+ {JSON.stringify({ view: table.view.target, schema }, null, 2)}
145
142
  </SyntaxHighlighter>
146
143
  </div>
147
144
  </div>
@@ -162,18 +159,19 @@ const meta = {
162
159
  decorators: [
163
160
  withTheme,
164
161
  withClientProvider({
165
- types: [DataType.View, Table.Table],
162
+ types: [View.View, Table.Table],
166
163
  createIdentity: true,
167
164
  createSpace: true,
168
165
  onCreateSpace: async ({ client, space }) => {
169
- const [schema] = await space.db.schemaRegistry.register([TestSchema]);
170
- const { view } = await Table.makeView({ client, space, typename: schema.typename });
166
+ const [schema] = await space.db.schemaRegistry.register([Example]);
167
+ const { view, jsonSchema } = await View.makeFromSpace({ client, space, typename: schema.typename });
168
+ const table = Table.make({ view, jsonSchema });
171
169
  view.projection.fields = [
172
- view.projection.fields.find((field) => field.path === 'name')!,
173
- ...view.projection.fields.filter((field) => field.path !== 'name'),
170
+ view.projection.fields.find((field: any) => field.path === 'name')!,
171
+ ...view.projection.fields.filter((field: any) => field.path !== 'name'),
174
172
  ];
175
173
 
176
- space.db.add(view);
174
+ space.db.add(table);
177
175
 
178
176
  Array.from({ length: 10 }).map(() => {
179
177
  return space.db.add(
@@ -192,7 +190,7 @@ const meta = {
192
190
  controls: {
193
191
  disable: true,
194
192
  },
195
- translations,
193
+ translations: [...translations, ...formTranslations],
196
194
  },
197
195
  } satisfies Meta<typeof TableComponent>;
198
196
 
@@ -206,29 +204,30 @@ export const StaticSchema: StoryObj = {
206
204
  render: DefaultStory,
207
205
  decorators: [
208
206
  withClientProvider({
209
- types: [DataType.View, Table.Table, Testing.Contact, Testing.Organization],
207
+ types: [View.View, Table.Table],
210
208
  createIdentity: true,
211
209
  createSpace: true,
212
210
  onCreateSpace: async ({ client, space }) => {
213
- const { view } = await Table.makeView({ client, space, typename: Testing.Contact.typename });
214
- space.db.add(view);
211
+ const { view, jsonSchema } = await View.makeFromSpace({ client, space, typename: TestSchema.Person.typename });
212
+ const table = Table.make({ view, jsonSchema });
213
+ space.db.add(table);
215
214
 
216
215
  const factory = createObjectFactory(space.db, faker as any);
217
216
  await factory([
218
- { type: Testing.Contact, count: 10 },
219
- // { type: Testing.Organization, count: 1 },
217
+ { type: TestSchema.Person, count: 10 },
218
+ // { type: TestSchema.Organization, count: 1 },
220
219
  ]);
221
220
  },
222
221
  }),
223
222
  ],
224
223
  parameters: {
225
224
  layout: 'fullscreen',
226
- translations,
225
+ translations: [...translations, ...formTranslations],
227
226
  },
228
227
  };
229
228
 
230
229
  const ContactWithArrayOfEmails = Schema.Struct({
231
- name: Schema.String.pipe(GeneratorAnnotation.set('person.fullName')),
230
+ name: Schema.String.pipe(Annotation.GeneratorAnnotation.set('person.fullName')),
232
231
  emails: Schema.optional(
233
232
  Schema.Array(
234
233
  Schema.Struct({
@@ -238,7 +237,7 @@ const ContactWithArrayOfEmails = Schema.Struct({
238
237
  ),
239
238
  ),
240
239
  }).pipe(
241
- EchoObject({
240
+ Type.Obj({
242
241
  typename: 'dxos.org/type/ContactWithArrayOfEmails',
243
242
  version: '0.1.0',
244
243
  }),
@@ -248,17 +247,22 @@ export const ArrayOfObjects: StoryObj = {
248
247
  render: DefaultStory,
249
248
  decorators: [
250
249
  withClientProvider({
251
- types: [DataType.View, Table.Table, Testing.Contact, Testing.Organization, ContactWithArrayOfEmails],
250
+ types: [View.View, Table.Table, TestSchema.Person, TestSchema.Organization, ContactWithArrayOfEmails],
252
251
  createIdentity: true,
253
252
  createSpace: true,
254
253
  onCreateSpace: async ({ client, space }) => {
255
- const { view } = await Table.makeView({ client, space, typename: ContactWithArrayOfEmails.typename });
256
- space.db.add(view);
254
+ const { view, jsonSchema } = await View.makeFromSpace({
255
+ client,
256
+ space,
257
+ typename: ContactWithArrayOfEmails.typename,
258
+ });
259
+ const table = Table.make({ view, jsonSchema });
260
+ space.db.add(table);
257
261
 
258
262
  const factory = createObjectFactory(space.db, faker as any);
259
263
  await factory([
260
- // { type: Testing.Contact, count: 10 },
261
- // { type: Testing.Organization, count: 1 },
264
+ // { type: TestSchema.Person, count: 10 },
265
+ // { type: TestSchema.Organization, count: 1 },
262
266
  { type: ContactWithArrayOfEmails, count: 10 },
263
267
  ]);
264
268
  },
@@ -275,7 +279,7 @@ export const Tags: Meta<StoryProps> = {
275
279
  render: DefaultStory,
276
280
  decorators: [
277
281
  withClientProvider({
278
- types: [DataType.View, Table.Table],
282
+ types: [View.View, Table.Table],
279
283
  createIdentity: true,
280
284
  createSpace: true,
281
285
  onCreateSpace: async ({ client, space }) => {
@@ -294,25 +298,26 @@ export const Tags: Meta<StoryProps> = {
294
298
  const schema = getSchemaFromPropertyDefinitions(typename, [
295
299
  {
296
300
  name: 'single',
297
- format: FormatEnum.SingleSelect,
301
+ format: Format.TypeFormat.SingleSelect,
298
302
  config: { options: selectOptions },
299
303
  },
300
304
  {
301
305
  name: 'multiple',
302
- format: FormatEnum.MultiSelect,
306
+ format: Format.TypeFormat.MultiSelect,
303
307
  config: { options: selectOptions },
304
308
  },
305
309
  ]);
306
310
  const [storedSchema] = await space.db.schemaRegistry.register([schema]);
307
311
 
308
312
  // Initialize table.
309
- const { view } = await Table.makeView({ client, space, typename });
310
- space.db.add(view);
313
+ const { view, jsonSchema } = await View.makeFromSpace({ client, space, typename });
314
+ const table = Table.make({ view, jsonSchema });
315
+ space.db.add(table);
311
316
 
312
317
  // Populate.
313
318
  Array.from({ length: 10 }).map(() => {
314
319
  return space.db.add(
315
- live(storedSchema, {
320
+ Obj.make(storedSchema, {
316
321
  single: faker.helpers.arrayElement([...selectOptionIds, undefined]),
317
322
  multiple: faker.helpers.randomSubset(selectOptionIds),
318
323
  }),
@@ -6,7 +6,7 @@ import * as Schema from 'effect/Schema';
6
6
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
7
7
 
8
8
  import { Filter, Obj } from '@dxos/echo';
9
- import { FormatEnum, Ref, type TypeAnnotation, getValue } from '@dxos/echo/internal';
9
+ import { Format, Ref, type TypeAnnotation, getValue } from '@dxos/echo/internal';
10
10
  import { invariant } from '@dxos/invariant';
11
11
  import { getSnapshot } from '@dxos/live-object';
12
12
  import { type Client } from '@dxos/react-client';
@@ -150,24 +150,24 @@ export const FormCellEditor = ({
150
150
  setLocalEditing(nextOpen);
151
151
  }, []);
152
152
 
153
- const createSchema = useMemo(() => {
154
- if (fieldProjection.props.format === FormatEnum.Ref && fieldProjection.props.referenceSchema) {
153
+ const refSchema = useMemo(() => {
154
+ if (fieldProjection.props.format === Format.TypeFormat.Ref && fieldProjection.props.referenceSchema) {
155
155
  const { schema: refSchema } = getSchema({
156
156
  typename: fieldProjection.props.referenceSchema,
157
157
  });
158
- if (!refSchema) {
159
- return null;
160
- }
161
- const omit = Schema.omit<any, any, ['id']>('id');
162
- return omit(refSchema);
158
+ return refSchema;
163
159
  }
164
160
  return null;
165
161
  }, [fieldProjection.props.format, fieldProjection.props.referenceSchema, getSchema]);
166
162
 
163
+ const createSchema = useMemo(() => {
164
+ return refSchema ? Schema.omit<any, any, ['id']>('id')(refSchema) : null;
165
+ }, [refSchema]);
166
+
167
167
  const handleCreate = useCallback(
168
168
  (values: any) => {
169
- if (schema && onCreate) {
170
- const objectWithId = onCreate(schema, values);
169
+ if (refSchema && onCreate) {
170
+ const objectWithId = onCreate(refSchema, values);
171
171
  if (objectWithId) {
172
172
  const ref = Ref.make(objectWithId);
173
173
  const path = fieldProjection.field.path;
@@ -179,7 +179,7 @@ export const FormCellEditor = ({
179
179
  setLocalEditing(false);
180
180
  onSave?.();
181
181
  },
182
- [fieldProjection.field.path, onSave, contextEditing, originalRow, createSchema, onCreate],
182
+ [fieldProjection.field.path, onSave, contextEditing, originalRow, refSchema, onCreate],
183
183
  );
184
184
 
185
185
  if (!editing) {
@@ -10,7 +10,8 @@ import { faker } from '@dxos/random';
10
10
  import { withClientProvider } from '@dxos/react-client/testing';
11
11
  import { withTheme } from '@dxos/react-ui/testing';
12
12
  import { Grid, type GridEditing, defaultRowSize } from '@dxos/react-ui-grid';
13
- import { DataType } from '@dxos/schema';
13
+ import { View } from '@dxos/schema';
14
+ import { Task } from '@dxos/types';
14
15
 
15
16
  import { useTestTableModel } from '../../testing';
16
17
  import { translations } from '../../translations';
@@ -23,16 +24,16 @@ type StoryProps = {
23
24
  };
24
25
 
25
26
  const DefaultStory = ({ editing }: StoryProps) => {
26
- const { model, view } = useTestTableModel();
27
+ const { model, table } = useTestTableModel();
27
28
 
28
- if (!model || !view) {
29
+ if (!model || !table) {
29
30
  return <div />;
30
31
  }
31
32
 
32
33
  return (
33
- <div className='flex w-[300px] border border-separator' style={{ height: defaultRowSize }}>
34
+ <div className='flex is-[300px] border border-separator' style={{ height: defaultRowSize }}>
34
35
  <Grid.Root id='test' editing={editing}>
35
- <TableCellEditor model={model} schema={DataType.Task} />
36
+ <TableCellEditor model={model} schema={Task.Task} />
36
37
  </Grid.Root>
37
38
  </div>
38
39
  );
@@ -45,15 +46,16 @@ const meta = {
45
46
  decorators: [
46
47
  withTheme,
47
48
  withClientProvider({
48
- types: [DataType.View, DataType.Task, Table.Table],
49
+ types: [View.View, Task.Task, Table.Table],
49
50
  createIdentity: true,
50
51
  createSpace: true,
51
52
  onCreateSpace: async ({ client, space }) => {
52
- const { view } = await Table.makeView({ client, space, typename: DataType.Task.typename });
53
- space.db.add(view);
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);
54
56
  Array.from({ length: 10 }).forEach(() => {
55
57
  space.db.add(
56
- Obj.make(DataType.Task, {
58
+ Obj.make(Task.Task, {
57
59
  title: faker.person.fullName(),
58
60
  status: faker.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
59
61
  description: faker.lorem.sentence(),
@@ -9,7 +9,7 @@ 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/internal';
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
15
  import {
@@ -74,9 +74,9 @@ export const TableValueEditor = ({
74
74
 
75
75
  if (
76
76
  fieldProjection?.props.type === TypeEnum.Array ||
77
- fieldProjection?.props.format === FormatEnum.SingleSelect ||
78
- fieldProjection?.props.format === FormatEnum.Ref
79
- // 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`
80
80
  ) {
81
81
  return (
82
82
  <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,
@@ -5,13 +5,13 @@
5
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/internal';
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';
@@ -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/internal';
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(() => {
@@ -12,7 +12,7 @@ import { updateCounter } from '@dxos/echo/testing';
12
12
  import { createEchoSchema } from '@dxos/echo/testing';
13
13
  import { registerSignalsRuntime } from '@dxos/echo-signals';
14
14
  import { live } from '@dxos/live-object';
15
- import { createView } from '@dxos/schema';
15
+ import { ProjectionModel, View } from '@dxos/schema';
16
16
 
17
17
  import { Table } from '../types';
18
18
 
@@ -112,11 +112,11 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
112
112
 
113
113
  const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
114
114
  const schema = createEchoSchema(Test);
115
- const table = Table.make();
116
- const view = createView({
115
+ const view = View.make({
117
116
  query: Query.select(Filter.type(schema)),
118
117
  jsonSchema: schema.jsonSchema,
119
- presentation: table,
120
118
  });
121
- return new TableModel({ view, schema: schema.jsonSchema, ...props });
119
+ const object = Table.make({ view });
120
+ const projection = new ProjectionModel(schema.jsonSchema, view.projection);
121
+ return new TableModel({ object, projection, ...props });
122
122
  };