@dxos/react-ui-table 0.8.2-main.f11618f → 0.8.2-main.fbd8ed0

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 (82) hide show
  1. package/dist/lib/browser/{chunk-YWNRUF4J.mjs → chunk-PFFO5LYM.mjs} +7 -6
  2. package/dist/lib/browser/chunk-PFFO5LYM.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +290 -105
  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-TGG6OZ54.cjs → chunk-VREQOYRW.cjs} +9 -8
  8. package/dist/lib/node/chunk-VREQOYRW.cjs.map +7 -0
  9. package/dist/lib/node/index.cjs +263 -81
  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-KKLP54L3.mjs → chunk-34JG3SDI.mjs} +7 -6
  15. package/dist/lib/node-esm/chunk-34JG3SDI.mjs.map +7 -0
  16. package/dist/lib/node-esm/index.mjs +290 -105
  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 +3 -4
  24. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  25. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  26. package/dist/types/src/components/Table/Table.d.ts +2 -0
  27. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  28. package/dist/types/src/components/Table/Table.stories.d.ts +1 -0
  29. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  30. package/dist/types/src/components/Table/relations.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +13 -0
  32. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -0
  33. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +3 -0
  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 +39 -39
  40. package/dist/types/src/components/testing.d.ts.map +1 -1
  41. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  42. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  43. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  44. package/dist/types/src/model/table-model.d.ts.map +1 -1
  45. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  46. package/dist/types/src/types/table.d.ts +44 -43
  47. package/dist/types/src/types/table.d.ts.map +1 -1
  48. package/dist/types/src/util/dynamic-table.d.ts +4 -3
  49. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  50. package/dist/types/src/util/index.d.ts +1 -0
  51. package/dist/types/src/util/index.d.ts.map +1 -1
  52. package/dist/types/src/util/schema.d.ts +12 -0
  53. package/dist/types/src/util/schema.d.ts.map +1 -0
  54. package/dist/types/src/util/signals.d.ts.map +1 -1
  55. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  56. package/dist/types/src/util/table.d.ts +2 -2
  57. package/dist/types/src/util/table.d.ts.map +1 -1
  58. package/dist/types/src/util/tag.d.ts.map +1 -1
  59. package/dist/types/tsconfig.tsbuildinfo +1 -1
  60. package/package.json +34 -33
  61. package/src/components/Table/CreateRefPanel.tsx +3 -3
  62. package/src/components/Table/DynamicTable.stories.tsx +2 -2
  63. package/src/components/Table/DynamicTable.tsx +4 -5
  64. package/src/components/Table/Table.stories.tsx +65 -19
  65. package/src/components/Table/Table.tsx +8 -5
  66. package/src/components/Table/relations.stories.tsx +16 -18
  67. package/src/components/TableCellEditor/FormCellEditor.tsx +94 -0
  68. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +8 -7
  69. package/src/components/TableCellEditor/TableCellEditor.tsx +45 -4
  70. package/src/components/TableToolbar/TableToolbar.tsx +10 -11
  71. package/src/components/testing.ts +7 -6
  72. package/src/model/table-model.test.ts +4 -3
  73. package/src/model/table-presentation.test.ts +4 -3
  74. package/src/model/table-presentation.ts +52 -6
  75. package/src/types/table.ts +15 -7
  76. package/src/util/dynamic-table.ts +7 -4
  77. package/src/util/index.ts +1 -0
  78. package/src/util/schema.ts +36 -0
  79. package/src/util/table.ts +11 -10
  80. package/dist/lib/browser/chunk-YWNRUF4J.mjs.map +0 -7
  81. package/dist/lib/node/chunk-TGG6OZ54.cjs.map +0 -7
  82. package/dist/lib/node-esm/chunk-KKLP54L3.mjs.map +0 -7
@@ -5,14 +5,17 @@
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';
8
10
  import React, { useEffect, useMemo } from 'react';
9
11
 
10
- import { AST, type BaseObject, ImmutableSchema, type BaseSchema, type HasId } from '@dxos/echo-schema';
12
+ import { type BaseObject, getSchemaTypename, type HasId, toJsonSchema } from '@dxos/echo-schema';
11
13
  import { getAnnotation } from '@dxos/effect';
14
+ import { invariant } from '@dxos/invariant';
12
15
  import { faker } from '@dxos/random';
13
16
  import { live, makeRef } from '@dxos/react-client/echo';
