react-f0rm 0.2.2 → 0.3.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 (45) hide show
  1. package/README.md +527 -33
  2. package/dist/devtools/index.cjs.js +737 -0
  3. package/dist/devtools/index.cjs.js.map +1 -0
  4. package/dist/devtools/index.d.ts +33 -0
  5. package/dist/devtools/index.mjs +717 -0
  6. package/dist/devtools/index.mjs.map +1 -0
  7. package/dist/form-61297bc0.d.ts +578 -0
  8. package/dist/form-94c70b4b.mjs +378 -0
  9. package/dist/form-94c70b4b.mjs.map +1 -0
  10. package/dist/form-b9441d8c.cjs.js +387 -0
  11. package/dist/form-b9441d8c.cjs.js.map +1 -0
  12. package/dist/index.cjs.js +1257 -157
  13. package/dist/index.cjs.js.map +1 -1
  14. package/dist/index.d.ts +357 -53
  15. package/dist/index.mjs +1676 -0
  16. package/dist/index.mjs.map +1 -0
  17. package/dist/index.umd.js +1257 -157
  18. package/dist/index.umd.js.map +1 -1
  19. package/dist/index.umd.min.js +2 -2
  20. package/dist/index.umd.min.js.map +1 -1
  21. package/dist/resolvers/standard-schema.cjs.js +90 -0
  22. package/dist/resolvers/standard-schema.cjs.js.map +1 -0
  23. package/dist/resolvers/standard-schema.d.ts +66 -0
  24. package/dist/resolvers/standard-schema.mjs +86 -0
  25. package/dist/resolvers/standard-schema.mjs.map +1 -0
  26. package/dist/resolvers/yup.cjs.js +12 -2
  27. package/dist/resolvers/yup.cjs.js.map +1 -1
  28. package/dist/resolvers/yup.d.ts +2 -2
  29. package/dist/resolvers/yup.mjs +23 -0
  30. package/dist/resolvers/yup.mjs.map +1 -0
  31. package/dist/resolvers/zod.cjs.js +14 -1
  32. package/dist/resolvers/zod.cjs.js.map +1 -1
  33. package/dist/resolvers/zod.d.ts +2 -2
  34. package/dist/resolvers/zod.mjs +23 -0
  35. package/dist/resolvers/zod.mjs.map +1 -0
  36. package/dist/validate-148fe167.d.ts +22 -0
  37. package/package.json +34 -8
  38. package/dist/form-d06e6444.d.ts +0 -201
  39. package/dist/index.esm.js +0 -593
  40. package/dist/index.esm.js.map +0 -1
  41. package/dist/resolvers/yup.esm.js +0 -13
  42. package/dist/resolvers/yup.esm.js.map +0 -1
  43. package/dist/resolvers/zod.esm.js +0 -10
  44. package/dist/resolvers/zod.esm.js.map +0 -1
  45. package/dist/validate-0f17f86a.d.ts +0 -8
package/dist/index.umd.js CHANGED
@@ -49,9 +49,56 @@
49
49
  };
50
50
  }
51
51
 
52
+ const pathCache = /* @__PURE__ */ new Map();
52
53
  function normalizePath(path) {
53
54
  if (Array.isArray(path)) return path;
54
- return path.split(/\.|\[/).map((prop) => prop.endsWith("]") ? Number.parseInt(prop, 10) : prop);
55
+ const cached = pathCache.get(path);
56
+ if (cached) return cached;
57
+ const value = parsePath(path);
58
+ pathCache.set(path, value);
59
+ return value;
60
+ }
61
+ function parsePath(path) {
62
+ const result = [];
63
+ let identifier = "";
64
+ const flushIdentifier = () => {
65
+ result.push(identifier);
66
+ identifier = "";
67
+ };
68
+ for (let i = 0; i < path.length; i++) {
69
+ const char = path[i];
70
+ if (char === ".") {
71
+ if (identifier !== "") flushIdentifier();
72
+ } else if (char === "[") {
73
+ if (identifier !== "") flushIdentifier();
74
+ const quote = path[i + 1];
75
+ if (quote === '"' || quote === "'") {
76
+ const close = path.indexOf(quote, i + 2);
77
+ if (close === -1) {
78
+ throw new TypeError(`Unterminated quote in path: ${path}`);
79
+ }
80
+ if (path[close + 1] !== "]") {
81
+ throw new TypeError(
82
+ `Expected "]" after quoted segment in path: ${path}`
83
+ );
84
+ }
85
+ result.push(path.slice(i + 2, close));
86
+ i = close + 1;
87
+ } else {
88
+ const close = path.indexOf("]", i + 1);
89
+ if (close === -1) {
90
+ throw new TypeError(`Unterminated bracket in path: ${path}`);
91
+ }
92
+ const content = path.slice(i + 1, close);
93
+ result.push(/^-?\d+$/.test(content) ? Number(content) : content);
94
+ i = close;
95
+ }
96
+ } else {
97
+ identifier += char;
98
+ }
99
+ }
100
+ if (identifier !== "" || result.length === 0) flushIdentifier();
101
+ return result;
55
102
  }
56
103
  function get(values, path) {
57
104
  return path.reduce((current, p) => {
@@ -59,6 +106,24 @@
59
106
  return current[p];
60
107
  }, values);
61
108
  }
109
+ function unset(values, path) {
110
+ if (!path.length || values == null) return values;
111
+ const [prop, ...props] = path;
112
+ if (props.length) {
113
+ const next = unset(values[prop], props);
114
+ return next === values[prop] ? values : set(values, path, next);
115
+ }
116
+ if (Array.isArray(values)) {
117
+ if (!(prop in values)) return values;
118
+ const arr = values.slice();
119
+ delete arr[prop];
120
+ return arr;
121
+ }
122
+ if (typeof values !== "object" || !(prop in values)) return values;
123
+ const copy = { ...values };
124
+ delete copy[prop];
125
+ return copy;
126
+ }
62
127
  function set(values, path, value) {
63
128
  if (!path.length) return value;
64
129
  const [prop, ...props] = path;
@@ -69,6 +134,35 @@
69
134
  }
70
135
  return { ...values, [prop]: set(values && values[prop], props, value) };
71
136
  }
137
+ function setOwned(root, path, value, owned) {
138
+ if (!path.length) return value;
139
+ let container = root;
140
+ let parent = null;
141
+ let parentProp = "";
142
+ for (let i = 0; i < path.length; i++) {
143
+ const prop = path[i];
144
+ if (!owned.has(container)) {
145
+ let copy;
146
+ if (typeof prop === "number") {
147
+ copy = Array.isArray(container) ? container.slice() : [];
148
+ } else {
149
+ copy = { ...container };
150
+ }
151
+ owned.add(copy);
152
+ if (i === 0) root = copy;
153
+ else parent[parentProp] = copy;
154
+ container = copy;
155
+ }
156
+ if (i === path.length - 1) {
157
+ container[prop] = value;
158
+ } else {
159
+ parent = container;
160
+ parentProp = prop;
161
+ container = container[prop];
162
+ }
163
+ }
164
+ return root;
165
+ }
72
166
  function isPromise(value) {
73
167
  return value && typeof value.then === "function";
74
168
  }
@@ -82,7 +176,7 @@
82
176
  reject();
83
177
  return;
84
178
  }
85
- if (isResolve()) return;
179
+ if (!isResolve()) return;
86
180
  off();
87
181
  resolve();
88
182
  });
@@ -95,85 +189,148 @@
95
189
  }
96
190
 
97
191
  const emit = emit$1;
192
+ const VALIDATION_OUTCOME = /* @__PURE__ */ Symbol("validation-outcome");
98
193
  function create(options) {
99
194
  const emitter = create$2();
100
195
  return {
101
196
  emitter,
102
- revalidateOnChange: true,
103
197
  ...options,
198
+ mode: options?.mode ?? "onSubmit",
199
+ reValidateMode: options?.reValidateMode ?? "onChange",
200
+ disabled: options?.disabled ?? false,
104
201
  initialValues: options?.initialValues ?? {},
105
202
  values: /* @__PURE__ */ new Map(),
203
+ deleted: /* @__PURE__ */ new Set(),
106
204
  errors: /* @__PURE__ */ new Map(),
107
205
  touched: /* @__PURE__ */ new Set(),
108
206
  validators: /* @__PURE__ */ new Map(),
109
207
  validating: /* @__PURE__ */ new Set(),
208
+ parsedValues: void 0,
110
209
  isSubmitting: false,
111
210
  submitCount: 0,
112
211
  isSubmitSuccessful: void 0
113
212
  };
114
213
  }
115
214
  function getValues(form) {
116
- return Array.from(form.values.keys()).reduce(
117
- (v, k) => set(v, JSON.parse(k), form.values.get(k)),
118
- form.initialValues
119
- );
215
+ const { initialValues, parsedValues, values, deleted } = form;
216
+ const owned = /* @__PURE__ */ new Set();
217
+ let merged = parsedValues ?? initialValues;
218
+ for (const [key, value] of values) {
219
+ merged = setOwned(merged, JSON.parse(key), value, owned);
220
+ }
221
+ for (const key of deleted) {
222
+ merged = unset(merged, JSON.parse(key));
223
+ }
224
+ return merged;
120
225
  }
