srcdev-nuxt-forms 0.1.0 → 1.0.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/LICENSE +21 -0
- package/assets/styles/brand/_brand.css +150 -0
- package/assets/styles/brand/_brand_dark.css +152 -0
- package/assets/styles/brand/_palette_dark.css +148 -0
- package/assets/styles/brand/_palette_light.css +148 -0
- package/assets/styles/brand/_typography.css +176 -0
- package/assets/styles/brand/index.css +1 -0
- package/assets/styles/forms/index.css +1 -1
- package/assets/styles/forms/themes/_default.css +3 -0
- package/assets/styles/forms/themes/_error.css +45 -11
- package/assets/styles/forms/themes/_ghost.css +42 -10
- package/assets/styles/forms/themes/_primary.css +42 -10
- package/assets/styles/forms/themes/_secondary.css +42 -10
- package/assets/styles/forms/themes/_success.css +42 -11
- package/assets/styles/forms/themes/_tertiary.css +42 -10
- package/assets/styles/forms/themes/_warning.css +42 -10
- package/assets/styles/forms/themes/index.css +1 -0
- package/assets/styles/forms/variables/_palette.css +104 -0
- package/assets/styles/forms/variables/_theme.css +12 -18
- package/assets/styles/forms/variables/index.css +2 -0
- package/assets/styles/main.css +2 -0
- package/assets/styles/scaffolding/_margin-helpers.css +308 -0
- package/assets/styles/scaffolding/_padding-helpers.css +308 -0
- package/assets/styles/scaffolding/_page.css +23 -0
- package/assets/styles/scaffolding/index.css +3 -0
- package/assets/styles/variables/colors/_blue.css +2 -2
- package/assets/styles/variables/colors/_gray.css +2 -1
- package/assets/styles/variables/colors/_green.css +2 -2
- package/assets/styles/variables/colors/_orange.css +2 -2
- package/assets/styles/variables/colors/_red.css +2 -2
- package/assets/styles/variables/colors/_yellow.css +1 -1
- package/components/forms/c12/prop-validators/index.ts +8 -20
- package/components/forms/c12/utils.ts +14 -0
- package/components/forms/c12/validation-patterns/en.json +12 -0
- package/components/forms/form-errors/InputError.vue +177 -0
- package/components/forms/input-button/InputButtonCore.vue +33 -109
- package/components/forms/input-button/variants/InputButtonConfirm.vue +1 -1
- package/components/forms/input-button/variants/InputButtonSubmit.vue +1 -1
- package/components/forms/input-checkbox/InputCheckboxCore.vue +263 -0
- package/components/forms/input-checkbox/InputCheckboxWithLabel.vue +116 -0
- package/components/forms/input-checkbox/variants/MultipleCheckboxes.vue +167 -0
- package/components/forms/input-checkbox/variants/SingleCheckbox.vue +172 -0
- package/components/forms/input-number/InputNumberCore.vue +184 -0
- package/components/forms/input-number/variants/InputNumberDefault.vue +155 -0
- package/components/forms/input-radio/InputRadiobuttonCore.vue +212 -0
- package/components/forms/input-radio/InputRadiobuttonWithLabel.vue +103 -0
- package/components/forms/input-radio/variants/MultipleRadiobuttons.vue +166 -0
- package/components/forms/input-range/InputRangeCore.vue +153 -0
- package/components/forms/input-range/variants/InputRangeDefault.vue +159 -0
- package/components/forms/input-text/InputTextCore.vue +149 -87
- package/components/forms/input-text/variants/material/InputPasswordWithLabel.vue +99 -0
- package/components/forms/input-text/variants/material/InputTextAsNumberWithLabel.vue +142 -0
- package/components/forms/input-text/variants/material/InputTextWithLabel.vue +125 -0
- package/components/forms/input-textarea/InputTextareaCore.vue +161 -0
- package/components/forms/input-textarea/variants/InputTextareaWithLabel.vue +106 -0
- package/components/scaffolding/footer/NavFooter.vue +62 -0
- package/components/ui/content-grid/ContentGrid.vue +85 -0
- package/composables/useApiRequest.ts +25 -0
- package/composables/useErrorMessages.ts +17 -5
- package/composables/useFormControl.ts +149 -37
- package/composables/useSleep.ts +2 -2
- package/composables/useStyleClassPassthrough.ts +30 -0
- package/composables/useZodValidation.ts +120 -0
- package/layouts/default.vue +26 -16
- package/nuxt.config.ts +22 -0
- package/package.json +13 -8
- package/pages/forms/examples/buttons/index.vue +14 -13
- package/pages/forms/examples/material/cssbattle.vue +60 -0
- package/pages/forms/examples/material/text-fields.vue +551 -93
- package/pages/index.vue +2 -2
- package/pages/limit-text.vue +43 -0
- package/pages/typography.vue +83 -0
- package/server/api/places/list.get.ts +23 -0
- package/server/api/textFields.post.ts +37 -0
- package/server/api/utils/index.get.ts +20 -0
- package/server/data/places/cities.json +43 -0
- package/server/data/places/countries.json +55 -0
- package/server/data/utils/title.json +49 -0
- package/types/types.forms.ts +135 -3
- package/types/types.places.ts +8 -0
- package/types/types.zodFormControl.ts +21 -0
- package/components/forms/input-text/variants/material/InputEmailMaterial.vue +0 -72
- package/components/forms/input-text/variants/material/InputPasswordMaterial.vue +0 -88
- package/components/forms/input-text/variants/material/InputTextMaterial.vue +0 -75
- package/components/forms/input-text/variants/material/InputTextMaterialCore.vue +0 -258
- package/composables/useUpdateStyleClassPassthrough.ts +0 -29
- package/pages/forms/examples/material/text-fields-compact.vue +0 -136
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { IFormData, IFieldsInitialState, ICustomErrorMessage,
|
|
1
|
+
import type { IFormData, IFieldsInitialState, IFormFieldC12, IApiErrorMessages, ICustomErrorMessage, IErrorMessagesArr } from '@/types/types.forms';
|
|
2
|
+
import { formFieldC12 } from '@/components/forms/c12/utils';
|
|
2
3
|
|
|
3
|
-
export function useFormControl(
|
|
4
|
+
// export function useFormControl(name: string = '') {
|
|
5
|
+
export function useFormControl(name: string = '') {
|
|
4
6
|
let savedInitialState = {};
|
|
5
7
|
|
|
6
8
|
const formData = ref<IFormData>({
|
|
@@ -10,24 +12,27 @@ export function useFormControl(fieldsInitialState: IFieldsInitialState | Ref<IFi
|
|
|
10
12
|
focusedField: '',
|
|
11
13
|
isPending: false,
|
|
12
14
|
errorCount: 0,
|
|
13
|
-
|
|
15
|
+
errorMessages: {},
|
|
16
|
+
formFieldsC12: {},
|
|
14
17
|
hasCustomErrorMessages: false,
|
|
15
18
|
formIsValid: false,
|
|
16
|
-
|
|
19
|
+
submitAttempted: false,
|
|
17
20
|
submitDisabled: false,
|
|
21
|
+
submitSuccess: false,
|
|
22
|
+
displayErrorMessages: false,
|
|
18
23
|
});
|
|
19
24
|
|
|
20
|
-
const initValidationState = async () => {
|
|
25
|
+
const initValidationState = async (fieldsInitialState: IFieldsInitialState | Ref<IFieldsInitialState | null>) => {
|
|
21
26
|
const fields = Object.keys(fieldsInitialState.value || {});
|
|
22
|
-
const state = fields.reduce((
|
|
23
|
-
|
|
24
|
-
return
|
|
27
|
+
const state = fields.reduce((accumulatedFields, field) => {
|
|
28
|
+
accumulatedFields[field] = false;
|
|
29
|
+
return accumulatedFields;
|
|
25
30
|
}, {} as Record<string, boolean>);
|
|
26
31
|
formData.value.validityState = state;
|
|
27
32
|
};
|
|
28
33
|
|
|
29
|
-
const initFormData = async () => {
|
|
30
|
-
|
|
34
|
+
const initFormData = async (fieldsInitialState: IFieldsInitialState | Ref<IFieldsInitialState | null>) => {
|
|
35
|
+
initValidationState(fieldsInitialState);
|
|
31
36
|
|
|
32
37
|
if (fieldsInitialState !== null) {
|
|
33
38
|
savedInitialState = toRaw(fieldsInitialState.value) as IFieldsInitialState;
|
|
@@ -36,6 +41,19 @@ export function useFormControl(fieldsInitialState: IFieldsInitialState | Ref<IFi
|
|
|
36
41
|
return;
|
|
37
42
|
};
|
|
38
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
|
+
|
|
39
57
|
const getErrorCount = async (updateState: boolean = false) => {
|
|
40
58
|
await nextTick();
|
|
41
59
|
|
|
@@ -45,17 +63,26 @@ export function useFormControl(fieldsInitialState: IFieldsInitialState | Ref<IFi
|
|
|
45
63
|
|
|
46
64
|
if (updateState) {
|
|
47
65
|
formData.value.submitDisabled = true;
|
|
66
|
+
formData.value.displayErrorMessages = formData.value.errorCount > 0;
|
|
67
|
+
formData.value.submitAttempted = true;
|
|
48
68
|
}
|
|
49
69
|
|
|
50
70
|
if (formData.value.submitDisabled) {
|
|
51
71
|
formData.value.submitDisabled = !formData.value.formIsValid;
|
|
52
72
|
}
|
|
53
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
|
+
|
|
54
81
|
return formData.value.errorCount;
|
|
55
82
|
};
|
|
56
83
|
|
|
57
84
|
// Function to count items with "useCustomError" set to true
|
|
58
|
-
const countItemsWithCustomError = (obj:
|
|
85
|
+
const countItemsWithCustomError = (obj: IErrorMessagesArr) => {
|
|
59
86
|
let count = 0;
|
|
60
87
|
|
|
61
88
|
for (const key in obj) {
|
|
@@ -70,37 +97,100 @@ export function useFormControl(fieldsInitialState: IFieldsInitialState | Ref<IFi
|
|
|
70
97
|
/*
|
|
71
98
|
* Useage:
|
|
72
99
|
*
|
|
73
|
-
* const {
|
|
100
|
+
* const { updateErrorMessages } = useFormControl();
|
|
74
101
|
*
|
|
75
102
|
* Add/Update entry
|
|
76
103
|
* const sampleCustomErrorEmail = {
|
|
77
104
|
* useCustomError: true,
|
|
78
105
|
* message: "This is a sample custom error for error EMAIL",
|
|
79
106
|
* };
|
|
80
|
-
*
|
|
107
|
+
* updateErrorMessages("email", sampleCustomErrorEmail);
|
|
81
108
|
*/
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
84
|
-
formData.value.validityState[name] = valid;
|
|
85
|
-
formData.value.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
};
|
|
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;
|
|
89
130
|
}
|
|
90
|
-
formData.value.hasCustomErrorMessages = countItemsWithCustomError(formData.value.
|
|
131
|
+
formData.value.hasCustomErrorMessages = countItemsWithCustomError(formData.value.errorMessages) > 0;
|
|
91
132
|
};
|
|
92
133
|
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
+
}
|
|
100
161
|
};
|
|
101
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
|
+
|
|
102
192
|
const formIsValid = computed(() => {
|
|
103
|
-
return formData.value.
|
|
193
|
+
return formData.value.formIsValid;
|
|
104
194
|
});
|
|
105
195
|
|
|
106
196
|
const submitDisabled = computed(() => {
|
|
@@ -109,9 +199,12 @@ export function useFormControl(fieldsInitialState: IFieldsInitialState | Ref<IFi
|
|
|
109
199
|
|
|
110
200
|
// Keep an eye on this for performance issue
|
|
111
201
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
+
// };
|
|
115
208
|
|
|
116
209
|
watch(
|
|
117
210
|
() => formData.value.validityState,
|
|
@@ -121,16 +214,35 @@ export function useFormControl(fieldsInitialState: IFieldsInitialState | Ref<IFi
|
|
|
121
214
|
{ deep: true }
|
|
122
215
|
);
|
|
123
216
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
+
);
|
|
127
233
|
|
|
128
234
|
return {
|
|
129
235
|
formData,
|
|
236
|
+
initFormData,
|
|
237
|
+
initFormFieldsC12,
|
|
130
238
|
getErrorCount,
|
|
131
|
-
|
|
132
|
-
resetForm,
|
|
239
|
+
updateErrorMessages,
|
|
240
|
+
// resetForm,
|
|
133
241
|
formIsValid,
|
|
134
242
|
submitDisabled,
|
|
243
|
+
useApiErrors,
|
|
244
|
+
// fieldHasError,
|
|
245
|
+
fieldIsDirty,
|
|
246
|
+
// updateFieldValidity,
|
|
135
247
|
};
|
|
136
248
|
}
|
package/composables/useSleep.ts
CHANGED
|
@@ -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,120 @@
|
|
|
1
|
+
import { z, ZodError } from 'zod';
|
|
2
|
+
import type { IFormFieldStateObj, ApiErrorResponse } from '@/types/types.forms';
|
|
3
|
+
|
|
4
|
+
const useZodValidation = (formSchema: any) => {
|
|
5
|
+
const zodFormControl = reactive({
|
|
6
|
+
errorCount: 0,
|
|
7
|
+
displayLoader: false,
|
|
8
|
+
submitDisabled: false,
|
|
9
|
+
submitAttempted: false,
|
|
10
|
+
submitSuccessful: false,
|
|
11
|
+
formIsValid: false,
|
|
12
|
+
isPending: false,
|
|
13
|
+
isDisabled: false,
|
|
14
|
+
previousState: {} as Record<string, any>,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
type formSchema = z.infer<typeof formSchema>;
|
|
18
|
+
const zodErrorObj = ref<z.ZodFormattedError<formSchema> | null>(null);
|
|
19
|
+
|
|
20
|
+
const resetPreviousValues = () => {
|
|
21
|
+
for (const [field] of Object.entries(formSchema.shape)) {
|
|
22
|
+
const previousValue = {
|
|
23
|
+
value: null,
|
|
24
|
+
message: '',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
zodFormControl.previousState[field] = previousValue;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const initZodForm = () => {
|
|
32
|
+
resetPreviousValues();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const getErrorCount = (zodErrorObj: Ref<z.ZodFormattedError<formSchema> | null>) => {
|
|
36
|
+
const zodCountErrors = zodErrorObj.value ?? [];
|
|
37
|
+
// @ts-ignore
|
|
38
|
+
delete zodCountErrors._errors;
|
|
39
|
+
const errorCount = Object.keys(zodCountErrors ?? []).length;
|
|
40
|
+
return errorCount;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const transformErrorMessages = (errors: any) => {
|
|
44
|
+
const apiErrors = ref({}) as any;
|
|
45
|
+
for (const [key, value] of Object.entries(errors)) {
|
|
46
|
+
const fieldPath = key.split('.').map((key: string) => key.charAt(0).toLowerCase() + key.slice(1));
|
|
47
|
+
apiErrors.value[fieldPath.join('.')] = value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return apiErrors.value;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const updatePreviousValue = async (field: string, message: string, state: Record<string, any>) => {
|
|
54
|
+
const previousValue = {
|
|
55
|
+
value: state[field],
|
|
56
|
+
message: message,
|
|
57
|
+
};
|
|
58
|
+
zodFormControl.previousState[field] = previousValue;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const pushCustomErrors = async (apiErrorResponse: ApiErrorResponse, state: Record<string, any>) => {
|
|
62
|
+
const apiErrors = transformErrorMessages(apiErrorResponse.data.errors);
|
|
63
|
+
|
|
64
|
+
// 1: Create a ZodError object to hold the issues
|
|
65
|
+
const zodError = new ZodError([]);
|
|
66
|
+
|
|
67
|
+
// 2: Reset previous state values
|
|
68
|
+
resetPreviousValues();
|
|
69
|
+
|
|
70
|
+
// 3: Add issues to the ZodError object
|
|
71
|
+
for (const [path, message] of Object.entries(apiErrors)) {
|
|
72
|
+
zodError.addIssue({
|
|
73
|
+
path: path.split('.'),
|
|
74
|
+
message: message as string,
|
|
75
|
+
code: z.ZodIssueCode.custom,
|
|
76
|
+
});
|
|
77
|
+
await updatePreviousValue(path, message as string, state);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
zodErrorObj.value = zodError.format();
|
|
81
|
+
zodFormControl.errorCount = getErrorCount(zodErrorObj);
|
|
82
|
+
zodFormControl.formIsValid = zodFormControl.errorCount === 0;
|
|
83
|
+
zodFormControl.displayLoader = false;
|
|
84
|
+
zodFormControl.submitAttempted = true;
|
|
85
|
+
return zodErrorObj.value;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const doZodValidate = async (state: Record<string, any>) => {
|
|
89
|
+
const valid = formSchema.safeParse(toRaw(state));
|
|
90
|
+
if (!valid.success) {
|
|
91
|
+
zodErrorObj.value = valid.error.format();
|
|
92
|
+
} else {
|
|
93
|
+
zodErrorObj.value = null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
zodFormControl.errorCount = getErrorCount(zodErrorObj);
|
|
97
|
+
zodFormControl.formIsValid = valid.success;
|
|
98
|
+
|
|
99
|
+
return valid.success;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const fieldMaxLength = (name: string) => {
|
|
103
|
+
const fieldSchema = formSchema.shape[name];
|
|
104
|
+
if (fieldSchema instanceof z.ZodString) {
|
|
105
|
+
return fieldSchema._def.checks.find((check) => check.kind === 'max')?.value || null;
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
initZodForm,
|
|
112
|
+
zodFormControl,
|
|
113
|
+
zodErrorObj,
|
|
114
|
+
pushCustomErrors,
|
|
115
|
+
doZodValidate,
|
|
116
|
+
fieldMaxLength,
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export default useZodValidation;
|
package/layouts/default.vue
CHANGED
|
@@ -4,31 +4,22 @@
|
|
|
4
4
|
<h1><NuxtLink to="/">Home</NuxtLink></h1>
|
|
5
5
|
<ul class="flex-group">
|
|
6
6
|
<li>
|
|
7
|
-
<NuxtLink to="/
|
|
8
|
-
>Material UI text fields</NuxtLink
|
|
9
|
-
>
|
|
7
|
+
<NuxtLink to="/typography" class="link-normal">Typography</NuxtLink>
|
|
10
8
|
</li>
|
|
11
9
|
<li>
|
|
12
|
-
<NuxtLink to="/forms/examples/material/text-fields-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
<NuxtLink to="/forms/examples/material/text-fields" class="link-normal">Material UI text fields</NuxtLink>
|
|
11
|
+
</li>
|
|
12
|
+
<li>
|
|
13
|
+
<NuxtLink to="/forms/examples/buttons" class="link-normal">Buttons</NuxtLink>
|
|
15
14
|
</li>
|
|
16
15
|
</ul>
|
|
17
16
|
</div>
|
|
18
|
-
<h2>Buttons</h2>
|
|
19
|
-
<ul class="flex-group">
|
|
20
|
-
<li>
|
|
21
|
-
<NuxtLink to="/forms/examples/buttons">Buttons</NuxtLink>
|
|
22
|
-
</li>
|
|
23
|
-
</ul>
|
|
24
17
|
|
|
25
|
-
<div>
|
|
18
|
+
<div class="page-layout-content">
|
|
26
19
|
<slot name="layout-content"></slot>
|
|
27
20
|
</div>
|
|
28
21
|
|
|
29
|
-
<
|
|
30
|
-
<h1>Footer</h1>
|
|
31
|
-
</div>
|
|
22
|
+
<NavFooter />
|
|
32
23
|
</div>
|
|
33
24
|
</template>
|
|
34
25
|
|
|
@@ -38,6 +29,21 @@ useHead({
|
|
|
38
29
|
class: 'body-default',
|
|
39
30
|
id: 'body',
|
|
40
31
|
},
|
|
32
|
+
// link: [
|
|
33
|
+
// {
|
|
34
|
+
// rel: 'preconnect',
|
|
35
|
+
// href: 'https://fonts.googleapis.com',
|
|
36
|
+
// },
|
|
37
|
+
// {
|
|
38
|
+
// rel: 'preconnect',
|
|
39
|
+
// href: 'https://fonts.gstatic.com',
|
|
40
|
+
// crossorigin: 'use-credentials',
|
|
41
|
+
// },
|
|
42
|
+
// {
|
|
43
|
+
// href: 'https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap',
|
|
44
|
+
// rel: 'stylesheet',
|
|
45
|
+
// },
|
|
46
|
+
// ],
|
|
41
47
|
});
|
|
42
48
|
</script>
|
|
43
49
|
|
|
@@ -47,6 +53,10 @@ useHead({
|
|
|
47
53
|
grid-template-rows: auto 1fr auto;
|
|
48
54
|
}
|
|
49
55
|
|
|
56
|
+
.page-layout-content {
|
|
57
|
+
container: content / inline-size;
|
|
58
|
+
}
|
|
59
|
+
|
|
50
60
|
.flex-group {
|
|
51
61
|
align-items: flex-start;
|
|
52
62
|
display: flex;
|
package/nuxt.config.ts
CHANGED
|
@@ -4,7 +4,26 @@ const { resolve } = createResolver(import.meta.url);
|
|
|
4
4
|
|
|
5
5
|
export default defineNuxtConfig({
|
|
6
6
|
devtools: { enabled: true },
|
|
7
|
+
|
|
8
|
+
app: {
|
|
9
|
+
head: {
|
|
10
|
+
htmlAttrs: {
|
|
11
|
+
lang: 'en',
|
|
12
|
+
},
|
|
13
|
+
titleTemplate: '%s - Website name',
|
|
14
|
+
meta: [{ charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' }],
|
|
15
|
+
},
|
|
16
|
+
pageTransition: {
|
|
17
|
+
name: 'page',
|
|
18
|
+
mode: 'out-in',
|
|
19
|
+
},
|
|
20
|
+
layoutTransition: {
|
|
21
|
+
name: 'layout',
|
|
22
|
+
mode: 'out-in',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
7
25
|
css: [resolve('./assets/styles/main.css')],
|
|
26
|
+
|
|
8
27
|
runtimeConfig: {
|
|
9
28
|
public: {
|
|
10
29
|
validatorLocale: 'en-GB',
|
|
@@ -12,10 +31,13 @@ export default defineNuxtConfig({
|
|
|
12
31
|
},
|
|
13
32
|
|
|
14
33
|
modules: ['@nuxt/icon'],
|
|
34
|
+
|
|
15
35
|
components: [
|
|
16
36
|
{
|
|
17
37
|
path: './components',
|
|
18
38
|
pathPrefix: false,
|
|
19
39
|
},
|
|
20
40
|
],
|
|
41
|
+
|
|
42
|
+
compatibilityDate: '2024-07-13',
|
|
21
43
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "srcdev-nuxt-forms",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "1.0.0",
|
|
5
5
|
"main": "./nuxt.config.ts",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"reinstall": "rm -rf node_modules && npm install",
|
|
@@ -14,12 +14,17 @@
|
|
|
14
14
|
"release": "release-it"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@iconify-json/
|
|
18
|
-
"@
|
|
19
|
-
"@
|
|
20
|
-
"eslint": "
|
|
21
|
-
"nuxt": "
|
|
22
|
-
"
|
|
23
|
-
"
|
|
17
|
+
"@iconify-json/gridicons": "1.2.1",
|
|
18
|
+
"@iconify-json/material-symbols": "1.2.5",
|
|
19
|
+
"@iconify-json/radix-icons": "1.2.1",
|
|
20
|
+
"@nuxt/eslint-config": "0.6.1",
|
|
21
|
+
"@nuxt/icon": "1.6.1",
|
|
22
|
+
"eslint": "9.13.0",
|
|
23
|
+
"nuxt": "3.13.2",
|
|
24
|
+
"release-it": "17.10.0",
|
|
25
|
+
"typescript": "5.6.3"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"zod": "3.23.8"
|
|
24
29
|
}
|
|
25
30
|
}
|
|
@@ -9,25 +9,25 @@
|
|
|
9
9
|
<p>Themes switcher</p>
|
|
10
10
|
<ul class="flex-group">
|
|
11
11
|
<li>
|
|
12
|
-
<InputButtonSubmit @click.stop.prevent="swapTheme('primary')" :is-pending="false" button-text="Primary" theme="primary" size="normal" />
|
|
12
|
+
<InputButtonSubmit type="button" @click.stop.prevent="swapTheme('primary')" :is-pending="false" button-text="Primary" theme="primary" size="normal" />
|
|
13
13
|
</li>
|
|
14
14
|
<li>
|
|
15
|
-
<InputButtonSubmit @click.stop.prevent="swapTheme('secondary')" :is-pending="false" button-text="Secondary" theme="secondary" size="normal" />
|
|
15
|
+
<InputButtonSubmit type="button" @click.stop.prevent="swapTheme('secondary')" :is-pending="false" button-text="Secondary" theme="secondary" size="normal" />
|
|
16
16
|
</li>
|
|
17
17
|
<li>
|
|
18
|
-
<InputButtonSubmit @click.stop.prevent="swapTheme('tertiary')" :is-pending="false" button-text="Tertiary" theme="tertiary" size="normal" />
|
|
18
|
+
<InputButtonSubmit type="button" @click.stop.prevent="swapTheme('tertiary')" :is-pending="false" button-text="Tertiary" theme="tertiary" size="normal" />
|
|
19
19
|
</li>
|
|
20
20
|
<li>
|
|
21
|
-
<InputButtonSubmit @click.stop.prevent="swapTheme('warning')" :is-pending="false" button-text="Warning" theme="warning" size="normal" />
|
|
21
|
+
<InputButtonSubmit type="button" @click.stop.prevent="swapTheme('warning')" :is-pending="false" button-text="Warning" theme="warning" size="normal" />
|
|
22
22
|
</li>
|
|
23
23
|
<li>
|
|
24
|
-
<InputButtonSubmit @click.stop.prevent="swapTheme('success')" :is-pending="false" button-text="Success" theme="success" size="normal" />
|
|
24
|
+
<InputButtonSubmit type="button" @click.stop.prevent="swapTheme('success')" :is-pending="false" button-text="Success" theme="success" size="normal" />
|
|
25
25
|
</li>
|
|
26
26
|
<li>
|
|
27
|
-
<InputButtonSubmit @click.stop.prevent="swapTheme('error')" :is-pending="false" button-text="Error" theme="error" size="normal" />
|
|
27
|
+
<InputButtonSubmit type="button" @click.stop.prevent="swapTheme('error')" :is-pending="false" button-text="Error" theme="error" size="normal" />
|
|
28
28
|
</li>
|
|
29
29
|
<li>
|
|
30
|
-
<InputButtonSubmit @click.stop.prevent="swapTheme('ghost')" :is-pending="false" button-text="Ghost" theme="ghost" size="normal" />
|
|
30
|
+
<InputButtonSubmit type="button" @click.stop.prevent="swapTheme('ghost')" :is-pending="false" button-text="Ghost" theme="ghost" size="normal" />
|
|
31
31
|
</li>
|
|
32
32
|
</ul>
|
|
33
33
|
|
|
@@ -35,13 +35,13 @@
|
|
|
35
35
|
<template #default>
|
|
36
36
|
<form @submit.prevent="submitForm">
|
|
37
37
|
<div class="flex-group">
|
|
38
|
-
<InputButtonSubmit @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="x-small" />
|
|
38
|
+
<InputButtonSubmit type="button" @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="x-small" />
|
|
39
39
|
|
|
40
|
-
<InputButtonSubmit @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="small" />
|
|
40
|
+
<InputButtonSubmit type="button" @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="small" />
|
|
41
41
|
|
|
42
|
-
<InputButtonSubmit @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="normal" />
|
|
43
|
-
<InputButtonSubmit @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="medium" />
|
|
44
|
-
<InputButtonSubmit @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="large" />
|
|
42
|
+
<InputButtonSubmit type="button" @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="normal" />
|
|
43
|
+
<InputButtonSubmit type="button" @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="medium" />
|
|
44
|
+
<InputButtonSubmit type="button" @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="large" />
|
|
45
45
|
</div>
|
|
46
46
|
|
|
47
47
|
<div class="flex-group">
|
|
@@ -122,7 +122,8 @@ const fieldsInitialState = ref<IFieldsInitialState>({
|
|
|
122
122
|
});
|
|
123
123
|
|
|
124
124
|
// Setup formData
|
|
125
|
-
const { formData,
|
|
125
|
+
const { formData, initFormData, getErrorCount, updateErrorMessages, formIsValid, submitDisabled, useApiErrors } = useFormControl();
|
|
126
|
+
await initFormData(fieldsInitialState);
|
|
126
127
|
|
|
127
128
|
const submitForm = async () => {
|
|
128
129
|
await getErrorCount(true);
|