@dxos/react-ui-table 0.8.2-main.fbd8ed0 → 0.8.2-staging.7ac8446

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 (95) hide show
  1. package/dist/lib/browser/{chunk-PFFO5LYM.mjs → chunk-YWNRUF4J.mjs} +6 -7
  2. package/dist/lib/browser/chunk-YWNRUF4J.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +181 -330
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/types/index.mjs +1 -1
  7. package/dist/lib/node/{chunk-VREQOYRW.cjs → chunk-TGG6OZ54.cjs} +8 -9
  8. package/dist/lib/node/chunk-TGG6OZ54.cjs.map +7 -0
  9. package/dist/lib/node/index.cjs +160 -306
  10. package/dist/lib/node/index.cjs.map +4 -4
  11. package/dist/lib/node/meta.json +1 -1
  12. package/dist/lib/node/types/index.cjs +3 -3
  13. package/dist/lib/node/types/index.cjs.map +1 -1
  14. package/dist/lib/node-esm/{chunk-34JG3SDI.mjs → chunk-KKLP54L3.mjs} +6 -7
  15. package/dist/lib/node-esm/chunk-KKLP54L3.mjs.map +7 -0
  16. package/dist/lib/node-esm/index.mjs +181 -330
  17. package/dist/lib/node-esm/index.mjs.map +4 -4
  18. package/dist/lib/node-esm/meta.json +1 -1
  19. package/dist/lib/node-esm/types/index.mjs +1 -1
  20. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  21. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  22. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
  23. package/dist/types/src/components/Table/DynamicTable.d.ts +4 -3
  24. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  25. package/dist/types/src/components/Table/RefPanel.d.ts +9 -0
  26. package/dist/types/src/components/Table/RefPanel.d.ts.map +1 -0
  27. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  28. package/dist/types/src/components/Table/Table.d.ts +0 -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 +0 -1
  31. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  32. package/dist/types/src/components/Table/relations.stories.d.ts.map +1 -1
  33. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +0 -3
  34. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  35. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  36. package/dist/types/src/components/TableCellEditor/extension.d.ts.map +1 -1
  37. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +2 -2
  38. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
  39. package/dist/types/src/components/testing.d.ts +40 -40
  40. package/dist/types/src/components/testing.d.ts.map +1 -1
  41. package/dist/types/src/hooks/useTableModel.d.ts +2 -2
  42. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  43. package/dist/types/src/model/modal-controller.d.ts +3 -3
  44. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  45. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  46. package/dist/types/src/model/table-model.d.ts.map +1 -1
  47. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  48. package/dist/types/src/types/table.d.ts +43 -44
  49. package/dist/types/src/types/table.d.ts.map +1 -1
  50. package/dist/types/src/util/dynamic-table.d.ts +3 -4
  51. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  52. package/dist/types/src/util/index.d.ts +0 -1
  53. package/dist/types/src/util/index.d.ts.map +1 -1
  54. package/dist/types/src/util/signals.d.ts.map +1 -1
  55. package/dist/types/src/util/table-buttons.d.ts +14 -0
  56. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  57. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  58. package/dist/types/src/util/table.d.ts +2 -2
  59. package/dist/types/src/util/table.d.ts.map +1 -1
  60. package/dist/types/tsconfig.tsbuildinfo +1 -1
  61. package/package.json +33 -34
  62. package/src/components/Table/CreateRefPanel.tsx +5 -5
  63. package/src/components/Table/DynamicTable.stories.tsx +4 -4
  64. package/src/components/Table/DynamicTable.tsx +5 -4
  65. package/src/components/Table/RefPanel.tsx +52 -0
  66. package/src/components/Table/Table.stories.tsx +27 -73
  67. package/src/components/Table/Table.tsx +28 -25
  68. package/src/components/Table/relations.stories.tsx +31 -38
  69. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +10 -11
  70. package/src/components/TableCellEditor/TableCellEditor.tsx +4 -45
  71. package/src/components/TableToolbar/TableToolbar.tsx +13 -12
  72. package/src/components/testing.ts +9 -10
  73. package/src/hooks/useTableModel.ts +2 -2
  74. package/src/model/modal-controller.ts +3 -3
  75. package/src/model/table-model.test.ts +6 -7
  76. package/src/model/table-model.ts +2 -19
  77. package/src/model/table-presentation.test.ts +6 -7
  78. package/src/model/table-presentation.ts +10 -54
  79. package/src/types/table.ts +7 -15
  80. package/src/util/dynamic-table.ts +6 -9
  81. package/src/util/index.ts +0 -1
  82. package/src/util/table-buttons.ts +23 -0
  83. package/src/util/table.ts +16 -13
  84. package/dist/lib/browser/chunk-PFFO5LYM.mjs.map +0 -7
  85. package/dist/lib/node/chunk-VREQOYRW.cjs.map +0 -7
  86. package/dist/lib/node-esm/chunk-34JG3SDI.mjs.map +0 -7
  87. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +0 -13
  88. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +0 -1
  89. package/dist/types/src/util/schema.d.ts +0 -12
  90. package/dist/types/src/util/schema.d.ts.map +0 -1
  91. package/dist/types/src/util/tag.d.ts +0 -11
  92. package/dist/types/src/util/tag.d.ts.map +0 -1
  93. package/src/components/TableCellEditor/FormCellEditor.tsx +0 -94
  94. package/src/util/schema.ts +0 -36
  95. package/src/util/tag.ts +0 -33
