@dxos/react-ui-form 0.8.4-main.dedc0f3 → 0.8.4-main.e8ec1fe

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 (89) hide show
  1. package/dist/lib/browser/index.mjs +480 -300
  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 +480 -300
  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.stories.d.ts +5 -5
  8. package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -1
  9. package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
  10. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +5 -5
  11. package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
  12. package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -1
  13. package/dist/types/src/components/Form/Form.d.ts +4 -3
  14. package/dist/types/src/components/Form/Form.d.ts.map +1 -1
  15. package/dist/types/src/components/Form/Form.stories.d.ts +15 -15
  16. package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
  17. package/dist/types/src/components/Form/FormContent.d.ts +5 -5
  18. package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
  19. package/dist/types/src/components/Form/FormContext.d.ts +2 -2
  20. package/dist/types/src/components/Form/FormContext.d.ts.map +1 -1
  21. package/dist/types/src/components/Form/Input.d.ts +1 -1
  22. package/dist/types/src/components/Form/Input.d.ts.map +1 -1
  23. package/dist/types/src/components/Form/RefField.d.ts +10 -6
  24. package/dist/types/src/components/Form/RefField.d.ts.map +1 -1
  25. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +5 -5
  26. package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -1
  27. package/dist/types/src/components/Form/factory.d.ts +1 -1
  28. package/dist/types/src/components/Form/factory.d.ts.map +1 -1
  29. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +41 -0
  30. package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -0
  31. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +81 -0
  32. package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -0
  33. package/dist/types/src/components/ObjectPicker/index.d.ts +2 -0
  34. package/dist/types/src/components/ObjectPicker/index.d.ts.map +1 -0
  35. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +20 -7
  36. package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
  37. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +10 -10
  38. package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
  39. package/dist/types/src/components/index.d.ts +1 -0
  40. package/dist/types/src/components/index.d.ts.map +1 -1
  41. package/dist/types/src/hooks/useForm.d.ts +2 -2
  42. package/dist/types/src/hooks/useForm.d.ts.map +1 -1
  43. package/dist/types/src/hooks/useQueryRefOptions.d.ts +9 -3
  44. package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +1 -1
  45. package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +1 -1
  46. package/dist/types/src/translations.d.ts +4 -4
  47. package/dist/types/src/util/field.d.ts +1 -1
  48. package/dist/types/src/util/field.d.ts.map +1 -1
  49. package/dist/types/src/util/formatting.d.ts +1 -1
  50. package/dist/types/src/util/formatting.d.ts.map +1 -1
  51. package/dist/types/src/util/refs.d.ts +1 -1
  52. package/dist/types/src/util/refs.d.ts.map +1 -1
  53. package/dist/types/src/util/schema.d.ts +1 -1
  54. package/dist/types/src/util/schema.d.ts.map +1 -1
  55. package/dist/types/tsconfig.tsbuildinfo +1 -1
  56. package/package.json +40 -39
  57. package/src/components/ControlSection/ControlSection.stories.tsx +4 -5
  58. package/src/components/ControlSection/ControlSection.tsx +1 -1
  59. package/src/components/FieldEditor/FieldEditor.stories.tsx +6 -5
  60. package/src/components/FieldEditor/FieldEditor.tsx +5 -2
  61. package/src/components/Form/ArrayField.tsx +4 -3
  62. package/src/components/Form/Defaults.tsx +1 -1
  63. package/src/components/Form/Form.stories.tsx +35 -35
  64. package/src/components/Form/Form.tsx +16 -6
  65. package/src/components/Form/FormActions.tsx +2 -2
  66. package/src/components/Form/FormContent.tsx +82 -22
  67. package/src/components/Form/FormContext.tsx +3 -3
  68. package/src/components/Form/Input.tsx +1 -1
  69. package/src/components/Form/RefField.tsx +48 -64
  70. package/src/components/Form/custom/GeoPointInput.tsx +1 -1
  71. package/src/components/Form/custom/SelectOptionsInput.stories.tsx +5 -6
  72. package/src/components/Form/custom/SelectOptionsInput.tsx +2 -2
  73. package/src/components/Form/factory.tsx +1 -1
  74. package/src/components/ObjectPicker/ObjectPicker.stories.tsx +207 -0
  75. package/src/components/ObjectPicker/ObjectPicker.tsx +161 -0
  76. package/src/components/ObjectPicker/index.ts +5 -0
  77. package/src/components/ViewEditor/ViewEditor.stories.tsx +107 -59
  78. package/src/components/ViewEditor/ViewEditor.test.tsx +6 -5
  79. package/src/components/ViewEditor/ViewEditor.tsx +275 -231
  80. package/src/components/index.ts +1 -0
  81. package/src/components/testing.tsx +2 -2
  82. package/src/hooks/useForm.ts +7 -4
  83. package/src/hooks/useQueryRefOptions.ts +15 -6
  84. package/src/hooks/useRefQueryLookupHandler.ts +3 -3
  85. package/src/translations.ts +4 -4
  86. package/src/util/field.ts +1 -1
  87. package/src/util/formatting.ts +1 -1
  88. package/src/util/refs.ts +7 -7
  89. package/src/util/schema.ts +2 -4
