@dxos/react-ui-table 0.8.2-main.f11618f → 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 (47) hide show
  1. package/dist/lib/browser/index.mjs +116 -80
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +127 -91
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/lib/node-esm/index.mjs +116 -80
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/types/src/components/Table/RefPanel.d.ts +9 -0
  11. package/dist/types/src/components/Table/RefPanel.d.ts.map +1 -0
  12. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  13. package/dist/types/src/components/Table/relations.stories.d.ts.map +1 -1
  14. package/dist/types/src/components/testing.d.ts +1 -1
  15. package/dist/types/src/components/testing.d.ts.map +1 -1
  16. package/dist/types/src/hooks/useTableModel.d.ts +2 -2
  17. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  18. package/dist/types/src/model/modal-controller.d.ts +3 -3
  19. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  20. package/dist/types/src/model/table-model.d.ts.map +1 -1
  21. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  22. package/dist/types/src/types/table.d.ts.map +1 -1
  23. package/dist/types/src/util/table-buttons.d.ts +14 -0
  24. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  25. package/dist/types/src/util/table.d.ts.map +1 -1
  26. package/package.json +33 -33
  27. package/src/components/Table/CreateRefPanel.tsx +2 -2
  28. package/src/components/Table/DynamicTable.stories.tsx +2 -2
  29. package/src/components/Table/RefPanel.tsx +52 -0
  30. package/src/components/Table/Table.stories.tsx +9 -9
  31. package/src/components/Table/Table.tsx +24 -18
  32. package/src/components/Table/relations.stories.tsx +21 -30
  33. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +3 -3
  34. package/src/components/TableToolbar/TableToolbar.tsx +2 -2
  35. package/src/components/testing.ts +3 -3
  36. package/src/hooks/useTableModel.ts +2 -2
  37. package/src/model/modal-controller.ts +3 -3
  38. package/src/model/table-model.test.ts +3 -3
  39. package/src/model/table-model.ts +2 -19
  40. package/src/model/table-presentation.test.ts +3 -3
  41. package/src/model/table-presentation.ts +5 -3
  42. package/src/util/dynamic-table.ts +2 -2
  43. package/src/util/table-buttons.ts +23 -0
  44. package/src/util/table.ts +7 -3
  45. package/dist/types/src/util/tag.d.ts +0 -11
  46. package/dist/types/src/util/tag.d.ts.map +0 -1
  47. package/src/util/tag.ts +0 -33
@@ -12,7 +12,7 @@ import { invariant } from '@dxos/invariant';
12
12
  import { useGlobalFilteredObjects } from '@dxos/plugin-search';
13
13
  import { faker } from '@dxos/random';
14
14
  import { useClient } from '@dxos/react-client';
15
- import { Filter, useQuery, useSchema, live } from '@dxos/react-client/echo';
15
+ import { Filter, useQuery, useSchema, create } from '@dxos/react-client/echo';
16
16
  import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
17
17
  import { ViewEditor } from '@dxos/react-ui-form';
18
18
  import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
