@dxos/react-ui-form 0.8.4-main.fd6878d → 0.8.4-main.fffef41

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 (115) hide show
  1. package/dist/lib/browser/index.mjs +806 -572
  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 +806 -572
  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/ControlSection/ControlSection.d.ts +2 -2
  8. package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +1 -1
  9. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +80 -5
  10. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -1
  11. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +1 -2
  12. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  13. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +75 -5
  14. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  15. package/dist/types/src/components/Form/ArrayField.d.ts +2 -2
  16. package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -1
  17. package/dist/types/src/components/Form/Defaults.d.ts +5 -5
  18. package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
  19. package/dist/types/src/components/Form/Form.d.ts +6 -4
  20. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  21. package/dist/types/src/components/Form/Form.stories.d.ts +128 -32
  22. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  23. package/dist/types/src/components/Form/FormContent.d.ts +16 -10
  24. package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
  25. package/dist/types/src/components/Form/FormContext.d.ts +2 -2
  26. package/dist/types/src/components/Form/FormContext.d.ts.map +1 -1
  27. package/dist/types/src/components/Form/Input.d.ts +5 -2
  28. package/dist/types/src/components/Form/Input.d.ts.map +1 -1
  29. package/dist/types/src/components/Form/RefField.d.ts +14 -4
  30. package/dist/types/src/components/Form/RefField.d.ts.map +1 -1
  31. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +1 -1
  32. package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +1 -1
  33. package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +1 -1
  34. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +75 -4
  35. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -1
  36. package/dist/types/src/components/Form/custom/TupleInput.d.ts +1 -1
  37. package/dist/types/src/components/Form/factory.d.ts +1 -1
  38. package/dist/types/src/components/Form/factory.d.ts.map +1 -1
  39. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +41 -0
  40. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -0
  41. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +81 -0
  42. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -0
  43. package/dist/types/src/components/ObjectPicker/index.d.ts +2 -0
  44. package/dist/types/src/components/ObjectPicker/index.d.ts.map +1 -0
  45. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +20 -6
  46. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  47. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +79 -8
  48. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  49. package/dist/types/src/components/index.d.ts +1 -1
  50. package/dist/types/src/components/index.d.ts.map +1 -1
  51. package/dist/types/src/components/testing.d.ts +6 -3
  52. package/dist/types/src/components/testing.d.ts.map +1 -1
  53. package/dist/types/src/hooks/useForm.d.ts +2 -2
  54. package/dist/types/src/hooks/useForm.d.ts.map +1 -1
  55. package/dist/types/src/hooks/useQueryRefOptions.d.ts +9 -3
  56. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +1 -1
  57. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +1 -1
  58. package/dist/types/src/translations.d.ts +11 -7
  59. package/dist/types/src/translations.d.ts.map +1 -1
  60. package/dist/types/src/util/field.d.ts +1 -1
  61. package/dist/types/src/util/field.d.ts.map +1 -1
  62. package/dist/types/src/util/formatting.d.ts +1 -1
  63. package/dist/types/src/util/formatting.d.ts.map +1 -1
  64. package/dist/types/src/util/refs.d.ts +1 -1
  65. package/dist/types/src/util/refs.d.ts.map +1 -1
  66. package/dist/types/src/util/schema.d.ts +1 -1
  67. package/dist/types/src/util/schema.d.ts.map +1 -1
  68. package/dist/types/tsconfig.tsbuildinfo +1 -1
  69. package/package.json +41 -36
  70. package/src/components/ControlSection/ControlSection.stories.tsx +10 -14
  71. package/src/components/ControlSection/ControlSection.tsx +26 -12
  72. package/src/components/FieldEditor/FieldEditor.stories.tsx +10 -11
  73. package/src/components/FieldEditor/FieldEditor.tsx +12 -14
  74. package/src/components/Form/ArrayField.tsx +6 -6
  75. package/src/components/Form/Defaults.tsx +21 -20
  76. package/src/components/Form/Form.stories.tsx +132 -97
  77. package/src/components/Form/Form.tsx +27 -14
  78. package/src/components/Form/FormActions.tsx +2 -2
  79. package/src/components/Form/FormContent.tsx +166 -45
  80. package/src/components/Form/FormContext.tsx +3 -3
  81. package/src/components/Form/Input.tsx +5 -2
  82. package/src/components/Form/RefField.tsx +107 -38
  83. package/src/components/Form/custom/GeoPointInput.tsx +28 -20
  84. package/src/components/Form/custom/SelectOptionsInput.stories.tsx +9 -10
  85. package/src/components/Form/custom/SelectOptionsInput.tsx +10 -10
  86. package/src/components/Form/custom/TupleInput.tsx +2 -2
  87. package/src/components/Form/factory.tsx +1 -1
  88. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +207 -0
  89. package/src/components/ObjectPicker/ObjectPicker.tsx +161 -0
  90. package/src/components/ObjectPicker/index.ts +5 -0
  91. package/src/components/ViewEditor/ViewEditor.stories.tsx +123 -49
  92. package/src/components/ViewEditor/ViewEditor.test.tsx +8 -11
  93. package/src/components/ViewEditor/ViewEditor.tsx +281 -233
  94. package/src/components/index.ts +1 -2
  95. package/src/components/testing.tsx +15 -9
  96. package/src/hooks/useForm.ts +7 -4
  97. package/src/hooks/useQueryRefOptions.ts +15 -6
  98. package/src/hooks/useRefQueryLookupHandler.ts +3 -3
  99. package/src/translations.ts +13 -8
  100. package/src/util/field.ts +9 -19
  101. package/src/util/formatting.ts +1 -1
  102. package/src/util/refs.ts +7 -7
  103. package/src/util/schema.ts +2 -4
  104. package/dist/types/src/components/deprecated/Form.d.ts +0 -15
  105. package/dist/types/src/components/deprecated/Form.d.ts.map +0 -1
  106. package/dist/types/src/components/deprecated/Form.stories.d.ts +0 -13
  107. package/dist/types/src/components/deprecated/Form.stories.d.ts.map +0 -1
  108. package/dist/types/src/components/deprecated/FormInput.d.ts +0 -16
  109. package/dist/types/src/components/deprecated/FormInput.d.ts.map +0 -1
  110. package/dist/types/src/components/deprecated/index.d.ts +0 -2
  111. package/dist/types/src/components/deprecated/index.d.ts.map +0 -1
  112. package/src/components/deprecated/Form.stories.tsx +0 -63
  113. package/src/components/deprecated/Form.tsx +0 -119
  114. package/src/components/deprecated/FormInput.tsx +0 -71
  115. package/src/components/deprecated/index.ts +0 -5
