@dxos/react-ui-form 0.8.4-main.c4373fc → 0.8.4-main.c85a9c8dae
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 +2289 -2269
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +2289 -2269
- 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 +8 -7
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +13 -7
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +81 -26
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +59 -84
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts +37 -0
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +78 -0
- package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +9 -0
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldSet.d.ts +19 -0
- package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/ArrayField.d.ts +18 -0
- package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/BooleanField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts +5 -0
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/NumberField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/RefField.d.ts +14 -0
- package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/SelectField.d.ts +10 -0
- package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +5 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -0
- package/dist/types/src/components/{ControlSection/ControlSection.stories.d.ts → Form/fields/SelectOptionField.stories.d.ts} +12 -14
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/TextAreaField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/TextField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/TupleField.d.ts +6 -0
- package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/index.d.ts +12 -0
- package/dist/types/src/components/Form/fields/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/index.d.ts +2 -4
- package/dist/types/src/components/Form/index.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +15 -10
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +10 -6
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/Settings/Settings.d.ts +56 -0
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
- package/dist/types/src/components/{Form/custom/SelectOptionsInput.stories.d.ts → Settings/Settings.stories.d.ts} +10 -8
- 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 +16 -16
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +13 -9
- 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 +0 -2
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -3
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useFormHandler.d.ts +82 -0
- package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -0
- package/dist/types/src/hooks/useKeyHandler.d.ts +6 -0
- package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +10 -7
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/field.d.ts +3 -3
- package/dist/types/src/util/field.d.ts.map +1 -1
- package/dist/types/src/util/formatting.d.ts +2 -2
- package/dist/types/src/util/formatting.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +1 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/properties.d.ts +7 -0
- package/dist/types/src/util/properties.d.ts.map +1 -0
- package/dist/types/src/util/refs.d.ts +1 -2
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +50 -46
- package/src/components/FieldEditor/FieldEditor.stories.tsx +43 -24
- package/src/components/FieldEditor/FieldEditor.tsx +74 -78
- package/src/components/Form/Form.stories.tsx +149 -248
- package/src/components/Form/Form.tsx +347 -107
- package/src/components/Form/FormField.tsx +294 -0
- package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
- package/src/components/Form/FormFieldComponent.tsx +172 -0
- package/src/components/Form/FormFieldSet.tsx +123 -0
- package/src/components/Form/README.md +3 -0
- package/src/components/Form/fields/ArrayField.tsx +149 -0
- package/src/components/Form/fields/BooleanField.tsx +22 -0
- package/src/components/Form/fields/GeoPointField.tsx +120 -0
- package/src/components/Form/fields/MarkdownField.tsx +78 -0
- package/src/components/Form/fields/NumberField.tsx +39 -0
- package/src/components/Form/fields/RefField.tsx +189 -0
- package/src/components/Form/fields/SelectField.tsx +65 -0
- package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +19 -16
- package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +23 -17
- package/src/components/Form/fields/TextAreaField.tsx +38 -0
- package/src/components/Form/fields/TextField.tsx +38 -0
- package/src/components/Form/{custom/TupleInput.tsx → fields/TupleField.tsx} +15 -8
- package/src/components/Form/fields/index.ts +15 -0
- package/src/components/Form/index.ts +3 -4
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +40 -37
- package/src/components/ObjectPicker/ObjectPicker.tsx +84 -70
- package/src/components/Settings/Settings.stories.tsx +56 -0
- package/src/components/Settings/Settings.tsx +230 -0
- package/src/components/{ControlSection → Settings}/index.ts +1 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +69 -49
- package/src/components/ViewEditor/ViewEditor.test.tsx +8 -6
- package/src/components/ViewEditor/ViewEditor.tsx +302 -271
- package/src/components/index.ts +1 -1
- package/src/components/testing.tsx +12 -7
- package/src/hooks/index.ts +2 -3
- package/src/hooks/useFormHandler.ts +380 -0
- package/src/hooks/useKeyHandler.tsx +37 -0
- package/src/translations.ts +12 -10
- package/src/util/field.ts +25 -25
- package/src/util/formatting.ts +22 -22
- package/src/util/index.ts +1 -1
- package/src/util/properties.ts +18 -0
- package/src/util/refs.ts +11 -17
- 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.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/ArrayField.d.ts +0 -16
- package/dist/types/src/components/Form/ArrayField.d.ts.map +0 -1
- package/dist/types/src/components/Form/Defaults.d.ts +0 -14
- package/dist/types/src/components/Form/Defaults.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormActions.d.ts +0 -9
- package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormContent.d.ts +0 -63
- package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormContext.d.ts +0 -23
- package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
- package/dist/types/src/components/Form/Input.d.ts +0 -29
- package/dist/types/src/components/Form/Input.d.ts.map +0 -1
- package/dist/types/src/components/Form/RefField.d.ts +0 -21
- package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/TupleInput.d.ts +0 -6
- package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/index.d.ts +0 -4
- package/dist/types/src/components/Form/custom/index.d.ts.map +0 -1
- package/dist/types/src/components/Form/factory.d.ts +0 -8
- package/dist/types/src/components/Form/factory.d.ts.map +0 -1
- package/dist/types/src/hooks/useForm.d.ts +0 -67
- package/dist/types/src/hooks/useForm.d.ts.map +0 -1
- package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -28
- package/dist/types/src/hooks/useQueryRefOptions.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/dist/types/src/util/schema.d.ts +0 -3
- package/dist/types/src/util/schema.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/ArrayField.tsx +0 -125
- package/src/components/Form/Defaults.tsx +0 -205
- package/src/components/Form/FormActions.tsx +0 -54
- package/src/components/Form/FormContent.tsx +0 -322
- package/src/components/Form/FormContext.tsx +0 -110
- package/src/components/Form/Input.tsx +0 -54
- package/src/components/Form/RefField.tsx +0 -269
- package/src/components/Form/custom/GeoPointInput.tsx +0 -80
- package/src/components/Form/custom/index.ts +0 -7
- package/src/components/Form/factory.tsx +0 -31
- package/src/hooks/useForm.ts +0 -295
- package/src/hooks/useQueryRefOptions.ts +0 -59
- package/src/hooks/useRefQueryLookupHandler.ts +0 -39
- package/src/util/schema.ts +0 -12
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2025 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import * as Function from 'effect/Function';
|
|
6
|
-
import * as Schema from 'effect/Schema';
|
|
7
|
-
import * as SchemaAST from 'effect/SchemaAST';
|
|
8
|
-
import * as StringEffect from 'effect/String';
|
|
9
|
-
import React, { forwardRef, useMemo } from 'react';
|
|
10
|
-
|
|
11
|
-
import { createJsonPath, findNode, getDiscriminatedType, isDiscriminatedUnion } from '@dxos/effect';
|
|
12
|
-
import { type ThemedClassName } from '@dxos/react-ui';
|
|
13
|
-
import { mx } from '@dxos/react-ui-theme';
|
|
14
|
-
import { type ProjectionModel, type SchemaProperty, getSchemaProperties } from '@dxos/schema';
|
|
15
|
-
import { isTruthy } from '@dxos/util';
|
|
16
|
-
|
|
17
|
-
import { type QueryRefOptions } from '../../hooks';
|
|
18
|
-
import { getRefProps } from '../../util';
|
|
19
|
-
|
|
20
|
-
import { ArrayField } from './ArrayField';
|
|
21
|
-
import { SelectInput } from './Defaults';
|
|
22
|
-
import { getInputComponent } from './factory';
|
|
23
|
-
import { type ComponentLookup } from './Form';
|
|
24
|
-
import { useFormValues, useInputProps } from './FormContext';
|
|
25
|
-
import { type InputComponent } from './Input';
|
|
26
|
-
import { RefField, type RefFieldProps } from './RefField';
|
|
27
|
-
|
|
28
|
-
export type FormFieldProps = {
|
|
29
|
-
property: SchemaProperty<any>;
|
|
30
|
-
path?: (string | number)[];
|
|
31
|
-
/** Used to indicate if input should be presented inline (e.g. for array items). */
|
|
32
|
-
inline?: boolean;
|
|
33
|
-
projection?: ProjectionModel;
|
|
34
|
-
lookupComponent?: ComponentLookup;
|
|
35
|
-
Custom?: Partial<Record<string, InputComponent>>;
|
|
36
|
-
} & Pick<
|
|
37
|
-
RefFieldProps,
|
|
38
|
-
| 'readonly'
|
|
39
|
-
| 'onQueryRefOptions'
|
|
40
|
-
| 'createOptionLabel'
|
|
41
|
-
| 'createOptionIcon'
|
|
42
|
-
| 'onCreate'
|
|
43
|
-
| 'createSchema'
|
|
44
|
-
| 'createInitialValuePath'
|
|
45
|
-
>;
|
|
46
|
-
|
|
47
|
-
export const FormField = ({
|
|
48
|
-
property,
|
|
49
|
-
path,
|
|
50
|
-
readonly,
|
|
51
|
-
inline,
|
|
52
|
-
projection,
|
|
53
|
-
onQueryRefOptions,
|
|
54
|
-
createOptionLabel,
|
|
55
|
-
createOptionIcon,
|
|
56
|
-
onCreate,
|
|
57
|
-
createSchema,
|
|
58
|
-
createInitialValuePath,
|
|
59
|
-
lookupComponent,
|
|
60
|
-
Custom,
|
|
61
|
-
}: FormFieldProps) => {
|
|
62
|
-
const { ast, name, type, format, title, description, options, examples, array } = property;
|
|
63
|
-
const inputProps = useInputProps(path);
|
|
64
|
-
|
|
65
|
-
const label = useMemo(() => title ?? Function.pipe(name, StringEffect.capitalize), [title, name]);
|
|
66
|
-
const placeholder = useMemo(
|
|
67
|
-
() => (examples?.length ? `Example: "${examples[0]}"` : description),
|
|
68
|
-
[examples, description],
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
//
|
|
72
|
-
// Registry and Custom.
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
const FoundComponent = lookupComponent?.({
|
|
76
|
-
prop: name,
|
|
77
|
-
schema: Schema.make(ast),
|
|
78
|
-
inputProps: {
|
|
79
|
-
type,
|
|
80
|
-
format,
|
|
81
|
-
label,
|
|
82
|
-
readonly,
|
|
83
|
-
placeholder,
|
|
84
|
-
...inputProps,
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
if (FoundComponent) {
|
|
89
|
-
return FoundComponent;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const jsonPath = createJsonPath(path ?? []);
|
|
93
|
-
const CustomComponent = Custom?.[jsonPath];
|
|
94
|
-
if (CustomComponent) {
|
|
95
|
-
return (
|
|
96
|
-
<CustomComponent
|
|
97
|
-
type={type}
|
|
98
|
-
format={format}
|
|
99
|
-
label={label}
|
|
100
|
-
inputOnly={inline}
|
|
101
|
-
placeholder={placeholder}
|
|
102
|
-
readonly={readonly}
|
|
103
|
-
{...inputProps}
|
|
104
|
-
/>
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
//
|
|
109
|
-
// Refs.
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
const refProps = getRefProps(property);
|
|
113
|
-
if (refProps) {
|
|
114
|
-
return (
|
|
115
|
-
<RefField
|
|
116
|
-
ast={refProps.ast}
|
|
117
|
-
array={refProps.isArray}
|
|
118
|
-
type={type}
|
|
119
|
-
format={format}
|
|
120
|
-
label={label}
|
|
121
|
-
placeholder={placeholder}
|
|
122
|
-
readonly={readonly}
|
|
123
|
-
inputOnly={inline}
|
|
124
|
-
onQueryRefOptions={onQueryRefOptions}
|
|
125
|
-
createOptionLabel={createOptionLabel}
|
|
126
|
-
createOptionIcon={createOptionIcon}
|
|
127
|
-
onCreate={onCreate}
|
|
128
|
-
createSchema={createSchema}
|
|
129
|
-
createInitialValuePath={createInitialValuePath}
|
|
130
|
-
{...inputProps}
|
|
131
|
-
/>
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
//
|
|
136
|
-
// Arrays.
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
if (array) {
|
|
140
|
-
return <ArrayField property={property} path={path} inputProps={inputProps} readonly={readonly} Custom={Custom} />;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
//
|
|
144
|
-
// Standard Inputs.
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
const InputComponent = getInputComponent(type, format);
|
|
148
|
-
if (InputComponent) {
|
|
149
|
-
return (
|
|
150
|
-
<InputComponent
|
|
151
|
-
type={type}
|
|
152
|
-
format={format}
|
|
153
|
-
label={label}
|
|
154
|
-
inputOnly={inline}
|
|
155
|
-
placeholder={placeholder}
|
|
156
|
-
readonly={readonly}
|
|
157
|
-
{...inputProps}
|
|
158
|
-
/>
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
//
|
|
163
|
-
// Select.
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
if (options) {
|
|
167
|
-
return (
|
|
168
|
-
<SelectInput
|
|
169
|
-
type={type}
|
|
170
|
-
format={format}
|
|
171
|
-
readonly={readonly}
|
|
172
|
-
inputOnly={inline}
|
|
173
|
-
label={label}
|
|
174
|
-
options={options.map((option) => ({ value: option, label: String(option) }))}
|
|
175
|
-
placeholder={placeholder}
|
|
176
|
-
{...inputProps}
|
|
177
|
-
/>
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
//
|
|
182
|
-
// Nested Objects.
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
if (type === 'object') {
|
|
186
|
-
const baseNode = findNode(ast, isDiscriminatedUnion);
|
|
187
|
-
const typeLiteral = baseNode
|
|
188
|
-
? getDiscriminatedType(baseNode, inputProps.getValue() as any)
|
|
189
|
-
: findNode(ast, SchemaAST.isTypeLiteral);
|
|
190
|
-
|
|
191
|
-
if (typeLiteral) {
|
|
192
|
-
return (
|
|
193
|
-
<>
|
|
194
|
-
{!inline && <h3 className={mx('text-lg mlb-inputSpacingBlock first:mbs-0')}>{label}</h3>}
|
|
195
|
-
<FormFields
|
|
196
|
-
schema={Schema.make(typeLiteral)}
|
|
197
|
-
path={path}
|
|
198
|
-
readonly={readonly}
|
|
199
|
-
projection={projection}
|
|
200
|
-
onQueryRefOptions={onQueryRefOptions}
|
|
201
|
-
createOptionLabel={createOptionLabel}
|
|
202
|
-
createOptionIcon={createOptionIcon}
|
|
203
|
-
onCreate={onCreate}
|
|
204
|
-
Custom={Custom}
|
|
205
|
-
lookupComponent={lookupComponent}
|
|
206
|
-
/>
|
|
207
|
-
</>
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return null;
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
export type FormFieldsProps = ThemedClassName<
|
|
216
|
-
{
|
|
217
|
-
testId?: string;
|
|
218
|
-
schema: Schema.Schema.All;
|
|
219
|
-
/**
|
|
220
|
-
* Path to the current object from the root. Used with nested forms.
|
|
221
|
-
*/
|
|
222
|
-
path?: (string | number)[];
|
|
223
|
-
filter?: (props: SchemaProperty<any>[]) => SchemaProperty<any>[];
|
|
224
|
-
sort?: string[];
|
|
225
|
-
/**
|
|
226
|
-
* Optional projection for projection-based field management
|
|
227
|
-
*/
|
|
228
|
-
projection?: ProjectionModel;
|
|
229
|
-
lookupComponent?: ComponentLookup;
|
|
230
|
-
/**
|
|
231
|
-
* Map of custom renderers for specific properties.
|
|
232
|
-
* Prefer lookupComponent for plugin specific input surfaces.
|
|
233
|
-
*/
|
|
234
|
-
Custom?: Partial<Record<string, InputComponent>>;
|
|
235
|
-
onQueryRefOptions?: QueryRefOptions;
|
|
236
|
-
} & Pick<FormFieldProps, 'readonly'> &
|
|
237
|
-
Pick<
|
|
238
|
-
RefFieldProps,
|
|
239
|
-
| 'onQueryRefOptions'
|
|
240
|
-
| 'createOptionLabel'
|
|
241
|
-
| 'createOptionIcon'
|
|
242
|
-
| 'onCreate'
|
|
243
|
-
| 'createSchema'
|
|
244
|
-
| 'createInitialValuePath'
|
|
245
|
-
>
|
|
246
|
-
>;
|
|
247
|
-
|
|
248
|
-
export const FormFields = forwardRef<HTMLDivElement, FormFieldsProps>(
|
|
249
|
-
(
|
|
250
|
-
{
|
|
251
|
-
classNames,
|
|
252
|
-
schema,
|
|
253
|
-
path,
|
|
254
|
-
filter,
|
|
255
|
-
sort,
|
|
256
|
-
projection,
|
|
257
|
-
readonly,
|
|
258
|
-
lookupComponent,
|
|
259
|
-
Custom,
|
|
260
|
-
onQueryRefOptions,
|
|
261
|
-
...props
|
|
262
|
-
},
|
|
263
|
-
forwardRef,
|
|
264
|
-
) => {
|
|
265
|
-
const values = useFormValues(path);
|
|
266
|
-
const properties = useMemo(() => {
|
|
267
|
-
const props = getSchemaProperties(schema.ast, values);
|
|
268
|
-
|
|
269
|
-
// Use projection-based field management when view and projection are available
|
|
270
|
-
if (projection) {
|
|
271
|
-
const fieldProjections = projection.getFieldProjections();
|
|
272
|
-
const hiddenProperties = new Set(projection.getHiddenProperties());
|
|
273
|
-
|
|
274
|
-
// Filter properties to only include visible ones and order by projection
|
|
275
|
-
const visibleProps = props.filter((prop) => !hiddenProperties.has(prop.name));
|
|
276
|
-
const orderedProps: SchemaProperty<any>[] = [];
|
|
277
|
-
|
|
278
|
-
// Add properties in projection field order
|
|
279
|
-
for (const fieldProjection of fieldProjections) {
|
|
280
|
-
const fieldPath = String(fieldProjection.field.path);
|
|
281
|
-
const prop = visibleProps.find((p) => p.name === fieldPath);
|
|
282
|
-
if (prop) {
|
|
283
|
-
orderedProps.push(prop);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Add any remaining properties not in projection
|
|
288
|
-
const projectionPaths = new Set(fieldProjections.map((fp) => String(fp.field.path)));
|
|
289
|
-
const remainingProps = visibleProps.filter((prop) => !projectionPaths.has(prop.name));
|
|
290
|
-
orderedProps.push(...remainingProps);
|
|
291
|
-
|
|
292
|
-
return orderedProps;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// Fallback to legacy filter/sort behavior
|
|
296
|
-
const filtered = filter ? filter(props) : props;
|
|
297
|
-
return sort ? filtered.sort((a, b) => sort.indexOf(a.name) - sort.indexOf(b.name)) : filtered;
|
|
298
|
-
}, [schema, values, filter, sort, projection?.fields]);
|
|
299
|
-
|
|
300
|
-
return (
|
|
301
|
-
<div role='form' className={mx('is-full', classNames)} ref={forwardRef}>
|
|
302
|
-
{properties
|
|
303
|
-
.map((property) => {
|
|
304
|
-
return (
|
|
305
|
-
<FormField
|
|
306
|
-
key={property.name}
|
|
307
|
-
property={property}
|
|
308
|
-
path={[...(path ?? []), property.name]}
|
|
309
|
-
readonly={readonly}
|
|
310
|
-
projection={projection}
|
|
311
|
-
onQueryRefOptions={onQueryRefOptions}
|
|
312
|
-
lookupComponent={lookupComponent}
|
|
313
|
-
Custom={Custom}
|
|
314
|
-
{...props}
|
|
315
|
-
/>
|
|
316
|
-
);
|
|
317
|
-
})
|
|
318
|
-
.filter(isTruthy)}
|
|
319
|
-
</div>
|
|
320
|
-
);
|
|
321
|
-
},
|
|
322
|
-
);
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2025 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import React, {
|
|
6
|
-
type FocusEvent,
|
|
7
|
-
type PropsWithChildren,
|
|
8
|
-
type RefObject,
|
|
9
|
-
createContext,
|
|
10
|
-
useCallback,
|
|
11
|
-
useContext,
|
|
12
|
-
useEffect,
|
|
13
|
-
useMemo,
|
|
14
|
-
} from 'react';
|
|
15
|
-
|
|
16
|
-
import { raise } from '@dxos/debug';
|
|
17
|
-
import { type BaseObject, getValue } from '@dxos/echo/internal';
|
|
18
|
-
import { type SimpleType, createJsonPath } from '@dxos/effect';
|
|
19
|
-
|
|
20
|
-
import { type FormHandler, type FormOptions, useForm } from '../../hooks';
|
|
21
|
-
|
|
22
|
-
type FormContextValue<T extends BaseObject> = FormHandler<T>;
|
|
23
|
-
|
|
24
|
-
const FormContext = createContext<FormContextValue<any> | undefined>(undefined);
|
|
25
|
-
|
|
26
|
-
export const useFormContext = <T extends BaseObject>(): FormContextValue<T> => {
|
|
27
|
-
return useContext(FormContext) ?? raise(new Error('Missing FormContext'));
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export const useFormValues = (path: (string | number)[] = []): any => {
|
|
31
|
-
const { values: formValues } = useFormContext();
|
|
32
|
-
const jsonPath = createJsonPath(path);
|
|
33
|
-
return getValue(formValues, jsonPath) as BaseObject;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export type FormInputStateProps = {
|
|
37
|
-
getStatus: () => { status?: 'error'; error?: string };
|
|
38
|
-
getValue: <V>() => V | undefined;
|
|
39
|
-
onValueChange: (type: SimpleType, value: any) => void;
|
|
40
|
-
onBlur: (event: FocusEvent<HTMLElement>) => void;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export const useInputProps = (path: (string | number)[] = []): FormInputStateProps => {
|
|
44
|
-
const { getStatus, getValue: getFormValue, onValueChange, onTouched } = useFormContext();
|
|
45
|
-
|
|
46
|
-
const stablePath = useMemo(() => path, [Array.isArray(path) ? path.join('.') : path]);
|
|
47
|
-
return useMemo(
|
|
48
|
-
() => ({
|
|
49
|
-
getStatus: () => getStatus(stablePath),
|
|
50
|
-
getValue: () => getFormValue(stablePath),
|
|
51
|
-
onValueChange: (type: SimpleType, value: any) => onValueChange(stablePath, type, value),
|
|
52
|
-
onBlur: () => onTouched(stablePath),
|
|
53
|
-
}),
|
|
54
|
-
[getStatus, getFormValue, onValueChange, onTouched, stablePath],
|
|
55
|
-
);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export const FormProvider = ({
|
|
59
|
-
children,
|
|
60
|
-
formRef,
|
|
61
|
-
autoSave,
|
|
62
|
-
...formOptions
|
|
63
|
-
}: PropsWithChildren<
|
|
64
|
-
FormOptions<any> & {
|
|
65
|
-
formRef?: RefObject<HTMLDivElement | null>;
|
|
66
|
-
autoSave?: boolean;
|
|
67
|
-
}
|
|
68
|
-
>) => {
|
|
69
|
-
const form = useForm(formOptions);
|
|
70
|
-
|
|
71
|
-
const handleKeyDown = useCallback(
|
|
72
|
-
(event: KeyboardEvent) => {
|
|
73
|
-
const keyIsEnter = event.key === 'Enter';
|
|
74
|
-
const modifierUsed = event.ctrlKey || event.altKey || event.metaKey || event.shiftKey;
|
|
75
|
-
const inputIsTextarea = (event.target as HTMLElement).tagName.toLowerCase() === 'textarea';
|
|
76
|
-
const inputOptOut =
|
|
77
|
-
(event.target as HTMLElement).hasAttribute('data-no-submit') ||
|
|
78
|
-
(event.target as HTMLElement).closest('[data-no-submit]') !== null;
|
|
79
|
-
|
|
80
|
-
// Regular inputs: Submit on Enter (no modifiers).
|
|
81
|
-
const shouldSubmitRegularInput = !inputIsTextarea && keyIsEnter && !modifierUsed;
|
|
82
|
-
|
|
83
|
-
// Textareas: Submit only on Meta+Enter.
|
|
84
|
-
const shouldSubmitTextarea = inputIsTextarea && keyIsEnter && event.metaKey;
|
|
85
|
-
|
|
86
|
-
if ((shouldSubmitRegularInput || shouldSubmitTextarea) && !inputOptOut) {
|
|
87
|
-
if (!autoSave && form.canSave) {
|
|
88
|
-
form.handleSave();
|
|
89
|
-
}
|
|
90
|
-
if (autoSave && form.formIsValid) {
|
|
91
|
-
(event.target as HTMLElement).blur();
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
[form.canSave, form.formIsValid, form.handleSave, autoSave],
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
useEffect(() => {
|
|
99
|
-
if (!formRef?.current) {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const formElement = formRef.current;
|
|
104
|
-
|
|
105
|
-
formElement.addEventListener('keydown', handleKeyDown);
|
|
106
|
-
return () => formElement.removeEventListener('keydown', handleKeyDown);
|
|
107
|
-
}, [formRef, form.canSave, form.formIsValid, form.handleSave, autoSave]);
|
|
108
|
-
|
|
109
|
-
return <FormContext.Provider value={form}>{children}</FormContext.Provider>;
|
|
110
|
-
};
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2024 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import React, { type FC } from 'react';
|
|
6
|
-
|
|
7
|
-
import { type FormatEnum } from '@dxos/echo/internal';
|
|
8
|
-
import { type SimpleType } from '@dxos/effect';
|
|
9
|
-
import { Icon, Input, Tooltip } from '@dxos/react-ui';
|
|
10
|
-
import { labelSpacing } from '@dxos/react-ui-stack';
|
|
11
|
-
import { errorText, mx } from '@dxos/react-ui-theme';
|
|
12
|
-
|
|
13
|
-
import { type FormInputStateProps } from './FormContext';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Props passed to input components.
|
|
17
|
-
*/
|
|
18
|
-
export type InputProps = {
|
|
19
|
-
type: SimpleType;
|
|
20
|
-
format?: FormatEnum;
|
|
21
|
-
label: string;
|
|
22
|
-
placeholder?: string;
|
|
23
|
-
inputOnly?: boolean;
|
|
24
|
-
/**
|
|
25
|
-
* Specifies the readonly variant: either disabled inputs, elements indicating they are usually editable but currently are not; or `static`, a field’s representation as regular content without signifiers that it is ever editable.
|
|
26
|
-
*/
|
|
27
|
-
readonly?: 'disabled-input' | 'static' | false;
|
|
28
|
-
} & FormInputStateProps;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Form input component.
|
|
32
|
-
*/
|
|
33
|
-
export type InputComponent = FC<InputProps>;
|
|
34
|
-
|
|
35
|
-
export type InputHeaderProps = {
|
|
36
|
-
error?: string;
|
|
37
|
-
label: string;
|
|
38
|
-
readonly?: boolean;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export const InputHeader = ({ error, label, readonly }: InputHeaderProps) => {
|
|
42
|
-
const Label = readonly ? 'span' : Input.Label;
|
|
43
|
-
const labelProps = readonly ? {} : { classNames: '!mlb-0' };
|
|
44
|
-
return (
|
|
45
|
-
<div role='none' className={mx('flex justify-between items-center', labelSpacing)}>
|
|
46
|
-
<Label {...labelProps}>{label}</Label>
|
|
47
|
-
{error && (
|
|
48
|
-
<Tooltip.Trigger asChild content={error} side='bottom'>
|
|
49
|
-
<Icon icon='ph--warning--regular' size={4} classNames={errorText} />
|
|
50
|
-
</Tooltip.Trigger>
|
|
51
|
-
)}
|
|
52
|
-
</div>
|
|
53
|
-
);
|
|
54
|
-
};
|