@dxos/react-ui-form 0.8.3 → 0.8.4-main.1068cf700f

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 (218) hide show
  1. package/dist/lib/browser/index.mjs +2267 -2088
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +2267 -2088
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +8 -8
  8. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  9. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +84 -7
  10. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  11. package/dist/types/src/components/Form/Form-old.stories.d.ts +131 -0
  12. package/dist/types/src/components/Form/Form-old.stories.d.ts.map +1 -0
  13. package/dist/types/src/components/Form/Form.d.ts +81 -25
  14. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  15. package/dist/types/src/components/Form/Form.stories.d.ts +127 -55
  16. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  17. package/dist/types/src/components/Form/FormField.d.ts +37 -0
  18. package/dist/types/src/components/Form/FormField.d.ts.map +1 -0
  19. package/dist/types/src/components/Form/FormFieldComponent.d.ts +78 -0
  20. package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -0
  21. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +9 -0
  22. package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -0
  23. package/dist/types/src/components/Form/FormFieldSet.d.ts +19 -0
  24. package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -0
  25. package/dist/types/src/components/Form/fields/ArrayField.d.ts +18 -0
  26. package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -0
  27. package/dist/types/src/components/Form/fields/BooleanField.d.ts +4 -0
  28. package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -0
  29. package/dist/types/src/components/Form/fields/GeoPointField.d.ts +5 -0
  30. package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -0
  31. package/dist/types/src/components/Form/fields/MarkdownField.d.ts +4 -0
  32. package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -0
  33. package/dist/types/src/components/Form/fields/NumberField.d.ts +4 -0
  34. package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -0
  35. package/dist/types/src/components/Form/fields/RefField.d.ts +14 -0
  36. package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -0
  37. package/dist/types/src/components/Form/fields/SelectField.d.ts +10 -0
  38. package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -0
  39. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +5 -0
  40. package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -0
  41. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +82 -0
  42. package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -0
  43. package/dist/types/src/components/Form/fields/TextAreaField.d.ts +4 -0
  44. package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -0
  45. package/dist/types/src/components/Form/fields/TextField.d.ts +4 -0
  46. package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -0
  47. package/dist/types/src/components/Form/fields/TupleField.d.ts +6 -0
  48. package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -0
  49. package/dist/types/src/components/Form/fields/index.d.ts +12 -0
  50. package/dist/types/src/components/Form/fields/index.d.ts.map +1 -0
  51. package/dist/types/src/components/Form/index.d.ts +2 -4
  52. package/dist/types/src/components/Form/index.d.ts.map +1 -1
  53. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +46 -0
  54. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -0
  55. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +85 -0
  56. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -0
  57. package/dist/types/src/components/ObjectPicker/index.d.ts +2 -0
  58. package/dist/types/src/components/ObjectPicker/index.d.ts.map +1 -0
  59. package/dist/types/src/components/Settings/Settings.d.ts +56 -0
  60. package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
  61. package/dist/types/src/components/Settings/Settings.stories.d.ts +81 -0
  62. package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
  63. package/dist/types/src/components/Settings/index.d.ts +2 -0
  64. package/dist/types/src/components/Settings/index.d.ts.map +1 -0
  65. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +21 -9
  66. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  67. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +84 -9
  68. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  69. package/dist/types/src/components/index.d.ts +2 -2
  70. package/dist/types/src/components/index.d.ts.map +1 -1
  71. package/dist/types/src/components/testing.d.ts +4 -3
  72. package/dist/types/src/components/testing.d.ts.map +1 -1
  73. package/dist/types/src/hooks/index.d.ts +2 -3
  74. package/dist/types/src/hooks/index.d.ts.map +1 -1
  75. package/dist/types/src/hooks/useFormHandler.d.ts +82 -0
  76. package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -0
  77. package/dist/types/src/hooks/useKeyHandler.d.ts +6 -0
  78. package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
  79. package/dist/types/src/index.d.ts +1 -1
  80. package/dist/types/src/index.d.ts.map +1 -1
  81. package/dist/types/src/translations.d.ts +66 -58
  82. package/dist/types/src/translations.d.ts.map +1 -1
  83. package/dist/types/src/util/echo.d.ts +6 -0
  84. package/dist/types/src/util/echo.d.ts.map +1 -0
  85. package/dist/types/src/util/field.d.ts +3 -3
  86. package/dist/types/src/util/field.d.ts.map +1 -1
  87. package/dist/types/src/util/formatting.d.ts +2 -2
  88. package/dist/types/src/util/formatting.d.ts.map +1 -1
  89. package/dist/types/src/util/index.d.ts +2 -1
  90. package/dist/types/src/util/index.d.ts.map +1 -1
  91. package/dist/types/src/util/properties.d.ts +7 -0
  92. package/dist/types/src/util/properties.d.ts.map +1 -0
  93. package/dist/types/src/util/refs.d.ts +2 -3
  94. package/dist/types/src/util/refs.d.ts.map +1 -1
  95. package/dist/types/tsconfig.tsbuildinfo +1 -1
  96. package/package.json +51 -42
  97. package/src/components/FieldEditor/FieldEditor.stories.tsx +54 -31
  98. package/src/components/FieldEditor/FieldEditor.test.tsx +14 -13
  99. package/src/components/FieldEditor/FieldEditor.tsx +87 -92
  100. package/src/components/Form/Form-old.stories.tsx +314 -0
  101. package/src/components/Form/Form.stories.tsx +162 -223
  102. package/src/components/Form/Form.tsx +329 -96
  103. package/src/components/Form/FormField.tsx +286 -0
  104. package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
  105. package/src/components/Form/FormFieldComponent.tsx +174 -0
  106. package/src/components/Form/FormFieldSet.tsx +127 -0
  107. package/src/components/Form/README.md +3 -0
  108. package/src/components/Form/fields/ArrayField.tsx +154 -0
  109. package/src/components/Form/fields/BooleanField.tsx +22 -0
  110. package/src/components/Form/fields/GeoPointField.tsx +120 -0
  111. package/src/components/Form/fields/MarkdownField.tsx +78 -0
  112. package/src/components/Form/fields/NumberField.tsx +39 -0
  113. package/src/components/Form/fields/RefField.tsx +189 -0
  114. package/src/components/Form/fields/SelectField.tsx +65 -0
  115. package/src/components/Form/fields/SelectOptionField.stories.tsx +64 -0
  116. package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +47 -42
  117. package/src/components/Form/fields/TextAreaField.tsx +38 -0
  118. package/src/components/Form/fields/TextField.tsx +38 -0
  119. package/src/components/Form/{custom/TupleInput.tsx → fields/TupleField.tsx} +16 -9
  120. package/src/components/Form/fields/index.ts +15 -0
  121. package/src/components/Form/index.ts +3 -4
  122. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +214 -0
  123. package/src/components/ObjectPicker/ObjectPicker.tsx +175 -0
  124. package/src/components/{ControlSection → ObjectPicker}/index.ts +1 -1
  125. package/src/components/Settings/Settings.stories.tsx +56 -0
  126. package/src/components/Settings/Settings.tsx +234 -0
  127. package/src/components/Settings/index.ts +5 -0
  128. package/src/components/ViewEditor/ViewEditor.stories.tsx +143 -61
  129. package/src/components/ViewEditor/ViewEditor.test.tsx +43 -43
  130. package/src/components/ViewEditor/ViewEditor.tsx +314 -216
  131. package/src/components/index.ts +2 -3
  132. package/src/components/testing.tsx +19 -11
  133. package/src/hooks/index.ts +2 -3
  134. package/src/hooks/useFormHandler.ts +362 -0
  135. package/src/hooks/useKeyHandler.tsx +37 -0
  136. package/src/index.ts +1 -1
  137. package/src/translations.ts +25 -14
  138. package/src/util/echo.ts +25 -0
  139. package/src/util/field.ts +33 -43
  140. package/src/util/formatting.ts +22 -22
  141. package/src/util/index.ts +2 -1
  142. package/src/util/properties.ts +18 -0
  143. package/src/util/refs.ts +12 -18
  144. package/dist/lib/node/index.cjs +0 -2416
  145. package/dist/lib/node/index.cjs.map +0 -7
  146. package/dist/lib/node/meta.json +0 -1
  147. package/dist/types/src/components/ControlSection/ControlSection.d.ts +0 -23
  148. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
  149. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +0 -8
  150. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
  151. package/dist/types/src/components/ControlSection/index.d.ts +0 -2
  152. package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
  153. package/dist/types/src/components/Form/ArrayField.d.ts +0 -16
  154. package/dist/types/src/components/Form/ArrayField.d.ts.map +0 -1
  155. package/dist/types/src/components/Form/Defaults.d.ts +0 -14
  156. package/dist/types/src/components/Form/Defaults.d.ts.map +0 -1
  157. package/dist/types/src/components/Form/FormActions.d.ts +0 -9
  158. package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
  159. package/dist/types/src/components/Form/FormContent.d.ts +0 -57
  160. package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
  161. package/dist/types/src/components/Form/FormContext.d.ts +0 -23
  162. package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
  163. package/dist/types/src/components/Form/Input.d.ts +0 -26
  164. package/dist/types/src/components/Form/Input.d.ts.map +0 -1
  165. package/dist/types/src/components/Form/RefField.d.ts +0 -12
  166. package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
  167. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
  168. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +0 -1
  169. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
  170. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
  171. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +0 -7
  172. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +0 -1
  173. package/dist/types/src/components/Form/custom/TupleInput.d.ts +0 -6
  174. package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +0 -1
  175. package/dist/types/src/components/Form/custom/index.d.ts +0 -4
  176. package/dist/types/src/components/Form/custom/index.d.ts.map +0 -1
  177. package/dist/types/src/components/Form/factory.d.ts +0 -8
  178. package/dist/types/src/components/Form/factory.d.ts.map +0 -1
  179. package/dist/types/src/components/ViewEditor/Test.stories.d.ts +0 -10
  180. package/dist/types/src/components/ViewEditor/Test.stories.d.ts.map +0 -1
  181. package/dist/types/src/components/deprecated/Form.d.ts +0 -15
  182. package/dist/types/src/components/deprecated/Form.d.ts.map +0 -1
  183. package/dist/types/src/components/deprecated/Form.stories.d.ts +0 -13
  184. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +0 -1
  185. package/dist/types/src/components/deprecated/FormInput.d.ts +0 -16
  186. package/dist/types/src/components/deprecated/FormInput.d.ts.map +0 -1
  187. package/dist/types/src/components/deprecated/index.d.ts +0 -2
  188. package/dist/types/src/components/deprecated/index.d.ts.map +0 -1
  189. package/dist/types/src/hooks/useForm.d.ts +0 -67
  190. package/dist/types/src/hooks/useForm.d.ts.map +0 -1
  191. package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -23
  192. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
  193. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +0 -8
  194. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +0 -1
  195. package/dist/types/src/util/schema.d.ts +0 -3
  196. package/dist/types/src/util/schema.d.ts.map +0 -1
  197. package/src/components/ControlSection/ControlSection.stories.tsx +0 -41
  198. package/src/components/ControlSection/ControlSection.tsx +0 -139
  199. package/src/components/Form/ArrayField.tsx +0 -124
  200. package/src/components/Form/Defaults.tsx +0 -203
  201. package/src/components/Form/FormActions.tsx +0 -53
  202. package/src/components/Form/FormContent.tsx +0 -240
  203. package/src/components/Form/FormContext.tsx +0 -110
  204. package/src/components/Form/Input.tsx +0 -51
  205. package/src/components/Form/RefField.tsx +0 -213
  206. package/src/components/Form/custom/GeoPointInput.tsx +0 -72
  207. package/src/components/Form/custom/SelectOptionsInput.stories.tsx +0 -61
  208. package/src/components/Form/custom/index.ts +0 -7
  209. package/src/components/Form/factory.tsx +0 -31
  210. package/src/components/ViewEditor/Test.stories.tsx +0 -45
  211. package/src/components/deprecated/Form.stories.tsx +0 -62
  212. package/src/components/deprecated/Form.tsx +0 -119
  213. package/src/components/deprecated/FormInput.tsx +0 -71
  214. package/src/components/deprecated/index.ts +0 -5
  215. package/src/hooks/useForm.ts +0 -291
  216. package/src/hooks/useQueryRefOptions.ts +0 -53
  217. package/src/hooks/useRefQueryLookupHandler.ts +0 -39
  218. package/src/util/schema.ts +0 -14
