vee-validate 4.5.10 → 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,5 +1,5 @@
1
1
  /**
2
- * vee-validate v4.5.10
2
+ * vee-validate v4.6.0
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)
@@ -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);
@@ -1290,26 +1362,12 @@
1290
1362
  if (hasCheckedAttr(ctx.attrs.type) && checked) {
1291
1363
  attrs.checked = checked.value;
1292
1364
  }
1293
- else {
1294
- attrs.value = value.value;
1295
- }
1296
1365
  const tag = resolveTag(props, ctx);
1297
1366
  if (shouldHaveValueBinding(tag, ctx.attrs)) {
1298
- delete attrs.value;
1367
+ attrs.value = value.value;
1299
1368
  }
1300
1369
  return attrs;
1301
1370
  });
1302
- const modelValue = vue.toRef(props, 'modelValue');
1303
- vue.watch(modelValue, newModelValue => {
1304
- // Don't attempt to sync absent values
1305
- if (newModelValue === IS_ABSENT && value.value === undefined) {
1306
- return;
1307
- }
1308
- if (newModelValue !== applyModifiers(value.value, props.modelModifiers)) {
1309
- value.value = newModelValue === IS_ABSENT ? undefined : newModelValue;
1310
- validateField();
1311
- }
1312
- });
1313
1371
  function slotProps() {
1314
1372
  return {
1315
1373
  field: fieldProps.value,
@@ -1361,12 +1419,6 @@
1361
1419
  validateOnModelUpdate: (_d = props.validateOnModelUpdate) !== null && _d !== void 0 ? _d : validateOnModelUpdate,
1362
1420
  };
1363
1421
  }
1364
- function applyModifiers(value, modifiers) {
1365
- if (modifiers.number) {
1366
- return toNumber(value);
1367
- }
1368
- return value;
1369
- }
1370
1422
  function resolveInitialValue(props, ctx) {
1371
1423
  // Gets the initial value either from `value` prop/attr or `v-model` binding (modelValue)
1372
1424
  // For checkboxes and radio buttons it will always be the model value not the `value` attribute
@@ -1379,6 +1431,7 @@
1379
1431
 
1380
1432
  let FORM_COUNTER = 0;
1381
1433
  function useForm(opts) {
1434
+ var _a;
1382
1435
  const formId = FORM_COUNTER++;
1383
1436
  // Prevents fields from double resetting their values, which causes checkboxes to toggle their initial value
1384
1437
  // TODO: This won't be needed if we centralize all the state inside the `form` for form inputs
@@ -1389,8 +1442,8 @@
1389
1442
  const isSubmitting = vue.ref(false);
1390
1443
  // The number of times the user tried to submit the form
1391
1444
  const submitCount = vue.ref(0);
1392
- // dictionary for field arrays to receive various signals like reset
1393
- const fieldArraysLookup = {};
1445
+ // field arrays managed by this form
1446
+ const fieldArrays = [];
1394
1447
  // a private ref for all form values
1395
1448
  const formValues = vue.reactive(klona(vue.unref(opts === null || opts === void 0 ? void 0 : opts.initialValues) || {}));
1396
1449
  // the source of errors for the form fields
@@ -1437,10 +1490,11 @@
1437
1490
  // mutable non-reactive reference to initial errors
1438
1491
  // we need this to process initial errors then unset them
1439
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;
1440
1494
  // initial form values
1441
1495
  const { initialValues, originalInitialValues, setInitialValues } = useFormInitialValues(fieldsByPath, formValues, opts === null || opts === void 0 ? void 0 : opts.initialValues);
1442
1496
  // form meta aggregations
1443
- const meta = useFormMeta(fieldsByPath, formValues, initialValues, errors);
1497
+ const meta = useFormMeta(fieldsByPath, formValues, originalInitialValues, errors);
1444
1498
  const schema = opts === null || opts === void 0 ? void 0 : opts.validationSchema;
1445
1499
  const formCtx = {
1446
1500
  formId,
@@ -1452,7 +1506,8 @@
1452
1506
  submitCount,
1453
1507
  meta,
1454
1508
  isSubmitting,
1455
- fieldArraysLookup,
1509
+ fieldArrays,
1510
+ keepValuesOnUnmount,
1456
1511
  validateSchema: vue.unref(schema) ? validateSchema : undefined,
1457
1512
  validate,
1458
1513
  register: registerField,
@@ -1470,6 +1525,7 @@
1470
1525
  stageInitialValue,
1471
1526
  unsetInitialValue,
1472
1527
  setFieldInitialValue,
1528
+ useFieldModel,
1473
1529
  };
1474
1530
  function isFieldGroup(fieldOrGroup) {
1475
1531
  return Array.isArray(fieldOrGroup);
@@ -1539,7 +1595,22 @@
1539
1595
  setFieldValue(path, fields[path]);
1540
1596
  });
1541
1597
  // regenerate the arrays when the form values change
1542
- 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);
1543
1614
  }
1544
1615
  /**
1545
1616
  * Sets the touched meta state on a field
@@ -1663,13 +1734,26 @@
1663
1734
  }
1664
1735
  function unregisterField(field) {
1665
1736
  const fieldName = vue.unref(field.name);
1737
+ const fieldInstance = fieldsByPath.value[fieldName];
1738
+ const isGroup = !!fieldInstance && isFieldGroup(fieldInstance);
1666
1739
  removeFieldFromPath(field, fieldName);
1667
1740
  vue.nextTick(() => {
1741
+ var _a;
1668
1742
  // clears a field error on unmounted
1669
1743
  // we wait till next tick to make sure if the field is completely removed and doesn't have any siblings like checkboxes
1670
1744
  // #3384
1671
1745
  if (!fieldExists(fieldName)) {
1672
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
+ }
1673
1757
  unsetPath(formValues, fieldName);
1674
1758
  }
1675
1759
  });
@@ -1776,9 +1860,12 @@
1776
1860
  /**
1777
1861
  * Sneaky function to set initial field values
1778
1862
  */
