@dxos/react-ui-table 0.8.4-main.70d3990 → 0.8.4-main.74a063c4e0

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 (134) hide show
  1. package/dist/lib/browser/{chunk-5XWSB3JP.mjs → chunk-BF54SSNL.mjs} +27 -28
  2. package/dist/lib/browser/chunk-BF54SSNL.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-KNC7XYT6.mjs → chunk-NTWHM2PX.mjs} +620 -652
  4. package/dist/lib/browser/chunk-NTWHM2PX.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +1164 -1021
  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 +22 -21
  9. package/dist/lib/browser/testing/index.mjs.map +3 -3
  10. package/dist/lib/browser/types/index.mjs +1 -1
  11. package/dist/lib/node-esm/{chunk-MKTMOY46.mjs → chunk-SIRDIPHZ.mjs} +27 -28
  12. package/dist/lib/node-esm/chunk-SIRDIPHZ.mjs.map +7 -0
  13. package/dist/lib/node-esm/{chunk-CWSGS3Y2.mjs → chunk-Z3TLFG6Z.mjs} +620 -651
  14. package/dist/lib/node-esm/chunk-Z3TLFG6Z.mjs.map +7 -0
  15. package/dist/lib/node-esm/index.mjs +1164 -1021
  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 +22 -21
  19. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  20. package/dist/lib/node-esm/types/index.mjs +1 -1
  21. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -2
  22. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  23. package/dist/types/src/components/Table/ColumnSettings.d.ts +2 -2
  24. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  25. package/dist/types/src/components/Table/DynamicTable.d.ts +7 -7
  26. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  27. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +21 -37
  28. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  29. package/dist/types/src/components/Table/Relations.stories.d.ts +82 -81
  30. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  32. package/dist/types/src/components/Table/Table.d.ts +28 -21
  33. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  34. package/dist/types/src/components/Table/Table.stories.d.ts +84 -83
  35. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  36. package/dist/types/src/components/Table/TableContent.d.ts +21 -0
  37. package/dist/types/src/components/Table/TableContent.d.ts.map +1 -0
  38. package/dist/types/src/components/Table/TableToolbar.d.ts +25 -0
  39. package/dist/types/src/components/Table/TableToolbar.d.ts.map +1 -0
  40. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
  41. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +10 -12
  42. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  43. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +5 -7
  44. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  45. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +24 -26
  46. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  47. package/dist/types/src/components/index.d.ts +0 -1
  48. package/dist/types/src/components/index.d.ts.map +1 -1
  49. package/dist/types/src/components/testing.d.ts +22 -13
  50. package/dist/types/src/components/testing.d.ts.map +1 -1
  51. package/dist/types/src/hooks/useAddRow.d.ts +5 -6
  52. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  53. package/dist/types/src/hooks/useProjectionModel.d.ts +2 -1
  54. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -1
  55. package/dist/types/src/hooks/useTableModel.d.ts +6 -5
  56. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  57. package/dist/types/src/model/modal-controller.d.ts +5 -1
  58. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  59. package/dist/types/src/model/selection-model.d.ts +17 -8
  60. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  61. package/dist/types/src/model/table-model.d.ts +100 -18
  62. package/dist/types/src/model/table-model.d.ts.map +1 -1
  63. package/dist/types/src/model/table-presentation.d.ts +3 -1
  64. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  65. package/dist/types/src/playwright/TableManager.d.ts +2 -1
  66. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  67. package/dist/types/src/testing/useTestTableModel.d.ts +5 -8
  68. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  69. package/dist/types/src/translations.d.ts +21 -23
  70. package/dist/types/src/translations.d.ts.map +1 -1
  71. package/dist/types/src/types/Table.d.ts +13 -15
  72. package/dist/types/src/types/Table.d.ts.map +1 -1
  73. package/dist/types/src/util/dynamic-table.d.ts +12 -10
  74. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  75. package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
  76. package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
  77. package/dist/types/src/util/index.d.ts +2 -0
  78. package/dist/types/src/util/index.d.ts.map +1 -1
  79. package/dist/types/src/util/query-ast.d.ts +7 -0
  80. package/dist/types/src/util/query-ast.d.ts.map +1 -0
  81. package/dist/types/src/util/sort.d.ts +12 -0
  82. package/dist/types/src/util/sort.d.ts.map +1 -0
  83. package/dist/types/tsconfig.tsbuildinfo +1 -1
  84. package/package.json +55 -56
  85. package/src/components/Table/ColumnActionsMenu.tsx +15 -11
  86. package/src/components/Table/ColumnSettings.tsx +8 -9
  87. package/src/components/Table/DynamicTable.stories.tsx +22 -23
  88. package/src/components/Table/DynamicTable.tsx +32 -40
  89. package/src/components/Table/Relations.stories.tsx +56 -48
  90. package/src/components/Table/RowActionsMenu.tsx +3 -2
  91. package/src/components/Table/Table.stories.tsx +89 -78
  92. package/src/components/Table/Table.tsx +43 -407
  93. package/src/components/Table/TableContent.tsx +421 -0
  94. package/src/components/{TableToolbar → Table}/TableToolbar.tsx +43 -44
  95. package/src/components/TableCellEditor/CellValidationMessage.tsx +5 -5
  96. package/src/components/TableCellEditor/FormCellEditor.tsx +118 -135
  97. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +16 -14
  98. package/src/components/TableCellEditor/TableCellEditor.tsx +33 -29
  99. package/src/components/index.ts +0 -1
  100. package/src/components/testing.ts +42 -16
  101. package/src/hooks/useAddRow.ts +8 -10
  102. package/src/hooks/useProjectionModel.ts +29 -9
  103. package/src/hooks/useTableModel.ts +27 -23
  104. package/src/model/modal-controller.ts +23 -12
  105. package/src/model/selection-model.ts +75 -44
  106. package/src/model/table-model.test.ts +81 -54
  107. package/src/model/table-model.ts +410 -138
  108. package/src/model/table-presentation.test.ts +58 -49
  109. package/src/model/table-presentation.ts +34 -29
  110. package/src/playwright/TableManager.ts +12 -5
  111. package/src/playwright/playwright.config.ts +1 -1
  112. package/src/playwright/smoke.spec.ts +51 -7
  113. package/src/testing/useTestTableModel.ts +26 -27
  114. package/src/translations.ts +20 -22
  115. package/src/types/Table.ts +28 -38
  116. package/src/util/dynamic-table.test.ts +70 -0
  117. package/src/util/dynamic-table.ts +36 -19
  118. package/src/util/index.ts +2 -0
  119. package/src/util/query-ast.ts +22 -0
  120. package/src/util/sort.ts +90 -0
  121. package/src/util/table-buttons.ts +1 -1
  122. package/src/util/table-controls.ts +2 -2
  123. package/dist/lib/browser/chunk-5XWSB3JP.mjs.map +0 -7
  124. package/dist/lib/browser/chunk-KNC7XYT6.mjs.map +0 -7
  125. package/dist/lib/node-esm/chunk-CWSGS3Y2.mjs.map +0 -7
  126. package/dist/lib/node-esm/chunk-MKTMOY46.mjs.map +0 -7
  127. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +0 -21
  128. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +0 -1
  129. package/dist/types/src/components/TableToolbar/index.d.ts +0 -2
  130. package/dist/types/src/components/TableToolbar/index.d.ts.map +0 -1
  131. package/dist/types/src/model/table-sorting.d.ts +0 -56
  132. package/dist/types/src/model/table-sorting.d.ts.map +0 -1
  133. package/src/components/TableToolbar/index.ts +0 -5
  134. package/src/model/table-sorting.ts +0 -234
