vee-validate 4.8.6 → 4.9.1

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.6
2
+ * vee-validate v4.9.1
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();
@@ -1490,9 +1548,27 @@
1490
1548
  };
1491
1549
  const fieldProps = vue.computed(() => {
1492
1550
  const { validateOnInput, validateOnChange, validateOnBlur, validateOnModelUpdate } = resolveValidationTriggers(props);
1493
- const baseOnBlur = [handleBlur, ctx.attrs.onBlur, validateOnBlur ? validateField : undefined].filter(Boolean);
1494
- const baseOnInput = [(e) => onChangeHandler(e, validateOnInput), ctx.attrs.onInput].filter(Boolean);
1495
- const baseOnChange = [(e) => onChangeHandler(e, validateOnChange), ctx.attrs.onChange].filter(Boolean);
1551
+ function baseOnBlur(e) {
1552
+ handleBlur(e);
1553
+ if (isCallable(ctx.attrs.onBlur)) {
1554
+ ctx.attrs.onBlur(e);
1555
+ }
1556
+ if (validateOnBlur) {
1557
+ validateField();
1558
+ }
1559
+ }
1560
+ function baseOnInput(e) {
1561
+ onChangeHandler(e, validateOnInput);
1562
+ if (isCallable(ctx.attrs.onInput)) {
1563
+ ctx.attrs.onInput(e);
1564
+ }
1565
+ }
1566
+ function baseOnChange(e) {
1567
+ onChangeHandler(e, validateOnChange);
1568
+ if (isCallable(ctx.attrs.onChange)) {
1569
+ ctx.attrs.onChange(e);
1570
+ }
1571
+ }
1496
1572
  const attrs = {
1497
1573
  name: props.name,
1498
1574
  onBlur: baseOnBlur,
@@ -1571,6 +1647,7 @@
1571
1647
  const Field = FieldImpl;
1572
1648
 
1573
1649
  let FORM_COUNTER = 0;
1650
+ const PRIVATE_PATH_STATE_KEYS = ['bails', 'fieldsCount', 'id', 'multiple', 'type', 'validate'];
1574
1651
  function resolveInitialValues(opts) {
1575
1652
  const providedValues = vue.unref(opts === null || opts === void 0 ? void 0 : opts.initialValues) || {};
1576
1653
  const schema = vue.unref(opts === null || opts === void 0 ? void 0 : opts.validationSchema);
@@ -1582,12 +1659,8 @@
1582
1659
  function useForm(opts) {
1583
1660
  var _a;
1584
1661
  const formId = FORM_COUNTER++;
1585
- const controlledModelPaths = new Set();
1586
1662
  // 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({});
1663
+ let FIELD_ID_COUNTER = 0;
1591
1664
  // If the form is currently submitting
1592
1665
  const isSubmitting = vue.ref(false);
1593
1666
  // The number of times the user tried to submit the form
@@ -1596,44 +1669,62 @@
1596
1669
  const fieldArrays = [];
1597
1670
  // a private ref for all form values
1598
1671
  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);
1672
+ const pathStates = vue.ref([]);
1673
+ const extraErrorsBag = vue.ref({});
1674
+ /**
1675
+ * Manually sets an error message on a specific field
1676
+ */
1677
+ function setFieldError(field, message) {
1678
+ const state = findPathState(field);
1679
+ if (!state) {
1680
+ extraErrorsBag.value[field] = normalizeErrorItem(message);
1681
+ return;
1682
+ }
1683
+ state.errors = normalizeErrorItem(message);
1684
+ }
1685
+ /**
1686
+ * Sets errors for the fields specified in the object
1687
+ */
1688
+ function setErrors(paths) {
1689
+ keysOf(paths).forEach(path => {
1690
+ setFieldError(path, paths[path]);
1691
+ });
1692
+ }
1693
+ if (opts === null || opts === void 0 ? void 0 : opts.initialErrors) {
1694
+ setErrors(opts.initialErrors);
1695
+ }
1696
+ const errorBag = vue.computed(() => {
1697
+ const pathErrors = pathStates.value.reduce((acc, state) => {
1698
+ if (state.errors.length) {
1699
+ acc[state.path] = state.errors;
1700
+ }
1701
+ return acc;
1702
+ }, {});
1703
+ return Object.assign(Object.assign({}, extraErrorsBag.value), pathErrors);
1704
+ });
1601
1705
  // Gets the first error of each field
1602
1706
  const errors = vue.computed(() => {
1603
1707
  return keysOf(errorBag.value).reduce((acc, key) => {
1604
- const bag = errorBag.value[key];
1605
- if (bag && bag.length) {
1606
- acc[key] = bag[0];
1708
+ const errors = errorBag.value[key];
1709
+ if (errors === null || errors === void 0 ? void 0 : errors.length) {
1710
+ acc[key] = errors[0];
1607
1711
  }
1608
1712
  return acc;
1609
1713
  }, {});
1610
1714
  });
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
1715
  /**
1619
1716
  * Holds a computed reference to all fields names and labels
1620
1717
  */
1621
1718
  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
- }
1719
+ return pathStates.value.reduce((names, state) => {
1720
+ names[state.path] = { name: state.path || '', label: state.label || '' };
1627
1721
  return names;
1628
1722
  }, {});
1629
1723
  });
1630
1724
  const fieldBailsMap = vue.computed(() => {
1631
- return keysOf(fieldsByPath.value).reduce((map, path) => {
1725
+ return pathStates.value.reduce((map, state) => {
1632
1726
  var _a;
1633
- const field = getFirstFieldAtPath(path);
1634
- if (field) {
1635
- map[path] = (_a = field.bails) !== null && _a !== void 0 ? _a : true;
1636
- }
1727
+ map[state.path] = (_a = state.bails) !== null && _a !== void 0 ? _a : true;
1637
1728
  return map;
1638
1729
  }, {});
1639
1730
  });
@@ -1642,17 +1733,74 @@
1642
1733
  const initialErrors = Object.assign({}, ((opts === null || opts === void 0 ? void 0 : opts.initialErrors) || {}));
1643
1734
  const keepValuesOnUnmount = (_a = opts === null || opts === void 0 ? void 0 : opts.keepValuesOnUnmount) !== null && _a !== void 0 ? _a : false;
1644
1735
  // initial form values
1645
- const { initialValues, originalInitialValues, setInitialValues } = useFormInitialValues(fieldsByPath, formValues, opts);
1736
+ const { initialValues, originalInitialValues, setInitialValues } = useFormInitialValues(pathStates, formValues, opts);
1646
1737
  // form meta aggregations
1647
- const meta = useFormMeta(fieldsByPath, formValues, originalInitialValues, errors);
1738
+ const meta = useFormMeta(pathStates, formValues, originalInitialValues, errors);
1648
1739
  const controlledValues = vue.computed(() => {
1649
- return [...controlledModelPaths, ...keysOf(fieldsByPath.value)].reduce((acc, path) => {
1650
- const value = getFromPath(formValues, path);
1651
- setInPath(acc, path, value);
1740
+ return pathStates.value.reduce((acc, state) => {
1741
+ const value = getFromPath(formValues, state.path);
1742
+ setInPath(acc, state.path, value);
1652
1743
  return acc;
1653
1744
  }, {});
1654
1745
  });
1655
1746
  const schema = opts === null || opts === void 0 ? void 0 : opts.validationSchema;
1747
+ function createPathState(path, config) {
1748
+ var _a, _b;
1749
+ const initialValue = vue.computed(() => getFromPath(initialValues.value, unravel(path)));
1750
+ const pathStateExists = pathStates.value.find(state => state.path === vue.unref(path));
1751
+ if (pathStateExists) {
1752
+ if ((config === null || config === void 0 ? void 0 : config.type) === 'checkbox' || (config === null || config === void 0 ? void 0 : config.type) === 'radio') {
1753
+ pathStateExists.multiple = true;
1754
+ }
1755
+ if (Array.isArray(pathStateExists.id)) {
1756
+ pathStateExists.id.push(FIELD_ID_COUNTER++);
1757
+ }
1758
+ else {
1759
+ pathStateExists.id = [pathStateExists.id, FIELD_ID_COUNTER++];
1760
+ }
1761
+ pathStateExists.fieldsCount++;
1762
+ return pathStateExists;
1763
+ }
1764
+ const currentValue = vue.computed(() => getFromPath(formValues, unravel(path)));
1765
+ const pathValue = unravel(path);
1766
+ const state = vue.reactive({
1767
+ id: FIELD_ID_COUNTER++,
1768
+ path,
1769
+ touched: false,
1770
+ pending: false,
1771
+ valid: true,
1772
+ validated: !!((_a = initialErrors[pathValue]) === null || _a === void 0 ? void 0 : _a.length),
1773
+ initialValue,
1774
+ errors: vue.shallowRef([]),
1775
+ bails: (_b = config === null || config === void 0 ? void 0 : config.bails) !== null && _b !== void 0 ? _b : false,
1776
+ label: config === null || config === void 0 ? void 0 : config.label,
1777
+ type: (config === null || config === void 0 ? void 0 : config.type) || 'default',
1778
+ value: currentValue,
1779
+ multiple: false,
1780
+ fieldsCount: 1,
1781
+ validate: config === null || config === void 0 ? void 0 : config.validate,
1782
+ dirty: vue.computed(() => {
1783
+ return !isEqual(vue.unref(currentValue), vue.unref(initialValue));
1784
+ }),
1785
+ });
1786
+ pathStates.value.push(state);
1787
+ // if it has errors before, validate it.
1788
+ if (errors.value[pathValue] && !initialErrors[pathValue]) {
1789
+ vue.nextTick(() => {
1790
+ validateField(pathValue);
1791
+ });
1792
+ }
1793
+ // Handles when a path changes
1794
+ if (vue.isRef(path)) {
1795
+ vue.watch(path, newPath => {
1796
+ const nextValue = klona(currentValue.value);
1797
+ vue.nextTick(() => {
1798
+ setInPath(formValues, newPath, nextValue);
1799
+ });
1800
+ });
1801
+ }
1802
+ return state;
1803
+ }
1656
1804
  /**
1657
1805
  * Batches validation runs in 5ms batches
1658
1806
  * Must have two distinct batch queues to make sure they don't override each other settings #3783
@@ -1663,17 +1811,17 @@
1663
1811
  return (await mode) === 'silent' ? debouncedSilentValidation() : debouncedValidation();
1664
1812
  }, (formResult, [mode]) => {
1665
1813
  // fields by id lookup
1666
- const fieldsById = formCtx.fieldsByPath.value || {};
1667
1814
  // errors fields names, we need it to also check if custom errors are updated
1668
1815
  const currentErrorsPaths = keysOf(formCtx.errorBag.value);
1669
1816
  // collect all the keys from the schema and all fields
1670
- // this ensures we have a complete keymap of all the fields
1817
+ // this ensures we have a complete key map of all the fields
1671
1818
  const paths = [
1672
- ...new Set([...keysOf(formResult.results), ...keysOf(fieldsById), ...currentErrorsPaths]),
1819
+ ...new Set([...keysOf(formResult.results), ...pathStates.value.map(p => p.path), ...currentErrorsPaths]),
1673
1820
  ];
1674
1821
  // 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];
1822
+ return paths.reduce((validation, _path) => {
1823
+ const path = _path;
1824
+ const pathState = findPathState(path);
1677
1825
  const messages = (formResult.results[path] || { errors: [] }).errors;
1678
1826
  const fieldResult = {
1679
1827
  errors: messages,
@@ -1683,24 +1831,37 @@
1683
1831
  if (!fieldResult.valid) {
1684
1832
  validation.errors[path] = fieldResult.errors[0];
1685
1833
  }
1834
+ // clean up extra errors if path state exists
1835
+ if (pathState && extraErrorsBag.value[path]) {
1836
+ delete extraErrorsBag.value[path];
1837
+ }
1686
1838
  // field not rendered
1687
- if (!field) {
1839
+ if (!pathState) {
1688
1840
  setFieldError(path, messages);
1689
1841
  return validation;
1690
1842
  }
1691
1843
  // always update the valid flag regardless of the mode
1692
- applyFieldMutation(field, f => (f.meta.valid = fieldResult.valid));
1844
+ pathState.valid = fieldResult.valid;
1693
1845
  if (mode === 'silent') {
1694
1846
  return validation;
1695
1847
  }
1696
- const wasValidated = Array.isArray(field) ? field.some(f => f.meta.validated) : field.meta.validated;
1697
- if (mode === 'validated-only' && !wasValidated) {
1848
+ if (mode === 'validated-only' && !pathState.validated) {
1698
1849
  return validation;
1699
1850
  }
1700
- applyFieldMutation(field, f => f.setState({ errors: fieldResult.errors }));
1851
+ setFieldError(pathState, fieldResult.errors);
1701
1852
  return validation;
1702
1853
  }, { valid: formResult.valid, results: {}, errors: {} });
1703
1854
  });
1855
+ function mutateAllPathState(mutation) {
1856
+ pathStates.value.forEach(mutation);
1857
+ }
1858
+ function findPathState(path) {
1859
+ const pathState = typeof path === 'string' ? pathStates.value.find(state => state.path === path) : path;
1860
+ return pathState;
1861
+ }
1862
+ function unsetPathValue(path) {
1863
+ unsetPath(formValues, path);
1864
+ }
1704
1865
  function makeSubmissionFactory(onlyControlled) {
1705
1866
  return function submitHandlerFactory(fn, onValidationError) {
1706
1867
  return function submissionHandler(e) {
@@ -1709,10 +1870,7 @@
1709
1870
  e.stopPropagation();
1710
1871
  }
1711
1872
  // Touch all fields
1712
- setTouched(keysOf(fieldsByPath.value).reduce((acc, field) => {
1713
- acc[field] = true;
1714
- return acc;
1715
- }, {}));
1873
+ mutateAllPathState(s => (s.touched = true));
1716
1874
  isSubmitting.value = true;
1717
1875
  submitCount.value++;
1718
1876
  return validate()
@@ -1720,7 +1878,7 @@
1720
1878
  const values = klona(formValues);
1721
1879
  if (result.valid && typeof fn === 'function') {
1722
1880
  const controlled = klona(controlledValues.value);
1723
- let submittedValues = onlyControlled ? controlled : values;
1881
+ let submittedValues = (onlyControlled ? controlled : values);
1724
1882
  if (result.values) {
1725
1883
  submittedValues = result.values;
1726
1884
  }
@@ -1760,9 +1918,22 @@
1760
1918
  const handleSubmitImpl = makeSubmissionFactory(false);
1761
1919
  const handleSubmit = handleSubmitImpl;
1762
1920
  handleSubmit.withControlled = makeSubmissionFactory(true);
1921
+ function removePathState(path) {
1922
+ const idx = pathStates.value.findIndex(s => s.path === path);
1923
+ const pathState = pathStates.value[idx];
1924
+ if (idx === -1 || !pathState) {
1925
+ return;
1926
+ }
1927
+ if (pathState.multiple && pathState.fieldsCount) {
1928
+ pathState.fieldsCount--;
1929
+ }
1930
+ if (!pathState.multiple || pathState.fieldsCount <= 0) {
1931
+ pathStates.value.splice(idx, 1);
1932
+ unsetInitialValue(path);
1933
+ }
1934
+ }
1763
1935
  const formCtx = {
1764
1936
  formId,
1765
- fieldsByPath,
1766
1937
  values: formValues,
1767
1938
  controlledValues,
1768
1939
  errorBag,
@@ -1775,14 +1946,11 @@
1775
1946
  keepValuesOnUnmount,
1776
1947
  validateSchema: vue.unref(schema) ? validateSchema : undefined,
1777
1948
  validate,
1778
- register: registerField,
1779
- unregister: unregisterField,
1780
- setFieldErrorBag,
1949
+ setFieldError,
1781
1950
  validateField,
1782
1951
  setFieldValue,
1783
1952
  setValues,
1784
1953
  setErrors,
1785
- setFieldError,
1786
1954
  setFieldTouched,
1787
1955
  setTouched,
1788
1956
  resetForm,
@@ -1792,102 +1960,63 @@
1792
1960
  unsetInitialValue,
1793
1961
  setFieldInitialValue,
1794
1962
  useFieldModel,
1963
+ createPathState,
1964
+ getPathState: findPathState,
1965
+ unsetPathValue,
1966
+ removePathState,
1967
+ initialValues: initialValues,
1968
+ getAllPathStates: () => pathStates.value,
1795
1969
  };
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
1970
  /**
1827
1971
  * Sets a single field value
1828
1972
  */
1829
- function setFieldValue(field, value, { force } = { force: false }) {
1830
- var _a;
1831
- const fieldInstance = fieldsByPath.value[field];
1973
+ function setFieldValue(field, value) {
1832
1974
  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;
1837
- }
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)));
1975
+ const path = typeof field === 'string' ? field : field.path;
1976
+ const pathState = findPathState(path);
1977
+ if (!pathState) {
1978
+ createPathState(path);
1848
1979
  }
1849
- setInPath(formValues, field, newValue);
1980
+ setInPath(formValues, path, clonedValue);
1850
1981
  }
1851
1982
  /**
1852
1983
  * Sets multiple fields values
1853
1984
  */
1854
1985
  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
- });
1986
+ merge(formValues, fields);
1863
1987
  // regenerate the arrays when the form values change
1864
1988
  fieldArrays.forEach(f => f && f.reset());
1865
1989
  }
