@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.
Files changed (61) hide show
  1. package/README.md +261 -0
  2. package/README.old.md +141 -0
  3. package/dist/components/field-error.d.ts +2 -0
  4. package/dist/components/field-error.js +14 -0
  5. package/dist/components/field.d.ts +4 -0
  6. package/dist/components/field.js +120 -0
  7. package/dist/components/form-spy.d.ts +16 -0
  8. package/dist/components/form-spy.js +66 -0
  9. package/dist/components/index.d.ts +4 -0
  10. package/dist/components/index.js +20 -0
  11. package/dist/components/label.d.ts +2 -0
  12. package/dist/components/label.js +46 -0
  13. package/dist/hooks/use-field.d.ts +21 -0
  14. package/dist/hooks/use-field.js +66 -0
  15. package/dist/hooks/use-form-by-id.d.ts +6 -0
  16. package/dist/hooks/use-form-by-id.js +25 -0
  17. package/dist/hooks/use-form-context.d.ts +5 -0
  18. package/dist/hooks/use-form-context.js +17 -0
  19. package/dist/hooks/use-form.d.ts +43 -0
  20. package/dist/hooks/use-form.js +961 -0
  21. package/dist/index.d.ts +9 -0
  22. package/dist/index.js +25 -0
  23. package/dist/lib/array-helpers.d.ts +6 -0
  24. package/dist/lib/array-helpers.js +281 -0
  25. package/dist/lib/css.d.ts +1 -0
  26. package/dist/lib/css.js +45 -0
  27. package/dist/lib/debounce.d.ts +13 -0
  28. package/dist/lib/debounce.js +28 -0
  29. package/dist/lib/deep-path.d.ts +4 -0
  30. package/dist/lib/deep-path.js +60 -0
  31. package/dist/lib/drafts-helpter.d.ts +31 -0
  32. package/dist/lib/drafts-helpter.js +67 -0
  33. package/dist/lib/form-registry.d.ts +9 -0
  34. package/dist/lib/form-registry.js +24 -0
  35. package/dist/lib/group-helpers.d.ts +9 -0
  36. package/dist/lib/group-helpers.js +29 -0
  37. package/dist/lib/pub-sub.d.ts +13 -0
  38. package/dist/lib/pub-sub.js +38 -0
  39. package/dist/lib/utils.d.ts +17 -0
  40. package/dist/lib/utils.js +190 -0
  41. package/dist/lib/validation.d.ts +22 -0
  42. package/dist/lib/validation.js +46 -0
  43. package/dist/lib/zod-helpers.d.ts +5 -0
  44. package/dist/lib/zod-helpers.js +63 -0
  45. package/dist/providers/form.d.ts +51 -0
  46. package/dist/providers/form.js +63 -0
  47. package/dist/types/array.d.ts +197 -0
  48. package/dist/types/array.js +2 -0
  49. package/dist/types/field.d.ts +61 -0
  50. package/dist/types/field.js +2 -0
  51. package/dist/types/group.d.ts +16 -0
  52. package/dist/types/group.js +2 -0
  53. package/dist/types/path.d.ts +8 -0
  54. package/dist/types/path.js +2 -0
  55. package/dist/types/pub-sub.d.ts +2 -0
  56. package/dist/types/pub-sub.js +2 -0
  57. package/dist/types/utils.d.ts +310 -0
  58. package/dist/types/utils.js +2 -0
  59. package/dist/utils/index.d.ts +4 -0
  60. package/dist/utils/index.js +14 -0
  61. 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
+ }