@@ -2,41 +2,42 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
+ import { RegistryContext } from '@effect-atom/atom-react';
5
6
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
- import type * as Schema from 'effect/Schema';
7
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
8
- import { expect, userEvent, within } from 'storybook/test';
7
+ import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
8
+ import { expect, userEvent, waitFor, within } from 'storybook/test';
9
9
 
10
10
  import { Obj, Type } from '@dxos/echo';
11
+ import { View } from '@dxos/echo';
12
+ import { invariant } from '@dxos/invariant';
11
13
  import { type DxGrid } from '@dxos/lit-grid';
12
- import '@dxos/lit-ui/dx-tag-picker.pcss';
13
- import { faker } from '@dxos/random';
14
- import { useClient } from '@dxos/react-client';
15
- import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
14
+ import { random } from '@dxos/random';
15
+ import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
16
16
  import { useAsyncEffect } from '@dxos/react-ui';
17
- import { withLayout, withTheme } from '@dxos/react-ui/testing';
18
17
  import { translations as formTranslations } from '@dxos/react-ui-form';
19
- import { View } from '@dxos/schema';
18
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
19
+ import { ViewModel } from '@dxos/schema';
20
20
  import { type ValueGenerator, createAsyncGenerator } from '@dxos/schema/testing';
21
+ import { withRegistry } from '@dxos/storybook-utils';
21
22
  import { Organization, Person } from '@dxos/types';
