vee-validate 4.8.5 → 4.9.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.8.4
2
+ * vee-validate v4.9.0
3
3
  * (c) 2023 Abdelrahman Awad
4
4
  * @license MIT
5
5
  */
@@ -23,6 +23,19 @@
23
23
  const n = parseFloat(value);
24
24
  return isNaN(n) ? value : n;
25
25
  }
26
+ function merge(target, source) {
27
+ Object.keys(source).forEach(key => {
28
+ if (isObject(source[key])) {
29
+ if (!target[key]) {
30
+ target[key] = {};
31
+ }
32
+ merge(target[key], source[key]);
33
+ return;
34
+ }
35
+ target[key] = source[key];
36
+ });
37
+ return target;
38
+ }
26
39
 
27
40
  const RULES = {};
28
41
  /**
@@ -393,15 +406,6 @@
393
406
  function warn(message) {
394
407
  vue.warn(`[vee-validate]: ${message}`);
395
408
  }
396
- /**
397
- * Ensures we deal with a singular field value
398
- */
399
- function normalizeField(field) {
400
- if (Array.isArray(field)) {
401
- return field[0];
402
- }
403
- return field;
404
- }
405
409
  function resolveNextCheckboxValue(currentValue, checkedValue, uncheckedValue) {
406
410
  if (Array.isArray(currentValue)) {
407
411
  const newVal = [...currentValue];
@@ -482,6 +486,27 @@
482
486
  function lazyToRef(value) {
483
487
  return vue.computed(() => unravel(value));
484
488
  }
489
+ function normalizeErrorItem(message) {
490
+ return Array.isArray(message) ? message : message ? [message] : [];
491
+ }
492
+ function resolveFieldOrPathState(path) {
493
+ const form = injectWithSelf(FormContextKey);
494
+ const state = path ? vue.computed(() => form === null || form === void 0 ? void 0 : form.getPathState(vue.unref(path))) : undefined;
495
+ const field = path ? undefined : vue.inject(FieldContextKey);
496
+ if (!field && !(state === null || state === void 0 ? void 0 : state.value)) {
497
+ warn(`field with name ${vue.unref(path)} was not found`);
498
+ }
499
+ return state || field;
500
+ }
501
+ function omit(obj, keys) {
502
+ const target = {};
503
+ for (const key in obj) {
504
+ if (!keys.includes(key)) {
505
+ target[key] = obj[key];
506
+ }
507
+ }
508
+ return target;
509
+ }
485
510
 
486
511
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
487
512
  const normalizeChildren = (tag, context, slotProps) => {
@@ -906,32 +931,64 @@
906
931
  let ID_COUNTER = 0;
907
932
  function useFieldState(path, init) {
908
933
  const { value, initialValue, setInitialValue } = _useFieldValue(path, init.modelValue, init.form);
909
- const { errorMessage, errors, setErrors } = _useFieldErrors(path, init.form);
910
- const meta = _useFieldMeta(value, initialValue, errors);
911
- const id = ID_COUNTER >= Number.MAX_SAFE_INTEGER ? 0 : ++ID_COUNTER;
934
+ if (!init.form) {
935
+ const { errors, setErrors } = createFieldErrors();
936
+ const id = ID_COUNTER >= Number.MAX_SAFE_INTEGER ? 0 : ++ID_COUNTER;
937
+ const meta = createFieldMeta(value, initialValue, errors);
938
+ function setState(state) {
939
+ var _a;
940
+ if ('value' in state) {
941
+ value.value = state.value;
942
+ }
943
+ if ('errors' in state) {
944
+ setErrors(state.errors);
945
+ }
946
+ if ('touched' in state) {
947
+ meta.touched = (_a = state.touched) !== null && _a !== void 0 ? _a : meta.touched;
948
+ }
949
+ if ('initialValue' in state) {
950
+ setInitialValue(state.initialValue);
951
+ }
952
+ }
953
+ return {
954
+ id,
955
+ path,
956
+ value,
957
+ initialValue,
958
+ meta,
959
+ errors,
960
+ setState,
961
+ };
962
+ }
963
+ const state = init.form.createPathState(path, {
964
+ bails: init.bails,
965
+ label: init.label,
966
+ type: init.type,
967
+ validate: init.validate,
968
+ });
969
+ const errors = vue.computed(() => state.errors);
912
970
  function setState(state) {
913
- var _a;
971
+ var _a, _b, _c;
914
972
  if ('value' in state) {
915
973
  value.value = state.value;
916
974
  }
917
975
  if ('errors' in state) {
918
- setErrors(state.errors);
976
+ (_a = init.form) === null || _a === void 0 ? void 0 : _a.setFieldError(vue.unref(path), state.errors);
919
977
  }
920
978
  if ('touched' in state) {
921
- meta.touched = (_a = state.touched) !== null && _a !== void 0 ? _a : meta.touched;
979
+ (_b = init.form) === null || _b === void 0 ? void 0 : _b.setFieldTouched(vue.unref(path), (_c = state.touched) !== null && _c !== void 0 ? _c : false);
922
980
  }
923
981
  if ('initialValue' in state) {
924
982
  setInitialValue(state.initialValue);
925
983
  }
926
984
  }
927
985
  return {
928
- id,
986
+ id: Array.isArray(state.id) ? state.id[state.id.length - 1] : state.id,
929
987
  path,
930
988
  value,
931
- initialValue,
932
- meta,
933
989
  errors,
934
- errorMessage,
990
+ meta: state,
991
+ initialValue,
935
992
  setState,
936
993
  };
937
994
  }
@@ -944,7 +1001,7 @@
944
1001
  if (!form) {
945
1002
  return vue.unref(modelRef);
946
1003
  }
947
- return getFromPath(form.meta.value.initialValues, vue.unref(path), vue.unref(modelRef));
1004
+ return getFromPath(form.initialValues.value, vue.unref(path), vue.unref(modelRef));
948
1005
  }
949
1006
  function setInitialValue(value) {
950
1007
  if (!form) {
@@ -1002,7 +1059,7 @@
1002
1059
  /**
1003
1060
  * Creates meta flags state and some associated effects with them
1004
1061
  */
1005
- function _useFieldMeta(currentValue, initialValue, errors) {
1062
+ function createFieldMeta(currentValue, initialValue, errors) {
1006
1063
  const meta = vue.reactive({
1007
1064
  touched: false,
1008
1065
  pending: false,
@@ -1024,29 +1081,12 @@
1024
1081
  /**
1025
1082
  * Creates the error message state for the field state
1026
1083
  */
1027
- function _useFieldErrors(path, form) {
1028
- function normalizeErrors(messages) {
1029
- if (!messages) {
1030
- return [];
1031
- }
1032
- return Array.isArray(messages) ? messages : [messages];
1033
- }
1034
- if (!form) {
1035
- const errors = vue.ref([]);
1036
- return {
1037
- errors,
1038
- errorMessage: vue.computed(() => errors.value[0]),
1039
- setErrors: (messages) => {
1040
- errors.value = normalizeErrors(messages);
1041
- },
1042
- };
1043
- }
1044
- const errors = vue.computed(() => form.errorBag.value[vue.unref(path)] || []);
1084
+ function createFieldErrors() {
1085
+ const errors = vue.ref([]);
1045
1086
  return {
1046
1087
  errors,
1047
- errorMessage: vue.computed(() => errors.value[0]),
1048
1088
  setErrors: (messages) => {
1049
- form.setFieldErrorBag(vue.unref(path), normalizeErrors(messages));
1089
+ errors.value = normalizeErrorItem(messages);
1050
1090
  },
1051
1091
  };
1052
1092
  }
@@ -1056,7 +1096,7 @@
1056
1096
  */
1057
1097
  function useField(path, rules, opts) {
1058
1098
  if (hasCheckedAttr(opts === null || opts === void 0 ? void 0 : opts.type)) {
1059
- return useCheckboxField(path, rules, opts);
1099
+ return useFieldWithChecked(path, rules, opts);
1060
1100
  }
1061
1101
  return _useField(path, rules, opts);
1062
1102
  }
@@ -1065,12 +1105,30 @@
1065
1105
  const injectedForm = controlled ? injectWithSelf(FormContextKey) : undefined;
1066
1106
  const form = controlForm || injectedForm;
1067
1107
  const name = lazyToRef(path);
1068
- // a flag indicating if the field is about to be removed/unmounted.
1069
- let markedForRemoval = false;
1070
- const { id, value, initialValue, meta, setState, errors, errorMessage } = useFieldState(name, {
1108
+ let PENDING_UNMOUNT = false;
1109
+ const validator = vue.computed(() => {
1110
+ const schema = vue.unref(form === null || form === void 0 ? void 0 : form.schema);
1111
+ if (schema) {
1112
+ return undefined;
1113
+ }
1114
+ const rulesValue = vue.unref(rules);
1115
+ if (isYupValidator(rulesValue) ||
1116
+ isTypedSchema(rulesValue) ||
1117
+ isCallable(rulesValue) ||
1118
+ Array.isArray(rulesValue)) {
1119
+ return rulesValue;
1120
+ }
1121
+ return normalizeRules(rulesValue);
1122
+ });
1123
+ const { id, value, initialValue, meta, setState, errors } = useFieldState(name, {
1071
1124
  modelValue,
1072
1125
  form,
1126
+ bails,
1127
+ label,
1128
+ type,
1129
+ validate: validator.value ? validate$1 : undefined,
1073
1130
  });
1131
+ const errorMessage = vue.computed(() => errors.value[0]);
1074
1132
  if (syncVModel) {
1075
1133
  useVModel({ value, prop: modelPropName, handleChange });
1076
1134
  }
@@ -1080,51 +1138,37 @@
1080
1138
  const handleBlur = () => {
1081
1139
  meta.touched = true;
1082
1140
  };
1083
- const normalizedRules = vue.computed(() => {
1084
- let rulesValue = vue.unref(rules);
1085
- const schema = vue.unref(form === null || form === void 0 ? void 0 : form.schema);
1086
- if (schema && !isYupValidator(schema) && !isTypedSchema(schema)) {
1087
- rulesValue = extractRuleFromSchema(schema, vue.unref(name)) || rulesValue;
1088
- }
1089
- if (isYupValidator(rulesValue) ||
1090
- isTypedSchema(rulesValue) ||
1091
- isCallable(rulesValue) ||
1092
- Array.isArray(rulesValue)) {
1093
- return rulesValue;
1094
- }
1095
- return normalizeRules(rulesValue);
1096
- });
1097
1141
  async function validateCurrentValue(mode) {
1098
1142
  var _a, _b;
1099
1143
  if (form === null || form === void 0 ? void 0 : form.validateSchema) {
1100
1144
  return (_a = (await form.validateSchema(mode)).results[vue.unref(name)]) !== null && _a !== void 0 ? _a : { valid: true, errors: [] };
1101
1145
  }
1102
- return validate(value.value, normalizedRules.value, {
1103
- name: vue.unref(name),
1104
- label: vue.unref(label),
1105
- values: (_b = form === null || form === void 0 ? void 0 : form.values) !== null && _b !== void 0 ? _b : {},
1106
- bails,
1107
- });
1146
+ if (validator.value) {
1147
+ return validate(value.value, validator.value, {
1148
+ name: vue.unref(name),
1149
+ label: vue.unref(label),
1150
+ values: (_b = form === null || form === void 0 ? void 0 : form.values) !== null && _b !== void 0 ? _b : {},
1151
+ bails,
1152
+ });
1153
+ }
1154
+ return { valid: true, errors: [] };
1108
1155
  }
1109
1156
  const validateWithStateMutation = withLatest(async () => {
1110
1157
  meta.pending = true;
1111
1158
  meta.validated = true;
1112
1159
  return validateCurrentValue('validated-only');
1113
1160
  }, result => {
1114
- if (markedForRemoval) {
1115
- result.valid = true;
1116
- result.errors = [];
1161
+ if (PENDING_UNMOUNT) {
1162
+ return;
1117
1163
  }
1118
1164
  setState({ errors: result.errors });
1119
1165
  meta.pending = false;
1166
+ meta.valid = result.valid;
1120
1167
  return result;
1121
1168
  });
1122
1169
  const validateValidStateOnly = withLatest(async () => {
1123
1170
  return validateCurrentValue('silent');
1124
1171
  }, result => {
1125
- if (markedForRemoval) {
1126
- result.valid = true;
1127
- }
1128
1172
  meta.valid = result.valid;
1129
1173
  return result;
1130
1174
  });
@@ -1137,7 +1181,7 @@
1137
1181
  // Common input/change event handler
1138
1182
  function handleChange(e, shouldValidate = true) {
1139
1183
  const newValue = normalizeEventValue(e);
1140
- value.value = newValue;
1184
+ setValue(newValue, false);
1141
1185
  if (!validateOnValueUpdate && shouldValidate) {
1142
1186
  validateWithStateMutation();
1143
1187
  }
@@ -1156,24 +1200,8 @@
1156
1200
  function setTouched(isTouched) {
1157
1201
  meta.touched = isTouched;
1158
1202
  }
1159
- let unwatchValue;
1160
- let lastWatchedValue = klona(value.value);
1161
- function watchValue() {
1162
- unwatchValue = vue.watch(value, (val, oldVal) => {
1163
- if (isEqual(val, oldVal) && isEqual(val, lastWatchedValue)) {
1164
- return;
1165
- }
1166
- const validateFn = validateOnValueUpdate ? validateWithStateMutation : validateValidStateOnly;
1167
- validateFn();
1168
- lastWatchedValue = klona(val);
1169
- }, {
1170
- deep: true,
1171
- });
1172
- }
1173
- watchValue();
1174
1203
  function resetField(state) {
1175
1204
  var _a;
1176
- unwatchValue === null || unwatchValue === void 0 ? void 0 : unwatchValue();
1177
1205
  const newValue = state && 'value' in state ? state.value : initialValue.value;
1178
1206
  setState({
1179
1207
  value: klona(newValue),
@@ -1184,22 +1212,31 @@
1184
1212
  meta.pending = false;
1185
1213
  meta.validated = false;
1186
1214
  validateValidStateOnly();
1187
- // need to watch at next tick to avoid triggering the value watcher
1188
- vue.nextTick(() => {
1189
- watchValue();
1190
- });
1191
1215
  }
1192
- function setValue(newValue) {
1216
+ function setValue(newValue, validate = true) {
1193
1217
  value.value = newValue;
1218
+ if (!validate) {
1219
+ return;
1220
+ }
1221
+ const validateFn = validateOnValueUpdate ? validateWithStateMutation : validateValidStateOnly;
1222
+ validateFn();
1194
1223
  }
1195
1224
  function setErrors(errors) {
1196
1225
  setState({ errors: Array.isArray(errors) ? errors : [errors] });
1197
1226
  }
1227
+ const valueProxy = vue.computed({
1228
+ get() {
1229
+ return value.value;
1230
+ },
1231
+ set(newValue) {
1232
+ setValue(newValue, validateOnValueUpdate);
1233
+ },
1234
+ });
1198
1235
  const field = {
1199
1236
  id,
1200
1237
  name,
1201
1238
  label,
1202
- value,
1239
+ value: valueProxy,
1203
1240
  meta,
1204
1241
  errors,
1205
1242
  errorMessage,
@@ -1234,14 +1271,9 @@
1234
1271
  return field;
1235
1272
  }
1236
1273
  // associate the field with the given form
1237
- form.register(field);
1238
- vue.onBeforeUnmount(() => {
1239
- markedForRemoval = true;
1240
- form.unregister(field);
1241
- });
1242
1274
  // extract cross-field dependencies in a computed prop
1243
1275
  const dependencies = vue.computed(() => {
1244
- const rulesVal = normalizedRules.value;
1276
+ const rulesVal = validator.value;
1245
1277
  // is falsy, a function schema or a yup schema
1246
1278
  if (!rulesVal ||
1247
1279
  isCallable(rulesVal) ||
@@ -1275,6 +1307,37 @@
1275
1307
  meta.validated ? validateWithStateMutation() : validateValidStateOnly();
1276
1308
  }
1277
1309
  });
1310
+ vue.onBeforeUnmount(() => {
1311
+ var _a;
1312
+ PENDING_UNMOUNT = true;
1313
+ const shouldKeepValue = (_a = vue.unref(field.keepValueOnUnmount)) !== null && _a !== void 0 ? _a : vue.unref(form.keepValuesOnUnmount);
1314
+ if (shouldKeepValue || !form) {
1315
+ return;
1316
+ }
1317
+ const path = unravel(name);
1318
+ const pathState = form.getPathState(path);
1319
+ const matchesId = Array.isArray(pathState === null || pathState === void 0 ? void 0 : pathState.id) && (pathState === null || pathState === void 0 ? void 0 : pathState.multiple)
1320
+ ? pathState === null || pathState === void 0 ? void 0 : pathState.id.includes(field.id)
1321
+ : (pathState === null || pathState === void 0 ? void 0 : pathState.id) === field.id;
1322
+ if (!matchesId) {
1323
+ return;
1324
+ }
1325
+ if ((pathState === null || pathState === void 0 ? void 0 : pathState.multiple) && Array.isArray(pathState.value)) {
1326
+ const valueIdx = pathState.value.findIndex(i => isEqual(i, vue.unref(field.checkedValue)));
1327
+ if (valueIdx > -1) {
1328
+ const newVal = [...pathState.value];
1329
+ newVal.splice(valueIdx, 1);
1330
+ form.setFieldValue(path, newVal);
1331
+ }
1332
+ if (Array.isArray(pathState.id)) {
1333
+ pathState.id.splice(pathState.id.indexOf(field.id), 1);
1334
+ }
1335
+ }
1336
+ else {
1337
+ form.unsetPathValue(path);
1338
+ }
1339
+ form.removePathState(path);
1340
+ });
1278
1341
  return field;
1279
1342
  }
1280
1343
  /**
@@ -1305,22 +1368,11 @@
1305
1368
  const controlled = 'standalone' in opts ? !opts.standalone : opts.controlled;
1306
1369
  return Object.assign(Object.assign(Object.assign({}, defaults()), (opts || {})), { initialValue, controlled: controlled !== null && controlled !== void 0 ? controlled : true, checkedValue });
1307
1370
  }
1308
- /**
1309
- * Extracts the validation rules from a schema
1310
- */
1311
- function extractRuleFromSchema(schema, fieldName) {
1312
- // no schema at all
1313
- if (!schema) {
1314
- return undefined;
1315
- }
1316
- // there is a key on the schema object for this field
1317
- return schema[fieldName];
1318
- }
1319
- function useCheckboxField(name, rules, opts) {
1371
+ function useFieldWithChecked(name, rules, opts) {
1320
1372
  const form = !(opts === null || opts === void 0 ? void 0 : opts.standalone) ? injectWithSelf(FormContextKey) : undefined;
1321
1373
  const checkedValue = opts === null || opts === void 0 ? void 0 : opts.checkedValue;
1322
1374
  const uncheckedValue = opts === null || opts === void 0 ? void 0 : opts.uncheckedValue;
1323
- function patchCheckboxApi(field) {
1375
+ function patchCheckedApi(field) {
1324
1376
  const handleChange = field.handleChange;
1325
1377
  const checked = vue.computed(() => {
1326
1378
  const currentValue = vue.unref(field.value);
@@ -1337,9 +1389,15 @@
1337
1389
  }
1338
1390
  return;
1339
1391
  }
1340
- let newValue = normalizeEventValue(e);
1341
- // Single checkbox field without a form to toggle it's value
1342
- if (!form) {
1392
+ const path = unravel(name);
1393
+ const pathState = form === null || form === void 0 ? void 0 : form.getPathState(path);
1394
+ const value = normalizeEventValue(e);
1395
+ let newValue;
1396
+ if (form && (pathState === null || pathState === void 0 ? void 0 : pathState.multiple) && pathState.type === 'checkbox') {
1397
+ newValue = resolveNextCheckboxValue(getFromPath(form.values, path) || [], value, undefined);
1398
+ }
1399
+ else {
1400
+ // Single checkbox field without a form to toggle it's value
1343
1401
  newValue = resolveNextCheckboxValue(vue.unref(field.value), vue.unref(checkedValue), vue.unref(uncheckedValue));
1344
1402
  }
1345
1403
  handleChange(newValue, shouldValidate);
@@ -1348,7 +1406,7 @@
1348
1406
  checkedValue,
1349
1407
  uncheckedValue, handleChange: handleCheckboxChange });
1350
1408
  }
1351
- return patchCheckboxApi(_useField(name, rules, opts));
1409
+ return patchCheckedApi(_useField(name, rules, opts));
1352
1410
  }
1353
1411
  function useVModel({ prop, value, handleChange }) {
1354
1412
  const vm = vue.getCurrentInstance();
@@ -1571,6 +1629,7 @@
1571
1629
  const Field = FieldImpl;
1572
1630
 
1573
1631
  let FORM_COUNTER = 0;
1632
+ const PRIVATE_PATH_STATE_KEYS = ['bails', 'fieldsCount', 'id', 'multiple', 'type', 'validate'];
1574
1633
  function resolveInitialValues(opts) {
1575
1634
  const providedValues = vue.unref(opts === null || opts === void 0 ? void 0 : opts.initialValues) || {};
1576
1635
  const schema = vue.unref(opts === null || opts === void 0 ? void 0 : opts.validationSchema);
@@ -1582,12 +1641,8 @@
1582
1641
  function useForm(opts) {
1583
1642
  var _a;
1584
1643
  const formId = FORM_COUNTER++;
1585
- const controlledModelPaths = new Set();
1586
1644
  // Prevents fields from double resetting their values, which causes checkboxes to toggle their initial value
1587
- // TODO: This won't be needed if we centralize all the state inside the `form` for form inputs
1588
- let RESET_LOCK = false;
1589
- // A lookup containing fields or field groups
1590
- const fieldsByPath = vue.ref({});
1645
+ let FIELD_ID_COUNTER = 0;
1591
1646
  // If the form is currently submitting
1592
1647
  const isSubmitting = vue.ref(false);
1593
1648
  // The number of times the user tried to submit the form
@@ -1596,44 +1651,62 @@
1596
1651
  const fieldArrays = [];
1597
1652
  // a private ref for all form values
1598
1653
  const formValues = vue.reactive(resolveInitialValues(opts));
1599
- // the source of errors for the form fields
1600
- const { errorBag, setErrorBag, setFieldErrorBag } = useErrorBag(opts === null || opts === void 0 ? void 0 : opts.initialErrors);
1654
+ const pathStates = vue.ref([]);
1655
+ const extraErrorsBag = vue.ref({});
1656
+ /**
1657
+ * Manually sets an error message on a specific field
1658
+ */
1659
+ function setFieldError(field, message) {
1660
+ const state = findPathState(field);
1661
+ if (!state) {
1662
+ extraErrorsBag.value[field] = normalizeErrorItem(message);
1663
+ return;
1664
+ }
1665
+ state.errors = normalizeErrorItem(message);
1666
+ }
1667
+ /**
1668
+ * Sets errors for the fields specified in the object
1669
+ */
1670
+ function setErrors(paths) {
1671
+ keysOf(paths).forEach(path => {
1672
+ setFieldError(path, paths[path]);
1673
+ });
1674
+ }
1675
+ if (opts === null || opts === void 0 ? void 0 : opts.initialErrors) {
1676
+ setErrors(opts.initialErrors);
1677
+ }
1678
+ const errorBag = vue.computed(() => {
1679
+ const pathErrors = pathStates.value.reduce((acc, state) => {
1680
+ if (state.errors.length) {
1681
+ acc[state.path] = state.errors;
1682
+ }
1683
+ return acc;
1684
+ }, {});
1685
+ return Object.assign(Object.assign({}, extraErrorsBag.value), pathErrors);
1686
+ });
1601
1687
  // Gets the first error of each field
1602
1688
  const errors = vue.computed(() => {
1603
1689
  return keysOf(errorBag.value).reduce((acc, key) => {
1604
- const bag = errorBag.value[key];
1605
- if (bag && bag.length) {
1606
- acc[key] = bag[0];
1690
+ const errors = errorBag.value[key];
1691
+ if (errors === null || errors === void 0 ? void 0 : errors.length) {
1692
+ acc[key] = errors[0];
1607
1693
  }
1608
1694
  return acc;
1609
1695
  }, {});
1610
1696
  });
1611
- function getFirstFieldAtPath(path) {
1612
- const fieldOrGroup = fieldsByPath.value[path];
1613
- return Array.isArray(fieldOrGroup) ? fieldOrGroup[0] : fieldOrGroup;
1614
- }
1615
- function fieldExists(path) {
1616
- return !!fieldsByPath.value[path];
1617
- }
1618
1697
  /**
1619
1698
  * Holds a computed reference to all fields names and labels
1620
1699
  */
1621
1700
  const fieldNames = vue.computed(() => {
1622
- return keysOf(fieldsByPath.value).reduce((names, path) => {
1623
- const field = getFirstFieldAtPath(path);
1624
- if (field) {
1625
- names[path] = { name: vue.unref(field.name) || '', label: vue.unref(field.label) || '' };
1626
- }
1701
+ return pathStates.value.reduce((names, state) => {
1702
+ names[state.path] = { name: state.path || '', label: state.label || '' };
1627
1703
  return names;
1628
1704
  }, {});
1629
1705
  });
1630
1706
  const fieldBailsMap = vue.computed(() => {
1631
- return keysOf(fieldsByPath.value).reduce((map, path) => {
1707
+ return pathStates.value.reduce((map, state) => {
1632
1708
  var _a;
1633
- const field = getFirstFieldAtPath(path);
1634
- if (field) {
1635
- map[path] = (_a = field.bails) !== null && _a !== void 0 ? _a : true;
1636
- }
1709
+ map[state.path] = (_a = state.bails) !== null && _a !== void 0 ? _a : true;
1637
1710
  return map;
1638
1711
  }, {});
1639
1712
  });
@@ -1642,17 +1715,74 @@
1642
1715
  const initialErrors = Object.assign({}, ((opts === null || opts === void 0 ? void 0 : opts.initialErrors) || {}));
1643
1716
  const keepValuesOnUnmount = (_a = opts === null || opts === void 0 ? void 0 : opts.keepValuesOnUnmount) !== null && _a !== void 0 ? _a : false;
1644
1717
  // initial form values
1645
- const { initialValues, originalInitialValues, setInitialValues } = useFormInitialValues(fieldsByPath, formValues, opts);
1718
+ const { initialValues, originalInitialValues, setInitialValues } = useFormInitialValues(pathStates, formValues, opts);
1646
1719
  // form meta aggregations
1647
- const meta = useFormMeta(fieldsByPath, formValues, originalInitialValues, errors);
1720
+ const meta = useFormMeta(pathStates, formValues, originalInitialValues, errors);
1648
1721
  const controlledValues = vue.computed(() => {
1649
- return [...controlledModelPaths, ...keysOf(fieldsByPath.value)].reduce((acc, path) => {
1650
- const value = getFromPath(formValues, path);
1651
- setInPath(acc, path, value);
1722
+ return pathStates.value.reduce((acc, state) => {
1723
+ const value = getFromPath(formValues, state.path);
1724
+ setInPath(acc, state.path, value);
1652
1725
  return acc;
1653
1726
  }, {});
1654
1727
  });
1655
1728
  const schema = opts === null || opts === void 0 ? void 0 : opts.validationSchema;
1729
+ function createPathState(path, config) {
1730
+ var _a, _b;
1731
+ const initialValue = vue.computed(() => getFromPath(initialValues.value, unravel(path)));
1732
+ const pathStateExists = pathStates.value.find(state => state.path === vue.unref(path));
1733
+ if (pathStateExists) {
1734
+ if ((config === null || config === void 0 ? void 0 : config.type) === 'checkbox' || (config === null || config === void 0 ? void 0 : config.type) === 'radio') {
1735
+ pathStateExists.multiple = true;
1736
+ }
1737
+ if (Array.isArray(pathStateExists.id)) {
1738
+ pathStateExists.id.push(FIELD_ID_COUNTER++);
1739
+ }
1740
+ else {
1741
+ pathStateExists.id = [pathStateExists.id, FIELD_ID_COUNTER++];
1742
+ }
1743
+ pathStateExists.fieldsCount++;
1744
+ return pathStateExists;
1745
+ }
1746
+ const currentValue = vue.computed(() => getFromPath(formValues, unravel(path)));
1747
+ const pathValue = unravel(path);
1748
+ const state = vue.reactive({
1749
+ id: FIELD_ID_COUNTER++,
1750
+ path,
1751
+ touched: false,
1752
+ pending: false,
1753
+ valid: true,
1754
+ validated: !!((_a = initialErrors[pathValue]) === null || _a === void 0 ? void 0 : _a.length),
1755
+ initialValue,
1756
+ errors: vue.shallowRef([]),
1757
+ bails: (_b = config === null || config === void 0 ? void 0 : config.bails) !== null && _b !== void 0 ? _b : false,
1758
+ label: config === null || config === void 0 ? void 0 : config.label,
1759
+ type: (config === null || config === void 0 ? void 0 : config.type) || 'default',
1760
+ value: currentValue,
1761
+ multiple: false,
1762
+ fieldsCount: 1,
1763
+ validate: config === null || config === void 0 ? void 0 : config.validate,
1764
+ dirty: vue.computed(() => {
1765
+ return !isEqual(vue.unref(currentValue), vue.unref(initialValue));
1766
+ }),
1767
+ });
1768
+ pathStates.value.push(state);
1769
+ // if it has errors before, validate it.
1770
+ if (errors.value[pathValue] && !initialErrors[pathValue]) {
1771
+ vue.nextTick(() => {
1772
+ validateField(pathValue);
1773
+ });
1774
+ }
1775
+ // Handles when a path changes
1776
+ if (vue.isRef(path)) {
1777
+ vue.watch(path, newPath => {
1778
+ const nextValue = klona(currentValue.value);
1779
+ vue.nextTick(() => {
1780
+ setInPath(formValues, newPath, nextValue);
1781
+ });
1782
+ });
1783
+ }
1784
+ return state;
1785
+ }
1656
1786
  /**
1657
1787
  * Batches validation runs in 5ms batches
1658
1788
  * Must have two distinct batch queues to make sure they don't override each other settings #3783
@@ -1663,17 +1793,17 @@
1663
1793
  return (await mode) === 'silent' ? debouncedSilentValidation() : debouncedValidation();
1664
1794
  }, (formResult, [mode]) => {
1665
1795
  // fields by id lookup
1666
- const fieldsById = formCtx.fieldsByPath.value || {};
1667
1796
  // errors fields names, we need it to also check if custom errors are updated
1668
1797
  const currentErrorsPaths = keysOf(formCtx.errorBag.value);
1669
1798
  // collect all the keys from the schema and all fields
1670
- // this ensures we have a complete keymap of all the fields
1799
+ // this ensures we have a complete key map of all the fields
1671
1800
  const paths = [
1672
- ...new Set([...keysOf(formResult.results), ...keysOf(fieldsById), ...currentErrorsPaths]),
1801
+ ...new Set([...keysOf(formResult.results), ...pathStates.value.map(p => p.path), ...currentErrorsPaths]),
1673
1802
  ];
1674
1803
  // aggregates the paths into a single result object while applying the results on the fields
1675
- return paths.reduce((validation, path) => {
1676
- const field = fieldsById[path];
1804
+ return paths.reduce((validation, _path) => {
1805
+ const path = _path;
1806
+ const pathState = findPathState(path);
1677
1807
  const messages = (formResult.results[path] || { errors: [] }).errors;
1678
1808
  const fieldResult = {
1679
1809
  errors: messages,
@@ -1683,24 +1813,37 @@
1683
1813
  if (!fieldResult.valid) {
1684
1814
  validation.errors[path] = fieldResult.errors[0];
1685
1815
  }
1816
+ // clean up extra errors if path state exists
1817
+ if (pathState && extraErrorsBag.value[path]) {
1818
+ delete extraErrorsBag.value[path];
1819
+ }
1686
1820
  // field not rendered
1687
- if (!field) {
1821
+ if (!pathState) {
1688
1822
  setFieldError(path, messages);
1689
1823
  return validation;
1690
1824
  }
1691
1825
  // always update the valid flag regardless of the mode
1692
- applyFieldMutation(field, f => (f.meta.valid = fieldResult.valid));
1826
+ pathState.valid = fieldResult.valid;
1693
1827
  if (mode === 'silent') {
1694
1828
  return validation;
1695
1829
  }
1696
- const wasValidated = Array.isArray(field) ? field.some(f => f.meta.validated) : field.meta.validated;
1697
- if (mode === 'validated-only' && !wasValidated) {
1830
+ if (mode === 'validated-only' && !pathState.validated) {
1698
1831
  return validation;
1699
1832
  }
1700
- applyFieldMutation(field, f => f.setState({ errors: fieldResult.errors }));
1833
+ setFieldError(pathState, fieldResult.errors);
1701
1834
  return validation;
1702
1835
  }, { valid: formResult.valid, results: {}, errors: {} });
1703
1836
  });
1837
+ function mutateAllPathState(mutation) {
1838
+ pathStates.value.forEach(mutation);
1839
+ }
1840
+ function findPathState(path) {
1841
+ const pathState = typeof path === 'string' ? pathStates.value.find(state => state.path === path) : path;
1842
+ return pathState;
1843
+ }
1844
+ function unsetPathValue(path) {
1845
+ unsetPath(formValues, path);
1846
+ }
1704
1847
  function makeSubmissionFactory(onlyControlled) {
1705
1848
  return function submitHandlerFactory(fn, onValidationError) {
1706
1849
  return function submissionHandler(e) {
@@ -1709,10 +1852,7 @@
1709
1852
  e.stopPropagation();
1710
1853
  }
1711
1854
  // Touch all fields
1712
- setTouched(keysOf(fieldsByPath.value).reduce((acc, field) => {
1713
- acc[field] = true;
1714
- return acc;
1715
- }, {}));
1855
+ mutateAllPathState(s => (s.touched = true));
1716
1856
  isSubmitting.value = true;
1717
1857
  submitCount.value++;
1718
1858
  return validate()
@@ -1720,7 +1860,7 @@
1720
1860
  const values = klona(formValues);
1721
1861
  if (result.valid && typeof fn === 'function') {
1722
1862
  const controlled = klona(controlledValues.value);
1723
- let submittedValues = onlyControlled ? controlled : values;
1863
+ let submittedValues = (onlyControlled ? controlled : values);
1724
1864
  if (result.values) {
1725
1865
  submittedValues = result.values;
1726
1866
  }
@@ -1760,9 +1900,22 @@
1760
1900
  const handleSubmitImpl = makeSubmissionFactory(false);
1761
1901
  const handleSubmit = handleSubmitImpl;
1762
1902
  handleSubmit.withControlled = makeSubmissionFactory(true);
1903
+ function removePathState(path) {
1904
+ const idx = pathStates.value.findIndex(s => s.path === path);
1905
+ const pathState = pathStates.value[idx];
1906
+ if (idx === -1 || !pathState) {
1907
+ return;
1908
+ }
1909
+ if (pathState.multiple && pathState.fieldsCount) {
1910
+ pathState.fieldsCount--;
1911
+ }
1912
+ if (!pathState.multiple || pathState.fieldsCount <= 0) {
1913
+ pathStates.value.splice(idx, 1);
1914
+ unsetInitialValue(path);
1915
+ }
1916
+ }
1763
1917
  const formCtx = {
1764
1918
  formId,
1765
- fieldsByPath,
1766
1919
  values: formValues,
1767
1920
  controlledValues,
1768
1921
  errorBag,
@@ -1775,14 +1928,11 @@
1775
1928
  keepValuesOnUnmount,
1776
1929
  validateSchema: vue.unref(schema) ? validateSchema : undefined,
1777
1930
  validate,
1778
- register: registerField,
1779
- unregister: unregisterField,
1780
- setFieldErrorBag,
1931
+ setFieldError,
1781
1932
  validateField,
1782
1933
  setFieldValue,
1783
1934
  setValues,
1784
1935
  setErrors,
1785
- setFieldError,
1786
1936
  setFieldTouched,
1787
1937
  setTouched,
1788
1938
  resetForm,
@@ -1792,102 +1942,63 @@
1792
1942
  unsetInitialValue,
1793
1943
  setFieldInitialValue,
1794
1944
  useFieldModel,
1945
+ createPathState,
1946
+ getPathState: findPathState,
1947
+ unsetPathValue,
1948
+ removePathState,
1949
+ initialValues: initialValues,
1950
+ getAllPathStates: () => pathStates.value,
1795
1951
  };
1796
- function isFieldGroup(fieldOrGroup) {
1797
- return Array.isArray(fieldOrGroup);
1798
- }
1799
- function applyFieldMutation(fieldOrGroup, mutation) {
1800
- if (Array.isArray(fieldOrGroup)) {
1801
- return fieldOrGroup.forEach(mutation);
1802
- }
1803
- return mutation(fieldOrGroup);
1804
- }
1805
- function mutateAllFields(mutation) {
1806
- Object.values(fieldsByPath.value).forEach(field => {
1807
- if (!field) {
1808
- return;
1809
- }
1810
- // avoid resetting the field values, because they should've been reset already.
1811
- applyFieldMutation(field, mutation);
1812
- });
1813
- }
1814
- /**
1815
- * Manually sets an error message on a specific field
1816
- */
1817
- function setFieldError(field, message) {
1818
- setFieldErrorBag(field, message);
1819
- }
1820
- /**
1821
- * Sets errors for the fields specified in the object
1822
- */
1823
- function setErrors(fields) {
1824
- setErrorBag(fields);
1825
- }
1826
1952
  /**
1827
1953
  * Sets a single field value
1828
1954
  */
1829
- function setFieldValue(field, value, { force } = { force: false }) {
1830
- var _a;
1831
- const fieldInstance = fieldsByPath.value[field];
1955
+ function setFieldValue(field, value) {
1832
1956
  const clonedValue = klona(value);
1833
- // field wasn't found, create a virtual field as a placeholder
1834
- if (!fieldInstance) {
1835
- setInPath(formValues, field, clonedValue);
1836
- return;
1957
+ const path = typeof field === 'string' ? field : field.path;
1958
+ const pathState = findPathState(path);
1959
+ if (!pathState) {
1960
+ createPathState(path);
1837
1961
  }
1838
- if (isFieldGroup(fieldInstance) && ((_a = fieldInstance[0]) === null || _a === void 0 ? void 0 : _a.type) === 'checkbox' && !Array.isArray(value)) {
1839
- // Multiple checkboxes, and only one of them got updated
1840
- const newValue = klona(resolveNextCheckboxValue(getFromPath(formValues, field) || [], value, undefined));
1841
- setInPath(formValues, field, newValue);
1842
- return;
1843
- }
1844
- let newValue = clonedValue;
1845
- // Single Checkbox: toggles the field value unless the field is being reset then force it
1846
- if (!isFieldGroup(fieldInstance) && fieldInstance.type === 'checkbox' && !force && !RESET_LOCK) {
1847
- newValue = klona(resolveNextCheckboxValue(getFromPath(formValues, field), value, vue.unref(fieldInstance.uncheckedValue)));
1848
- }
1849
- setInPath(formValues, field, newValue);
1962
+ setInPath(formValues, path, clonedValue);
1850
1963
  }
1851
1964
  /**
1852
1965
  * Sets multiple fields values
1853
1966
  */
1854
1967
  function setValues(fields) {
1855
- // clean up old values
1856
- keysOf(formValues).forEach(key => {
1857
- delete formValues[key];
1858
- });
1859
- // set up new values
1860
- keysOf(fields).forEach(path => {
1861
- setFieldValue(path, fields[path]);
1862
- });
1968
+ merge(formValues, fields);
1863
1969
  // regenerate the arrays when the form values change
1864
1970
  fieldArrays.forEach(f => f && f.reset());
1865
1971
  }
1866
1972
  function createModel(path) {
1867
- const { value } = _useFieldValue(path, undefined, formCtx);
1868
- vue.watch(value, () => {
1869
- if (!fieldExists(vue.unref(path))) {
1870
- validate({ mode: 'validated-only' });
1871
- }
1872
- }, {
1873
- deep: true,
1973
+ const pathState = findPathState(vue.unref(path)) || createPathState(path);
1974
+ return vue.computed({
1975
+ get() {
1976
+ return pathState.value;
1977
+ },
1978
+ set(value) {
1979
+ const pathValue = vue.unref(path);
1980
+ setFieldValue(pathValue, value);
1981
+ pathState.validated = true;
1982
+ pathState.pending = true;
1983
+ validateField(pathValue).then(() => {
1984
+ pathState.pending = false;
1985
+ });
1986
+ },
1874
1987
  });
1875
- controlledModelPaths.add(vue.unref(path));
1876
- return value;
1877
1988
  }
1878
- function useFieldModel(path) {
1879
- if (!Array.isArray(path)) {
1880
- return createModel(path);
1989
+ function useFieldModel(pathOrPaths) {
1990
+ if (!Array.isArray(pathOrPaths)) {
1991
+ return createModel(pathOrPaths);
1881
1992
  }
1882
- return path.map(createModel);
1993
+ return pathOrPaths.map(createModel);
1883
1994
  }
1884
1995
  /**
1885
1996
  * Sets the touched meta state on a field
1886
1997
  */
1887
1998
  function setFieldTouched(field, isTouched) {
1888
- const fieldInstance = fieldsByPath.value[field];
1889
- if (fieldInstance) {
1890
- applyFieldMutation(fieldInstance, f => f.setTouched(isTouched));
1999
+ const pathState = findPathState(field);
2000
+ if (pathState) {
2001
+ pathState.touched = isTouched;
1891
2002
  }
1892
2003
  }
1893
2004
  /**
@@ -1899,172 +2010,53 @@
1899
2010
  });
1900
2011
  }
1901
2012
  function resetField(field, state) {
1902
- const fieldInstance = fieldsByPath.value[field];
1903
- if (fieldInstance) {
1904
- applyFieldMutation(fieldInstance, f => f.resetField(state));
1905
- }
2013
+ var _a;
2014
+ const newValue = state && 'value' in state ? state.value : getFromPath(initialValues.value, field);
2015
+ setFieldInitialValue(field, klona(newValue));
2016
+ setFieldValue(field, newValue);
2017
+ setFieldTouched(field, (_a = state === null || state === void 0 ? void 0 : state.touched) !== null && _a !== void 0 ? _a : false);
2018
+ setFieldError(field, (state === null || state === void 0 ? void 0 : state.errors) || []);
1906
2019
  }
1907
2020
  /**
1908
2021
  * Resets all fields
1909
2022
  */
1910
- function resetForm(state) {
1911
- RESET_LOCK = true;
1912
- // Reset all field states first
1913
- mutateAllFields(f => f.resetField());
1914
- // reset values
1915
- const newValues = (state === null || state === void 0 ? void 0 : state.values) ? state.values : originalInitialValues.value;
2023
+ function resetForm(resetState) {
2024
+ const newValues = (resetState === null || resetState === void 0 ? void 0 : resetState.values) ? resetState.values : originalInitialValues.value;
1916
2025
  setInitialValues(newValues);
1917
2026
  setValues(newValues);
1918
- if (state === null || state === void 0 ? void 0 : state.touched) {
1919
- setTouched(state.touched);
1920
- }
1921
- setErrors((state === null || state === void 0 ? void 0 : state.errors) || {});
1922
- submitCount.value = (state === null || state === void 0 ? void 0 : state.submitCount) || 0;
1923
- vue.nextTick(() => {
1924
- RESET_LOCK = false;
2027
+ mutateAllPathState(state => {
2028
+ var _a;
2029
+ state.validated = false;
2030
+ state.touched = ((_a = resetState === null || resetState === void 0 ? void 0 : resetState.touched) === null || _a === void 0 ? void 0 : _a[state.path]) || false;
2031
+ setFieldValue(state.path, getFromPath(newValues, state.path));
2032
+ setFieldError(state.path, undefined);
1925
2033
  });
1926
- }
1927
- function insertFieldAtPath(field, path) {
1928
- const rawField = vue.markRaw(field);
1929
- const fieldPath = path;
1930
- // first field at that path
1931
- if (!fieldsByPath.value[fieldPath]) {
1932
- fieldsByPath.value[fieldPath] = rawField;
1933
- return;
1934
- }
1935
- const fieldAtPath = fieldsByPath.value[fieldPath];
1936
- if (fieldAtPath && !Array.isArray(fieldAtPath)) {
1937
- fieldsByPath.value[fieldPath] = [fieldAtPath];
1938
- }
1939
- // add the new array to that path
1940
- fieldsByPath.value[fieldPath] = [...fieldsByPath.value[fieldPath], rawField];
1941
- }
1942
- function removeFieldFromPath(field, path) {
1943
- const fieldPath = path;
1944
- const fieldAtPath = fieldsByPath.value[fieldPath];
1945
- if (!fieldAtPath) {
1946
- return;
1947
- }
1948
- // same field at path
1949
- if (!isFieldGroup(fieldAtPath) && field.id === fieldAtPath.id) {
1950
- delete fieldsByPath.value[fieldPath];
1951
- return;
1952
- }
1953
- if (isFieldGroup(fieldAtPath)) {
1954
- const idx = fieldAtPath.findIndex(f => f.id === field.id);
1955
- if (idx === -1) {
1956
- return;
1957
- }
1958
- fieldAtPath.splice(idx, 1);
1959
- if (!fieldAtPath.length) {
1960
- delete fieldsByPath.value[fieldPath];
1961
- }
1962
- }
1963
- }
1964
- function registerField(field) {
1965
- const fieldPath = vue.unref(field.name);
1966
- insertFieldAtPath(field, fieldPath);
1967
- if (vue.isRef(field.name)) {
1968
- // ensures when a field's name was already taken that it preserves its same value
1969
- // necessary for fields generated by loops
1970
- vue.watch(field.name, async (newPath, oldPath) => {
1971
- // cache the value
1972
- await vue.nextTick();
1973
- removeFieldFromPath(field, oldPath);
1974
- insertFieldAtPath(field, newPath);
1975
- // re-validate if either path had errors before
1976
- if (errors.value[oldPath] || errors.value[newPath]) {
1977
- // clear up both paths errors
1978
- setFieldError(oldPath, undefined);
1979
- validateField(newPath);
1980
- }
1981
- // clean up the old path if no other field is sharing that name
1982
- // #3325
1983
- await vue.nextTick();
1984
- if (!fieldExists(oldPath)) {
1985
- unsetPath(formValues, oldPath);
1986
- }
1987
- });
1988
- }
1989
- // if field already had errors (initial errors) that's not user-set, validate it again to ensure state is correct
1990
- // the difference being that `initialErrors` will contain the error message while other errors (pre-validated schema) won't have them as initial errors
1991
- // #3342
1992
- const initialErrorMessage = vue.unref(field.errorMessage);
1993
- if (initialErrorMessage && (initialErrors === null || initialErrors === void 0 ? void 0 : initialErrors[fieldPath]) !== initialErrorMessage) {
1994
- validateField(fieldPath);
1995
- }
1996
- // marks the initial error as "consumed" so it won't be matched later with same non-initial error
1997
- delete initialErrors[fieldPath];
1998
- }
1999
- function unregisterField(field) {
2000
- const fieldName = vue.unref(field.name);
2001
- const fieldInstance = fieldsByPath.value[fieldName];
2002
- const isGroup = !!fieldInstance && isFieldGroup(fieldInstance);
2003
- removeFieldFromPath(field, fieldName);
2004
- // clears a field error on unmounted
2005
- // we wait till next tick to make sure if the field is completely removed and doesn't have any siblings like checkboxes
2034
+ setErrors((resetState === null || resetState === void 0 ? void 0 : resetState.errors) || {});
2035
+ submitCount.value = (resetState === null || resetState === void 0 ? void 0 : resetState.submitCount) || 0;
2006
2036
  vue.nextTick(() => {
2007
- var _a;
2008
- const shouldKeepValue = (_a = vue.unref(field.keepValueOnUnmount)) !== null && _a !== void 0 ? _a : vue.unref(keepValuesOnUnmount);
2009
- const currentGroupValue = getFromPath(formValues, fieldName);
2010
- // The boolean here is we check if the field still belongs to the same control group with that name
2011
- // if another group claimed the name, we should avoid handling it since it is no longer the same group
2012
- // this happens with `v-for` over some checkboxes and field arrays.
2013
- // also if the group no longer exist we can assume this group was the last one that controlled it
2014
- const isSameGroup = isGroup && (fieldInstance === fieldsByPath.value[fieldName] || !fieldsByPath.value[fieldName]);
2015
- // group field that still has a dangling value, the field may exist or not after it was removed.
2016
- // This used to be handled in the useField composable but the form has better context on when it should/not happen.
2017
- // if it does belong to it that means the group still exists
2018
- // #3844
2019
- if (isSameGroup && !shouldKeepValue) {
2020
- if (Array.isArray(currentGroupValue)) {
2021
- const valueIdx = currentGroupValue.findIndex(i => isEqual(i, vue.unref(field.checkedValue)));
2022
- if (valueIdx > -1) {
2023
- const newVal = [...currentGroupValue];
2024
- newVal.splice(valueIdx, 1);
2025
- setFieldValue(fieldName, newVal, { force: true });
2026
- }
2027
- }
2028
- else if (currentGroupValue === vue.unref(field.checkedValue)) {
2029
- // Remove field if it is a group but does not have an array value, like for radio inputs #3963
2030
- unsetPath(formValues, fieldName);
2031
- }
2032
- }
2033
- // Field was removed entirely, we should unset its path
2034
- // #3384
2035
- if (!fieldExists(fieldName)) {
2036
- setFieldError(fieldName, undefined);
2037
- // Checks if the field was configured to be unset during unmount or not
2038
- // Checks both the form-level config and field-level one
2039
- // Field has the priority if it is set, otherwise it goes to the form settings
2040
- if (shouldKeepValue) {
2041
- return;
2042
- }
2043
- // Don't apply emptyContainer check unless the current group value is an array
2044
- if (isGroup && Array.isArray(currentGroupValue) && !isEmptyContainer(currentGroupValue)) {
2045
- return;
2046
- }
2047
- unsetPath(formValues, fieldName);
2048
- }
2037
+ validate({ mode: 'silent' });
2049
2038
  });
2050
2039
  }
2051
2040
  async function validate(opts) {
2052
2041
  const mode = (opts === null || opts === void 0 ? void 0 : opts.mode) || 'force';
2053
2042
  if (mode === 'force') {
2054
- mutateAllFields(f => (f.meta.validated = true));
2043
+ mutateAllPathState(f => (f.validated = true));
2055
2044
  }
2056
2045
  if (formCtx.validateSchema) {
2057
2046
  return formCtx.validateSchema(mode);
2058
2047
  }
2059
2048
  // No schema, each field is responsible to validate itself
2060
- const validations = await Promise.all(Object.values(fieldsByPath.value).map(field => {
2061
- const fieldInstance = Array.isArray(field) ? field[0] : field;
2062
- if (!fieldInstance) {
2063
- return Promise.resolve({ key: '', valid: true, errors: [] });
2049
+ const validations = await Promise.all(pathStates.value.map(state => {
2050
+ if (!state.validate) {
2051
+ return Promise.resolve({
2052
+ key: state.path,
2053
+ valid: true,
2054
+ errors: [],
2055
+ });
2064
2056
  }
2065
- return fieldInstance.validate(opts).then((result) => {
2057
+ return state.validate(opts).then((result) => {
2066
2058
  return {
2067
- key: vue.unref(fieldInstance.name),
2059
+ key: state.path,
2068
2060
  valid: result.valid,
2069
2061
  errors: result.errors,
2070
2062
  };
@@ -2087,16 +2079,22 @@
2087
2079
  errors,
2088
2080
  };
2089
2081
  }
2090
- async function validateField(field) {
2091
- const fieldInstance = fieldsByPath.value[field];
2092
- if (!fieldInstance) {
2093
- vue.warn(`field with name ${field} was not found`);
2094
- return Promise.resolve({ errors: [], valid: true });
2082
+ async function validateField(path) {
2083
+ const state = findPathState(path);
2084
+ if (state) {
2085
+ state.validated = true;
2086
+ }
2087
+ if (schema) {
2088
+ const { results } = await validateSchema('validated-only');
2089
+ return results[path] || { errors: [], valid: true };
2095
2090
  }
2096
- if (Array.isArray(fieldInstance)) {
2097
- return fieldInstance.map(f => f.validate())[0];
2091
+ if (state === null || state === void 0 ? void 0 : state.validate) {
2092
+ return state.validate();
2098
2093
  }
2099
- return fieldInstance.validate();
2094
+ if (!state) {
2095
+ vue.warn(`field with path ${path} was not found`);
2096
+ }
2097
+ return Promise.resolve({ errors: [], valid: true });
2100
2098
  }
2101
2099
  function unsetInitialValue(path) {
2102
2100
  unsetPath(initialValues.value, path);
@@ -2105,8 +2103,8 @@
2105
2103
  * Sneaky function to set initial field values
2106
2104
  */
2107
2105
  function stageInitialValue(path, value, updateOriginal = false) {
2108
- setInPath(formValues, path, value);
2109
2106
  setFieldInitialValue(path, value);
2107
+ setInPath(formValues, path, value);
2110
2108
  if (updateOriginal && !(opts === null || opts === void 0 ? void 0 : opts.initialValues)) {
2111
2109
  setInPath(originalInitialValues.value, path, klona(value));
2112
2110
  }
@@ -2159,12 +2157,95 @@
2159
2157
  }
2160
2158
  // Provide injections
2161
2159
  vue.provide(FormContextKey, formCtx);
2162
- return Object.assign(Object.assign({}, formCtx), { handleReset: () => resetForm(), submitForm });
2160
+ function defineComponentBinds(path, config) {
2161
+ const pathState = findPathState(unravel(path)) || createPathState(path);
2162
+ const evalConfig = () => (isCallable(config) ? config(omit(pathState, PRIVATE_PATH_STATE_KEYS)) : config || {});
2163
+ function onBlur() {
2164
+ var _a;
2165
+ pathState.touched = true;
2166
+ const validateOnBlur = (_a = evalConfig().validateOnBlur) !== null && _a !== void 0 ? _a : getConfig().validateOnBlur;
2167
+ if (validateOnBlur) {
2168
+ validateField(pathState.path);
2169
+ }
2170
+ }
2171
+ function onUpdateModelValue(value) {
2172
+ var _a;
2173
+ setFieldValue(pathState.path, value);
2174
+ const validateOnModelUpdate = (_a = evalConfig().validateOnModelUpdate) !== null && _a !== void 0 ? _a : getConfig().validateOnModelUpdate;
2175
+ if (validateOnModelUpdate) {
2176
+ validateField(pathState.path);
2177
+ }
2178
+ }
2179
+ const props = vue.computed(() => {
2180
+ const base = {
2181
+ modelValue: pathState.value,
2182
+ 'onUpdate:modelValue': onUpdateModelValue,
2183
+ onBlur,
2184
+ };
2185
+ if (isCallable(config)) {
2186
+ return Object.assign(Object.assign({}, base), (config(pathState).props || {}));
2187
+ }
2188
+ if (config === null || config === void 0 ? void 0 : config.mapProps) {
2189
+ return Object.assign(Object.assign({}, base), config.mapProps(omit(pathState, PRIVATE_PATH_STATE_KEYS)));
2190
+ }
2191
+ return base;
2192
+ });
2193
+ return props;
2194
+ }
2195
+ function defineInputBinds(path, config) {
2196
+ const pathState = (findPathState(unravel(path)) || createPathState(path));
2197
+ const evalConfig = () => (isCallable(config) ? config(omit(pathState, PRIVATE_PATH_STATE_KEYS)) : config || {});
2198
+ function onBlur() {
2199
+ var _a;
2200
+ pathState.touched = true;
2201
+ const validateOnBlur = (_a = evalConfig().validateOnBlur) !== null && _a !== void 0 ? _a : getConfig().validateOnBlur;
2202
+ if (validateOnBlur) {
2203
+ validateField(pathState.path);
2204
+ }
2205
+ }
2206
+ function onInput(e) {
2207
+ var _a;
2208
+ const value = normalizeEventValue(e);
2209
+ setFieldValue(pathState.path, value);
2210
+ const validateOnInput = (_a = evalConfig().validateOnInput) !== null && _a !== void 0 ? _a : getConfig().validateOnInput;
2211
+ if (validateOnInput) {
2212
+ validateField(pathState.path);
2213
+ }
2214
+ }
2215
+ function onChange(e) {
2216
+ var _a;
2217
+ const value = normalizeEventValue(e);
2218
+ setFieldValue(pathState.path, value);
2219
+ const validateOnChange = (_a = evalConfig().validateOnChange) !== null && _a !== void 0 ? _a : getConfig().validateOnChange;
2220
+ if (validateOnChange) {
2221
+ validateField(pathState.path);
2222
+ }
2223
+ }
2224
+ const props = vue.computed(() => {
2225
+ const base = {
2226
+ value: pathState.value,
2227
+ onChange,
2228
+ onInput,
2229
+ onBlur,
2230
+ };
2231
+ if (isCallable(config)) {
2232
+ return Object.assign(Object.assign({}, base), (config(omit(pathState, PRIVATE_PATH_STATE_KEYS)).attrs || {}));
2233
+ }
2234
+ if (config === null || config === void 0 ? void 0 : config.mapAttrs) {
2235
+ return Object.assign(Object.assign({}, base), config.mapAttrs(omit(pathState, PRIVATE_PATH_STATE_KEYS)));
2236
+ }
2237
+ return base;
2238
+ });
2239
+ return props;
2240
+ }
2241
+ return Object.assign(Object.assign({}, formCtx), { handleReset: () => resetForm(), submitForm,
2242
+ defineComponentBinds,
2243
+ defineInputBinds });
2163
2244
  }
2164
2245
  /**
2165
2246
  * Manages form meta aggregation
2166
2247
  */
2167
- function useFormMeta(fieldsByPath, currentValues, initialValues, errors) {
2248
+ function useFormMeta(pathsState, currentValues, initialValues, errors) {
2168
2249
  const MERGE_STRATEGIES = {
2169
2250
  touched: 'some',
2170
2251
  pending: 'some',
@@ -2174,10 +2255,10 @@
2174
2255
  return !isEqual(currentValues, vue.unref(initialValues));
2175
2256
  });
2176
2257
  function calculateFlags() {
2177
- const fields = Object.values(fieldsByPath.value).flat(1).filter(Boolean);
2258
+ const states = pathsState.value;
2178
2259
  return keysOf(MERGE_STRATEGIES).reduce((acc, flag) => {
2179
2260
  const mergeMethod = MERGE_STRATEGIES[flag];
2180
- acc[flag] = fields[mergeMethod](field => field.meta[flag]);
2261
+ acc[flag] = states[mergeMethod](s => s[flag]);
2181
2262
  return acc;
2182
2263
  }, {});
2183
2264
  }
@@ -2195,7 +2276,7 @@
2195
2276
  /**
2196
2277
  * Manages the initial values prop
2197
2278
  */
2198
- function useFormInitialValues(fields, formValues, opts) {
2279
+ function useFormInitialValues(pathsState, formValues, opts) {
2199
2280
  const values = resolveInitialValues(opts);
2200
2281
  const providedValues = opts === null || opts === void 0 ? void 0 : opts.initialValues;
2201
2282
  // these are the mutable initial values as the fields are mounted/unmounted
@@ -2216,14 +2297,13 @@
2216
2297
  // those are excluded because it's unlikely you want to change the form values using initial values
2217
2298
  // we mostly watch them for API population or newly inserted fields
2218
2299
  // if the user API is taking too much time before user interaction they should consider disabling or hiding their inputs until the values are ready
2219
- keysOf(fields.value).forEach(fieldPath => {
2220
- const field = fields.value[fieldPath];
2221
- const wasTouched = Array.isArray(field) ? field.some(f => f.meta.touched) : field === null || field === void 0 ? void 0 : field.meta.touched;
2222
- if (!field || wasTouched) {
2300
+ pathsState.value.forEach(state => {
2301
+ const wasTouched = state.touched;
2302
+ if (wasTouched) {
2223
2303
  return;
2224
2304
  }
2225
- const newValue = getFromPath(initialValues.value, fieldPath);
2226
- setInPath(formValues, fieldPath, klona(newValue));
2305
+ const newValue = getFromPath(initialValues.value, state.path);
2306
+ setInPath(formValues, state.path, klona(newValue));
2227
2307
  });
2228
2308
  }
2229
2309
  if (vue.isRef(providedValues)) {
@@ -2239,42 +2319,6 @@
2239
2319
  setInitialValues,
2240
2320
  };
2241
2321
  }
2242
- function useErrorBag(initialErrors) {
2243
- const errorBag = vue.ref({});
2244
- function normalizeErrorItem(message) {
2245
- return Array.isArray(message) ? message : message ? [message] : [];
2246
- }
2247
- /**
2248
- * Manually sets an error message on a specific field
2249
- */
2250
- function setFieldErrorBag(field, message) {
2251
- if (!message) {
2252
- delete errorBag.value[field];
2253
- return;
2254
- }
2255
- errorBag.value[field] = normalizeErrorItem(message);
2256
- }
2257
- /**
2258
- * Sets errors for the fields specified in the object
2259
- */
2260
- function setErrorBag(fields) {
2261
- errorBag.value = keysOf(fields).reduce((acc, key) => {
2262
- const message = fields[key];
2263
- if (message) {
2264
- acc[key] = normalizeErrorItem(message);
2265
- }
2266
- return acc;
2267
- }, {});
2268
- }
2269
- if (initialErrors) {
2270
- setErrorBag(initialErrors);
2271
- }
2272
- return {
2273
- errorBag,
2274
- setErrorBag,
2275
- setFieldErrorBag,
2276
- };
2277
- }
2278
2322
 
2279
2323
  const FormImpl = vue.defineComponent({
2280
2324
  name: 'Form',
@@ -2455,7 +2499,10 @@
2455
2499
  }
2456
2500
  function initFields() {
2457
2501
  const currentValues = getCurrentValues();
2458
- fields.value = currentValues.map(createEntry);
2502
+ if (!Array.isArray(currentValues)) {
2503
+ return;
2504
+ }
2505
+ fields.value = currentValues.map((v, idx) => createEntry(v, idx, fields.value));
2459
2506
  updateEntryFlags();
2460
2507
  }
2461
2508
  initFields();
@@ -2467,7 +2514,13 @@
2467
2514
  entry.isLast = i === fieldsLength - 1;
2468
2515
  }
2469
2516
  }
2470
- function createEntry(value) {
2517
+ function createEntry(value, idx, currentFields) {
2518
+ // Skips the work by returning the current entry if it already exists
2519
+ // This should make the `key` prop stable and doesn't cause more re-renders than needed
2520
+ // The value is computed and should update anyways
2521
+ if (currentFields && !isNullOrUndefined(idx) && currentFields[idx]) {
2522
+ return currentFields[idx];
2523
+ }
2471
2524
  const key = entryCounter++;
2472
2525
  const entry = {
2473
2526
  key,
@@ -2504,8 +2557,9 @@
2504
2557
  }
2505
2558
  const newValue = [...pathValue];
2506
2559
  newValue.splice(idx, 1);
2507
- form === null || form === void 0 ? void 0 : form.unsetInitialValue(pathName + `[${idx}]`);
2508
- form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, newValue);
2560
+ const fieldPath = pathName + `[${idx}]`;
2561
+ form.unsetInitialValue(fieldPath);
2562
+ setInPath(form.values, pathName, newValue);
2509
2563
  fields.value.splice(idx, 1);
2510
2564
  afterMutation();
2511
2565
  }
@@ -2518,8 +2572,8 @@
2518
2572
  }
2519
2573
  const newValue = [...normalizedPathValue];
2520
2574
  newValue.push(value);
2521
- form === null || form === void 0 ? void 0 : form.stageInitialValue(pathName + `[${newValue.length - 1}]`, value);
2522
- form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, newValue);
2575
+ form.stageInitialValue(pathName + `[${newValue.length - 1}]`, value);
2576
+ setInPath(form.values, pathName, newValue);
2523
2577
  fields.value.push(createEntry(value));
2524
2578
  afterMutation();
2525
2579
  }
@@ -2538,7 +2592,7 @@
2538
2592
  const tempEntry = newFields[indexA];
2539
2593
  newFields[indexA] = newFields[indexB];
2540
2594
  newFields[indexB] = tempEntry;
2541
- form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, newValue);
2595
+ setInPath(form.values, pathName, newValue);
2542
2596
  fields.value = newFields;
2543
2597
  updateEntryFlags();
2544
2598
  }
@@ -2552,13 +2606,14 @@
2552
2606
  const newFields = [...fields.value];
2553
2607
  newValue.splice(idx, 0, value);
2554
2608
  newFields.splice(idx, 0, createEntry(value));
2555
- form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, newValue);
2609
+ setInPath(form.values, pathName, newValue);
2556
2610
  fields.value = newFields;
2557
2611
  afterMutation();
2558
2612
  }
2559
2613
  function replace(arr) {
2560
2614
  const pathName = vue.unref(arrayPath);
2561
- form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, arr);
2615
+ form.stageInitialValue(pathName, arr);
2616
+ setInPath(form.values, pathName, arr);
2562
2617
  initFields();
2563
2618
  afterMutation();
2564
2619
  }
@@ -2568,7 +2623,7 @@
2568
2623
  if (!Array.isArray(pathValue) || pathValue.length - 1 < idx) {
2569
2624
  return;
2570
2625
  }
2571
- form === null || form === void 0 ? void 0 : form.setFieldValue(`${pathName}[${idx}]`, value);
2626
+ setInPath(form.values, `${pathName}[${idx}]`, value);
2572
2627
  form === null || form === void 0 ? void 0 : form.validate({ mode: 'validated-only' });
2573
2628
  }
2574
2629
  function prepend(value) {
@@ -2579,8 +2634,8 @@
2579
2634
  return;
2580
2635
  }
2581
2636
  const newValue = [value, ...normalizedPathValue];
2582
- form === null || form === void 0 ? void 0 : form.stageInitialValue(pathName + `[${newValue.length - 1}]`, value);
2583
- form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, newValue);
2637
+ form.stageInitialValue(pathName + `[${newValue.length - 1}]`, value);
2638
+ setInPath(form.values, pathName, newValue);
2584
2639
  fields.value.unshift(createEntry(value));
2585
2640
  afterMutation();
2586
2641
  }
@@ -2598,7 +2653,7 @@
2598
2653
  const movedValue = newValue[oldIdx];
2599
2654
  newValue.splice(oldIdx, 1);
2600
2655
  newValue.splice(newIdx, 0, movedValue);
2601
- form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, newValue);
2656
+ setInPath(form.values, pathName, newValue);
2602
2657
  fields.value = newFields;
2603
2658
  afterMutation();
2604
2659
  }
@@ -2737,17 +2792,13 @@
2737
2792
  * If a field is dirty or not
2738
2793
  */
2739
2794
  function useIsFieldDirty(path) {
2740
- const form = injectWithSelf(FormContextKey);
2741
- let field = path ? undefined : vue.inject(FieldContextKey);
2795
+ const fieldOrPath = resolveFieldOrPathState(path);
2742
2796
  return vue.computed(() => {
2743
- if (path) {
2744
- field = normalizeField(form === null || form === void 0 ? void 0 : form.fieldsByPath.value[vue.unref(path)]);
2745
- }
2746
- if (!field) {
2747
- warn(`field with name ${vue.unref(path)} was not found`);
2797
+ var _a, _b;
2798
+ if (!fieldOrPath) {
2748
2799
  return false;
2749
2800
  }
2750
- return field.meta.dirty;
2801
+ return (_b = ('meta' in fieldOrPath ? fieldOrPath.meta.dirty : (_a = fieldOrPath === null || fieldOrPath === void 0 ? void 0 : fieldOrPath.value) === null || _a === void 0 ? void 0 : _a.dirty)) !== null && _b !== void 0 ? _b : false;
2751
2802
  });
2752
2803
  }
2753
2804
 
@@ -2755,17 +2806,13 @@
2755
2806
  * If a field is touched or not
2756
2807
  */
2757
2808
  function useIsFieldTouched(path) {
2758
- const form = injectWithSelf(FormContextKey);
2759
- let field = path ? undefined : vue.inject(FieldContextKey);
2809
+ const fieldOrPath = resolveFieldOrPathState(path);
2760
2810
  return vue.computed(() => {
2761
- if (path) {
2762
- field = normalizeField(form === null || form === void 0 ? void 0 : form.fieldsByPath.value[vue.unref(path)]);
2763
- }
2764
- if (!field) {
2765
- warn(`field with name ${vue.unref(path)} was not found`);
2811
+ var _a, _b;
2812
+ if (!fieldOrPath) {
2766
2813
  return false;
2767
2814
  }
2768
- return field.meta.touched;
2815
+ return (_b = ('meta' in fieldOrPath ? fieldOrPath.meta.touched : (_a = fieldOrPath === null || fieldOrPath === void 0 ? void 0 : fieldOrPath.value) === null || _a === void 0 ? void 0 : _a.touched)) !== null && _b !== void 0 ? _b : false;
2769
2816
  });
2770
2817
  }
2771
2818
 
@@ -2773,17 +2820,13 @@
2773
2820
  * If a field is validated and is valid
2774
2821
  */
2775
2822
  function useIsFieldValid(path) {
2776
- const form = injectWithSelf(FormContextKey);
2777
- let field = path ? undefined : vue.inject(FieldContextKey);
2823
+ const fieldOrPath = resolveFieldOrPathState(path);
2778
2824
  return vue.computed(() => {
2779
- if (path) {
2780
- field = normalizeField(form === null || form === void 0 ? void 0 : form.fieldsByPath.value[vue.unref(path)]);
2781
- }
2782
- if (!field) {
2783
- warn(`field with name ${vue.unref(path)} was not found`);
2825
+ var _a, _b;
2826
+ if (!fieldOrPath) {
2784
2827
  return false;
2785
2828
  }
2786
- return field.meta.valid;
2829
+ return (_b = ('meta' in fieldOrPath ? fieldOrPath.meta.valid : (_a = fieldOrPath === null || fieldOrPath === void 0 ? void 0 : fieldOrPath.value) === null || _a === void 0 ? void 0 : _a.valid)) !== null && _b !== void 0 ? _b : false;
2787
2830
  });
2788
2831
  }
2789
2832
 
@@ -2806,19 +2849,19 @@
2806
2849
  */
2807
2850
  function useValidateField(path) {
2808
2851
  const form = injectWithSelf(FormContextKey);
2809
- let field = path ? undefined : vue.inject(FieldContextKey);
2852
+ const field = path ? undefined : vue.inject(FieldContextKey);
2810
2853
  return function validateField() {
2811
- if (path) {
2812
- field = normalizeField(form === null || form === void 0 ? void 0 : form.fieldsByPath.value[vue.unref(path)]);
2854
+ if (field) {
2855
+ return field.validate();
2813
2856
  }
2814
- if (!field) {
2815
- warn(`field with name ${vue.unref(path)} was not found`);
2816
- return Promise.resolve({
2817
- errors: [],
2818
- valid: true,
2819
- });
2857
+ if (form && path) {
2858
+ return form === null || form === void 0 ? void 0 : form.validateField(vue.unref(path));
2820
2859
  }
2821
- return field.validate();
2860
+ warn(`field with name ${vue.unref(path)} was not found`);
2861
+ return Promise.resolve({
2862
+ errors: [],
2863
+ valid: true,
2864
+ });
2822
2865
  };
2823
2866
  }
2824
2867