react-f0rm 1.4.0 → 1.5.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 (84) hide show
  1. package/README.md +17 -4
  2. package/dist/array-Bu7W8BSz.d.ts +54 -0
  3. package/dist/devtools/index.cjs.js +1 -1
  4. package/dist/devtools/index.cjs.js.map +1 -1
  5. package/dist/devtools/index.d.cts +8 -18
  6. package/dist/devtools/index.d.mts +8 -18
  7. package/dist/devtools/index.d.ts +8 -18
  8. package/dist/devtools/index.mjs +1 -1
  9. package/dist/devtools/index.mjs.map +1 -1
  10. package/dist/errors-DA4ReEd9.mjs +2 -0
  11. package/dist/errors-DA4ReEd9.mjs.map +1 -0
  12. package/dist/errors-TzyWwBfw.cjs.js +2 -0
  13. package/dist/errors-TzyWwBfw.cjs.js.map +1 -0
  14. package/dist/index.cjs.js +1 -1
  15. package/dist/index.cjs.js.map +1 -1
  16. package/dist/index.d.cts +347 -927
  17. package/dist/index.d.mts +347 -927
  18. package/dist/index.d.ts +347 -927
  19. package/dist/index.mjs +1 -1
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/index.umd.js +538 -451
  22. package/dist/index.umd.js.map +1 -1
  23. package/dist/index.umd.min.js +2 -2
  24. package/dist/index.umd.min.js.map +1 -1
  25. package/dist/persist.cjs.js +1 -1
  26. package/dist/persist.cjs.js.map +1 -1
  27. package/dist/persist.d.cts +9 -30
  28. package/dist/persist.d.mts +9 -30
  29. package/dist/persist.d.ts +9 -30
  30. package/dist/persist.mjs +1 -1
  31. package/dist/persist.mjs.map +1 -1
  32. package/dist/resolvers/standard-schema.cjs.js +1 -1
  33. package/dist/resolvers/standard-schema.d.cts +1 -1
  34. package/dist/resolvers/standard-schema.d.mts +1 -1
  35. package/dist/resolvers/standard-schema.d.ts +1 -1
  36. package/dist/resolvers/standard-schema.mjs +1 -1
  37. package/dist/resolvers/yup.cjs.js +1 -1
  38. package/dist/resolvers/yup.mjs +1 -1
  39. package/dist/resolvers/zod.cjs.js +1 -1
  40. package/dist/resolvers/zod.cjs.js.map +1 -1
  41. package/dist/resolvers/zod.d.cts +30 -2
  42. package/dist/resolvers/zod.d.mts +30 -2
  43. package/dist/resolvers/zod.d.ts +30 -2
  44. package/dist/resolvers/zod.mjs +1 -1
  45. package/dist/resolvers/zod.mjs.map +1 -1
  46. package/dist/server/index.cjs.js +1 -1
  47. package/dist/server/index.cjs.js.map +1 -1
  48. package/dist/server/index.d.cts +24 -92
  49. package/dist/server/index.d.mts +24 -92
  50. package/dist/server/index.d.ts +24 -92
  51. package/dist/server/index.mjs +1 -1
  52. package/dist/server/index.mjs.map +1 -1
  53. package/dist/standard-schema-BAaTmAHh.d.ts +682 -0
  54. package/dist/standardSchema-5WezjHlp.mjs +2 -0
  55. package/dist/standardSchema-5WezjHlp.mjs.map +1 -0
  56. package/dist/standardSchema-DINHlsYR.cjs.js +2 -0
  57. package/dist/standardSchema-DINHlsYR.cjs.js.map +1 -0
  58. package/dist/validate-2pX-N1O6.cjs.js +2 -0
  59. package/dist/validate-2pX-N1O6.cjs.js.map +1 -0
  60. package/dist/validate-C0HOsP9v.mjs +2 -0
  61. package/dist/validate-C0HOsP9v.mjs.map +1 -0
  62. package/dist/values-BbPnLByD.cjs.js +2 -0
  63. package/dist/values-BbPnLByD.cjs.js.map +1 -0
  64. package/dist/values-CHsmcZk4.mjs +2 -0
  65. package/dist/values-CHsmcZk4.mjs.map +1 -0
  66. package/package.json +6 -4
  67. package/dist/array-3hAjl6Bz.d.ts +0 -160
  68. package/dist/errors-8oPux7GR.cjs.js +0 -2
  69. package/dist/errors-8oPux7GR.cjs.js.map +0 -1
  70. package/dist/errors-ClH3eV1R.mjs +0 -2
  71. package/dist/errors-ClH3eV1R.mjs.map +0 -1
  72. package/dist/standard-schema-CYbnugL7.d.ts +0 -1184
  73. package/dist/standardSchema-BpZvg0tZ.mjs +0 -2
  74. package/dist/standardSchema-BpZvg0tZ.mjs.map +0 -1
  75. package/dist/standardSchema-CxV_DhBO.cjs.js +0 -2
  76. package/dist/standardSchema-CxV_DhBO.cjs.js.map +0 -1
  77. package/dist/validate-BEjNsNvd.mjs +0 -2
  78. package/dist/validate-BEjNsNvd.mjs.map +0 -1
  79. package/dist/validate-BvN6hiEG.cjs.js +0 -2
  80. package/dist/validate-BvN6hiEG.cjs.js.map +0 -1
  81. package/dist/values-Bod74Kzu.mjs +0 -2
  82. package/dist/values-Bod74Kzu.mjs.map +0 -1
  83. package/dist/values-DKpOCD5J.cjs.js +0 -2
  84. package/dist/values-DKpOCD5J.cjs.js.map +0 -1
package/dist/index.umd.js CHANGED
@@ -266,12 +266,31 @@
266
266
  delete copy[prop];
267
267
  return copy;
268
268
  }