1866
1990
  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,
1991
+ const pathState = findPathState(vue.unref(path)) || createPathState(path);
1992
+ return vue.computed({
1993
+ get() {
1994
+ return pathState.value;
1995
+ },
1996
+ set(value) {
1997
+ const pathValue = vue.unref(path);
1998
+ setFieldValue(pathValue, value);
1999
+ pathState.validated = true;
2000
+ pathState.pending = true;
2001
+ validateField(pathValue).then(() => {
2002
+ pathState.pending = false;
2003
+ });
2004
+ },
1874
2005
  });
1875
- controlledModelPaths.add(vue.unref(path));
1876
- return value;
1877
2006
  }
1878
- function useFieldModel(path) {
1879
- if (!Array.isArray(path)) {
1880
- return createModel(path);
2007
+ function useFieldModel(pathOrPaths) {
2008
+ if (!Array.isArray(pathOrPaths)) {
2009
+ return createModel(pathOrPaths);
1881
2010
  }
1882
- return path.map(createModel);
2011
+ return pathOrPaths.map(createModel);
1883
2012
  }
1884
2013
  /**
1885
2014
  * Sets the touched meta state on a field
1886
2015
  */
1887
2016
  function setFieldTouched(field, isTouched) {
1888
- const fieldInstance = fieldsByPath.value[field];
1889
- if (fieldInstance) {
1890
- applyFieldMutation(fieldInstance, f => f.setTouched(isTouched));
2017
+ const pathState = findPathState(field);
2018
+ if (pathState) {
2019
+ pathState.touched = isTouched;
1891
2020
  }
1892
2021
  }
1893
2022
  /**
@@ -1899,172 +2028,53 @@
1899
2028
  });
1900
2029
  }
1901
2030
  function resetField(field, state) {
1902
- const fieldInstance = fieldsByPath.value[field];
1903
- if (fieldInstance) {
1904
- applyFieldMutation(fieldInstance, f => f.resetField(state));
1905
- }
2031
+ var _a;
2032
+ const newValue = state && 'value' in state ? state.value : getFromPath(initialValues.value, field);
2033
+ setFieldInitialValue(field, klona(newValue));
2034
+ setFieldValue(field, newValue);
2035
+ setFieldTouched(field, (_a = state === null || state === void 0 ? void 0 : state.touched) !== null && _a !== void 0 ? _a : false);
2036
+ setFieldError(field, (state === null || state === void 0 ? void 0 : state.errors) || []);
1906
2037
  }
1907
2038
  /**
1908
2039
  * Resets all fields
1909
2040
  */
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;
2041
+ function resetForm(resetState) {
2042
+ const newValues = (resetState === null || resetState === void 0 ? void 0 : resetState.values) ? resetState.values : originalInitialValues.value;
1916
2043
  setInitialValues(newValues);
1917
2044
  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;
2045
+ mutateAllPathState(state => {
2046
+ var _a;
2047
+ state.validated = false;
2048
+ state.touched = ((_a = resetState === null || resetState === void 0 ? void 0 : resetState.touched) === null || _a === void 0 ? void 0 : _a[state.path]) || false;
2049
+ setFieldValue(state.path, getFromPath(newValues, state.path));
2050
+ setFieldError(state.path, undefined);
1925
2051
  });
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
2052
+ setErrors((resetState === null || resetState === void 0 ? void 0 : resetState.errors) || {});
2053
+ submitCount.value = (resetState === null || resetState === void 0 ? void 0 : resetState.submitCount) || 0;
2006
2054
  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
- }
2055
+ validate({ mode: 'silent' });
2049
2056
  });
2050
2057
  }