1779
- function stageInitialValue(path, value) {
1863
+ function stageInitialValue(path, value, updateOriginal = false) {
1780
1864
  setInPath(formValues, path, value);
1781
1865
  setFieldInitialValue(path, value);
1866
+ if (updateOriginal) {
1867
+ setInPath(originalInitialValues.value, path, klona(value));
1868
+ }
1782
1869
  }
1783
1870
  async function _validateSchema() {
1784
1871
  const schemaValue = vue.unref(schema);
@@ -1795,10 +1882,12 @@
1795
1882
  }
1796
1883
  /**
1797
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
1798
1886
  */
1799
- const debouncedSchemaValidation = debounceAsync(_validateSchema, 5);
1887
+ const debouncedSilentValidation = debounceAsync(_validateSchema, 5);
1888
+ const debouncedValidation = debounceAsync(_validateSchema, 5);
1800
1889
  async function validateSchema(mode) {
1801
- const formResult = await debouncedSchemaValidation();
1890
+ const formResult = await (mode === 'silent' ? debouncedSilentValidation() : debouncedValidation());
1802
1891
  // fields by id lookup
1803
1892
  const fieldsById = formCtx.fieldsByPath.value || {};
1804
1893
  // errors fields names, we need it to also check if custom errors are updated
@@ -1888,6 +1977,7 @@
1888
1977
  setValues,
1889
1978
  setFieldTouched,
1890
1979
  setTouched,
1980
+ useFieldModel,
1891
1981
  };
1892
1982
  }
