@dxos/react-ui-table 0.8.4-main.f9ba587 → 0.8.4-main.fd6878d

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 (122) hide show
  1. package/dist/lib/browser/chunk-L2LSB6KV.mjs +18 -0
  2. package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-ZAOSIG3L.mjs +1814 -0
  4. package/dist/lib/browser/chunk-ZAOSIG3L.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +658 -2359
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/testing/index.mjs +123 -0
  9. package/dist/lib/browser/testing/index.mjs.map +7 -0
  10. package/dist/lib/browser/types/index.mjs +3 -5
  11. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +20 -0
  12. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +7 -0
  13. package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs +1815 -0
  14. package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +658 -2359
  16. package/dist/lib/node-esm/index.mjs.map +4 -4
  17. package/dist/lib/node-esm/meta.json +1 -1
  18. package/dist/lib/node-esm/testing/index.mjs +124 -0
  19. package/dist/lib/node-esm/testing/index.mjs.map +7 -0
  20. package/dist/lib/node-esm/types/index.mjs +3 -5
  21. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -1
  22. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  23. package/dist/types/src/components/Table/ColumnSettings.d.ts +4 -2
  24. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  25. package/dist/types/src/components/Table/CreateRefPanel.d.ts +1 -1
  26. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/DynamicTable.d.ts +1 -1
  28. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +1 -1
  30. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/{relations.stories.d.ts → Relations.stories.d.ts} +2 -2
  32. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -0
  33. package/dist/types/src/components/Table/RowActionsMenu.d.ts +1 -1
  34. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  35. package/dist/types/src/components/Table/Table.d.ts +1 -0
  36. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  37. package/dist/types/src/components/Table/Table.stories.d.ts +1 -1
  38. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  39. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  40. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +2 -1
  41. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  42. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +8 -7
  43. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  44. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  45. package/dist/types/src/components/TableCellEditor/extension.d.ts.map +1 -1
  46. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
  47. package/dist/types/src/components/testing.d.ts +3 -40
  48. package/dist/types/src/components/testing.d.ts.map +1 -1
  49. package/dist/types/src/hooks/useTableModel.d.ts +8 -7
  50. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  51. package/dist/types/src/index.d.ts +0 -1
  52. package/dist/types/src/index.d.ts.map +1 -1
  53. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  54. package/dist/types/src/model/selection-model.d.ts +2 -1
  55. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  56. package/dist/types/src/model/table-model.d.ts +17 -16
  57. package/dist/types/src/model/table-model.d.ts.map +1 -1
  58. package/dist/types/src/model/table-presentation.d.ts +2 -1
  59. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  60. package/dist/types/src/model/table-sorting.d.ts +4 -4
  61. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  62. package/dist/types/src/testing/index.d.ts +2 -0
  63. package/dist/types/src/testing/index.d.ts.map +1 -0
  64. package/dist/types/src/testing/useTestTableModel.d.ts +41 -0
  65. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -0
  66. package/dist/types/src/translations.d.ts +2 -2
  67. package/dist/types/src/types/index.d.ts +1 -1
  68. package/dist/types/src/types/index.d.ts.map +1 -1
  69. package/dist/types/src/types/schema.d.ts +8 -0
  70. package/dist/types/src/types/schema.d.ts.map +1 -0
  71. package/dist/types/src/util/dynamic-table.d.ts +3 -4
  72. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  73. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  74. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  75. package/dist/types/src/util/table.d.ts +9 -11
  76. package/dist/types/src/util/table.d.ts.map +1 -1
  77. package/dist/types/tsconfig.tsbuildinfo +1 -1
  78. package/package.json +50 -36
  79. package/src/components/Table/ColumnActionsMenu.tsx +1 -1
  80. package/src/components/Table/ColumnSettings.tsx +15 -11
  81. package/src/components/Table/CreateRefPanel.tsx +6 -4
  82. package/src/components/Table/DynamicTable.stories.tsx +3 -2
  83. package/src/components/Table/DynamicTable.tsx +17 -10
  84. package/src/components/Table/{relations.stories.tsx → Relations.stories.tsx} +36 -35
  85. package/src/components/Table/RowActionsMenu.tsx +3 -3
  86. package/src/components/Table/Table.stories.tsx +99 -144
  87. package/src/components/Table/Table.tsx +87 -71
  88. package/src/components/TableCellEditor/CellValidationMessage.tsx +1 -1
  89. package/src/components/TableCellEditor/FormCellEditor.tsx +30 -21
  90. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +24 -27
  91. package/src/components/TableCellEditor/TableCellEditor.tsx +42 -27
  92. package/src/components/TableCellEditor/extension.ts +2 -2
  93. package/src/components/TableToolbar/TableToolbar.tsx +2 -2
  94. package/src/components/testing.ts +7 -24
  95. package/src/hooks/useTableModel.ts +21 -17
  96. package/src/index.ts +0 -1
  97. package/src/model/modal-controller.ts +1 -1
  98. package/src/model/selection-model.ts +6 -3
  99. package/src/model/table-model.test.ts +8 -8
  100. package/src/model/table-model.ts +68 -62
  101. package/src/model/table-presentation.test.ts +8 -8
  102. package/src/model/table-presentation.ts +35 -13
  103. package/src/model/table-sorting.ts +11 -11
  104. package/src/playwright/smoke.spec.ts +105 -14
  105. package/src/testing/index.ts +5 -0
  106. package/src/testing/useTestTableModel.ts +123 -0
  107. package/src/translations.ts +2 -2
  108. package/src/types/index.ts +2 -2
  109. package/src/types/schema.ts +16 -0
  110. package/src/util/dynamic-table.ts +20 -18
  111. package/src/util/schema.ts +1 -1
  112. package/src/util/table-buttons.ts +2 -1
  113. package/src/util/table-controls.ts +4 -1
  114. package/src/util/table.ts +37 -111
  115. package/dist/lib/browser/chunk-Q5QQXJUO.mjs +0 -27
  116. package/dist/lib/browser/chunk-Q5QQXJUO.mjs.map +0 -7
  117. package/dist/lib/node-esm/chunk-ZNOU6LHG.mjs +0 -29
  118. package/dist/lib/node-esm/chunk-ZNOU6LHG.mjs.map +0 -7
  119. package/dist/types/src/components/Table/relations.stories.d.ts.map +0 -1
  120. package/dist/types/src/types/table.d.ts +0 -56
  121. package/dist/types/src/types/table.d.ts.map +0 -1
  122. package/src/types/table.ts +0 -27
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import { type Schema } from 'effect';
6
- import React, { useCallback, useEffect, useMemo, useRef } from 'react';
6
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
7
7
 
