@firecms/formex 3.0.0-canary.23 → 3.0.0-canary.231
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 +2 -2
- package/dist/Field.d.ts +0 -1
- package/dist/index.es.js +456 -148
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +496 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/useCreateFormex.d.ts +4 -1
- package/dist/utils.d.ts +2 -1
- package/package.json +29 -24
- package/src/Field.tsx +0 -4
- package/src/types.ts +13 -0
- package/src/useCreateFormex.tsx +219 -105
- package/src/utils.ts +1 -1
package/src/useCreateFormex.tsx
CHANGED
@@ -1,28 +1,52 @@
|
|
1
|
-
import React, {
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
2
2
|
import { getIn, setIn } from "./utils";
|
3
|
-
import equal from "react-fast-compare"
|
3
|
+
import equal from "react-fast-compare";
|
4
4
|
|
5
5
|
import { FormexController, FormexResetProps } from "./types";
|
6
6
|
|
7
|
-
export function useCreateFormex<T extends object>({
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
7
|
+
export function useCreateFormex<T extends object>({
|
8
|
+
initialValues,
|
9
|
+
initialErrors,
|
10
|
+
initialDirty,
|
11
|
+
validation,
|
12
|
+
validateOnChange = false,
|
13
|
+
validateOnInitialRender = false,
|
14
|
+
onSubmit,
|
15
|
+
onReset,
|
16
|
+
debugId,
|
17
|
+
}: {
|
18
|
+
initialValues: T;
|
19
|
+
initialErrors?: Record<string, string>;
|
20
|
+
initialDirty?: boolean;
|
21
|
+
validateOnChange?: boolean;
|
22
|
+
validateOnInitialRender?: boolean;
|
23
|
+
validation?: (
|
24
|
+
values: T
|
25
|
+
) =>
|
26
|
+
| Record<string, string>
|
27
|
+
| Promise<Record<string, string>>
|
28
|
+
| undefined
|
29
|
+
| void;
|
30
|
+
onSubmit?: (values: T, controller: FormexController<T>) => void | Promise<void>;
|
31
|
+
onReset?: (controller: FormexController<T>) => void | Promise<void>;
|
32
|
+
debugId?: string;
|
14
33
|
}): FormexController<T> {
|
15
|
-
|
16
|
-
const
|
17
|
-
const
|
34
|
+
const initialValuesRef = useRef<T>(initialValues);
|
35
|
+
const valuesRef = useRef<T>(initialValues);
|
36
|
+
const debugIdRef = useRef<string | undefined>(debugId);
|
18
37
|
|
19
38
|
const [values, setValuesInner] = useState<T>(initialValues);
|
20
39
|
const [touchedState, setTouchedState] = useState<Record<string, boolean>>({});
|
21
40
|
const [errors, setErrors] = useState<Record<string, string>>(initialErrors ?? {});
|
22
|
-
const [dirty, setDirty] = useState(false);
|
41
|
+
const [dirty, setDirty] = useState(initialDirty ?? false);
|
23
42
|
const [submitCount, setSubmitCount] = useState(0);
|
24
43
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
25
44
|
const [isValidating, setIsValidating] = useState(false);
|
45
|
+
const [version, setVersion] = useState(0);
|
46
|
+
|
47
|
+
// Replace state for history with refs
|
48
|
+
const historyRef = useRef<T[]>([initialValues]);
|
49
|
+
const historyIndexRef = useRef<number>(0);
|
26
50
|
|
27
51
|
useEffect(() => {
|
28
52
|
if (validateOnInitialRender) {
|
@@ -30,121 +54,211 @@ export function useCreateFormex<T extends object>({ initialValues, initialErrors
|
|
30
54
|
}
|
31
55
|
}, []);
|
32
56
|
|
33
|
-
const setValues = (newValues: T) => {
|
57
|
+
const setValues = useCallback((newValues: T) => {
|
34
58
|
valuesRef.current = newValues;
|
35
59
|
setValuesInner(newValues);
|
36
|
-
setDirty(equal(initialValuesRef.current, newValues));
|
37
|
-
|
60
|
+
setDirty(!equal(initialValuesRef.current, newValues));
|
61
|
+
// Update history using refs
|
62
|
+
const newHistory = historyRef.current.slice(0, historyIndexRef.current + 1);
|
63
|
+
newHistory.push(newValues);
|
64
|
+
historyRef.current = newHistory;
|
65
|
+
historyIndexRef.current = newHistory.length - 1;
|
66
|
+
}, []);
|
38
67
|
|
39
|
-
const validate = async () => {
|
68
|
+
const validate = useCallback(async () => {
|
40
69
|
setIsValidating(true);
|
41
|
-
const
|
42
|
-
const validationErrors = await validation?.(values);
|
70
|
+
const validationErrors = await validation?.(valuesRef.current);
|
43
71
|
setErrors(validationErrors ?? {});
|
44
72
|
setIsValidating(false);
|
45
73
|
return validationErrors;
|
46
|
-
}
|
74
|
+
}, [validation]);
|
47
75
|
|
48
|
-
const setFieldValue = (
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
}
|
65
|
-
|
66
|
-
|
67
|
-
setErrors(newErrors);
|
68
|
-
}
|
69
|
-
|
70
|
-
const setFieldTouched = (key: string, touched: boolean, shouldValidate?: boolean | undefined) => {
|
71
|
-
const newTouched = { ...touchedState };
|
72
|
-
newTouched[key] = touched;
|
73
|
-
setTouchedState(newTouched);
|
74
|
-
if (shouldValidate) {
|
75
|
-
validate();
|
76
|
-
}
|
77
|
-
}
|
76
|
+
const setFieldValue = useCallback(
|
77
|
+
(key: string, value: any, shouldValidate?: boolean) => {
|
78
|
+
const newValues = setIn(valuesRef.current, key, value);
|
79
|
+
valuesRef.current = newValues;
|
80
|
+
setValuesInner(newValues);
|
81
|
+
if (!equal(getIn(initialValuesRef.current, key), value)) {
|
82
|
+
setDirty(true);
|
83
|
+
}
|
84
|
+
if (shouldValidate) {
|
85
|
+
validate();
|
86
|
+
}
|
87
|
+
// Update history using refs
|
88
|
+
const newHistory = historyRef.current.slice(0, historyIndexRef.current + 1);
|
89
|
+
newHistory.push(newValues);
|
90
|
+
historyRef.current = newHistory;
|
91
|
+
historyIndexRef.current = newHistory.length - 1;
|
92
|
+
},
|
93
|
+
[validate]
|
94
|
+
);
|
78
95
|
|
79
|
-
const
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
96
|
+
const setFieldError = useCallback((key: string, error: string | undefined) => {
|
97
|
+
setErrors((prevErrors) => {
|
98
|
+
const newErrors = { ...prevErrors };
|
99
|
+
if (error) {
|
100
|
+
newErrors[key] = error;
|
101
|
+
} else {
|
102
|
+
delete newErrors[key];
|
103
|
+
}
|
104
|
+
return newErrors;
|
105
|
+
});
|
106
|
+
}, []);
|
86
107
|
|
87
|
-
const
|
108
|
+
const setFieldTouched = useCallback(
|
109
|
+
(key: string, touched: boolean, shouldValidate?: boolean) => {
|
110
|
+
setTouchedState((prev) => ({
|
111
|
+
...prev,
|
112
|
+
[key]: touched,
|
113
|
+
}));
|
114
|
+
if (shouldValidate) {
|
115
|
+
validate();
|
116
|
+
}
|
117
|
+
},
|
118
|
+
[validate]
|
119
|
+
);
|
120
|
+
|
121
|
+
const handleChange = useCallback(
|
122
|
+
(event: React.SyntheticEvent) => {
|
123
|
+
const target = event.target as HTMLInputElement;
|
124
|
+
let value;
|
125
|
+
if (target.type === "checkbox") {
|
126
|
+
value = target.checked;
|
127
|
+
} else if (target.type === "number") {
|
128
|
+
value = target.valueAsNumber;
|
129
|
+
} else {
|
130
|
+
value = target.value;
|
131
|
+
}
|
132
|
+
const name = target.name;
|
133
|
+
setFieldValue(name, value, validateOnChange);
|
134
|
+
setFieldTouched(name, true);
|
135
|
+
},
|
136
|
+
[setFieldValue, setFieldTouched, validateOnChange]
|
137
|
+
);
|
138
|
+
|
139
|
+
const handleBlur = useCallback((event: React.FocusEvent) => {
|
88
140
|
const target = event.target as HTMLInputElement;
|
89
141
|
const name = target.name;
|
90
142
|
setFieldTouched(name, true);
|
91
|
-
}
|
143
|
+
}, [setFieldTouched]);
|
92
144
|
|
93
|
-
const submit =
|
94
|
-
e
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
145
|
+
const submit = useCallback(
|
146
|
+
async (e?: React.FormEvent<HTMLFormElement>) => {
|
147
|
+
e?.preventDefault();
|
148
|
+
e?.stopPropagation();
|
149
|
+
setIsSubmitting(true);
|
150
|
+
setSubmitCount((prev) => prev + 1);
|
151
|
+
const validationErrors = await validation?.(valuesRef.current);
|
152
|
+
if (validationErrors && Object.keys(validationErrors).length > 0) {
|
153
|
+
setErrors(validationErrors);
|
154
|
+
} else {
|
155
|
+
setErrors({});
|
156
|
+
await onSubmit?.(valuesRef.current, controllerRef.current);
|
157
|
+
}
|
158
|
+
setIsSubmitting(false);
|
159
|
+
setVersion((prev) => prev + 1);
|
160
|
+
},
|
161
|
+
[onSubmit, validation]
|
162
|
+
);
|
107
163
|
|
108
|
-
const resetForm = (props?: FormexResetProps<T>) => {
|
164
|
+
const resetForm = useCallback((props?: FormexResetProps<T>) => {
|
109
165
|
const {
|
110
166
|
submitCount: submitCountProp,
|
111
167
|
values: valuesProp,
|
112
168
|
errors: errorsProp,
|
113
169
|
touched: touchedProp
|
114
170
|
} = props ?? {};
|
115
|
-
|
116
|
-
|
117
|
-
setValuesInner(valuesProp ??
|
171
|
+
valuesRef.current = valuesProp ?? initialValuesRef.current;
|
172
|
+
initialValuesRef.current = valuesProp ?? initialValuesRef.current;
|
173
|
+
setValuesInner(valuesProp ?? initialValuesRef.current);
|
118
174
|
setErrors(errorsProp ?? {});
|
119
175
|
setTouchedState(touchedProp ?? {});
|
120
176
|
setDirty(false);
|
121
177
|
setSubmitCount(submitCountProp ?? 0);
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
178
|
+
setVersion((prev) => prev + 1);
|
179
|
+
onReset?.(controllerRef.current);
|
180
|
+
// Reset history with refs
|
181
|
+
historyRef.current = [valuesProp ?? initialValuesRef.current];
|
182
|
+
historyIndexRef.current = 0;
|
183
|
+
}, [onReset]);
|
184
|
+
|
185
|
+
const undo = useCallback(() => {
|
186
|
+
if (historyIndexRef.current > 0) {
|
187
|
+
const newIndex = historyIndexRef.current - 1;
|
188
|
+
const newValues = historyRef.current[newIndex];
|
189
|
+
setValuesInner(newValues);
|
190
|
+
valuesRef.current = newValues;
|
191
|
+
historyIndexRef.current = newIndex;
|
192
|
+
}
|
193
|
+
}, []);
|
194
|
+
|
195
|
+
const redo = useCallback(() => {
|
196
|
+
if (historyIndexRef.current < historyRef.current.length - 1) {
|
197
|
+
const newIndex = historyIndexRef.current + 1;
|
198
|
+
const newValues = historyRef.current[newIndex];
|
199
|
+
setValuesInner(newValues);
|
200
|
+
valuesRef.current = newValues;
|
201
|
+
historyIndexRef.current = newIndex;
|
202
|
+
}
|
203
|
+
}, []);
|
204
|
+
|
205
|
+
const controllerRef = useRef<FormexController<T>>({} as FormexController<T>);
|
206
|
+
|
207
|
+
const controller = useMemo<FormexController<T>>(
|
208
|
+
() => ({
|
209
|
+
values,
|
210
|
+
initialValues: initialValuesRef.current,
|
211
|
+
handleChange,
|
212
|
+
isSubmitting,
|
213
|
+
setSubmitting: setIsSubmitting,
|
214
|
+
setValues,
|
215
|
+
setFieldValue,
|
216
|
+
errors,
|
217
|
+
setFieldError,
|
218
|
+
touched: touchedState,
|
219
|
+
setFieldTouched,
|
220
|
+
dirty,
|
221
|
+
setDirty,
|
222
|
+
handleSubmit: submit,
|
223
|
+
submitCount,
|
224
|
+
setSubmitCount,
|
225
|
+
handleBlur,
|
226
|
+
validate,
|
227
|
+
isValidating,
|
228
|
+
resetForm,
|
229
|
+
version,
|
230
|
+
debugId: debugIdRef.current,
|
231
|
+
undo,
|
232
|
+
redo,
|
233
|
+
canUndo: historyIndexRef.current > 0,
|
234
|
+
canRedo: historyIndexRef.current < historyRef.current.length - 1,
|
235
|
+
}),
|
236
|
+
[
|
237
|
+
values,
|
238
|
+
errors,
|
239
|
+
touchedState,
|
240
|
+
dirty,
|
241
|
+
isSubmitting,
|
242
|
+
submitCount,
|
243
|
+
isValidating,
|
244
|
+
version,
|
245
|
+
handleChange,
|
246
|
+
handleBlur,
|
247
|
+
setValues,
|
248
|
+
setFieldValue,
|
249
|
+
setFieldTouched,
|
250
|
+
setFieldError,
|
251
|
+
validate,
|
252
|
+
submit,
|
253
|
+
resetForm,
|
254
|
+
undo,
|
255
|
+
redo,
|
256
|
+
]
|
257
|
+
);
|
258
|
+
|
259
|
+
useEffect(() => {
|
260
|
+
controllerRef.current = controller;
|
261
|
+
}, [controller]);
|
262
|
+
|
263
|
+
return controller;
|
150
264
|
}
|
package/src/utils.ts
CHANGED
@@ -152,7 +152,7 @@ export function setNestedObjectValues<T>(
|
|
152
152
|
return response;
|
153
153
|
}
|
154
154
|
|
155
|
-
function clone(value: any) {
|
155
|
+
export function clone(value: any) {
|
156
156
|
if (Array.isArray(value)) {
|
157
157
|
return [...value];
|
158
158
|
} else if (typeof value === "object" && value !== null) {
|