121
226
  function getValue(form, name) {
122
227
  return getValueByPath(form, create$1(name));
123
228
  }
124
- function getValueByPath({ initialValues, values }, path) {
229
+ function getValueByPath({ initialValues, parsedValues, values, deleted }, path) {
125
230
  if (values.has(path.key)) return values.get(path.key);
126
- return get(initialValues, path.value);
231
+ if (deleted.has(path.key)) return void 0;
232
+ return get(parsedValues ?? initialValues, path.value);
127
233
  }
128
- function setValue(form, name, value) {
129
- setValueByPath(form, create$1(name), value);
234
+ function setValue(form, name, value, options) {
235
+ setValueByPath(form, create$1(name), value, options);
130
236
  }
131
- function setValueByPath({ emitter, values }, path, value) {
237
+ function setValueByPath(form, path, value, options) {
238
+ const { emitter, values, deleted } = form;
132
239
  values.set(path.key, value);
240
+ reviveBranch(deleted, path);
241
+ bumpDirtyVersion(form);
242
+ if (options?.shouldTouch) setTouchedByPath(form, path);
243
+ if (options?.shouldValidate) form.validators.get(path.key)?.();
133
244
  emit(emitter, "change", path);
134
245
  }
135
246
  function getError(form, name) {
136
247
  return getErrorByPath(form, create$1(name));
137
248
  }
138
249
  function getErrorByPath({ errors }, path) {
139
- return errors.get(path.key);
250
+ return errors.get(path.key)?.[0];
251
+ }
252
+ const NO_ERRORS = [];
253
+ function getFieldErrors(form, name) {
254
+ return getFieldErrorsByPath(form, create$1(name));
255
+ }
256
+ function getFieldErrorsByPath({ errors }, path) {
257
+ return errors.get(path.key) ?? NO_ERRORS;
140
258
  }
141
259
  function getErrors({ errors }) {
142
- return Array.from(errors.values());
260
+ const entries = [];
261
+ for (const [key, list] of errors) {
262
+ const path = JSON.parse(key).join(".");
263
+ for (const { type, message } of list) entries.push({ path, type, message });
264
+ }
265
+ return entries;
143
266
  }
144
267
  function getFirstError({ errors }) {
145
- return errors.values().next().value;
268
+ return errors.values().next().value?.[0]?.message;
146
269
  }
147
- function unsetValidatingByPath({ emitter, validating }, { key }) {
148
- validating.delete(key);
149
- emit(emitter, "validating");
270
+ function getFieldState(form, name) {
271
+ const path = create$1(name);
272
+ const { initialValues, values, touched, validating } = form;
273
+ const live = values.get(path.key);
274
+ return {
275
+ value: getValueByPath(form, path),
276
+ error: getErrorByPath(form, path),
277
+ errors: getFieldErrorsByPath(form, path),
278
+ isDirty: values.has(path.key) && get(initialValues, path.value) !== live,
279
+ isTouched: touched.has(path.key),
280
+ isValidating: validating.has(path.key)
281
+ };
150
282
  }
151
- function setValidatingByPath({ emitter, validating }, { key }) {
152
- validating.add(key);
153
- emit(emitter, "validating");
283
+ function unsetValidatingByPath({ emitter, validating }, path) {
284
+ validating.delete(path.key);
285
+ emit(emitter, "validating", path);
286
+ }
287
+ function setValidatingByPath({ emitter, validating }, path) {
288
+ validating.add(path.key);
289
+ emit(emitter, "validating", path);
154
290
  }
155
291
  function setError(form, name, error) {
156
292
  setErrorByPath(form, create$1(name), error);
157
293
  }
158
294
  function setErrorByPath({ emitter, errors }, path, error) {
159
- if (error) {
160
- errors.set(path.key, error);
161
- } else {
162
- errors.delete(path.key);
295
+ const list = normalizeErrors(error);
296
+ if (list) errors.set(path.key, list);
297
+ else errors.delete(path.key);
298
+ emit(emitter, "errors", path);
299
+ }
300
+ function normalizeErrors(error) {
301
+ if (typeof error === "string") {
302
+ return error ? [{ type: "custom", message: error }] : void 0;
163
303
  }
164
- emit(emitter, "errors");
165
- }
166
- function clearErrors({ emitter, errors }) {
167
- errors.clear();
168
- emit(emitter, "errors");
304
+ if (isFieldError(error)) return [error];
305
+ if (!error) return void 0;
306
+ const list = [];
307
+ error.forEach((item) => {
308
+ if (typeof item === "string" && item) {
309
+ list.push({ type: "custom", message: item });
310
+ } else if (isFieldError(item)) {
311
+ list.push(item);
312
+ }
313
+ });
314
+ return list.length ? list : void 0;
315
+ }
316
+ function clearErrors(form, name) {
317
+ const { emitter, errors } = form;
318
+ if (name === void 0) {
319
+ errors.clear();
320
+ emit(emitter, "errors");
321
+ return;
322
+ }
323
+ const paths = typeof name === "string" || isSegmentsPath(name) ? [create$1(name)] : name.map((one) => create$1(one));
324
+ for (const { key } of paths) errors.delete(key);
325
+ for (const path of paths) emit(emitter, "errors", path);
169
326
  }
170
327
  function setTouched(form, name) {
171
328
  setTouchedByPath(form, create$1(name));
172
329
  }
173
- function setTouchedByPath({ emitter, touched }, { key }) {
174
- if (touched.has(key)) return;
175
- touched.add(key);
176
- emit(emitter, "touched");
330
+ function setTouchedByPath({ emitter, touched }, path) {
331
+ if (touched.has(path.key)) return;
332
+ touched.add(path.key);
333
+ emit(emitter, "touched", path);
177
334
  }
178
335
  function hasTouched(form, name) {
179
336
  return hasTouchedByPath(form, create$1(name));
@@ -181,12 +338,53 @@
181
338
  function hasTouchedByPath({ touched }, path) {
182
339
  return touched.has(path.key);
183
340
  }
184
- function isDirty({ initialValues, values }) {
341
+ function isDirty(form) {
342
+ let dirty = false;
343
+ forEachDirtyField(form, () => {
344
+ dirty = true;
345
+ });
346
+ return dirty;
347
+ }
348
+ function forEachDirtyField({ initialValues, values }, fn) {
185
349
  for (const [key, value] of values) {
186
350
  const path = JSON.parse(key);
187
- if (get(initialValues, path) !== value) return true;
351
+ if (get(initialValues, path) !== value) fn(path.join("."));
188
352
  }
189
- return false;
353
+ }
354
+ const dirtyFieldsCaches = /* @__PURE__ */ new WeakMap();
355
+ function bumpDirtyVersion(form) {
356
+ const cache = dirtyFieldsCaches.get(form);
357
+ if (cache) cache.version++;
358
+ }
359
+ function computeDirtyFields(form) {
360
+ const dirtyFields = {};
361
+ forEachDirtyField(form, (key) => {
362
+ dirtyFields[key] = true;
363
+ });
364
+ return dirtyFields;
365
+ }
366
+ function sameDirtyKeys(a, b) {
367
+ const aKeys = Object.keys(a);
368
+ if (aKeys.length !== Object.keys(b).length) return false;
369
+ return aKeys.every((key) => b[key] === true);
370
+ }
371
+ function getDirtyFields(form) {
372
+ let cache = dirtyFieldsCaches.get(form);
373
+ if (!cache) {
374
+ cache = { version: 0, result: computeDirtyFields(form) };
375
+ dirtyFieldsCaches.set(form, cache);
376
+ } else if (cache.version > 0) {
377
+ const result = computeDirtyFields(form);
378
+ if (!sameDirtyKeys(cache.result, result)) cache.result = result;
379
+ cache.version = 0;
380
+ }
381
+ return cache.result;
382
+ }
383
+ function getTouchedFields({ touched }) {
384
+ return Array.from(
385
+ touched,
386
+ (key) => JSON.parse(key).join(".")
387
+ );
190
388
  }
191
389
  function isTouched({ touched }) {
192
390
  return touched.size > 0;
@@ -194,38 +392,154 @@
194
392
  function removeField(form, name) {
195
393
  removeFieldByPath(form, create$1(name));
196
394
  }
197
- function removeFieldByPath(form, { key }) {
198
- const { emitter, values, touched, errors, validating } = form;
395
+ function removeFieldByPath(form, { key, value: segments }) {
396
+ const { emitter, values, touched, errors, validating, deleted } = form;
199
397
  values.delete(key);
200
398
  touched.delete(key);
201
399
  errors.delete(key);
202
400
  validating.delete(key);
401
+ if (!hasLiveBranch(values, segments)) deleted.add(key);
402
+ bumpDirtyVersion(form);
203
403
  emit(emitter, "change");
204
404
  emit(emitter, "touched");
205
405
  emit(emitter, "errors");
206
406
  emit(emitter, "validating");
207
407
  }
408
+ function hasLiveBranch(values, segments) {
409
+ for (let i = 1; i < segments.length; i++) {
410
+ if (values.has(JSON.stringify(segments.slice(0, i)))) return true;
411
+ }
412
+ const stem = `${JSON.stringify(segments).slice(0, -1)},`;
413
+ for (const key of values.keys()) {
414
+ if (key.startsWith(stem)) return true;
415
+ }
416
+ return false;
417
+ }
418
+ function reviveBranch(deleted, { key }) {
419
+ if (!deleted.size) return;
420
+ for (const tombstone of deleted) {
421
+ if (tombstone === key || tombstone.startsWith(`${key.slice(0, -1)},`) || key.startsWith(`${tombstone.slice(0, -1)},`)) {
422
+ deleted.delete(tombstone);
423
+ }
424
+ }
425
+ }
208
426
  function setInitialValues(form, initialValues) {
209
427
  if (form.initialValues === initialValues) return;
210
428
  form.initialValues = initialValues;
429
+ form.parsedValues = void 0;
211
430
  form.values.clear();
431
+ form.deleted.clear();
432
+ bumpDirtyVersion(form);
212
433
  emit(form.emitter, "change");
213
434
  }
214
- function reset(form, initialValues) {
435
+ function reset(form, initialValues, options) {
436
+ const dirtyValues = options?.keepDirtyValues ? Object.keys(getDirtyFields(form)).map((key) => ({
437
+ key,
438
+ value: getValue(form, key)
439
+ })) : [];
215
440
  form.initialValues = initialValues;
216
- clearErrors(form);
217
- const { emitter, touched, values } = form;
441
+ form.parsedValues = void 0;
442
+ if (!options?.keepErrors) clearErrors(form);
443
+ const { emitter, touched, values, deleted, validating } = form;
218
444
  values.clear();
219
- touched.clear();
445
+ deleted.clear();
446
+ if (!options?.keepTouched) touched.clear();
447
+ validating.clear();
448
+ if (!options?.keepIsSubmitting) form.isSubmitting = false;
449
+ if (!options?.keepSubmitCount) form.submitCount = 0;
450
+ if (!options?.keepIsSubmitted) form.isSubmitSuccessful = void 0;
451
+ bumpDirtyVersion(form);
452
+ for (const { key, value } of dirtyValues) {
453
+ setValueByPath(form, create$1(key), value);
454
+ }
220
455
  emit(emitter, "change");
221
456
  emit(emitter, "touched");
457
+ emit(emitter, "validating");
458
+ emit(emitter, "submitting");
459
+ emit(emitter, "submitCount");
460
+ emit(emitter, "submitSuccessful");
222
461
  emit(emitter, "reset");
223
462
  }
463
+ function resetField(form, name, options) {
464
+ const path = create$1(name);
465
+ const { emitter, values, touched, errors, deleted } = form;
466
+ values.delete(path.key);
467
+ if (form.parsedValues !== void 0) {
468
+ form.parsedValues = unset(form.parsedValues, path.value);
469
+ const initial = get(form.initialValues, path.value);
470
+ if (initial !== void 0) values.set(path.key, initial);
471
+ }
472
+ if (options && "value" in options) {
473
+ values.set(path.key, options.value);
474
+ }
475
+ reviveBranch(deleted, path);
476
+ emit(emitter, "change");
477
+ if (!options?.keepTouched && touched.delete(path.key)) {
478
+ emit(emitter, "touched", path);
479
+ }
480
+ if (!options?.keepErrors && errors.delete(path.key)) {
481
+ emit(emitter, "errors", path);
482
+ }
483
+ bumpDirtyVersion(form);
484
+ }
224
485
  function hasErrors({ errors }) {
225
486
  return errors.size > 0;
226
487
  }
227
- function trigger(form) {
228
- form.validators.forEach((validator) => validator());
488
+ async function trigger(form, name) {
489
+ const settle = () => waitUntil(
490
+ form.emitter,
491
+ "validating",
492
+ () => !form.validating.size,
493
+ () => false
494
+ );
495
+ if (name === void 0) {
496
+ form.validators.forEach((validator) => validator());
497
+ await settle();
498
+ if (form.validate) {
499
+ const result = await form.validate(getValues(form));
500
+ applyValidateResult(form, result);
501
+ }
502
+ return !hasErrors(form);
503
+ }
504
+ const keys = typeof name === "string" || isSegmentsPath(name) ? [create$1(name).key] : name.map((one) => create$1(one).key);
505
+ keys.forEach((key) => form.validators.get(key)?.());
506
+ await settle();
507
+ return keys.every((key) => !form.errors.has(key));
508
+ }
509
+ function isSegmentsPath(name) {
510
+ return name.some((part) => typeof part === "number");
511
+ }
512
+ function isFieldError(value) {
513
+ return !!value && typeof value === "object" && typeof value.type === "string" && typeof value.message === "string";
514
+ }
515
+ function setFormErrors(form, result, segments = []) {
516
+ Object.entries(result).forEach(([key, value]) => {
517
+ const path = [...segments, ...normalizePath(key)];
518
+ if (typeof value === "string") {
519
+ if (value) setError(form, path, value);
520
+ } else if (Array.isArray(value)) {
521
+ setError(form, path, value);
522
+ } else if (isFieldError(value)) {
523
+ setError(form, path, value);
524
+ } else if (value && typeof value === "object") {
525
+ setFormErrors(form, value, path);
526
+ }
527
+ });
528
+ }
529
+ function setParsedValues(form, values) {
530
+ if (values === void 0 || values === form.parsedValues) return;
531
+ form.parsedValues = values;
532
+ emit(form.emitter, "change");
533
+ }
534
+ function applyValidateResult(form, result) {
535
+ if (!result) return;
536
+ if (typeof result === "object" && VALIDATION_OUTCOME in result) {
537
+ const outcome = result;
538
+ if (outcome.errors) setFormErrors(form, outcome.errors);
539
+ setParsedValues(form, outcome.values);
540
+ return;
541
+ }
542
+ setFormErrors(form, result);
229
543
  }
230
544
  async function ensureValidate(form) {
231
545
  form.validators.forEach((validator) => validator());
@@ -239,13 +553,8 @@
239
553
  });
240
554
  if (form.validate) {
241
555
  const result = await form.validate(getValues(form));
242
- const entries = result ? Object.entries(result) : [];
243
- if (entries.length) {
244
- entries.forEach(([field, error]) => {
245
- setError(form, field, error);
246
- });
247
- throw new Error(getFirstError(form));
248
- }
556
+ applyValidateResult(form, result);
557
+ if (hasErrors(form)) throw new Error(getFirstError(form));
249
558
  }
250
559
  }
251
560
  async function validate(form) {
@@ -263,68 +572,510 @@
263
572
  form.isSubmitSuccessful = value;
264
573
  emit(form.emitter, "submitSuccessful");
265
574
  }
575
+ function setDisabled(form, value) {
576
+ form.disabled = value;
577
+ emit(form.emitter, "disabled");
578
+ }
579
+ function nameToPath(name) {
580
+ if (name.startsWith("[")) {
581
+ try {
582
+ const segments = JSON.parse(name);
583
+ if (Array.isArray(segments)) return segments.join(".");
584
+ } catch {
585
+ }
586
+ }
587
+ return name;
588
+ }
589
+ function getNativeErrors(formEl) {
590
+ const errors = [];
591
+ const { elements } = formEl;
592
+ for (let i = 0; i < elements.length; i++) {
593
+ const el = elements[i];
594
+ if (el.name && typeof el.checkValidity === "function" && !el.checkValidity()) {
595
+ errors.push({
596
+ path: nameToPath(el.name),
597
+ type: "native",
598
+ message: el.validationMessage
599
+ });
600
+ }
601
+ }
602
+ return errors;
603
+ }
604
+ function handleSubmit(form, options) {
605
+ const {
606
+ onSubmit,
607
+ onValidSubmit,
608
+ onInvalidSubmit,
609
+ shouldFocusError = true
610
+ } = options ?? {};
611
+ return async (e) => {
612
+ if (e && typeof e.preventDefault === "function") {
613
+ e.preventDefault();
614
+ }
615
+ const formEl = e?.currentTarget;
616
+ setIsSubmitting(form, true);
617
+ incrementSubmitCount(form);
618
+ const values = getValues(form);
619
+ if (formEl && typeof formEl.checkValidity === "function" && formEl.checkValidity() === false) {
620
+ formEl.reportValidity();
621
+ if (shouldFocusError && typeof formEl.querySelector === "function") {
622
+ const invalid = formEl.querySelector(":invalid");
623
+ if (invalid && typeof invalid.focus === "function") invalid.focus();
624
+ }
625
+ setIsSubmitting(form, false);
626
+ setSubmitSuccessful(form, false);
627
+ if (onInvalidSubmit) onInvalidSubmit(getNativeErrors(formEl), values);
628
+ return;
629
+ }
630
+ const error = await validate(form);
631
+ if (error) {
632
+ setIsSubmitting(form, false);
633
+ setSubmitSuccessful(form, false);
634
+ if (shouldFocusError) {
635
+ const firstKey = form.errors.keys().next().value;
636
+ if (firstKey !== void 0) emit(form.emitter, "focusError", firstKey);
637
+ }
638
+ if (onInvalidSubmit) onInvalidSubmit(getErrors(form), values);
639
+ return;
640
+ }
641
+ try {
642
+ const submitted = getValues(form);
643
+ if (onSubmit) await onSubmit(submitted, e);
644
+ if (onValidSubmit) await onValidSubmit(submitted, e);
645
+ setSubmitSuccessful(form, true);
646
+ } catch {
647
+ setSubmitSuccessful(form, false);
648
+ } finally {
649
+ setIsSubmitting(form, false);
650
+ }
651
+ };
652
+ }
653
+ function setFocus(form, name, options) {
654
+ const { key } = create$1(name);
655
+ if (options) emit(form.emitter, "focusError", key, options);
656
+ else emit(form.emitter, "focusError", key);
657
+ }
266
658
 
267
- const FormContext = React.createContext(null);
268
- const FormProvider = FormContext.Provider;
269
- function useFormContext() {
270
- const form = React.useContext(FormContext);
271
- if (!form) throw new Error("no form provided");
272
- return form;
659
+ function defaultMessage(type, bound) {
660
+ switch (type) {
661
+ case "required":
662
+ return "This field is required";
663
+ case "min":
664
+ return `Must be at least ${bound}`;
665
+ case "max":
666
+ return `Must be at most ${bound}`;
667
+ case "minLength":
668
+ return `Must be at least ${bound} characters`;
669
+ case "maxLength":
670
+ return `Must be at most ${bound} characters`;
671
+ case "pattern":
672
+ return "Invalid format";
673
+ default:
674
+ return "Invalid value";
675
+ }
273
676
  }
274
- const CheckboxGroupContext = React.createContext(null);
275
- const CheckboxGroupProvider = CheckboxGroupContext.Provider;
276
- function useCheckboxGroupContext() {
277
- const group = React.useContext(CheckboxGroupContext);
278
- if (!group) throw new Error("no group provided");
279
- return group;
677
+ function rulesToValidator(rules) {
678
+ return (value) => {
679
+ if (rules.required) {
680
+ if (value === "" || value === void 0 || value === null) {
681
+ return [
682
+ {
683
+ type: "required",
684
+ message: typeof rules.required === "string" ? rules.required : defaultMessage("required")
685
+ }
686
+ ];
687
+ }
688
+ }
689
+ const errors = [];
690
+ const message = (type, bound) => rules.messages?.[type] ?? defaultMessage(type, bound);
691
+ if (rules.min !== void 0) {
692
+ const n = Number(value);
693
+ if (!Number.isNaN(n) && n < rules.min) {
694
+ errors.push({ type: "min", message: message("min", rules.min) });
695
+ }
696
+ }
697
+ if (rules.max !== void 0) {
698
+ const n = Number(value);
699
+ if (!Number.isNaN(n) && n > rules.max) {
700
+ errors.push({ type: "max", message: message("max", rules.max) });
701
+ }
702
+ }
703
+ if (rules.minLength !== void 0 && typeof value === "string" && value.length < rules.minLength) {
704
+ errors.push({
705
+ type: "minLength",
706
+ message: message("minLength", rules.minLength)
707
+ });
708
+ }
709
+ if (rules.maxLength !== void 0 && typeof value === "string" && value.length > rules.maxLength) {
710
+ errors.push({
711
+ type: "maxLength",
712
+ message: message("maxLength", rules.maxLength)
713
+ });
714
+ }
715
+ if (rules.pattern && !rules.pattern.value.test(value)) {
716
+ errors.push({
717
+ type: "pattern",
718
+ // pattern.message is type-required but JS consumers may omit it.
719
+ message: rules.messages?.pattern ?? rules.pattern.message ?? defaultMessage("pattern")
720
+ });
721
+ }
722
+ return errors.length ? errors : void 0;
723
+ };
724
+ }
725
+
726
+ var shim = {exports: {}};
727
+
728
+ var useSyncExternalStoreShim_production = {};
729
+
730
+ /**
731
+ * @license React
732
+ * use-sync-external-store-shim.production.js
733
+ *
734
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
735
+ *
736
+ * This source code is licensed under the MIT license found in the
737
+ * LICENSE file in the root directory of this source tree.
738
+ */
739
+
740
+ var hasRequiredUseSyncExternalStoreShim_production;
741
+
742
+ function requireUseSyncExternalStoreShim_production () {
743
+ if (hasRequiredUseSyncExternalStoreShim_production) return useSyncExternalStoreShim_production;
744
+ hasRequiredUseSyncExternalStoreShim_production = 1;
745
+ var React$1 = React;
746
+ function is(x, y) {
747
+ return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y);
748
+ }
749
+ var objectIs = "function" === typeof Object.is ? Object.is : is,
750
+ useState = React$1.useState,
751
+ useEffect = React$1.useEffect,
752
+ useLayoutEffect = React$1.useLayoutEffect,
753
+ useDebugValue = React$1.useDebugValue;
754
+ function useSyncExternalStore$2(subscribe, getSnapshot) {
755
+ var value = getSnapshot(),
756
+ _useState = useState({ inst: { value: value, getSnapshot: getSnapshot } }),
757
+ inst = _useState[0].inst,
758
+ forceUpdate = _useState[1];
759
+ useLayoutEffect(
760
+ function () {
761
+ inst.value = value;
762
+ inst.getSnapshot = getSnapshot;
763
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });
764
+ },
765
+ [subscribe, value, getSnapshot]
766
+ );
767
+ useEffect(
768
+ function () {
769
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });
770
+ return subscribe(function () {
771
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });
772
+ });
773
+ },
774
+ [subscribe]
775
+ );
776
+ useDebugValue(value);
777
+ return value;
778
+ }
779
+ function checkIfSnapshotChanged(inst) {
780
+ var latestGetSnapshot = inst.getSnapshot;
781
+ inst = inst.value;
782
+ try {
783
+ var nextValue = latestGetSnapshot();
784
+ return !objectIs(inst, nextValue);
785
+ } catch (error) {
786
+ return !0;
787
+ }
788
+ }
789
+ function useSyncExternalStore$1(subscribe, getSnapshot) {
790
+ return getSnapshot();
791
+ }
792
+ var shim =
793
+ "undefined" === typeof window ||
794
+ "undefined" === typeof window.document ||
795
+ "undefined" === typeof window.document.createElement
796
+ ? useSyncExternalStore$1
797
+ : useSyncExternalStore$2;
798
+ useSyncExternalStoreShim_production.useSyncExternalStore =
799
+ void 0 !== React$1.useSyncExternalStore ? React$1.useSyncExternalStore : shim;
800
+ return useSyncExternalStoreShim_production;
801
+ }
802
+
803
+ var useSyncExternalStoreShim_development = {};
804
+
805
+ /**
806
+ * @license React
807
+ * use-sync-external-store-shim.development.js
808
+ *
809
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
810
+ *
811
+ * This source code is licensed under the MIT license found in the
812
+ * LICENSE file in the root directory of this source tree.
813
+ */
814
+
815
+ var hasRequiredUseSyncExternalStoreShim_development;
816
+
817
+ function requireUseSyncExternalStoreShim_development () {
818
+ if (hasRequiredUseSyncExternalStoreShim_development) return useSyncExternalStoreShim_development;
819
+ hasRequiredUseSyncExternalStoreShim_development = 1;
820
+ "production" !== process.env.NODE_ENV &&
821
+ (function () {
822
+ function is(x, y) {
823
+ return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y);
824
+ }
825
+ function useSyncExternalStore$2(subscribe, getSnapshot) {
826
+ didWarnOld18Alpha ||
827
+ void 0 === React$1.startTransition ||
828
+ ((didWarnOld18Alpha = !0),
829
+ console.error(
830
+ "You are using an outdated, pre-release alpha of React 18 that does not support useSyncExternalStore. The use-sync-external-store shim will not work correctly. Upgrade to a newer pre-release."
831
+ ));
832
+ var value = getSnapshot();
833
+ if (!didWarnUncachedGetSnapshot) {
834
+ var cachedValue = getSnapshot();
835
+ objectIs(value, cachedValue) ||
836
+ (console.error(
837
+ "The result of getSnapshot should be cached to avoid an infinite loop"
838
+ ),
839
+ (didWarnUncachedGetSnapshot = !0));
840
+ }
841
+ cachedValue = useState({
842
+ inst: { value: value, getSnapshot: getSnapshot }
843
+ });
844
+ var inst = cachedValue[0].inst,
845
+ forceUpdate = cachedValue[1];
846
+ useLayoutEffect(
847
+ function () {
848
+ inst.value = value;
849
+ inst.getSnapshot = getSnapshot;
850
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });
851
+ },
852
+ [subscribe, value, getSnapshot]
853
+ );
854
+ useEffect(
855
+ function () {
856
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });
857
+ return subscribe(function () {
858
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });
859
+ });
860
+ },
861
+ [subscribe]
862
+ );
863
+ useDebugValue(value);
864
+ return value;
865
+ }
866
+ function checkIfSnapshotChanged(inst) {
867
+ var latestGetSnapshot = inst.getSnapshot;
868
+ inst = inst.value;
869
+ try {
870
+ var nextValue = latestGetSnapshot();
871
+ return !objectIs(inst, nextValue);
872
+ } catch (error) {
873
+ return !0;
874
+ }
875
+ }
876
+ function useSyncExternalStore$1(subscribe, getSnapshot) {
877
+ return getSnapshot();
878
+ }
879
+ "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
880
+ "function" ===
881
+ typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart &&
882
+ __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
883
+ var React$1 = React,
884
+ objectIs = "function" === typeof Object.is ? Object.is : is,
885
+ useState = React$1.useState,
886
+ useEffect = React$1.useEffect,
887
+ useLayoutEffect = React$1.useLayoutEffect,
888
+ useDebugValue = React$1.useDebugValue,
889
+ didWarnOld18Alpha = !1,
890
+ didWarnUncachedGetSnapshot = !1,
891
+ shim =
892
+ "undefined" === typeof window ||
893
+ "undefined" === typeof window.document ||
894
+ "undefined" === typeof window.document.createElement
895
+ ? useSyncExternalStore$1
896
+ : useSyncExternalStore$2;
897
+ useSyncExternalStoreShim_development.useSyncExternalStore =
898
+ void 0 !== React$1.useSyncExternalStore ? React$1.useSyncExternalStore : shim;
899
+ "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
900
+ "function" ===
901
+ typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&
902
+ __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
903
+ })();
904
+ return useSyncExternalStoreShim_development;
905
+ }
906
+
907
+ if (process.env.NODE_ENV === 'production') {
908
+ shim.exports = requireUseSyncExternalStoreShim_production();
909
+ } else {
910
+ shim.exports = requireUseSyncExternalStoreShim_development();
911
+ }
912
+
913
+ var shimExports = shim.exports;
914
+
915
+ function isDescendant(key, ancestorKey) {
916
+ return key.startsWith(`${ancestorKey.slice(0, -1)},`);
917
+ }
918
+ function onPathEvent(emitter, event, path, scope, cb) {
919
+ const { key } = path;
920
+ return on(emitter, event, (changed) => {
921
+ if (changed === void 0 || changed.key === key || isDescendant(key, changed.key) || scope === "branch" && isDescendant(changed.key, key)) {
922
+ cb();
923
+ }
924
+ });
925
+ }
926
+ function onKeyEvent(emitter, event, key, cb) {
927
+ return on(emitter, event, (changed) => {
928
+ if (changed === void 0 || changed.key === key) cb();
929
+ });
930
+ }
931
+ function isNameList(name) {
932
+ return Array.isArray(name) && name.every((part) => typeof part !== "number");
933
+ }
934
+ function subscribe(form, options) {
935
+ const { name, event = "change", scope = "branch", callback } = options;
936
+ if (name === void 0) return on(form.emitter, event, callback);
937
+ const names = isNameList(name) ? name : [name];
938
+ const unsubscribes = names.map((one) => {
939
+ const path = create$1(one);
940
+ return event === "errors" || event === "touched" ? onKeyEvent(form.emitter, event, path.key, callback) : onPathEvent(form.emitter, event, path, scope, callback);
941
+ });
942
+ return unsubscribes.length === 1 ? unsubscribes[0] : () => unsubscribes.forEach((unsubscribe) => unsubscribe());
280
943
  }