269
+ function arraySlot(container, prop) {
270
+ if (typeof prop === "number") return prop;
271
+ if (Array.isArray(container) && typeof prop === "string" && isIndex(prop)) {
272
+ return Number(prop);
273
+ }
274
+ return void 0;
275
+ }
276
+ function copyForProp(container, prop) {
277
+ if (arraySlot(container, prop) !== void 0) {
278
+ return Array.isArray(container) ? container.slice() : [];
279
+ }
280
+ return { ...container };
281
+ }
282
+ function ensureOwned(container, prop, owned) {
283
+ if (owned.has(container)) return container;
284
+ const copy = copyForProp(container, prop);
285
+ owned.add(copy);
286
+ return copy;
287
+ }
269
288
  function set(values, path, value) {
270
289
  if (!path.length) return value;
271
290
  const [prop, ...props] = path;
272
- const index = typeof prop === "number" ? prop : Array.isArray(values) && typeof prop === "string" && isIndex(prop) ? Number(prop) : void 0;
291
+ const index = arraySlot(values, prop);
273
292
  if (index !== void 0) {
274
- const arr = Array.isArray(values) ? values.slice() : [];
293
+ const arr = copyForProp(values, prop);
275
294
  arr[index] = set(arr[index], props, value);
276
295
  return arr;
277
296
  }
@@ -284,14 +303,8 @@
284
303
  let parentProp = "";
285
304
  for (let i = 0; i < path.length; i++) {
286
305
  const prop = path[i];
287
- if (!owned.has(container)) {
288
- let copy;
289
- if (typeof prop === "number" || Array.isArray(container) && typeof prop === "string" && isIndex(prop)) {
290
- copy = Array.isArray(container) ? container.slice() : [];
291
- } else {
292
- copy = { ...container };
293
- }
294
- owned.add(copy);
306
+ const copy = ensureOwned(container, prop, owned);
307
+ if (copy !== container) {
295
308
  if (i === 0) root = copy;
296
309
  else parent[parentProp] = copy;
297
310
  container = copy;
@@ -318,6 +331,9 @@
318
331
  if (options?.valueAsDate) return target.valueAsDate;
319
332
  return target.value;
320
333
  }
334
+ function eventToValueOrDefault(eventToValue, options) {
335
+ return eventToValue ?? ((e) => extractEventValue(e, options));
336
+ }
321
337
  function isEqual(a, b) {
322
338
  if (Object.is(a, b)) return true;
323
339
  if (a instanceof Date && b instanceof Date)
@@ -364,16 +380,27 @@
364
380
  const value = normalizePath(name);
365
381
  return { value, key: JSON.stringify(value) };
366
382
  }
383
+ function segmentsFromKey(key) {
384
+ return JSON.parse(key);
385
+ }
367
386
 
387
+ function getOrCreate(map, form, create) {
388
+ if (map.has(form)) return map.get(form);
389
+ const value = create();
390
+ map.set(form, value);
391
+ return value;
392
+ }
368
393
  const valuesCaches = /* @__PURE__ */ new WeakMap();
369
- function bumpValuesVersion(form) {
370
- const cache = valuesCaches.get(form);
394
+ function bumpVersion(map, form) {
395
+ const cache = map.get(form);
371
396
  if (cache) cache.version++;
372
397
  }
398
+ function bumpValuesVersion(form) {
399
+ bumpVersion(valuesCaches, form);
400
+ }
373
401
  const errorsCaches = /* @__PURE__ */ new WeakMap();
374
402
  function bumpErrorsVersion(form) {
375
- const cache = errorsCaches.get(form);
376
- if (cache) cache.version++;
403
+ bumpVersion(errorsCaches, form);
377
404
  }
378
405
  const dirtyBaselines = /* @__PURE__ */ new WeakMap();
379
406
  function getDirtyBaseline(form, key, segments) {
@@ -382,12 +409,7 @@
382
409
  return get(form.initialValues, segments);
383
410
  }
384
411
  function setDirtyBaseline(form, { key }, value) {
385
- let baselines = dirtyBaselines.get(form);
386
- if (!baselines) {
387
- baselines = /* @__PURE__ */ new Map();
388
- dirtyBaselines.set(form, baselines);
389
- }
390
- baselines.set(key, value);
412
+ getOrCreate(dirtyBaselines, form, () => /* @__PURE__ */ new Map()).set(key, value);
391
413
  }
392
414
  function pruneDirtyBaselines(form, { key }) {
393
415
  const baselines = dirtyBaselines.get(form);
@@ -405,8 +427,7 @@
405
427
  }
406
428
  const dirtyFieldsCaches = /* @__PURE__ */ new WeakMap();
407
429
  function bumpDirtyVersion(form) {
408
- const cache = dirtyFieldsCaches.get(form);
409
- if (cache) cache.version++;
430
+ bumpVersion(dirtyFieldsCaches, form);
410
431
  }
411
432
  function isSegmentsPath(name) {
412
433
  return name.some((part) => typeof part === "number");
@@ -439,18 +460,18 @@
439
460
  function getErrors({ errors }) {
440
461
  const entries = [];
441
462
  for (const [key, list] of errors) {
442
- const path = JSON.parse(key).join(".");
463
+ const path = segmentsFromKey(key).join(".");
443
464
  for (const { type, message } of list) entries.push({ path, type, message });
444
465
  }
445
466
  return entries;
446
467
  }
447
468
  function computeErrorsViews(form) {
448
469
  const cached = errorsCaches.get(form);
449
- if (cached && cached.version === 0) return;
470
+ if (cached?.version === 0) return;
450
471
  const result = {};
451
472
  const tree = {};
452
473
  for (const [key, list] of form.errors) {
453
- const segments = JSON.parse(key);
474
+ const segments = segmentsFromKey(key);
454
475
  result[segments.join(".")] = list;
455
476
  let node = tree;
456
477
  for (let i = 0; i < segments.length - 1; i++) {
@@ -546,21 +567,17 @@
546
567
  }
547
568
  }
548
569
  function clearServerErrors(form) {
549
- let changed = false;
550
- for (const [key, errors] of form.errors) {
551
- const kept = errors.filter((error) => error.type !== "server");
552
- if (kept.length === 0) {
553
- form.errors.delete(key);
554
- changed = true;
555
- } else if (kept.length !== errors.length) {
556
- form.errors.set(key, kept);
557
- changed = true;
558
- }
559
- }
560
- if (changed) {
561
- bumpErrorsVersion(form);
562
- emit(form.emitter, "errors");
570
+ const affected = Array.from(form.errors).filter(
571
+ ([, list]) => list.some((error) => error.type === "server")
572
+ );
573
+ if (affected.length === 0) return;
574
+ for (const [key, list] of affected) {
575
+ const kept = list.filter((error) => error.type !== "server");
576
+ if (kept.length) form.errors.set(key, kept);
577
+ else form.errors.delete(key);
563
578
  }
579
+ bumpErrorsVersion(form);
580
+ emit(form.emitter, "errors");
564
581
  }
565
582
  function hasErrors({ errors }) {
566
583
  return errors.size > 0;
@@ -603,22 +620,18 @@
603
620
  };
604
621
  }
605
622
  function toPathSegments(issue) {
606
- const path = issue.path || [];
607
- const segments = [];
608
- for (const segment of path) {
609
- const key = typeof segment === "object" && segment !== null ? segment.key : segment;
610
- segments.push(String(key));
611
- }
612
- return segments;
623
+ return (issue.path || []).map(
624
+ (segment) => String(
625
+ typeof segment === "object" && segment !== null ? segment.key : segment
626
+ )
627
+ );
613
628
  }
614
629
  function assignAtPath(root, segments, error) {
615
630
  let node = root;
616
631
  for (let i = 0; i < segments.length - 1; i++) {
617
632
  const segment = segments[i];
618
- let next = node[segment];
619
- if (next === void 0) {
620
- next = node[segment] = {};
621
- }
633
+ if (node[segment] === void 0) node[segment] = {};
634
+ const next = node[segment];
622
635
  if (!isBranch(next)) return;
623
636
  node = next;
624
637
  }
@@ -631,21 +644,16 @@
631
644
  return !!value && typeof value === "object" && !Array.isArray(value);
632
645
  }
633
646
  function pruneEmpty(node) {
634
- let hasLeaf = false;
635
647
  const result = {};
636
- Object.entries(node).forEach(([key, value]) => {
648
+ for (const [key, value] of Object.entries(node)) {
637
649
  if (isBranch(value)) {
638
650
  const pruned = pruneEmpty(value);
639
- if (pruned) {
640
- result[key] = pruned;
641
- hasLeaf = true;
642
- }
651
+ if (pruned) result[key] = pruned;
643
652
  } else {
644
653
  result[key] = value;
645
- hasLeaf = true;
646
654
  }
647
- });
648
- return hasLeaf ? result : void 0;
655
+ }
656
+ return Object.keys(result).length ? result : void 0;
649
657
  }
650
658
 
651
659
  function isFieldDirtyByPath(form, path) {
@@ -653,15 +661,15 @@
653
661
  return form.values.has(path.key) && getDirtyBaseline(form, path.key, path.value) !== live;
654
662
  }
655
663
  function isDirty(form) {
656
- let dirty = false;
657
- forEachDirtyField(form, () => {
658
- dirty = true;
659
- });
660
- return dirty;
664
+ for (const [key, value] of form.values) {
665
+ const path = segmentsFromKey(key);
666
+ if (getDirtyBaseline(form, key, path) !== value) return true;
667
+ }
668
+ return false;
661
669
  }
662
670
  function forEachDirtyField(form, fn) {
663
671
  for (const [key, value] of form.values) {
664
- const path = JSON.parse(key);
672
+ const path = segmentsFromKey(key);
665
673
  if (getDirtyBaseline(form, key, path) !== value) fn(path.join("."));
666
674
  }
667
675
  }
@@ -678,11 +686,11 @@
678
686
  return aKeys.every((key) => b[key] === true);
679
687
  }
680
688
  function getDirtyFields(form) {
681
- let cache = dirtyFieldsCaches.get(form);
682
- if (!cache) {
683
- cache = { version: 0, result: computeDirtyFields(form) };
684
- dirtyFieldsCaches.set(form, cache);
685
- } else if (cache.version > 0) {
689
+ const cache = getOrCreate(dirtyFieldsCaches, form, () => ({
690
+ version: 0,
691
+ result: computeDirtyFields(form)
692
+ }));
693
+ if (cache.version > 0) {
686
694
  const result = computeDirtyFields(form);
687
695
  if (!sameDirtyKeys(cache.result, result)) cache.result = result;
688
696
  cache.version = 0;
@@ -705,21 +713,18 @@
705
713
  return touched.has(path.key);
706
714
  }
707
715
  function getTouchedFields({ touched }) {
708
- return Array.from(
709
- touched,
710
- (key) => JSON.parse(key).join(".")
711
- );
716
+ return Array.from(touched, (key) => segmentsFromKey(key).join("."));
712
717
  }
713
718
  function isTouched({ touched }) {
714
719
  return touched.size > 0;
715
720
  }
716
721
 
717
722
  function getValues(form) {
718
- let cache = valuesCaches.get(form);
719
- if (!cache) {
720
- cache = { version: 0, result: computeValues(form) };
721
- valuesCaches.set(form, cache);
722
- } else if (cache.version > 0) {
723
+ const cache = getOrCreate(valuesCaches, form, () => ({
724
+ version: 0,
725
+ result: computeValues(form)
726
+ }));
727
+ if (cache.version > 0) {
723
728
  cache.result = computeValues(form);
724
729
  cache.version = 0;
725
730
  }
@@ -730,10 +735,10 @@
730
735
  const owned = /* @__PURE__ */ new Set();
731
736
  let merged = parsedValues ?? initialValues;
732
737
  for (const [key, value] of values) {
733
- merged = setOwned(merged, JSON.parse(key), value, owned);
738
+ merged = setOwned(merged, segmentsFromKey(key), value, owned);
734
739
  }
735
740
  for (const key of deleted) {
736
- merged = unset(merged, JSON.parse(key));
741
+ merged = unset(merged, segmentsFromKey(key));
737
742
  }
738
743
  return merged;
739
744
  }
@@ -857,9 +862,9 @@
857
862
  function collectValueLeaves(node, segments, out) {
858
863
  if (node !== null && typeof node === "object") {
859
864
  if (Array.isArray(node)) {
860
- for (let i = 0; i < node.length; i++) {
861
- collectValueLeaves(node[i], [...segments, i], out);
862
- }
865
+ node.forEach(
866
+ (item, i) => collectValueLeaves(item, [...segments, i], out)
867
+ );
863
868
  return;
864
869
  }
865
870
  const keys = Object.keys(node);
@@ -872,18 +877,22 @@
872
877
  }
873
878
  out.push({ segments, value: node });
874
879
  }
875
- function reset(form, initialValues, options) {
876
- const keptValues = [];
880
+ function collectKeptValues(form, options) {
881
+ const kept = [];
877
882
  if (options?.keepValues) {
878
- collectValueLeaves(getValues(form), [], keptValues);
883
+ collectValueLeaves(getValues(form), [], kept);
879
884
  } else if (options?.keepDirtyValues) {
880
885
  for (const [key, value] of form.values) {
881
- const segments = JSON.parse(key);
886
+ const segments = segmentsFromKey(key);
882
887
  if (getDirtyBaseline(form, key, segments) !== value) {
883
- keptValues.push({ segments, value });
888
+ kept.push({ segments, value });
884
889
  }
885
890
  }
886
891
  }
892
+ return kept;
893
+ }
894
+ function reset(form, initialValues, options) {
895
+ const keptValues = collectKeptValues(form, options);
887
896
  form.initialValues = options?.keepDefaultValues ? form.initialValues : initialValues ?? form.initialValues;
888
897
  form.parsedValues = void 0;
889
898
  if (!options?.keepErrors) clearErrors(form);
@@ -944,13 +953,17 @@
944
953
  validating.add(path.key);
945
954
  emit(emitter, "validating", path);
946
955
  }
956
+ function asErrorList(output) {
957
+ if (output === void 0) return null;
958
+ const list = Array.isArray(output) ? output : [output];
959
+ return list.length ? list : null;
960
+ }
947
961
  function registerValidatorByPath(form, path, registration) {
948
962
  let timer = null;
949
963
  let controller = null;
950
964
  let marked = false;
951
965
  let lock = null;
952
966
  let errorSource = null;
953
- const hasErrors2 = (errors) => errors !== void 0 && !(Array.isArray(errors) && errors.length === 0);
954
967
  const mark = () => {
955
968
  if (marked) return;
956
969
  marked = true;
@@ -964,44 +977,42 @@
964
977
  const runSync = () => {
965
978
  const sync = registration.sync();
966
979
  if (!sync) return false;
967
- const errors = sync(getValueByPath(form, path), { form, path });
968
- if (!hasErrors2(errors)) {
980
+ const gate = asErrorList(sync(getValueByPath(form, path), { form, path }));
981
+ if (!gate) {
969
982
  if (!registration.validate() || errorSource === "sync") {
970
983
  setErrorByPath(form, path, void 0);
971
984
  errorSource = null;
972
985
  }
973
986
  return false;
974
987
  }
975
- setErrorByPath(form, path, errors);
988
+ setErrorByPath(form, path, gate);
976
989
  errorSource = "sync";
977
990
  return true;
978
991
  };
979
992
  const collectSyncErrors = () => {
980
993
  const sync = registration.sync();
981
994
  if (!sync) return null;
982
- const errors = sync(getValueByPath(form, path), { form, path });
983
- if (errors === void 0) return null;
984
- const list = Array.isArray(errors) ? errors : [errors];
985
- return list.length ? list : null;
995
+ return asErrorList(sync(getValueByPath(form, path), { form, path }));
986
996
  };
987
997
  const land = (result) => {
988
998
  if (registration.asyncAlways?.()) {
989
999
  const gate = collectSyncErrors();
990
- const own = result === void 0 ? [] : Array.isArray(result) ? result : [result];
1000
+ const own = asErrorList(result) ?? [];
991
1001
  setErrorByPath(form, path, [...gate ?? [], ...own]);
992
- errorSource = hasErrors2(result) ? "validator" : gate ? "sync" : null;
1002
+ errorSource = own.length ? "validator" : gate ? "sync" : null;
993
1003
  } else {
994
1004
  setErrorByPath(form, path, result);
995
- errorSource = hasErrors2(result) ? "validator" : null;
1005
+ errorSource = asErrorList(result) ? "validator" : null;
996
1006
  }
997
1007
  };
998
- const supersede = () => {
1008
+ const cancel = () => {
999
1009
  if (timer !== null) {
1000
1010
  clearTimeout(timer);
1001
1011
  timer = null;
1002
1012
  }
1003
1013
  controller?.abort();
1004
1014
  lock = {};
1015
+ unmark();
1005
1016
  };
1006
1017
  const runValidator = () => {
1007
1018
  const fn = registration.validate();
@@ -1029,13 +1040,11 @@
1029
1040
  return;
1030
1041
  }
1031
1042
  mark();
1032
- result.then(
1033
- (error) => {
1034
- if (lock === round) {
1035
- land(error);
1036
- }
1043
+ result.then((error) => {
1044
+ if (lock === round) {
1045
+ land(error);
1037
1046
  }
1038
- ).catch(() => {
1047
+ }).catch(() => {
1039
1048
  }).finally(() => {
1040
1049
  if (lock === round) {
1041
1050
  unmark();
@@ -1043,21 +1052,18 @@
1043
1052
  }
1044
1053
  });
1045
1054
  };
1055
+ const gateOwnsKick = () => {
1056
+ if (!(runSync() && !registration.asyncAlways?.())) return false;
1057
+ cancel();
1058
+ return true;
1059
+ };
1046
1060
  const run = () => {
1047
1061
  timer = null;
1048
- if (runSync() && !registration.asyncAlways?.()) {
1049
- supersede();
1050
- unmark();
1051
- return;
1052
- }
1062
+ if (gateOwnsKick()) return;
1053
1063
  runValidator();
1054
1064
  };
1055
1065
  const kick = () => {
1056
- if (runSync() && !registration.asyncAlways?.()) {
1057
- supersede();
1058
- unmark();
1059
- return;
1060
- }
1066
+ if (gateOwnsKick()) return;
1061
1067
  if (!registration.validate()) return;
1062
1068
  const debounce = registration.debounce();
1063
1069
  if (debounce > 0) {
@@ -1071,12 +1077,7 @@
1071
1077
  form.validators.set(path.key, kick);
1072
1078
  return () => {
1073
1079
  form.validators.delete(path.key);
1074
- if (timer !== null) {
1075
- clearTimeout(timer);
1076
- timer = null;
1077
- }
1078
- unmark();
1079
- controller?.abort();
1080
+ cancel();
1080
1081
  };
1081
1082
  }
1082
1083
  async function trigger(form, name, options) {
@@ -1086,29 +1087,27 @@
1086
1087
  () => keys2 === void 0 ? fieldsSettled(form) : keys2.every((key) => !form.validating.has(key)),
1087
1088
  () => false
1088
1089
  );
1090
+ const focusFirstError = (keys2) => {
1091
+ const firstKey = keys2 === void 0 ? form.errors.keys().next().value : keys2.find((key) => form.errors.has(key));
1092
+ if (firstKey !== void 0) emit(form.emitter, "focusError", firstKey);
1093
+ };
1089
1094
  if (name === void 0) {
1090
1095
  form.validators.forEach((validator) => validator());
1091
1096
  await settle();
1092
1097
  if (form.validate) await runFormValidate(form);
1093
1098
  if (options?.shouldTouch) touchKeys(form, [...form.validators.keys()]);
1094
- if (options?.shouldFocus) {
1095
- const firstKey = form.errors.keys().next().value;
1096
- if (firstKey !== void 0) emit(form.emitter, "focusError", firstKey);
1097
- }
1099
+ if (options?.shouldFocus) focusFirstError();
1098
1100
  return !hasErrors(form);
1099
1101
  }
1100
1102
  const keys = typeof name === "string" || isSegmentsPath(name) ? [create$1(name).key] : name.map((one) => create$1(one).key);
1101
1103
  keys.forEach((key) => form.validators.get(key)?.());
1102
1104
  await settle(keys);
1103
1105
  if (options?.shouldTouch) touchKeys(form, keys);
1104
- if (options?.shouldFocus) {
1105
- const firstKey = keys.find((key) => form.errors.has(key));
1106
- if (firstKey !== void 0) emit(form.emitter, "focusError", firstKey);
1107
- }
1106
+ if (options?.shouldFocus) focusFirstError(keys);
1108
1107
  return keys.every((key) => !form.errors.has(key));
1109
1108
  }
1110
1109
  function touchKeys(form, keys) {
1111
- keys.forEach((key) => setTouchedByPath(form, create$1(JSON.parse(key))));
1110
+ keys.forEach((key) => setTouchedByPath(form, create$1(segmentsFromKey(key))));
1112
1111
  }
1113
1112
  function setFormErrors(form, result, segments = [], footprint) {
1114
1113
  Object.entries(result).forEach(([key, value]) => {
@@ -1117,16 +1116,9 @@
1117
1116
  ...isIndex(key) ? [key] : normalizePath(key)
1118
1117
  ];
1119
1118
  if (typeof value === "string") {
1120
- if (value) {
1121
- setError(form, path, value);
1122
- recordFootprint(form, path, footprint);
1123
- }
1124
- } else if (Array.isArray(value)) {
1125
- setError(form, path, value);
1126
- recordFootprint(form, path, footprint);
1127
- } else if (isFieldError(value)) {
1128
- setError(form, path, value);
1129
- recordFootprint(form, path, footprint);
1119
+ if (value) landLeafError(form, path, value, footprint);
1120
+ } else if (Array.isArray(value) || isFieldError(value)) {
1121
+ landLeafError(form, path, value, footprint);
1130
1122
  } else if (value && typeof value === "object") {
1131
1123
  setFormErrors(form, value, path, footprint);
1132
1124
  }
@@ -1138,6 +1130,10 @@
1138
1130
  const stored = form.errors.get(path.key);
1139
1131
  if (stored) footprint.set(path.key, stored);
1140
1132
  }
1133
+ function landLeafError(form, path, error, footprint) {
1134
+ setError(form, path, error);
1135
+ recordFootprint(form, path, footprint);
1136
+ }
1141
1137
  function applyValidateResult(form, result) {
1142
1138
  const footprint = form.validateDeps || form.validateMode !== "onSubmit" ? getFormErrorFootprint(form) : void 0;
1143
1139
  if (footprint) {
@@ -1155,12 +1151,7 @@
1155
1151
  }
1156
1152
  const formErrorFootprints = /* @__PURE__ */ new WeakMap();
1157
1153
  function getFormErrorFootprint(form) {
1158
- let footprint = formErrorFootprints.get(form);
1159
- if (!footprint) {
1160
- footprint = /* @__PURE__ */ new Map();
1161
- formErrorFootprints.set(form, footprint);
1162
- }
1163
- return footprint;
1154
+ return getOrCreate(formErrorFootprints, form, () => /* @__PURE__ */ new Map());
1164
1155
  }
1165
1156
  function hasFormValidateErrors(form) {
1166
1157
  const footprint = formErrorFootprints.get(form);
@@ -1176,7 +1167,7 @@
1176
1167
  if (stored !== written) continue;
1177
1168
  form.errors.delete(key);
1178
1169
  bumpErrorsVersion(form);
1179
- emit(form.emitter, "errors", create$1(JSON.parse(key)));
1170
+ emit(form.emitter, "errors", create$1(segmentsFromKey(key)));
1180
1171
  }
1181
1172
  }
1182
1173
  const FORM_VALIDATING_KEY = "__form_validate__";
@@ -1189,18 +1180,13 @@
1189
1180
  const SETTLED = /* @__PURE__ */ Symbol("form-validate-settled");
1190
1181
  const formValidateStates = /* @__PURE__ */ new WeakMap();
1191
1182
  function getFormValidateState(form) {
1192
- let state = formValidateStates.get(form);
1193
- if (!state) {
1194
- state = {
1195
- timer: null,
1196
- controller: null,
1197
- round: null,
1198
- marked: false,
1199
- waiters: []
1200
- };
1201
- formValidateStates.set(form, state);
1202
- }
1203
- return state;
1183
+ return getOrCreate(formValidateStates, form, () => ({
1184
+ timer: null,
1185
+ controller: null,
1186
+ round: null,
1187
+ marked: false,
1188
+ waiters: []
1189
+ }));
1204
1190
  }
1205
1191
  function runFormValidate(form) {
1206
1192
  const validate2 = form.validate;
@@ -1238,14 +1224,9 @@
1238
1224
  if (!validate2) return Promise.resolve();
1239
1225
  state.controller?.abort();
1240
1226
  const controller = state.controller = new AbortController();
1241
- let outcome;
1242
- try {
1243
- outcome = Promise.resolve(
1244
- validate2(getValues(form), { form, signal: controller.signal })
1245
- );
1246
- } catch (error) {
1247
- outcome = Promise.reject(error);
1248
- }
1227
+ const outcome = new Promise(
1228
+ (resolve) => resolve(validate2(getValues(form), { form, signal: controller.signal }))
1229
+ );
1249
1230
  return outcome.then(
1250
1231
  (result) => {
1251
1232
  if (state.round === round) applyValidateResult(form, result);
@@ -1271,27 +1252,29 @@
1271
1252
  else waiter.reject(outcome);
1272
1253
  }
1273
1254
  }
1255
+ function shouldKick(mode, cadence, touched, hasError, reValidateMode) {
1256
+ return mode === cadence || mode === "all" || mode === "onTouched" && touched || reValidateMode === cadence && hasError();
1257
+ }
1274
1258
  function revalidateFormOnChange(form, path, mode) {
1275
1259
  if (!form.validateDeps?.has(path.key) || !form.validate) return;
1276
- if (mode === "onChange" || mode === "all" || mode === "onTouched" && hasTouchedByPath(form, path) || form.reValidateMode === "onChange" && hasFormValidateErrors(form)) {
1260
+ if (shouldKick(
1261
+ mode,
1262
+ "onChange",
1263
+ hasTouchedByPath(form, path),
1264
+ () => hasFormValidateErrors(form),
1265
+ form.reValidateMode
1266
+ )) {
1277
1267
  runFormValidate(form).catch(() => {
1278
1268
  });
1279
1269
  }
1280
1270
  }
1281
1271
  const fieldValidateDeps = /* @__PURE__ */ new WeakMap();
1282
1272
  function registerFieldValidateDeps(form, key, depKeys) {
1283
- let deps = fieldValidateDeps.get(form);
1284
- if (!deps) {
1285
- deps = /* @__PURE__ */ new Map();
1286
- fieldValidateDeps.set(form, deps);
1287
- }
1273
+ const deps = getOrCreate(fieldValidateDeps, form, () => /* @__PURE__ */ new Map());
1288
1274
  for (const depKey of depKeys) {
1289
- let dependents = deps.get(depKey);
1290
- if (!dependents) {
1291
- dependents = /* @__PURE__ */ new Set();
1292
- deps.set(depKey, dependents);
1293
- }
1275
+ const dependents = deps.get(depKey) ?? /* @__PURE__ */ new Set();
1294
1276
  dependents.add(key);
1277
+ deps.set(depKey, dependents);
1295
1278
  }
1296
1279
  }
1297
1280
  function unregisterFieldValidateDeps(form, key, depKeys) {
@@ -1308,7 +1291,13 @@
1308
1291
  if (!dependents?.size) return;
1309
1292
  for (const dependent of dependents) {
1310
1293
  if (dependent === path.key) continue;
1311
- if (mode === "onChange" || mode === "all" || mode === "onTouched" && hasTouchedByPath(form, path) || form.reValidateMode === "onChange" && form.errors.has(dependent)) {
1294
+ if (shouldKick(
1295
+ mode,
1296
+ "onChange",
1297
+ hasTouchedByPath(form, path),
1298
+ () => form.errors.has(dependent),
1299
+ form.reValidateMode
1300
+ )) {
1312
1301
  form.validators.get(dependent)?.();
1313
1302
  }
1314
1303
  }
@@ -1350,11 +1339,7 @@
1350
1339
  }
1351
1340
  }
1352
1341
  function registerFieldMode(form, path, mode) {
1353
- let modes = fieldModes.get(form);
1354
- if (!modes) {
1355
- modes = /* @__PURE__ */ new Map();
1356
- fieldModes.set(form, modes);
1357
- }
1342
+ const modes = getOrCreate(fieldModes, form, () => /* @__PURE__ */ new Map());
1358
1343
  const displaced = modes.has(path.key);
1359
1344
  const token = {};
1360
1345
  modes.set(path.key, { mode, token });
@@ -1365,14 +1350,38 @@
1365
1350
  const entry = modes?.get(path.key);
1366
1351
  if (modes && entry && entry.token === token) modes.delete(path.key);
1367
1352
  }
1368
- function runUserChangeGate(form, path, mode) {
1369
- if (mode === "onChange" || mode === "all" || mode === "onTouched" && hasTouchedByPath(form, path) || getFieldErrorsByPath(form, path).length > 0 && form.reValidateMode === "onChange")
1353
+ function firstMountedDescendantKey(form, ancestor) {
1354
+ const prefix = `${ancestor.key.slice(0, -1)},`;
1355
+ for (const key of fieldModes.get(form)?.keys() ?? []) {
1356
+ if (key.startsWith(prefix)) return key;
1357
+ }
1358
+ return void 0;
1359
+ }
1360
+ function fireFormValidate(form, cadence) {
1361
+ if (form.validateMode !== cadence || !form.validate) return;
1362
+ runFormValidate(form).catch(() => {
1363
+ });
1364
+ }
1365
+ function kickFieldValidator(form, path, mode, cadence, touched) {
1366
+ if (shouldKick(
1367
+ mode,
1368
+ cadence,
1369
+ touched,
1370
+ () => getFieldErrorsByPath(form, path).length > 0,
1371
+ form.reValidateMode
1372
+ ))
1370
1373
  form.validators.get(path.key)?.();
1374
+ }
1375
+ function runUserChangeGate(form, path, mode) {
1376
+ kickFieldValidator(
1377
+ form,
1378
+ path,
1379
+ mode,
1380
+ "onChange",
1381
+ hasTouchedByPath(form, path)
1382
+ );
1371
1383
  revalidateFormOnChange(form, path, mode);
1372
- if (form.validateMode === "onChange" && form.validate) {
1373
- runFormValidate(form).catch(() => {
1374
- });
1375
- }
1384
+ fireFormValidate(form, "onChange");
1376
1385
  revalidateDependentsOnChange(form, path, mode);
1377
1386
  }
1378
1387
  function userChangeByPath(form, path, value) {
@@ -1384,13 +1393,8 @@
1384
1393
  setTouchedByPath(form, path);
1385
1394
  const entry = fieldModes.get(form)?.get(path.key);
1386
1395
  if (!entry) return;
1387
- const mode = entry.mode ?? form.mode;
1388
- if (mode === "onBlur" || mode === "onTouched" || mode === "all" || getFieldErrorsByPath(form, path).length > 0 && form.reValidateMode === "onBlur")
1389
- form.validators.get(path.key)?.();
1390
- if (form.validateMode === "onBlur" && form.validate) {
1391
- runFormValidate(form).catch(() => {
1392
- });
1393
- }
1396
+ kickFieldValidator(form, path, entry.mode ?? form.mode, "onBlur", true);
1397
+ fireFormValidate(form, "onBlur");
1394
1398
  }
1395
1399
 
1396
1400
  function removeFieldForUnmount(form, path) {
@@ -1438,50 +1442,49 @@
1438
1442
  return "Invalid value";
1439
1443
  }
1440
1444
  }
1441
- function rulesToValidator(rules) {
1445
+ function resolveMessage(entry, bound) {
1446
+ return typeof entry === "function" ? entry(bound) : entry?.replace("{bound}", String(bound));
1447
+ }
1448
+ function numericMetric(value) {
1449
+ const n = Number(value);
1450
+ return Number.isNaN(n) ? void 0 : n;
1451
+ }
1452
+ function sizedMetric(value) {
1453
+ return typeof value === "string" || Array.isArray(value) ? value.length : void 0;
1454
+ }
1455
+ const BOUND_CHECKS = [
1456
+ ["min", numericMetric, (metric, bound) => metric < bound],
1457
+ ["max", numericMetric, (metric, bound) => metric > bound],
1458
+ ["minLength", sizedMetric, (metric, bound) => metric < bound],
1459
+ ["maxLength", sizedMetric, (metric, bound) => metric > bound]
1460
+ ];
1461
+ function rulesToValidator(rules, formMessages) {
1442
1462
  return (value, meta) => {
1443
1463
  if (rules.required) {
1444
1464
  if (value === "" || value === void 0 || value === null || Array.isArray(value) && value.length === 0) {
1445
1465
  return [
1446
1466
  {
1447
1467
  type: "required",
1448
- message: typeof rules.required === "string" ? rules.required : defaultMessage("required")
1468
+ message: typeof rules.required === "string" ? rules.required : resolveMessage(formMessages?.required) ?? defaultMessage("required")
1449
1469
  }
1450
1470
  ];
1451
1471
  }
1452
1472
  }
1453
1473
  const errors = [];
1454
- const message = (type, bound) => rules.messages?.[type] ?? defaultMessage(type, bound);
1455
- if (rules.min !== void 0) {
1456
- const n = Number(value);
1457
- if (!Number.isNaN(n) && n < rules.min) {
1458
- errors.push({ type: "min", message: message("min", rules.min) });
1459
- }
1460
- }
1461
- if (rules.max !== void 0) {
1462
- const n = Number(value);
1463
- if (!Number.isNaN(n) && n > rules.max) {
1464
- errors.push({ type: "max", message: message("max", rules.max) });
1474
+ const message = (type, bound) => rules.messages?.[type] ?? resolveMessage(formMessages?.[type], bound) ?? defaultMessage(type, bound);
1475
+ for (const [type, measure, fails] of BOUND_CHECKS) {
1476
+ const bound = rules[type];
1477
+ if (bound === void 0) continue;
1478
+ const metric = measure(value);
1479
+ if (metric !== void 0 && fails(metric, bound)) {
1480
+ errors.push({ type, message: message(type, bound) });
1465
1481
  }
1466
1482
  }
1467
- const isSized = typeof value === "string" || Array.isArray(value);
1468
- if (rules.minLength !== void 0 && isSized && value.length < rules.minLength) {
1469
- errors.push({
1470
- type: "minLength",
1471
- message: message("minLength", rules.minLength)
1472
- });
1473
- }
1474
- if (rules.maxLength !== void 0 && isSized && value.length > rules.maxLength) {
1475
- errors.push({
1476
- type: "maxLength",
1477
- message: message("maxLength", rules.maxLength)
1478
- });
1479
- }
1480
1483
  if (rules.pattern && !rules.pattern.value.test(value)) {
1481
1484
  errors.push({
1482
1485
  type: "pattern",
1483
1486
  // pattern.message is type-required but JS consumers may omit it.
1484
- message: rules.messages?.pattern ?? rules.pattern.message ?? defaultMessage("pattern")
1487
+ message: rules.messages?.pattern ?? rules.pattern.message ?? resolveMessage(formMessages?.pattern) ?? defaultMessage("pattern")
1485
1488
  });
1486
1489
  }
1487
1490
  if (rules.validate !== void 0) {
@@ -1502,10 +1505,9 @@
1502
1505
  function rulesToConstraintAttrs(rules) {
1503
1506
  const attrs = {};
1504
1507
  if (rules.required) attrs.required = true;
1505
- if (rules.min !== void 0) attrs.min = rules.min;
1506
- if (rules.max !== void 0) attrs.max = rules.max;
1507
- if (rules.minLength !== void 0) attrs.minLength = rules.minLength;
1508
- if (rules.maxLength !== void 0) attrs.maxLength = rules.maxLength;
1508
+ for (const key of ["min", "max", "minLength", "maxLength"]) {
1509
+ if (rules[key] !== void 0) attrs[key] = rules[key];
1510
+ }
1509
1511
  if (rules.pattern) attrs.pattern = rules.pattern.value.source;
1510
1512
  return attrs;
1511
1513
  }
@@ -1557,12 +1559,12 @@
1557
1559
  }
1558
1560
  modeToken = registerFieldMode(form, path, mode).token;
1559
1561
  if (rules && hasRuleConstraints(rules)) {
1560
- const sync = rules.required !== void 0 ? rulesToValidator({ required: rules.required }) : void 0;
1561
- const rest = rules.required !== void 0 ? { ...rules, required: void 0 } : rules;
1562
+ const { required, ...rest } = rules;
1563
+ const requiredGate = required !== void 0 ? rulesToValidator({ required }, form.messages) : void 0;
1562
1564
  disposeValidator = registerValidatorByPath(form, path, {
1563
- validate: () => hasRuleConstraints(rest) ? rulesToValidator(rest) : void 0,
1565
+ validate: () => hasRuleConstraints(rest) ? rulesToValidator(rest, form.messages) : void 0,
1564
1566
  debounce: () => 0,
1565
- sync: () => sync
1567
+ sync: () => requiredGate
1566
1568
  });
1567
1569
  }
1568
1570
  offFocus = on(
@@ -1742,9 +1744,7 @@
1742
1744
  }
1743
1745
  function getNativeErrors(formEl) {
1744
1746
  const errors = [];
1745
- const { elements } = formEl;
1746
- for (let i = 0; i < elements.length; i++) {
1747
- const el = elements[i];
1747
+ for (const el of formEl.elements) {
1748
1748
  if (el.name && typeof el.checkValidity === "function" && !el.checkValidity()) {
1749
1749
  errors.push({
1750
1750
  path: nameToPath(el.name),
@@ -1769,6 +1769,7 @@
1769
1769
  e.preventDefault();
1770
1770
  }
1771
1771
  const formEl = e?.currentTarget;
1772
+ if (form.isSubmitting) return;
1772
1773
  clearServerErrors(form);
1773
1774
  form.isSubmitted = true;
1774
1775
  setIsSubmitting(form, true);
@@ -1823,11 +1824,30 @@
1823
1824
  else emit(form.emitter, "focusError", key);
1824
1825
  }
1825
1826
 
1827
+ function seedInitialValues(form, emitter, source) {
1828
+ if (!isPromise(source)) {
1829
+ form.initialValues = source;
1830
+ return;
1831
+ }
1832
+ form.isLoading = true;
1833
+ emit(emitter, "loading");
1834
+ Promise.resolve(source).then(
1835
+ (resolved) => {
1836
+ setInitialValues(form, resolved ?? {});
1837
+ form.isLoading = false;
1838
+ emit(emitter, "loading");
1839
+ },
1840
+ (error) => {
1841
+ form.isLoading = false;
1842
+ emit(emitter, "loading");
1843
+ }
1844
+ );
1845
+ }
1826
1846
  function create(options) {
1827
1847
  const emitter = create$2();
1828
1848
  _setMaxListeners(emitter, 0);
1829
- let source = options?.initialValues ?? {};
1830
- if (typeof source === "function") source = source();
1849
+ const initialSource = options?.initialValues ?? {};
1850
+ const source = typeof initialSource === "function" ? initialSource() : initialSource;
1831
1851
  const validateOption = options?.validate;
1832
1852
  const wrappedValidate = validateOption && hasStandardProps(validateOption) ? schemaToFormValidator(
1833
1853
  validateOption
@@ -1860,23 +1880,7 @@
1860
1880
  status: void 0,
1861
1881
  register: (name, registerOptions) => registerField(form, name, registerOptions)
1862
1882
  };
1863
- if (isPromise(source)) {
1864
- form.isLoading = true;
1865
- emit(emitter, "loading");
1866
- Promise.resolve(source).then(
1867
- (resolved) => {
1868
- setInitialValues(form, resolved ?? {});
1869
- form.isLoading = false;
1870
- emit(emitter, "loading");
1871
- },
1872
- (error) => {
1873
- form.isLoading = false;
1874
- emit(emitter, "loading");
1875
- }
1876
- );
1877
- } else {
1878
- form.initialValues = source;
1879
- }
1883
+ seedInitialValues(form, emitter, source);
1880
1884
  return form;
1881
1885
  }
1882
1886
 
@@ -1891,11 +1895,7 @@
1891
1895
  const fieldDisabledRegistry = /* @__PURE__ */ new WeakMap();
1892
1896
  function registerFieldDisabled(form, path, disabled) {
1893
1897
  if (disabled === void 0) return null;
1894
- let registry = fieldDisabledRegistry.get(form);
1895
- if (!registry) {
1896
- registry = /* @__PURE__ */ new Map();
1897
- fieldDisabledRegistry.set(form, registry);
1898
- }
1898
+ const registry = getOrCreate(fieldDisabledRegistry, form, () => /* @__PURE__ */ new Map());
1899
1899
  const token = {};
1900
1900
  registry.set(path.key, { disabled, token });
1901
1901
  emit(form.emitter, "disabled", path);
@@ -1985,6 +1985,26 @@
1985
1985
  };
1986
1986
  }
1987
1987
 
1988
+ function pathFromKey(key) {
1989
+ return create$1(segmentsFromKey(key));
1990
+ }
1991
+ function usePath(name) {
1992
+ const key = React.useMemo(() => create$1(normalizePath(name)).key, [name]);
1993
+ return React.useMemo(() => pathFromKey(key), [key]);
1994
+ }
1995
+
1996
+ function seedIfChanged(form, seeded, source, resetOptions) {
1997
+ if (seeded.done && (seeded.source === source || isEqual(seeded.source, source))) {
1998
+ return;
1999
+ }
2000
+ seeded.done = true;
2001
+ seeded.source = source;
2002
+ if (resetOptions !== void 0) {
2003
+ reset(form, source, resetOptions);
2004
+ } else {
2005
+ setInitialValues(form, source);
2006
+ }
2007
+ }
1988
2008
  function useForm(options) {
1989
2009
  const [form] = React.useState(() => {
1990
2010
  const created = create(options);
@@ -1995,6 +2015,7 @@
1995
2015
  });
1996
2016
  const initialValues = options && options.initialValues;
1997
2017
  const values = options && options.values;
2018
+ const resetOptions = options && options.resetOptions;
1998
2019
  const seededRef = React.useRef(null);
1999
2020
  if (seededRef.current === null)
2000
2021
  seededRef.current = { done: false, source: void 0 };
@@ -2003,28 +2024,15 @@
2003
2024
  if (typeof initialValues === "function" || isPromise(initialValues)) {
2004
2025
  return;
2005
2026
  }
2006
- const seeded = seededRef.current;
2007
- if (seeded.done && (seeded.source === initialValues || isEqual(seeded.source, initialValues))) {
2008
- return;
2009
- }
2010
- seeded.done = true;
2011
- seeded.source = initialValues;
2012
- setInitialValues(form, initialValues);
2027
+ seedIfChanged(form, seededRef.current, initialValues);
2013
2028
  }, [form, initialValues]);
2014
2029
  const controlledRef = React.useRef(null);
2015
- if (controlledRef.current === null) {
2030
+ if (controlledRef.current === null)
2016
2031
  controlledRef.current = { done: false, source: void 0 };
2017
- }
2018
2032
  React.useEffect(() => {
2019
2033
  if (values === void 0) return;
2020
- const seeded = controlledRef.current;
2021
- if (seeded.done && (seeded.source === values || isEqual(seeded.source, values))) {
2022
- return;
2023
- }
2024
- seeded.done = true;
2025
- seeded.source = values;
2026
- setInitialValues(form, values);
2027
- }, [form, values]);
2034
+ seedIfChanged(form, controlledRef.current, values, resetOptions);
2035
+ }, [form, values, resetOptions]);
2028
2036
  React.useEffect(() => {
2029
2037
  if (!form.validateOnMount || !form.validate) return;
2030
2038
  const run = () => {
@@ -2092,13 +2100,7 @@
2092
2100
  const { key } = path;
2093
2101
  const scope = options?.exact === false ? "branch" : "leaf";
2094
2102
  const subscribeFactory = React.useCallback(
2095
- (invalidate) => onPathEvent(
2096
- emitter,
2097
- "change",
2098
- create$1(JSON.parse(key)),
2099
- scope,
2100
- invalidate
2101
- ),
2103
+ (invalidate) => onPathEvent(emitter, "change", pathFromKey(key), scope, invalidate),
2102
2104
  [emitter, key, scope]
2103
2105
  );
2104
2106
  return useWatchCore(subscribeFactory, () => {
@@ -2115,15 +2117,20 @@
2115
2117
  function useTouched(form, name) {
2116
2118
  return useTouchedByPath(form, create$1(name));
2117
2119
  }
2118
- function useTouchedByPath(form, path) {
2120
+ function useKeyWatch(form, path, event, getter) {
2119
2121
  const { emitter } = form;
2120
2122
  const { key } = path;
2121
2123
  const subscribeFactory = React.useCallback(
2122
- (invalidate) => onKeyEvent(emitter, "touched", key, invalidate),
2123
- [emitter, key]
2124
+ (invalidate) => onKeyEvent(emitter, event, key, invalidate),
2125
+ [emitter, event, key]
2124
2126
  );
2125
- return useWatchCore(
2126
- subscribeFactory,
2127
+ return useWatchCore(subscribeFactory, getter);
2128
+ }
2129
+ function useTouchedByPath(form, path) {
2130
+ return useKeyWatch(
2131
+ form,
2132
+ path,
2133
+ "touched",
2127
2134
  hasTouchedByPath.bind(null, form, path)
2128
2135
  );
2129
2136
  }
@@ -2131,26 +2138,21 @@
2131
2138
  return useErrorByPath(form, create$1(name))?.message;
2132
2139
  }
2133
2140
  function useErrorByPath(form, path) {
2134
- const { emitter } = form;
2135
- const { key } = path;
2136
- const subscribeFactory = React.useCallback(
2137
- (invalidate) => onKeyEvent(emitter, "errors", key, invalidate),
2138
- [emitter, key]
2141
+ return useKeyWatch(
2142
+ form,
2143
+ path,
2144
+ "errors",
2145
+ getErrorByPath.bind(null, form, path)
2139
2146
  );
2140
- return useWatchCore(subscribeFactory, getErrorByPath.bind(null, form, path));
2141
2147
  }
2142
2148
  function useFieldErrors(form, name) {
2143
2149
  return useFieldErrorsByPath(form, create$1(name));
2144
2150
  }
2145
2151
  function useFieldErrorsByPath(form, path) {
2146
- const { emitter } = form;
2147
- const { key } = path;
2148
- const subscribeFactory = React.useCallback(
2149
- (invalidate) => onKeyEvent(emitter, "errors", key, invalidate),
2150
- [emitter, key]
2151
- );
2152
- return useWatchCore(
2153
- subscribeFactory,
2152
+ return useKeyWatch(
2153
+ form,
2154
+ path,
2155
+ "errors",
2154
2156
  getFieldErrorsByPath.bind(null, form, path)
2155
2157
  );
2156
2158
  }
@@ -2164,13 +2166,7 @@
2164
2166
  const { emitter } = form;
2165
2167
  const { key } = path;
2166
2168
  const subscribeFactory = React.useCallback(
2167
- (invalidate) => onPathEvent(
2168
- emitter,
2169
- "change",
2170
- create$1(JSON.parse(key)),
2171
- "leaf",
2172
- invalidate
2173
- ),
2169
+ (invalidate) => onPathEvent(emitter, "change", pathFromKey(key), "leaf", invalidate),
2174
2170
  [emitter, key]
2175
2171
  );
2176
2172
  return useWatchCore(subscribeFactory, () => isFieldDirtyByPath(form, path));
@@ -2192,14 +2188,21 @@
2192
2188
  "disabled",
2193
2189
  "loading"
2194
2190
  ];
2191
+ function subscribeFormStateEvents(emitter, invalidate) {
2192
+ const offs = FORM_STATE_EVENTS.map((event) => on(emitter, event, invalidate));
2193
+ return () => {
2194
+ for (const off of offs) off();
2195
+ };
2196
+ }
2195
2197
  function getFormState(form) {
2198
+ const anyErrors = hasErrors(form);
2196
2199
  return {
2197
2200
  isDirty: isDirty(form),
2198
2201
  dirtyFields: getDirtyFields(form),
2199
2202
  isTouched: form.touched.size > 0,
2200
2203
  touchedFields: getTouchedFields(form),
2201
- hasErrors: hasErrors(form),
2202
- isValid: !hasErrors(form),
2204
+ hasErrors: anyErrors,
2205
+ isValid: !anyErrors,
2203
2206
  errors: getErrorsRecord(form),
2204
2207
  isSubmitting: form.isSubmitting,
2205
2208
  isSubmitted: form.isSubmitted,
@@ -2217,28 +2220,14 @@
2217
2220
  function useFormState(form) {
2218
2221
  const getter = React.useCallback(() => getFormState(form), [form]);
2219
2222
  const subscribeFactory = React.useCallback(
2220
- (invalidate) => {
2221
- const offs = FORM_STATE_EVENTS.map(
2222
- (event) => on(form.emitter, event, invalidate)
2223
- );
2224
- return () => {
2225
- for (const off of offs) off();
2226
- };
2227
- },
2223
+ (invalidate) => subscribeFormStateEvents(form.emitter, invalidate),
2228
2224
  [form.emitter]
2229
2225
  );
2230
2226
  return useWatchCore(subscribeFactory, getter, isSameFormState);
2231
2227
  }
2232
2228
  function useStore(form, selector, isEqual2) {
2233
2229
  const subscribeFactory = React.useCallback(
2234
- (invalidate) => {
2235
- const offs = FORM_STATE_EVENTS.map(
2236
- (event) => on(form.emitter, event, invalidate)
2237
- );
2238
- return () => {
2239
- for (const off of offs) off();
2240
- };
2241
- },
2230
+ (invalidate) => subscribeFormStateEvents(form.emitter, invalidate),
2242
2231
  [form.emitter]
2243
2232
  );
2244
2233
  return useWatchCore(subscribeFactory, selector, isEqual2);
@@ -2302,11 +2291,6 @@
2302
2291
  return useFieldErrorsByPath(form, create$1(FORM_ERROR));
2303
2292
  }
2304
2293
 
2305
- function usePath(name) {
2306
- const key = React.useMemo(() => create$1(normalizePath(name)).key, [name]);
2307
- return React.useMemo(() => create$1(JSON.parse(key)), [key]);
2308
- }
2309
-
2310
2294
  function useStage(value) {
2311
2295
  const ref = React.useRef(value);
2312
2296
  ref.current = value;
@@ -2336,24 +2320,34 @@
2336
2320
  };
2337
2321
  }, [removedRef, teardownRef, restoreRef]);
2338
2322
  }
2323
+ function useUnmountFieldRemoval(form, path, shouldUnregister) {
2324
+ const removalSnapshotRef = React.useRef(null);
2325
+ const teardownOnUnmount = useStageFn(() => {
2326
+ if ((shouldUnregister ?? form.shouldUnregister) === false) return;
2327
+ removalSnapshotRef.current = removeFieldForUnmount(form, path);
2328
+ });
2329
+ const restoreAfterStrictMode = useStageFn(() => {
2330
+ const snapshot = removalSnapshotRef.current;
2331
+ removalSnapshotRef.current = null;
2332
+ if (snapshot) restoreRemovedField(form, path, snapshot);
2333
+ });
2334
+ useUnmountRestore(teardownOnUnmount, restoreAfterStrictMode);
2335
+ }
2339
2336
 
2337
+ function schemaAsValidator(validate) {
2338
+ return validate && hasStandardProps(validate) ? schemaToFieldValidator(validate) : validate;
2339
+ }
2340
2340
  function useValidate(validate, path, formProp, options) {
2341
2341
  const contextForm = React.useContext(FormContext);
2342
2342
  const form = formProp || contextForm;
2343
2343
  if (!form) throw new Error("no form provided");
2344
- const validateOption = validate;
2345
- const validateRef = React.useRef(void 0);
2346
- validateRef.current = validateOption && hasStandardProps(validateOption) ? schemaToFieldValidator(validateOption) : validateOption;
2347
- const debounceRef = React.useRef(options?.debounce ?? 0);
2348
- debounceRef.current = options?.debounce ?? 0;
2349
- const syncRef = React.useRef(options?.sync);
2350
- syncRef.current = options?.sync;
2351
- const asyncAlwaysRef = React.useRef(options?.asyncAlways ?? false);
2352
- asyncAlwaysRef.current = options?.asyncAlways ?? false;
2353
- const validateOnMountRef = React.useRef(options?.validateOnMount);
2354
- validateOnMountRef.current = options?.validateOnMount;
2344
+ const validateRef = useStage(schemaAsValidator(validate));
2345
+ const debounceRef = useStage(options?.debounce ?? 0);
2346
+ const syncRef = useStage(options?.sync);
2347
+ const asyncAlwaysRef = useStage(options?.asyncAlways ?? false);
2348
+ const validateOnMountRef = useStage(options?.validateOnMount);
2355
2349
  React.useEffect(() => {
2356
- const spath = create$1(JSON.parse(path.key));
2350
+ const spath = pathFromKey(path.key);
2357
2351
  const dispose = registerValidatorByPath(form, spath, {
2358
2352
  validate: () => validateRef.current,
2359
2353
  debounce: () => debounceRef.current,
@@ -2381,14 +2375,40 @@
2381
2375
  disposed = true;
2382
2376
  dispose();
2383
2377
  };
2384
- }, [form, path.key]);
2378
+ }, [
2379
+ form,
2380
+ path.key,
2381
+ debounceRef,
2382
+ syncRef,
2383
+ asyncAlwaysRef,
2384
+ validateRef,
2385
+ validateOnMountRef
2386
+ ]);
2385
2387
  return useStageFn(() => form.validators.get(path.key)?.());
2386
2388
  }
2387
2389
 
2388
2390
  const uncontrolledSyncRegistry = /* @__PURE__ */ new WeakMap();
2389
- function combineRulesAndValidate(rules, validate) {
2391
+ function getUncontrolledSyncEntry(form) {
2392
+ const existing = uncontrolledSyncRegistry.get(form);
2393
+ if (existing) return existing;
2394
+ const cells = /* @__PURE__ */ new Map();
2395
+ const off = on(form.emitter, "change", (changed) => {
2396
+ if (changed) return;
2397
+ for (const read of cells.values()) {
2398
+ const { el, path } = read();
2399
+ if (!el || el.type === "file") continue;
2400
+ const next = getValueByPath(form, path);
2401
+ const asString = next == null ? "" : String(next);
2402
+ if (el.value !== asString) el.value = asString;
2403
+ }
2404
+ });
2405
+ const entry = { cells, off };
2406
+ uncontrolledSyncRegistry.set(form, entry);
2407
+ return entry;
2408
+ }
2409
+ function combineRulesAndValidate(rules, validate, formMessages) {
2390
2410
  if (!rules || !hasRuleConstraints(rules)) return validate;
2391
- const ruleValidator = rulesToValidator(rules);
2411
+ const ruleValidator = rulesToValidator(rules, formMessages);
2392
2412
  if (!validate) return ruleValidator;
2393
2413
  return (value, meta) => {
2394
2414
  const ruleErrors = ruleValidator(value, meta);
@@ -2409,7 +2429,7 @@
2409
2429
  const timerRef = React.useRef(null);
2410
2430
  const subscribeFactory = React.useCallback(
2411
2431
  (invalidate) => {
2412
- const spath = create$1(JSON.parse(path.key));
2432
+ const spath = pathFromKey(path.key);
2413
2433
  const clearTimer = () => {
2414
2434
  if (timerRef.current !== null) {
2415
2435
  clearTimeout(timerRef.current);
@@ -2511,14 +2531,18 @@
2511
2531
  emitChangeByPath(form, path);
2512
2532
  });
2513
2533
  const restRules = rules ? { ...rules, required: void 0 } : void 0;
2514
- const validateOption = validate;
2515
- const validateWrapped = validateOption && hasStandardProps(validateOption) ? schemaToFieldValidator(validateOption) : validateOption;
2516
- useValidate(combineRulesAndValidate(restRules, validateWrapped), path, form, {
2517
- debounce: validateDebounce,
2518
- validateOnMount,
2519
- asyncAlways: asyncAlways ?? form.asyncAlways,
2520
- sync: rules && rules.required !== void 0 ? rulesToValidator({ required: rules.required }) : void 0
2521
- });
2534
+ const validateWrapped = schemaAsValidator(validate);
2535
+ useValidate(
2536
+ combineRulesAndValidate(restRules, validateWrapped, form.messages),
2537
+ path,
2538
+ form,
2539
+ {
2540
+ debounce: validateDebounce,
2541
+ validateOnMount,
2542
+ asyncAlways: asyncAlways ?? form.asyncAlways,
2543
+ sync: rules && rules.required !== void 0 ? rulesToValidator({ required: rules.required }, form.messages) : void 0
2544
+ }
2545
+ );
2522
2546
  const errors = useDelayedErrors(form, path, delayError);
2523
2547
  const errorObject = errors[0];
2524
2548
  const error = errorObject?.message;
@@ -2529,7 +2553,7 @@
2529
2553
  } : onPathEvent(
2530
2554
  form.emitter,
2531
2555
  "change",
2532
- create$1(JSON.parse(path.key)),
2556
+ pathFromKey(path.key),
2533
2557
  "leaf",
2534
2558
  invalidate
2535
2559
  ),
@@ -2549,7 +2573,7 @@
2549
2573
  (invalidate) => onPathEvent(
2550
2574
  form.emitter,
2551
2575
  "disabled",
2552
- create$1(JSON.parse(path.key)),
2576
+ pathFromKey(path.key),
2553
2577
  "branch",
2554
2578
  invalidate
2555
2579
  ),
@@ -2559,7 +2583,7 @@
2559
2583
  );
2560
2584
  const disabledTokenRef = React.useRef(null);
2561
2585
  React.useEffect(() => {
2562
- const spath = create$1(JSON.parse(path.key));
2586
+ const spath = pathFromKey(path.key);
2563
2587
  const registration = registerFieldDisabled(form, spath, disabled);
2564
2588
  if (registration) disabledTokenRef.current = registration.token;
2565
2589
  return () => {
@@ -2572,7 +2596,7 @@
2572
2596
  const onChange = useStageFn((v) => userChangeByPath(form, path, v));
2573
2597
  const onBlur = useStageFn(() => userBlur(form, path));
2574
2598
  React.useEffect(() => {
2575
- const spath = create$1(JSON.parse(path.key));
2599
+ const spath = pathFromKey(path.key);
2576
2600
  const { token, displaced } = registerFieldMode(form, spath, modeOption);
2577
2601
  return () => unregisterFieldMode(form, spath, token);
2578
2602
  }, [form, path.key, modeOption]);
@@ -2605,23 +2629,8 @@
2605
2629
  [form, path.key]
2606
2630
  );
2607
2631
  React.useEffect(() => {
2608
- const spath = create$1(JSON.parse(path.key));
2609
- let entry = uncontrolledSyncRegistry.get(form);
2610
- if (!entry) {
2611
- const cells = /* @__PURE__ */ new Map();
2612
- const off = on(form.emitter, "change", (changed) => {
2613
- if (changed) return;
2614
- for (const read of cells.values()) {
2615
- const { el, path: path2 } = read();
2616
- if (!el || el.type === "file") continue;
2617
- const next = getValueByPath(form, path2);
2618
- const asString = next == null ? "" : String(next);
2619
- if (el.value !== asString) el.value = asString;
2620
- }
2621
- });
2622
- entry = { cells, off };
2623
- uncontrolledSyncRegistry.set(form, entry);
2624
- }
2632
+ const spath = pathFromKey(path.key);
2633
+ const entry = getUncontrolledSyncEntry(form);
2625
2634
  entry.cells.set(path.key, () => ({ el: elementRef.current, path: spath }));
2626
2635
  return () => {
2627
2636
  entry.cells.delete(path.key);
@@ -2631,27 +2640,28 @@
2631
2640
  }
2632
2641
  };
2633
2642
  }, [form, path.key, uncontrolled]);
2634
- const removalSnapshotRef = React.useRef(null);
2635
- const teardownOnUnmount = useStageFn(() => {
2636
- if ((shouldUnregister ?? form.shouldUnregister) === false) return;
2637
- removalSnapshotRef.current = removeFieldForUnmount(form, path);
2638
- });
2639
- const restoreAfterStrictMode = useStageFn(() => {
2640
- const snapshot = removalSnapshotRef.current;
2641
- removalSnapshotRef.current = null;
2642
- if (snapshot) restoreRemovedField(form, path, snapshot);
2643
+ useUnmountFieldRemoval(form, path, shouldUnregister);
2644
+ const extract = eventToValueOrDefault(eventToValue, {
2645
+ valueAsNumber,
2646
+ valueAsDate
2643
2647
  });
2644
- useUnmountRestore(teardownOnUnmount, restoreAfterStrictMode);
2645
- const extract = eventToValue ?? ((e) => extractEventValue(e, { valueAsNumber, valueAsDate }));
2646
2648
  const inputProps = {
2647
2649
  name: path.key,
2648
2650
  ref: focusRef,
2649
2651
  onChange: (e) => onChange(extract(e)),
2650
2652
  onBlur,
2651
- disabled: mergedDisabled,
2652
- ...type === "checkbox" ? { checked: !!value } : type === "file" ? {} : uncontrolled ? { defaultValue: value } : { value: value ?? "" },
2653
- ...error ? { "aria-invalid": true, "aria-describedby": errorIdFromKey(path.key) } : {}
2653
+ disabled: mergedDisabled
2654
2654
  };
2655
+ if (type === "checkbox") {
2656
+ inputProps.checked = !!value;
2657
+ } else if (type !== "file") {
2658
+ if (uncontrolled) inputProps.defaultValue = value;
2659
+ else inputProps.value = value ?? "";
2660
+ }
2661
+ if (error) {
2662
+ inputProps["aria-invalid"] = true;
2663
+ inputProps["aria-describedby"] = errorIdFromKey(path.key);
2664
+ }
2655
2665
  return {
2656
2666
  form,
2657
2667
  value,
@@ -2683,12 +2693,18 @@
2683
2693
  return arrayIdsRegistry.get(form)?.get(key);
2684
2694
  }
2685
2695
  function registerArrayIds(form, key, ids) {
2686
- let registry = arrayIdsRegistry.get(form);
2687
- if (!registry) {
2688
- registry = /* @__PURE__ */ new Map();
2689
- arrayIdsRegistry.set(form, registry);
2690
- }
2696
+ const registry = arrayIdsRegistry.get(form) ?? /* @__PURE__ */ new Map();
2691
2697
  registry.set(key, ids);
2698
+ arrayIdsRegistry.set(form, registry);
2699
+ }
2700
+ function scheduleArrayRowFocus(form, path, index, options) {
2701
+ const { focus } = options ?? {};
2702
+ if (focus === void 0) return;
2703
+ setTimeout(() => {
2704
+ const segments = [...path.value, index];
2705
+ const key = typeof focus === "string" ? create$1([...segments, focus]).key : firstMountedDescendantKey(form, create$1(segments));
2706
+ if (key !== void 0) emit(form.emitter, "focusError", key);
2707
+ }, 0);
2692
2708
  }
2693
2709
  function bumpReducer(count) {
2694
2710
  return count + 1;
@@ -2701,12 +2717,8 @@
2701
2717
  const { rules, shouldUnregister } = options;
2702
2718
  const path = usePath(options.name);
2703
2719
  const idsRef = React.useRef([]);
2704
- const getArray = React.useCallback(
2705
- () => getValueByPath(form, path) || [],
2706
- [form, path]
2707
- );
2708
2720
  const computeFields = React.useCallback(() => {
2709
- const arr = getArray();
2721
+ const arr = getValueByPath(form, path) || [];
2710
2722
  while (idsRef.current.length < arr.length) {
2711
2723
  idsRef.current.push(generateId(form));
2712
2724
  }
@@ -2718,7 +2730,7 @@
2718
2730
  index,
2719
2731
  [keyName]: id
2720
2732
  }));
2721
- }, [getArray, form, keyName]);
2733
+ }, [form, path, keyName]);
2722
2734
  const [fields, syncFields] = React.useReducer(
2723
2735
  computeFields,
2724
2736
  void 0,
@@ -2730,43 +2742,43 @@
2730
2742
  );
2731
2743
  registerArrayIds(form, path.key, idsRef.current);
2732
2744
  React.useEffect(() => {
2733
- registerArrayIds(form, path.key, idsRef.current);
2745
+ const ids = idsRef.current;
2746
+ registerArrayIds(form, path.key, ids);
2734
2747
  return () => {
2735
2748
  const registry = arrayIdsRegistry.get(form);
2736
- if (registry?.get(path.key) === idsRef.current) registry.delete(path.key);
2749
+ if (registry?.get(path.key) === ids) registry.delete(path.key);
2737
2750
  };
2738
2751
  }, [form, path.key]);
2739
- const removalSnapshotRef = React.useRef(null);
2740
- const teardownOnUnmount = useStageFn(() => {
2741
- if ((shouldUnregister ?? form.shouldUnregister) === false) return;
2742
- removalSnapshotRef.current = removeFieldForUnmount(form, path);
2743
- });
2744
- const restoreAfterStrictMode = useStageFn(() => {
2745
- const snapshot = removalSnapshotRef.current;
2746
- removalSnapshotRef.current = null;
2747
- if (snapshot) restoreRemovedField(form, path, snapshot);
2748
- });
2749
- useUnmountRestore(teardownOnUnmount, restoreAfterStrictMode);
2752
+ useUnmountFieldRemoval(form, path, shouldUnregister);
2750
2753
  useValidate(
2751
- rules ? rulesToValidator({ ...rules, required: void 0 }) : void 0,
2754
+ rules ? rulesToValidator({ ...rules, required: void 0 }, form.messages) : void 0,
2752
2755
  path,
2753
2756
  form,
2754
2757
  {
2755
- sync: rules && rules.required !== void 0 ? rulesToValidator({ required: rules.required }) : void 0
2758
+ sync: rules?.required !== void 0 ? rulesToValidator({ required: rules.required }, form.messages) : void 0
2759
+ }
2760
+ );
2761
+ const append = useStageFn(
2762
+ (value, options2) => {
2763
+ idsRef.current.push(generateId(form));
2764
+ appendValueByPath(form, path, value);
2765
+ scheduleArrayRowFocus(form, path, idsRef.current.length - 1, options2);
2766
+ }
2767
+ );
2768
+ const prepend = useStageFn(
2769
+ (value, options2) => {
2770
+ idsRef.current.unshift(generateId(form));
2771
+ prependValueByPath(form, path, value);
2772
+ scheduleArrayRowFocus(form, path, 0, options2);
2773
+ }
2774
+ );
2775
+ const insert = useStageFn(
2776
+ (index, value, options2) => {
2777
+ if (!insertValueByPath(form, path, index, value)) return;
2778
+ idsRef.current.splice(index, 0, generateId(form));
2779
+ scheduleArrayRowFocus(form, path, index, options2);
2756
2780
  }
2757
2781
  );
2758
- const append = useStageFn((value) => {
2759
- idsRef.current.push(generateId(form));
2760
- appendValueByPath(form, path, value);
2761
- });
2762
- const prepend = useStageFn((value) => {
2763
- idsRef.current.unshift(generateId(form));
2764
- prependValueByPath(form, path, value);
2765
- });
2766
- const insert = useStageFn((index, value) => {
2767
- if (!insertValueByPath(form, path, index, value)) return;
2768
- idsRef.current.splice(index, 0, generateId(form));
2769
- });
2770
2782
  const remove = useStageFn((indices) => {
2771
2783
  const dropped = removeValueByPath(form, path, indices);
2772
2784
  for (const index of dropped) idsRef.current.splice(index, 1);
@@ -2906,7 +2918,7 @@
2906
2918
  (invalidate) => onPathEvent(
2907
2919
  form.emitter,
2908
2920
  "change",
2909
- create$1(JSON.parse(path.key)),
2921
+ pathFromKey(path.key),
2910
2922
  "leaf",
2911
2923
  invalidate
2912
2924
  ),
@@ -2916,10 +2928,8 @@
2916
2928
  const value = toDisplay ? toDisplay(raw) : raw;
2917
2929
  const timerRef = React.useRef(null);
2918
2930
  const seqRef = React.useRef(0);
2919
- const fromDisplayRef = React.useRef(fromDisplay);
2920
- fromDisplayRef.current = fromDisplay;
2921
- const debounceRef = React.useRef(asyncDebounceMs ?? 0);
2922
- debounceRef.current = asyncDebounceMs ?? 0;
2931
+ const fromDisplayRef = useStage(fromDisplay);
2932
+ const debounceRef = useStage(asyncDebounceMs ?? 0);
2923
2933
  React.useEffect(
2924
2934
  () => () => {
2925
2935
  if (timerRef.current !== null) clearTimeout(timerRef.current);
@@ -2953,7 +2963,7 @@
2953
2963
  run();
2954
2964
  }
2955
2965
  },
2956
- [form, path]
2966
+ [form, path, fromDisplayRef, debounceRef]
2957
2967
  );
2958
2968
  return { value, onChange };
2959
2969
  }
@@ -3054,6 +3064,9 @@
3054
3064
  "aria-describedby": error ? [props["aria-describedby"], errorIdFromKey(fieldKey)].filter(Boolean).join(" ") : props["aria-describedby"]
3055
3065
  };
3056
3066
  }
3067
+ function toConstraintAttrs(rules) {
3068
+ return rules ? rulesToConstraintAttrs(rules) : void 0;
3069
+ }
3057
3070
  const Field = React__namespace.forwardRef(
3058
3071
  ({
3059
3072
  validate,
@@ -3082,8 +3095,8 @@
3082
3095
  const [nativeInvalidCount, setNativeInvalidCount] = React__namespace.useState(0);
3083
3096
  const contextForm = React__namespace.useContext(FormContext);
3084
3097
  const resolvedForm = formProp ?? contextForm;
3085
- const validateOption = validate;
3086
- const validateWrapped = validateOption && hasStandardProps(validateOption) ? schemaToFieldValidator(validateOption) : validateOption;
3098
+ const isFile = props.type === "file";
3099
+ const validateWrapped = validate && hasStandardProps(validate) ? schemaToFieldValidator(validate) : validate;
3087
3100
  const {
3088
3101
  value,
3089
3102
  onChange,
@@ -3091,7 +3104,8 @@
3091
3104
  error,
3092
3105
  name: fieldKey,
3093
3106
  disabled: isDisabled,
3094
- focusRef
3107
+ focusRef,
3108
+ validating
3095
3109
  } = useField({
3096
3110
  name,
3097
3111
  form: formProp,
@@ -3104,9 +3118,8 @@
3104
3118
  disabled,
3105
3119
  asyncAlways,
3106
3120
  mode,
3107
- // File inputs cannot be value-controlled at all — force the
3108
- // uncontrolled path so no `value` prop ever reaches the element.
3109
- uncontrolled: uncontrolled || props.type === "file",
3121
+ // File inputs can't be value-controlled — force the uncontrolled path.
3122
+ uncontrolled: uncontrolled || isFile,
3110
3123
  validate: (...params) => {
3111
3124
  if (resolvedForm?.shouldUseNativeValidation !== false) {
3112
3125
  const el = innerRef.current;
@@ -3129,7 +3142,6 @@
3129
3142
  },
3130
3143
  [ref, focusRef]
3131
3144
  );
3132
- const Component = as || "input";
3133
3145
  React__namespace.useEffect(() => {
3134
3146
  const el = innerRef.current;
3135
3147
  if (!el || typeof el.setCustomValidity !== "function") return;
@@ -3143,11 +3155,14 @@
3143
3155
  React__namespace.useEffect(() => {
3144
3156
  if (nativeInvalidCount > 0) innerRef.current?.reportValidity();
3145
3157
  }, [nativeInvalidCount]);
3146
- const isFile = props.type === "file";
3147
- const toValue = eventToValue ?? ((e) => extractEventValue(e, { valueAsNumber, valueAsDate }));
3158
+ const Component = as || "input";
3159
+ const toValue = eventToValueOrDefault(eventToValue, {
3160
+ valueAsNumber,
3161
+ valueAsDate
3162
+ });
3148
3163
  const valueProps = valueToProps ? valueToProps(value) : isFile ? {} : uncontrolled ? { defaultValue: value } : { value };
3149
3164
  const errorId = errorIdFromKey(fieldKey);
3150
- const constraintAttrs = rules ? rulesToConstraintAttrs(rules) : void 0;
3165
+ const constraintAttrs = toConstraintAttrs(rules);
3151
3166
  return /* @__PURE__ */ React__namespace.createElement(React__namespace.Fragment, null, /* @__PURE__ */ React__namespace.createElement(
3152
3167
  Component,
3153
3168
  {
@@ -3159,6 +3174,7 @@
3159
3174
  ...valueProps,
3160
3175
  ...ariaProps(error, fieldKey, props),
3161
3176
  disabled: isDisabled,
3177
+ "aria-busy": validating || void 0,
3162
3178
  onChange: (e) => onChange(toValue(e)),
3163
3179
  ref: mergedRef
3164
3180
  }
@@ -3187,7 +3203,8 @@
3187
3203
  onBlur,
3188
3204
  error,
3189
3205
  name: fieldKey,
3190
- disabled: isDisabled
3206
+ disabled: isDisabled,
3207
+ validating
3191
3208
  } = useField({
3192
3209
  name,
3193
3210
  form,
@@ -3202,7 +3219,7 @@
3202
3219
  disabled,
3203
3220
  mode
3204
3221
  });
3205
- const constraintAttrs = rules ? rulesToConstraintAttrs(rules) : void 0;
3222
+ const constraintAttrs = toConstraintAttrs(rules);
3206
3223
  return /* @__PURE__ */ React__namespace.createElement(
3207
3224
  "input",
3208
3225
  {
@@ -3214,6 +3231,7 @@
3214
3231
  checked: !!value,
3215
3232
  ...ariaProps(error, fieldKey, props),
3216
3233
  disabled: isDisabled,
3234
+ "aria-busy": validating || void 0,
3217
3235
  onChange: (e) => onChange(e.target.checked),
3218
3236
  ref
3219
3237
  }
@@ -3248,7 +3266,8 @@
3248
3266
  onBlur,
3249
3267
  error,
3250
3268
  name: fieldKey,
3251
- disabled: isDisabled
3269
+ disabled: isDisabled,
3270
+ validating
3252
3271
  } = useField({
3253
3272
  name,
3254
3273
  form,
@@ -3263,7 +3282,7 @@
3263
3282
  disabled,
3264
3283
  mode
3265
3284
  });
3266
- const constraintAttrs = rules ? rulesToConstraintAttrs(rules) : void 0;
3285
+ const constraintAttrs = toConstraintAttrs(rules);
3267
3286
  return /* @__PURE__ */ React__namespace.createElement(
3268
3287
  "select",
3269
3288
  {
@@ -3275,6 +3294,7 @@
3275
3294
  value: toSelectValue(multiple, value),
3276
3295
  ...ariaProps(error, fieldKey, props),
3277
3296
  disabled: isDisabled,
3297
+ "aria-busy": validating || void 0,
3278
3298
  onChange: (e) => onChange(
3279
3299
  multiple ? Array.from(e.target.selectedOptions, (option) => option.value) : e.target.value
3280
3300
  ),
@@ -3285,9 +3305,74 @@
3285
3305
  }
3286
3306
  );
3287
3307
 
3308
+ function ErrorSummary({
3309
+ form: formProp,
3310
+ context,
3311
+ heading
3312
+ }) {
3313
+ const contextForm = React__namespace.useContext(context ?? FormContext);
3314
+ const form = formProp ?? contextForm;
3315
+ if (!form) throw new Error("no form provided");
3316
+ const containerRef = React__namespace.useRef(null);
3317
+ const wasSubmittingRef = React__namespace.useRef(false);
3318
+ const headingId = React__namespace.useId();
3319
+ const errors = useErrors(form);
3320
+ const subscribeFactory = React__namespace.useCallback(
3321
+ (invalidate) => {
3322
+ const check = () => {
3323
+ const wasSubmitting = wasSubmittingRef.current;
3324
+ wasSubmittingRef.current = form.isSubmitting;
3325
+ if (wasSubmitting && !form.isSubmitting && hasErrors(form)) {
3326
+ containerRef.current?.focus();
3327
+ }
3328
+ invalidate();
3329
+ };
3330
+ const offErrors = on(form.emitter, "errors", check);
3331
+ const offSubmitting = on(form.emitter, "submitting", check);
3332
+ return () => {
3333
+ offErrors();
3334
+ offSubmitting();
3335
+ };
3336
+ },
3337
+ [form]
3338
+ );
3339
+ useWatchCore(subscribeFactory, () => null);
3340
+ const entries = Object.entries(errors);
3341
+ if (entries.length === 0) return null;
3342
+ return /* @__PURE__ */ React__namespace.createElement(
3343
+ "div",
3344
+ {
3345
+ role: "alert",
3346
+ tabIndex: -1,
3347
+ ref: containerRef,
3348
+ "aria-labelledby": headingId
3349
+ },
3350
+ /* @__PURE__ */ React__namespace.createElement("h2", { id: headingId }, heading ?? "There is a problem"),
3351
+ /* @__PURE__ */ React__namespace.createElement("ul", null, entries.map(([dottedKey, list]) => {
3352
+ const message = list[0]?.message;
3353
+ if (dottedKey === FORM_ERROR) {
3354
+ return /* @__PURE__ */ React__namespace.createElement("li", { key: dottedKey }, /* @__PURE__ */ React__namespace.createElement("p", null, message));
3355
+ }
3356
+ const path = dottedKeyToFieldPath(dottedKey);
3357
+ return /* @__PURE__ */ React__namespace.createElement("li", { key: dottedKey }, /* @__PURE__ */ React__namespace.createElement(
3358
+ "a",
3359
+ {
3360
+ href: `#${fieldErrorId(path)}`,
3361
+ onClick: (e) => {
3362
+ e.preventDefault();
3363
+ setFocus(form, path);
3364
+ }
3365
+ },
3366
+ message
3367
+ ));
3368
+ }))
3369
+ );
3370
+ }
3371
+
3288
3372
  exports.Checkbox = Checkbox;
3289
3373
  exports.CheckboxGroupContext = CheckboxGroupContext;
3290
3374
  exports.CheckboxGroupProvider = CheckboxGroupProvider;
3375
+ exports.ErrorSummary = ErrorSummary;
3291
3376
  exports.FORM_ERROR = FORM_ERROR;
3292
3377
  exports.Field = Field;
3293
3378
  exports.Form = Form;
@@ -3309,6 +3394,7 @@
3309
3394
  exports.ensureValidate = ensureValidate;
3310
3395
  exports.fieldErrorId = fieldErrorId;
3311
3396
  exports.fieldPathToDottedKey = fieldPathToDottedKey;
3397
+ exports.firstMountedDescendantKey = firstMountedDescendantKey;
3312
3398
  exports.getDirtyFields = getDirtyFields;
3313
3399
  exports.getError = getError;
3314
3400
  exports.getErrorByPath = getErrorByPath;
@@ -3367,6 +3453,7 @@
3367
3453
  exports.setValidatingByPath = setValidatingByPath;
3368
3454
  exports.setValue = setValue;
3369
3455
  exports.setValueByPath = setValueByPath;
3456
+ exports.shouldKick = shouldKick;
3370
3457
  exports.subscribe = subscribe;
3371
3458
  exports.swapValues = swapValues;
3372
3459
  exports.swapValuesByPath = swapValuesByPath;