8
8
  import { getSnapshot } from '@dxos/echo-schema';
9
9
  import { invariant } from '@dxos/invariant';
@@ -20,18 +20,26 @@ type FormCellEditorProps = {
20
20
  fieldProjection: FieldProjection;
21
21
  model?: TableModel;
22
22
  schema?: Schema.Schema.AnyNoContext;
23
+ onSave?: () => void;
23
24
  __gridScope: any;
24
25
  };
25
26
 
26
- export const FormCellEditor = ({ fieldProjection, model, schema, __gridScope }: FormCellEditorProps) => {
27
- const { editing, setEditing } = useGridContext('ArrayEditor', __gridScope);
28
- const cellRef = useRef<HTMLButtonElement | null>(null);
27
+ export const FormCellEditor = ({ fieldProjection, model, schema, onSave, __gridScope }: FormCellEditorProps) => {
28
+ const { editing: contextEditing, setEditing } = useGridContext('ArrayEditor', __gridScope);
29
+ const [editing, setLocalEditing] = useState(false);
30
+ const anchorRef = useRef<HTMLButtonElement | null>(null);
29
31
 
30
32
  useEffect(() => {
31
- if (editing && editing.cellElement) {
32
- cellRef.current = editing.cellElement as HTMLButtonElement;
33
+ if (contextEditing && contextEditing.cellElement) {
34
+ anchorRef.current = (contextEditing.cellElement as HTMLElement).querySelector(
35
+ '.dx-grid__cell__content',
36
+ ) as HTMLButtonElement;
37
+ setLocalEditing(true);
38
+ } else {
39
+ anchorRef.current = null;
40
+ setLocalEditing(false);
33
41
  }
34
- }, [editing?.cellElement]);
42
+ }, [contextEditing]);
35
43
 
36
44
  const narrowedSchema = useMemo(() => {
37
45
  if (!schema) {
@@ -41,8 +49,8 @@ export const FormCellEditor = ({ fieldProjection, model, schema, __gridScope }:
41
49
  }, [JSON.stringify(schema), fieldProjection.field.path]); // TODO(burdon): Avoid stringify.
42
50
 
43
51
  const originalRow = useMemo(() => {
44
- if (model && editing) {
45
- const cell = parseCellIndex(editing.index);
52
+ if (model && contextEditing) {
53
+ const cell = parseCellIndex(contextEditing.index);
46
54
  const row = model.getRowAt(cell.row);
47
55
  invariant(row);
48
56
 
@@ -50,7 +58,7 @@ export const FormCellEditor = ({ fieldProjection, model, schema, __gridScope }:
50
58
  }
51
59
 
52
60
  return undefined;
53
- }, [model, editing]);
61
+ }, [model, contextEditing]);
54
62
 
55
63
  const formValues = useMemo(() => {
56
64
  if (originalRow) {
@@ -65,27 +73,28 @@ export const FormCellEditor = ({ fieldProjection, model, schema, __gridScope }:
65
73
  const value = getDeep(values, [path]);
66
74
  setDeep(originalRow, [path], value);
67
75
  setEditing(null);
76
+ setLocalEditing(false);
77
+ onSave?.();
68
78
  },
69
79
  [fieldProjection.field.path, originalRow],
70
80
  );
71
81
 
72
- const handleOpenChange = useCallback(
73
- (nextOpen: boolean) => {
74
- if (nextOpen === false) {
75
- setEditing(null);
76
- }
77
- },
78
- [setEditing],
79
- );
82
+ const handleOpenChange = useCallback((nextOpen: boolean) => {
83
+ if (nextOpen === false) {
84
+ setEditing(null);
85
+ onSave?.();
86
+ }
87
+ setLocalEditing(nextOpen);
88
+ }, []);
80
89
 
81
90
  if (!editing) {
82
91
  return null;
83
92
  }
84
93
 
85
94
  return (
86
- <Popover.Root open={editing !== null} onOpenChange={handleOpenChange}>
87
- <Popover.VirtualTrigger virtualRef={cellRef} />
88
- <Popover.Content tabIndex={-1} classNames='popover-card-width'>
95
+ <Popover.Root open={editing} onOpenChange={handleOpenChange}>
96
+ <Popover.VirtualTrigger virtualRef={anchorRef} />
97
+ <Popover.Content tabIndex={-1} classNames='popover-card-width density-fine'>
89
98
  <Popover.Arrow />
90
99
  <Popover.Viewport>
91
100
  <Form values={formValues} schema={narrowedSchema as any} onSave={handleSave} />
@@ -11,18 +11,19 @@ import { Obj } from '@dxos/echo';
11
11
  import { type EchoSchema, isMutable } from '@dxos/echo-schema';
12
12
  import { invariant } from '@dxos/invariant';
13
13
  import { faker } from '@dxos/random';
14
- import { Filter, useQuery, live } from '@dxos/react-client/echo';
14
+ import { Filter, useQuery } from '@dxos/react-client/echo';
15
15
  import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
16
- import { defaultRowSize, Grid, type GridEditing } from '@dxos/react-ui-grid';
17
- import { ViewProjection, ViewType } from '@dxos/schema';
16
+ import { Grid, type GridEditing, defaultRowSize } from '@dxos/react-ui-grid';
17
+ import { DataType, createDefaultSchema } from '@dxos/schema';
18
18
  import { withLayout, withTheme } from '@dxos/storybook-utils';
19
19
 
20
- import { TableCellEditor, type TableCellEditorProps } from './TableCellEditor';
21
20
  import { useTableModel } from '../../hooks';
22
21
  import { type TableFeatures } from '../../model';
23
22
  import { translations } from '../../translations';
24
- import { TableType } from '../../types';
25
- import { initializeTable } from '../../util';
23
+ import { TableView } from '../../types';
24
+ import { createTable } from '../../util';
25
+
26
+ import { TableCellEditor, type TableCellEditorProps } from './TableCellEditor';
26
27
 
27
28
  type StoryProps = {
28
29
  editing: GridEditing;
@@ -32,23 +33,16 @@ const DefaultStory = ({ editing }: StoryProps) => {
32
33
  const { space } = useClientProvider();
33
34
  invariant(space);
34
35
 
35
- const tables = useQuery(space, Filter.type(TableType));
36
- const [table, setTable] = useState<TableType>();
36
+ const views = useQuery(space, Filter.type(DataType.View));
37
+ const [view, setView] = useState<DataType.View>();
37
38
  const [schema, setSchema] = useState<EchoSchema>();
38
39
  useEffect(() => {
39
- if (space && tables.length && !table) {
40
- const table = tables[0];
41
- invariant(table.view);
42
- setTable(table);
43
- setSchema(space.db.schemaRegistry.getSchema(table.view.target!.query.typename!));
44
- }
45
- }, [space, tables]);
46
-
47
- const projection = useMemo(() => {
48
- if (schema && table?.view) {
49
- return new ViewProjection(schema.jsonSchema, table.view.target!);
40
+ if (space && views.length && !view) {
41
+ const view = views[0];
42
+ setView(view);
43
+ setSchema(space.db.schemaRegistry.getSchema(view.query.typename!));
50
44
  }
51
- }, [schema, table?.view]);
45
+ }, [space, views, view]);
52
46
 
53
47
  const features: Partial<TableFeatures> = useMemo(
54
48
  () => ({
@@ -59,7 +53,7 @@ const DefaultStory = ({ editing }: StoryProps) => {
59
53
  [schema],
60
54
  );
61
55
 
62
- const model = useTableModel({ table, projection, features });
56
+ const model = useTableModel({ view, schema: schema?.jsonSchema, features });
63
57
 
64
58
  const handleQuery: TableCellEditorProps['onQuery'] = async ({ field }) => {
65
59
  // TODO(dmaretskyi): If no schema query nothing
@@ -73,7 +67,7 @@ const DefaultStory = ({ editing }: StoryProps) => {
73
67
  });
74
68
  };
75
69
 
76
- if (!model || !schema || !table) {
70
+ if (!model || !schema || !view) {
77
71
  return <div />;
78
72
  }
79
73
 
@@ -93,16 +87,19 @@ const meta: Meta<StoryProps> = {
93
87
  parameters: { translations, layout: 'centered' },
94
88
  decorators: [
95
89
  withClientProvider({
96
- types: [TableType, ViewType],
90
+ types: [DataType.View, TableView],
97
91
  createIdentity: true,
98
92
  createSpace: true,
99
93
  onSpaceCreated: async ({ client, space }) => {
100
- const table = space.db.add(Obj.make(TableType, {}));
101
- const schema = await initializeTable({ client, space, table });
94
+ const schema = createDefaultSchema();
95
+ const { view } = await createTable({ client, space, typename: schema.typename });
96
+ space.db.add(view);
102
97
  Array.from({ length: 10 }).forEach(() => {
103
98
  space.db.add(
104
- live(schema, {
105
- name: faker.person.fullName(),
99
+ Obj.make(schema, {
100
+ title: faker.person.fullName(),
101
+ status: faker.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
102
+ description: faker.lorem.sentence(),
106
103
  }),
107
104
  );
108
105
  });
@@ -8,29 +8,29 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
8
8
 
9
9
  import { FormatEnum, TypeEnum } from '@dxos/echo-schema';
10
10
  import { invariant } from '@dxos/invariant';
11
- import { type DxGrid } from '@dxos/lit-grid';
11
+ import { type DxGridAxis, type DxGridPosition } from '@dxos/lit-grid';
12
12
  import { useThemeContext } from '@dxos/react-ui';
13
13
  import { createMarkdownExtensions } from '@dxos/react-ui-editor';
14
14
  import {
15
- cellQuery,
16
- editorKeys,
17
- parseCellIndex,
18
- useGridContext,
19
- type DxGridPlanePosition,
15
+ type EditorBlurHandler,
20
16
  type EditorKeyEvent,
21
17
  type EditorKeyOrBlurHandler,
22
- type EditorBlurHandler,
23
18
  GridCellEditor,
24
19
  type GridCellEditorProps,
25
20
  type GridScopedProps,
21
+ cellQuery,
22
+ editorKeys,
23
+ parseCellIndex,
24
+ useGridContext,
26
25
  } from '@dxos/react-ui-grid';
27
- import { tagPicker, createLinks } from '@dxos/react-ui-tag-picker';
26
+ import { createLinks, tagPicker } from '@dxos/react-ui-tag-picker';
28
27
  import { type FieldProjection } from '@dxos/schema';
29
28
 
29
+ import { type ModalController, type TableModel } from '../../model';
30
+
30
31
  import { CellValidationMessage } from './CellValidationMessage';
31
- import { FormCellEditor } from './FormCellEditor';
32
32
  import { completion } from './extension';
33
- import { type TableModel, type ModalController } from '../../model';
33
+ import { FormCellEditor } from './FormCellEditor';
34
34
 
35
35
  const newValue = Symbol.for('newValue');
36
36
 
@@ -47,8 +47,9 @@ export type TableCellEditorProps = {
47
47
  model?: TableModel;
48
48
  modals?: ModalController;
49
49
  schema?: Schema.AnyNoContext;
50
- onEnter?: (cell: DxGridPlanePosition) => void;
51
- onFocus?: DxGrid['refocus'];
50
+ onEnter?: (cell: DxGridPosition) => void;
51
+ onFocus?: (axis?: DxGridAxis, delta?: -1 | 0 | 1, cell?: DxGridPosition) => void;
52
+ onSave?: () => void;
52
53
  onQuery?: (field: FieldProjection, text: string) => Promise<QueryResult[]>;
53
54
  };
54
55
 
@@ -58,6 +59,7 @@ export const TableValueEditor = ({
58
59
  schema,
59
60
  onEnter,
60
61
  onFocus,
62
+ onSave,
61
63
  onQuery,
62
64
  __gridScope,
63
65
  }: GridScopedProps<TableCellEditorProps>) => {
@@ -69,14 +71,26 @@ export const TableValueEditor = ({
69
71
  }
70
72
 
71
73
  const { col } = parseCellIndex(editing.index);
72
- const field = model.projection.view.fields[col];
74
+ const field = model.projection.fields[col];
73
75
  const fieldProjection = model.projection.getFieldProjection(field.id);
74
76
  invariant(fieldProjection);
75
77
  return fieldProjection;
76
78
  }, [model, editing]);
77
79
 
78
- if (fieldProjection?.props.type === TypeEnum.Array) {
79
- return <FormCellEditor fieldProjection={fieldProjection} model={model} schema={schema} __gridScope={__gridScope} />;
80
+ if (
81
+ fieldProjection?.props.type === TypeEnum.Array ||
82
+ fieldProjection?.props.format === FormatEnum.SingleSelect
83
+ // TODO(thure): Support `FormatEnum.MultiSelect`
84
+ ) {
85
+ return (
86
+ <FormCellEditor
87
+ fieldProjection={fieldProjection}
88
+ model={model}
89
+ schema={schema}
90
+ __gridScope={__gridScope}
91
+ onSave={onSave}
92
+ />
93
+ );
80
94
  }
81
95
 
82
96
  // For all other types, use the existing cell editor
@@ -87,6 +101,7 @@ export const TableValueEditor = ({
87
101
  onEnter={onEnter}
88
102
  onFocus={onFocus}
89
103
  onQuery={onQuery}
104
+ onSave={onSave}
90
105
  __gridScope={__gridScope}
91
106
  />
92
107
  );
@@ -100,13 +115,14 @@ export const TableCellEditor = ({
100
115
  onEnter,
101
116
  onFocus,
102
117
  onQuery,
118
+ onSave,
103
119
  __gridScope,
104
120
  }: GridScopedProps<TableCellEditorProps>) => {
105
121
  const { id: gridId, editing, setEditing } = useGridContext('TableCellEditor', __gridScope);
106
122
  const suppressNextBlur = useRef(false);
107
123
  const { themeMode } = useThemeContext();
108
- const [_validationError, setValidationError] = useState<string | null>(null);
109
- const [_validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
124
+ const [validationError, setValidationError] = useState<string | null>(null);
125
+ const [validationVariant, setValidationVariant] = useState<'error' | 'warning'>('error');
110
126
 
111
127
  const fieldProjection = useMemo<FieldProjection | undefined>(() => {
112
128
  if (!model || !editing) {
@@ -114,7 +130,7 @@ export const TableCellEditor = ({
114
130
  }
115
131
 
116
132
  const { col } = parseCellIndex(editing.index);
117
- const field = model.projection.view.fields[col];
133
+ const field = model.projection.fields[col];
118
134
  const fieldProjection = model.projection.getFieldProjection(field.id);
119
135
  invariant(fieldProjection);
120
136
  return fieldProjection;
@@ -131,7 +147,7 @@ export const TableCellEditor = ({
131
147
  const { row, col } = cell;
132
148
 
133
149
  if (model.isDraftCell(cell)) {
134
- const field = model.projection.view.fields[col];
150
+ const field = model.projection.fields[col];
135
151
  const hasValidationError = model.hasDraftRowValidationError(row, field.path);
136
152
 
137
153
  if (hasValidationError) {
@@ -167,6 +183,7 @@ export const TableCellEditor = ({
167
183
  onEnter?.(cell);
168
184
  onFocus?.();
169
185
  setEditing(null);
186
+ onSave?.();
170
187
  } else {
171
188
  setValidationError(validationResult.error);
172
189
  setValidationVariant('error');
@@ -205,9 +222,10 @@ export const TableCellEditor = ({
205
222
  setValidationError(null);
206
223
  model.setCellData(cell, value);
207
224
  setEditing(null);
225
+ onSave?.();
208
226
  onEnter?.(cell);
209
227
  if (event && onFocus) {
210
- onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
228
+ onFocus(determineNavigationAxis(event), determineNavigationDelta(event), cell);
211
229
  }
212
230
  } else {
213
231
  setValidationError(result.error);
@@ -216,12 +234,13 @@ export const TableCellEditor = ({
216
234
  } else {
217
235
  setValidationError(null);
218
236
  setEditing(null);
237
+ onSave?.();
219
238
  if (event && onFocus) {
220
239
  onFocus(determineNavigationAxis(event), determineNavigationDelta(event));
221
240
  }
222
241
  }
223
242
  },
224
- [model, editing, onFocus, onEnter, fieldProjection, setEditing],
243
+ [model, editing, onFocus, onEnter, fieldProjection, setEditing, onSave],
225
244
  );
226
245
 
227
246
  const extension = useMemo(() => {
@@ -322,7 +341,7 @@ export const TableCellEditor = ({
322
341
  if (model && editing) {
323
342
  const cell = parseCellIndex(editing.index);
324
343
  const { col } = cell;
325
- const field = model.projection.view.fields[col];
344
+ const field = model.projection.fields[col];
326
345
  const fieldProjection = model.projection.getFieldProjection(field.id);
327
346
 
328
347
  if (
@@ -376,11 +395,7 @@ export const TableCellEditor = ({
376
395
 
377
396
  return (
378
397
  <>
379
- <CellValidationMessage
380
- validationError={_validationError}
381
- variant={_validationVariant}
382
- __gridScope={__gridScope}
383
- />
398
+ <CellValidationMessage validationError={validationError} variant={validationVariant} __gridScope={__gridScope} />
384
399
  <GridCellEditor extension={extension} getCellContent={getCellContent} onBlur={handleBlur} slots={editorSlots} />
385
400
  </>
386
401
  );
@@ -3,10 +3,10 @@
3
3
  //
4
4
 
5
5
  import {
6
- autocompletion,
7
- pickedCompletion,
8
6
  type CompletionContext,
9
7
  type CompletionResult,
8
+ autocompletion,
9
+ pickedCompletion,
10
10
  } from '@codemirror/autocomplete';
11
11
  import { type Extension } from '@codemirror/state';
12
12
  import { EditorView } from '@codemirror/view';
@@ -11,11 +11,11 @@ import {
11
11
  type ActionGraphEdges,
12
12
  type ActionGraphNodes,
13
13
  type ActionGraphProps,
14
- createGapSeparator,
15
- createMenuAction,
16
14
  type MenuAction,
17
15
  MenuProvider,
18
16
  ToolbarMenu,
17
+ createGapSeparator,
18
+ createMenuAction,
19
19
  useMenuActions,
20
20
  } from '@dxos/react-ui-menu';
21
21
  import { rxFromSignal } from '@dxos/react-ui-menu';
@@ -5,13 +5,10 @@
5
5
  import { Schema } from 'effect';
6
6
  import { useEffect } from 'react';
7
7
 
8
- import { Obj, Ref } from '@dxos/echo';
9
- import { FormatEnum, ObjectId, setValue, toJsonSchema, TypedObject, TypeEnum } from '@dxos/echo-schema';
8
+ import { FormatEnum, ObjectId, TypeEnum, TypedObject, setValue } from '@dxos/echo-schema';
9
+ import { live } from '@dxos/live-object';
10
10
  import { faker } from '@dxos/random';
11
- import { live } from '@dxos/react-client/echo';
12
- import { createView, type ViewProjection } from '@dxos/schema';
13
-
14
- import { TableType } from '../types';
11
+ import { type ProjectionModel } from '@dxos/schema';
15
12
 
16
13
  export const TestSchema = TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
17
14
  id: ObjectId,
@@ -21,18 +18,6 @@ export const TestSchema = TypedObject({ typename: 'example.com/type/Test', versi
21
18
  netWorth: Schema.optional(Schema.Number),
22
19
  });
23
20
 
24
- export const createTable = (schema = TestSchema) => {
25
- return Obj.make(TableType, {
26
- view: Ref.make(
27
- createView({
28
- name: 'Test',
29
- typename: schema.typename,
30
- jsonSchema: toJsonSchema(schema),
31
- }),
32
- ),
33
- });
34
- };
35
-
36
21
  export const createItems = (n: number) => {
37
22
  const { data } = live({
38
23
  data: Array.from({ length: n }, () => ({
@@ -47,13 +32,13 @@ export const createItems = (n: number) => {
47
32
  };
48
33
 
49
34
  export type SimulatorProps = {
50
- table: TableType;
35
+ projection: ProjectionModel;
51
36
  items: any[];
52
37
  insertInterval?: number;
53
38
  updateInterval?: number;
54
39
  };
55
40
 
56
- export const useSimulator = ({ items, table, insertInterval, updateInterval }: SimulatorProps) => {
41
+ export const useSimulator = ({ items, projection, insertInterval, updateInterval }: SimulatorProps) => {
57
42
  useEffect(() => {
58
43
  if (!insertInterval) {
59
44
  return;
@@ -74,10 +59,8 @@ export const useSimulator = ({ items, table, insertInterval, updateInterval }: S
74
59
 
75
60
  const i = setInterval(() => {
76
61
  const rowIdx = Math.floor(Math.random() * items.length);
77
- const fields = table.view?.target?.fields ?? [];
62
+ const fields = projection.fields ?? [];
78
63
  const columnIdx = Math.floor(Math.random() * fields.length);
79
- // TODO(ZaymonFC): ... This is borked.
80
- const projection: ViewProjection = (table as any)._projection;
81
64
  const field = fields[columnIdx];
82
65
  const item = items[rowIdx];
83
66
 
@@ -115,5 +98,5 @@ export const useSimulator = ({ items, table, insertInterval, updateInterval }: S
115
98
  }, updateInterval);
116
99
 
117
100
  return () => clearInterval(i);
118
- }, [items, table.view?.target?.fields, updateInterval]);
101
+ }, [items, projection.fields, updateInterval]);
119
102
  };
@@ -4,19 +4,21 @@
4
4
 
5
5
  import { effect } from '@preact/signals-core';
6
6
  import orderBy from 'lodash.orderby';
7
- import { useEffect, useState } from 'react';
7
+ import { useEffect, useMemo, useState } from 'react';
8
8
 
9
- import { fullyQualifiedId, getSpace, type Live } from '@dxos/react-client/echo';
10
- import { useSelectionActions } from '@dxos/react-ui-attention';
11
- import { type ViewProjection } from '@dxos/schema';
9
+ import { type JsonSchemaType } from '@dxos/echo-schema';
10
+ import { type Live } from '@dxos/live-object';
11
+ import { fullyQualifiedId } from '@dxos/react-client/echo';
12
+ import { useSelected, useSelectionActions } from '@dxos/react-ui-attention';
13
+ import { type DataType, type ProjectionModel } from '@dxos/schema';
12
14
  import { isNonNullable } from '@dxos/util';
13
15
 
14
- import { type TableRow, TableModel, type TableModelProps, type TableRowAction } from '../model';
15
- import { type TableType } from '../types';
16
+ import { TableModel, type TableModelProps, type TableRow, type TableRowAction } from '../model';
16
17
 
17
18
  export type UseTableModelParams<T extends TableRow = TableRow> = {
18
- table?: TableType;
19
- projection?: ViewProjection;
19
+ view?: DataType.View;
20
+ schema?: JsonSchemaType;
21
+ projection?: ProjectionModel;
20
22
  rows?: Live<T>[];
21
23
  rowActions?: TableRowAction[];
22
24
  onSelectionChanged?: (selection: string[]) => void;
@@ -27,7 +29,8 @@ export type UseTableModelParams<T extends TableRow = TableRow> = {
27
29
  >;
28
30
 
29
31
  export const useTableModel = <T extends TableRow = TableRow>({
30
- table,
32
+ view,
33
+ schema,
31
34
  projection,
32
35
  rows,
33
36
  rowActions,
@@ -36,21 +39,24 @@ export const useTableModel = <T extends TableRow = TableRow>({
36
39
  onRowAction,
37
40
  ...props
38
41
  }: UseTableModelParams<T>): TableModel<T> | undefined => {
42
+ const selected = useSelected(view && fullyQualifiedId(view), 'multi');
43
+ const initialSelection = useMemo(() => selected, [view]);
44
+
39
45
  const [model, setModel] = useState<TableModel<T>>();
40
46
  useEffect(() => {
41
- if (!table || !projection) {
47
+ if (!view || !schema) {
42
48
  return;
43
49
  }
44
50
 
45
51
  let model: TableModel<T> | undefined;
46
52
  const t = setTimeout(async () => {
47
53
  model = new TableModel<T>({
48
- id: fullyQualifiedId(table),
49
- space: getSpace(table),
50
- view: table.view?.target,
54
+ view,
55
+ schema,
51
56
  projection,
52
57
  features,
53
58
  rowActions,
59
+ initialSelection,
54
60
  onRowAction,
55
61
  ...props,
56
62
  });
@@ -62,7 +68,7 @@ export const useTableModel = <T extends TableRow = TableRow>({
62
68
  clearTimeout(t);
63
69
  void model?.close();
64
70
  };
65
- }, [table, projection, table?.view?.target, features, rowActions]); // TODO(burdon): Trigger if callbacks change?
71
+ }, [view, schema, projection, features, rowActions, initialSelection]); // TODO(burdon): Trigger if callbacks change?
66
72
 
67
73
  // Update data.
68
74
  useEffect(() => {
@@ -77,9 +83,7 @@ export const useTableModel = <T extends TableRow = TableRow>({
77
83
  }
78
84
  }, [model, rows]);
79
85
 
80
- const { multiSelect, clear } = useSelectionActions(
81
- [table?.id, table?.view?.target?.query.typename].filter(isNonNullable),
82
- );
86
+ const { multiSelect, clear } = useSelectionActions([model?.id].filter(isNonNullable));
83
87
 
84
88
  useEffect(() => {
85
89
  if (!model) {
package/src/index.ts CHANGED
@@ -7,7 +7,6 @@ import { translations } from './translations';
7
7
  export * from './components';
8
8
  export * from './hooks';
9
9
  export * from './model';
10
- export * from './types';
11
10
  export * from './util';
12
11
 
13
12
  export { translations };
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import { signal } from '@preact/signals-core';
6
- import { type RefObject, type MutableRefObject } from 'react';
6
+ import { type MutableRefObject, type RefObject } from 'react';
7
7
 
8
8
  import { type Live } from '@dxos/live-object';
9
9
  import { log } from '@dxos/log';
@@ -2,17 +2,18 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { computed, effect, signal, type ReadonlySignal } from '@preact/signals-core';
5
+ import { type ReadonlySignal, type Signal, computed, effect, signal } from '@preact/signals-core';
6
6
 
7
7
  import { Resource } from '@dxos/context';
8
8
 
9
- import { type TableRow } from './table-model';
10
9
  import { touch } from '../util';
11
10
 
11
+ import { type TableRow } from './table-model';
12
+
12
13
  export type SelectionMode = 'single' | 'multiple';
13
14
 
14
15
  export class SelectionModel<T extends TableRow> extends Resource {
15
- private readonly _selection = signal<Set<string>>(new Set());
16
+ private readonly _selection: Signal<Set<string>>;
16
17
 
17
18
  private readonly _validSelectedIds = computed<Set<string>>(() => {
18
19
  const rows = this._rows.value;
@@ -38,9 +39,11 @@ export class SelectionModel<T extends TableRow> extends Resource {
38
39
  constructor(
39
40
  private readonly _rows: ReadonlySignal<T[]>,
40
41
  private readonly _selectionMode: SelectionMode,
42
+ private readonly _initialSelection: string[],
41
43
  private readonly _onSelectionChanged?: () => void,
42
44
  ) {
43
45
  super();
46
+ this._selection = signal(new Set(this._initialSelection));
44
47
  }
45
48
 
46
49
  protected override async _open(): Promise<void> {
@@ -6,16 +6,17 @@ import { computed } from '@preact/signals-core';
6
6
  import { Schema } from 'effect';
7
7
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
8
8
 
9
- import { Obj, Ref } from '@dxos/echo';
9
+ import { Obj } from '@dxos/echo';
10
10
  import { TypedObject } from '@dxos/echo-schema';
11
11
  import { updateCounter } from '@dxos/echo-schema/testing';
12
12
  import { registerSignalsRuntime } from '@dxos/echo-signals';
13
+ import { live } from '@dxos/live-object';
13
14
  import { createEchoSchema } from '@dxos/live-object/testing';
14
- import { live } from '@dxos/react-client/echo';
15
- import { createView, ViewProjection } from '@dxos/schema';
15
+ import { createView } from '@dxos/schema';
16
+
17
+ import { TableView } from '../types';
16
18
 
17
19
  import { TableModel, type TableModelProps } from './table-model';
18
- import { TableType } from '../types';
19
20
 
20
21
  // TODO(burdon): Tests are disabled in project.json since they bring in plugin deps.
21
22
  // Restore once factored out into react-ui-table.
@@ -107,8 +108,7 @@ class Test extends TypedObject({ typename: 'example.com/type/Test', version: '0.
107
108
 
108
109
  const createTableModel = (props: Partial<TableModelProps> = {}): TableModel => {
109
110
  const schema = createEchoSchema(Test);
110
- const view = createView({ name: 'Test', typename: schema.typename, jsonSchema: schema.jsonSchema });
111
- const projection = new ViewProjection(schema.jsonSchema, view);
112
- const table = Obj.make(TableType, { view: Ref.make(view) });
113
- return new TableModel({ id: table.id, space: undefined, view, projection, ...props });
111
+ const table = Obj.make(TableView, { sizes: {} });
112
+ const view = createView({ typename: schema.typename, jsonSchema: schema.jsonSchema, presentation: table });
113
+ return new TableModel({ view, schema: schema.jsonSchema, ...props });
114
114
  };