@@ -2,107 +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 { Filter, Obj, Query, Type } from '@dxos/echo';
12
- import { type EchoSchema, Format, TypedObject, toJsonSchema } from '@dxos/echo-schema';
13
- import { 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 { QueryParser, createFilter } from '@dxos/react-ui-components';
17
- import { type DataType, ProjectionModel, createView, typenameFromQuery } from '@dxos/schema';
18
- 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';
19
17
 
20
18
  import { translations } from '../../translations';
21
19
  import { TestLayout, TestPanel, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
22
20
 
23
21
  import { ViewEditor, type ViewEditorProps } from './ViewEditor';
24
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
+
25
31
  // Type definition for debug objects exposed to tests.
26
32
  export type ViewEditorDebugObjects = {
27
33
  schema: EchoSchema;
28
- view: DataType.View;
34
+ view: View.View;
29
35
  projection: ProjectionModel;
30
36
  };
31
37
 
32
- type StoryProps = Pick<ViewEditorProps, 'readonly' | 'kind'>;
38
+ type StoryProps = Pick<ViewEditorProps, 'readonly' | 'mode'>;
33
39
 
34
40
  const DefaultStory = (props: StoryProps) => {
35
- const space = useSpace();
41
+ const { space } = useClientProvider();
36
42
  const [schema, setSchema] = useState<EchoSchema>();
37
- const [view, setView] = useState<DataType.View>();
38
- 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
+
39
48
  useAsyncEffect(async () => {
40
49
  if (space) {
41
- class TestSchema extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
50
+ const TestSchema = Schema.Struct({
42
51
  name: Schema.String,
43
52
  email: Format.Email,
44
53
  salary: Format.Currency(),
45
- }) {}
46
-
47
- const [schema] = await space.db.schemaRegistry.register([TestSchema]);
48
- 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({
49
74
  name: 'Test',
50
- typename: schema.typename,
51
- jsonSchema: toJsonSchema(TestSchema),
75
+ query: Query.select(Filter.type(TestSchema)),
76
+ jsonSchema: Type.toJsonSchema(TestSchema),
52
77
  presentation: Obj.make(Type.Expando, {}),
53
78
  });
54
- const projection = new ProjectionModel(schema.jsonSchema, view.projection);
55
79
 
56
- setSchema(schema);
80
+ setSchema(testSchema);
57
81
  setView(view);
58
- setProjection(projection);
59
82
  }
60
83
  }, [space]);
61
84
 
62
85
  const updateViewQuery = useCallback(
63
- (newQueryString: string) => {
64
- if (!schema || !view) {
86
+ async (newQuery: QueryAST.Query, target?: string) => {
87
+ if (!schema || !view || !space) {
65
88
  return;
66
89
  }
67
90
 
68
- if (props.kind === 'advanced') {
69
- try {
70
- const parser = new QueryParser(newQueryString);
71
- // TODO(wittjosiah): When this fails it should show validation errors in the UI.
72
- const newQuery = Query.select(createFilter(parser.parse()));
73
- view.query = newQuery.ast;
74
-
75
- const typename = typenameFromQuery(newQuery.ast);
76
- if (typename) {
77
- schema.updateTypename(typename);
78
- }
79
- } catch {}
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);
80
109
  } else {
81
- const typename = newQueryString;
82
- const newQuery = Query.select(Filter.typename(typename));
83
- view.query = newQuery.ast;
84
- schema.updateTypename(typename);
110
+ console.log('updateViewQuery', { newQuery });
111
+ view.query.ast = newQuery;
112
+ schema.updateTypename(getTypenameFromQuery(newQuery));
85
113
  }
86
114
  },
87
115
  [view, schema],
88
116
  );
89
117
 
90
- const handleDelete = useCallback((property: string) => projection?.deleteFieldProjection(property), [projection]);
118
+ const handleDelete = useCallback(
119
+ (property: string) => projectionRef.current?.deleteFieldProjection(property),
120
+ [projectionRef],
121
+ );
91
122
 
92
123
  // Expose objects on window for test access.
93
124
  useEffect(() => {
94
- if (typeof window !== 'undefined' && schema && view && projection) {
95
- (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;
96
131
  }
97
- }, [schema, view, projection]);
132
+ }, [schema, view]);
98
133
 
99
134
  // NOTE(ZaymonFC): This looks awkward but it resolves an infinite parsing issue with sb.
100
135
  const json = useMemo(
101
- () => JSON.parse(JSON.stringify({ schema, view, projection })),
102
- [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)],
103
138
  );
104
139
 
105
- if (!schema || !view || !projection) {
140
+ if (!schema || !view) {
106
141
  return <div />;
107
142
  }
108
143
 
@@ -110,11 +145,14 @@ const DefaultStory = (props: StoryProps) => {
110
145
  <TestLayout json={json}>
111
146
  <TestPanel>
112
147
  <ViewEditor
148
+ ref={projectionRef}
113
149
  schema={schema}
114
150
  view={view}
115
151
  registry={space?.db.schemaRegistry}
116
- kind={props.kind}
152
+ mode={props.mode}
117
153
  readonly={props.readonly}
154
+ types={types}
155
+ tags={tags}
118
156
  onQueryChanged={updateViewQuery}
119
157
  onDelete={handleDelete}
120
158
  />
@@ -129,13 +167,19 @@ const meta = {
129
167
  decorators: [
130
168
  withClientProvider({
131
169
  createSpace: true,
132
- }),
133
- withLayout({
134
- fullscreen: 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
+ },
135
176
  }),
136
177
  withTheme,
137
178
  ],
138
- parameters: { translations },
179
+ parameters: {
180
+ layout: 'fullscreen',
181
+ translations,
182
+ },
139
183
  } satisfies Meta<typeof DefaultStory>;
140
184
 
141
185
  export default meta;
@@ -145,9 +189,13 @@ type Story = StoryObj<typeof meta>;
145
189
  export const Default: Story = {};
146
190
 
147
191
  export const Readonly: Story = {
148
- args: { readonly: true },
192
+ args: {
193
+ readonly: true,
194
+ },
149
195
  };
150
196
 
151
- export const Advanced: Story = {
152
- args: { kind: 'advanced' },
197
+ export const QueryMode: Story = {
198
+ args: {
199
+ mode: 'query',
200
+ },
153
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
 
@@ -200,4 +201,4 @@ describe('ViewEditor', () => {
200
201
  // Also verify that the field is back in the visible fields list.
201
202
  expect(debugObjects.view.projection.fields.filter(({ visible }) => visible !== false).length).toBeGreaterThan(0);
202
203
  });
203
- });
204
+ }, 30_000);