@dxos/react-ui-table 0.8.4-main.72ec0f3 → 0.8.4-main.7996785055

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