srcdev-nuxt-forms 3.0.0 → 4.1.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.
- package/assets/styles/ally/_utils.css +20 -0
- package/assets/styles/ally/_variables.css +8 -0
- package/assets/styles/ally/index.css +2 -0
- package/assets/styles/forms/index.css +2 -0
- package/assets/styles/forms/themes/_error.css +85 -0
- package/assets/styles/forms/themes/_ghost.css +85 -0
- package/assets/styles/forms/themes/_input-action-underlined.css +20 -0
- package/assets/styles/forms/themes/_input-action.css +20 -0
- package/assets/styles/forms/themes/_primary.css +92 -0
- package/assets/styles/forms/themes/_secondary.css +85 -0
- package/assets/styles/forms/themes/_success.css +85 -0
- package/assets/styles/forms/themes/_tertiary.css +85 -0
- package/assets/styles/forms/themes/_warning.css +85 -0
- package/assets/styles/forms/themes/index.css +9 -0
- package/assets/styles/forms/variables/_sizes.css +71 -0
- package/assets/styles/forms/variables/_theme.css +11 -0
- package/assets/styles/forms/variables/index.css +2 -0
- package/assets/styles/main.css +5 -0
- package/assets/styles/typography/index.css +2 -0
- package/assets/styles/typography/utils/_font-classes.css +190 -0
- package/assets/styles/typography/utils/_weights.css +69 -0
- package/assets/styles/typography/utils/index.css +2 -0
- package/assets/styles/typography/variables/_colors.css +14 -0
- package/assets/styles/typography/variables/_reponsive-font-size.css +10 -0
- package/assets/styles/typography/variables/index.css +2 -0
- package/assets/styles/utils/_margin-helpers.css +334 -0
- package/assets/styles/utils/_padding-helpers.css +308 -0
- package/assets/styles/utils/_page.css +49 -0
- package/assets/styles/utils/index.css +3 -0
- package/assets/styles/variables/colors/_blue.css +15 -0
- package/assets/styles/variables/colors/_gray.css +16 -0
- package/assets/styles/variables/colors/_green.css +15 -0
- package/assets/styles/variables/colors/_orange.css +15 -0
- package/assets/styles/variables/colors/_red.css +15 -0
- package/assets/styles/variables/colors/_yellow.css +15 -0
- package/assets/styles/variables/colors/colors.css +6 -0
- package/assets/styles/variables/index.css +1 -0
- package/components/forms/c12/prop-validators/index.ts +38 -0
- package/components/forms/c12/utils.ts +14 -0
- package/components/forms/form-errors/InputError.vue +172 -0
- package/components/forms/form-errors/tests/InputError.spec.ts +67 -0
- package/components/forms/input-button/InputButtonCore.vue +191 -0
- package/components/forms/input-button/variants/InputButtonConfirm.vue +66 -0
- package/components/forms/input-button/variants/InputButtonSubmit.vue +62 -0
- package/components/forms/input-checkbox/MultipleCheckboxes.vue +203 -0
- package/components/forms/input-checkbox/SingleCheckbox.vue +169 -0
- package/components/forms/input-checkbox/tests/MultipleCheckboxes.spec.ts +98 -0
- package/components/forms/input-checkbox/tests/data/tags.json +67 -0
- package/components/forms/input-checkbox-radio/InputCheckboxRadioButton.vue +214 -0
- package/components/forms/input-checkbox-radio/InputCheckboxRadioCore.vue +191 -0
- package/components/forms/input-checkbox-radio/InputCheckboxRadioWithLabel.vue +111 -0
- package/components/forms/input-number/InputNumberCore.vue +203 -0
- package/components/forms/input-number/variants/InputNumberDefault.vue +154 -0
- package/components/forms/input-radio/MultipleRadiobuttons.vue +201 -0
- package/components/forms/input-radio/tests/MultipleRadioButtons.spec.ts +89 -0
- package/components/forms/input-radio/tests/data/tags.json +67 -0
- package/components/forms/input-range/InputRangeCore.vue +274 -0
- package/components/forms/input-range/variants/InputRangeDefault.vue +156 -0
- package/components/forms/input-range-fancy/InputRangeFancyCore.vue +450 -0
- package/components/forms/input-range-fancy/InputRangeFancyWithLabel.vue +124 -0
- package/components/forms/input-select/InputSelect.vue +289 -0
- package/components/forms/input-text/InputTextCore.vue +331 -0
- package/components/forms/input-text/variants/InputPasswordWithLabel.vue +130 -0
- package/components/forms/input-text/variants/InputTextAsNumberWithLabel.vue +187 -0
- package/components/forms/input-text/variants/InputTextWithLabel.vue +298 -0
- package/components/forms/input-textarea/InputTextareaCore.vue +234 -0
- package/components/forms/input-textarea/variants/InputTextareaWithLabel.vue +267 -0
- package/components/forms/toggle-switch/ToggleSwitchCore.vue +198 -0
- package/components/forms/toggle-switch/ToggleSwitchCoreOld.vue +216 -0
- package/components/forms/toggle-switch/variants/ToggleSwitchWithLabel.vue +105 -0
- package/components/forms/toggle-switch/variants/ToggleSwitchWithLabelInline.vue +102 -0
- package/components/forms/ui/FormField.vue +78 -0
- package/components/forms/ui/FormWrapper.vue +35 -0
- package/components/utils/colour-scheme-select/ColourSchemeSelect.vue +270 -0
- package/components/utils/colour-scheme-select/ColourSchemeSelectOld.vue +225 -0
- package/components/utils/dark-mode-switcher/DarkModeSwitcher.vue +47 -0
- package/composables/useApiRequest.ts +25 -0
- package/composables/useColourScheme.ts +25 -0
- package/composables/useErrorMessages.ts +59 -0
- package/composables/useFormControl.ts +248 -0
- package/composables/useSleep.ts +5 -0
- package/composables/useStyleClassPassthrough.ts +30 -0
- package/composables/useZodValidation.ts +148 -0
- package/nuxt.config.ts +0 -3
- package/package.json +1 -1
- package/types/types.forms.ts +217 -0
- package/types/types.zodFormControl.ts +21 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import type { IFormData, IFieldsInitialState, IFormFieldC12, IApiErrorMessages, ICustomErrorMessage, IErrorMessagesArr } from '@/types/types.forms';
|
|
2
|
+
import { formFieldC12 } from '@/components/forms/c12/utils';
|
|
3
|
+
|
|
4
|
+
// export function useFormControl(name: string = '') {
|
|
5
|
+
export function useFormControl(name: string = '') {
|
|
6
|
+
let savedInitialState = {};
|
|
7
|
+
|
|
8
|
+
const formData = ref<IFormData>({
|
|
9
|
+
data: {} as IFieldsInitialState,
|
|
10
|
+
validityState: {},
|
|
11
|
+
dirtyFields: {},
|
|
12
|
+
focusedField: '',
|
|
13
|
+
isPending: false,
|
|
14
|
+
errorCount: 0,
|
|
15
|
+
errorMessages: {},
|
|
16
|
+
formFieldsC12: {},
|
|
17
|
+
hasCustomErrorMessages: false,
|
|
18
|
+
formIsValid: false,
|
|
19
|
+
submitAttempted: false,
|
|
20
|
+
submitDisabled: false,
|
|
21
|
+
submitSuccess: false,
|
|
22
|
+
displayErrorMessages: false,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const initValidationState = async (fieldsInitialState: IFieldsInitialState | Ref<IFieldsInitialState | null>) => {
|
|
26
|
+
const fields = Object.keys(fieldsInitialState.value || {});
|
|
27
|
+
const state = fields.reduce((accumulatedFields, field) => {
|
|
28
|
+
accumulatedFields[field] = false;
|
|
29
|
+
return accumulatedFields;
|
|
30
|
+
}, {} as Record<string, boolean>);
|
|
31
|
+
formData.value.validityState = state;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const initFormData = async (fieldsInitialState: IFieldsInitialState | Ref<IFieldsInitialState | null>) => {
|
|
35
|
+
initValidationState(fieldsInitialState);
|
|
36
|
+
|
|
37
|
+
if (fieldsInitialState !== null) {
|
|
38
|
+
savedInitialState = toRaw(fieldsInitialState.value) as IFieldsInitialState;
|
|
39
|
+
formData.value.data = fieldsInitialState as IFieldsInitialState;
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const initFormFieldsC12 = (name: string, formFieldC12: IFormFieldC12) => {
|
|
45
|
+
formData.value.formFieldsC12[name] = formFieldC12;
|
|
46
|
+
return;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const updatePreviousValues = () => {
|
|
50
|
+
console.log(`useFormControl | updatePreviousValues`);
|
|
51
|
+
|
|
52
|
+
Object.keys(formData.value.data).forEach((key) => {
|
|
53
|
+
formData.value.formFieldsC12[key].previousValue = formData.value.data[key];
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const getErrorCount = async (updateState: boolean = false) => {
|
|
58
|
+
await nextTick();
|
|
59
|
+
|
|
60
|
+
const errorCount = Object.values(formData.value.validityState).filter((value) => !value).length;
|
|
61
|
+
formData.value.errorCount = errorCount;
|
|
62
|
+
formData.value.formIsValid = errorCount === 0;
|
|
63
|
+
|
|
64
|
+
if (updateState) {
|
|
65
|
+
formData.value.submitDisabled = true;
|
|
66
|
+
formData.value.displayErrorMessages = formData.value.errorCount > 0;
|
|
67
|
+
formData.value.submitAttempted = true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (formData.value.submitDisabled) {
|
|
71
|
+
formData.value.submitDisabled = !formData.value.formIsValid;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// update fieldHasError ref
|
|
75
|
+
// if (typeof formData.value!.formFieldsC12[name] !== 'undefined') {
|
|
76
|
+
// fieldHasError.value = formData.value!.submitAttempted && !formData.value!.formFieldsC12[name].isValid;
|
|
77
|
+
// } else {
|
|
78
|
+
// fieldHasError.value = false;
|
|
79
|
+
// }
|
|
80
|
+
|
|
81
|
+
return formData.value.errorCount;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Function to count items with "useCustomError" set to true
|
|
85
|
+
const countItemsWithCustomError = (obj: IErrorMessagesArr) => {
|
|
86
|
+
let count = 0;
|
|
87
|
+
|
|
88
|
+
for (const key in obj) {
|
|
89
|
+
if (obj.hasOwnProperty(key) && obj[key].useCustomError === true) {
|
|
90
|
+
count++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return count;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/*
|
|
98
|
+
* Useage:
|
|
99
|
+
*
|
|
100
|
+
* const { updateErrorMessages } = useFormControl();
|
|
101
|
+
*
|
|
102
|
+
* Add/Update entry
|
|
103
|
+
* const sampleCustomErrorEmail = {
|
|
104
|
+
* useCustomError: true,
|
|
105
|
+
* message: "This is a sample custom error for error EMAIL",
|
|
106
|
+
* };
|
|
107
|
+
* updateErrorMessages("email", sampleCustomErrorEmail);
|
|
108
|
+
*/
|
|
109
|
+
const updateErrorMessages = async (name: string, message: string = '', valid: boolean = false) => {
|
|
110
|
+
if (!valid) {
|
|
111
|
+
// formData.value.validityState[name] = valid;
|
|
112
|
+
// formData.value.errorMessages[name] = {
|
|
113
|
+
// useCustomError: true,
|
|
114
|
+
// message,
|
|
115
|
+
// };
|
|
116
|
+
|
|
117
|
+
formData.value.formFieldsC12[name].useCustomError = true;
|
|
118
|
+
|
|
119
|
+
// if (typeof message === 'string') {
|
|
120
|
+
// formData.value.formFieldsC12[name].customErrors = message;
|
|
121
|
+
// } else if (typeof message === 'object') {
|
|
122
|
+
// formData.value.formFieldsC12[name].customErrors = message;
|
|
123
|
+
// }
|
|
124
|
+
|
|
125
|
+
formData.value.formFieldsC12[name].customErrors = message;
|
|
126
|
+
formData.value.formFieldsC12[name].isValid = valid;
|
|
127
|
+
|
|
128
|
+
// formData.value.errorMessages[name].useCustomError = true;
|
|
129
|
+
// formData.value.errorMessages[name].message = message;
|
|
130
|
+
}
|
|
131
|
+
formData.value.hasCustomErrorMessages = countItemsWithCustomError(formData.value.errorMessages) > 0;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const useApiErrors = async (errors: IApiErrorMessages) => {
|
|
135
|
+
// Object.keys(errors).forEach((key) => {
|
|
136
|
+
// updateErrorMessages(key, errors[key]);
|
|
137
|
+
// });
|
|
138
|
+
|
|
139
|
+
for (const [key, message] of Object.entries(errors)) {
|
|
140
|
+
// console.log(`${key}: ${message}`);
|
|
141
|
+
updateErrorMessages(key, message);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// const resetForm = () => {
|
|
146
|
+
// console.log('resetForm()');
|
|
147
|
+
// formData.value.data = toRaw(fieldsInitialState.value) as IFieldsInitialState;
|
|
148
|
+
// formData.value.validityState = {};
|
|
149
|
+
// formData.value.errorCount = 0;
|
|
150
|
+
// formData.value.isPending = false;
|
|
151
|
+
// formData.value.errorMessages = {};
|
|
152
|
+
// formData.value.formIsValid = false;
|
|
153
|
+
// };
|
|
154
|
+
|
|
155
|
+
const fieldIsDirty = (name: string) => {
|
|
156
|
+
if (typeof formData.value.formFieldsC12[name] !== 'undefined') {
|
|
157
|
+
return formData.value.formFieldsC12[name].isDirty;
|
|
158
|
+
} else {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// const fieldHasError = (name: string) => {
|
|
164
|
+
// const currentValidityState = formData.value.validityState[name];
|
|
165
|
+
|
|
166
|
+
// if (formData.value.submitAttempted) {
|
|
167
|
+
// return currentValidityState;
|
|
168
|
+
// }
|
|
169
|
+
// return false;
|
|
170
|
+
// };
|
|
171
|
+
|
|
172
|
+
// const fieldHasError = computed({
|
|
173
|
+
// // getter
|
|
174
|
+
// get() {
|
|
175
|
+
// console.log(`fieldHasError getter: ${name}`);
|
|
176
|
+
// if (typeof formData.value!.formFieldsC12[name] !== 'undefined') {
|
|
177
|
+
// return !formData.value!.formFieldsC12[name].isValid;
|
|
178
|
+
// }
|
|
179
|
+
// return formData.value.validityState[name];
|
|
180
|
+
// },
|
|
181
|
+
// // setter
|
|
182
|
+
// set(newValue) {
|
|
183
|
+
// if (formData.value.submitAttempted) {
|
|
184
|
+
// return newValue;
|
|
185
|
+
// }
|
|
186
|
+
// return false;
|
|
187
|
+
// },
|
|
188
|
+
// });
|
|
189
|
+
|
|
190
|
+
// const fieldHasError = ref(false);
|
|
191
|
+
|
|
192
|
+
const formIsValid = computed(() => {
|
|
193
|
+
return formData.value.formIsValid;
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const submitDisabled = computed(() => {
|
|
197
|
+
return formData.value.submitDisabled;
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Keep an eye on this for performance issue
|
|
201
|
+
|
|
202
|
+
// const updateFieldValidity = (name: string, valid: boolean) => {
|
|
203
|
+
// console.log(`updateFieldValidity: name:${name} - valid:${valid}`);
|
|
204
|
+
// console.log(formData.value);
|
|
205
|
+
// // formData.value.formFieldsC12[name].isValid = valid;
|
|
206
|
+
// formData.value.validityState[name] = valid;
|
|
207
|
+
// };
|
|
208
|
+
|
|
209
|
+
watch(
|
|
210
|
+
() => formData.value.validityState,
|
|
211
|
+
() => {
|
|
212
|
+
getErrorCount();
|
|
213
|
+
},
|
|
214
|
+
{ deep: true }
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
watch(
|
|
218
|
+
() => formData.value.formFieldsC12,
|
|
219
|
+
() => {
|
|
220
|
+
formData.value.formFieldsC12;
|
|
221
|
+
},
|
|
222
|
+
{ deep: true }
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
watch(
|
|
226
|
+
() => formData.value.isPending,
|
|
227
|
+
(newValue, oldValue) => {
|
|
228
|
+
if (newValue) {
|
|
229
|
+
updatePreviousValues();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
formData,
|
|
236
|
+
initFormData,
|
|
237
|
+
initFormFieldsC12,
|
|
238
|
+
getErrorCount,
|
|
239
|
+
updateErrorMessages,
|
|
240
|
+
// resetForm,
|
|
241
|
+
formIsValid,
|
|
242
|
+
submitDisabled,
|
|
243
|
+
useApiErrors,
|
|
244
|
+
// fieldHasError,
|
|
245
|
+
fieldIsDirty,
|
|
246
|
+
// updateFieldValidity,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const useStyleClassPassthrough = (styleClassPassthrough: string[]) => {
|
|
2
|
+
const styleClassPassthroughRef = ref(styleClassPassthrough);
|
|
3
|
+
|
|
4
|
+
const elementClasses = computed(() => {
|
|
5
|
+
return styleClassPassthroughRef.value.join(' ');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const updateElementClasses = (cssClass: string | string[]) => {
|
|
9
|
+
let cssClasses = [] as string[];
|
|
10
|
+
if (typeof cssClass === 'string') {
|
|
11
|
+
cssClasses = [cssClass];
|
|
12
|
+
} else if (Array.isArray(cssClass)) {
|
|
13
|
+
cssClasses = cssClass;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
cssClasses.forEach((cssClass) => {
|
|
17
|
+
if (styleClassPassthroughRef.value.includes(cssClass)) {
|
|
18
|
+
styleClassPassthroughRef.value = styleClassPassthroughRef.value.filter((className) => className !== cssClass);
|
|
19
|
+
} else {
|
|
20
|
+
styleClassPassthroughRef.value.push(cssClass);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
elementClasses,
|
|
27
|
+
updateElementClasses,
|
|
28
|
+
styleClassPassthroughRef,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { ref, reactive, toRaw, type Ref } from 'vue';
|
|
2
|
+
import { z, ZodError } from 'zod';
|
|
3
|
+
import type { ApiErrorResponse } from '../types/types.forms';
|
|
4
|
+
|
|
5
|
+
const useZodValidation = (formSchema: any, formRef: Ref<HTMLFormElement | null>) => {
|
|
6
|
+
const zodFormControl = reactive({
|
|
7
|
+
errorCount: 0,
|
|
8
|
+
displayLoader: false,
|
|
9
|
+
submitDisabled: false,
|
|
10
|
+
submitAttempted: false,
|
|
11
|
+
submitSuccessful: false,
|
|
12
|
+
formIsValid: false,
|
|
13
|
+
isPending: false,
|
|
14
|
+
isDisabled: false,
|
|
15
|
+
previousState: {} as Record<string, any>,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
type formSchema = z.infer<typeof formSchema>;
|
|
19
|
+
const zodErrorObj = ref<z.ZodFormattedError<formSchema> | null>(null);
|
|
20
|
+
|
|
21
|
+
const resetPreviousValues = () => {
|
|
22
|
+
for (const [field] of Object.entries(formSchema.shape)) {
|
|
23
|
+
const previousValue = {
|
|
24
|
+
value: null,
|
|
25
|
+
message: '',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
zodFormControl.previousState[field] = previousValue;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const initZodForm = () => {
|
|
33
|
+
resetPreviousValues();
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const getErrorCount = (zodErrorObj: Ref<z.ZodFormattedError<formSchema> | null>) => {
|
|
37
|
+
const zodCountErrors = zodErrorObj.value ?? [];
|
|
38
|
+
// @ts-ignore
|
|
39
|
+
delete zodCountErrors._errors;
|
|
40
|
+
const errorCount = Object.keys(zodCountErrors ?? []).length;
|
|
41
|
+
return errorCount;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const transformErrorMessages = (errors: any) => {
|
|
45
|
+
const apiErrors = ref({}) as any;
|
|
46
|
+
for (const [key, value] of Object.entries(errors)) {
|
|
47
|
+
const fieldPath = key.split('.').map((key: string) => key.charAt(0).toLowerCase() + key.slice(1));
|
|
48
|
+
apiErrors.value[fieldPath.join('.')] = value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return apiErrors.value;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const updatePreviousValue = async (field: string, message: string, state: Record<string, any>) => {
|
|
55
|
+
const previousValue = {
|
|
56
|
+
value: state[field],
|
|
57
|
+
message: message,
|
|
58
|
+
};
|
|
59
|
+
zodFormControl.previousState[field] = previousValue;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const pushCustomErrors = async (apiErrorResponse: ApiErrorResponse, state: Record<string, any>) => {
|
|
63
|
+
const apiErrors = transformErrorMessages(apiErrorResponse.data.errors);
|
|
64
|
+
|
|
65
|
+
// 1: Create a ZodError object to hold the issues
|
|
66
|
+
const zodError = new ZodError([]);
|
|
67
|
+
|
|
68
|
+
// 2: Reset previous state values
|
|
69
|
+
resetPreviousValues();
|
|
70
|
+
|
|
71
|
+
// 3: Add issues to the ZodError object
|
|
72
|
+
for (const [path, message] of Object.entries(apiErrors)) {
|
|
73
|
+
zodError.addIssue({
|
|
74
|
+
path: path.split('.'),
|
|
75
|
+
message: message as string,
|
|
76
|
+
code: z.ZodIssueCode.custom,
|
|
77
|
+
});
|
|
78
|
+
await updatePreviousValue(path, message as string, state);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
zodErrorObj.value = zodError.format();
|
|
82
|
+
zodFormControl.errorCount = getErrorCount(zodErrorObj);
|
|
83
|
+
zodFormControl.formIsValid = zodFormControl.errorCount === 0;
|
|
84
|
+
zodFormControl.displayLoader = false;
|
|
85
|
+
zodFormControl.submitAttempted = true;
|
|
86
|
+
scrollToFirstError();
|
|
87
|
+
return zodErrorObj.value;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const doZodValidate = async (state: Record<string, any>) => {
|
|
91
|
+
const valid = formSchema.safeParse(toRaw(state));
|
|
92
|
+
if (!valid.success) {
|
|
93
|
+
zodErrorObj.value = valid.error.format();
|
|
94
|
+
} else {
|
|
95
|
+
zodErrorObj.value = null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
zodFormControl.errorCount = getErrorCount(zodErrorObj);
|
|
99
|
+
zodFormControl.formIsValid = valid.success;
|
|
100
|
+
|
|
101
|
+
return valid.success;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const fieldMaxLength = (name: string) => {
|
|
105
|
+
const fieldSchema = formSchema.shape[name];
|
|
106
|
+
if (fieldSchema instanceof z.ZodString) {
|
|
107
|
+
return fieldSchema._def.checks.find((check) => check.kind === 'max')?.value;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const scrollToFirstError = async () => {
|
|
112
|
+
if (formRef.value) {
|
|
113
|
+
const firstErrorElement = formRef.value.querySelector('[aria-invalid=true]');
|
|
114
|
+
|
|
115
|
+
if (firstErrorElement) {
|
|
116
|
+
window.scrollTo({
|
|
117
|
+
top: firstErrorElement?.getBoundingClientRect().y + window.scrollY,
|
|
118
|
+
left: 0,
|
|
119
|
+
behavior: 'smooth',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const scrollToFormHead = () => {
|
|
126
|
+
if (formRef.value) {
|
|
127
|
+
const formHead = formRef.value.getBoundingClientRect().top;
|
|
128
|
+
window.scrollTo({
|
|
129
|
+
top: formHead,
|
|
130
|
+
left: 0,
|
|
131
|
+
behavior: 'smooth',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
initZodForm,
|
|
138
|
+
zodFormControl,
|
|
139
|
+
zodErrorObj,
|
|
140
|
+
pushCustomErrors,
|
|
141
|
+
doZodValidate,
|
|
142
|
+
fieldMaxLength,
|
|
143
|
+
scrollToFirstError,
|
|
144
|
+
scrollToFormHead,
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export default useZodValidation;
|
package/nuxt.config.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
|
2
2
|
|
|
3
3
|
export default defineNuxtConfig({
|
|
4
|
-
future: {
|
|
5
|
-
compatibilityVersion: 4,
|
|
6
|
-
},
|
|
7
4
|
devtools: { enabled: true },
|
|
8
5
|
css: ['modern-normalize', './assets/styles/main.css'],
|
|
9
6
|
modules: ['@nuxt/icon', '@nuxt/test-utils/module'],
|
package/package.json
CHANGED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
export interface IValidationPatterns {
|
|
2
|
+
pattern: string;
|
|
3
|
+
minlength: string;
|
|
4
|
+
maxlength: string;
|
|
5
|
+
hint: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface IOptionsConfig {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
value: string;
|
|
12
|
+
label: string;
|
|
13
|
+
icon?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface IFormMultipleOptions {
|
|
17
|
+
data: IOptionsConfig[];
|
|
18
|
+
total: number;
|
|
19
|
+
skip: number;
|
|
20
|
+
limit: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface IOptionsValueArr {
|
|
24
|
+
[key: string]: string | boolean | number | URL | object;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface IFieldsInitialState {
|
|
28
|
+
[key: string]: null | string | boolean | number | URL | object | IOptionsValueArr[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type TFieldsInitialState = {
|
|
32
|
+
[key: string]: null | string | boolean | number | URL | object | IOptionsValueArr[];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export interface IValidityState {
|
|
36
|
+
badInput: boolean;
|
|
37
|
+
customError: boolean;
|
|
38
|
+
patternMismatch: boolean;
|
|
39
|
+
rangeOverflow: boolean;
|
|
40
|
+
rangeUnderflow: boolean;
|
|
41
|
+
stepMismatch: boolean;
|
|
42
|
+
tooLong: boolean;
|
|
43
|
+
tooShort: boolean;
|
|
44
|
+
typeMismatch: boolean;
|
|
45
|
+
valid: boolean;
|
|
46
|
+
valueMissing: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface IValidityStateArr {
|
|
50
|
+
[key: string]: {
|
|
51
|
+
badInput: boolean;
|
|
52
|
+
customError: boolean;
|
|
53
|
+
patternMismatch: boolean;
|
|
54
|
+
rangeOverflow: boolean;
|
|
55
|
+
rangeUnderflow: boolean;
|
|
56
|
+
stepMismatch: boolean;
|
|
57
|
+
tooLong: boolean;
|
|
58
|
+
tooShort: boolean;
|
|
59
|
+
typeMismatch: boolean;
|
|
60
|
+
valid: boolean;
|
|
61
|
+
valueMissing: boolean;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface IFormFieldsState {
|
|
66
|
+
[key: string]: boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface ICustomErrorMessage {
|
|
70
|
+
useCustomError: boolean;
|
|
71
|
+
message: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface InpuTextC12 {
|
|
75
|
+
label: string;
|
|
76
|
+
placeholder: string;
|
|
77
|
+
errorMessage: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface IErrorMessagesArr {
|
|
81
|
+
[x: string]: ICustomErrorMessage;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface IFormFieldC12 {
|
|
85
|
+
label: string;
|
|
86
|
+
placeholder: string;
|
|
87
|
+
errorMessage: string;
|
|
88
|
+
useCustomError: boolean;
|
|
89
|
+
customErrors: null | string | string[];
|
|
90
|
+
isValid: boolean;
|
|
91
|
+
isDirty: boolean;
|
|
92
|
+
type: string;
|
|
93
|
+
previousValue: null | string | boolean | number | URL | object;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface IFormFieldsC12 {
|
|
97
|
+
[x: string]: IFormFieldC12;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface IFormFieldState {
|
|
101
|
+
isValid: boolean;
|
|
102
|
+
isDirty: boolean;
|
|
103
|
+
previousValue: null | string | boolean | number | URL | object;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface IFormFieldStateObj {
|
|
107
|
+
[x: string]: IFormFieldState;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface IFormData {
|
|
111
|
+
[x: string]: string | boolean | number | URL | object;
|
|
112
|
+
data: IFieldsInitialState;
|
|
113
|
+
validityState: IFormFieldsState;
|
|
114
|
+
dirtyFields: IFormFieldsState;
|
|
115
|
+
focusedField: string;
|
|
116
|
+
isPending: boolean;
|
|
117
|
+
errorCount: number;
|
|
118
|
+
errorMessages: IErrorMessagesArr;
|
|
119
|
+
formFieldsC12: IFormFieldsC12;
|
|
120
|
+
formIsValid: boolean;
|
|
121
|
+
submitAttempted: boolean;
|
|
122
|
+
submitDisabled: boolean;
|
|
123
|
+
submitSuccess: boolean;
|
|
124
|
+
displayErrorMessages: boolean;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface IApiErrorMessages {
|
|
128
|
+
[x: string]: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// New types
|
|
132
|
+
|
|
133
|
+
export interface C12nInputText {
|
|
134
|
+
type: string;
|
|
135
|
+
id: string;
|
|
136
|
+
name: string;
|
|
137
|
+
label: string;
|
|
138
|
+
placeholder: string;
|
|
139
|
+
errorMessage: string;
|
|
140
|
+
fieldHasError: boolean;
|
|
141
|
+
required: boolean;
|
|
142
|
+
styleClassPassthrough: string[];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface C12nMultipleCheckboxes {
|
|
146
|
+
id: string;
|
|
147
|
+
name: string;
|
|
148
|
+
label: string;
|
|
149
|
+
legend: string;
|
|
150
|
+
placeholder: string;
|
|
151
|
+
errorMessage: string;
|
|
152
|
+
fieldHasError: boolean;
|
|
153
|
+
required: boolean;
|
|
154
|
+
styleClassPassthrough: string[];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface C12nInputCheckboxWithLabel {
|
|
158
|
+
id: string;
|
|
159
|
+
name: string;
|
|
160
|
+
required: string;
|
|
161
|
+
label: string;
|
|
162
|
+
placeholder: string;
|
|
163
|
+
errorMessage: string | string[];
|
|
164
|
+
fieldHasError: boolean;
|
|
165
|
+
styleClassPassthrough: string[];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface C12nInputRange {
|
|
169
|
+
id: string;
|
|
170
|
+
name: string;
|
|
171
|
+
label: string;
|
|
172
|
+
min: number;
|
|
173
|
+
max: number;
|
|
174
|
+
step: number;
|
|
175
|
+
placeholder: string;
|
|
176
|
+
errorMessage: string;
|
|
177
|
+
fieldHasError: boolean;
|
|
178
|
+
required: boolean;
|
|
179
|
+
styleClassPassthrough: string[];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface C12nInputTextCore {
|
|
183
|
+
type: string;
|
|
184
|
+
id: string;
|
|
185
|
+
name: string;
|
|
186
|
+
label: string;
|
|
187
|
+
placeholder: string;
|
|
188
|
+
errorMessage: string;
|
|
189
|
+
fieldHasError: boolean;
|
|
190
|
+
required: boolean;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export interface InputTextWithLabel {
|
|
194
|
+
type: string;
|
|
195
|
+
id: string;
|
|
196
|
+
name: string;
|
|
197
|
+
label: string;
|
|
198
|
+
placeholder: string;
|
|
199
|
+
errorMessage: string;
|
|
200
|
+
fieldHasError: boolean;
|
|
201
|
+
required: boolean;
|
|
202
|
+
styleClassPassthrough: string[];
|
|
203
|
+
deepCssClassPassthrough: string[];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export interface ApiErrorResponse {
|
|
207
|
+
url: string;
|
|
208
|
+
statusCode: number;
|
|
209
|
+
statusMessage: string;
|
|
210
|
+
message: string;
|
|
211
|
+
stack: string;
|
|
212
|
+
data: {
|
|
213
|
+
errors: {
|
|
214
|
+
[key: string]: string | string[]; // Index signature for dynamic keys
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IFieldsInitialState, IFormFieldsState, IFormFieldsC12, IFormFieldC12, IApiErrorMessages, ICustomErrorMessage, IErrorMessagesArr } from '@/types/types.forms';
|
|
2
|
+
|
|
3
|
+
export interface IZodeFormControl {
|
|
4
|
+
[x: string]: string | boolean | number | URL | object;
|
|
5
|
+
data: IFieldsInitialState;
|
|
6
|
+
validityState: IFormFieldsState;
|
|
7
|
+
dirtyFields: IFormFieldsState;
|
|
8
|
+
focusedField: string;
|
|
9
|
+
isDisabled: boolean;
|
|
10
|
+
isPending: boolean;
|
|
11
|
+
errorCount: number;
|
|
12
|
+
errorMessages: IErrorMessagesArr;
|
|
13
|
+
formFieldsC12: IFormFieldsC12;
|
|
14
|
+
formIsValid: boolean;
|
|
15
|
+
submitAttempted: boolean;
|
|
16
|
+
submitDisabled: boolean;
|
|
17
|
+
submitSuccess: boolean;
|
|
18
|
+
displayErrorMessages: boolean;
|
|
19
|
+
displayLoader: boolean;
|
|
20
|
+
submitSuccessful: boolean;
|
|
21
|
+
}
|