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,9 +1,9 @@
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
- import { inject, getCurrentInstance, warn as warn$1, ref, unref, computed, reactive, watch, onUnmounted, nextTick, onMounted, provide, isRef, onBeforeUnmount, defineComponent, toRef, resolveDynamicComponent, h, markRaw, readonly } from 'vue';
6
+ import { inject, getCurrentInstance, warn as warn$1, ref, unref, computed, reactive, watch, onUnmounted, nextTick, onMounted, provide, isRef, onBeforeUnmount, defineComponent, toRef, resolveDynamicComponent, h, watchEffect, markRaw } from 'vue';
7
7
  import { setupDevtoolsPlugin } from '@vue/devtools-api';
8
8
 
9
9
  function isCallable(fn) {
@@ -53,18 +53,6 @@ const IS_ABSENT = Symbol('Default empty value');
53
53
  function isLocator(value) {
54
54
  return isCallable(value) && !!value.__locatorRef;
55
55
  }
56
- /**
57
- * Checks if an tag name is a native HTML tag and not a Vue component
58
- */
59
- function isHTMLTag(tag) {
60
- return ['input', 'textarea', 'select'].includes(tag);
61
- }
62
- /**
63
- * Checks if an input is of type file
64
- */
65
- function isFileInputNode(tag, attrs) {
66
- return isHTMLTag(tag) && attrs.type === 'file';
67
- }
68
56
  function isYupValidator(value) {
69
57
  return !!value && isCallable(value.validate);
70
58
  }
@@ -117,7 +105,7 @@ function isNativeMultiSelectNode(tag, attrs) {
117
105
  * For multi-selects because the value binding will reset the value
118
106
  */
119
107
  function shouldHaveValueBinding(tag, attrs) {
120
- return isNativeMultiSelectNode(tag, attrs) || isFileInputNode(tag, attrs);
108
+ return !isNativeMultiSelectNode(tag, attrs) && attrs.type !== 'file' && !hasCheckedAttr(attrs.type);
121
109
  }
122
110
  function isFormSubmitEvent(evt) {
123
111
  return isEvent(evt) && evt.target && 'submit' in evt.target;
@@ -301,6 +289,15 @@ function debounceAsync(inner, ms = 0) {
301
289
  }, ms);
302
290
  return new Promise(resolve => resolves.push(resolve));
303
291
  };
292
+ }
293
+ function applyModelModifiers(value, modifiers) {
294
+ if (!isObject(modifiers)) {
295
+ return;
296
+ }
297
+ if (modifiers.number) {
298
+ return toNumber(value);
299
+ }
300
+ return value;
304
301
  }
305
302
 
306
303
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -344,7 +341,8 @@ function normalizeEventValue(value) {
344
341
  return getBoundValue(input);
345
342
  }
346
343
  if (input.type === 'file' && input.files) {
347
- return Array.from(input.files);
344
+ const files = Array.from(input.files);
345
+ return input.multiple ? files : files[0];
348
346
  }
