@dxos/react-ui-form 0.8.4-main.c1de068 → 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,71 @@
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';
6
+ import * as Schema from 'effect/Schema';
9
7
  import React, { useCallback, useState } from 'react';
10
8
 
11
9
  import { ContactType } from '@dxos/client/testing';
12
- import { type BaseObject, Expando, Format, getObjectDXN, Ref, type TypeAnnotation } from '@dxos/echo-schema';
13
- import { live } from '@dxos/live-object';
14
- import { withSurfaceVariantsLayout } from '@dxos/react-ui/testing';
10
+ import { type Type } from '@dxos/echo';
11
+ import { type BaseObject, Expando, Format, Ref, type TypeAnnotation, getObjectDXN } from '@dxos/echo/internal';
12
+ import { live } from '@dxos/echo/internal';
13
+ import { Tooltip } from '@dxos/react-ui';
14
+ import { withSurfaceVariantsLayout, withTheme } from '@dxos/react-ui/testing';
15
15
  import { Testing } from '@dxos/schema/testing';
16
- import { withLayout, withTheme } from '@dxos/storybook-utils';
17
16
 
18
- import { SelectInput } from './Defaults';
19
- import { Form, type FormProps } from './Form';
20
17
  import { translations } from '../../translations';
21
18
  import { TestLayout, TestPanel } from '../testing';
22
19
 