14
17
  import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
15
- import { Contact, createView, Organization, ViewProjection, ViewType } from '@dxos/schema';
18
+ import { DataType, createView, ViewProjection, ViewType } from '@dxos/schema';
16
19
  import { createAsyncGenerator, type ValueGenerator } from '@dxos/schema/testing';
17
20
  import { withLayout, withTheme } from '@dxos/storybook-utils';
18
21
 
@@ -29,14 +32,15 @@ const generator: ValueGenerator = faker as any;
29
32
  // TODO(burdon): Mutable and immutable views.
30
33
  // TODO(burdon): Reconcile schemas types and utils (see API PR).
31
34
  // TODO(burdon): Base type for T (with id); see ECHO API PR?
32
- const useTestModel = <T extends BaseObject & HasId>(schema: BaseSchema<T>, count: number) => {
35
+ const useTestModel = <T extends BaseObject & HasId>(schema: Schema.Schema<T>, count: number) => {
33
36
  const { space } = useClientProvider();
34
37
 
38
+ const jsonSchema = useMemo(() => toJsonSchema(schema), [schema]);
35
39
  const table = useMemo(() => {
36
- // const { typename } = pipe(schema.ast, AST.getAnnotation<TypeAnnotation>(TypeAnnotationId), Option.getOrThrow);
37
- const typename = schema.typename;
38
- const name = getAnnotation<string>(AST.TitleAnnotationId)(schema.ast) ?? typename;
39
- const view = createView({ name, typename, jsonSchema: schema.jsonSchema });
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 });
40
44
  return live(TableType, { view: makeRef(view) });
41
45
  }, [schema]);
42
46
 
@@ -46,7 +50,7 @@ const useTestModel = <T extends BaseObject & HasId>(schema: BaseSchema<T>, count
46
50
  }
47
51
 
48
52
  // TODO(burdon): Just pass in view? Reuse same jsonSchema instance? View determines if mutable, etc.
49
- return new ViewProjection(schema.jsonSchema, table.view.target);
53
+ return new ViewProjection(jsonSchema, table.view.target);
50
54
  }, [schema, table]);
51
55
 
52
56
  const features = useMemo<TableFeatures>(
@@ -66,8 +70,6 @@ const useTestModel = <T extends BaseObject & HasId>(schema: BaseSchema<T>, count
66
70
  });
67
71
  }, [model, space]);
68
72
 
