srcdev-nuxt-forms 0.0.18 → 0.0.19
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/.prettierrc +3 -3
- package/components/forms/c12/validation-patterns/en.json +44 -0
- package/components/forms/c12/validation-patterns/index.ts +6 -0
- package/components/forms/input-text/InputTextCore.vue +112 -11
- package/nuxt.config.ts +6 -0
- package/package.json +1 -1
- package/pages/index.vue +59 -4
- package/types/types.forms.ts +14 -1
package/.prettierrc
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
"semi": true,
|
|
3
|
+
"singleQuote": true
|
|
4
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"username": {
|
|
3
|
+
"pattern": "^[a-zA-Z0-9_\\-'.]{8,20}$",
|
|
4
|
+
"minlength": 8,
|
|
5
|
+
"maxlength": 20,
|
|
6
|
+
"hint": "Uppercase letters and numbers, with ,.- and space"
|
|
7
|
+
},
|
|
8
|
+
"usernameWeak": {
|
|
9
|
+
"pattern": "^[a-zA-Z0-9]{6,20}$",
|
|
10
|
+
"minlength": 6,
|
|
11
|
+
"maxlength": 20,
|
|
12
|
+
"hint": "Mixed case letters"
|
|
13
|
+
},
|
|
14
|
+
"password": {
|
|
15
|
+
"pattern": "^(?=.*\\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[^\\w\\d\\s:])([^\\s]){8,16}$",
|
|
16
|
+
"minlength": 8,
|
|
17
|
+
"maxlength": 20,
|
|
18
|
+
"hint": "!Pa55word"
|
|
19
|
+
},
|
|
20
|
+
"passwordWeak": {
|
|
21
|
+
"pattern": "^[a-zA-Z0-9]{6,20}$",
|
|
22
|
+
"minlength": 6,
|
|
23
|
+
"maxlength": 20,
|
|
24
|
+
"hint": "password"
|
|
25
|
+
},
|
|
26
|
+
"emailaddress": {
|
|
27
|
+
"pattern": "^(?!@)((?!([\\.]))([\\w\\.\\-\\+']{1,}))?((@)([\\w\\-]{2,})+((\\.)([\\w]{2,}))+)$",
|
|
28
|
+
"minlength": 7,
|
|
29
|
+
"maxlength": 255,
|
|
30
|
+
"hint": "you@your-email.com"
|
|
31
|
+
},
|
|
32
|
+
"url": {
|
|
33
|
+
"pattern": "^((http|https)://){1}([\\w\\-]{2,}(\\.))+([\\w]{2,})((/))*((/)[\\w\\-]{1,})*((/)[\\w]{1,}(/))*(/)*$",
|
|
34
|
+
"minlength": 13,
|
|
35
|
+
"maxlength": 255,
|
|
36
|
+
"hint": "https://your-website.com"
|
|
37
|
+
},
|
|
38
|
+
"telephone": {
|
|
39
|
+
"pattern": "^(0|\\+44|\\+353)([\\d\\- ]{10})$",
|
|
40
|
+
"minlength": 11,
|
|
41
|
+
"maxlength": 14,
|
|
42
|
+
"hint": "+441632123123"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -1,22 +1,123 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
<input
|
|
3
|
+
:type
|
|
4
|
+
:placeholder="c12.placeholder"
|
|
5
|
+
:id
|
|
6
|
+
:name
|
|
7
|
+
:pattern="componentValidation.pattern"
|
|
8
|
+
:maxlength="componentValidation.maxlength"
|
|
9
|
+
:required
|
|
10
|
+
:class="['input-text', 'text-normal', { error: fieldHasError() }]"
|
|
11
|
+
v-model="modelValue.data[name]"
|
|
12
|
+
ref="inputField"
|
|
13
|
+
@focusin="isFocused = true"
|
|
14
|
+
@focusout="isFocused = false"
|
|
15
|
+
/>
|
|
5
16
|
</template>
|
|
17
|
+
|
|
6
18
|
<script setup lang="ts">
|
|
7
|
-
|
|
19
|
+
import type { InpuTextC12, IFormData } from '@/types/types.forms';
|
|
20
|
+
import { validationConfig } from '@/components/forms/c12/validation-patterns';
|
|
21
|
+
|
|
22
|
+
const props = defineProps({
|
|
23
|
+
type: {
|
|
24
|
+
// type: String as PropType<"text" | "password" | "tel" | "number" | "email" | "url">, // This breaks props setup in unit tests
|
|
25
|
+
type: String,
|
|
26
|
+
validator(value: string) {
|
|
27
|
+
return ['text', 'password', 'tel', 'number', 'email', 'url'].includes(
|
|
28
|
+
value
|
|
29
|
+
);
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
id: {
|
|
33
|
+
// type: String as PropType<string>,
|
|
34
|
+
type: String,
|
|
35
|
+
required: true,
|
|
36
|
+
},
|
|
37
|
+
name: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: null,
|
|
40
|
+
},
|
|
41
|
+
validation: {
|
|
42
|
+
type: String,
|
|
43
|
+
default: '',
|
|
44
|
+
},
|
|
45
|
+
required: {
|
|
46
|
+
type: Boolean,
|
|
47
|
+
value: false,
|
|
48
|
+
},
|
|
49
|
+
isPending: {
|
|
50
|
+
type: Boolean,
|
|
51
|
+
value: false,
|
|
52
|
+
},
|
|
53
|
+
c12: {
|
|
54
|
+
type: Object as PropType<InpuTextC12>,
|
|
55
|
+
required: true,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const modelValue = defineModel() as Ref<IFormData>;
|
|
60
|
+
const isFocused = defineModel('isFocused') as Ref<boolean>;
|
|
61
|
+
|
|
62
|
+
const name = computed(() => {
|
|
63
|
+
return props.name !== null ? props.name : props.id;
|
|
64
|
+
});
|
|
65
|
+
const validatorLocale = toRef(useRuntimeConfig().public.validatorLocale);
|
|
66
|
+
|
|
67
|
+
const componentValidation =
|
|
68
|
+
validationConfig[validatorLocale.value][props.validation];
|
|
69
|
+
const inputField = ref<HTMLInputElement | null>(null);
|
|
70
|
+
|
|
71
|
+
const { hasCustomError, removeCustomError } = useErrorMessage(
|
|
72
|
+
name.value,
|
|
73
|
+
modelValue
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const fieldHasError = () => {
|
|
77
|
+
const hasApiErrorMessage = hasCustomError();
|
|
78
|
+
const inputBad = !inputField.value?.validity.valid;
|
|
79
|
+
|
|
80
|
+
if (modelValue.value.isPending) {
|
|
81
|
+
modelValue.value!.validityState[name.value] =
|
|
82
|
+
inputField.value?.validity.valid ?? false;
|
|
83
|
+
return hasApiErrorMessage ? hasApiErrorMessage : inputBad;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// watchEffect(() => {
|
|
89
|
+
// console.log('Form value changed to: ', modelValue.value.data[name.value]);
|
|
90
|
+
// });
|
|
91
|
+
|
|
92
|
+
const isValid = () => {
|
|
93
|
+
setTimeout(() => {
|
|
94
|
+
modelValue.value!.validityState[name.value] =
|
|
95
|
+
inputField.value?.validity.valid ?? false;
|
|
96
|
+
}, 0);
|
|
97
|
+
};
|
|
8
98
|
|
|
9
|
-
|
|
99
|
+
// Keep an eye on this for performance issue
|
|
100
|
+
watch(
|
|
101
|
+
() => modelValue.value.data[name.value],
|
|
102
|
+
() => {
|
|
103
|
+
modelValue.value!.validityState[name.value] =
|
|
104
|
+
inputField.value?.validity.valid ?? false;
|
|
105
|
+
if (hasCustomError()) {
|
|
106
|
+
removeCustomError(inputField.value?.validity.valid);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
{ deep: true }
|
|
110
|
+
);
|
|
10
111
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
})
|
|
112
|
+
onMounted(() => {
|
|
113
|
+
isValid();
|
|
114
|
+
});
|
|
14
115
|
</script>
|
|
15
116
|
|
|
16
117
|
<style lang="css">
|
|
17
118
|
.input-text {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
119
|
+
transition: all linear 200ms;
|
|
120
|
+
padding-left: 4px;
|
|
121
|
+
width: 100%;
|
|
21
122
|
}
|
|
22
123
|
</style>
|
package/nuxt.config.ts
CHANGED
|
@@ -5,6 +5,12 @@ const { resolve } = createResolver(import.meta.url)
|
|
|
5
5
|
export default defineNuxtConfig({
|
|
6
6
|
devtools: { enabled: true },
|
|
7
7
|
css: [resolve('./assets/styles/main.css')],
|
|
8
|
+
runtimeConfig: {
|
|
9
|
+
public: {
|
|
10
|
+
validatorLocale: 'en-GB',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
|
|
8
14
|
components: [
|
|
9
15
|
{
|
|
10
16
|
path: './components',
|
package/package.json
CHANGED
package/pages/index.vue
CHANGED
|
@@ -7,27 +7,51 @@
|
|
|
7
7
|
|
|
8
8
|
<FormWrapper width="medium">
|
|
9
9
|
<template #default>
|
|
10
|
-
<form>
|
|
10
|
+
<form @submit.prevent="isPending">
|
|
11
11
|
<p>Form content</p>
|
|
12
12
|
<FormField width="wide" :has-gutter="true">
|
|
13
13
|
<template #default>
|
|
14
14
|
<p>Input text</p>
|
|
15
|
-
<InputTextCore
|
|
15
|
+
<InputTextCore
|
|
16
|
+
id="username"
|
|
17
|
+
type="text"
|
|
18
|
+
validation="username"
|
|
19
|
+
:required="true"
|
|
20
|
+
v-model="formData"
|
|
21
|
+
:c12="{
|
|
22
|
+
label: 'Choose Username',
|
|
23
|
+
placeholder: 'eg. YourUserName',
|
|
24
|
+
errorMessage: 'Please enter a valid username',
|
|
25
|
+
}"
|
|
26
|
+
/>
|
|
16
27
|
</template>
|
|
17
28
|
</FormField>
|
|
29
|
+
<input
|
|
30
|
+
type="submit"
|
|
31
|
+
@click.prevent="isPending"
|
|
32
|
+
value="Submit"
|
|
33
|
+
/>
|
|
18
34
|
</form>
|
|
19
35
|
</template>
|
|
20
36
|
</FormWrapper>
|
|
21
37
|
</div>
|
|
38
|
+
<ClientOnly>
|
|
39
|
+
<p>Client only content</p>
|
|
40
|
+
<pre>
|
|
41
|
+
{{ formData }}
|
|
42
|
+
</pre>
|
|
43
|
+
</ClientOnly>
|
|
22
44
|
</template>
|
|
23
45
|
</NuxtLayout>
|
|
24
46
|
</div>
|
|
25
47
|
</template>
|
|
26
48
|
|
|
27
49
|
<script setup lang="ts">
|
|
50
|
+
import type { IFieldsInitialState, IOptionsConfig } from '@/types/types.forms';
|
|
51
|
+
|
|
28
52
|
definePageMeta({
|
|
29
53
|
layout: false,
|
|
30
|
-
})
|
|
54
|
+
});
|
|
31
55
|
|
|
32
56
|
useHead({
|
|
33
57
|
title: 'Homepage',
|
|
@@ -35,7 +59,38 @@ useHead({
|
|
|
35
59
|
bodyAttrs: {
|
|
36
60
|
class: '',
|
|
37
61
|
},
|
|
38
|
-
})
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/*
|
|
65
|
+
* Setup forms
|
|
66
|
+
*/
|
|
67
|
+
const fieldsInitialState = ref<IFieldsInitialState>({
|
|
68
|
+
username: '',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Setup formData
|
|
72
|
+
const {
|
|
73
|
+
formData,
|
|
74
|
+
initFormData,
|
|
75
|
+
getErrorCount,
|
|
76
|
+
updateCustomErrors,
|
|
77
|
+
resetForm,
|
|
78
|
+
formIsValid,
|
|
79
|
+
showErrors,
|
|
80
|
+
} = useFormControl(fieldsInitialState);
|
|
81
|
+
|
|
82
|
+
await initFormData();
|
|
83
|
+
|
|
84
|
+
const isPending = async () => {
|
|
85
|
+
formData.value.isPending = true;
|
|
86
|
+
await getErrorCount();
|
|
87
|
+
|
|
88
|
+
if (formIsValid.value) {
|
|
89
|
+
console.log('Form is good - post it!');
|
|
90
|
+
} else {
|
|
91
|
+
console.warn('Form has errors');
|
|
92
|
+
}
|
|
93
|
+
};
|
|
39
94
|
</script>
|
|
40
95
|
|
|
41
96
|
<style lang="css">
|
package/types/types.forms.ts
CHANGED
|
@@ -17,7 +17,14 @@ export interface IOptionsValueArr {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export interface IFieldsInitialState {
|
|
20
|
-
[key: string]:
|
|
20
|
+
[key: string]:
|
|
21
|
+
| null
|
|
22
|
+
| string
|
|
23
|
+
| boolean
|
|
24
|
+
| number
|
|
25
|
+
| URL
|
|
26
|
+
| object
|
|
27
|
+
| IOptionsValueArr[];
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
export interface IValidityState {
|
|
@@ -59,6 +66,12 @@ export interface ICustomErrorMessage {
|
|
|
59
66
|
message: string;
|
|
60
67
|
}
|
|
61
68
|
|
|
69
|
+
export interface InpuTextC12 {
|
|
70
|
+
label: string;
|
|
71
|
+
placeholder: string;
|
|
72
|
+
errorMessage: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
62
75
|
export interface ICustomErrorMessagesArr {
|
|
63
76
|
[x: string]: ICustomErrorMessage;
|
|
64
77
|
}
|