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

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 (183) hide show
  1. package/LICENSE +102 -5
  2. package/dist/lib/browser/chunk-4KOQTPFC.mjs +1702 -0
  3. package/dist/lib/browser/chunk-4KOQTPFC.mjs.map +7 -0
  4. package/dist/lib/browser/chunk-62PYOJPY.mjs +67 -0
  5. package/dist/lib/browser/chunk-62PYOJPY.mjs.map +7 -0
  6. package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
  7. package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +7 -0
  8. package/dist/lib/browser/index.mjs +1151 -1175
  9. package/dist/lib/browser/index.mjs.map +4 -4
  10. package/dist/lib/browser/meta.json +1 -1
  11. package/dist/lib/browser/testing/index.mjs +37 -45
  12. package/dist/lib/browser/testing/index.mjs.map +3 -3
  13. package/dist/lib/browser/translations.mjs +36 -0
  14. package/dist/lib/browser/translations.mjs.map +7 -0
  15. package/dist/lib/browser/types/index.mjs +4 -3
  16. package/dist/lib/node-esm/chunk-7LMOZUTS.mjs +68 -0
  17. package/dist/lib/node-esm/chunk-7LMOZUTS.mjs.map +7 -0
  18. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
  19. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +7 -0
  20. package/dist/lib/node-esm/chunk-KOSV2J5N.mjs +1704 -0
  21. package/dist/lib/node-esm/chunk-KOSV2J5N.mjs.map +7 -0
  22. package/dist/lib/node-esm/index.mjs +1151 -1175
  23. package/dist/lib/node-esm/index.mjs.map +4 -4
  24. package/dist/lib/node-esm/meta.json +1 -1
  25. package/dist/lib/node-esm/testing/index.mjs +37 -45
  26. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  27. package/dist/lib/node-esm/translations.mjs +37 -0
  28. package/dist/lib/node-esm/translations.mjs.map +7 -0
  29. package/dist/lib/node-esm/types/index.mjs +4 -3
  30. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -2
  31. package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
  32. package/dist/types/src/components/Table/ColumnSettings.d.ts +2 -2
  33. package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
  34. package/dist/types/src/components/Table/DynamicTable.d.ts +6 -7
  35. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  36. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +36 -3
  37. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
  38. package/dist/types/src/components/Table/Relations.stories.d.ts +112 -5
  39. package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
  40. package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
  41. package/dist/types/src/components/Table/Table.d.ts +21 -19
  42. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  43. package/dist/types/src/components/Table/Table.stories.d.ts +115 -5
  44. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  45. package/dist/types/src/components/Table/TableContent.d.ts +18 -0
  46. package/dist/types/src/components/Table/TableContent.d.ts.map +1 -0
  47. package/dist/types/src/components/Table/TableToolbar.d.ts +21 -0
  48. package/dist/types/src/components/Table/TableToolbar.d.ts.map +1 -0
  49. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
  50. package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts.map +1 -1
  51. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +12 -9
  52. package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
  53. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +7 -14
  54. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  55. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +38 -5
  56. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  57. package/dist/types/src/components/TableCellEditor/index.d.ts +1 -0
  58. package/dist/types/src/components/TableCellEditor/index.d.ts.map +1 -1
  59. package/dist/types/src/components/index.d.ts +0 -1
  60. package/dist/types/src/components/index.d.ts.map +1 -1
  61. package/dist/types/src/components/testing.d.ts +23 -16
  62. package/dist/types/src/components/testing.d.ts.map +1 -1
  63. package/dist/types/src/hooks/index.d.ts +2 -1
  64. package/dist/types/src/hooks/index.d.ts.map +1 -1
  65. package/dist/types/src/hooks/useAddRow.d.ts +6 -6
  66. package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
  67. package/dist/types/src/hooks/useProjectionModel.d.ts +6 -0
  68. package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
  69. package/dist/types/src/hooks/useTableModel.d.ts +9 -9
  70. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  71. package/dist/types/src/index.d.ts +0 -2
  72. package/dist/types/src/index.d.ts.map +1 -1
  73. package/dist/types/src/model/modal-controller.d.ts +5 -8
  74. package/dist/types/src/model/modal-controller.d.ts.map +1 -1
  75. package/dist/types/src/model/selection-model.d.ts +18 -9
  76. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  77. package/dist/types/src/model/table-model.d.ts +111 -30
  78. package/dist/types/src/model/table-model.d.ts.map +1 -1
  79. package/dist/types/src/model/table-presentation.d.ts +4 -2
  80. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  81. package/dist/types/src/playwright/TableManager.d.ts +2 -1
  82. package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
  83. package/dist/types/src/playwright/playwright.config.d.ts.map +1 -1
  84. package/dist/types/src/testing/useTestTableModel.d.ts +18 -34
  85. package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
  86. package/dist/types/src/translations.d.ts +21 -23
  87. package/dist/types/src/translations.d.ts.map +1 -1
  88. package/dist/types/src/types/Table.d.ts +32 -0
  89. package/dist/types/src/types/Table.d.ts.map +1 -0
  90. package/dist/types/src/types/index.d.ts +1 -1
  91. package/dist/types/src/types/index.d.ts.map +1 -1
  92. package/dist/types/src/util/dynamic-table.d.ts +14 -12
  93. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  94. package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
  95. package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
  96. package/dist/types/src/util/index.d.ts +2 -1
  97. package/dist/types/src/util/index.d.ts.map +1 -1
  98. package/dist/types/src/util/query-ast.d.ts +7 -0
  99. package/dist/types/src/util/query-ast.d.ts.map +1 -0
  100. package/dist/types/src/util/schema.d.ts +1 -1
  101. package/dist/types/src/util/schema.d.ts.map +1 -1
  102. package/dist/types/src/util/signals.d.ts.map +1 -1
  103. package/dist/types/src/util/sort.d.ts +12 -0
  104. package/dist/types/src/util/sort.d.ts.map +1 -0
  105. package/dist/types/src/util/table-buttons.d.ts +20 -20
  106. package/dist/types/src/util/table-buttons.d.ts.map +1 -1
  107. package/dist/types/src/util/table-controls.d.ts +4 -4
  108. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  109. package/dist/types/src/util/tag.d.ts.map +1 -1
  110. package/dist/types/tsconfig.tsbuildinfo +1 -1
  111. package/package.json +68 -77
  112. package/src/components/Table/ColumnActionsMenu.tsx +17 -12
  113. package/src/components/Table/ColumnSettings.tsx +8 -9
  114. package/src/components/Table/DynamicTable.stories.tsx +41 -38
  115. package/src/components/Table/DynamicTable.tsx +32 -36
  116. package/src/components/Table/Relations.stories.tsx +219 -61
  117. package/src/components/Table/RowActionsMenu.tsx +27 -17
  118. package/src/components/Table/Table.stories.tsx +217 -141
  119. package/src/components/Table/Table.tsx +46 -391
  120. package/src/components/Table/TableContent.tsx +426 -0
  121. package/src/components/Table/TableToolbar.tsx +111 -0
  122. package/src/components/TableCellEditor/CellValidationMessage.tsx +5 -6
  123. package/src/components/TableCellEditor/FormCellEditor.tsx +136 -41
  124. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +39 -73
  125. package/src/components/TableCellEditor/TableCellEditor.tsx +81 -223
  126. package/src/components/TableCellEditor/index.ts +1 -0
  127. package/src/components/index.ts +0 -1
  128. package/src/components/testing.ts +42 -21
  129. package/src/hooks/index.ts +2 -1
  130. package/src/hooks/useAddRow.ts +14 -14
  131. package/src/hooks/useProjectionModel.ts +38 -0
  132. package/src/hooks/useTableModel.ts +36 -34
  133. package/src/index.ts +0 -4
  134. package/src/model/modal-controller.ts +23 -40
  135. package/src/model/selection-model.ts +75 -44
  136. package/src/model/table-model.test.ts +86 -56
  137. package/src/model/table-model.ts +464 -182
  138. package/src/model/table-presentation.test.ts +64 -59
  139. package/src/model/table-presentation.ts +107 -83
  140. package/src/playwright/TableManager.ts +13 -6
  141. package/src/playwright/playwright.config.ts +1 -1
  142. package/src/playwright/smoke.spec.ts +92 -46
  143. package/src/testing/useTestTableModel.ts +51 -42
  144. package/src/translations.ts +20 -22
  145. package/src/types/Table.ts +80 -0
  146. package/src/types/index.ts +1 -1
  147. package/src/util/dynamic-table.test.ts +63 -0
  148. package/src/util/dynamic-table.ts +53 -39
  149. package/src/util/index.ts +2 -1
  150. package/src/util/query-ast.ts +22 -0
  151. package/src/util/schema.ts +4 -4
  152. package/src/util/sort.ts +90 -0
  153. package/src/util/table-buttons.ts +3 -3
  154. package/src/util/table-controls.ts +2 -2
  155. package/dist/lib/browser/chunk-L2LSB6KV.mjs +0 -18
  156. package/dist/lib/browser/chunk-L2LSB6KV.mjs.map +0 -7
  157. package/dist/lib/browser/chunk-ZAOSIG3L.mjs +0 -1814
  158. package/dist/lib/browser/chunk-ZAOSIG3L.mjs.map +0 -7
  159. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs +0 -20
  160. package/dist/lib/node-esm/chunk-NXZ4CJYX.mjs.map +0 -7
  161. package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs +0 -1815
  162. package/dist/lib/node-esm/chunk-ZOFFI3JJ.mjs.map +0 -7
  163. package/dist/types/src/components/Table/CreateRefPanel.d.ts +0 -9
  164. package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +0 -1
  165. package/dist/types/src/components/TableCellEditor/extension.d.ts +0 -13
  166. package/dist/types/src/components/TableCellEditor/extension.d.ts.map +0 -1
  167. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +0 -21
  168. package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +0 -1
  169. package/dist/types/src/components/TableToolbar/index.d.ts +0 -2
  170. package/dist/types/src/components/TableToolbar/index.d.ts.map +0 -1
  171. package/dist/types/src/model/table-sorting.d.ts +0 -53
  172. package/dist/types/src/model/table-sorting.d.ts.map +0 -1
  173. package/dist/types/src/types/schema.d.ts +0 -8
  174. package/dist/types/src/types/schema.d.ts.map +0 -1
  175. package/dist/types/src/util/table.d.ts +0 -12
  176. package/dist/types/src/util/table.d.ts.map +0 -1
  177. package/src/components/Table/CreateRefPanel.tsx +0 -86
  178. package/src/components/TableCellEditor/extension.ts +0 -61
  179. package/src/components/TableToolbar/TableToolbar.tsx +0 -102
  180. package/src/components/TableToolbar/index.ts +0 -5
  181. package/src/model/table-sorting.ts +0 -216
  182. package/src/types/schema.ts +0 -16
  183. package/src/util/table.ts +0 -53