2051
2058
  async function validate(opts) {
2052
2059
  const mode = (opts === null || opts === void 0 ? void 0 : opts.mode) || 'force';
2053
2060
  if (mode === 'force') {
2054
- mutateAllFields(f => (f.meta.validated = true));
2061
+ mutateAllPathState(f => (f.validated = true));
2055
2062
  }
2056
2063
  if (formCtx.validateSchema) {
2057
2064
  return formCtx.validateSchema(mode);
2058
2065
  }
2059
2066
  // 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: [] });
2067
+ const validations = await Promise.all(pathStates.value.map(state => {
2068
+ if (!state.validate) {
2069
+ return Promise.resolve({
2070
+ key: state.path,
2071
+ valid: true,
2072
+ errors: [],
2073
+ });
2064
2074
  }
2065
- return fieldInstance.validate(opts).then((result) => {
2075
+ return state.validate(opts).then((result) => {
2066
2076
  return {
2067
- key: vue.unref(fieldInstance.name),
2077
+ key: state.path,
2068
2078
  valid: result.valid,
2069
2079
  errors: result.errors,
2070
2080
  };
@@ -2087,16 +2097,22 @@
2087
2097
  errors,
2088
2098
  };
2089
2099
  }
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 });
2100
+ async function validateField(path) {
2101
+ const state = findPathState(path);
2102
+ if (state) {
2103
+ state.validated = true;
2104
+ }
2105
+ if (schema) {
2106
+ const { results } = await validateSchema('validated-only');
2107
+ return results[path] || { errors: [], valid: true };
2108
+ }
2109
+ if (state === null || state === void 0 ? void 0 : state.validate) {
2110
+ return state.validate();
2095
2111
  }
2096
- if (Array.isArray(fieldInstance)) {
2097
- return fieldInstance.map(f => f.validate())[0];
2112
+ if (!state) {
2113
+ vue.warn(`field with path ${path} was not found`);
2098
2114
  }
2099
- return fieldInstance.validate();
2115
+ return Promise.resolve({ errors: [], valid: true });
2100
2116
  }
2101
2117
  function unsetInitialValue(path) {
2102
2118
  unsetPath(initialValues.value, path);
@@ -2105,8 +2121,8 @@
2105
2121
  * Sneaky function to set initial field values
2106
2122
  */
2107
2123
  function stageInitialValue(path, value, updateOriginal = false) {
2108
- setInPath(formValues, path, value);
2109
2124
  setFieldInitialValue(path, value);
2125
+ setInPath(formValues, path, value);
2110
2126
  if (updateOriginal && !(opts === null || opts === void 0 ? void 0 : opts.initialValues)) {
2111
2127
  setInPath(originalInitialValues.value, path, klona(value));
2112
2128
  }
@@ -2159,12 +2175,95 @@
2159
2175
  }
2160
2176
  // Provide injections
2161
2177
  vue.provide(FormContextKey, formCtx);
2162
- return Object.assign(Object.assign({}, formCtx), { handleReset: () => resetForm(), submitForm });
2178
+ function defineComponentBinds(path, config) {
2179
+ const pathState = findPathState(unravel(path)) || createPathState(path);
2180
+ const evalConfig = () => (isCallable(config) ? config(omit(pathState, PRIVATE_PATH_STATE_KEYS)) : config || {});
2181
+ function onBlur() {
2182
+ var _a;
2183
+ pathState.touched = true;
2184
+ const validateOnBlur = (_a = evalConfig().validateOnBlur) !== null && _a !== void 0 ? _a : getConfig().validateOnBlur;
2185
+ if (validateOnBlur) {
2186
+ validateField(pathState.path);
2187
+ }
2188
+ }
2189
+ function onUpdateModelValue(value) {
2190
+ var _a;
2191
+ setFieldValue(pathState.path, value);
2192
+ const validateOnModelUpdate = (_a = evalConfig().validateOnModelUpdate) !== null && _a !== void 0 ? _a : getConfig().validateOnModelUpdate;
2193
+ if (validateOnModelUpdate) {
2194
+ validateField(pathState.path);
2195
+ }
2196
+ }
2197
+ const props = vue.computed(() => {
2198
+ const base = {
2199
+ modelValue: pathState.value,
2200
+ 'onUpdate:modelValue': onUpdateModelValue,
2201
+ onBlur,
2202
+ };
2203
+ if (isCallable(config)) {
2204
+ return Object.assign(Object.assign({}, base), (config(pathState).props || {}));
2205
+ }
2206
+ if (config === null || config === void 0 ? void 0 : config.mapProps) {
2207
+ return Object.assign(Object.assign({}, base), config.mapProps(omit(pathState, PRIVATE_PATH_STATE_KEYS)));
2208
+ }
2209
+ return base;
2210
+ });
2211
+ return props;
2212
+ }
2213
+ function defineInputBinds(path, config) {
2214
+ const pathState = (findPathState(unravel(path)) || createPathState(path));
2215
+ const evalConfig = () => (isCallable(config) ? config(omit(pathState, PRIVATE_PATH_STATE_KEYS)) : config || {});
2216
+ function onBlur() {
2217
+ var _a;
2218
+ pathState.touched = true;
2219
+ const validateOnBlur = (_a = evalConfig().validateOnBlur) !== null && _a !== void 0 ? _a : getConfig().validateOnBlur;
2220
+ if (validateOnBlur) {
2221
+ validateField(pathState.path);
2222
+ }
2223
+ }
2224
+ function onInput(e) {
2225
+ var _a;
2226
+ const value = normalizeEventValue(e);
2227
+ setFieldValue(pathState.path, value);
2228
+ const validateOnInput = (_a = evalConfig().validateOnInput) !== null && _a !== void 0 ? _a : getConfig().validateOnInput;
2229
+ if (validateOnInput) {
2230
+ validateField(pathState.path);
2231
+ }
2232
+ }
2233
+ function onChange(e) {
2234
+ var _a;
2235
+ const value = normalizeEventValue(e);
2236
+ setFieldValue(pathState.path, value);
2237
+ const validateOnChange = (_a = evalConfig().validateOnChange) !== null && _a !== void 0 ? _a : getConfig().validateOnChange;
2238
+ if (validateOnChange) {
2239
+ validateField(pathState.path);
2240
+ }
2241
+ }
2242
+ const props = vue.computed(() => {
2243
+ const base = {
2244
+ value: pathState.value,
2245
+ onChange,
2246
+ onInput,
2247
+ onBlur,
2248
+ };
2249
+ if (isCallable(config)) {
2250
+ return Object.assign(Object.assign({}, base), (config(omit(pathState, PRIVATE_PATH_STATE_KEYS)).attrs || {}));
2251
+ }
2252
+ if (config === null || config === void 0 ? void 0 : config.mapAttrs) {
2253
+ return Object.assign(Object.assign({}, base), config.mapAttrs(omit(pathState, PRIVATE_PATH_STATE_KEYS)));
2254
+ }
2255
+ return base;
2256
+ });
2257
+ return props;
2258
+ }
2259
+ return Object.assign(Object.assign({}, formCtx), { handleReset: () => resetForm(), submitForm,
2260
+ defineComponentBinds,
2261
+ defineInputBinds });
2163
2262
  }
2164
2263
  /**
2165
2264
  * Manages form meta aggregation
2166
2265
  */
2167
- function useFormMeta(fieldsByPath, currentValues, initialValues, errors) {
2266
+ function useFormMeta(pathsState, currentValues, initialValues, errors) {
2168
2267
  const MERGE_STRATEGIES = {
2169
2268
  touched: 'some',
2170
2269
  pending: 'some',
@@ -2174,10 +2273,10 @@
2174
2273
  return !isEqual(currentValues, vue.unref(initialValues));
2175
2274
  });
2176
2275
  function calculateFlags() {
2177
- const fields = Object.values(fieldsByPath.value).flat(1).filter(Boolean);
2276
+ const states = pathsState.value;
2178
2277
  return keysOf(MERGE_STRATEGIES).reduce((acc, flag) => {
2179
2278
  const mergeMethod = MERGE_STRATEGIES[flag];
2180
- acc[flag] = fields[mergeMethod](field => field.meta[flag]);
2279
+ acc[flag] = states[mergeMethod](s => s[flag]);
2181
2280
  return acc;
2182
2281
  }, {});
2183
2282
  }
@@ -2195,7 +2294,7 @@
2195
2294
  /**
2196
2295
  * Manages the initial values prop
2197
2296
  */
2198
- function useFormInitialValues(fields, formValues, opts) {
2297
+ function useFormInitialValues(pathsState, formValues, opts) {
2199
2298
  const values = resolveInitialValues(opts);
2200
2299
  const providedValues = opts === null || opts === void 0 ? void 0 : opts.initialValues;
2201
2300
  // these are the mutable initial values as the fields are mounted/unmounted
@@ -2216,14 +2315,13 @@
2216
2315
  // those are excluded because it's unlikely you want to change the form values using initial values
2217
2316
  // we mostly watch them for API population or newly inserted fields
2218
2317
  // 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) {
2318
+ pathsState.value.forEach(state => {
2319
+ const wasTouched = state.touched;
2320
+ if (wasTouched) {
2223
2321
  return;
2224
2322
  }
2225
- const newValue = getFromPath(initialValues.value, fieldPath);
2226
- setInPath(formValues, fieldPath, klona(newValue));
2323
+ const newValue = getFromPath(initialValues.value, state.path);
2324
+ setInPath(formValues, state.path, klona(newValue));
2227
2325
  });
2228
2326
  }
2229
2327
  if (vue.isRef(providedValues)) {
@@ -2239,42 +2337,6 @@
2239
2337
  setInitialValues,
2240
2338
  };
2241
2339
  }
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
2340
 
2279
2341
  const FormImpl = vue.defineComponent({
2280
2342
  name: 'Form',
@@ -2455,7 +2517,10 @@
2455
2517
  }
2456
2518
  function initFields() {
2457
2519
  const currentValues = getCurrentValues();
2458
- fields.value = currentValues.map(createEntry);
2520
+ if (!Array.isArray(currentValues)) {
2521
+ return;
2522
+ }
2523
+ fields.value = currentValues.map((v, idx) => createEntry(v, idx, fields.value));
2459
2524
  updateEntryFlags();
2460
2525
  }
2461
2526
  initFields();
@@ -2467,7 +2532,13 @@
2467
2532
  entry.isLast = i === fieldsLength - 1;
2468
2533
  }
2469
2534
  }
2470
- function createEntry(value) {
2535
+ function createEntry(value, idx, currentFields) {
2536
+ // Skips the work by returning the current entry if it already exists
2537
+ // This should make the `key` prop stable and doesn't cause more re-renders than needed
2538
+ // The value is computed and should update anyways
2539
+ if (currentFields && !isNullOrUndefined(idx) && currentFields[idx]) {
2540
+ return currentFields[idx];
2541
+ }
2471
2542
  const key = entryCounter++;
2472
2543
  const entry = {
2473
2544
  key,
@@ -2504,8 +2575,9 @@
2504
2575
  }
2505
2576
  const newValue = [...pathValue];
2506
2577
  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);
2578
+ const fieldPath = pathName + `[${idx}]`;
2579
+ form.unsetInitialValue(fieldPath);
2580
+ setInPath(form.values, pathName, newValue);
2509
2581
  fields.value.splice(idx, 1);
2510
2582
  afterMutation();
2511
2583
  }
@@ -2518,8 +2590,8 @@
2518
2590
  }
2519
2591
  const newValue = [...normalizedPathValue];
2520
2592
  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);
2593
+ form.stageInitialValue(pathName + `[${newValue.length - 1}]`, value);
2594
+ setInPath(form.values, pathName, newValue);
2523
2595
  fields.value.push(createEntry(value));
2524
2596
  afterMutation();
2525
2597
  }
@@ -2538,7 +2610,7 @@
2538
2610
  const tempEntry = newFields[indexA];
2539
2611
  newFields[indexA] = newFields[indexB];
2540
2612
  newFields[indexB] = tempEntry;
2541
- form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, newValue);
2613
+ setInPath(form.values, pathName, newValue);
2542
2614
  fields.value = newFields;
2543
2615
  updateEntryFlags();
2544
2616
  }