@@ -5,22 +5,14 @@
5
5
  import '@dxos-theme';
6
6
 
7
7
  import { type StoryObj, type Meta } from '@storybook/react';
8
- import { Schema } from 'effect';
9
8
  import React, { useCallback, useMemo, useRef } from 'react';
10
9
 
11
- import {
12
- assertEchoSchema,
13
- FormatEnum,
14
- isMutable,
15
- toJsonSchema,
16
- EchoObject,
17
- GeneratorAnnotationId,
18
- } from '@dxos/echo-schema';
10
+ import { FormatEnum, ImmutableSchema } from '@dxos/echo-schema';
19
11
  import { invariant } from '@dxos/invariant';
20
12
  import { useGlobalFilteredObjects } from '@dxos/plugin-search';
21
13
  import { faker } from '@dxos/random';
22
14
  import { useClient } from '@dxos/react-client';
23
- import { Filter, useQuery, useSchema, live } from '@dxos/react-client/echo';
15
+ import { Filter, useQuery, useSchema, create } from '@dxos/react-client/echo';
24
16
  import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
25
17
  import { ViewEditor } from '@dxos/react-ui-form';
26
18
  import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
@@ -36,7 +28,8 @@ import { TableType } from '../../types';
36
28
  import { initializeTable } from '../../util';
37
29
  import { TableToolbar } from '../TableToolbar';
38
30
 
39
- faker.seed(0); // NOTE(ZaymonFC): Required for smoke tests.
31
+ // NOTE(ZaymonFC): We rely on this seed being 0 in the smoke tests.
32
+ faker.seed(0);
40
33
 