@@ -2,119 +2,201 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
- import { type Meta, type StoryObj } from '@storybook/react';
8
- import { Schema } from 'effect';
9
- import React, { useCallback, useMemo, useState, useEffect } from 'react';
10
-
11
- import { Format, type EchoSchema, toJsonSchema, TypedObject } from '@dxos/echo-schema';
12
- import { Filter, useQuery, useSpace } from '@dxos/react-client/echo';
13
- import { withClientProvider } from '@dxos/react-client/testing';
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import * as Schema from 'effect/Schema';
7
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
8
+
9
+ import { DXN, Filter, Obj, Query, type QueryAST, Tag, Type } from '@dxos/echo';
10
+ import { type EchoSchema, Format, type Mutable } from '@dxos/echo/internal';
11
+ import { useQuery } from '@dxos/react-client/echo';
12
+ import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
14
13
  import { useAsyncEffect } from '@dxos/react-ui';
15
- import { ViewProjection, ViewType, createView } from '@dxos/schema';
16
- import { withTheme, withLayout } from '@dxos/storybook-utils';
14
+ import { withTheme } from '@dxos/react-ui/testing';
15
+ import { type ProjectionModel, View, getTypenameFromQuery } from '@dxos/schema';
16
+ import { Employer, Organization, Person, Pipeline } from '@dxos/types';
17
+
18
+ import { translations } from '../../translations';
19
+ import { TestLayout, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
17
20
 
18
21
  import { ViewEditor, type ViewEditorProps } from './ViewEditor';
19
- import translations from '../../translations';
20
- import { TestLayout, TestPanel, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
22
+
23
+ const types = [
24
+ // TODO(burdon): Get label from annotation.
25
+ { value: Organization.Organization.typename, label: 'Organization' },
26
+ { value: Person.Person.typename, label: 'Person' },
27
+ { value: Pipeline.Pipeline.typename, label: 'Project' },
28
+ { value: Employer.Employer.typename, label: 'Employer' },
29
+ ];
21
30
 
22
31
  // Type definition for debug objects exposed to tests.
23
32
  export type ViewEditorDebugObjects = {
24
33
  schema: EchoSchema;
25
- view: ViewType;
26
- projection: ViewProjection;
34
+ view: View.View;
35
+ projection: ProjectionModel;
27
36
  };
28
37
 
29
- type StoryProps = Pick<ViewEditorProps, 'readonly'>;
38
+ type StoryProps = Pick<ViewEditorProps, 'readonly' | 'mode'>;
30
39
 
31
40
  const DefaultStory = (props: StoryProps) => {
32
- const space = useSpace();
41
+ const { space } = useClientStory();
33
42
  const [schema, setSchema] = useState<EchoSchema>();
34
- const [view, setView] = useState<ViewType>();
35
- const [projection, setProjection] = useState<ViewProjection>();
43
+ const [view, setView] = useState<View.View>();
44
+ const projectionRef = useRef<ProjectionModel>(null);
45
+
46
+ const tags = useQuery(space?.db, Filter.type(Tag.Tag));
47
+
36
48
  useAsyncEffect(async () => {
37
49
  if (space) {
38
- class TestSchema extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
50
+ const TestSchema = Schema.Struct({
39
51
  name: Schema.String,
40
52
  email: Format.Email,
41
53
  salary: Format.Currency(),
42
- }) {}
43
-
44
- const [schema] = await space.db.schemaRegistry.register([TestSchema]);
45
- const view = createView({ name: 'Test', typename: schema.typename, jsonSchema: toJsonSchema(TestSchema) });
46
- const projection = new ViewProjection(schema.jsonSchema, view);
47
-
48
- setSchema(schema);
54
+ }).pipe(
55
+ Type.object({
56
+ typename: 'example.com/type/Test',
57
+ version: '0.1.0',
58
+ }),
59
+ );
60
+
61
+ const AlternateSchema = Schema.Struct({
62
+ title: Schema.String,
63
+ description: Schema.String,
64
+ completed: Schema.Boolean,
65
+ }).pipe(
66
+ Type.object({
67
+ typename: 'example.com/type/Alternate',
68
+ version: '0.1.0',
69
+ }),
70
+ );
71
+
72
+ const [testSchema] = await space.db.schemaRegistry.register([TestSchema, AlternateSchema]);
73
+ const view = View.make({
74
+ name: 'Test',
75
+ query: Query.select(Filter.type(TestSchema)),
76
+ jsonSchema: Type.toJsonSchema(TestSchema),
77
+ });
78
+
79
+ setSchema(testSchema);
49
80
  setView(view);
50
- setProjection(projection);
51
81
  }
52
82
  }, [space]);
53
83
 
54
- const views = useQuery(space, Filter.type(ViewType));
55
- const currentTypename = useMemo(() => view?.query?.typename, [view]);
56
- const updateViewTypename = useCallback(
57
- (newTypename: string) => {
58
- if (!schema) {
84
+ const updateViewQuery = useCallback(
85
+ async (newQuery: QueryAST.Query, target?: string) => {
86
+ if (!schema || !view || !space) {
59
87
  return;
60
88
  }
61
- const matchingViews = views.filter((view) => view.query.typename === currentTypename);
62
- for (const view of matchingViews) {
63
- view.query.typename = newTypename;
89
+
90
+ if (props.mode === 'tag') {
91
+ const queue = target && DXN.tryParse(target) ? target : undefined;
92
+ const query = queue ? Query.fromAst(newQuery).options({ queues: [queue] }) : Query.fromAst(newQuery);
93
+ Obj.change(view, (v) => {
94
+ v.query.ast = query.ast as Mutable<typeof query.ast>;
95
+ });
96
+
97
+ const typename = getTypenameFromQuery(query.ast);
98
+ const [newSchema] = await space.db.schemaRegistry.query({ typename }).run();
99
+ if (!newSchema) {
100
+ return;
101
+ }
102
+
103
+ const newView = View.make({
104
+ query,
105
+ jsonSchema: newSchema.jsonSchema,
106
+ });
107
+ Obj.change(view, (v) => {
108
+ v.projection = Obj.getSnapshot(newView).projection as Mutable<typeof v.projection>;
109
+ });
110
+ setSchema(() => newSchema);
111
+ } else {
112
+ Obj.change(view, (v) => {
113
+ v.query.ast = newQuery as Mutable<typeof newQuery>;
114
+ });
115
+ schema.updateTypename(getTypenameFromQuery(newQuery));
64
116
  }
65
- schema.updateTypename(newTypename);
66
117
  },
67
- [views, schema],
118
+ [view, schema],
68
119
  );
69
120
 
70
- const handleDelete = useCallback((property: string) => projection?.deleteFieldProjection(property), [projection]);
121
+ const handleDelete = useCallback(
122
+ (property: string) => projectionRef.current?.deleteFieldProjection(property),
123
+ [projectionRef],
124
+ );
71
125
 
72
126
  // Expose objects on window for test access.
73
127
  useEffect(() => {
74
- if (typeof window !== 'undefined' && schema && view && projection) {
75
- (window as any)[VIEW_EDITOR_DEBUG_SYMBOL] = { schema, view, projection } satisfies ViewEditorDebugObjects;
128
+ if (typeof window !== 'undefined' && schema && view && projectionRef.current) {
129
+ (window as any)[VIEW_EDITOR_DEBUG_SYMBOL] = {
130
+ schema,
131
+ view,
132
+ projection: projectionRef.current,
133
+ } satisfies ViewEditorDebugObjects;
76
134
  }
77
- }, [schema, view, projection]);
135
+ }, [schema, view]);
78
136
 
79
137
  // NOTE(ZaymonFC): This looks awkward but it resolves an infinite parsing issue with sb.
80
138
  const json = useMemo(
81
- () => JSON.parse(JSON.stringify({ schema, view, projection })),
82
- [JSON.stringify(schema), JSON.stringify(view), JSON.stringify(projection)],
139
+ () => JSON.parse(JSON.stringify({ schema, view, projection: projectionRef.current })),
140
+ [JSON.stringify(schema), JSON.stringify(view)],
83
141
  );
84
142
 
85
- if (!schema || !view || !projection) {
143
+ if (!schema || !view) {
86
144
  return <div />;
87
145
  }
88
146
 
89
147
  return (
90
148
  <TestLayout json={json}>
91
- <TestPanel>
92
- <ViewEditor
93
- schema={schema}
94
- view={view}
95
- registry={space?.db.schemaRegistry}
96
- readonly={props.readonly}
97
- onTypenameChanged={updateViewTypename}
98
- onDelete={handleDelete}
99
- />
100
- </TestPanel>
149
+ <ViewEditor
150
+ ref={projectionRef}
151
+ schema={schema}
152
+ view={view}
153
+ registry={space?.db.schemaRegistry}
154
+ mode={props.mode}
155
+ readonly={props.readonly}
156
+ types={types}
157
+ tags={tags}
158
+ onQueryChanged={updateViewQuery}
159
+ onDelete={handleDelete}
160
+ />
101
161
  </TestLayout>
102
162
  );
103
163
  };
104
164
 
105
- const meta: Meta<StoryProps> = {
165
+ const meta = {
106
166
  title: 'ui/react-ui-form/ViewEditor',
107
167
  render: DefaultStory,
108
- decorators: [withClientProvider({ createSpace: true }), withLayout({ fullscreen: true }), withTheme],
109
- parameters: { translations },
110
- };
168
+ decorators: [
169
+ withClientProvider({
170
+ createSpace: true,
171
+ types: [Tag.Tag],
172
+ onCreateSpace: ({ space }) => {
173
+ space.db.add(Tag.make({ label: 'Important' }));
174
+ space.db.add(Tag.make({ label: 'Investor' }));
175
+ space.db.add(Tag.make({ label: 'New' }));
176
+ },
177
+ }),
178
+ withTheme(),
179
+ ],
180
+ parameters: {
181
+ layout: 'fullscreen',
182
+ translations,
183
+ },
184
+ } satisfies Meta<typeof DefaultStory>;
111
185
 
112
186
  export default meta;
113
187
 
114
- type Story = StoryObj<StoryProps>;
188
+ type Story = StoryObj<typeof meta>;
115
189
 
116
190
  export const Default: Story = {};
117
191
 
118
192
  export const Readonly: Story = {
119
- args: { readonly: true },
193
+ args: {
194
+ readonly: true,
195
+ },
196
+ };
197
+
198
+ export const TagQueryMode: Story = {
199
+ args: {
200
+ mode: 'tag',
201
+ },
120
202
  };
@@ -3,15 +3,16 @@
3
3
  //
4
4
 
5
5
  import { composeStories } from '@storybook/react';
6
- import { screen, cleanup, fireEvent, waitFor } from '@testing-library/react';
6
+ import { cleanup, fireEvent, screen, waitFor } from '@testing-library/react';
7
7
  import { afterEach, describe, expect, test } from 'vitest';
8
8
 
9
- import { EchoSchema } from '@dxos/echo-schema';
10
- import { ViewProjection, ViewType } from '@dxos/schema';
9
+ import { EchoSchema, isInstanceOf } from '@dxos/echo/internal';
10
+ import { ProjectionModel, View } from '@dxos/schema';
11
+
12
+ import { VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
11
13
 
12
14
  import * as stories from './ViewEditor.stories';
13
15
  import { type ViewEditorDebugObjects } from './ViewEditor.stories';
14
- import { VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
15
16
 
16
17
  const { Default } = composeStories(stories);
17
18
 
@@ -19,8 +20,8 @@ const getViewEditorDebugObjects = (): ViewEditorDebugObjects => {
19
20
  const debugObjects = (window as any)[VIEW_EDITOR_DEBUG_SYMBOL] as ViewEditorDebugObjects;
20
21
  expect(debugObjects).toBeDefined();
21
22
  expect(debugObjects.schema).toBeInstanceOf(EchoSchema);
22
- expect(debugObjects.view).toBeInstanceOf(ViewType);
23
- expect(debugObjects.projection).toBeInstanceOf(ViewProjection);
23
+ expect(isInstanceOf(View.View, debugObjects.view)).toBeTruthy();
24
+ expect(debugObjects.projection).toBeInstanceOf(ProjectionModel);
24
25
  return debugObjects;
25
26
  };
26
27
 
@@ -41,7 +42,7 @@ describe('ViewEditor', () => {
41
42
  test('renders view editor', async () => {
42
43
  await Default.run();
43
44
  await waitForViewEditor();
44
- expect(screen.getByText('View')).toBeInTheDocument();
45
+ expect(screen.getByText('Record type')).toBeInTheDocument();
45
46
  });
46
47
 
47
48
  test('change property name', async () => {
@@ -53,12 +54,12 @@ describe('ViewEditor', () => {
53
54
  const nameField = screen.getByText('name');
54
55
  fireEvent.click(nameField);
55
56
 
56
- const fieldInput = screen.getByPlaceholderText('Field name.');
57
+ const fieldInput = screen.getByPlaceholderText('Property name');
57
58
  fireEvent.change(fieldInput, { target: { value: 'new_property' } });
58
59
 
59
60
  fireEvent.click(screen.getByTestId('save-button'));
60
61
 
61
- // Wait for the changes to propagate to both UI and schema
62
+ // Wait for the changes to propagate to both UI and schema.
62
63
  await waitFor(() => {
63
64
  expect(screen.getByText('new_property')).toBeInTheDocument();
64
65
  });
@@ -66,18 +67,18 @@ describe('ViewEditor', () => {
66
67
  // Access the live objects directly from window using symbol.
67
68
  const debugObjects = getViewEditorDebugObjects();
68
69
 
69
- // Check schema contains new_property
70
+ // Check schema contains new_property.
70
71
  const schemaProperties = debugObjects.schema.jsonSchema.properties;
71
72
  expect(schemaProperties).toBeDefined();
72
73
  expect(schemaProperties!.new_property).toBeDefined();
73
74
  expect(schemaProperties!.name).toBeUndefined();
74
75
 
75
- // Check view contains new_property field
76
- const newPropertyField = debugObjects.view.fields.find((field: any) => field.path === 'new_property');
76
+ // Check view contains new_property field.
77
+ const newPropertyField = debugObjects.view.projection.fields.find((field: any) => field.path === 'new_property');
77
78
  expect(newPropertyField).toBeDefined();
78
79
  expect(newPropertyField!.path).toBe('new_property');
79
80
 
80
- // Check projection contains new_property
81
+ // Check projection contains new_property.
81
82
  const fieldProjections = debugObjects.projection.getFieldProjections();
82
83
  const newPropertyProjection = fieldProjections.find((proj: any) => proj.field.path === 'new_property');
83
84
  expect(newPropertyProjection).toBeDefined();
@@ -88,25 +89,25 @@ describe('ViewEditor', () => {
88
89
  await Default.run();
89
90
  await waitForViewEditor();
90
91
 
91
- // Click the add property button
92
+ // Click the add property button. The new field is auto-expanded by handleAdd.
92
93
  const addButton = screen.getByText('Add property');
93
94
  fireEvent.click(addButton);
94
95
 
95
- // Fill out the property field
96
- const fieldInput = screen.getByPlaceholderText('Field name.');
96
+ // Fill out the property field.
97
+ const fieldInput = screen.getByPlaceholderText('Property name');
97
98
  fireEvent.change(fieldInput, { target: { value: 'added_property' } });
98
99
 
99
- // Click the format combo box and select the first option
100
+ // Click the format combo box and select the first option.
100
101
  const formatCombo = screen.getByRole('combobox');
101
102
  fireEvent.click(formatCombo);
102
103
 
103
104
  const firstOption = screen.getAllByRole('option')[0];
104
105
  fireEvent.click(firstOption);
105
106
 
106
- // Save the changes
107
+ // Save the changes.
107
108
  fireEvent.click(screen.getByTestId('save-button'));
108
109
 
109
- // Wait for the changes to propagate
110
+ // Wait for the changes to propagate.
110
111
  await waitFor(() => {
111
112
  expect(screen.getByText('added_property')).toBeInTheDocument();
112
113
  });
@@ -114,40 +115,43 @@ describe('ViewEditor', () => {
114
115
  // Access the live objects directly from window using symbol.
115
116
  const debugObjects = getViewEditorDebugObjects();
116
117
 
117
- // Check schema contains added_property
118
+ // Check schema contains added_property.
118
119
  const schemaProperties = debugObjects.schema.jsonSchema.properties;
119
120
  expect(schemaProperties).toBeDefined();
120
121
  expect(schemaProperties!.added_property).toBeDefined();
121
122
 
122
- // Check view contains added_property field
123
- const addedPropertyField = debugObjects.view.fields.find((field: any) => field.path === 'added_property');
123
+ // Check view contains added_property field.
124
+ const addedPropertyField = debugObjects.view.projection.fields.find(
125
+ (field: any) => field.path === 'added_property',
126
+ );
124
127
  expect(addedPropertyField).toBeDefined();
125
128
  expect(addedPropertyField!.path).toBe('added_property');
126
129
 
127
- // Check projection contains added_property
130
+ // Check projection contains added_property.
128
131
  const fieldProjections = debugObjects.projection.getFieldProjections();
129
132
  const addedPropertyProjection = fieldProjections.find((proj: any) => proj.field.path === 'added_property');
130
133
  expect(addedPropertyProjection).toBeDefined();
131
134
  expect(addedPropertyProjection!.props.property).toBe('added_property');
132
135
  });
133
136
 
134
- test('delete property', async () => {
137
+ // TODO(mykola): Fix flaky test.
138
+ test('delete property', { retry: 2 }, async () => {
135
139
  await Default.run();
136
140
  await waitForViewEditor();
137
141
 
138
- // Find the delete button for the 'name' property
142
+ // Find the delete button for the 'name' property.
139
143
  const nameProperty = screen.getByText('name');
140
144
  const propertyRow = nameProperty.closest('[role="listitem"]');
141
- const deleteButton = propertyRow?.querySelector('button:last-child');
145
+ const deleteButton = propertyRow?.querySelector('[data-testid="field.delete"]');
142
146
 
143
147
  if (!deleteButton) {
144
148
  throw new Error('Delete button not found');
145
149
  }
146
150
 
147
- // Click the delete button
151
+ // Click the delete button.
148
152
  fireEvent.click(deleteButton);
149
153
 
150
- // Wait for the changes to propagate
154
+ // Wait for the changes to propagate.
151
155
  await waitFor(() => {
152
156
  expect(screen.queryByText('name')).not.toBeInTheDocument();
153
157
  });
@@ -155,16 +159,16 @@ describe('ViewEditor', () => {
155
159
  // Access the live objects directly from window using symbol.
156
160
  const debugObjects = getViewEditorDebugObjects();
157
161
 
158
- // Check schema no longer contains the name property
162
+ // Check schema no longer contains the name property.
159
163
  const schemaProperties = debugObjects.schema.jsonSchema.properties;
160
164
  expect(schemaProperties).toBeDefined();
161
165
  expect(schemaProperties!.name).toBeUndefined();
162
166
 
163
- // Check view no longer contains the name field
164
- const nameField = debugObjects.view.fields.find((field: any) => field.path === 'name');
167
+ // Check view no longer contains the name field.
168
+ const nameField = debugObjects.view.projection.fields.find((field: any) => field.path === 'name');
165
169
  expect(nameField).toBeUndefined();
166
170
 
167
- // Check projection no longer contains the name property
171
+ // Check projection no longer contains the name property.
168
172
  const fieldProjections = debugObjects.projection.getFieldProjections();
169
173
  const nameProjection = fieldProjections.find((proj: any) => proj.field.path === 'name');
170
174
  expect(nameProjection).toBeUndefined();
@@ -174,11 +178,11 @@ describe('ViewEditor', () => {
174
178
  await Default.run();
175
179
  await waitForViewEditor();
176
180
 
177
- // Click the first hide button
181
+ // Click the first hide button.
178
182
  const hideButtons = screen.getAllByTestId('hide-field-button');
179
183
  fireEvent.click(hideButtons[0]);
180
184
 
181
- // Check that hiddenFields is non-empty
185
+ // Check that hiddenFields is non-empty.
182
186
  await waitFor(() => {
183
187
  expect(screen.getByTestId('show-field-button')).toBeInTheDocument();
184
188
  });
@@ -186,19 +190,15 @@ describe('ViewEditor', () => {
186
190
  // Access the live objects directly from window using symbol.
187
191
  const debugObjects = getViewEditorDebugObjects();
188
192
 
189
- // Check that hiddenFields is non-empty
190
- expect(debugObjects.view.hiddenFields).toBeDefined();
191
- expect(debugObjects.view.hiddenFields!.length).toBeGreaterThan(0);
192
-
193
193
  // Click the show button
194
194
  fireEvent.click(screen.getByTestId('show-field-button'));
195
195
 
196
- // Wait for the data to update and check that hiddenFields is empty
196
+ // Wait for the data to update and check that hiddenFields is empty.
197
197
  await waitFor(() => {
198
- expect(debugObjects.view.hiddenFields!.length).toBe(0);
198
+ expect(debugObjects.view.projection.fields.filter(({ visible }) => visible === false)!.length).toBe(0);
199
199
  });
200
200
 
201
- // Also verify that the field is back in the visible fields list
202
- expect(debugObjects.view.fields.length).toBeGreaterThan(0);
201
+ // Also verify that the field is back in the visible fields list.
202
+ expect(debugObjects.view.projection.fields.filter(({ visible }) => visible !== false).length).toBeGreaterThan(0);
203
203
  });
204
- });
204
+ }, 30_000);