@@ -73,7 +73,7 @@ const useTestTableModel = () => {
73
73
 
74
74
  const handleInsertRow = useCallback(() => {
75
75
  if (space && schema) {
76
- space.db.add(live(schema, {}));
76
+ space.db.add(create(schema, {}));
77
77
  }
78
78
  }, [space, schema]);
79
79
 
@@ -222,11 +222,11 @@ const meta: Meta<StoryProps> = {
222
222
  createIdentity: true,
223
223
  createSpace: true,
224
224
  onSpaceCreated: async ({ client, space }) => {
225
- const table = space.db.add(live(TableType, {}));
225
+ const table = space.db.add(create(TableType, {}));
226
226
  const schema = await initializeTable({ client, space, table, initialRow: false });
227
227
  Array.from({ length: 10 }).map(() => {
228
228
  return space.db.add(
229
- live(schema, {
229
+ create(schema, {
230
230
  name: faker.person.fullName(),
231
231
  }),
232
232
  );
@@ -247,17 +247,17 @@ export const StaticSchema: StoryObj = {
247
247
  parameters: { translations },
248
248
  decorators: [
249
249
  withClientProvider({
250
- types: [TableType, ViewType, Testing.Contact, Testing.Organization],
250
+ types: [TableType, ViewType, Testing.Contact, Testing.Org],
251
251
  createIdentity: true,
252
252
  createSpace: true,
253
253
  onSpaceCreated: async ({ client, space }) => {
254
- const table = space.db.add(live(TableType, {}));
254
+ const table = space.db.add(create(TableType, {}));
255
255
  await initializeTable({ client, space, table, typename: Testing.Contact.typename });
256
256
 
257
257
  const factory = createObjectFactory(space.db, faker as any);
258
258
  await factory([
259
259
  { type: Testing.Contact, count: 10 },
260
- { type: Testing.Organization, count: 1 },
260
+ { type: Testing.Org, count: 1 },
261
261
  ]);
262
262
  },
263
263
  }),
@@ -303,13 +303,13 @@ export const Tags: Meta<StoryProps> = {
303
303
  const [storedSchema] = await space.db.schemaRegistry.register([schema]);
304
304
 
305
305
  // Initialize table.
306
- const table = space.db.add(live(TableType, {}));
306
+ const table = space.db.add(create(TableType, {}));
307
307
  await initializeTable({ client, space, table, initialRow: false, typename });
308
308
 
309
309
  // Populate.
310
310
  Array.from({ length: 10 }).map(() => {
311
311
  return space.db.add(
312
- live(storedSchema, {
312
+ create(storedSchema, {
313
313
  single: faker.helpers.arrayElement([...selectOptionIds, undefined]),
314
314
  multiple: faker.helpers.randomSubset(selectOptionIds),
315
315
  }),
@@ -19,29 +19,27 @@ import { invariant } from '@dxos/invariant';
19
19
  import { Filter } from '@dxos/react-client/echo';
20
20
  import { useTranslation } from '@dxos/react-ui';
21
21
  import { useAttention } from '@dxos/react-ui-attention';
22
- import {
23
- closestCell,
24
- type DxGridElement,
25
- type DxGridPosition,
26
- type GridContentProps,
27
- Grid,
28
- type DxGridPlane,
29
- type DxGridPlaneRange,
30
- gridSeparatorInlineEnd,
31
- gridSeparatorBlockEnd,
32
- } from '@dxos/react-ui-grid';
22
+ import { closestCell, type DxGridElement, type DxGridPosition, type GridContentProps, Grid } from '@dxos/react-ui-grid';
33
23
  import { mx } from '@dxos/react-ui-theme';
34
24
  import { isNotFalsy, safeParseInt } from '@dxos/util';
35
25
 
36
26
  import { ColumnActionsMenu } from './ColumnActionsMenu';
37
27
  import { ColumnSettings } from './ColumnSettings';
38
28
  import { CreateRefPanel } from './CreateRefPanel';
29
+ import { RefPanel } from './RefPanel';
39
30
  import { RowActionsMenu } from './RowActionsMenu';
40
31
  import { ModalController, type TableModel, type TablePresentation } from '../../model';
41
32
  import { translationKey } from '../../translations';
42
33
  import { tableButtons, tableControls } from '../../util';
43
34
  import { createOption, TableCellEditor, type TableCellEditorProps } from '../TableCellEditor';
44
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';
42
+
45
43
  //
46
44
  // Table.Root
47
45
  //
@@ -100,17 +98,20 @@ const TableMain = forwardRef<TableController, TableMainProps>(
100
98
  };
101
99
  }, [model]);
102
100
 
103
- const getCells = useCallback<NonNullable<GridContentProps['getCells']>>(
104
- (range: DxGridPlaneRange, plane: DxGridPlane) => presentation?.getCells(range, plane) ?? {},
105
- [presentation],
106
- );
101
+ // TODO(burdon): Replace useEffect below.
102
+ // const getCells = useCallback<GridContentProps['getCells']>(
103
+ // (range: DxGridRange, plane: DxGridPlane) => presentation?.getCells(range, plane) ?? {},
104
+ // [presentation],
105
+ // );
107
106
 
108
107
  useEffect(() => {
109
108
  if (!presentation || !dxGrid) {
110
109
  return;
111
110
  }
112
- dxGrid.getCells = getCells;
113
- }, [presentation, dxGrid, getCells]);
111
+
112
+ // TODO(burdon): Pass to Grid.Content?
113
+ dxGrid.getCells = (range, plane) => presentation.getCells(range, plane);
114
+ }, [presentation, dxGrid]);
114
115
 
115
116
  /**
116
117
  * Provides an external controller that can be called to repaint the table.
@@ -180,6 +181,10 @@ const TableMain = forwardRef<TableController, TableMainProps>(
180
181
  modals.showColumnCreator();
181
182
  break;
182
183
  }
184
+ case 'referencedCell': {
185
+ modals.showReferencePanel(data.targetId, data.schemaId);
186
+ break;
187
+ }
183
188
  case 'sort': {
184
189
  model?.sorting?.toggleSort(data.fieldId);
185
190
  break;
@@ -351,7 +356,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
351
356
  onQuery={handleQuery}
352
357
  />
353
358
  <Grid.Content
354
- className={mx('[--dx-grid-base:var(--surface-bg)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
359
+ className={mx('[--dx-grid-base:var(--surface-bg)]', inlineEndLine, blockEndLine)}
355
360
  frozen={frozen}
356
361
  // getCells={getCells}
357
362
  columns={model.columnMeta.value}
@@ -368,6 +373,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
368
373
  <ColumnActionsMenu model={model} modals={modals} />
369
374
  <ColumnSettings model={model} modals={modals} onNewColumn={handleNewColumn} />
370
375
  <CreateRefPanel model={model} modals={modals} />
376
+ <RefPanel model={model} modals={modals} />
371
377
  </Grid.Root>
372
378
  );
373
379
  },
@@ -10,15 +10,15 @@ import React, { useEffect, useMemo } from 'react';
10
10
  import { AST, type BaseObject, ImmutableSchema, type BaseSchema, type HasId } from '@dxos/echo-schema';
11
11
  import { getAnnotation } from '@dxos/effect';
12
12
  import { faker } from '@dxos/random';
13
- import { live, makeRef } from '@dxos/react-client/echo';
14
- import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
15
- import { Contact, createView, Organization, ViewProjection, ViewType } from '@dxos/schema';
16
- 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';
17
17
  import { withLayout, withTheme } from '@dxos/storybook-utils';
18
18
 
19
19
  import { Table } from './Table';
20
20
  import { useTableModel } from '../../hooks';
21
- import { type TableFeatures, TablePresentation, type TableRow } from '../../model';
21
+ import { TablePresentation } from '../../model';
22
22
  import translations from '../../translations';
23
23
  import { TableType } from '../../types';
24
24
 
@@ -30,14 +30,12 @@ const generator: ValueGenerator = faker as any;
30
30
  // TODO(burdon): Reconcile schemas types and utils (see API PR).
31
31
  // TODO(burdon): Base type for T (with id); see ECHO API PR?
32
32
  const useTestModel = <T extends BaseObject & HasId>(schema: BaseSchema<T>, count: number) => {
33
- const { space } = useClientProvider();
34
-
35
33
  const table = useMemo(() => {
36
34
  // const { typename } = pipe(schema.ast, AST.getAnnotation<TypeAnnotation>(TypeAnnotationId), Option.getOrThrow);
37
35
  const typename = schema.typename;
38
36
  const name = getAnnotation<string>(AST.TitleAnnotationId)(schema.ast) ?? typename;
39
37
  const view = createView({ name, typename, jsonSchema: schema.jsonSchema });
40
- return live(TableType, { view: makeRef(view) });
38
+ return create(TableType, { view: makeRef(view) });
41
39
  }, [schema]);
42
40
 
43
41
  const projection = useMemo(() => {
@@ -49,24 +47,16 @@ const useTestModel = <T extends BaseObject & HasId>(schema: BaseSchema<T>, count
49
47
  return new ViewProjection(schema.jsonSchema, table.view.target);
50
48
  }, [schema, table]);
51
49
 
52
- const features = useMemo<TableFeatures>(
53
- () => ({ schemaEditable: false, dataEditable: true, selection: { enabled: false } }),
54
- [],
55
- );
56
-
57
- const model = useTableModel<TableRow>({ table, projection, rows: [], features });
50
+ const model = useTableModel({ table, projection, rows: [] });
58
51
  useEffect(() => {
59
- if (!model || !space) {
52
+ if (!model) {
60
53
  return;
61
54
  }
62
55
 
63
- const objectGenerator = createAsyncGenerator(generator, schema, { optional: true, db: space?.db });
64
- void objectGenerator.createObjects(count).then((objects) => {
65
- model.setRows(objects);
66
- });
67
- }, [model, space]);
68
-
69
- console.log(projection?.getFieldProjections());
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]);
70
60
 
71
61
  const presentation = useMemo(() => {
72
62
  if (!model) {
@@ -81,20 +71,21 @@ const useTestModel = <T extends BaseObject & HasId>(schema: BaseSchema<T>, count
81
71
 
82
72
  const DefaultStory = () => {
83
73
  // TODO(burdon): Remove need for ImmutableSchema wrapper at API-level.
84
- const orgSchema = useMemo(() => new ImmutableSchema(Organization), []);
85
- const { model: orgModel, presentation: orgPresentation } = useTestModel<Organization>(orgSchema, 50);
74
+ const orgSchema = useMemo(() => new ImmutableSchema(Testing.Org), []);
75
+ const { model: orgModel, presentation: orgPresentation } = useTestModel<Testing.Org>(orgSchema, 50);
86
76
 
87
77
  // TODO(burdon): Generate links with references.
88
- const contactSchema = useMemo(() => new ImmutableSchema(Contact), []);
89
- const { model: contactModel, presentation: contactPresentation } = useTestModel<Contact>(contactSchema, 50);
78
+ const contactSchema = useMemo(() => new ImmutableSchema(Testing.Contact), []);
79
+ const { model: contactModel, presentation: contactPresentation } = useTestModel<Testing.Contact>(contactSchema, 50);
90
80
 
81
+ // TODO(burdon): Scrolling isn't working.
91
82
  return (
92
- <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'>
93
84
  <Table.Root>
94
- <Table.Main model={orgModel} presentation={orgPresentation} ignoreAttention />
85
+ <Table.Main model={orgModel} presentation={orgPresentation} />
95
86
  </Table.Root>
96
87
  <Table.Root>
97
- <Table.Main model={contactModel} presentation={contactPresentation} ignoreAttention />
88
+ <Table.Main model={contactModel} presentation={contactPresentation} />
98
89
  </Table.Root>
99
90
  </div>
100
91
  );
@@ -106,7 +97,7 @@ const meta: Meta<typeof DefaultStory> = {
106
97
  parameters: { translations },
107
98
  decorators: [
108
99
  withClientProvider({
109
- types: [TableType, ViewType, Organization, Contact],
100
+ types: [TableType, ViewType, Testing.Org, Testing.Contact],
110
101
  createIdentity: true,
111
102
  createSpace: true,
112
103
  }),
@@ -10,7 +10,7 @@ import React, { useEffect, useMemo, useState } from 'react';
10
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
15
  import { defaultSizeRow, Grid, type GridEditing } from '@dxos/react-ui-grid';
16
16
  import { ViewProjection, ViewType } from '@dxos/schema';
@@ -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
  );
@@ -4,7 +4,7 @@
4
4
 
5
5
  import React, { useCallback, useMemo } from 'react';
6
6
 
7
- import { live } from '@dxos/live-object';
7
+ import { create } from '@dxos/live-object';
8
8
  import { type ThemedClassName } from '@dxos/react-ui';
9
9
  import {
10
10
  createGapSeparator,
@@ -60,7 +60,7 @@ const createTableToolbarActions = (state: TableToolbarState) => {
60
60
  };
61
61
 
62
62
  export const TableToolbar = ({ classNames, viewDirty, attendableId, onAction }: TableToolbarProps) => {
63
- const state = useMemo(() => live<TableToolbarState>({ viewDirty }), []);
63
+ const state = useMemo(() => create<TableToolbarState>({ viewDirty }), []);
64
64
  const actionsCreator = useCallback(() => createTableToolbarActions(state), [state]);
65
65
  const menu = useMenuActions(actionsCreator);
66
66
 
@@ -6,7 +6,7 @@ import { useEffect } from 'react';
6
6
 
7
7
  import { setValue, toJsonSchema, S, TypeEnum, TypedObject, FormatEnum } from '@dxos/echo-schema';
8
8
  import { faker } from '@dxos/random';
9
- import { live, makeRef } from '@dxos/react-client/echo';
9
+ import { create, makeRef } from '@dxos/react-client/echo';
10
10
  import { createView, type ViewProjection } from '@dxos/schema';
11
11
 
12
12
  import { TableType } from '../types';
@@ -20,7 +20,7 @@ export const TestSchema = TypedObject({ typename: 'example.com/type/Test', versi
20
20
  });
21
21
 
22
22
  export const createTable = (schema = TestSchema) => {
23
- return live(TableType, {
23
+ return create(TableType, {
24
24
  view: makeRef(
25
25
  createView({
26
26
  name: 'Test',
@@ -32,7 +32,7 @@ export const createTable = (schema = TestSchema) => {
32
32
  };
33
33
 
34
34
  export const createItems = (n: number) => {
35
- const { data } = live({
35
+ const { data } = create({
36
36
  data: Array.from({ length: n }, () => ({
37
37
  name: faker.person.fullName(),
38
38
  age: faker.number.int({ min: 20, max: 70 }),
@@ -5,7 +5,7 @@
5
5
  import { effect } from '@preact/signals-core';
6
6
  import { useEffect, useState } from 'react';
7
7
 
8
- import { fullyQualifiedId, getSpace, type Live } from '@dxos/react-client/echo';
8
+ import { fullyQualifiedId, getSpace, type ReactiveObject } from '@dxos/react-client/echo';
9
9
  import { useSelectionActions } from '@dxos/react-ui-attention';
10
10
  import { type ViewProjection } from '@dxos/schema';
11
11
  import { isNonNullable } from '@dxos/util';
@@ -16,7 +16,7 @@ import { type TableType } from '../types';
16
16
  export type UseTableModelParams<T extends TableRow = TableRow> = {
17
17
  table?: TableType;
18
18
  projection?: ViewProjection;
19
- rows?: Live<T>[];
19
+ rows?: ReactiveObject<T>[];
20
20
  rowActions?: TableRowAction[];
21
21
  onSelectionChanged?: (selection: string[]) => void;
22
22
  onRowAction?: (actionId: string, data: T) => void;
@@ -5,7 +5,7 @@
5
5
  import { signal } from '@preact/signals-core';
6
6
  import { type RefObject, type MutableRefObject } from 'react';
7
7
 
8
- import { type Live } from '@dxos/live-object';
8
+ import { type ReactiveObject } from '@dxos/live-object';
9
9
  import { log } from '@dxos/log';
10
10
 
11
11
  export type ColumnSettingsMode = { type: 'create' } | { type: 'edit'; fieldId: string };
@@ -18,7 +18,7 @@ export type ModalState =
18
18
  type: 'createRefPanel';
19
19
  typename: string;
20
20
  initialValues?: Record<string, string>;
21
- onCreate?: (obj: Live<any>) => void;
21
+ onCreate?: (obj: ReactiveObject<any>) => void;
22
22
  }
23
23
  | { type: 'columnSettings'; mode: ColumnSettingsMode }
24
24
  | { type: 'closed' };
@@ -72,7 +72,7 @@ export class ModalController {
72
72
  typename: string,
73
73
  anchorCell: Element | null,
74
74
  initialValues?: Record<string, string>,
75
- onCreate?: (obj: Live<any>) => void,
75
+ onCreate?: (obj: ReactiveObject<any>) => void,
76
76
  ) => {
77
77
  if (anchorCell) {
78
78
  this._triggerRef.current = anchorCell as HTMLElement;
@@ -9,7 +9,7 @@ import { S, TypedObject } from '@dxos/echo-schema';
9
9
  import { updateCounter } from '@dxos/echo-schema/testing';
10
10
  import { registerSignalsRuntime } from '@dxos/echo-signals';
11
11
  import { createEchoSchema } from '@dxos/live-object/testing';
12
- import { live, makeRef } from '@dxos/react-client/echo';
12
+ import { create, makeRef } from '@dxos/react-client/echo';
13
13
  import { createView, ViewProjection } from '@dxos/schema';
14
14
 
15
15
  import { TableModel, type TableModelProps } from './table-model';
@@ -69,7 +69,7 @@ describe('TableModel', () => {
69
69
 
70
70
  describe('reactivity', () => {
71
71
  it('pure signals should nest', () => {
72
- const signal$ = live({ arr: [{ thingInside: 1 }, { thingInside: 2 }] });
72
+ const signal$ = create({ arr: [{ thingInside: 1 }, { thingInside: 2 }] });
73
73
 
74
74
  const computed$ = computed(() => {
75
75
  return signal$.arr.map((row) =>
@@ -107,6 +107,6 @@ const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
107
107
  const schema = createEchoSchema(Test);
108
108
  const view = createView({ name: 'Test', typename: schema.typename, jsonSchema: schema.jsonSchema });
109
109
  const projection = new ViewProjection(schema.jsonSchema, view);
110
- const table = live(TableType, { view: makeRef(view) });
110
+ const table = create(TableType, { view: makeRef(view) });
111
111
  return new TableModel({ id: table.id, space: undefined, view, projection, ...props });
112
112
  };
@@ -8,7 +8,7 @@ import { type Space } from '@dxos/client/echo';
8
8
  import { Resource } from '@dxos/context';
9
9
  import { type FieldSortType, FormatEnum, getValue, setValue, type JsonProp } from '@dxos/echo-schema';
10
10
  import { invariant } from '@dxos/invariant';
11
- import { isLiveObject, makeRef } from '@dxos/live-object';
11
+ import { isReactiveObject, makeRef } from '@dxos/live-object';
12
12
  import { formatForEditing, parseValue } from '@dxos/react-ui-form';
13
13
  import {
14
14
  type DxGridAxisMeta,
@@ -21,7 +21,6 @@ import { type ViewType, type ViewProjection } from '@dxos/schema';
21
21
  import { type SelectionMode, SelectionModel } from './selection-model';
22
22
  import { TableSorting } from './table-sorting';
23
23
  import { touch } from '../util';
24
- import { extractTagIds } from '../util/tag';
25
24
 
26
25
  // TODO(burdon): Use schema types.
27
26
  export type TableRow = Record<JsonProp, any> & { id: string };
@@ -343,28 +342,12 @@ export class TableModel<T extends TableRow = TableRow> extends Resource {
343
342
  case FormatEnum.Ref: {
344
343
  // TODO(ZaymonFC): This get's called an additional time by the cell editor onBlur, but with the cell editors
345
344
  // plain string value. Maybe onBlur should be called with the actual value?
346
- if (isLiveObject(value)) {
345
+ if (isReactiveObject(value)) {
347
346
  setValue(this._rows.value[rowIdx], field.path, makeRef(value));
348
347
  }
349
348
  break;
350
349
  }
351
350
 
352
- case FormatEnum.SingleSelect: {
353
- const ids = extractTagIds(value);
354
- if (ids && ids.length > 0) {
355
- setValue(this._rows.value[rowIdx], field.path, ids[0]);
356
- }
357
- break;
358
- }
359
-
360
- case FormatEnum.MultiSelect: {
361
- const ids = extractTagIds(value);
362
- if (ids) {
363
- setValue(this._rows.value[rowIdx], field.path, ids);
364
- }
365
- break;
366
- }
367
-
368
351
  default: {
369
352
  setValue(
370
353
  this._rows.value[rowIdx],
@@ -5,7 +5,7 @@
5
5
  import { describe, expect, it, beforeEach } from 'vitest';
6
6
 
7
7
  import { S, TypedObject } from '@dxos/echo-schema';
8
- import { live, makeRef } from '@dxos/live-object';
8
+ import { create, makeRef } from '@dxos/live-object';
9
9
  import { createEchoSchema } from '@dxos/live-object/testing';
10
10
  import { createView, ViewProjection } from '@dxos/schema';
11
11
 
@@ -22,7 +22,7 @@ describe('TablePresentation', () => {
22
22
 
23
23
  beforeEach(async () => {
24
24
  updateCount = 0;
25
- ({ data } = live({
25
+ ({ data } = create({
26
26
  data: [
27
27
  { col1: 'A', col2: 1, col3: true },
28
28
  { col1: 'B', col2: 2, col3: false },
@@ -97,6 +97,6 @@ const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
97
97
  const schema = createEchoSchema(Test);
98
98
  const view = createView({ name: 'Test', typename: schema.typename, jsonSchema: schema.jsonSchema });
99
99
  const projection = new ViewProjection(schema.jsonSchema, view);
100
- const table = live(TableType, { view: makeRef(view) });
100
+ const table = create(TableType, { view: makeRef(view) });
101
101
  return new TableModel({ id: table.id, view, projection, ...props });
102
102
  };
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { signal } from '@preact/signals-core';
6
6
 
7
- import { FormatEnum, getDXN, getValue } from '@dxos/echo-schema';
7
+ import { FormatEnum, getValue } from '@dxos/echo-schema';
8
8
  import { cellClassesForFieldType, formatForDisplay } from '@dxos/react-ui-form';
9
9
  import {
10
10
  type DxGridPlane,
@@ -124,8 +124,10 @@ export class TablePresentation<T extends TableRow = TableRow> {
124
124
  if (props.format === FormatEnum.Ref && props.referenceSchema) {
125
125
  const targetObj = getValue(obj, field.path)?.target;
126
126
  if (targetObj) {
127
- const dxn = getDXN(targetObj)?.toString();
128
- cell.accessoryHtml = `<dx-ref-tag ref=${dxn} class="dx-button block is-6 pli-[3px] plb-[3px] pbe-[2px] min-bs-0 absolute inline-end-1"><dx-icon icon="ph--link-simple--regular"/></dx-ref-tag>`;
127
+ cell.accessoryHtml = tableButtons.referencedCell.render({
128
+ targetId: targetObj.id as any as string,
129
+ schemaId: props.referenceSchema,
130
+ });
129
131
  }
130
132
  }
131
133
 
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import type { BaseSchema, JsonSchemaType, SortDirectionType } from '@dxos/echo-schema';
6
- import { live, makeRef } from '@dxos/live-object';
6
+ import { create, makeRef } from '@dxos/live-object';
7
7
  import {
8
8
  createView,
9
9
  getSchemaFromPropertyDefinitions,
@@ -66,7 +66,7 @@ export const makeDynamicTable = ({
66
66
  ...(properties && { fields: properties.map((property) => property.name) }),
67
67
  });
68
68
 
69
- const table = live(TableType, { name: 'dynamic-table', view: makeRef(view) });
69
+ const table = create(TableType, { name: 'dynamic-table', view: makeRef(view) });
70
70
  const projection = new ViewProjection(jsonSchema, view);
71
71
  if (properties && view.fields) {
72
72
  setProperties(view, projection, properties);
@@ -15,6 +15,7 @@ export const BUTTON_IDENTIFIERS: { [K in TableButton]: string } = {
15
15
  type ButtonData =
16
16
  | { type: 'columnSettings'; fieldId: string }
17
17
  | { type: 'newColumn'; disabled?: boolean }
18
+ | { type: 'referencedCell'; schemaId: string; targetId: string }
18
19
  | { type: 'rowMenu'; rowIndex: number }
19
20
  | { type: 'sort'; fieldId: string; direction?: 'asc' | 'desc' };
20
21
 
@@ -75,6 +76,27 @@ const columnSettingsButton = {
75
76
  }),
76
77
  } as const;
77
78
 
79
+ const referencedCellButton = {
80
+ attr: BUTTON_IDENTIFIERS.referencedCell,
81
+ icon: 'ph--link-simple-horizontal--regular',
82
+ render: ({ targetId, schemaId }: Omit<Extract<ButtonData, { type: 'referencedCell' }>, 'type'>) => {
83
+ return createButton({
84
+ attr: BUTTON_IDENTIFIERS.referencedCell,
85
+ icon: referencedCellButton.icon,
86
+ data: {
87
+ 'data-target-id': targetId,
88
+ 'data-schema-id': schemaId,
89
+ },
90
+ testId: 'table-ref-cell-button',
91
+ });
92
+ },
93
+ getData: (el: HTMLElement): Extract<ButtonData, { type: 'referencedCell' }> => ({
94
+ type: 'referencedCell',
95
+ targetId: el.getAttribute('data-target-id')!,
96
+ schemaId: el.getAttribute('data-schema-id')!,
97
+ }),
98
+ } as const;
99
+
78
100
  const rowMenuButton = {
79
101
  attr: BUTTON_IDENTIFIERS.rowMenu,
80
102
  icon: 'ph--dots-three--regular',
@@ -121,6 +143,7 @@ const sortButton = {
121
143
  export const tableButtons = {
122
144
  addColumn: addColumnButton,
123
145
  columnSettings: columnSettingsButton,
146
+ referencedCell: referencedCellButton,
124
147
  rowMenu: rowMenuButton,
125
148
  sort: sortButton,
126
149
  } as const;
package/src/util/table.ts CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  TypeEnum,
15
15
  } from '@dxos/echo-schema';
16
16
  import { type Client, PublicKey } from '@dxos/react-client';
17
- import { live, makeRef, type Space } from '@dxos/react-client/echo';
17
+ import { create, makeRef, type Space } from '@dxos/react-client/echo';
18
18
  import { createFieldId, createView, getSchemaProperties, ViewProjection, type ViewType } from '@dxos/schema';
19
19
 
20
20
  import { type TableType } from '../types';
@@ -41,7 +41,11 @@ export const initializeTable = async ({
41
41
  throw new Error(`Schema not found: ${typename}`);
42
42
  }
43
43
 
44
- const fields = getSchemaProperties(schema.ast).map((prop) => prop.name);
44
+ // We need to get the schema properties here.
45
+ // For now, only simple types and refs, not compound types are going to be supported.
46
+ const fields = getSchemaProperties(schema.ast)
47
+ .filter((prop) => prop.format !== FormatEnum.Ref)
48
+ .map((prop) => prop.name);
45
49
 
46
50
  table.view = makeRef(
47
51
  createView({
@@ -71,7 +75,7 @@ export const initializeTable = async ({
71
75
 
72
76
  if (initialRow) {
73
77
  // TODO(burdon): Last (first) row should not be in db and should be managed by the model.
74
- space.db.add(live(schema, {}));
78
+ space.db.add(create(schema, {}));
75
79
  }
76
80
 
77
81
  return schema;
@@ -1,11 +0,0 @@
1
- /**
2
- * Regex pattern for matching tags in the format [Label](id)
3
- */
4
- export declare const TAG_PATTERN_REGEX: RegExp;
5
- /**
6
- * Extracts tag IDs from a string in the format [Label](id)
7
- * @param value The string to extract IDs from
8
- * @returns Array of extracted IDs or undefined if no matches or value is not a string
9
- */
10
- export declare const extractTagIds: (value: unknown) => string[] | undefined;
11
- //# sourceMappingURL=tag.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tag.d.ts","sourceRoot":"","sources":["../../../../src/util/tag.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,eAAO,MAAM,iBAAiB,QAA6B,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,aAAa,UAAW,OAAO,KAAG,MAAM,EAAE,GAAG,SAgBzD,CAAC"}
package/src/util/tag.ts DELETED
@@ -1,33 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- // TODO(ZaymonFC): Push this into the tag picker module.
6
-
7
- /**
8
- * Regex pattern for matching tags in the format [Label](id)
9
- */
10
- export const TAG_PATTERN_REGEX = /\[([^\]]+)\]\(([^)]+)\)/g;
11
-
12
- /**
13
- * Extracts tag IDs from a string in the format [Label](id)
14
- * @param value The string to extract IDs from
15
- * @returns Array of extracted IDs or undefined if no matches or value is not a string
16
- */
17
- export const extractTagIds = (value: unknown): string[] | undefined => {
18
- if (typeof value !== 'string') {
19
- return undefined;
20
- }
21
-
22
- const ids: string[] = [];
23
- let match;
24
-
25
- // Use the global regex to find all matches
26
- while ((match = TAG_PATTERN_REGEX.exec(value)) !== null) {
27
- if (match[2]) {
28
- ids.push(match[2]);
29
- }
30
- }
31
-
32
- return ids.length > 0 ? ids : undefined;
33
- };