@vuehookform/core 0.4.1 → 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 +334 -169
- package/dist/vuehookform.js +331 -171
- 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,14 +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) {
|
|
626
|
-
if (cached.isValid) ctx.errors.value = cancelError(fieldPath);
|
|
627
|
-
return cached.isValid;
|
|
628
|
-
}
|
|
629
710
|
const analysis = analyzeSchemaPath(ctx.options.schema, fieldPath);
|
|
630
|
-
|
|
631
|
-
|
|
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);
|
|
632
720
|
}
|
|
633
721
|
const validatingKey = "_form";
|
|
634
722
|
setValidating(ctx, validatingKey, true);
|
|
@@ -659,47 +747,31 @@ function createValidation(ctx) {
|
|
|
659
747
|
clearAllPendingErrors
|
|
660
748
|
};
|
|
661
749
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
}
|
|
670
|
-
function markFieldTouched(touchedFields, touchedFieldCount, fieldName) {
|
|
671
|
-
if (touchedFields.value[fieldName]) return;
|
|
672
|
-
touchedFields.value = {
|
|
673
|
-
...touchedFields.value,
|
|
674
|
-
[fieldName]: true
|
|
675
|
-
};
|
|
676
|
-
touchedFieldCount.value++;
|
|
677
|
-
}
|
|
678
|
-
function clearFieldDirty(dirtyFields, dirtyFieldCount, fieldName) {
|
|
679
|
-
if (!(fieldName in dirtyFields.value)) return;
|
|
680
|
-
const newDirty = { ...dirtyFields.value };
|
|
681
|
-
delete newDirty[fieldName];
|
|
682
|
-
dirtyFields.value = newDirty;
|
|
683
|
-
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}`);
|
|
684
758
|
}
|
|
685
|
-
function
|
|
686
|
-
if (
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
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;
|
|
691
768
|
}
|
|
692
|
-
function
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
for (const key of keys) if (key === fieldName || key.startsWith(prefix)) keysToDelete.push(key);
|
|
699
|
-
if (keysToDelete.length === 0) return;
|
|
700
|
-
const newErrors = { ...currentErrors };
|
|
701
|
-
for (const key of keysToDelete) delete newErrors[key];
|
|
702
|
-
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");
|
|
703
775
|
}
|
|
704
776
|
var validationRequestCounter = 0;
|
|
705
777
|
function createFieldRegistration(ctx, validate) {
|
|
@@ -740,11 +812,14 @@ function createFieldRegistration(ctx, validate) {
|
|
|
740
812
|
};
|
|
741
813
|
const onInput = async (e) => {
|
|
742
814
|
const target = e.target;
|
|
743
|
-
|
|
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;
|
|
744
819
|
set(ctx.formData, name, value);
|
|
745
|
-
|
|
820
|
+
updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, value);
|
|
746
821
|
const fieldOpts = ctx.fieldOptions.get(name);
|
|
747
|
-
if (ctx.options.mode
|
|
822
|
+
if (shouldValidateOnChange(ctx.options.mode ?? "onSubmit", ctx.touchedFields.value[name] === true, ctx.options.reValidateMode, ctx.submitCount.value > 0)) {
|
|
748
823
|
const validationDebounceMs = ctx.options.validationDebounce || 0;
|
|
749
824
|
if (validationDebounceMs > 0) {
|
|
750
825
|
const existingTimer = ctx.schemaValidationTimers.get(name);
|
|
@@ -777,15 +852,18 @@ function createFieldRegistration(ctx, validate) {
|
|
|
777
852
|
const timer = setTimeout(async () => {
|
|
778
853
|
ctx.debounceTimers.delete(name);
|
|
779
854
|
await runCustomValidation(name, value, requestId, resetGenAtStart);
|
|
780
|
-
ctx.validationRequestIds.delete(name);
|
|
855
|
+
if (ctx.validationRequestIds.get(name) === requestId) ctx.validationRequestIds.delete(name);
|
|
781
856
|
}, debounceMs);
|
|
782
857
|
ctx.debounceTimers.set(name, timer);
|
|
783
|
-
} else
|
|
858
|
+
} else {
|
|
859
|
+
await runCustomValidation(name, value, requestId, resetGenAtStart);
|
|
860
|
+
if (ctx.validationRequestIds.get(name) === requestId) ctx.validationRequestIds.delete(name);
|
|
861
|
+
}
|
|
784
862
|
}
|
|
785
863
|
};
|
|
786
864
|
const onBlur = async (_e) => {
|
|
787
|
-
markFieldTouched(ctx.touchedFields,
|
|
788
|
-
if (ctx.options.mode
|
|
865
|
+
markFieldTouched(ctx.touchedFields, name);
|
|
866
|
+
if (shouldValidateOnBlur(ctx.options.mode ?? "onSubmit", ctx.submitCount.value > 0, ctx.options.reValidateMode)) {
|
|
789
867
|
await validate(name);
|
|
790
868
|
const fieldOpts = ctx.fieldOptions.get(name);
|
|
791
869
|
if (fieldOpts?.deps && fieldOpts.deps.length > 0) {
|
|
@@ -818,15 +896,21 @@ function createFieldRegistration(ctx, validate) {
|
|
|
818
896
|
clearTimeout(schemaTimer);
|
|
819
897
|
ctx.schemaValidationTimers.delete(name);
|
|
820
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);
|
|
821
905
|
ctx.validationRequestIds.delete(name);
|
|
906
|
+
ctx.fieldRefs.delete(name);
|
|
907
|
+
ctx.fieldOptions.delete(name);
|
|
908
|
+
ctx.fieldHandlers.delete(name);
|
|
822
909
|
if (opts?.shouldUnregister ?? ctx.options.shouldUnregister ?? false) {
|
|
823
910
|
unset(ctx.formData, name);
|
|
824
911
|
clearFieldErrors(ctx.errors, name);
|
|
825
|
-
clearFieldTouched(ctx.touchedFields,
|
|
826
|
-
clearFieldDirty(ctx.dirtyFields,
|
|
827
|
-
ctx.fieldRefs.delete(name);
|
|
828
|
-
ctx.fieldOptions.delete(name);
|
|
829
|
-
ctx.fieldHandlers.delete(name);
|
|
912
|
+
clearFieldTouched(ctx.touchedFields, name);
|
|
913
|
+
clearFieldDirty(ctx.dirtyFields, name);
|
|
830
914
|
}
|
|
831
915
|
}
|
|
832
916
|
};
|
|
@@ -847,7 +931,7 @@ function createFieldRegistration(ctx, validate) {
|
|
|
847
931
|
get: () => get(ctx.formData, name),
|
|
848
932
|
set: (val) => {
|
|
849
933
|
set(ctx.formData, name, val);
|
|
850
|
-
|
|
934
|
+
updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, val);
|
|
851
935
|
}
|
|
852
936
|
}) }
|
|
853
937
|
};
|
|
@@ -856,8 +940,8 @@ function createFieldRegistration(ctx, validate) {
|
|
|
856
940
|
const opts = options || {};
|
|
857
941
|
if (!opts.keepValue) unset(ctx.formData, name);
|
|
858
942
|
if (!opts.keepError) clearFieldErrors(ctx.errors, name);
|
|
859
|
-
if (!opts.keepTouched) clearFieldTouched(ctx.touchedFields,
|
|
860
|
-
if (!opts.keepDirty) clearFieldDirty(ctx.dirtyFields,
|
|
943
|
+
if (!opts.keepTouched) clearFieldTouched(ctx.touchedFields, name);
|
|
944
|
+
if (!opts.keepDirty) clearFieldDirty(ctx.dirtyFields, name);
|
|
861
945
|
ctx.fieldRefs.delete(name);
|
|
862
946
|
ctx.fieldOptions.delete(name);
|
|
863
947
|
ctx.fieldHandlers.delete(name);
|
|
@@ -872,6 +956,8 @@ function createFieldRegistration(ctx, validate) {
|
|
|
872
956
|
ctx.schemaValidationTimers.delete(name);
|
|
873
957
|
}
|
|
874
958
|
ctx.validationRequestIds.delete(name);
|
|
959
|
+
ctx.validationCache.delete(`${name}:partial`);
|
|
960
|
+
ctx.validationCache.delete(`${name}:full`);
|
|
875
961
|
}
|
|
876
962
|
return {
|
|
877
963
|
register,
|
|
@@ -961,10 +1047,32 @@ function createFieldArrayManager(ctx, validate, setFocus) {
|
|
|
961
1047
|
const normalizeToArray = (value) => {
|
|
962
1048
|
return Array.isArray(value) ? value : [value];
|
|
963
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
|
+
};
|
|
964
1071
|
const append = (value, focusOptions) => {
|
|
1072
|
+
clearValidationCache();
|
|
965
1073
|
const values = normalizeToArray(value);
|
|
966
1074
|
if (values.length === 0) return true;
|
|
967
|
-
const currentValues =
|
|
1075
|
+
const currentValues = ensureSync();
|
|
968
1076
|
const insertIndex = currentValues.length;
|
|
969
1077
|
const rules = fa.rules;
|
|
970
1078
|
if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) {
|
|
@@ -979,15 +1087,16 @@ function createFieldArrayManager(ctx, validate, setFocus) {
|
|
|
979
1087
|
const newItems = values.map(() => createItem(generateId()));
|
|
980
1088
|
fa.items.value = [...fa.items.value, ...newItems];
|
|
981
1089
|
appendToCache(insertIndex);
|
|
982
|
-
|
|
983
|
-
|
|
1090
|
+
updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
|
|
1091
|
+
validateIfNeeded();
|
|
984
1092
|
handleFocus(insertIndex, values.length, focusOptions);
|
|
985
1093
|
return true;
|
|
986
1094
|
};
|
|
987
1095
|
const prepend = (value, focusOptions) => {
|
|
1096
|
+
clearValidationCache();
|
|
988
1097
|
const values = normalizeToArray(value);
|
|
989
1098
|
if (values.length === 0) return true;
|
|
990
|
-
const currentValues =
|
|
1099
|
+
const currentValues = ensureSync();
|
|
991
1100
|
const rules = fa.rules;
|
|
992
1101
|
if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) {
|
|
993
1102
|
if (__DEV__) warnArrayOperationRejected("prepend", name, "maxLength", {
|
|
@@ -1001,13 +1110,14 @@ function createFieldArrayManager(ctx, validate, setFocus) {
|
|
|
1001
1110
|
const newItems = values.map(() => createItem(generateId()));
|
|
1002
1111
|
fa.items.value = [...newItems, ...fa.items.value];
|
|
1003
1112
|
updateCacheAfterInsert(0, values.length);
|
|
1004
|
-
|
|
1005
|
-
|
|
1113
|
+
updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
|
|
1114
|
+
validateIfNeeded();
|
|
1006
1115
|
handleFocus(0, values.length, focusOptions);
|
|
1007
1116
|
return true;
|
|
1008
1117
|
};
|
|
1009
1118
|
const update = (index, value) => {
|
|
1010
|
-
|
|
1119
|
+
clearValidationCache();
|
|
1120
|
+
const currentValues = ensureSync();
|
|
1011
1121
|
if (index < 0 || index >= currentValues.length) {
|
|
1012
1122
|
if (__DEV__) warnArrayIndexOutOfBounds("update", name, index, currentValues.length);
|
|
1013
1123
|
return false;
|
|
@@ -1015,12 +1125,13 @@ function createFieldArrayManager(ctx, validate, setFocus) {
|
|
|
1015
1125
|
const newValues = [...currentValues];
|
|
1016
1126
|
newValues[index] = value;
|
|
1017
1127
|
set(ctx.formData, name, newValues);
|
|
1018
|
-
|
|
1019
|
-
|
|
1128
|
+
updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
|
|
1129
|
+
validateIfNeeded();
|
|
1020
1130
|
return true;
|
|
1021
1131
|
};
|
|
1022
1132
|
const removeAt = (index) => {
|
|
1023
|
-
|
|
1133
|
+
clearValidationCache();
|
|
1134
|
+
const currentValues = ensureSync();
|
|
1024
1135
|
if (index < 0 || index >= currentValues.length) {
|
|
1025
1136
|
if (__DEV__) warnArrayIndexOutOfBounds("remove", name, index, currentValues.length);
|
|
1026
1137
|
return false;
|
|
@@ -1038,14 +1149,15 @@ function createFieldArrayManager(ctx, validate, setFocus) {
|
|
|
1038
1149
|
const keyToRemove = fa.items.value[index]?.key;
|
|
1039
1150
|
fa.items.value = fa.items.value.filter((item) => item.key !== keyToRemove);
|
|
1040
1151
|
if (keyToRemove) updateCacheAfterRemove(keyToRemove, index);
|
|
1041
|
-
|
|
1042
|
-
|
|
1152
|
+
updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
|
|
1153
|
+
validateIfNeeded();
|
|
1043
1154
|
return true;
|
|
1044
1155
|
};
|
|
1045
1156
|
const insert = (index, value, focusOptions) => {
|
|
1157
|
+
clearValidationCache();
|
|
1046
1158
|
const values = normalizeToArray(value);
|
|
1047
1159
|
if (values.length === 0) return true;
|
|
1048
|
-
const currentValues =
|
|
1160
|
+
const currentValues = ensureSync();
|
|
1049
1161
|
const rules = fa.rules;
|
|
1050
1162
|
if (rules?.maxLength && currentValues.length + values.length > rules.maxLength.value) {
|
|
1051
1163
|
if (__DEV__) warnArrayOperationRejected("insert", name, "maxLength", {
|
|
@@ -1054,27 +1166,31 @@ function createFieldArrayManager(ctx, validate, setFocus) {
|
|
|
1054
1166
|
});
|
|
1055
1167
|
return false;
|
|
1056
1168
|
}
|
|
1057
|
-
|
|
1169
|
+
if (index < 0 || index > currentValues.length) {
|
|
1170
|
+
if (__DEV__) warnArrayIndexOutOfBounds("insert", name, index, currentValues.length);
|
|
1171
|
+
return false;
|
|
1172
|
+
}
|
|
1058
1173
|
const newValues = [
|
|
1059
|
-
...currentValues.slice(0,
|
|
1174
|
+
...currentValues.slice(0, index),
|
|
1060
1175
|
...values,
|
|
1061
|
-
...currentValues.slice(
|
|
1176
|
+
...currentValues.slice(index)
|
|
1062
1177
|
];
|
|
1063
1178
|
set(ctx.formData, name, newValues);
|
|
1064
1179
|
const newItems = values.map(() => createItem(generateId()));
|
|
1065
1180
|
fa.items.value = [
|
|
1066
|
-
...fa.items.value.slice(0,
|
|
1181
|
+
...fa.items.value.slice(0, index),
|
|
1067
1182
|
...newItems,
|
|
1068
|
-
...fa.items.value.slice(
|
|
1183
|
+
...fa.items.value.slice(index)
|
|
1069
1184
|
];
|
|
1070
|
-
updateCacheAfterInsert(
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
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);
|
|
1074
1189
|
return true;
|
|
1075
1190
|
};
|
|
1076
1191
|
const swap = (indexA, indexB) => {
|
|
1077
|
-
|
|
1192
|
+
clearValidationCache();
|
|
1193
|
+
const currentValues = ensureSync();
|
|
1078
1194
|
if (indexA < 0 || indexB < 0 || indexA >= currentValues.length || indexB >= currentValues.length) {
|
|
1079
1195
|
if (__DEV__) warnArrayIndexOutOfBounds("swap", name, indexA < 0 || indexA >= currentValues.length ? indexA : indexB, currentValues.length);
|
|
1080
1196
|
return false;
|
|
@@ -1091,51 +1207,52 @@ function createFieldArrayManager(ctx, validate, setFocus) {
|
|
|
1091
1207
|
fa.items.value = newItems;
|
|
1092
1208
|
swapInCache(indexA, indexB);
|
|
1093
1209
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1210
|
+
updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
|
|
1211
|
+
validateIfNeeded();
|
|
1096
1212
|
return true;
|
|
1097
1213
|
};
|
|
1098
1214
|
const move = (from, to) => {
|
|
1099
|
-
|
|
1100
|
-
|
|
1215
|
+
clearValidationCache();
|
|
1216
|
+
const currentValues = ensureSync();
|
|
1217
|
+
if (from < 0 || from >= currentValues.length || to < 0 || to >= currentValues.length) {
|
|
1101
1218
|
if (__DEV__) warnArrayIndexOutOfBounds("move", name, from < 0 || from >= currentValues.length ? from : to, currentValues.length);
|
|
1102
1219
|
return false;
|
|
1103
1220
|
}
|
|
1104
1221
|
const newValues = [...currentValues];
|
|
1105
1222
|
const [removed] = newValues.splice(from, 1);
|
|
1106
1223
|
if (removed !== void 0) {
|
|
1107
|
-
|
|
1108
|
-
newValues.splice(clampedTo, 0, removed);
|
|
1224
|
+
newValues.splice(to, 0, removed);
|
|
1109
1225
|
set(ctx.formData, name, newValues);
|
|
1110
1226
|
}
|
|
1111
1227
|
const newItems = [...fa.items.value];
|
|
1112
1228
|
const [removedItem] = newItems.splice(from, 1);
|
|
1113
1229
|
if (removedItem) {
|
|
1114
|
-
|
|
1115
|
-
newItems.splice(clampedTo, 0, removedItem);
|
|
1230
|
+
newItems.splice(to, 0, removedItem);
|
|
1116
1231
|
fa.items.value = newItems;
|
|
1117
|
-
const minIdx = Math.min(from,
|
|
1118
|
-
const maxIdx = Math.max(from,
|
|
1232
|
+
const minIdx = Math.min(from, to);
|
|
1233
|
+
const maxIdx = Math.max(from, to);
|
|
1119
1234
|
const items = fa.items.value;
|
|
1120
1235
|
for (let i = minIdx; i <= maxIdx; i++) {
|
|
1121
1236
|
const item = items[i];
|
|
1122
1237
|
if (item) indexCache.set(item.key, i);
|
|
1123
1238
|
}
|
|
1124
1239
|
}
|
|
1125
|
-
|
|
1126
|
-
|
|
1240
|
+
updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
|
|
1241
|
+
validateIfNeeded();
|
|
1127
1242
|
return true;
|
|
1128
1243
|
};
|
|
1129
1244
|
const replace = (newValues) => {
|
|
1245
|
+
clearValidationCache();
|
|
1130
1246
|
if (!Array.isArray(newValues)) return false;
|
|
1131
1247
|
set(ctx.formData, name, newValues);
|
|
1132
1248
|
fa.items.value = newValues.map(() => createItem(generateId()));
|
|
1133
1249
|
rebuildIndexCache();
|
|
1134
|
-
|
|
1135
|
-
|
|
1250
|
+
updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
|
|
1251
|
+
validateIfNeeded();
|
|
1136
1252
|
return true;
|
|
1137
1253
|
};
|
|
1138
1254
|
const removeAll = () => {
|
|
1255
|
+
clearValidationCache();
|
|
1139
1256
|
const rules = fa.rules;
|
|
1140
1257
|
if (rules?.minLength && rules.minLength.value > 0) {
|
|
1141
1258
|
if (__DEV__) warnArrayOperationRejected("removeAll", name, "minLength", {
|
|
@@ -1147,12 +1264,13 @@ function createFieldArrayManager(ctx, validate, setFocus) {
|
|
|
1147
1264
|
set(ctx.formData, name, []);
|
|
1148
1265
|
fa.items.value = [];
|
|
1149
1266
|
indexCache.clear();
|
|
1150
|
-
|
|
1151
|
-
|
|
1267
|
+
updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
|
|
1268
|
+
validateIfNeeded();
|
|
1152
1269
|
return true;
|
|
1153
1270
|
};
|
|
1154
1271
|
const removeMany = (indices) => {
|
|
1155
|
-
|
|
1272
|
+
clearValidationCache();
|
|
1273
|
+
const currentValues = ensureSync();
|
|
1156
1274
|
const validIndices = indices.filter((i) => i >= 0 && i < currentValues.length);
|
|
1157
1275
|
if (validIndices.length === 0) return true;
|
|
1158
1276
|
const rules = fa.rules;
|
|
@@ -1174,8 +1292,8 @@ function createFieldArrayManager(ctx, validate, setFocus) {
|
|
|
1174
1292
|
fa.items.value.forEach((item, idx) => {
|
|
1175
1293
|
indexCache.set(item.key, idx);
|
|
1176
1294
|
});
|
|
1177
|
-
|
|
1178
|
-
|
|
1295
|
+
updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, get(ctx.formData, name));
|
|
1296
|
+
validateIfNeeded();
|
|
1179
1297
|
return true;
|
|
1180
1298
|
};
|
|
1181
1299
|
return {
|
|
@@ -1198,7 +1316,13 @@ function syncUncontrolledInputs(fieldRefs, fieldOptions, formData) {
|
|
|
1198
1316
|
for (const [name, fieldRef] of Array.from(fieldRefs.entries())) {
|
|
1199
1317
|
const el = fieldRef.value;
|
|
1200
1318
|
if (el) {
|
|
1201
|
-
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
|
+
}
|
|
1202
1326
|
}
|
|
1203
1327
|
}
|
|
1204
1328
|
}
|
|
@@ -1208,6 +1332,10 @@ function updateDomElement(el, value) {
|
|
|
1208
1332
|
}
|
|
1209
1333
|
function useForm(options) {
|
|
1210
1334
|
const ctx = createFormContext(options);
|
|
1335
|
+
let isSubmissionLocked = false;
|
|
1336
|
+
onUnmounted(() => {
|
|
1337
|
+
ctx.cleanup();
|
|
1338
|
+
});
|
|
1211
1339
|
const { validate, clearAllPendingErrors } = createValidation(ctx);
|
|
1212
1340
|
const { register, unregister } = createFieldRegistration(ctx, validate);
|
|
1213
1341
|
function setFocus(name, focusOptions) {
|
|
@@ -1250,10 +1378,10 @@ function useForm(options) {
|
|
|
1250
1378
|
};
|
|
1251
1379
|
return cachedMergedErrors;
|
|
1252
1380
|
}
|
|
1253
|
-
const isDirtyComputed = computed(() => ctx.
|
|
1381
|
+
const isDirtyComputed = computed(() => Object.keys(ctx.dirtyFields.value).length > 0);
|
|
1254
1382
|
const errorsComputed = computed(() => getMergedErrors());
|
|
1255
1383
|
const isValidComputed = computed(() => {
|
|
1256
|
-
if (!(ctx.submitCount.value > 0 || ctx.
|
|
1384
|
+
if (!(ctx.submitCount.value > 0 || Object.keys(ctx.touchedFields.value).length > 0)) return false;
|
|
1257
1385
|
return Object.keys(errorsComputed.value).length === 0;
|
|
1258
1386
|
});
|
|
1259
1387
|
const isReadyComputed = computed(() => !ctx.isLoading.value);
|
|
@@ -1311,7 +1439,8 @@ function useForm(options) {
|
|
|
1311
1439
|
return async (e) => {
|
|
1312
1440
|
e.preventDefault();
|
|
1313
1441
|
if (ctx.isDisabled.value) return;
|
|
1314
|
-
if (
|
|
1442
|
+
if (isSubmissionLocked) return;
|
|
1443
|
+
isSubmissionLocked = true;
|
|
1315
1444
|
ctx.isSubmitting.value = true;
|
|
1316
1445
|
ctx.submitCount.value++;
|
|
1317
1446
|
ctx.isSubmitSuccessful.value = false;
|
|
@@ -1329,6 +1458,7 @@ function useForm(options) {
|
|
|
1329
1458
|
}
|
|
1330
1459
|
} finally {
|
|
1331
1460
|
ctx.isSubmitting.value = false;
|
|
1461
|
+
isSubmissionLocked = false;
|
|
1332
1462
|
}
|
|
1333
1463
|
};
|
|
1334
1464
|
}
|
|
@@ -1342,8 +1472,9 @@ function useForm(options) {
|
|
|
1342
1472
|
}
|
|
1343
1473
|
}
|
|
1344
1474
|
set(ctx.formData, name, value);
|
|
1345
|
-
if (setValueOptions?.shouldDirty
|
|
1346
|
-
|
|
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);
|
|
1347
1478
|
if (!ctx.fieldOptions.get(name)?.controlled) {
|
|
1348
1479
|
const fieldRef = ctx.fieldRefs.get(name);
|
|
1349
1480
|
if (fieldRef?.value) updateDomElement(fieldRef.value, value);
|
|
@@ -1352,25 +1483,28 @@ function useForm(options) {
|
|
|
1352
1483
|
}
|
|
1353
1484
|
function reset(values, resetOptions) {
|
|
1354
1485
|
const opts = resetOptions || {};
|
|
1486
|
+
ctx.validationCache.clear();
|
|
1355
1487
|
ctx.resetGeneration.value++;
|
|
1356
1488
|
clearAllPendingErrors();
|
|
1357
1489
|
ctx.validatingFields.value = /* @__PURE__ */ new Set();
|
|
1358
|
-
ctx.validationCache.clear();
|
|
1359
1490
|
for (const timer of ctx.schemaValidationTimers.values()) clearTimeout(timer);
|
|
1360
1491
|
ctx.schemaValidationTimers.clear();
|
|
1361
|
-
|
|
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
|
+
}
|
|
1362
1499
|
Object.keys(ctx.formData).forEach((key) => delete ctx.formData[key]);
|
|
1363
1500
|
const newValues = deepClone(values || ctx.defaultValues);
|
|
1364
1501
|
Object.assign(ctx.formData, newValues);
|
|
1365
|
-
if (!opts.keepErrors)
|
|
1366
|
-
|
|
1367
|
-
ctx.
|
|
1368
|
-
ctx.touchedFieldCount.value = 0;
|
|
1369
|
-
}
|
|
1370
|
-
if (!opts.keepDirty) {
|
|
1371
|
-
ctx.dirtyFields.value = {};
|
|
1372
|
-
ctx.dirtyFieldCount.value = 0;
|
|
1502
|
+
if (!opts.keepErrors) {
|
|
1503
|
+
ctx.errors.value = {};
|
|
1504
|
+
ctx.persistentErrorFields.clear();
|
|
1373
1505
|
}
|
|
1506
|
+
if (!opts.keepTouched) ctx.touchedFields.value = {};
|
|
1507
|
+
if (!opts.keepDirty) ctx.dirtyFields.value = {};
|
|
1374
1508
|
if (!opts.keepSubmitCount) ctx.submitCount.value = 0;
|
|
1375
1509
|
if (!opts.keepIsSubmitting) ctx.isSubmitting.value = false;
|
|
1376
1510
|
if (!opts.keepIsSubmitSuccessful) ctx.isSubmitSuccessful.value = false;
|
|
@@ -1395,6 +1529,8 @@ function useForm(options) {
|
|
|
1395
1529
|
}
|
|
1396
1530
|
const opts = resetFieldOptions || {};
|
|
1397
1531
|
ctx.resetGeneration.value++;
|
|
1532
|
+
ctx.validationCache.delete(`${name}:partial`);
|
|
1533
|
+
ctx.validationCache.delete(`${name}:full`);
|
|
1398
1534
|
const errorTimer = ctx.errorDelayTimers.get(name);
|
|
1399
1535
|
if (errorTimer) {
|
|
1400
1536
|
clearTimeout(errorTimer);
|
|
@@ -1403,12 +1539,15 @@ function useForm(options) {
|
|
|
1403
1539
|
ctx.pendingErrors.delete(name);
|
|
1404
1540
|
let defaultValue = opts.defaultValue;
|
|
1405
1541
|
if (defaultValue === void 0) defaultValue = get(ctx.defaultValues, name);
|
|
1406
|
-
else
|
|
1542
|
+
else {
|
|
1543
|
+
set(ctx.defaultValues, name, defaultValue);
|
|
1544
|
+
ctx.defaultValueHashes.set(name, hashValue(defaultValue));
|
|
1545
|
+
}
|
|
1407
1546
|
const clonedValue = defaultValue !== void 0 ? deepClone(defaultValue) : void 0;
|
|
1408
1547
|
set(ctx.formData, name, clonedValue);
|
|
1409
1548
|
if (!opts.keepError) clearFieldErrors(ctx.errors, name);
|
|
1410
|
-
if (!opts.keepDirty) clearFieldDirty(ctx.dirtyFields,
|
|
1411
|
-
if (!opts.keepTouched) clearFieldTouched(ctx.touchedFields,
|
|
1549
|
+
if (!opts.keepDirty) clearFieldDirty(ctx.dirtyFields, name);
|
|
1550
|
+
if (!opts.keepTouched) clearFieldTouched(ctx.touchedFields, name);
|
|
1412
1551
|
if (!ctx.fieldOptions.get(name)?.controlled) {
|
|
1413
1552
|
const fieldRef = ctx.fieldRefs.get(name);
|
|
1414
1553
|
if (fieldRef?.value) updateDomElement(fieldRef.value, clonedValue ?? (fieldRef.value.type === "checkbox" ? false : ""));
|
|
@@ -1451,10 +1590,16 @@ function useForm(options) {
|
|
|
1451
1590
|
}
|
|
1452
1591
|
if (name === void 0) {
|
|
1453
1592
|
ctx.errors.value = {};
|
|
1593
|
+
ctx.externalErrors.value = {};
|
|
1594
|
+
ctx.persistentErrorFields.clear();
|
|
1454
1595
|
return;
|
|
1455
1596
|
}
|
|
1456
1597
|
const fieldsToClean = Array.isArray(name) ? name : [name];
|
|
1457
|
-
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
|
+
}
|
|
1458
1603
|
}
|
|
1459
1604
|
function setError(name, error) {
|
|
1460
1605
|
const newErrors = { ...ctx.errors.value };
|
|
@@ -1463,6 +1608,7 @@ function useForm(options) {
|
|
|
1463
1608
|
message: error.message
|
|
1464
1609
|
} : error.message);
|
|
1465
1610
|
ctx.errors.value = newErrors;
|
|
1611
|
+
if (error.persistent) ctx.persistentErrorFields.add(name);
|
|
1466
1612
|
}
|
|
1467
1613
|
function setErrors(errors, options$1) {
|
|
1468
1614
|
const newErrors = options$1?.shouldReplace ? {} : { ...ctx.errors.value };
|
|
@@ -1524,7 +1670,7 @@ function useForm(options) {
|
|
|
1524
1670
|
error
|
|
1525
1671
|
};
|
|
1526
1672
|
}
|
|
1527
|
-
async function trigger(name) {
|
|
1673
|
+
async function trigger(name, options$1) {
|
|
1528
1674
|
if (__DEV__ && name) {
|
|
1529
1675
|
const names = Array.isArray(name) ? name : [name];
|
|
1530
1676
|
for (const n of names) {
|
|
@@ -1536,6 +1682,7 @@ function useForm(options) {
|
|
|
1536
1682
|
}
|
|
1537
1683
|
}
|
|
1538
1684
|
}
|
|
1685
|
+
if (options$1?.markAsSubmitted) ctx.submitCount.value++;
|
|
1539
1686
|
if (name === void 0) return await validate();
|
|
1540
1687
|
if (Array.isArray(name)) {
|
|
1541
1688
|
let allValid = true;
|
|
@@ -1563,7 +1710,11 @@ function useForm(options) {
|
|
|
1563
1710
|
getValues,
|
|
1564
1711
|
getFieldState,
|
|
1565
1712
|
trigger,
|
|
1566
|
-
setFocus
|
|
1713
|
+
setFocus,
|
|
1714
|
+
options: {
|
|
1715
|
+
mode: ctx.options.mode ?? "onSubmit",
|
|
1716
|
+
reValidateMode: ctx.options.reValidateMode
|
|
1717
|
+
}
|
|
1567
1718
|
};
|
|
1568
1719
|
}
|
|
1569
1720
|
const FormContextKey = Symbol("FormContext");
|
|
@@ -1602,13 +1753,22 @@ function useController(options) {
|
|
|
1602
1753
|
}
|
|
1603
1754
|
});
|
|
1604
1755
|
const onChange = (newValue) => {
|
|
1605
|
-
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 });
|
|
1606
1762
|
};
|
|
1607
1763
|
const onBlur = () => {
|
|
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);
|
|
1608
1768
|
const currentValue = form.getValues(name);
|
|
1609
1769
|
form.setValue(name, currentValue, {
|
|
1610
1770
|
shouldTouch: true,
|
|
1611
|
-
shouldValidate
|
|
1771
|
+
shouldValidate,
|
|
1612
1772
|
shouldDirty: false
|
|
1613
1773
|
});
|
|
1614
1774
|
};
|
|
@@ -1646,4 +1806,4 @@ function useFormState(options = {}) {
|
|
|
1646
1806
|
function isFieldError(error) {
|
|
1647
1807
|
return typeof error === "object" && error !== null && "type" in error && "message" in error && typeof error.type === "string" && typeof error.message === "string";
|
|
1648
1808
|
}
|
|
1649
|
-
export { FormContextKey, isFieldError, provideForm, useController, useForm, useFormContext, useFormState, useWatch };
|
|
1809
|
+
export { FormContextKey, clearPathCache, generateId, get, isFieldError, provideForm, set, unset, useController, useForm, useFormContext, useFormState, useWatch };
|