@dxos/react-ui-form 0.8.4-main.c85a9c8dae → 0.8.4-main.d05539e30a
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/LICENSE +102 -5
- package/dist/lib/browser/index.mjs +1028 -625
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/translations.mjs +82 -0
- package/dist/lib/browser/translations.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1028 -625
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/translations.mjs +84 -0
- package/dist/lib/node-esm/translations.mjs.map +7 -0
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +63 -61
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +40 -12
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +76 -74
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts +11 -5
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +8 -7
- package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldSet.d.ts +5 -9
- package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/ArrayField.d.ts +4 -4
- package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts +8 -1
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts +84 -0
- package/dist/types/src/components/Form/fields/MarkdownField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/RefField.d.ts +15 -7
- package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectField.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 +63 -61
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/TextField.d.ts +1 -1
- package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -1
- package/dist/types/src/components/ObjectForm/ObjectForm.d.ts +9 -0
- package/dist/types/src/components/ObjectForm/ObjectForm.d.ts.map +1 -0
- package/dist/types/src/components/ObjectForm/index.d.ts +2 -0
- package/dist/types/src/components/ObjectForm/index.d.ts.map +1 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +26 -4
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +63 -61
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts +9 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.d.ts.map +1 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts +127 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.stories.d.ts.map +1 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.test.d.ts +2 -0
- package/dist/types/src/components/ObjectProperties/ObjectProperties.test.d.ts.map +1 -0
- package/dist/types/src/components/ObjectProperties/index.d.ts +2 -0
- package/dist/types/src/components/ObjectProperties/index.d.ts.map +1 -0
- package/dist/types/src/components/Settings/Settings.d.ts +28 -33
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -1
- package/dist/types/src/components/Settings/Settings.stories.d.ts +64 -61
- package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -1
- package/dist/types/src/components/Settings/SettingsFieldSet.d.ts +43 -0
- package/dist/types/src/components/Settings/SettingsFieldSet.d.ts.map +1 -0
- package/dist/types/src/components/Settings/SettingsItem.d.ts +16 -0
- package/dist/types/src/components/Settings/SettingsItem.d.ts.map +1 -0
- package/dist/types/src/components/Settings/SettingsSelectField.d.ts +18 -0
- package/dist/types/src/components/Settings/SettingsSelectField.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 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +3 -4
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +65 -63
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +2 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +4 -4
- 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/hooks/useKeyHandler.d.ts +2 -1
- package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +0 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +63 -61
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/field-type.d.ts +19 -0
- package/dist/types/src/util/field-type.d.ts.map +1 -0
- package/dist/types/src/util/field.d.ts.map +1 -1
- package/dist/types/src/util/formatting.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +1 -0
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/properties.d.ts +7 -1
- package/dist/types/src/util/properties.d.ts.map +1 -1
- package/dist/types/src/util/properties.test.d.ts +2 -0
- package/dist/types/src/util/properties.test.d.ts.map +1 -0
- 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 +43 -41
- package/src/components/FieldEditor/FieldEditor.stories.tsx +2 -2
- package/src/components/FieldEditor/FieldEditor.test.tsx +0 -1
- package/src/components/FieldEditor/FieldEditor.tsx +4 -3
- package/src/components/Form/AUDIT.md +17 -0
- package/src/components/Form/Form.stories.tsx +25 -38
- package/src/components/Form/Form.tsx +77 -48
- package/src/components/Form/FormField.tsx +27 -15
- package/src/components/Form/FormFieldComponent.tsx +6 -4
- package/src/components/Form/FormFieldSet.tsx +98 -77
- package/src/components/Form/fields/ArrayField.tsx +14 -8
- package/src/components/Form/fields/GeoPointField.tsx +8 -7
- package/src/components/Form/fields/MarkdownField.stories.tsx +206 -0
- package/src/components/Form/fields/MarkdownField.tsx +135 -45
- package/src/components/Form/fields/RefField.tsx +79 -39
- package/src/components/Form/fields/SelectOptionField.stories.tsx +8 -6
- package/src/components/Form/fields/SelectOptionField.tsx +12 -12
- package/src/components/Form/fields/TextField.tsx +2 -2
- package/src/components/Form/fields/TupleField.tsx +1 -1
- package/src/components/ObjectForm/ObjectForm.tsx +114 -0
- package/src/components/ObjectForm/index.ts +5 -0
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +12 -10
- package/src/components/ObjectPicker/ObjectPicker.tsx +92 -65
- package/src/components/ObjectProperties/ObjectProperties.stories.tsx +362 -0
- package/src/components/ObjectProperties/ObjectProperties.test.tsx +101 -0
- package/src/components/ObjectProperties/ObjectProperties.tsx +148 -0
- package/src/components/ObjectProperties/index.ts +5 -0
- package/src/components/Settings/AUDIT.md +51 -0
- package/src/components/Settings/Settings.stories.tsx +69 -21
- package/src/components/Settings/Settings.tsx +33 -183
- package/src/components/Settings/SettingsFieldSet.tsx +257 -0
- package/src/components/Settings/SettingsItem.tsx +58 -0
- package/src/components/Settings/SettingsSelectField.tsx +59 -0
- package/src/components/Settings/index.ts +2 -0
- package/src/components/ViewEditor/ViewEditor.stories.tsx +14 -15
- package/src/components/ViewEditor/ViewEditor.test.tsx +0 -1
- package/src/components/ViewEditor/ViewEditor.tsx +40 -36
- package/src/components/index.ts +2 -0
- package/src/components/testing.tsx +19 -11
- package/src/hooks/useKeyHandler.tsx +7 -7
- package/src/index.ts +0 -4
- package/src/translations.ts +66 -63
- package/src/util/field-type.ts +60 -0
- package/src/util/index.ts +1 -0
- package/src/util/properties.test.ts +63 -0
- package/src/util/properties.ts +14 -3
- package/src/util/refs.ts +7 -3
|
@@ -23,9 +23,9 @@ import {
|
|
|
23
23
|
import { useTranslation } from '@dxos/react-ui';
|
|
24
24
|
import { type ProjectionModel } from '@dxos/schema';
|
|
25
25
|
|
|
26
|
-
import { translationKey } from '
|
|
27
|
-
import { getRefProps } from '../../util';
|
|
26
|
+
import { translationKey } from '#translations';
|
|
28
27
|
|
|
28
|
+
import { getRefProps } from '../../util';
|
|
29
29
|
import {
|
|
30
30
|
ArrayField,
|
|
31
31
|
BooleanField,
|
|
@@ -78,14 +78,21 @@ 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
|
+
| 'useSchema'
|
|
89
96
|
| 'getOptions'
|
|
90
97
|
| 'onCreate'
|
|
91
98
|
>;
|
|
@@ -102,12 +109,14 @@ 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
|
-
schemaHook,
|
|
110
|
-
getOptions
|
|
118
|
+
useSchema: schemaHook,
|
|
119
|
+
getOptions,
|
|
111
120
|
onCreate,
|
|
112
121
|
} = props;
|
|
113
122
|
const { t } = useTranslation(translationKey);
|
|
@@ -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
|
|
|
@@ -129,6 +138,7 @@ export const FormField = (props: FormFieldProps) => {
|
|
|
129
138
|
label,
|
|
130
139
|
placeholder,
|
|
131
140
|
layout,
|
|
141
|
+
db,
|
|
132
142
|
...fieldState,
|
|
133
143
|
};
|
|
134
144
|
|
|
@@ -169,7 +179,7 @@ export const FormField = (props: FormFieldProps) => {
|
|
|
169
179
|
// Select field.
|
|
170
180
|
//
|
|
171
181
|
|
|
172
|
-
const options =
|
|
182
|
+
const options = getSelectOptions(type);
|
|
173
183
|
if (options) {
|
|
174
184
|
// Resolve labels from projection metadata when available.
|
|
175
185
|
const fieldProjections = projection?.getFieldProjections();
|
|
@@ -196,16 +206,18 @@ export const FormField = (props: FormFieldProps) => {
|
|
|
196
206
|
|
|
197
207
|
const refProps = getRefProps(type);
|
|
198
208
|
if (refProps) {
|
|
209
|
+
const isCreateTarget = !createTypename || refProps.typename === createTypename;
|
|
199
210
|
return (
|
|
200
211
|
<RefField
|
|
201
212
|
{...fieldProps}
|
|
202
213
|
{...refProps}
|
|
203
|
-
createOptionLabel={createOptionLabel}
|
|
204
|
-
createOptionIcon={createOptionIcon}
|
|
205
|
-
createInitialValuePath={createInitialValuePath}
|
|
214
|
+
createOptionLabel={isCreateTarget ? createOptionLabel : undefined}
|
|
215
|
+
createOptionIcon={isCreateTarget ? createOptionIcon : undefined}
|
|
216
|
+
createInitialValuePath={isCreateTarget ? createInitialValuePath : undefined}
|
|
217
|
+
createFieldMap={isCreateTarget ? createFieldMap : undefined}
|
|
206
218
|
db={db}
|
|
207
|
-
|
|
208
|
-
getOptions={
|
|
219
|
+
useSchema={schemaHook}
|
|
220
|
+
getOptions={getOptions}
|
|
209
221
|
onCreate={onCreate}
|
|
210
222
|
/>
|
|
211
223
|
);
|
|
@@ -237,8 +249,8 @@ export const FormField = (props: FormFieldProps) => {
|
|
|
237
249
|
createOptionIcon={createOptionIcon}
|
|
238
250
|
createInitialValuePath={createInitialValuePath}
|
|
239
251
|
db={db}
|
|
240
|
-
|
|
241
|
-
getOptions={
|
|
252
|
+
useSchema={schemaHook}
|
|
253
|
+
getOptions={getOptions}
|
|
242
254
|
onCreate={onCreate}
|
|
243
255
|
/>
|
|
244
256
|
);
|
|
@@ -285,7 +297,7 @@ const getFormField = ({ type, format }: FormFieldComponentProps): FormFieldCompo
|
|
|
285
297
|
}
|
|
286
298
|
};
|
|
287
299
|
|
|
288
|
-
const
|
|
300
|
+
const getSelectOptions = (ast: SchemaAST.AST): Format.Options[] | undefined => {
|
|
289
301
|
if (isLiteralUnion(ast)) {
|
|
290
302
|
return ast.types.map((type) => type.literal).filter((v): v is string | number => v !== null);
|
|
291
303
|
}
|
|
@@ -13,6 +13,7 @@ import React, {
|
|
|
13
13
|
type ReactNode,
|
|
14
14
|
} from 'react';
|
|
15
15
|
|
|
16
|
+
import { type Database } from '@dxos/echo';
|
|
16
17
|
import { type Format } from '@dxos/echo/internal';
|
|
17
18
|
import { Icon, Input, Tooltip } from '@dxos/react-ui';
|
|
18
19
|
import { inputTextLabel } from '@dxos/ui-theme';
|
|
@@ -31,7 +32,6 @@ export type FormFieldStateProps<T = any> = {
|
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* Presentation mode.
|
|
34
|
-
*
|
|
35
35
|
* - full: Show label, control, and status.
|
|
36
36
|
* - compact: Show label and control.
|
|
37
37
|
* - inline: Control only.
|
|
@@ -51,6 +51,8 @@ export type FormFieldComponentProps<T = any> = {
|
|
|
51
51
|
placeholder?: string;
|
|
52
52
|
autoFocus?: boolean;
|
|
53
53
|
layout?: Presentation;
|
|
54
|
+
/** Database the form is editing against; populated for fields whose value is an ECHO object/Ref. */
|
|
55
|
+
db?: Database.Database;
|
|
54
56
|
} & FormFieldStateProps<T>;
|
|
55
57
|
|
|
56
58
|
/**
|
|
@@ -78,7 +80,7 @@ export type FormFieldLabelProps = {
|
|
|
78
80
|
export const FormFieldLabel = ({ label, error, readonly, asChild }: FormFieldLabelProps) => {
|
|
79
81
|
const Label = readonly || asChild ? 'span' : Input.Label;
|
|
80
82
|
return (
|
|
81
|
-
<div
|
|
83
|
+
<div className='flex items-center justify-between'>
|
|
82
84
|
<Label className={inputTextLabel}>{label}</Label>
|
|
83
85
|
{error && (
|
|
84
86
|
<Tooltip.Trigger asChild content={error} side='bottom'>
|
|
@@ -114,7 +116,7 @@ export const FormFieldWrapper = <T,>(props: FormFieldWrapperProps<T>) => {
|
|
|
114
116
|
const str = String(value ?? '');
|
|
115
117
|
|
|
116
118
|
return (
|
|
117
|
-
<div
|
|
119
|
+
<div className='contents'>
|
|
118
120
|
<Input.Root validationValence={status}>
|
|
119
121
|
{layout !== 'inline' && <FormFieldLabel error={error} readonly={readonly} label={label} />}
|
|
120
122
|
{layout === 'static' ? <p>{str}</p> : children ? children({ value }) : null}
|
|
@@ -157,7 +159,7 @@ export class FormFieldErrorBoundary extends Component<FormFieldErrorBoundaryProp
|
|
|
157
159
|
if (this.state.error) {
|
|
158
160
|
return (
|
|
159
161
|
<div className='flex gap-2 border border-rose-fill font-mono text-sm'>
|
|
160
|
-
<span className='bg-rose-fill text-
|
|
162
|
+
<span className='bg-rose-fill text-base-foreground px-1 font-thin'>ERROR</span>
|
|
161
163
|
{String(this.props.path?.join('.'))}
|
|
162
164
|
</div>
|
|
163
165
|
);
|
|
@@ -2,16 +2,13 @@
|
|
|
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 { mx } from '@dxos/ui-theme';
|
|
10
|
-
import { isTruthy } from '@dxos/util';
|
|
11
9
|
|
|
12
10
|
import { type FormHandlerProps } from '../../hooks';
|
|
13
11
|
import { getFormProperties } from '../../util';
|
|
14
|
-
|
|
15
12
|
import { useFormValues } from './Form';
|
|
16
13
|
import { FormField, type FormFieldProps } from './FormField';
|
|
17
14
|
import { FormFieldErrorBoundary, FormFieldLabel } from './FormFieldComponent';
|
|
@@ -20,8 +17,8 @@ const FORM_FIELDSET_NAME = 'Form.FieldSet';
|
|
|
20
17
|
|
|
21
18
|
export type FormFieldSetProps<T extends AnyProperties> = {
|
|
22
19
|
label?: string;
|
|
23
|
-
exclude?: (props: SchemaProperty[]) => SchemaProperty[];
|
|
24
20
|
sort?: string[];
|
|
21
|
+
exclude?: (props: SchemaProperty[]) => SchemaProperty[];
|
|
25
22
|
} & Pick<FormHandlerProps<T>, 'schema'> &
|
|
26
23
|
Pick<
|
|
27
24
|
FormFieldProps,
|
|
@@ -32,11 +29,13 @@ export type FormFieldSetProps<T extends AnyProperties> = {
|
|
|
32
29
|
| 'projection'
|
|
33
30
|
| 'fieldMap'
|
|
34
31
|
| 'fieldProvider'
|
|
32
|
+
| 'createTypename'
|
|
35
33
|
| 'createOptionLabel'
|
|
36
34
|
| 'createOptionIcon'
|
|
37
35
|
| 'createInitialValuePath'
|
|
36
|
+
| 'createFieldMap'
|
|
38
37
|
| 'db'
|
|
39
|
-
| '
|
|
38
|
+
| 'useSchema'
|
|
40
39
|
| 'getOptions'
|
|
41
40
|
| 'onCreate'
|
|
42
41
|
>;
|
|
@@ -44,80 +43,102 @@ export type FormFieldSetProps<T extends AnyProperties> = {
|
|
|
44
43
|
/**
|
|
45
44
|
* Renders a set of form fields derived from a schema object.
|
|
46
45
|
*/
|
|
47
|
-
export const FormFieldSet =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
46
|
+
export const FormFieldSet = ({
|
|
47
|
+
label,
|
|
48
|
+
schema,
|
|
49
|
+
readonly,
|
|
50
|
+
path,
|
|
51
|
+
sort,
|
|
52
|
+
exclude,
|
|
53
|
+
projection,
|
|
54
|
+
layout,
|
|
55
|
+
...props
|
|
56
|
+
}: FormFieldSetProps<any>) => {
|
|
57
|
+
const values = useFormValues(FORM_FIELDSET_NAME, path);
|
|
58
|
+
const properties = useFormFieldSetProperties({ schema, values, exclude, sort, projection });
|
|
59
|
+
if ((readonly || layout === 'static') && values == null) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<>
|
|
65
|
+
{layout !== 'inline' && label && <FormFieldLabel label={label} asChild />}
|
|
66
|
+
{properties.map((property) => {
|
|
67
|
+
const name = property.name.toString();
|
|
68
|
+
return (
|
|
69
|
+
<FormFieldErrorBoundary key={name} path={[...(path ?? []), name]}>
|
|
70
|
+
<FormField
|
|
71
|
+
type={property.type}
|
|
72
|
+
name={name}
|
|
73
|
+
path={[...(path ?? []), name]}
|
|
74
|
+
readonly={readonly}
|
|
75
|
+
layout={layout}
|
|
76
|
+
projection={projection}
|
|
77
|
+
{...props}
|
|
78
|
+
/>
|
|
79
|
+
</FormFieldErrorBoundary>
|
|
80
|
+
);
|
|
81
|
+
})}
|
|
82
|
+
</>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
57
85
|
|
|
58
|
-
|
|
59
|
-
const props = getFormProperties(schema.ast);
|
|
60
|
-
|
|
61
|
-
// Use projection-based field management when view and projection are available.
|
|
62
|
-
if (projection) {
|
|
63
|
-
const fieldProjections = projection.getFieldProjections();
|
|
64
|
-
const hiddenProperties = new Set(projection.getHiddenProperties());
|
|
65
|
-
|
|
66
|
-
// Filter properties to only include visible ones and order by projection.
|
|
67
|
-
const visibleProps = props.filter((prop) => !hiddenProperties.has(prop.name.toString()));
|
|
68
|
-
const orderedProps: SchemaProperty[] = [];
|
|
69
|
-
|
|
70
|
-
// Add properties in projection field order.
|
|
71
|
-
for (const fieldProjection of fieldProjections) {
|
|
72
|
-
const fieldPath = String(fieldProjection.field.path);
|
|
73
|
-
const prop = visibleProps.find((p) => p.name === fieldPath);
|
|
74
|
-
if (prop) {
|
|
75
|
-
orderedProps.push(prop);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Add any remaining properties not in projection.
|
|
80
|
-
const projectionPaths = new Set(fieldProjections.map((projection) => String(projection.field.path)));
|
|
81
|
-
const remainingProps = visibleProps.filter((prop) => !projectionPaths.has(prop.name.toString()));
|
|
82
|
-
orderedProps.push(...remainingProps);
|
|
83
|
-
return orderedProps;
|
|
84
|
-
}
|
|
86
|
+
FormFieldSet.displayName = FORM_FIELDSET_NAME;
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
: filtered;
|
|
91
|
-
}, [schema, values, exclude, sort, projection?.fields]);
|
|
88
|
+
type UseFormFieldSetPropertiesParams = Pick<FormFieldSetProps<any>, 'schema' | 'exclude' | 'projection' | 'sort'> & {
|
|
89
|
+
values: AnyProperties | undefined;
|
|
90
|
+
sort?: string[];
|
|
91
|
+
};
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Resolves ordered schema properties for a field set (projection order, exclude, or sort).
|
|
95
|
+
*/
|
|
96
|
+
const useFormFieldSetProperties = ({
|
|
97
|
+
schema,
|
|
98
|
+
exclude,
|
|
99
|
+
projection,
|
|
100
|
+
values,
|
|
101
|
+
sort,
|
|
102
|
+
}: UseFormFieldSetPropertiesParams): SchemaProperty[] => {
|
|
103
|
+
// TODO(burdon): Updates on every value change.
|
|
104
|
+
// Remove values dep if can remove from getSchemaProperties.
|
|
105
|
+
return useMemo(() => {
|
|
106
|
+
if (!schema) {
|
|
107
|
+
return [];
|
|
95
108
|
}
|
|
96
109
|
|
|
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
|
-
);
|
|
110
|
+
// TODO(wittjosiah): Reconcile FormInputAnnotation with projection hidden properties & exclude function.
|
|
111
|
+
const schemaProps = getFormProperties(schema.ast);
|
|
112
|
+
const filteredProps = exclude ? exclude(schemaProps) : schemaProps;
|
|
113
|
+
|
|
114
|
+
// Use projection-based field management when view and projection are available.
|
|
115
|
+
if (projection) {
|
|
116
|
+
const fieldProjections = projection.getFieldProjections();
|
|
117
|
+
const hiddenProperties = new Set(projection.getHiddenProperties());
|
|
118
|
+
|
|
119
|
+
// Filter properties to only include visible ones and order by projection.
|
|
120
|
+
const visibleProps = filteredProps.filter((prop) => !hiddenProperties.has(prop.name.toString()));
|
|
121
|
+
const orderedProps: SchemaProperty[] = [];
|
|
122
|
+
|
|
123
|
+
// Add properties in projection field order.
|
|
124
|
+
for (const fieldProjection of fieldProjections) {
|
|
125
|
+
const fieldPath = String(fieldProjection.field.path);
|
|
126
|
+
const prop = visibleProps.find((prop) => prop.name === fieldPath);
|
|
127
|
+
if (prop) {
|
|
128
|
+
orderedProps.push(prop);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Add any remaining properties not in projection.
|
|
133
|
+
const projectionPaths = new Set(fieldProjections.map((projection) => String(projection.field.path)));
|
|
134
|
+
const remainingProps = visibleProps.filter((prop) => !projectionPaths.has(prop.name.toString()));
|
|
135
|
+
orderedProps.push(...remainingProps);
|
|
136
|
+
return orderedProps;
|
|
137
|
+
}
|
|
122
138
|
|
|
123
|
-
|
|
139
|
+
// Fallback to legacy filter/sort behavior.
|
|
140
|
+
return sort
|
|
141
|
+
? [...filteredProps].sort(({ name: a }, { name: b }) => sort.indexOf(a.toString()) - sort.indexOf(b.toString()))
|
|
142
|
+
: filteredProps;
|
|
143
|
+
}, [schema, values, exclude, sort, projection]);
|
|
144
|
+
};
|
|
@@ -4,12 +4,13 @@
|
|
|
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';
|
|
11
11
|
|
|
12
|
-
import { translationKey } from '
|
|
12
|
+
import { translationKey } from '#translations';
|
|
13
|
+
|
|
13
14
|
import { getFormProperties } from '../../../util';
|
|
14
15
|
import { useFormValues } from '../Form';
|
|
15
16
|
import { FormField, type FormFieldProps } from '../FormField';
|
|
@@ -77,10 +78,10 @@ export const ArrayField = ({
|
|
|
77
78
|
<FormFieldLabel readonly={readonly} label={label} asChild />
|
|
78
79
|
)}
|
|
79
80
|
|
|
80
|
-
<div
|
|
81
|
+
<div className='flex flex-col gap-form-gap'>
|
|
81
82
|
{values?.map((_, index) => {
|
|
82
83
|
return (
|
|
83
|
-
<div
|
|
84
|
+
<div key={index} className='grid grid-cols-[1fr_min-content] gap-form-gap last:mb-form-gap items-center'>
|
|
84
85
|
<FormField
|
|
85
86
|
autoFocus={index === values.length - 1}
|
|
86
87
|
type={elementType}
|
|
@@ -91,13 +92,13 @@ export const ArrayField = ({
|
|
|
91
92
|
/>
|
|
92
93
|
|
|
93
94
|
{!readonly && layout !== 'static' && (
|
|
94
|
-
<div
|
|
95
|
+
<div className='flex flex-col h-full justify-end'>
|
|
95
96
|
{/* NOTE: Aligns with center of last field if multi-field object. */}
|
|
96
|
-
<div
|
|
97
|
+
<div className='flex items-center h-[2rem]'>
|
|
97
98
|
<IconButton
|
|
98
99
|
icon='ph--x--regular'
|
|
99
100
|
iconOnly
|
|
100
|
-
label={t('button
|
|
101
|
+
label={t('remove.button')}
|
|
101
102
|
onClick={() => handleDelete(index)}
|
|
102
103
|
classNames='self-center'
|
|
103
104
|
/>
|
|
@@ -111,7 +112,12 @@ export const ArrayField = ({
|
|
|
111
112
|
|
|
112
113
|
{/* TODO(burdon): Get label from schema. */}
|
|
113
114
|
{!readonly && layout !== 'static' && (
|
|
114
|
-
<IconButton
|
|
115
|
+
<IconButton
|
|
116
|
+
classNames='flex w-full'
|
|
117
|
+
icon='ph--plus--regular'
|
|
118
|
+
label={t('add-item.button')}
|
|
119
|
+
onClick={handleAdd}
|
|
120
|
+
/>
|
|
115
121
|
)}
|
|
116
122
|
</>
|
|
117
123
|
);
|
|
@@ -8,7 +8,8 @@ import { GeoLocation, type GeoPoint } from '@dxos/echo/internal';
|
|
|
8
8
|
import { Input, useTranslation } from '@dxos/react-ui';
|
|
9
9
|
import { safeParseFloat } from '@dxos/util';
|
|
10
10
|
|
|
11
|
-
import { translationKey } from '
|
|
11
|
+
import { translationKey } from '#translations';
|
|
12
|
+
|
|
12
13
|
import { type FormFieldComponentProps, FormFieldLabel } from '../FormFieldComponent';
|
|
13
14
|
|
|
14
15
|
export const GeoPointField = ({
|
|
@@ -62,17 +63,17 @@ export const GeoPointField = ({
|
|
|
62
63
|
{layout === 'static' ? (
|
|
63
64
|
<LatLng {...value} />
|
|
64
65
|
) : (
|
|
65
|
-
<div
|
|
66
|
+
<div className='grid grid-cols-2 gap-form-gap'>
|
|
66
67
|
<div>
|
|
67
68
|
<Input.Root>
|
|
68
|
-
{layout !== 'inline' && <Input.Label>{t('latitude
|
|
69
|
+
{layout !== 'inline' && <Input.Label>{t('latitude.label')}</Input.Label>}
|
|
69
70
|
<Input.TextInput
|
|
70
71
|
type='number'
|
|
71
72
|
step='0.00001'
|
|
72
73
|
min='-90'
|
|
73
74
|
max='90'
|
|
74
75
|
disabled={!!readonly}
|
|
75
|
-
placeholder={t('latitude
|
|
76
|
+
placeholder={t('latitude.placeholder')}
|
|
76
77
|
value={latitudeText ?? ''}
|
|
77
78
|
onChange={handleChange('latitude', setLatitudeText)}
|
|
78
79
|
onBlur={onBlur}
|
|
@@ -81,14 +82,14 @@ export const GeoPointField = ({
|
|
|
81
82
|
</div>
|
|
82
83
|
<div>
|
|
83
84
|
<Input.Root>
|
|
84
|
-
{layout !== 'inline' && <Input.Label>{t('longitude
|
|
85
|
+
{layout !== 'inline' && <Input.Label>{t('longitude.label')}</Input.Label>}
|
|
85
86
|
<Input.TextInput
|
|
86
87
|
type='number'
|
|
87
88
|
step='0.00001'
|
|
88
89
|
min='-180'
|
|
89
90
|
max='180'
|
|
90
91
|
disabled={!!readonly}
|
|
91
|
-
placeholder={t('longitude
|
|
92
|
+
placeholder={t('longitude.placeholder')}
|
|
92
93
|
value={longitudeText ?? ''}
|
|
93
94
|
onChange={handleChange('longitude', setLongitudeText)}
|
|
94
95
|
onBlur={onBlur}
|
|
@@ -106,7 +107,7 @@ const LatLng = ({ latitude = 0, longitude = 0 }: GeoLocation) => {
|
|
|
106
107
|
const lngHem = longitude >= 0 ? 'E' : 'W';
|
|
107
108
|
|
|
108
109
|
return (
|
|
109
|
-
<span className='inline-flex items-center gap-
|
|
110
|
+
<span className='inline-flex items-center gap-form-gap'>
|
|
110
111
|
<span>
|
|
111
112
|
<span>{Math.abs(latitude).toFixed(5)}</span>
|
|
112
113
|
<span className='text-subdued'>°{latHem}</span>
|