@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,31 +2,22 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
- import { Schema } from 'effect';
6
+ import * as Schema from 'effect/Schema';
9
7
  import React, { useCallback } from 'react';
10
8
 
11
- import { Obj, Type } from '@dxos/echo';
12
- import {
13
- EchoObject,
14
- FormatAnnotation,
15
- FormatEnum,
16
- GeneratorAnnotation,
17
- LabelAnnotation,
18
- PropertyMetaAnnotationId,
19
- } from '@dxos/echo-schema';
9
+ import { Annotation, Format, Obj, type QueryAST, Type } from '@dxos/echo';
10
+ import { PropertyMetaAnnotationId } from '@dxos/echo/internal';
20
11
  import { invariant } from '@dxos/invariant';
21
12
  import { faker } from '@dxos/random';
22
13
  import { PublicKey } from '@dxos/react-client';
23
- import { type Space, live } from '@dxos/react-client/echo';
14
+ import { type Space } from '@dxos/react-client/echo';
24
15
  import { withClientProvider } from '@dxos/react-client/testing';
25
- import { ViewEditor } from '@dxos/react-ui-form';
16
+ import { withTheme } from '@dxos/react-ui/testing';
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';
29
- import { withLayout, withTheme } from '@dxos/storybook-utils';
19
+ import { View, getSchemaFromPropertyDefinitions, getTypenameFromQuery } from '@dxos/schema';
20
+ import { TestSchema, createObjectFactory } from '@dxos/schema/testing';
30
21
 
31
22
  import { useTestTableModel } from '../../testing';
32
23
  import { translations } from '../../translations';
@@ -35,13 +26,13 @@ import { TableToolbar } from '../TableToolbar';
35
26
 
36
27
  import { Table as TableComponent } from './Table';
37
28
 