@@ -2,94 +2,142 @@
2
2
  // Copyright 2024 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';
9
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
6
+ import * as Schema from 'effect/Schema';
7
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
10
8
 
11
- import { Obj, Type } from '@dxos/echo';
12
- import { type EchoSchema, Format, TypedObject, toJsonSchema } from '@dxos/echo-schema';
13
- import { Filter, useQuery, useSpace } from '@dxos/react-client/echo';
14
- import { withClientProvider } from '@dxos/react-client/testing';
9
+ import { DXN, Filter, Obj, Query, type QueryAST, Tag, Type } from '@dxos/echo';
10
+ import { type EchoSchema, Format } from '@dxos/echo/internal';
11
+ import { useQuery } from '@dxos/react-client/echo';
12
+ import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
15
13
  import { useAsyncEffect } from '@dxos/react-ui';
16
- import { DataType, ProjectionModel, createView } from '@dxos/schema';
17
- import { withLayout, withTheme } 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, Project } from '@dxos/types';
18
17
 
19
18
  import { translations } from '../../translations';
20
19
  import { TestLayout, TestPanel, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
21
20
 
22
21
  import { ViewEditor, type ViewEditorProps } from './ViewEditor';
23
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: Type.getTypename(Project.Project), label: 'Project' },
28
+ { value: Employer.Employer.typename, label: 'Employer' },
29
+ ];
30
+
24
31
  // Type definition for debug objects exposed to tests.
25
32
  export type ViewEditorDebugObjects = {
26
33
  schema: EchoSchema;
27
- view: DataType.View;
34
+ view: View.View;
28
35
  projection: ProjectionModel;
29
36
  };
30
37
 
31
- type StoryProps = Pick<ViewEditorProps, 'readonly'>;
38
+ type StoryProps = Pick<ViewEditorProps, 'readonly' | 'mode'>;
32
39
 
