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