@@ -2552,13 +2624,14 @@
2552
2624
  const newFields = [...fields.value];
2553
2625
  newValue.splice(idx, 0, value);
2554
2626
  newFields.splice(idx, 0, createEntry(value));
2555
- form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, newValue);
2627
+ setInPath(form.values, pathName, newValue);
2556
2628
  fields.value = newFields;
2557
2629
  afterMutation();
2558
2630
  }
2559
2631
  function replace(arr) {
2560
2632
  const pathName = vue.unref(arrayPath);
2561
- form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, arr);
2633
+ form.stageInitialValue(pathName, arr);
2634
+ setInPath(form.values, pathName, arr);
2562
2635
  initFields();
2563
2636
  afterMutation();
2564
2637
  }
@@ -2568,7 +2641,7 @@
2568
2641
  if (!Array.isArray(pathValue) || pathValue.length - 1 < idx) {
2569
2642
  return;
2570
2643
  }
2571
- form === null || form === void 0 ? void 0 : form.setFieldValue(`${pathName}[${idx}]`, value);
2644
+ setInPath(form.values, `${pathName}[${idx}]`, value);
2572
2645
  form === null || form === void 0 ? void 0 : form.validate({ mode: 'validated-only' });
2573
2646
  }
2574
2647
  function prepend(value) {
@@ -2579,8 +2652,8 @@
2579
2652
  return;
2580
2653
  }