33
40
  const DefaultStory = (props: StoryProps) => {
34
- const space = useSpace();
41
+ const { space } = useClientProvider();
35
42
  const [schema, setSchema] = useState<EchoSchema>();
36
- const [view, setView] = useState<DataType.View>();
37
- const [projection, setProjection] = useState<ProjectionModel>();
43
+ const [view, setView] = useState<View.View>();
44
+ const projectionRef = useRef<ProjectionModel>(null);
45
+
46
+ const tags = useQuery(space, Filter.type(Tag.Tag));
47
+
38
48
  useAsyncEffect(async () => {
39
49
  if (space) {
40
- class TestSchema extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
50
+ const TestSchema = Schema.Struct({
41
51
  name: Schema.String,
42
52
  email: Format.Email,
43
53
  salary: Format.Currency(),
44
- }) {}
45
-
46
- const [schema] = await space.db.schemaRegistry.register([TestSchema]);
47
- const view = createView({
54
+ }).pipe(
55
+ Type.Obj({
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.Obj({
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({
48
74
  name: 'Test',
49
- typename: schema.typename,
50
- jsonSchema: toJsonSchema(TestSchema),
75
+ query: Query.select(Filter.type(TestSchema)),
76
+ jsonSchema: Type.toJsonSchema(TestSchema),
51
77
  presentation: Obj.make(Type.Expando, {}),
52
78
  });
53
- const projection = new ProjectionModel(schema.jsonSchema, view.projection);
54
79
 
55
- setSchema(schema);
80
+ setSchema(testSchema);
56
81
  setView(view);
57
- setProjection(projection);
58
82
  }
59
83
  }, [space]);
60
84
 
61
- const views = useQuery(space, Filter.type(DataType.View));
62
- const currentTypename = useMemo(() => view?.query?.typename, [view]);
63
- const updateViewTypename = useCallback(
64
- (newTypename: string) => {
65
- if (!schema) {
85
+ const updateViewQuery = useCallback(
86
+ async (newQuery: QueryAST.Query, target?: string) => {
87
+ if (!schema || !view || !space) {
66
88
  return;
67
89
  }
68
- const matchingViews = views.filter((view) => view.query.typename === currentTypename);
69
- for (const view of matchingViews) {
70
- view.query.typename = newTypename;
90
+
91
+ if (props.mode === 'query') {
92
+ const queue = target && DXN.tryParse(target) ? target : undefined;
93
+ const query = queue ? Query.fromAst(newQuery).options({ queues: [queue] }) : Query.fromAst(newQuery);
94
+ view.query.ast = query.ast;
95
+
96
+ const typename = getTypenameFromQuery(query.ast);
97
+ const [newSchema] = await space.db.schemaRegistry.query({ typename }).run();
98
+ if (!newSchema) {
99
+ return;
100
+ }
101
+
102
+ const newView = View.make({
103
+ query,
104
+ jsonSchema: newSchema.jsonSchema,
105
+ presentation: Obj.make(Type.Expando, {}),
106
+ });
107
+ view.projection = Obj.getSnapshot(newView).projection;
108
+ setSchema(() => newSchema);
109
+ } else {
110
+ console.log('updateViewQuery', { newQuery });
111
+ view.query.ast = newQuery;
112
+ schema.updateTypename(getTypenameFromQuery(newQuery));
71
113
  }
72
- schema.updateTypename(newTypename);
73
114
  },
74
- [views, schema],
115
+ [view, schema],
75
116
  );
76
117
 
77
- const handleDelete = useCallback((property: string) => projection?.deleteFieldProjection(property), [projection]);
118
+ const handleDelete = useCallback(
119
+ (property: string) => projectionRef.current?.deleteFieldProjection(property),
120
+ [projectionRef],
121
+ );
78
122
 
79
123
  // Expose objects on window for test access.
80
124
  useEffect(() => {
81
- if (typeof window !== 'undefined' && schema && view && projection) {
82
- (window as any)[VIEW_EDITOR_DEBUG_SYMBOL] = { schema, view, projection } satisfies ViewEditorDebugObjects;
125
+ if (typeof window !== 'undefined' && schema && view && projectionRef.current) {
126
+ (window as any)[VIEW_EDITOR_DEBUG_SYMBOL] = {
127
+ schema,
128
+ view,
129
+ projection: projectionRef.current,
130
+ } satisfies ViewEditorDebugObjects;
83
131
  }
84
- }, [schema, view, projection]);
132
+ }, [schema, view]);
85
133
 
86
134
  // NOTE(ZaymonFC): This looks awkward but it resolves an infinite parsing issue with sb.
87
135
  const json = useMemo(
88
- () => JSON.parse(JSON.stringify({ schema, view, projection })),
89
- [JSON.stringify(schema), JSON.stringify(view), JSON.stringify(projection)],
136
+ () => JSON.parse(JSON.stringify({ schema, view, projection: projectionRef.current })),
137
+ [JSON.stringify(schema), JSON.stringify(view)],
90
138
  );
91
139
 
92
- if (!schema || !view || !projection) {
140
+ if (!schema || !view) {
93
141
  return <div />;
94
142
  }
95
143
 
@@ -97,11 +145,15 @@ const DefaultStory = (props: StoryProps) => {
97
145
  <TestLayout json={json}>
98
146
  <TestPanel>
99
147
  <ViewEditor
148
+ ref={projectionRef}
100
149
  schema={schema}
101
150
  view={view}
102
151
  registry={space?.db.schemaRegistry}
152
+ mode={props.mode}
103
153
  readonly={props.readonly}
104
- onTypenameChanged={updateViewTypename}
154
+ types={types}
155
+ tags={tags}
156
+ onQueryChanged={updateViewQuery}
105
157
  onDelete={handleDelete}
106
158
  />
107
159
  </TestPanel>
@@ -109,19 +161,41 @@ const DefaultStory = (props: StoryProps) => {
109
161
  );
110
162
  };
111
163
 
112
- const meta: Meta<StoryProps> = {
164
+ const meta = {
113
165
  title: 'ui/react-ui-form/ViewEditor',
114
166
  render: DefaultStory,
115
- decorators: [withClientProvider({ createSpace: true }), withLayout({ fullscreen: true }), withTheme],
116
- parameters: { translations },
117
- };
167
+ decorators: [
168
+ withClientProvider({
169
+ createSpace: true,
170
+ types: [Tag.Tag],
171
+ onCreateSpace: ({ space }) => {
172
+ space.db.add(Tag.make({ label: 'Important' }));
173
+ space.db.add(Tag.make({ label: 'Investor' }));
174
+ space.db.add(Tag.make({ label: 'New' }));
175
+ },
176
+ }),
177
+ withTheme,
178
+ ],
179
+ parameters: {
180
+ layout: 'fullscreen',
181
+ translations,
182
+ },
183
+ } satisfies Meta<typeof DefaultStory>;
118
184
 
119
185
  export default meta;
120
186
 
121
- type Story = StoryObj<StoryProps>;
187
+ type Story = StoryObj<typeof meta>;
122
188
 
123
189
  export const Default: Story = {};
124
190
 
125
191
  export const Readonly: Story = {
126
- args: { readonly: true },
192
+ args: {
193
+ readonly: true,
194
+ },
195
+ };
196
+
197
+ export const QueryMode: Story = {
198
+ args: {
199
+ mode: 'query',
200
+ },
127
201
  };
@@ -6,8 +6,8 @@ import { composeStories } from '@storybook/react';
6
6
  import { cleanup, fireEvent, screen, waitFor } from '@testing-library/react';
7
7
  import { afterEach, describe, expect, test } from 'vitest';
8
8
 
9
- import { EchoSchema, isInstanceOf } from '@dxos/echo-schema';
10
- import { DataType, ProjectionModel } from '@dxos/schema';
9
+ import { EchoSchema, isInstanceOf } from '@dxos/echo/internal';
10
+ import { ProjectionModel, View } from '@dxos/schema';
11
11
 
12
12
  import { VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
13
13
 
@@ -20,7 +20,7 @@ const getViewEditorDebugObjects = (): ViewEditorDebugObjects => {
20
20
  const debugObjects = (window as any)[VIEW_EDITOR_DEBUG_SYMBOL] as ViewEditorDebugObjects;
21
21
  expect(debugObjects).toBeDefined();
22
22
  expect(debugObjects.schema).toBeInstanceOf(EchoSchema);
23
- expect(isInstanceOf(DataType.View, debugObjects.view)).toBeTruthy();
23
+ expect(isInstanceOf(View.View, debugObjects.view)).toBeTruthy();
24
24
  expect(debugObjects.projection).toBeInstanceOf(ProjectionModel);
25
25
  return debugObjects;
26
26
  };
@@ -134,7 +134,8 @@ describe('ViewEditor', () => {
134
134
  expect(addedPropertyProjection!.props.property).toBe('added_property');
135
135
  });
136
136
 
137
- test('delete property', async () => {
137
+ // TODO(mykola): Fix flaky test.
138
+ test('delete property', { retry: 2 }, async () => {
138
139
  await Default.run();
139
140
  await waitForViewEditor();
140
141
 
@@ -189,19 +190,15 @@ describe('ViewEditor', () => {
189
190
  // Access the live objects directly from window using symbol.
190
191
  const debugObjects = getViewEditorDebugObjects();
191
192
 
192
- // Check that hiddenFields is non-empty.
193
- expect(debugObjects.view.projection.hiddenFields).toBeDefined();
194
- expect(debugObjects.view.projection.hiddenFields!.length).toBeGreaterThan(0);
195
-
196
193
  // Click the show button
197
194
  fireEvent.click(screen.getByTestId('show-field-button'));
198
195
 
199
196
  // Wait for the data to update and check that hiddenFields is empty.
200
197
  await waitFor(() => {
201
- expect(debugObjects.view.projection.hiddenFields!.length).toBe(0);
198
+ expect(debugObjects.view.projection.fields.filter(({ visible }) => visible === false)!.length).toBe(0);
202
199
  });
203
200
 
204
201
  // Also verify that the field is back in the visible fields list.
205
- expect(debugObjects.view.projection.fields.length).toBeGreaterThan(0);
202
+ expect(debugObjects.view.projection.fields.filter(({ visible }) => visible !== false).length).toBeGreaterThan(0);
206
203
  });
207
- });
204
+ }, 30_000);