281
944
 
282
945
  function useForm(options) {
283
- const ref = React.useRef(null);
284
- const form = ref.current = ref.current || create(options);
946
+ const [form] = React.useState(() => {
947
+ const created = create(options);
948
+ if (options && options.values !== void 0) {
949
+ setInitialValues(created, options.values);
950
+ }
951
+ return created;
952
+ });
285
953
  const initialValues = options && options.initialValues;
954
+ const values = options && options.values;
955
+ const seededRef = React.useRef(null);
956
+ if (seededRef.current === null)
957
+ seededRef.current = { done: false, source: void 0 };
286
958
  React.useEffect(() => {
959
+ const seeded = seededRef.current;
960
+ if (seeded.done && (seeded.source === initialValues || isEqual(seeded.source, initialValues))) {
961
+ return;
962
+ }
963
+ seeded.done = true;
964
+ seeded.source = initialValues;
287
965
  setInitialValues(form, initialValues);
288
- }, [initialValues]);
966
+ }, [form, initialValues]);
967
+ const controlledRef = React.useRef(null);
968
+ if (controlledRef.current === null) {
969
+ controlledRef.current = { done: false, source: void 0 };
970
+ }
971
+ React.useEffect(() => {
972
+ if (values === void 0) return;
973
+ const seeded = controlledRef.current;
974
+ if (seeded.done && (seeded.source === values || isEqual(seeded.source, values))) {
975
+ return;
976
+ }
977
+ seeded.done = true;
978
+ seeded.source = values;
979
+ setInitialValues(form, values);
980
+ }, [form, values]);
289
981
  return form;