2581
2654
  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);
2655
+ form.stageInitialValue(pathName + `[${newValue.length - 1}]`, value);
2656
+ setInPath(form.values, pathName, newValue);
2584
2657
  fields.value.unshift(createEntry(value));
2585
2658
  afterMutation();
2586
2659
  }
@@ -2598,7 +2671,7 @@
2598
2671
  const movedValue = newValue[oldIdx];
2599
2672
  newValue.splice(oldIdx, 1);
2600
2673
  newValue.splice(newIdx, 0, movedValue);
2601
- form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, newValue);
2674
+ setInPath(form.values, pathName, newValue);
2602
2675
  fields.value = newFields;
2603
2676
  afterMutation();
2604
2677
  }
@@ -2737,17 +2810,13 @@
2737
2810
  * If a field is dirty or not
2738
2811
  */
2739
2812
  function useIsFieldDirty(path) {
2740
- const form = injectWithSelf(FormContextKey);
2741
- let field = path ? undefined : vue.inject(FieldContextKey);
2813
+ const fieldOrPath = resolveFieldOrPathState(path);
2742
2814
  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`);
2815
+ var _a, _b;
2816
+ if (!fieldOrPath) {
2748
2817
  return false;
2749
2818
  }
2750
- return field.meta.dirty;
2819
+ 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
2820
  });
2752
2821
  }
2753
2822
 
@@ -2755,17 +2824,13 @@
2755
2824
  * If a field is touched or not
2756
2825
  */
2757
2826
  function useIsFieldTouched(path) {
2758
- const form = injectWithSelf(FormContextKey);
2759
- let field = path ? undefined : vue.inject(FieldContextKey);
2827
+ const fieldOrPath = resolveFieldOrPathState(path);
2760
2828
  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`);
