@dxos/react-ui-form 0.8.4-main.b97322e → 0.8.4-main.c4373fc

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