@@ -2,47 +2,39 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
- import { Schema } from 'effect';
6
+ import * as Schema from 'effect/Schema';
9
7
  import React, { useCallback } from 'react';
8
+ import { expect, userEvent, waitFor, within } from 'storybook/test';
10
9
 
11
- import { Obj, Type } from '@dxos/echo';
12
- import {
13
- EchoObject,
14
- FormatAnnotation,
15
- FormatEnum,
16
- GeneratorAnnotation,
17
- LabelAnnotation,
18
- PropertyMetaAnnotationId,
19
- } from '@dxos/echo-schema';
10
+ import { DXN, Annotation, type Database, Format, Obj, type QueryAST, Ref, Type, View } from '@dxos/echo';
11
+ import { type Mutable, PropertyMetaAnnotationId } from '@dxos/echo/internal';
20
12
  import { invariant } from '@dxos/invariant';
21
- import { faker } from '@dxos/random';
13
+ import { random } from '@dxos/random';
22
14
  import { PublicKey } from '@dxos/react-client';
23
- import { type Space, live } from '@dxos/react-client/echo';
24
15
  import { withClientProvider } from '@dxos/react-client/testing';
16
+ import { Panel, ScrollArea } from '@dxos/react-ui';
25
17
  import { ViewEditor } from '@dxos/react-ui-form';