20
+ import { SelectInput } from './Defaults';
21
+ import { Form, type FormProps } from './Form';
22
+
23
+ type StoryProps<T extends BaseObject> = {
24
+ debug?: boolean;
25
+ schema: Type.Obj.Any;
26
+ } & FormProps<T>;
27
+
28
+ const DefaultStory = <T extends BaseObject = any>({
29
+ debug,
30
+ schema,
31
+ values: initialValues,
32
+ ...props
33
+ }: StoryProps<T>) => {
34
+ const [values, setValues] = useState(initialValues);
35
+ const handleSave = useCallback<NonNullable<FormProps<T>['onSave']>>((values) => {
36
+ setValues(values);
37
+ }, []);
38
+
39
+ if (debug) {
40
+ return (
41
+ <Tooltip.Provider>
42
+ <TestLayout json={{ values, schema: schema.ast }}>
43
+ <TestPanel>
44
+ <Form<T> schema={schema} values={values} onSave={handleSave} {...props} />
45
+ </TestPanel>
46
+ </TestLayout>
47
+ </Tooltip.Provider>
48
+ );
49
+ }
50
+
51
+ return <Form<T> schema={schema} values={values} onSave={handleSave} {...props} />;
52
+ };
53
+
54
+ const RefStory = <T extends BaseObject = any>(props: StoryProps<T>) => {
55
+ const onQueryRefOptions = useCallback((typeInfo: TypeAnnotation) => {
56
+ switch (typeInfo.typename) {
57
+ case ContactType.typename:
58
+ return [
59
+ { dxn: getObjectDXN(contact1)!, label: 'John Coltraine' },
60
+ { dxn: getObjectDXN(contact2)!, label: 'Erykah Badu' },
61
+ ];
62
+ default:
63
+ return [];
64
+ }
65
+ }, []);
66
+
67
+ return <DefaultStory<T> onQueryRefOptions={onQueryRefOptions} {...props} />;
68
+ };
69
+
23
70
  const AddressSchema = Schema.Struct({
24
71
  street: Schema.optional(Schema.String.annotations({ title: 'Street' })),
25
72
  city: Schema.optional(Schema.String.annotations({ title: 'City' })),
@@ -27,8 +74,7 @@ const AddressSchema = Schema.Struct({
27
74
  location: Schema.optional(Format.GeoPoint.annotations({ title: 'Location' })),
28
75
  }).annotations({ title: 'Address' });
29
76
 
30
- // TODO(burdon): Translations?
31
- const TestSchema = Schema.Struct({
77
+ const ContactSchema = Schema.Struct({
32
78
  name: Schema.optional(Schema.String.annotations({ title: 'Name' })),
33
79
  active: Schema.optional(Schema.Boolean.annotations({ title: 'Active' })),
34
80
  rank: Schema.optional(Schema.Number.annotations({ title: 'Rank' })),
@@ -36,40 +82,15 @@ const TestSchema = Schema.Struct({
36
82
  address: Schema.optional(AddressSchema),
37
83
  }).pipe(Schema.mutable);
38
84
 
39
- type TestType = Schema.Schema.Type<typeof TestSchema>;
40
-
41
- type StoryProps<T extends BaseObject> = { schema: Schema.Schema<T> } & FormProps<T>;
42
-
43
- const DefaultStory = <T extends BaseObject>({ schema, values: initialValues, ...props }: StoryProps<T>) => {
44
- const [values, setValues] = useState(initialValues);
45
- const handleSave = useCallback<NonNullable<FormProps<T>['onSave']>>((values) => {
46
- setValues(values);
47
- }, []);
48
-
49
- return <Form<T> schema={schema} values={values} onSave={handleSave} {...props} />;
50
- };
85
+ type ContactSchema = Schema.Schema.Type<typeof ContactSchema>;
51
86
 
52
- const DebugStory = <T extends BaseObject>({ schema, values: initialValues, ...props }: StoryProps<T>) => {
53
- const [values, setValues] = useState(initialValues);
54
- const handleSave = useCallback<NonNullable<FormProps<T>['onSave']>>((values) => {
55
- setValues(values);
56
- }, []);
57
-
58
- return (
59
- <TestLayout json={{ values, schema: schema.ast.toJSON() }}>
60
- <TestPanel>
61
- <Form<T> schema={schema} values={values} onSave={handleSave} {...props} />
62
- </TestPanel>
63
- </TestLayout>
64
- );
65
- };
66
-
67
- const meta: Meta<StoryProps<any>> = {
87
+ const meta = {
68
88
  title: 'ui/react-ui-form/Form',
69
- component: Form<any>,
70
- render: DebugStory,
71
- decorators: [withLayout({ fullscreen: true }), withTheme],
89
+ component: Form as any,
90
+ render: DefaultStory,
91
+ decorators: [withTheme, withSurfaceVariantsLayout()],
72
92
  parameters: {
93
+ layout: 'fullscreen',
73
94
  translations,
74
95
  },
75
96
  argTypes: {
@@ -78,17 +99,15 @@ const meta: Meta<StoryProps<any>> = {
78
99
  description: 'Readonly',
79
100
  },
80
101
  },
81
- };
102
+ } satisfies Meta<StoryProps<any>>;
82
103
 
83
104
  export default meta;
84
105
 
85
- type Story<T extends BaseObject> = StoryObj<StoryProps<T>>;
106
+ type Story = StoryObj<typeof meta>;
86
107
 
87
- export const Default: Story<TestType> = {
88
- render: DefaultStory,
89
- decorators: [withSurfaceVariantsLayout(), withTheme],
108
+ export const Default: Story = {
90
109
  args: {
91
- schema: TestSchema,
110
+ schema: ContactSchema,
92
111
  values: {
93
112
  name: 'DXOS',
94
113
  active: true,
@@ -100,8 +119,9 @@ export const Default: Story<TestType> = {
100
119
  },
101
120
  };
102
121
 
103
- export const Organization: Story<Schema.Schema.Type<typeof Testing.OrganizationSchema>> = {
122
+ export const Organization: Story = {
104
123
  args: {
124
+ debug: true,
105
125
  schema: Testing.OrganizationSchema,
106
126
  values: {
107
127
  name: 'DXOS',
@@ -111,8 +131,9 @@ export const Organization: Story<Schema.Schema.Type<typeof Testing.OrganizationS
111
131
  },
112
132
  };
113
133
 
114
- export const OrganizationAutoSave: Story<Schema.Schema.Type<typeof Testing.OrganizationSchema>> = {
134
+ export const OrganizationAutoSave: Story = {
115
135
  args: {
136
+ debug: true,
116
137
  schema: Testing.OrganizationSchema,
117
138
  values: {
118
139
  name: 'DXOS',
@@ -125,15 +146,20 @@ export const OrganizationAutoSave: Story<Schema.Schema.Type<typeof Testing.Organ
125
146
 
126
147
  // TODO(burdon): Type issue with employer reference.
127
148
  // TODO(burdon): Test table/form with compound values (e.g., address).
128
- // export const Contact: Story<Testing.ContactSchemaType> = {
129
- // args: {
130
- // // Property name is missing in type EncodedReference but required in type
131
- // schema: Testing.ContactSchema,
132
- // values: {
133
- // name: 'Bot',
134
- // },
135
- // },
136
- // };
149
+ // Property name is missing in type EncodedReference but required in type
150
+ export const Contact: Story = {
151
+ args: {
152
+ debug: true,
153
+ schema: Testing.ContactSchema,
154
+ values: {
155
+ name: 'Bot',
156
+ },
157
+ },
158
+ };
159
+
160
+ //
161
+ // Union
162
+ //
137
163
 
138
164
  const ShapeSchema = Schema.Struct({
139
165
  shape: Schema.optional(
@@ -150,10 +176,11 @@ const ShapeSchema = Schema.Struct({
150
176
  ),
151
177
  }).pipe(Schema.mutable);
152
178
 
153
- type ShapeType = Schema.Schema.Type<typeof ShapeSchema>;
179
+ type ShapeSchema = Schema.Schema.Type<typeof ShapeSchema>;
154
180
 
155
- export const DiscriminatedShape: Story<ShapeType> = {
181
+ export const DiscriminatedShape: StoryObj<StoryProps<ShapeSchema>> = {
156
182
  args: {
183
+ debug: true,
157
184
  schema: ShapeSchema,
158
185
  readonly: false,
159
186
  values: {
@@ -176,15 +203,20 @@ export const DiscriminatedShape: Story<ShapeType> = {
176
203
  },
177
204
  };
178
205
 
206
+ //
207
+ // Arrays
208
+ //
209
+
179
210
  const ArraysSchema = Schema.Struct({
180
211
  names: Schema.Array(Schema.String.pipe(Schema.nonEmptyString())),
181
212
  addresses: Schema.Array(AddressSchema),
182
213
  }).pipe(Schema.mutable);
183
214
 
184
- type ArraysType = Schema.Schema.Type<typeof ArraysSchema>;
215
+ type ArraysSchema = Schema.Schema.Type<typeof ArraysSchema>;
185
216
 
186
- export const Arrays: StoryObj<FormProps<ArraysType>> = {
217
+ export const Arrays: StoryObj<StoryProps<ArraysSchema>> = {
187
218
  args: {
219
+ debug: true,
188
220
  schema: ArraysSchema,
189
221
  readonly: false,
190
222
  values: {
@@ -194,6 +226,34 @@ export const Arrays: StoryObj<FormProps<ArraysType>> = {
194
226
  },
195
227
  };
196
228
 
229
+ //
230
+ // Tuple
231
+ //
232
+
233
+ // TODO(wittjosiah): Only GeoPoint is works currently.
234
+ const TupleSchema = Schema.Struct({
235
+ tuple: Schema.Tuple(Schema.Number, Schema.String, Schema.Boolean),
236
+ geopoint: Format.GeoPoint,
237
+ }).pipe(Schema.mutable);
238
+
239
+ type TupleSchema = Schema.Schema.Type<typeof TupleSchema>;
240
+
241
+ export const Tuple: StoryObj<StoryProps<TupleSchema>> = {
242
+ args: {
243
+ debug: true,
244
+ schema: TupleSchema,
245
+ readonly: false,
246
+ values: {
247
+ tuple: [1, 'a', true],
248
+ geopoint: [-122.41941, 37.77492],
249
+ },
250
+ },
251
+ };
252
+
253
+ //
254
+ // Enum
255
+ //
256
+
197
257
  const ColorSchema = Schema.Struct({
198
258
  color: Schema.Union(Schema.Literal('red'), Schema.Literal('green'), Schema.Literal('blue')).annotations({
199
259
  title: 'Color',
@@ -202,8 +262,9 @@ const ColorSchema = Schema.Struct({
202
262
 
203
263
  type ColorType = Schema.Schema.Type<typeof ColorSchema>;
204
264
 
205
- export const Enum: StoryObj<FormProps<ColorType>> = {
265
+ export const Enum: StoryObj<StoryProps<ColorType>> = {
206
266
  args: {
267
+ debug: true,
207
268
  schema: ColorSchema,
208
269
  readonly: false,
209
270
  values: {
@@ -212,6 +273,10 @@ export const Enum: StoryObj<FormProps<ColorType>> = {
212
273
  },
213
274
  };
214
275
 
276
+ //
277
+ // Refs
278
+ //
279
+
215
280
  const RefSchema = Schema.Struct({
216
281
  contact: Ref(ContactType).annotations({ title: 'Contact Reference' }),
217
282
  optionalContact: Schema.optional(Ref(ContactType).annotations({ title: 'Optional Contact Reference' })),
@@ -219,48 +284,19 @@ const RefSchema = Schema.Struct({
219
284
  unknownExpando: Schema.optional(Ref(Expando).annotations({ title: 'Optional Ref to an Expando (DXN Input)' })),
220
285
  });
221
286
 
287
+ type RefSchema = Schema.Schema.Type<typeof RefSchema>;
288
+
222
289
  const contact1 = live(ContactType, { identifiers: [] });
223
290
  const contact2 = live(ContactType, { identifiers: [] });
224
291
 
225
- const RefStory = ({ values: initialValues, readonly }: FormProps<any>) => {
226
- const [values, setValues] = useState(initialValues);
227
- const handleSave = useCallback<NonNullable<FormProps<any>['onSave']>>((values) => {
228
- setValues(values);
229
- }, []);
230
-
231
- const onQueryRefOptions = useCallback((typeInfo: TypeAnnotation) => {
232
- switch (typeInfo.typename) {
233
- case ContactType.typename:
234
- return [
235
- { dxn: getObjectDXN(contact1)!, label: 'John Coltraine' },
236
- { dxn: getObjectDXN(contact2)!, label: 'Erykah Badu' },
237
- ];
238
- default:
239
- return [];
240
- }
241
- }, []);
242
-
243
- return (
244
- <TestLayout json={{ values, schema: RefSchema.ast.toJSON() }}>
245
- <TestPanel>
246
- <Form
247
- schema={RefSchema}
248
- values={values}
249
- readonly={readonly}
250
- onSave={handleSave}
251
- onQueryRefOptions={onQueryRefOptions}
252
- />
253
- </TestPanel>
254
- </TestLayout>
255
- );
256
- };
257
-
258
- export const Refs: StoryObj<FormProps<ContactType>> = {
292
+ export const Refs: StoryObj<StoryProps<RefSchema>> = {
259
293
  render: RefStory,
260
294
  args: {
295
+ debug: true,
296
+ schema: RefSchema,
261
297
  readonly: false,
262
298
  values: {
263
299
  refArray: [Ref.make(contact1), Ref.make(contact2)],
264
- } as any,
300
+ },
265
301
  },
266
302
  };
@@ -2,19 +2,21 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type Schema } from 'effect';
6
- import React, { type ReactElement, useEffect, useMemo, useRef } from 'react';
5
+ import { useFocusFinders } from '@fluentui/react-tabster';
6
+ import type * as Schema from 'effect/Schema';
7
+ import React, { type ReactElement, useCallback, useEffect, useRef } from 'react';
7
8
 
8
- import { type BaseObject, type PropertyKey } from '@dxos/echo-schema';
9
+ import { type BaseObject, type PropertyKey } from '@dxos/echo/internal';
9
10
  import { type ThemedClassName } from '@dxos/react-ui';
10
11
  import { cardDialogOverflow, cardSpacing } from '@dxos/react-ui-stack';
11
- import { type SchemaProperty } from '@dxos/schema';
12
+ import { type ProjectionModel, type SchemaProperty } from '@dxos/schema';
13
+
14
+ import { type FormHandler, type FormOptions } from '../../hooks';
12
15
 
13
16
  import { FormActions, type FormOuterSpacing } from './FormActions';
14
17
  import { FormFields, type FormFieldsProps } from './FormContent';
15
18
  import { FormProvider } from './FormContext';
16
- import { type InputProps, type InputComponent } from './Input';
17
- import { type FormOptions } from '../../hooks';
19
+ import { type InputComponent, type InputProps } from './Input';
18
20
 
19
21
  export type PropsFilter<T extends BaseObject> = (props: SchemaProperty<T>[]) => SchemaProperty<T>[];
20
22
 
@@ -33,6 +35,7 @@ export type FormProps<T extends BaseObject> = ThemedClassName<{
33
35
  // TODO(burdon): Change to JsonPath includes/excludes.
34
36
  filter?: PropsFilter<T>;
35
37
  sort?: PropertyKey<T>[];
38
+ projection?: ProjectionModel;
36
39
  autoSave?: boolean;
37
40
  outerSpacing?: FormOuterSpacing;
38
41
  onCancel?: () => void;
@@ -48,8 +51,8 @@ export const Form = <T extends BaseObject>({
48
51
  readonly,
49
52
  autoSave,
50
53
  outerSpacing = true,
51
- onCancel,
52
54
  schema,
55
+ onCancel,
53
56
  onValuesChanged,
54
57
  onValidate,
55
58
  onSave,
@@ -57,19 +60,27 @@ export const Form = <T extends BaseObject>({
57
60
  }: FormProps<T>) => {
58
61
  const formRef = useRef<HTMLDivElement>(null);
59
62
 
60
- // TODO(burdon): Rename.
61
- const handleValid = useMemo(() => (autoSave ? onSave : undefined), [autoSave, onSave]);
62
-
63
- // Focus the first input element within this form.
63
+ // Focus the first focusable element within this form.
64
+ const { findFirstFocusable } = useFocusFinders();
64
65
  useEffect(() => {
65
66
  if (autoFocus && formRef.current) {
66
- const input = formRef.current.querySelector('input');
67
- if (input) {
68
- input.focus();
67
+ const firstFocusable = findFirstFocusable(formRef.current);
68
+ if (firstFocusable) {
69
+ firstFocusable.focus();
69
70
  }
70
71
  }
71
72
  }, [autoFocus]);
72
73
 
74
+ // TODO(burdon): Name?
75
+ const handleValid = useCallback(
76
+ async (values: any, meta: { changed: FormHandler<T>['changed'] }) => {
77
+ if (autoSave) {
78
+ await onSave?.(values, meta);
79
+ }
80
+ },
81
+ [autoSave, onSave],
82
+ );
83
+
73
84
  return (
74
85
  <FormProvider
75
86
  formRef={formRef}
@@ -99,7 +110,7 @@ export const Form = <T extends BaseObject>({
99
110
  readonly={readonly}
100
111
  schema={schema}
101
112
  />
102
- {(onCancel || onSave) && !autoSave && (
113
+ {(onCancel || onSave) && !autoSave && !readonly && (
103
114
  <FormActions readonly={readonly} onCancel={onCancel} outerSpacing={outerSpacing} />
104
115
  )}
105
116
  </div>
@@ -8,9 +8,10 @@ import { IconButton, useTranslation } from '@dxos/react-ui';
8
8
  import { cardSpacing } from '@dxos/react-ui-stack';
9
9
  import { mx } from '@dxos/react-ui-theme';
10
10
 
11
- import { useFormContext } from './FormContext';
12
11
  import { translationKey } from '../../translations';
13
12
 
13
+ import { useFormContext } from './FormContext';
14
+
14
15
  export type FormOuterSpacing = boolean | 'blockStart-0' | 'scroll-fields';
15
16
 
16
17
  export type FormActionsProps = {