vee-validate 4.5.4 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  /**
2
- * vee-validate v4.5.4
3
- * (c) 2021 Abdelrahman Awad
2
+ * vee-validate v4.6.0
3
+ * (c) 2022 Abdelrahman Awad
4
4
  * @license MIT
5
5
  */
6
6
  (function (global, factory) {
@@ -56,18 +56,6 @@
56
56
  function isLocator(value) {
57
57
  return isCallable(value) && !!value.__locatorRef;
58
58
  }
59
- /**
60
- * Checks if an tag name is a native HTML tag and not a Vue component
61
- */
62
- function isHTMLTag(tag) {
63
- return ['input', 'textarea', 'select'].includes(tag);
64
- }
65
- /**
66
- * Checks if an input is of type file
67
- */
68
- function isFileInputNode(tag, attrs) {
69
- return isHTMLTag(tag) && attrs.type === 'file';
70
- }
71
59
  function isYupValidator(value) {
72
60
  return !!value && isCallable(value.validate);
73
61
  }
@@ -120,7 +108,7 @@
120
108
  * For multi-selects because the value binding will reset the value
121
109
  */
122
110
  function shouldHaveValueBinding(tag, attrs) {
123
- return isNativeMultiSelectNode(tag, attrs) || isFileInputNode(tag, attrs);
111
+ return !isNativeMultiSelectNode(tag, attrs) && attrs.type !== 'file' && !hasCheckedAttr(attrs.type);
124
112
  }
125
113
  function isFormSubmitEvent(evt) {
126
114
  return isEvent(evt) && evt.target && 'submit' in evt.target;
@@ -286,6 +274,15 @@
286
274
  }, ms);
287
275
  return new Promise(resolve => resolves.push(resolve));
288
276
  };
277
+ }
278
+ function applyModelModifiers(value, modifiers) {
279
+ if (!isObject(modifiers)) {
280
+ return;
281
+ }
282
+ if (modifiers.number) {
283
+ return toNumber(value);
284
+ }
285
+ return value;
289
286
  }
290
287
 
291
288
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -329,7 +326,8 @@
329
326
  return getBoundValue(input);
330
327
  }
331
328
  if (input.type === 'file' && input.files) {
332
- return Array.from(input.files);
329
+ const files = Array.from(input.files);
330
+ return input.multiple ? files : files[0];
333
331
  }
