@dxos/react-ui-form 0.8.2-main.2f9c567 → 0.8.2-main.36232bc
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 +117 -275
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +103 -258
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +117 -275
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +3 -6
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +32 -35
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormContent.d.ts +11 -14
- package/dist/types/src/components/Form/FormContent.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/index.d.ts +0 -1
- package/dist/types/src/components/Form/index.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/deprecated/Form.d.ts +2 -2
- package/dist/types/src/components/deprecated/Form.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +0 -1
- package/dist/types/src/hooks/index.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/package.json +25 -25
- package/src/components/Form/ArrayField.tsx +6 -5
- package/src/components/Form/Defaults.tsx +1 -4
- package/src/components/Form/Form.stories.tsx +38 -86
- package/src/components/Form/Form.tsx +2 -7
- package/src/components/Form/FormContent.tsx +19 -62
- package/src/components/Form/custom/SelectOptionsInput.stories.tsx +4 -5
- package/src/components/Form/index.ts +1 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +2 -3
- package/src/components/ViewEditor/ViewEditor.tsx +10 -12
- package/src/components/deprecated/Form.tsx +6 -6
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useForm.ts +3 -3
- package/dist/types/src/components/Form/RefField.d.ts +0 -24
- package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
- package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +0 -8
- package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +0 -1
- package/src/components/Form/RefField.tsx +0 -152
- package/src/hooks/useRefQueryLookupHandler.ts +0 -43
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { pipe } from 'effect';
|
|
6
6
|
import { capitalize } from 'effect/String';
|
|
7
7
|
import React, { useCallback, useMemo } from 'react';
|
|
8
8
|
|
|
9
|
+
import { AST } from '@dxos/echo-schema';
|
|
9
10
|
import { findNode, getDiscriminatedType, isDiscriminatedUnion, SimpleType } from '@dxos/effect';
|
|
10
11
|
import { invariant } from '@dxos/invariant';
|
|
11
12
|
import { IconButton, useTranslation } from '@dxos/react-ui';
|
|
@@ -38,12 +39,12 @@ export const ArrayField = ({ property, readonly, path, inputProps, Custom, looku
|
|
|
38
39
|
invariant(Array.isArray(values), `Values at path ${path?.join('.')} must be an array.`);
|
|
39
40
|
const label = title ?? pipe(name, capitalize);
|
|
40
41
|
|
|
41
|
-
const tupleType = findNode(ast,
|
|
42
|
-
const elementType = (tupleType as
|
|
42
|
+
const tupleType = findNode(ast, AST.isTupleType);
|
|
43
|
+
const elementType = (tupleType as AST.TupleType | undefined)?.rest[0]?.type;
|
|
43
44
|
|
|
44
|
-
const getDefaultObjectValue = (typeNode:
|
|
45
|
+
const getDefaultObjectValue = (typeNode: AST.AST): any => {
|
|
45
46
|
const baseNode = findNode(typeNode, isDiscriminatedUnion);
|
|
46
|
-
const typeLiteral = baseNode ? getDiscriminatedType(baseNode, {}) : findNode(typeNode,
|
|
47
|
+
const typeLiteral = baseNode ? getDiscriminatedType(baseNode, {}) : findNode(typeNode, AST.isTypeLiteral);
|
|
47
48
|
if (!typeLiteral) {
|
|
48
49
|
return {};
|
|
49
50
|
}
|
|
@@ -107,9 +107,6 @@ export const SelectInput = ({
|
|
|
107
107
|
}: SelectInputOptions) => {
|
|
108
108
|
const { status, error } = getStatus();
|
|
109
109
|
|
|
110
|
-
const value = getValue() as string | undefined;
|
|
111
|
-
const handleValueChange = useCallback((value: string | number) => onValueChange(type, value), [type, onValueChange]);
|
|
112
|
-
|
|
113
110
|
return (
|
|
114
111
|
<Input.Root validationValence={status}>
|
|
115
112
|
{!inputOnly && (
|
|
@@ -117,7 +114,7 @@ export const SelectInput = ({
|
|
|
117
114
|
<Input.Label>{label}</Input.Label>
|
|
118
115
|
</InputHeader>
|
|
119
116
|
)}
|
|
120
|
-
<Select.Root value={
|
|
117
|
+
<Select.Root value={getValue()} onValueChange={(value) => onValueChange(type, value)}>
|
|
121
118
|
{/* TODO(burdon): Placeholder not working? */}
|
|
122
119
|
<Select.TriggerButton classNames='is-full' disabled={disabled} placeholder={placeholder} />
|
|
123
120
|
<Select.Portal>
|
|
@@ -5,12 +5,9 @@
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
7
|
import { type Meta, type StoryObj } from '@storybook/react';
|
|
8
|
-
import { Schema, SchemaAST } from 'effect';
|
|
9
8
|
import React, { useCallback, useState } from 'react';
|
|
10
9
|
|
|
11
|
-
import {
|
|
12
|
-
import { type BaseObject, Expando, Format, getDXN, Ref, type TypeAnnotation } from '@dxos/echo-schema';
|
|
13
|
-
import { live } from '@dxos/live-object';
|
|
10
|
+
import { AST, type BaseObject, Format, S } from '@dxos/echo-schema';
|
|
14
11
|
import { Testing } from '@dxos/schema/testing';
|
|
15
12
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
16
13
|
|
|
@@ -19,27 +16,25 @@ import { Form, type FormProps } from './Form';
|
|
|
19
16
|
import translations from '../../translations';
|
|
20
17
|
import { TestLayout, TestPanel } from '../testing';
|
|
21
18
|
|
|
22
|
-
const AddressSchema =
|
|
23
|
-
street:
|
|
24
|
-
city:
|
|
25
|
-
zip:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
location: Schema.optional(Format.GeoPoint.annotations({ [SchemaAST.TitleAnnotationId]: 'Location' })),
|
|
29
|
-
}).annotations({ [SchemaAST.TitleAnnotationId]: 'Address' });
|
|
19
|
+
const AddressSchema = S.Struct({
|
|
20
|
+
street: S.optional(S.String.annotations({ [AST.TitleAnnotationId]: 'Street' })),
|
|
21
|
+
city: S.optional(S.String.annotations({ [AST.TitleAnnotationId]: 'City' })),
|
|
22
|
+
zip: S.optional(S.String.pipe(S.pattern(/^\d{5}(-\d{4})?$/)).annotations({ [AST.TitleAnnotationId]: 'ZIP' })),
|
|
23
|
+
location: S.optional(Format.GeoPoint.annotations({ [AST.TitleAnnotationId]: 'Location' })),
|
|
24
|
+
}).annotations({ [AST.TitleAnnotationId]: 'Address' });
|
|
30
25
|
|
|
31
26
|
// TODO(burdon): Translations?
|
|
32
|
-
const TestSchema =
|
|
33
|
-
name:
|
|
34
|
-
active:
|
|
35
|
-
rank:
|
|
36
|
-
website:
|
|
37
|
-
address:
|
|
38
|
-
}).pipe(
|
|
27
|
+
const TestSchema = S.Struct({
|
|
28
|
+
name: S.optional(S.String.annotations({ [AST.TitleAnnotationId]: 'Name' })),
|
|
29
|
+
active: S.optional(S.Boolean.annotations({ [AST.TitleAnnotationId]: 'Active' })),
|
|
30
|
+
rank: S.optional(S.Number.annotations({ [AST.TitleAnnotationId]: 'Rank' })),
|
|
31
|
+
website: S.optional(Format.URL.annotations({ [AST.TitleAnnotationId]: 'Website' })),
|
|
32
|
+
address: S.optional(AddressSchema),
|
|
33
|
+
}).pipe(S.mutable);
|
|
39
34
|
|
|
40
|
-
type TestType =
|
|
35
|
+
type TestType = S.Schema.Type<typeof TestSchema>;
|
|
41
36
|
|
|
42
|
-
type StoryProps<T extends BaseObject> = { schema:
|
|
37
|
+
type StoryProps<T extends BaseObject> = { schema: S.Schema<T> } & FormProps<T>;
|
|
43
38
|
|
|
44
39
|
const DefaultStory = <T extends BaseObject>({ schema, values: initialValues, ...props }: StoryProps<T>) => {
|
|
45
40
|
const [values, setValues] = useState(initialValues);
|
|
@@ -120,24 +115,22 @@ export const OrganizationAutoSave: Story<Testing.Organization> = {
|
|
|
120
115
|
// TODO(burdon): Move into separate storybook and use test types.
|
|
121
116
|
//
|
|
122
117
|
|
|
123
|
-
const ShapeSchema =
|
|
124
|
-
shape:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
type:
|
|
128
|
-
radius:
|
|
118
|
+
const ShapeSchema = S.Struct({
|
|
119
|
+
shape: S.optional(
|
|
120
|
+
S.Union(
|
|
121
|
+
S.Struct({
|
|
122
|
+
type: S.Literal('circle').annotations({ [AST.TitleAnnotationId]: 'Type' }),
|
|
123
|
+
radius: S.optional(S.Number.annotations({ [AST.TitleAnnotationId]: 'Radius' })),
|
|
129
124
|
}),
|
|
130
|
-
|
|
131
|
-
type:
|
|
132
|
-
size:
|
|
133
|
-
Schema.Number.pipe(Schema.nonNegative()).annotations({ [SchemaAST.TitleAnnotationId]: 'Size' }),
|
|
134
|
-
),
|
|
125
|
+
S.Struct({
|
|
126
|
+
type: S.Literal('square').annotations({ [AST.TitleAnnotationId]: 'Type' }),
|
|
127
|
+
size: S.optional(S.Number.pipe(S.nonNegative()).annotations({ [AST.TitleAnnotationId]: 'Size' })),
|
|
135
128
|
}),
|
|
136
|
-
).annotations({ [
|
|
129
|
+
).annotations({ [AST.TitleAnnotationId]: 'Shape' }),
|
|
137
130
|
),
|
|
138
|
-
}).pipe(
|
|
131
|
+
}).pipe(S.mutable);
|
|
139
132
|
|
|
140
|
-
type ShapeType =
|
|
133
|
+
type ShapeType = S.Schema.Type<typeof ShapeSchema>;
|
|
141
134
|
|
|
142
135
|
type DiscriminatedUnionStoryProps = FormProps<ShapeType>;
|
|
143
136
|
|
|
@@ -183,12 +176,12 @@ export const DiscriminatedShape: StoryObj<DiscriminatedUnionStoryProps> = {
|
|
|
183
176
|
},
|
|
184
177
|
};
|
|
185
178
|
|
|
186
|
-
const ArraysSchema =
|
|
187
|
-
names:
|
|
188
|
-
addresses:
|
|
189
|
-
}).pipe(
|
|
179
|
+
const ArraysSchema = S.Struct({
|
|
180
|
+
names: S.Array(S.String.pipe(S.nonEmptyString())),
|
|
181
|
+
addresses: S.Array(AddressSchema),
|
|
182
|
+
}).pipe(S.mutable);
|
|
190
183
|
|
|
191
|
-
type ArraysType =
|
|
184
|
+
type ArraysType = S.Schema.Type<typeof ArraysSchema>;
|
|
192
185
|
|
|
193
186
|
const ArraysStory = ({ values: initialValues }: FormProps<ArraysType>) => {
|
|
194
187
|
const [values, setValues] = useState(initialValues);
|
|
@@ -215,13 +208,13 @@ export const Arrays: StoryObj<FormProps<ArraysType>> = {
|
|
|
215
208
|
},
|
|
216
209
|
};
|
|
217
210
|
|
|
218
|
-
const ColorSchema =
|
|
219
|
-
color:
|
|
220
|
-
[
|
|
211
|
+
const ColorSchema = S.Struct({
|
|
212
|
+
color: S.Union(S.Literal('red'), S.Literal('green'), S.Literal('blue')).annotations({
|
|
213
|
+
[AST.TitleAnnotationId]: 'Color',
|
|
221
214
|
}),
|
|
222
|
-
}).pipe(
|
|
215
|
+
}).pipe(S.mutable);
|
|
223
216
|
|
|
224
|
-
type ColorType =
|
|
217
|
+
type ColorType = S.Schema.Type<typeof ColorSchema>;
|
|
225
218
|
|
|
226
219
|
const EnumStory = ({ values: initialValues }: FormProps<ColorType>) => {
|
|
227
220
|
const [values, setValues] = useState(initialValues);
|
|
@@ -246,44 +239,3 @@ export const Enum: StoryObj<FormProps<ColorType>> = {
|
|
|
246
239
|
},
|
|
247
240
|
},
|
|
248
241
|
};
|
|
249
|
-
|
|
250
|
-
const RefSchema = Schema.Struct({
|
|
251
|
-
contact: Ref(ContactType).annotations({ title: 'Contact Reference' }),
|
|
252
|
-
optionalContact: Schema.optional(Ref(ContactType).annotations({ title: 'Optional Contact Reference' })),
|
|
253
|
-
unknownExpando: Schema.optional(Ref(Expando).annotations({ title: 'Optional Ref to an Expando (DXN Input)' })),
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
const RefStory = ({ values: initialValues }: FormProps<any>) => {
|
|
257
|
-
const [values, setValues] = useState(initialValues);
|
|
258
|
-
const handleSave = useCallback<NonNullable<FormProps<any>['onSave']>>((values) => {
|
|
259
|
-
setValues(values);
|
|
260
|
-
}, []);
|
|
261
|
-
|
|
262
|
-
const contact1 = live(ContactType, { identifiers: [] });
|
|
263
|
-
const contact2 = live(ContactType, { identifiers: [] });
|
|
264
|
-
|
|
265
|
-
const onQueryRefOptions = useCallback((typeInfo: TypeAnnotation) => {
|
|
266
|
-
switch (typeInfo.typename) {
|
|
267
|
-
case ContactType.typename:
|
|
268
|
-
return [
|
|
269
|
-
{ dxn: getDXN(contact1)!, label: 'John Coltraine' },
|
|
270
|
-
{ dxn: getDXN(contact2)!, label: 'Erykah Badu' },
|
|
271
|
-
];
|
|
272
|
-
default:
|
|
273
|
-
return [];
|
|
274
|
-
}
|
|
275
|
-
}, []);
|
|
276
|
-
|
|
277
|
-
return (
|
|
278
|
-
<TestLayout json={{ values, schema: RefSchema.ast.toJSON() }}>
|
|
279
|
-
<TestPanel>
|
|
280
|
-
<Form schema={RefSchema} values={values} onSave={handleSave} onQueryRefOptions={onQueryRefOptions} />
|
|
281
|
-
</TestPanel>
|
|
282
|
-
</TestLayout>
|
|
283
|
-
);
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
export const Refs: StoryObj<FormProps<ContactType>> = {
|
|
287
|
-
render: RefStory,
|
|
288
|
-
args: { values: {} },
|
|
289
|
-
};
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { type Schema } from 'effect';
|
|
6
5
|
import React, { type ReactElement, useEffect, useMemo, useRef } from 'react';
|
|
7
6
|
|
|
8
|
-
import { type BaseObject, type PropertyKey } from '@dxos/echo-schema';
|
|
7
|
+
import { type BaseObject, type S, type PropertyKey } from '@dxos/echo-schema';
|
|
9
8
|
import { type ThemedClassName } from '@dxos/react-ui';
|
|
10
9
|
import { mx } from '@dxos/react-ui-theme';
|
|
11
10
|
import { type SchemaProperty } from '@dxos/schema';
|
|
@@ -14,14 +13,13 @@ import { FormActions } from './FormActions';
|
|
|
14
13
|
import { FormFields } from './FormContent';
|
|
15
14
|
import { FormProvider } from './FormContext';
|
|
16
15
|
import { type InputProps, type InputComponent } from './Input';
|
|
17
|
-
import { type QueryRefOptions } from './RefField';
|
|
18
16
|
import { type FormOptions } from '../../hooks';
|
|
19
17
|
|
|
20
18
|
export type PropsFilter<T extends BaseObject> = (props: SchemaProperty<T>[]) => SchemaProperty<T>[];
|
|
21
19
|
|
|
22
20
|
export type ComponentLookup = (args: {
|
|
23
21
|
prop: string;
|
|
24
|
-
schema:
|
|
22
|
+
schema: S.Schema<any>;
|
|
25
23
|
inputProps: InputProps;
|
|
26
24
|
}) => ReactElement | undefined;
|
|
27
25
|
|
|
@@ -43,7 +41,6 @@ export type FormProps<T extends BaseObject> = ThemedClassName<
|
|
|
43
41
|
autoSave?: boolean;
|
|
44
42
|
testId?: string;
|
|
45
43
|
onCancel?: () => void;
|
|
46
|
-
onQueryRefOptions?: QueryRefOptions;
|
|
47
44
|
lookupComponent?: ComponentLookup;
|
|
48
45
|
/**
|
|
49
46
|
* Map of custom renderers for specific properties.
|
|
@@ -68,7 +65,6 @@ export const Form = <T extends BaseObject>({
|
|
|
68
65
|
onValidate,
|
|
69
66
|
onSave,
|
|
70
67
|
onCancel,
|
|
71
|
-
onQueryRefOptions,
|
|
72
68
|
lookupComponent,
|
|
73
69
|
Custom,
|
|
74
70
|
}: FormProps<T>) => {
|
|
@@ -104,7 +100,6 @@ export const Form = <T extends BaseObject>({
|
|
|
104
100
|
readonly={readonly}
|
|
105
101
|
filter={filter}
|
|
106
102
|
sort={sort}
|
|
107
|
-
onQueryRefOptions={onQueryRefOptions}
|
|
108
103
|
lookupComponent={lookupComponent}
|
|
109
104
|
Custom={Custom}
|
|
110
105
|
/>
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
//
|
|
2
|
-
// Copyright
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { pipe } from 'effect';
|
|
6
6
|
import { capitalize } from 'effect/String';
|
|
7
7
|
import React, { useMemo } from 'react';
|
|
8
8
|
|
|
9
|
+
import { AST, S } from '@dxos/echo-schema';
|
|
9
10
|
import { createJsonPath, findNode, getDiscriminatedType, isDiscriminatedUnion } from '@dxos/effect';
|
|
10
11
|
import { mx } from '@dxos/react-ui-theme';
|
|
11
12
|
import { getSchemaProperties, type SchemaProperty } from '@dxos/schema';
|
|
@@ -16,29 +17,29 @@ import { SelectInput } from './Defaults';
|
|
|
16
17
|
import { type ComponentLookup } from './Form';
|
|
17
18
|
import { useInputProps, useFormValues } from './FormContext';
|
|
18
19
|
import { type InputComponent } from './Input';
|
|
19
|
-
import { RefField, type QueryRefOptions } from './RefField';
|
|
20
20
|
import { getInputComponent } from './factory';
|
|
21
21
|
|
|
22
|
+
export type FormContentProps = {
|
|
23
|
+
schema: S.Schema.All;
|
|
24
|
+
path?: (string | number)[];
|
|
25
|
+
filter?: (props: SchemaProperty<any>[]) => SchemaProperty<any>[];
|
|
26
|
+
sort?: string[];
|
|
27
|
+
readonly?: boolean;
|
|
28
|
+
lookupComponent?: ComponentLookup;
|
|
29
|
+
Custom?: Partial<Record<string, InputComponent>>;
|
|
30
|
+
};
|
|
31
|
+
|
|
22
32
|
export type FormFieldProps = {
|
|
23
33
|
property: SchemaProperty<any>;
|
|
24
34
|
path?: (string | number)[];
|
|
25
35
|
readonly?: boolean;
|
|
26
36
|
/** Used to indicate if input should be presented inline (e.g. for array items). */
|
|
27
37
|
inline?: boolean;
|
|
28
|
-
onQueryRefOptions?: QueryRefOptions;
|
|
29
38
|
lookupComponent?: ComponentLookup;
|
|
30
39
|
Custom?: Partial<Record<string, InputComponent>>;
|
|
31
40
|
};
|
|
32
41
|
|
|
33
|
-
export const FormField = ({
|
|
34
|
-
property,
|
|
35
|
-
path,
|
|
36
|
-
readonly,
|
|
37
|
-
inline,
|
|
38
|
-
onQueryRefOptions,
|
|
39
|
-
lookupComponent,
|
|
40
|
-
Custom,
|
|
41
|
-
}: FormFieldProps) => {
|
|
42
|
+
export const FormField = ({ property, path, readonly, inline, lookupComponent, Custom }: FormFieldProps) => {
|
|
42
43
|
const inputProps = useInputProps(path);
|
|
43
44
|
const { ast, name, type, format, title, description, options, examples, array } = property;
|
|
44
45
|
|
|
@@ -50,7 +51,7 @@ export const FormField = ({
|
|
|
50
51
|
|
|
51
52
|
const FoundComponent = lookupComponent?.({
|
|
52
53
|
prop: name,
|
|
53
|
-
schema:
|
|
54
|
+
schema: S.make(ast),
|
|
54
55
|
inputProps: {
|
|
55
56
|
type,
|
|
56
57
|
format,
|
|
@@ -119,40 +120,17 @@ export const FormField = ({
|
|
|
119
120
|
);
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
// TODO(ZaymonFC): Extract this to it's own component.
|
|
123
|
-
if (format === 'ref') {
|
|
124
|
-
return (
|
|
125
|
-
<RefField
|
|
126
|
-
ast={ast}
|
|
127
|
-
type={type}
|
|
128
|
-
label={label}
|
|
129
|
-
readonly={readonly}
|
|
130
|
-
placeholder={placeholder}
|
|
131
|
-
inline={inline}
|
|
132
|
-
onQueryRefOptions={onQueryRefOptions}
|
|
133
|
-
inputProps={inputProps}
|
|
134
|
-
/>
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
123
|
if (type === 'object') {
|
|
139
124
|
const baseNode = findNode(ast, isDiscriminatedUnion);
|
|
140
125
|
const typeLiteral = baseNode
|
|
141
126
|
? getDiscriminatedType(baseNode, inputProps.getValue() as any)
|
|
142
|
-
: findNode(ast,
|
|
127
|
+
: findNode(ast, AST.isTypeLiteral);
|
|
143
128
|
|
|
144
129
|
if (typeLiteral) {
|
|
145
130
|
return (
|
|
146
131
|
<div role='none'>
|
|
147
|
-
{!inline && <
|
|
148
|
-
<FormFields
|
|
149
|
-
schema={Schema.make(typeLiteral)}
|
|
150
|
-
path={path}
|
|
151
|
-
readonly={readonly}
|
|
152
|
-
onQueryRefOptions={onQueryRefOptions}
|
|
153
|
-
Custom={Custom}
|
|
154
|
-
lookupComponent={lookupComponent}
|
|
155
|
-
/>
|
|
132
|
+
{!inline && <div>{label}</div>}
|
|
133
|
+
<FormFields schema={S.make(typeLiteral)} path={path} readonly={readonly} Custom={Custom} />
|
|
156
134
|
</div>
|
|
157
135
|
);
|
|
158
136
|
}
|
|
@@ -161,27 +139,7 @@ export const FormField = ({
|
|
|
161
139
|
return null;
|
|
162
140
|
};
|
|
163
141
|
|
|
164
|
-
export
|
|
165
|
-
schema: Schema.Schema.All;
|
|
166
|
-
path?: (string | number)[];
|
|
167
|
-
filter?: (props: SchemaProperty<any>[]) => SchemaProperty<any>[];
|
|
168
|
-
sort?: string[];
|
|
169
|
-
readonly?: boolean;
|
|
170
|
-
onQueryRefOptions?: QueryRefOptions;
|
|
171
|
-
lookupComponent?: ComponentLookup;
|
|
172
|
-
Custom?: Partial<Record<string, InputComponent>>;
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
export const FormFields = ({
|
|
176
|
-
schema,
|
|
177
|
-
path,
|
|
178
|
-
filter,
|
|
179
|
-
sort,
|
|
180
|
-
readonly,
|
|
181
|
-
onQueryRefOptions,
|
|
182
|
-
lookupComponent,
|
|
183
|
-
Custom,
|
|
184
|
-
}: FormContentProps) => {
|
|
142
|
+
export const FormFields = ({ schema, path, filter, sort, readonly, lookupComponent, Custom }: FormContentProps) => {
|
|
185
143
|
const values = useFormValues(path);
|
|
186
144
|
|
|
187
145
|
const properties = useMemo(() => {
|
|
@@ -201,7 +159,6 @@ export const FormFields = ({
|
|
|
201
159
|
property={property}
|
|
202
160
|
path={[...(path ?? []), property.name]}
|
|
203
161
|
readonly={readonly}
|
|
204
|
-
onQueryRefOptions={onQueryRefOptions}
|
|
205
162
|
lookupComponent={lookupComponent}
|
|
206
163
|
Custom={Custom}
|
|
207
164
|
/>
|
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
7
|
import { type Meta, type StoryObj } from '@storybook/react';
|
|
8
|
-
import { Schema } from 'effect';
|
|
9
8
|
import React, { useState } from 'react';
|
|
10
9
|
|
|
11
|
-
import { SelectOptionSchema, type SelectOption } from '@dxos/echo-schema';
|
|
10
|
+
import { S, SelectOptionSchema, type SelectOption } from '@dxos/echo-schema';
|
|
12
11
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
13
12
|
|
|
14
13
|
import { SelectOptionInput } from './SelectOptionsInput';
|
|
@@ -16,9 +15,9 @@ import translations from '../../../translations';
|
|
|
16
15
|
import { TestLayout, TestPanel } from '../../testing';
|
|
17
16
|
import { Form } from '../Form';
|
|
18
17
|
|
|
19
|
-
const schema =
|
|
20
|
-
options:
|
|
21
|
-
}).pipe(
|
|
18
|
+
const schema = S.Struct({
|
|
19
|
+
options: S.Array(SelectOptionSchema).pipe(S.mutable),
|
|
20
|
+
}).pipe(S.mutable);
|
|
22
21
|
|
|
23
22
|
const DefaultStory = () => {
|
|
24
23
|
const [values, setValues] = useState<{ options: SelectOption[] }>({
|
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
7
|
import { type Meta, type StoryObj } from '@storybook/react';
|
|
8
|
-
import { Schema } from 'effect';
|
|
9
8
|
import React, { useCallback, useMemo, useState } from 'react';
|
|
10
9
|
|
|
11
|
-
import { Format, type EchoSchema, toJsonSchema, TypedObject } from '@dxos/echo-schema';
|
|
10
|
+
import { Format, type EchoSchema, S, toJsonSchema, TypedObject } from '@dxos/echo-schema';
|
|
12
11
|
import { Filter, useQuery, useSpace } from '@dxos/react-client/echo';
|
|
13
12
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
14
13
|
import { useAsyncEffect } from '@dxos/react-ui';
|
|
@@ -27,7 +26,7 @@ const DefaultStory = () => {
|
|
|
27
26
|
useAsyncEffect(async () => {
|
|
28
27
|
if (space) {
|
|
29
28
|
class TestSchema extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
|
|
30
|
-
name:
|
|
29
|
+
name: S.String,
|
|
31
30
|
email: Format.Email,
|
|
32
31
|
salary: Format.Currency(),
|
|
33
32
|
}) {}
|
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Schema, SchemaAST } from 'effect';
|
|
6
5
|
import React, { useCallback, useMemo, useState } from 'react';
|
|
7
6
|
|
|
8
7
|
import { type SchemaRegistry } from '@dxos/echo-db';
|
|
9
|
-
import { Format, type BaseSchema, type JsonProp } from '@dxos/echo-schema';
|
|
8
|
+
import { AST, Format, type BaseSchema, S, type JsonProp } from '@dxos/echo-schema';
|
|
10
9
|
import { invariant } from '@dxos/invariant';
|
|
11
10
|
import { IconButton, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
12
11
|
import { List } from '@dxos/react-ui-list';
|
|
@@ -19,16 +18,16 @@ import { Form } from '../Form';
|
|
|
19
18
|
|
|
20
19
|
const grid = 'grid grid-cols-[32px_1fr_32px] min-bs-[2.5rem]';
|
|
21
20
|
|
|
22
|
-
const ViewMetaSchema =
|
|
23
|
-
name:
|
|
24
|
-
[
|
|
21
|
+
const ViewMetaSchema = S.Struct({
|
|
22
|
+
name: S.String.annotations({
|
|
23
|
+
[AST.TitleAnnotationId]: 'View',
|
|
25
24
|
}),
|
|
26
25
|
typename: Format.URL.annotations({
|
|
27
|
-
[
|
|
26
|
+
[AST.TitleAnnotationId]: 'Typename',
|
|
28
27
|
}),
|
|
29
|
-
}).pipe(
|
|
28
|
+
}).pipe(S.mutable);
|
|
30
29
|
|
|
31
|
-
type ViewMetaType =
|
|
30
|
+
type ViewMetaType = S.Schema.Type<typeof ViewMetaSchema>;
|
|
32
31
|
|
|
33
32
|
export type ViewEditorProps = ThemedClassName<{
|
|
34
33
|
schema: BaseSchema;
|
|
@@ -140,16 +139,15 @@ export const ViewEditor = ({
|
|
|
140
139
|
);
|
|
141
140
|
|
|
142
141
|
return (
|
|
143
|
-
<div role='none' className={mx('
|
|
142
|
+
<div role='none' className={mx('flex flex-col w-full divide-y divide-separator', classNames)}>
|
|
144
143
|
<Form<ViewMetaType>
|
|
145
144
|
autoSave
|
|
146
145
|
schema={ViewMetaSchema}
|
|
147
146
|
values={viewValues}
|
|
148
147
|
onSave={schema.readonly ? undefined : handleUpdate}
|
|
149
|
-
classNames='min-bs-0 overflow-y-auto'
|
|
150
148
|
/>
|
|
151
149
|
|
|
152
|
-
<div
|
|
150
|
+
<div>
|
|
153
151
|
{/* TODO(burdon): Clean up common form ux. */}
|
|
154
152
|
<div role='none' className='p-2'>
|
|
155
153
|
<label className={mx(inputTextLabel)}>{t('fields label')}</label>
|
|
@@ -157,7 +155,7 @@ export const ViewEditor = ({
|
|
|
157
155
|
|
|
158
156
|
<List.Root<FieldType>
|
|
159
157
|
items={view.fields}
|
|
160
|
-
isItem={
|
|
158
|
+
isItem={S.is(FieldSchema)}
|
|
161
159
|
getId={(field) => field.id}
|
|
162
160
|
onMove={schema.readonly ? undefined : handleMove}
|
|
163
161
|
>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { SchemaAST, type Schema, Option, pipe } from 'effect';
|
|
5
|
+
import { SchemaAST as AST, type Schema as S, Option, pipe } from 'effect';
|
|
6
6
|
import React, { useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { findProperty } from '@dxos/effect';
|
|
@@ -13,7 +13,7 @@ import { type ViewType, getFieldValue, setFieldValue } from '@dxos/schema';
|
|
|
13
13
|
export type DeprecatedFormProps<T extends {} = {}> = ThemedClassName<{
|
|
14
14
|
view: ViewType;
|
|
15
15
|
object: T;
|
|
16
|
-
schema?:
|
|
16
|
+
schema?: S.Schema<T>;
|
|
17
17
|
readonly?: boolean;
|
|
18
18
|
}>;
|
|
19
19
|
|
|
@@ -31,16 +31,16 @@ export const DeprecatedForm = <T extends {} = {}>({
|
|
|
31
31
|
<div role='none' className={mx('flex flex-col w-full gap-2 p-2', classNames)}>
|
|
32
32
|
{view.fields.map((field) => {
|
|
33
33
|
const prop = schema && findProperty(schema, field.path);
|
|
34
|
-
const title = (prop && pipe(
|
|
35
|
-
const description = (prop && pipe(
|
|
34
|
+
const title = (prop && pipe(AST.getTitleAnnotation(prop), Option.getOrUndefined)) ?? '';
|
|
35
|
+
const description = (prop && pipe(AST.getDescriptionAnnotation(prop), Option.getOrUndefined)) ?? title;
|
|
36
36
|
// const format =
|
|
37
|
-
// (prop && (getPatternAnnotation(prop) ?? (
|
|
37
|
+
// (prop && (getPatternAnnotation(prop) ?? (AST.isNumberKeyword(prop) && RealNumberFormat))) || undefined;
|
|
38
38
|
|
|
39
39
|
//
|
|
40
40
|
// Boolean
|
|
41
41
|
//
|
|
42
42
|
|
|
43
|
-
if (prop &&
|
|
43
|
+
if (prop && AST.isBooleanKeyword(prop)) {
|
|
44
44
|
const value = object && getFieldValue(object, field);
|
|
45
45
|
const onChange = (checked: boolean) => {
|
|
46
46
|
setFieldValue(object, field, checked);
|
package/src/hooks/index.ts
CHANGED
package/src/hooks/useForm.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { SchemaAST, type Schema } from 'effect';
|
|
5
|
+
import { SchemaAST as AST, type Schema as S } from 'effect';
|
|
6
6
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { type BaseObject, getValue, setValue } from '@dxos/echo-schema';
|
|
@@ -45,7 +45,7 @@ export interface FormOptions<T extends BaseObject> {
|
|
|
45
45
|
/**
|
|
46
46
|
* Effect schema (Type literal).
|
|
47
47
|
*/
|
|
48
|
-
schema:
|
|
48
|
+
schema: S.Schema<T>;
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* Initial values (which may not pass validation).
|
|
@@ -90,7 +90,7 @@ export const useForm = <T extends BaseObject>({
|
|
|
90
90
|
onValid,
|
|
91
91
|
onSave,
|
|
92
92
|
}: FormOptions<T>): FormHandler<T> => {
|
|
93
|
-
invariant(
|
|
93
|
+
invariant(AST.isTypeLiteral(schema.ast));
|
|
94
94
|
|
|
95
95
|
const [values, setValues] = useState<Partial<T>>(initialValues);
|
|
96
96
|
useEffect(() => {
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { type SchemaAST } from 'effect';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { type TypeAnnotation } from '@dxos/echo-schema';
|
|
4
|
-
import { type SimpleType } from '@dxos/effect';
|
|
5
|
-
import { DXN } from '@dxos/keys';
|
|
6
|
-
import { type MaybePromise } from '@dxos/util';
|
|
7
|
-
import { type FormInputStateProps } from './FormContext';
|
|
8
|
-
export type QueryRefOptions = (type: TypeAnnotation) => MaybePromise<{
|
|
9
|
-
dxn: DXN;
|
|
10
|
-
label?: string;
|
|
11
|
-
}[]>;
|
|
12
|
-
type RefFieldProps = {
|
|
13
|
-
ast: SchemaAST.AST;
|
|
14
|
-
type: SimpleType;
|
|
15
|
-
label: string;
|
|
16
|
-
readonly?: boolean;
|
|
17
|
-
placeholder?: string;
|
|
18
|
-
inline?: boolean;
|
|
19
|
-
onQueryRefOptions?: QueryRefOptions;
|
|
20
|
-
inputProps: FormInputStateProps;
|
|
21
|
-
};
|
|
22
|
-
export declare const RefField: ({ ast, type, label, readonly, placeholder, inline, onQueryRefOptions, inputProps, }: RefFieldProps) => React.JSX.Element | null;
|
|
23
|
-
export {};
|
|
24
|
-
//# sourceMappingURL=RefField.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"RefField.d.ts","sourceRoot":"","sources":["../../../../../src/components/Form/RefField.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,OAAO,EAML,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAGjC,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEzD,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,cAAc,KAAK,YAAY,CAAC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAAC;AAErG,KAAK,aAAa,GAAG;IACnB,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC;IACnB,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iBAAiB,CAAC,EAAE,eAAe,CAAC;IACpC,UAAU,EAAE,mBAAmB,CAAC;CACjC,CAAC;AAEF,eAAO,MAAM,QAAQ,wFASlB,aAAa,6BAyGf,CAAC"}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { type Space } from '@dxos/react-client/echo';
|
|
2
|
-
import { type QueryRefOptions } from '../components';
|
|
3
|
-
type UseRefQueryLookupProps = {
|
|
4
|
-
space?: Space;
|
|
5
|
-
};
|
|
6
|
-
export declare const useRefQueryLookupHandler: ({ space }: UseRefQueryLookupProps) => QueryRefOptions;
|
|
7
|
-
export {};
|
|
8
|
-
//# sourceMappingURL=useRefQueryLookupHandler.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useRefQueryLookupHandler.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useRefQueryLookupHandler.ts"],"names":[],"mappings":"AAOA,OAAO,EAAU,KAAK,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAG7D,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,KAAK,sBAAsB,GAAG;IAC5B,KAAK,CAAC,EAAE,KAAK,CAAC;CACf,CAAC;AAEF,eAAO,MAAM,wBAAwB,cAAe,sBAAsB,KAAG,eA0B5E,CAAC"}
|