@vuehookform/core 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,21 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let vue = require("vue");
3
+ var pathCache = /* @__PURE__ */ new Map();
4
+ var PATH_CACHE_MAX_SIZE = 256;
5
+ function getPathSegments(path) {
6
+ let segments = pathCache.get(path);
7
+ if (segments) return segments;
8
+ segments = path.split(".");
9
+ if (pathCache.size >= PATH_CACHE_MAX_SIZE) {
10
+ const firstKey = pathCache.keys().next().value;
11
+ if (firstKey !== void 0) pathCache.delete(firstKey);
12
+ }
13
+ pathCache.set(path, segments);
14
+ return segments;
15
+ }
3
16
  function get(obj, path) {
4
17
  if (!path || obj === null || obj === void 0) return obj;
5
- const keys = path.split(".");
18
+ const keys = getPathSegments(path);
6
19
  let result = obj;
7
20
  for (const key of keys) {
8
21
  if (result === null || result === void 0) return;
@@ -12,7 +25,7 @@ function get(obj, path) {
12
25
  }
13
26
  function set(obj, path, value) {
14
27
  if (!path) return;
15
- const keys = path.split(".");
28
+ const keys = getPathSegments(path).slice();
16
29
  const UNSAFE_KEYS = [
17
30
  "__proto__",
18
31
  "constructor",
@@ -37,7 +50,7 @@ function set(obj, path, value) {
37
50
  }
38
51
  function unset(obj, path) {
39
52
  if (!path) return;
40
- const keys = path.split(".");
53
+ const keys = getPathSegments(path).slice();
41
54
  const lastKey = keys.pop();
42
55
  let current = obj;
43
56
  for (const key of keys) {
@@ -53,11 +66,35 @@ function generateId() {
53
66
  const random = Math.random().toString(36).substring(2, 11);
54
67
  return `field_${Date.now()}_${idCounter++}_${random}`;
55
68
  }
56
- const __DEV__ = false;
69
+ function deepClone(obj) {
70
+ if (obj === null || obj === void 0) return obj;
71
+ if (typeof obj !== "object") return obj;
72
+ if (obj instanceof Date) return new Date(obj.getTime());
73
+ if (Array.isArray(obj)) return obj.map((item) => deepClone(item));
74
+ const cloned = {};
75
+ for (const key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) cloned[key] = deepClone(obj[key]);
76
+ return cloned;
77
+ }
78
+ var proc = globalThis.process;
79
+ const __DEV__ = proc?.env?.NODE_ENV !== "production";
57
80
  var warnedMessages = /* @__PURE__ */ new Set();
58
- function warnOnce(message, key) {}
59
- function warn(message) {}
81
+ function warnOnce(message, key) {
82
+ if (!__DEV__) return;
83
+ const cacheKey = key ?? message;
84
+ if (warnedMessages.has(cacheKey)) return;
85
+ warnedMessages.add(cacheKey);
86
+ console.warn(`[vue-hook-form] ${message}`);
87
+ }
88
+ function warn(message) {
89
+ if (!__DEV__) return;
90
+ console.warn(`[vue-hook-form] ${message}`);
91
+ }
60
92
  function validatePathSyntax(path) {
93
+ if (!__DEV__) return null;
94
+ if (!path || path.trim() === "") return "Path cannot be empty";
95
+ if (path.startsWith(".") || path.endsWith(".") || path.includes("..")) return `Invalid path "${path}": contains empty segments`;
96
+ if (path.includes("[")) return `Invalid path "${path}": use dot notation (e.g., "items.0") instead of bracket notation (e.g., "items[0]")`;
97
+ if (/\s/.test(path)) return `Invalid path "${path}": paths cannot contain whitespace`;
61
98
  return null;
62
99
  }
63
100
  function traverseSchemaPath(schema, path) {
@@ -67,7 +104,7 @@ function traverseSchemaPath(schema, path) {
67
104
  const segment = segments[i];
68
105
  if (!segment) continue;
69
106
  currentSchema = unwrapSchema(currentSchema);
70
- if (isZodObject(currentSchema)) {
107
+ if (isZodObject$1(currentSchema)) {
71
108
  const shape = currentSchema.shape;
72
109
  if (segment in shape) {
73
110
  const nextSchema = shape[segment];
@@ -82,7 +119,7 @@ function traverseSchemaPath(schema, path) {
82
119
  segmentIndex: i
83
120
  };
84
121
  }
85
- if (isZodArray(currentSchema) && /^\d+$/.test(segment)) {
122
+ if (isZodArray$1(currentSchema) && /^\d+$/.test(segment)) {
86
123
  currentSchema = currentSchema.element;
87
124
  continue;
88
125
  }
@@ -94,32 +131,93 @@ function traverseSchemaPath(schema, path) {
94
131
  return { schema: currentSchema };
95
132
  }
96
133
  function validatePathAgainstSchema(schema, path) {
97
- return { valid: true };
134
+ if (!__DEV__) return { valid: true };
135
+ try {
136
+ const result = traverseSchemaPath(schema, path);
137
+ if ("error" in result) return {
138
+ valid: false,
139
+ reason: result.error,
140
+ availableFields: result.availableFields
141
+ };
142
+ return { valid: true };
143
+ } catch {
144
+ return { valid: true };
145
+ }
98
146
  }
99
147
  function isArrayFieldInSchema(schema, path) {
100
- return null;
148
+ if (!__DEV__) return null;
149
+ try {
150
+ const result = traverseSchemaPath(schema, path);
151
+ if ("error" in result) return null;
152
+ return isZodArray$1(unwrapSchema(result.schema));
153
+ } catch {
154
+ return null;
155
+ }
101
156
  }
102
- function warnInvalidPath(fnName, path, reason) {}
103
- function warnPathNotInSchema(fnName, path, availableFields) {}
104
- function warnFieldsOnNonArray(path) {}
105
- function warnArrayOperationRejected(operation, path, reason, details) {}
106
- function warnArrayIndexOutOfBounds(operation, path, index, length) {}
107
- function getDefProp(schema, prop) {
157
+ function warnInvalidPath(fnName, path, reason) {
158
+ if (!__DEV__) return;
159
+ let message = `${fnName}("${path}"): ${reason}`;
160
+ if (reason.includes("bracket notation")) {
161
+ const fixedPath = path.replace(/\[(\d+)\]/g, ".$1");
162
+ message += `\n FIX: Use dot notation for array indices`;
163
+ message += `\n EXAMPLE: ${fnName}("${fixedPath}")`;
164
+ } else if (reason.includes("empty")) {
165
+ message += `\n FIX: Provide a non-empty field path`;
166
+ message += `\n EXAMPLE: ${fnName}("email") or ${fnName}("user.address.city")`;
167
+ } else if (reason.includes("whitespace")) {
168
+ const fixedPath = path.replace(/\s/g, "");
169
+ message += `\n FIX: Remove spaces from the field path`;
170
+ message += `\n EXAMPLE: ${fnName}("${fixedPath}")`;
171
+ } else if (reason.includes("empty segments")) {
172
+ const fixedPath = path.replace(/\.{2,}/g, ".").replace(/^\./, "").replace(/\.$/, "");
173
+ message += `\n FIX: Remove extra dots from the path`;
174
+ message += `\n EXAMPLE: ${fnName}("${fixedPath}")`;
175
+ }
176
+ warnOnce(message, `invalid-path:${fnName}:${path}`);
177
+ }
178
+ function warnPathNotInSchema(fnName, path, availableFields) {
179
+ if (!__DEV__) return;
180
+ let message = `${fnName}("${path}"): Path does not exist in your Zod schema.`;
181
+ message += `\n FIX: Check that the path matches your schema definition exactly (case-sensitive)`;
182
+ if (availableFields && availableFields.length > 0) {
183
+ const pathLower = path.toLowerCase();
184
+ const suggestions = availableFields.filter((f) => f.toLowerCase().includes(pathLower) || pathLower.includes(f.toLowerCase()));
185
+ if (suggestions.length > 0) message += `\n DID YOU MEAN: ${suggestions.slice(0, 3).map((s) => `"${s}"`).join(", ")}`;
186
+ message += `\n AVAILABLE: ${availableFields.slice(0, 8).join(", ")}${availableFields.length > 8 ? "..." : ""}`;
187
+ }
188
+ warnOnce(message, `path-not-in-schema:${fnName}:${path}`);
189
+ }
190
+ function warnFieldsOnNonArray(path) {
191
+ if (!__DEV__) return;
192
+ warnOnce(`fields("${path}"): Expected an array field, but this path does not point to an array in your schema. The fields() method is only for array fields. Use register() for non-array fields.`, `fields-non-array:${path}`);
193
+ }
194
+ function warnArrayOperationRejected(operation, path, reason, details) {
195
+ if (!__DEV__) return;
196
+ warn(`${operation}() on "${path}": ${{
197
+ maxLength: details ? `Would exceed maxLength (current: ${details.current}, max: ${details.limit})` : "Would exceed maxLength rule",
198
+ minLength: details ? `Would violate minLength (current: ${details.current}, min: ${details.limit})` : "Would violate minLength rule"
199
+ }[reason]}. Operation was silently ignored.`);
200
+ }
201
+ function warnArrayIndexOutOfBounds(operation, path, index, length) {
202
+ if (!__DEV__) return;
203
+ warn(`${operation}() on "${path}": Index ${index} is out of bounds (array length: ${length}). Operation was silently ignored.`);
204
+ }
205
+ function getDefProp$1(schema, prop) {
108
206
  return schema.def[prop];
109
207
  }
110
208
  function getTypeName(schema) {
111
- return getDefProp(schema, "typeName");
209
+ return getDefProp$1(schema, "typeName");
112
210
  }
113
- function isZodObject(schema) {
211
+ function isZodObject$1(schema) {
114
212
  return getTypeName(schema) === "ZodObject";
115
213
  }
116
- function isZodArray(schema) {
214
+ function isZodArray$1(schema) {
117
215
  return getTypeName(schema) === "ZodArray";
118
216
  }
119
217
  function unwrapSchema(schema) {
120
218
  const typeName = getTypeName(schema);
121
- const innerType = getDefProp(schema, "innerType");
122
- const schemaType = getDefProp(schema, "schema");
219
+ const innerType = getDefProp$1(schema, "innerType");
220
+ const schemaType = getDefProp$1(schema, "schema");
123
221
  if ((typeName === "ZodOptional" || typeName === "ZodNullable" || typeName === "ZodDefault") && innerType) return unwrapSchema(innerType);
124
222
  if (typeName === "ZodEffects" && schemaType) return unwrapSchema(schemaType);
125
223
  return schema;
@@ -152,7 +250,7 @@ function createFormContext(options) {
152
250
  const submitCount = (0, vue.ref)(0);
153
251
  const defaultValuesError = (0, vue.ref)(null);
154
252
  const isSubmitSuccessful = (0, vue.ref)(false);
155
- const validatingFields = (0, vue.shallowRef)({});
253
+ const validatingFields = (0, vue.shallowRef)(/* @__PURE__ */ new Set());
156
254
  const externalErrors = (0, vue.shallowRef)({});
157
255
  const errorDelayTimers = /* @__PURE__ */ new Map();
158
256
  const pendingErrors = /* @__PURE__ */ new Map();
@@ -163,6 +261,10 @@ function createFormContext(options) {
163
261
  const debounceTimers = /* @__PURE__ */ new Map();
164
262
  const validationRequestIds = /* @__PURE__ */ new Map();
165
263
  const resetGeneration = (0, vue.ref)(0);
264
+ const dirtyFieldCount = (0, vue.ref)(0);
265
+ const touchedFieldCount = (0, vue.ref)(0);
266
+ const validationCache = /* @__PURE__ */ new Map();
267
+ const schemaValidationTimers = /* @__PURE__ */ new Map();
166
268
  const isDisabled = (0, vue.ref)(false);
167
269
  if (options.disabled !== void 0) {
168
270
  isDisabled.value = (0, vue.toValue)(options.disabled) ?? false;
@@ -220,24 +322,136 @@ function createFormContext(options) {
220
322
  validationRequestIds,
221
323
  resetGeneration,
222
324
  isDisabled,
325
+ dirtyFieldCount,
326
+ touchedFieldCount,
327
+ validationCache,
328
+ schemaValidationTimers,
223
329
  options
224
330
  };
225
331
  }
332
+ var uniqueIdCounter = 0;
333
+ function hashValue(value) {
334
+ if (value === null) return "null";
335
+ if (value === void 0) return "undefined";
336
+ const type = typeof value;
337
+ if (type === "string") return `s:${value}`;
338
+ if (type === "number") return `n:${value}`;
339
+ if (type === "boolean") return `b:${value}`;
340
+ if (type === "object") try {
341
+ return `o:${JSON.stringify(value)}`;
342
+ } catch {
343
+ return `o:_${++uniqueIdCounter}`;
344
+ }
345
+ return `x:_${++uniqueIdCounter}`;
346
+ }
347
+ function getDefType(schema) {
348
+ return schema._def?.type;
349
+ }
350
+ function getDefProp(schema, prop) {
351
+ return schema._def?.[prop];
352
+ }
353
+ function isZodObject(schema) {
354
+ return getDefType(schema) === "object";
355
+ }
356
+ function isZodArray(schema) {
357
+ return getDefType(schema) === "array";
358
+ }
359
+ function hasChecks(schema) {
360
+ const checks = getDefProp(schema, "checks");
361
+ return Array.isArray(checks) && checks.length > 0;
362
+ }
363
+ function unwrapNonEffects(schema) {
364
+ const type = getDefType(schema);
365
+ const innerType = getDefProp(schema, "innerType");
366
+ if ((type === "optional" || type === "nullable" || type === "default") && innerType) return unwrapNonEffects(innerType);
367
+ return schema;
368
+ }
369
+ var analysisCache = /* @__PURE__ */ new WeakMap();
370
+ function hasRootEffects(schema) {
371
+ return hasChecks(schema);
372
+ }
373
+ function extractSubSchema(schema, path) {
374
+ const segments = path.split(".");
375
+ let currentSchema = schema;
376
+ let hasEffects = false;
377
+ for (const segment of segments) {
378
+ if (!segment) continue;
379
+ if (hasChecks(currentSchema)) hasEffects = true;
380
+ const unwrapped = unwrapNonEffects(currentSchema);
381
+ if (hasChecks(unwrapped)) hasEffects = true;
382
+ if (isZodObject(unwrapped)) {
383
+ const shape = getDefProp(unwrapped, "shape");
384
+ if (!shape || !(segment in shape)) return null;
385
+ currentSchema = shape[segment];
386
+ } else if (isZodArray(unwrapped) && /^\d+$/.test(segment)) {
387
+ const element = getDefProp(unwrapped, "element");
388
+ if (!element) return null;
389
+ currentSchema = element;
390
+ } else return null;
391
+ }
392
+ const finalUnwrapped = unwrapNonEffects(currentSchema);
393
+ const finalChecks = getDefProp(finalUnwrapped, "checks");
394
+ if (finalChecks) {
395
+ for (const check of finalChecks) if (check && typeof check === "object" && "type" in check && check.type === "custom") {
396
+ hasEffects = true;
397
+ break;
398
+ }
399
+ }
400
+ return {
401
+ schema: finalUnwrapped,
402
+ hasEffects
403
+ };
404
+ }
405
+ function analyzeSchemaPath(schema, path) {
406
+ let cache = analysisCache.get(schema);
407
+ if (!cache) {
408
+ cache = /* @__PURE__ */ new Map();
409
+ analysisCache.set(schema, cache);
410
+ }
411
+ const cached = cache.get(path);
412
+ if (cached) return cached;
413
+ if (hasRootEffects(schema)) {
414
+ const result$1 = {
415
+ canPartialValidate: false,
416
+ reason: "root-checks"
417
+ };
418
+ cache.set(path, result$1);
419
+ return result$1;
420
+ }
421
+ const extracted = extractSubSchema(schema, path);
422
+ if (!extracted) {
423
+ const result$1 = {
424
+ canPartialValidate: false,
425
+ reason: "invalid-path"
426
+ };
427
+ cache.set(path, result$1);
428
+ return result$1;
429
+ }
430
+ if (extracted.hasEffects) {
431
+ const result$1 = {
432
+ canPartialValidate: false,
433
+ reason: "path-checks"
434
+ };
435
+ cache.set(path, result$1);
436
+ return result$1;
437
+ }
438
+ const result = {
439
+ canPartialValidate: true,
440
+ subSchema: extracted.schema
441
+ };
442
+ cache.set(path, result);
443
+ return result;
444
+ }
226
445
  function clearFieldErrors$1(errors, fieldPath) {
227
446
  const newErrors = { ...errors };
228
447
  for (const key of Object.keys(newErrors)) if (key === fieldPath || key.startsWith(`${fieldPath}.`)) delete newErrors[key];
229
448
  return newErrors;
230
449
  }
231
450
  function setValidating(ctx, fieldPath, isValidating) {
232
- if (isValidating) ctx.validatingFields.value = {
233
- ...ctx.validatingFields.value,
234
- [fieldPath]: true
235
- };
236
- else {
237
- const newValidating = { ...ctx.validatingFields.value };
238
- delete newValidating[fieldPath];
239
- ctx.validatingFields.value = newValidating;
240
- }
451
+ const newSet = new Set(ctx.validatingFields.value);
452
+ if (isValidating) newSet.add(fieldPath);
453
+ else newSet.delete(fieldPath);
454
+ ctx.validatingFields.value = newSet;
241
455
  }
242
456
  function groupErrorsByPath(issues) {
243
457
  const grouped = /* @__PURE__ */ new Map();
@@ -279,6 +493,18 @@ function createValidation(ctx) {
279
493
  if (!ctx.options.shouldUseNativeValidation) return;
280
494
  for (const [path] of ctx.fieldRefs) applyNativeValidation(path, null);
281
495
  }
496
+ function scheduleErrorsBatch(errors) {
497
+ if ((ctx.options.delayError || 0) <= 0) {
498
+ const newErrors = { ...ctx.errors.value };
499
+ for (const [fieldPath, error] of errors) {
500
+ set(newErrors, fieldPath, error);
501
+ applyNativeValidation(fieldPath, typeof error === "string" ? error : error.message);
502
+ }
503
+ ctx.errors.value = newErrors;
504
+ return;
505
+ }
506
+ for (const [fieldPath, error] of errors) scheduleError(fieldPath, error);
507
+ }
282
508
  function scheduleError(fieldPath, error) {
283
509
  const delayMs = ctx.options.delayError || 0;
284
510
  const errorMessage = typeof error === "string" ? error : error.message;
@@ -320,42 +546,108 @@ function createValidation(ctx) {
320
546
  ctx.errorDelayTimers.clear();
321
547
  ctx.pendingErrors.clear();
322
548
  }
549
+ async function validateFieldPartial(fieldPath, subSchema, valueHash, criteriaMode, generationAtStart) {
550
+ const fieldValue = get(ctx.formData, fieldPath);
551
+ setValidating(ctx, fieldPath, true);
552
+ try {
553
+ const result = await subSchema.safeParseAsync(fieldValue);
554
+ if (ctx.resetGeneration.value !== generationAtStart) return true;
555
+ if (result.success) {
556
+ ctx.errors.value = cancelError(fieldPath);
557
+ if (valueHash) ctx.validationCache.set(fieldPath, {
558
+ hash: valueHash,
559
+ isValid: true
560
+ });
561
+ return true;
562
+ }
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);
568
+ const grouped = groupErrorsByPath(fieldErrors);
569
+ const errorBatch = [];
570
+ for (const [path, errors] of grouped) errorBatch.push([path, createFieldError(errors, criteriaMode)]);
571
+ scheduleErrorsBatch(errorBatch);
572
+ if (valueHash) ctx.validationCache.set(fieldPath, {
573
+ hash: valueHash,
574
+ isValid: false
575
+ });
576
+ return false;
577
+ } finally {
578
+ setValidating(ctx, fieldPath, false);
579
+ }
580
+ }
581
+ async function validateFieldFull(fieldPath, valueHash, criteriaMode, generationAtStart) {
582
+ setValidating(ctx, fieldPath, true);
583
+ try {
584
+ const result = await ctx.options.schema.safeParseAsync(ctx.formData);
585
+ if (ctx.resetGeneration.value !== generationAtStart) return true;
586
+ if (result.success) {
587
+ ctx.errors.value = cancelError(fieldPath);
588
+ if (valueHash) ctx.validationCache.set(fieldPath, {
589
+ hash: valueHash,
590
+ isValid: true
591
+ });
592
+ return true;
593
+ }
594
+ const fieldErrors = result.error.issues.filter((issue) => {
595
+ const path = issue.path.join(".");
596
+ return path === fieldPath || path.startsWith(`${fieldPath}.`);
597
+ });
598
+ if (fieldErrors.length === 0) {
599
+ ctx.errors.value = cancelError(fieldPath);
600
+ if (valueHash) ctx.validationCache.set(fieldPath, {
601
+ hash: valueHash,
602
+ isValid: true
603
+ });
604
+ return true;
605
+ }
606
+ ctx.errors.value = cancelError(fieldPath);
607
+ const grouped = groupErrorsByPath(fieldErrors);
608
+ const errorBatch = [];
609
+ for (const [path, errors] of grouped) errorBatch.push([path, createFieldError(errors, criteriaMode)]);
610
+ scheduleErrorsBatch(errorBatch);
611
+ if (valueHash) ctx.validationCache.set(fieldPath, {
612
+ hash: valueHash,
613
+ isValid: false
614
+ });
615
+ return false;
616
+ } finally {
617
+ setValidating(ctx, fieldPath, false);
618
+ }
619
+ }
323
620
  async function validate(fieldPath) {
324
621
  const generationAtStart = ctx.resetGeneration.value;
325
622
  const criteriaMode = ctx.options.criteriaMode || "firstError";
326
- const validatingKey = fieldPath || "_form";
623
+ let valueHash;
624
+ if (fieldPath) {
625
+ valueHash = hashValue(get(ctx.formData, fieldPath));
626
+ const cached = ctx.validationCache.get(fieldPath);
627
+ if (cached && cached.hash === valueHash) return cached.isValid;
628
+ const analysis = analyzeSchemaPath(ctx.options.schema, fieldPath);
629
+ if (analysis.canPartialValidate && analysis.subSchema) return validateFieldPartial(fieldPath, analysis.subSchema, valueHash, criteriaMode, generationAtStart);
630
+ return validateFieldFull(fieldPath, valueHash, criteriaMode, generationAtStart);
631
+ }
632
+ const validatingKey = "_form";
327
633
  setValidating(ctx, validatingKey, true);
328
634
  try {
329
635
  const result = await ctx.options.schema.safeParseAsync(ctx.formData);
330
636
  if (ctx.resetGeneration.value !== generationAtStart) return true;
331
637
  if (result.success) {
332
- if (fieldPath) ctx.errors.value = cancelError(fieldPath);
333
- else {
334
- clearAllPendingErrors();
335
- ctx.errors.value = {};
336
- clearAllNativeValidation();
337
- }
638
+ clearAllPendingErrors();
639
+ ctx.errors.value = {};
640
+ clearAllNativeValidation();
641
+ ctx.validationCache.clear();
338
642
  return true;
339
643
  }
340
- const zodErrors = result.error.issues;
341
- if (fieldPath) {
342
- const fieldErrors = zodErrors.filter((issue) => {
343
- const path = issue.path.join(".");
344
- return path === fieldPath || path.startsWith(`${fieldPath}.`);
345
- });
346
- if (fieldErrors.length === 0) {
347
- ctx.errors.value = cancelError(fieldPath);
348
- return true;
349
- }
350
- ctx.errors.value = cancelError(fieldPath);
351
- const grouped$1 = groupErrorsByPath(fieldErrors);
352
- for (const [path, errors] of grouped$1) scheduleError(path, createFieldError(errors, criteriaMode));
353
- return false;
354
- }
355
644
  clearAllPendingErrors();
356
645
  ctx.errors.value = {};
357
- const grouped = groupErrorsByPath(zodErrors);
358
- for (const [path, errors] of grouped) scheduleError(path, createFieldError(errors, criteriaMode));
646
+ const grouped = groupErrorsByPath(result.error.issues);
647
+ const errorBatch = [];
648
+ for (const [path, errors] of grouped) errorBatch.push([path, createFieldError(errors, criteriaMode)]);
649
+ scheduleErrorsBatch(errorBatch);
650
+ ctx.validationCache.clear();
359
651
  return false;
360
652
  } finally {
361
653
  setValidating(ctx, validatingKey, false);
@@ -366,9 +658,57 @@ function createValidation(ctx) {
366
658
  clearAllPendingErrors
367
659
  };
368
660
  }
661
+ function markFieldDirty(dirtyFields, dirtyFieldCount, fieldName) {
662
+ if (dirtyFields.value[fieldName]) return;
663
+ dirtyFields.value = {
664
+ ...dirtyFields.value,
665
+ [fieldName]: true
666
+ };
667
+ dirtyFieldCount.value++;
668
+ }
669
+ function markFieldTouched(touchedFields, touchedFieldCount, fieldName) {
670
+ if (touchedFields.value[fieldName]) return;
671
+ touchedFields.value = {
672
+ ...touchedFields.value,
673
+ [fieldName]: true
674
+ };
675
+ touchedFieldCount.value++;
676
+ }
677
+ function clearFieldDirty(dirtyFields, dirtyFieldCount, fieldName) {
678
+ if (!(fieldName in dirtyFields.value)) return;
679
+ const newDirty = { ...dirtyFields.value };
680
+ delete newDirty[fieldName];
681
+ dirtyFields.value = newDirty;
682
+ dirtyFieldCount.value--;
683
+ }
684
+ function clearFieldTouched(touchedFields, touchedFieldCount, fieldName) {
685
+ if (!(fieldName in touchedFields.value)) return;
686
+ const newTouched = { ...touchedFields.value };
687
+ delete newTouched[fieldName];
688
+ touchedFields.value = newTouched;
689
+ touchedFieldCount.value--;
690
+ }
691
+ function clearFieldErrors(errors, fieldName) {
692
+ const currentErrors = errors.value;
693
+ const keys = Object.keys(currentErrors);
694
+ if (keys.length === 0) return;
695
+ const prefix = `${fieldName}.`;
696
+ const keysToDelete = [];
697
+ for (const key of keys) if (key === fieldName || key.startsWith(prefix)) keysToDelete.push(key);
698
+ if (keysToDelete.length === 0) return;
699
+ const newErrors = { ...currentErrors };
700
+ for (const key of keysToDelete) delete newErrors[key];
701
+ errors.value = newErrors;
702
+ }
369
703
  var validationRequestCounter = 0;
370
704
  function createFieldRegistration(ctx, validate) {
371
705
  function register(name, registerOptions) {
706
+ if (__DEV__) {
707
+ const syntaxError = validatePathSyntax(name);
708
+ if (syntaxError) warnInvalidPath("register", name, syntaxError);
709
+ const schemaResult = validatePathAgainstSchema(ctx.options.schema, name);
710
+ if (!schemaResult.valid) warnPathNotInSchema("register", name, schemaResult.availableFields);
711
+ }
372
712
  let fieldRef = ctx.fieldRefs.get(name);
373
713
  if (!fieldRef) {
374
714
  fieldRef = (0, vue.ref)(null);
@@ -401,16 +741,30 @@ function createFieldRegistration(ctx, validate) {
401
741
  const target = e.target;
402
742
  const value = target.type === "checkbox" ? target.checked : target.value;
403
743
  set(ctx.formData, name, value);
404
- ctx.dirtyFields.value = {
405
- ...ctx.dirtyFields.value,
406
- [name]: true
407
- };
744
+ markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
745
+ const fieldOpts = ctx.fieldOptions.get(name);
408
746
  if (ctx.options.mode === "onChange" || ctx.options.mode === "onTouched" && ctx.touchedFields.value[name] || ctx.touchedFields.value[name] && ctx.options.reValidateMode === "onChange") {
409
- await validate(name);
410
- const fieldOpts$1 = ctx.fieldOptions.get(name);
411
- if (fieldOpts$1?.deps && fieldOpts$1.deps.length > 0) for (const depField of fieldOpts$1.deps) validate(depField);
747
+ const validationDebounceMs = ctx.options.validationDebounce || 0;
748
+ if (validationDebounceMs > 0) {
749
+ const existingTimer = ctx.schemaValidationTimers.get(name);
750
+ if (existingTimer) clearTimeout(existingTimer);
751
+ const timer = setTimeout(async () => {
752
+ ctx.schemaValidationTimers.delete(name);
753
+ await validate(name);
754
+ if (fieldOpts?.deps && fieldOpts.deps.length > 0) {
755
+ const uniqueDeps = [...new Set(fieldOpts.deps)];
756
+ await Promise.all(uniqueDeps.map((depField) => validate(depField)));
757
+ }
758
+ }, validationDebounceMs);
759
+ ctx.schemaValidationTimers.set(name, timer);
760
+ } else {
761
+ await validate(name);
762
+ if (fieldOpts?.deps && fieldOpts.deps.length > 0) {
763
+ const uniqueDeps = [...new Set(fieldOpts.deps)];
764
+ await Promise.all(uniqueDeps.map((depField) => validate(depField)));
765
+ }
766
+ }
412
767
  }
413
- const fieldOpts = ctx.fieldOptions.get(name);
414
768
  if (fieldOpts?.validate && !fieldOpts.disabled) {
415
769
  const requestId = ++validationRequestCounter;
416
770
  ctx.validationRequestIds.set(name, requestId);
@@ -419,23 +773,24 @@ function createFieldRegistration(ctx, validate) {
419
773
  if (debounceMs > 0) {
420
774
  const existingTimer = ctx.debounceTimers.get(name);
421
775
  if (existingTimer) clearTimeout(existingTimer);
422
- const timer = setTimeout(() => {
776
+ const timer = setTimeout(async () => {
423
777
  ctx.debounceTimers.delete(name);
424
- runCustomValidation(name, value, requestId, resetGenAtStart);
778
+ await runCustomValidation(name, value, requestId, resetGenAtStart);
779
+ ctx.validationRequestIds.delete(name);
425
780
  }, debounceMs);
426
781
  ctx.debounceTimers.set(name, timer);
427
782
  } else await runCustomValidation(name, value, requestId, resetGenAtStart);
428
783
  }
429
784
  };
430
785
  const onBlur = async (_e) => {
431
- ctx.touchedFields.value = {
432
- ...ctx.touchedFields.value,
433
- [name]: true
434
- };
786
+ markFieldTouched(ctx.touchedFields, ctx.touchedFieldCount, name);
435
787
  if (ctx.options.mode === "onBlur" || ctx.options.mode === "onTouched" || ctx.submitCount.value > 0 && ctx.options.reValidateMode === "onBlur") {
436
788
  await validate(name);
437
789
  const fieldOpts = ctx.fieldOptions.get(name);
438
- if (fieldOpts?.deps && fieldOpts.deps.length > 0) for (const depField of fieldOpts.deps) validate(depField);
790
+ if (fieldOpts?.deps && fieldOpts.deps.length > 0) {
791
+ const uniqueDeps = [...new Set(fieldOpts.deps)];
792
+ await Promise.all(uniqueDeps.map((depField) => validate(depField)));
793
+ }
439
794
  }
440
795
  };
441
796
  const refCallback = (el) => {
@@ -457,18 +812,17 @@ function createFieldRegistration(ctx, validate) {
457
812
  clearTimeout(timer);
458
813
  ctx.debounceTimers.delete(name);
459
814
  }
815
+ const schemaTimer = ctx.schemaValidationTimers.get(name);
816
+ if (schemaTimer) {
817
+ clearTimeout(schemaTimer);
818
+ ctx.schemaValidationTimers.delete(name);
819
+ }
460
820
  ctx.validationRequestIds.delete(name);
461
821
  if (opts?.shouldUnregister ?? ctx.options.shouldUnregister ?? false) {
462
822
  unset(ctx.formData, name);
463
- const newErrors = { ...ctx.errors.value };
464
- delete newErrors[name];
465
- ctx.errors.value = newErrors;
466
- const newTouched = { ...ctx.touchedFields.value };
467
- delete newTouched[name];
468
- ctx.touchedFields.value = newTouched;
469
- const newDirty = { ...ctx.dirtyFields.value };
470
- delete newDirty[name];
471
- ctx.dirtyFields.value = newDirty;
823
+ clearFieldErrors(ctx.errors, name);
824
+ clearFieldTouched(ctx.touchedFields, ctx.touchedFieldCount, name);
825
+ clearFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
472
826
  ctx.fieldRefs.delete(name);
473
827
  ctx.fieldOptions.delete(name);
474
828
  ctx.fieldHandlers.delete(name);
@@ -492,10 +846,7 @@ function createFieldRegistration(ctx, validate) {
492
846
  get: () => get(ctx.formData, name),
493
847
  set: (val) => {
494
848
  set(ctx.formData, name, val);
495
- ctx.dirtyFields.value = {
496
- ...ctx.dirtyFields.value,
497
- [name]: true
498
- };
849
+ markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
499
850
  }
500
851
  }) }
501
852
  };
@@ -503,21 +854,9 @@ function createFieldRegistration(ctx, validate) {
503
854
  function unregister(name, options) {
504
855
  const opts = options || {};
505
856
  if (!opts.keepValue) unset(ctx.formData, name);
506
- if (!opts.keepError) {
507
- const newErrors = { ...ctx.errors.value };
508
- delete newErrors[name];
509
- ctx.errors.value = newErrors;
510
- }
511
- if (!opts.keepTouched) {
512
- const newTouched = { ...ctx.touchedFields.value };
513
- delete newTouched[name];
514
- ctx.touchedFields.value = newTouched;
515
- }
516
- if (!opts.keepDirty) {
517
- const newDirty = { ...ctx.dirtyFields.value };
518
- delete newDirty[name];
519
- ctx.dirtyFields.value = newDirty;
520
- }
857
+ if (!opts.keepError) clearFieldErrors(ctx.errors, name);
858
+ if (!opts.keepTouched) clearFieldTouched(ctx.touchedFields, ctx.touchedFieldCount, name);
859
+ if (!opts.keepDirty) clearFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
521
860
  ctx.fieldRefs.delete(name);
522
861
  ctx.fieldOptions.delete(name);
523
862
  ctx.fieldHandlers.delete(name);
@@ -526,6 +865,11 @@ function createFieldRegistration(ctx, validate) {
526
865
  clearTimeout(timer);
527
866
  ctx.debounceTimers.delete(name);
528
867
  }
868
+ const schemaTimer = ctx.schemaValidationTimers.get(name);
869
+ if (schemaTimer) {
870
+ clearTimeout(schemaTimer);
871
+ ctx.schemaValidationTimers.delete(name);
872
+ }
529
873
  ctx.validationRequestIds.delete(name);
530
874
  }
531
875
  return {
@@ -535,6 +879,13 @@ function createFieldRegistration(ctx, validate) {
535
879
  }
536
880
  function createFieldArrayManager(ctx, validate, setFocus) {
537
881
  function fields(name, options) {
882
+ if (__DEV__) {
883
+ const syntaxError = validatePathSyntax(name);
884
+ if (syntaxError) warnInvalidPath("fields", name, syntaxError);
885
+ const schemaResult = validatePathAgainstSchema(ctx.options.schema, name);
886
+ if (!schemaResult.valid) warnPathNotInSchema("fields", name, schemaResult.availableFields);
887
+ if (isArrayFieldInSchema(ctx.options.schema, name) === false) warnFieldsOnNonArray(name);
888
+ }
538
889
  let fieldArray = ctx.fieldArrays.get(name);
539
890
  if (!fieldArray) {
540
891
  const existingValues = get(ctx.formData, name) || [];
@@ -555,6 +906,35 @@ function createFieldArrayManager(ctx, validate, setFocus) {
555
906
  indexCache.set(item.key, idx);
556
907
  });
557
908
  };
909
+ const appendToCache = (startIndex) => {
910
+ const items = fa.items.value;
911
+ for (let i = startIndex; i < items.length; i++) {
912
+ const item = items[i];
913
+ if (item) indexCache.set(item.key, i);
914
+ }
915
+ };
916
+ const updateCacheAfterInsert = (insertIndex, _insertCount) => {
917
+ const items = fa.items.value;
918
+ for (let i = insertIndex; i < items.length; i++) {
919
+ const item = items[i];
920
+ if (item) indexCache.set(item.key, i);
921
+ }
922
+ };
923
+ const swapInCache = (indexA, indexB) => {
924
+ const items = fa.items.value;
925
+ const itemA = items[indexA];
926
+ const itemB = items[indexB];
927
+ if (itemA) indexCache.set(itemA.key, indexA);
928
+ if (itemB) indexCache.set(itemB.key, indexB);
929
+ };
930
+ const updateCacheAfterRemove = (removedKey, startIndex) => {
931
+ indexCache.delete(removedKey);
932
+ const items = fa.items.value;
933
+ for (let i = startIndex; i < items.length; i++) {
934
+ const item = items[i];
935
+ if (item) indexCache.set(item.key, i);
936
+ }
937
+ };
558
938
  const createItem = (key) => ({
559
939
  key,
560
940
  get index() {
@@ -586,16 +966,19 @@ function createFieldArrayManager(ctx, validate, setFocus) {
586
966
  const currentValues = get(ctx.formData, name) || [];
587
967
  const insertIndex = currentValues.length;
588
968
  const rules = fa.rules;
589
- if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) return false;
969
+ if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) {
970
+ if (__DEV__) warnArrayOperationRejected("append", name, "maxLength", {
971
+ current: currentValues.length,
972
+ limit: rules.maxLength.value
973
+ });
974
+ return false;
975
+ }
590
976
  const newValues = [...currentValues, ...values];
591
977
  set(ctx.formData, name, newValues);
592
978
  const newItems = values.map(() => createItem(generateId()));
593
979
  fa.items.value = [...fa.items.value, ...newItems];
594
- rebuildIndexCache();
595
- ctx.dirtyFields.value = {
596
- ...ctx.dirtyFields.value,
597
- [name]: true
598
- };
980
+ appendToCache(insertIndex);
981
+ markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
599
982
  if (ctx.options.mode === "onChange") validate(name);
600
983
  handleFocus(insertIndex, values.length, focusOptions);
601
984
  return true;
@@ -605,47 +988,56 @@ function createFieldArrayManager(ctx, validate, setFocus) {
605
988
  if (values.length === 0) return true;
606
989
  const currentValues = get(ctx.formData, name) || [];
607
990
  const rules = fa.rules;
608
- if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) return false;
991
+ if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) {
992
+ if (__DEV__) warnArrayOperationRejected("prepend", name, "maxLength", {
993
+ current: currentValues.length,
994
+ limit: rules.maxLength.value
995
+ });
996
+ return false;
997
+ }
609
998
  const newValues = [...values, ...currentValues];
610
999
  set(ctx.formData, name, newValues);
611
1000
  const newItems = values.map(() => createItem(generateId()));
612
1001
  fa.items.value = [...newItems, ...fa.items.value];
613
- rebuildIndexCache();
614
- ctx.dirtyFields.value = {
615
- ...ctx.dirtyFields.value,
616
- [name]: true
617
- };
1002
+ updateCacheAfterInsert(0, values.length);
1003
+ markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
618
1004
  if (ctx.options.mode === "onChange") validate(name);
619
1005
  handleFocus(0, values.length, focusOptions);
620
1006
  return true;
621
1007
  };
622
1008
  const update = (index, value) => {
623
1009
  const currentValues = get(ctx.formData, name) || [];
624
- if (index < 0 || index >= currentValues.length) return false;
1010
+ if (index < 0 || index >= currentValues.length) {
1011
+ if (__DEV__) warnArrayIndexOutOfBounds("update", name, index, currentValues.length);
1012
+ return false;
1013
+ }
625
1014
  const newValues = [...currentValues];
626
1015
  newValues[index] = value;
627
1016
  set(ctx.formData, name, newValues);
628
- ctx.dirtyFields.value = {
629
- ...ctx.dirtyFields.value,
630
- [name]: true
631
- };
1017
+ markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
632
1018
  if (ctx.options.mode === "onChange") validate(name);
633
1019
  return true;
634
1020
  };
635
1021
  const removeAt = (index) => {
636
1022
  const currentValues = get(ctx.formData, name) || [];
637
- if (index < 0 || index >= currentValues.length) return false;
1023
+ if (index < 0 || index >= currentValues.length) {
1024
+ if (__DEV__) warnArrayIndexOutOfBounds("remove", name, index, currentValues.length);
1025
+ return false;
1026
+ }
638
1027
  const rules = fa.rules;
639
- if (rules?.minLength && currentValues.length - 1 < rules.minLength.value) return false;
1028
+ if (rules?.minLength && currentValues.length - 1 < rules.minLength.value) {
1029
+ if (__DEV__) warnArrayOperationRejected("remove", name, "minLength", {
1030
+ current: currentValues.length,
1031
+ limit: rules.minLength.value
1032
+ });
1033
+ return false;
1034
+ }
640
1035
  const newValues = currentValues.filter((_, i) => i !== index);
641
1036
  set(ctx.formData, name, newValues);
642
1037
  const keyToRemove = fa.items.value[index]?.key;
643
1038
  fa.items.value = fa.items.value.filter((item) => item.key !== keyToRemove);
644
- rebuildIndexCache();
645
- ctx.dirtyFields.value = {
646
- ...ctx.dirtyFields.value,
647
- [name]: true
648
- };
1039
+ if (keyToRemove) updateCacheAfterRemove(keyToRemove, index);
1040
+ markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
649
1041
  if (ctx.options.mode === "onChange") validate(name);
650
1042
  return true;
651
1043
  };
@@ -654,7 +1046,13 @@ function createFieldArrayManager(ctx, validate, setFocus) {
654
1046
  if (values.length === 0) return true;
655
1047
  const currentValues = get(ctx.formData, name) || [];
656
1048
  const rules = fa.rules;
657
- if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) return false;
1049
+ if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) {
1050
+ if (__DEV__) warnArrayOperationRejected("insert", name, "maxLength", {
1051
+ current: currentValues.length,
1052
+ limit: rules.maxLength.value
1053
+ });
1054
+ return false;
1055
+ }
658
1056
  const clampedIndex = Math.max(0, Math.min(index, currentValues.length));
659
1057
  const newValues = [
660
1058
  ...currentValues.slice(0, clampedIndex),
@@ -668,18 +1066,18 @@ function createFieldArrayManager(ctx, validate, setFocus) {
668
1066
  ...newItems,
669
1067
  ...fa.items.value.slice(clampedIndex)
670
1068
  ];
671
- rebuildIndexCache();
672
- ctx.dirtyFields.value = {
673
- ...ctx.dirtyFields.value,
674
- [name]: true
675
- };
1069
+ updateCacheAfterInsert(clampedIndex, values.length);
1070
+ markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
676
1071
  if (ctx.options.mode === "onChange") validate(name);
677
1072
  handleFocus(clampedIndex, values.length, focusOptions);
678
1073
  return true;
679
1074
  };
680
1075
  const swap = (indexA, indexB) => {
681
1076
  const currentValues = get(ctx.formData, name) || [];
682
- if (indexA < 0 || indexB < 0 || indexA >= currentValues.length || indexB >= currentValues.length) return false;
1077
+ if (indexA < 0 || indexB < 0 || indexA >= currentValues.length || indexB >= currentValues.length) {
1078
+ if (__DEV__) warnArrayIndexOutOfBounds("swap", name, indexA < 0 || indexA >= currentValues.length ? indexA : indexB, currentValues.length);
1079
+ return false;
1080
+ }
683
1081
  const newValues = [...currentValues];
684
1082
  [newValues[indexA], newValues[indexB]] = [newValues[indexB], newValues[indexA]];
685
1083
  set(ctx.formData, name, newValues);
@@ -690,18 +1088,18 @@ function createFieldArrayManager(ctx, validate, setFocus) {
690
1088
  newItems[indexA] = itemB;
691
1089
  newItems[indexB] = itemA;
692
1090
  fa.items.value = newItems;
693
- rebuildIndexCache();
1091
+ swapInCache(indexA, indexB);
694
1092
  }
695
- ctx.dirtyFields.value = {
696
- ...ctx.dirtyFields.value,
697
- [name]: true
698
- };
1093
+ markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
699
1094
  if (ctx.options.mode === "onChange") validate(name);
700
1095
  return true;
701
1096
  };
702
1097
  const move = (from, to) => {
703
1098
  const currentValues = get(ctx.formData, name) || [];
704
- if (from < 0 || from >= currentValues.length || to < 0) return false;
1099
+ if (from < 0 || from >= currentValues.length || to < 0) {
1100
+ if (__DEV__) warnArrayIndexOutOfBounds("move", name, from < 0 || from >= currentValues.length ? from : to, currentValues.length);
1101
+ return false;
1102
+ }
705
1103
  const newValues = [...currentValues];
706
1104
  const [removed] = newValues.splice(from, 1);
707
1105
  if (removed !== void 0) {
@@ -715,12 +1113,15 @@ function createFieldArrayManager(ctx, validate, setFocus) {
715
1113
  const clampedTo = Math.min(to, newItems.length);
716
1114
  newItems.splice(clampedTo, 0, removedItem);
717
1115
  fa.items.value = newItems;
718
- rebuildIndexCache();
1116
+ const minIdx = Math.min(from, clampedTo);
1117
+ const maxIdx = Math.max(from, clampedTo);
1118
+ const items = fa.items.value;
1119
+ for (let i = minIdx; i <= maxIdx; i++) {
1120
+ const item = items[i];
1121
+ if (item) indexCache.set(item.key, i);
1122
+ }
719
1123
  }
720
- ctx.dirtyFields.value = {
721
- ...ctx.dirtyFields.value,
722
- [name]: true
723
- };
1124
+ markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
724
1125
  if (ctx.options.mode === "onChange") validate(name);
725
1126
  return true;
726
1127
  };
@@ -729,23 +1130,23 @@ function createFieldArrayManager(ctx, validate, setFocus) {
729
1130
  set(ctx.formData, name, newValues);
730
1131
  fa.items.value = newValues.map(() => createItem(generateId()));
731
1132
  rebuildIndexCache();
732
- ctx.dirtyFields.value = {
733
- ...ctx.dirtyFields.value,
734
- [name]: true
735
- };
1133
+ markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
736
1134
  if (ctx.options.mode === "onChange") validate(name);
737
1135
  return true;
738
1136
  };
739
1137
  const removeAll = () => {
740
1138
  const rules = fa.rules;
741
- if (rules?.minLength && rules.minLength.value > 0) return false;
1139
+ if (rules?.minLength && rules.minLength.value > 0) {
1140
+ if (__DEV__) warnArrayOperationRejected("removeAll", name, "minLength", {
1141
+ current: fa.items.value.length,
1142
+ limit: rules.minLength.value
1143
+ });
1144
+ return false;
1145
+ }
742
1146
  set(ctx.formData, name, []);
743
1147
  fa.items.value = [];
744
- rebuildIndexCache();
745
- ctx.dirtyFields.value = {
746
- ...ctx.dirtyFields.value,
747
- [name]: true
748
- };
1148
+ indexCache.clear();
1149
+ markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
749
1150
  if (ctx.options.mode === "onChange") validate(name);
750
1151
  return true;
751
1152
  };
@@ -755,17 +1156,24 @@ function createFieldArrayManager(ctx, validate, setFocus) {
755
1156
  if (validIndices.length === 0) return true;
756
1157
  const rules = fa.rules;
757
1158
  const remainingCount = currentValues.length - validIndices.length;
758
- if (rules?.minLength && remainingCount < rules.minLength.value) return false;
1159
+ if (rules?.minLength && remainingCount < rules.minLength.value) {
1160
+ if (__DEV__) warnArrayOperationRejected("removeMany", name, "minLength", {
1161
+ current: currentValues.length,
1162
+ limit: rules.minLength.value
1163
+ });
1164
+ return false;
1165
+ }
759
1166
  const sortedIndices = [...new Set(validIndices)].sort((a, b) => b - a);
760
1167
  const indicesToRemove = new Set(sortedIndices);
1168
+ const keysToRemove = fa.items.value.filter((_, i) => indicesToRemove.has(i)).map((item) => item.key);
761
1169
  const newValues = currentValues.filter((_, i) => !indicesToRemove.has(i));
762
1170
  set(ctx.formData, name, newValues);
763
1171
  fa.items.value = fa.items.value.filter((_, i) => !indicesToRemove.has(i));
764
- rebuildIndexCache();
765
- ctx.dirtyFields.value = {
766
- ...ctx.dirtyFields.value,
767
- [name]: true
768
- };
1172
+ for (const key of keysToRemove) indexCache.delete(key);
1173
+ fa.items.value.forEach((item, idx) => {
1174
+ indexCache.set(item.key, idx);
1175
+ });
1176
+ markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
769
1177
  if (ctx.options.mode === "onChange") validate(name);
770
1178
  return true;
771
1179
  };
@@ -797,38 +1205,19 @@ function updateDomElement(el, value) {
797
1205
  if (el.type === "checkbox") el.checked = value;
798
1206
  else el.value = value;
799
1207
  }
800
- function markFieldDirty(dirtyFields, fieldName) {
801
- dirtyFields.value = {
802
- ...dirtyFields.value,
803
- [fieldName]: true
804
- };
805
- }
806
- function markFieldTouched(touchedFields, fieldName) {
807
- touchedFields.value = {
808
- ...touchedFields.value,
809
- [fieldName]: true
810
- };
811
- }
812
- function clearFieldDirty(dirtyFields, fieldName) {
813
- const newDirty = { ...dirtyFields.value };
814
- delete newDirty[fieldName];
815
- dirtyFields.value = newDirty;
816
- }
817
- function clearFieldTouched(touchedFields, fieldName) {
818
- const newTouched = { ...touchedFields.value };
819
- delete newTouched[fieldName];
820
- touchedFields.value = newTouched;
821
- }
822
- function clearFieldErrors(errors, fieldName) {
823
- const newErrors = { ...errors.value };
824
- for (const key of Object.keys(newErrors)) if (key === fieldName || key.startsWith(`${fieldName}.`)) delete newErrors[key];
825
- errors.value = newErrors;
826
- }
827
1208
  function useForm(options) {
828
1209
  const ctx = createFormContext(options);
829
1210
  const { validate, clearAllPendingErrors } = createValidation(ctx);
830
1211
  const { register, unregister } = createFieldRegistration(ctx, validate);
831
1212
  function setFocus(name, focusOptions) {
1213
+ if (__DEV__) {
1214
+ const syntaxError = validatePathSyntax(name);
1215
+ if (syntaxError) warnInvalidPath("setFocus", name, syntaxError);
1216
+ else {
1217
+ const schemaResult = validatePathAgainstSchema(ctx.options.schema, name);
1218
+ if (!schemaResult.valid) warnPathNotInSchema("setFocus", name, schemaResult.availableFields);
1219
+ }
1220
+ }
832
1221
  const fieldRef = ctx.fieldRefs.get(name);
833
1222
  if (!fieldRef?.value) return;
834
1223
  const el = fieldRef.value;
@@ -839,32 +1228,84 @@ function useForm(options) {
839
1228
  }
840
1229
  const setFocusWrapper = (name) => setFocus(name);
841
1230
  const { fields } = createFieldArrayManager(ctx, validate, setFocusWrapper);
1231
+ let lastSyncTime = 0;
1232
+ const SYNC_DEBOUNCE_MS = 16;
1233
+ function syncWithDebounce() {
1234
+ const now = typeof performance !== "undefined" ? performance.now() : Date.now();
1235
+ if (now - lastSyncTime < SYNC_DEBOUNCE_MS) return;
1236
+ syncUncontrolledInputs(ctx.fieldRefs, ctx.fieldOptions, ctx.formData);
1237
+ lastSyncTime = now;
1238
+ }
1239
+ let lastErrors = ctx.errors.value;
1240
+ let lastExternalErrors = ctx.externalErrors.value;
1241
+ let cachedMergedErrors = null;
842
1242
  function getMergedErrors() {
843
- return {
1243
+ if (cachedMergedErrors !== null && lastErrors === ctx.errors.value && lastExternalErrors === ctx.externalErrors.value) return cachedMergedErrors;
1244
+ lastErrors = ctx.errors.value;
1245
+ lastExternalErrors = ctx.externalErrors.value;
1246
+ cachedMergedErrors = {
844
1247
  ...ctx.errors.value,
845
1248
  ...ctx.externalErrors.value
846
1249
  };
1250
+ return cachedMergedErrors;
847
1251
  }
848
- const formState = (0, vue.computed)(() => {
849
- const mergedErrors = getMergedErrors();
850
- return {
851
- errors: mergedErrors,
852
- isDirty: Object.keys(ctx.dirtyFields.value).some((k) => ctx.dirtyFields.value[k]),
853
- dirtyFields: ctx.dirtyFields.value,
854
- isValid: (ctx.submitCount.value > 0 || Object.keys(ctx.touchedFields.value).length > 0) && Object.keys(mergedErrors).length === 0,
855
- isSubmitting: ctx.isSubmitting.value,
856
- isLoading: ctx.isLoading.value,
857
- isReady: !ctx.isLoading.value,
858
- isValidating: Object.keys(ctx.validatingFields.value).some((k) => ctx.validatingFields.value[k]),
859
- validatingFields: ctx.validatingFields.value,
860
- touchedFields: ctx.touchedFields.value,
861
- submitCount: ctx.submitCount.value,
862
- defaultValuesError: ctx.defaultValuesError.value,
863
- isSubmitted: ctx.submitCount.value > 0,
864
- isSubmitSuccessful: ctx.isSubmitSuccessful.value,
865
- disabled: ctx.isDisabled.value
866
- };
1252
+ const isDirtyComputed = (0, vue.computed)(() => ctx.dirtyFieldCount.value > 0);
1253
+ const errorsComputed = (0, vue.computed)(() => getMergedErrors());
1254
+ const isValidComputed = (0, vue.computed)(() => {
1255
+ if (!(ctx.submitCount.value > 0 || ctx.touchedFieldCount.value > 0)) return false;
1256
+ return Object.keys(errorsComputed.value).length === 0;
1257
+ });
1258
+ const isReadyComputed = (0, vue.computed)(() => !ctx.isLoading.value);
1259
+ const isValidatingComputed = (0, vue.computed)(() => ctx.validatingFields.value.size > 0);
1260
+ const isSubmittedComputed = (0, vue.computed)(() => ctx.submitCount.value > 0);
1261
+ const formStateInternal = (0, vue.reactive)({
1262
+ get errors() {
1263
+ return errorsComputed.value;
1264
+ },
1265
+ get isDirty() {
1266
+ return isDirtyComputed.value;
1267
+ },
1268
+ get dirtyFields() {
1269
+ return ctx.dirtyFields.value;
1270
+ },
1271
+ get isValid() {
1272
+ return isValidComputed.value;
1273
+ },
1274
+ get isSubmitting() {
1275
+ return ctx.isSubmitting.value;
1276
+ },
1277
+ get isLoading() {
1278
+ return ctx.isLoading.value;
1279
+ },
1280
+ get isReady() {
1281
+ return isReadyComputed.value;
1282
+ },
1283
+ get isValidating() {
1284
+ return isValidatingComputed.value;
1285
+ },
1286
+ get validatingFields() {
1287
+ return ctx.validatingFields.value;
1288
+ },
1289
+ get touchedFields() {
1290
+ return ctx.touchedFields.value;
1291
+ },
1292
+ get submitCount() {
1293
+ return ctx.submitCount.value;
1294
+ },
1295
+ get defaultValuesError() {
1296
+ return ctx.defaultValuesError.value;
1297
+ },
1298
+ get isSubmitted() {
1299
+ return isSubmittedComputed.value;
1300
+ },
1301
+ get isSubmitSuccessful() {
1302
+ return ctx.isSubmitSuccessful.value;
1303
+ },
1304
+ get disabled() {
1305
+ return ctx.isDisabled.value;
1306
+ }
867
1307
  });
1308
+ const formState = (0, vue.computed)(() => formStateInternal);
868
1309
  function handleSubmit(onValid, onInvalid) {
869
1310
  return async (e) => {
870
1311
  e.preventDefault();
@@ -874,7 +1315,7 @@ function useForm(options) {
874
1315
  ctx.submitCount.value++;
875
1316
  ctx.isSubmitSuccessful.value = false;
876
1317
  try {
877
- syncUncontrolledInputs(ctx.fieldRefs, ctx.fieldOptions, ctx.formData);
1318
+ syncWithDebounce();
878
1319
  if (await validate()) {
879
1320
  await onValid(ctx.formData);
880
1321
  ctx.isSubmitSuccessful.value = true;
@@ -891,9 +1332,17 @@ function useForm(options) {
891
1332
  };
892
1333
  }
893
1334
  function setValue(name, value, setValueOptions) {
1335
+ if (__DEV__) {
1336
+ const syntaxError = validatePathSyntax(name);
1337
+ if (syntaxError) warnInvalidPath("setValue", name, syntaxError);
1338
+ else {
1339
+ const schemaResult = validatePathAgainstSchema(ctx.options.schema, name);
1340
+ if (!schemaResult.valid) warnPathNotInSchema("setValue", name, schemaResult.availableFields);
1341
+ }
1342
+ }
894
1343
  set(ctx.formData, name, value);
895
- if (setValueOptions?.shouldDirty !== false) markFieldDirty(ctx.dirtyFields, name);
896
- if (setValueOptions?.shouldTouch) markFieldTouched(ctx.touchedFields, name);
1344
+ if (setValueOptions?.shouldDirty !== false) markFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1345
+ if (setValueOptions?.shouldTouch) markFieldTouched(ctx.touchedFields, ctx.touchedFieldCount, name);
897
1346
  if (!ctx.fieldOptions.get(name)?.controlled) {
898
1347
  const fieldRef = ctx.fieldRefs.get(name);
899
1348
  if (fieldRef?.value) updateDomElement(fieldRef.value, value);
@@ -904,15 +1353,23 @@ function useForm(options) {
904
1353
  const opts = resetOptions || {};
905
1354
  ctx.resetGeneration.value++;
906
1355
  clearAllPendingErrors();
907
- ctx.validatingFields.value = {};
1356
+ ctx.validatingFields.value = /* @__PURE__ */ new Set();
1357
+ ctx.validationCache.clear();
1358
+ for (const timer of ctx.schemaValidationTimers.values()) clearTimeout(timer);
1359
+ ctx.schemaValidationTimers.clear();
908
1360
  if (!opts.keepDefaultValues && values) Object.assign(ctx.defaultValues, values);
909
1361
  Object.keys(ctx.formData).forEach((key) => delete ctx.formData[key]);
910
- const sourceValues = values || ctx.defaultValues;
911
- const newValues = JSON.parse(JSON.stringify(sourceValues));
1362
+ const newValues = deepClone(values || ctx.defaultValues);
912
1363
  Object.assign(ctx.formData, newValues);
913
1364
  if (!opts.keepErrors) ctx.errors.value = {};
914
- if (!opts.keepTouched) ctx.touchedFields.value = {};
915
- if (!opts.keepDirty) ctx.dirtyFields.value = {};
1365
+ if (!opts.keepTouched) {
1366
+ ctx.touchedFields.value = {};
1367
+ ctx.touchedFieldCount.value = 0;
1368
+ }
1369
+ if (!opts.keepDirty) {
1370
+ ctx.dirtyFields.value = {};
1371
+ ctx.dirtyFieldCount.value = 0;
1372
+ }
916
1373
  if (!opts.keepSubmitCount) ctx.submitCount.value = 0;
917
1374
  if (!opts.keepIsSubmitting) ctx.isSubmitting.value = false;
918
1375
  if (!opts.keepIsSubmitSuccessful) ctx.isSubmitSuccessful.value = false;
@@ -927,6 +1384,14 @@ function useForm(options) {
927
1384
  }
928
1385
  }
929
1386
  function resetField(name, resetFieldOptions) {
1387
+ if (__DEV__) {
1388
+ const syntaxError = validatePathSyntax(name);
1389
+ if (syntaxError) warnInvalidPath("resetField", name, syntaxError);
1390
+ else {
1391
+ const schemaResult = validatePathAgainstSchema(ctx.options.schema, name);
1392
+ if (!schemaResult.valid) warnPathNotInSchema("resetField", name, schemaResult.availableFields);
1393
+ }
1394
+ }
930
1395
  const opts = resetFieldOptions || {};
931
1396
  ctx.resetGeneration.value++;
932
1397
  const errorTimer = ctx.errorDelayTimers.get(name);
@@ -938,17 +1403,28 @@ function useForm(options) {
938
1403
  let defaultValue = opts.defaultValue;
939
1404
  if (defaultValue === void 0) defaultValue = get(ctx.defaultValues, name);
940
1405
  else set(ctx.defaultValues, name, defaultValue);
941
- const clonedValue = defaultValue !== void 0 ? JSON.parse(JSON.stringify(defaultValue)) : void 0;
1406
+ const clonedValue = defaultValue !== void 0 ? deepClone(defaultValue) : void 0;
942
1407
  set(ctx.formData, name, clonedValue);
943
1408
  if (!opts.keepError) clearFieldErrors(ctx.errors, name);
944
- if (!opts.keepDirty) clearFieldDirty(ctx.dirtyFields, name);
945
- if (!opts.keepTouched) clearFieldTouched(ctx.touchedFields, name);
1409
+ if (!opts.keepDirty) clearFieldDirty(ctx.dirtyFields, ctx.dirtyFieldCount, name);
1410
+ if (!opts.keepTouched) clearFieldTouched(ctx.touchedFields, ctx.touchedFieldCount, name);
946
1411
  if (!ctx.fieldOptions.get(name)?.controlled) {
947
1412
  const fieldRef = ctx.fieldRefs.get(name);
948
1413
  if (fieldRef?.value) updateDomElement(fieldRef.value, clonedValue ?? (fieldRef.value.type === "checkbox" ? false : ""));
949
1414
  }
950
1415
  }
951
1416
  function watch$1(name) {
1417
+ if (__DEV__ && name) {
1418
+ const names = Array.isArray(name) ? name : [name];
1419
+ for (const n of names) {
1420
+ const syntaxError = validatePathSyntax(n);
1421
+ if (syntaxError) warnInvalidPath("watch", n, syntaxError);
1422
+ else {
1423
+ const schemaResult = validatePathAgainstSchema(ctx.options.schema, n);
1424
+ if (!schemaResult.valid) warnPathNotInSchema("watch", n, schemaResult.availableFields);
1425
+ }
1426
+ }
1427
+ }
952
1428
  return (0, vue.computed)(() => {
953
1429
  if (!name) return ctx.formData;
954
1430
  if (Array.isArray(name)) {
@@ -960,6 +1436,18 @@ function useForm(options) {
960
1436
  });
961
1437
  }
962
1438
  function clearErrors(name) {
1439
+ if (__DEV__ && name && !String(name).startsWith("root")) {
1440
+ const names = Array.isArray(name) ? name : [name];
1441
+ for (const n of names) {
1442
+ if (String(n).startsWith("root")) continue;
1443
+ const syntaxError = validatePathSyntax(n);
1444
+ if (syntaxError) warnInvalidPath("clearErrors", n, syntaxError);
1445
+ else {
1446
+ const schemaResult = validatePathAgainstSchema(ctx.options.schema, n);
1447
+ if (!schemaResult.valid) warnPathNotInSchema("clearErrors", n, schemaResult.availableFields);
1448
+ }
1449
+ }
1450
+ }
963
1451
  if (name === void 0) {
964
1452
  ctx.errors.value = {};
965
1453
  return;
@@ -998,7 +1486,18 @@ function useForm(options) {
998
1486
  return get(mergedErrors, fieldPath);
999
1487
  }
1000
1488
  function getValues(nameOrNames) {
1001
- syncUncontrolledInputs(ctx.fieldRefs, ctx.fieldOptions, ctx.formData);
1489
+ if (__DEV__ && nameOrNames) {
1490
+ const names = Array.isArray(nameOrNames) ? nameOrNames : [nameOrNames];
1491
+ for (const n of names) {
1492
+ const syntaxError = validatePathSyntax(n);
1493
+ if (syntaxError) warnInvalidPath("getValues", n, syntaxError);
1494
+ else {
1495
+ const schemaResult = validatePathAgainstSchema(ctx.options.schema, n);
1496
+ if (!schemaResult.valid) warnPathNotInSchema("getValues", n, schemaResult.availableFields);
1497
+ }
1498
+ }
1499
+ }
1500
+ syncWithDebounce();
1002
1501
  if (nameOrNames === void 0) return { ...ctx.formData };
1003
1502
  if (Array.isArray(nameOrNames)) {
1004
1503
  const result = {};
@@ -1008,6 +1507,14 @@ function useForm(options) {
1008
1507
  return get(ctx.formData, nameOrNames);
1009
1508
  }
1010
1509
  function getFieldState(name) {
1510
+ if (__DEV__) {
1511
+ const syntaxError = validatePathSyntax(name);
1512
+ if (syntaxError) warnInvalidPath("getFieldState", name, syntaxError);
1513
+ else {
1514
+ const schemaResult = validatePathAgainstSchema(ctx.options.schema, name);
1515
+ if (!schemaResult.valid) warnPathNotInSchema("getFieldState", name, schemaResult.availableFields);
1516
+ }
1517
+ }
1011
1518
  const error = get(ctx.errors.value, name);
1012
1519
  return {
1013
1520
  isDirty: ctx.dirtyFields.value[name] === true,
@@ -1017,6 +1524,17 @@ function useForm(options) {
1017
1524
  };
1018
1525
  }
1019
1526
  async function trigger(name) {
1527
+ if (__DEV__ && name) {
1528
+ const names = Array.isArray(name) ? name : [name];
1529
+ for (const n of names) {
1530
+ const syntaxError = validatePathSyntax(n);
1531
+ if (syntaxError) warnInvalidPath("trigger", n, syntaxError);
1532
+ else {
1533
+ const schemaResult = validatePathAgainstSchema(ctx.options.schema, n);
1534
+ if (!schemaResult.valid) warnPathNotInSchema("trigger", n, schemaResult.availableFields);
1535
+ }
1536
+ }
1537
+ }
1020
1538
  if (name === void 0) return await validate();
1021
1539
  if (Array.isArray(name)) {
1022
1540
  let allValid = true;