vee-validate 4.5.11 → 4.6.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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * vee-validate v4.5.11
2
+ * vee-validate v4.6.2
3
3
  * (c) 2022 Abdelrahman Awad
4
4
  * @license MIT
5
5
  */
@@ -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)
@@ -828,8 +826,8 @@
828
826
  /**
829
827
  * Creates the field value and resolves the initial value
830
828
  */
831
- function _useFieldValue(path, modelValue, shouldInjectForm) {
832
- const form = shouldInjectForm ? injectWithSelf(FormContextKey, undefined) : undefined;
829
+ function _useFieldValue(path, modelValue, shouldInjectForm = true) {
830
+ const form = shouldInjectForm === true ? injectWithSelf(FormContextKey, undefined) : undefined;
833
831
  const modelRef = vue.ref(vue.unref(modelValue));
834
832
  function resolveInitialValue() {
835
833
  if (!form) {
@@ -859,7 +857,7 @@
859
857
  // prioritize model value over form values
860
858
  // #3429
861
859
  const currentValue = modelValue ? vue.unref(modelValue) : getFromPath(form.values, vue.unref(path), vue.unref(initialValue));
862
- form.stageInitialValue(vue.unref(path), currentValue);
860
+ form.stageInitialValue(vue.unref(path), currentValue, true);
863
861
  // otherwise use a computed setter that triggers the `setFieldValue`
864
862
  const value = vue.computed({
865
863
  get() {
@@ -938,7 +936,7 @@
938
936
  return _useField(name, rules, opts);
939
937
  }
940
938
  function _useField(name, rules, opts) {
941
- 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);
942
940
  const form = !standalone ? injectWithSelf(FormContextKey) : undefined;
943
941
  // a flag indicating if the field is about to be removed/unmounted.
944
942
  let markedForRemoval = false;
@@ -946,6 +944,9 @@
946
944
  modelValue,
947
945
  standalone,
948
946
  });
947
+ if (syncVModel) {
948
+ useVModel({ value, prop: modelPropName, handleChange });
949
+ }
949
950
  /**
950
951
  * Handles common onBlur meta update
951
952
  */
@@ -1004,13 +1005,13 @@
1004
1005
  return validateValidStateOnly();
1005
1006
  }
1006
1007
  // Common input/change event handler
1007
- const handleChange = (e, shouldValidate = true) => {
1008
+ function handleChange(e, shouldValidate = true) {
1008
1009
  const newValue = normalizeEventValue(e);
1009
1010
  value.value = newValue;
1010
1011
  if (!validateOnValueUpdate && shouldValidate) {
1011
1012
  validateWithStateMutation();
1012
1013
  }
1013
- };
1014
+ }
1014
1015
  // Runs the initial validation
1015
1016
  vue.onMounted(() => {
1016
1017
  if (validateOnMount) {
@@ -1027,7 +1028,13 @@
1027
1028
  }
1028
1029
  let unwatchValue;
1029
1030
  function watchValue() {
1030
- 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
+ }, {
1031
1038
  deep: true,
1032
1039
  });
1033
1040
  }
@@ -1068,6 +1075,7 @@
1068
1075
  checkedValue,
1069
1076
  uncheckedValue,
1070
1077
  bails,
1078
+ keepValueOnUnmount,
1071
1079
  resetField,
1072
1080
  handleReset: () => resetField(),
1073
1081
  validate: validate$1,
@@ -1145,6 +1153,9 @@
1145
1153
  label: name,
1146
1154
  validateOnValueUpdate: true,
1147
1155
  standalone: false,
1156
+ keepValueOnUnmount: undefined,
1157
+ modelPropName: 'modelValue',
1158
+ syncVModel: true,
1148
1159
  });
1149
1160
  if (!opts) {
1150
1161
  return defaults();
@@ -1176,8 +1187,8 @@
1176
1187
  return Array.isArray(currentValue) ? currentValue.includes(checkedVal) : checkedVal === currentValue;
1177
1188
  });
1178
1189
  function handleCheckboxChange(e, shouldValidate = true) {
1179
- var _a, _b;
1180
- 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)) {
1181
1192
  return;
1182
1193
  }
