vee-validate 4.4.10 → 4.5.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.4.10
2
+ * vee-validate v4.5.0
3
3
  * (c) 2021 Abdelrahman Awad
4
4
  * @license MIT
5
5
  */
@@ -50,8 +50,6 @@
50
50
  }
51
51
 
52
52
  const FormContextKey = Symbol('vee-validate-form');
53
- const FormErrorsKey = Symbol('vee-validate-form-errors');
54
- const FormInitialValuesKey = Symbol('vee-validate-form-initial-values');
55
53
  const FieldContextKey = Symbol('vee-validate-field-instance');
56
54
  const IS_ABSENT = Symbol('Default empty value');
57
55
 
@@ -151,17 +149,14 @@
151
149
  }
152
150
  return path;
153
151
  }
154
- /**
155
- * Gets a nested property value from an object
156
- */
157
- function getFromPath(object, path, fallback = undefined) {
152
+ function getFromPath(object, path, fallback) {
158
153
  if (!object) {
159
154
  return fallback;
160
155
  }
161
156
  if (isNotNestedPath(path)) {
162
157
  return object[cleanupNonNestedPath(path)];
163
158
  }
164
- const resolvedValue = path
159
+ const resolvedValue = (path || '')
165
160
  .split(/\.|\[(\d+)\]/)
166
161
  .filter(Boolean)
167
162
  .reduce((acc, propKey) => {
@@ -265,20 +260,6 @@
265
260
  }
266
261
  return field;
267
262
  }
268
- /**
269
- * Applies a mutation function on a field or field group
270
- */
271
- function applyFieldMutation(field, mutation, onlyFirst = false) {
272
- if (!Array.isArray(field)) {
273
- mutation(field);
274
- return;
275
- }
276
- if (onlyFirst) {
277
- mutation(field[0]);
278
- return;
279
- }
280
- field.forEach(mutation);
281
- }
282
263
  function resolveNextCheckboxValue(currentValue, checkedValue, uncheckedValue) {
283
264
  if (Array.isArray(currentValue)) {
284
265
  const newVal = [...currentValue];
@@ -287,6 +268,24 @@
287
268
  return newVal;
288
269
  }
289
270
  return currentValue === checkedValue ? uncheckedValue : checkedValue;
271
+ }
272
+ function debounceAsync(inner, ms = 0) {
273
+ let timer = null;
274
+ let resolves = [];
275
+ return function (...args) {
276
+ // Run the function after a certain amount of time
277
+ if (timer) {
278
+ window.clearTimeout(timer);
279
+ }
280
+ timer = window.setTimeout(() => {
281
+ // Get the result of the inner function, then apply it to the resolve function of
282
+ // each promise that has been created since the last time the inner function was run
283
+ const result = inner(...args);
284
+ resolves.forEach(r => r(result));
285
+ resolves = [];
286
+ }, ms);
287
+ return new Promise(resolve => resolves.push(resolve));
288
+ };
290
289
  }
291
290
 
292
291
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -777,21 +776,162 @@
777
776
  };
778
777
 
779
778
  let ID_COUNTER = 0;
779
+ function useFieldState(path, init) {
780
+ const { value, initialValue, setInitialValue } = _useFieldValue(path, init.modelValue, !init.standalone);
781
+ const { errorMessage, errors, setErrors } = _useFieldErrors(path, !init.standalone);
782
+ const meta = _useFieldMeta(value, initialValue, errors);
783
+ const id = ID_COUNTER >= Number.MAX_SAFE_INTEGER ? 0 : ++ID_COUNTER;
784
+ function setState(state) {
785
+ var _a;
786
+ if ('value' in state) {
787
+ value.value = state.value;
788
+ }
789
+ if ('errors' in state) {
790
+ setErrors(state.errors);
791
+ }
792
+ if ('touched' in state) {
793
+ meta.touched = (_a = state.touched) !== null && _a !== void 0 ? _a : meta.touched;
794
+ }
795
+ if ('initialValue' in state) {
796
+ setInitialValue(state.initialValue);
797
+ }
798
+ }
799
+ return {
800
+ id,
801
+ path,
802
+ value,
803
+ initialValue,
804
+ meta,
805
+ errors,
806
+ errorMessage,
807
+ setState,
808
+ };
809
+ }
810
+ /**
811
+ * Creates the field value and resolves the initial value
812
+ */
813
+ function _useFieldValue(path, modelValue, shouldInjectForm) {
814
+ const form = shouldInjectForm ? injectWithSelf(FormContextKey, undefined) : undefined;
815
+ const modelRef = vue.ref(vue.unref(modelValue));
816
+ function resolveInitialValue() {
817
+ if (!form) {
818
+ return vue.unref(modelRef);
819
+ }
820
+ return getFromPath(form.meta.value.initialValues, vue.unref(path), vue.unref(modelRef));
821
+ }
822
+ function setInitialValue(value) {
823
+ if (!form) {
824
+ modelRef.value = value;
825
+ return;
826
+ }
827
+ form.setFieldInitialValue(vue.unref(path), value);
828
+ }
829
+ const initialValue = vue.computed(resolveInitialValue);
830
+ // if no form is associated, use a regular ref.
831
+ if (!form) {
832
+ const value = vue.ref(resolveInitialValue());
833
+ return {
834
+ value,
835
+ initialValue,
836
+ setInitialValue,
837
+ };
838
+ }
839
+ // to set the initial value, first check if there is a current value, if there is then use it.
840
+ // otherwise use the configured initial value if it exists.
841
+ // prioritize model value over form values
842
+ // #3429
843
+ const currentValue = modelValue ? vue.unref(modelValue) : getFromPath(form.values, vue.unref(path), vue.unref(initialValue));
844
+ form.stageInitialValue(vue.unref(path), currentValue);
845
+ // otherwise use a computed setter that triggers the `setFieldValue`
846
+ const value = vue.computed({
847
+ get() {
848
+ return getFromPath(form.values, vue.unref(path));
849
+ },
850
+ set(newVal) {
851
+ form.setFieldValue(vue.unref(path), newVal);
852
+ },
853
+ });
854
+ return {
855
+ value,
856
+ initialValue,
857
+ setInitialValue,
858
+ };
859
+ }
860
+ /**
861
+ * Creates meta flags state and some associated effects with them
862
+ */
863
+ function _useFieldMeta(currentValue, initialValue, errors) {
864
+ const meta = vue.reactive({
865
+ touched: false,
866
+ pending: false,
867
+ valid: true,
868
+ validated: !!vue.unref(errors).length,
869
+ initialValue: vue.computed(() => vue.unref(initialValue)),
870
+ dirty: vue.computed(() => {
871
+ return !es6(vue.unref(currentValue), vue.unref(initialValue));
872
+ }),
873
+ });
874
+ vue.watch(errors, value => {
875
+ meta.valid = !value.length;
876
+ }, {
877
+ immediate: true,
878
+ flush: 'sync',
879
+ });
880
+ return meta;
881
+ }
882
+ /**
883
+ * Creates the error message state for the field state
884
+ */
885
+ function _useFieldErrors(path, shouldInjectForm) {
886
+ const form = shouldInjectForm ? injectWithSelf(FormContextKey, undefined) : undefined;
887
+ function normalizeErrors(messages) {
888
+ if (!messages) {
889
+ return [];
890
+ }
891
+ return Array.isArray(messages) ? messages : [messages];
892
+ }
893
+ if (!form) {
894
+ const errors = vue.ref([]);
895
+ return {
896
+ errors,
897
+ errorMessage: vue.computed(() => errors.value[0]),
898
+ setErrors: (messages) => {
899
+ errors.value = normalizeErrors(messages);
900
+ },
901
+ };
902
+ }
903
+ const errors = vue.computed(() => form.errorBag.value[vue.unref(path)] || []);
904
+ return {
905
+ errors,
906
+ errorMessage: vue.computed(() => errors.value[0]),
907
+ setErrors: (messages) => {
908
+ form.setFieldErrorBag(vue.unref(path), normalizeErrors(messages));
909
+ },
910
+ };
911
+ }
912
+
780
913
  /**
781
914
  * Creates a field composite.
782
915
  */
783
916
  function useField(name, rules, opts) {
784
- const fid = ID_COUNTER >= Number.MAX_SAFE_INTEGER ? 0 : ++ID_COUNTER;
785
- const { initialValue, validateOnMount, bails, type, checkedValue, label, validateOnValueUpdate, uncheckedValue, standalone, } = normalizeOptions(vue.unref(name), opts);
917
+ if (hasCheckedAttr(opts === null || opts === void 0 ? void 0 : opts.type)) {
918
+ return useCheckboxField(name, rules, opts);
919
+ }
920
+ return _useField(name, rules, opts);
921
+ }
922
+ function _useField(name, rules, opts) {
923
+ const { initialValue: modelValue, validateOnMount, bails, type, checkedValue, label, validateOnValueUpdate, uncheckedValue, standalone, } = normalizeOptions(vue.unref(name), opts);
786
924
  const form = !standalone ? injectWithSelf(FormContextKey) : undefined;
787
- const { meta, errors, errorMessage, handleBlur, handleInput, resetValidationState, setValidationState, setErrors, value, checked, } = useValidationState({
788
- name,
789
- initValue: initialValue,
790
- form,
791
- type,
792
- checkedValue,
925
+ const { id, value, initialValue, meta, setState, errors, errorMessage } = useFieldState(name, {
926
+ modelValue,
793
927
  standalone,
794
928
  });
929
+ /**
930
+ * Handles common onBlur meta update
931
+ */
932
+ const handleBlur = () => {
933
+ meta.touched = true;
934
+ };
795
935
  const normalizedRules = vue.computed(() => {
796
936
  let rulesValue = vue.unref(rules);
797
937
  const schema = vue.unref(form === null || form === void 0 ? void 0 : form.schema);
@@ -818,27 +958,30 @@
818
958
  meta.pending = true;
819
959
  meta.validated = true;
820
960
  const result = await validateCurrentValue('validated-only');
961
+ setState({ errors: result.errors });
821
962
  meta.pending = false;
822
- return setValidationState(result);
963
+ return result;
823
964
  }
824
965
  async function validateValidStateOnly() {
825
966
  const result = await validateCurrentValue('silent');
826
967
  meta.valid = result.valid;
968
+ return result;
827
969
  }
828
- // Common input/change event handler
829
- const handleChange = (e, shouldValidate = true) => {
830
- var _a, _b;
831
- if (checked && checked.value === ((_b = (_a = e) === null || _a === void 0 ? void 0 : _a.target) === null || _b === void 0 ? void 0 : _b.checked)) {
832
- return;
970
+ function validate$1(opts) {
971
+ if (!(opts === null || opts === void 0 ? void 0 : opts.mode) || (opts === null || opts === void 0 ? void 0 : opts.mode) === 'force') {
972
+ return validateWithStateMutation();
833
973
  }
834
- let newValue = normalizeEventValue(e);
835
- // Single checkbox field without a form to toggle it's value
836
- if (checked && type === 'checkbox' && !form) {
837
- newValue = resolveNextCheckboxValue(value.value, vue.unref(checkedValue), vue.unref(uncheckedValue));
974
+ if ((opts === null || opts === void 0 ? void 0 : opts.mode) === 'validated-only') {
975
+ return validateWithStateMutation();
838
976
  }
977
+ return validateValidStateOnly();
978
+ }
979
+ // Common input/change event handler
980
+ const handleChange = (e, shouldValidate = true) => {
981
+ const newValue = normalizeEventValue(e);
839
982
  value.value = newValue;
840
983
  if (!validateOnValueUpdate && shouldValidate) {
841
- return validateWithStateMutation();
984
+ validateWithStateMutation();
842
985
  }
843
986
  };
844
987
  // Runs the initial validation
@@ -863,17 +1006,31 @@
863
1006
  }
864
1007
  watchValue();
865
1008
  function resetField(state) {
1009
+ var _a;
866
1010
  unwatchValue === null || unwatchValue === void 0 ? void 0 : unwatchValue();
867
- resetValidationState(state);
1011
+ const newValue = state && 'value' in state ? state.value : initialValue.value;
1012
+ setState({
1013
+ value: klona(newValue),
1014
+ initialValue: klona(newValue),
1015
+ touched: (_a = state === null || state === void 0 ? void 0 : state.touched) !== null && _a !== void 0 ? _a : false,
1016
+ errors: (state === null || state === void 0 ? void 0 : state.errors) || [],
1017
+ });
1018
+ meta.pending = false;
1019
+ meta.validated = false;
868
1020
  validateValidStateOnly();
869
1021
  // need to watch at next tick to avoid triggering the value watcher
870
1022
  vue.nextTick(() => {
871
1023
  watchValue();
872
1024
  });
873
1025
  }
1026
+ function setValue(newValue) {
1027
+ value.value = newValue;
1028
+ }
1029
+ function setErrors(errors) {
1030
+ setState({ errors: Array.isArray(errors) ? errors : [errors] });
1031
+ }
874
1032
  const field = {
875
- idx: -1,
876
- fid,
1033
+ id,
877
1034
  name,
878
1035
  label,
879
1036
  value,
@@ -883,17 +1040,16 @@
883
1040
  type,
884
1041
  checkedValue,
885
1042
  uncheckedValue,
886
- checked,
887
1043
  bails,
888
1044
  resetField,
889
1045
  handleReset: () => resetField(),
890
- validate: validateWithStateMutation,
1046
+ validate: validate$1,
891
1047
  handleChange,
892
1048
  handleBlur,
893
- handleInput,
894
- setValidationState,
1049
+ setState,
895
1050
  setTouched,
896
1051
  setErrors,
1052
+ setValue,
897
1053
  };
898
1054
  vue.provide(FieldContextKey, field);
899
1055
  if (vue.isRef(rules) && typeof vue.unref(rules) !== 'function') {
@@ -901,7 +1057,7 @@
901
1057
  if (es6(value, oldValue)) {
902
1058
  return;
903
1059
  }
904
- return validateWithStateMutation();
1060
+ meta.validated ? validateWithStateMutation() : validateValidStateOnly();
905
1061
  }, {
906
1062
  deep: true,
907
1063
  });
@@ -944,7 +1100,7 @@
944
1100
  }
945
1101
  const shouldValidate = !es6(deps, oldDeps);
946
1102
  if (shouldValidate) {
947
- meta.dirty ? validateWithStateMutation() : validateValidStateOnly();
1103
+ meta.validated ? validateWithStateMutation() : validateValidStateOnly();
948
1104
  }
949
1105
  });
950
1106
  return field;
@@ -969,104 +1125,6 @@
969
1125
  const checkedValue = 'valueProp' in opts ? opts.valueProp : opts.checkedValue;
970
1126
  return Object.assign(Object.assign(Object.assign({}, defaults()), (opts || {})), { checkedValue });
971
1127
  }
972
- /**
973
- * Manages the validation state of a field.
974
- */
975
- function useValidationState({ name, initValue, form, type, checkedValue, standalone, }) {
976
- const { errors, errorMessage, setErrors } = useFieldErrors(name, form);
977
- const formInitialValues = standalone ? undefined : injectWithSelf(FormInitialValuesKey, undefined);
978
- // clones the ref value to a mutable version
979
- const initialValueRef = vue.ref(vue.unref(initValue));
980
- const initialValue = vue.computed(() => {
981
- return getFromPath(vue.unref(formInitialValues), vue.unref(name), vue.unref(initialValueRef));
982
- });
983
- const value = useFieldValue$1(initialValue, name, form);
984
- const meta = useFieldMeta(initialValue, value, errors);
985
- const checked = hasCheckedAttr(type)
986
- ? vue.computed(() => {
987
- if (Array.isArray(value.value)) {
988
- return value.value.includes(vue.unref(checkedValue));
989
- }
990
- return vue.unref(checkedValue) === value.value;
991
- })
992
- : undefined;
993
- /**
994
- * Handles common onBlur meta update
995
- */
996
- const handleBlur = () => {
997
- meta.touched = true;
998
- };
999
- /**
1000
- * Handles common on blur events
1001
- * @deprecated You should use `handleChange` instead
1002
- */
1003
- const handleInput = (e) => {
1004
- // Checkboxes/Radio will emit a `change` event anyway, custom components will use `update:modelValue`
1005
- // so this is redundant
1006
- if (!hasCheckedAttr(type)) {
1007
- value.value = normalizeEventValue(e);
1008
- }
1009
- };
1010
- // Updates the validation state with the validation result
1011
- function setValidationState(result) {
1012
- setErrors(result.errors);
1013
- return result;
1014
- }
1015
- // Resets the validation state
1016
- function resetValidationState(state) {
1017
- var _a;
1018
- const fieldPath = vue.unref(name);
1019
- const newValue = state && 'value' in state
1020
- ? state.value
1021
- : getFromPath(vue.unref(formInitialValues), fieldPath, vue.unref(initValue));
1022
- if (form) {
1023
- form.setFieldValue(fieldPath, newValue, { force: true });
1024
- form.setFieldInitialValue(fieldPath, newValue);
1025
- }
1026
- else {
1027
- value.value = klona(newValue);
1028
- initialValueRef.value = klona(newValue);
1029
- }
1030
- setErrors((state === null || state === void 0 ? void 0 : state.errors) || []);
1031
- meta.touched = (_a = state === null || state === void 0 ? void 0 : state.touched) !== null && _a !== void 0 ? _a : false;
1032
- meta.pending = false;
1033
- meta.validated = false;
1034
- }
1035
- return {
1036
- meta,
1037
- errors,
1038
- errorMessage,
1039
- setErrors,
1040
- setValidationState,
1041
- resetValidationState,
1042
- handleBlur,
1043
- handleInput,
1044
- value,
1045
- checked,
1046
- };
1047
- }
1048
- /**
1049
- * Exposes meta flags state and some associated actions with them.
1050
- */
1051
- function useFieldMeta(initialValue, currentValue, errors) {
1052
- const meta = vue.reactive({
1053
- touched: false,
1054
- pending: false,
1055
- valid: true,
1056
- validated: !!vue.unref(errors).length,
1057
- initialValue: vue.computed(() => vue.unref(initialValue)),
1058
- dirty: vue.computed(() => {
1059
- return !es6(vue.unref(currentValue), vue.unref(initialValue));
1060
- }),
1061
- });
1062
- vue.watch(errors, value => {
1063
- meta.valid = !value.length;
1064
- }, {
1065
- immediate: true,
1066
- flush: 'sync',
1067
- });
1068
- return meta;
1069
- }
1070
1128
  /**
1071
1129
  * Extracts the validation rules from a schema
1072
1130
  */
@@ -1078,49 +1136,40 @@
1078
1136
  // there is a key on the schema object for this field
1079
1137
  return schema[fieldName];
1080
1138
  }
1081
- /**
1082
- * Manages the field value
1083
- */
1084
- function useFieldValue$1(initialValue, path, form) {
1085
- // if no form is associated, use a regular ref.
1086
- if (!form) {
1087
- return vue.ref(vue.unref(initialValue));
1088
- }
1089
- // to set the initial value, first check if there is a current value, if there is then use it.
1090
- // otherwise use the configured initial value if it exists.
1091
- // #3429
1092
- const currentValue = getFromPath(form.values, vue.unref(path), vue.unref(initialValue));
1093
- form.stageInitialValue(vue.unref(path), currentValue === undefined ? vue.unref(initialValue) : currentValue);
1094
- // otherwise use a computed setter that triggers the `setFieldValue`
1095
- const value = vue.computed({
1096
- get() {
1097
- return getFromPath(form.values, vue.unref(path));
1098
- },
1099
- set(newVal) {
1100
- form.setFieldValue(vue.unref(path), newVal);
1101
- },
1102
- });
1103
- return value;
1104
- }
1105
- function useFieldErrors(path, form) {
1106
- if (!form) {
1107
- const errors = vue.ref([]);
1108
- return {
1109
- errors: vue.computed(() => errors.value),
1110
- errorMessage: vue.computed(() => errors.value[0]),
1111
- setErrors: (messages) => {
1112
- errors.value = Array.isArray(messages) ? messages : [messages];
1113
- },
1114
- };
1139
+ function useCheckboxField(name, rules, opts) {
1140
+ const form = !(opts === null || opts === void 0 ? void 0 : opts.standalone) ? injectWithSelf(FormContextKey) : undefined;
1141
+ const checkedValue = opts === null || opts === void 0 ? void 0 : opts.checkedValue;
1142
+ const uncheckedValue = opts === null || opts === void 0 ? void 0 : opts.uncheckedValue;
1143
+ function patchCheckboxApi(field) {
1144
+ const handleChange = field.handleChange;
1145
+ const checked = vue.computed(() => {
1146
+ const currentValue = vue.unref(field.value);
1147
+ const checkedVal = vue.unref(checkedValue);
1148
+ return Array.isArray(currentValue) ? currentValue.includes(checkedVal) : checkedVal === currentValue;
1149
+ });
1150
+ function handleCheckboxChange(e, shouldValidate = true) {
1151
+ var _a, _b;
1152
+ if (checked.value === ((_b = (_a = e) === null || _a === void 0 ? void 0 : _a.target) === null || _b === void 0 ? void 0 : _b.checked)) {
1153
+ return;
1154
+ }
1155
+ let newValue = normalizeEventValue(e);
1156
+ // Single checkbox field without a form to toggle it's value
1157
+ if (!form) {
1158
+ newValue = resolveNextCheckboxValue(vue.unref(field.value), vue.unref(checkedValue), vue.unref(uncheckedValue));
1159
+ }
1160
+ handleChange(newValue, shouldValidate);
1161
+ }
1162
+ vue.onBeforeUnmount(() => {
1163
+ // toggles the checkbox value if it was checked
1164
+ if (checked.value) {
1165
+ handleCheckboxChange(vue.unref(checkedValue), false);
1166
+ }
1167
+ });
1168
+ return Object.assign(Object.assign({}, field), { checked,
1169
+ checkedValue,
1170
+ uncheckedValue, handleChange: handleCheckboxChange });
1115
1171
  }
1116
- const errors = vue.computed(() => form.errorBag.value[vue.unref(path)] || []);
1117
- return {
1118
- errors,
1119
- errorMessage: vue.computed(() => errors.value[0]),
1120
- setErrors: (messages) => {
1121
- form.setFieldErrorBag(vue.unref(path), messages);
1122
- },
1123
- };
1172
+ return patchCheckboxApi(_useField(name, rules, opts));
1124
1173
  }
1125
1174
 
1126
1175
  const Field = vue.defineComponent({
@@ -1194,7 +1243,7 @@
1194
1243
  const label = vue.toRef(props, 'label');
1195
1244
  const uncheckedValue = vue.toRef(props, 'uncheckedValue');
1196
1245
  const hasModelEvents = isPropPresent(props, 'onUpdate:modelValue');
1197
- const { errors, value, errorMessage, validate: validateField, handleChange, handleBlur, handleInput, setTouched, resetField, handleReset, meta, checked, setErrors, } = useField(name, rules, {
1246
+ const { errors, value, errorMessage, validate: validateField, handleChange, handleBlur, setTouched, resetField, handleReset, meta, checked, setErrors, } = useField(name, rules, {
1198
1247
  validateOnMount: props.validateOnMount,
1199
1248
  bails: props.bails,
1200
1249
  standalone: props.standalone,
@@ -1213,8 +1262,13 @@
1213
1262
  ctx.emit('update:modelValue', value.value);
1214
1263
  }
1215
1264
  : handleChange;
1265
+ const handleInput = (e) => {
1266
+ if (!hasCheckedAttr(ctx.attrs.type)) {
1267
+ value.value = normalizeEventValue(e);
1268
+ }
1269
+ };
1216
1270
  const onInputHandler = hasModelEvents
1217
- ? function handleChangeWithModel(e) {
1271
+ ? function handleInputWithModel(e) {
1218
1272
  handleInput(e);
1219
1273
  ctx.emit('update:modelValue', value.value);
1220
1274
  }
@@ -1273,6 +1327,13 @@
1273
1327
  setErrors,
1274
1328
  };
1275
1329
  }
1330
+ ctx.expose({
1331
+ setErrors,
1332
+ setTouched,
1333
+ reset: resetField,
1334
+ validate: validateField,
1335
+ handleChange,
1336
+ });
1276
1337
  return () => {
1277
1338
  const tag = vue.resolveDynamicComponent(resolveTag(props, ctx));
1278
1339
  const children = normalizeChildren(tag, ctx, slotProps);
@@ -1315,40 +1376,19 @@
1315
1376
  return isPropPresent(props, 'modelValue') ? props.modelValue : undefined;
1316
1377
  }
1317
1378
 
1379
+ let FORM_COUNTER = 0;
1318
1380
  function useForm(opts) {
1319
- // A flat array containing field references
1320
- const fields = vue.ref([]);
1381
+ const formId = FORM_COUNTER++;
1382
+ // A lookup containing fields or field groups
1383
+ const fieldsByPath = vue.ref({});
1321
1384
  // If the form is currently submitting
1322
1385
  const isSubmitting = vue.ref(false);
1323
- // a field map object useful for faster access of fields
1324
- const fieldsById = vue.computed(() => {
1325
- return fields.value.reduce((acc, field) => {
1326
- const fieldPath = vue.unref(field.name);
1327
- // if the field was not added before
1328
- if (!acc[fieldPath]) {
1329
- acc[fieldPath] = field;
1330
- field.idx = -1;
1331
- return acc;
1332
- }
1333
- // if the same name is detected
1334
- const existingField = acc[fieldPath];
1335
- if (!Array.isArray(existingField)) {
1336
- existingField.idx = 0;
1337
- acc[fieldPath] = [existingField];
1338
- }
1339
- const fieldGroup = acc[fieldPath];
1340
- field.idx = fieldGroup.length;
1341
- fieldGroup.push(field);
1342
- return acc;
1343
- }, {});
1344
- });
1345
1386
  // The number of times the user tried to submit the form
1346
1387
  const submitCount = vue.ref(0);
1388
+ // dictionary for field arrays to receive various signals like reset
1389
+ const fieldArraysLookup = {};
1347
1390
  // a private ref for all form values
1348
1391
  const formValues = vue.reactive(klona(vue.unref(opts === null || opts === void 0 ? void 0 : opts.initialValues) || {}));
1349
- // a lookup to keep track of values by their field ids
1350
- // this is important because later we need it if fields swap names
1351
- const valuesByFid = {};
1352
1392
  // the source of errors for the form fields
1353
1393
  const { errorBag, setErrorBag, setFieldErrorBag } = useErrorBag(opts === null || opts === void 0 ? void 0 : opts.initialErrors);
1354
1394
  // Gets the first error of each field
@@ -1361,12 +1401,19 @@
1361
1401
  return acc;
1362
1402
  }, {});
1363
1403
  });
1404
+ function getFirstFieldAtPath(path) {
1405
+ const fieldOrGroup = fieldsByPath.value[path];
1406
+ return Array.isArray(fieldOrGroup) ? fieldOrGroup[0] : fieldOrGroup;
1407
+ }
1408
+ function fieldExists(path) {
1409
+ return !!fieldsByPath.value[path];
1410
+ }
1364
1411
  /**
1365
1412
  * Holds a computed reference to all fields names and labels
1366
1413
  */
1367
1414
  const fieldNames = vue.computed(() => {
1368
- return keysOf(fieldsById.value).reduce((names, path) => {
1369
- const field = normalizeField(fieldsById.value[path]);
1415
+ return keysOf(fieldsByPath.value).reduce((names, path) => {
1416
+ const field = getFirstFieldAtPath(path);
1370
1417
  if (field) {
1371
1418
  names[path] = vue.unref(field.label || field.name) || '';
1372
1419
  }
@@ -1374,9 +1421,9 @@
1374
1421
  }, {});
1375
1422
  });
1376
1423
  const fieldBailsMap = vue.computed(() => {
1377
- return keysOf(fieldsById.value).reduce((map, path) => {
1424
+ return keysOf(fieldsByPath.value).reduce((map, path) => {
1378
1425
  var _a;
1379
- const field = normalizeField(fieldsById.value[path]);
1426
+ const field = getFirstFieldAtPath(path);
1380
1427
  if (field) {
1381
1428
  map[path] = (_a = field.bails) !== null && _a !== void 0 ? _a : true;
1382
1429
  }
@@ -1387,18 +1434,21 @@
1387
1434
  // we need this to process initial errors then unset them
1388
1435
  const initialErrors = Object.assign({}, ((opts === null || opts === void 0 ? void 0 : opts.initialErrors) || {}));
1389
1436
  // initial form values
1390
- const { readonlyInitialValues, initialValues, setInitialValues } = useFormInitialValues(fieldsById, formValues, opts === null || opts === void 0 ? void 0 : opts.initialValues);
1437
+ const { initialValues, originalInitialValues, setInitialValues } = useFormInitialValues(fieldsByPath, formValues, opts === null || opts === void 0 ? void 0 : opts.initialValues);
1391
1438
  // form meta aggregations
1392
- const meta = useFormMeta(fields, formValues, readonlyInitialValues, errors);
1439
+ const meta = useFormMeta(fieldsByPath, formValues, initialValues, errors);
1393
1440
  const schema = opts === null || opts === void 0 ? void 0 : opts.validationSchema;
1394
1441
  const formCtx = {
1395
- fieldsById,
1442
+ formId,
1443
+ fieldsByPath,
1396
1444
  values: formValues,
1397
1445
  errorBag,
1446
+ errors,
1398
1447
  schema,
1399
1448
  submitCount,
1400
1449
  meta,
1401
1450
  isSubmitting,
1451
+ fieldArraysLookup,
1402
1452
  validateSchema: vue.unref(schema) ? validateSchema : undefined,
1403
1453
  validate,
1404
1454
  register: registerField,
@@ -1414,8 +1464,18 @@
1414
1464
  resetForm,
1415
1465
  handleSubmit,
1416
1466
  stageInitialValue,
1467
+ unsetInitialValue,
1417
1468
  setFieldInitialValue,
1418
1469
  };
1470
+ function isFieldGroup(fieldOrGroup) {
1471
+ return Array.isArray(fieldOrGroup);
1472
+ }
1473
+ function applyFieldMutation(fieldOrGroup, mutation) {
1474
+ if (Array.isArray(fieldOrGroup)) {
1475
+ return fieldOrGroup.forEach(mutation);
1476
+ }
1477
+ return mutation(fieldOrGroup);
1478
+ }
1419
1479
  /**
1420
1480
  * Manually sets an error message on a specific field
1421
1481
  */
@@ -1433,7 +1493,7 @@
1433
1493
  */
1434
1494
  function setFieldValue(field, value, { force } = { force: false }) {
1435
1495
  var _a;
1436
- const fieldInstance = fieldsById.value[field];
1496
+ const fieldInstance = fieldsByPath.value[field];
1437
1497
  const clonedValue = klona(value);
1438
1498
  // field wasn't found, create a virtual field as a placeholder
1439
1499
  if (!fieldInstance) {
@@ -1441,28 +1501,17 @@
1441
1501
  return;
1442
1502
  }
1443
1503
  // Multiple checkboxes, and only one of them got updated
1444
- if (Array.isArray(fieldInstance) && ((_a = fieldInstance[0]) === null || _a === void 0 ? void 0 : _a.type) === 'checkbox' && !Array.isArray(value)) {
1445
- const newVal = klona(resolveNextCheckboxValue(getFromPath(formValues, field) || [], value, undefined));
1446
- setInPath(formValues, field, newVal);
1447
- fieldInstance.forEach(fieldItem => {
1448
- valuesByFid[fieldItem.fid] = newVal;
1449
- });
1504
+ if (isFieldGroup(fieldInstance) && ((_a = fieldInstance[0]) === null || _a === void 0 ? void 0 : _a.type) === 'checkbox' && !Array.isArray(value)) {
1505
+ const newValue = klona(resolveNextCheckboxValue(getFromPath(formValues, field) || [], value, undefined));
1506
+ setInPath(formValues, field, newValue);
1450
1507
  return;
1451
1508
  }
1452
1509
  let newValue = value;
1453
1510
  // Single Checkbox: toggles the field value unless the field is being reset then force it
1454
- if (!Array.isArray(fieldInstance) && (fieldInstance === null || fieldInstance === void 0 ? void 0 : fieldInstance.type) === 'checkbox' && !force) {
1511
+ if (!isFieldGroup(fieldInstance) && fieldInstance.type === 'checkbox' && !force) {
1455
1512
  newValue = klona(resolveNextCheckboxValue(getFromPath(formValues, field), value, vue.unref(fieldInstance.uncheckedValue)));
1456
1513
  }
1457
1514
  setInPath(formValues, field, newValue);
1458
- // multiple radio fields
1459
- if (fieldInstance && Array.isArray(fieldInstance)) {
1460
- fieldInstance.forEach(fieldItem => {
1461
- valuesByFid[fieldItem.fid] = newValue;
1462
- });
1463
- return;
1464
- }
1465
- valuesByFid[fieldInstance.fid] = newValue;
1466
1515
  }
1467
1516
  /**
1468
1517
  * Sets multiple fields values
@@ -1476,16 +1525,17 @@
1476
1525
  keysOf(fields).forEach(path => {
1477
1526
  setFieldValue(path, fields[path]);
1478
1527
  });
1528
+ // regenerate the arrays when the form values change
1529
+ Object.values(fieldArraysLookup).forEach(f => f && f.reset());
1479
1530
  }
1480
1531
  /**
1481
1532
  * Sets the touched meta state on a field
1482
1533
  */
1483
1534
  function setFieldTouched(field, isTouched) {
1484
- const fieldInstance = fieldsById.value[field];
1485
- if (!fieldInstance) {
1486
- return;
1535
+ const fieldInstance = fieldsByPath.value[field];
1536
+ if (fieldInstance) {
1537
+ applyFieldMutation(fieldInstance, f => f.setTouched(isTouched));
1487
1538
  }
1488
- applyFieldMutation(fieldInstance, f => f.setTouched(isTouched));
1489
1539
  }
1490
1540
  /**
1491
1541
  * Sets the touched meta state on multiple fields
@@ -1505,108 +1555,123 @@
1505
1555
  setValues(state === null || state === void 0 ? void 0 : state.values);
1506
1556
  }
1507
1557
  else {
1558
+ // clean up the initial values back to the original
1559
+ setInitialValues(originalInitialValues.value);
1508
1560
  // otherwise clean the current values
1509
- setValues(initialValues.value);
1561
+ setValues(originalInitialValues.value);
1510
1562
  }
1511
- // Reset all fields state
1512
- fields.value.forEach(f => f.resetField());
1563
+ Object.values(fieldsByPath.value).forEach(field => {
1564
+ if (!field) {
1565
+ return;
1566
+ }
1567
+ applyFieldMutation(field, f => f.resetField());
1568
+ });
1513
1569
  if (state === null || state === void 0 ? void 0 : state.touched) {
1514
1570
  setTouched(state.touched);
1515
1571
  }
1516
1572
  setErrors((state === null || state === void 0 ? void 0 : state.errors) || {});
1517
1573
  submitCount.value = (state === null || state === void 0 ? void 0 : state.submitCount) || 0;
1518
1574
  }
1575
+ function insertFieldAtPath(field, path) {
1576
+ const rawField = vue.markRaw(field);
1577
+ const fieldPath = path;
1578
+ // first field at that path
1579
+ if (!fieldsByPath.value[fieldPath]) {
1580
+ fieldsByPath.value[fieldPath] = rawField;
1581
+ return;
1582
+ }
1583
+ const fieldAtPath = fieldsByPath.value[fieldPath];
1584
+ if (fieldAtPath && !Array.isArray(fieldAtPath)) {
1585
+ fieldsByPath.value[fieldPath] = [fieldAtPath];
1586
+ }
1587
+ // add the new array to that path
1588
+ fieldsByPath.value[fieldPath] = [...fieldsByPath.value[fieldPath], rawField];
1589
+ }
1590
+ function removeFieldFromPath(field, path) {
1591
+ const fieldPath = path;
1592
+ const fieldAtPath = fieldsByPath.value[fieldPath];
1593
+ if (!fieldAtPath) {
1594
+ return;
1595
+ }
1596
+ // same field at path
1597
+ if (!isFieldGroup(fieldAtPath) && field.id === fieldAtPath.id) {
1598
+ delete fieldsByPath.value[fieldPath];
1599
+ return;
1600
+ }
1601
+ if (isFieldGroup(fieldAtPath)) {
1602
+ const idx = fieldAtPath.findIndex(f => f.id === field.id);
1603
+ if (idx === -1) {
1604
+ return;
1605
+ }
1606
+ fieldAtPath.splice(idx, 1);
1607
+ if (fieldAtPath.length === 1) {
1608
+ fieldsByPath.value[fieldPath] = fieldAtPath[0];
1609
+ return;
1610
+ }
1611
+ if (!fieldAtPath.length) {
1612
+ delete fieldsByPath.value[fieldPath];
1613
+ }
1614
+ }
1615
+ }
1519
1616
  function registerField(field) {
1520
- fields.value.push(field);
1521
- // TODO: Do this automatically on registration
1522
- // eslint-disable-next-line no-unused-expressions
1523
- fieldsById.value; // force computation of the fields ids to properly set their idx
1617
+ const fieldPath = vue.unref(field.name);
1618
+ insertFieldAtPath(field, fieldPath);
1524
1619
  if (vue.isRef(field.name)) {
1525
- valuesByFid[field.fid] = field.value.value;
1526
1620
  // ensures when a field's name was already taken that it preserves its same value
1527
1621
  // necessary for fields generated by loops
1528
- vue.watch(field.name, (newPath, oldPath) => {
1529
- setFieldValue(newPath, valuesByFid[field.fid]);
1530
- const isSharingName = fields.value.find(f => vue.unref(f.name) === oldPath);
1622
+ vue.watch(field.name, async (newPath, oldPath) => {
1623
+ // cache the value
1624
+ await vue.nextTick();
1625
+ removeFieldFromPath(field, oldPath);
1626
+ insertFieldAtPath(field, newPath);
1627
+ // re-validate if either path had errors before
1628
+ if (errors.value[oldPath] || errors.value[newPath]) {
1629
+ validateField(newPath);
1630
+ }
1531
1631
  // clean up the old path if no other field is sharing that name
1532
1632
  // #3325
1533
- if (!isSharingName) {
1633
+ await vue.nextTick();
1634
+ if (!fieldExists(oldPath)) {
1534
1635
  unsetPath(formValues, oldPath);
1535
- unsetPath(initialValues.value, oldPath);
1536
1636
  }
1537
- }, {
1538
- flush: 'post',
1539
1637
  });
1540
1638
  }
1541
1639
  // if field already had errors (initial errors) that's not user-set, validate it again to ensure state is correct
1542
1640
  // the difference being that `initialErrors` will contain the error message while other errors (pre-validated schema) won't have them as initial errors
1543
1641
  // #3342
1544
- const path = vue.unref(field.name);
1545
1642
  const initialErrorMessage = vue.unref(field.errorMessage);
1546
- if (initialErrorMessage && (initialErrors === null || initialErrors === void 0 ? void 0 : initialErrors[path]) !== initialErrorMessage) {
1547
- validateField(path);
1643
+ if (initialErrorMessage && (initialErrors === null || initialErrors === void 0 ? void 0 : initialErrors[fieldPath]) !== initialErrorMessage) {
1644
+ validateField(fieldPath);
1548
1645
  }
1549
1646
  // marks the initial error as "consumed" so it won't be matched later with same non-initial error
1550
- delete initialErrors[path];
1647
+ delete initialErrors[fieldPath];
1551
1648
  }
1552
1649
  function unregisterField(field) {
1553
- var _a, _b;
1554
- const idx = fields.value.indexOf(field);
1555
- if (idx === -1) {
1556
- return;
1557
- }
1558
- fields.value.splice(idx, 1);
1559
- const fid = field.fid;
1560
- // cleans up the field value from fid lookup
1650
+ const fieldName = vue.unref(field.name);
1651
+ removeFieldFromPath(field, fieldName);
1561
1652
  vue.nextTick(() => {
1562
- delete valuesByFid[fid];
1563
1653
  // clears a field error on unmounted
1564
1654
  // we wait till next tick to make sure if the field is completely removed and doesn't have any siblings like checkboxes
1565
1655
  // #3384
1566
- if (!fieldsById.value[fieldName]) {
1656
+ if (!fieldExists(fieldName)) {
1567
1657
  setFieldError(fieldName, undefined);
1658
+ unsetPath(formValues, fieldName);
1568
1659
  }
1569
1660
  });
1570
- const fieldName = vue.unref(field.name);
1571
- // in this case, this is a single field not a group (checkbox or radio)
1572
- // so remove the field value key immediately
1573
- if (field.idx === -1) {
1574
- // avoid un-setting the value if the field was switched with another that shares the same name
1575
- // they will be unset once the new field takes over the new name, look at `#registerField()`
1576
- // #3166
1577
- const isSharingName = fields.value.find(f => vue.unref(f.name) === fieldName);
1578
- if (isSharingName) {
1579
- return;
1580
- }
1581
- unsetPath(formValues, fieldName);
1582
- unsetPath(initialValues.value, fieldName);
1583
- return;
1584
- }
1585
- // otherwise find the actual value in the current array of values and remove it
1586
- const valueIdx = (_b = (_a = getFromPath(formValues, fieldName)) === null || _a === void 0 ? void 0 : _a.indexOf) === null || _b === void 0 ? void 0 : _b.call(_a, vue.unref(field.checkedValue));
1587
- if (valueIdx === undefined) {
1588
- unsetPath(formValues, fieldName);
1589
- return;
1590
- }
1591
- if (valueIdx === -1) {
1592
- return;
1593
- }
1594
- if (Array.isArray(formValues[fieldName])) {
1595
- unsetPath(formValues, `${fieldName}.${valueIdx}`);
1596
- return;
1597
- }
1598
- unsetPath(formValues, fieldName);
1599
- unsetPath(initialValues.value, fieldName);
1600
1661
  }
1601
- async function validate() {
1662
+ async function validate(opts) {
1602
1663
  if (formCtx.validateSchema) {
1603
- return formCtx.validateSchema('force');
1664
+ return formCtx.validateSchema((opts === null || opts === void 0 ? void 0 : opts.mode) || 'force');
1604
1665
  }
1605
1666
  // No schema, each field is responsible to validate itself
1606
- const validations = await Promise.all(fields.value.map(f => {
1607
- return f.validate().then((result) => {
1667
+ const validations = await Promise.all(Object.values(fieldsByPath.value).map(field => {
1668
+ const fieldInstance = Array.isArray(field) ? field[0] : field;
1669
+ if (!fieldInstance) {
1670
+ return Promise.resolve({ key: '', valid: true, errors: [] });
1671
+ }
1672
+ return fieldInstance.validate(opts).then((result) => {
1608
1673
  return {
1609
- key: vue.unref(f.name),
1674
+ key: vue.unref(fieldInstance.name),
1610
1675
  valid: result.valid,
1611
1676
  errors: result.errors,
1612
1677
  };
@@ -1629,8 +1694,8 @@
1629
1694
  errors,
1630
1695
  };
1631
1696
  }
1632
- function validateField(field) {
1633
- const fieldInstance = fieldsById.value[field];
1697
+ async function validateField(field) {
1698
+ const fieldInstance = fieldsByPath.value[field];
1634
1699
  if (!fieldInstance) {
1635
1700
  vue.warn(`field with name ${field} was not found`);
1636
1701
  return Promise.resolve({ errors: [], valid: true });
@@ -1640,14 +1705,14 @@
1640
1705
  }
1641
1706
  return fieldInstance.validate();
1642
1707
  }
1643
- function handleSubmit(fn) {
1708
+ function handleSubmit(fn, onValidationError) {
1644
1709
  return function submissionHandler(e) {
1645
1710
  if (e instanceof Event) {
1646
1711
  e.preventDefault();
1647
1712
  e.stopPropagation();
1648
1713
  }
1649
1714
  // Touch all fields
1650
- setTouched(keysOf(fieldsById.value).reduce((acc, field) => {
1715
+ setTouched(keysOf(fieldsByPath.value).reduce((acc, field) => {
1651
1716
  acc[field] = true;
1652
1717
  return acc;
1653
1718
  }, {}));
@@ -1667,9 +1732,18 @@
1667
1732
  resetForm,
1668
1733
  });
1669
1734
  }
1735
+ if (!result.valid && typeof onValidationError === 'function') {
1736
+ onValidationError({
1737
+ values: klona(formValues),
1738
+ evt: e,
1739
+ errors: result.errors,
1740
+ results: result.results,
1741
+ });
1742
+ }
1670
1743
  })
1671
- .then(() => {
1744
+ .then(returnVal => {
1672
1745
  isSubmitting.value = false;
1746
+ return returnVal;
1673
1747
  }, err => {
1674
1748
  isSubmitting.value = false;
1675
1749
  // re-throw the err so it doesn't go silent
@@ -1680,6 +1754,9 @@
1680
1754
  function setFieldInitialValue(path, value) {
1681
1755
  setInPath(initialValues.value, path, klona(value));
1682
1756
  }
1757
+ function unsetInitialValue(path) {
1758
+ unsetPath(initialValues.value, path);
1759
+ }
1683
1760
  /**
1684
1761
  * Sneaky function to set initial field values
1685
1762
  */
@@ -1687,7 +1764,7 @@
1687
1764
  setInPath(formValues, path, value);
1688
1765
  setFieldInitialValue(path, value);
1689
1766
  }
1690
- async function validateSchema(mode) {
1767
+ async function _validateSchema() {
1691
1768
  const schemaValue = vue.unref(schema);
1692
1769
  if (!schemaValue) {
1693
1770
  return { valid: true, results: {}, errors: {} };
@@ -1698,8 +1775,16 @@
1698
1775
  names: fieldNames.value,
1699
1776
  bailsMap: fieldBailsMap.value,
1700
1777
  });
1778
+ return formResult;
1779
+ }
1780
+ /**
1781
+ * Batches validation runs in 5ms batches
1782
+ */
1783
+ const debouncedSchemaValidation = debounceAsync(_validateSchema, 5);
1784
+ async function validateSchema(mode) {
1785
+ const formResult = await debouncedSchemaValidation();
1701
1786
  // fields by id lookup
1702
- const fieldsById = formCtx.fieldsById.value || {};
1787
+ const fieldsById = formCtx.fieldsByPath.value || {};
1703
1788
  // errors fields names, we need it to also check if custom errors are updated
1704
1789
  const currentErrorsPaths = keysOf(formCtx.errorBag.value);
1705
1790
  // collect all the keys from the schema and all fields
@@ -1733,7 +1818,7 @@
1733
1818
  if (mode === 'validated-only' && !wasValidated) {
1734
1819
  return validation;
1735
1820
  }
1736
- applyFieldMutation(field, f => f.setValidationState(fieldResult), true);
1821
+ applyFieldMutation(field, f => f.setState({ errors: fieldResult.errors }));
1737
1822
  return validation;
1738
1823
  }, { valid: formResult.valid, results: {}, errors: {} });
1739
1824
  }
@@ -1769,7 +1854,6 @@
1769
1854
  }
1770
1855
  // Provide injections
1771
1856
  vue.provide(FormContextKey, formCtx);
1772
- vue.provide(FormErrorsKey, errors);
1773
1857
  return {
1774
1858
  errors,
1775
1859
  meta,
@@ -1793,7 +1877,7 @@
1793
1877
  /**
1794
1878
  * Manages form meta aggregation
1795
1879
  */
1796
- function useFormMeta(fields, currentValues, initialValues, errors) {
1880
+ function useFormMeta(fieldsByPath, currentValues, initialValues, errors) {
1797
1881
  const MERGE_STRATEGIES = {
1798
1882
  touched: 'some',
1799
1883
  pending: 'some',
@@ -1803,9 +1887,10 @@
1803
1887
  return !es6(currentValues, vue.unref(initialValues));
1804
1888
  });
1805
1889
  const flags = vue.computed(() => {
1890
+ const fields = Object.values(fieldsByPath.value).flat(1).filter(Boolean);
1806
1891
  return keysOf(MERGE_STRATEGIES).reduce((acc, flag) => {
1807
1892
  const mergeMethod = MERGE_STRATEGIES[flag];
1808
- acc[flag] = fields.value[mergeMethod](field => field.meta[flag]);
1893
+ acc[flag] = fields[mergeMethod](field => field.meta[flag]);
1809
1894
  return acc;
1810
1895
  }, {});
1811
1896
  });
@@ -1817,13 +1902,17 @@
1817
1902
  * Manages the initial values prop
1818
1903
  */
1819
1904
  function useFormInitialValues(fields, formValues, providedValues) {
1820
- const initialValues = vue.ref(vue.unref(providedValues) || {});
1821
- // acts as a read only proxy of the initial values object
1822
- const computedInitials = vue.computed(() => {
1823
- return initialValues.value;
1824
- });
1905
+ // these are the mutable initial values as the fields are mounted/unmounted
1906
+ const initialValues = vue.ref(klona(vue.unref(providedValues)) || {});
1907
+ // these are the original initial value as provided by the user initially, they don't keep track of conditional fields
1908
+ // this is important because some conditional fields will overwrite the initial values for other fields who had the same name
1909
+ // like array fields, any push/insert operation will overwrite the initial values because they "create new fields"
1910
+ // so these are the values that the reset function should use
1911
+ // these only change when the user explicitly chanegs the initial values or when the user resets them with new values.
1912
+ const originalInitialValues = vue.ref(klona(vue.unref(providedValues)) || {});
1825
1913
  function setInitialValues(values, updateFields = false) {
1826
1914
  initialValues.value = klona(values);
1915
+ originalInitialValues.value = klona(values);
1827
1916
  if (!updateFields) {
1828
1917
  return;
1829
1918
  }
@@ -1831,15 +1920,14 @@
1831
1920
  // those are excluded because it's unlikely you want to change the form values using initial values
1832
1921
  // we mostly watch them for API population or newly inserted fields
1833
1922
  // 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
1834
- const hadInteraction = (f) => f.meta.touched;
1835
1923
  keysOf(fields.value).forEach(fieldPath => {
1836
1924
  const field = fields.value[fieldPath];
1837
- const touchedByUser = Array.isArray(field) ? field.some(hadInteraction) : hadInteraction(field);
1838
- if (touchedByUser) {
1925
+ const wasTouched = Array.isArray(field) ? field.some(f => f.meta.touched) : field === null || field === void 0 ? void 0 : field.meta.touched;
1926
+ if (!field || wasTouched) {
1839
1927
  return;
1840
1928
  }
1841
1929
  const newValue = getFromPath(initialValues.value, fieldPath);
1842
- setInPath(formValues, fieldPath, newValue);
1930
+ setInPath(formValues, fieldPath, klona(newValue));
1843
1931
  });
1844
1932
  }
1845
1933
  if (vue.isRef(providedValues)) {
@@ -1849,10 +1937,9 @@
1849
1937
  deep: true,
1850
1938
  });
1851
1939
  }
1852
- vue.provide(FormInitialValuesKey, computedInitials);
1853
1940
  return {
1854
- readonlyInitialValues: computedInitials,
1855
1941
  initialValues,
1942
+ originalInitialValues,
1856
1943
  setInitialValues,
1857
1944
  };
1858
1945
  }
@@ -1925,6 +2012,10 @@
1925
2012
  type: Function,
1926
2013
  default: undefined,
1927
2014
  },
2015
+ onInvalidSubmit: {
2016
+ type: Function,
2017
+ default: undefined,
2018
+ },
1928
2019
  },
1929
2020
  setup(props, ctx) {
1930
2021
  const initialValues = vue.toRef(props, 'initialValues');
@@ -1936,7 +2027,7 @@
1936
2027
  initialTouched: props.initialTouched,
1937
2028
  validateOnMount: props.validateOnMount,
1938
2029
  });
1939
- const onSubmit = props.onSubmit ? handleSubmit(props.onSubmit) : submitForm;
2030
+ const onSubmit = props.onSubmit ? handleSubmit(props.onSubmit, props.onInvalidSubmit) : submitForm;
1940
2031
  function handleFormReset(e) {
1941
2032
  if (isEvent(e)) {
1942
2033
  // Prevent default form reset behavior
@@ -1949,7 +2040,7 @@
1949
2040
  }
1950
2041
  function handleScopedSlotSubmit(evt, onSubmit) {
1951
2042
  const onSuccess = typeof evt === 'function' && !onSubmit ? evt : onSubmit;
1952
- return handleSubmit(onSuccess)(evt);
2043
+ return handleSubmit(onSuccess, props.onInvalidSubmit)(evt);
1953
2044
  }
1954
2045
  function slotProps() {
1955
2046
  return {
@@ -2003,6 +2094,203 @@
2003
2094
  },
2004
2095
  });
2005
2096
 
2097
+ let FIELD_ARRAY_COUNTER = 0;
2098
+ function useFieldArray(arrayPath) {
2099
+ const id = FIELD_ARRAY_COUNTER++;
2100
+ const form = injectWithSelf(FormContextKey, undefined);
2101
+ const fields = vue.ref([]);
2102
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
2103
+ const noOp = () => { };
2104
+ const noOpApi = {
2105
+ fields: vue.readonly(fields),
2106
+ remove: noOp,
2107
+ push: noOp,
2108
+ swap: noOp,
2109
+ insert: noOp,
2110
+ update: noOp,
2111
+ replace: noOp,
2112
+ prepend: noOp,
2113
+ };
2114
+ if (!form) {
2115
+ warn('FieldArray requires being a child of `<Form/>` or `useForm` being called before it. Array fields may not work correctly');
2116
+ return noOpApi;
2117
+ }
2118
+ if (!vue.unref(arrayPath)) {
2119
+ warn('FieldArray requires a field path to be provided, did you forget to pass the `name` prop?');
2120
+ return noOpApi;
2121
+ }
2122
+ let entryCounter = 0;
2123
+ function initFields() {
2124
+ const currentValues = getFromPath(form === null || form === void 0 ? void 0 : form.values, vue.unref(arrayPath), []);
2125
+ fields.value = currentValues.map(createEntry);
2126
+ updateEntryFlags();
2127
+ }
2128
+ initFields();
2129
+ function updateEntryFlags() {
2130
+ const fieldsLength = fields.value.length;
2131
+ for (let i = 0; i < fieldsLength; i++) {
2132
+ const entry = fields.value[i];
2133
+ entry.isFirst = i === 0;
2134
+ entry.isLast = i === fieldsLength - 1;
2135
+ }
2136
+ }
2137
+ function createEntry(value) {
2138
+ const key = entryCounter++;
2139
+ const entry = {
2140
+ key,
2141
+ value: vue.computed(() => {
2142
+ const currentValues = getFromPath(form === null || form === void 0 ? void 0 : form.values, vue.unref(arrayPath), []);
2143
+ const idx = fields.value.findIndex(e => e.key === key);
2144
+ return idx === -1 ? value : currentValues[idx];
2145
+ }),
2146
+ isFirst: false,
2147
+ isLast: false,
2148
+ };
2149
+ return entry;
2150
+ }
2151
+ function remove(idx) {
2152
+ const pathName = vue.unref(arrayPath);
2153
+ const pathValue = getFromPath(form === null || form === void 0 ? void 0 : form.values, pathName);
2154
+ if (!pathValue || !Array.isArray(pathValue)) {
2155
+ return;
2156
+ }
2157
+ const newValue = [...pathValue];
2158
+ newValue.splice(idx, 1);
2159
+ form === null || form === void 0 ? void 0 : form.unsetInitialValue(pathName + `[${idx}]`);
2160
+ form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, newValue);
2161
+ fields.value.splice(idx, 1);
2162
+ updateEntryFlags();
2163
+ }
2164
+ function push(value) {
2165
+ const pathName = vue.unref(arrayPath);
2166
+ const pathValue = getFromPath(form === null || form === void 0 ? void 0 : form.values, pathName);
2167
+ const normalizedPathValue = isNullOrUndefined(pathValue) ? [] : pathValue;
2168
+ if (!Array.isArray(normalizedPathValue)) {
2169
+ return;
2170
+ }
2171
+ const newValue = [...normalizedPathValue];
2172
+ newValue.push(value);
2173
+ form === null || form === void 0 ? void 0 : form.stageInitialValue(pathName + `[${newValue.length - 1}]`, value);
2174
+ form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, newValue);
2175
+ fields.value.push(createEntry(value));
2176
+ updateEntryFlags();
2177
+ }
2178
+ function swap(indexA, indexB) {
2179
+ const pathName = vue.unref(arrayPath);
2180
+ const pathValue = getFromPath(form === null || form === void 0 ? void 0 : form.values, pathName);
2181
+ if (!Array.isArray(pathValue) || !pathValue[indexA] || !pathValue[indexB]) {
2182
+ return;
2183
+ }
2184
+ const newValue = [...pathValue];
2185
+ const newFields = [...fields.value];
2186
+ // the old switcheroo
2187
+ const temp = newValue[indexA];
2188
+ newValue[indexA] = newValue[indexB];
2189
+ newValue[indexB] = temp;
2190
+ const tempEntry = newFields[indexA];
2191
+ newFields[indexA] = newFields[indexB];
2192
+ newFields[indexB] = tempEntry;
2193
+ form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, newValue);
2194
+ fields.value = newFields;
2195
+ updateEntryFlags();
2196
+ }
2197
+ function insert(idx, value) {
2198
+ const pathName = vue.unref(arrayPath);
2199
+ const pathValue = getFromPath(form === null || form === void 0 ? void 0 : form.values, pathName);
2200
+ if (!Array.isArray(pathValue) || pathValue.length < idx) {
2201
+ return;
2202
+ }
2203
+ const newValue = [...pathValue];
2204
+ const newFields = [...fields.value];
2205
+ newValue.splice(idx, 0, value);
2206
+ newFields.splice(idx, 0, createEntry(value));
2207
+ form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, newValue);
2208
+ fields.value = newFields;
2209
+ updateEntryFlags();
2210
+ }
2211
+ function replace(arr) {
2212
+ const pathName = vue.unref(arrayPath);
2213
+ form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, arr);
2214
+ initFields();
2215
+ }
2216
+ function update(idx, value) {
2217
+ const pathName = vue.unref(arrayPath);
2218
+ const pathValue = getFromPath(form === null || form === void 0 ? void 0 : form.values, pathName);
2219
+ if (!Array.isArray(pathValue) || pathValue.length - 1 < idx) {
2220
+ return;
2221
+ }
2222
+ form === null || form === void 0 ? void 0 : form.setFieldValue(`${pathName}[${idx}]`, value);
2223
+ }
2224
+ function prepend(value) {
2225
+ const pathName = vue.unref(arrayPath);
2226
+ const pathValue = getFromPath(form === null || form === void 0 ? void 0 : form.values, pathName);
2227
+ const normalizedPathValue = isNullOrUndefined(pathValue) ? [] : pathValue;
2228
+ if (!Array.isArray(normalizedPathValue)) {
2229
+ return;
2230
+ }
2231
+ const newValue = [value, ...normalizedPathValue];
2232
+ form === null || form === void 0 ? void 0 : form.stageInitialValue(pathName + `[${newValue.length - 1}]`, value);
2233
+ form === null || form === void 0 ? void 0 : form.setFieldValue(pathName, newValue);
2234
+ fields.value.unshift(createEntry(value));
2235
+ updateEntryFlags();
2236
+ }
2237
+ form.fieldArraysLookup[id] = {
2238
+ reset: initFields,
2239
+ };
2240
+ vue.onBeforeUnmount(() => {
2241
+ delete form.fieldArraysLookup[id];
2242
+ });
2243
+ return {
2244
+ fields: vue.readonly(fields),
2245
+ remove,
2246
+ push,
2247
+ swap,
2248
+ insert,
2249
+ update,
2250
+ replace,
2251
+ prepend,
2252
+ };
2253
+ }
2254
+
2255
+ const FieldArray = vue.defineComponent({
2256
+ name: 'FieldArray',
2257
+ inheritAttrs: false,
2258
+ props: {
2259
+ name: {
2260
+ type: String,
2261
+ required: true,
2262
+ },
2263
+ },
2264
+ setup(props, ctx) {
2265
+ const { push, remove, swap, insert, replace, update, prepend, fields } = useFieldArray(vue.toRef(props, 'name'));
2266
+ function slotProps() {
2267
+ return {
2268
+ fields: fields.value,
2269
+ push,
2270
+ remove,
2271
+ swap,
2272
+ insert,
2273
+ update,
2274
+ replace,
2275
+ prepend,
2276
+ };
2277
+ }
2278
+ ctx.expose({
2279
+ push,
2280
+ remove,
2281
+ swap,
2282
+ insert,
2283
+ update,
2284
+ replace,
2285
+ prepend,
2286
+ });
2287
+ return () => {
2288
+ const children = normalizeChildren(undefined, ctx, slotProps);
2289
+ return children;
2290
+ };
2291
+ },
2292
+ });
2293
+
2006
2294
  const ErrorMessage = vue.defineComponent({
2007
2295
  name: 'ErrorMessage',
2008
2296
  props: {
@@ -2016,9 +2304,9 @@
2016
2304
  },
2017
2305
  },
2018
2306
  setup(props, ctx) {
2019
- const errors = vue.inject(FormErrorsKey, undefined);
2307
+ const form = vue.inject(FormContextKey, undefined);
2020
2308
  const message = vue.computed(() => {
2021
- return errors === null || errors === void 0 ? void 0 : errors.value[props.name];
2309
+ return form === null || form === void 0 ? void 0 : form.errors.value[props.name];
2022
2310
  });
2023
2311
  function slotProps() {
2024
2312
  return {
@@ -2069,7 +2357,7 @@
2069
2357
  let field = path ? undefined : vue.inject(FieldContextKey);
2070
2358
  return vue.computed(() => {
2071
2359
  if (path) {
2072
- field = normalizeField(form === null || form === void 0 ? void 0 : form.fieldsById.value[vue.unref(path)]);
2360
+ field = normalizeField(form === null || form === void 0 ? void 0 : form.fieldsByPath.value[vue.unref(path)]);
2073
2361
  }
2074
2362
  if (!field) {
2075
2363
  warn(`field with name ${vue.unref(path)} was not found`);
@@ -2087,7 +2375,7 @@
2087
2375
  let field = path ? undefined : vue.inject(FieldContextKey);
2088
2376
  return vue.computed(() => {
2089
2377
  if (path) {
2090
- field = normalizeField(form === null || form === void 0 ? void 0 : form.fieldsById.value[vue.unref(path)]);
2378
+ field = normalizeField(form === null || form === void 0 ? void 0 : form.fieldsByPath.value[vue.unref(path)]);
2091
2379
  }
2092
2380
  if (!field) {
2093
2381
  warn(`field with name ${vue.unref(path)} was not found`);
@@ -2105,7 +2393,7 @@
2105
2393
  let field = path ? undefined : vue.inject(FieldContextKey);
2106
2394
  return vue.computed(() => {
2107
2395
  if (path) {
2108
- field = normalizeField(form === null || form === void 0 ? void 0 : form.fieldsById.value[vue.unref(path)]);
2396
+ field = normalizeField(form === null || form === void 0 ? void 0 : form.fieldsByPath.value[vue.unref(path)]);
2109
2397
  }
2110
2398
  if (!field) {
2111
2399
  warn(`field with name ${vue.unref(path)} was not found`);
@@ -2137,7 +2425,7 @@
2137
2425
  let field = path ? undefined : vue.inject(FieldContextKey);
2138
2426
  return function validateField() {
2139
2427
  if (path) {
2140
- field = normalizeField(form === null || form === void 0 ? void 0 : form.fieldsById.value[vue.unref(path)]);
2428
+ field = normalizeField(form === null || form === void 0 ? void 0 : form.fieldsByPath.value[vue.unref(path)]);
2141
2429
  }
2142
2430
  if (!field) {
2143
2431
  warn(`field with name ${vue.unref(path)} was not found`);
@@ -2230,11 +2518,10 @@
2230
2518
  // We don't want to use self injected context as it doesn't make sense
2231
2519
  const field = path ? undefined : vue.inject(FieldContextKey);
2232
2520
  return vue.computed(() => {
2233
- var _a;
2234
2521
  if (path) {
2235
2522
  return getFromPath(form === null || form === void 0 ? void 0 : form.values, vue.unref(path));
2236
2523
  }
2237
- return (_a = field === null || field === void 0 ? void 0 : field.value) === null || _a === void 0 ? void 0 : _a.value;
2524
+ return vue.unref(field === null || field === void 0 ? void 0 : field.value);
2238
2525
  });
2239
2526
  }
2240
2527
 
@@ -2255,24 +2542,25 @@
2255
2542
  * Gives access to all form errors
2256
2543
  */
2257
2544
  function useFormErrors() {
2258
- const errors = injectWithSelf(FormErrorsKey);
2259
- if (!errors) {
2545
+ const form = injectWithSelf(FormContextKey);
2546
+ if (!form) {
2260
2547
  warn('No vee-validate <Form /> or `useForm` was detected in the component tree');
2261
2548
  }
2262
- return errors || vue.computed(() => ({}));
2549
+ return vue.computed(() => {
2550
+ return ((form === null || form === void 0 ? void 0 : form.errors.value) || {});
2551
+ });
2263
2552
  }
2264
2553
 
2265
2554
  /**
2266
2555
  * Gives access to a single field error
2267
2556
  */
2268
2557
  function useFieldError(path) {
2269
- const errors = injectWithSelf(FormErrorsKey);
2558
+ const form = injectWithSelf(FormContextKey);
2270
2559
  // We don't want to use self injected context as it doesn't make sense
2271
2560
  const field = path ? undefined : vue.inject(FieldContextKey);
2272
2561
  return vue.computed(() => {
2273
- var _a;
2274
2562
  if (path) {
2275
- return (_a = errors === null || errors === void 0 ? void 0 : errors.value) === null || _a === void 0 ? void 0 : _a[vue.unref(path)];
2563
+ return form === null || form === void 0 ? void 0 : form.errors.value[vue.unref(path)];
2276
2564
  }
2277
2565
  return field === null || field === void 0 ? void 0 : field.errorMessage.value;
2278
2566
  });
@@ -2294,12 +2582,14 @@
2294
2582
 
2295
2583
  exports.ErrorMessage = ErrorMessage;
2296
2584
  exports.Field = Field;
2585
+ exports.FieldArray = FieldArray;
2297
2586
  exports.FieldContextKey = FieldContextKey;
2298
2587
  exports.Form = Form;
2299
2588
  exports.FormContextKey = FormContextKey;
2300
2589
  exports.configure = configure;
2301
2590
  exports.defineRule = defineRule;
2302
2591
  exports.useField = useField;
2592
+ exports.useFieldArray = useFieldArray;
2303
2593
  exports.useFieldError = useFieldError;
2304
2594
  exports.useFieldValue = useFieldValue;
2305
2595
  exports.useForm = useForm;