23
+ import '@dxos/lit-ui/dx-tag-picker.pcss';
22
24
 
23
25
  import { useProjectionModel, useTableModel } from '../../hooks';
24
26
  import { type TableFeatures, TablePresentation, type TableRow } from '../../model';
25
27
  import { translations } from '../../translations';
26
28
  import { Table } from '../../types';
27
-
28
29
  import { Table as TableComponent } from './Table';
29
30
 
30
- faker.seed(1);
31
- const generator: ValueGenerator = faker as any;
31
+ random.seed(1);
32
+ const generator: ValueGenerator = random as any;
32
33
 
33
34
  // TODO(burdon): Many-to-many relations.
34
35
  // TODO(burdon): Mutable and immutable views.
35
36
  // TODO(burdon): Reconcile schemas types and utils (see API PR).
36
37
  // TODO(burdon): Base type for T (with id); see ECHO API PR?
37
- const useTestModel = <S extends Type.Obj.Any>(schema: S, count: number) => {
38
- const client = useClient();
39
- const { space } = useClientProvider();
38
+ const useTestModel = <S extends Type.AnyObj>(schema: S, count: number) => {
39
+ const registry = useContext(RegistryContext);
40
+ const { space } = useClientStory();
40
41
  const [object, setObject] = useState<Table.Table>();
41
42
 
42
43
  const features = useMemo<TableFeatures>(
@@ -49,15 +50,14 @@ const useTestModel = <S extends Type.Obj.Any>(schema: S, count: number) => {
49
50
  return;
50
51
  }
51
52
 
52
- const { view, jsonSchema } = await View.makeFromSpace({ client, space, typename: Type.getTypename(schema) });
53
+ const { view, jsonSchema } = await ViewModel.makeFromDatabase({ db: space.db, typename: Type.getTypename(schema) });
53
54
  const object = Table.make({ view, jsonSchema });
54
55
  setObject(object);
55
56
  space.db.add(object);
56
- await space.db.schemaRegistry.register([schema]);
57
- }, [client, space, schema]);
57
+ }, [space, schema]);
58
58
 
59
- const projection = useProjectionModel(schema, object);
60
- const model = useTableModel<TableRow>({ object, projection, features });
59
+ const projection = useProjectionModel(schema, object, registry);
60
+ const model = useTableModel<TableRow>({ object, projection, db: space?.db, features });
61
61
 
62
62
  useEffect(() => {
63
63
  if (!model || !space) {
@@ -75,45 +75,44 @@ const useTestModel = <S extends Type.Obj.Any>(schema: S, count: number) => {
75
75
  return;
76
76
  }
77
77
 
78
- return new TablePresentation(model);
79
- }, [model]);
78
+ return new TablePresentation(registry, model);
79
+ }, [registry, model]);
80
80
 
81
81
  return { model, presentation };
82
82
  };
83
83
 
