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