69
- console.log(projection?.getFieldProjections());
70
-
71
73
  const presentation = useMemo(() => {
72
74
  if (!model) {
73
75
  return;
@@ -80,13 +82,9 @@ const useTestModel = <T extends BaseObject & HasId>(schema: BaseSchema<T>, count
80
82
  };
81
83
 
82
84
  const DefaultStory = () => {
83
- // 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);
86
-
85
+ const { model: orgModel, presentation: orgPresentation } = useTestModel(DataType.Organization, 50);
87
86
  // TODO(burdon): Generate links with references.
88
- const contactSchema = useMemo(() => new ImmutableSchema(Contact), []);
89
- const { model: contactModel, presentation: contactPresentation } = useTestModel<Contact>(contactSchema, 50);
87
+ const { model: contactModel, presentation: contactPresentation } = useTestModel(DataType.Person, 50);
90
88
 
91
89
  return (
92
90
  <div className='is-full bs-full grid grid-cols-2 divide-x divide-separator'>
@@ -106,12 +104,12 @@ const meta: Meta<typeof DefaultStory> = {
106
104
  parameters: { translations },
107
105
  decorators: [
108
106
  withClientProvider({
109
- types: [TableType, ViewType, Organization, Contact],
107
+ types: [TableType, ViewType, DataType.Organization, DataType.Person],
110
108
  createIdentity: true,
111
109
  createSpace: true,
112
110
  }),
113
111
  withTheme,
114
- withLayout({ fullscreen: true, tooltips: true }),
112
+ withLayout({ fullscreen: true }),
115
113
  ],
116
114
  };
117
115
 
@@ -0,0 +1,94 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Schema } from 'effect';
6
+ import React, { useCallback, useEffect, useMemo, useRef } from 'react';
7
+
8
+ import { getSnapshot } from '@dxos/echo-schema';
9
+ import { invariant } from '@dxos/invariant';
10
+ import { Popover } from '@dxos/react-ui';
11
+ import { Form } from '@dxos/react-ui-form';
12
+ import { parseCellIndex, useGridContext } from '@dxos/react-ui-grid';
13
+ import { type FieldProjection } from '@dxos/schema';
14
+ import { getDeep, setDeep } from '@dxos/util';
15
+
16
+ import { type TableModel } from '../../model';
17
+ import { narrowSchema } from '../../util';
18
+
19
+ type FormCellEditorProps = {
20
+ fieldProjection: FieldProjection;
21
+ model?: TableModel;
22
+ schema?: Schema.Schema.AnyNoContext;
23
+ __gridScope: any;
24
+ };
25
+
26
+ export const FormCellEditor = ({ fieldProjection, model, schema, __gridScope }: FormCellEditorProps) => {
27
+ const { editing, setEditing } = useGridContext('ArrayEditor', __gridScope);
28
+ const cellRef = useRef<HTMLButtonElement | null>(null);
29
+
30
+ useEffect(() => {
31
+ if (editing && editing.cellElement) {
32
+ cellRef.current = editing.cellElement as HTMLButtonElement;
33
+ }
34
+ }, [editing?.cellElement]);
35
+
36
+ const narrowedSchema = useMemo(() => {
37
+ if (!schema) {
38
+ return undefined;
39
+ }
40
+ return narrowSchema(schema, [fieldProjection.field.path]);
41
+ }, [JSON.stringify(schema), fieldProjection.field.path]);
42
+
43
+ const originalRow = useMemo(() => {
44
+ if (model && editing) {
45
+ const cell = parseCellIndex(editing.index);
46
+ const row = model.getRowAt(cell.row);
47
+ invariant(row);
48
+
49
+ return row;
50
+ }
51
+
52
+ return undefined;
53
+ }, [model, editing]);
54
+
55
+ const formValues = useMemo(() => {
56
+ if (originalRow) {
57
+ // NOTE(ZaymonFC): Important to get a snapshot to eject from the live object.
58
+ return getSnapshot(originalRow);
59
+ }
60
+ }, [originalRow]);
61
+
62
+ const handleSave = useCallback(
63
+ (values: any) => {
64
+ const path = fieldProjection.field.path;
65
+ const value = getDeep(values, [path]);
66
+ setDeep(originalRow, [path], value);
67
+ setEditing(null);
68
+ },
69
+ [fieldProjection.field.path, originalRow],
70
+ );
71
+
72
+ const handleOpenChange = useCallback(
73
+ (nextOpen: boolean) => {
74
+ if (nextOpen === false) {
75
+ setEditing(null);
76
+ }
77
+ },
78
+ [setEditing],
79
+ );
80
+
81
+ if (!editing) {
82
+ return null;
83
+ }
84
+
85
+ return (
86
+ <Popover.Root open={editing !== null} onOpenChange={handleOpenChange}>
87
+ <Popover.VirtualTrigger virtualRef={cellRef} />
88
+ <Popover.Content tabIndex={-1} classNames='popover-consistent-width'>
89
+ <Popover.Arrow />
90
+ <Form values={formValues} schema={narrowedSchema as any} onSave={handleSave} />
91
+ </Popover.Content>
92
+ </Popover.Root>
93
+ );
94
+ };
@@ -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 { ImmutableSchema, type EchoSchema } from '@dxos/echo-schema';
10
+ import { type EchoSchema, isMutable } from '@dxos/echo-schema';
11
11
  import { invariant } from '@dxos/invariant';
12
12
  import { faker } from '@dxos/random';
13
13
  import { Filter, useQuery, live } from '@dxos/react-client/echo';
14
14
  import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
15
- import { defaultSizeRow, Grid, type GridEditing } from '@dxos/react-ui-grid';
15
+ import { defaultRowSize, 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.schema(TableType));
34
+ const tables = useQuery(space, Filter.type(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 instanceof ImmutableSchema),
56
+ schemaEditable: schema && isMutable(schema),
57
57
  }),
58
- [],
58
+ [schema],
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: defaultSizeRow }}>
79
+ <div className='flex w-[300px] border border-separator' style={{ height: defaultRowSize }}>
80
80
  <Grid.Root id='test' editing={editing}>
81
81
  <TableCellEditor model={model} onQuery={handleQuery} />
82
82
  </Grid.Root>
@@ -107,7 +107,7 @@ const meta: Meta<StoryProps> = {
107
107
  },
108
108
  }),
