@vuehookform/core 0.4.1 → 0.4.3

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;
@@ -202,26 +222,68 @@ function warnArrayIndexOutOfBounds(operation, path, index, length) {
202
222
  if (!__DEV__) return;
203
223
  warn(`${operation}() on "${path}": Index ${index} is out of bounds (array length: ${length}). Operation was silently ignored.`);
204
224
  }
205
- function getDefProp$1(schema, prop) {
206
- return schema.def[prop];
207
- }
208
- function getTypeName(schema) {
209
- return getDefProp$1(schema, "typeName");
225
+ function getSchemaType(schema) {
226
+ return schema.type;
210
227
  }
211
228
  function isZodObject$1(schema) {
212
- return getTypeName(schema) === "ZodObject";
229
+ return getSchemaType(schema) === "object";
213
230
  }
214
231
  function isZodArray$1(schema) {
215
- return getTypeName(schema) === "ZodArray";
232
+ return getSchemaType(schema) === "array";
216
233
  }
217
234
  function unwrapSchema(schema) {
218
- const typeName = getTypeName(schema);
219
- const innerType = getDefProp$1(schema, "innerType");
220
- const schemaType = getDefProp$1(schema, "schema");
221
- if ((typeName === "ZodOptional" || typeName === "ZodNullable" || typeName === "ZodDefault") && innerType) return unwrapSchema(innerType);
222
- if (typeName === "ZodEffects" && schemaType) return unwrapSchema(schemaType);
235
+ const schemaType = getSchemaType(schema);
236
+ if (schemaType === "optional" || schemaType === "nullable" || schemaType === "default") {
237
+ const schemaWithUnwrap = schema;
238
+ if (typeof schemaWithUnwrap.unwrap === "function") return unwrapSchema(schemaWithUnwrap.unwrap());
239
+ }
223
240
  return schema;
224
241
  }
242
+ function getInputElement(refValue) {
243
+ if (!refValue) return null;
244
+ if (refValue instanceof HTMLInputElement) return refValue;
245
+ if (refValue instanceof HTMLSelectElement || refValue instanceof HTMLTextAreaElement) return refValue;
246
+ if (typeof refValue === "object" && "$el" in refValue) {
247
+ const el = refValue.$el;
248
+ if (el instanceof HTMLInputElement) return el;
249
+ if (el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement) return el;
250
+ if (el instanceof Element) {
251
+ const input = el.querySelector("input, select, textarea");
252
+ if (input instanceof HTMLInputElement || input instanceof HTMLSelectElement || input instanceof HTMLTextAreaElement) return input;
253
+ }
254
+ }
255
+ return null;
256
+ }
257
+ function getFocusableElement(refValue) {
258
+ const input = getInputElement(refValue);
259
+ if (input) return input;
260
+ if (typeof refValue === "object" && refValue && "$el" in refValue) {
261
+ const el = refValue.$el;
262
+ if (el instanceof HTMLElement && typeof el.focus === "function") return el;
263
+ }
264
+ if (refValue instanceof HTMLElement && typeof refValue.focus === "function") return refValue;
265
+ return null;
266
+ }
267
+ function syncUncontrolledInputs(fieldRefs, fieldOptions, formData) {
268
+ for (const [name, fieldRef] of Array.from(fieldRefs.entries())) {
269
+ const el = getInputElement(fieldRef.value);
270
+ if (el) {
271
+ if (!fieldOptions.get(name)?.controlled) {
272
+ let value;
273
+ if (el.type === "checkbox") value = el.checked;
274
+ else if (el.type === "number" || el.type === "range") value = el.valueAsNumber;
275
+ else value = el.value;
276
+ set(formData, name, value);
277
+ }
278
+ }
279
+ }
280
+ }
281
+ function updateDomElement(refValue, value) {
282
+ const el = getInputElement(refValue);
283
+ if (!el) return;
284
+ if (el.type === "checkbox") el.checked = value;
285
+ else el.value = value;
286
+ }
225
287
  function createFormContext(options) {
226
288
  const formData = (0, vue.reactive)({});
227
289
  const defaultValues = (0, vue.reactive)({});
@@ -230,8 +292,8 @@ function createFormContext(options) {
230
292
  if (isAsyncDefaults) {
231
293
  const asyncFn = options.defaultValues;
232
294
  asyncFn().then((values) => {
233
- Object.assign(defaultValues, values);
234
- Object.assign(formData, values);
295
+ Object.assign(defaultValues, deepClone(values));
296
+ Object.assign(formData, deepClone(values));
235
297
  isLoading.value = false;
236
298
  }).catch((error) => {
237
299
  console.error("Failed to load async default values:", error);
@@ -240,8 +302,8 @@ function createFormContext(options) {
240
302
  options.onDefaultValuesError?.(error);
241
303
  });
242
304
  } else if (options.defaultValues) {
243
- Object.assign(defaultValues, options.defaultValues);
244
- Object.assign(formData, defaultValues);
305
+ Object.assign(defaultValues, deepClone(options.defaultValues));
306
+ Object.assign(formData, deepClone(options.defaultValues));
245
307
  }
246
308
  const errors = (0, vue.shallowRef)({});
247
309
  const touchedFields = (0, vue.shallowRef)({});
@@ -261,43 +323,44 @@ function createFormContext(options) {
261
323
  const debounceTimers = /* @__PURE__ */ new Map();
262
324
  const validationRequestIds = /* @__PURE__ */ new Map();
263
325
  const resetGeneration = (0, vue.ref)(0);
264
- const dirtyFieldCount = (0, vue.ref)(0);
265
- const touchedFieldCount = (0, vue.ref)(0);
266
326
  const validationCache = /* @__PURE__ */ new Map();
267
327
  const schemaValidationTimers = /* @__PURE__ */ new Map();
328
+ const persistentErrorFields = /* @__PURE__ */ new Set();
329
+ const defaultValueHashes = /* @__PURE__ */ new Map();
268
330
  const isDisabled = (0, vue.ref)(false);
331
+ const watchStopHandles = [];
269
332
  if (options.disabled !== void 0) {
270
333
  isDisabled.value = (0, vue.toValue)(options.disabled) ?? false;
271
- (0, vue.watch)(() => (0, vue.toValue)(options.disabled), (newDisabled) => {
334
+ watchStopHandles.push((0, vue.watch)(() => (0, vue.toValue)(options.disabled), (newDisabled) => {
272
335
  isDisabled.value = newDisabled ?? false;
273
- });
336
+ }));
274
337
  }
275
338
  if (options.values !== void 0) {
276
339
  const initialValues = (0, vue.toValue)(options.values);
277
340
  if (initialValues && !isAsyncDefaults) {
278
341
  for (const [key, value] of Object.entries(initialValues)) if (value !== void 0) set(formData, key, value);
279
342
  }
280
- (0, vue.watch)(() => (0, vue.toValue)(options.values), (newValues) => {
343
+ watchStopHandles.push((0, vue.watch)(() => (0, vue.toValue)(options.values), (newValues) => {
281
344
  if (newValues) {
282
345
  for (const [key, value] of Object.entries(newValues)) if (value !== void 0) {
283
346
  set(formData, key, value);
284
347
  const fieldRef = fieldRefs.get(key);
285
348
  const opts = fieldOptions.get(key);
286
349
  if (fieldRef?.value && !opts?.controlled) {
287
- const el = fieldRef.value;
288
- if (el.type === "checkbox") el.checked = value;
350
+ const el = getInputElement(fieldRef.value);
351
+ if (el) if (el.type === "checkbox") el.checked = value;
289
352
  else el.value = value;
290
353
  }
291
354
  }
292
355
  }
293
- }, { deep: true });
356
+ }, { deep: true }));
294
357
  }
295
358
  if (options.errors !== void 0) {
296
359
  const initialErrors = (0, vue.toValue)(options.errors);
297
360
  if (initialErrors) externalErrors.value = initialErrors;
298
- (0, vue.watch)(() => (0, vue.toValue)(options.errors), (newErrors) => {
361
+ watchStopHandles.push((0, vue.watch)(() => (0, vue.toValue)(options.errors), (newErrors) => {
299
362
  externalErrors.value = newErrors || {};
300
- }, { deep: true });
363
+ }, { deep: true }));
301
364
  }
302
365
  return {
303
366
  formData,
@@ -322,25 +385,40 @@ function createFormContext(options) {
322
385
  validationRequestIds,
323
386
  resetGeneration,
324
387
  isDisabled,
325
- dirtyFieldCount,
326
- touchedFieldCount,
327
388
  validationCache,
328
389
  schemaValidationTimers,
329
- options
390
+ persistentErrorFields,
391
+ defaultValueHashes,
392
+ options,
393
+ cleanup: () => {
394
+ for (const stop of watchStopHandles) stop();
395
+ }
330
396
  };
331
397
  }
332
398
  var uniqueIdCounter = 0;
399
+ var circularRefMap = /* @__PURE__ */ new WeakMap();
333
400
  function hashValue(value) {
334
401
  if (value === null) return "null";
335
402
  if (value === void 0) return "undefined";
336
403
  const type = typeof value;
337
404
  if (type === "string") return `s:${value}`;
338
- if (type === "number") return `n:${value}`;
405
+ if (type === "number") {
406
+ const num = value;
407
+ if (Number.isNaN(num)) return "n:NaN";
408
+ if (num === 0) return "n:0";
409
+ if (!Number.isFinite(num)) return `n:${num > 0 ? "Infinity" : "-Infinity"}`;
410
+ return `n:${num}`;
411
+ }
339
412
  if (type === "boolean") return `b:${value}`;
340
413
  if (type === "object") try {
341
414
  return `o:${JSON.stringify(value)}`;
342
415
  } catch {
343
- return `o:_${++uniqueIdCounter}`;
416
+ let id = circularRefMap.get(value);
417
+ if (!id) {
418
+ id = String(++uniqueIdCounter);
419
+ circularRefMap.set(value, id);
420
+ }
421
+ return `o:_${id}`;
344
422
  }
345
423
  return `x:_${++uniqueIdCounter}`;
346
424
  }
@@ -375,7 +453,7 @@ function extractSubSchema(schema, path) {
375
453
  let currentSchema = schema;
376
454
  let hasEffects = false;
377
455
  for (const segment of segments) {
378
- if (!segment) continue;
456
+ if (!segment) return null;
379
457
  if (hasChecks(currentSchema)) hasEffects = true;
380
458
  const unwrapped = unwrapNonEffects(currentSchema);
381
459
  if (hasChecks(unwrapped)) hasEffects = true;
@@ -442,10 +520,54 @@ function analyzeSchemaPath(schema, path) {
442
520
  cache.set(path, result);
443
521
  return result;
444
522
  }
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;
523
+ function markFieldTouched(touchedFields, fieldName) {
524
+ if (touchedFields.value[fieldName]) return;
525
+ touchedFields.value = {
526
+ ...touchedFields.value,
527
+ [fieldName]: true
528
+ };
529
+ }
530
+ function clearFieldDirty(dirtyFields, fieldName) {
531
+ if (!(fieldName in dirtyFields.value)) return;
532
+ const newDirty = { ...dirtyFields.value };
533
+ delete newDirty[fieldName];
534
+ dirtyFields.value = newDirty;
535
+ }
536
+ function clearFieldTouched(touchedFields, fieldName) {
537
+ if (!(fieldName in touchedFields.value)) return;
538
+ const newTouched = { ...touchedFields.value };
539
+ delete newTouched[fieldName];
540
+ touchedFields.value = newTouched;
541
+ }
542
+ function clearFieldErrors(errors, fieldName) {
543
+ const currentErrors = errors.value;
544
+ const keys = Object.keys(currentErrors);
545
+ if (keys.length === 0) return;
546
+ const prefix = `${fieldName}.`;
547
+ const keysToDelete = [];
548
+ for (const key of keys) if (key === fieldName || key.startsWith(prefix)) keysToDelete.push(key);
549
+ if (keysToDelete.length === 0) return;
550
+ const newErrors = { ...currentErrors };
551
+ for (const key of keysToDelete) delete newErrors[key];
552
+ errors.value = newErrors;
553
+ }
554
+ function updateFieldDirtyState(dirtyFields, defaultValues, defaultValueHashes, fieldName, currentValue) {
555
+ let defaultHash = defaultValueHashes.get(fieldName);
556
+ if (defaultHash === void 0) {
557
+ defaultHash = hashValue(get(defaultValues, fieldName));
558
+ defaultValueHashes.set(fieldName, defaultHash);
559
+ }
560
+ const isDirty = hashValue(currentValue) !== defaultHash;
561
+ const wasDirty = dirtyFields.value[fieldName] === true;
562
+ if (isDirty && !wasDirty) dirtyFields.value = {
563
+ ...dirtyFields.value,
564
+ [fieldName]: true
565
+ };
566
+ else if (!isDirty && wasDirty) {
567
+ const newDirty = { ...dirtyFields.value };
568
+ delete newDirty[fieldName];
569
+ dirtyFields.value = newDirty;
570
+ }
449
571
  }
450
572
  function setValidating(ctx, fieldPath, isValidating) {
451
573
  const newSet = new Set(ctx.validatingFields.value);
@@ -486,7 +608,7 @@ function createFieldError(errors, criteriaMode = "firstError") {
486
608
  function createValidation(ctx) {
487
609
  function applyNativeValidation(fieldPath, errorMessage) {
488
610
  if (!ctx.options.shouldUseNativeValidation) return;
489
- const el = ctx.fieldRefs.get(fieldPath)?.value;
611
+ const el = getInputElement(ctx.fieldRefs.get(fieldPath)?.value);
490
612
  if (el && "setCustomValidity" in el) el.setCustomValidity(errorMessage || "");
491
613
  }
492
614
  function clearAllNativeValidation() {
@@ -539,37 +661,43 @@ function createValidation(ctx) {
539
661
  }
540
662
  ctx.pendingErrors.delete(fieldPath);
541
663
  applyNativeValidation(fieldPath, null);
542
- return clearFieldErrors$1(ctx.errors.value, fieldPath);
664
+ if (ctx.persistentErrorFields.has(fieldPath)) return;
665
+ clearFieldErrors(ctx.errors, fieldPath);
543
666
  }
544
667
  function clearAllPendingErrors() {
545
668
  for (const timer of ctx.errorDelayTimers.values()) clearTimeout(timer);
546
669
  ctx.errorDelayTimers.clear();
547
670
  ctx.pendingErrors.clear();
548
671
  }
549
- async function validateFieldPartial(fieldPath, subSchema, valueHash, criteriaMode, generationAtStart) {
672
+ async function validateFieldPartial(fieldPath, subSchema, valueHash, cacheKey, criteriaMode, generationAtStart) {
550
673
  const fieldValue = get(ctx.formData, fieldPath);
551
674
  setValidating(ctx, fieldPath, true);
552
675
  try {
553
676
  const result = await subSchema.safeParseAsync(fieldValue);
554
677
  if (ctx.resetGeneration.value !== generationAtStart) return true;
555
678
  if (result.success) {
556
- ctx.errors.value = cancelError(fieldPath);
557
- if (valueHash) ctx.validationCache.set(fieldPath, {
679
+ cancelError(fieldPath);
680
+ if (valueHash) ctx.validationCache.set(cacheKey, {
558
681
  hash: valueHash,
559
682
  isValid: true
560
683
  });
561
684
  return true;
562
685
  }
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);
686
+ const fieldSegments = fieldPath.split(".");
687
+ const fieldErrors = result.error.issues.map((issue) => {
688
+ const issuePath = issue.path.map(String);
689
+ const alreadyPrefixed = issuePath.length >= fieldSegments.length && fieldSegments.every((seg, i) => issuePath[i] === seg);
690
+ return {
691
+ ...issue,
692
+ path: alreadyPrefixed ? issuePath : fieldSegments.concat(issuePath)
693
+ };
694
+ });
695
+ cancelError(fieldPath);
568
696
  const grouped = groupErrorsByPath(fieldErrors);
569
697
  const errorBatch = [];
570
698
  for (const [path, errors] of grouped) errorBatch.push([path, createFieldError(errors, criteriaMode)]);
571
699
  scheduleErrorsBatch(errorBatch);
572
- if (valueHash) ctx.validationCache.set(fieldPath, {
700
+ if (valueHash) ctx.validationCache.set(cacheKey, {
573
701
  hash: valueHash,
574
702
  isValid: false
575
703
  });
@@ -578,14 +706,14 @@ function createValidation(ctx) {
578
706
  setValidating(ctx, fieldPath, false);
579
707
  }
580
708
  }
581
- async function validateFieldFull(fieldPath, valueHash, criteriaMode, generationAtStart) {
709
+ async function validateFieldFull(fieldPath, valueHash, cacheKey, criteriaMode, generationAtStart) {
582
710
  setValidating(ctx, fieldPath, true);
583
711
  try {
584
712
  const result = await ctx.options.schema.safeParseAsync(ctx.formData);
585
713
  if (ctx.resetGeneration.value !== generationAtStart) return true;
586
714
  if (result.success) {
587
- ctx.errors.value = cancelError(fieldPath);
588
- if (valueHash) ctx.validationCache.set(fieldPath, {
715
+ cancelError(fieldPath);
716
+ if (valueHash) ctx.validationCache.set(cacheKey, {
589
717
  hash: valueHash,
590
718
  isValid: true
591
719
  });
@@ -596,19 +724,19 @@ function createValidation(ctx) {
596
724
  return path === fieldPath || path.startsWith(`${fieldPath}.`);
597
725
  });
598
726
  if (fieldErrors.length === 0) {
599
- ctx.errors.value = cancelError(fieldPath);
600
- if (valueHash) ctx.validationCache.set(fieldPath, {
727
+ cancelError(fieldPath);
728
+ if (valueHash) ctx.validationCache.set(cacheKey, {
601
729
  hash: valueHash,
602
730
  isValid: true
603
731
  });
604
732
  return true;
605
733
  }
606
- ctx.errors.value = cancelError(fieldPath);
734
+ cancelError(fieldPath);
607
735
  const grouped = groupErrorsByPath(fieldErrors);
608
736
  const errorBatch = [];
609
737
  for (const [path, errors] of grouped) errorBatch.push([path, createFieldError(errors, criteriaMode)]);
610
738
  scheduleErrorsBatch(errorBatch);
611
- if (valueHash) ctx.validationCache.set(fieldPath, {
739
+ if (valueHash) ctx.validationCache.set(cacheKey, {
612
740
  hash: valueHash,
613
741
  isValid: false
614
742
  });
@@ -623,14 +751,16 @@ function createValidation(ctx) {
623
751
  let valueHash;
624
752
  if (fieldPath) {
625
753
  valueHash = hashValue(get(ctx.formData, fieldPath));
626
- const cached = ctx.validationCache.get(fieldPath);
627
- if (cached && cached.hash === valueHash) {
628
- if (cached.isValid) ctx.errors.value = cancelError(fieldPath);
629
- return cached.isValid;
630
- }
631
754
  const analysis = analyzeSchemaPath(ctx.options.schema, fieldPath);
632
- if (analysis.canPartialValidate && analysis.subSchema) return validateFieldPartial(fieldPath, analysis.subSchema, valueHash, criteriaMode, generationAtStart);
633
- return validateFieldFull(fieldPath, valueHash, criteriaMode, generationAtStart);
755
+ const usePartial = analysis.canPartialValidate && analysis.subSchema;
756
+ const cacheKey = `${fieldPath}:${usePartial ? "partial" : "full"}`;
757
+ const cached = ctx.validationCache.get(cacheKey);
758
+ if (cached && cached.hash === valueHash && cached.isValid) {
759
+ cancelError(fieldPath);
760
+ return true;
761
+ }
762
+ if (usePartial) return validateFieldPartial(fieldPath, analysis.subSchema, valueHash, cacheKey, criteriaMode, generationAtStart);
763
+ return validateFieldFull(fieldPath, valueHash, cacheKey, criteriaMode, generationAtStart);
634
764
  }
635
765
  const validatingKey = "_form";
636
766
  setValidating(ctx, validatingKey, true);
@@ -661,47 +791,31 @@ function createValidation(ctx) {
661
791
  clearAllPendingErrors
662
792
  };
663
793
  }
664
- function markFieldDirty(dirtyFields, dirtyFieldCount, fieldName) {
665
- if (dirtyFields.value[fieldName]) return;
666
- dirtyFields.value = {
667
- ...dirtyFields.value,
668
- [fieldName]: true
669
- };
670
- dirtyFieldCount.value++;
794
+ var VALID_MODES = [
795
+ "onSubmit",
796
+ "onBlur",
797
+ "onChange",
798
+ "onTouched"
799
+ ];
800
+ function validateMode(mode, paramName) {
801
+ if (__DEV__ && !VALID_MODES.includes(mode)) warnOnce(`Invalid ${paramName}: "${mode}". Expected one of: ${VALID_MODES.join(", ")}`, `invalid-mode-${mode}`);
671
802
  }
672
- function markFieldTouched(touchedFields, touchedFieldCount, fieldName) {
673
- if (touchedFields.value[fieldName]) return;
674
- touchedFields.value = {
675
- ...touchedFields.value,
676
- [fieldName]: true
677
- };
678
- touchedFieldCount.value++;
679
- }
680
- function clearFieldDirty(dirtyFields, dirtyFieldCount, fieldName) {
681
- if (!(fieldName in dirtyFields.value)) return;
682
- const newDirty = { ...dirtyFields.value };
683
- delete newDirty[fieldName];
684
- dirtyFields.value = newDirty;
685
- dirtyFieldCount.value--;
686
- }
687
- function clearFieldTouched(touchedFields, touchedFieldCount, fieldName) {
688
- if (!(fieldName in touchedFields.value)) return;
689
- const newTouched = { ...touchedFields.value };
690
- delete newTouched[fieldName];
691
- touchedFields.value = newTouched;
692
- touchedFieldCount.value--;
803
+ function shouldValidateOnChange(mode, isTouched, reValidateMode, hasSubmitted) {
804
+ if (__DEV__) {
805
+ validateMode(mode, "validation mode");
806
+ if (reValidateMode) validateMode(reValidateMode, "reValidateMode");
807
+ }
808
+ if (mode === "onChange") return true;
809
+ if (mode === "onTouched" && isTouched) return true;
810
+ if (hasSubmitted === true && reValidateMode === "onChange") return true;
811
+ return false;
693
812
  }
694
- function clearFieldErrors(errors, fieldName) {
695
- const currentErrors = errors.value;
696
- const keys = Object.keys(currentErrors);
697
- if (keys.length === 0) return;
698
- const prefix = `${fieldName}.`;
699
- const keysToDelete = [];
700
- for (const key of keys) if (key === fieldName || key.startsWith(prefix)) keysToDelete.push(key);
701
- if (keysToDelete.length === 0) return;
702
- const newErrors = { ...currentErrors };
703
- for (const key of keysToDelete) delete newErrors[key];
704
- errors.value = newErrors;
813
+ function shouldValidateOnBlur(mode, hasSubmitted, reValidateMode) {
814
+ if (__DEV__) {
815
+ validateMode(mode, "validation mode");
816
+ if (reValidateMode) validateMode(reValidateMode, "reValidateMode");
817
+ }
818
+ return mode === "onBlur" || mode === "onTouched" || hasSubmitted && (reValidateMode === "onBlur" || reValidateMode === "onTouched");
705
819
  }
706
820
  var validationRequestCounter = 0;
707
821
  function createFieldRegistration(ctx, validate) {
@@ -742,11 +856,14 @@ function createFieldRegistration(ctx, validate) {
742
856
  };
743
857
  const onInput = async (e) => {
744
858
  const target = e.target;
745
- const value = target.type === "checkbox" ? target.checked : target.value;
859
+ let value;
860
+ if (target.type === "checkbox") value = target.checked;
861
+ else if (target.type === "number" || target.type === "range") value = target.valueAsNumber;
862
+ else value = target.value;
746
863
  set(ctx.formData, name, value);
747
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
864
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, value);
748
865
  const fieldOpts = ctx.fieldOptions.get(name);
749
- if (ctx.options.mode === "onChange" || ctx.options.mode === "onTouched" && ctx.touchedFields.value[name] || ctx.touchedFields.value[name] && ctx.options.reValidateMode === "onChange") {
866
+ if (shouldValidateOnChange(ctx.options.mode ?? "onSubmit", ctx.touchedFields.value[name] === true, ctx.options.reValidateMode, ctx.submitCount.value > 0)) {
750
867
  const validationDebounceMs = ctx.options.validationDebounce || 0;
751
868
  if (validationDebounceMs > 0) {
752
869
  const existingTimer = ctx.schemaValidationTimers.get(name);
@@ -779,15 +896,18 @@ function createFieldRegistration(ctx, validate) {
779
896
  const timer = setTimeout(async () => {
780
897
  ctx.debounceTimers.delete(name);
781
898
  await runCustomValidation(name, value, requestId, resetGenAtStart);
782
- ctx.validationRequestIds.delete(name);
899
+ if (ctx.validationRequestIds.get(name) === requestId) ctx.validationRequestIds.delete(name);
783
900
  }, debounceMs);
784
901
  ctx.debounceTimers.set(name, timer);
785
- } else await runCustomValidation(name, value, requestId, resetGenAtStart);
902
+ } else {
903
+ await runCustomValidation(name, value, requestId, resetGenAtStart);
904
+ if (ctx.validationRequestIds.get(name) === requestId) ctx.validationRequestIds.delete(name);
905
+ }
786
906
  }
787
907
  };
788
- const onBlur = async (_e) => {
789
- markFieldTouched(ctx.touchedFields, ctx.touchedFieldCount, name);
790
- if (ctx.options.mode === "onBlur" || ctx.options.mode === "onTouched" || ctx.submitCount.value > 0 && ctx.options.reValidateMode === "onBlur") {
908
+ const onBlur = async () => {
909
+ markFieldTouched(ctx.touchedFields, name);
910
+ if (shouldValidateOnBlur(ctx.options.mode ?? "onSubmit", ctx.submitCount.value > 0, ctx.options.reValidateMode)) {
791
911
  await validate(name);
792
912
  const fieldOpts = ctx.fieldOptions.get(name);
793
913
  if (fieldOpts?.deps && fieldOpts.deps.length > 0) {
@@ -804,10 +924,11 @@ function createFieldRegistration(ctx, validate) {
804
924
  if (previousEl && el) return;
805
925
  currentFieldRef.value = el;
806
926
  const opts = ctx.fieldOptions.get(name);
807
- if (el && !opts?.controlled && el instanceof HTMLInputElement) {
927
+ const inputEl = getInputElement(el);
928
+ if (inputEl && !opts?.controlled) {
808
929
  const value = get(ctx.formData, name);
809
- if (value !== void 0) if (el.type === "checkbox") el.checked = value;
810
- else el.value = value;
930
+ if (value !== void 0) if (inputEl.type === "checkbox") inputEl.checked = value;
931
+ else inputEl.value = value;
811
932
  }
812
933
  if (previousEl && !el) {
813
934
  const timer = ctx.debounceTimers.get(name);
@@ -820,15 +941,21 @@ function createFieldRegistration(ctx, validate) {
820
941
  clearTimeout(schemaTimer);
821
942
  ctx.schemaValidationTimers.delete(name);
822
943
  }
944
+ const errorTimer = ctx.errorDelayTimers.get(name);
945
+ if (errorTimer) {
946
+ clearTimeout(errorTimer);
947
+ ctx.errorDelayTimers.delete(name);
948
+ }
949
+ ctx.pendingErrors.delete(name);
823
950
  ctx.validationRequestIds.delete(name);
951
+ ctx.fieldRefs.delete(name);
952
+ ctx.fieldOptions.delete(name);
953
+ ctx.fieldHandlers.delete(name);
824
954
  if (opts?.shouldUnregister ?? ctx.options.shouldUnregister ?? false) {
825
955
  unset(ctx.formData, name);
826
956
  clearFieldErrors(ctx.errors, name);
827
- clearFieldTouched(ctx.touchedFields, ctx.touchedFieldCount, name);
828
- clearFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
829
- ctx.fieldRefs.delete(name);
830
- ctx.fieldOptions.delete(name);
831
- ctx.fieldHandlers.delete(name);
957
+ clearFieldTouched(ctx.touchedFields, name);
958
+ clearFieldDirty(ctx.dirtyFields, name);
832
959
  }
833
960
  }
834
961
  };
@@ -849,7 +976,7 @@ function createFieldRegistration(ctx, validate) {
849
976
  get: () => get(ctx.formData, name),
850
977
  set: (val) => {
851
978
  set(ctx.formData, name, val);
852
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
979
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, val);
853
980
  }
854
981
  }) }
855
982
  };
@@ -858,8 +985,8 @@ function createFieldRegistration(ctx, validate) {
858
985
  const opts = options || {};
859
986
  if (!opts.keepValue) unset(ctx.formData, name);
860
987
  if (!opts.keepError) clearFieldErrors(ctx.errors, name);
861
- if (!opts.keepTouched) clearFieldTouched(ctx.touchedFields, ctx.touchedFieldCount, name);
862
- if (!opts.keepDirty) clearFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
988
+ if (!opts.keepTouched) clearFieldTouched(ctx.touchedFields, name);
989
+ if (!opts.keepDirty) clearFieldDirty(ctx.dirtyFields, name);
863
990
  ctx.fieldRefs.delete(name);
864
991
  ctx.fieldOptions.delete(name);
865
992
  ctx.fieldHandlers.delete(name);
@@ -874,6 +1001,8 @@ function createFieldRegistration(ctx, validate) {
874
1001
  ctx.schemaValidationTimers.delete(name);
875
1002
  }
876
1003
  ctx.validationRequestIds.delete(name);
1004
+ ctx.validationCache.delete(`${name}:partial`);
1005
+ ctx.validationCache.delete(`${name}:full`);
877
1006
  }
878
1007
  return {
879
1008
  register,
@@ -909,20 +1038,6 @@ function createFieldArrayManager(ctx, validate, setFocus) {
909
1038
  indexCache.set(item.key, idx);
910
1039
  });
911
1040
  };
912
- const appendToCache = (startIndex) => {
913
- const items = fa.items.value;
914
- for (let i = startIndex; i < items.length; i++) {
915
- const item = items[i];
916
- if (item) indexCache.set(item.key, i);
917
- }
918
- };
919
- const updateCacheAfterInsert = (insertIndex, _insertCount) => {
920
- const items = fa.items.value;
921
- for (let i = insertIndex; i < items.length; i++) {
922
- const item = items[i];
923
- if (item) indexCache.set(item.key, i);
924
- }
925
- };
926
1041
  const swapInCache = (indexA, indexB) => {
927
1042
  const items = fa.items.value;
928
1043
  const itemA = items[indexA];
@@ -963,10 +1078,32 @@ function createFieldArrayManager(ctx, validate, setFocus) {
963
1078
  const normalizeToArray = (value) => {
964
1079
  return Array.isArray(value) ? value : [value];
965
1080
  };
1081
+ const clearValidationCache = () => {
1082
+ for (const key of ctx.validationCache.keys()) {
1083
+ const colonIndex = key.lastIndexOf(":");
1084
+ const fieldPath = colonIndex > 0 ? key.slice(0, colonIndex) : key;
1085
+ if (fieldPath === name || fieldPath.startsWith(`${name}.`)) ctx.validationCache.delete(key);
1086
+ }
1087
+ };
1088
+ const validateIfNeeded = () => {
1089
+ const isTouched = ctx.touchedFields.value[name] === true;
1090
+ const hasSubmitted = ctx.submitCount.value > 0;
1091
+ if (shouldValidateOnChange(ctx.options.mode ?? "onSubmit", isTouched, ctx.options.reValidateMode, hasSubmitted)) validate(name);
1092
+ };
1093
+ const ensureSync = () => {
1094
+ const currentValues = get(ctx.formData, name) || [];
1095
+ if (fa.items.value.length !== currentValues.length) {
1096
+ 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})`);
1097
+ fa.items.value = currentValues.map(() => createItem(generateId()));
1098
+ rebuildIndexCache();
1099
+ }
1100
+ return currentValues;
1101
+ };
966
1102
  const append = (value, focusOptions) => {
1103
+ clearValidationCache();
967
1104
  const values = normalizeToArray(value);
968
1105
  if (values.length === 0) return true;
969
- const currentValues = get(ctx.formData, name) || [];
1106
+ const currentValues = ensureSync();
970
1107
  const insertIndex = currentValues.length;
971
1108
  const rules = fa.rules;
972
1109
  if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) {
@@ -979,17 +1116,22 @@ function createFieldArrayManager(ctx, validate, setFocus) {
979
1116
  const newValues = [...currentValues, ...values];
980
1117
  set(ctx.formData, name, newValues);
981
1118
  const newItems = values.map(() => createItem(generateId()));
982
- fa.items.value = [...fa.items.value, ...newItems];
983
- appendToCache(insertIndex);
984
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
985
- if (ctx.options.mode === "onChange") validate(name);
1119
+ const newItemsArray = [...fa.items.value, ...newItems];
1120
+ for (let i = insertIndex; i < newItemsArray.length; i++) {
1121
+ const item = newItemsArray[i];
1122
+ if (item) indexCache.set(item.key, i);
1123
+ }
1124
+ fa.items.value = newItemsArray;
1125
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1126
+ validateIfNeeded();
986
1127
  handleFocus(insertIndex, values.length, focusOptions);
987
1128
  return true;
988
1129
  };
989
1130
  const prepend = (value, focusOptions) => {
1131
+ clearValidationCache();
990
1132
  const values = normalizeToArray(value);
991
1133
  if (values.length === 0) return true;
992
- const currentValues = get(ctx.formData, name) || [];
1134
+ const currentValues = ensureSync();
993
1135
  const rules = fa.rules;
994
1136
  if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) {
995
1137
  if (__DEV__) warnArrayOperationRejected("prepend", name, "maxLength", {
@@ -1000,16 +1142,20 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1000
1142
  }
1001
1143
  const newValues = [...values, ...currentValues];
1002
1144
  set(ctx.formData, name, newValues);
1003
- const newItems = values.map(() => createItem(generateId()));
1004
- fa.items.value = [...newItems, ...fa.items.value];
1005
- updateCacheAfterInsert(0, values.length);
1006
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1007
- if (ctx.options.mode === "onChange") validate(name);
1145
+ const newItemsArray = [...values.map(() => createItem(generateId())), ...fa.items.value];
1146
+ for (let i = 0; i < newItemsArray.length; i++) {
1147
+ const item = newItemsArray[i];
1148
+ if (item) indexCache.set(item.key, i);
1149
+ }
1150
+ fa.items.value = newItemsArray;
1151
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1152
+ validateIfNeeded();
1008
1153
  handleFocus(0, values.length, focusOptions);
1009
1154
  return true;
1010
1155
  };
1011
1156
  const update = (index, value) => {
1012
- const currentValues = get(ctx.formData, name) || [];
1157
+ clearValidationCache();
1158
+ const currentValues = ensureSync();
1013
1159
  if (index < 0 || index >= currentValues.length) {
1014
1160
  if (__DEV__) warnArrayIndexOutOfBounds("update", name, index, currentValues.length);
1015
1161
  return false;
@@ -1017,12 +1163,13 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1017
1163
  const newValues = [...currentValues];
1018
1164
  newValues[index] = value;
1019
1165
  set(ctx.formData, name, newValues);
1020
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1021
- if (ctx.options.mode === "onChange") validate(name);
1166
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1167
+ validateIfNeeded();
1022
1168
  return true;
1023
1169
  };
1024
1170
  const removeAt = (index) => {
1025
- const currentValues = get(ctx.formData, name) || [];
1171
+ clearValidationCache();
1172
+ const currentValues = ensureSync();
1026
1173
  if (index < 0 || index >= currentValues.length) {
1027
1174
  if (__DEV__) warnArrayIndexOutOfBounds("remove", name, index, currentValues.length);
1028
1175
  return false;
@@ -1040,14 +1187,15 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1040
1187
  const keyToRemove = fa.items.value[index]?.key;
1041
1188
  fa.items.value = fa.items.value.filter((item) => item.key !== keyToRemove);
1042
1189
  if (keyToRemove) updateCacheAfterRemove(keyToRemove, index);
1043
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1044
- if (ctx.options.mode === "onChange") validate(name);
1190
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1191
+ validateIfNeeded();
1045
1192
  return true;
1046
1193
  };
1047
1194
  const insert = (index, value, focusOptions) => {
1195
+ clearValidationCache();
1048
1196
  const values = normalizeToArray(value);
1049
1197
  if (values.length === 0) return true;
1050
- const currentValues = get(ctx.formData, name) || [];
1198
+ const currentValues = ensureSync();
1051
1199
  const rules = fa.rules;
1052
1200
  if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) {
1053
1201
  if (__DEV__) warnArrayOperationRejected("insert", name, "maxLength", {
@@ -1056,27 +1204,35 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1056
1204
  });
1057
1205
  return false;
1058
1206
  }
1059
- const clampedIndex = Math.max(0, Math.min(index, currentValues.length));
1207
+ if (index < 0 || index > currentValues.length) {
1208
+ if (__DEV__) warnArrayIndexOutOfBounds("insert", name, index, currentValues.length);
1209
+ return false;
1210
+ }
1060
1211
  const newValues = [
1061
- ...currentValues.slice(0, clampedIndex),
1212
+ ...currentValues.slice(0, index),
1062
1213
  ...values,
1063
- ...currentValues.slice(clampedIndex)
1214
+ ...currentValues.slice(index)
1064
1215
  ];
1065
1216
  set(ctx.formData, name, newValues);
1066
1217
  const newItems = values.map(() => createItem(generateId()));
1067
- fa.items.value = [
1068
- ...fa.items.value.slice(0, clampedIndex),
1218
+ const newItemsArray = [
1219
+ ...fa.items.value.slice(0, index),
1069
1220
  ...newItems,
1070
- ...fa.items.value.slice(clampedIndex)
1221
+ ...fa.items.value.slice(index)
1071
1222
  ];
1072
- updateCacheAfterInsert(clampedIndex, values.length);
1073
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1074
- if (ctx.options.mode === "onChange") validate(name);
1075
- handleFocus(clampedIndex, values.length, focusOptions);
1223
+ for (let i = index; i < newItemsArray.length; i++) {
1224
+ const item = newItemsArray[i];
1225
+ if (item) indexCache.set(item.key, i);
1226
+ }
1227
+ fa.items.value = newItemsArray;
1228
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1229
+ validateIfNeeded();
1230
+ handleFocus(index, values.length, focusOptions);
1076
1231
  return true;
1077
1232
  };
1078
1233
  const swap = (indexA, indexB) => {
1079
- const currentValues = get(ctx.formData, name) || [];
1234
+ clearValidationCache();
1235
+ const currentValues = ensureSync();
1080
1236
  if (indexA < 0 || indexB < 0 || indexA >= currentValues.length || indexB >= currentValues.length) {
1081
1237
  if (__DEV__) warnArrayIndexOutOfBounds("swap", name, indexA < 0 || indexA >= currentValues.length ? indexA : indexB, currentValues.length);
1082
1238
  return false;
@@ -1093,51 +1249,52 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1093
1249
  fa.items.value = newItems;
1094
1250
  swapInCache(indexA, indexB);
1095
1251
  }
1096
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1097
- if (ctx.options.mode === "onChange") validate(name);
1252
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1253
+ validateIfNeeded();
1098
1254
  return true;
1099
1255
  };
1100
1256
  const move = (from, to) => {
1101
- const currentValues = get(ctx.formData, name) || [];
1102
- if (from < 0 || from >= currentValues.length || to < 0) {
1257
+ clearValidationCache();
1258
+ const currentValues = ensureSync();
1259
+ if (from < 0 || from >= currentValues.length || to < 0 || to >= currentValues.length) {
1103
1260
  if (__DEV__) warnArrayIndexOutOfBounds("move", name, from < 0 || from >= currentValues.length ? from : to, currentValues.length);
1104
1261
  return false;
1105
1262
  }
1106
1263
  const newValues = [...currentValues];
1107
1264
  const [removed] = newValues.splice(from, 1);
1108
1265
  if (removed !== void 0) {
1109
- const clampedTo = Math.min(to, newValues.length);
1110
- newValues.splice(clampedTo, 0, removed);
1266
+ newValues.splice(to, 0, removed);
1111
1267
  set(ctx.formData, name, newValues);
1112
1268
  }
1113
1269
  const newItems = [...fa.items.value];
1114
1270
  const [removedItem] = newItems.splice(from, 1);
1115
1271
  if (removedItem) {
1116
- const clampedTo = Math.min(to, newItems.length);
1117
- newItems.splice(clampedTo, 0, removedItem);
1272
+ newItems.splice(to, 0, removedItem);
1118
1273
  fa.items.value = newItems;
1119
- const minIdx = Math.min(from, clampedTo);
1120
- const maxIdx = Math.max(from, clampedTo);
1274
+ const minIdx = Math.min(from, to);
1275
+ const maxIdx = Math.max(from, to);
1121
1276
  const items = fa.items.value;
1122
1277
  for (let i = minIdx; i <= maxIdx; i++) {
1123
1278
  const item = items[i];
1124
1279
  if (item) indexCache.set(item.key, i);
1125
1280
  }
1126
1281
  }
1127
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1128
- if (ctx.options.mode === "onChange") validate(name);
1282
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1283
+ validateIfNeeded();
1129
1284
  return true;
1130
1285
  };
1131
1286
  const replace = (newValues) => {
1287
+ clearValidationCache();
1132
1288
  if (!Array.isArray(newValues)) return false;
1133
1289
  set(ctx.formData, name, newValues);
1134
1290
  fa.items.value = newValues.map(() => createItem(generateId()));
1135
1291
  rebuildIndexCache();
1136
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1137
- if (ctx.options.mode === "onChange") validate(name);
1292
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1293
+ validateIfNeeded();
1138
1294
  return true;
1139
1295
  };
1140
1296
  const removeAll = () => {
1297
+ clearValidationCache();
1141
1298
  const rules = fa.rules;
1142
1299
  if (rules?.minLength && rules.minLength.value > 0) {
1143
1300
  if (__DEV__) warnArrayOperationRejected("removeAll", name, "minLength", {
@@ -1149,12 +1306,13 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1149
1306
  set(ctx.formData, name, []);
1150
1307
  fa.items.value = [];
1151
1308
  indexCache.clear();
1152
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1153
- if (ctx.options.mode === "onChange") validate(name);
1309
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1310
+ validateIfNeeded();
1154
1311
  return true;
1155
1312
  };
1156
1313
  const removeMany = (indices) => {
1157
- const currentValues = get(ctx.formData, name) || [];
1314
+ clearValidationCache();
1315
+ const currentValues = ensureSync();
1158
1316
  const validIndices = indices.filter((i) => i >= 0 && i < currentValues.length);
1159
1317
  if (validIndices.length === 0) return true;
1160
1318
  const rules = fa.rules;
@@ -1176,12 +1334,14 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1176
1334
  fa.items.value.forEach((item, idx) => {
1177
1335
  indexCache.set(item.key, idx);
1178
1336
  });
1179
- markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1180
- if (ctx.options.mode === "onChange") validate(name);
1337
+ updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
1338
+ validateIfNeeded();
1181
1339
  return true;
1182
1340
  };
1183
1341
  return {
1184
- value: fa.items.value,
1342
+ get value() {
1343
+ return fa.items.value;
1344
+ },
1185
1345
  append,
1186
1346
  prepend,
1187
1347
  remove: removeAt,
@@ -1196,20 +1356,16 @@ function createFieldArrayManager(ctx, validate, setFocus) {
1196
1356
  }
1197
1357
  return { fields };
1198
1358
  }
1199
- function syncUncontrolledInputs(fieldRefs, fieldOptions, formData) {
1200
- for (const [name, fieldRef] of Array.from(fieldRefs.entries())) {
1201
- const el = fieldRef.value;
1202
- if (el) {
1203
- if (!fieldOptions.get(name)?.controlled) set(formData, name, el.type === "checkbox" ? el.checked : el.value);
1204
- }
1205
- }
1206
- }
1207
- function updateDomElement(el, value) {
1208
- if (el.type === "checkbox") el.checked = value;
1209
- else el.value = value;
1359
+ var isCalledFromController = false;
1360
+ function setCalledFromController(value) {
1361
+ isCalledFromController = value;
1210
1362
  }
1211
1363
  function useForm(options) {
1212
1364
  const ctx = createFormContext(options);
1365
+ let isSubmissionLocked = false;
1366
+ (0, vue.onScopeDispose)(() => {
1367
+ ctx.cleanup();
1368
+ });
1213
1369
  const { validate, clearAllPendingErrors } = createValidation(ctx);
1214
1370
  const { register, unregister } = createFieldRegistration(ctx, validate);
1215
1371
  function setFocus(name, focusOptions) {
@@ -1223,11 +1379,10 @@ function useForm(options) {
1223
1379
  }
1224
1380
  const fieldRef = ctx.fieldRefs.get(name);
1225
1381
  if (!fieldRef?.value) return;
1226
- const el = fieldRef.value;
1227
- if (typeof el.focus === "function") {
1228
- el.focus();
1229
- if (focusOptions?.shouldSelect && el instanceof HTMLInputElement && typeof el.select === "function") el.select();
1230
- }
1382
+ const el = getFocusableElement(fieldRef.value);
1383
+ if (!el) return;
1384
+ el.focus();
1385
+ if (focusOptions?.shouldSelect && el instanceof HTMLInputElement && typeof el.select === "function") el.select();
1231
1386
  }
1232
1387
  const setFocusWrapper = (name) => setFocus(name);
1233
1388
  const { fields } = createFieldArrayManager(ctx, validate, setFocusWrapper);
@@ -1252,10 +1407,10 @@ function useForm(options) {
1252
1407
  };
1253
1408
  return cachedMergedErrors;
1254
1409
  }
1255
- const isDirtyComputed = (0, vue.computed)(() => ctx.dirtyFieldCount.value > 0);
1410
+ const isDirtyComputed = (0, vue.computed)(() => Object.keys(ctx.dirtyFields.value).length > 0);
1256
1411
  const errorsComputed = (0, vue.computed)(() => getMergedErrors());
1257
1412
  const isValidComputed = (0, vue.computed)(() => {
1258
- if (!(ctx.submitCount.value > 0 || ctx.touchedFieldCount.value > 0)) return false;
1413
+ if (!(ctx.submitCount.value > 0 || Object.keys(ctx.touchedFields.value).length > 0)) return false;
1259
1414
  return Object.keys(errorsComputed.value).length === 0;
1260
1415
  });
1261
1416
  const isReadyComputed = (0, vue.computed)(() => !ctx.isLoading.value);
@@ -1313,7 +1468,8 @@ function useForm(options) {
1313
1468
  return async (e) => {
1314
1469
  e.preventDefault();
1315
1470
  if (ctx.isDisabled.value) return;
1316
- if (ctx.isSubmitting.value) return;
1471
+ if (isSubmissionLocked) return;
1472
+ isSubmissionLocked = true;
1317
1473
  ctx.isSubmitting.value = true;
1318
1474
  ctx.submitCount.value++;
1319
1475
  ctx.isSubmitSuccessful.value = false;
@@ -1331,6 +1487,7 @@ function useForm(options) {
1331
1487
  }
1332
1488
  } finally {
1333
1489
  ctx.isSubmitting.value = false;
1490
+ isSubmissionLocked = false;
1334
1491
  }
1335
1492
  };
1336
1493
  }
@@ -1344,8 +1501,9 @@ function useForm(options) {
1344
1501
  }
1345
1502
  }
1346
1503
  set(ctx.formData, name, value);
1347
- if (setValueOptions?.shouldDirty !== false) markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1348
- if (setValueOptions?.shouldTouch) markFieldTouched(ctx.touchedFields, ctx.touchedFieldCount, name);
1504
+ if (setValueOptions?.shouldDirty === false) clearFieldDirty(ctx.dirtyFields, name);
1505
+ else updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, value);
1506
+ if (setValueOptions?.shouldTouch) markFieldTouched(ctx.touchedFields, name);
1349
1507
  if (!ctx.fieldOptions.get(name)?.controlled) {
1350
1508
  const fieldRef = ctx.fieldRefs.get(name);
1351
1509
  if (fieldRef?.value) updateDomElement(fieldRef.value, value);
@@ -1354,31 +1512,34 @@ function useForm(options) {
1354
1512
  }
1355
1513
  function reset(values, resetOptions) {
1356
1514
  const opts = resetOptions || {};
1515
+ ctx.validationCache.clear();
1357
1516
  ctx.resetGeneration.value++;
1358
1517
  clearAllPendingErrors();
1359
1518
  ctx.validatingFields.value = /* @__PURE__ */ new Set();
1360
- ctx.validationCache.clear();
1361
1519
  for (const timer of ctx.schemaValidationTimers.values()) clearTimeout(timer);
1362
1520
  ctx.schemaValidationTimers.clear();
1363
- if (!opts.keepDefaultValues && values) Object.assign(ctx.defaultValues, values);
1521
+ for (const timer of ctx.debounceTimers.values()) clearTimeout(timer);
1522
+ ctx.debounceTimers.clear();
1523
+ ctx.validationRequestIds.clear();
1524
+ if (!opts.keepDefaultValues && values) {
1525
+ Object.assign(ctx.defaultValues, values);
1526
+ ctx.defaultValueHashes.clear();
1527
+ }
1364
1528
  Object.keys(ctx.formData).forEach((key) => delete ctx.formData[key]);
1365
1529
  const newValues = deepClone(values || ctx.defaultValues);
1366
1530
  Object.assign(ctx.formData, newValues);
1367
- if (!opts.keepErrors) ctx.errors.value = {};
1368
- if (!opts.keepTouched) {
1369
- ctx.touchedFields.value = {};
1370
- ctx.touchedFieldCount.value = 0;
1371
- }
1372
- if (!opts.keepDirty) {
1373
- ctx.dirtyFields.value = {};
1374
- ctx.dirtyFieldCount.value = 0;
1531
+ if (!opts.keepErrors) {
1532
+ ctx.errors.value = {};
1533
+ ctx.persistentErrorFields.clear();
1375
1534
  }
1535
+ if (!opts.keepTouched) ctx.touchedFields.value = {};
1536
+ if (!opts.keepDirty) ctx.dirtyFields.value = {};
1376
1537
  if (!opts.keepSubmitCount) ctx.submitCount.value = 0;
1377
1538
  if (!opts.keepIsSubmitting) ctx.isSubmitting.value = false;
1378
1539
  if (!opts.keepIsSubmitSuccessful) ctx.isSubmitSuccessful.value = false;
1379
1540
  ctx.fieldArrays.clear();
1380
1541
  for (const [name, fieldRef] of Array.from(ctx.fieldRefs.entries())) {
1381
- const el = fieldRef.value;
1542
+ const el = getInputElement(fieldRef.value);
1382
1543
  if (el) {
1383
1544
  const value = get(newValues, name);
1384
1545
  if (value !== void 0) if (el.type === "checkbox") el.checked = value;
@@ -1397,6 +1558,8 @@ function useForm(options) {
1397
1558
  }
1398
1559
  const opts = resetFieldOptions || {};
1399
1560
  ctx.resetGeneration.value++;
1561
+ ctx.validationCache.delete(`${name}:partial`);
1562
+ ctx.validationCache.delete(`${name}:full`);
1400
1563
  const errorTimer = ctx.errorDelayTimers.get(name);
1401
1564
  if (errorTimer) {
1402
1565
  clearTimeout(errorTimer);
@@ -1405,15 +1568,21 @@ function useForm(options) {
1405
1568
  ctx.pendingErrors.delete(name);
1406
1569
  let defaultValue = opts.defaultValue;
1407
1570
  if (defaultValue === void 0) defaultValue = get(ctx.defaultValues, name);
1408
- else set(ctx.defaultValues, name, defaultValue);
1571
+ else {
1572
+ set(ctx.defaultValues, name, defaultValue);
1573
+ ctx.defaultValueHashes.set(name, hashValue(defaultValue));
1574
+ }
1409
1575
  const clonedValue = defaultValue !== void 0 ? deepClone(defaultValue) : void 0;
1410
1576
  set(ctx.formData, name, clonedValue);
1411
1577
  if (!opts.keepError) clearFieldErrors(ctx.errors, name);
1412
- if (!opts.keepDirty) clearFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1413
- if (!opts.keepTouched) clearFieldTouched(ctx.touchedFields, ctx.touchedFieldCount, name);
1578
+ if (!opts.keepDirty) clearFieldDirty(ctx.dirtyFields, name);
1579
+ if (!opts.keepTouched) clearFieldTouched(ctx.touchedFields, name);
1414
1580
  if (!ctx.fieldOptions.get(name)?.controlled) {
1415
1581
  const fieldRef = ctx.fieldRefs.get(name);
1416
- if (fieldRef?.value) updateDomElement(fieldRef.value, clonedValue ?? (fieldRef.value.type === "checkbox" ? false : ""));
1582
+ if (fieldRef?.value) {
1583
+ const el = getInputElement(fieldRef.value);
1584
+ updateDomElement(fieldRef.value, clonedValue ?? (el?.type === "checkbox" ? false : ""));
1585
+ }
1417
1586
  }
1418
1587
  }
1419
1588
  function watch$1(name) {
@@ -1453,10 +1622,16 @@ function useForm(options) {
1453
1622
  }
1454
1623
  if (name === void 0) {
1455
1624
  ctx.errors.value = {};
1625
+ ctx.externalErrors.value = {};
1626
+ ctx.persistentErrorFields.clear();
1456
1627
  return;
1457
1628
  }
1458
1629
  const fieldsToClean = Array.isArray(name) ? name : [name];
1459
- for (const field of fieldsToClean) clearFieldErrors(ctx.errors, field);
1630
+ for (const field of fieldsToClean) {
1631
+ clearFieldErrors(ctx.errors, field);
1632
+ clearFieldErrors(ctx.externalErrors, field);
1633
+ ctx.persistentErrorFields.delete(field);
1634
+ }
1460
1635
  }
1461
1636
  function setError(name, error) {
1462
1637
  const newErrors = { ...ctx.errors.value };
@@ -1465,6 +1640,7 @@ function useForm(options) {
1465
1640
  message: error.message
1466
1641
  } : error.message);
1467
1642
  ctx.errors.value = newErrors;
1643
+ if (error.persistent) ctx.persistentErrorFields.add(name);
1468
1644
  }
1469
1645
  function setErrors(errors, options$1) {
1470
1646
  const newErrors = options$1?.shouldReplace ? {} : { ...ctx.errors.value };
@@ -1517,6 +1693,7 @@ function useForm(options) {
1517
1693
  const schemaResult = validatePathAgainstSchema(ctx.options.schema, name);
1518
1694
  if (!schemaResult.valid) warnPathNotInSchema("getFieldState", name, schemaResult.availableFields);
1519
1695
  }
1696
+ if ((0, vue.getCurrentInstance)() && !isCalledFromController) console.warn(`[vue-hook-form] getFieldState('${name}') returns a snapshot, not reactive refs.\nFor reactive error display, use one of these alternatives:\n • formState.value.errors.${name}\n • const { fieldState } = useController({ name: '${name}', control })\n • const ${name}Error = computed(() => formState.value.errors.${name})\n\nSee docs: https://github.com/vuehookform/core#common-mistakes`);
1520
1697
  }
1521
1698
  const error = get(ctx.errors.value, name);
1522
1699
  return {
@@ -1526,7 +1703,7 @@ function useForm(options) {
1526
1703
  error
1527
1704
  };
1528
1705
  }
1529
- async function trigger(name) {
1706
+ async function trigger(name, options$1) {
1530
1707
  if (__DEV__ && name) {
1531
1708
  const names = Array.isArray(name) ? name : [name];
1532
1709
  for (const n of names) {
@@ -1538,6 +1715,7 @@ function useForm(options) {
1538
1715
  }
1539
1716
  }
1540
1717
  }
1718
+ if (options$1?.markAsSubmitted) ctx.submitCount.value++;
1541
1719
  if (name === void 0) return await validate();
1542
1720
  if (Array.isArray(name)) {
1543
1721
  let allValid = true;
@@ -1565,7 +1743,11 @@ function useForm(options) {
1565
1743
  getValues,
1566
1744
  getFieldState,
1567
1745
  trigger,
1568
- setFocus
1746
+ setFocus,
1747
+ options: {
1748
+ mode: ctx.options.mode ?? "onSubmit",
1749
+ reValidateMode: ctx.options.reValidateMode
1750
+ }
1569
1751
  };
1570
1752
  }
1571
1753
  const FormContextKey = Symbol("FormContext");
@@ -1604,13 +1786,22 @@ function useController(options) {
1604
1786
  }
1605
1787
  });
1606
1788
  const onChange = (newValue) => {
1607
- form.setValue(name, newValue);
1789
+ const isTouched = form.formState.value.touchedFields[name] === true;
1790
+ const hasSubmitted = form.formState.value.submitCount > 0;
1791
+ const mode = form.options.mode ?? "onSubmit";
1792
+ const reValidateMode = form.options.reValidateMode;
1793
+ const shouldValidate = shouldValidateOnChange(mode, isTouched, reValidateMode, hasSubmitted);
1794
+ form.setValue(name, newValue, { shouldValidate });
1608
1795
  };
1609
1796
  const onBlur = () => {
1797
+ const hasSubmitted = form.formState.value.submitCount > 0;
1798
+ const mode = form.options.mode ?? "onSubmit";
1799
+ const reValidateMode = form.options.reValidateMode;
1800
+ const shouldValidate = shouldValidateOnBlur(mode, hasSubmitted, reValidateMode);
1610
1801
  const currentValue = form.getValues(name);
1611
1802
  form.setValue(name, currentValue, {
1612
1803
  shouldTouch: true,
1613
- shouldValidate: true,
1804
+ shouldValidate,
1614
1805
  shouldDirty: false
1615
1806
  });
1616
1807
  };
@@ -1618,7 +1809,10 @@ function useController(options) {
1618
1809
  elementRef.value = el;
1619
1810
  };
1620
1811
  const fieldState = (0, vue.computed)(() => {
1621
- return form.getFieldState(name);
1812
+ setCalledFromController(true);
1813
+ const state = form.getFieldState(name);
1814
+ setCalledFromController(false);
1815
+ return state;
1622
1816
  });
1623
1817
  return {
1624
1818
  field: {
@@ -1649,8 +1843,13 @@ function isFieldError(error) {
1649
1843
  return typeof error === "object" && error !== null && "type" in error && "message" in error && typeof error.type === "string" && typeof error.message === "string";
1650
1844
  }
1651
1845
  exports.FormContextKey = FormContextKey;
1846
+ exports.clearPathCache = clearPathCache;
1847
+ exports.generateId = generateId;
1848
+ exports.get = get;
1652
1849
  exports.isFieldError = isFieldError;
1653
1850
  exports.provideForm = provideForm;
1851
+ exports.set = set;
1852
+ exports.unset = unset;
1654
1853
  exports.useController = useController;
1655
1854
  exports.useForm = useForm;
1656
1855
  exports.useFormContext = useFormContext;