@vuehookform/core 0.4.1 → 0.4.2

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.
@@ -2,6 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let vue = require("vue");
3
3
  var pathCache = /* @__PURE__ */ new Map();
4
4
  var PATH_CACHE_MAX_SIZE = 256;
5
+ var MAX_ARRAY_INDEX = 1e4;
5
6
  function getPathSegments(path) {
6
7
  let segments = pathCache.get(path);
7
8
  if (segments) return segments;
@@ -13,6 +14,9 @@ function getPathSegments(path) {
13
14
  pathCache.set(path, segments);
14
15
  return segments;
15
16
  }
17
+ function clearPathCache() {
18
+ pathCache.clear();
19
+ }
16
20
  function get(obj, path) {
17
21
  if (!path || obj === null || obj === void 0) return obj;
18
22
  const keys = getPathSegments(path);
@@ -32,6 +36,13 @@ function set(obj, path, value) {
32
36
  "prototype"
33
37
  ];
34
38
  if (keys.some((k) => UNSAFE_KEYS.includes(k))) return;
39
+ for (const key of keys) if (/^\d+$/.test(key)) {
40
+ const index = parseInt(key, 10);
41
+ if (index > MAX_ARRAY_INDEX) {
42
+ if (typeof console !== "undefined" && console.warn) console.warn(`[vue-hook-form] set(): Array index ${index} exceeds maximum allowed (${MAX_ARRAY_INDEX}). Path "${path}" was not set to prevent memory exhaustion.`);
43
+ return;
44
+ }
45
+ }
35
46
  const lastKey = keys.pop();
36
47
  let current = obj;
37
48
  for (let i = 0; i < keys.length; i++) {
@@ -66,13 +77,22 @@ function generateId() {
66
77
  const random = Math.random().toString(36).substring(2, 11);
67
78
  return `field_${Date.now()}_${idCounter++}_${random}`;
68
79
  }
69
- function deepClone(obj) {
80
+ function deepClone(obj, seen) {
70
81
  if (obj === null || obj === void 0) return obj;
71
82
  if (typeof obj !== "object") return obj;
72
83
  if (obj instanceof Date) return new Date(obj.getTime());
73
- if (Array.isArray(obj)) return obj.map((item) => deepClone(item));
84
+ if (!seen) seen = /* @__PURE__ */ new Map();
85
+ const existingClone = seen.get(obj);
86
+ if (existingClone !== void 0) return existingClone;
87
+ if (Array.isArray(obj)) {
88
+ const clonedArray = [];
89
+ seen.set(obj, clonedArray);
90
+ for (const item of obj) clonedArray.push(deepClone(item, seen));
91
+ return clonedArray;
92
+ }
74
93
  const cloned = {};
75
- for (const key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) cloned[key] = deepClone(obj[key]);
94
+ seen.set(obj, cloned);
95
+ for (const key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) cloned[key] = deepClone(obj[key], seen);
76
96
  return cloned;
77
97
  }
78
98
  var proc = globalThis.process;
@@ -230,8 +250,8 @@ function createFormContext(options) {
230
250
  if (isAsyncDefaults) {
231
251
  const asyncFn = options.defaultValues;
232
252
  asyncFn().then((values) => {
233
- Object.assign(defaultValues, values);
234
- Object.assign(formData, values);
253
+ Object.assign(defaultValues, deepClone(values));
254
+ Object.assign(formData, deepClone(values));
235
255
  isLoading.value = false;
236
256
  }).catch((error) => {
237
257
  console.error("Failed to load async default values:", error);
@@ -240,8 +260,8 @@ function createFormContext(options) {
240
260
  options.onDefaultValuesError?.(error);
241
261
  });
242
262
  } else if (options.defaultValues) {
243
- Object.assign(defaultValues, options.defaultValues);
244
- Object.assign(formData, defaultValues);
263
+ Object.assign(defaultValues, deepClone(options.defaultValues));
264
+ Object.assign(formData, deepClone(options.defaultValues));
245
265
  }
246
266
  const errors = (0, vue.shallowRef)({});
247
267
  const touchedFields = (0, vue.shallowRef)({});
@@ -261,23 +281,24 @@ function createFormContext(options) {
261
281
  const debounceTimers = /* @__PURE__ */ new Map();
262
282
  const validationRequestIds = /* @__PURE__ */ new Map();
263
283
  const resetGeneration = (0, vue.ref)(0);
264
- const dirtyFieldCount = (0, vue.ref)(0);
265
- const touchedFieldCount = (0, vue.ref)(0);
266
284
  const validationCache = /* @__PURE__ */ new Map();
267
285
  const schemaValidationTimers = /* @__PURE__ */ new Map();
286
+ const persistentErrorFields = /* @__PURE__ */ new Set();
287
+ const defaultValueHashes = /* @__PURE__ */ new Map();
268
288
  const isDisabled = (0, vue.ref)(false);
289
+ const watchStopHandles = [];
269
290
  if (options.disabled !== void 0) {
270
291
  isDisabled.value = (0, vue.toValue)(options.disabled) ?? false;
271
- (0, vue.watch)(() => (0, vue.toValue)(options.disabled), (newDisabled) => {
292
+ watchStopHandles.push((0, vue.watch)(() => (0, vue.toValue)(options.disabled), (newDisabled) => {
272
293
  isDisabled.value = newDisabled ?? false;
273
- });
294
+ }));
274
295
  }
275
296
  if (options.values !== void 0) {
276
297
  const initialValues = (0, vue.toValue)(options.values);
277
298
  if (initialValues && !isAsyncDefaults) {
278
299
  for (const [key, value] of Object.entries(initialValues)) if (value !== void 0) set(formData, key, value);
279
300
  }
280
- (0, vue.watch)(() => (0, vue.toValue)(options.values), (newValues) => {
301
+ watchStopHandles.push((0, vue.watch)(() => (0, vue.toValue)(options.values), (newValues) => {
281
302
  if (newValues) {
282
303
  for (const [key, value] of Object.entries(newValues)) if (value !== void 0) {
283
304
  set(formData, key, value);
@@ -290,14 +311,14 @@ function createFormContext(options) {
290
311
  }
291
312
  }
292
313
  }
293
- }, { deep: true });
314
+ }, { deep: true }));
294
315
  }
295
316
  if (options.errors !== void 0) {
296
317
  const initialErrors = (0, vue.toValue)(options.errors);
297
318
  if (initialErrors) externalErrors.value = initialErrors;
298
- (0, vue.watch)(() => (0, vue.toValue)(options.errors), (newErrors) => {
319
+ watchStopHandles.push((0, vue.watch)(() => (0, vue.toValue)(options.errors), (newErrors) => {
299
320
  externalErrors.value = newErrors || {};
300
- }, { deep: true });
321
+ }, { deep: true }));
301
322
  }
302
323
  return {
303
324
  formData,
@@ -322,25 +343,40 @@ function createFormContext(options) {
322
343
  validationRequestIds,
323
344
  resetGeneration,
324
345
  isDisabled,
325
- dirtyFieldCount,
326
- touchedFieldCount,
327
346
  validationCache,
328
347
  schemaValidationTimers,
329
- options
348
+ persistentErrorFields,
349
+ defaultValueHashes,
350
+ options,
351
+ cleanup: () => {
352
+ for (const stop of watchStopHandles) stop();
353
+ }
330
354
  };
331
355
  }
332
356
  var uniqueIdCounter = 0;
357
+ var circularRefMap = /* @__PURE__ */ new WeakMap();
333
358
  function hashValue(value) {
334
359
  if (value === null) return "null";
335
360
  if (value === void 0) return "undefined";
336
361
  const type = typeof value;
337
362
  if (type === "string") return `s:${value}`;
338
- if (type === "number") return `n:${value}`;
363
+ if (type === "number") {
364
+ const num = value;
365
+ if (Number.isNaN(num)) return "n:NaN";
366
+ if (num === 0) return "n:0";
367
+ if (!Number.isFinite(num)) return `n:${num > 0 ? "Infinity" : "-Infinity"}`;
368
+ return `n:${num}`;
369
+ }
339
370
  if (type === "boolean") return `b:${value}`;
340
371
  if (type === "object") try {
341
372
  return `o:${JSON.stringify(value)}`;
342
373
  } catch {
343
- return `o:_${++uniqueIdCounter}`;
374
+ let id = circularRefMap.get(value);
375
+ if (!id) {
376
+ id = String(++uniqueIdCounter);
377
+ circularRefMap.set(value, id);
378
+ }
379
+ return `o:_${id}`;
344
380
  }
345
381
  return `x:_${++uniqueIdCounter}`;
346
382
  }
@@ -375,7 +411,7 @@ function extractSubSchema(schema, path) {
375
411
  let currentSchema = schema;
376
412
  let hasEffects = false;
377
413
  for (const segment of segments) {
378
- if (!segment) continue;
414
+ if (!segment) return null;
379
415
  if (hasChecks(currentSchema)) hasEffects = true;
380
416
  const unwrapped = unwrapNonEffects(currentSchema);
381
417
  if (hasChecks(unwrapped)) hasEffects = true;
@@ -442,10 +478,54 @@ function analyzeSchemaPath(schema, path) {
442
478
  cache.set(path, result);
443
479
  return result;
444
480
  }
445
- function clearFieldErrors$1(errors, fieldPath) {
446
- const newErrors = { ...errors };
447
- for (const key of Object.keys(newErrors)) if (key === fieldPath || key.startsWith(`${fieldPath}.`)) delete newErrors[key];
448
- return newErrors;
481
+ function markFieldTouched(touchedFields, fieldName) {
482
+ if (touchedFields.value[fieldName]) return;
483
+ touchedFields.value = {
484
+ ...touchedFields.value,
485
+ [fieldName]: true
486
+ };
487
+ }
488
+ function clearFieldDirty(dirtyFields, fieldName) {
489
+ if (!(fieldName in dirtyFields.value)) return;
490
+ const newDirty = { ...dirtyFields.value };
491
+ delete newDirty[fieldName];
492
+ dirtyFields.value = newDirty;
493
+ }
494
+ function clearFieldTouched(touchedFields, fieldName) {
495
+ if (!(fieldName in touchedFields.value)) return;
496
+ const newTouched = { ...touchedFields.value };
497
+ delete newTouched[fieldName];
498
+ touchedFields.value = newTouched;
499
+ }
500
+ function clearFieldErrors(errors, fieldName) {
501
+ const currentErrors = errors.value;
502
+ const keys = Object.keys(currentErrors);
503
+ if (keys.length === 0) return;
504
+ const prefix = `${fieldName}.`;
505
+ const keysToDelete = [];
506
+ for (const key of keys) if (key === fieldName || key.startsWith(prefix)) keysToDelete.push(key);
507
+ if (keysToDelete.length === 0) return;
508
+ const newErrors = { ...currentErrors };
509
+ for (const key of keysToDelete) delete newErrors[key];
510
+ errors.value = newErrors;
511
+ }
512
+ function updateFieldDirtyState(dirtyFields, defaultValues, defaultValueHashes, fieldName, currentValue) {
513
+ let defaultHash = defaultValueHashes.get(fieldName);
514
+ if (defaultHash === void 0) {
515
+ defaultHash = hashValue(get(defaultValues, fieldName));
516
+ defaultValueHashes.set(fieldName, defaultHash);
517
+ }
518
+ const isDirty = hashValue(currentValue) !== defaultHash;
519
+ const wasDirty = dirtyFields.value[fieldName] === true;
520
+ if (isDirty && !wasDirty) dirtyFields.value = {
521
+ ...dirtyFields.value,
522
+ [fieldName]: true
523
+ };
524
+ else if (!isDirty && wasDirty) {
525
+ const newDirty = { ...dirtyFields.value };
526
+ delete newDirty[fieldName];
527
+ dirtyFields.value = newDirty;
528
+ }
449
529
  }
450
530
  function setValidating(ctx, fieldPath, isValidating) {
451
531
  const newSet = new Set(ctx.validatingFields.value);
@@ -539,37 +619,43 @@ function createValidation(ctx) {
539
619
  }
540
620
  ctx.pendingErrors.delete(fieldPath);
541
621
  applyNativeValidation(fieldPath, null);
542
- return clearFieldErrors$1(ctx.errors.value, fieldPath);
622
+ if (ctx.persistentErrorFields.has(fieldPath)) return;
623
+ clearFieldErrors(ctx.errors, fieldPath);
543
624
  }
544
625
  function clearAllPendingErrors() {
545
626
  for (const timer of ctx.errorDelayTimers.values()) clearTimeout(timer);
546
627
  ctx.errorDelayTimers.clear();
547
628
  ctx.pendingErrors.clear();
548
629
  }
549
- async function validateFieldPartial(fieldPath, subSchema, valueHash, criteriaMode, generationAtStart) {
630
+ async function validateFieldPartial(fieldPath, subSchema, valueHash, cacheKey, criteriaMode, generationAtStart) {
550
631
  const fieldValue = get(ctx.formData, fieldPath);
551
632
  setValidating(ctx, fieldPath, true);
552
633
  try {
553
634
  const result = await subSchema.safeParseAsync(fieldValue);
554
635
  if (ctx.resetGeneration.value !== generationAtStart) return true;
555
636
  if (result.success) {
556
- ctx.errors.value = cancelError(fieldPath);
557
- if (valueHash) ctx.validationCache.set(fieldPath, {
637
+ cancelError(fieldPath);
638
+ if (valueHash) ctx.validationCache.set(cacheKey, {
558
639
  hash: valueHash,
559
640
  isValid: true
560
641
  });
561
642
  return true;
562
643
  }
563
- const fieldErrors = result.error.issues.map((issue) => ({
564
- ...issue,
565
- path: fieldPath.split(".").concat(issue.path.map(String))
566
- }));
567
- ctx.errors.value = cancelError(fieldPath);
644
+ const fieldSegments = fieldPath.split(".");
645
+ const fieldErrors = result.error.issues.map((issue) => {
646
+ const issuePath = issue.path.map(String);
647
+ const alreadyPrefixed = issuePath.length >= fieldSegments.length && fieldSegments.every((seg, i) => issuePath[i] === seg);
648
+ return {
649
+ ...issue,
650
+ path: alreadyPrefixed ? issuePath : fieldSegments.concat(issuePath)
651
+ };
652
+ });
653
+ cancelError(fieldPath);
568
654
  const grouped = groupErrorsByPath(fieldErrors);
569
655
  const errorBatch = [];
570
656
  for (const [path, errors] of grouped) errorBatch.push([path, createFieldError(errors, criteriaMode)]);
571
657
  scheduleErrorsBatch(errorBatch);
572
- if (valueHash) ctx.validationCache.set(fieldPath, {
658
+ if (valueHash) ctx.validationCache.set(cacheKey, {
573
659
  hash: valueHash,
574
660
  isValid: false
575
661
  });
@@ -578,14 +664,14 @@ function createValidation(ctx) {
578
664
  setValidating(ctx, fieldPath, false);
579
665
  }
580
666
  }
581
- async function validateFieldFull(fieldPath, valueHash, criteriaMode, generationAtStart) {
667
+ async function validateFieldFull(fieldPath, valueHash, cacheKey, criteriaMode, generationAtStart) {
582
668
  setValidating(ctx, fieldPath, true);
583
669
  try {
584
670
  const result = await ctx.options.schema.safeParseAsync(ctx.formData);
585
671
  if (ctx.resetGeneration.value !== generationAtStart) return true;
586
672
  if (result.success) {
587
- ctx.errors.value = cancelError(fieldPath);
588
- if (valueHash) ctx.validationCache.set(fieldPath, {
673
+ cancelError(fieldPath);
674
+ if (valueHash) ctx.validationCache.set(cacheKey, {
589
675
  hash: valueHash,
590
676
  isValid: true
591
677
  });
@@ -596,19 +682,19 @@ function createValidation(ctx) {
596
682
  return path === fieldPath || path.startsWith(`${fieldPath}.`);
597
683
  });
598
684
  if (fieldErrors.length === 0) {
599
- ctx.errors.value = cancelError(fieldPath);
600
- if (valueHash) ctx.validationCache.set(fieldPath, {
685
+ cancelError(fieldPath);
686
+ if (valueHash) ctx.validationCache.set(cacheKey, {
601
687
  hash: valueHash,
602
688
  isValid: true
603
689
  });
604
690
  return true;
605
691
  }
606
- ctx.errors.value = cancelError(fieldPath);
692
+ cancelError(fieldPath);
607
693
  const grouped = groupErrorsByPath(fieldErrors);
608
694
  const errorBatch = [];
609
695
  for (const [path, errors] of grouped) errorBatch.push([path, createFieldError(errors, criteriaMode)]);
610
696
  scheduleErrorsBatch(errorBatch);
611
- if (valueHash) ctx.validationCache.set(fieldPath, {
697
+ if (valueHash) ctx.validationCache.set(cacheKey, {
612
698
  hash: valueHash,
613
699
  isValid: false
614
700
  });
@@ -623,14 +709,16 @@ function createValidation(ctx) {
623
709
  let valueHash;
624
710
  if (fieldPath) {
625
711
  valueHash = hashValue(get(ctx.formData, fieldPath));
626
- const cached = ctx.validationCache.get(fieldPath);
627
- if (cached && cached.hash === valueHash) {
628
- if (cached.isValid) ctx.errors.value = cancelError(fieldPath);
629
- return cached.isValid;
630
- }
631
712
  const analysis = analyzeSchemaPath(ctx.options.schema, fieldPath);
632
- if (analysis.canPartialValidate && analysis.subSchema) return validateFieldPartial(fieldPath, analysis.subSchema, valueHash, criteriaMode, generationAtStart);
633
- return validateFieldFull(fieldPath, valueHash, criteriaMode, generationAtStart);
713
+ const usePartial = analysis.canPartialValidate && analysis.subSchema;
714
+ const cacheKey = `${fieldPath}:${usePartial ? "partial" : "full"}`;
715
+ const cached = ctx.validationCache.get(cacheKey);
716
+ if (cached && cached.hash === valueHash && cached.isValid) {
717
+ cancelError(fieldPath);
718
+ return true;
719
+ }
720
+ if (usePartial) return validateFieldPartial(fieldPath, analysis.subSchema, valueHash, cacheKey, criteriaMode, generationAtStart);
721
+ return validateFieldFull(fieldPath, valueHash, cacheKey, criteriaMode, generationAtStart);
634
722
  }
635
723
  const validatingKey = "_form";
636
724
  setValidating(ctx, validatingKey, true);
@@ -661,47 +749,31 @@ function createValidation(ctx) {
661
749
  clearAllPendingErrors
662
750
  };
663
751
  }
664
- function markFieldDirty(dirtyFields, dirtyFieldCount, fieldName) {
665
- if (dirtyFields.value[fieldName]) return;
666
- dirtyFields.value = {
667
- ...dirtyFields.value,
668
- [fieldName]: true
669
- };
670
- dirtyFieldCount.value++;
671
- }
672
- function markFieldTouched(touchedFields, touchedFieldCount, fieldName) {
673
- if (touchedFields.value[fieldName]) return;
674
- touchedFields.value = {
675
- ...touchedFields.value,
676
- [fieldName]: true
677
- };
678
- touchedFieldCount.value++;
679
- }
680
- function clearFieldDirty(dirtyFields, dirtyFieldCount, fieldName) {
681
- if (!(fieldName in dirtyFields.value)) return;
682
- const newDirty = { ...dirtyFields.value };
683
- delete newDirty[fieldName];
684
- dirtyFields.value = newDirty;
685
- dirtyFieldCount.value--;
752
+ var VALID_MODES = [
753
+ "onSubmit",
754
+ "onBlur",
755
+ "onChange",
756
+ "onTouched"
757
+ ];
758
+ function validateMode(mode, paramName) {
759
+ if (__DEV__ && !VALID_MODES.includes(mode)) warnOnce(`Invalid ${paramName}: "${mode}". Expected one of: ${VALID_MODES.join(", ")}`, `invalid-mode-${mode}`);
686
760
  }
687
- function clearFieldTouched(touchedFields, touchedFieldCount, fieldName) {
688
- if (!(fieldName in touchedFields.value)) return;
689
- const newTouched = { ...touchedFields.value };
690
- delete newTouched[fieldName];
691
- touchedFields.value = newTouched;
692
- touchedFieldCount.value--;
761
+ function shouldValidateOnChange(mode, isTouched, reValidateMode, hasSubmitted) {
762
+ if (__DEV__) {
763
+ validateMode(mode, "validation mode");
764
+ if (reValidateMode) validateMode(reValidateMode, "reValidateMode");
765
+ }
766
+ if (mode === "onChange") return true;
767
+ if (mode === "onTouched" && isTouched) return true;
768
+ if (hasSubmitted === true && reValidateMode === "onChange") return true;
769
+ return false;
693
770
  }
694
- function clearFieldErrors(errors, fieldName) {
695
- const currentErrors = errors.value;
696
- const keys = Object.keys(currentErrors);
697
- if (keys.length === 0) return;
698
- const prefix = `${fieldName}.`;
699
- const keysToDelete = [];
700
- for (const key of keys) if (key === fieldName || key.startsWith(prefix)) keysToDelete.push(key);
701
- if (keysToDelete.length === 0) return;
702
- const newErrors = { ...currentErrors };
703
- for (const key of keysToDelete) delete newErrors[key];
704
- errors.value = newErrors;
771
+ function shouldValidateOnBlur(mode, hasSubmitted, reValidateMode) {
772
+ if (__DEV__) {
773
+ validateMode(mode, "validation mode");
774
+ if (reValidateMode) validateMode(reValidateMode, "reValidateMode");
775
+ }
776
+ return mode === "onBlur" || mode === "onTouched" || hasSubmitted && (reValidateMode === "onBlur" || reValidateMode === "onTouched");
705
777
  }
706
778
  var validationRequestCounter = 0;
707
779
  function createFieldRegistration(ctx, validate) {
@@ -742,11 +814,14 @@ function createFieldRegistration(ctx, validate) {
742
814
  };
743
815
  const onInput = async (e) => {
744
816
  const target = e.target;
745
- const value = target.type === "checkbox" ? target.checked : target.value;
817
+ let value;
818
+ if (target.type === "checkbox") value = target.checked;
819
+ else if (target.type === "number" || target.type === "range") value = target.valueAsNumber;
820
+ else value = target.value;
746
821
  set(ctx.formData, name, value);
747
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
822
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, value);
748
823
  const fieldOpts = ctx.fieldOptions.get(name);
749
- if (ctx.options.mode === "onChange" || ctx.options.mode === "onTouched" && ctx.touchedFields.value[name] || ctx.touchedFields.value[name] && ctx.options.reValidateMode === "onChange") {
824
+ if (shouldValidateOnChange(ctx.options.mode ?? "onSubmit", ctx.touchedFields.value[name] === true, ctx.options.reValidateMode, ctx.submitCount.value > 0)) {
750
825
  const validationDebounceMs = ctx.options.validationDebounce || 0;
751
826
  if (validationDebounceMs > 0) {
752
827
  const existingTimer = ctx.schemaValidationTimers.get(name);
@@ -779,15 +854,18 @@ function createFieldRegistration(ctx, validate) {
779
854
  const timer = setTimeout(async () => {
780
855
  ctx.debounceTimers.delete(name);
781
856
  await runCustomValidation(name, value, requestId, resetGenAtStart);
782
- ctx.validationRequestIds.delete(name);
857
+ if (ctx.validationRequestIds.get(name) === requestId) ctx.validationRequestIds.delete(name);
783
858
  }, debounceMs);
784
859
  ctx.debounceTimers.set(name, timer);
785
- } else await runCustomValidation(name, value, requestId, resetGenAtStart);
860
+ } else {
861
+ await runCustomValidation(name, value, requestId, resetGenAtStart);
862
+ if (ctx.validationRequestIds.get(name) === requestId) ctx.validationRequestIds.delete(name);
863
+ }
786
864
  }
787
865
  };
788
866
  const onBlur = async (_e) => {
789
- markFieldTouched(ctx.touchedFields, ctx.touchedFieldCount, name);
790
- if (ctx.options.mode === "onBlur" || ctx.options.mode === "onTouched" || ctx.submitCount.value > 0 && ctx.options.reValidateMode === "onBlur") {
867
+ markFieldTouched(ctx.touchedFields, name);
868
+ if (shouldValidateOnBlur(ctx.options.mode ?? "onSubmit", ctx.submitCount.value > 0, ctx.options.reValidateMode)) {
791
869
  await validate(name);
792
870
  const fieldOpts = ctx.fieldOptions.get(name);
793
871
  if (fieldOpts?.deps && fieldOpts.deps.length > 0) {
@@ -820,15 +898,21 @@ function createFieldRegistration(ctx, validate) {
820
898
  clearTimeout(schemaTimer);
821
899
  ctx.schemaValidationTimers.delete(name);
822
900
  }
901
+ const errorTimer = ctx.errorDelayTimers.get(name);
902
+ if (errorTimer) {
903
+ clearTimeout(errorTimer);
904
+ ctx.errorDelayTimers.delete(name);
905
+ }
906
+ ctx.pendingErrors.delete(name);
823
907
  ctx.validationRequestIds.delete(name);
908
+ ctx.fieldRefs.delete(name);
909
+ ctx.fieldOptions.delete(name);
910
+ ctx.fieldHandlers.delete(name);
824
911
  if (opts?.shouldUnregister ?? ctx.options.shouldUnregister ?? false) {
825
912
  unset(ctx.formData, name);
826
913
  clearFieldErrors(ctx.errors, name);
827
- clearFieldTouched(ctx.touchedFields, ctx.touchedFieldCount, name);
828
- clearFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
829
- ctx.fieldRefs.delete(name);
830
- ctx.fieldOptions.delete(name);
831
- ctx.fieldHandlers.delete(name);
914
+ clearFieldTouched(ctx.touchedFields, name);
915
+ clearFieldDirty(ctx.dirtyFields, name);
832
916
  }
833
917
  }
834
918
  };
@@ -849,7 +933,7 @@ function createFieldRegistration(ctx, validate) {
849
933
  get: () => get(ctx.formData, name),
850
934
  set: (val) => {
851
935
  set(ctx.formData, name, val);
852
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
936
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, val);
853
937
  }
854
938
  }) }
855
939
  };
@@ -858,8 +942,8 @@ function createFieldRegistration(ctx, validate) {
858
942
  const opts = options || {};
859
943
  if (!opts.keepValue) unset(ctx.formData, name);
860
944
  if (!opts.keepError) clearFieldErrors(ctx.errors, name);
861
- if (!opts.keepTouched) clearFieldTouched(ctx.touchedFields, ctx.touchedFieldCount, name);
862
- if (!opts.keepDirty) clearFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
945
+ if (!opts.keepTouched) clearFieldTouched(ctx.touchedFields, name);
946
+ if (!opts.keepDirty) clearFieldDirty(ctx.dirtyFields, name);
863
947
  ctx.fieldRefs.delete(name);
864
948
  ctx.fieldOptions.delete(name);
865
949
  ctx.fieldHandlers.delete(name);
@@ -874,6 +958,8 @@ function createFieldRegistration(ctx, validate) {
874
958
  ctx.schemaValidationTimers.delete(name);
875
959
  }
876
960
  ctx.validationRequestIds.delete(name);
961
+ ctx.validationCache.delete(`${name}:partial`);
962
+ ctx.validationCache.delete(`${name}:full`);
877
963
  }
878
964
  return {
879
965
  register,
@@ -963,10 +1049,32 @@ function createFieldArrayManager(ctx, validate, setFocus) {
963
1049
  const normalizeToArray = (value) => {
964
1050
  return Array.isArray(value) ? value : [value];
965
1051
  };
1052
+ const clearValidationCache = () => {
1053
+ for (const key of ctx.validationCache.keys()) {
1054
+ const colonIndex = key.lastIndexOf(":");
1055
+ const fieldPath = colonIndex > 0 ? key.slice(0, colonIndex) : key;
1056
+ if (fieldPath === name || fieldPath.startsWith(`${name}.`)) ctx.validationCache.delete(key);
1057
+ }
1058
+ };
1059
+ const validateIfNeeded = () => {
1060
+ const isTouched = ctx.touchedFields.value[name] === true;
1061
+ const hasSubmitted = ctx.submitCount.value > 0;
1062
+ if (shouldValidateOnChange(ctx.options.mode ?? "onSubmit", isTouched, ctx.options.reValidateMode, hasSubmitted)) validate(name);
1063
+ };
1064
+ const ensureSync = () => {
1065
+ const currentValues = get(ctx.formData, name) || [];
1066
+ if (fa.items.value.length !== currentValues.length) {
1067
+ if (__DEV__) console.warn(`[vue-hook-form] Field array out of sync with formData. Rebuilding items array (items: ${fa.items.value.length}, formData: ${currentValues.length})`);
1068
+ fa.items.value = currentValues.map(() => createItem(generateId()));
1069
+ rebuildIndexCache();
1070
+ }
1071
+ return currentValues;
1072
+ };
966
1073
  const append = (value, focusOptions) => {
1074
+ clearValidationCache();
967
1075
  const values = normalizeToArray(value);
968
1076
  if (values.length === 0) return true;
969
- const currentValues = get(ctx.formData, name) || [];
1077
+ const currentValues = ensureSync();
970
1078
  const insertIndex = currentValues.length;
971
1079
  const rules = fa.rules;
972
1080
  if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) {
@@ -981,15 +1089,16 @@ function createFieldArrayManager(ctx, validate, setFocus) {
981
1089
  const newItems = values.map(() => createItem(generateId()));
982
1090
  fa.items.value = [...fa.items.value, ...newItems];
983
1091
  appendToCache(insertIndex);
984
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
985
- if (ctx.options.mode === "onChange") validate(name);
1092
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1093
+ validateIfNeeded();
986
1094
  handleFocus(insertIndex, values.length, focusOptions);
987
1095
  return true;
988
1096
  };
989
1097
  const prepend = (value, focusOptions) => {
1098
+ clearValidationCache();
990
1099
  const values = normalizeToArray(value);
991
1100
  if (values.length === 0) return true;
992
- const currentValues = get(ctx.formData, name) || [];
1101
+ const currentValues = ensureSync();
993
1102
  const rules = fa.rules;
994
1103
  if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) {
995
1104
  if (__DEV__) warnArrayOperationRejected("prepend", name, "maxLength", {
@@ -1003,13 +1112,14 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1003
1112
  const newItems = values.map(() => createItem(generateId()));
1004
1113
  fa.items.value = [...newItems, ...fa.items.value];
1005
1114
  updateCacheAfterInsert(0, values.length);
1006
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1007
- if (ctx.options.mode === "onChange") validate(name);
1115
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1116
+ validateIfNeeded();
1008
1117
  handleFocus(0, values.length, focusOptions);
1009
1118
  return true;
1010
1119
  };
1011
1120
  const update = (index, value) => {
1012
- const currentValues = get(ctx.formData, name) || [];
1121
+ clearValidationCache();
1122
+ const currentValues = ensureSync();
1013
1123
  if (index < 0 || index >= currentValues.length) {
1014
1124
  if (__DEV__) warnArrayIndexOutOfBounds("update", name, index, currentValues.length);
1015
1125
  return false;
@@ -1017,12 +1127,13 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1017
1127
  const newValues = [...currentValues];
1018
1128
  newValues[index] = value;
1019
1129
  set(ctx.formData, name, newValues);
1020
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1021
- if (ctx.options.mode === "onChange") validate(name);
1130
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1131
+ validateIfNeeded();
1022
1132
  return true;
1023
1133
  };
1024
1134
  const removeAt = (index) => {
1025
- const currentValues = get(ctx.formData, name) || [];
1135
+ clearValidationCache();
1136
+ const currentValues = ensureSync();
1026
1137
  if (index < 0 || index >= currentValues.length) {
1027
1138
  if (__DEV__) warnArrayIndexOutOfBounds("remove", name, index, currentValues.length);
1028
1139
  return false;
@@ -1040,14 +1151,15 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1040
1151
  const keyToRemove = fa.items.value[index]?.key;
1041
1152
  fa.items.value = fa.items.value.filter((item) => item.key !== keyToRemove);
1042
1153
  if (keyToRemove) updateCacheAfterRemove(keyToRemove, index);
1043
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1044
- if (ctx.options.mode === "onChange") validate(name);
1154
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1155
+ validateIfNeeded();
1045
1156
  return true;
1046
1157
  };
1047
1158
  const insert = (index, value, focusOptions) => {
1159
+ clearValidationCache();
1048
1160
  const values = normalizeToArray(value);
1049
1161
  if (values.length === 0) return true;
1050
- const currentValues = get(ctx.formData, name) || [];
1162
+ const currentValues = ensureSync();
1051
1163
  const rules = fa.rules;
1052
1164
  if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) {
1053
1165
  if (__DEV__) warnArrayOperationRejected("insert", name, "maxLength", {
@@ -1056,27 +1168,31 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1056
1168
  });
1057
1169
  return false;
1058
1170
  }
1059
- const clampedIndex = Math.max(0, Math.min(index, currentValues.length));
1171
+ if (index < 0 || index > currentValues.length) {
1172
+ if (__DEV__) warnArrayIndexOutOfBounds("insert", name, index, currentValues.length);
1173
+ return false;
1174
+ }
1060
1175
  const newValues = [
1061
- ...currentValues.slice(0, clampedIndex),
1176
+ ...currentValues.slice(0, index),
1062
1177
  ...values,
1063
- ...currentValues.slice(clampedIndex)
1178
+ ...currentValues.slice(index)
1064
1179
  ];
1065
1180
  set(ctx.formData, name, newValues);
1066
1181
  const newItems = values.map(() => createItem(generateId()));
1067
1182
  fa.items.value = [
1068
- ...fa.items.value.slice(0, clampedIndex),
1183
+ ...fa.items.value.slice(0, index),
1069
1184
  ...newItems,
1070
- ...fa.items.value.slice(clampedIndex)
1185
+ ...fa.items.value.slice(index)
1071
1186
  ];
1072
- updateCacheAfterInsert(clampedIndex, values.length);
1073
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1074
- if (ctx.options.mode === "onChange") validate(name);
1075
- handleFocus(clampedIndex, values.length, focusOptions);
1187
+ updateCacheAfterInsert(index, values.length);
1188
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1189
+ validateIfNeeded();
1190
+ handleFocus(index, values.length, focusOptions);
1076
1191
  return true;
1077
1192
  };
1078
1193
  const swap = (indexA, indexB) => {
1079
- const currentValues = get(ctx.formData, name) || [];
1194
+ clearValidationCache();
1195
+ const currentValues = ensureSync();
1080
1196
  if (indexA < 0 || indexB < 0 || indexA >= currentValues.length || indexB >= currentValues.length) {
1081
1197
  if (__DEV__) warnArrayIndexOutOfBounds("swap", name, indexA < 0 || indexA >= currentValues.length ? indexA : indexB, currentValues.length);
1082
1198
  return false;
@@ -1093,51 +1209,52 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1093
1209
  fa.items.value = newItems;
1094
1210
  swapInCache(indexA, indexB);
1095
1211
  }
1096
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1097
- if (ctx.options.mode === "onChange") validate(name);
1212
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1213
+ validateIfNeeded();
1098
1214
  return true;
1099
1215
  };
1100
1216
  const move = (from, to) => {
1101
- const currentValues = get(ctx.formData, name) || [];
1102
- if (from < 0 || from >= currentValues.length || to < 0) {
1217
+ clearValidationCache();
1218
+ const currentValues = ensureSync();
1219
+ if (from < 0 || from >= currentValues.length || to < 0 || to >= currentValues.length) {
1103
1220
  if (__DEV__) warnArrayIndexOutOfBounds("move", name, from < 0 || from >= currentValues.length ? from : to, currentValues.length);
1104
1221
  return false;
1105
1222
  }
1106
1223
  const newValues = [...currentValues];
1107
1224
  const [removed] = newValues.splice(from, 1);
1108
1225
  if (removed !== void 0) {
1109
- const clampedTo = Math.min(to, newValues.length);
1110
- newValues.splice(clampedTo, 0, removed);
1226
+ newValues.splice(to, 0, removed);
1111
1227
  set(ctx.formData, name, newValues);
1112
1228
  }
1113
1229
  const newItems = [...fa.items.value];
1114
1230
  const [removedItem] = newItems.splice(from, 1);
1115
1231
  if (removedItem) {
1116
- const clampedTo = Math.min(to, newItems.length);
1117
- newItems.splice(clampedTo, 0, removedItem);
1232
+ newItems.splice(to, 0, removedItem);
1118
1233
  fa.items.value = newItems;
1119
- const minIdx = Math.min(from, clampedTo);
1120
- const maxIdx = Math.max(from, clampedTo);
1234
+ const minIdx = Math.min(from, to);
1235
+ const maxIdx = Math.max(from, to);
1121
1236
  const items = fa.items.value;
1122
1237
  for (let i = minIdx; i <= maxIdx; i++) {
1123
1238
  const item = items[i];
1124
1239
  if (item) indexCache.set(item.key, i);
1125
1240
  }
1126
1241
  }
1127
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1128
- if (ctx.options.mode === "onChange") validate(name);
1242
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1243
+ validateIfNeeded();
1129
1244
  return true;
1130
1245
  };
1131
1246
  const replace = (newValues) => {
1247
+ clearValidationCache();
1132
1248
  if (!Array.isArray(newValues)) return false;
1133
1249
  set(ctx.formData, name, newValues);
1134
1250
  fa.items.value = newValues.map(() => createItem(generateId()));
1135
1251
  rebuildIndexCache();
1136
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1137
- if (ctx.options.mode === "onChange") validate(name);
1252
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1253
+ validateIfNeeded();
1138
1254
  return true;
1139
1255
  };
1140
1256
  const removeAll = () => {
1257
+ clearValidationCache();
1141
1258
  const rules = fa.rules;
1142
1259
  if (rules?.minLength && rules.minLength.value > 0) {
1143
1260
  if (__DEV__) warnArrayOperationRejected("removeAll", name, "minLength", {
@@ -1149,12 +1266,13 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1149
1266
  set(ctx.formData, name, []);
1150
1267
  fa.items.value = [];
1151
1268
  indexCache.clear();
1152
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1153
- if (ctx.options.mode === "onChange") validate(name);
1269
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1270
+ validateIfNeeded();
1154
1271
  return true;
1155
1272
  };
1156
1273
  const removeMany = (indices) => {
1157
- const currentValues = get(ctx.formData, name) || [];
1274
+ clearValidationCache();
1275
+ const currentValues = ensureSync();
1158
1276
  const validIndices = indices.filter((i) => i >= 0 && i < currentValues.length);
1159
1277
  if (validIndices.length === 0) return true;
1160
1278
  const rules = fa.rules;
@@ -1176,8 +1294,8 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1176
1294
  fa.items.value.forEach((item, idx) => {
1177
1295
  indexCache.set(item.key, idx);
1178
1296
  });
1179
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1180
- if (ctx.options.mode === "onChange") validate(name);
1297
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1298
+ validateIfNeeded();
1181
1299
  return true;
1182
1300
  };
1183
1301
  return {
@@ -1200,7 +1318,13 @@ function syncUncontrolledInputs(fieldRefs, fieldOptions, formData) {
1200
1318
  for (const [name, fieldRef] of Array.from(fieldRefs.entries())) {
1201
1319
  const el = fieldRef.value;
1202
1320
  if (el) {
1203
- if (!fieldOptions.get(name)?.controlled) set(formData, name, el.type === "checkbox" ? el.checked : el.value);
1321
+ if (!fieldOptions.get(name)?.controlled) {
1322
+ let value;
1323
+ if (el.type === "checkbox") value = el.checked;
1324
+ else if (el.type === "number" || el.type === "range") value = el.valueAsNumber;
1325
+ else value = el.value;
1326
+ set(formData, name, value);
1327
+ }
1204
1328
  }
1205
1329
  }
1206
1330
  }
@@ -1210,6 +1334,10 @@ function updateDomElement(el, value) {
1210
1334
  }
1211
1335
  function useForm(options) {
1212
1336
  const ctx = createFormContext(options);
1337
+ let isSubmissionLocked = false;
1338
+ (0, vue.onUnmounted)(() => {
1339
+ ctx.cleanup();
1340
+ });
1213
1341
  const { validate, clearAllPendingErrors } = createValidation(ctx);
1214
1342
  const { register, unregister } = createFieldRegistration(ctx, validate);
1215
1343
  function setFocus(name, focusOptions) {
@@ -1252,10 +1380,10 @@ function useForm(options) {
1252
1380
  };
1253
1381
  return cachedMergedErrors;
1254
1382
  }
1255
- const isDirtyComputed = (0, vue.computed)(() => ctx.dirtyFieldCount.value > 0);
1383
+ const isDirtyComputed = (0, vue.computed)(() => Object.keys(ctx.dirtyFields.value).length > 0);
1256
1384
  const errorsComputed = (0, vue.computed)(() => getMergedErrors());
1257
1385
  const isValidComputed = (0, vue.computed)(() => {
1258
- if (!(ctx.submitCount.value > 0 || ctx.touchedFieldCount.value > 0)) return false;
1386
+ if (!(ctx.submitCount.value > 0 || Object.keys(ctx.touchedFields.value).length > 0)) return false;
1259
1387
  return Object.keys(errorsComputed.value).length === 0;
1260
1388
  });
1261
1389
  const isReadyComputed = (0, vue.computed)(() => !ctx.isLoading.value);
@@ -1313,7 +1441,8 @@ function useForm(options) {
1313
1441
  return async (e) => {
1314
1442
  e.preventDefault();
1315
1443
  if (ctx.isDisabled.value) return;
1316
- if (ctx.isSubmitting.value) return;
1444
+ if (isSubmissionLocked) return;
1445
+ isSubmissionLocked = true;
1317
1446
  ctx.isSubmitting.value = true;
1318
1447
  ctx.submitCount.value++;
1319
1448
  ctx.isSubmitSuccessful.value = false;
@@ -1331,6 +1460,7 @@ function useForm(options) {
1331
1460
  }
1332
1461
  } finally {
1333
1462
  ctx.isSubmitting.value = false;
1463
+ isSubmissionLocked = false;
1334
1464
  }
1335
1465
  };
1336
1466
  }
@@ -1344,8 +1474,9 @@ function useForm(options) {
1344
1474
  }
1345
1475
  }
1346
1476
  set(ctx.formData, name, value);
1347
- if (setValueOptions?.shouldDirty !== false) markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1348
- if (setValueOptions?.shouldTouch) markFieldTouched(ctx.touchedFields, ctx.touchedFieldCount, name);
1477
+ if (setValueOptions?.shouldDirty === false) clearFieldDirty(ctx.dirtyFields, name);
1478
+ else updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, value);
1479
+ if (setValueOptions?.shouldTouch) markFieldTouched(ctx.touchedFields, name);
1349
1480
  if (!ctx.fieldOptions.get(name)?.controlled) {
1350
1481
  const fieldRef = ctx.fieldRefs.get(name);
1351
1482
  if (fieldRef?.value) updateDomElement(fieldRef.value, value);
@@ -1354,25 +1485,28 @@ function useForm(options) {
1354
1485
  }
1355
1486
  function reset(values, resetOptions) {
1356
1487
  const opts = resetOptions || {};
1488
+ ctx.validationCache.clear();
1357
1489
  ctx.resetGeneration.value++;
1358
1490
  clearAllPendingErrors();
1359
1491
  ctx.validatingFields.value = /* @__PURE__ */ new Set();
1360
- ctx.validationCache.clear();
1361
1492
  for (const timer of ctx.schemaValidationTimers.values()) clearTimeout(timer);
1362
1493
  ctx.schemaValidationTimers.clear();
1363
- if (!opts.keepDefaultValues && values) Object.assign(ctx.defaultValues, values);
1494
+ for (const timer of ctx.debounceTimers.values()) clearTimeout(timer);
1495
+ ctx.debounceTimers.clear();
1496
+ ctx.validationRequestIds.clear();
1497
+ if (!opts.keepDefaultValues && values) {
1498
+ Object.assign(ctx.defaultValues, values);
1499
+ ctx.defaultValueHashes.clear();
1500
+ }
1364
1501
  Object.keys(ctx.formData).forEach((key) => delete ctx.formData[key]);
1365
1502
  const newValues = deepClone(values || ctx.defaultValues);
1366
1503
  Object.assign(ctx.formData, newValues);
1367
- if (!opts.keepErrors) ctx.errors.value = {};
1368
- if (!opts.keepTouched) {
1369
- ctx.touchedFields.value = {};
1370
- ctx.touchedFieldCount.value = 0;
1371
- }
1372
- if (!opts.keepDirty) {
1373
- ctx.dirtyFields.value = {};
1374
- ctx.dirtyFieldCount.value = 0;
1504
+ if (!opts.keepErrors) {
1505
+ ctx.errors.value = {};
1506
+ ctx.persistentErrorFields.clear();
1375
1507
  }
1508
+ if (!opts.keepTouched) ctx.touchedFields.value = {};
1509
+ if (!opts.keepDirty) ctx.dirtyFields.value = {};
1376
1510
  if (!opts.keepSubmitCount) ctx.submitCount.value = 0;
1377
1511
  if (!opts.keepIsSubmitting) ctx.isSubmitting.value = false;
1378
1512
  if (!opts.keepIsSubmitSuccessful) ctx.isSubmitSuccessful.value = false;
@@ -1397,6 +1531,8 @@ function useForm(options) {
1397
1531
  }
1398
1532
  const opts = resetFieldOptions || {};
1399
1533
  ctx.resetGeneration.value++;
1534
+ ctx.validationCache.delete(`${name}:partial`);
1535
+ ctx.validationCache.delete(`${name}:full`);
1400
1536
  const errorTimer = ctx.errorDelayTimers.get(name);
1401
1537
  if (errorTimer) {
1402
1538
  clearTimeout(errorTimer);
@@ -1405,12 +1541,15 @@ function useForm(options) {
1405
1541
  ctx.pendingErrors.delete(name);
1406
1542
  let defaultValue = opts.defaultValue;
1407
1543
  if (defaultValue === void 0) defaultValue = get(ctx.defaultValues, name);
1408
- else set(ctx.defaultValues, name, defaultValue);
1544
+ else {
1545
+ set(ctx.defaultValues, name, defaultValue);
1546
+ ctx.defaultValueHashes.set(name, hashValue(defaultValue));
1547
+ }
1409
1548
  const clonedValue = defaultValue !== void 0 ? deepClone(defaultValue) : void 0;
1410
1549
  set(ctx.formData, name, clonedValue);
1411
1550
  if (!opts.keepError) clearFieldErrors(ctx.errors, name);
1412
- if (!opts.keepDirty) clearFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1413
- if (!opts.keepTouched) clearFieldTouched(ctx.touchedFields, ctx.touchedFieldCount, name);
1551
+ if (!opts.keepDirty) clearFieldDirty(ctx.dirtyFields, name);
1552
+ if (!opts.keepTouched) clearFieldTouched(ctx.touchedFields, name);
1414
1553
  if (!ctx.fieldOptions.get(name)?.controlled) {
1415
1554
  const fieldRef = ctx.fieldRefs.get(name);
1416
1555
  if (fieldRef?.value) updateDomElement(fieldRef.value, clonedValue ?? (fieldRef.value.type === "checkbox" ? false : ""));
@@ -1453,10 +1592,16 @@ function useForm(options) {
1453
1592
  }
1454
1593
  if (name === void 0) {
1455
1594
  ctx.errors.value = {};
1595
+ ctx.externalErrors.value = {};
1596
+ ctx.persistentErrorFields.clear();
1456
1597
  return;
1457
1598
  }
1458
1599
  const fieldsToClean = Array.isArray(name) ? name : [name];
1459
- for (const field of fieldsToClean) clearFieldErrors(ctx.errors, field);
1600
+ for (const field of fieldsToClean) {
1601
+ clearFieldErrors(ctx.errors, field);
1602
+ clearFieldErrors(ctx.externalErrors, field);
1603
+ ctx.persistentErrorFields.delete(field);
1604
+ }
1460
1605
  }
1461
1606
  function setError(name, error) {
1462
1607
  const newErrors = { ...ctx.errors.value };
@@ -1465,6 +1610,7 @@ function useForm(options) {
1465
1610
  message: error.message
1466
1611
  } : error.message);
1467
1612
  ctx.errors.value = newErrors;
1613
+ if (error.persistent) ctx.persistentErrorFields.add(name);
1468
1614
  }
1469
1615
  function setErrors(errors, options$1) {
1470
1616
  const newErrors = options$1?.shouldReplace ? {} : { ...ctx.errors.value };
@@ -1526,7 +1672,7 @@ function useForm(options) {
1526
1672
  error
1527
1673
  };
1528
1674
  }
1529
- async function trigger(name) {
1675
+ async function trigger(name, options$1) {
1530
1676
  if (__DEV__ && name) {
1531
1677
  const names = Array.isArray(name) ? name : [name];
1532
1678
  for (const n of names) {
@@ -1538,6 +1684,7 @@ function useForm(options) {
1538
1684
  }
1539
1685
  }
1540
1686
  }
1687
+ if (options$1?.markAsSubmitted) ctx.submitCount.value++;
1541
1688
  if (name === void 0) return await validate();
1542
1689
  if (Array.isArray(name)) {
1543
1690
  let allValid = true;
@@ -1565,7 +1712,11 @@ function useForm(options) {
1565
1712
  getValues,
1566
1713
  getFieldState,
1567
1714
  trigger,
1568
- setFocus
1715
+ setFocus,
1716
+ options: {
1717
+ mode: ctx.options.mode ?? "onSubmit",
1718
+ reValidateMode: ctx.options.reValidateMode
1719
+ }
1569
1720
  };
1570
1721
  }
1571
1722
  const FormContextKey = Symbol("FormContext");
@@ -1604,13 +1755,22 @@ function useController(options) {
1604
1755
  }
1605
1756
  });
1606
1757
  const onChange = (newValue) => {
1607
- form.setValue(name, newValue);
1758
+ const isTouched = form.formState.value.touchedFields[name] === true;
1759
+ const hasSubmitted = form.formState.value.submitCount > 0;
1760
+ const mode = form.options.mode ?? "onSubmit";
1761
+ const reValidateMode = form.options.reValidateMode;
1762
+ const shouldValidate = shouldValidateOnChange(mode, isTouched, reValidateMode, hasSubmitted);
1763
+ form.setValue(name, newValue, { shouldValidate });
1608
1764
  };
1609
1765
  const onBlur = () => {
1766
+ const hasSubmitted = form.formState.value.submitCount > 0;
1767
+ const mode = form.options.mode ?? "onSubmit";
1768
+ const reValidateMode = form.options.reValidateMode;
1769
+ const shouldValidate = shouldValidateOnBlur(mode, hasSubmitted, reValidateMode);
1610
1770
  const currentValue = form.getValues(name);
1611
1771
  form.setValue(name, currentValue, {
1612
1772
  shouldTouch: true,
1613
- shouldValidate: true,
1773
+ shouldValidate,
1614
1774
  shouldDirty: false
1615
1775
  });
1616
1776
  };
@@ -1649,8 +1809,13 @@ function isFieldError(error) {
1649
1809
  return typeof error === "object" && error !== null && "type" in error && "message" in error && typeof error.type === "string" && typeof error.message === "string";
1650
1810
  }
1651
1811
  exports.FormContextKey = FormContextKey;
1812
+ exports.clearPathCache = clearPathCache;
1813
+ exports.generateId = generateId;
1814
+ exports.get = get;
1652
1815
  exports.isFieldError = isFieldError;
1653
1816
  exports.provideForm = provideForm;
1817
+ exports.set = set;
1818
+ exports.unset = unset;
1654
1819
  exports.useController = useController;
1655
1820
  exports.useForm = useForm;
1656
1821
  exports.useFormContext = useFormContext;