1183
1194
  let newValue = normalizeEventValue(e);
@@ -1187,17 +1198,43 @@
1187
1198
  }
1188
1199
  handleChange(newValue, shouldValidate);
1189
1200
  }
1190
- vue.onBeforeUnmount(() => {
1191
- // toggles the checkbox value if it was checked
1192
- if (checked.value) {
1193
- handleCheckboxChange(vue.unref(checkedValue), false);
1194
- }
1195
- });
1196
1201
  return Object.assign(Object.assign({}, field), { checked,
1197
1202
  checkedValue,
1198
1203
  uncheckedValue, handleChange: handleCheckboxChange });
1199
1204
  }
1200
1205
  return patchCheckboxApi(_useField(name, rules, opts));
1206
+ }
1207
+ function useVModel({ prop, value, handleChange }) {
1208
+ const vm = vue.getCurrentInstance();
1209
+ /* istanbul ignore next */
1210
+ if (!vm) {
1211
+ return;
1212
+ }
1213
+ const propName = prop || 'modelValue';
1214
+ const emitName = `update:${propName}`;
1215
+ // Component doesn't have a model prop setup (must be defined on the props)
1216
+ if (!(propName in vm.props)) {
1217
+ return;
1218
+ }
1219
+ vue.watch(value, newValue => {
1220
+ if (es6(newValue, getCurrentModelValue(vm, propName))) {
1221
+ return;
1222
+ }
1223
+ vm.emit(emitName, newValue);
1224
+ });
1225
+ vue.watch(() => getCurrentModelValue(vm, propName), propValue => {
1226
+ if (propValue === IS_ABSENT && value.value === undefined) {
1227
+ return;
1228
+ }
1229
+ const newValue = propValue === IS_ABSENT ? undefined : propValue;
1230
+ if (es6(newValue, applyModelModifiers(value.value, vm.props.modelModifiers))) {
1231
+ return;
1232
+ }
1233
+ handleChange(newValue);
1234
+ });
1235
+ }
1236
+ function getCurrentModelValue(vm, propName) {
1237
+ return vm.props[propName];
1201
1238
  }
1202
1239
 
1203
1240
  const FieldImpl = vue.defineComponent({
@@ -1264,13 +1301,17 @@
1264
1301
  type: Boolean,
1265
1302
  default: false,
1266
1303
  },
1304
+ keepValue: {
1305
+ type: Boolean,
1306
+ default: undefined,
1307
+ },
1267
1308
  },