2829
+ var _a, _b;
2830
+ if (!fieldOrPath) {
2766
2831
  return false;
2767
2832
  }
2768
- return field.meta.touched;
2833
+ 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
2834
  });
2770
2835
  }
2771
2836
 
@@ -2773,17 +2838,13 @@
2773
2838
  * If a field is validated and is valid
2774
2839
  */
2775
2840
  function useIsFieldValid(path) {
2776
- const form = injectWithSelf(FormContextKey);
2777
- let field = path ? undefined : vue.inject(FieldContextKey);
2841
+ const fieldOrPath = resolveFieldOrPathState(path);
2778
2842
  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`);
2843
+ var _a, _b;
2844
+ if (!fieldOrPath) {
2784
2845
  return false;
2785
2846
  }
2786
- return field.meta.valid;
2847
+ 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
2848
  });
2788
2849
  }
2789
2850
 
@@ -2806,19 +2867,19 @@
2806
2867
  */
2807
2868
  function useValidateField(path) {
2808
2869
  const form = injectWithSelf(FormContextKey);
2809
- let field = path ? undefined : vue.inject(FieldContextKey);
2870
+ const field = path ? undefined : vue.inject(FieldContextKey);
2810
2871
  return function validateField() {
2811
- if (path) {
2812
- field = normalizeField(form === null || form === void 0 ? void 0 : form.fieldsByPath.value[vue.unref(path)]);
2872
+ if (field) {
2873
+ return field.validate();
2813
2874
  }
2814
- if (!field) {
2815
- warn(`field with name ${vue.unref(path)} was not found`);
2816
- return Promise.resolve({
2817
- errors: [],
2818
- valid: true,
2819
- });
2875
+ if (form && path) {
2876
+ return form === null || form === void 0 ? void 0 : form.validateField(vue.unref(path));
2820
2877
  }
2821
- return field.validate();
2878
+ warn(`field with name ${vue.unref(path)} was not found`);
2879
+ return Promise.resolve({
2880
+ errors: [],
2881
+ valid: true,
2882
+ });
2822
2883
  };
2823
2884
  }
2824
2885