38
- const TestSchema = Schema.Struct({
29
+ const Example = Schema.Struct({
39
30
  // TODO(wittjosiah): Should be title. Currently name to work with default label.
40
31
  name: Schema.optional(Schema.String).annotations({ title: 'Title' }),
41
32
  urgent: Schema.optional(Schema.Boolean).annotations({ title: 'Urgent' }),
42
33
  status: Schema.optional(
43
34
  Schema.Literal('todo', 'in-progress', 'done')
44
- .pipe(FormatAnnotation.set(FormatEnum.SingleSelect))
35
+ .pipe(Format.FormatAnnotation.set(Format.TypeFormat.SingleSelect))
45
36
  .annotations({
46
37
  title: 'Status',
47
38
  [PropertyMetaAnnotationId]: {
@@ -56,14 +47,14 @@ const TestSchema = Schema.Struct({
56
47
  }),
57
48
  ),
58
49
  description: Schema.optional(Schema.String).annotations({ title: 'Description' }),
59
- 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({
60
51
  title: 'Parent',
61
52
  }),
62
53
  }).pipe(
63
54
  Type.Obj({ typename: `example.com/type/${PublicKey.random().truncate()}`, version: '0.1.0' }),
64
- LabelAnnotation.set(['name']),
55
+ Annotation.LabelAnnotation.set(['name']),
65
56
  );
66
- interface TestSchema extends Schema.Schema.Type<typeof TestSchema> {}
57
+ interface Example extends Schema.Schema.Type<typeof Example> {}
67
58
 
68
59
  const StoryViewEditor = ({
69
60
  view,
@@ -71,18 +62,18 @@ const StoryViewEditor = ({
71
62
  space,
72
63
  handleDeleteColumn,
73
64
  }: {
74
- view?: DataType.View;
65
+ view?: View.View;
75
66
  schema?: Schema.Schema.AnyNoContext;
76
67
  space?: Space;
77
68
  handleDeleteColumn: (fieldId: string) => void;
78
69
  }) => {
79
- const handleTypenameChanged = useCallback(
80
- (typename: string) => {
70
+ const handleQueryChanged = useCallback(
71
+ (newQuery: QueryAST.Query) => {
81
72
  invariant(schema);
82
73
  invariant(Type.isMutable(schema));
83
- schema.updateTypename(typename);
74
+ schema.updateTypename(getTypenameFromQuery(newQuery));
84
75
  invariant(view);
85
- view.query.typename = typename;
76
+ view.query.ast = newQuery;
86
77
  },
87
78
  [schema, view],
88
79
  );
@@ -96,7 +87,7 @@ const StoryViewEditor = ({
96
87
  registry={space?.db.schemaRegistry}
97
88
  schema={schema}
98
89
  view={view}
99
- onTypenameChanged={handleTypenameChanged}
90
+ onQueryChanged={handleQueryChanged}
100
91
  onDelete={handleDeleteColumn}
101
92
  />
102
93
  );
@@ -110,7 +101,7 @@ const DefaultStory = () => {
110
101
  const {
111
102
  space,
112
103
  schema,
113
- view,
104
+ table,
114
105
  tableRef,
115
106
  model,
116
107
  presentation,
@@ -120,7 +111,7 @@ const DefaultStory = () => {
120
111
  handleDeleteColumn,
121
112
  } = useTestTableModel();
122
113
 
123
- if (!schema || !view) {
114
+ if (!schema || !table?.view.target) {
124
115
  return <div />;
125
116
  }
126
117
 
@@ -139,10 +130,15 @@ const DefaultStory = () => {
139
130
  />
140
131
  </TableComponent.Root>
141
132
  </div>
142
- <div className='flex flex-col h-full border-l border-separator overflow-y-auto'>
143
- <StoryViewEditor view={view} schema={schema} space={space} handleDeleteColumn={handleDeleteColumn} />
144
- <SyntaxHighlighter language='json' className='w-full text-xs'>
145
- {JSON.stringify({ view, schema }, null, 2)}
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
+ />
140
+ <SyntaxHighlighter language='json' className='text-xs'>
141
+ {JSON.stringify({ view: table.view.target, schema }, null, 2)}
146
142
  </SyntaxHighlighter>
147
143
  </div>
148
144
  </div>
@@ -160,29 +156,22 @@ type StoryProps = { rows?: number };
160
156
  const meta = {
161
157
  title: 'ui/react-ui-table/Table',
162
158
  render: DefaultStory,
163
- parameters: {
164
- translations,
165
- layout: 'fullscreen',
166
- controls: {
167
- disable: true,
168
- },
169
- },
170
159
  decorators: [
171
160
  withTheme,
172
- withLayout({ fullscreen: true }),
173
161
  withClientProvider({
174
- types: [DataType.View, Table.Table],
162
+ types: [View.View, Table.Table],
175
163
  createIdentity: true,
176
164
  createSpace: true,
177
- onSpaceCreated: async ({ client, space }) => {
178
- const [schema] = await space.db.schemaRegistry.register([TestSchema]);
179
- const { view } = await Table.makeView({ client, space, typename: schema.typename });
165
+ onCreateSpace: async ({ client, space }) => {
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 });
180
169
  view.projection.fields = [
181
- view.projection.fields.find((field) => field.path === 'name')!,
182
- ...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'),
183
172
  ];
184
173
 
185
- space.db.add(view);
174
+ space.db.add(table);
186
175
 
187
176
  Array.from({ length: 10 }).map(() => {
188
177
  return space.db.add(
@@ -196,6 +185,13 @@ const meta = {
196
185
  },
197
186
  }),
198
187
  ],
188
+ parameters: {
189
+ layout: 'fullscreen',
190
+ controls: {
191
+ disable: true,
192
+ },
193
+ translations: [...translations, ...formTranslations],
194
+ },
199
195
  } satisfies Meta<typeof TableComponent>;
200
196
 
201
197
  export default meta;
@@ -206,30 +202,32 @@ export const Default: Story = {};
206
202
 
207
203
  export const StaticSchema: StoryObj = {
208
204
  render: DefaultStory,
209
- parameters: { translations },
210
205
  decorators: [
211
206
  withClientProvider({
212
- types: [DataType.View, Table.Table, Testing.Contact, Testing.Organization],
207
+ types: [View.View, Table.Table],
213
208
  createIdentity: true,
214
209
  createSpace: true,
215
- onSpaceCreated: async ({ client, space }) => {
216
- const { view } = await Table.makeView({ client, space, typename: Testing.Contact.typename });
217
- space.db.add(view);
210
+ onCreateSpace: async ({ client, space }) => {
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);
218
214
 
219
215
  const factory = createObjectFactory(space.db, faker as any);
220
216
  await factory([
221
- { type: Testing.Contact, count: 10 },
222
- // { type: Testing.Organization, count: 1 },
217
+ { type: TestSchema.Person, count: 10 },
218
+ // { type: TestSchema.Organization, count: 1 },
223
219
  ]);
224
220
  },
225
221
  }),
226
- withLayout({ fullscreen: true }),
227
- withTheme,
228
222
  ],
223
+ parameters: {
224
+ layout: 'fullscreen',
225
+ translations: [...translations, ...formTranslations],
226
+ },
229
227
  };
230
228
 
231
229
  const ContactWithArrayOfEmails = Schema.Struct({
232
- name: Schema.String.pipe(GeneratorAnnotation.set('person.fullName')),
230
+ name: Schema.String.pipe(Annotation.GeneratorAnnotation.set('person.fullName')),
233
231
  emails: Schema.optional(
234
232
  Schema.Array(
235
233
  Schema.Struct({
@@ -239,7 +237,7 @@ const ContactWithArrayOfEmails = Schema.Struct({
239
237
  ),
240
238
  ),
241
239
  }).pipe(
242
- EchoObject({
240
+ Type.Obj({
243
241
  typename: 'dxos.org/type/ContactWithArrayOfEmails',
244
242
  version: '0.1.0',
245
243
  }),
@@ -247,39 +245,44 @@ const ContactWithArrayOfEmails = Schema.Struct({
247
245
 
248
246
  export const ArrayOfObjects: StoryObj = {
249
247
  render: DefaultStory,
250
- parameters: { translations },
251
248
  decorators: [
252
249
  withClientProvider({
253
- types: [DataType.View, Table.Table, Testing.Contact, Testing.Organization, ContactWithArrayOfEmails],
250
+ types: [View.View, Table.Table, TestSchema.Person, TestSchema.Organization, ContactWithArrayOfEmails],
254
251
  createIdentity: true,
255
252
  createSpace: true,
256
- onSpaceCreated: async ({ client, space }) => {
257
- const { view } = await Table.makeView({ client, space, typename: ContactWithArrayOfEmails.typename });
258
- space.db.add(view);
253
+ onCreateSpace: async ({ client, space }) => {
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);
259
261
 
260
262
  const factory = createObjectFactory(space.db, faker as any);
261
263
  await factory([
262
- // { type: Testing.Contact, count: 10 },
263
- // { type: Testing.Organization, count: 1 },
264
+ // { type: TestSchema.Person, count: 10 },
265
+ // { type: TestSchema.Organization, count: 1 },
264
266
  { type: ContactWithArrayOfEmails, count: 10 },
265
267
  ]);
266
268
  },
267
269
  }),
268
- withLayout({ fullscreen: true }),
269
- withTheme,
270
270
  ],
271
+ parameters: {
272
+ layout: 'fullscreen',
273
+ translations,
274
+ },
271
275
  };
272
276
 
273
277
  export const Tags: Meta<StoryProps> = {
274
278
  title: 'ui/react-ui-table/Table',
275
279
  render: DefaultStory,
276
- parameters: { translations },
277
280
  decorators: [
278
281
  withClientProvider({
279
- types: [DataType.View, Table.Table],
282
+ types: [View.View, Table.Table],
280
283
  createIdentity: true,
281
284
  createSpace: true,
282
- onSpaceCreated: async ({ client, space }) => {
285
+ onCreateSpace: async ({ client, space }) => {
283
286
  // Configure schema.
284
287
  const typename = 'example.com/SingleSelect';
285
288
  const selectOptions = [
@@ -295,25 +298,26 @@ export const Tags: Meta<StoryProps> = {
295
298
  const schema = getSchemaFromPropertyDefinitions(typename, [
296
299
  {
297
300
  name: 'single',
298
- format: FormatEnum.SingleSelect,
301
+ format: Format.TypeFormat.SingleSelect,
299
302
  config: { options: selectOptions },
300
303
  },
301
304
  {
302
305
  name: 'multiple',
303
- format: FormatEnum.MultiSelect,
306
+ format: Format.TypeFormat.MultiSelect,
304
307
  config: { options: selectOptions },
305
308
  },
306
309
  ]);
307
310
  const [storedSchema] = await space.db.schemaRegistry.register([schema]);
308
311
 
309
312
  // Initialize table.
310
- const { view } = await Table.makeView({ client, space, typename });
311
- 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);
312
316
 
313
317
  // Populate.
314
318
  Array.from({ length: 10 }).map(() => {
315
319
  return space.db.add(
316
- live(storedSchema, {
320
+ Obj.make(storedSchema, {
317
321
  single: faker.helpers.arrayElement([...selectOptionIds, undefined]),
318
322
  multiple: faker.helpers.randomSubset(selectOptionIds),
319
323
  }),
@@ -321,7 +325,9 @@ export const Tags: Meta<StoryProps> = {
321
325
  });
322
326
  },
323
327
  }),
324
- withLayout({ fullscreen: true }),
325
- withTheme,
326
328
  ],
329
+ parameters: {
330
+ layout: 'fullscreen',
331
+ translations,
332
+ },
327
333
  };
@@ -2,7 +2,8 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type Schema } from 'effect/Schema';
5
+ import type * as Schema from 'effect/Schema';
6
+ import * as String from 'effect/String';
6
7
  import React, {
7
8
  type MouseEvent,
8
9
  type PropsWithChildren,
@@ -15,8 +16,8 @@ import React, {
15
16
  useState,
16
17
  } from 'react';
17
18
 
18
- import { type Client } from '@dxos/react-client';
19
19
  // TODO(wittjosiah): Remove dependency on react-client.
20
+ import { type Client } from '@dxos/react-client';
20
21
  import { useAttention } from '@dxos/react-ui-attention';
21
22
  import {
22
23
  type DxGridElement,
@@ -34,11 +35,10 @@ import { mx } from '@dxos/react-ui-theme';
34
35
 
35
36
  import { type InsertRowResult, ModalController, type TableModel, type TablePresentation } from '../../model';
36
37
  import { tableButtons, tableControls } from '../../util';
37
- import { type TableCellEditorProps, TableValueEditor } from '../TableCellEditor';
38
+ import { type OnCreateHandler, type TableCellEditorProps, TableValueEditor } from '../TableCellEditor';
38
39
 
39
40
  import { ColumnActionsMenu } from './ColumnActionsMenu';
40
41
  import { ColumnSettings } from './ColumnSettings';
41
- import { CreateRefPanel } from './CreateRefPanel';
42
42
  import { RowActionsMenu } from './RowActionsMenu';
43
43
 
44
44
  const columnDefault = { grid: { minSize: 80, maxSize: 640 } };
@@ -80,15 +80,17 @@ export type TableController = {
80
80
  export type TableMainProps = {
81
81
  model?: TableModel;
82
82
  presentation?: TablePresentation;
83
- schema?: Schema.AnyNoContext;
83
+ schema?: Schema.Schema.AnyNoContext;
84
84
  client?: Client;
85
85
  // TODO(burdon): Rename since attention isn't a useful concept here? Standardize across other components. Pass property into useAttention.
86
86
  ignoreAttention?: boolean;
87
+ onCreate?: OnCreateHandler;
87
88
  onRowClick?: (row: any) => void;
89
+ testId?: string;
88
90
  };
89
91
 
90
92
  const TableMain = forwardRef<TableController, TableMainProps>(
91
- ({ model, presentation, ignoreAttention, schema, client, onRowClick }, forwardedRef) => {
93
+ ({ model, presentation, ignoreAttention, schema, client, onCreate, onRowClick, testId }, forwardedRef) => {
92
94
  const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
93
95
  const { hasAttention } = useAttention(model?.id ?? 'table');
94
96
  const modals = useMemo(() => new ModalController(), []);
@@ -275,7 +277,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
275
277
  const handleFocus = useCallback<NonNullable<TableCellEditorProps['onFocus']>>(
276
278
  (increment, delta, cell) => {
277
279
  if (dxGrid && model) {
278
- if (cell?.plane === 'grid' && cell?.row >= model.getRowCount() - 1) {
280
+ if (cell?.plane === 'grid' && cell?.row >= model.getRowCount() - 1 && increment !== 'col') {
279
281
  handleInsertRowResult(draftRowCount < 1 ? model.insertRow() : 'final');
280
282
  } else if (cell?.plane === 'frozenRowsEnd' && increment === 'row') {
281
283
  handleSaveDraftRow(cell.row);
@@ -320,7 +322,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
320
322
  void navigator.clipboard.readText().then((clipboardText) => {
321
323
  try {
322
324
  // Attempt to set the cell's content to clipboard content
323
- model.setCellData(cell, clipboardText);
325
+ model.setCellData(cell, String.trim(clipboardText).replace(/[\n\r]+/, ' '));
324
326
  handleSave();
325
327
  } catch {
326
328
  // If validation fails, emit a DxEditRequest event with initialContent from clipboard
@@ -356,6 +358,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
356
358
  try {
357
359
  model.setCellData(cell, undefined);
358
360
  event.preventDefault();
361
+ handleSave();
359
362
  } catch {
360
363
  // Delete results in a validation error; don’t prevent default so dx-grid can emit an edit request.
361
364
  }
@@ -404,6 +407,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
404
407
  schema={schema}
405
408
  onFocus={handleFocus}
406
409
  onSave={handleSave}
410
+ onCreate={onCreate}
407
411
  client={client}
408
412
  />
409
413
  <Grid.Content
@@ -419,12 +423,12 @@ const TableMain = forwardRef<TableController, TableMainProps>(
419
423
  onClick={handleGridClick}
420
424
  onKeyDownCapture={handleKeyDown}
421
425
  onWheelCapture={handleWheel}
426
+ {...(testId && { 'data-testid': testId })}
422
427
  ref={setDxGrid}
423
428
  />
424
429
  <RowActionsMenu model={model} modals={modals} />
425
430
  <ColumnActionsMenu model={model} modals={modals} />
426
431
  <ColumnSettings model={model} modals={modals} onNewColumn={handleNewColumn} />
427
- <CreateRefPanel model={model} modals={modals} />
428
432
  </Grid.Root>
429
433
  );
430
434
  },
@@ -2,34 +2,37 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type Schema } from 'effect';
5
+ 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 { Ref, type TypeAnnotation, getValue } from '@dxos/echo-schema';
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';
13
13
  import { getSpace } from '@dxos/react-client/echo';
14
14
  import { Popover } from '@dxos/react-ui';
15
15
  import { Form, type FormProps } from '@dxos/react-ui-form';
16
- import { cellQuery, parseCellIndex, useGridContext } from '@dxos/react-ui-grid';
16
+ import { parseCellIndex, useGridContext } from '@dxos/react-ui-grid';
17
17
  import { type FieldProjection } from '@dxos/schema';
18
- import { getDeep, isNotFalsy, setDeep } from '@dxos/util';
18
+ import { getDeep, isTruthy, setDeep } from '@dxos/util';
19
19
 
20
20
  import { type ModalController, type TableModel } from '../../model';
21
21
  import { translationKey } from '../../translations';
22
22
  import { narrowSchema } from '../../util';
23
23
 
24
- type FormCellEditorProps = {
24
+ export type OnCreateHandler = (schema: Schema.Schema.AnyNoContext, values: any) => Parameters<typeof Ref.make>[0];
25
+
26
+ export type FormCellEditorProps = {
25
27
  fieldProjection: FieldProjection;
26
28
  model?: TableModel;
27
29
  schema?: Schema.Schema.AnyNoContext;
28
30
  onSave?: () => void;
31
+ onCreate?: OnCreateHandler;
29
32
  client?: Client;
30
33
  modals?: ModalController;
31
34
  __gridScope: any;
32
- } & Omit<FormProps<any>, 'values' | 'schema'>;
35
+ } & Omit<FormProps<any>, 'values' | 'schema' | 'onCreate'>;
33
36
 
34
37
  const createOptionLabel = ['create new object label', { ns: translationKey }] as [string, { ns: string }];
35
38
 
@@ -40,6 +43,7 @@ export const FormCellEditor = ({
40
43
  onSave,
41
44
  client,
42
45
  modals,
46
+ onCreate,
43
47
  __gridScope,
44
48
  ...formProps
45
49
  }: FormCellEditorProps) => {
@@ -48,7 +52,7 @@ export const FormCellEditor = ({
48
52
  const anchorRef = useRef<HTMLButtonElement | null>(null);
49
53
 
50
54
  const getSchema = useCallback(
51
- (typeAnnotation: TypeAnnotation) => {
55
+ (typeAnnotation: Pick<TypeAnnotation, 'typename'>) => {
52
56
  const space = getSpace(model!.view);
53
57
  invariant(space);
54
58
 
@@ -76,7 +80,7 @@ export const FormCellEditor = ({
76
80
  label: getValue(obj, fieldProjection.field.referencePath!) || obj.id.toString(),
77
81
  };
78
82
  })
79
- .filter(isNotFalsy);
83
+ .filter(isTruthy);
80
84
  }
81
85
 
82
86
  return [];
@@ -116,36 +120,6 @@ export const FormCellEditor = ({
116
120
  [fieldProjection.field.path, onSave, contextEditing, originalRow],
117
121
  );
118
122
 
119
- const handleCreate = useCallback(
120
- (object: any) => {
121
- const ref = Ref.make(object);
122
- const path = fieldProjection.field.path;
123
- setDeep(originalRow, [path], ref);
124
- contextEditing?.cellElement?.focus();
125
- setEditing(null);
126
- setLocalEditing(false);
127
- onSave?.();
128
- },
129
- [fieldProjection.field.path, onSave, contextEditing, originalRow],
130
- );
131
-
132
- const handleCreateFromQuery = useCallback(
133
- async (typeAnnotation: TypeAnnotation, query: string) => {
134
- if (model && modals && contextEditing?.index) {
135
- setLocalEditing(false);
136
- modals.openCreateRef(
137
- typeAnnotation.typename,
138
- document.querySelector(cellQuery(contextEditing.index, gridId)),
139
- {
140
- [fieldProjection.field.referencePath!]: query,
141
- },
142
- handleCreate,
143
- );
144
- }
145
- },
146
- [model, modals, client, contextEditing?.index, gridId, fieldProjection, handleSave],
147
- );
148
-
149
123
  useEffect(() => {
150
124
  if (contextEditing && contextEditing.cellElement) {
151
125
  anchorRef.current = (contextEditing.cellElement as HTMLElement).querySelector(
@@ -176,6 +150,38 @@ export const FormCellEditor = ({
176
150
  setLocalEditing(nextOpen);
177
151
  }, []);
178
152
 
153
+ const refSchema = useMemo(() => {
154
+ if (fieldProjection.props.format === Format.TypeFormat.Ref && fieldProjection.props.referenceSchema) {
155
+ const { schema: refSchema } = getSchema({
156
+ typename: fieldProjection.props.referenceSchema,
157
+ });
158
+ return refSchema;
159
+ }
160
+ return null;
161
+ }, [fieldProjection.props.format, fieldProjection.props.referenceSchema, getSchema]);
162
+
163
+ const createSchema = useMemo(() => {
164
+ return refSchema ? Schema.omit<any, any, ['id']>('id')(refSchema) : null;
165
+ }, [refSchema]);
166
+
167
+ const handleCreate = useCallback(
168
+ (values: any) => {
169
+ if (refSchema && onCreate) {
170
+ const objectWithId = onCreate(refSchema, values);
171
+ if (objectWithId) {
172
+ const ref = Ref.make(objectWithId);
173
+ const path = fieldProjection.field.path;
174
+ setDeep(originalRow, [path], ref);
175
+ }
176
+ }
177
+ contextEditing?.cellElement?.focus();
178
+ setEditing(null);
179
+ setLocalEditing(false);
180
+ onSave?.();
181
+ },
182
+ [fieldProjection.field.path, onSave, contextEditing, originalRow, refSchema, onCreate],
183
+ );
184
+
179
185
  if (!editing) {
180
186
  return null;
181
187
  }
@@ -194,9 +200,13 @@ export const FormCellEditor = ({
194
200
  onSave={handleSave}
195
201
  {...formProps}
196
202
  onQueryRefOptions={handleQueryRefOptions}
197
- onCreateFromQuery={handleCreateFromQuery}
198
- createOptionIcon='ph--plus--regular'
199
- createOptionLabel={createOptionLabel}
203
+ {...(createSchema && {
204
+ onCreate: handleCreate,
205
+ createSchema,
206
+ createInitialValuePath: fieldProjection.field.referencePath,
207
+ createOptionIcon: 'ph--plus--regular',
208
+ createOptionLabel,
209
+ })}
200
210
  />
201
211
  </Popover.Viewport>
202
212
  </Popover.Content>