84
84
  const DefaultStory = () => {
85
- const client = useClient();
86
85
  const { model: orgModel, presentation: orgPresentation } = useTestModel(Organization.Organization, 50);
87
86
  const { model: contactModel, presentation: contactPresentation } = useTestModel(Person.Person, 50);
88
- const { space } = useClientProvider();
87
+ const { space } = useClientStory();
89
88
 
90
89
  const handleCreate = useCallback(
91
- (schema: Schema.Schema.AnyNoContext, values: any) => {
92
- return client.spaces.default.db.add(Obj.make(schema, values));
90
+ (schema: Type.AnyEntity, values: any) => {
91
+ invariant(Type.isObjectSchema(schema));
92
+ invariant(space);
93
+ return space.db.add(Obj.make(schema, values));
93
94
  },
94
95
  [space],
95
96
  );
96
97
 
97
98
  return (
98
- <div className='is-full bs-full grid grid-cols-2 divide-x divide-separator'>
99
+ <div className='dx-expander grid grid-cols-2 divide-x divide-separator'>
99
100
  <TableComponent.Root>
100
- <TableComponent.Main
101
+ <TableComponent.Content
101
102
  model={orgModel}
102
103
  schema={Organization.Organization}
103
104
  presentation={orgPresentation}
104
105
  onCreate={handleCreate}
105
- client={client}
106
106
  ignoreAttention
107
107
  testId='relations-0'
108
108
  />
109
109
  </TableComponent.Root>
110
110
  <TableComponent.Root>
111
- <TableComponent.Main
111
+ <TableComponent.Content
112
112
  model={contactModel}
113
113
  schema={Person.Person}
114
114
  presentation={contactPresentation}
115
115
  onCreate={handleCreate}
116
- client={client}
117
116
  ignoreAttention
118
117
  testId='relations-1'
119
118
  />
@@ -126,8 +125,9 @@ const meta = {
126
125
  title: 'ui/react-ui-table/Relations',
127
126
  render: DefaultStory,
128
127
  decorators: [
129
- withTheme,
130
- // TODO(thure): Shouldn’t `layout: 'fullscreen'` below make this unnecessary?
128
+ withTheme(),
129
+ withRegistry,
130
+ // TODO(thure): Shouldn't `layout: 'fullscreen'` below make this unnecessary?
131
131
  withLayout({ classNames: 'fixed inset-0' }),
132
132
  withClientProvider({
133
133
  types: [View.View, Organization.Organization, Person.Person, Table.Table],
@@ -147,7 +147,7 @@ export default meta;
147
147
  type Story = StoryObj<typeof meta>;
148
148
 
149
149
  export const Default: Story = {
150
- play: async ({ canvasElement }: any) => {
150
+ play: async ({ canvasElement }) => {
151
151
  const canvas = within(canvasElement);
152
152
  const body = within(document.body);
153
153
 
@@ -195,8 +195,15 @@ export const Default: Story = {
195
195
  const saveButton = await body.findByTestId('save-button');
196
196
  await userEvent.click(saveButton);
197
197
 
198
- // Verify the relation was set (cell should now contain the org name)
199
- await expect(targetCell).toHaveTextContent(orgName.substring(0, 4));
198
+ // Verify the relation was set (cell should now contain the org name).
199
+ // Wait for the grid (Lit web component) to repaint after the reactive data update.
200
+ await waitFor(
201
+ async () => {
202
+ const updatedCell = within(secondGrid).getByTestId('grid.4.0');
203
+ await expect(updatedCell).toHaveTextContent(orgName.substring(0, 4));
204
+ },
205
+ { timeout: 5000 },
206
+ );
200
207
 
201
208
  // Test object creation (new relations) - equivalent to "new relations work as expected" test
202
209
  // Find a different cell to test object creation (second row, relations column)
@@ -215,23 +222,24 @@ export const Default: Story = {
215
222
  const newSearchField = await body.findByPlaceholderText('Search…');
216
223
  await userEvent.click(newSearchField);
217
224
 
218
- // Type a new object name (this will create a new object)
225
+ // Type a new object name (this will create a new object).
219
226
  const newOrgName = 'Salieri LLC';
220
227
  await userEvent.type(newSearchField, newOrgName);
221
228
 
222
- // Look for an option to select (should be the create new option)
223
- const newOption = await body.findAllByRole('option');
224
- await expect(newOption[0]).toBeVisible();
229
+ // Click the create option directly to open the create form.
230
+ const createOptionLabel = await body.findByText(/Create new object/, undefined, { timeout: 2000 });
231
+ await userEvent.click(createOptionLabel.closest('[role="option"]') as HTMLElement);
225
232
 
226
- // Press Enter to select/create
227
- await userEvent.keyboard('{Enter}');
228
-
229
- // Look for and click save button
230
- const createReferencedObjectForm = await body.findByTestId('create-referenced-object-form');
233
+ // Look for and click save button.
234
+ const createReferencedObjectForm = await body.findByTestId('create-referenced-object-form', undefined, {
235
+ timeout: 2000,
236
+ });
231
237
  const saveObjectButton = await within(createReferencedObjectForm).findByTestId('save-button');
238
+ await expect(saveObjectButton).not.toBeDisabled();
232
239
  await userEvent.click(saveObjectButton);
233
240
 
234
- // Verify the new object was created and relation was set
235
- await expect(newTargetCell).toHaveTextContent(newOrgName);
241
+ // Verify the relation was set by checking for the accessory link anchor in the cell.
242
+ const updatedNewCell = await within(secondGrid).findByTestId('grid.4.1', undefined, { timeout: 5000 });
243
+ await expect(updatedNewCell.querySelector('dx-anchor')).toBeTruthy();
236
244
  },
237
- } as any;
245
+ };
@@ -2,6 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { useAtomValue } from '@effect-atom/atom-react';
5
6
  import React from 'react';
6
7
 
7
8
  import { DropdownMenu, toLocalizedString, useTranslation } from '@dxos/react-ui';
@@ -13,8 +14,8 @@ type RowActionsMenuProps = { model: TableModel; modals: ModalController };
13
14
 
14
15
  export const RowActionsMenu = ({ model, modals }: RowActionsMenuProps) => {
15
16
  const { t } = useTranslation(translationKey);
16
- const hasSelection = model.selection.hasSelection.value;
17
- const state = modals.state.value;
17
+ const hasSelection = model.selection.hasSelection;
18
+ const state = useAtomValue(modals.state);
18
19
  if (state?.type !== 'row') {
19
20
  return null;
20
21
  }
@@ -6,24 +6,24 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import * as Schema from 'effect/Schema';
7
7
  import React, { useCallback } from 'react';
8
8
 
9
- import { Annotation, Format, Obj, type QueryAST, Type } from '@dxos/echo';
10
- import { PropertyMetaAnnotationId } from '@dxos/echo/internal';
9
+ import { Annotation, type Database, Format, Obj, type QueryAST, Ref, Type } from '@dxos/echo';
10
+ import { View } from '@dxos/echo';
11
+ import { type Mutable, PropertyMetaAnnotationId } from '@dxos/echo/internal';
11
12
  import { invariant } from '@dxos/invariant';
12
- import { faker } from '@dxos/random';
13
+ import { random } from '@dxos/random';
13
14
  import { PublicKey } from '@dxos/react-client';
14
- import { type Space } from '@dxos/react-client/echo';
15
15
  import { withClientProvider } from '@dxos/react-client/testing';
16
- import { withTheme } from '@dxos/react-ui/testing';
16
+ import { Panel, ScrollArea } from '@dxos/react-ui';
17
17
  import { ViewEditor, translations as formTranslations } from '@dxos/react-ui-form';
18
18
  import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
19
- import { View, getSchemaFromPropertyDefinitions, getTypenameFromQuery } from '@dxos/schema';
19
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
20
+ import { ViewModel, getSchemaFromPropertyDefinitions, getTypenameFromQuery } from '@dxos/schema';
20
21
  import { TestSchema, createObjectFactory } from '@dxos/schema/testing';
22
+ import { withRegistry } from '@dxos/storybook-utils';
21
23
 
22
24
  import { useTestTableModel } from '../../testing';
23
25
  import { translations } from '../../translations';
24
26
  import { Table } from '../../types';
25
- import { TableToolbar } from '../TableToolbar';
26
-
27
27
  import { Table as TableComponent } from './Table';
28
28
 
29
29
  const Example = Schema.Struct({
@@ -47,11 +47,11 @@ const Example = Schema.Struct({
47
47
  }),
48
48
  ),
49
49
  description: Schema.optional(Schema.String).annotations({ title: 'Description' }),
50
- parent: Schema.optional(Schema.suspend((): Type.Ref<Example> => Type.Ref(Example))).annotations({
50
+ parent: Schema.optional(Schema.suspend((): Ref.RefSchema<Example> => Ref.Ref(Example))).annotations({
51
51
  title: 'Parent',
52
52
  }),
53
53
  }).pipe(
54
- Type.Obj({ typename: `example.com/type/${PublicKey.random().truncate()}`, version: '0.1.0' }),
54
+ Type.object({ typename: `com.example.type.${PublicKey.random().truncate()}`, version: '0.1.0' }),
55
55
  Annotation.LabelAnnotation.set(['name']),
56
56
  );
57
57
  interface Example extends Schema.Schema.Type<typeof Example> {}
@@ -59,12 +59,12 @@ interface Example extends Schema.Schema.Type<typeof Example> {}
59
59
  const StoryViewEditor = ({
60
60
  view,
61
61
  schema,
62
- space,
62
+ db,
63
63
  handleDeleteColumn,
64
64
  }: {
65
65
  view?: View.View;
66
66
  schema?: Schema.Schema.AnyNoContext;
67
- space?: Space;
67
+ db?: Database.Database;
68
68
  handleDeleteColumn: (fieldId: string) => void;
69
69
  }) => {
70
70
  const handleQueryChanged = useCallback(
@@ -73,7 +73,9 @@ const StoryViewEditor = ({
73
73
  invariant(Type.isMutable(schema));
74
74
  schema.updateTypename(getTypenameFromQuery(newQuery));
75
75
  invariant(view);
76
- view.query.ast = newQuery;
76
+ Obj.change(view, (obj) => {
77
+ obj.query.ast = newQuery as Mutable<typeof newQuery>;
78
+ });
77
79
  },
78
80
  [schema, view],
79
81
  );
@@ -84,7 +86,7 @@ const StoryViewEditor = ({
84
86
 
85
87
  return (
86
88
  <ViewEditor
87
- registry={space?.db.schemaRegistry}
89
+ registry={db?.schemaRegistry}
88
90
  schema={schema}
89
91
  view={view}
90
92
  onQueryChanged={handleQueryChanged}
@@ -98,18 +100,17 @@ const StoryViewEditor = ({
98
100
  //
99
101
 
100
102
  const DefaultStory = () => {
101
- const {
102
- space,
103
- schema,
104
- table,
105
- tableRef,
106
- model,
107
- presentation,
108
- client,
109
- handleInsertRow,
110
- handleSaveView,
111
- handleDeleteColumn,
112
- } = useTestTableModel();
103
+ const { db, schema, table, tableRef, model, presentation, handleInsertRow, handleSaveView, handleDeleteColumn } =
104
+ useTestTableModel();
105
+
106
+ const handleRowClick = useCallback(
107
+ (row: any) => {
108
+ if (model?.getDraftRowCount() === 0 && ['frozenRowsEnd', 'fixedEndStart', 'fixedEndEnd'].includes(row?.plane)) {
109
+ handleInsertRow();
110
+ }
111
+ },
112
+ [model, handleInsertRow],
113
+ );
113
114
 
114
115
  if (!schema || !table?.view.target) {
115
116
  return <div />;
@@ -117,68 +118,76 @@ const DefaultStory = () => {
117
118
 
118
119
  return (
119
120
  <div className='grow grid grid-cols-[1fr_350px]'>
120
- <div className='grid grid-rows-[min-content_1fr] min-bs-0 overflow-hidden'>
121
- <TableToolbar classNames='border-be border-subduedSeparator' onAdd={handleInsertRow} onSave={handleSaveView} />
122
- <TableComponent.Root>
123
- <TableComponent.Main
124
- ref={tableRef}
125
- model={model}
126
- presentation={presentation}
127
- schema={schema}
128
- client={client}
129
- ignoreAttention
130
- />
131
- </TableComponent.Root>
132
- </div>
133
- <div className='flex flex-col bs-full border-l border-separator overflow-y-auto'>
134
- <StoryViewEditor
135
- view={table.view.target}
136
- schema={schema}
137
- space={space}
138
- handleDeleteColumn={handleDeleteColumn}
139
- />
140
- <SyntaxHighlighter language='json' className='text-xs'>
141
- {JSON.stringify({ view: table.view.target, schema }, null, 2)}
142
- </SyntaxHighlighter>
143
- </div>
121
+ <TableComponent.Root ref={tableRef}>
122
+ <Panel.Root>
123
+ <Panel.Toolbar asChild>
124
+ <TableComponent.Toolbar
125
+ classNames='border-b border-subdued-separator'
126
+ onAdd={handleInsertRow}
127
+ onSave={handleSaveView}
128
+ />
129
+ </Panel.Toolbar>
130
+ <Panel.Content asChild>
131
+ <TableComponent.Content
132
+ schema={schema}
133
+ model={model}
134
+ presentation={presentation}
135
+ onRowClick={handleRowClick}
136
+ ignoreAttention
137
+ />
138
+ </Panel.Content>
139
+ </Panel.Root>
140
+ </TableComponent.Root>
141
+ <ScrollArea.Root orientation='vertical' classNames='border-l border-separator'>
142
+ <ScrollArea.Viewport>
143
+ <StoryViewEditor view={table.view.target} schema={schema} db={db} handleDeleteColumn={handleDeleteColumn} />
144
+ <SyntaxHighlighter language='json' className='text-xs'>
145
+ {JSON.stringify({ view: table.view.target, schema }, null, 2)}
146
+ </SyntaxHighlighter>
147
+ </ScrollArea.Viewport>
148
+ </ScrollArea.Root>
144
149
  </div>
145
150
  );
146
151
  };
147
152
 
148
- type StoryProps = { rows?: number };
153
+ type DefaultStoryProps = { rows?: number };
149
154
 
150
155
  //
151
156
  // Story definitions.
152
157
  //
153
158
 
154
- // TODO(burdon): Need super simple story.
159
+ // TODO(burdon): Need simpler story.
155
160
 
156
161
  const meta = {
157
162
  title: 'ui/react-ui-table/Table',
158
163
  render: DefaultStory,
159
164
  decorators: [
160
- withTheme,
165
+ withTheme(),
166
+ withLayout({ layout: 'fullscreen' }),
167
+ withRegistry,
161
168
  withClientProvider({
162
169
  types: [View.View, Table.Table],
163
170
  createIdentity: true,
164
171
  createSpace: true,
165
- onCreateSpace: async ({ client, space }) => {
172
+ onCreateSpace: async ({ space }) => {
166
173
  const [schema] = await space.db.schemaRegistry.register([Example]);
167
- const { view, jsonSchema } = await View.makeFromSpace({ client, space, typename: schema.typename });
174
+ const { view, jsonSchema } = await ViewModel.makeFromDatabase({ db: space.db, typename: schema.typename });
168
175
  const table = Table.make({ view, jsonSchema });
169
- view.projection.fields = [
170
- view.projection.fields.find((field: any) => field.path === 'name')!,
171
- ...view.projection.fields.filter((field: any) => field.path !== 'name'),
172
- ];
176
+ Obj.change(view, (obj) => {
177
+ obj.projection.fields = [
178
+ obj.projection.fields.find((field) => field.path === 'name')!,
179
+ ...obj.projection.fields.filter((field) => field.path !== 'name'),
180
+ ];
181
+ });
173
182
 
174
183
  space.db.add(table);
175
184
 
176
185
  Array.from({ length: 10 }).map(() => {
177
186
  return space.db.add(
178
187
  Obj.make(schema, {
179
- name: faker.lorem.sentence(),
180
- status: faker.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
181
- description: faker.lorem.paragraph(),
188
+ name: random.lorem.sentence(),
189
+ status: random.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
190
+ description: random.lorem.paragraph(),
182
191
  }),
183
192
  );
184
193
  });
@@ -207,12 +216,15 @@ export const StaticSchema: StoryObj = {
207
216
  types: [View.View, Table.Table],
208
217
  createIdentity: true,
209
218
  createSpace: true,
210
- onCreateSpace: async ({ client, space }) => {
211
- const { view, jsonSchema } = await View.makeFromSpace({ client, space, typename: TestSchema.Person.typename });
219
+ onCreateSpace: async ({ space }) => {
220
+ const { view, jsonSchema } = await ViewModel.makeFromDatabase({
221
+ db: space.db,
222
+ typename: TestSchema.Person.typename,
223
+ });
212
224
  const table = Table.make({ view, jsonSchema });
213
225
  space.db.add(table);
214
226
 
215
- const factory = createObjectFactory(space.db, faker as any);
227
+ const factory = createObjectFactory(space.db, random as any);
216
228
  await factory([
217
229
  { type: TestSchema.Person, count: 10 },
218
230
  // { type: TestSchema.Organization, count: 1 },
@@ -237,8 +249,8 @@ const ContactWithArrayOfEmails = Schema.Struct({
237
249
  ),
238
250
  ),
239
251
  }).pipe(
240
- Type.Obj({
241
- typename: 'dxos.org/type/ContactWithArrayOfEmails',
252
+ Type.object({
253
+ typename: 'org.dxos.type.contactWithArrayOfEmails',
242
254
  version: '0.1.0',
243
255
  }),
244
256
  );
@@ -250,16 +262,15 @@ export const ArrayOfObjects: StoryObj = {
250
262
  types: [View.View, Table.Table, TestSchema.Person, TestSchema.Organization, ContactWithArrayOfEmails],
251
263
  createIdentity: true,
252
264
  createSpace: true,
253
- onCreateSpace: async ({ client, space }) => {
254
- const { view, jsonSchema } = await View.makeFromSpace({
255
- client,
256
- space,
265
+ onCreateSpace: async ({ space }) => {
266
+ const { view, jsonSchema } = await ViewModel.makeFromDatabase({
267
+ db: space.db,
257
268
  typename: ContactWithArrayOfEmails.typename,
258
269
  });
259
270
  const table = Table.make({ view, jsonSchema });
260
271
  space.db.add(table);
261
272
 
262
- const factory = createObjectFactory(space.db, faker as any);
273
+ const factory = createObjectFactory(space.db, random as any);
263
274
  await factory([
264
275
  // { type: TestSchema.Person, count: 10 },
265
276
  // { type: TestSchema.Organization, count: 1 },
@@ -274,7 +285,7 @@ export const ArrayOfObjects: StoryObj = {
274
285
  },
275
286
  };
276
287
 
277
- export const Tags: Meta<StoryProps> = {
288
+ export const Tags: Meta<DefaultStoryProps> = {
278
289
  title: 'ui/react-ui-table/Table',
279
290
  render: DefaultStory,
280
291
  decorators: [
@@ -282,9 +293,9 @@ export const Tags: Meta<StoryProps> = {
282
293
  types: [View.View, Table.Table],
283
294
  createIdentity: true,
284
295
  createSpace: true,
285
- onCreateSpace: async ({ client, space }) => {
296
+ onCreateSpace: async ({ space }) => {
286
297
  // Configure schema.
287
- const typename = 'example.com/SingleSelect';
298
+ const typename = 'com.example.type.single-select';
288
299
  const selectOptions = [
289
300
  { id: 'one', title: 'One', color: 'emerald' },
290
301
  { id: 'two', title: 'Two', color: 'blue' },
@@ -310,7 +321,7 @@ export const Tags: Meta<StoryProps> = {
310
321
  const [storedSchema] = await space.db.schemaRegistry.register([schema]);
311
322
 
312
323
  // Initialize table.
313
- const { view, jsonSchema } = await View.makeFromSpace({ client, space, typename });
324
+ const { view, jsonSchema } = await ViewModel.makeFromDatabase({ db: space.db, typename });
314
325
  const table = Table.make({ view, jsonSchema });
315
326
  space.db.add(table);
316
327
 
@@ -318,8 +329,8 @@ export const Tags: Meta<StoryProps> = {
318
329
  Array.from({ length: 10 }).map(() => {
319
330
  return space.db.add(
320
331
  Obj.make(storedSchema, {
321
- single: faker.helpers.arrayElement([...selectOptionIds, undefined]),
322
- multiple: faker.helpers.randomSubset(selectOptionIds),
332
+ single: random.helpers.arrayElement([...selectOptionIds, undefined]),
333
+ multiple: random.helpers.randomSubset(selectOptionIds),
323
334
  }),
324
335
  );
325
336
  });