290
982
  }
983
+ function useWatchCore(subscribeFactory, getter) {
984
+ const cacheRef = React.useRef(null);
985
+ if (cacheRef.current === null) cacheRef.current = { hasValue: false };
986
+ const cache = cacheRef.current;
987
+ const getterRef = React.useRef(getter);
988
+ getterRef.current = getter;
989
+ const getSnapshot = React.useCallback(() => {
990
+ if (!cache.hasValue) {
991
+ cache.value = getterRef.current();
992
+ cache.hasValue = true;
993
+ }
994
+ return cache.value;
995
+ }, [cache]);
996
+ const subscribe = React.useCallback(
997
+ (notify) => {
998
+ cache.hasValue = false;
999
+ const invalidate = () => {
1000
+ cache.hasValue = false;
1001
+ notify();
1002
+ };
1003
+ return subscribeFactory(invalidate);
1004
+ },
1005
+ [subscribeFactory, cache]
1006
+ );
1007
+ return shimExports.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1008
+ }
291
1009
  function useWatch(emitter, event, getter) {
292
- const [value, syncValue] = React.useReducer(getter, void 0, getter);
293
- React.useEffect(() => on(emitter, event, syncValue), [emitter, event]);
294
- return value;
1010
+ const subscribeFactory = React.useCallback(
1011
+ (invalidate) => on(emitter, event, invalidate),
1012
+ [emitter, event]
1013
+ );
1014
+ return useWatchCore(subscribeFactory, getter);
295
1015
  }