1268
1309
  setup(props, ctx) {
1269
1310
  const rules = vue.toRef(props, 'rules');
1270
1311
  const name = vue.toRef(props, 'name');
1271
1312
  const label = vue.toRef(props, 'label');
1272
1313
  const uncheckedValue = vue.toRef(props, 'uncheckedValue');
1273
- const hasModelEvents = isPropPresent(props, 'onUpdate:modelValue');
1314
+ const keepValue = vue.toRef(props, 'keepValue');
1274
1315
  const { errors, value, errorMessage, validate: validateField, handleChange, handleBlur, setTouched, resetField, handleReset, meta, checked, setErrors, } = useField(name, rules, {
1275
1316
  validateOnMount: props.validateOnMount,
1276
1317
  bails: props.bails,
@@ -1282,25 +1323,22 @@
1282
1323
  uncheckedValue,
1283
1324
  label,
1284
1325
  validateOnValueUpdate: false,
1326
+ keepValueOnUnmount: keepValue,
1285
1327
  });
1286
1328
  // If there is a v-model applied on the component we need to emit the `update:modelValue` whenever the value binding changes
1287
- const onChangeHandler = hasModelEvents
1288
- ? function handleChangeWithModel(e, shouldValidate = true) {
1289
- handleChange(e, shouldValidate);
1290
- ctx.emit('update:modelValue', value.value);
1291
- }
1292
- : handleChange;
1329
+ const onChangeHandler = function handleChangeWithModel(e, shouldValidate = true) {
1330
+ handleChange(e, shouldValidate);
1331
+ ctx.emit('update:modelValue', value.value);
1332
+ };
1293
1333
  const handleInput = (e) => {
1294
1334
  if (!hasCheckedAttr(ctx.attrs.type)) {
1295
1335
  value.value = normalizeEventValue(e);
1296
1336
  }
1297
1337
  };
1298
- const onInputHandler = hasModelEvents
1299
- ? function handleInputWithModel(e) {
1300
- handleInput(e);
1301
- ctx.emit('update:modelValue', value.value);
1302
- }
1303
- : handleInput;
1338
+ const onInputHandler = function handleInputWithModel(e) {
1339
+ handleInput(e);
1340
+ ctx.emit('update:modelValue', value.value);
1341
+ };
1304
1342
  const fieldProps = vue.computed(() => {
1305
1343
  const { validateOnInput, validateOnChange, validateOnBlur, validateOnModelUpdate } = resolveValidationTriggers(props);
1306
1344
  const baseOnBlur = [handleBlur, ctx.attrs.onBlur, validateOnBlur ? validateField : undefined].filter(Boolean);
@@ -1316,26 +1354,12 @@
1316
1354
  if (hasCheckedAttr(ctx.attrs.type) && checked) {
1317
1355
  attrs.checked = checked.value;
1318
1356
  }
1319
- else {
1320
- attrs.value = value.value;
1321
- }
1322
1357
  const tag = resolveTag(props, ctx);
1323
1358
  if (shouldHaveValueBinding(tag, ctx.attrs)) {
1324
- delete attrs.value;
1359
+ attrs.value = value.value;
1325
1360
  }
1326
1361
  return attrs;
1327
1362
  });
1328
- const modelValue = vue.toRef(props, 'modelValue');
1329
- vue.watch(modelValue, newModelValue => {
1330
- // Don't attempt to sync absent values
1331
- if (newModelValue === IS_ABSENT && value.value === undefined) {
1332
- return;
1333
- }
1334
- if (newModelValue !== applyModifiers(value.value, props.modelModifiers)) {
1335
- value.value = newModelValue === IS_ABSENT ? undefined : newModelValue;
1336
- validateField();
1337
- }
1338
- });
1339
1363
  function slotProps() {
1340
1364
  return {
1341
1365
  field: fieldProps.value,
@@ -1387,12 +1411,6 @@
1387
1411
  validateOnModelUpdate: (_d = props.validateOnModelUpdate) !== null && _d !== void 0 ? _d : validateOnModelUpdate,
1388
1412
  };
1389
1413
  }
1390
- function applyModifiers(value, modifiers) {
1391
- if (modifiers.number) {
1392
- return toNumber(value);
1393
- }
1394
- return value;
1395
- }
1396
1414
  function resolveInitialValue(props, ctx) {
1397
1415
  // Gets the initial value either from `value` prop/attr or `v-model` binding (modelValue)
1398
1416
  // For checkboxes and radio buttons it will always be the model value not the `value` attribute
@@ -1405,6 +1423,7 @@
1405
1423
 
1406
1424
  let FORM_COUNTER = 0;
1407
1425
  function useForm(opts) {
1426
+ var _a;
1408
1427
  const formId = FORM_COUNTER++;
1409
1428
  // Prevents fields from double resetting their values, which causes checkboxes to toggle their initial value
1410
1429
  // TODO: This won't be needed if we centralize all the state inside the `form` for form inputs
@@ -1415,8 +1434,8 @@
1415
1434
  const isSubmitting = vue.ref(false);
1416
1435
  // The number of times the user tried to submit the form
1417
1436
  const submitCount = vue.ref(0);
1418
- // dictionary for field arrays to receive various signals like reset
1419
- const fieldArraysLookup = {};
1437
+ // field arrays managed by this form
1438
+ const fieldArrays = [];
1420
1439
  // a private ref for all form values
1421
1440
  const formValues = vue.reactive(klona(vue.unref(opts === null || opts === void 0 ? void 0 : opts.initialValues) || {}));
1422
1441
  // the source of errors for the form fields
@@ -1463,10 +1482,11 @@
1463
1482
  // mutable non-reactive reference to initial errors
1464
1483
  // we need this to process initial errors then unset them
1465
1484
  const initialErrors = Object.assign({}, ((opts === null || opts === void 0 ? void 0 : opts.initialErrors) || {}));
1485
+ const keepValuesOnUnmount = (_a = opts === null || opts === void 0 ? void 0 : opts.keepValuesOnUnmount) !== null && _a !== void 0 ? _a : false;
1466
1486
  // initial form values
1467
1487
  const { initialValues, originalInitialValues, setInitialValues } = useFormInitialValues(fieldsByPath, formValues, opts === null || opts === void 0 ? void 0 : opts.initialValues);
1468
1488
  // form meta aggregations
1469
- const meta = useFormMeta(fieldsByPath, formValues, initialValues, errors);
1489
+ const meta = useFormMeta(fieldsByPath, formValues, originalInitialValues, errors);
1470
1490
  const schema = opts === null || opts === void 0 ? void 0 : opts.validationSchema;
1471
1491
  const formCtx = {
1472
1492
  formId,
@@ -1478,7 +1498,8 @@
1478
1498
  submitCount,
1479
1499
  meta,
1480
1500
  isSubmitting,
1481
- fieldArraysLookup,
1501
+ fieldArrays,
1502
+ keepValuesOnUnmount,
1482
1503
  validateSchema: vue.unref(schema) ? validateSchema : undefined,
1483
1504
  validate,
1484
1505
  register: registerField,
@@ -1496,6 +1517,7 @@
1496
1517
  stageInitialValue,
1497
1518
  unsetInitialValue,
1498
1519
  setFieldInitialValue,
1520
+ useFieldModel,
1499
1521
  };
1500
1522
  function isFieldGroup(fieldOrGroup) {
1501
1523
  return Array.isArray(fieldOrGroup);
@@ -1565,7 +1587,22 @@
1565
1587
  setFieldValue(path, fields[path]);
1566
1588
  });
1567
1589
  // regenerate the arrays when the form values change
1568
- Object.values(fieldArraysLookup).forEach(f => f && f.reset());
1590
+ fieldArrays.forEach(f => f && f.reset());
1591
+ }
1592
+ function createModel(path) {
1593
+ const { value } = _useFieldValue(path);
1594
+ vue.watch(value, () => {
1595
+ if (!fieldExists(vue.unref(path))) {
1596
+ validate({ mode: 'validated-only' });
1597
+ }
1598
+ });
1599
+ return value;
1600
+ }
1601
+ function useFieldModel(path) {
1602
+ if (!Array.isArray(path)) {
1603
+ return createModel(path);
1604
+ }
1605
+ return path.map(createModel);
1569
1606
  }
1570
1607
  /**
1571
1608
  * Sets the touched meta state on a field
@@ -1643,10 +1680,6 @@
1643
1680
  return;
1644
1681
  }
1645
1682
  fieldAtPath.splice(idx, 1);
1646
- if (fieldAtPath.length === 1) {
1647
- fieldsByPath.value[fieldPath] = fieldAtPath[0];
1648
- return;
1649
- }
1650
1683
  if (!fieldAtPath.length) {
1651
1684
  delete fieldsByPath.value[fieldPath];
1652
1685
  }
@@ -1689,13 +1722,45 @@
1689
1722
  }
1690
1723
  function unregisterField(field) {
1691
1724
  const fieldName = vue.unref(field.name);
1725
+ const fieldInstance = fieldsByPath.value[fieldName];
1726
+ const isGroup = !!fieldInstance && isFieldGroup(fieldInstance);
1692
1727
  removeFieldFromPath(field, fieldName);
1728
+ // clears a field error on unmounted
1729
+ // we wait till next tick to make sure if the field is completely removed and doesn't have any siblings like checkboxes
1693
1730
  vue.nextTick(() => {
1694
- // clears a field error on unmounted
1695
- // we wait till next tick to make sure if the field is completely removed and doesn't have any siblings like checkboxes
1731
+ var _a;
1732
+ const shouldKeepValue = (_a = vue.unref(field.keepValueOnUnmount)) !== null && _a !== void 0 ? _a : vue.unref(keepValuesOnUnmount);
1733
+ const currentGroupValue = getFromPath(formValues, fieldName);
1734
+ // The boolean here is we check if the field still belongs to the same control group with that name
1735
+ // if another group claimed the name, we should avoid handling it since it is no longer the same group
1736
+ // this happens with `v-for` over some checkboxes and field arrays.
1737
+ // also if the group no longer exist we can assume this group was the last one that controlled it
1738
+ const isSameGroup = isGroup && (fieldInstance === fieldsByPath.value[fieldName] || !fieldsByPath.value[fieldName]);
1739
+ // group field that still has a dangling value, the field may exist or not after it was removed.
1740
+ // This used to be handled in the useField composable but the form has better context on when it should/not happen.
1741
+ // if it does belong to it that means the group still exists
1742
+ // #3844
1743
+ if (isSameGroup && Array.isArray(currentGroupValue) && !shouldKeepValue) {
1744
+ const valueIdx = currentGroupValue.findIndex(i => es6(i, vue.unref(field.checkedValue)));
1745
+ if (valueIdx > -1) {
1746
+ const newVal = [...currentGroupValue];
1747
+ newVal.splice(valueIdx, 1);
1748
+ setFieldValue(fieldName, newVal, { force: true });
1749
+ }
1750
+ }
1751
+ // Field was removed entirely, we should unset its path
1696
1752
  // #3384
1697
1753
  if (!fieldExists(fieldName)) {
1698
1754
  setFieldError(fieldName, undefined);
1755
+ // Checks if the field was configured to be unset during unmount or not
1756
+ // Checks both the form-level config and field-level one
1757
+ // Field has the priority if it is set, otherwise it goes to the form settings
1758
+ if (shouldKeepValue) {
1759
+ return;
1760
+ }
1761
+ if (isGroup && !isEmptyContainer(getFromPath(formValues, fieldName))) {
1762
+ return;
1763
+ }
1699
1764
  unsetPath(formValues, fieldName);
1700
1765
  }
1701
1766
  });
@@ -1802,9 +1867,12 @@
1802
1867
  /**
1803
1868
  * Sneaky function to set initial field values
1804
1869
  */
1805
- function stageInitialValue(path, value) {
1870
+ function stageInitialValue(path, value, updateOriginal = false) {
1806
1871
  setInPath(formValues, path, value);
1807
1872
  setFieldInitialValue(path, value);
1873
+ if (updateOriginal) {
1874
+ setInPath(originalInitialValues.value, path, klona(value));
1875
+ }
1808
1876
  }
1809
1877
  async function _validateSchema() {
1810
1878
  const schemaValue = vue.unref(schema);
@@ -1821,10 +1889,12 @@
1821
1889
  }
1822
1890
  /**
1823
1891
  * Batches validation runs in 5ms batches
1892
+ * Must have two distinct batch queues to make sure they don't override each other settings #3783
1824
1893
  */
1825
- const debouncedSchemaValidation = debounceAsync(_validateSchema, 5);
1894
+ const debouncedSilentValidation = debounceAsync(_validateSchema, 5);
1895
+ const debouncedValidation = debounceAsync(_validateSchema, 5);
1826
1896
  async function validateSchema(mode) {
1827
- const formResult = await debouncedSchemaValidation();
1897
+ const formResult = await (mode === 'silent' ? debouncedSilentValidation() : debouncedValidation());
1828
1898
  // fields by id lookup
1829
1899
  const fieldsById = formCtx.fieldsByPath.value || {};
1830
1900
  // errors fields names, we need it to also check if custom errors are updated
@@ -1914,6 +1984,7 @@
1914
1984
  setValues,
1915
1985
  setFieldTouched,
1916
1986
  setTouched,
1987
+ useFieldModel,
1917
1988
  };
1918
1989
  }
1919
1990
  /**
@@ -2065,17 +2136,28 @@
2065
2136
  type: Function,
2066
2137
  default: undefined,
2067
2138
  },
2139
+ keepValues: {
2140
+ type: Boolean,
2141
+ default: false,
2142
+ },
2068
2143
  },
2069
2144
  setup(props, ctx) {
2070
2145
  const initialValues = vue.toRef(props, 'initialValues');
2071
2146
  const validationSchema = vue.toRef(props, 'validationSchema');
2072
- const { errors, values, meta, isSubmitting, submitCount, validate, validateField, handleReset, resetForm, handleSubmit, submitForm, setErrors, setFieldError, setFieldValue, setValues, setFieldTouched, setTouched, } = useForm({
2147
+ const keepValues = vue.toRef(props, 'keepValues');
2148
+ const { errors, values, meta, isSubmitting, submitCount, validate, validateField, handleReset, resetForm, handleSubmit, setErrors, setFieldError, setFieldValue, setValues, setFieldTouched, setTouched, } = useForm({
2073
2149
  validationSchema: validationSchema.value ? validationSchema : undefined,
2074
2150
  initialValues,
2075
2151
  initialErrors: props.initialErrors,
2076
2152
  initialTouched: props.initialTouched,
2077
2153
  validateOnMount: props.validateOnMount,
2154
+ keepValuesOnUnmount: keepValues,
2078
2155
  });
2156
+ const submitForm = handleSubmit((_, { evt }) => {
2157
+ if (isFormSubmitEvent(evt)) {
2158
+ evt.target.submit();
2159
+ }
2160
+ }, props.onInvalidSubmit);
2079
2161
  const onSubmit = props.onSubmit ? handleSubmit(props.onSubmit, props.onInvalidSubmit) : submitForm;
2080
2162
  function handleFormReset(e) {
2081
2163
  if (isEvent(e)) {
@@ -2144,15 +2226,13 @@
2144
2226
  });
2145
2227
  const Form = FormImpl;
2146
2228
 
2147
- let FIELD_ARRAY_COUNTER = 0;
2148
2229
  function useFieldArray(arrayPath) {
2149
- const id = FIELD_ARRAY_COUNTER++;
2150
2230
  const form = injectWithSelf(FormContextKey, undefined);
2151
2231
  const fields = vue.ref([]);
2152
2232
  // eslint-disable-next-line @typescript-eslint/no-empty-function
2153
2233
  const noOp = () => { };
2154
2234
  const noOpApi = {
2155
- fields: vue.readonly(fields),
2235
+ fields,
2156
2236
  remove: noOp,
2157
2237
  push: noOp,
2158
2238
  swap: noOp,
@@ -2160,6 +2240,7 @@
2160
2240
  update: noOp,
2161
2241
  replace: noOp,
2162
2242
  prepend: noOp,
2243
+ move: noOp,
2163
2244
  };
2164
2245
  if (!form) {
2165
2246
  warn('FieldArray requires being a child of `<Form/>` or `useForm` being called before it. Array fields may not work correctly');
@@ -2169,9 +2250,13 @@
2169
2250
  warn('FieldArray requires a field path to be provided, did you forget to pass the `name` prop?');
2170
2251
  return noOpApi;
2171
2252
  }
2253
+ const alreadyExists = form.fieldArrays.find(a => vue.unref(a.path) === vue.unref(arrayPath));
2254
+ if (alreadyExists) {
2255
+ return alreadyExists;
2256
+ }
2172
2257
  let entryCounter = 0;
2173
2258
  function initFields() {
2174
- const currentValues = getFromPath(form === null || form === void 0 ? void 0 : form.values, vue.unref(arrayPath), []);
2259
+ const currentValues = getFromPath(form === null || form === void 0 ? void 0 : form.values, vue.unref(arrayPath), []) || [];
2175
2260
  fields.value = currentValues.map(createEntry);
2176
2261
  updateEntryFlags();
2177
2262
  }
@@ -2188,10 +2273,20 @@
2188
2273
  const key = entryCounter++;
2189
2274
  const entry = {
2190
2275
  key,
2191
- value: vue.computed(() => {
2192
- const currentValues = getFromPath(form === null || form === void 0 ? void 0 : form.values, vue.unref(arrayPath), []);
2193
- const idx = fields.value.findIndex(e => e.key === key);
2194
- return idx === -1 ? value : currentValues[idx];
2276
+ value: vue.computed({
2277
+ get() {
2278
+ const currentValues = getFromPath(form === null || form === void 0 ? void 0 : form.values, vue.unref(arrayPath), []) || [];
2279
+ const idx = fields.value.findIndex(e => e.key === key);
2280
+ return idx === -1 ? value : currentValues[idx];
2281
+ },
2282
+ set(value) {
2283
+ const idx = fields.value.findIndex(e => e.key === key);
2284
+ if (idx === -1) {
2285
+ warn(`Attempting to update a non-existent array item`);
2286
+ return;
2287
+ }
2288
+ update(idx, value);
2289
+ },
2195
2290
  }),
2196
2291
  isFirst: false,
2197
2292
  isLast: false,
@@ -2284,14 +2379,26 @@
2284
2379
  fields.value.unshift(createEntry(value));
2285
2380
  updateEntryFlags();
2286
2381
  }
2287
- form.fieldArraysLookup[id] = {
2288
- reset: initFields,
2289
- };
2290
- vue.onBeforeUnmount(() => {
2291
- delete form.fieldArraysLookup[id];
2292
- });
2293
- return {
2294
- fields: vue.readonly(fields),
2382
+ function move(oldIdx, newIdx) {
2383
+ const pathName = vue.unref(arrayPath);
2384
+ const pathValue = getFromPath(form === null || form === void 0 ? void 0 : form.values, pathName);
2385
+ const newValue = isNullOrUndefined(pathValue) ? [] : [...pathValue];
2386
+ if (!Array.isArray(pathValue) || !(oldIdx in pathValue) || !(newIdx in pathValue)) {
2387
+ return;
2388
+ }
2389
+ const newFields = [...fields.value];
2390
+ const movedItem = newFields[oldIdx];
2391
+ newFields.splice(oldIdx, 1);
2392
+ newFields.splice(newIdx, 0, movedItem);
2393
+ const movedValue = newValue[oldIdx];
2394
+ newValue.splice(oldIdx, 1);
2395
+ newValue.splice(newIdx, 0, movedValue);
2396
+ form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, newValue);
2397
+ fields.value = newFields;
2398
+ updateEntryFlags();
2399
+ }
2400
+ const fieldArrayCtx = {
2401
+ fields,
2295
2402
  remove,
2296
2403
  push,
2297
2404
  swap,
@@ -2299,7 +2406,16 @@
2299
2406
  update,
2300
2407
  replace,
2301
2408
  prepend,
2409
+ move,
2302
2410
  };
2411
+ form.fieldArrays.push(Object.assign({ path: arrayPath, reset: initFields }, fieldArrayCtx));
2412
+ vue.onBeforeUnmount(() => {
2413
+ const idx = form.fieldArrays.findIndex(i => vue.unref(i.path) === vue.unref(arrayPath));
2414
+ if (idx >= 0) {
2415
+ form.fieldArrays.splice(idx, 1);
2416
+ }
2417
+ });
2418
+ return fieldArrayCtx;
2303
2419
  }
2304
2420
 
2305
2421
  const FieldArrayImpl = vue.defineComponent({
@@ -2312,7 +2428,7 @@
2312
2428
  },
2313
2429
  },
2314
2430
  setup(props, ctx) {
2315
- const { push, remove, swap, insert, replace, update, prepend, fields } = useFieldArray(vue.toRef(props, 'name'));
2431
+ const { push, remove, swap, insert, replace, update, prepend, move, fields } = useFieldArray(vue.toRef(props, 'name'));
2316
2432
  function slotProps() {
2317
2433
  return {
2318
2434
  fields: fields.value,
@@ -2323,6 +2439,7 @@
2323
2439
  update,
2324
2440
  replace,
2325
2441
  prepend,
2442
+ move,
2326
2443
  };
2327
2444
  }
2328
2445
  ctx.expose({
@@ -2333,6 +2450,7 @@
2333
2450
  update,
2334
2451
  replace,
2335
2452
  prepend,
2453
+ move,
2336
2454
  });
2337
2455
  return () => {
2338
2456
  const children = normalizeChildren(undefined, ctx, slotProps);
@@ -2638,6 +2756,7 @@
2638
2756
  exports.FieldContextKey = FieldContextKey;
2639
2757
  exports.Form = Form;
2640
2758
  exports.FormContextKey = FormContextKey;
2759
+ exports.IS_ABSENT = IS_ABSENT;
2641
2760
  exports.configure = configure;
2642
2761
  exports.defineRule = defineRule;
2643
2762
  exports.useField = useField;