334
332
  if (isNativeMultiSelect(input)) {
335
333
  return Array.from(input.options)
@@ -488,18 +486,34 @@
488
486
  if (isYupValidator(field.rules)) {
489
487
  return validateFieldWithYup(value, field.rules, { bails: field.bails });
490
488
  }
491
- // if a generic function, use it as the pipeline.
492
- if (isCallable(field.rules)) {
489
+ // if a generic function or chain of generic functions
490
+ if (isCallable(field.rules) || Array.isArray(field.rules)) {
493
491
  const ctx = {
494
492
  field: field.name,
495
493
  form: field.formData,
496
494
  value: value,
497
495
  };
498
- const result = await field.rules(value, ctx);
499
- const isValid = typeof result !== 'string' && result;
500
- const message = typeof result === 'string' ? result : _generateFieldError(ctx);
496
+ // Normalize the pipeline
497
+ const pipeline = Array.isArray(field.rules) ? field.rules : [field.rules];
498
+ const length = pipeline.length;
499
+ const errors = [];
500
+ for (let i = 0; i < length; i++) {
501
+ const rule = pipeline[i];
502
+ const result = await rule(value, ctx);
503
+ const isValid = typeof result !== 'string' && result;
504
+ if (isValid) {
505
+ continue;
506
+ }
507
+ const message = typeof result === 'string' ? result : _generateFieldError(ctx);
508
+ errors.push(message);
509
+ if (field.bails) {
510
+ return {
511
+ errors,
512
+ };
513
+ }
514
+ }
501
515
  return {
502
- errors: !isValid ? [message] : [],
516
+ errors,
503
517
  };
504
518
  }
505
519
  const normalizedContext = Object.assign(Object.assign({}, field), { rules: normalizeRules(field.rules) });
@@ -812,8 +826,8 @@
812
826
  /**
813
827
  * Creates the field value and resolves the initial value
814
828
  */
815
- function _useFieldValue(path, modelValue, shouldInjectForm) {
816
- const form = shouldInjectForm ? injectWithSelf(FormContextKey, undefined) : undefined;
829
+ function _useFieldValue(path, modelValue, shouldInjectForm = true) {
830
+ const form = shouldInjectForm === true ? injectWithSelf(FormContextKey, undefined) : undefined;
817
831
  const modelRef = vue.ref(vue.unref(modelValue));
818
832
  function resolveInitialValue() {
819
833
  if (!form) {
@@ -843,7 +857,7 @@
843
857
  // prioritize model value over form values
844
858
  // #3429
845
859
  const currentValue = modelValue ? vue.unref(modelValue) : getFromPath(form.values, vue.unref(path), vue.unref(initialValue));
846
- form.stageInitialValue(vue.unref(path), currentValue);
860
+ form.stageInitialValue(vue.unref(path), currentValue, true);
847
861
  // otherwise use a computed setter that triggers the `setFieldValue`
848
862
  const value = vue.computed({
849
863
  get() {
@@ -922,12 +936,17 @@
922
936
  return _useField(name, rules, opts);
923
937
  }
924
938
  function _useField(name, rules, opts) {
925
- const { initialValue: modelValue, validateOnMount, bails, type, checkedValue, label, validateOnValueUpdate, uncheckedValue, standalone, } = normalizeOptions(vue.unref(name), opts);
939
+ const { initialValue: modelValue, validateOnMount, bails, type, checkedValue, label, validateOnValueUpdate, uncheckedValue, standalone, keepValueOnUnmount, modelPropName, syncVModel, } = normalizeOptions(vue.unref(name), opts);
926
940
  const form = !standalone ? injectWithSelf(FormContextKey) : undefined;
941
+ // a flag indicating if the field is about to be removed/unmounted.
942
+ let markedForRemoval = false;
927
943
  const { id, value, initialValue, meta, setState, errors, errorMessage } = useFieldState(name, {
928
944
  modelValue,
929
945
  standalone,
930
946
  });
947
+ if (syncVModel) {
948
+ useVModel({ value, prop: modelPropName, handleChange });
949
+ }
931
950
  /**
932
951
  * Handles common onBlur meta update
933
952
  */
@@ -940,7 +959,7 @@
940
959
  if (schema && !isYupValidator(schema)) {
941
960
  rulesValue = extractRuleFromSchema(schema, vue.unref(name)) || rulesValue;
942
961
  }
943
- if (isYupValidator(rulesValue) || isCallable(rulesValue)) {
962
+ if (isYupValidator(rulesValue) || isCallable(rulesValue) || Array.isArray(rulesValue)) {
944
963
  return rulesValue;
945
964
  }
946
965
  return normalizeRules(rulesValue);
@@ -960,12 +979,19 @@
960
979
  meta.pending = true;
961
980
  meta.validated = true;
962
981
  const result = await validateCurrentValue('validated-only');
982
+ if (markedForRemoval) {
983
+ result.valid = true;
984
+ result.errors = [];
985
+ }
963
986
  setState({ errors: result.errors });
964
987
  meta.pending = false;
965
988
  return result;
966
989
  }
967
990
  async function validateValidStateOnly() {
968
991
  const result = await validateCurrentValue('silent');
992
+ if (markedForRemoval) {
993
+ result.valid = true;
994
+ }
969
995
  meta.valid = result.valid;
970
996
  return result;
971
997
  }
@@ -979,13 +1005,13 @@
979
1005
  return validateValidStateOnly();
980
1006
  }
981
1007
  // Common input/change event handler
982
- const handleChange = (e, shouldValidate = true) => {
1008
+ function handleChange(e, shouldValidate = true) {
983
1009
  const newValue = normalizeEventValue(e);
984
1010
  value.value = newValue;
985
1011
  if (!validateOnValueUpdate && shouldValidate) {
986
1012
  validateWithStateMutation();
987
1013
  }
988
- };
1014
+ }
989
1015
  // Runs the initial validation
990
1016
  vue.onMounted(() => {
991
1017
  if (validateOnMount) {
@@ -1002,7 +1028,13 @@
1002
1028
  }
1003
1029
  let unwatchValue;
1004
1030
  function watchValue() {
1005
- unwatchValue = vue.watch(value, validateOnValueUpdate ? validateWithStateMutation : validateValidStateOnly, {
1031
+ unwatchValue = vue.watch(value, (val, oldVal) => {
1032
+ if (es6(val, oldVal)) {
1033
+ return;
1034
+ }
1035
+ const validateFn = validateOnValueUpdate ? validateWithStateMutation : validateValidStateOnly;
1036
+ validateFn();
1037
+ }, {
1006
1038
  deep: true,
1007
1039
  });
1008
1040
  }
@@ -1043,6 +1075,7 @@
1043
1075
  checkedValue,
1044
1076
  uncheckedValue,
1045
1077
  bails,
1078
+ keepValueOnUnmount,
1046
1079
  resetField,
1047
1080
  handleReset: () => resetField(),
1048
1081
  validate: validate$1,
@@ -1071,13 +1104,14 @@
1071
1104
  // associate the field with the given form
1072
1105
  form.register(field);
1073
1106
  vue.onBeforeUnmount(() => {
1107
+ markedForRemoval = true;
1074
1108
  form.unregister(field);
1075
1109
  });
1076
1110
  // extract cross-field dependencies in a computed prop
1077
1111
  const dependencies = vue.computed(() => {
1078
1112
  const rulesVal = normalizedRules.value;
1079
1113
  // is falsy, a function schema or a yup schema
1080
- if (!rulesVal || isCallable(rulesVal) || isYupValidator(rulesVal)) {
1114
+ if (!rulesVal || isCallable(rulesVal) || isYupValidator(rulesVal) || Array.isArray(rulesVal)) {
1081
1115
  return {};
1082
1116
  }
1083
1117
  return Object.keys(rulesVal).reduce((acc, rule) => {
@@ -1119,6 +1153,9 @@
1119
1153
  label: name,
1120
1154
  validateOnValueUpdate: true,
1121
1155
  standalone: false,
1156
+ keepValueOnUnmount: undefined,
1157
+ modelPropName: 'modelValue',
1158
+ syncVModel: true,
1122
1159
  });
1123
1160
  if (!opts) {
1124
1161
  return defaults();
@@ -1150,8 +1187,8 @@
1150
1187
  return Array.isArray(currentValue) ? currentValue.includes(checkedVal) : checkedVal === currentValue;
1151
1188
  });
1152
1189
  function handleCheckboxChange(e, shouldValidate = true) {
1153
- var _a, _b;
1154
- if (checked.value === ((_b = (_a = e) === null || _a === void 0 ? void 0 : _a.target) === null || _b === void 0 ? void 0 : _b.checked)) {
1190
+ var _a;
1191
+ if (checked.value === ((_a = e === null || e === void 0 ? void 0 : e.target) === null || _a === void 0 ? void 0 : _a.checked)) {
1155
1192
  return;
1156
1193
  }
1157
1194
  let newValue = normalizeEventValue(e);
@@ -1162,8 +1199,10 @@
1162
1199
  handleChange(newValue, shouldValidate);
1163
1200
  }
1164
1201
  vue.onBeforeUnmount(() => {
1165
- // toggles the checkbox value if it was checked
1166
- if (checked.value) {
1202
+ var _a, _b;
1203
+ const shouldKeepValue = (_b = (_a = vue.unref(field.keepValueOnUnmount)) !== null && _a !== void 0 ? _a : vue.unref(form === null || form === void 0 ? void 0 : form.keepValuesOnUnmount)) !== null && _b !== void 0 ? _b : false;
1204
+ // toggles the checkbox value if it was checked and the unset behavior is set
1205
+ if (checked.value && !shouldKeepValue) {
1167
1206
  handleCheckboxChange(vue.unref(checkedValue), false);
1168
1207
  }
1169
1208
  });
@@ -1172,6 +1211,38 @@
1172
1211
  uncheckedValue, handleChange: handleCheckboxChange });
1173
1212
  }
1174
1213
  return patchCheckboxApi(_useField(name, rules, opts));
1214
+ }
1215
+ function useVModel({ prop, value, handleChange }) {
1216
+ const vm = vue.getCurrentInstance();
1217
+ /* istanbul ignore next */
1218
+ if (!vm) {
1219
+ return;
1220
+ }
1221
+ const propName = prop || 'modelValue';
1222
+ const emitName = `update:${propName}`;
1223
+ // Component doesn't have a model prop setup (must be defined on the props)
1224
+ if (!(propName in vm.props)) {
1225
+ return;
1226
+ }
1227
+ vue.watch(value, newValue => {
1228
+ if (es6(newValue, getCurrentModelValue(vm, propName))) {
1229
+ return;
1230
+ }
1231
+ vm.emit(emitName, newValue);
1232
+ });
1233
+ vue.watch(() => getCurrentModelValue(vm, propName), propValue => {
1234
+ if (propValue === IS_ABSENT && value.value === undefined) {
1235
+ return;
1236
+ }
1237
+ const newValue = propValue === IS_ABSENT ? undefined : propValue;
1238
+ if (es6(newValue, applyModelModifiers(value.value, vm.props.modelModifiers))) {
1239
+ return;
1240
+ }
1241
+ handleChange(newValue);
1242
+ });
1243
+ }
1244
+ function getCurrentModelValue(vm, propName) {
1245
+ return vm.props[propName];
1175
1246
  }
1176
1247
 
1177
1248
  const FieldImpl = vue.defineComponent({
@@ -1238,13 +1309,17 @@
1238
1309
  type: Boolean,
1239
1310
  default: false,
1240
1311
  },
1312
+ keepValue: {
1313
+ type: Boolean,
1314
+ default: undefined,
1315
+ },
1241
1316
  },
1242
1317
  setup(props, ctx) {
1243
1318
  const rules = vue.toRef(props, 'rules');
1244
1319
  const name = vue.toRef(props, 'name');
1245
1320
  const label = vue.toRef(props, 'label');
1246
1321
  const uncheckedValue = vue.toRef(props, 'uncheckedValue');
1247
- const hasModelEvents = isPropPresent(props, 'onUpdate:modelValue');
1322
+ const keepValue = vue.toRef(props, 'keepValue');
1248
1323
  const { errors, value, errorMessage, validate: validateField, handleChange, handleBlur, setTouched, resetField, handleReset, meta, checked, setErrors, } = useField(name, rules, {
1249
1324
  validateOnMount: props.validateOnMount,
1250
1325
  bails: props.bails,
@@ -1256,25 +1331,22 @@
1256
1331
  uncheckedValue,
1257
1332
  label,
1258
1333
  validateOnValueUpdate: false,
1334
+ keepValueOnUnmount: keepValue,
1259
1335
  });
1260
1336
  // If there is a v-model applied on the component we need to emit the `update:modelValue` whenever the value binding changes
1261
- const onChangeHandler = hasModelEvents
1262
- ? function handleChangeWithModel(e, shouldValidate = true) {
1263
- handleChange(e, shouldValidate);
1264
- ctx.emit('update:modelValue', value.value);
1265
- }
1266
- : handleChange;
1337
+ const onChangeHandler = function handleChangeWithModel(e, shouldValidate = true) {
1338
+ handleChange(e, shouldValidate);
1339
+ ctx.emit('update:modelValue', value.value);
1340
+ };
1267
1341
  const handleInput = (e) => {
1268
1342
  if (!hasCheckedAttr(ctx.attrs.type)) {
1269
1343
  value.value = normalizeEventValue(e);
1270
1344
  }
1271
1345
  };
1272
- const onInputHandler = hasModelEvents
1273
- ? function handleInputWithModel(e) {
1274
- handleInput(e);
1275
- ctx.emit('update:modelValue', value.value);
1276
- }
1277
- : handleInput;
1346
+ const onInputHandler = function handleInputWithModel(e) {
1347
+ handleInput(e);
1348
+ ctx.emit('update:modelValue', value.value);
1349
+ };
1278
1350
  const fieldProps = vue.computed(() => {
1279
1351
  const { validateOnInput, validateOnChange, validateOnBlur, validateOnModelUpdate } = resolveValidationTriggers(props);
1280
1352
  const baseOnBlur = [handleBlur, ctx.attrs.onBlur, validateOnBlur ? validateField : undefined].filter(Boolean);
@@ -1286,32 +1358,16 @@
1286
1358
  onInput: baseOnInput,
1287
1359
  onChange: baseOnChange,
1288
1360
  };
1289
- if (validateOnModelUpdate) {
1290
- attrs['onUpdate:modelValue'] = [onChangeHandler];
1291
- }
1361
+ attrs['onUpdate:modelValue'] = e => onChangeHandler(e, validateOnModelUpdate);
1292
1362
  if (hasCheckedAttr(ctx.attrs.type) && checked) {
1293
1363
  attrs.checked = checked.value;
1294
1364
  }
1295
- else {
1296
- attrs.value = value.value;
1297
- }
1298
1365
  const tag = resolveTag(props, ctx);
1299
1366
  if (shouldHaveValueBinding(tag, ctx.attrs)) {
1300
- delete attrs.value;
1367
+ attrs.value = value.value;
1301
1368
  }
1302
1369
  return attrs;
1303
1370
  });
1304
- const modelValue = vue.toRef(props, 'modelValue');
1305
- vue.watch(modelValue, newModelValue => {
1306
- // Don't attempt to sync absent values
1307
- if (newModelValue === IS_ABSENT && value.value === undefined) {
1308
- return;
1309
- }
1310
- if (newModelValue !== applyModifiers(value.value, props.modelModifiers)) {
1311
- value.value = newModelValue === IS_ABSENT ? undefined : newModelValue;
1312
- validateField();
1313
- }
1314
- });
1315
1371
  function slotProps() {
1316
1372
  return {
1317
1373
  field: fieldProps.value,
@@ -1363,12 +1419,6 @@
1363
1419
  validateOnModelUpdate: (_d = props.validateOnModelUpdate) !== null && _d !== void 0 ? _d : validateOnModelUpdate,
1364
1420
  };
1365
1421
  }
1366
- function applyModifiers(value, modifiers) {
1367
- if (modifiers.number) {
1368
- return toNumber(value);
1369
- }
1370
- return value;
1371
- }
1372
1422
  function resolveInitialValue(props, ctx) {
1373
1423
  // Gets the initial value either from `value` prop/attr or `v-model` binding (modelValue)
1374
1424
  // For checkboxes and radio buttons it will always be the model value not the `value` attribute
@@ -1381,15 +1431,19 @@
1381
1431
 
1382
1432
  let FORM_COUNTER = 0;
1383
1433
  function useForm(opts) {
1434
+ var _a;
1384
1435
  const formId = FORM_COUNTER++;
1436
+ // Prevents fields from double resetting their values, which causes checkboxes to toggle their initial value
1437
+ // TODO: This won't be needed if we centralize all the state inside the `form` for form inputs
1438
+ let RESET_LOCK = false;
1385
1439
  // A lookup containing fields or field groups
1386
1440
  const fieldsByPath = vue.ref({});
1387
1441
  // If the form is currently submitting
1388
1442
  const isSubmitting = vue.ref(false);
1389
1443
  // The number of times the user tried to submit the form
1390
1444
  const submitCount = vue.ref(0);
1391
- // dictionary for field arrays to receive various signals like reset
1392
- const fieldArraysLookup = {};
1445
+ // field arrays managed by this form
1446
+ const fieldArrays = [];
1393
1447
  // a private ref for all form values
1394
1448
  const formValues = vue.reactive(klona(vue.unref(opts === null || opts === void 0 ? void 0 : opts.initialValues) || {}));
1395
1449
  // the source of errors for the form fields
@@ -1436,10 +1490,11 @@
1436
1490
  // mutable non-reactive reference to initial errors
1437
1491
  // we need this to process initial errors then unset them
1438
1492
  const initialErrors = Object.assign({}, ((opts === null || opts === void 0 ? void 0 : opts.initialErrors) || {}));
1493
+ const keepValuesOnUnmount = (_a = opts === null || opts === void 0 ? void 0 : opts.keepValuesOnUnmount) !== null && _a !== void 0 ? _a : false;
1439
1494
  // initial form values
1440
1495
  const { initialValues, originalInitialValues, setInitialValues } = useFormInitialValues(fieldsByPath, formValues, opts === null || opts === void 0 ? void 0 : opts.initialValues);
1441
1496
  // form meta aggregations
1442
- const meta = useFormMeta(fieldsByPath, formValues, initialValues, errors);
1497
+ const meta = useFormMeta(fieldsByPath, formValues, originalInitialValues, errors);
1443
1498
  const schema = opts === null || opts === void 0 ? void 0 : opts.validationSchema;
1444
1499
  const formCtx = {
1445
1500
  formId,
@@ -1451,7 +1506,8 @@
1451
1506
  submitCount,
1452
1507
  meta,
1453
1508
  isSubmitting,
1454
- fieldArraysLookup,
1509
+ fieldArrays,
1510
+ keepValuesOnUnmount,
1455
1511
  validateSchema: vue.unref(schema) ? validateSchema : undefined,
1456
1512
  validate,
1457
1513
  register: registerField,
@@ -1469,6 +1525,7 @@
1469
1525
  stageInitialValue,
1470
1526
  unsetInitialValue,
1471
1527
  setFieldInitialValue,
1528
+ useFieldModel,
1472
1529
  };
1473
1530
  function isFieldGroup(fieldOrGroup) {
1474
1531
  return Array.isArray(fieldOrGroup);
@@ -1479,6 +1536,15 @@
1479
1536
  }
1480
1537
  return mutation(fieldOrGroup);
1481
1538
  }
1539
+ function mutateAllFields(mutation) {
1540
+ Object.values(fieldsByPath.value).forEach(field => {
1541
+ if (!field) {
1542
+ return;
1543
+ }
1544
+ // avoid resetting the field values, because they should've been reset already.
1545
+ applyFieldMutation(field, mutation);
1546
+ });
1547
+ }
1482
1548
  /**
1483
1549
  * Manually sets an error message on a specific field
1484
1550
  */
@@ -1503,15 +1569,15 @@
1503
1569
  setInPath(formValues, field, clonedValue);
1504
1570
  return;
1505
1571
  }
1506
- // Multiple checkboxes, and only one of them got updated
1507
1572
  if (isFieldGroup(fieldInstance) && ((_a = fieldInstance[0]) === null || _a === void 0 ? void 0 : _a.type) === 'checkbox' && !Array.isArray(value)) {
1573
+ // Multiple checkboxes, and only one of them got updated
1508
1574
  const newValue = klona(resolveNextCheckboxValue(getFromPath(formValues, field) || [], value, undefined));
1509
1575
  setInPath(formValues, field, newValue);
1510
1576
  return;
1511
1577
  }
1512
1578
  let newValue = value;
1513
1579
  // Single Checkbox: toggles the field value unless the field is being reset then force it
1514
- if (!isFieldGroup(fieldInstance) && fieldInstance.type === 'checkbox' && !force) {
1580
+ if (!isFieldGroup(fieldInstance) && fieldInstance.type === 'checkbox' && !force && !RESET_LOCK) {
1515
1581
  newValue = klona(resolveNextCheckboxValue(getFromPath(formValues, field), value, vue.unref(fieldInstance.uncheckedValue)));
1516
1582
  }
1517
1583
  setInPath(formValues, field, newValue);
@@ -1529,7 +1595,22 @@
1529
1595
  setFieldValue(path, fields[path]);
1530
1596
  });
1531
1597
  // regenerate the arrays when the form values change
1532
- Object.values(fieldArraysLookup).forEach(f => f && f.reset());
1598
+ fieldArrays.forEach(f => f && f.reset());
1599
+ }
1600
+ function createModel(path) {
1601
+ const { value } = _useFieldValue(path);
1602
+ vue.watch(value, () => {
1603
+ if (!fieldExists(vue.unref(path))) {
1604
+ validate({ mode: 'validated-only' });
1605
+ }
1606
+ });
1607
+ return value;
1608
+ }
1609
+ function useFieldModel(path) {
1610
+ if (!Array.isArray(path)) {
1611
+ return createModel(path);
1612
+ }
1613
+ return path.map(createModel);
1533
1614
  }
1534
1615
  /**
1535
1616
  * Sets the touched meta state on a field
@@ -1552,6 +1633,7 @@
1552
1633
  * Resets all fields
1553
1634
  */
1554
1635
  function resetForm(state) {
1636
+ RESET_LOCK = true;
1555
1637
  // set initial values if provided
1556
1638
  if (state === null || state === void 0 ? void 0 : state.values) {
1557
1639
  setInitialValues(state.values);
@@ -1563,17 +1645,16 @@
1563
1645
  // otherwise clean the current values
1564
1646
  setValues(originalInitialValues.value);
1565
1647
  }
1566
- Object.values(fieldsByPath.value).forEach(field => {
1567
- if (!field) {
1568
- return;
1569
- }
1570
- applyFieldMutation(field, f => f.resetField());
1571
- });
1648
+ // avoid resetting the field values, because they should've been reset already.
1649
+ mutateAllFields(f => f.resetField());
1572
1650
  if (state === null || state === void 0 ? void 0 : state.touched) {
1573
1651
  setTouched(state.touched);
1574
1652
  }
1575
1653
  setErrors((state === null || state === void 0 ? void 0 : state.errors) || {});
1576
1654
  submitCount.value = (state === null || state === void 0 ? void 0 : state.submitCount) || 0;
1655
+ vue.nextTick(() => {
1656
+ RESET_LOCK = false;
1657
+ });
1577
1658
  }
1578
1659
  function insertFieldAtPath(field, path) {
1579
1660
  const rawField = vue.markRaw(field);
@@ -1629,6 +1710,8 @@
1629
1710
  insertFieldAtPath(field, newPath);
1630
1711
  // re-validate if either path had errors before
1631
1712
  if (errors.value[oldPath] || errors.value[newPath]) {
1713
+ // clear up both paths errors
1714
+ setFieldError(oldPath, undefined);
1632
1715
  validateField(newPath);
1633
1716
  }
1634
1717
  // clean up the old path if no other field is sharing that name
@@ -1651,18 +1734,32 @@
1651
1734
  }
1652
1735
  function unregisterField(field) {
1653
1736
  const fieldName = vue.unref(field.name);
1737
+ const fieldInstance = fieldsByPath.value[fieldName];
1738
+ const isGroup = !!fieldInstance && isFieldGroup(fieldInstance);
1654
1739
  removeFieldFromPath(field, fieldName);
1655
1740
  vue.nextTick(() => {
1741
+ var _a;
1656
1742
  // clears a field error on unmounted
1657
1743
  // we wait till next tick to make sure if the field is completely removed and doesn't have any siblings like checkboxes
1658
1744
  // #3384
1659
1745
  if (!fieldExists(fieldName)) {
1660
1746
  setFieldError(fieldName, undefined);
1747
+ // Checks if the field was configured to be unset during unmount or not
1748
+ // Checks both the form-level config and field-level one
1749
+ // Field has the priority if it is set, otherwise it goes to the form settings
1750
+ const shouldKeepValue = (_a = vue.unref(field.keepValueOnUnmount)) !== null && _a !== void 0 ? _a : vue.unref(keepValuesOnUnmount);
1751
+ if (shouldKeepValue) {
1752
+ return;
1753
+ }
1754
+ if (isGroup && !isEmptyContainer(getFromPath(formValues, fieldName))) {
1755
+ return;
1756
+ }
1661
1757
  unsetPath(formValues, fieldName);
1662
1758
  }
1663
1759
  });
1664
1760
  }
1665
1761
  async function validate(opts) {
1762
+ mutateAllFields(f => (f.meta.validated = true));
1666
1763
  if (formCtx.validateSchema) {
1667
1764
  return formCtx.validateSchema((opts === null || opts === void 0 ? void 0 : opts.mode) || 'force');
1668
1765
  }
@@ -1763,9 +1860,12 @@
1763
1860
  /**
1764
1861
  * Sneaky function to set initial field values
1765
1862
  */
1766
- function stageInitialValue(path, value) {
1863
+ function stageInitialValue(path, value, updateOriginal = false) {
1767
1864
  setInPath(formValues, path, value);
1768
1865
  setFieldInitialValue(path, value);
1866
+ if (updateOriginal) {
1867
+ setInPath(originalInitialValues.value, path, klona(value));
1868
+ }
1769
1869
  }
1770
1870
  async function _validateSchema() {
1771
1871
  const schemaValue = vue.unref(schema);
@@ -1782,10 +1882,12 @@
1782
1882
  }
1783
1883
  /**
1784
1884
  * Batches validation runs in 5ms batches
1885
+ * Must have two distinct batch queues to make sure they don't override each other settings #3783
1785
1886
  */
1786
- const debouncedSchemaValidation = debounceAsync(_validateSchema, 5);
1887
+ const debouncedSilentValidation = debounceAsync(_validateSchema, 5);
1888
+ const debouncedValidation = debounceAsync(_validateSchema, 5);
1787
1889
  async function validateSchema(mode) {
1788
- const formResult = await debouncedSchemaValidation();
1890
+ const formResult = await (mode === 'silent' ? debouncedSilentValidation() : debouncedValidation());
1789
1891
  // fields by id lookup
1790
1892
  const fieldsById = formCtx.fieldsByPath.value || {};
1791
1893
  // errors fields names, we need it to also check if custom errors are updated
@@ -1875,6 +1977,7 @@
1875
1977
  setValues,
1876
1978
  setFieldTouched,
1877
1979
  setTouched,
1980
+ useFieldModel,
1878
1981
  };
1879
1982
  }
1880
1983
  /**
@@ -1889,16 +1992,23 @@
1889
1992
  const isDirty = vue.computed(() => {
1890
1993
  return !es6(currentValues, vue.unref(initialValues));
1891
1994
  });
1892
- const flags = vue.computed(() => {
1995
+ function calculateFlags() {
1893
1996
  const fields = Object.values(fieldsByPath.value).flat(1).filter(Boolean);
1894
1997
  return keysOf(MERGE_STRATEGIES).reduce((acc, flag) => {
1895
1998
  const mergeMethod = MERGE_STRATEGIES[flag];
1896
1999
  acc[flag] = fields[mergeMethod](field => field.meta[flag]);
1897
2000
  return acc;
1898
2001
  }, {});
2002
+ }
2003
+ const flags = vue.reactive(calculateFlags());
2004
+ vue.watchEffect(() => {
2005
+ const value = calculateFlags();
2006
+ flags.touched = value.touched;
2007
+ flags.valid = value.valid;
2008
+ flags.pending = value.pending;
1899
2009
  });
1900
2010
  return vue.computed(() => {
1901
- return Object.assign(Object.assign({ initialValues: vue.unref(initialValues) }, flags.value), { valid: flags.value.valid && !keysOf(errors.value).length, dirty: isDirty.value });
2011
+ return Object.assign(Object.assign({ initialValues: vue.unref(initialValues) }, flags), { valid: flags.valid && !keysOf(errors.value).length, dirty: isDirty.value });
1902
2012
  });
1903
2013
  }
1904
2014
  /**
@@ -2019,16 +2129,22 @@
2019
2129
  type: Function,
2020
2130
  default: undefined,
2021
2131
  },
2132
+ keepValues: {
2133
+ type: Boolean,
2134
+ default: false,
2135
+ },
2022
2136
  },
2023
2137
  setup(props, ctx) {
2024
2138
  const initialValues = vue.toRef(props, 'initialValues');
2025
2139
  const validationSchema = vue.toRef(props, 'validationSchema');
2140
+ const keepValues = vue.toRef(props, 'keepValues');
2026
2141
  const { errors, values, meta, isSubmitting, submitCount, validate, validateField, handleReset, resetForm, handleSubmit, submitForm, setErrors, setFieldError, setFieldValue, setValues, setFieldTouched, setTouched, } = useForm({
2027
2142
  validationSchema: validationSchema.value ? validationSchema : undefined,
2028
2143
  initialValues,
2029
2144
  initialErrors: props.initialErrors,
2030
2145
  initialTouched: props.initialTouched,
2031
2146
  validateOnMount: props.validateOnMount,
2147
+ keepValuesOnUnmount: keepValues,
2032
2148
  });
2033
2149
  const onSubmit = props.onSubmit ? handleSubmit(props.onSubmit, props.onInvalidSubmit) : submitForm;
2034
2150
  function handleFormReset(e) {
@@ -2098,15 +2214,13 @@
2098
2214
  });
2099
2215
  const Form = FormImpl;
2100
2216
 
2101
- let FIELD_ARRAY_COUNTER = 0;
2102
2217
  function useFieldArray(arrayPath) {
2103
- const id = FIELD_ARRAY_COUNTER++;
2104
2218
  const form = injectWithSelf(FormContextKey, undefined);
2105
2219
  const fields = vue.ref([]);
2106
2220
  // eslint-disable-next-line @typescript-eslint/no-empty-function
2107
2221
  const noOp = () => { };
2108
2222
  const noOpApi = {
2109
- fields: vue.readonly(fields),
2223
+ fields,
2110
2224
  remove: noOp,
2111
2225
  push: noOp,
2112
2226
  swap: noOp,
@@ -2114,6 +2228,7 @@
2114
2228
  update: noOp,
2115
2229
  replace: noOp,
2116
2230
  prepend: noOp,
2231
+ move: noOp,
2117
2232
  };
2118
2233
  if (!form) {
2119
2234
  warn('FieldArray requires being a child of `<Form/>` or `useForm` being called before it. Array fields may not work correctly');
@@ -2123,9 +2238,13 @@
2123
2238
  warn('FieldArray requires a field path to be provided, did you forget to pass the `name` prop?');
2124
2239
  return noOpApi;
2125
2240
  }
2241
+ const alreadyExists = form.fieldArrays.find(a => vue.unref(a.path) === vue.unref(arrayPath));
2242
+ if (alreadyExists) {
2243
+ return alreadyExists;
2244
+ }
2126
2245
  let entryCounter = 0;
2127
2246
  function initFields() {
2128
- const currentValues = getFromPath(form === null || form === void 0 ? void 0 : form.values, vue.unref(arrayPath), []);
2247
+ const currentValues = getFromPath(form === null || form === void 0 ? void 0 : form.values, vue.unref(arrayPath), []) || [];
2129
2248
  fields.value = currentValues.map(createEntry);
2130
2249
  updateEntryFlags();
2131
2250
  }
@@ -2142,10 +2261,20 @@
2142
2261
  const key = entryCounter++;
2143
2262
  const entry = {
2144
2263
  key,
2145
- value: vue.computed(() => {
2146
- const currentValues = getFromPath(form === null || form === void 0 ? void 0 : form.values, vue.unref(arrayPath), []);
2147
- const idx = fields.value.findIndex(e => e.key === key);
2148
- return idx === -1 ? value : currentValues[idx];
2264
+ value: vue.computed({
2265
+ get() {
2266
+ const currentValues = getFromPath(form === null || form === void 0 ? void 0 : form.values, vue.unref(arrayPath), []) || [];
2267
+ const idx = fields.value.findIndex(e => e.key === key);
2268
+ return idx === -1 ? value : currentValues[idx];
2269
+ },
2270
+ set(value) {
2271
+ const idx = fields.value.findIndex(e => e.key === key);
2272
+ if (idx === -1) {
2273
+ warn(`Attempting to update a non-existent array item`);
2274
+ return;
2275
+ }
2276
+ update(idx, value);
2277
+ },
2149
2278
  }),
2150
2279
  isFirst: false,
2151
2280
  isLast: false,
@@ -2182,7 +2311,7 @@
2182
2311
  function swap(indexA, indexB) {
2183
2312
  const pathName = vue.unref(arrayPath);
2184
2313
  const pathValue = getFromPath(form === null || form === void 0 ? void 0 : form.values, pathName);
2185
- if (!Array.isArray(pathValue) || !pathValue[indexA] || !pathValue[indexB]) {
2314
+ if (!Array.isArray(pathValue) || !(indexA in pathValue) || !(indexB in pathValue)) {
2186
2315
  return;
2187
2316
  }
2188
2317
  const newValue = [...pathValue];
@@ -2238,14 +2367,26 @@
2238
2367
  fields.value.unshift(createEntry(value));
2239
2368
  updateEntryFlags();
2240
2369
  }
2241
- form.fieldArraysLookup[id] = {
2242
- reset: initFields,
2243
- };
2244
- vue.onBeforeUnmount(() => {
2245
- delete form.fieldArraysLookup[id];
2246
- });
2247
- return {
2248
- fields: vue.readonly(fields),
2370
+ function move(oldIdx, newIdx) {
2371
+ const pathName = vue.unref(arrayPath);
2372
+ const pathValue = getFromPath(form === null || form === void 0 ? void 0 : form.values, pathName);
2373
+ const newValue = isNullOrUndefined(pathValue) ? [] : [...pathValue];
2374
+ if (!Array.isArray(pathValue) || !(oldIdx in pathValue) || !(newIdx in pathValue)) {
2375
+ return;
2376
+ }
2377
+ const newFields = [...fields.value];
2378
+ const movedItem = newFields[oldIdx];
2379
+ newFields.splice(oldIdx, 1);
2380
+ newFields.splice(newIdx, 0, movedItem);
2381
+ const movedValue = newValue[oldIdx];
2382
+ newValue.splice(oldIdx, 1);
2383
+ newValue.splice(newIdx, 0, movedValue);
2384
+ form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, newValue);
2385
+ fields.value = newFields;
2386
+ updateEntryFlags();
2387
+ }
2388
+ const fieldArrayCtx = {
2389
+ fields,
2249
2390
  remove,
2250
2391
  push,
2251
2392
  swap,
@@ -2253,7 +2394,16 @@
2253
2394
  update,
2254
2395
  replace,
2255
2396
  prepend,
2397
+ move,
2256
2398
  };
2399
+ form.fieldArrays.push(Object.assign({ path: arrayPath, reset: initFields }, fieldArrayCtx));
2400
+ vue.onBeforeUnmount(() => {
2401
+ const idx = form.fieldArrays.findIndex(i => vue.unref(i.path) === vue.unref(arrayPath));
2402
+ if (idx >= 0) {
2403
+ form.fieldArrays.splice(idx, 1);
2404
+ }
2405
+ });
2406
+ return fieldArrayCtx;
2257
2407
  }
2258
2408
 
2259
2409
  const FieldArrayImpl = vue.defineComponent({
@@ -2266,7 +2416,7 @@
2266
2416
  },
2267
2417
  },
2268
2418
  setup(props, ctx) {
2269
- const { push, remove, swap, insert, replace, update, prepend, fields } = useFieldArray(vue.toRef(props, 'name'));
2419
+ const { push, remove, swap, insert, replace, update, prepend, move, fields } = useFieldArray(vue.toRef(props, 'name'));
2270
2420
  function slotProps() {
2271
2421
  return {
2272
2422
  fields: fields.value,
@@ -2277,6 +2427,7 @@
2277
2427
  update,
2278
2428
  replace,
2279
2429
  prepend,
2430
+ move,
2280
2431
  };
2281
2432
  }
2282
2433
  ctx.expose({
@@ -2287,6 +2438,7 @@
2287
2438
  update,
2288
2439
  replace,
2289
2440
  prepend,
2441
+ move,
2290
2442
  });
2291
2443
  return () => {
2292
2444
  const children = normalizeChildren(undefined, ctx, slotProps);
@@ -2592,6 +2744,7 @@
2592
2744
  exports.FieldContextKey = FieldContextKey;
2593
2745
  exports.Form = Form;
2594
2746
  exports.FormContextKey = FormContextKey;
2747
+ exports.IS_ABSENT = IS_ABSENT;
2595
2748
  exports.configure = configure;
2596
2749
  exports.defineRule = defineRule;
2597
2750
  exports.useField = useField;