109
109
  withTheme,
110
- withLayout({ tooltips: true }),
110
+ withLayout(),
111
111
  ],
112
112
  };
113
113
 
@@ -120,6 +120,7 @@ export const Default: Story = {
120
120
  editing: {
121
121
  index: 'grid,0,3',
122
122
  initialContent: 'Test',
123
+ cellElement: null,
123
124
  },
124
125
  },
125
126
  };
@@ -1,11 +1,12 @@
1
1
  //
2
- // Copyright 2024 DXOS.org
2
+ // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
5
  import { type Completion } from '@codemirror/autocomplete';
6
+ import { type Schema } from 'effect/Schema';
6
7
  import React, { useCallback, useMemo, useRef } from 'react';
7
8
 
8
- import { FormatEnum } from '@dxos/echo-schema';
9
+ import { FormatEnum, TypeEnum } from '@dxos/echo-schema';
9
10
  import { invariant } from '@dxos/invariant';
10
11
  import { type DxGrid } from '@dxos/lit-grid';
11
12
  import { useThemeContext } from '@dxos/react-ui';
@@ -26,6 +27,7 @@ import {
26
27
  import { tagPickerExtension, createLinks } from '@dxos/react-ui-tag-picker';
27
28
  import { type FieldProjection } from '@dxos/schema';
28
29
 
30
+ import { FormCellEditor } from './FormCellEditor';
29
31
  import { completion } from './extension';
30
32
  import { type TableModel, type ModalController } from '../../model';
31
33
 
@@ -43,11 +45,52 @@ export type QueryResult = Pick<Completion, 'label'> & { data: any };
43
45
  export type TableCellEditorProps = {
44
46
  model?: TableModel;
45
47
  modals?: ModalController;
48
+ schema?: Schema.AnyNoContext;
46
49
  onEnter?: (cell: DxGridPlanePosition) => void;
47
50
  onFocus?: DxGrid['refocus'];
48
51
  onQuery?: (field: FieldProjection, text: string) => Promise<QueryResult[]>;
49
52
  };
50
53
 
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
+
51
94
  export const TableCellEditor = ({
52
95
  model,
53
96
  modals,
@@ -261,11 +304,9 @@ export const TableCellEditor = ({
261
304
  }
262
305
  }
263
306
 
264
- // Return empty string if no value
265
307
  return '';
266
308
  }
267
309
 
268
- // Default behavior for other formats
269
310
  const value = model.getCellData(cell);
270
311
  return value !== undefined ? String(value) : '';
271
312
  }
@@ -10,7 +10,6 @@ import {
10
10
  createGapSeparator,
11
11
  createMenuAction,
12
12
  type MenuAction,
13
- type MenuActionHandler,
14
13
  MenuProvider,
15
14
  ToolbarMenu,
16
15
  useMenuActions,
@@ -27,18 +26,18 @@ export type TableToolbarActionType = 'add-row' | 'comment' | 'save-view';
27
26
  type TableToolbarState = Partial<{ viewDirty: boolean }>;
28
27
 
29
28
  export type TableToolbarProps = ThemedClassName<
30
- TableToolbarState & { onAction?: MenuActionHandler<TableToolbarAction>; attendableId?: string }
29
+ TableToolbarState & { onAction: (action: TableToolbarAction) => void; attendableId?: string }
31
30
  >;
32
31
 
33
- const createTableToolbarActions = (state: TableToolbarState) => {
34
- const add = createMenuAction<TableToolbarActionProperties>('add-row', {
35
- type: 'add-row',
32
+ const createTableToolbarActions = (state: TableToolbarState, onAction: (action: TableToolbarAction) => void) => {
33
+ const add = createMenuAction<TableToolbarActionProperties>('add-row', () => onAction(add), {
34
+ type: 'add-row' as const,
36
35
  icon: 'ph--plus--regular',
37
36
  label: ['add row', { ns: translationKey }],
38
37
  testId: 'table.toolbar.add-row',
39
38
  });
40
- const save = createMenuAction<TableToolbarActionProperties>('save-view', {
41
- type: 'save-view',
39
+ const save = createMenuAction<TableToolbarActionProperties>('save-view', () => onAction(save), {
40
+ type: 'save-view' as const,
42
41
  icon: 'ph--floppy-disk--regular',
43
42
  label: ['save view label', { ns: translationKey }],
44
43
  testId: 'table.toolbar.save-view',
@@ -46,8 +45,8 @@ const createTableToolbarActions = (state: TableToolbarState) => {
46
45
  hidden: !state.viewDirty,
47
46
  });
48
47
  const gap = createGapSeparator();
49
- const comment = createMenuAction('comment', {
50
- type: 'comment',
48
+ const comment = createMenuAction('comment', () => onAction(comment), {
49
+ type: 'comment' as const,
51
50
  icon: 'ph--chat-text--regular',
52
51
  label: ['create comment', { ns: translationKey }],
53
52
  testId: 'table.toolbar.comment',
@@ -61,11 +60,11 @@ const createTableToolbarActions = (state: TableToolbarState) => {
61
60
 
62
61
  export const TableToolbar = ({ classNames, viewDirty, attendableId, onAction }: TableToolbarProps) => {
63
62
  const state = useMemo(() => live<TableToolbarState>({ viewDirty }), []);
64
- const actionsCreator = useCallback(() => createTableToolbarActions(state), [state]);
63
+ const actionsCreator = useCallback(() => createTableToolbarActions(state, onAction), [state, onAction]);
65
64
  const menu = useMenuActions(actionsCreator);
66
65
 
67
66
  return (
68
- <MenuProvider {...menu} attendableId={attendableId} onAction={onAction as MenuActionHandler}>
67
+ <MenuProvider {...menu} attendableId={attendableId}>
69
68
  <ToolbarMenu classNames={classNames} />
70
69
  </MenuProvider>
71
70
  );
@@ -2,9 +2,10 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { Schema } from 'effect';
5
6
  import { useEffect } from 'react';
6
7
 
7
- import { setValue, toJsonSchema, S, TypeEnum, TypedObject, FormatEnum } from '@dxos/echo-schema';
8
+ import { setValue, toJsonSchema, TypeEnum, TypedObject, FormatEnum, ObjectId } from '@dxos/echo-schema';
8
9
  import { faker } from '@dxos/random';
9
10
  import { live, makeRef } from '@dxos/react-client/echo';
10
11
  import { createView, type ViewProjection } from '@dxos/schema';
@@ -12,11 +13,11 @@ import { createView, type ViewProjection } from '@dxos/schema';
12
13
  import { TableType } from '../types';
13
14
 
14
15
  export const TestSchema = TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
15
- id: S.String,
16
- name: S.optional(S.String),
17
- age: S.optional(S.Number),
18
- active: S.optional(S.Boolean),
19
- netWorth: S.optional(S.Number),
16
+ id: ObjectId,
17
+ name: Schema.optional(Schema.String),
18
+ age: Schema.optional(Schema.Number),
19
+ active: Schema.optional(Schema.Boolean),
20
+ netWorth: Schema.optional(Schema.Number),
20
21
  });
21
22
 
22
23
  export const createTable = (schema = TestSchema) => {
@@ -3,9 +3,10 @@
3
3
  //
4
4
 
5
5
  import { computed } from '@preact/signals-core';
6
+ import { Schema } from 'effect';
6
7
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
7
8
 
8
- import { S, TypedObject } from '@dxos/echo-schema';
9
+ import { TypedObject } from '@dxos/echo-schema';
9
10
  import { updateCounter } from '@dxos/echo-schema/testing';
10
11
  import { registerSignalsRuntime } from '@dxos/echo-signals';
11
12
  import { createEchoSchema } from '@dxos/live-object/testing';
@@ -99,8 +100,8 @@ describe('TableModel', () => {
99
100
  });
100
101
 
101
102
  class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
102
- title: S.String,
103
- completed: S.Boolean,
103
+ title: Schema.String,
104
+ completed: Schema.Boolean,
104
105
  }) {}
105
106
 
106
107
  const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
@@ -2,9 +2,10 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { Schema } from 'effect';
5
6
  import { describe, expect, it, beforeEach } from 'vitest';
6
7
 
7
- import { S, TypedObject } from '@dxos/echo-schema';
8
+ import { TypedObject } from '@dxos/echo-schema';
8
9
  import { live, makeRef } from '@dxos/live-object';
9
10
  import { createEchoSchema } from '@dxos/live-object/testing';
10
11
  import { createView, ViewProjection } from '@dxos/schema';
@@ -89,8 +90,8 @@ describe('TablePresentation', () => {
89
90
  });
90
91
 
91
92
  class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
92
- title: S.String,
93
- completed: S.Boolean,
93
+ title: Schema.String,
94
+ completed: Schema.Boolean,
94
95
  }) {}
95
96
 
96
97
  const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
@@ -3,8 +3,9 @@
3
3
  //
4
4
 
5
5
  import { signal } from '@preact/signals-core';
6
+ import { isNotNullable } from 'effect/Predicate';
6
7
 
7
- import { FormatEnum, getDXN, getValue } from '@dxos/echo-schema';
8
+ import { FormatEnum, getDXN, getValue, TypeEnum } from '@dxos/echo-schema';
8
9
  import { cellClassesForFieldType, formatForDisplay } from '@dxos/react-ui-form';
9
10
  import {
10
11
  type DxGridPlane,
@@ -84,6 +85,10 @@ export class TablePresentation<T extends TableRow = TableRow> {
84
85
  return '';
85
86
  }
86
87
 
88
+ if (props.type === 'array') {
89
+ return '';
90
+ }
91
+
87
92
  switch (props.format) {
88
93
  case FormatEnum.Boolean:
89
94
  case FormatEnum.SingleSelect:
@@ -121,6 +126,47 @@ export class TablePresentation<T extends TableRow = TableRow> {
121
126
  cell.className = mx(classes.flat());
122
127
  }
123
128
 
129
+ if (props.type === TypeEnum.Array) {
130
+ const targetArray = getValue(obj, field.path);
131
+ if (targetArray && Array.isArray(targetArray)) {
132
+ // TODO(ZaymonFC): For arrays of objects, objects should have a 'label' annotation on the field
133
+ // that can be used to render tags.
134
+ // TODO(ZaymonFC): Move to util.
135
+ const getLabel = (value: any) => {
136
+ // If the value is falsy, return undefined
137
+ // If the object is an array, check for 'name' property
138
+ // If the object is a primitive type (string, number, boolean) stringify
139
+ // If the object is an array return 'Array'
140
+ if (!value) {
141
+ return undefined;
142
+ }
143
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
144
+ return String(value);
145
+ }
146
+ if (Array.isArray(value)) {
147
+ return 'Array';
148
+ }
149
+ if (typeof value === 'object' && value.name) {
150
+ return value.name;
151
+ }
152
+ if (typeof value === 'object' && value.value) {
153
+ return `${value.value}`;
154
+ }
155
+ return 'Object';
156
+ };
157
+
158
+ const tags = targetArray
159
+ .map(getLabel)
160
+ .filter(isNotNullable)
161
+ .map((title) => {
162
+ return `<span class="dx-tag rounded-xl" data-hue="neutral">${title}</span>`;
163
+ })
164
+ .join('');
165
+
166
+ cell.accessoryHtml = `<div role='none' class="flex flex-row gap-1 overflow-x-auto">${tags}</div>`;
167
+ }
168
+ }
169
+
124
170
  if (props.format === FormatEnum.Ref && props.referenceSchema) {
125
171
  const targetObj = getValue(obj, field.path)?.target;
126
172
  if (targetObj) {
@@ -166,7 +212,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
166
212
  .join(' ');
167
213
 
168
214
  if (tags) {
169
- cell.accessoryHtml = tags;
215
+ cell.accessoryHtml = `<div role='none' class="flex flex-row gap-1">${tags}</div>`;
170
216
  }
171
217
  }
172
218
  }
@@ -206,7 +252,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
206
252
  ${direction !== undefined ? tableButtons.sort.render({ fieldId: field.id, direction }) : ''}
207
253
  ${tableButtons.columnSettings.render({ fieldId: field.id })}
208
254
  `,
209
- className: '!bg-gridHeader',
255
+ className: '!bg-toolbarSurface',
210
256
  };
211
257
  }
212
258
 
@@ -249,7 +295,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
249
295
  if (!this.model.features.selection.enabled || this.model.selection.selectionMode === 'single') {
250
296
  return {
251
297
  [toPlaneCellIndex({ col: 0, row: 0 })]: {
252
- className: '!bg-gridHeader',
298
+ className: '!bg-toolbarSurface',
253
299
  readonly: true,
254
300
  value: '',
255
301
  },
@@ -263,7 +309,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
263
309
  header: true,
264
310
  checked: this.model.selection.allRowsSeleted.value,
265
311
  }),
266
- className: '!bg-gridHeader',
312
+ className: '!bg-toolbarSurface',
267
313
  readonly: true,
268
314
  value: '',
269
315
  },
@@ -278,7 +324,7 @@ export class TablePresentation<T extends TableRow = TableRow> {
278
324
  disabled: (this.model.view?.fields?.length ?? 0) >= VIEW_FIELD_LIMIT,
279
325
  })
280
326
  : undefined,
281
- className: '!bg-gridHeader',
327
+ className: '!bg-toolbarSurface',
282
328
  readonly: true,
283
329
  value: '',
284
330
  },
@@ -2,20 +2,28 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { EchoObject, Expando, LabelAnnotationId, ObjectId, Ref, S } from '@dxos/echo-schema';
5
+ import { Schema } from 'effect';
6
+
6
7
  // import { ThreadType } from '@dxos/plugin-space/types';
8
+ import { LabelAnnotationId, ObjectId, Ref, Expando, EchoObject } from '@dxos/echo-schema';
7
9
  import { ViewType } from '@dxos/schema';
8
10
 
9
- export const TableSchema = S.Struct({
11
+ export const TableSchema = Schema.Struct({
10
12
  id: ObjectId,
11
- name: S.optional(S.String),
12
- view: S.optional(Ref(ViewType)),
13
+ name: Schema.optional(Schema.String),
14
+ view: Schema.optional(Ref(ViewType)),
13
15
  // TODO(burdon): Document why threads is included here?
14
- threads: S.optional(S.Array(Ref(Expando /* ThreadType */))),
16
+ threads: Schema.optional(Schema.Array(Ref(Expando /* ThreadType */))),
15
17
  }).annotations({
16
18
  // TODO(burdon): Move annotation to property.
17
19
  [LabelAnnotationId]: 'name',
18
20
  });
19
21
 
20
- export const TableType = TableSchema.pipe(EchoObject({ typename: 'dxos.org/type/Table', version: '0.1.0' }));
21
- export interface TableType extends S.Schema.Type<typeof TableType> {}
22
+ // TODO(burdon): Move out of react-ui-xxx.
23
+ export const TableType = TableSchema.pipe(
24
+ EchoObject({
25
+ typename: 'dxos.org/type/Table',
26
+ version: '0.1.0',
27
+ }),
28
+ );
29
+ export interface TableType extends Schema.Schema.Type<typeof TableType> {}
@@ -2,7 +2,10 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import type { BaseSchema, JsonSchemaType, SortDirectionType } from '@dxos/echo-schema';
5
+ import { type Schema } from 'effect';
6
+
7
+ import { getTypename, toJsonSchema } from '@dxos/echo-schema';
8
+ import type { JsonSchemaType, SortDirectionType } from '@dxos/echo-schema';
6
9
  import { live, makeRef } from '@dxos/live-object';
7
10
  import {
8
11
  createView,
@@ -27,7 +30,7 @@ export type TablePropertyDefinition = SchemaPropertyDefinition & Partial<Propert
27
30
  * @deprecated
28
31
  */
29
32
  // TODO(burdon): Remove variance.
30
- export const getBaseSchems = ({
33
+ export const getBaseSchema = ({
31
34
  typename,
32
35
  properties,
33
36
  jsonSchema,
@@ -36,13 +39,13 @@ export const getBaseSchems = ({
36
39
  typename?: string;
37
40
  properties?: TablePropertyDefinition[];
38
41
  jsonSchema?: JsonSchemaType;
39
- schema?: BaseSchema;
42
+ schema?: Schema.Schema.AnyNoContext;
40
43
  }): { typename: string; jsonSchema: JsonSchemaType } => {
41
44
  if (typename && properties) {
42
45
  const schema = getSchemaFromPropertyDefinitions(typename, properties);
43
46
  return { typename: schema.typename, jsonSchema: schema.jsonSchema };
44
47
  } else if (schema) {
45
- return { typename: schema.typename, jsonSchema: schema.jsonSchema };
48
+ return { typename: getTypename(schema)!, jsonSchema: toJsonSchema(schema) };
46
49
  } else if (typename && jsonSchema) {
47
50
  return { typename, jsonSchema };
48
51
  } else {
package/src/util/index.ts CHANGED
@@ -7,3 +7,4 @@ export * from './signals';
7
7
  export * from './table';
8
8
  export * from './table-buttons';
9
9
  export * from './table-controls';
10
+ export * from './schema';