@vuehookform/core 0.4.0 → 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,11 +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) return cached.isValid;
628
712
  const analysis = analyzeSchemaPath(ctx.options.schema, fieldPath);
629
- if (analysis.canPartialValidate && analysis.subSchema) return validateFieldPartial(fieldPath, analysis.subSchema, valueHash, criteriaMode, generationAtStart);
630
- 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);
631
722
  }
632
723
  const validatingKey = "_form";
633
724
  setValidating(ctx, validatingKey, true);
@@ -658,47 +749,31 @@ function createValidation(ctx) {
658
749
  clearAllPendingErrors
659
750
  };
660
751
  }
661
- function markFieldDirty(dirtyFields, dirtyFieldCount, fieldName) {
662
- if (dirtyFields.value[fieldName]) return;
663
- dirtyFields.value = {
664
- ...dirtyFields.value,
665
- [fieldName]: true
666
- };
667
- dirtyFieldCount.value++;
668
- }
669
- function markFieldTouched(touchedFields, touchedFieldCount, fieldName) {
670
- if (touchedFields.value[fieldName]) return;
671
- touchedFields.value = {
672
- ...touchedFields.value,
673
- [fieldName]: true
674
- };
675
- touchedFieldCount.value++;
676
- }
677
- function clearFieldDirty(dirtyFields, dirtyFieldCount, fieldName) {
678
- if (!(fieldName in dirtyFields.value)) return;
679
- const newDirty = { ...dirtyFields.value };
680
- delete newDirty[fieldName];
681
- dirtyFields.value = newDirty;
682
- 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}`);
683
760
  }
684
- function clearFieldTouched(touchedFields, touchedFieldCount, fieldName) {
685
- if (!(fieldName in touchedFields.value)) return;
686
- const newTouched = { ...touchedFields.value };
687
- delete newTouched[fieldName];
688
- touchedFields.value = newTouched;
689
- 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;
690
770
  }
691
- function clearFieldErrors(errors, fieldName) {
692
- const currentErrors = errors.value;
693
- const keys = Object.keys(currentErrors);
694
- if (keys.length === 0) return;
695
- const prefix = `${fieldName}.`;
696
- const keysToDelete = [];
697
- for (const key of keys) if (key === fieldName || key.startsWith(prefix)) keysToDelete.push(key);
698
- if (keysToDelete.length === 0) return;
699
- const newErrors = { ...currentErrors };
700
- for (const key of keysToDelete) delete newErrors[key];
701
- 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");
702
777
  }
703
778
  var validationRequestCounter = 0;
704
779
  function createFieldRegistration(ctx, validate) {
@@ -739,11 +814,14 @@ function createFieldRegistration(ctx, validate) {
739
814
  };
740
815
  const onInput = async (e) => {
741
816
  const target = e.target;
742
- 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;
743
821
  set(ctx.formData, name, value);
744
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
822
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, value);
745
823
  const fieldOpts = ctx.fieldOptions.get(name);
746
- 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)) {
747
825
  const validationDebounceMs = ctx.options.validationDebounce || 0;
748
826
  if (validationDebounceMs > 0) {
749
827
  const existingTimer = ctx.schemaValidationTimers.get(name);
@@ -776,15 +854,18 @@ function createFieldRegistration(ctx, validate) {
776
854
  const timer = setTimeout(async () => {
777
855
  ctx.debounceTimers.delete(name);
778
856
  await runCustomValidation(name, value, requestId, resetGenAtStart);
779
- ctx.validationRequestIds.delete(name);
857
+ if (ctx.validationRequestIds.get(name) === requestId) ctx.validationRequestIds.delete(name);
780
858
  }, debounceMs);
781
859
  ctx.debounceTimers.set(name, timer);
782
- } 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
+ }
783
864
  }
784
865
  };
785
866
  const onBlur = async (_e) => {
786
- markFieldTouched(ctx.touchedFields, ctx.touchedFieldCount, name);
787
- 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)) {
788
869
  await validate(name);
789
870
  const fieldOpts = ctx.fieldOptions.get(name);
790
871
  if (fieldOpts?.deps && fieldOpts.deps.length > 0) {
@@ -817,15 +898,21 @@ function createFieldRegistration(ctx, validate) {
817
898
  clearTimeout(schemaTimer);
818
899
  ctx.schemaValidationTimers.delete(name);
819
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);
820
907
  ctx.validationRequestIds.delete(name);
908
+ ctx.fieldRefs.delete(name);
909
+ ctx.fieldOptions.delete(name);
910
+ ctx.fieldHandlers.delete(name);
821
911
  if (opts?.shouldUnregister ?? ctx.options.shouldUnregister ?? false) {
822
912
  unset(ctx.formData, name);
823
913
  clearFieldErrors(ctx.errors, name);
824
- clearFieldTouched(ctx.touchedFields, ctx.touchedFieldCount, name);
825
- clearFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
826
- ctx.fieldRefs.delete(name);
827
- ctx.fieldOptions.delete(name);
828
- ctx.fieldHandlers.delete(name);
914
+ clearFieldTouched(ctx.touchedFields, name);
915
+ clearFieldDirty(ctx.dirtyFields, name);
829
916
  }
830
917
  }
831
918
  };
@@ -846,7 +933,7 @@ function createFieldRegistration(ctx, validate) {
846
933
  get: () => get(ctx.formData, name),
847
934
  set: (val) => {
848
935
  set(ctx.formData, name, val);
849
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
936
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, val);
850
937
  }
851
938
  }) }
852
939
  };
@@ -855,8 +942,8 @@ function createFieldRegistration(ctx, validate) {
855
942
  const opts = options || {};
856
943
  if (!opts.keepValue) unset(ctx.formData, name);
857
944
  if (!opts.keepError) clearFieldErrors(ctx.errors, name);
858
- if (!opts.keepTouched) clearFieldTouched(ctx.touchedFields, ctx.touchedFieldCount, name);
859
- 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);
860
947
  ctx.fieldRefs.delete(name);
861
948
  ctx.fieldOptions.delete(name);
862
949
  ctx.fieldHandlers.delete(name);
@@ -871,6 +958,8 @@ function createFieldRegistration(ctx, validate) {
871
958
  ctx.schemaValidationTimers.delete(name);
872
959
  }
873
960
  ctx.validationRequestIds.delete(name);
961
+ ctx.validationCache.delete(`${name}:partial`);
962
+ ctx.validationCache.delete(`${name}:full`);
874
963
  }
875
964
  return {
876
965
  register,
@@ -960,10 +1049,32 @@ function createFieldArrayManager(ctx, validate, setFocus) {
960
1049
  const normalizeToArray = (value) => {
961
1050
  return Array.isArray(value) ? value : [value];
962
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
+ };
963
1073
  const append = (value, focusOptions) => {
1074
+ clearValidationCache();
964
1075
  const values = normalizeToArray(value);
965
1076
  if (values.length === 0) return true;
966
- const currentValues = get(ctx.formData, name) || [];
1077
+ const currentValues = ensureSync();
967
1078
  const insertIndex = currentValues.length;
968
1079
  const rules = fa.rules;
969
1080
  if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) {
@@ -978,15 +1089,16 @@ function createFieldArrayManager(ctx, validate, setFocus) {
978
1089
  const newItems = values.map(() => createItem(generateId()));
979
1090
  fa.items.value = [...fa.items.value, ...newItems];
980
1091
  appendToCache(insertIndex);
981
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
982
- if (ctx.options.mode === "onChange") validate(name);
1092
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1093
+ validateIfNeeded();
983
1094
  handleFocus(insertIndex, values.length, focusOptions);
984
1095
  return true;
985
1096
  };
986
1097
  const prepend = (value, focusOptions) => {
1098
+ clearValidationCache();
987
1099
  const values = normalizeToArray(value);
988
1100
  if (values.length === 0) return true;
989
- const currentValues = get(ctx.formData, name) || [];
1101
+ const currentValues = ensureSync();
990
1102
  const rules = fa.rules;
991
1103
  if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) {
992
1104
  if (__DEV__) warnArrayOperationRejected("prepend", name, "maxLength", {
@@ -1000,13 +1112,14 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1000
1112
  const newItems = values.map(() => createItem(generateId()));
1001
1113
  fa.items.value = [...newItems, ...fa.items.value];
1002
1114
  updateCacheAfterInsert(0, values.length);
1003
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1004
- if (ctx.options.mode === "onChange") validate(name);
1115
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1116
+ validateIfNeeded();
1005
1117
  handleFocus(0, values.length, focusOptions);
1006
1118
  return true;
1007
1119
  };
1008
1120
  const update = (index, value) => {
1009
- const currentValues = get(ctx.formData, name) || [];
1121
+ clearValidationCache();
1122
+ const currentValues = ensureSync();
1010
1123
  if (index < 0 || index >= currentValues.length) {
1011
1124
  if (__DEV__) warnArrayIndexOutOfBounds("update", name, index, currentValues.length);
1012
1125
  return false;
@@ -1014,12 +1127,13 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1014
1127
  const newValues = [...currentValues];
1015
1128
  newValues[index] = value;
1016
1129
  set(ctx.formData, name, newValues);
1017
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1018
- if (ctx.options.mode === "onChange") validate(name);
1130
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1131
+ validateIfNeeded();
1019
1132
  return true;
1020
1133
  };
1021
1134
  const removeAt = (index) => {
1022
- const currentValues = get(ctx.formData, name) || [];
1135
+ clearValidationCache();
1136
+ const currentValues = ensureSync();
1023
1137
  if (index < 0 || index >= currentValues.length) {
1024
1138
  if (__DEV__) warnArrayIndexOutOfBounds("remove", name, index, currentValues.length);
1025
1139
  return false;
@@ -1037,14 +1151,15 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1037
1151
  const keyToRemove = fa.items.value[index]?.key;
1038
1152
  fa.items.value = fa.items.value.filter((item) => item.key !== keyToRemove);
1039
1153
  if (keyToRemove) updateCacheAfterRemove(keyToRemove, index);
1040
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1041
- if (ctx.options.mode === "onChange") validate(name);
1154
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1155
+ validateIfNeeded();
1042
1156
  return true;
1043
1157
  };
1044
1158
  const insert = (index, value, focusOptions) => {
1159
+ clearValidationCache();
1045
1160
  const values = normalizeToArray(value);
1046
1161
  if (values.length === 0) return true;
1047
- const currentValues = get(ctx.formData, name) || [];
1162
+ const currentValues = ensureSync();
1048
1163
  const rules = fa.rules;
1049
1164
  if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) {
1050
1165
  if (__DEV__) warnArrayOperationRejected("insert", name, "maxLength", {
@@ -1053,27 +1168,31 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1053
1168
  });
1054
1169
  return false;
1055
1170
  }
1056
- 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
+ }
1057
1175
  const newValues = [
1058
- ...currentValues.slice(0, clampedIndex),
1176
+ ...currentValues.slice(0, index),
1059
1177
  ...values,
1060
- ...currentValues.slice(clampedIndex)
1178
+ ...currentValues.slice(index)
1061
1179
  ];
1062
1180
  set(ctx.formData, name, newValues);
1063
1181
  const newItems = values.map(() => createItem(generateId()));
1064
1182
  fa.items.value = [
1065
- ...fa.items.value.slice(0, clampedIndex),
1183
+ ...fa.items.value.slice(0, index),
1066
1184
  ...newItems,
1067
- ...fa.items.value.slice(clampedIndex)
1185
+ ...fa.items.value.slice(index)
1068
1186
  ];
1069
- updateCacheAfterInsert(clampedIndex, values.length);
1070
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1071
- if (ctx.options.mode === "onChange") validate(name);
1072
- 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);
1073
1191
  return true;
1074
1192
  };
1075
1193
  const swap = (indexA, indexB) => {
1076
- const currentValues = get(ctx.formData, name) || [];
1194
+ clearValidationCache();
1195
+ const currentValues = ensureSync();
1077
1196
  if (indexA < 0 || indexB < 0 || indexA >= currentValues.length || indexB >= currentValues.length) {
1078
1197
  if (__DEV__) warnArrayIndexOutOfBounds("swap", name, indexA < 0 || indexA >= currentValues.length ? indexA : indexB, currentValues.length);
1079
1198
  return false;
@@ -1090,51 +1209,52 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1090
1209
  fa.items.value = newItems;
1091
1210
  swapInCache(indexA, indexB);
1092
1211
  }
1093
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1094
- if (ctx.options.mode === "onChange") validate(name);
1212
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1213
+ validateIfNeeded();
1095
1214
  return true;
1096
1215
  };
1097
1216
  const move = (from, to) => {
1098
- const currentValues = get(ctx.formData, name) || [];
1099
- 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) {
1100
1220
  if (__DEV__) warnArrayIndexOutOfBounds("move", name, from < 0 || from >= currentValues.length ? from : to, currentValues.length);
1101
1221
  return false;
1102
1222
  }
1103
1223
  const newValues = [...currentValues];
1104
1224
  const [removed] = newValues.splice(from, 1);
1105
1225
  if (removed !== void 0) {
1106
- const clampedTo = Math.min(to, newValues.length);
1107
- newValues.splice(clampedTo, 0, removed);
1226
+ newValues.splice(to, 0, removed);
1108
1227
  set(ctx.formData, name, newValues);
1109
1228
  }
1110
1229
  const newItems = [...fa.items.value];
1111
1230
  const [removedItem] = newItems.splice(from, 1);
1112
1231
  if (removedItem) {
1113
- const clampedTo = Math.min(to, newItems.length);
1114
- newItems.splice(clampedTo, 0, removedItem);
1232
+ newItems.splice(to, 0, removedItem);
1115
1233
  fa.items.value = newItems;
1116
- const minIdx = Math.min(from, clampedTo);
1117
- const maxIdx = Math.max(from, clampedTo);
1234
+ const minIdx = Math.min(from, to);
1235
+ const maxIdx = Math.max(from, to);
1118
1236
  const items = fa.items.value;
1119
1237
  for (let i = minIdx; i <= maxIdx; i++) {
1120
1238
  const item = items[i];
1121
1239
  if (item) indexCache.set(item.key, i);
1122
1240
  }
1123
1241
  }
1124
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1125
- if (ctx.options.mode === "onChange") validate(name);
1242
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1243
+ validateIfNeeded();
1126
1244
  return true;
1127
1245
  };
1128
1246
  const replace = (newValues) => {
1247
+ clearValidationCache();
1129
1248
  if (!Array.isArray(newValues)) return false;
1130
1249
  set(ctx.formData, name, newValues);
1131
1250
  fa.items.value = newValues.map(() => createItem(generateId()));
1132
1251
  rebuildIndexCache();
1133
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1134
- if (ctx.options.mode === "onChange") validate(name);
1252
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1253
+ validateIfNeeded();
1135
1254
  return true;
1136
1255
  };
1137
1256
  const removeAll = () => {
1257
+ clearValidationCache();
1138
1258
  const rules = fa.rules;
1139
1259
  if (rules?.minLength && rules.minLength.value > 0) {
1140
1260
  if (__DEV__) warnArrayOperationRejected("removeAll", name, "minLength", {
@@ -1146,12 +1266,13 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1146
1266
  set(ctx.formData, name, []);
1147
1267
  fa.items.value = [];
1148
1268
  indexCache.clear();
1149
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1150
- if (ctx.options.mode === "onChange") validate(name);
1269
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1270
+ validateIfNeeded();
1151
1271
  return true;
1152
1272
  };
1153
1273
  const removeMany = (indices) => {
1154
- const currentValues = get(ctx.formData, name) || [];
1274
+ clearValidationCache();
1275
+ const currentValues = ensureSync();
1155
1276
  const validIndices = indices.filter((i) => i >= 0 && i < currentValues.length);
1156
1277
  if (validIndices.length === 0) return true;
1157
1278
  const rules = fa.rules;
@@ -1173,8 +1294,8 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1173
1294
  fa.items.value.forEach((item, idx) => {
1174
1295
  indexCache.set(item.key, idx);
1175
1296
  });
1176
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1177
- if (ctx.options.mode === "onChange") validate(name);
1297
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1298
+ validateIfNeeded();
1178
1299
  return true;
1179
1300
  };
1180
1301
  return {
@@ -1197,7 +1318,13 @@ function syncUncontrolledInputs(fieldRefs, fieldOptions, formData) {
1197
1318
  for (const [name, fieldRef] of Array.from(fieldRefs.entries())) {
1198
1319
  const el = fieldRef.value;
1199
1320
  if (el) {
1200
- 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
+ }
1201
1328
  }
1202
1329
  }
1203
1330
  }
@@ -1207,6 +1334,10 @@ function updateDomElement(el, value) {
1207
1334
  }
1208
1335
  function useForm(options) {
1209
1336
  const ctx = createFormContext(options);
1337
+ let isSubmissionLocked = false;
1338
+ (0, vue.onUnmounted)(() => {
1339
+ ctx.cleanup();
1340
+ });
1210
1341
  const { validate, clearAllPendingErrors } = createValidation(ctx);
1211
1342
  const { register, unregister } = createFieldRegistration(ctx, validate);
1212
1343
  function setFocus(name, focusOptions) {
@@ -1249,10 +1380,10 @@ function useForm(options) {
1249
1380
  };
1250
1381
  return cachedMergedErrors;
1251
1382
  }
1252
- const isDirtyComputed = (0, vue.computed)(() => ctx.dirtyFieldCount.value > 0);
1383
+ const isDirtyComputed = (0, vue.computed)(() => Object.keys(ctx.dirtyFields.value).length > 0);
1253
1384
  const errorsComputed = (0, vue.computed)(() => getMergedErrors());
1254
1385
  const isValidComputed = (0, vue.computed)(() => {
1255
- 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;
1256
1387
  return Object.keys(errorsComputed.value).length === 0;
1257
1388
  });
1258
1389
  const isReadyComputed = (0, vue.computed)(() => !ctx.isLoading.value);
@@ -1310,7 +1441,8 @@ function useForm(options) {
1310
1441
  return async (e) => {
1311
1442
  e.preventDefault();
1312
1443
  if (ctx.isDisabled.value) return;
1313
- if (ctx.isSubmitting.value) return;
1444
+ if (isSubmissionLocked) return;
1445
+ isSubmissionLocked = true;
1314
1446
  ctx.isSubmitting.value = true;
1315
1447
  ctx.submitCount.value++;
1316
1448
  ctx.isSubmitSuccessful.value = false;
@@ -1328,6 +1460,7 @@ function useForm(options) {
1328
1460
  }
1329
1461
  } finally {
1330
1462
  ctx.isSubmitting.value = false;
1463
+ isSubmissionLocked = false;
1331
1464
  }
1332
1465
  };
1333
1466
  }
@@ -1341,8 +1474,9 @@ function useForm(options) {
1341
1474
  }
1342
1475
  }
1343
1476
  set(ctx.formData, name, value);
1344
- if (setValueOptions?.shouldDirty !== false) markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1345
- 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);
1346
1480
  if (!ctx.fieldOptions.get(name)?.controlled) {
1347
1481
  const fieldRef = ctx.fieldRefs.get(name);
1348
1482
  if (fieldRef?.value) updateDomElement(fieldRef.value, value);
@@ -1351,25 +1485,28 @@ function useForm(options) {
1351
1485
  }
1352
1486
  function reset(values, resetOptions) {
1353
1487
  const opts = resetOptions || {};
1488
+ ctx.validationCache.clear();
1354
1489
  ctx.resetGeneration.value++;
1355
1490
  clearAllPendingErrors();
1356
1491
  ctx.validatingFields.value = /* @__PURE__ */ new Set();
1357
- ctx.validationCache.clear();
1358
1492
  for (const timer of ctx.schemaValidationTimers.values()) clearTimeout(timer);
1359
1493
  ctx.schemaValidationTimers.clear();
1360
- 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
+ }
1361
1501
  Object.keys(ctx.formData).forEach((key) => delete ctx.formData[key]);
1362
1502
  const newValues = deepClone(values || ctx.defaultValues);
1363
1503
  Object.assign(ctx.formData, newValues);
1364
- if (!opts.keepErrors) ctx.errors.value = {};
1365
- if (!opts.keepTouched) {
1366
- ctx.touchedFields.value = {};
1367
- ctx.touchedFieldCount.value = 0;
1368
- }
1369
- if (!opts.keepDirty) {
1370
- ctx.dirtyFields.value = {};
1371
- ctx.dirtyFieldCount.value = 0;
1504
+ if (!opts.keepErrors) {
1505
+ ctx.errors.value = {};
1506
+ ctx.persistentErrorFields.clear();
1372
1507
  }
1508
+ if (!opts.keepTouched) ctx.touchedFields.value = {};
1509
+ if (!opts.keepDirty) ctx.dirtyFields.value = {};
1373
1510
  if (!opts.keepSubmitCount) ctx.submitCount.value = 0;
1374
1511
  if (!opts.keepIsSubmitting) ctx.isSubmitting.value = false;
1375
1512
  if (!opts.keepIsSubmitSuccessful) ctx.isSubmitSuccessful.value = false;
@@ -1394,6 +1531,8 @@ function useForm(options) {
1394
1531
  }
1395
1532
  const opts = resetFieldOptions || {};
1396
1533
  ctx.resetGeneration.value++;
1534
+ ctx.validationCache.delete(`${name}:partial`);
1535
+ ctx.validationCache.delete(`${name}:full`);
1397
1536
  const errorTimer = ctx.errorDelayTimers.get(name);
1398
1537
  if (errorTimer) {
1399
1538
  clearTimeout(errorTimer);
@@ -1402,12 +1541,15 @@ function useForm(options) {
1402
1541
  ctx.pendingErrors.delete(name);
1403
1542
  let defaultValue = opts.defaultValue;
1404
1543
  if (defaultValue === void 0) defaultValue = get(ctx.defaultValues, name);
1405
- else set(ctx.defaultValues, name, defaultValue);
1544
+ else {
1545
+ set(ctx.defaultValues, name, defaultValue);
1546
+ ctx.defaultValueHashes.set(name, hashValue(defaultValue));
1547
+ }
1406
1548
  const clonedValue = defaultValue !== void 0 ? deepClone(defaultValue) : void 0;
1407
1549
  set(ctx.formData, name, clonedValue);
1408
1550
  if (!opts.keepError) clearFieldErrors(ctx.errors, name);
1409
- if (!opts.keepDirty) clearFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1410
- 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);
1411
1553
  if (!ctx.fieldOptions.get(name)?.controlled) {
1412
1554
  const fieldRef = ctx.fieldRefs.get(name);
1413
1555
  if (fieldRef?.value) updateDomElement(fieldRef.value, clonedValue ?? (fieldRef.value.type === "checkbox" ? false : ""));
@@ -1450,10 +1592,16 @@ function useForm(options) {
1450
1592
  }
1451
1593
  if (name === void 0) {
1452
1594
  ctx.errors.value = {};
1595
+ ctx.externalErrors.value = {};
1596
+ ctx.persistentErrorFields.clear();
1453
1597
  return;
1454
1598
  }
1455
1599
  const fieldsToClean = Array.isArray(name) ? name : [name];
1456
- 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
+ }
1457
1605
  }
1458
1606
  function setError(name, error) {
1459
1607
  const newErrors = { ...ctx.errors.value };
@@ -1462,6 +1610,7 @@ function useForm(options) {
1462
1610
  message: error.message
1463
1611
  } : error.message);
1464
1612
  ctx.errors.value = newErrors;
1613
+ if (error.persistent) ctx.persistentErrorFields.add(name);
1465
1614
  }
1466
1615
  function setErrors(errors, options$1) {
1467
1616
  const newErrors = options$1?.shouldReplace ? {} : { ...ctx.errors.value };
@@ -1523,7 +1672,7 @@ function useForm(options) {
1523
1672
  error
1524
1673
  };
1525
1674
  }
1526
- async function trigger(name) {
1675
+ async function trigger(name, options$1) {
1527
1676
  if (__DEV__ && name) {
1528
1677
  const names = Array.isArray(name) ? name : [name];
1529
1678
  for (const n of names) {
@@ -1535,6 +1684,7 @@ function useForm(options) {
1535
1684
  }
1536
1685
  }
1537
1686
  }
1687
+ if (options$1?.markAsSubmitted) ctx.submitCount.value++;
1538
1688
  if (name === void 0) return await validate();
1539
1689
  if (Array.isArray(name)) {
1540
1690
  let allValid = true;
@@ -1562,7 +1712,11 @@ function useForm(options) {
1562
1712
  getValues,
1563
1713
  getFieldState,
1564
1714
  trigger,
1565
- setFocus
1715
+ setFocus,
1716
+ options: {
1717
+ mode: ctx.options.mode ?? "onSubmit",
1718
+ reValidateMode: ctx.options.reValidateMode
1719
+ }
1566
1720
  };
1567
1721
  }
1568
1722
  const FormContextKey = Symbol("FormContext");
@@ -1601,10 +1755,24 @@ function useController(options) {
1601
1755
  }
1602
1756
  });
1603
1757
  const onChange = (newValue) => {
1604
- 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 });
1605
1764
  };
1606
1765
  const onBlur = () => {
1607
- form.trigger(name);
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);
1770
+ const currentValue = form.getValues(name);
1771
+ form.setValue(name, currentValue, {
1772
+ shouldTouch: true,
1773
+ shouldValidate,
1774
+ shouldDirty: false
1775
+ });
1608
1776
  };
1609
1777
  const refCallback = (el) => {
1610
1778
  elementRef.value = el;
@@ -1641,8 +1809,13 @@ function isFieldError(error) {
1641
1809
  return typeof error === "object" && error !== null && "type" in error && "message" in error && typeof error.type === "string" && typeof error.message === "string";
1642
1810
  }
1643
1811
  exports.FormContextKey = FormContextKey;
1812
+ exports.clearPathCache = clearPathCache;
1813
+ exports.generateId = generateId;
1814
+ exports.get = get;
1644
1815
  exports.isFieldError = isFieldError;
1645
1816
  exports.provideForm = provideForm;
1817
+ exports.set = set;
1818
+ exports.unset = unset;
1646
1819
  exports.useController = useController;
1647
1820
  exports.useForm = useForm;
1648
1821
  exports.useFormContext = useFormContext;