349
347
  if (isNativeMultiSelect(input)) {
350
348
  return Array.from(input.options)
@@ -503,18 +501,34 @@ async function _validate(field, value) {
503
501
  if (isYupValidator(field.rules)) {
504
502
  return validateFieldWithYup(value, field.rules, { bails: field.bails });
505
503
  }
506
- // if a generic function, use it as the pipeline.
507
- if (isCallable(field.rules)) {
504
+ // if a generic function or chain of generic functions
505
+ if (isCallable(field.rules) || Array.isArray(field.rules)) {
508
506
  const ctx = {
509
507
  field: field.name,
510
508
  form: field.formData,
511
509
  value: value,
512
510
  };
513
- const result = await field.rules(value, ctx);
514
- const isValid = typeof result !== 'string' && result;
515
- const message = typeof result === 'string' ? result : _generateFieldError(ctx);
511
+ // Normalize the pipeline
512
+ const pipeline = Array.isArray(field.rules) ? field.rules : [field.rules];
513
+ const length = pipeline.length;
514
+ const errors = [];
515
+ for (let i = 0; i < length; i++) {
516
+ const rule = pipeline[i];
517
+ const result = await rule(value, ctx);
518
+ const isValid = typeof result !== 'string' && result;
519
+ if (isValid) {
520
+ continue;
521
+ }
522
+ const message = typeof result === 'string' ? result : _generateFieldError(ctx);
523
+ errors.push(message);
524
+ if (field.bails) {
525
+ return {
526
+ errors,
527
+ };
528
+ }
529
+ }
516
530
  return {
517
- errors: !isValid ? [message] : [],
531
+ errors,
518
532
  };
519
533
  }
520
534
  const normalizedContext = Object.assign(Object.assign({}, field), { rules: normalizeRules(field.rules) });
@@ -827,8 +841,8 @@ function useFieldState(path, init) {
827
841
  /**
828
842
  * Creates the field value and resolves the initial value
829
843
  */
830
- function _useFieldValue(path, modelValue, shouldInjectForm) {
831
- const form = shouldInjectForm ? injectWithSelf(FormContextKey, undefined) : undefined;
844
+ function _useFieldValue(path, modelValue, shouldInjectForm = true) {
845
+ const form = shouldInjectForm === true ? injectWithSelf(FormContextKey, undefined) : undefined;
832
846
  const modelRef = ref(unref(modelValue));
833
847
  function resolveInitialValue() {
834
848
  if (!form) {
@@ -858,7 +872,7 @@ function _useFieldValue(path, modelValue, shouldInjectForm) {
858
872
  // prioritize model value over form values
859
873
  // #3429
860
874
  const currentValue = modelValue ? unref(modelValue) : getFromPath(form.values, unref(path), unref(initialValue));
861
- form.stageInitialValue(unref(path), currentValue);
875
+ form.stageInitialValue(unref(path), currentValue, true);
862
876
  // otherwise use a computed setter that triggers the `setFieldValue`
863
877
  const value = computed({
864
878
  get() {
@@ -1297,12 +1311,17 @@ function useField(name, rules, opts) {
1297
1311
  return _useField(name, rules, opts);
1298
1312
  }
1299
1313
  function _useField(name, rules, opts) {
1300
- const { initialValue: modelValue, validateOnMount, bails, type, checkedValue, label, validateOnValueUpdate, uncheckedValue, standalone, } = normalizeOptions(unref(name), opts);
1314
+ const { initialValue: modelValue, validateOnMount, bails, type, checkedValue, label, validateOnValueUpdate, uncheckedValue, standalone, keepValueOnUnmount, modelPropName, syncVModel, } = normalizeOptions(unref(name), opts);
1301
1315
  const form = !standalone ? injectWithSelf(FormContextKey) : undefined;
1316
+ // a flag indicating if the field is about to be removed/unmounted.
1317
+ let markedForRemoval = false;
1302
1318
  const { id, value, initialValue, meta, setState, errors, errorMessage } = useFieldState(name, {
1303
1319
  modelValue,
1304
1320
  standalone,
1305
1321
  });
1322
+ if (syncVModel) {
1323
+ useVModel({ value, prop: modelPropName, handleChange });
1324
+ }
1306
1325
  /**
1307
1326
  * Handles common onBlur meta update
1308
1327
  */
@@ -1315,7 +1334,7 @@ function _useField(name, rules, opts) {
1315
1334
  if (schema && !isYupValidator(schema)) {
1316
1335
  rulesValue = extractRuleFromSchema(schema, unref(name)) || rulesValue;
1317
1336
  }
1318
- if (isYupValidator(rulesValue) || isCallable(rulesValue)) {
1337
+ if (isYupValidator(rulesValue) || isCallable(rulesValue) || Array.isArray(rulesValue)) {
1319
1338
  return rulesValue;
1320
1339
  }
1321
1340
  return normalizeRules(rulesValue);
@@ -1335,12 +1354,19 @@ function _useField(name, rules, opts) {
1335
1354
  meta.pending = true;
1336
1355
  meta.validated = true;
1337
1356
  const result = await validateCurrentValue('validated-only');
1357
+ if (markedForRemoval) {
1358
+ result.valid = true;
1359
+ result.errors = [];
1360
+ }
1338
1361
  setState({ errors: result.errors });
1339
1362
  meta.pending = false;
1340
1363
  return result;
1341
1364
  }
1342
1365
  async function validateValidStateOnly() {
1343
1366
  const result = await validateCurrentValue('silent');
1367
+ if (markedForRemoval) {
1368
+ result.valid = true;
1369
+ }
1344
1370
  meta.valid = result.valid;
1345
1371
  return result;
1346
1372
  }
@@ -1354,13 +1380,13 @@ function _useField(name, rules, opts) {
1354
1380
  return validateValidStateOnly();
1355
1381
  }
1356
1382
  // Common input/change event handler
1357
- const handleChange = (e, shouldValidate = true) => {
1383
+ function handleChange(e, shouldValidate = true) {
1358
1384
  const newValue = normalizeEventValue(e);
1359
1385
  value.value = newValue;
1360
1386
  if (!validateOnValueUpdate && shouldValidate) {
1361
1387
  validateWithStateMutation();
1362
1388
  }
1363
- };
1389
+ }
1364
1390
  // Runs the initial validation
1365
1391
  onMounted(() => {
1366
1392
  if (validateOnMount) {
@@ -1377,7 +1403,13 @@ function _useField(name, rules, opts) {
1377
1403
  }
1378
1404
  let unwatchValue;
1379
1405
  function watchValue() {
1380
- unwatchValue = watch(value, validateOnValueUpdate ? validateWithStateMutation : validateValidStateOnly, {
1406
+ unwatchValue = watch(value, (val, oldVal) => {
1407
+ if (es6(val, oldVal)) {
1408
+ return;
1409
+ }
1410
+ const validateFn = validateOnValueUpdate ? validateWithStateMutation : validateValidStateOnly;
1411
+ validateFn();
1412
+ }, {
1381
1413
  deep: true,
1382
1414
  });
1383
1415
  }
@@ -1418,6 +1450,7 @@ function _useField(name, rules, opts) {
1418
1450
  checkedValue,
1419
1451
  uncheckedValue,
1420
1452
  bails,
1453
+ keepValueOnUnmount,
1421
1454
  resetField,
1422
1455
  handleReset: () => resetField(),
1423
1456
  validate: validate$1,
@@ -1455,13 +1488,14 @@ function _useField(name, rules, opts) {
1455
1488
  // associate the field with the given form
1456
1489
  form.register(field);
1457
1490
  onBeforeUnmount(() => {
1491
+ markedForRemoval = true;
1458
1492
  form.unregister(field);
1459
1493
  });
1460
1494
  // extract cross-field dependencies in a computed prop
1461
1495
  const dependencies = computed(() => {
1462
1496
  const rulesVal = normalizedRules.value;
1463
1497
  // is falsy, a function schema or a yup schema
1464
- if (!rulesVal || isCallable(rulesVal) || isYupValidator(rulesVal)) {
1498
+ if (!rulesVal || isCallable(rulesVal) || isYupValidator(rulesVal) || Array.isArray(rulesVal)) {
1465
1499
  return {};
1466
1500
  }
1467
1501
  return Object.keys(rulesVal).reduce((acc, rule) => {
@@ -1503,6 +1537,9 @@ function normalizeOptions(name, opts) {
1503
1537
  label: name,
1504
1538
  validateOnValueUpdate: true,
1505
1539
  standalone: false,
1540
+ keepValueOnUnmount: undefined,
1541
+ modelPropName: 'modelValue',
1542
+ syncVModel: true,
1506
1543
  });
1507
1544
  if (!opts) {
1508
1545
  return defaults();
@@ -1534,8 +1571,8 @@ function useCheckboxField(name, rules, opts) {
1534
1571
  return Array.isArray(currentValue) ? currentValue.includes(checkedVal) : checkedVal === currentValue;
1535
1572
  });
1536
1573
  function handleCheckboxChange(e, shouldValidate = true) {
1537
- var _a, _b;
1538
- if (checked.value === ((_b = (_a = e) === null || _a === void 0 ? void 0 : _a.target) === null || _b === void 0 ? void 0 : _b.checked)) {
1574
+ var _a;
1575
+ if (checked.value === ((_a = e === null || e === void 0 ? void 0 : e.target) === null || _a === void 0 ? void 0 : _a.checked)) {
1539
1576
  return;
1540
1577
  }
1541
1578
  let newValue = normalizeEventValue(e);
@@ -1546,8 +1583,10 @@ function useCheckboxField(name, rules, opts) {
1546
1583
  handleChange(newValue, shouldValidate);
1547
1584
  }
1548
1585
  onBeforeUnmount(() => {
1549
- // toggles the checkbox value if it was checked
1550
- if (checked.value) {
1586
+ var _a, _b;
1587
+ const shouldKeepValue = (_b = (_a = unref(field.keepValueOnUnmount)) !== null && _a !== void 0 ? _a : unref(form === null || form === void 0 ? void 0 : form.keepValuesOnUnmount)) !== null && _b !== void 0 ? _b : false;
1588
+ // toggles the checkbox value if it was checked and the unset behavior is set
1589
+ if (checked.value && !shouldKeepValue) {
1551
1590
  handleCheckboxChange(unref(checkedValue), false);
1552
1591
  }
1553
1592
  });
@@ -1556,6 +1595,41 @@ function useCheckboxField(name, rules, opts) {
1556
1595
  uncheckedValue, handleChange: handleCheckboxChange });
1557
1596
  }
1558
1597
  return patchCheckboxApi(_useField(name, rules, opts));
1598
+ }
1599
+ function useVModel({ prop, value, handleChange }) {
1600
+ const vm = getCurrentInstance();
1601
+ /* istanbul ignore next */
1602
+ if (!vm) {
1603
+ if ((process.env.NODE_ENV !== 'production')) {
1604
+ console.warn('Failed to setup model events because `useField` was not called in setup.');
1605
+ }
1606
+ return;
1607
+ }
1608
+ const propName = prop || 'modelValue';
1609
+ const emitName = `update:${propName}`;
1610
+ // Component doesn't have a model prop setup (must be defined on the props)
1611
+ if (!(propName in vm.props)) {
1612
+ return;
1613
+ }
1614
+ watch(value, newValue => {
1615
+ if (es6(newValue, getCurrentModelValue(vm, propName))) {
1616
+ return;
1617
+ }
1618
+ vm.emit(emitName, newValue);
1619
+ });
1620
+ watch(() => getCurrentModelValue(vm, propName), propValue => {
1621
+ if (propValue === IS_ABSENT && value.value === undefined) {
1622
+ return;
1623
+ }
1624
+ const newValue = propValue === IS_ABSENT ? undefined : propValue;
1625
+ if (es6(newValue, applyModelModifiers(value.value, vm.props.modelModifiers))) {
1626
+ return;
1627
+ }
1628
+ handleChange(newValue);
1629
+ });
1630
+ }
1631
+ function getCurrentModelValue(vm, propName) {
1632
+ return vm.props[propName];
1559
1633
  }
1560
1634
 
1561
1635
  const FieldImpl = defineComponent({
@@ -1622,13 +1696,17 @@ const FieldImpl = defineComponent({
1622
1696
  type: Boolean,
1623
1697
  default: false,
1624
1698
  },
1699
+ keepValue: {
1700
+ type: Boolean,
1701
+ default: undefined,
1702
+ },
1625
1703
  },
1626
1704
  setup(props, ctx) {
1627
1705
  const rules = toRef(props, 'rules');
1628
1706
  const name = toRef(props, 'name');
1629
1707
  const label = toRef(props, 'label');
1630
1708
  const uncheckedValue = toRef(props, 'uncheckedValue');
1631
- const hasModelEvents = isPropPresent(props, 'onUpdate:modelValue');
1709
+ const keepValue = toRef(props, 'keepValue');
1632
1710
  const { errors, value, errorMessage, validate: validateField, handleChange, handleBlur, setTouched, resetField, handleReset, meta, checked, setErrors, } = useField(name, rules, {
1633
1711
  validateOnMount: props.validateOnMount,
1634
1712
  bails: props.bails,
@@ -1640,25 +1718,22 @@ const FieldImpl = defineComponent({
1640
1718
  uncheckedValue,
1641
1719
  label,
1642
1720
  validateOnValueUpdate: false,
1721
+ keepValueOnUnmount: keepValue,
1643
1722
  });
1644
1723
  // If there is a v-model applied on the component we need to emit the `update:modelValue` whenever the value binding changes
1645
- const onChangeHandler = hasModelEvents
1646
- ? function handleChangeWithModel(e, shouldValidate = true) {
1647
- handleChange(e, shouldValidate);
1648
- ctx.emit('update:modelValue', value.value);
1649
- }
1650
- : handleChange;
1724
+ const onChangeHandler = function handleChangeWithModel(e, shouldValidate = true) {
1725
+ handleChange(e, shouldValidate);
1726
+ ctx.emit('update:modelValue', value.value);
1727
+ };
1651
1728
  const handleInput = (e) => {
1652
1729
  if (!hasCheckedAttr(ctx.attrs.type)) {
1653
1730
  value.value = normalizeEventValue(e);
1654
1731
  }
1655
1732
  };
1656
- const onInputHandler = hasModelEvents
1657
- ? function handleInputWithModel(e) {
1658
- handleInput(e);
1659
- ctx.emit('update:modelValue', value.value);
1660
- }
1661
- : handleInput;
1733
+ const onInputHandler = function handleInputWithModel(e) {
1734
+ handleInput(e);
1735
+ ctx.emit('update:modelValue', value.value);
1736
+ };
1662
1737
  const fieldProps = computed(() => {
1663
1738
  const { validateOnInput, validateOnChange, validateOnBlur, validateOnModelUpdate } = resolveValidationTriggers(props);
1664
1739
  const baseOnBlur = [handleBlur, ctx.attrs.onBlur, validateOnBlur ? validateField : undefined].filter(Boolean);
@@ -1670,32 +1745,16 @@ const FieldImpl = defineComponent({
1670
1745
  onInput: baseOnInput,
1671
1746
  onChange: baseOnChange,
1672
1747
  };
1673
- if (validateOnModelUpdate) {
1674
- attrs['onUpdate:modelValue'] = [onChangeHandler];
1675
- }
1748
+ attrs['onUpdate:modelValue'] = e => onChangeHandler(e, validateOnModelUpdate);
1676
1749
  if (hasCheckedAttr(ctx.attrs.type) && checked) {
1677
1750
  attrs.checked = checked.value;
1678
1751
  }
1679
- else {
1680
- attrs.value = value.value;
1681
- }
1682
1752
  const tag = resolveTag(props, ctx);
1683
1753
  if (shouldHaveValueBinding(tag, ctx.attrs)) {
1684
- delete attrs.value;
1754
+ attrs.value = value.value;
1685
1755
  }
1686
1756
  return attrs;
1687
1757
  });
1688
- const modelValue = toRef(props, 'modelValue');
1689
- watch(modelValue, newModelValue => {
1690
- // Don't attempt to sync absent values
1691
- if (newModelValue === IS_ABSENT && value.value === undefined) {
1692
- return;
1693
- }
1694
- if (newModelValue !== applyModifiers(value.value, props.modelModifiers)) {
1695
- value.value = newModelValue === IS_ABSENT ? undefined : newModelValue;
1696
- validateField();
1697
- }
1698
- });
1699
1758
  function slotProps() {
1700
1759
  return {
1701
1760
  field: fieldProps.value,
@@ -1747,12 +1806,6 @@ function resolveValidationTriggers(props) {
1747
1806
  validateOnModelUpdate: (_d = props.validateOnModelUpdate) !== null && _d !== void 0 ? _d : validateOnModelUpdate,
1748
1807
  };
1749
1808
  }
1750
- function applyModifiers(value, modifiers) {
1751
- if (modifiers.number) {
1752
- return toNumber(value);
1753
- }
1754
- return value;
1755
- }
1756
1809
  function resolveInitialValue(props, ctx) {
1757
1810
  // Gets the initial value either from `value` prop/attr or `v-model` binding (modelValue)
1758
1811
  // For checkboxes and radio buttons it will always be the model value not the `value` attribute
@@ -1765,15 +1818,19 @@ const Field = FieldImpl;
1765
1818
 
1766
1819
  let FORM_COUNTER = 0;
1767
1820
  function useForm(opts) {
1821
+ var _a;
1768
1822
  const formId = FORM_COUNTER++;
1823
+ // Prevents fields from double resetting their values, which causes checkboxes to toggle their initial value
1824
+ // TODO: This won't be needed if we centralize all the state inside the `form` for form inputs
1825
+ let RESET_LOCK = false;
1769
1826
  // A lookup containing fields or field groups
1770
1827
  const fieldsByPath = ref({});
1771
1828
  // If the form is currently submitting
1772
1829
  const isSubmitting = ref(false);
1773
1830
  // The number of times the user tried to submit the form
1774
1831
  const submitCount = ref(0);
1775
- // dictionary for field arrays to receive various signals like reset
1776
- const fieldArraysLookup = {};
1832
+ // field arrays managed by this form
1833
+ const fieldArrays = [];
1777
1834
  // a private ref for all form values
1778
1835
  const formValues = reactive(klona(unref(opts === null || opts === void 0 ? void 0 : opts.initialValues) || {}));
1779
1836
  // the source of errors for the form fields
@@ -1820,10 +1877,11 @@ function useForm(opts) {
1820
1877
  // mutable non-reactive reference to initial errors
1821
1878
  // we need this to process initial errors then unset them
1822
1879
  const initialErrors = Object.assign({}, ((opts === null || opts === void 0 ? void 0 : opts.initialErrors) || {}));
1880
+ const keepValuesOnUnmount = (_a = opts === null || opts === void 0 ? void 0 : opts.keepValuesOnUnmount) !== null && _a !== void 0 ? _a : false;
1823
1881
  // initial form values
1824
1882
  const { initialValues, originalInitialValues, setInitialValues } = useFormInitialValues(fieldsByPath, formValues, opts === null || opts === void 0 ? void 0 : opts.initialValues);
1825
1883
  // form meta aggregations
1826
- const meta = useFormMeta(fieldsByPath, formValues, initialValues, errors);
1884
+ const meta = useFormMeta(fieldsByPath, formValues, originalInitialValues, errors);
1827
1885
  const schema = opts === null || opts === void 0 ? void 0 : opts.validationSchema;
1828
1886
  const formCtx = {
1829
1887
  formId,
@@ -1835,7 +1893,8 @@ function useForm(opts) {
1835
1893
  submitCount,
1836
1894
  meta,
1837
1895
  isSubmitting,
1838
- fieldArraysLookup,
1896
+ fieldArrays,
1897
+ keepValuesOnUnmount,
1839
1898
  validateSchema: unref(schema) ? validateSchema : undefined,
1840
1899
  validate,
1841
1900
  register: registerField,
@@ -1853,6 +1912,7 @@ function useForm(opts) {
1853
1912
  stageInitialValue,
1854
1913
  unsetInitialValue,
1855
1914
  setFieldInitialValue,
1915
+ useFieldModel,
1856
1916
  };
1857
1917
  function isFieldGroup(fieldOrGroup) {
1858
1918
  return Array.isArray(fieldOrGroup);
@@ -1863,6 +1923,15 @@ function useForm(opts) {
1863
1923
  }
1864
1924
  return mutation(fieldOrGroup);
1865
1925
  }
1926
+ function mutateAllFields(mutation) {
1927
+ Object.values(fieldsByPath.value).forEach(field => {
1928
+ if (!field) {
1929
+ return;
1930
+ }
1931
+ // avoid resetting the field values, because they should've been reset already.
1932
+ applyFieldMutation(field, mutation);
1933
+ });
1934
+ }
1866
1935
  /**
1867
1936
  * Manually sets an error message on a specific field
1868
1937
  */
@@ -1887,15 +1956,15 @@ function useForm(opts) {
1887
1956
  setInPath(formValues, field, clonedValue);
1888
1957
  return;
1889
1958
  }
1890
- // Multiple checkboxes, and only one of them got updated
1891
1959
  if (isFieldGroup(fieldInstance) && ((_a = fieldInstance[0]) === null || _a === void 0 ? void 0 : _a.type) === 'checkbox' && !Array.isArray(value)) {
1960
+ // Multiple checkboxes, and only one of them got updated
1892
1961
  const newValue = klona(resolveNextCheckboxValue(getFromPath(formValues, field) || [], value, undefined));
1893
1962
  setInPath(formValues, field, newValue);
1894
1963
  return;
1895
1964
  }
1896
1965
  let newValue = value;
1897
1966
  // Single Checkbox: toggles the field value unless the field is being reset then force it
1898
- if (!isFieldGroup(fieldInstance) && fieldInstance.type === 'checkbox' && !force) {
1967
+ if (!isFieldGroup(fieldInstance) && fieldInstance.type === 'checkbox' && !force && !RESET_LOCK) {
1899
1968
  newValue = klona(resolveNextCheckboxValue(getFromPath(formValues, field), value, unref(fieldInstance.uncheckedValue)));
1900
1969
  }
1901
1970
  setInPath(formValues, field, newValue);
@@ -1913,7 +1982,22 @@ function useForm(opts) {
1913
1982
  setFieldValue(path, fields[path]);
1914
1983
  });
1915
1984
  // regenerate the arrays when the form values change
1916
- Object.values(fieldArraysLookup).forEach(f => f && f.reset());
1985
+ fieldArrays.forEach(f => f && f.reset());
1986
+ }
1987
+ function createModel(path) {
1988
+ const { value } = _useFieldValue(path);
1989
+ watch(value, () => {
1990
+ if (!fieldExists(unref(path))) {
1991
+ validate({ mode: 'validated-only' });
1992
+ }
1993
+ });
1994
+ return value;
1995
+ }
1996
+ function useFieldModel(path) {
1997
+ if (!Array.isArray(path)) {
1998
+ return createModel(path);
1999
+ }
2000
+ return path.map(createModel);
1917
2001
  }
1918
2002
  /**
1919
2003
  * Sets the touched meta state on a field
@@ -1936,6 +2020,7 @@ function useForm(opts) {
1936
2020
  * Resets all fields
1937
2021
  */
1938
2022
  function resetForm(state) {
2023
+ RESET_LOCK = true;
1939
2024
  // set initial values if provided
1940
2025
  if (state === null || state === void 0 ? void 0 : state.values) {
1941
2026
  setInitialValues(state.values);
@@ -1947,17 +2032,16 @@ function useForm(opts) {
1947
2032
  // otherwise clean the current values
1948
2033
  setValues(originalInitialValues.value);
1949
2034
  }
1950
- Object.values(fieldsByPath.value).forEach(field => {
1951
- if (!field) {
1952
- return;
1953
- }
1954
- applyFieldMutation(field, f => f.resetField());
1955
- });
2035
+ // avoid resetting the field values, because they should've been reset already.
2036
+ mutateAllFields(f => f.resetField());
1956
2037
  if (state === null || state === void 0 ? void 0 : state.touched) {
1957
2038
  setTouched(state.touched);
1958
2039
  }
1959
2040
  setErrors((state === null || state === void 0 ? void 0 : state.errors) || {});
1960
2041
  submitCount.value = (state === null || state === void 0 ? void 0 : state.submitCount) || 0;
2042
+ nextTick(() => {
2043
+ RESET_LOCK = false;
2044
+ });
1961
2045
  }
1962
2046
  function insertFieldAtPath(field, path) {
1963
2047
  const rawField = markRaw(field);
@@ -2013,6 +2097,8 @@ function useForm(opts) {
2013
2097
  insertFieldAtPath(field, newPath);
2014
2098
  // re-validate if either path had errors before
2015
2099
  if (errors.value[oldPath] || errors.value[newPath]) {
2100
+ // clear up both paths errors
2101
+ setFieldError(oldPath, undefined);
2016
2102
  validateField(newPath);
2017
2103
  }
2018
2104
  // clean up the old path if no other field is sharing that name
@@ -2035,18 +2121,32 @@ function useForm(opts) {
2035
2121
  }
2036
2122
  function unregisterField(field) {
2037
2123
  const fieldName = unref(field.name);
2124
+ const fieldInstance = fieldsByPath.value[fieldName];
2125
+ const isGroup = !!fieldInstance && isFieldGroup(fieldInstance);
2038
2126
  removeFieldFromPath(field, fieldName);
2039
2127
  nextTick(() => {
2128
+ var _a;
2040
2129
  // clears a field error on unmounted
2041
2130
  // we wait till next tick to make sure if the field is completely removed and doesn't have any siblings like checkboxes
2042
2131
  // #3384
2043
2132
  if (!fieldExists(fieldName)) {
2044
2133
  setFieldError(fieldName, undefined);
2134
+ // Checks if the field was configured to be unset during unmount or not
2135
+ // Checks both the form-level config and field-level one
2136
+ // Field has the priority if it is set, otherwise it goes to the form settings
2137
+ const shouldKeepValue = (_a = unref(field.keepValueOnUnmount)) !== null && _a !== void 0 ? _a : unref(keepValuesOnUnmount);
2138
+ if (shouldKeepValue) {
2139
+ return;
2140
+ }
2141
+ if (isGroup && !isEmptyContainer(getFromPath(formValues, fieldName))) {
2142
+ return;
2143
+ }
2045
2144
  unsetPath(formValues, fieldName);
2046
2145
  }
2047
2146
  });
2048
2147
  }
2049
2148
  async function validate(opts) {
2149
+ mutateAllFields(f => (f.meta.validated = true));
2050
2150
  if (formCtx.validateSchema) {
2051
2151
  return formCtx.validateSchema((opts === null || opts === void 0 ? void 0 : opts.mode) || 'force');
2052
2152
  }
@@ -2147,9 +2247,12 @@ function useForm(opts) {
2147
2247
  /**
2148
2248
  * Sneaky function to set initial field values
2149
2249
  */
2150
- function stageInitialValue(path, value) {
2250
+ function stageInitialValue(path, value, updateOriginal = false) {
2151
2251
  setInPath(formValues, path, value);
2152
2252
  setFieldInitialValue(path, value);
2253
+ if (updateOriginal) {
2254
+ setInPath(originalInitialValues.value, path, klona(value));
2255
+ }
2153
2256
  }
2154
2257
  async function _validateSchema() {
2155
2258
  const schemaValue = unref(schema);
@@ -2166,10 +2269,12 @@ function useForm(opts) {
2166
2269
  }
2167
2270
  /**
2168
2271
  * Batches validation runs in 5ms batches
2272
+ * Must have two distinct batch queues to make sure they don't override each other settings #3783
2169
2273
  */
2170
- const debouncedSchemaValidation = debounceAsync(_validateSchema, 5);
2274
+ const debouncedSilentValidation = debounceAsync(_validateSchema, 5);
2275
+ const debouncedValidation = debounceAsync(_validateSchema, 5);
2171
2276
  async function validateSchema(mode) {
2172
- const formResult = await debouncedSchemaValidation();
2277
+ const formResult = await (mode === 'silent' ? debouncedSilentValidation() : debouncedValidation());
2173
2278
  // fields by id lookup
2174
2279
  const fieldsById = formCtx.fieldsByPath.value || {};
2175
2280
  // errors fields names, we need it to also check if custom errors are updated
@@ -2265,6 +2370,7 @@ function useForm(opts) {
2265
2370
  setValues,
2266
2371
  setFieldTouched,
2267
2372
  setTouched,
2373
+ useFieldModel,
2268
2374
  };
2269
2375
  }
2270
2376
  /**
@@ -2279,16 +2385,23 @@ function useFormMeta(fieldsByPath, currentValues, initialValues, errors) {
2279
2385
  const isDirty = computed(() => {
2280
2386
  return !es6(currentValues, unref(initialValues));
2281
2387
  });
2282
- const flags = computed(() => {
2388
+ function calculateFlags() {
2283
2389
  const fields = Object.values(fieldsByPath.value).flat(1).filter(Boolean);
2284
2390
  return keysOf(MERGE_STRATEGIES).reduce((acc, flag) => {
2285
2391
  const mergeMethod = MERGE_STRATEGIES[flag];
2286
2392
  acc[flag] = fields[mergeMethod](field => field.meta[flag]);
2287
2393
  return acc;
2288
2394
  }, {});
2395
+ }
2396
+ const flags = reactive(calculateFlags());
2397
+ watchEffect(() => {
2398
+ const value = calculateFlags();
2399
+ flags.touched = value.touched;
2400
+ flags.valid = value.valid;
2401
+ flags.pending = value.pending;
2289
2402
  });
2290
2403
  return computed(() => {
2291
- return Object.assign(Object.assign({ initialValues: unref(initialValues) }, flags.value), { valid: flags.value.valid && !keysOf(errors.value).length, dirty: isDirty.value });
2404
+ return Object.assign(Object.assign({ initialValues: unref(initialValues) }, flags), { valid: flags.valid && !keysOf(errors.value).length, dirty: isDirty.value });
2292
2405
  });
2293
2406
  }
2294
2407
  /**
@@ -2409,16 +2522,22 @@ const FormImpl = defineComponent({
2409
2522
  type: Function,
2410
2523
  default: undefined,
2411
2524
  },
2525
+ keepValues: {
2526
+ type: Boolean,
2527
+ default: false,
2528
+ },
2412
2529
  },
2413
2530
  setup(props, ctx) {
2414
2531
  const initialValues = toRef(props, 'initialValues');
2415
2532
  const validationSchema = toRef(props, 'validationSchema');
2533
+ const keepValues = toRef(props, 'keepValues');
2416
2534
  const { errors, values, meta, isSubmitting, submitCount, validate, validateField, handleReset, resetForm, handleSubmit, submitForm, setErrors, setFieldError, setFieldValue, setValues, setFieldTouched, setTouched, } = useForm({
2417
2535
  validationSchema: validationSchema.value ? validationSchema : undefined,
2418
2536
  initialValues,
2419
2537
  initialErrors: props.initialErrors,
2420
2538
  initialTouched: props.initialTouched,
2421
2539
  validateOnMount: props.validateOnMount,
2540
+ keepValuesOnUnmount: keepValues,
2422
2541
  });
2423
2542
  const onSubmit = props.onSubmit ? handleSubmit(props.onSubmit, props.onInvalidSubmit) : submitForm;
2424
2543
  function handleFormReset(e) {
@@ -2488,15 +2607,13 @@ const FormImpl = defineComponent({
2488
2607
  });
2489
2608
  const Form = FormImpl;
2490
2609
 
2491
- let FIELD_ARRAY_COUNTER = 0;
2492
2610
  function useFieldArray(arrayPath) {
2493
- const id = FIELD_ARRAY_COUNTER++;
2494
2611
  const form = injectWithSelf(FormContextKey, undefined);
2495
2612
  const fields = ref([]);
2496
2613
  // eslint-disable-next-line @typescript-eslint/no-empty-function
2497
2614
  const noOp = () => { };
2498
2615
  const noOpApi = {
2499
- fields: readonly(fields),
2616
+ fields,
2500
2617
  remove: noOp,
2501
2618
  push: noOp,
2502
2619
  swap: noOp,
@@ -2504,6 +2621,7 @@ function useFieldArray(arrayPath) {
2504
2621
  update: noOp,
2505
2622
  replace: noOp,
2506
2623
  prepend: noOp,
2624
+ move: noOp,
2507
2625
  };
2508
2626
  if (!form) {
2509
2627
  warn('FieldArray requires being a child of `<Form/>` or `useForm` being called before it. Array fields may not work correctly');
@@ -2513,9 +2631,13 @@ function useFieldArray(arrayPath) {
2513
2631
  warn('FieldArray requires a field path to be provided, did you forget to pass the `name` prop?');
2514
2632
  return noOpApi;
2515
2633
  }
2634
+ const alreadyExists = form.fieldArrays.find(a => unref(a.path) === unref(arrayPath));
2635
+ if (alreadyExists) {
2636
+ return alreadyExists;
2637
+ }
2516
2638
  let entryCounter = 0;
2517
2639
  function initFields() {
2518
- const currentValues = getFromPath(form === null || form === void 0 ? void 0 : form.values, unref(arrayPath), []);
2640
+ const currentValues = getFromPath(form === null || form === void 0 ? void 0 : form.values, unref(arrayPath), []) || [];
2519
2641
  fields.value = currentValues.map(createEntry);
2520
2642
  updateEntryFlags();
2521
2643
  }
@@ -2532,10 +2654,20 @@ function useFieldArray(arrayPath) {
2532
2654
  const key = entryCounter++;
2533
2655
  const entry = {
2534
2656
  key,
2535
- value: computed(() => {
2536
- const currentValues = getFromPath(form === null || form === void 0 ? void 0 : form.values, unref(arrayPath), []);
2537
- const idx = fields.value.findIndex(e => e.key === key);
2538
- return idx === -1 ? value : currentValues[idx];
2657
+ value: computed({
2658
+ get() {
2659
+ const currentValues = getFromPath(form === null || form === void 0 ? void 0 : form.values, unref(arrayPath), []) || [];
2660
+ const idx = fields.value.findIndex(e => e.key === key);
2661
+ return idx === -1 ? value : currentValues[idx];
2662
+ },
2663
+ set(value) {
2664
+ const idx = fields.value.findIndex(e => e.key === key);
2665
+ if (idx === -1) {
2666
+ warn(`Attempting to update a non-existent array item`);
2667
+ return;
2668
+ }
2669
+ update(idx, value);
2670
+ },
2539
2671
  }),
2540
2672
  isFirst: false,
2541
2673
  isLast: false,
@@ -2572,7 +2704,7 @@ function useFieldArray(arrayPath) {
2572
2704
  function swap(indexA, indexB) {
2573
2705
  const pathName = unref(arrayPath);
2574
2706
  const pathValue = getFromPath(form === null || form === void 0 ? void 0 : form.values, pathName);
2575
- if (!Array.isArray(pathValue) || !pathValue[indexA] || !pathValue[indexB]) {
2707
+ if (!Array.isArray(pathValue) || !(indexA in pathValue) || !(indexB in pathValue)) {
2576
2708
  return;
2577
2709
  }
2578
2710
  const newValue = [...pathValue];
@@ -2628,14 +2760,26 @@ function useFieldArray(arrayPath) {
2628
2760
  fields.value.unshift(createEntry(value));
2629
2761
  updateEntryFlags();
2630
2762
  }
2631
- form.fieldArraysLookup[id] = {
2632
- reset: initFields,
2633
- };
2634
- onBeforeUnmount(() => {
2635
- delete form.fieldArraysLookup[id];
2636
- });
2637
- return {
2638
- fields: readonly(fields),
2763
+ function move(oldIdx, newIdx) {
2764
+ const pathName = unref(arrayPath);
2765
+ const pathValue = getFromPath(form === null || form === void 0 ? void 0 : form.values, pathName);
2766
+ const newValue = isNullOrUndefined(pathValue) ? [] : [...pathValue];
2767
+ if (!Array.isArray(pathValue) || !(oldIdx in pathValue) || !(newIdx in pathValue)) {
2768
+ return;
2769
+ }
2770
+ const newFields = [...fields.value];
2771
+ const movedItem = newFields[oldIdx];
2772
+ newFields.splice(oldIdx, 1);
2773
+ newFields.splice(newIdx, 0, movedItem);
2774
+ const movedValue = newValue[oldIdx];
2775
+ newValue.splice(oldIdx, 1);
2776
+ newValue.splice(newIdx, 0, movedValue);
2777
+ form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, newValue);
2778
+ fields.value = newFields;
2779
+ updateEntryFlags();
2780
+ }
2781
+ const fieldArrayCtx = {
2782
+ fields,
2639
2783
  remove,
2640
2784
  push,
2641
2785
  swap,
@@ -2643,7 +2787,16 @@ function useFieldArray(arrayPath) {
2643
2787
  update,
2644
2788
  replace,
2645
2789
  prepend,
2790
+ move,
2646
2791
  };
2792
+ form.fieldArrays.push(Object.assign({ path: arrayPath, reset: initFields }, fieldArrayCtx));
2793
+ onBeforeUnmount(() => {
2794
+ const idx = form.fieldArrays.findIndex(i => unref(i.path) === unref(arrayPath));
2795
+ if (idx >= 0) {
2796
+ form.fieldArrays.splice(idx, 1);
2797
+ }
2798
+ });
2799
+ return fieldArrayCtx;
2647
2800
  }
2648
2801
 
2649
2802
  const FieldArrayImpl = defineComponent({
@@ -2656,7 +2809,7 @@ const FieldArrayImpl = defineComponent({
2656
2809
  },
2657
2810
  },
2658
2811
  setup(props, ctx) {
2659
- const { push, remove, swap, insert, replace, update, prepend, fields } = useFieldArray(toRef(props, 'name'));
2812
+ const { push, remove, swap, insert, replace, update, prepend, move, fields } = useFieldArray(toRef(props, 'name'));
2660
2813
  function slotProps() {
2661
2814
  return {
2662
2815
  fields: fields.value,
@@ -2667,6 +2820,7 @@ const FieldArrayImpl = defineComponent({
2667
2820
  update,
2668
2821
  replace,
2669
2822
  prepend,
2823
+ move,
2670
2824
  };
2671
2825
  }
2672
2826
  ctx.expose({
@@ -2677,6 +2831,7 @@ const FieldArrayImpl = defineComponent({
2677
2831
  update,
2678
2832
  replace,
2679
2833
  prepend,
2834
+ move,
2680
2835
  });
2681
2836
  return () => {
2682
2837
  const children = normalizeChildren(undefined, ctx, slotProps);
@@ -2976,4 +3131,4 @@ function useSubmitForm(cb) {
2976
3131
  };
2977
3132
  }
2978
3133
 
2979
- export { ErrorMessage, Field, FieldArray, FieldContextKey, Form, FormContextKey, configure, defineRule, useField, useFieldArray, useFieldError, useFieldValue, useForm, useFormErrors, useFormValues, useIsFieldDirty, useIsFieldTouched, useIsFieldValid, useIsFormDirty, useIsFormTouched, useIsFormValid, useIsSubmitting, useResetForm, useSubmitCount, useSubmitForm, useValidateField, useValidateForm, validate };
3134
+ export { ErrorMessage, Field, FieldArray, FieldContextKey, Form, FormContextKey, IS_ABSENT, configure, defineRule, useField, useFieldArray, useFieldError, useFieldValue, useForm, useFormErrors, useFormValues, useIsFieldDirty, useIsFieldTouched, useIsFieldValid, useIsFormDirty, useIsFormTouched, useIsFormValid, useIsSubmitting, useResetForm, useSubmitCount, useSubmitForm, useValidateField, useValidateForm, validate };