@explita/formly 0.1.0
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 +261 -0
- package/README.old.md +141 -0
- package/dist/components/field-error.d.ts +2 -0
- package/dist/components/field-error.js +14 -0
- package/dist/components/field.d.ts +4 -0
- package/dist/components/field.js +120 -0
- package/dist/components/form-spy.d.ts +16 -0
- package/dist/components/form-spy.js +66 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.js +20 -0
- package/dist/components/label.d.ts +2 -0
- package/dist/components/label.js +46 -0
- package/dist/hooks/use-field.d.ts +21 -0
- package/dist/hooks/use-field.js +66 -0
- package/dist/hooks/use-form-by-id.d.ts +6 -0
- package/dist/hooks/use-form-by-id.js +25 -0
- package/dist/hooks/use-form-context.d.ts +5 -0
- package/dist/hooks/use-form-context.js +17 -0
- package/dist/hooks/use-form.d.ts +43 -0
- package/dist/hooks/use-form.js +961 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +25 -0
- package/dist/lib/array-helpers.d.ts +6 -0
- package/dist/lib/array-helpers.js +281 -0
- package/dist/lib/css.d.ts +1 -0
- package/dist/lib/css.js +45 -0
- package/dist/lib/debounce.d.ts +13 -0
- package/dist/lib/debounce.js +28 -0
- package/dist/lib/deep-path.d.ts +4 -0
- package/dist/lib/deep-path.js +60 -0
- package/dist/lib/drafts-helpter.d.ts +31 -0
- package/dist/lib/drafts-helpter.js +67 -0
- package/dist/lib/form-registry.d.ts +9 -0
- package/dist/lib/form-registry.js +24 -0
- package/dist/lib/group-helpers.d.ts +9 -0
- package/dist/lib/group-helpers.js +29 -0
- package/dist/lib/pub-sub.d.ts +13 -0
- package/dist/lib/pub-sub.js +38 -0
- package/dist/lib/utils.d.ts +17 -0
- package/dist/lib/utils.js +190 -0
- package/dist/lib/validation.d.ts +22 -0
- package/dist/lib/validation.js +46 -0
- package/dist/lib/zod-helpers.d.ts +5 -0
- package/dist/lib/zod-helpers.js +63 -0
- package/dist/providers/form.d.ts +51 -0
- package/dist/providers/form.js +63 -0
- package/dist/types/array.d.ts +197 -0
- package/dist/types/array.js +2 -0
- package/dist/types/field.d.ts +61 -0
- package/dist/types/field.js +2 -0
- package/dist/types/group.d.ts +16 -0
- package/dist/types/group.js +2 -0
- package/dist/types/path.d.ts +8 -0
- package/dist/types/path.js +2 -0
- package/dist/types/pub-sub.d.ts +2 -0
- package/dist/types/pub-sub.js +2 -0
- package/dist/types/utils.d.ts +310 -0
- package/dist/types/utils.js +2 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +14 -0
- package/package.json +53 -0
|
@@ -0,0 +1,961 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.useForm = useForm;
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const utils_1 = require("../utils");
|
|
7
|
+
const utils_2 = require("../lib/utils");
|
|
8
|
+
const deep_path_1 = require("../lib/deep-path");
|
|
9
|
+
const array_helpers_1 = require("../lib/array-helpers");
|
|
10
|
+
const zod_helpers_1 = require("../lib/zod-helpers");
|
|
11
|
+
const group_helpers_1 = require("../lib/group-helpers");
|
|
12
|
+
const components_1 = require("../components");
|
|
13
|
+
const pub_sub_1 = require("../lib/pub-sub");
|
|
14
|
+
const form_registry_1 = require("../lib/form-registry");
|
|
15
|
+
/**
|
|
16
|
+
* useForm is a React hook that allows you to manage form state and validation.
|
|
17
|
+
*
|
|
18
|
+
* @param schema - a Zod schema object that defines the form structure and validation rules.
|
|
19
|
+
* @param defaultValues - an object containing the default values for the form fields.
|
|
20
|
+
* @param errors - an object containing the initial errors for the form fields.
|
|
21
|
+
* @param mode - the form mode, either "controlled" or "uncontrolled".
|
|
22
|
+
* @param errorParser - a function that takes an error and returns a string to be displayed to the user.
|
|
23
|
+
* @param persistKey - a string that defines the key under which the form state is persisted.
|
|
24
|
+
* @param check - a function that takes the form values and an object containing the focus method for each field.
|
|
25
|
+
* @param onSubmit - a function that takes the form values and an object containing the focus method for each field.
|
|
26
|
+
* @param persistKey - a string that defines the key under which the form state is persisted.
|
|
27
|
+
* @param computed - an object containing the computed fields.
|
|
28
|
+
* @param autoFocusOnError - a boolean that defines whether to focus on the first error field.
|
|
29
|
+
* @param savedFormFirst - a boolean that defines whether to prioritize the saved form state.
|
|
30
|
+
* @param validateOn - a string that defines when to validate the form.
|
|
31
|
+
*
|
|
32
|
+
* @returns an object containing the useForm API methods.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* const form = useForm({
|
|
36
|
+
* schema,
|
|
37
|
+
* defaultValues,
|
|
38
|
+
* errors,
|
|
39
|
+
* mode,
|
|
40
|
+
* errorParser,
|
|
41
|
+
* persistKey,
|
|
42
|
+
* check,
|
|
43
|
+
* onSubmit: async(values)=>{}
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* <Form use={form}>
|
|
47
|
+
* {formFields.map((field) => (
|
|
48
|
+
* <div key={field.name}>
|
|
49
|
+
* <label>{field.label}</label>
|
|
50
|
+
* <input type="text" {...register(field.name)} />
|
|
51
|
+
* </div>
|
|
52
|
+
* ))}
|
|
53
|
+
* </Form>
|
|
54
|
+
*/
|
|
55
|
+
function useForm(options) {
|
|
56
|
+
var _a;
|
|
57
|
+
const { schema, validateOn = "change-submit", defaultValues = {}, errors = {}, mode = "controlled", errorParser, check, computed, onSubmit, onReady, autoFocusOnError = true, savedFormFirst = true, id, } = options || {};
|
|
58
|
+
const persistKey = (_a = options === null || options === void 0 ? void 0 : options.persistKey) !== null && _a !== void 0 ? _a : options === null || options === void 0 ? void 0 : options.id;
|
|
59
|
+
const channelBus = (0, react_1.useMemo)(() => (0, pub_sub_1.createFormBus)(), []);
|
|
60
|
+
// validation state
|
|
61
|
+
const [isValidated, setIsValidated] = (0, react_1.useState)(false);
|
|
62
|
+
const [isSubmitting, setIsSubmitting] = (0, react_1.useState)(false);
|
|
63
|
+
const [, forceRender] = (0, react_1.useState)(0);
|
|
64
|
+
// Draft listeners
|
|
65
|
+
const draftListeners = (0, react_1.useRef)({});
|
|
66
|
+
// values
|
|
67
|
+
const formValues = (0, react_1.useRef)({});
|
|
68
|
+
const computing = (0, react_1.useRef)(new Set());
|
|
69
|
+
const computedFieldsRef = (0, react_1.useRef)({});
|
|
70
|
+
// watched fields state (triggers re-renders)
|
|
71
|
+
const watchedFieldsRef = (0, react_1.useRef)(new Set());
|
|
72
|
+
// errors
|
|
73
|
+
const formErrors = (0, react_1.useRef)({});
|
|
74
|
+
// subscribers
|
|
75
|
+
const globalSubscribers = (0, react_1.useRef)(new Set());
|
|
76
|
+
const fieldSubscribersRef = (0, react_1.useRef)({});
|
|
77
|
+
const fieldRegistryRef = (0, react_1.useRef)({});
|
|
78
|
+
const fieldsTransformsRef = (0, react_1.useRef)(new Map());
|
|
79
|
+
const fieldsValidationsRef = (0, react_1.useRef)(new Map());
|
|
80
|
+
const metaRef = (0, react_1.useRef)(new Map());
|
|
81
|
+
const fieldErrorSubscribersRef = (0, react_1.useRef)({});
|
|
82
|
+
// store field refs
|
|
83
|
+
const fieldRefs = (0, react_1.useRef)({});
|
|
84
|
+
const pendingFields = new Set();
|
|
85
|
+
let notifyScheduled = false;
|
|
86
|
+
const dirtyFieldsRef = (0, react_1.useRef)({});
|
|
87
|
+
const touchedFieldsRef = (0, react_1.useRef)({});
|
|
88
|
+
const currentSchema = (0, react_1.useRef)(schema && (0, zod_helpers_1.isZodSchema)(schema) ? schema : undefined);
|
|
89
|
+
//conditionals
|
|
90
|
+
const conditionalRulesRef = (0, react_1.useRef)([]);
|
|
91
|
+
const hiddenFieldsRef = (0, react_1.useRef)({});
|
|
92
|
+
const requiredFieldsRef = (0, react_1.useRef)({});
|
|
93
|
+
// optional: track unregister for each field
|
|
94
|
+
const unregisteredRef = (0, react_1.useRef)({});
|
|
95
|
+
const visibilitySubscribersRef = (0, react_1.useRef)({});
|
|
96
|
+
// placeholders
|
|
97
|
+
const generatePlaceholders = (0, react_1.useMemo)(() => {
|
|
98
|
+
if (mode === "controlled")
|
|
99
|
+
return (0, utils_2.flattenFormValues)({
|
|
100
|
+
...(0, zod_helpers_1.createEmptyValues)(currentSchema.current),
|
|
101
|
+
// ...defaultValues,
|
|
102
|
+
});
|
|
103
|
+
return {};
|
|
104
|
+
}, [mode, currentSchema, defaultValues]);
|
|
105
|
+
//validate on values change
|
|
106
|
+
(0, react_1.useEffect)(() => {
|
|
107
|
+
const handler = (0, utils_1.debounce)(() => {
|
|
108
|
+
formValidation().then(({ isValidated, formValues: values }) => {
|
|
109
|
+
setIsValidated(isValidated);
|
|
110
|
+
formValues.current = values;
|
|
111
|
+
});
|
|
112
|
+
}, 200);
|
|
113
|
+
handler();
|
|
114
|
+
return () => handler.cancel();
|
|
115
|
+
}, [formValues, currentSchema, mode]);
|
|
116
|
+
//set schema on schema change
|
|
117
|
+
(0, react_1.useEffect)(() => {
|
|
118
|
+
if (schema) {
|
|
119
|
+
if (!(0, zod_helpers_1.isZodSchema)(schema)) {
|
|
120
|
+
throw new Error("Schema is not a zod schema.");
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
resetErrors();
|
|
124
|
+
currentSchema.current = schema;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}, [schema]);
|
|
128
|
+
//set errors on errors change
|
|
129
|
+
(0, react_1.useEffect)(() => {
|
|
130
|
+
setErrors(errors);
|
|
131
|
+
}, []);
|
|
132
|
+
//set default values on mount
|
|
133
|
+
(0, react_1.useEffect)(() => {
|
|
134
|
+
var _a, _b;
|
|
135
|
+
const saved = persistKey ? (0, utils_1.readDraft)(persistKey) : {};
|
|
136
|
+
const primary = savedFormFirst ? saved : defaultValues;
|
|
137
|
+
const secondary = savedFormFirst ? defaultValues : saved;
|
|
138
|
+
const merged = (0, utils_2.mergeValues)((0, utils_2.mergeValues)(primary || {}, secondary || {}), generatePlaceholders);
|
|
139
|
+
(_b = (_a = draftListeners.current).restore) === null || _b === void 0 ? void 0 : _b.call(_a, merged);
|
|
140
|
+
onReady === null || onReady === void 0 ? void 0 : onReady(merged);
|
|
141
|
+
if (computed) {
|
|
142
|
+
for (const key in computed) {
|
|
143
|
+
const { deps, fn } = computed[key];
|
|
144
|
+
if (key.includes("*")) {
|
|
145
|
+
const [arrayName, fieldName] = key.split("*");
|
|
146
|
+
const arrLength = (0, array_helpers_1.getArrayKeys)(arrayName, merged).length;
|
|
147
|
+
for (let i = 0; i < arrLength; i++) {
|
|
148
|
+
const computedKey = `${arrayName}.${i}.${fieldName}`;
|
|
149
|
+
const fieldDeps = (0, array_helpers_1.extraxtArrayPrefixies)(arrayName, i, deps);
|
|
150
|
+
compute(computedKey, fieldDeps, (vals) => fn(vals, i));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
compute(key, deps || [], fn);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
setValues({ ...merged }, { overwrite: true }, true);
|
|
159
|
+
}, [defaultValues]);
|
|
160
|
+
// -----------------------------
|
|
161
|
+
// Draft hooks
|
|
162
|
+
// -----------------------------
|
|
163
|
+
const onDraftSave = (0, react_1.useCallback)((callback) => {
|
|
164
|
+
draftListeners.current.save = callback;
|
|
165
|
+
}, []);
|
|
166
|
+
const onDraftRestore = (0, react_1.useCallback)((callback) => {
|
|
167
|
+
draftListeners.current.restore = callback;
|
|
168
|
+
}, []);
|
|
169
|
+
const writeDraftDebounced = (0, react_1.useCallback)((channel) => {
|
|
170
|
+
var _a, _b;
|
|
171
|
+
if (!persistKey)
|
|
172
|
+
return () => { };
|
|
173
|
+
(_b = (_a = draftListeners.current).save) === null || _b === void 0 ? void 0 : _b.call(_a, formValues.current);
|
|
174
|
+
if (channel === "immediate") {
|
|
175
|
+
return (0, utils_1.writeDraftImmediate)(persistKey, (0, utils_2.nestFormValues)(formValues.current));
|
|
176
|
+
}
|
|
177
|
+
return (0, utils_1.writeDraft)(persistKey, (0, utils_2.nestFormValues)(formValues.current));
|
|
178
|
+
}, [persistKey]);
|
|
179
|
+
const setSchema = (0, react_1.useCallback)((newSchema) => {
|
|
180
|
+
currentSchema.current = newSchema;
|
|
181
|
+
}, []);
|
|
182
|
+
function triggerRerender() {
|
|
183
|
+
forceRender((prev) => prev + 1);
|
|
184
|
+
}
|
|
185
|
+
const setValue = (0, react_1.useCallback)((name, value, opts = {}) => {
|
|
186
|
+
if (mode === "uncontrolled")
|
|
187
|
+
return;
|
|
188
|
+
value = applyTransformations(name, value);
|
|
189
|
+
// Always update ref for consistency
|
|
190
|
+
formValues.current[name] = value;
|
|
191
|
+
markDirty(name);
|
|
192
|
+
if (!opts.silent) {
|
|
193
|
+
channelBus.channel("value:*").emit(getValues());
|
|
194
|
+
channelBus.channel(`value:${name}`).emit(value);
|
|
195
|
+
if (!computedFieldsRef.current[name] &&
|
|
196
|
+
(validateOn === "change-submit" || validateOn === "change")) {
|
|
197
|
+
validateField(name, value);
|
|
198
|
+
}
|
|
199
|
+
const validator = fieldsValidationsRef.current.get(name);
|
|
200
|
+
if (validator) {
|
|
201
|
+
const error = validator(value);
|
|
202
|
+
setFieldError(name, error);
|
|
203
|
+
}
|
|
204
|
+
// 🔔 Notify subscribers
|
|
205
|
+
notifySubscribers(name);
|
|
206
|
+
// Update state if field is watched (triggers re-render)
|
|
207
|
+
if (watchedFieldsRef.current.has(name)) {
|
|
208
|
+
writeDraftDebounced("immediate");
|
|
209
|
+
triggerRerender();
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
writeDraftDebounced();
|
|
213
|
+
evaluateConditionals();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}, [mode, writeDraftDebounced]);
|
|
217
|
+
const setValues = (0, react_1.useCallback)((values, options, skipWriteDraft = false) => {
|
|
218
|
+
// Flatten incoming values for consistent key access
|
|
219
|
+
const flattened = (0, utils_2.flattenFormValues)(values);
|
|
220
|
+
formValues.current = {
|
|
221
|
+
...((options === null || options === void 0 ? void 0 : options.overwrite) ? {} : formValues.current),
|
|
222
|
+
...flattened,
|
|
223
|
+
};
|
|
224
|
+
channelBus.channel("value:*").emit(getValues());
|
|
225
|
+
// 🔔 Notify all affected subscribers
|
|
226
|
+
Object.keys(flattened).forEach((key) => {
|
|
227
|
+
channelBus.channel(`value:${key}`).emit(getValue(key));
|
|
228
|
+
notifySubscribers(key);
|
|
229
|
+
});
|
|
230
|
+
if (!skipWriteDraft) {
|
|
231
|
+
// Trigger rerender when structure changes
|
|
232
|
+
triggerRerender();
|
|
233
|
+
writeDraftDebounced();
|
|
234
|
+
}
|
|
235
|
+
}, [writeDraftDebounced, triggerRerender]);
|
|
236
|
+
const getValue = (0, react_1.useCallback)((name) => (0, deep_path_1.getValueByPath)(getValues(), name), []);
|
|
237
|
+
const getValues = (0, react_1.useCallback)(() => (0, utils_2.nestFormValues)(formValues.current), []);
|
|
238
|
+
const setFieldError = (0, react_1.useCallback)((name, error) => {
|
|
239
|
+
var _a;
|
|
240
|
+
const prevError = formErrors.current[name];
|
|
241
|
+
// Prevent triggering the same error again
|
|
242
|
+
if (prevError === error)
|
|
243
|
+
return;
|
|
244
|
+
formErrors.current[name] = errorParser ? errorParser(error !== null && error !== void 0 ? error : "") : error;
|
|
245
|
+
// notify only the affected field
|
|
246
|
+
(_a = fieldErrorSubscribersRef.current[name]) === null || _a === void 0 ? void 0 : _a.forEach((fn) => fn(error));
|
|
247
|
+
}, []);
|
|
248
|
+
const setErrors = (0, react_1.useCallback)((errors) => {
|
|
249
|
+
if (!errors || Object.keys(errors).length === 0)
|
|
250
|
+
return Object.keys(fieldErrorSubscribersRef.current).forEach((key) => {
|
|
251
|
+
setFieldError(key, undefined);
|
|
252
|
+
});
|
|
253
|
+
const mapped = (0, zod_helpers_1.isZodError)(errors) ? (0, zod_helpers_1.mapZodErrors)(errors) : errors;
|
|
254
|
+
Object.keys(formErrors.current).forEach((key) => {
|
|
255
|
+
setFieldError(key, undefined);
|
|
256
|
+
});
|
|
257
|
+
Object.keys(mapped).forEach((key) => {
|
|
258
|
+
setFieldError(key, mapped[key]);
|
|
259
|
+
});
|
|
260
|
+
}, [setFieldError]);
|
|
261
|
+
const getError = (0, react_1.useCallback)((name) => (0, deep_path_1.getValueByPath)(formErrors.current, name), []);
|
|
262
|
+
const getErrors = (0, react_1.useCallback)(() => formErrors.current, []);
|
|
263
|
+
function resetErrors() {
|
|
264
|
+
if (!formErrors.current || Object.keys(formErrors.current).length === 0)
|
|
265
|
+
return;
|
|
266
|
+
setErrors(Object.keys(formErrors.current).reduce((acc, key) => {
|
|
267
|
+
acc[key] = undefined;
|
|
268
|
+
return acc;
|
|
269
|
+
}, {}));
|
|
270
|
+
}
|
|
271
|
+
//validate single field
|
|
272
|
+
const validateField = (0, react_1.useCallback)((0, utils_1.debounce)(async (name, inputValue) => {
|
|
273
|
+
var _a;
|
|
274
|
+
if (!name || !currentSchema.current || mode === "uncontrolled")
|
|
275
|
+
return;
|
|
276
|
+
const result = await (0, utils_1.validateForm)(currentSchema.current, {
|
|
277
|
+
[name]: inputValue,
|
|
278
|
+
});
|
|
279
|
+
if (!result.success) {
|
|
280
|
+
const fieldError = (_a = result.errors[name]) !== null && _a !== void 0 ? _a : undefined;
|
|
281
|
+
setFieldError(name, fieldError);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
setFieldError(name, undefined);
|
|
285
|
+
}
|
|
286
|
+
}, 150), [currentSchema, mode, setFieldError]);
|
|
287
|
+
async function formValidation() {
|
|
288
|
+
if (!currentSchema.current || mode === "uncontrolled")
|
|
289
|
+
return { isValidated: false, formValues: formValues.current };
|
|
290
|
+
const result = await (0, utils_1.validateForm)(currentSchema.current, formValues.current);
|
|
291
|
+
if (result.success) {
|
|
292
|
+
formValues.current = (0, utils_2.flattenFormValues)(result.data);
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
isValidated: result.success,
|
|
296
|
+
formValues: result.data,
|
|
297
|
+
formErrors: !result.success ? result.errors : undefined,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
const validatePartial = (0, react_1.useCallback)(async (values) => {
|
|
301
|
+
if (!currentSchema.current || mode === "uncontrolled")
|
|
302
|
+
return;
|
|
303
|
+
const flat = (0, utils_2.flattenFormValues)(values);
|
|
304
|
+
const fields = {};
|
|
305
|
+
for (const [key, val] of Object.entries(flat)) {
|
|
306
|
+
//@ts-ignore
|
|
307
|
+
fields[key] = val;
|
|
308
|
+
}
|
|
309
|
+
const result = await (0, utils_1.validateForm)(currentSchema.current, fields);
|
|
310
|
+
if (!result.success) {
|
|
311
|
+
const errors = {};
|
|
312
|
+
for (const [key, _] of Object.entries(flat)) {
|
|
313
|
+
//@ts-ignore
|
|
314
|
+
errors[key] = result.errors[key];
|
|
315
|
+
}
|
|
316
|
+
setErrors({ ...formErrors.current, ...errors });
|
|
317
|
+
}
|
|
318
|
+
}, []);
|
|
319
|
+
//run validation before submit
|
|
320
|
+
async function validateAndSubmit() {
|
|
321
|
+
// resetErrors();
|
|
322
|
+
// 1️⃣ Schema validation
|
|
323
|
+
if (currentSchema.current &&
|
|
324
|
+
(validateOn === "change-submit" || validateOn === "submit")) {
|
|
325
|
+
const validate = await formValidation();
|
|
326
|
+
if (!validate.isValidated) {
|
|
327
|
+
setIsValidated(false);
|
|
328
|
+
setErrors(validate.formErrors);
|
|
329
|
+
focusFirst(validate.formErrors);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
resetErrors();
|
|
333
|
+
}
|
|
334
|
+
// 2️⃣ Custom check
|
|
335
|
+
if (check) {
|
|
336
|
+
//@ts-ignore
|
|
337
|
+
const checkResult = await check(getValues(), {
|
|
338
|
+
multiPathError: utils_2.multiPathError,
|
|
339
|
+
focus,
|
|
340
|
+
// setErrors,
|
|
341
|
+
// mapErrors: (errors, path) => setErrors(mapErrors(errors, path)),
|
|
342
|
+
});
|
|
343
|
+
if (checkResult && Object.keys(checkResult).length > 0) {
|
|
344
|
+
setIsValidated(false);
|
|
345
|
+
setErrors(checkResult);
|
|
346
|
+
focusFirst(checkResult);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
resetErrors();
|
|
350
|
+
}
|
|
351
|
+
// resetErrors();
|
|
352
|
+
return getValues();
|
|
353
|
+
}
|
|
354
|
+
const register = (0, react_1.useCallback)((name, options, internal) => {
|
|
355
|
+
const [error, setError] = (0, react_1.useState)(formErrors.current[name]);
|
|
356
|
+
// internal value ref
|
|
357
|
+
const valueRef = (0, react_1.useRef)(getValue(name));
|
|
358
|
+
const refId = name + "-" + (0, react_1.useId)();
|
|
359
|
+
// setup field metadata on mount
|
|
360
|
+
(0, react_1.useEffect)(() => {
|
|
361
|
+
if (options) {
|
|
362
|
+
fieldRegistryRef.current[name] = options;
|
|
363
|
+
if (options.transform) {
|
|
364
|
+
const arr = Array.isArray(options.transform)
|
|
365
|
+
? options.transform
|
|
366
|
+
: [options.transform];
|
|
367
|
+
fieldsTransformsRef.current.set(name, arr);
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
fieldsTransformsRef.current.delete(name);
|
|
371
|
+
}
|
|
372
|
+
//@ts-ignore
|
|
373
|
+
if (options.compute) {
|
|
374
|
+
//@ts-ignore
|
|
375
|
+
const { deps, fn } = options.compute;
|
|
376
|
+
compute(name, deps, fn);
|
|
377
|
+
}
|
|
378
|
+
if (options.validate) {
|
|
379
|
+
fieldsValidationsRef.current.set(name, options.validate);
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
fieldsValidationsRef.current.delete(name);
|
|
383
|
+
}
|
|
384
|
+
// handle defaultValue only when field is initially undefined
|
|
385
|
+
const currentValue = getValue(name);
|
|
386
|
+
if (options.defaultValue !== undefined &&
|
|
387
|
+
currentValue === undefined) {
|
|
388
|
+
setValue(name, options.defaultValue, { silent: true });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (!internal) {
|
|
392
|
+
// subscribe to internal store → updates ref but not UI
|
|
393
|
+
const unsub = subscribe(name, (val) => {
|
|
394
|
+
valueRef.current = val;
|
|
395
|
+
});
|
|
396
|
+
const unsubError = subscribeFieldError(name, setError);
|
|
397
|
+
return () => {
|
|
398
|
+
unsub();
|
|
399
|
+
unsubError();
|
|
400
|
+
delete fieldRegistryRef.current[name];
|
|
401
|
+
fieldsTransformsRef.current.delete(name);
|
|
402
|
+
fieldsValidationsRef.current.delete(name);
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
}, [name, options]); // keep stable
|
|
406
|
+
// handlers
|
|
407
|
+
const onChange = (0, react_1.useCallback)((e) => {
|
|
408
|
+
var _a, _b;
|
|
409
|
+
const val = (_b = (_a = e === null || e === void 0 ? void 0 : e.target) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : e;
|
|
410
|
+
setValue(name, val);
|
|
411
|
+
touchedFieldsRef.current[name] = true;
|
|
412
|
+
dirtyFieldsRef.current[name] = true;
|
|
413
|
+
if (["change", "change-submit"].includes(validateOn)) {
|
|
414
|
+
validateField(name);
|
|
415
|
+
}
|
|
416
|
+
}, [name]);
|
|
417
|
+
const onBlur = (0, react_1.useCallback)(() => {
|
|
418
|
+
touchedFieldsRef.current[name] = true;
|
|
419
|
+
if (validateOn === "blur") {
|
|
420
|
+
validateField(name);
|
|
421
|
+
}
|
|
422
|
+
}, [name]);
|
|
423
|
+
return {
|
|
424
|
+
name,
|
|
425
|
+
defaultValue: valueRef.current,
|
|
426
|
+
onChange,
|
|
427
|
+
onBlur,
|
|
428
|
+
"data-input-ref": refId,
|
|
429
|
+
"data-input-error": !!error,
|
|
430
|
+
"aria-invalid": !!error,
|
|
431
|
+
};
|
|
432
|
+
}, []);
|
|
433
|
+
const focus = (0, react_1.useCallback)((name) => {
|
|
434
|
+
if (typeof document === "undefined")
|
|
435
|
+
return;
|
|
436
|
+
const ref = fieldRefs.current[name];
|
|
437
|
+
const element = document.querySelector(`[data-input-ref="${ref}"]`);
|
|
438
|
+
if (element && typeof element.focus === "function") {
|
|
439
|
+
element.focus();
|
|
440
|
+
}
|
|
441
|
+
}, []);
|
|
442
|
+
function focusFirst(obj) {
|
|
443
|
+
if (!obj || !autoFocusOnError)
|
|
444
|
+
return;
|
|
445
|
+
const first = Object.keys(obj)[0];
|
|
446
|
+
focus(first);
|
|
447
|
+
}
|
|
448
|
+
// --- Public notify() ---
|
|
449
|
+
function notifySubscribers(path) {
|
|
450
|
+
pendingFields.add(path);
|
|
451
|
+
scheduleNotify();
|
|
452
|
+
}
|
|
453
|
+
function scheduleNotify() {
|
|
454
|
+
if (notifyScheduled)
|
|
455
|
+
return;
|
|
456
|
+
notifyScheduled = true;
|
|
457
|
+
queueMicrotask(() => {
|
|
458
|
+
notifyScheduled = false;
|
|
459
|
+
// Capture and clear pending fields
|
|
460
|
+
const fields = Array.from(pendingFields);
|
|
461
|
+
pendingFields.clear();
|
|
462
|
+
// 1️⃣ Notify all relevant field subscribers
|
|
463
|
+
for (const [subPath, subscribers] of Object.entries(fieldSubscribersRef.current)) {
|
|
464
|
+
const shouldNotify = fields.some((path) => path === subPath || path.startsWith(`${subPath}.`));
|
|
465
|
+
if (shouldNotify) {
|
|
466
|
+
const value = getValue(subPath) || "";
|
|
467
|
+
for (const cb of subscribers) {
|
|
468
|
+
cb(value);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
// 2️⃣ Notify global subscribers once
|
|
473
|
+
if (globalSubscribers.current.size > 0) {
|
|
474
|
+
for (const cb of globalSubscribers.current) {
|
|
475
|
+
cb(getValues());
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
const subscribe = (0, react_1.useCallback)((nameOrCallback, callback, opts) => {
|
|
481
|
+
// --- GLOBAL SUBSCRIPTION ---
|
|
482
|
+
if (typeof nameOrCallback === "function") {
|
|
483
|
+
globalSubscribers.current.add(nameOrCallback);
|
|
484
|
+
return () => {
|
|
485
|
+
globalSubscribers.current.delete(nameOrCallback);
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
// --- FIELD SUBSCRIPTION ---
|
|
489
|
+
const fields = Array.isArray(nameOrCallback)
|
|
490
|
+
? nameOrCallback
|
|
491
|
+
: [nameOrCallback];
|
|
492
|
+
const cb = callback;
|
|
493
|
+
for (const field of fields) {
|
|
494
|
+
// Associate ref (for cleanup when unmounting a field)
|
|
495
|
+
if (opts === null || opts === void 0 ? void 0 : opts.internalRef) {
|
|
496
|
+
fieldRefs.current[field] = opts.internalRef;
|
|
497
|
+
}
|
|
498
|
+
// Create new set if it doesn’t exist
|
|
499
|
+
let subscribers = fieldSubscribersRef.current[field];
|
|
500
|
+
if (!subscribers) {
|
|
501
|
+
subscribers = new Set();
|
|
502
|
+
fieldSubscribersRef.current[field] = subscribers;
|
|
503
|
+
}
|
|
504
|
+
cb(getValue(field));
|
|
505
|
+
subscribers.add(cb);
|
|
506
|
+
}
|
|
507
|
+
// --- UNSUBSCRIBE CLEANUP ---
|
|
508
|
+
return () => {
|
|
509
|
+
for (const field of fields) {
|
|
510
|
+
const subscribers = fieldSubscribersRef.current[field];
|
|
511
|
+
if (!subscribers)
|
|
512
|
+
continue;
|
|
513
|
+
subscribers.delete(cb);
|
|
514
|
+
// If no subscribers remain, clean up
|
|
515
|
+
if (subscribers.size === 0) {
|
|
516
|
+
delete fieldSubscribersRef.current[field];
|
|
517
|
+
// Optional: clean up fieldRef if linked
|
|
518
|
+
if ((opts === null || opts === void 0 ? void 0 : opts.internalRef) &&
|
|
519
|
+
fieldRefs.current[field] === opts.internalRef) {
|
|
520
|
+
delete fieldRefs.current[field];
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
}, []);
|
|
526
|
+
async function safeCompute(name, fn, index) {
|
|
527
|
+
if (computing.current.has(name))
|
|
528
|
+
return; // avoid infinite loops
|
|
529
|
+
computing.current.add(name);
|
|
530
|
+
try {
|
|
531
|
+
//@ts-ignore
|
|
532
|
+
const result = fn(getValues(), index);
|
|
533
|
+
const currentValue = getValue(name);
|
|
534
|
+
if (result instanceof Promise) {
|
|
535
|
+
result.then((value) => {
|
|
536
|
+
if (value !== currentValue) {
|
|
537
|
+
setValue(name, value);
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
if (result !== currentValue) {
|
|
543
|
+
setValue(name, result);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
finally {
|
|
548
|
+
computing.current.delete(name);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const compute = (0, react_1.useCallback)((name, depsOrFn, maybeFn, index) => {
|
|
552
|
+
const deps = Array.isArray(depsOrFn) ? depsOrFn : null;
|
|
553
|
+
const fn = Array.isArray(depsOrFn) ? maybeFn : depsOrFn;
|
|
554
|
+
if (deps && deps.includes(name))
|
|
555
|
+
throw new Error(`Computed field "${name}" cannot depend on itself.`);
|
|
556
|
+
// if (Object.keys(formValues.current || {}).includes(name)) {
|
|
557
|
+
// throw new Error(`Computed field "${name}" already exists`);
|
|
558
|
+
// }
|
|
559
|
+
if (typeof fn !== "function") {
|
|
560
|
+
throw new Error("Invalid compute function");
|
|
561
|
+
}
|
|
562
|
+
// Store for introspection
|
|
563
|
+
//@ts-ignore
|
|
564
|
+
computedFieldsRef.current[name] = { deps, fn };
|
|
565
|
+
// Initial compute
|
|
566
|
+
void safeCompute(name, fn, index);
|
|
567
|
+
// Subscribe to form changes
|
|
568
|
+
if (deps && deps.length > 0) {
|
|
569
|
+
deps.forEach((dep) => {
|
|
570
|
+
subscribe(dep, () => void safeCompute(name, fn, index));
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
subscribe(() => void safeCompute(name, fn, index));
|
|
575
|
+
}
|
|
576
|
+
}, []);
|
|
577
|
+
const transform = (0, react_1.useCallback)((path, fn) => {
|
|
578
|
+
if (!fieldsTransformsRef.current.has(path)) {
|
|
579
|
+
fieldsTransformsRef.current.set(path, []);
|
|
580
|
+
}
|
|
581
|
+
fieldsTransformsRef.current.get(path).push(fn);
|
|
582
|
+
//@ts-ignore
|
|
583
|
+
setValue(path, fn(getValue(path)));
|
|
584
|
+
}, []);
|
|
585
|
+
function applyTransformations(path, value) {
|
|
586
|
+
const fns = fieldsTransformsRef.current.get(path);
|
|
587
|
+
if (fns) {
|
|
588
|
+
for (const transformFn of fns) {
|
|
589
|
+
value = transformFn(value);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return value;
|
|
593
|
+
}
|
|
594
|
+
function evaluateConditionals() {
|
|
595
|
+
var _a, _b;
|
|
596
|
+
const values = getValues();
|
|
597
|
+
// reset ephemeral metadata (we re-evaluate everything)
|
|
598
|
+
hiddenFieldsRef.current = { ...(hiddenFieldsRef.current || {}) };
|
|
599
|
+
requiredFieldsRef.current = { ...(requiredFieldsRef.current || {}) };
|
|
600
|
+
unregisteredRef.current = { ...(unregisteredRef.current || {}) };
|
|
601
|
+
for (const rule of conditionalRulesRef.current) {
|
|
602
|
+
const { fields, config } = rule;
|
|
603
|
+
let result;
|
|
604
|
+
try {
|
|
605
|
+
result = Boolean(config.when(values));
|
|
606
|
+
}
|
|
607
|
+
catch (err) {
|
|
608
|
+
// if the condition throws, treat as false and continue
|
|
609
|
+
result = false;
|
|
610
|
+
// optionally log or surface dev warning
|
|
611
|
+
// console.warn("conditional when() threw for rule", rule, err);
|
|
612
|
+
}
|
|
613
|
+
const effects = result ? (_a = config.then) !== null && _a !== void 0 ? _a : {} : (_b = config.else) !== null && _b !== void 0 ? _b : {};
|
|
614
|
+
for (const field of fields) {
|
|
615
|
+
// apply visibility
|
|
616
|
+
if (effects.visible !== undefined) {
|
|
617
|
+
hiddenFieldsRef.current[field] = !effects.visible;
|
|
618
|
+
}
|
|
619
|
+
// apply unregister or clear behavior when hidden
|
|
620
|
+
const isHidden = hiddenFieldsRef.current[field] === true;
|
|
621
|
+
if (isHidden) {
|
|
622
|
+
if (effects.clear) {
|
|
623
|
+
// clear the value silently (avoid firing user-level subscriptions twice)
|
|
624
|
+
setValue(field, undefined, { silent: true });
|
|
625
|
+
}
|
|
626
|
+
if (effects.unregister) {
|
|
627
|
+
unregisteredRef.current[field] = true;
|
|
628
|
+
// form.unregister(field);
|
|
629
|
+
}
|
|
630
|
+
// remove errors for hidden field
|
|
631
|
+
delete formErrors.current[field];
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
// field visible → ensure unregister flag false
|
|
635
|
+
unregisteredRef.current[field] = false;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
notifyConditionalChange();
|
|
640
|
+
}
|
|
641
|
+
const conditional = (0, react_1.useCallback)((fields, cfg) => {
|
|
642
|
+
const normalized = Array.isArray(fields) ? fields : [fields];
|
|
643
|
+
conditionalRulesRef.current.push({
|
|
644
|
+
fields: normalized,
|
|
645
|
+
config: cfg,
|
|
646
|
+
});
|
|
647
|
+
// evaluate immediately so state is correct before UI renders
|
|
648
|
+
evaluateConditionals();
|
|
649
|
+
// return an unregister handle for this rule.
|
|
650
|
+
const ruleIndex = conditionalRulesRef.current.length - 1;
|
|
651
|
+
return () => {
|
|
652
|
+
conditionalRulesRef.current.splice(ruleIndex, 1);
|
|
653
|
+
evaluateConditionals();
|
|
654
|
+
};
|
|
655
|
+
}, []);
|
|
656
|
+
function subscribeVisibility(name, cb) {
|
|
657
|
+
if (!visibilitySubscribersRef.current[name]) {
|
|
658
|
+
visibilitySubscribersRef.current[name] = new Set();
|
|
659
|
+
}
|
|
660
|
+
visibilitySubscribersRef.current[name].add(cb);
|
|
661
|
+
return () => {
|
|
662
|
+
visibilitySubscribersRef.current[name].delete(cb);
|
|
663
|
+
if (visibilitySubscribersRef.current[name].size === 0) {
|
|
664
|
+
delete visibilitySubscribersRef.current[name];
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
function notifyConditionalChange() {
|
|
669
|
+
const map = visibilitySubscribersRef.current;
|
|
670
|
+
for (const key in map) {
|
|
671
|
+
const set = map[key];
|
|
672
|
+
set.forEach((cb) => cb());
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const watch = (0, react_1.useCallback)((fields) => {
|
|
676
|
+
// If no fields provided, watch all fields
|
|
677
|
+
const fieldsToWatch = !fields
|
|
678
|
+
? Object.keys(formValues.current) // Watch all fields
|
|
679
|
+
: Array.isArray(fields)
|
|
680
|
+
? fields
|
|
681
|
+
: [fields];
|
|
682
|
+
if (!fields)
|
|
683
|
+
watchedFieldsRef.current.clear();
|
|
684
|
+
// Track fields for reactivity
|
|
685
|
+
fieldsToWatch.forEach((field) => {
|
|
686
|
+
if (field) {
|
|
687
|
+
// Ensure field is not empty
|
|
688
|
+
watchedFieldsRef.current.add(field);
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
// Return current values
|
|
692
|
+
if (!fields)
|
|
693
|
+
return getValues();
|
|
694
|
+
const result = fieldsToWatch.map((field) => getValue(field));
|
|
695
|
+
if (!Array.isArray(fields) && result.length === 1)
|
|
696
|
+
return result[0];
|
|
697
|
+
if (Array.isArray(fields)) {
|
|
698
|
+
// Return both array and object types
|
|
699
|
+
return Object.assign(result, Object.fromEntries(fields.map((field, index) => [field, result[index]])));
|
|
700
|
+
}
|
|
701
|
+
return result;
|
|
702
|
+
}, []);
|
|
703
|
+
const subscribeFieldError = (0, react_1.useCallback)((name, callback) => {
|
|
704
|
+
if (!fieldErrorSubscribersRef.current[name]) {
|
|
705
|
+
fieldErrorSubscribersRef.current[name] = new Set();
|
|
706
|
+
}
|
|
707
|
+
const set = fieldErrorSubscribersRef.current[name];
|
|
708
|
+
set.add(callback);
|
|
709
|
+
// immediately emit current error
|
|
710
|
+
callback(formErrors.current[name]);
|
|
711
|
+
return () => {
|
|
712
|
+
set.delete(callback);
|
|
713
|
+
if (set.size === 0) {
|
|
714
|
+
delete fieldErrorSubscribersRef.current[name];
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
}, []);
|
|
718
|
+
const unsubscribeField = (0, react_1.useCallback)((name, callback) => {
|
|
719
|
+
const set = fieldSubscribersRef.current[name];
|
|
720
|
+
if (set && callback)
|
|
721
|
+
set.delete(callback);
|
|
722
|
+
delete fieldErrorSubscribersRef.current[name];
|
|
723
|
+
delete fieldRefs.current[name];
|
|
724
|
+
delete dirtyFieldsRef.current[name];
|
|
725
|
+
delete touchedFieldsRef.current[name];
|
|
726
|
+
delete computedFieldsRef.current[name];
|
|
727
|
+
fieldsTransformsRef.current.delete(name);
|
|
728
|
+
delete formErrors.current[name];
|
|
729
|
+
fieldsValidationsRef.current.delete(name);
|
|
730
|
+
}, []);
|
|
731
|
+
const unsubscribeFieldPrefix = (0, react_1.useCallback)((prefix) => {
|
|
732
|
+
for (const key of Object.keys(fieldSubscribersRef.current)) {
|
|
733
|
+
if (key.startsWith(prefix)) {
|
|
734
|
+
fieldSubscribersRef.current[key].forEach((cb) => {
|
|
735
|
+
unsubscribeField(key, cb);
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}, [unsubscribeField]);
|
|
740
|
+
const reset = (0, react_1.useCallback)(() => {
|
|
741
|
+
Object.keys(fieldSubscribersRef.current).forEach((name) => { var _a; return (_a = fieldSubscribersRef.current[name]) === null || _a === void 0 ? void 0 : _a.forEach((cb) => cb("")); });
|
|
742
|
+
Object.keys(fieldErrorSubscribersRef.current).forEach((name) => { var _a; return (_a = fieldErrorSubscribersRef.current[name]) === null || _a === void 0 ? void 0 : _a.forEach((fn) => fn(undefined)); });
|
|
743
|
+
dirtyFieldsRef.current = {};
|
|
744
|
+
setValues({ ...generatePlaceholders, ...defaultValues }, { overwrite: true }, true);
|
|
745
|
+
channelBus.channel("value:*").emit({});
|
|
746
|
+
setErrors({});
|
|
747
|
+
if (persistKey)
|
|
748
|
+
(0, utils_1.deleteDraft)(persistKey);
|
|
749
|
+
triggerRerender();
|
|
750
|
+
}, []);
|
|
751
|
+
const resetField = (0, react_1.useCallback)((name) => {
|
|
752
|
+
const combined = { ...generatePlaceholders, ...defaultValues };
|
|
753
|
+
setValue(name, combined[name]);
|
|
754
|
+
markTouched(name);
|
|
755
|
+
}, []);
|
|
756
|
+
const isDirty = (0, react_1.useCallback)((name) => {
|
|
757
|
+
if (!name)
|
|
758
|
+
return Object.values(dirtyFieldsRef.current).some(Boolean);
|
|
759
|
+
return !!dirtyFieldsRef.current[name];
|
|
760
|
+
}, []);
|
|
761
|
+
const isTouched = (0, react_1.useCallback)((name) => {
|
|
762
|
+
if (!name)
|
|
763
|
+
return Object.values(touchedFieldsRef.current).some(Boolean);
|
|
764
|
+
return !!touchedFieldsRef.current[name];
|
|
765
|
+
}, []);
|
|
766
|
+
const markTouched = (0, react_1.useCallback)((name) => {
|
|
767
|
+
touchedFieldsRef.current[name] = true;
|
|
768
|
+
}, []);
|
|
769
|
+
const markDirty = (0, react_1.useCallback)((name) => {
|
|
770
|
+
dirtyFieldsRef.current[name] = true;
|
|
771
|
+
}, []);
|
|
772
|
+
const handleSubmit = (0, react_1.useCallback)((onValid) => {
|
|
773
|
+
return async (event) => {
|
|
774
|
+
if (event)
|
|
775
|
+
event.preventDefault();
|
|
776
|
+
try {
|
|
777
|
+
const validatedData = await validateAndSubmit();
|
|
778
|
+
if (!validatedData)
|
|
779
|
+
return;
|
|
780
|
+
setIsSubmitting(true);
|
|
781
|
+
const data = structuredClone(validatedData);
|
|
782
|
+
await onValid(validatedData, {
|
|
783
|
+
setValues,
|
|
784
|
+
setErrors,
|
|
785
|
+
mapErrors: (errors, path) => setErrors((0, utils_2.mapErrors)(errors, path)),
|
|
786
|
+
reset,
|
|
787
|
+
focus,
|
|
788
|
+
//@ts-ignore
|
|
789
|
+
array: (path) => (0, array_helpers_1.handlerArrayHelpers)(path, data),
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
finally {
|
|
793
|
+
setIsSubmitting(false);
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
}, []);
|
|
797
|
+
const field = (0, react_1.useCallback)((path) => {
|
|
798
|
+
return {
|
|
799
|
+
get: () => getValue(path),
|
|
800
|
+
set: (value) => setValue(path, value),
|
|
801
|
+
transform(fn) {
|
|
802
|
+
transform(path, fn);
|
|
803
|
+
},
|
|
804
|
+
validate: () => {
|
|
805
|
+
//@ts-ignore
|
|
806
|
+
validateField(path, getValue(path));
|
|
807
|
+
//@ts-ignore
|
|
808
|
+
if (getError(path)) {
|
|
809
|
+
focus(path);
|
|
810
|
+
}
|
|
811
|
+
},
|
|
812
|
+
get error() {
|
|
813
|
+
//@ts-ignore
|
|
814
|
+
return getError(path);
|
|
815
|
+
},
|
|
816
|
+
get hasError() {
|
|
817
|
+
//@ts-ignore
|
|
818
|
+
return !!getError(path);
|
|
819
|
+
},
|
|
820
|
+
get isTouched() {
|
|
821
|
+
return isTouched(path);
|
|
822
|
+
},
|
|
823
|
+
get isDirty() {
|
|
824
|
+
return isDirty(path);
|
|
825
|
+
},
|
|
826
|
+
focus: () => focus(path),
|
|
827
|
+
//@ts-ignore
|
|
828
|
+
reset: () => resetField(path),
|
|
829
|
+
};
|
|
830
|
+
}, []);
|
|
831
|
+
const array = (0, react_1.useCallback)((path) => {
|
|
832
|
+
return (0, array_helpers_1.formArrayHelper)({
|
|
833
|
+
path,
|
|
834
|
+
get formValues() {
|
|
835
|
+
return formValues.current;
|
|
836
|
+
},
|
|
837
|
+
setValues,
|
|
838
|
+
//@ts-ignore
|
|
839
|
+
computed,
|
|
840
|
+
compute,
|
|
841
|
+
getCurrentArrayValue: () => (0, deep_path_1.getDeepValue)(formValues.current, path),
|
|
842
|
+
});
|
|
843
|
+
}, []);
|
|
844
|
+
const group = (0, react_1.useCallback)((path) => {
|
|
845
|
+
return (0, group_helpers_1.groupHelpers)({
|
|
846
|
+
path,
|
|
847
|
+
//@ts-ignore
|
|
848
|
+
formValues: formValues.current,
|
|
849
|
+
//@ts-ignore
|
|
850
|
+
defaultValues,
|
|
851
|
+
setValues,
|
|
852
|
+
getCurrentValue: () => (0, deep_path_1.getDeepValue)(formValues.current, path),
|
|
853
|
+
validateField,
|
|
854
|
+
validatePartial,
|
|
855
|
+
});
|
|
856
|
+
}, []);
|
|
857
|
+
const debug = (0, react_1.useCallback)(() => {
|
|
858
|
+
return {
|
|
859
|
+
values: { ...getValues() },
|
|
860
|
+
errors: { ...getErrors() },
|
|
861
|
+
dirty: { ...(0, utils_2.nestFormValues)(dirtyFieldsRef.current) },
|
|
862
|
+
touched: { ...(0, utils_2.nestFormValues)(touchedFieldsRef.current) },
|
|
863
|
+
computed: { ...computedFieldsRef.current },
|
|
864
|
+
subscriptions: {
|
|
865
|
+
fields: { ...fieldSubscribersRef.current },
|
|
866
|
+
errors: { ...fieldErrorSubscribersRef.current },
|
|
867
|
+
},
|
|
868
|
+
state: {
|
|
869
|
+
isSubmitting,
|
|
870
|
+
isValidated,
|
|
871
|
+
},
|
|
872
|
+
};
|
|
873
|
+
}, []);
|
|
874
|
+
const formMetadata = {
|
|
875
|
+
get(key) {
|
|
876
|
+
return metaRef.current.get(key);
|
|
877
|
+
},
|
|
878
|
+
set(key, value) {
|
|
879
|
+
metaRef.current.set(key, value);
|
|
880
|
+
},
|
|
881
|
+
delete(key) {
|
|
882
|
+
metaRef.current.delete(key);
|
|
883
|
+
},
|
|
884
|
+
has(key) {
|
|
885
|
+
return metaRef.current.has(key);
|
|
886
|
+
},
|
|
887
|
+
keys() {
|
|
888
|
+
return metaRef.current.keys();
|
|
889
|
+
},
|
|
890
|
+
values() {
|
|
891
|
+
return metaRef.current.values();
|
|
892
|
+
},
|
|
893
|
+
clear() {
|
|
894
|
+
metaRef.current.clear();
|
|
895
|
+
},
|
|
896
|
+
};
|
|
897
|
+
const values = {
|
|
898
|
+
register,
|
|
899
|
+
validate: async () => {
|
|
900
|
+
const result = await formValidation();
|
|
901
|
+
focusFirst(result.formErrors);
|
|
902
|
+
setErrors(result.formErrors);
|
|
903
|
+
setIsValidated(result.isValidated);
|
|
904
|
+
},
|
|
905
|
+
validateOn,
|
|
906
|
+
validatePartial,
|
|
907
|
+
setSchema,
|
|
908
|
+
setValue,
|
|
909
|
+
getValue,
|
|
910
|
+
getValues,
|
|
911
|
+
setValues,
|
|
912
|
+
setErrors,
|
|
913
|
+
getError,
|
|
914
|
+
getErrors,
|
|
915
|
+
reset,
|
|
916
|
+
resetField,
|
|
917
|
+
handleSubmit,
|
|
918
|
+
onSubmit: onSubmit ? handleSubmit(onSubmit) : undefined,
|
|
919
|
+
subscribe,
|
|
920
|
+
unsubscribeField,
|
|
921
|
+
subscribeFieldError,
|
|
922
|
+
errorParser,
|
|
923
|
+
field,
|
|
924
|
+
array,
|
|
925
|
+
group,
|
|
926
|
+
get values() {
|
|
927
|
+
return getValues();
|
|
928
|
+
},
|
|
929
|
+
get errors() {
|
|
930
|
+
return getErrors();
|
|
931
|
+
},
|
|
932
|
+
isSubmitting,
|
|
933
|
+
isValidated,
|
|
934
|
+
isDirty,
|
|
935
|
+
markDirty,
|
|
936
|
+
isTouched,
|
|
937
|
+
markTouched,
|
|
938
|
+
focus,
|
|
939
|
+
compute,
|
|
940
|
+
transform,
|
|
941
|
+
conditional,
|
|
942
|
+
onDraftSave,
|
|
943
|
+
onDraftRestore,
|
|
944
|
+
debug,
|
|
945
|
+
watch,
|
|
946
|
+
Field: components_1.Field,
|
|
947
|
+
channel: channelBus.channel,
|
|
948
|
+
meta: formMetadata,
|
|
949
|
+
};
|
|
950
|
+
//@ts-ignore
|
|
951
|
+
if (id)
|
|
952
|
+
form_registry_1.registry.add(id, values); // synchronous
|
|
953
|
+
(0, react_1.useEffect)(() => {
|
|
954
|
+
return () => {
|
|
955
|
+
if (id)
|
|
956
|
+
form_registry_1.registry.delete(id);
|
|
957
|
+
};
|
|
958
|
+
}, [id]);
|
|
959
|
+
//@ts-ignore
|
|
960
|
+
return values;
|
|
961
|
+
}
|