@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.
- package/dist/lib/browser/index.mjs +806 -572
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +806 -572
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/ControlSection/ControlSection.d.ts +2 -2
- package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +1 -1
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +80 -5
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +1 -2
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +75 -5
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/ArrayField.d.ts +2 -2
- package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/Defaults.d.ts +5 -5
- package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +6 -4
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +128 -32
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormContent.d.ts +16 -10
- package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormContext.d.ts +2 -2
- package/dist/types/src/components/Form/FormContext.d.ts.map +1 -1
- package/dist/types/src/components/Form/Input.d.ts +5 -2
- package/dist/types/src/components/Form/Input.d.ts.map +1 -1
- package/dist/types/src/components/Form/RefField.d.ts +14 -4
- package/dist/types/src/components/Form/RefField.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +1 -1
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +1 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +75 -4
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/TupleInput.d.ts +1 -1
- package/dist/types/src/components/Form/factory.d.ts +1 -1
- package/dist/types/src/components/Form/factory.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +41 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +81 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -0
- package/dist/types/src/components/ObjectPicker/index.d.ts +2 -0
- package/dist/types/src/components/ObjectPicker/index.d.ts.map +1 -0
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +20 -6
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +79 -8
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +6 -3
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/useForm.d.ts +2 -2
- package/dist/types/src/hooks/useForm.d.ts.map +1 -1
- package/dist/types/src/hooks/useQueryRefOptions.d.ts +9 -3
- package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +1 -1
- package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +11 -7
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/field.d.ts +1 -1
- package/dist/types/src/util/field.d.ts.map +1 -1
- package/dist/types/src/util/formatting.d.ts +1 -1
- package/dist/types/src/util/formatting.d.ts.map +1 -1
- package/dist/types/src/util/refs.d.ts +1 -1
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/src/util/schema.d.ts +1 -1
- package/dist/types/src/util/schema.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +41 -36
- package/src/components/ControlSection/ControlSection.stories.tsx +10 -14
- package/src/components/ControlSection/ControlSection.tsx +26 -12
- package/src/components/FieldEditor/FieldEditor.stories.tsx +10 -11
- package/src/components/FieldEditor/FieldEditor.tsx +12 -14
- package/src/components/Form/ArrayField.tsx +6 -6
- package/src/components/Form/Defaults.tsx +21 -20
- package/src/components/Form/Form.stories.tsx +132 -97
- package/src/components/Form/Form.tsx +27 -14
- package/src/components/Form/FormActions.tsx +2 -2
- package/src/components/Form/FormContent.tsx +166 -45
- package/src/components/Form/FormContext.tsx +3 -3
- package/src/components/Form/Input.tsx +5 -2
- package/src/components/Form/RefField.tsx +107 -38
- package/src/components/Form/custom/GeoPointInput.tsx +28 -20
- package/src/components/Form/custom/SelectOptionsInput.stories.tsx +9 -10
- package/src/components/Form/custom/SelectOptionsInput.tsx +10 -10
- package/src/components/Form/custom/TupleInput.tsx +2 -2
- package/src/components/Form/factory.tsx +1 -1
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +207 -0
- package/src/components/ObjectPicker/ObjectPicker.tsx +161 -0
- package/src/components/ObjectPicker/index.ts +5 -0
- package/src/components/ViewEditor/ViewEditor.stories.tsx +123 -49
- package/src/components/ViewEditor/ViewEditor.test.tsx +8 -11
- package/src/components/ViewEditor/ViewEditor.tsx +281 -233
- package/src/components/index.ts +1 -2
- package/src/components/testing.tsx +15 -9
- package/src/hooks/useForm.ts +7 -4
- package/src/hooks/useQueryRefOptions.ts +15 -6
- package/src/hooks/useRefQueryLookupHandler.ts +3 -3
- package/src/translations.ts +13 -8
- package/src/util/field.ts +9 -19
- package/src/util/formatting.ts +1 -1
- package/src/util/refs.ts +7 -7
- package/src/util/schema.ts +2 -4
- package/dist/types/src/components/deprecated/Form.d.ts +0 -15
- package/dist/types/src/components/deprecated/Form.d.ts.map +0 -1
- package/dist/types/src/components/deprecated/Form.stories.d.ts +0 -13
- package/dist/types/src/components/deprecated/Form.stories.d.ts.map +0 -1
- package/dist/types/src/components/deprecated/FormInput.d.ts +0 -16
- package/dist/types/src/components/deprecated/FormInput.d.ts.map +0 -1
- package/dist/types/src/components/deprecated/index.d.ts +0 -2
- package/dist/types/src/components/deprecated/index.d.ts.map +0 -1
- package/src/components/deprecated/Form.stories.tsx +0 -63
- package/src/components/deprecated/Form.tsx +0 -119
- package/src/components/deprecated/FormInput.tsx +0 -71
- package/src/components/deprecated/index.ts +0 -5
|
@@ -2,18 +2,17 @@
|
|
|
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
|
|
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
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
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 { withLayoutVariants, 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
17
|
import { translations } from '../../translations';
|
|
19
18
|
import { TestLayout, TestPanel } from '../testing';
|
|
@@ -21,6 +20,53 @@ import { TestLayout, TestPanel } from '../testing';
|
|
|
21
20
|
import { SelectInput } from './Defaults';
|
|
22
21
|
import { Form, type FormProps } from './Form';
|
|
23
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 Testing.Person.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
|
+
|
|
24
70
|
const AddressSchema = Schema.Struct({
|
|
25
71
|
street: Schema.optional(Schema.String.annotations({ title: 'Street' })),
|
|
26
72
|
city: Schema.optional(Schema.String.annotations({ title: 'City' })),
|
|
@@ -28,8 +74,7 @@ const AddressSchema = Schema.Struct({
|
|
|
28
74
|
location: Schema.optional(Format.GeoPoint.annotations({ title: 'Location' })),
|
|
29
75
|
}).annotations({ title: 'Address' });
|
|
30
76
|
|
|
31
|
-
|
|
32
|
-
const TestSchema = Schema.Struct({
|
|
77
|
+
const ContactSchema = Schema.Struct({
|
|
33
78
|
name: Schema.optional(Schema.String.annotations({ title: 'Name' })),
|
|
34
79
|
active: Schema.optional(Schema.Boolean.annotations({ title: 'Active' })),
|
|
35
80
|
rank: Schema.optional(Schema.Number.annotations({ title: 'Rank' })),
|
|
@@ -37,40 +82,15 @@ const TestSchema = Schema.Struct({
|
|
|
37
82
|
address: Schema.optional(AddressSchema),
|
|
38
83
|
}).pipe(Schema.mutable);
|
|
39
84
|
|
|
40
|
-
type
|
|
85
|
+
type ContactSchema = Schema.Schema.Type<typeof ContactSchema>;
|
|
41
86
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const DefaultStory = <T extends BaseObject>({ schema, values: initialValues, ...props }: StoryProps<T>) => {
|
|
45
|
-
const [values, setValues] = useState(initialValues);
|
|
46
|
-
const handleSave = useCallback<NonNullable<FormProps<T>['onSave']>>((values) => {
|
|
47
|
-
setValues(values);
|
|
48
|
-
}, []);
|
|
49
|
-
|
|
50
|
-
return <Form<T> schema={schema} values={values} onSave={handleSave} {...props} />;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const DebugStory = <T extends BaseObject>({ schema, values: initialValues, ...props }: StoryProps<T>) => {
|
|
54
|
-
const [values, setValues] = useState(initialValues);
|
|
55
|
-
const handleSave = useCallback<NonNullable<FormProps<T>['onSave']>>((values) => {
|
|
56
|
-
setValues(values);
|
|
57
|
-
}, []);
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<TestLayout json={{ values, schema: schema.ast.toJSON() }}>
|
|
61
|
-
<TestPanel>
|
|
62
|
-
<Form<T> schema={schema} values={values} onSave={handleSave} {...props} />
|
|
63
|
-
</TestPanel>
|
|
64
|
-
</TestLayout>
|
|
65
|
-
);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const meta: Meta<StoryProps<any>> = {
|
|
87
|
+
const meta = {
|
|
69
88
|
title: 'ui/react-ui-form/Form',
|
|
70
|
-
component: Form
|
|
71
|
-
render:
|
|
72
|
-
decorators: [
|
|
89
|
+
component: Form as any,
|
|
90
|
+
render: DefaultStory,
|
|
91
|
+
decorators: [withTheme, withLayoutVariants()],
|
|
73
92
|
parameters: {
|
|
93
|
+
layout: 'fullscreen',
|
|
74
94
|
translations,
|
|
75
95
|
},
|
|
76
96
|
argTypes: {
|
|
@@ -79,17 +99,15 @@ const meta: Meta<StoryProps<any>> = {
|
|
|
79
99
|
description: 'Readonly',
|
|
80
100
|
},
|
|
81
101
|
},
|
|
82
|
-
}
|
|
102
|
+
} satisfies Meta<StoryProps<any>>;
|
|
83
103
|
|
|
84
104
|
export default meta;
|
|
85
105
|
|
|
86
|
-
type Story
|
|
106
|
+
type Story = StoryObj<typeof meta>;
|
|
87
107
|
|
|
88
|
-
export const Default: Story
|
|
89
|
-
render: DefaultStory,
|
|
90
|
-
decorators: [withSurfaceVariantsLayout(), withTheme],
|
|
108
|
+
export const Default: Story = {
|
|
91
109
|
args: {
|
|
92
|
-
schema:
|
|
110
|
+
schema: ContactSchema,
|
|
93
111
|
values: {
|
|
94
112
|
name: 'DXOS',
|
|
95
113
|
active: true,
|
|
@@ -101,8 +119,9 @@ export const Default: Story<TestType> = {
|
|
|
101
119
|
},
|
|
102
120
|
};
|
|
103
121
|
|
|
104
|
-
export const Organization: Story
|
|
122
|
+
export const Organization: Story = {
|
|
105
123
|
args: {
|
|
124
|
+
debug: true,
|
|
106
125
|
schema: Testing.OrganizationSchema,
|
|
107
126
|
values: {
|
|
108
127
|
name: 'DXOS',
|
|
@@ -112,8 +131,9 @@ export const Organization: Story<Schema.Schema.Type<typeof Testing.OrganizationS
|
|
|
112
131
|
},
|
|
113
132
|
};
|
|
114
133
|
|
|
115
|
-
export const OrganizationAutoSave: Story
|
|
134
|
+
export const OrganizationAutoSave: Story = {
|
|
116
135
|
args: {
|
|
136
|
+
debug: true,
|
|
117
137
|
schema: Testing.OrganizationSchema,
|
|
118
138
|
values: {
|
|
119
139
|
name: 'DXOS',
|
|
@@ -126,15 +146,20 @@ export const OrganizationAutoSave: Story<Schema.Schema.Type<typeof Testing.Organ
|
|
|
126
146
|
|
|
127
147
|
// TODO(burdon): Type issue with employer reference.
|
|
128
148
|
// TODO(burdon): Test table/form with compound values (e.g., address).
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
149
|
+
// Property name is missing in type EncodedReference but required in type
|
|
150
|
+
export const Person: Story = {
|
|
151
|
+
args: {
|
|
152
|
+
debug: true,
|
|
153
|
+
schema: Testing.Person,
|
|
154
|
+
values: {
|
|
155
|
+
name: 'Bot',
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
//
|
|
161
|
+
// Union
|
|
162
|
+
//
|
|
138
163
|
|
|
139
164
|
const ShapeSchema = Schema.Struct({
|
|
140
165
|
shape: Schema.optional(
|
|
@@ -151,10 +176,11 @@ const ShapeSchema = Schema.Struct({
|
|
|
151
176
|
),
|
|
152
177
|
}).pipe(Schema.mutable);
|
|
153
178
|
|
|
154
|
-
type
|
|
179
|
+
type ShapeSchema = Schema.Schema.Type<typeof ShapeSchema>;
|
|
155
180
|
|
|
156
|
-
export const DiscriminatedShape:
|
|
181
|
+
export const DiscriminatedShape: StoryObj<StoryProps<ShapeSchema>> = {
|
|
157
182
|
args: {
|
|
183
|
+
debug: true,
|
|
158
184
|
schema: ShapeSchema,
|
|
159
185
|
readonly: false,
|
|
160
186
|
values: {
|
|
@@ -177,15 +203,20 @@ export const DiscriminatedShape: Story<ShapeType> = {
|
|
|
177
203
|
},
|
|
178
204
|
};
|
|
179
205
|
|
|
206
|
+
//
|
|
207
|
+
// Arrays
|
|
208
|
+
//
|
|
209
|
+
|
|
180
210
|
const ArraysSchema = Schema.Struct({
|
|
181
211
|
names: Schema.Array(Schema.String.pipe(Schema.nonEmptyString())),
|
|
182
212
|
addresses: Schema.Array(AddressSchema),
|
|
183
213
|
}).pipe(Schema.mutable);
|
|
184
214
|
|
|
185
|
-
type
|
|
215
|
+
type ArraysSchema = Schema.Schema.Type<typeof ArraysSchema>;
|
|
186
216
|
|
|
187
|
-
export const Arrays: StoryObj<
|
|
217
|
+
export const Arrays: StoryObj<StoryProps<ArraysSchema>> = {
|
|
188
218
|
args: {
|
|
219
|
+
debug: true,
|
|
189
220
|
schema: ArraysSchema,
|
|
190
221
|
readonly: false,
|
|
191
222
|
values: {
|
|
@@ -195,6 +226,34 @@ export const Arrays: StoryObj<FormProps<ArraysType>> = {
|
|
|
195
226
|
},
|
|
196
227
|
};
|
|
197
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
|
+
|
|
198
257
|
const ColorSchema = Schema.Struct({
|
|
199
258
|
color: Schema.Union(Schema.Literal('red'), Schema.Literal('green'), Schema.Literal('blue')).annotations({
|
|
200
259
|
title: 'Color',
|
|
@@ -203,8 +262,9 @@ const ColorSchema = Schema.Struct({
|
|
|
203
262
|
|
|
204
263
|
type ColorType = Schema.Schema.Type<typeof ColorSchema>;
|
|
205
264
|
|
|
206
|
-
export const Enum: StoryObj<
|
|
265
|
+
export const Enum: StoryObj<StoryProps<ColorType>> = {
|
|
207
266
|
args: {
|
|
267
|
+
debug: true,
|
|
208
268
|
schema: ColorSchema,
|
|
209
269
|
readonly: false,
|
|
210
270
|
values: {
|
|
@@ -213,6 +273,10 @@ export const Enum: StoryObj<FormProps<ColorType>> = {
|
|
|
213
273
|
},
|
|
214
274
|
};
|
|
215
275
|
|
|
276
|
+
//
|
|
277
|
+
// Refs
|
|
278
|
+
//
|
|
279
|
+
|
|
216
280
|
const RefSchema = Schema.Struct({
|
|
217
281
|
contact: Ref(ContactType).annotations({ title: 'Contact Reference' }),
|
|
218
282
|
optionalContact: Schema.optional(Ref(ContactType).annotations({ title: 'Optional Contact Reference' })),
|
|
@@ -220,48 +284,19 @@ const RefSchema = Schema.Struct({
|
|
|
220
284
|
unknownExpando: Schema.optional(Ref(Expando).annotations({ title: 'Optional Ref to an Expando (DXN Input)' })),
|
|
221
285
|
});
|
|
222
286
|
|
|
287
|
+
type RefSchema = Schema.Schema.Type<typeof RefSchema>;
|
|
288
|
+
|
|
223
289
|
const contact1 = live(ContactType, { identifiers: [] });
|
|
224
290
|
const contact2 = live(ContactType, { identifiers: [] });
|
|
225
291
|
|
|
226
|
-
const
|
|
227
|
-
const [values, setValues] = useState(initialValues);
|
|
228
|
-
const handleSave = useCallback<NonNullable<FormProps<any>['onSave']>>((values) => {
|
|
229
|
-
setValues(values);
|
|
230
|
-
}, []);
|
|
231
|
-
|
|
232
|
-
const onQueryRefOptions = useCallback((typeInfo: TypeAnnotation) => {
|
|
233
|
-
switch (typeInfo.typename) {
|
|
234
|
-
case ContactType.typename:
|
|
235
|
-
return [
|
|
236
|
-
{ dxn: getObjectDXN(contact1)!, label: 'John Coltraine' },
|
|
237
|
-
{ dxn: getObjectDXN(contact2)!, label: 'Erykah Badu' },
|
|
238
|
-
];
|
|
239
|
-
default:
|
|
240
|
-
return [];
|
|
241
|
-
}
|
|
242
|
-
}, []);
|
|
243
|
-
|
|
244
|
-
return (
|
|
245
|
-
<TestLayout json={{ values, schema: RefSchema.ast.toJSON() }}>
|
|
246
|
-
<TestPanel>
|
|
247
|
-
<Form
|
|
248
|
-
schema={RefSchema}
|
|
249
|
-
values={values}
|
|
250
|
-
readonly={readonly}
|
|
251
|
-
onSave={handleSave}
|
|
252
|
-
onQueryRefOptions={onQueryRefOptions}
|
|
253
|
-
/>
|
|
254
|
-
</TestPanel>
|
|
255
|
-
</TestLayout>
|
|
256
|
-
);
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
export const Refs: StoryObj<FormProps<ContactType>> = {
|
|
292
|
+
export const Refs: StoryObj<StoryProps<RefSchema>> = {
|
|
260
293
|
render: RefStory,
|
|
261
294
|
args: {
|
|
295
|
+
debug: true,
|
|
296
|
+
schema: RefSchema,
|
|
262
297
|
readonly: false,
|
|
263
298
|
values: {
|
|
264
299
|
refArray: [Ref.make(contact1), Ref.make(contact2)],
|
|
265
|
-
}
|
|
300
|
+
},
|
|
266
301
|
},
|
|
267
302
|
};
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
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
|
|
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';
|
|
12
13
|
|
|
13
|
-
import { type FormOptions } from '../../hooks';
|
|
14
|
+
import { type FormHandler, type FormOptions } from '../../hooks';
|
|
14
15
|
|
|
15
16
|
import { FormActions, type FormOuterSpacing } from './FormActions';
|
|
16
17
|
import { FormFields, type FormFieldsProps } from './FormContent';
|
|
@@ -28,29 +29,33 @@ export type ComponentLookup = (args: {
|
|
|
28
29
|
export type CustomInputMap = Partial<Record<string, InputComponent>>;
|
|
29
30
|
|
|
30
31
|
export type FormProps<T extends BaseObject> = ThemedClassName<{
|
|
32
|
+
id?: string;
|
|
31
33
|
values: Partial<T>;
|
|
32
34
|
// TODO(burdon): Autofocus first input.
|
|
33
35
|
autoFocus?: boolean;
|
|
34
36
|
// TODO(burdon): Change to JsonPath includes/excludes.
|
|
35
37
|
filter?: PropsFilter<T>;
|
|
36
38
|
sort?: PropertyKey<T>[];
|
|
39
|
+
projection?: ProjectionModel;
|
|
37
40
|
autoSave?: boolean;
|
|
38
41
|
outerSpacing?: FormOuterSpacing;
|
|
39
42
|
onCancel?: () => void;
|
|
40
43
|
}> &
|
|
41
44
|
Pick<FormOptions<T>, 'schema' | 'onValuesChanged' | 'onValidate' | 'onSave'> &
|
|
45
|
+
// TODO(wittjosiah): This needs to support different ref field options per field.
|
|
42
46
|
FormFieldsProps;
|
|
43
47
|
|
|
44
48
|
export const Form = <T extends BaseObject>({
|
|
45
49
|
classNames,
|
|
50
|
+
id,
|
|
46
51
|
testId,
|
|
47
52
|
values: initialValues,
|
|
48
53
|
autoFocus,
|
|
49
54
|
readonly,
|
|
50
55
|
autoSave,
|
|
51
56
|
outerSpacing = true,
|
|
52
|
-
onCancel,
|
|
53
57
|
schema,
|
|
58
|
+
onCancel,
|
|
54
59
|
onValuesChanged,
|
|
55
60
|
onValidate,
|
|
56
61
|
onSave,
|
|
@@ -58,19 +63,27 @@ export const Form = <T extends BaseObject>({
|
|
|
58
63
|
}: FormProps<T>) => {
|
|
59
64
|
const formRef = useRef<HTMLDivElement>(null);
|
|
60
65
|
|
|
61
|
-
//
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
// Focus the first input element within this form.
|
|
66
|
+
// Focus the first focusable element within this form.
|
|
67
|
+
const { findFirstFocusable } = useFocusFinders();
|
|
65
68
|
useEffect(() => {
|
|
66
69
|
if (autoFocus && formRef.current) {
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
+
const firstFocusable = findFirstFocusable(formRef.current);
|
|
71
|
+
if (firstFocusable) {
|
|
72
|
+
firstFocusable.focus();
|
|
70
73
|
}
|
|
71
74
|
}
|
|
72
75
|
}, [autoFocus]);
|
|
73
76
|
|
|
77
|
+
// TODO(burdon): Name?
|
|
78
|
+
const handleValid = useCallback(
|
|
79
|
+
async (values: any, meta: { changed: FormHandler<T>['changed'] }) => {
|
|
80
|
+
if (autoSave) {
|
|
81
|
+
await onSave?.(values, meta);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
[autoSave, onSave],
|
|
85
|
+
);
|
|
86
|
+
|
|
74
87
|
return (
|
|
75
88
|
<FormProvider
|
|
76
89
|
formRef={formRef}
|
|
@@ -82,7 +95,7 @@ export const Form = <T extends BaseObject>({
|
|
|
82
95
|
onValid={handleValid}
|
|
83
96
|
onSave={onSave}
|
|
84
97
|
>
|
|
85
|
-
<div role='none' className='contents' data-testid={testId}>
|
|
98
|
+
<div role='none' className='contents' {...(id && { 'data-object-id': id })} data-testid={testId}>
|
|
86
99
|
<FormFields
|
|
87
100
|
{...props}
|
|
88
101
|
ref={formRef}
|
|
@@ -37,7 +37,7 @@ export const FormActions = ({ readonly, onCancel, outerSpacing = true }: FormAct
|
|
|
37
37
|
)}
|
|
38
38
|
>
|
|
39
39
|
{onCancel && !readonly && (
|
|
40
|
-
<IconButton data-testid='cancel-button' icon='ph--x--regular' label={t('button
|
|
40
|
+
<IconButton data-testid='cancel-button' icon='ph--x--regular' label={t('cancel button')} onClick={onCancel} />
|
|
41
41
|
)}
|
|
42
42
|
{handleSave && (
|
|
43
43
|
<IconButton
|
|
@@ -45,7 +45,7 @@ export const FormActions = ({ readonly, onCancel, outerSpacing = true }: FormAct
|
|
|
45
45
|
data-testid='save-button'
|
|
46
46
|
disabled={!canSave}
|
|
47
47
|
icon='ph--check--regular'
|
|
48
|
-
label={t('button
|
|
48
|
+
label={t('save button')}
|
|
49
49
|
onClick={handleSave}
|
|
50
50
|
/>
|
|
51
51
|
)}
|