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