1893
1983
  /**
@@ -2039,16 +2129,22 @@
2039
2129
  type: Function,
2040
2130
  default: undefined,
2041
2131
  },
2132
+ keepValues: {
2133
+ type: Boolean,
2134
+ default: false,
2135
+ },
2042
2136
  },
2043
2137
  setup(props, ctx) {
2044
2138
  const initialValues = vue.toRef(props, 'initialValues');
2045
2139
  const validationSchema = vue.toRef(props, 'validationSchema');
2140
+ const keepValues = vue.toRef(props, 'keepValues');
2046
2141
  const { errors, values, meta, isSubmitting, submitCount, validate, validateField, handleReset, resetForm, handleSubmit, submitForm, setErrors, setFieldError, setFieldValue, setValues, setFieldTouched, setTouched, } = useForm({
2047
2142
  validationSchema: validationSchema.value ? validationSchema : undefined,
2048
2143
  initialValues,
2049
2144
  initialErrors: props.initialErrors,
2050
2145
  initialTouched: props.initialTouched,
2051
2146
  validateOnMount: props.validateOnMount,
2147
+ keepValuesOnUnmount: keepValues,
2052
2148
  });
2053
2149
  const onSubmit = props.onSubmit ? handleSubmit(props.onSubmit, props.onInvalidSubmit) : submitForm;
2054
2150
  function handleFormReset(e) {
@@ -2118,15 +2214,13 @@
2118
2214
  });
2119
2215
  const Form = FormImpl;
2120
2216
 
2121
- let FIELD_ARRAY_COUNTER = 0;
2122
2217
  function useFieldArray(arrayPath) {
2123
- const id = FIELD_ARRAY_COUNTER++;
2124
2218
  const form = injectWithSelf(FormContextKey, undefined);
2125
2219
  const fields = vue.ref([]);
2126
2220
  // eslint-disable-next-line @typescript-eslint/no-empty-function
2127
2221
  const noOp = () => { };
2128
2222
  const noOpApi = {
2129
- fields: vue.readonly(fields),
2223
+ fields,
2130
2224
  remove: noOp,
2131
2225
  push: noOp,
2132
2226
  swap: noOp,
@@ -2134,6 +2228,7 @@
2134
2228
  update: noOp,
2135
2229
  replace: noOp,
2136
2230
  prepend: noOp,
2231
+ move: noOp,
2137
2232
  };
2138
2233
  if (!form) {
2139
2234
  warn('FieldArray requires being a child of `<Form/>` or `useForm` being called before it. Array fields may not work correctly');
@@ -2143,9 +2238,13 @@
2143
2238
  warn('FieldArray requires a field path to be provided, did you forget to pass the `name` prop?');
2144
2239
  return noOpApi;
2145
2240
  }
2241
+ const alreadyExists = form.fieldArrays.find(a => vue.unref(a.path) === vue.unref(arrayPath));
2242
+ if (alreadyExists) {
2243
+ return alreadyExists;
2244
+ }
2146
2245
  let entryCounter = 0;
2147
2246
  function initFields() {
2148
- 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), []) || [];
2149
2248
  fields.value = currentValues.map(createEntry);
2150
2249
  updateEntryFlags();
2151
2250
  }
@@ -2162,10 +2261,20 @@
2162
2261
  const key = entryCounter++;
2163
2262
  const entry = {
2164
2263
  key,
2165
- value: vue.computed(() => {
2166
- const currentValues = getFromPath(form === null || form === void 0 ? void 0 : form.values, vue.unref(arrayPath), []);
2167
- const idx = fields.value.findIndex(e => e.key === key);
2168
- 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
+ },
2169
2278
  }),
2170
2279
  isFirst: false,
2171
2280
  isLast: false,
@@ -2258,14 +2367,26 @@
2258
2367
  fields.value.unshift(createEntry(value));
2259
2368
  updateEntryFlags();
2260
2369
  }
2261
- form.fieldArraysLookup[id] = {
2262
- reset: initFields,
2263
- };
2264
- vue.onBeforeUnmount(() => {
2265
- delete form.fieldArraysLookup[id];
2266
- });
2267
- return {
2268
- 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,
2269
2390
  remove,
2270
2391
  push,
2271
2392
  swap,
@@ -2273,7 +2394,16 @@
2273
2394
  update,
2274
2395
  replace,
2275
2396
  prepend,
2397
+ move,
2276
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;
2277
2407
  }
2278
2408
 
2279
2409
  const FieldArrayImpl = vue.defineComponent({
@@ -2286,7 +2416,7 @@
2286
2416
  },
2287
2417
  },
2288
2418
  setup(props, ctx) {
2289
- 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'));
2290
2420
  function slotProps() {
2291
2421
  return {
2292
2422
  fields: fields.value,
@@ -2297,6 +2427,7 @@
2297
2427
  update,
2298
2428
  replace,
2299
2429
  prepend,
2430
+ move,
2300
2431
  };
2301
2432
  }
2302
2433
  ctx.expose({
@@ -2307,6 +2438,7 @@
2307
2438
  update,
2308
2439
  replace,
2309
2440
  prepend,
2441
+ move,
2310
2442
  });
2311
2443
  return () => {
2312
2444
  const children = normalizeChildren(undefined, ctx, slotProps);
@@ -2612,6 +2744,7 @@
2612
2744
  exports.FieldContextKey = FieldContextKey;
2613
2745
  exports.Form = Form;
2614
2746
  exports.FormContextKey = FormContextKey;
2747
+ exports.IS_ABSENT = IS_ABSENT;
2615
2748
  exports.configure = configure;
2616
2749
  exports.defineRule = defineRule;
2617
2750
  exports.useField = useField;