srcdev-nuxt-forms 0.1.0 → 0.2.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/forms/index.css +1 -0
- package/assets/styles/forms/themes/_primary.css +2 -0
- package/assets/styles/forms/themes/_secondary.css +2 -0
- package/assets/styles/forms/utils/_a11y.css +5 -0
- package/assets/styles/forms/utils/index.css +1 -0
- package/assets/styles/forms/variables/_theme.css +11 -17
- package/assets/styles/variables/colors/_gray.css +1 -0
- 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 +132 -0
- package/components/forms/input-button/InputButtonCore.vue +11 -8
- 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 +407 -0
- package/components/forms/input-checkbox/InputCheckboxWithLabel.vue +125 -0
- package/components/forms/input-checkbox/variants/MultipleCheckboxes.vue +194 -0
- package/components/forms/input-checkbox/variants/SingleCheckbox.vue +157 -0
- package/components/forms/input-radio/InputRadioCore.vue +226 -0
- package/components/forms/input-radio/InputRadioWithLabel.vue +118 -0
- package/components/forms/input-radio/variants/MultipleRadio.vue +183 -0
- package/components/forms/input-radio/variants/SingleRadio.vue +131 -0
- package/components/forms/input-range/InputRangeCore.vue +171 -0
- package/components/forms/input-range/variants/InputRangeDefault.vue +131 -0
- package/components/forms/input-text/InputTextCore.vue +61 -31
- package/components/forms/input-text/variants/material/InputPasswordMaterial.vue +27 -1
- package/components/forms/input-text/variants/material/InputTextMaterial.vue +1 -8
- package/components/forms/input-text/variants/material/InputTextMaterialCore.vue +83 -28
- package/components/forms/input-textarea/InputTextareaCore.vue +170 -0
- package/components/forms/input-textarea/variants/material/InputTextareaMaterial.vue +75 -0
- package/components/forms/input-textarea/variants/material/InputTextareaMaterialCore.vue +290 -0
- package/components/ui/content-grid/ContentGrid.vue +85 -0
- package/composables/useErrorMessages.ts +17 -5
- package/composables/useFormControl.ts +147 -37
- package/layouts/default.vue +7 -13
- package/nuxt.config.ts +22 -0
- package/package.json +9 -8
- package/pages/forms/examples/buttons/index.vue +14 -13
- package/pages/forms/examples/material/text-fields.vue +320 -84
- package/pages/limit-text.vue +43 -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 +37 -0
- package/server/data/places/countries.json +55 -0
- package/server/data/utils/title.json +49 -0
- package/types/types.forms.ts +33 -3
- package/types/types.places.ts +8 -0
- package/pages/forms/examples/material/text-fields-compact.vue +0 -136
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="input-textarea-material" :class="[`theme-${theme}`, { error: fieldHasError }, { compact: compact }]">
|
|
3
|
+
<label class="input-textarea-label" :for="id" :class="[{ active: isFocused }, { error: fieldHasError }, { dirty: fieldIsDirty }, { compact: compact }]">
|
|
4
|
+
<span>{{ c12.label }}</span>
|
|
5
|
+
</label>
|
|
6
|
+
<div class="input-textarea-container" :class="[{ active: isFocused }, { error: fieldHasError }, { dirty: fieldIsDirty }, { compact: compact }]">
|
|
7
|
+
<slot name="input"></slot>
|
|
8
|
+
</div>
|
|
9
|
+
<InputError :errorMessaging :fieldHasError :id :isDetached="false" />
|
|
10
|
+
</div>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup lang="ts">
|
|
14
|
+
import type { InpuTextC12, IFormData } from '@/types/types.forms';
|
|
15
|
+
|
|
16
|
+
import propValidators from '../../../c12/prop-validators';
|
|
17
|
+
|
|
18
|
+
const props = defineProps({
|
|
19
|
+
size: {
|
|
20
|
+
type: String as PropType<string>,
|
|
21
|
+
default: 'normal',
|
|
22
|
+
validator(value: string) {
|
|
23
|
+
return propValidators.size.includes(value);
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
weight: {
|
|
27
|
+
type: String as PropType<string>,
|
|
28
|
+
default: 'wght-400',
|
|
29
|
+
validator(value: string) {
|
|
30
|
+
return propValidators.weight.includes(value);
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
theme: {
|
|
34
|
+
type: String as PropType<string>,
|
|
35
|
+
default: 'primary',
|
|
36
|
+
validator(value: string) {
|
|
37
|
+
return propValidators.theme.includes(value);
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
id: {
|
|
41
|
+
type: String,
|
|
42
|
+
required: true,
|
|
43
|
+
},
|
|
44
|
+
name: {
|
|
45
|
+
type: String,
|
|
46
|
+
default: null,
|
|
47
|
+
},
|
|
48
|
+
required: {
|
|
49
|
+
type: Boolean,
|
|
50
|
+
value: false,
|
|
51
|
+
},
|
|
52
|
+
c12: {
|
|
53
|
+
type: Object as PropType<InpuTextC12>,
|
|
54
|
+
required: true,
|
|
55
|
+
},
|
|
56
|
+
styleClassPassthrough: {
|
|
57
|
+
type: String,
|
|
58
|
+
default: '',
|
|
59
|
+
},
|
|
60
|
+
compact: {
|
|
61
|
+
type: Boolean,
|
|
62
|
+
value: false,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const errorMessaging = computed(() => {
|
|
67
|
+
if (
|
|
68
|
+
typeof modelValue.value!.formFieldsC12[props.name] !== 'undefined' &&
|
|
69
|
+
modelValue.value!.formFieldsC12[props.name].useCustomError &&
|
|
70
|
+
modelValue.value.data[props.name] === modelValue.value.formFieldsC12[props.name].previousValue
|
|
71
|
+
) {
|
|
72
|
+
return modelValue.value.formFieldsC12[props.name]?.customErrors || [];
|
|
73
|
+
} else {
|
|
74
|
+
return props.c12.errorMessage;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const modelValue = defineModel() as Ref<IFormData>;
|
|
79
|
+
|
|
80
|
+
const isFocused = computed(() => {
|
|
81
|
+
return modelValue.value.focusedField == props.name;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const fieldIsDirty = computed(() => {
|
|
85
|
+
if (typeof modelValue.value!.formFieldsC12[props.name] !== 'undefined') {
|
|
86
|
+
return modelValue.value!.formFieldsC12[props.name].isDirty;
|
|
87
|
+
} else {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const fieldHasError = computed(() => {
|
|
93
|
+
if (typeof modelValue.value!.formFieldsC12[props.name] !== 'undefined') {
|
|
94
|
+
return modelValue.value!.submitAttempted && !modelValue.value!.formFieldsC12[props.name].isValid;
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
});
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<style lang="css">
|
|
101
|
+
.input-textarea-material {
|
|
102
|
+
--_form-theme: var(--theme-form-primary);
|
|
103
|
+
--_focus-colour: var(--theme-form-primary-focus);
|
|
104
|
+
--_gutter: 12px;
|
|
105
|
+
--_border-width: var(--input-border-width-thin);
|
|
106
|
+
--_border-color: var(--_form-theme);
|
|
107
|
+
--_outline-width: var(--input-outline-width-thin);
|
|
108
|
+
|
|
109
|
+
&.theme-secondary {
|
|
110
|
+
--_form-theme: var(--theme-form-secondary);
|
|
111
|
+
--_focus-colour: var(--theme-form-secondary-focus);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
&.error {
|
|
115
|
+
--_form-theme: var(--theme-error);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.input-textarea-core {
|
|
119
|
+
color: var(--_form-theme);
|
|
120
|
+
background-color: transparent;
|
|
121
|
+
line-height: var(--line-height);
|
|
122
|
+
field-sizing: content;
|
|
123
|
+
min-height: 3rem;
|
|
124
|
+
|
|
125
|
+
&:focus {
|
|
126
|
+
outline: none;
|
|
127
|
+
box-shadow: none;
|
|
128
|
+
border: none;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
&:-internal-autofill-selected,
|
|
132
|
+
&:autofill {
|
|
133
|
+
background-color: transparent !important;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.input-textarea-label {
|
|
138
|
+
color: var(--_form-theme);
|
|
139
|
+
margin: initial;
|
|
140
|
+
line-height: var(--line-height);
|
|
141
|
+
padding: initial;
|
|
142
|
+
transition: color 0.2s ease-in-out;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
display: grid;
|
|
146
|
+
border-radius: 2px;
|
|
147
|
+
border: var(--_border-width) solid var(--_border-color);
|
|
148
|
+
|
|
149
|
+
margin-bottom: 20px;
|
|
150
|
+
overflow: hidden;
|
|
151
|
+
|
|
152
|
+
&:focus-within {
|
|
153
|
+
--_border-color: white;
|
|
154
|
+
outline: var(--_outline-width) solid hsl(from var(--_form-theme) h s 50%);
|
|
155
|
+
background-color: hsl(from var(--_form-theme) h s 95%);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
&:has(.input-textarea-core:focus-visible) {
|
|
159
|
+
box-shadow: 0 0 2px 3px var(--_focus-colour);
|
|
160
|
+
outline-color: var(--_focus-colour);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
&.error {
|
|
164
|
+
/* outline: calc(var(--_border-width) * 2) solid var(--theme-error); */
|
|
165
|
+
|
|
166
|
+
border: var(--_border-width) solid var(--theme-error);
|
|
167
|
+
outline: var(--_outline-width) solid hsl(from var(--theme-error) h s 75%);
|
|
168
|
+
|
|
169
|
+
background-color: hsl(from var(--theme-error) h s 95%);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.input-textarea-label {
|
|
173
|
+
grid-row: 1;
|
|
174
|
+
grid-column: 1;
|
|
175
|
+
|
|
176
|
+
font-family: var(--font-family);
|
|
177
|
+
font-size: 20px;
|
|
178
|
+
font-weight: 700;
|
|
179
|
+
padding: var(--_gutter);
|
|
180
|
+
transform: translateY(12px);
|
|
181
|
+
transition: all linear 0.2s;
|
|
182
|
+
background-color: transparent;
|
|
183
|
+
z-index: 2;
|
|
184
|
+
height: 3.5rem;
|
|
185
|
+
transition: color 0.2s ease-in-out;
|
|
186
|
+
|
|
187
|
+
&.active,
|
|
188
|
+
&.dirty,
|
|
189
|
+
&.error {
|
|
190
|
+
font-size: 16px;
|
|
191
|
+
height: 1.5em;
|
|
192
|
+
transform: translateY(-2px);
|
|
193
|
+
z-index: auto;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.input-textarea-container {
|
|
198
|
+
display: grid;
|
|
199
|
+
grid-row: 1;
|
|
200
|
+
grid-column: 1;
|
|
201
|
+
margin-top: 2rem;
|
|
202
|
+
background-color: transparent;
|
|
203
|
+
opacity: 0;
|
|
204
|
+
transition: opacity linear 0.2s;
|
|
205
|
+
|
|
206
|
+
&.active,
|
|
207
|
+
&.dirty,
|
|
208
|
+
&.error {
|
|
209
|
+
opacity: 1;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.input-textarea-core {
|
|
213
|
+
font-family: var(--font-family);
|
|
214
|
+
border: 0px solid green;
|
|
215
|
+
padding: calc(var(--_gutter) / 2) var(--_gutter);
|
|
216
|
+
font-size: var(--font-size);
|
|
217
|
+
margin: 0;
|
|
218
|
+
/* opacity: 0;
|
|
219
|
+
transition: opacity linear 0.2s;
|
|
220
|
+
|
|
221
|
+
&.active,
|
|
222
|
+
&.dirty {
|
|
223
|
+
opacity: 1;
|
|
224
|
+
} */
|
|
225
|
+
/*
|
|
226
|
+
&::placeholder,
|
|
227
|
+
&:-ms-input-placeholder,
|
|
228
|
+
&::-moz-placeholder, */
|
|
229
|
+
&::-webkit-input-placeholder {
|
|
230
|
+
font-family: var(--font-family);
|
|
231
|
+
/* color: var(--gray-5); */
|
|
232
|
+
color: hsl(from var(--_form-theme) h s 50%);
|
|
233
|
+
font-size: var(--font-size);
|
|
234
|
+
font-style: italic;
|
|
235
|
+
font-weight: 500;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/*
|
|
242
|
+
* Compact UI
|
|
243
|
+
**/
|
|
244
|
+
|
|
245
|
+
.input-textarea-material {
|
|
246
|
+
&.compact {
|
|
247
|
+
overflow: initial;
|
|
248
|
+
|
|
249
|
+
&:focus-within {
|
|
250
|
+
background-color: initial;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
&.error {
|
|
254
|
+
background-color: initial;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.input-textarea-label {
|
|
259
|
+
&.compact {
|
|
260
|
+
align-content: center;
|
|
261
|
+
font-size: 16px;
|
|
262
|
+
padding: 0 12px;
|
|
263
|
+
transform: translateY(0);
|
|
264
|
+
|
|
265
|
+
span {
|
|
266
|
+
padding: 0 8px;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
&.active,
|
|
270
|
+
&.dirty,
|
|
271
|
+
&.error {
|
|
272
|
+
font-size: 16px;
|
|
273
|
+
font-weight: 500;
|
|
274
|
+
transform: translateY(-14px);
|
|
275
|
+
z-index: auto;
|
|
276
|
+
|
|
277
|
+
span {
|
|
278
|
+
background-color: white;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.input-textarea-container {
|
|
285
|
+
&.compact {
|
|
286
|
+
margin: 10px 8px 6px 8px;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
</style>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ui-content-grid" :class="[applyClasses]" :data-testid="dataTestid">
|
|
3
|
+
<div v-if="hasSlot1" class="col-1">
|
|
4
|
+
<slot name="slot1"></slot>
|
|
5
|
+
</div>
|
|
6
|
+
<div v-if="hasSlot2" class="col-2">
|
|
7
|
+
<slot name="slot2"></slot>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
const props = defineProps({
|
|
14
|
+
dataTestid: {
|
|
15
|
+
type: String,
|
|
16
|
+
default: 'ui-content-grid',
|
|
17
|
+
},
|
|
18
|
+
applyClasses: {
|
|
19
|
+
type: String,
|
|
20
|
+
default: '',
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const slots = useSlots();
|
|
25
|
+
const hasSlot1 = ref(slots.slot1 !== undefined);
|
|
26
|
+
const hasSlot2 = ref(slots.slot2 !== undefined);
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<style lang="css">
|
|
30
|
+
.ui-content-grid {
|
|
31
|
+
--_margin-inline: 0;
|
|
32
|
+
--_grid-template-columns: repeat(4, 1fr);
|
|
33
|
+
--_grid-template-rows: repeat(2, auto);
|
|
34
|
+
--_grid-gap: 16px;
|
|
35
|
+
|
|
36
|
+
display: grid;
|
|
37
|
+
gap: var(--_grid-gap);
|
|
38
|
+
grid-template-columns: var(--_grid-template-columns);
|
|
39
|
+
grid-template-rows: var(--_grid-template-rows);
|
|
40
|
+
margin-inline: var(--_margin-inline);
|
|
41
|
+
|
|
42
|
+
@container content (min-width: 768px) {
|
|
43
|
+
--_margin-inline: 0;
|
|
44
|
+
--_grid-template-columns: repeat(6, 1fr);
|
|
45
|
+
--_grid-gap: 32px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@container content (min-width: 1024px) {
|
|
49
|
+
--_margin-inline: 0;
|
|
50
|
+
--_grid-template-columns: repeat(12, 1fr);
|
|
51
|
+
--_grid-template-rows: initial;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.col-1 {
|
|
55
|
+
--_grid-column: 1 / span 4;
|
|
56
|
+
--_grid-row: 1;
|
|
57
|
+
grid-column: var(--_grid-column);
|
|
58
|
+
grid-row: var(--_grid-row);
|
|
59
|
+
|
|
60
|
+
@container content (min-width: 768px) {
|
|
61
|
+
--_grid-column: 1 / span 6;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@container content (min-width: 1024px) {
|
|
65
|
+
--_grid-column: 1 / span 6;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.col-2 {
|
|
70
|
+
--_grid-column: 1 / span 4;
|
|
71
|
+
--_grid-row: 2;
|
|
72
|
+
grid-column: var(--_grid-column);
|
|
73
|
+
grid-row: var(--_grid-row);
|
|
74
|
+
|
|
75
|
+
@container content (min-width: 768px) {
|
|
76
|
+
--_grid-column: 1 / span 6;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@container content (min-width: 1024px) {
|
|
80
|
+
--_grid-column: 7 / span 6;
|
|
81
|
+
--_grid-row: 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
</style>
|
|
@@ -2,15 +2,17 @@ import type { IFormData } from '@/types/types.forms';
|
|
|
2
2
|
|
|
3
3
|
export function useErrorMessage(name: string, formData: Ref<IFormData>) {
|
|
4
4
|
const defaultError = ref('');
|
|
5
|
-
const
|
|
5
|
+
const errorMessages = ref(formData.value.errorMessages);
|
|
6
6
|
|
|
7
7
|
const hasCustomError = () => {
|
|
8
|
-
return
|
|
8
|
+
return errorMessages.value[name] !== undefined && errorMessages.value[name].useCustomError;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
const errorMessage = computed(() => {
|
|
12
|
+
console.log(`errorMessage()`);
|
|
12
13
|
if (hasCustomError()) {
|
|
13
|
-
|
|
14
|
+
console.log(`errorMessage() | IF`);
|
|
15
|
+
return errorMessages.value[name].message;
|
|
14
16
|
} else {
|
|
15
17
|
return defaultError.value;
|
|
16
18
|
}
|
|
@@ -21,20 +23,30 @@ export function useErrorMessage(name: string, formData: Ref<IFormData>) {
|
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
const fieldHasError = computed(() => {
|
|
26
|
+
// console.log(`fieldHasError() | name(${name})`);
|
|
27
|
+
nextTick();
|
|
24
28
|
if (formData.value.submitDisabled) {
|
|
29
|
+
console.log(`fieldHasError() | name(${name}) | IF`);
|
|
25
30
|
if (hasCustomError()) {
|
|
31
|
+
console.log(`fieldHasError() | name(${name}) | IF | IF`);
|
|
32
|
+
|
|
26
33
|
return true;
|
|
27
34
|
} else if (Object.keys(formData.value.validityState).length > 0 && formData.value.validityState[name] !== undefined) {
|
|
35
|
+
console.log(`fieldHasError() | name(${name}) | IF | ELSE IF`);
|
|
36
|
+
|
|
28
37
|
return !formData.value.validityState[name];
|
|
29
38
|
}
|
|
39
|
+
console.log(`fieldHasError() | name(${name}) | IF | ELSE`);
|
|
40
|
+
|
|
30
41
|
return false;
|
|
31
42
|
}
|
|
32
43
|
});
|
|
33
44
|
|
|
34
45
|
const removeCustomError = (valid: boolean = false) => {
|
|
35
|
-
|
|
46
|
+
console.log(`useErrorMessage | removeCustomError | name(${name}) | valid(${valid})`);
|
|
47
|
+
// formData.value.validityState[name] = valid;
|
|
36
48
|
// await nextTick();
|
|
37
|
-
delete formData.value.
|
|
49
|
+
// delete formData.value.errorMessages[name];
|
|
38
50
|
};
|
|
39
51
|
|
|
40
52
|
return {
|
|
@@ -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,17 @@ 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
|
+
Object.keys(formData.value.data).forEach((key) => {
|
|
51
|
+
formData.value.formFieldsC12[key].previousValue = formData.value.data[key];
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
|
|
39
55
|
const getErrorCount = async (updateState: boolean = false) => {
|
|
40
56
|
await nextTick();
|
|
41
57
|
|
|
@@ -45,17 +61,26 @@ export function useFormControl(fieldsInitialState: IFieldsInitialState | Ref<IFi
|
|
|
45
61
|
|
|
46
62
|
if (updateState) {
|
|
47
63
|
formData.value.submitDisabled = true;
|
|
64
|
+
formData.value.displayErrorMessages = formData.value.errorCount > 0;
|
|
65
|
+
formData.value.submitAttempted = true;
|
|
48
66
|
}
|
|
49
67
|
|
|
50
68
|
if (formData.value.submitDisabled) {
|
|
51
69
|
formData.value.submitDisabled = !formData.value.formIsValid;
|
|
52
70
|
}
|
|
53
71
|
|
|
72
|
+
// update fieldHasError ref
|
|
73
|
+
// if (typeof formData.value!.formFieldsC12[name] !== 'undefined') {
|
|
74
|
+
// fieldHasError.value = formData.value!.submitAttempted && !formData.value!.formFieldsC12[name].isValid;
|
|
75
|
+
// } else {
|
|
76
|
+
// fieldHasError.value = false;
|
|
77
|
+
// }
|
|
78
|
+
|
|
54
79
|
return formData.value.errorCount;
|
|
55
80
|
};
|
|
56
81
|
|
|
57
82
|
// Function to count items with "useCustomError" set to true
|
|
58
|
-
const countItemsWithCustomError = (obj:
|
|
83
|
+
const countItemsWithCustomError = (obj: IErrorMessagesArr) => {
|
|
59
84
|
let count = 0;
|
|
60
85
|
|
|
61
86
|
for (const key in obj) {
|
|
@@ -70,37 +95,100 @@ export function useFormControl(fieldsInitialState: IFieldsInitialState | Ref<IFi
|
|
|
70
95
|
/*
|
|
71
96
|
* Useage:
|
|
72
97
|
*
|
|
73
|
-
* const {
|
|
98
|
+
* const { updateErrorMessages } = useFormControl();
|
|
74
99
|
*
|
|
75
100
|
* Add/Update entry
|
|
76
101
|
* const sampleCustomErrorEmail = {
|
|
77
102
|
* useCustomError: true,
|
|
78
103
|
* message: "This is a sample custom error for error EMAIL",
|
|
79
104
|
* };
|
|
80
|
-
*
|
|
105
|
+
* updateErrorMessages("email", sampleCustomErrorEmail);
|
|
81
106
|
*/
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
84
|
-
formData.value.validityState[name] = valid;
|
|
85
|
-
formData.value.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
};
|
|
107
|
+
const updateErrorMessages = async (name: string, message: string = '', valid: boolean = false) => {
|
|
108
|
+
if (!valid) {
|
|
109
|
+
// formData.value.validityState[name] = valid;
|
|
110
|
+
// formData.value.errorMessages[name] = {
|
|
111
|
+
// useCustomError: true,
|
|
112
|
+
// message,
|
|
113
|
+
// };
|
|
114
|
+
|
|
115
|
+
formData.value.formFieldsC12[name].useCustomError = true;
|
|
116
|
+
|
|
117
|
+
// if (typeof message === 'string') {
|
|
118
|
+
// formData.value.formFieldsC12[name].customErrors = message;
|
|
119
|
+
// } else if (typeof message === 'object') {
|
|
120
|
+
// formData.value.formFieldsC12[name].customErrors = message;
|
|
121
|
+
// }
|
|
122
|
+
|
|
123
|
+
formData.value.formFieldsC12[name].customErrors = message;
|
|
124
|
+
formData.value.formFieldsC12[name].isValid = valid;
|
|
125
|
+
|
|
126
|
+
// formData.value.errorMessages[name].useCustomError = true;
|
|
127
|
+
// formData.value.errorMessages[name].message = message;
|
|
128
|
+
}
|
|
129
|
+
formData.value.hasCustomErrorMessages = countItemsWithCustomError(formData.value.errorMessages) > 0;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const useApiErrors = async (errors: IApiErrorMessages) => {
|
|
133
|
+
// Object.keys(errors).forEach((key) => {
|
|
134
|
+
// updateErrorMessages(key, errors[key]);
|
|
135
|
+
// });
|
|
136
|
+
|
|
137
|
+
for (const [key, message] of Object.entries(errors)) {
|
|
138
|
+
// console.log(`${key}: ${message}`);
|
|
139
|
+
updateErrorMessages(key, message);
|
|
89
140
|
}
|
|
90
|
-
formData.value.hasCustomErrorMessages = countItemsWithCustomError(formData.value.customErrorMessages) > 0;
|
|
91
141
|
};
|
|
92
142
|
|
|
93
|
-
const resetForm = () => {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
143
|
+
// const resetForm = () => {
|
|
144
|
+
// console.log('resetForm()');
|
|
145
|
+
// formData.value.data = toRaw(fieldsInitialState.value) as IFieldsInitialState;
|
|
146
|
+
// formData.value.validityState = {};
|
|
147
|
+
// formData.value.errorCount = 0;
|
|
148
|
+
// formData.value.isPending = false;
|
|
149
|
+
// formData.value.errorMessages = {};
|
|
150
|
+
// formData.value.formIsValid = false;
|
|
151
|
+
// };
|
|
152
|
+
|
|
153
|
+
const fieldIsDirty = (name: string) => {
|
|
154
|
+
if (typeof formData.value.formFieldsC12[name] !== 'undefined') {
|
|
155
|
+
return formData.value.formFieldsC12[name].isDirty;
|
|
156
|
+
} else {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
100
159
|
};
|
|
101
160
|
|
|
161
|
+
// const fieldHasError = (name: string) => {
|
|
162
|
+
// const currentValidityState = formData.value.validityState[name];
|
|
163
|
+
|
|
164
|
+
// if (formData.value.submitAttempted) {
|
|
165
|
+
// return currentValidityState;
|
|
166
|
+
// }
|
|
167
|
+
// return false;
|
|
168
|
+
// };
|
|
169
|
+
|
|
170
|
+
// const fieldHasError = computed({
|
|
171
|
+
// // getter
|
|
172
|
+
// get() {
|
|
173
|
+
// console.log(`fieldHasError getter: ${name}`);
|
|
174
|
+
// if (typeof formData.value!.formFieldsC12[name] !== 'undefined') {
|
|
175
|
+
// return !formData.value!.formFieldsC12[name].isValid;
|
|
176
|
+
// }
|
|
177
|
+
// return formData.value.validityState[name];
|
|
178
|
+
// },
|
|
179
|
+
// // setter
|
|
180
|
+
// set(newValue) {
|
|
181
|
+
// if (formData.value.submitAttempted) {
|
|
182
|
+
// return newValue;
|
|
183
|
+
// }
|
|
184
|
+
// return false;
|
|
185
|
+
// },
|
|
186
|
+
// });
|
|
187
|
+
|
|
188
|
+
// const fieldHasError = ref(false);
|
|
189
|
+
|
|
102
190
|
const formIsValid = computed(() => {
|
|
103
|
-
return formData.value.
|
|
191
|
+
return formData.value.formIsValid;
|
|
104
192
|
});
|
|
105
193
|
|
|
106
194
|
const submitDisabled = computed(() => {
|
|
@@ -109,9 +197,12 @@ export function useFormControl(fieldsInitialState: IFieldsInitialState | Ref<IFi
|
|
|
109
197
|
|
|
110
198
|
// Keep an eye on this for performance issue
|
|
111
199
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
200
|
+
// const updateFieldValidity = (name: string, valid: boolean) => {
|
|
201
|
+
// console.log(`updateFieldValidity: name:${name} - valid:${valid}`);
|
|
202
|
+
// console.log(formData.value);
|
|
203
|
+
// // formData.value.formFieldsC12[name].isValid = valid;
|
|
204
|
+
// formData.value.validityState[name] = valid;
|
|
205
|
+
// };
|
|
115
206
|
|
|
116
207
|
watch(
|
|
117
208
|
() => formData.value.validityState,
|
|
@@ -121,16 +212,35 @@ export function useFormControl(fieldsInitialState: IFieldsInitialState | Ref<IFi
|
|
|
121
212
|
{ deep: true }
|
|
122
213
|
);
|
|
123
214
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
215
|
+
watch(
|
|
216
|
+
() => formData.value.formFieldsC12,
|
|
217
|
+
() => {
|
|
218
|
+
formData.value.formFieldsC12;
|
|
219
|
+
},
|
|
220
|
+
{ deep: true }
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
watch(
|
|
224
|
+
() => formData.value.isPending,
|
|
225
|
+
(newValue, oldValue) => {
|
|
226
|
+
if (newValue) {
|
|
227
|
+
updatePreviousValues();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
);
|
|
127
231
|
|
|
128
232
|
return {
|
|
129
233
|
formData,
|
|
234
|
+
initFormData,
|
|
235
|
+
initFormFieldsC12,
|
|
130
236
|
getErrorCount,
|
|
131
|
-
|
|
132
|
-
resetForm,
|
|
237
|
+
updateErrorMessages,
|
|
238
|
+
// resetForm,
|
|
133
239
|
formIsValid,
|
|
134
240
|
submitDisabled,
|
|
241
|
+
useApiErrors,
|
|
242
|
+
// fieldHasError,
|
|
243
|
+
fieldIsDirty,
|
|
244
|
+
// updateFieldValidity,
|
|
135
245
|
};
|
|
136
246
|
}
|