26
- import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
27
- import { DataType, getSchemaFromPropertyDefinitions } from '@dxos/schema';
28
- import { Testing, createObjectFactory } from '@dxos/schema/testing';
29
- import { withLayout, withTheme } from '@dxos/storybook-utils';
18
+ import { translations as formTranslations } from '@dxos/react-ui-form/translations';
19
+ import { JsonHighlighter } from '@dxos/react-ui-syntax-highlighter';
20
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
21
+ import { ViewModel, getSchemaFromPropertyDefinitions } from '@dxos/schema';
22
+ import { TestSchema, createObjectFactory } from '@dxos/schema/testing';
23
+ import { withRegistry } from '@dxos/storybook-utils';
30
24
 
31
- import { useTestTableModel } from '../../testing';
32
- import { translations } from '../../translations';
33
- import { TableView } from '../../types';
34
- import { createTable } from '../../util';
35
- import { TableToolbar } from '../TableToolbar';
25
+ import { translations } from '#translations';
36
26
 
37
- import { Table } from './Table';
27
+ import { useTestTableModel } from '../../testing';
28
+ import { Table } from '../../types';
29
+ import { Table as TableComponent } from './Table';
38
30
 
39
- const TestSchema = Schema.Struct({
31
+ const Example = Schema.Struct({
40
32
  // TODO(wittjosiah): Should be title. Currently name to work with default label.
41
33
  name: Schema.optional(Schema.String).annotations({ title: 'Title' }),
42
34
  urgent: Schema.optional(Schema.Boolean).annotations({ title: 'Urgent' }),
43
35
  status: Schema.optional(
44
36
  Schema.Literal('todo', 'in-progress', 'done')
45
- .pipe(FormatAnnotation.set(FormatEnum.SingleSelect))
37
+ .pipe(Format.FormatAnnotation.set(Format.TypeFormat.SingleSelect))
46
38
  .annotations({
47
39
  title: 'Status',
48
40
  [PropertyMetaAnnotationId]: {
@@ -57,33 +49,35 @@ const TestSchema = Schema.Struct({
57
49
  }),
58
50
  ),
59
51
  description: Schema.optional(Schema.String).annotations({ title: 'Description' }),
60
- parent: Schema.optional(Schema.suspend((): Type.Ref<TestSchema> => Type.Ref(TestSchema))).annotations({
52
+ parent: Schema.optional(Schema.suspend((): Ref.RefSchema<Example> => Ref.Ref(Example))).annotations({
61
53
  title: 'Parent',
62
54
  }),
63
55
  }).pipe(
64
- Type.Obj({ typename: `example.com/type/${PublicKey.random().truncate()}`, version: '0.1.0' }),
65
- LabelAnnotation.set(['name']),
56
+ Annotation.LabelAnnotation.set(['name']),
57
+ // NSID last segment must start with a letter (DXN spec), so prefix the random hex.
58
+ Type.makeObject(DXN.make(`com.example.type.example${PublicKey.random().truncate()}`, '0.1.0')),
66
59
  );
67
- interface TestSchema extends Schema.Schema.Type<typeof TestSchema> {}
60
+ interface Example extends Type.InstanceType<typeof Example> {}
68
61
 
69
62
  const StoryViewEditor = ({
70
63
  view,
71
64
  schema,
72
- space,
65
+ db,
73
66
  handleDeleteColumn,
74
67
  }: {
75
- view?: DataType.View;
76
- schema?: Schema.Schema.AnyNoContext;
77
- space?: Space;
68
+ view?: View.View;
69
+ schema?: Type.AnyEntity;
70
+ db?: Database.Database;
78
71
  handleDeleteColumn: (fieldId: string) => void;
79
72
  }) => {
80
- const handleTypenameChanged = useCallback(
81
- (typename: string) => {
73
+ const handleQueryChanged = useCallback(
74
+ (newQuery: QueryAST.Query) => {
82
75
  invariant(schema);
83
- invariant(Type.isMutable(schema));
84
- schema.updateTypename(typename);
76
+ invariant(Type.getDatabase(schema) != null);
85
77
  invariant(view);
86
- view.query.typename = typename;
78
+ Obj.update(view, (view) => {
79
+ view.query.ast = newQuery as Mutable<typeof newQuery>;
80
+ });
87
81
  },
88
82
  [schema, view],
89
83
  );
@@ -94,10 +88,10 @@ const StoryViewEditor = ({
94
88
 
95
89
  return (
96
90
  <ViewEditor
97
- registry={space?.db.schemaRegistry}
98
- schema={schema}
91
+ registry={db?.graph.registry}
92
+ type={schema}
99
93
  view={view}
100
- onTypenameChanged={handleTypenameChanged}
94
+ onQueryChanged={handleQueryChanged}
101
95
  onDelete={handleDeleteColumn}
102
96
  />
103
97
  );
@@ -108,122 +102,147 @@ const StoryViewEditor = ({
108
102
  //
109
103
 
110
104
  const DefaultStory = () => {
111
- const {
112
- space,
113
- schema,
114
- view,
115
- tableRef,
116
- model,
117
- presentation,
118
- client,
119
- handleInsertRow,
120
- handleSaveView,
121
- handleDeleteColumn,
122
- } = useTestTableModel();
123
-
124
- if (!schema || !view) {
105
+ const { db, schema, table, tableRef, model, presentation, handleInsertRow, handleSaveView, handleDeleteColumn } =
106
+ useTestTableModel();
107
+
108
+ const handleRowClick = useCallback(
109
+ (row: any) => {
110
+ if (model?.getDraftRowCount() === 0 && ['frozenRowsEnd', 'fixedEndStart', 'fixedEndEnd'].includes(row?.plane)) {
111
+ handleInsertRow();
112
+ }
113
+ },
114
+ [model, handleInsertRow],
115
+ );
116
+
117
+ if (!schema || !table?.view.target) {
125
118
  return <div />;
126
119
  }
127
120
 
128
121
  return (
129
122
  <div className='grow grid grid-cols-[1fr_350px]'>
130
- <div className='grid grid-rows-[min-content_1fr] min-bs-0 overflow-hidden'>
131
- <TableToolbar classNames='border-be border-subduedSeparator' onAdd={handleInsertRow} onSave={handleSaveView} />
132
- <Table.Root>
133
- <Table.Main
134
- ref={tableRef}
135
- model={model}
136
- presentation={presentation}
137
- schema={schema}
138
- client={client}
139
- ignoreAttention
140
- />
141
- </Table.Root>
142
- </div>
143
- <div className='flex flex-col h-full border-l border-separator overflow-y-auto'>
144
- <StoryViewEditor view={view} schema={schema} space={space} handleDeleteColumn={handleDeleteColumn} />
145
- <SyntaxHighlighter language='json' className='w-full text-xs'>
146
- {JSON.stringify({ view, schema }, null, 2)}
147
- </SyntaxHighlighter>
148
- </div>
123
+ <TableComponent.Root ref={tableRef}>
124
+ <Panel.Root>
125
+ <Panel.Toolbar asChild>
126
+ <TableComponent.Toolbar
127
+ classNames='border-b border-subdued-separator'
128
+ onAdd={handleInsertRow}
129
+ onSave={handleSaveView}
130
+ />
131
+ </Panel.Toolbar>
132
+ <Panel.Content asChild>
133
+ <TableComponent.Content
134
+ schema={schema}
135
+ model={model}
136
+ presentation={presentation}
137
+ onRowClick={handleRowClick}
138
+ ignoreAttention
139
+ />
140
+ </Panel.Content>
141
+ </Panel.Root>
142
+ </TableComponent.Root>
143
+ <ScrollArea.Root orientation='vertical' classNames='border-l border-separator'>
144
+ <ScrollArea.Viewport>
145
+ <StoryViewEditor view={table.view.target} schema={schema} db={db} handleDeleteColumn={handleDeleteColumn} />
146
+ <JsonHighlighter data={{ view: table.view.target, schema }} classNames='text-xs' />
147
+ </ScrollArea.Viewport>
148
+ </ScrollArea.Root>
149
149
  </div>
150
150
  );
151
151
  };
152
152
 
153
- type StoryProps = { rows?: number };
153
+ type DefaultStoryProps = { rows?: number };
154
154
 
155
155
  //
156
156
  // Story definitions.
157
157
  //
158
158
 
159
- // TODO(burdon): Need super simple story.
159
+ // TODO(burdon): Need simpler story.
160
160
 
161
- const meta: Meta<StoryProps> = {
161
+ const meta = {
162
162
  title: 'ui/react-ui-table/Table',
163
163
  render: DefaultStory,
164
- parameters: {
165
- translations,
166
- layout: 'fullscreen',
167
- controls: {
168
- disable: true,
169
- },
170
- },
171
164
  decorators: [
172
- withTheme,
173
- withLayout({ fullscreen: true }),
165
+ withTheme(),
166
+ withLayout({ layout: 'fullscreen' }),
167
+ withRegistry,
174
168
  withClientProvider({
175
- types: [DataType.View, TableView],
169
+ types: [View.View, Table.Table],
176
170
  createIdentity: true,
177
171
  createSpace: true,
178
- onSpaceCreated: async ({ client, space }) => {
179
- const [schema] = await space.db.schemaRegistry.register([TestSchema]);
180
- const { view } = await createTable({ client, space, typename: schema.typename });
181
- space.db.add(view);
172
+ onCreateSpace: async ({ space }) => {
173
+ const type = await space.db.addType(Example);
174
+ const { view, jsonSchema } = await ViewModel.makeFromDatabase({
175
+ db: space.db,
176
+ typename: Type.getTypename(type),
177
+ });
178
+ const table = Table.make({ view, jsonSchema });
179
+ Obj.update(view, (view) => {
180
+ view.projection.fields = [
181
+ view.projection.fields.find((field) => field.path === 'name')!,
182
+ ...view.projection.fields.filter((field) => field.path !== 'name'),
183
+ ];
184
+ });
185
+
186
+ space.db.add(table);
182
187
 
183
188
  Array.from({ length: 10 }).map(() => {
184
189
  return space.db.add(
185
- Obj.make(schema, {
186
- name: faker.lorem.sentence(),
187
- status: faker.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
188
- description: faker.lorem.paragraph(),
190
+ Obj.make(type, {
191
+ name: random.lorem.sentence(),
192
+ status: random.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
193
+ description: random.lorem.paragraph(),
189
194
  }),
190
195
  );
191
196
  });
192
197
  },
193
198
  }),
194
199
  ],
195
- };
200
+ parameters: {
201
+ layout: 'fullscreen',
202
+ controls: {
203
+ disable: true,
204
+ },
205
+ translations: [...translations, ...formTranslations],
206
+ },
207
+ } satisfies Meta<typeof TableComponent>;
196
208
 
197
209
  export default meta;
198
210
 
199
- export const Default = {};
211
+ type Story = StoryObj<typeof meta>;
212
+
213
+ export const Default: Story = {};
200
214
 
201
215
  export const StaticSchema: StoryObj = {
202
216
  render: DefaultStory,
203
- parameters: { translations },
204
217
  decorators: [
205
218
  withClientProvider({
206
- types: [DataType.View, TableView, Testing.Contact, Testing.Organization],
219
+ types: [View.View, Table.Table],
207
220
  createIdentity: true,
208
221
  createSpace: true,
209
- onSpaceCreated: async ({ client, space }) => {
210
- const { view } = await createTable({ client, space, typename: Testing.Contact.typename });
211
- space.db.add(view);
222
+ onCreateSpace: async ({ space }) => {
223
+ const { view, jsonSchema } = await ViewModel.makeFromDatabase({
224
+ db: space.db,
225
+ typename: Type.getTypename(TestSchema.Person),
226
+ });
227
+ const table = Table.make({ view, jsonSchema });
228
+ space.db.add(table);
212
229
 
213
- const factory = createObjectFactory(space.db, faker as any);
230
+ const factory = createObjectFactory(space.db, random as any);
214
231
  await factory([
215
- { type: Testing.Contact, count: 10 },
216
- // { type: Testing.Organization, count: 1 },
232
+ { type: TestSchema.Person, count: 10 },
233
+ // { type: TestSchema.Organization, count: 1 },
217
234
  ]);
218
235
  },
219
236
  }),
220
- withLayout({ fullscreen: true }),
221
- withTheme,
222
237
  ],
238
+ parameters: {
239
+ layout: 'fullscreen',
240
+ translations: [...translations, ...formTranslations],
241
+ },
223
242
  };
224
243
 
225
244
  const ContactWithArrayOfEmails = Schema.Struct({
226
- name: Schema.String.pipe(GeneratorAnnotation.set('person.fullName')),
245
+ name: Schema.String.pipe(Annotation.GeneratorAnnotation.set('person.fullName')),
227
246
  emails: Schema.optional(
228
247
  Schema.Array(
229
248
  Schema.Struct({
@@ -232,50 +251,104 @@ const ContactWithArrayOfEmails = Schema.Struct({
232
251
  }),
233
252
  ),
234
253
  ),
235
- }).pipe(
236
- EchoObject({
237
- typename: 'dxos.org/type/ContactWithArrayOfEmails',
238
- version: '0.1.0',
239
- }),
240
- );
254
+ }).pipe(Type.makeObject(DXN.make('org.dxos.type.contactWithArrayOfEmails', '0.1.0')));
241
255
 
242
256
  export const ArrayOfObjects: StoryObj = {
243
257
  render: DefaultStory,
244
- parameters: { translations },
245
258
  decorators: [
246
259
  withClientProvider({
247
- types: [DataType.View, TableView, Testing.Contact, Testing.Organization, ContactWithArrayOfEmails],
260
+ types: [View.View, Table.Table, TestSchema.Person, TestSchema.Organization, ContactWithArrayOfEmails],
248
261
  createIdentity: true,
249
262
  createSpace: true,
250
- onSpaceCreated: async ({ client, space }) => {
251
- const { view } = await createTable({ client, space, typename: ContactWithArrayOfEmails.typename });
252
- space.db.add(view);
263
+ onCreateSpace: async ({ space }) => {
264
+ const { view, jsonSchema } = await ViewModel.makeFromDatabase({
265
+ db: space.db,
266
+ typename: Type.getTypename(ContactWithArrayOfEmails),
267
+ });
268
+ const table = Table.make({ view, jsonSchema });
269
+ space.db.add(table);
253
270
 
254
- const factory = createObjectFactory(space.db, faker as any);
271
+ const factory = createObjectFactory(space.db, random as any);
255
272
  await factory([
256
- // { type: Testing.Contact, count: 10 },
257
- // { type: Testing.Organization, count: 1 },
273
+ // { type: TestSchema.Person, count: 10 },
274
+ // { type: TestSchema.Organization, count: 1 },
258
275
  { type: ContactWithArrayOfEmails, count: 10 },
259
276
  ]);
260
277
  },
261
278
  }),
262
- withLayout({ fullscreen: true }),
263
- withTheme,
264
279
  ],
280
+ parameters: {
281
+ layout: 'fullscreen',
282
+ translations,
283
+ },
265
284
  };
266
285
 
267
- export const Tags: Meta<StoryProps> = {
286
+ export const RequiredSchema: StoryObj = {
287
+ render: DefaultStory,
288
+ decorators: [
289
+ withClientProvider({
290
+ types: [View.View, Table.Table, TestSchema.Person],
291
+ createIdentity: true,
292
+ createSpace: true,
293
+ onCreateSpace: async ({ space }) => {
294
+ // TestSchema.Person has a required `name: Schema.String` field.
295
+ // No pre-populated rows so the add-row flow is the first interaction.
296
+ const { view, jsonSchema } = await ViewModel.makeFromDatabase({
297
+ db: space.db,
298
+ typename: Type.getTypename(TestSchema.Person),
299
+ });
300
+ const table = Table.make({ view, jsonSchema });
301
+ space.db.add(table);
302
+ },
303
+ }),
304
+ ],
305
+ parameters: {
306
+ layout: 'fullscreen',
307
+ translations: [...translations, ...formTranslations],
308
+ },
309
+ play: async ({ canvasElement }) => {
310
+ const canvas = within(canvasElement);
311
+
312
+ // 30 s covers ECHO client + space + table creation (the only inherently slow step).
313
+ const addRowButton = await canvas.findByTestId('table.toolbar.add-row', undefined, { timeout: 30_000 });
314
+
315
+ // Person.name is required, so db.add throws → useAddRow returns 'draft' →
316
+ // a draft row appears in frozenRowsEnd and focus is set to its first cell.
317
+ await userEvent.click(addRowButton);
318
+
319
+ const draftCell = await canvas.findByTestId('frozenRowsEnd.0.0');
320
+ await userEvent.click(draftCell);
321
+
322
+ // Open the editor with Enter rather than typing to open — the latter routes the first
323
+ // character into dx-grid's `initialContent` and races the editor mount. The empty value
324
+ // fails validation; the editor must stay open so the value below can be entered.
325
+ await userEvent.keyboard('{Enter}');
326
+ await canvas.findByTestId('grid.cell-editor');
327
+
328
+ // The editor is focused (autoFocus); type the required value and commit.
329
+ await userEvent.keyboard('Alice');
330
+ await userEvent.keyboard('{Enter}');
331
+
332
+ // The draft row is committed and appears in the grid with the typed value.
333
+ await waitFor(async () => {
334
+ const cell = canvas.getByTestId('grid.0.0');
335
+ const text = cell.querySelector('.dx-grid__cell__content')?.textContent;
336
+ await expect(text).toBe('Alice');
337
+ });
338
+ },
339
+ };
340
+
341
+ export const Tags: Meta<DefaultStoryProps> = {
268
342
  title: 'ui/react-ui-table/Table',
269
343
  render: DefaultStory,
270
- parameters: { translations },
271
344
  decorators: [
272
345
  withClientProvider({
273
- types: [DataType.View, TableView],
346
+ types: [View.View, Table.Table],
274
347
  createIdentity: true,
275
348
  createSpace: true,
276
- onSpaceCreated: async ({ client, space }) => {
349
+ onCreateSpace: async ({ space }) => {
277
350
  // Configure schema.
278
- const typename = 'example.com/SingleSelect';
351
+ const typename = 'com.example.type.singleSelect';
279
352
  const selectOptions = [
280
353
  { id: 'one', title: 'One', color: 'emerald' },
281
354
  { id: 'two', title: 'Two', color: 'blue' },
@@ -286,36 +359,39 @@ export const Tags: Meta<StoryProps> = {
286
359
 
287
360
  const selectOptionIds = selectOptions.map((o) => o.id);
288
361
 
289
- const schema = getSchemaFromPropertyDefinitions(typename, [
362
+ const type = getSchemaFromPropertyDefinitions(typename, [
290
363
  {
291
364
  name: 'single',
292
- format: FormatEnum.SingleSelect,
365
+ format: Format.TypeFormat.SingleSelect,
293
366
  config: { options: selectOptions },
294
367
  },
295
368
  {
296
369
  name: 'multiple',
297
- format: FormatEnum.MultiSelect,
370
+ format: Format.TypeFormat.MultiSelect,
298
371
  config: { options: selectOptions },
299
372
  },
300
373
  ]);
301
- const [storedSchema] = await space.db.schemaRegistry.register([schema]);
374
+ const storedType = await space.db.addType(type);
302
375
 
303
376
  // Initialize table.
304
- const { view } = await createTable({ client, space, typename });
305
- space.db.add(view);
377
+ const { view, jsonSchema } = await ViewModel.makeFromDatabase({ db: space.db, typename });
378
+ const table = Table.make({ view, jsonSchema });
379
+ space.db.add(table);
306
380
 
307
381
  // Populate.
308
382
  Array.from({ length: 10 }).map(() => {
309
383
  return space.db.add(
310
- live(storedSchema, {
311
- single: faker.helpers.arrayElement([...selectOptionIds, undefined]),
312
- multiple: faker.helpers.randomSubset(selectOptionIds),
384
+ Obj.make(Type.assertObject(storedType), {
385
+ single: random.helpers.arrayElement([...selectOptionIds, undefined]),
386
+ multiple: random.helpers.randomSubset(selectOptionIds),
313
387
  }),
314
388
  );
315
389
  });
316
390
  },
317
391
  }),
318
- withLayout({ fullscreen: true }),
319
- withTheme,
320
392
  ],
393
+ parameters: {
394
+ layout: 'fullscreen',
395
+ translations,
396
+ },
321
397
  };