296
1016
  function useValue(form, name) {
297
1017
  return useValueByPath(form, create$1(name));
298
1018
  }
299
1019
  function useValueByPath(form, path) {
300
- return useWatch(
301
- form.emitter,
302
- "change",
303
- getValueByPath.bind(null, form, path)
1020
+ const { emitter } = form;
1021
+ const { key } = path;
1022
+ const subscribeFactory = React.useCallback(
1023
+ (invalidate) => onPathEvent(emitter, "change", path, "leaf", invalidate),
1024
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- deps are `key` on purpose: useValue creates a fresh Path per render, so the object must stay out of the deps while the key string pins the subscription
1025
+ [emitter, key]
304
1026
  );
1027
+ return useWatchCore(subscribeFactory, getValueByPath.bind(null, form, path));
305
1028
  }
306
1029
  function useTouched(form, name) {
307
1030
  return useTouchedByPath(form, create$1(name));
308
1031
  }
309
1032
  function useTouchedByPath(form, path) {
310
- return useWatch(
311
- form.emitter,
312
- "touched",
1033
+ const { emitter } = form;
1034
+ const { key } = path;
1035
+ const subscribeFactory = React.useCallback(
1036
+ (invalidate) => onKeyEvent(emitter, "touched", key, invalidate),
1037
+ [emitter, key]
1038
+ );
1039
+ return useWatchCore(
1040
+ subscribeFactory,
313
1041
  hasTouchedByPath.bind(null, form, path)
314
1042
  );
315
1043
  }
316
1044
  function useError(form, name) {
317
- return useErrorByPath(form, create$1(name));
1045
+ return useErrorByPath(form, create$1(name))?.message;
318
1046
  }
319
1047
  function useErrorByPath(form, path) {
320
- return useWatch(
321
- form.emitter,
322
- "errors",
323
- getErrorByPath.bind(null, form, path)
1048
+ const { emitter } = form;
1049
+ const { key } = path;
1050
+ const subscribeFactory = React.useCallback(
1051
+ (invalidate) => onKeyEvent(emitter, "errors", key, invalidate),
1052
+ [emitter, key]
1053
+ );
1054
+ return useWatchCore(subscribeFactory, getErrorByPath.bind(null, form, path));
1055
+ }
1056
+ function useFieldErrors(form, name) {
1057
+ return useFieldErrorsByPath(form, create$1(name));
1058
+ }
1059
+ function useFieldErrorsByPath(form, path) {
1060
+ const { emitter } = form;
1061
+ const { key } = path;
1062
+ const subscribeFactory = React.useCallback(
1063
+ (invalidate) => onKeyEvent(emitter, "errors", key, invalidate),
1064
+ [emitter, key]
1065
+ );
1066
+ return useWatchCore(
1067
+ subscribeFactory,
1068
+ getFieldErrorsByPath.bind(null, form, path)
324
1069
  );
325
1070
  }
326
1071
  function useIsDirty(form) {
327
- return useWatch(form.emitter, "touched", isDirty.bind(null, form));
1072
+ return useWatch(form.emitter, "change", isDirty.bind(null, form));
1073
+ }
1074
+ function useDirtyFields(form) {
1075
+ return useWatch(form.emitter, "change", getDirtyFields.bind(null, form));
1076
+ }
1077
+ function useTouchedFields(form) {
1078
+ return useWatch(form.emitter, "touched", getTouchedFields.bind(null, form));
328
1079
  }
329
1080
  function useHasErrors(form) {
330
1081
  return useWatch(form.emitter, "errors", hasErrors.bind(null, form));
@@ -335,6 +1086,31 @@
335
1086
  function useSubmitCount(form) {
336
1087
  return useWatch(form.emitter, "submitCount", () => form.submitCount);
337
1088
  }
1089
+ function isEqual(a, b) {
1090
+ if (Object.is(a, b)) return true;
1091
+ if (a instanceof Date && b instanceof Date)
1092
+ return a.getTime() === b.getTime();
1093
+ if (!a || !b || typeof a !== "object" || typeof b !== "object") return false;
1094
+ const isArray = Array.isArray(a);
1095
+ if (isArray !== Array.isArray(b)) return false;
1096
+ if (isArray) {
1097
+ if (a.length !== b.length) return false;
1098
+ for (let i = 0; i < a.length; i++) {
1099
+ if (!isEqual(a[i], b[i])) return false;
1100
+ }
1101
+ return true;
1102
+ }
1103
+ const proto = Object.getPrototypeOf(a);
1104
+ if (proto !== Object.prototype && proto !== null) return false;
1105
+ if (Object.getPrototypeOf(b) !== proto) return false;
1106
+ const keysA = Object.keys(a);
1107
+ const keysB = Object.keys(b);
1108
+ if (keysA.length !== keysB.length) return false;
1109
+ for (const key of keysA) {
1110
+ if (!isEqual(a[key], b[key])) return false;
1111
+ }
1112
+ return true;
1113
+ }
338
1114
 
339
1115
  function usePath(name) {
340
1116
  const path = React.useMemo(() => create$1(normalizePath(name)), [name]);
@@ -350,70 +1126,174 @@
350
1126
  const ref = useStage(fn);
351
1127
  return React.useCallback(
352
1128
  (...params) => ref.current(...params),
353
- []
1129
+ [ref]
354
1130
  );
355
1131
  }
356
1132
 
357
- function useValidate(validate, path) {
358
- const form = useFormContext();
1133
+ function useValidate(validate, path, formProp, options) {
1134
+ const contextForm = React.useContext(FormContext);
1135
+ const form = formProp || contextForm;
1136
+ if (!form) throw new Error("no form provided");
359
1137
  const lockRef = React.useRef(null);
360
1138
  const validateRef = React.useRef(validate);
361
1139
  validateRef.current = validate;
1140
+ const debounceRef = React.useRef(options?.debounce ?? 0);
1141
+ debounceRef.current = options?.debounce ?? 0;
362
1142
  React.useEffect(() => {
363
- const validator = () => {
1143
+ let timer = null;
1144
+ let controller = null;
1145
+ let marked = false;
1146
+ const mark = () => {
1147
+ if (marked) return;
1148
+ marked = true;
1149
+ setValidatingByPath(form, path);
1150
+ };
1151
+ const unmark = () => {
1152
+ if (!marked) return;
1153
+ marked = false;
1154
+ unsetValidatingByPath(form, path);
1155
+ };
1156
+ const run = () => {
1157
+ timer = null;
364
1158
  const fn = validateRef.current;
365
- if (!fn) return;
366
- const result = fn(getValueByPath(form, path), { form, path });
1159
+ if (!fn) {
1160
+ unmark();
1161
+ return;
1162
+ }
1163
+ controller?.abort();
1164
+ controller = new AbortController();
1165
+ const lock = lockRef.current = {};
1166
+ let result;
1167
+ try {
1168
+ result = fn(getValueByPath(form, path), {
1169
+ form,
1170
+ path,
1171
+ signal: controller.signal
1172
+ });
1173
+ } catch (e) {
1174
+ unmark();
1175
+ throw e;
1176
+ }
367
1177
  if (!isPromise(result)) {
368
1178
  setErrorByPath(form, path, result);
1179
+ unmark();
369
1180
  return;
370
1181
  }
371
- const lock = lockRef.current = {};
372
- setValidatingByPath(form, path);
373
- result.then((error) => {
374
- if (lock === lockRef.current) {
375
- setErrorByPath(form, path, error);
1182
+ mark();
1183
+ result.then(
1184
+ (error) => {
1185
+ if (lock === lockRef.current) {
1186
+ setErrorByPath(form, path, error);
1187
+ }
376
1188
  }
1189
+ ).catch(() => {
377
1190
  }).finally(() => {
378
1191
  if (lock === lockRef.current) {
379
- unsetValidatingByPath(form, path);
1192
+ unmark();
380
1193
  lockRef.current = null;
381
1194
  }
382
1195
  });
383
1196
  };
1197
+ const validator = () => {
1198
+ const debounce = debounceRef.current;
1199
+ if (debounce > 0) {
1200
+ if (timer !== null) clearTimeout(timer);
1201
+ else mark();
1202
+ timer = setTimeout(run, debounce);
1203
+ return;
1204
+ }
1205
+ run();
1206
+ };
384
1207
  form.validators.set(path.key, validator);
385
1208
  return () => {
386
1209
  form.validators.delete(path.key);
1210
+ if (timer !== null) {
1211
+ clearTimeout(timer);
1212
+ timer = null;
1213
+ }
1214
+ unmark();
1215
+ controller?.abort();
387
1216
  };
388
1217
  }, [form, path.key]);
389
1218
  return useStageFn(() => form.validators.get(path.key)?.());
390
1219
  }
391
1220
 
392
- function useField({
1221
+ function combineRulesAndValidate(rules, validate) {
1222
+ if (!rules) return validate;
1223
+ const ruleValidator = rulesToValidator(rules);
1224
+ if (!validate) return ruleValidator;
1225
+ return (value, meta) => {
1226
+ const ruleErrors = ruleValidator(value, meta);
1227
+ const merge = (other) => {
1228
+ const list = [...ruleErrors ?? []];
1229
+ if (Array.isArray(other)) list.push(...other);
1230
+ else if (other) list.push(other);
1231
+ return list.length ? list : void 0;
1232
+ };
1233
+ const result = validate(value, meta);
1234
+ return isPromise(result) ? result.then(merge) : merge(result);
1235
+ };
1236
+ }
1237
+ function useDelayedErrors(errors, delay) {
1238
+ const [shown, setShown] = React.useState(errors);
1239
+ React.useEffect(() => {
1240
+ if (delay === void 0) return;
1241
+ if (errors.length === 0) {
1242
+ setShown(errors);
1243
+ return;
1244
+ }
1245
+ if (shown.length > 0) {
1246
+ setShown(errors);
1247
+ return;
1248
+ }
1249
+ const timer = setTimeout(() => setShown(errors), delay);
1250
+ return () => clearTimeout(timer);
1251
+ }, [errors, delay, shown]);
1252
+ return delay === void 0 ? errors : shown;
1253
+ }
1254
+ function useFieldCore({
393
1255
  form: f1,
394
1256
  name,
395
1257
  initialValue,
396
1258
  shouldUnregister,
397
1259
  validate,
398
- ...rest
399
- }) {
400
- const f2 = useFormContext();
401
- const form = f1 || f2;
1260
+ rules,
1261
+ validateDebounce,
1262
+ delayError,
1263
+ disabled
1264
+ }, Context) {
1265
+ const contextForm = React.useContext(Context);
1266
+ const form = f1 || contextForm;
1267
+ if (!form) throw new Error("no form provided");
402
1268
  const path = usePath(name);
403
- const validator = useValidate(validate, path);
404
- React.useMemo(() => {
405
- if (initialValue !== void 0) setValueByPath(form, path, initialValue);
406
- }, [form, path]);
407
- const error = useErrorByPath(form, path);
1269
+ const validator = useValidate(
1270
+ combineRulesAndValidate(rules, validate),
1271
+ path,
1272
+ form,
1273
+ {
1274
+ debounce: validateDebounce
1275
+ }
1276
+ );
1277
+ const liveErrors = useFieldErrorsByPath(form, path);
1278
+ const errors = useDelayedErrors(liveErrors, delayError);
1279
+ const errorObject = errors[0];
1280
+ const error = errorObject?.message;
408
1281
  const value = useValueByPath(form, path);
1282
+ const formDisabled = useWatch(form.emitter, "disabled", () => form.disabled);
1283
+ React.useEffect(() => {
1284
+ if (initialValue === void 0) return;
1285
+ if (getValueByPath(form, path) === void 0) {
1286
+ setValueByPath(form, path, initialValue);
1287
+ }
1288
+ }, [form, path, initialValue]);
409
1289
  const onChange = useStageFn((v) => {
410
1290
  setValueByPath(form, path, v);
411
- if (form.validateOnChange || form.revalidateOnChange && error && error !== void 0)
1291
+ if (form.mode === "onChange" || form.mode === "all" || form.mode === "onTouched" && hasTouchedByPath(form, path) || liveErrors.length > 0 && form.reValidateMode === "onChange")
412
1292
  validator();
413
1293
  });
414
1294
  const onBlur = useStageFn(() => {
415
1295
  setTouchedByPath(form, path);
416
- if (form.validateOnBlur || form.revalidateOnBlur && error !== void 0)
1296
+ if (form.mode === "onBlur" || form.mode === "onTouched" || form.mode === "all" || liveErrors.length > 0 && form.reValidateMode === "onBlur")
417
1297
  validator();
418
1298
  });
419
1299
  React.useEffect(
@@ -424,16 +1304,30 @@
424
1304
  },
425
1305
  [path, form, shouldUnregister]
426
1306
  );
427
- return { ...rest, value, error, onChange, onBlur, name: path.key };
1307
+ return {
1308
+ form,
1309
+ value,
1310
+ error,
1311
+ errorObject,
1312
+ errors,
1313
+ onChange,
1314
+ onBlur,
1315
+ name: path.key,
1316
+ disabled: formDisabled || !!disabled
1317
+ };
1318
+ }
1319
+ function useField(options) {
1320
+ return useFieldCore(options, FormContext);
428
1321
  }
429
1322
 
430
1323
  let idCounter = 0;
431
1324
  function generateId() {
432
1325
  return `_${++idCounter}`;
433
1326
  }
434
- function useFieldArray(options) {
435
- const f2 = useFormContext();
436
- const form = options.form || f2;
1327
+ function useFieldArrayCore(options, Context) {
1328
+ const contextForm = React.useContext(Context);
1329
+ const form = options.form || contextForm;
1330
+ if (!form) throw new Error("no form provided");
437
1331
  const path = usePath(options.name);
438
1332
  const idsRef = React.useRef([]);
439
1333
  const getArray = React.useCallback(
@@ -461,7 +1355,10 @@
461
1355
  void 0,
462
1356
  computeFields
463
1357
  );
464
- React.useEffect(() => on(form.emitter, "change", syncFields), [form.emitter]);
1358
+ React.useEffect(
1359
+ () => onPathEvent(form.emitter, "change", path, "branch", syncFields),
1360
+ [form.emitter, path]
1361
+ );
465
1362
  const append = useStageFn((value) => {
466
1363
  const arr = getArray();
467
1364
  idsRef.current.push(generateId());
@@ -503,45 +1400,80 @@
503
1400
  newArr.splice(to, 0, item);
504
1401
  setArray(newArr);
505
1402
  });
506
- return { fields, append, prepend, insert, remove, swap, move };
1403
+ const replace = useStageFn((values) => {
1404
+ idsRef.current = values.map(() => generateId());
1405
+ setArray([...values]);
1406
+ });
1407
+ const update = useStageFn((index, value) => {
1408
+ const arr = getArray();
1409
+ if (index < 0 || index >= arr.length) return;
1410
+ const newArr = [...arr];
1411
+ newArr[index] = value;
1412
+ setArray(newArr);
1413
+ });
1414
+ return { fields, append, prepend, insert, remove, swap, move, replace, update };
1415
+ }
1416
+ function useFieldArray(options) {
1417
+ return useFieldArrayCore(options, FormContext);
1418
+ }
1419
+
1420
+ const FormContext = React.createContext(null);
1421
+ const FormProvider = FormContext.Provider;
1422
+ function useFormContext() {
1423
+ const form = React.useContext(FormContext);
1424
+ if (!form) throw new Error("no form provided");
1425
+ return form;
1426
+ }
1427
+ function createFormContext() {
1428
+ const Context = React.createContext(null);
1429
+ function FormProvider2({
1430
+ form,
1431
+ children
1432
+ }) {
1433
+ return React.createElement(Context.Provider, { value: form }, children);
1434
+ }
1435
+ function useFormContext2() {
1436
+ const form = React.useContext(Context);
1437
+ if (!form) throw new Error("no form provided");
1438
+ return form;
1439
+ }
1440
+ function useField(options) {
1441
+ return useFieldCore(options, Context);
1442
+ }
1443
+ function useFieldArray(options) {
1444
+ return useFieldArrayCore(options, Context);
1445
+ }
1446
+ return { FormProvider: FormProvider2, useFormContext: useFormContext2, useField, useFieldArray };
1447
+ }
1448
+ const CheckboxGroupContext = React.createContext(null);
1449
+ const CheckboxGroupProvider = CheckboxGroupContext.Provider;
1450
+ function useCheckboxGroupContext() {
1451
+ const group = React.useContext(CheckboxGroupContext);
1452
+ if (!group) throw new Error("no group provided");
1453
+ return group;
507
1454
  }
508
1455
 
509
1456
  function Form({
510
1457
  form: f1,
511
1458
  initialValues,
1459
+ values,
512
1460
  onSubmit,
513
1461
  onValidSubmit,
514
1462
  onInvalidSubmit,
1463
+ shouldFocusError,
515
1464
  ...props
516
1465
  }) {
517
- const f2 = useForm({ initialValues });
1466
+ const f2 = useForm({ initialValues, values });
518
1467
  const form = f1 || f2;
519
- async function handleSubmit(e) {
520
- e.preventDefault();
521
- setIsSubmitting(form, true);
522
- incrementSubmitCount(form);
523
- const error = await validate(form);
524
- const values = getValues(form);
525
- if (error) {
526
- setIsSubmitting(form, false);
527
- setSubmitSuccessful(form, false);
528
- if (onInvalidSubmit) onInvalidSubmit(getErrors(form), values);
529
- return;
530
- }
531
- try {
532
- if (onSubmit) await onSubmit(values, e);
533
- if (onValidSubmit) onValidSubmit(values, e);
534
- setSubmitSuccessful(form, true);
535
- } catch {
536
- setSubmitSuccessful(form, false);
537
- } finally {
538
- setIsSubmitting(form, false);
539
- }
540
- }
541
- return /* @__PURE__ */ React__namespace.createElement(FormProvider, { value: form }, /* @__PURE__ */ React__namespace.createElement("form", { ...props, noValidate: true, onSubmit: handleSubmit }));
1468
+ const submit = handleSubmit(form, {
1469
+ onSubmit,
1470
+ onValidSubmit,
1471
+ onInvalidSubmit,
1472
+ shouldFocusError
1473
+ });
1474
+ return /* @__PURE__ */ React__namespace.createElement(FormProvider, { value: form }, /* @__PURE__ */ React__namespace.createElement("form", { ...props, noValidate: true, onSubmit: submit }));
542
1475
  }
543
1476
 
544
- const buildInError = /* @__PURE__ */ Symbol("buildInError");
545
1477
  function setRef(ref, value) {
546
1478
  if (typeof ref === "function") {
547
1479
  ref(value);
@@ -549,9 +1481,30 @@
549
1481
  ref.current = value;
550
1482
  }
551
1483
  }
1484
+ function errorIdFromKey(key) {
1485
+ const id = key.replace(/["'[\],\s]+/g, "-").replace(/^-+|-+$/g, "");
1486
+ return id || "field";
1487
+ }
552
1488
  const Field = React__namespace.forwardRef(
553
- ({ validate, eventToValue, initialValue, name, asProps, ...props }, ref) => {
1489
+ ({
1490
+ validate,
1491
+ eventToValue,
1492
+ initialValue,
1493
+ name,
1494
+ asProps,
1495
+ renderError,
1496
+ as,
1497
+ valueToProps,
1498
+ form: formProp,
1499
+ shouldUnregister,
1500
+ rules,
1501
+ validateDebounce,
1502
+ disabled,
1503
+ delayError,
1504
+ ...props
1505
+ }, ref) => {
554
1506
  const innerRef = React__namespace.useRef(null);
1507
+ const [nativeInvalidCount, setNativeInvalidCount] = React__namespace.useState(0);
555
1508
  const mergedRef = React__namespace.useCallback(
556
1509
  (node) => {
557
1510
  innerRef.current = node;
@@ -559,57 +1512,187 @@
559
1512
  },
560
1513
  [ref]
561
1514
  );
562
- const { as, value, valueToProps, onChange, error, ...rest } = useField({
563
- ...props,
1515
+ const {
1516
+ value,
1517
+ onChange,
1518
+ onBlur,
1519
+ error,
1520
+ form,
1521
+ name: fieldKey,
1522
+ disabled: isDisabled
1523
+ } = useField({
564
1524
  name,
1525
+ form: formProp,
565
1526
  initialValue,
1527
+ shouldUnregister,
1528
+ rules,
1529
+ validateDebounce,
1530
+ delayError,
1531
+ disabled,
566
1532
  validate: (...params) => {
567
- if (innerRef.current?.checkValidity() === false)
568
- return buildInError;
1533
+ const el = innerRef.current;
1534
+ if (el && typeof el.checkValidity === "function") {
1535
+ el.setCustomValidity("");
1536
+ if (el.checkValidity() === false) {
1537
+ setNativeInvalidCount((count) => count + 1);
1538
+ return void 0;
1539
+ }
1540
+ }
569
1541
  if (validate) return validate(...params);
570
1542
  }
571
1543
  });
572
1544
  const Component = as || "input";
573
1545
  React__namespace.useEffect(() => {
574
- if (!innerRef.current) return;
575
- if (error === buildInError) {
576
- innerRef.current.setCustomValidity("");
577
- innerRef.current.reportValidity();
578
- return;
579
- }
1546
+ const el = innerRef.current;
1547
+ if (!el || typeof el.setCustomValidity !== "function") return;
580
1548
  if (typeof error === "string") {
581
- innerRef.current.setCustomValidity(error);
582
- innerRef.current.reportValidity();
1549
+ el.setCustomValidity(error);
1550
+ el.reportValidity();
1551
+ } else {
1552
+ el.setCustomValidity("");
583
1553
  }
584
1554
  }, [error]);
1555
+ React__namespace.useEffect(() => {
1556
+ if (nativeInvalidCount > 0) innerRef.current?.reportValidity();
1557
+ }, [nativeInvalidCount]);
1558
+ React__namespace.useEffect(
1559
+ () => on(
1560
+ form.emitter,
1561
+ "focusError",
1562
+ (key, options) => {
1563
+ if (key !== fieldKey) return;
1564
+ const el = innerRef.current;
1565
+ if (!el || typeof el.focus !== "function") return;
1566
+ el.focus();
1567
+ if (options?.shouldSelect && typeof el.select === "function") {
1568
+ el.select();
1569
+ }
1570
+ }
1571
+ ),
1572
+ [form, fieldKey]
1573
+ );
585
1574
  const toValue = eventToValue ?? ((e) => e.target.value);
586
- return /* @__PURE__ */ React__namespace.createElement(
1575
+ const errorId = errorIdFromKey(fieldKey);
1576
+ return /* @__PURE__ */ React__namespace.createElement(React__namespace.Fragment, null, /* @__PURE__ */ React__namespace.createElement(
587
1577
  Component,
588
1578
  {
589
- ...rest,
1579
+ "aria-invalid": error ? true : void 0,
1580
+ "aria-describedby": error && renderError ? errorId : void 0,
1581
+ ...props,
1582
+ name: fieldKey,
1583
+ onBlur,
590
1584
  ...asProps,
591
1585
  ...valueToProps ? valueToProps(value) : { value },
1586
+ disabled: isDisabled,
592
1587
  onChange: (e) => onChange(toValue(e)),
593
1588
  ref: mergedRef
594
1589
  }
595
- );
1590
+ ), error && renderError ? /* @__PURE__ */ React__namespace.createElement("span", { id: errorId, role: "alert" }, renderError(error, errorId)) : null);
596
1591
  }
597
1592
  );
598
1593
  const Checkbox = React__namespace.forwardRef(
599
- ({ name, ...props }, ref) => {
600
- const { value, onChange, error, ...rest } = useField({ ...props, name });
1594
+ ({
1595
+ name,
1596
+ form,
1597
+ initialValue,
1598
+ shouldUnregister,
1599
+ validate,
1600
+ rules,
1601
+ validateDebounce,
1602
+ disabled,
1603
+ delayError,
1604
+ ...props
1605
+ }, ref) => {
1606
+ const {
1607
+ value,
1608
+ onChange,
1609
+ onBlur,
1610
+ error,
1611
+ name: fieldKey,
1612
+ disabled: isDisabled
1613
+ } = useField({
1614
+ name,
1615
+ form,
1616
+ initialValue,
1617
+ shouldUnregister,
1618
+ validate,
1619
+ rules,
1620
+ validateDebounce,
1621
+ delayError,
1622
+ disabled
1623
+ });
601
1624
  return /* @__PURE__ */ React__namespace.createElement(
602
1625
  "input",
603
1626
  {
604
- ...rest,
1627
+ "aria-invalid": error ? true : void 0,
1628
+ ...props,
1629
+ name: fieldKey,
1630
+ onBlur,
605
1631
  type: "checkbox",
606
1632
  checked: !!value,
1633
+ disabled: isDisabled,
607
1634
  onChange: (e) => onChange(e.target.checked),
608
1635
  ref
609
1636
  }
610
1637
  );
611
1638
  }
612
1639
  );
1640
+ function toSelectValue(multiple, value) {
1641
+ if (multiple) return Array.isArray(value) ? value : [];
1642
+ return value ?? "";
1643
+ }
1644
+ const Select = React__namespace.forwardRef(
1645
+ ({
1646
+ name,
1647
+ multiple,
1648
+ children,
1649
+ form,
1650
+ initialValue,
1651
+ shouldUnregister,
1652
+ validate,
1653
+ rules,
1654
+ validateDebounce,
1655
+ disabled,
1656
+ delayError,
1657
+ ...props
1658
+ }, ref) => {
1659
+ const {
1660
+ value,
1661
+ onChange,
1662
+ onBlur,
1663
+ error,
1664
+ name: fieldKey,
1665
+ disabled: isDisabled
1666
+ } = useField({
1667
+ name,
1668
+ form,
1669
+ initialValue,
1670
+ shouldUnregister,
1671
+ validate,
1672
+ rules,
1673
+ validateDebounce,
1674
+ delayError,
1675
+ disabled
1676
+ });
1677
+ return /* @__PURE__ */ React__namespace.createElement(
1678
+ "select",
1679
+ {
1680
+ "aria-invalid": error ? true : void 0,
1681
+ ...props,
1682
+ name: fieldKey,
1683
+ onBlur,
1684
+ multiple,
1685
+ value: toSelectValue(multiple, value),
1686
+ disabled: isDisabled,
1687
+ onChange: (e) => onChange(
1688
+ multiple ? Array.from(e.target.selectedOptions, (option) => option.value) : e.target.value
1689
+ ),
1690
+ ref
1691
+ },
1692
+ children
1693
+ );
1694
+ }
1695
+ );
613
1696
 
614
1697
  exports.Checkbox = Checkbox;
615
1698
  exports.CheckboxGroupContext = CheckboxGroupContext;
@@ -618,16 +1701,25 @@
618
1701
  exports.Form = Form;
619
1702
  exports.FormContext = FormContext;
620
1703
  exports.FormProvider = FormProvider;
1704
+ exports.Select = Select;
1705
+ exports.VALIDATION_OUTCOME = VALIDATION_OUTCOME;
621
1706
  exports.clearErrors = clearErrors;
622
1707
  exports.createForm = create;
1708
+ exports.createFormContext = createFormContext;
623
1709
  exports.ensureValidate = ensureValidate;
1710
+ exports.getDirtyFields = getDirtyFields;
624
1711
  exports.getError = getError;
625
1712
  exports.getErrorByPath = getErrorByPath;
626
1713
  exports.getErrors = getErrors;
1714
+ exports.getFieldErrors = getFieldErrors;
1715
+ exports.getFieldErrorsByPath = getFieldErrorsByPath;
1716
+ exports.getFieldState = getFieldState;
627
1717
  exports.getFirstError = getFirstError;
1718
+ exports.getTouchedFields = getTouchedFields;
628
1719
  exports.getValue = getValue;
629
1720
  exports.getValueByPath = getValueByPath;
630
1721
  exports.getValues = getValues;
1722
+ exports.handleSubmit = handleSubmit;
631
1723
  exports.hasErrors = hasErrors;
632
1724
  exports.hasTouched = hasTouched;
633
1725
  exports.hasTouchedByPath = hasTouchedByPath;
@@ -637,8 +1729,11 @@
637
1729
  exports.removeField = removeField;
638
1730
  exports.removeFieldByPath = removeFieldByPath;
639
1731
  exports.reset = reset;
1732
+ exports.resetField = resetField;
1733
+ exports.setDisabled = setDisabled;
640
1734
  exports.setError = setError;
641
1735
  exports.setErrorByPath = setErrorByPath;
1736
+ exports.setFocus = setFocus;
642
1737
  exports.setInitialValues = setInitialValues;
643
1738
  exports.setIsSubmitting = setIsSubmitting;
644
1739
  exports.setSubmitSuccessful = setSubmitSuccessful;
@@ -647,13 +1742,17 @@
647
1742
  exports.setValidatingByPath = setValidatingByPath;
648
1743
  exports.setValue = setValue;
649
1744
  exports.setValueByPath = setValueByPath;
1745
+ exports.subscribe = subscribe;
650
1746
  exports.trigger = trigger;
651
1747
  exports.unsetValidatingByPath = unsetValidatingByPath;
652
1748
  exports.useCheckboxGroupContext = useCheckboxGroupContext;
1749
+ exports.useDirtyFields = useDirtyFields;
653
1750
  exports.useError = useError;
654
1751
  exports.useErrorByPath = useErrorByPath;
655
1752
  exports.useField = useField;
656
1753
  exports.useFieldArray = useFieldArray;
1754
+ exports.useFieldErrors = useFieldErrors;
1755
+ exports.useFieldErrorsByPath = useFieldErrorsByPath;
657
1756
  exports.useForm = useForm;
658
1757
  exports.useFormContext = useFormContext;
659
1758
  exports.useHasErrors = useHasErrors;
@@ -662,6 +1761,7 @@
662
1761
  exports.useSubmitCount = useSubmitCount;
663
1762
  exports.useTouched = useTouched;
664
1763
  exports.useTouchedByPath = useTouchedByPath;
1764
+ exports.useTouchedFields = useTouchedFields;
665
1765
  exports.useValue = useValue;
666
1766
  exports.useValueByPath = useValueByPath;
667
1767
  exports.useWatch = useWatch;