41
34
  /**
42
35
  * Custom hook to create and manage a test table model for storybook demonstrations.
@@ -46,14 +39,14 @@ const useTestTableModel = () => {
46
39
  const client = useClient();
47
40
  const { space } = useClientProvider();
48
41
 
49
- const filter = useMemo(() => Filter.type(TableType), []);
42
+ const filter = useMemo(() => Filter.schema(TableType), []);
50
43
  const tables = useQuery(space, filter);
51
44
  const table = useMemo(() => tables.at(0), [tables]);
52
45
  const schema = useSchema(client, space, table?.view?.target?.query.typename);
53
46
 
54
47
  const projection = useMemo(() => {
55
48
  if (schema && table?.view?.target) {
56
- return new ViewProjection(toJsonSchema(schema), table.view.target);
49
+ return new ViewProjection(schema.jsonSchema, table.view.target);
57
50
  }
58
51
  }, [schema, table?.view?.target]);
59
52
 
@@ -61,12 +54,12 @@ const useTestTableModel = () => {
61
54
  () => ({
62
55
  selection: { enabled: true, mode: 'multiple' as const },
63
56
  dataEditable: true,
64
- schemaEditable: schema && isMutable(schema),
57
+ schemaEditable: !(schema instanceof ImmutableSchema),
65
58
  }),
66
59
  [schema],
67
60
  );
68
61
 
69
- const objects = useQuery(space, schema ? Filter.type(schema) : Filter.nothing());
62
+ const objects = useQuery(space, schema ? Filter.schema(schema) : Filter.nothing());
70
63
  const filteredObjects = useGlobalFilteredObjects(objects);
71
64
 
72
65
  const tableRef = useRef<TableController>(null);
@@ -80,7 +73,7 @@ const useTestTableModel = () => {
80
73
 
81
74
  const handleInsertRow = useCallback(() => {
82
75
  if (space && schema) {
83
- space.db.add(live(schema, {}));
76
+ space.db.add(create(schema, {}));
84
77
  }
85
78
  }, [space, schema]);
86
79
 
@@ -158,10 +151,11 @@ const StoryViewEditor = () => {
158
151
 
159
152
  const handleTypenameChanged = useCallback(
160
153
  (typename: string) => {
161
- invariant(schema);
162
- invariant(table?.view?.target);
163
- assertEchoSchema(schema).updateTypename(typename);
164
- table.view.target.query.typename = typename;
154
+ if (table?.view?.target) {
155
+ invariant(schema);
156
+ schema.mutable.updateTypename(typename);
157
+ table.view.target.query.typename = typename;
158
+ }
165
159
  },
166
160
  [schema, table?.view?.target],
167
161
  );
@@ -197,7 +191,7 @@ const DefaultStory = () => {
197
191
  <div className='grid grid-rows-[min-content_1fr] min-bs-0 overflow-hidden'>
198
192
  <TableToolbar classNames='border-be border-separator' onAction={handleToolbarAction} />
199
193
  <Table.Root>
200
- <Table.Main ref={tableRef} model={model} presentation={presentation} schema={schema} ignoreAttention />
194
+ <Table.Main ref={tableRef} model={model} presentation={presentation} ignoreAttention />
201
195
  </Table.Root>
202
196
  </div>
203
197
  <div className='flex flex-col h-full border-l border-separator overflow-y-auto'>
@@ -228,19 +222,19 @@ const meta: Meta<StoryProps> = {
228
222
  createIdentity: true,
229
223
  createSpace: true,
230
224
  onSpaceCreated: async ({ client, space }) => {
231
- const table = space.db.add(live(TableType, {}));
225
+ const table = space.db.add(create(TableType, {}));
232
226
  const schema = await initializeTable({ client, space, table, initialRow: false });
233
227
  Array.from({ length: 10 }).map(() => {
234
228
  return space.db.add(
235
- live(schema, {
229
+ create(schema, {
236
230
  name: faker.person.fullName(),
237
231
  }),
238
232
  );
239
233
  });
240
234
  },
241
235
  }),
242
- withLayout({ fullscreen: true }),
243
236
  withTheme,
237
+ withLayout({ fullscreen: true, tooltips: true }),
244
238
  ],
245
239
  };
246
240
 
@@ -253,62 +247,22 @@ export const StaticSchema: StoryObj = {
253
247
  parameters: { translations },
254
248
  decorators: [
255
249
  withClientProvider({
256
- types: [TableType, ViewType, Testing.Contact, Testing.Organization],
257
- createIdentity: true,
258
- createSpace: true,
259
- onSpaceCreated: async ({ client, space }) => {
260
- const table = space.db.add(live(TableType, {}));
261
- await initializeTable({ client, space, table, typename: Testing.Organization.typename });
262
-
263
- const factory = createObjectFactory(space.db, faker as any);
264
- await factory([
265
- // { type: Testing.Contact, count: 10 },
266
- // { type: Testing.Organization, count: 1 },
267
- { type: ContactWithArrayOfEmails, count: 10 },
268
- ]);
269
- },
270
- }),
271
- withLayout({ fullscreen: true }),
272
- withTheme,
273
- ],
274
- };
275
-
276
- const ContactWithArrayOfEmails = Schema.Struct({
277
- name: Schema.String.annotations({
278
- [GeneratorAnnotationId]: 'person.fullName',
279
- }),
280
- emails: Schema.optional(
281
- Schema.Array(
282
- Schema.Struct({
283
- value: Schema.String,
284
- label: Schema.String.pipe(Schema.optional),
285
- }),
286
- ),
287
- ),
288
- }).pipe(EchoObject({ typename: 'dxos.org/type/ContactWithArrayOfEmails', version: '0.1.0' }));
289
-
290
- export const ArrayOfObjects: StoryObj = {
291
- render: DefaultStory,
292
- parameters: { translations },
293
- decorators: [
294
- withClientProvider({
295
- types: [TableType, ViewType, Testing.Contact, Testing.Organization, ContactWithArrayOfEmails],
250
+ types: [TableType, ViewType, Testing.Contact, Testing.Org],
296
251
  createIdentity: true,
297
252
  createSpace: true,
298
253
  onSpaceCreated: async ({ client, space }) => {
299
- const table = space.db.add(live(TableType, {}));
300
- await initializeTable({ client, space, table, typename: ContactWithArrayOfEmails.typename });
254
+ const table = space.db.add(create(TableType, {}));
255
+ await initializeTable({ client, space, table, typename: Testing.Contact.typename });
301
256
 
302
257
  const factory = createObjectFactory(space.db, faker as any);
303
258
  await factory([
304
- // { type: Testing.Contact, count: 10 },
305
- // { type: Testing.Organization, count: 1 },
306
- { type: ContactWithArrayOfEmails, count: 10 },
259
+ { type: Testing.Contact, count: 10 },
260
+ { type: Testing.Org, count: 1 },
307
261
  ]);
308
262
  },
309
263
  }),
310
- withLayout({ fullscreen: true }),
311
264
  withTheme,
265
+ withLayout({ fullscreen: true, tooltips: true }),
312
266
  ],
313
267
  };
314
268
 
@@ -349,13 +303,13 @@ export const Tags: Meta<StoryProps> = {
349
303
  const [storedSchema] = await space.db.schemaRegistry.register([schema]);
350
304
 
351
305
  // Initialize table.
352
- const table = space.db.add(live(TableType, {}));
306
+ const table = space.db.add(create(TableType, {}));
353
307
  await initializeTable({ client, space, table, initialRow: false, typename });
354
308
 
355
309
  // Populate.
356
310
  Array.from({ length: 10 }).map(() => {
357
311
  return space.db.add(
358
- live(storedSchema, {
312
+ create(storedSchema, {
359
313
  single: faker.helpers.arrayElement([...selectOptionIds, undefined]),
360
314
  multiple: faker.helpers.randomSubset(selectOptionIds),
361
315
  }),
@@ -363,7 +317,7 @@ export const Tags: Meta<StoryProps> = {
363
317
  });
364
318
  },
365
319
  }),
366
- withLayout({ fullscreen: true }),
367
320
  withTheme,
321
+ withLayout({ fullscreen: true, tooltips: true }),
368
322
  ],
369
323
  };
@@ -2,7 +2,6 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type Schema } from 'effect/Schema';
6
5
  import React, {
7
6
  type MouseEvent,
8
7
  type PropsWithChildren,
@@ -20,28 +19,26 @@ import { invariant } from '@dxos/invariant';
20
19
  import { Filter } from '@dxos/react-client/echo';
21
20
  import { useTranslation } from '@dxos/react-ui';
22
21
  import { useAttention } from '@dxos/react-ui-attention';
23
- import {
24
- closestCell,
25
- type DxGridElement,
26
- type DxGridPosition,
27
- type GridContentProps,
28
- Grid,
29
- type DxGridPlane,
30
- type DxGridPlaneRange,
31
- gridSeparatorInlineEnd,
32
- gridSeparatorBlockEnd,
33
- } from '@dxos/react-ui-grid';
22
+ import { closestCell, type DxGridElement, type DxGridPosition, type GridContentProps, Grid } from '@dxos/react-ui-grid';
34
23
  import { mx } from '@dxos/react-ui-theme';
35
24
  import { isNotFalsy, safeParseInt } from '@dxos/util';
36
25
 
37
26
  import { ColumnActionsMenu } from './ColumnActionsMenu';
38
27
  import { ColumnSettings } from './ColumnSettings';
39
28
  import { CreateRefPanel } from './CreateRefPanel';
29
+ import { RefPanel } from './RefPanel';
40
30
  import { RowActionsMenu } from './RowActionsMenu';
41
31
  import { ModalController, type TableModel, type TablePresentation } from '../../model';
42
32
  import { translationKey } from '../../translations';
43
33
  import { tableButtons, tableControls } from '../../util';
44
- import { createOption, TableValueEditor, type TableCellEditorProps } from '../TableCellEditor';
34
+ import { createOption, TableCellEditor, type TableCellEditorProps } from '../TableCellEditor';
35
+
36
+ // NOTE(Zan): These fragments add border to inline-end and block-end of the grid using pseudo-elements.
37
+ // These are offset by 1px to avoid double borders in planks.
38
+ const inlineEndLine =
39
+ '[&>.dx-grid]:relative [&>.dx-grid]:after:absolute [&>.dx-grid]:after:inset-block-0 [&>.dx-grid]:after:-inline-end-px [&>.dx-grid]:after:is-px [&>.dx-grid]:after:bg-separator';
40
+ const blockEndLine =
41
+ '[&>.dx-grid]:before:absolute [&>.dx-grid]:before:inset-inline-0 [&>.dx-grid]:before:-block-end-px [&>.dx-grid]:before:bs-px [&>.dx-grid]:before:bg-separator';
45
42
 
46
43
  //
47
44
  // Table.Root
@@ -76,14 +73,13 @@ export type TableController = {
76
73
  export type TableMainProps = {
77
74
  model?: TableModel;
78
75
  presentation?: TablePresentation;
79
- schema?: Schema.AnyNoContext;
80
76
  // TODO(burdon): Rename since attention isn't a useful concept here? Standardize across other components. Pass property into useAttention.
81
77
  ignoreAttention?: boolean;
82
78
  onRowClick?: (row: any) => void;
83
79
  };
84
80
 
85
81
  const TableMain = forwardRef<TableController, TableMainProps>(
86
- ({ model, presentation, ignoreAttention, schema, onRowClick }, forwardedRef) => {
82
+ ({ model, presentation, ignoreAttention, onRowClick }, forwardedRef) => {
87
83
  const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
88
84
  const { hasAttention } = useAttention(model?.id ?? 'table');
89
85
  const { t } = useTranslation(translationKey);
@@ -102,17 +98,20 @@ const TableMain = forwardRef<TableController, TableMainProps>(
102
98
  };
103
99
  }, [model]);
104
100
 
105
- const getCells = useCallback<NonNullable<GridContentProps['getCells']>>(
106
- (range: DxGridPlaneRange, plane: DxGridPlane) => presentation?.getCells(range, plane) ?? {},
107
- [presentation],
108
- );
101
+ // TODO(burdon): Replace useEffect below.
102
+ // const getCells = useCallback<GridContentProps['getCells']>(
103
+ // (range: DxGridRange, plane: DxGridPlane) => presentation?.getCells(range, plane) ?? {},
104
+ // [presentation],
105
+ // );
109
106
 
110
107
  useEffect(() => {
111
108
  if (!presentation || !dxGrid) {
112
109
  return;
113
110
  }
114
- dxGrid.getCells = getCells;
115
- }, [presentation, dxGrid, getCells]);
111
+
112
+ // TODO(burdon): Pass to Grid.Content?
113
+ dxGrid.getCells = (range, plane) => presentation.getCells(range, plane);
114
+ }, [presentation, dxGrid]);
116
115
 
117
116
  /**
118
117
  * Provides an external controller that can be called to repaint the table.
@@ -182,6 +181,10 @@ const TableMain = forwardRef<TableController, TableMainProps>(
182
181
  modals.showColumnCreator();
183
182
  break;
184
183
  }
184
+ case 'referencedCell': {
185
+ modals.showReferencePanel(data.targetId, data.schemaId);
186
+ break;
187
+ }
185
188
  case 'sort': {
186
189
  model?.sorting?.toggleSort(data.fieldId);
187
190
  break;
@@ -309,7 +312,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
309
312
  invariant(space);
310
313
  const schema = space.db.schemaRegistry.getSchema(props.referenceSchema);
311
314
  if (schema) {
312
- const { objects } = await space.db.query(Filter.type(schema)).run();
315
+ const { objects } = await space.db.query(Filter.schema(schema)).run();
313
316
  const options = objects
314
317
  .map((obj) => {
315
318
  const value = getValue(obj, field.referencePath!);
@@ -345,16 +348,15 @@ const TableMain = forwardRef<TableController, TableMainProps>(
345
348
 
346
349
  return (
347
350
  <Grid.Root id={model.id ?? 'table-grid'}>
348
- <TableValueEditor
351
+ <TableCellEditor
349
352
  model={model}
350
353
  modals={modals}
351
- schema={schema}
352
354
  onEnter={handleEnter}
353
355
  onFocus={handleFocus}
354
356
  onQuery={handleQuery}
355
357
  />
356
358
  <Grid.Content
357
- className={mx('[--dx-grid-base:var(--baseSurface)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
359
+ className={mx('[--dx-grid-base:var(--surface-bg)]', inlineEndLine, blockEndLine)}
358
360
  frozen={frozen}
359
361
  // getCells={getCells}
360
362
  columns={model.columnMeta.value}
@@ -371,6 +373,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
371
373
  <ColumnActionsMenu model={model} modals={modals} />
372
374
  <ColumnSettings model={model} modals={modals} onNewColumn={handleNewColumn} />
373
375
  <CreateRefPanel model={model} modals={modals} />
376
+ <RefPanel model={model} modals={modals} />
374
377
  </Grid.Root>
375
378
  );
376
379
  },
@@ -5,23 +5,20 @@
5
5
  import '@dxos-theme';
6
6
 
7
7
  import { type StoryObj, type Meta } from '@storybook/react';
8
- import { type Schema } from 'effect';
9
- import { SchemaAST } from 'effect';
10
8
  import React, { useEffect, useMemo } from 'react';
11
9
 
12
- import { type BaseObject, getSchemaTypename, type HasId, toJsonSchema } from '@dxos/echo-schema';
10
+ import { AST, type BaseObject, ImmutableSchema, type BaseSchema, type HasId } from '@dxos/echo-schema';
13
11
  import { getAnnotation } from '@dxos/effect';
14
- import { invariant } from '@dxos/invariant';
15
12
  import { faker } from '@dxos/random';
16
- import { live, makeRef } from '@dxos/react-client/echo';
17
- import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
18
- import { DataType, createView, ViewProjection, ViewType } from '@dxos/schema';
19
- import { createAsyncGenerator, type ValueGenerator } from '@dxos/schema/testing';
13
+ import { create, makeRef, type ReactiveObject } from '@dxos/react-client/echo';
14
+ import { withClientProvider } from '@dxos/react-client/testing';
15
+ import { createView, ViewProjection, ViewType } from '@dxos/schema';
16
+ import { createGenerator, Testing, type ValueGenerator } from '@dxos/schema/testing';
20
17
  import { withLayout, withTheme } from '@dxos/storybook-utils';
21
18
 
22
19
  import { Table } from './Table';
23
20
  import { useTableModel } from '../../hooks';
24
- import { type TableFeatures, TablePresentation, type TableRow } from '../../model';
21
+ import { TablePresentation } from '../../model';
25
22
  import translations from '../../translations';
26
23
  import { TableType } from '../../types';
27
24
 
@@ -32,16 +29,13 @@ const generator: ValueGenerator = faker as any;
32
29
  // TODO(burdon): Mutable and immutable views.
33
30
  // TODO(burdon): Reconcile schemas types and utils (see API PR).
34
31
  // TODO(burdon): Base type for T (with id); see ECHO API PR?
35
- const useTestModel = <T extends BaseObject & HasId>(schema: Schema.Schema<T>, count: number) => {
36
- const { space } = useClientProvider();
37
-
38
- const jsonSchema = useMemo(() => toJsonSchema(schema), [schema]);
32
+ const useTestModel = <T extends BaseObject & HasId>(schema: BaseSchema<T>, count: number) => {
39
33
  const table = useMemo(() => {
40
- const typename = getSchemaTypename(schema);
41
- invariant(typename);
42
- const name = getAnnotation<string>(SchemaAST.TitleAnnotationId)(schema.ast) ?? typename;
43
- const view = createView({ name, typename, jsonSchema });
44
- return live(TableType, { view: makeRef(view) });
34
+ // const { typename } = pipe(schema.ast, AST.getAnnotation<TypeAnnotation>(TypeAnnotationId), Option.getOrThrow);
35
+ const typename = schema.typename;
36
+ const name = getAnnotation<string>(AST.TitleAnnotationId)(schema.ast) ?? typename;
37
+ const view = createView({ name, typename, jsonSchema: schema.jsonSchema });
38
+ return create(TableType, { view: makeRef(view) });
45
39
  }, [schema]);
46
40
 
47
41
  const projection = useMemo(() => {
@@ -50,25 +44,19 @@ const useTestModel = <T extends BaseObject & HasId>(schema: Schema.Schema<T>, co
50
44
  }
51
45
 
52
46
  // TODO(burdon): Just pass in view? Reuse same jsonSchema instance? View determines if mutable, etc.
53
- return new ViewProjection(jsonSchema, table.view.target);
47
+ return new ViewProjection(schema.jsonSchema, table.view.target);
54
48
  }, [schema, table]);
55
49
 
56
- const features = useMemo<TableFeatures>(
57
- () => ({ schemaEditable: false, dataEditable: true, selection: { enabled: false } }),
58
- [],
59
- );
60
-
61
- const model = useTableModel<TableRow>({ table, projection, rows: [], features });
50
+ const model = useTableModel({ table, projection, rows: [] });
62
51
  useEffect(() => {
63
- if (!model || !space) {
52
+ if (!model) {
64
53
  return;
65
54
  }
66
55
 
67
- const objectGenerator = createAsyncGenerator(generator, schema, { optional: true, db: space?.db });
68
- void objectGenerator.createObjects(count).then((objects) => {
69
- model.setRows(objects);
70
- });
71
- }, [model, space]);
56
+ const objectGenerator = createGenerator(generator, schema, { optional: true });
57
+ const objects: ReactiveObject<T>[] = Array.from({ length: count }).map(() => objectGenerator.createObject());
58
+ model.setRows(objects);
59
+ }, [model]);
72
60
 
73
61
  const presentation = useMemo(() => {
74
62
  if (!model) {
@@ -82,17 +70,22 @@ const useTestModel = <T extends BaseObject & HasId>(schema: Schema.Schema<T>, co
82
70
  };
83
71
 
84
72
  const DefaultStory = () => {
85
- const { model: orgModel, presentation: orgPresentation } = useTestModel(DataType.Organization, 50);
73
+ // TODO(burdon): Remove need for ImmutableSchema wrapper at API-level.
74
+ const orgSchema = useMemo(() => new ImmutableSchema(Testing.Org), []);
75
+ const { model: orgModel, presentation: orgPresentation } = useTestModel<Testing.Org>(orgSchema, 50);
76
+
86
77
  // TODO(burdon): Generate links with references.
87
- const { model: contactModel, presentation: contactPresentation } = useTestModel(DataType.Person, 50);
78
+ const contactSchema = useMemo(() => new ImmutableSchema(Testing.Contact), []);
79
+ const { model: contactModel, presentation: contactPresentation } = useTestModel<Testing.Contact>(contactSchema, 50);
88
80
 
81
+ // TODO(burdon): Scrolling isn't working.
89
82
  return (
90
- <div className='is-full bs-full grid grid-cols-2 divide-x divide-separator'>
83
+ <div className='grow grid grid-cols-2 divide-x divide-separator'>
91
84
  <Table.Root>
92
- <Table.Main model={orgModel} presentation={orgPresentation} ignoreAttention />
85
+ <Table.Main model={orgModel} presentation={orgPresentation} />
93
86
  </Table.Root>
94
87
  <Table.Root>
95
- <Table.Main model={contactModel} presentation={contactPresentation} ignoreAttention />
88
+ <Table.Main model={contactModel} presentation={contactPresentation} />
96
89
  </Table.Root>
97
90
  </div>
98
91
  );
@@ -104,12 +97,12 @@ const meta: Meta<typeof DefaultStory> = {
104
97
  parameters: { translations },
105
98
  decorators: [
106
99
  withClientProvider({
107
- types: [TableType, ViewType, DataType.Organization, DataType.Person],
100
+ types: [TableType, ViewType, Testing.Org, Testing.Contact],
108
101
  createIdentity: true,
109
102
  createSpace: true,
110
103
  }),
111
104
  withTheme,
112
- withLayout({ fullscreen: true }),
105
+ withLayout({ fullscreen: true, tooltips: true }),
113
106
  ],
114
107
  };
115
108
 
@@ -7,12 +7,12 @@ import '@dxos-theme';
7
7
  import { type Meta, type StoryObj } from '@storybook/react';
8
8
  import React, { useEffect, useMemo, useState } from 'react';
9
9
 
10
- import { type EchoSchema, isMutable } from '@dxos/echo-schema';
10
+ import { ImmutableSchema, type EchoSchema } from '@dxos/echo-schema';
11
11
  import { invariant } from '@dxos/invariant';
12
12
  import { faker } from '@dxos/random';
13
- import { Filter, useQuery, live } from '@dxos/react-client/echo';
13
+ import { Filter, useQuery, create } from '@dxos/react-client/echo';
14
14
  import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
15
- import { defaultRowSize, Grid, type GridEditing } from '@dxos/react-ui-grid';
15
+ import { defaultSizeRow, Grid, type GridEditing } from '@dxos/react-ui-grid';
16
16
  import { ViewProjection, ViewType } from '@dxos/schema';
17
17
  import { withLayout, withTheme } from '@dxos/storybook-utils';
18
18
 
@@ -31,7 +31,7 @@ const DefaultStory = ({ editing }: StoryProps) => {
31
31
  const { space } = useClientProvider();
32
32
  invariant(space);
33
33
 
34
- const tables = useQuery(space, Filter.type(TableType));
34
+ const tables = useQuery(space, Filter.schema(TableType));
35
35
  const [table, setTable] = useState<TableType>();
36
36
  const [schema, setSchema] = useState<EchoSchema>();
37
37
  useEffect(() => {
@@ -53,9 +53,9 @@ const DefaultStory = ({ editing }: StoryProps) => {
53
53
  () => ({
54
54
  selection: { enabled: true, mode: 'multiple' },
55
55
  dataEditable: true,
56
- schemaEditable: schema && isMutable(schema),
56
+ schemaEditable: !(schema instanceof ImmutableSchema),
57
57
  }),
58
- [schema],
58
+ [],
59
59
  );
60
60
 
61
61
  const model = useTableModel({ table, projection, features });
@@ -76,7 +76,7 @@ const DefaultStory = ({ editing }: StoryProps) => {
76
76
  }
77
77
 
78
78
  return (
79
- <div className='flex w-[300px] border border-separator' style={{ height: defaultRowSize }}>
79
+ <div className='flex w-[300px] border border-separator' style={{ height: defaultSizeRow }}>
80
80
  <Grid.Root id='test' editing={editing}>
81
81
  <TableCellEditor model={model} onQuery={handleQuery} />
82
82
  </Grid.Root>
@@ -95,11 +95,11 @@ const meta: Meta<StoryProps> = {
95
95
  createIdentity: true,
96
96
  createSpace: true,
97
97
  onSpaceCreated: async ({ client, space }) => {
98
- const table = space.db.add(live(TableType, {}));
98
+ const table = space.db.add(create(TableType, {}));
99
99
  const schema = await initializeTable({ client, space, table });
100
100
  Array.from({ length: 10 }).forEach(() => {
101
101
  space.db.add(
102
- live(schema, {
102
+ create(schema, {
103
103
  name: faker.person.fullName(),
104
104
  }),
105
105
  );
@@ -107,7 +107,7 @@ const meta: Meta<StoryProps> = {
107
107
  },
108
108
  }),
109
109
  withTheme,
110
- withLayout(),
110
+ withLayout({ tooltips: true }),
111
111
  ],
112
112
  };
113
113
 
@@ -120,7 +120,6 @@ export const Default: Story = {
120
120
  editing: {
121
121
  index: 'grid,0,3',
122
122
  initialContent: 'Test',
123
- cellElement: null,
124
123
  },
125
124
  },
126
125
  };
@@ -1,12 +1,11 @@
1
1
  //
2
- // Copyright 2025 DXOS.org
2
+ // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
5
  import { type Completion } from '@codemirror/autocomplete';
6
- import { type Schema } from 'effect/Schema';
7
6
  import React, { useCallback, useMemo, useRef } from 'react';
8
7
 
9
- import { FormatEnum, TypeEnum } from '@dxos/echo-schema';
8
+ import { FormatEnum } from '@dxos/echo-schema';
10
9
  import { invariant } from '@dxos/invariant';
11
10
  import { type DxGrid } from '@dxos/lit-grid';
12
11
  import { useThemeContext } from '@dxos/react-ui';
@@ -27,7 +26,6 @@ import {
27
26
  import { tagPickerExtension, createLinks } from '@dxos/react-ui-tag-picker';
28
27
  import { type FieldProjection } from '@dxos/schema';
29
28
 
30
- import { FormCellEditor } from './FormCellEditor';
31
29
  import { completion } from './extension';
32
30
  import { type TableModel, type ModalController } from '../../model';
33
31
 
@@ -45,52 +43,11 @@ export type QueryResult = Pick<Completion, 'label'> & { data: any };
45
43
  export type TableCellEditorProps = {
46
44
  model?: TableModel;
47
45
  modals?: ModalController;
48
- schema?: Schema.AnyNoContext;
49
46
  onEnter?: (cell: DxGridPlanePosition) => void;
50
47
  onFocus?: DxGrid['refocus'];
51
48
  onQuery?: (field: FieldProjection, text: string) => Promise<QueryResult[]>;
52
49
  };
53
50
 
54
- export const TableValueEditor = ({
55
- model,
56
- modals,
57
- schema,
58
- onEnter,
59
- onFocus,
60
- onQuery,
61
- __gridScope,
62
- }: GridScopedProps<TableCellEditorProps>) => {
63
- const { editing } = useGridContext('TableValueEditor', __gridScope);
64
-
65
- const fieldProjection = useMemo<FieldProjection | undefined>(() => {
66
- if (!model || !editing) {
67
- return;
68
- }
69
-
70
- const { col } = parseCellIndex(editing.index);
71
- const field = model.projection.view.fields[col];
72
- const fieldProjection = model.projection.getFieldProjection(field.id);
73
- invariant(fieldProjection);
74
- return fieldProjection;
75
- }, [model, editing]);
76
-
77
- if (fieldProjection?.props.type === TypeEnum.Array) {
78
- return <FormCellEditor fieldProjection={fieldProjection} model={model} schema={schema} __gridScope={__gridScope} />;
79
- }
80
-
81
- // For all other types, use the existing cell editor
82
- return (
83
- <TableCellEditor
84
- model={model}
85
- modals={modals}
86
- onEnter={onEnter}
87
- onFocus={onFocus}
88
- onQuery={onQuery}
89
- __gridScope={__gridScope}
90
- />
91
- );
92
- };
93
-
94
51
  export const TableCellEditor = ({
95
52
  model,
96
53
  modals,
@@ -304,9 +261,11 @@ export const TableCellEditor = ({
304
261
  }
305
262
  }
306
263
 
264
+ // Return empty string if no value
307
265
  return '';
308
266
  }
309
267
 
268
+ // Default behavior for other formats
310
269
  const value = model.getCellData(cell);
311
270
  return value !== undefined ? String(value) : '';
312
271
  }