@dxos/react-ui-form 0.8.4-main.bc674ce → 0.8.4-main.bcb3aa67d6
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/README.md +0 -1
- package/dist/lib/browser/index.mjs +620 -583
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +620 -583
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +3 -3
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +61 -61
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +18 -15
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +67 -67
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts +7 -1
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +0 -1
- package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +2 -2
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldSet.d.ts +9 -10
- package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/RefField.d.ts +7 -5
- package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +61 -61
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +10 -2
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +61 -61
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/Settings/Settings.d.ts +39 -0
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
- package/dist/types/src/components/Settings/Settings.stories.d.ts +81 -0
- package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
- package/dist/types/src/components/Settings/index.d.ts +2 -0
- package/dist/types/src/components/Settings/index.d.ts.map +1 -0
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +5 -5
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +65 -64
- 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.map +1 -1
- package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +61 -61
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +0 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/refs.d.ts +1 -0
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +36 -38
- package/src/components/FieldEditor/FieldEditor.stories.tsx +4 -6
- package/src/components/FieldEditor/FieldEditor.tsx +3 -4
- package/src/components/Form/AUDIT.md +17 -0
- package/src/components/Form/Form.stories.tsx +23 -24
- package/src/components/Form/Form.tsx +58 -65
- package/src/components/Form/FormField.tsx +27 -8
- package/src/components/Form/FormFieldComponent.stories.tsx +5 -5
- package/src/components/Form/FormFieldComponent.tsx +17 -20
- package/src/components/Form/FormFieldSet.tsx +108 -104
- package/src/components/Form/fields/ArrayField.tsx +11 -8
- package/src/components/Form/fields/GeoPointField.tsx +6 -6
- package/src/components/Form/fields/MarkdownField.tsx +1 -1
- package/src/components/Form/fields/RefField.tsx +35 -17
- package/src/components/Form/fields/SelectField.tsx +1 -1
- package/src/components/Form/fields/SelectOptionField.stories.tsx +2 -2
- package/src/components/Form/fields/SelectOptionField.tsx +9 -10
- package/src/components/Form/fields/TupleField.tsx +1 -1
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +10 -14
- package/src/components/ObjectPicker/ObjectPicker.tsx +71 -60
- package/src/components/Settings/Settings.stories.tsx +56 -0
- package/src/components/Settings/Settings.tsx +129 -0
- package/src/components/{ControlSection → Settings}/index.ts +1 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +22 -20
- package/src/components/ViewEditor/ViewEditor.test.tsx +3 -6
- package/src/components/ViewEditor/ViewEditor.tsx +99 -70
- package/src/components/index.ts +1 -1
- package/src/components/testing.tsx +20 -8
- package/src/hooks/useFormHandler.ts +42 -24
- package/src/translations.ts +62 -63
- package/src/util/index.ts +0 -1
- package/src/util/refs.ts +7 -3
- package/dist/types/src/components/ControlSection/ControlSection.d.ts +0 -23
- package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +0 -87
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
- package/dist/types/src/components/ControlSection/index.d.ts +0 -2
- package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
- package/dist/types/src/components/Form/Form-old.stories.d.ts +0 -131
- package/dist/types/src/components/Form/Form-old.stories.d.ts.map +0 -1
- package/dist/types/src/util/echo.d.ts +0 -6
- package/dist/types/src/util/echo.d.ts.map +0 -1
- package/src/components/ControlSection/ControlSection.stories.tsx +0 -38
- package/src/components/ControlSection/ControlSection.tsx +0 -153
- package/src/components/Form/Form-old.stories.tsx +0 -314
- package/src/util/echo.ts +0 -25
|
@@ -78,12 +78,19 @@ export type FormFieldProps = {
|
|
|
78
78
|
* Function to lookup custom renderers for specific properties.
|
|
79
79
|
*/
|
|
80
80
|
fieldProvider?: FormFieldProvider;
|
|
81
|
+
/**
|
|
82
|
+
* Typename of the ref type that the create props apply to.
|
|
83
|
+
* When set, createOptionLabel/createOptionIcon/createInitialValuePath/createFieldMap/onCreate
|
|
84
|
+
* are only passed to ref fields whose typename matches.
|
|
85
|
+
*/
|
|
86
|
+
createTypename?: string;
|
|
81
87
|
} & Pick<FormFieldComponentProps, 'autoFocus' | 'readonly' | 'layout'> &
|
|
82
88
|
Pick<
|
|
83
89
|
RefFieldProps,
|
|
84
90
|
| 'createOptionLabel'
|
|
85
91
|
| 'createOptionIcon'
|
|
86
92
|
| 'createInitialValuePath'
|
|
93
|
+
| 'createFieldMap'
|
|
87
94
|
| 'db'
|
|
88
95
|
| 'schemaHook'
|
|
89
96
|
| 'getOptions'
|
|
@@ -102,9 +109,11 @@ export const FormField = (props: FormFieldProps) => {
|
|
|
102
109
|
layout,
|
|
103
110
|
|
|
104
111
|
// RefFieldProps
|
|
112
|
+
createTypename,
|
|
105
113
|
createOptionLabel,
|
|
106
114
|
createOptionIcon,
|
|
107
115
|
createInitialValuePath,
|
|
116
|
+
createFieldMap,
|
|
108
117
|
db,
|
|
109
118
|
schemaHook,
|
|
110
119
|
getOptions: getRefOptions,
|
|
@@ -117,7 +126,7 @@ export const FormField = (props: FormFieldProps) => {
|
|
|
117
126
|
|
|
118
127
|
const label = useMemo(() => title ?? String.capitalize(name), [title, name]);
|
|
119
128
|
const placeholder = useMemo(
|
|
120
|
-
() => (examples?.length ? `${t('example
|
|
129
|
+
() => (examples?.length ? `${t('example.placeholder')}: ${examples[0]}` : (description ?? label)),
|
|
121
130
|
[examples, description, label],
|
|
122
131
|
);
|
|
123
132
|
|
|
@@ -171,13 +180,21 @@ export const FormField = (props: FormFieldProps) => {
|
|
|
171
180
|
|
|
172
181
|
const options = getOptions(type);
|
|
173
182
|
if (options) {
|
|
183
|
+
// Resolve labels from projection metadata when available.
|
|
184
|
+
const fieldProjections = projection?.getFieldProjections();
|
|
185
|
+
const fieldProjection = fieldProjections?.find((fp) => fp.field.path === name);
|
|
186
|
+
const selectOptions = fieldProjection?.props.options;
|
|
187
|
+
|
|
174
188
|
return (
|
|
175
189
|
<SelectField
|
|
176
190
|
{...fieldProps}
|
|
177
|
-
options={options.map((option) =>
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
191
|
+
options={options.map((option) => {
|
|
192
|
+
const selectOption = selectOptions?.find((so) => so.id === globalThis.String(option));
|
|
193
|
+
return {
|
|
194
|
+
value: option,
|
|
195
|
+
label: selectOption?.title ?? option.toString(),
|
|
196
|
+
};
|
|
197
|
+
})}
|
|
181
198
|
/>
|
|
182
199
|
);
|
|
183
200
|
}
|
|
@@ -188,13 +205,15 @@ export const FormField = (props: FormFieldProps) => {
|
|
|
188
205
|
|
|
189
206
|
const refProps = getRefProps(type);
|
|
190
207
|
if (refProps) {
|
|
208
|
+
const isCreateTarget = !createTypename || refProps.typename === createTypename;
|
|
191
209
|
return (
|
|
192
210
|
<RefField
|
|
193
211
|
{...fieldProps}
|
|
194
212
|
{...refProps}
|
|
195
|
-
createOptionLabel={createOptionLabel}
|
|
196
|
-
createOptionIcon={createOptionIcon}
|
|
197
|
-
createInitialValuePath={createInitialValuePath}
|
|
213
|
+
createOptionLabel={isCreateTarget ? createOptionLabel : undefined}
|
|
214
|
+
createOptionIcon={isCreateTarget ? createOptionIcon : undefined}
|
|
215
|
+
createInitialValuePath={isCreateTarget ? createInitialValuePath : undefined}
|
|
216
|
+
createFieldMap={isCreateTarget ? createFieldMap : undefined}
|
|
198
217
|
db={db}
|
|
199
218
|
schemaHook={schemaHook}
|
|
200
219
|
getOptions={getRefOptions}
|
|
@@ -10,7 +10,7 @@ import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
|
10
10
|
|
|
11
11
|
import { type FormFieldComponentProps, FormFieldWrapper } from './FormFieldComponent';
|
|
12
12
|
|
|
13
|
-
const
|
|
13
|
+
const DefaultStory = ({
|
|
14
14
|
type,
|
|
15
15
|
placeholder,
|
|
16
16
|
readonly,
|
|
@@ -24,7 +24,7 @@ const Component = ({
|
|
|
24
24
|
);
|
|
25
25
|
|
|
26
26
|
return (
|
|
27
|
-
<div className='
|
|
27
|
+
<div className='p-form-padding'>
|
|
28
28
|
<FormFieldWrapper<string> {...props}>
|
|
29
29
|
{({ value = '' }) => (
|
|
30
30
|
<Input.TextInput
|
|
@@ -41,10 +41,10 @@ const Component = ({
|
|
|
41
41
|
);
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
const meta: Meta<typeof
|
|
44
|
+
const meta: Meta<typeof DefaultStory> = {
|
|
45
45
|
title: 'ui/react-ui-form/FormFieldComponent',
|
|
46
|
-
component:
|
|
47
|
-
decorators: [withTheme, withLayout({ layout: 'column' })],
|
|
46
|
+
component: DefaultStory,
|
|
47
|
+
decorators: [withTheme(), withLayout({ layout: 'column' })],
|
|
48
48
|
parameters: {
|
|
49
49
|
layout: 'fullscreen',
|
|
50
50
|
},
|
|
@@ -15,12 +15,10 @@ import React, {
|
|
|
15
15
|
|
|
16
16
|
import { type Format } from '@dxos/echo/internal';
|
|
17
17
|
import { Icon, Input, Tooltip } from '@dxos/react-ui';
|
|
18
|
-
import {
|
|
18
|
+
import { inputTextLabel } from '@dxos/ui-theme';
|
|
19
19
|
|
|
20
20
|
import { type FormFieldStatus } from '../../hooks';
|
|
21
21
|
|
|
22
|
-
const labelSpacing = 'mbs-inputSpacingBlock mbe-labelSpacingBlock';
|
|
23
|
-
|
|
24
22
|
/**
|
|
25
23
|
* Dynamic props passed to input components.
|
|
26
24
|
*/
|
|
@@ -33,7 +31,6 @@ export type FormFieldStateProps<T = any> = {
|
|
|
33
31
|
|
|
34
32
|
/**
|
|
35
33
|
* Presentation mode.
|
|
36
|
-
*
|
|
37
34
|
* - full: Show label, control, and status.
|
|
38
35
|
* - compact: Show label and control.
|
|
39
36
|
* - inline: Control only.
|
|
@@ -79,14 +76,12 @@ export type FormFieldLabelProps = {
|
|
|
79
76
|
|
|
80
77
|
export const FormFieldLabel = ({ label, error, readonly, asChild }: FormFieldLabelProps) => {
|
|
81
78
|
const Label = readonly || asChild ? 'span' : Input.Label;
|
|
82
|
-
const labelProps = readonly || asChild ? { className: 'text-description text-sm' } : { classNames: '!mlb-0 text-sm' };
|
|
83
|
-
|
|
84
79
|
return (
|
|
85
|
-
<div role='none' className=
|
|
86
|
-
<Label {
|
|
80
|
+
<div role='none' className='flex items-center justify-between'>
|
|
81
|
+
<Label className={inputTextLabel}>{label}</Label>
|
|
87
82
|
{error && (
|
|
88
83
|
<Tooltip.Trigger asChild content={error} side='bottom'>
|
|
89
|
-
<Icon icon='ph--warning--regular' size={4} classNames=
|
|
84
|
+
<Icon icon='ph--warning--regular' size={4} classNames='text-error-text' />
|
|
90
85
|
</Tooltip.Trigger>
|
|
91
86
|
)}
|
|
92
87
|
</div>
|
|
@@ -118,15 +113,17 @@ export const FormFieldWrapper = <T,>(props: FormFieldWrapperProps<T>) => {
|
|
|
118
113
|
const str = String(value ?? '');
|
|
119
114
|
|
|
120
115
|
return (
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
<Input.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
116
|
+
<div role='none' className='contents'>
|
|
117
|
+
<Input.Root validationValence={status}>
|
|
118
|
+
{layout !== 'inline' && <FormFieldLabel error={error} readonly={readonly} label={label} />}
|
|
119
|
+
{layout === 'static' ? <p>{str}</p> : children ? children({ value }) : null}
|
|
120
|
+
{layout === 'full' && (
|
|
121
|
+
<Input.DescriptionAndValidation>
|
|
122
|
+
<Input.Validation>{error}</Input.Validation>
|
|
123
|
+
</Input.DescriptionAndValidation>
|
|
124
|
+
)}
|
|
125
|
+
</Input.Root>
|
|
126
|
+
</div>
|
|
130
127
|
);
|
|
131
128
|
};
|
|
132
129
|
|
|
@@ -158,8 +155,8 @@ export class FormFieldErrorBoundary extends Component<FormFieldErrorBoundaryProp
|
|
|
158
155
|
override render() {
|
|
159
156
|
if (this.state.error) {
|
|
160
157
|
return (
|
|
161
|
-
<div className='flex gap-2 border border-
|
|
162
|
-
<span className='bg-
|
|
158
|
+
<div className='flex gap-2 border border-rose-fill font-mono text-sm'>
|
|
159
|
+
<span className='bg-rose-fill text-surface-text px-1 font-thin'>ERROR</span>
|
|
163
160
|
{String(this.props.path?.join('.'))}
|
|
164
161
|
</div>
|
|
165
162
|
);
|
|
@@ -2,13 +2,10 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, {
|
|
5
|
+
import React, { useMemo } from 'react';
|
|
6
6
|
|
|
7
7
|
import { type AnyProperties } from '@dxos/echo/internal';
|
|
8
8
|
import { type SchemaProperty } from '@dxos/effect';
|
|
9
|
-
import { type ThemedClassName } from '@dxos/react-ui';
|
|
10
|
-
import { mx } from '@dxos/ui-theme';
|
|
11
|
-
import { isTruthy } from '@dxos/util';
|
|
12
9
|
|
|
13
10
|
import { type FormHandlerProps } from '../../hooks';
|
|
14
11
|
import { getFormProperties } from '../../util';
|
|
@@ -17,109 +14,116 @@ import { useFormValues } from './Form';
|
|
|
17
14
|
import { FormField, type FormFieldProps } from './FormField';
|
|
18
15
|
import { FormFieldErrorBoundary, FormFieldLabel } from './FormFieldComponent';
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
17
|
+
const FORM_FIELDSET_NAME = 'Form.FieldSet';
|
|
18
|
+
|
|
19
|
+
export type FormFieldSetProps<T extends AnyProperties> = {
|
|
20
|
+
label?: string;
|
|
21
|
+
sort?: string[];
|
|
22
|
+
exclude?: (props: SchemaProperty[]) => SchemaProperty[];
|
|
23
|
+
} & Pick<FormHandlerProps<T>, 'schema'> &
|
|
24
|
+
Pick<
|
|
25
|
+
FormFieldProps,
|
|
26
|
+
| 'path'
|
|
27
|
+
| 'autoFocus'
|
|
28
|
+
| 'readonly'
|
|
29
|
+
| 'layout'
|
|
30
|
+
| 'projection'
|
|
31
|
+
| 'fieldMap'
|
|
32
|
+
| 'fieldProvider'
|
|
33
|
+
| 'createTypename'
|
|
34
|
+
| 'createOptionLabel'
|
|
35
|
+
| 'createOptionIcon'
|
|
36
|
+
| 'createInitialValuePath'
|
|
37
|
+
| 'createFieldMap'
|
|
38
|
+
| 'db'
|
|
39
|
+
| 'schemaHook'
|
|
40
|
+
| 'getOptions'
|
|
41
|
+
| 'onCreate'
|
|
42
|
+
>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Renders a set of form fields derived from a schema object.
|
|
46
|
+
*/
|
|
47
|
+
export const FormFieldSet = ({
|
|
48
|
+
label,
|
|
49
|
+
schema,
|
|
50
|
+
readonly,
|
|
51
|
+
path,
|
|
52
|
+
sort,
|
|
53
|
+
exclude,
|
|
54
|
+
projection,
|
|
55
|
+
layout,
|
|
56
|
+
...props
|
|
57
|
+
}: FormFieldSetProps<any>) => {
|
|
58
|
+
const values = useFormValues(FORM_FIELDSET_NAME, path);
|
|
59
|
+
|
|
60
|
+
// TODO(burdon): Updates on every value change.
|
|
61
|
+
// Remove values dep if can remove from getSchemaProperties.
|
|
62
|
+
const properties = useMemo(() => {
|
|
63
|
+
if (!schema) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
55
66
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
// TODO(wittjosiah): Reconcile FormInputAnnotation with projection hidden properties & exclude function.
|
|
68
|
+
const schemaProps = getFormProperties(schema.ast);
|
|
69
|
+
const filteredProps = exclude ? exclude(schemaProps) : schemaProps;
|
|
70
|
+
|
|
71
|
+
// Use projection-based field management when view and projection are available.
|
|
72
|
+
if (projection) {
|
|
73
|
+
const fieldProjections = projection.getFieldProjections();
|
|
74
|
+
const hiddenProperties = new Set(projection.getHiddenProperties());
|
|
75
|
+
|
|
76
|
+
// Filter properties to only include visible ones and order by projection.
|
|
77
|
+
const visibleProps = filteredProps.filter((prop) => !hiddenProperties.has(prop.name.toString()));
|
|
78
|
+
const orderedProps: SchemaProperty[] = [];
|
|
79
|
+
|
|
80
|
+
// Add properties in projection field order.
|
|
81
|
+
for (const fieldProjection of fieldProjections) {
|
|
82
|
+
const fieldPath = String(fieldProjection.field.path);
|
|
83
|
+
const prop = visibleProps.find((p) => p.name === fieldPath);
|
|
84
|
+
if (prop) {
|
|
85
|
+
orderedProps.push(prop);
|
|
75
86
|
}
|
|
76
|
-
|
|
77
|
-
// Add any remaining properties not in projection.
|
|
78
|
-
const projectionPaths = new Set(fieldProjections.map((projection) => String(projection.field.path)));
|
|
79
|
-
const remainingProps = visibleProps.filter((prop) => !projectionPaths.has(prop.name.toString()));
|
|
80
|
-
orderedProps.push(...remainingProps);
|
|
81
|
-
return orderedProps;
|
|
82
87
|
}
|
|
83
88
|
|
|
84
|
-
//
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}, [schema, values, exclude, sort, projection?.fields]);
|
|
90
|
-
|
|
91
|
-
if ((readonly || layout === 'static') && values == null) {
|
|
92
|
-
return null;
|
|
89
|
+
// Add any remaining properties not in projection.
|
|
90
|
+
const projectionPaths = new Set(fieldProjections.map((projection) => String(projection.field.path)));
|
|
91
|
+
const remainingProps = visibleProps.filter((prop) => !projectionPaths.has(prop.name.toString()));
|
|
92
|
+
orderedProps.push(...remainingProps);
|
|
93
|
+
return orderedProps;
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
|
|
96
|
+
// Fallback to legacy filter/sort behavior.
|
|
97
|
+
return sort
|
|
98
|
+
? [...filteredProps].sort(({ name: a }, { name: b }) => sort.indexOf(a.toString()) - sort.indexOf(b.toString()))
|
|
99
|
+
: filteredProps;
|
|
100
|
+
}, [schema, values, exclude, sort, projection]);
|
|
101
|
+
|
|
102
|
+
if ((readonly || layout === 'static') && values == null) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<>
|
|
108
|
+
{layout !== 'inline' && label && <FormFieldLabel label={label} asChild />}
|
|
109
|
+
{properties.map((property) => {
|
|
110
|
+
const name = property.name.toString();
|
|
111
|
+
return (
|
|
112
|
+
<FormFieldErrorBoundary key={name} path={[...(path ?? []), name]}>
|
|
113
|
+
<FormField
|
|
114
|
+
type={property.type}
|
|
115
|
+
name={name}
|
|
116
|
+
path={[...(path ?? []), name]}
|
|
117
|
+
readonly={readonly}
|
|
118
|
+
layout={layout}
|
|
119
|
+
projection={projection}
|
|
120
|
+
{...props}
|
|
121
|
+
/>
|
|
122
|
+
</FormFieldErrorBoundary>
|
|
123
|
+
);
|
|
124
|
+
})}
|
|
125
|
+
</>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
FormFieldSet.displayName = FORM_FIELDSET_NAME;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import * as Option from 'effect/Option';
|
|
6
6
|
import * as SchemaAST from 'effect/SchemaAST';
|
|
7
|
-
import React, {
|
|
7
|
+
import React, { useCallback } from 'react';
|
|
8
8
|
|
|
9
9
|
import { findNode, getArrayElementType, getDiscriminatedType, isDiscriminatedUnion, isNestedType } from '@dxos/effect';
|
|
10
10
|
import { IconButton, useTranslation } from '@dxos/react-ui';
|
|
@@ -77,10 +77,10 @@ export const ArrayField = ({
|
|
|
77
77
|
<FormFieldLabel readonly={readonly} label={label} asChild />
|
|
78
78
|
)}
|
|
79
79
|
|
|
80
|
-
<div role='none' className='flex flex-col gap-
|
|
80
|
+
<div role='none' className='flex flex-col gap-form-gap'>
|
|
81
81
|
{values?.map((_, index) => {
|
|
82
82
|
return (
|
|
83
|
-
<div role='none' key={index} className='
|
|
83
|
+
<div role='none' key={index} className='flex gap-form-gap last:mb-form-gap'>
|
|
84
84
|
<FormField
|
|
85
85
|
autoFocus={index === values.length - 1}
|
|
86
86
|
type={elementType}
|
|
@@ -91,13 +91,13 @@ export const ArrayField = ({
|
|
|
91
91
|
/>
|
|
92
92
|
|
|
93
93
|
{!readonly && layout !== 'static' && (
|
|
94
|
-
<div role='none' className='flex flex-col
|
|
94
|
+
<div role='none' className='flex flex-col h-full justify-end'>
|
|
95
95
|
{/* NOTE: Aligns with center of last field if multi-field object. */}
|
|
96
|
-
<div role='none' className='flex items-center
|
|
96
|
+
<div role='none' className='flex items-center h-[2rem]'>
|
|
97
97
|
<IconButton
|
|
98
98
|
icon='ph--x--regular'
|
|
99
99
|
iconOnly
|
|
100
|
-
label={t('button
|
|
100
|
+
label={t('remove.button')}
|
|
101
101
|
onClick={() => handleDelete(index)}
|
|
102
102
|
classNames='self-center'
|
|
103
103
|
/>
|
|
@@ -112,9 +112,9 @@ export const ArrayField = ({
|
|
|
112
112
|
{/* TODO(burdon): Get label from schema. */}
|
|
113
113
|
{!readonly && layout !== 'static' && (
|
|
114
114
|
<IconButton
|
|
115
|
-
classNames='flex
|
|
115
|
+
classNames='flex w-full'
|
|
116
116
|
icon='ph--plus--regular'
|
|
117
|
-
label={t('add
|
|
117
|
+
label={t('add-field.button')}
|
|
118
118
|
onClick={handleAdd}
|
|
119
119
|
/>
|
|
120
120
|
)}
|
|
@@ -140,6 +140,9 @@ export const getDefaultValue = (ast?: SchemaAST.AST): any => {
|
|
|
140
140
|
case 'BooleanKeyword': {
|
|
141
141
|
return false;
|
|
142
142
|
}
|
|
143
|
+
case 'Suspend': {
|
|
144
|
+
return getDefaultValue(ast.f());
|
|
145
|
+
}
|
|
143
146
|
default: {
|
|
144
147
|
if (ast && isNestedType(ast)) {
|
|
145
148
|
return {};
|
|
@@ -62,17 +62,17 @@ export const GeoPointField = ({
|
|
|
62
62
|
{layout === 'static' ? (
|
|
63
63
|
<LatLng {...value} />
|
|
64
64
|
) : (
|
|
65
|
-
<div role='none' className='grid grid-cols-2 gap-
|
|
65
|
+
<div role='none' className='grid grid-cols-2 gap-form-gap'>
|
|
66
66
|
<div>
|
|
67
67
|
<Input.Root>
|
|
68
|
-
{layout !== 'inline' && <Input.Label>{t('latitude
|
|
68
|
+
{layout !== 'inline' && <Input.Label>{t('latitude.label')}</Input.Label>}
|
|
69
69
|
<Input.TextInput
|
|
70
70
|
type='number'
|
|
71
71
|
step='0.00001'
|
|
72
72
|
min='-90'
|
|
73
73
|
max='90'
|
|
74
74
|
disabled={!!readonly}
|
|
75
|
-
placeholder={t('latitude
|
|
75
|
+
placeholder={t('latitude.placeholder')}
|
|
76
76
|
value={latitudeText ?? ''}
|
|
77
77
|
onChange={handleChange('latitude', setLatitudeText)}
|
|
78
78
|
onBlur={onBlur}
|
|
@@ -81,14 +81,14 @@ export const GeoPointField = ({
|
|
|
81
81
|
</div>
|
|
82
82
|
<div>
|
|
83
83
|
<Input.Root>
|
|
84
|
-
{layout !== 'inline' && <Input.Label>{t('longitude
|
|
84
|
+
{layout !== 'inline' && <Input.Label>{t('longitude.label')}</Input.Label>}
|
|
85
85
|
<Input.TextInput
|
|
86
86
|
type='number'
|
|
87
87
|
step='0.00001'
|
|
88
88
|
min='-180'
|
|
89
89
|
max='180'
|
|
90
90
|
disabled={!!readonly}
|
|
91
|
-
placeholder={t('longitude
|
|
91
|
+
placeholder={t('longitude.placeholder')}
|
|
92
92
|
value={longitudeText ?? ''}
|
|
93
93
|
onChange={handleChange('longitude', setLongitudeText)}
|
|
94
94
|
onBlur={onBlur}
|
|
@@ -106,7 +106,7 @@ const LatLng = ({ latitude = 0, longitude = 0 }: GeoLocation) => {
|
|
|
106
106
|
const lngHem = longitude >= 0 ? 'E' : 'W';
|
|
107
107
|
|
|
108
108
|
return (
|
|
109
|
-
<span className='inline-flex items-center gap-
|
|
109
|
+
<span className='inline-flex items-center gap-form-gap'>
|
|
110
110
|
<span>
|
|
111
111
|
<span>{Math.abs(latitude).toFixed(5)}</span>
|
|
112
112
|
<span className='text-subdued'>°{latHem}</span>
|
|
@@ -65,7 +65,7 @@ export const MarkdownField = ({
|
|
|
65
65
|
disabled={!!readonly}
|
|
66
66
|
placeholder={placeholder}
|
|
67
67
|
value={value ?? ''}
|
|
68
|
-
classNames={'min-
|
|
68
|
+
classNames={'min-h-auto max-h-40 overflow-auto'}
|
|
69
69
|
onChange={handleChange}
|
|
70
70
|
onBlur={onBlur}
|
|
71
71
|
style={{ resize: 'none' }}
|