srcdev-nuxt-forms 2.0.3 → 2.0.4
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/package.json +19 -2
- package/.editorconfig +0 -12
- package/.eslintignore +0 -7
- package/.eslintrc.cjs +0 -12
- package/.nmprc +0 -2
- package/.nuxtrc +0 -1
- package/.playground/components/scaffolding/footer/NavFooter.vue +0 -62
- package/.playground/components/ui/content-grid/ContentGrid.vue +0 -85
- package/.playground/composables/useApiRequest.ts +0 -25
- package/.playground/composables/useErrorMessages.ts +0 -59
- package/.playground/composables/useFormControl.ts +0 -248
- package/.playground/composables/useSleep.ts +0 -5
- package/.playground/composables/useStyleClassPassthrough.ts +0 -30
- package/.playground/composables/useZodValidation.ts +0 -120
- package/.playground/layouts/default.vue +0 -72
- package/.playground/nuxt.config.ts +0 -27
- package/.playground/pages/forms/examples/buttons/index.vue +0 -155
- package/.playground/pages/forms/examples/material/cssbattle.vue +0 -60
- package/.playground/pages/forms/examples/material/text-fields.vue +0 -594
- package/.playground/pages/index.vue +0 -33
- package/.playground/pages/limit-text.vue +0 -43
- package/.playground/pages/typography.vue +0 -83
- package/.playground/server/api/places/list.get.ts +0 -23
- package/.playground/server/api/textFields.post.ts +0 -37
- package/.playground/server/api/utils/index.get.ts +0 -20
- package/.playground/server/data/places/cities.json +0 -43
- package/.playground/server/data/places/countries.json +0 -55
- package/.playground/server/data/utils/title.json +0 -49
- package/.playground/types/types.forms.ts +0 -216
- package/.playground/types/types.places.ts +0 -8
- package/.playground/types/types.zodFormControl.ts +0 -21
- package/.prettierrc +0 -5
- package/.release-it.json +0 -6
- package/tsconfig.json +0 -3
|
@@ -1,120 +0,0 @@
|
|
|
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;
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="page-layout">
|
|
3
|
-
<div>
|
|
4
|
-
<h1><NuxtLink to="/">Home</NuxtLink></h1>
|
|
5
|
-
<ul class="flex-group">
|
|
6
|
-
<li>
|
|
7
|
-
<NuxtLink to="/typography" class="link-normal">Typography</NuxtLink>
|
|
8
|
-
</li>
|
|
9
|
-
<li>
|
|
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>
|
|
14
|
-
</li>
|
|
15
|
-
</ul>
|
|
16
|
-
</div>
|
|
17
|
-
|
|
18
|
-
<div class="page-layout-content">
|
|
19
|
-
<slot name="layout-content"></slot>
|
|
20
|
-
</div>
|
|
21
|
-
|
|
22
|
-
<NavFooter />
|
|
23
|
-
</div>
|
|
24
|
-
</template>
|
|
25
|
-
|
|
26
|
-
<script setup lang="ts">
|
|
27
|
-
useHead({
|
|
28
|
-
bodyAttrs: {
|
|
29
|
-
class: 'body-default',
|
|
30
|
-
id: 'body',
|
|
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
|
-
// ],
|
|
47
|
-
});
|
|
48
|
-
</script>
|
|
49
|
-
|
|
50
|
-
<style lang="css">
|
|
51
|
-
.page-layout {
|
|
52
|
-
display: grid;
|
|
53
|
-
grid-template-rows: auto 1fr auto;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
.page-layout-content {
|
|
57
|
-
container: content / inline-size;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
.flex-group {
|
|
61
|
-
align-items: flex-start;
|
|
62
|
-
display: flex;
|
|
63
|
-
flex-wrap: wrap;
|
|
64
|
-
gap: 24px;
|
|
65
|
-
margin-bottom: 32px;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
ul.flex-group {
|
|
69
|
-
list-style-type: none;
|
|
70
|
-
padding: 0;
|
|
71
|
-
}
|
|
72
|
-
</style>
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
export default defineNuxtConfig({
|
|
2
|
-
devtools: { enabled: true },
|
|
3
|
-
extends: ['..'],
|
|
4
|
-
app: {
|
|
5
|
-
head: {
|
|
6
|
-
htmlAttrs: {
|
|
7
|
-
lang: 'en',
|
|
8
|
-
},
|
|
9
|
-
titleTemplate: '%s - Website name',
|
|
10
|
-
meta: [{ charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' }],
|
|
11
|
-
},
|
|
12
|
-
pageTransition: {
|
|
13
|
-
name: 'page',
|
|
14
|
-
mode: 'out-in',
|
|
15
|
-
},
|
|
16
|
-
layoutTransition: {
|
|
17
|
-
name: 'layout',
|
|
18
|
-
mode: 'out-in',
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
components: [
|
|
22
|
-
{
|
|
23
|
-
path: './components',
|
|
24
|
-
pathPrefix: false,
|
|
25
|
-
},
|
|
26
|
-
],
|
|
27
|
-
});
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div>
|
|
3
|
-
<NuxtLayout name="default">
|
|
4
|
-
<template #layout-content>
|
|
5
|
-
<div>
|
|
6
|
-
<h1>Example buttons</h1>
|
|
7
|
-
<p>Primary submit</p>
|
|
8
|
-
|
|
9
|
-
<p>Themes switcher</p>
|
|
10
|
-
<ul class="flex-group">
|
|
11
|
-
<li>
|
|
12
|
-
<InputButtonSubmit type="button" @click.stop.prevent="swapTheme('primary')" :is-pending="false" button-text="Primary" theme="primary" size="normal" />
|
|
13
|
-
</li>
|
|
14
|
-
<li>
|
|
15
|
-
<InputButtonSubmit type="button" @click.stop.prevent="swapTheme('secondary')" :is-pending="false" button-text="Secondary" theme="secondary" size="normal" />
|
|
16
|
-
</li>
|
|
17
|
-
<li>
|
|
18
|
-
<InputButtonSubmit type="button" @click.stop.prevent="swapTheme('tertiary')" :is-pending="false" button-text="Tertiary" theme="tertiary" size="normal" />
|
|
19
|
-
</li>
|
|
20
|
-
<li>
|
|
21
|
-
<InputButtonSubmit type="button" @click.stop.prevent="swapTheme('warning')" :is-pending="false" button-text="Warning" theme="warning" size="normal" />
|
|
22
|
-
</li>
|
|
23
|
-
<li>
|
|
24
|
-
<InputButtonSubmit type="button" @click.stop.prevent="swapTheme('success')" :is-pending="false" button-text="Success" theme="success" size="normal" />
|
|
25
|
-
</li>
|
|
26
|
-
<li>
|
|
27
|
-
<InputButtonSubmit type="button" @click.stop.prevent="swapTheme('error')" :is-pending="false" button-text="Error" theme="error" size="normal" />
|
|
28
|
-
</li>
|
|
29
|
-
<li>
|
|
30
|
-
<InputButtonSubmit type="button" @click.stop.prevent="swapTheme('ghost')" :is-pending="false" button-text="Ghost" theme="ghost" size="normal" />
|
|
31
|
-
</li>
|
|
32
|
-
</ul>
|
|
33
|
-
|
|
34
|
-
<FormWrapper width="medium">
|
|
35
|
-
<template #default>
|
|
36
|
-
<form @submit.prevent="submitForm">
|
|
37
|
-
<div class="flex-group">
|
|
38
|
-
<InputButtonSubmit type="button" @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="x-small" />
|
|
39
|
-
|
|
40
|
-
<InputButtonSubmit type="button" @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="small" />
|
|
41
|
-
|
|
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
|
-
</div>
|
|
46
|
-
|
|
47
|
-
<div class="flex-group">
|
|
48
|
-
<InputButtonConfirm @click.stop.prevent="submitForm" :is-pending="false" button-text="Confirm" :theme size="x-small" />
|
|
49
|
-
<InputButtonConfirm @click.stop.prevent="submitForm" :is-pending="false" button-text="Confirm" :theme size="small" />
|
|
50
|
-
<InputButtonConfirm @click.stop.prevent="submitForm" :is-pending="false" button-text="Confirm" :theme size="normal" />
|
|
51
|
-
<InputButtonConfirm @click.stop.prevent="submitForm" :is-pending="false" button-text="Confirm" :theme size="medium" />
|
|
52
|
-
<InputButtonConfirm @click.stop.prevent="submitForm" :is-pending="false" button-text="Confirm" :theme size="large" />
|
|
53
|
-
</div>
|
|
54
|
-
<div class="flex-group">
|
|
55
|
-
<InputButtonCore @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="x-small">
|
|
56
|
-
<template #iconOnly>
|
|
57
|
-
<Icon name="radix-icons:eye-none" class="icon" />
|
|
58
|
-
</template>
|
|
59
|
-
</InputButtonCore>
|
|
60
|
-
|
|
61
|
-
<InputButtonCore @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="small">
|
|
62
|
-
<template #iconOnly>
|
|
63
|
-
<Icon name="radix-icons:eye-none" class="icon" />
|
|
64
|
-
</template>
|
|
65
|
-
</InputButtonCore>
|
|
66
|
-
|
|
67
|
-
<InputButtonCore @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="normal">
|
|
68
|
-
<template #iconOnly>
|
|
69
|
-
<Icon name="radix-icons:eye-none" class="icon" />
|
|
70
|
-
</template>
|
|
71
|
-
</InputButtonCore>
|
|
72
|
-
|
|
73
|
-
<InputButtonCore @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="medium">
|
|
74
|
-
<template #iconOnly>
|
|
75
|
-
<Icon name="radix-icons:eye-none" class="icon" />
|
|
76
|
-
</template>
|
|
77
|
-
</InputButtonCore>
|
|
78
|
-
|
|
79
|
-
<InputButtonCore @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="large">
|
|
80
|
-
<template #iconOnly>
|
|
81
|
-
<Icon name="radix-icons:eye-none" class="icon" />
|
|
82
|
-
</template>
|
|
83
|
-
</InputButtonCore>
|
|
84
|
-
</div>
|
|
85
|
-
</form>
|
|
86
|
-
</template>
|
|
87
|
-
</FormWrapper>
|
|
88
|
-
</div>
|
|
89
|
-
</template>
|
|
90
|
-
</NuxtLayout>
|
|
91
|
-
</div>
|
|
92
|
-
</template>
|
|
93
|
-
|
|
94
|
-
<script setup lang="ts">
|
|
95
|
-
import type { IFieldsInitialState, IOptionsConfig } from '@/types/types.forms';
|
|
96
|
-
|
|
97
|
-
definePageMeta({
|
|
98
|
-
layout: false,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
useHead({
|
|
102
|
-
title: 'Homepage',
|
|
103
|
-
meta: [{ name: 'description', content: 'Homepage' }],
|
|
104
|
-
bodyAttrs: {
|
|
105
|
-
class: '',
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
const theme = ref('primary');
|
|
110
|
-
|
|
111
|
-
const swapTheme = (newTheme: string) => {
|
|
112
|
-
theme.value = newTheme;
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
/*
|
|
116
|
-
* Setup forms
|
|
117
|
-
*/
|
|
118
|
-
const fieldsInitialState = ref<IFieldsInitialState>({
|
|
119
|
-
emailAddress: '',
|
|
120
|
-
username: '',
|
|
121
|
-
password: '',
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// Setup formData
|
|
125
|
-
const { formData, initFormData, getErrorCount, updateErrorMessages, formIsValid, submitDisabled, useApiErrors } = useFormControl();
|
|
126
|
-
await initFormData(fieldsInitialState);
|
|
127
|
-
|
|
128
|
-
const submitForm = async () => {
|
|
129
|
-
await getErrorCount(true);
|
|
130
|
-
|
|
131
|
-
if (formIsValid.value) {
|
|
132
|
-
formData.value.isPending = true;
|
|
133
|
-
console.log('Form is good - post it!');
|
|
134
|
-
// await useSleep(2000);
|
|
135
|
-
// formData.value.isPending = false;
|
|
136
|
-
} else {
|
|
137
|
-
console.warn('Form has errors');
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
</script>
|
|
141
|
-
|
|
142
|
-
<style lang="css">
|
|
143
|
-
.flex-group {
|
|
144
|
-
align-items: flex-start;
|
|
145
|
-
display: flex;
|
|
146
|
-
flex-wrap: wrap;
|
|
147
|
-
gap: 24px;
|
|
148
|
-
margin-bottom: 32px;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
ul.flex-group {
|
|
152
|
-
list-style-type: none;
|
|
153
|
-
padding: 0;
|
|
154
|
-
}
|
|
155
|
-
</style>
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div>
|
|
3
|
-
<i a></i>
|
|
4
|
-
<i b></i>
|
|
5
|
-
<i c></i>
|
|
6
|
-
</div>
|
|
7
|
-
</template>
|
|
8
|
-
<script setup lang="ts">
|
|
9
|
-
definePageMeta({
|
|
10
|
-
layout: false,
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
useHead({
|
|
14
|
-
title: 'CSS Battle',
|
|
15
|
-
meta: [{ name: 'description', content: 'CSS Battle' }],
|
|
16
|
-
bodyAttrs: {
|
|
17
|
-
class: '',
|
|
18
|
-
},
|
|
19
|
-
});
|
|
20
|
-
</script>
|
|
21
|
-
|
|
22
|
-
<style scoped lang="css">
|
|
23
|
-
html * {
|
|
24
|
-
margin: 0;
|
|
25
|
-
padding: 0;
|
|
26
|
-
}
|
|
27
|
-
div {
|
|
28
|
-
height: 300px;
|
|
29
|
-
width: 400px;
|
|
30
|
-
background: #0b2429;
|
|
31
|
-
display: grid;
|
|
32
|
-
grid-template-areas: stack;
|
|
33
|
-
place-content: center;
|
|
34
|
-
|
|
35
|
-
i {
|
|
36
|
-
grid-area: stack;
|
|
37
|
-
margin: auto;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
i[a] {
|
|
41
|
-
background: #998235;
|
|
42
|
-
height: 300px;
|
|
43
|
-
width: 90px;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
i[b] {
|
|
47
|
-
background: #0b2429;
|
|
48
|
-
height: 130px;
|
|
49
|
-
width: 100%;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
i[c] {
|
|
53
|
-
background: #f3ac3c;
|
|
54
|
-
height: 90px;
|
|
55
|
-
width: 130px;
|
|
56
|
-
border-radius: 50px;
|
|
57
|
-
outline: 10px solid #0b2429;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
</style>
|