@dxos/react-ui-form 0.7.5-feature-compute.4d9d99a → 0.7.5-labs.401163d
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 +881 -556
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +910 -596
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +881 -556
- 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.map +1 -1
- package/dist/types/src/components/Form/ArrayField.d.ts +16 -0
- package/dist/types/src/components/Form/ArrayField.d.ts.map +1 -0
- package/dist/types/src/components/Form/Defaults.d.ts +6 -6
- package/dist/types/src/components/Form/Defaults.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +12 -21
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +2 -1
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormActions.d.ts +7 -0
- package/dist/types/src/components/Form/FormActions.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormContent.d.ts +26 -0
- package/dist/types/src/components/Form/FormContent.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormContext.d.ts +23 -0
- package/dist/types/src/components/Form/FormContext.d.ts.map +1 -0
- package/dist/types/src/components/Form/Input.d.ts +5 -6
- package/dist/types/src/components/Form/Input.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +1 -2
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +4 -0
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +1 -0
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +7 -0
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/custom/TupleInput.d.ts +1 -2
- package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +1 -1
- package/dist/types/src/components/Form/custom/index.d.ts +1 -0
- package/dist/types/src/components/Form/custom/index.d.ts.map +1 -1
- package/dist/types/src/components/Form/factory.d.ts +2 -2
- package/dist/types/src/components/Form/factory.d.ts.map +1 -1
- package/dist/types/src/components/Form/index.d.ts +2 -0
- 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/deprecated/FormInput.d.ts +5 -0
- package/dist/types/src/components/deprecated/FormInput.d.ts.map +1 -1
- package/dist/types/src/hooks/useForm.d.ts +10 -10
- package/dist/types/src/hooks/useForm.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +6 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +25 -24
- package/src/components/FieldEditor/FieldEditor.stories.tsx +1 -1
- package/src/components/FieldEditor/FieldEditor.tsx +7 -6
- package/src/components/Form/ArrayField.tsx +111 -0
- package/src/components/Form/Defaults.tsx +78 -36
- package/src/components/Form/Form.stories.tsx +15 -5
- package/src/components/Form/Form.tsx +50 -355
- package/src/components/Form/FormActions.tsx +41 -0
- package/src/components/Form/FormContent.tsx +170 -0
- package/src/components/Form/FormContext.tsx +92 -0
- package/src/components/Form/Input.tsx +5 -6
- package/src/components/Form/custom/GeoPointInput.tsx +6 -15
- package/src/components/Form/custom/SelectOptionsInput.stories.tsx +58 -0
- package/src/components/Form/custom/SelectOptionsInput.tsx +190 -0
- package/src/components/Form/custom/TupleInput.tsx +5 -9
- package/src/components/Form/custom/index.ts +1 -0
- package/src/components/Form/factory.tsx +5 -6
- package/src/components/Form/index.ts +3 -0
- package/src/components/ViewEditor/ViewEditor.tsx +7 -4
- package/src/components/deprecated/FormInput.tsx +20 -7
- package/src/hooks/useForm.ts +60 -47
- package/src/translations.ts +8 -0
package/src/hooks/useForm.ts
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type BaseObject,
|
|
8
|
-
import { type
|
|
7
|
+
import { type BaseObject, getValue, setValue } from '@dxos/echo-schema';
|
|
8
|
+
import { AST, type S, type SimpleType, type JsonPath, createJsonPath, fromEffectValidationPath } from '@dxos/effect';
|
|
9
9
|
import { invariant } from '@dxos/invariant';
|
|
10
10
|
import { log } from '@dxos/log';
|
|
11
11
|
import { validateSchema, type ValidationError } from '@dxos/schema';
|
|
@@ -20,20 +20,21 @@ export type FormHandler<T extends BaseObject> = {
|
|
|
20
20
|
//
|
|
21
21
|
|
|
22
22
|
values: Partial<T>;
|
|
23
|
-
errors: Record<
|
|
24
|
-
touched: Record<
|
|
25
|
-
changed: Record<
|
|
23
|
+
errors: Record<JsonPath, string>;
|
|
24
|
+
touched: Record<JsonPath, boolean>;
|
|
25
|
+
changed: Record<JsonPath, boolean>;
|
|
26
26
|
canSave: boolean;
|
|
27
|
+
formIsValid: boolean;
|
|
27
28
|
handleSave: () => void;
|
|
28
29
|
|
|
29
30
|
//
|
|
30
31
|
// Form input component helpers.
|
|
31
32
|
//
|
|
32
33
|
|
|
33
|
-
getStatus: (
|
|
34
|
-
getValue: <V>(
|
|
35
|
-
onValueChange: <V>(
|
|
36
|
-
|
|
34
|
+
getStatus: (path: string | (string | number)[]) => { status?: 'error'; error?: string };
|
|
35
|
+
getValue: <V>(path: (string | number)[]) => V | undefined;
|
|
36
|
+
onValueChange: <V>(path: (string | number)[], type: SimpleType, value: V) => void;
|
|
37
|
+
onTouched: (path: (string | number)[]) => void;
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
/**
|
|
@@ -95,16 +96,15 @@ export const useForm = <T extends BaseObject>({
|
|
|
95
96
|
setValues(initialValues);
|
|
96
97
|
}, [initialValues]);
|
|
97
98
|
|
|
98
|
-
const [touched, setTouched] = useState<Record<
|
|
99
|
-
const [changed, setChanged] = useState<Record<
|
|
100
|
-
const [errors, setErrors] = useState<Record<
|
|
99
|
+
const [touched, setTouched] = useState<Record<JsonPath, boolean>>(createKeySet(initialValues, false));
|
|
100
|
+
const [changed, setChanged] = useState<Record<JsonPath, boolean>>(createKeySet(initialValues, false));
|
|
101
|
+
const [errors, setErrors] = useState<Record<JsonPath, string>>({});
|
|
101
102
|
const [saving, setSaving] = useState(false);
|
|
102
103
|
|
|
103
104
|
//
|
|
104
105
|
// Validation.
|
|
105
106
|
//
|
|
106
107
|
|
|
107
|
-
// TODO(burdon): Validate each property separately.
|
|
108
108
|
const validate = useCallback(
|
|
109
109
|
(values: Partial<T>): values is T => {
|
|
110
110
|
let errors: ValidationError[] = validateSchema(schema, values) ?? [];
|
|
@@ -130,6 +130,10 @@ export const useForm = <T extends BaseObject>({
|
|
|
130
130
|
}
|
|
131
131
|
}, [values, schemaDirty]);
|
|
132
132
|
|
|
133
|
+
const formIsValid = useMemo(() => {
|
|
134
|
+
return Object.keys(errors).length === 0;
|
|
135
|
+
}, [errors]);
|
|
136
|
+
|
|
133
137
|
/**
|
|
134
138
|
* NOTE: We can submit if there is no touched field that has an error.
|
|
135
139
|
* Basically, if there's a validation message visible in the form, submit should be disabled.
|
|
@@ -137,8 +141,15 @@ export const useForm = <T extends BaseObject>({
|
|
|
137
141
|
const canSave = useMemo(
|
|
138
142
|
() =>
|
|
139
143
|
!saving &&
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
// Check if any error paths that are touched have errors
|
|
145
|
+
!Object.entries(touched).some(
|
|
146
|
+
([path, isTouched]) =>
|
|
147
|
+
isTouched &&
|
|
148
|
+
Object.keys(errors).some(
|
|
149
|
+
(errorPath) => errorPath === path || errorPath.startsWith(`${path}.`) || errorPath.startsWith(`${path}[`),
|
|
150
|
+
),
|
|
151
|
+
),
|
|
152
|
+
[touched, errors, saving],
|
|
142
153
|
);
|
|
143
154
|
|
|
144
155
|
const handleSave = useCallback(async () => {
|
|
@@ -157,19 +168,27 @@ export const useForm = <T extends BaseObject>({
|
|
|
157
168
|
//
|
|
158
169
|
|
|
159
170
|
const getStatus = useCallback<FormHandler<T>['getStatus']>(
|
|
160
|
-
(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
171
|
+
(path) => {
|
|
172
|
+
const jsonPath = Array.isArray(path) ? createJsonPath(path) : path;
|
|
173
|
+
const matchingError = Object.entries(errors).find(
|
|
174
|
+
([errorPath]) =>
|
|
175
|
+
errorPath === jsonPath || errorPath.startsWith(`${jsonPath}.`) || errorPath.startsWith(`${jsonPath}[`),
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
status: matchingError ? 'error' : undefined,
|
|
180
|
+
error: matchingError ? matchingError[1] : undefined,
|
|
181
|
+
};
|
|
182
|
+
},
|
|
183
|
+
[errors],
|
|
165
184
|
);
|
|
166
185
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return getValue(values, property as any as JsonPath);
|
|
186
|
+
const getFormValue = <V>(path: (string | number)[]): V | undefined => {
|
|
187
|
+
return getValue(values, createJsonPath(path));
|
|
170
188
|
};
|
|
171
189
|
|
|
172
|
-
const onValueChange = (
|
|
190
|
+
const onValueChange = (path: (string | number)[], type: SimpleType, value: any) => {
|
|
191
|
+
const jsonPath = createJsonPath(path);
|
|
173
192
|
let parsedValue = value;
|
|
174
193
|
try {
|
|
175
194
|
if (type === 'number') {
|
|
@@ -180,9 +199,9 @@ export const useForm = <T extends BaseObject>({
|
|
|
180
199
|
parsedValue = undefined;
|
|
181
200
|
}
|
|
182
201
|
|
|
183
|
-
const newValues = { ...setValue(values,
|
|
202
|
+
const newValues = { ...setValue(values, jsonPath, parsedValue) };
|
|
184
203
|
setValues(newValues);
|
|
185
|
-
setChanged((prev) => ({ ...prev, [
|
|
204
|
+
setChanged((prev) => ({ ...prev, [jsonPath]: true }));
|
|
186
205
|
onValuesChanged?.(newValues);
|
|
187
206
|
|
|
188
207
|
const isValid = validate(newValues);
|
|
@@ -191,19 +210,10 @@ export const useForm = <T extends BaseObject>({
|
|
|
191
210
|
}
|
|
192
211
|
};
|
|
193
212
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
setTouched((touched) => ({ ...touched, [name]: true }));
|
|
199
|
-
|
|
200
|
-
// TODO(Zan): This should be configurable behavior.
|
|
201
|
-
if (event.relatedTarget?.getAttribute('type') === 'submit') {
|
|
202
|
-
// NOTE: We do this here instead of onSave because the blur event is triggered before the submit event
|
|
203
|
-
// and results in the submit button being disabled when the form is invalid.
|
|
204
|
-
setTouched(createKeySet(values, true));
|
|
205
|
-
}
|
|
206
|
-
|
|
213
|
+
const onTouched = useCallback(
|
|
214
|
+
(path: (string | number)[]) => {
|
|
215
|
+
const jsonPath = createJsonPath(path);
|
|
216
|
+
setTouched((touched) => ({ ...touched, [jsonPath]: true }));
|
|
207
217
|
validate(values);
|
|
208
218
|
},
|
|
209
219
|
[validate, values],
|
|
@@ -217,28 +227,31 @@ export const useForm = <T extends BaseObject>({
|
|
|
217
227
|
changed,
|
|
218
228
|
canSave,
|
|
219
229
|
handleSave,
|
|
230
|
+
formIsValid,
|
|
220
231
|
|
|
221
232
|
// Field utils.
|
|
222
233
|
getStatus,
|
|
223
234
|
getValue: getFormValue,
|
|
224
235
|
onValueChange,
|
|
225
|
-
|
|
236
|
+
onTouched,
|
|
226
237
|
} satisfies FormHandler<T>;
|
|
227
238
|
};
|
|
228
239
|
|
|
229
|
-
const createKeySet = <T extends BaseObject, V>(obj: T, value: V): Record<
|
|
240
|
+
const createKeySet = <T extends BaseObject, V>(obj: T, value: V): Record<JsonPath, V> => {
|
|
230
241
|
invariant(obj);
|
|
231
|
-
return Object.keys(obj).reduce((acc, key) => ({ ...acc, [key]: value }), {} as Record<
|
|
242
|
+
return Object.keys(obj).reduce((acc, key) => ({ ...acc, [key]: value }), {} as Record<JsonPath, V>);
|
|
232
243
|
};
|
|
233
244
|
|
|
234
|
-
const flatMap =
|
|
245
|
+
const flatMap = (errors: ValidationError[]) => {
|
|
235
246
|
return errors.reduce(
|
|
236
247
|
(result, { path, message }) => {
|
|
237
|
-
|
|
238
|
-
|
|
248
|
+
// Convert the validation error path format to our JsonPath format
|
|
249
|
+
const jsonPath = fromEffectValidationPath(path);
|
|
250
|
+
if (!(jsonPath in result)) {
|
|
251
|
+
result[jsonPath] = message;
|
|
239
252
|
}
|
|
240
253
|
return result;
|
|
241
254
|
},
|
|
242
|
-
{} as Record<
|
|
255
|
+
{} as Record<JsonPath, string>,
|
|
243
256
|
);
|
|
244
257
|
};
|
package/src/translations.ts
CHANGED
|
@@ -42,6 +42,7 @@ export default [
|
|
|
42
42
|
'format ref': 'Reference',
|
|
43
43
|
'format regex': 'RegExp',
|
|
44
44
|
'format string': 'String',
|
|
45
|
+
'format single-select': 'Select',
|
|
45
46
|
'format text': 'Long text',
|
|
46
47
|
'format time': 'Time',
|
|
47
48
|
'format timestamp': 'Timestamp',
|
|
@@ -53,6 +54,13 @@ export default [
|
|
|
53
54
|
// Placeholders
|
|
54
55
|
'placeholder latitude': 'Latitude',
|
|
55
56
|
'placeholder longitude': 'Longitude',
|
|
57
|
+
|
|
58
|
+
// SelectOptionsInput
|
|
59
|
+
'select option label': 'Label',
|
|
60
|
+
'select option label placeholder': 'Option label',
|
|
61
|
+
'select option color': 'Color',
|
|
62
|
+
'select option delete': 'Delete',
|
|
63
|
+
'select option add': 'Add option',
|
|
56
64
|
},
|
|
57
65
|
},
|
|
58
66
|
},
|