srcdev-nuxt-forms 3.0.0 → 4.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/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-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 +216 -0
- package/types/types.zodFormControl.ts +21 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="colour-scheme-select" :data-size="size" :data-form-theme="theme">
|
|
3
|
+
<p>Color Scheme select</p>
|
|
4
|
+
|
|
5
|
+
<form class="colour-scheme-select-form mbe-20">
|
|
6
|
+
<div class="input-range-markers">
|
|
7
|
+
<div class="marker">
|
|
8
|
+
<LazyIcon name="material-symbols:night-sight-auto-sharp" class="marker-icon" />
|
|
9
|
+
</div>
|
|
10
|
+
<div class="marker">
|
|
11
|
+
<LazyIcon name="radix-icons:sun" class="marker-icon" />
|
|
12
|
+
</div>
|
|
13
|
+
<div class="marker" v>
|
|
14
|
+
<LazyIcon name="radix-icons:moon" class="marker-icon" />
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div class="scheme-marker-wrapper">
|
|
19
|
+
<div class="scheme-marker-indicator"></div>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="form-elements">
|
|
23
|
+
<div class="form-input">
|
|
24
|
+
<input type="radio" id="auto" name="colour-scheme" v-model="colourScheme" value="auto" />
|
|
25
|
+
<label for="auto" class="sr-only">Auto</label>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="form-input">
|
|
28
|
+
<input type="radio" id="light" name="colour-scheme" v-model="colourScheme" value="light" />
|
|
29
|
+
<label for="light" class="sr-only">Light</label>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="form-input">
|
|
32
|
+
<input type="radio" id="dark" name="colour-scheme" v-model="colourScheme" value="dark" />
|
|
33
|
+
<label for="dark" class="sr-only">Dark</label>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</form>
|
|
37
|
+
</div>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
import propValidators from '../../forms/c12/prop-validators';
|
|
42
|
+
|
|
43
|
+
defineProps({
|
|
44
|
+
name: {
|
|
45
|
+
type: String,
|
|
46
|
+
defaul: useId(),
|
|
47
|
+
},
|
|
48
|
+
size: {
|
|
49
|
+
type: String as PropType<string>,
|
|
50
|
+
default: 'medium',
|
|
51
|
+
validator(value: string) {
|
|
52
|
+
return propValidators.size.includes(value);
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
theme: {
|
|
56
|
+
type: String as PropType<string>,
|
|
57
|
+
default: 'primary',
|
|
58
|
+
validator(value: string) {
|
|
59
|
+
return propValidators.theme.includes(value);
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
styleClassPassthrough: {
|
|
63
|
+
type: Array as PropType<string[]>,
|
|
64
|
+
default: () => [],
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const colourScheme = ref<'auto' | 'dark' | 'light'>('auto');
|
|
69
|
+
|
|
70
|
+
useColourScheme(colourScheme);
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<style lang="css">
|
|
74
|
+
.colour-scheme-select {
|
|
75
|
+
--_outline-width: 0.1rem;
|
|
76
|
+
--_border-width: 0.1rem;
|
|
77
|
+
--_border-radius: 50%;
|
|
78
|
+
--_background-color: var(--theme-form-checkbox-bg);
|
|
79
|
+
--_box-shadow: none;
|
|
80
|
+
|
|
81
|
+
--_icon-size: var(--form-input-checkbox-radio-button-size);
|
|
82
|
+
|
|
83
|
+
--_background-color: var(--theme-form-radio-bg);
|
|
84
|
+
--_border-color: var(--theme-form-radio-border);
|
|
85
|
+
--_border-radius: 50%;
|
|
86
|
+
--_outline-color: var(--theme-form-radio-outline);
|
|
87
|
+
|
|
88
|
+
--_form-padding: 0.5rem;
|
|
89
|
+
--_form-input-outline-width: 0.2rem;
|
|
90
|
+
|
|
91
|
+
--_scheme-marker-position: start;
|
|
92
|
+
|
|
93
|
+
.colour-scheme-select-form {
|
|
94
|
+
display: grid;
|
|
95
|
+
grid-template-areas: 'element-stack';
|
|
96
|
+
align-items: center;
|
|
97
|
+
|
|
98
|
+
background-color: var(--theme-form-radio-bg);
|
|
99
|
+
border: var(--_border-width) solid var(--_border-color);
|
|
100
|
+
|
|
101
|
+
border-radius: calc((var(--_icon-size) / 2) + var(--_form-padding) + var(--_border-width) + var(--_outline-width) + var(--_form-input-outline-width));
|
|
102
|
+
|
|
103
|
+
outline: var(--_outline-width) solid var(--_outline-color);
|
|
104
|
+
box-shadow: var(--_box-shadow);
|
|
105
|
+
|
|
106
|
+
/* height: calc(var(--_icon-size) + 4px); */
|
|
107
|
+
width: calc((var(--_icon-size) * 3) + (var(--_form-padding) * 17));
|
|
108
|
+
|
|
109
|
+
transition: all 0.2s ease-in-out;
|
|
110
|
+
|
|
111
|
+
.input-range-markers {
|
|
112
|
+
grid-area: element-stack;
|
|
113
|
+
display: flex;
|
|
114
|
+
align-items: center;
|
|
115
|
+
justify-content: space-between;
|
|
116
|
+
width: 100%;
|
|
117
|
+
padding: var(--_form-padding);
|
|
118
|
+
|
|
119
|
+
.marker {
|
|
120
|
+
background-color: black;
|
|
121
|
+
padding: var(--_form-padding);
|
|
122
|
+
border-radius: 50%;
|
|
123
|
+
overflow: hidden;
|
|
124
|
+
/* outline: 1px solid gray; */
|
|
125
|
+
|
|
126
|
+
&:hover {
|
|
127
|
+
cursor: pointer;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.marker-icon {
|
|
131
|
+
/* font-size: 2rem; */
|
|
132
|
+
display: block;
|
|
133
|
+
color: white;
|
|
134
|
+
height: var(--_icon-size);
|
|
135
|
+
width: var(--_icon-size);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.scheme-marker-wrapper {
|
|
141
|
+
grid-area: element-stack;
|
|
142
|
+
display: grid;
|
|
143
|
+
gap: 1rem;
|
|
144
|
+
|
|
145
|
+
grid-template-columns: repeat(3, 1fr);
|
|
146
|
+
/* transition: grid-template-columns 0.2s; */
|
|
147
|
+
/* text-align: var(--_scheme-marker-position); */
|
|
148
|
+
padding: var(--_form-padding);
|
|
149
|
+
/* z-index: 9; */
|
|
150
|
+
|
|
151
|
+
.scheme-marker-indicator {
|
|
152
|
+
grid-column: 3;
|
|
153
|
+
aspect-ratio: 1;
|
|
154
|
+
background-color: var(--theme-form-radio-bg);
|
|
155
|
+
background-color: red;
|
|
156
|
+
border-radius: 50%;
|
|
157
|
+
height: calc(var(--_icon-size) + var(--_form-padding));
|
|
158
|
+
/* width: calc(var(--_icon-size) + var(--_form-padding)); */
|
|
159
|
+
padding: var(--_form-padding);
|
|
160
|
+
transition: grid-columns 0.2s;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.form-elements {
|
|
165
|
+
grid-area: element-stack;
|
|
166
|
+
display: flex;
|
|
167
|
+
align-items: center;
|
|
168
|
+
justify-content: space-between;
|
|
169
|
+
gap: 1rem;
|
|
170
|
+
padding: var(--_form-padding);
|
|
171
|
+
|
|
172
|
+
.form-input {
|
|
173
|
+
border-radius: 50%;
|
|
174
|
+
display: flex;
|
|
175
|
+
place-content: center;
|
|
176
|
+
padding: var(--_form-padding);
|
|
177
|
+
outline: var(--_form-input-outline-width) solid gray;
|
|
178
|
+
opacity: 0.75;
|
|
179
|
+
|
|
180
|
+
&:has(input[value='auto']) {
|
|
181
|
+
background-color: green;
|
|
182
|
+
|
|
183
|
+
&:has(input[value='auto']:checked) {
|
|
184
|
+
--_scheme-marker-position: start;
|
|
185
|
+
outline: var(--_form-input-outline-width) solid var(--_border-color);
|
|
186
|
+
opacity: 1;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
&:has(input[value='light']) {
|
|
191
|
+
background-color: orange;
|
|
192
|
+
|
|
193
|
+
&:has(input[value='light']:checked) {
|
|
194
|
+
--_scheme-marker-position: center;
|
|
195
|
+
outline: var(--_form-input-outline-width) solid var(--_border-color);
|
|
196
|
+
opacity: 1;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
&:has(input[value='dark']) {
|
|
201
|
+
background-color: black;
|
|
202
|
+
|
|
203
|
+
&:has(input[value='dark']:checked) {
|
|
204
|
+
--_scheme-marker-position: end;
|
|
205
|
+
outline: var(--_form-input-outline-width) solid var(--_border-color);
|
|
206
|
+
opacity: 1;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
input[type='radio'] {
|
|
211
|
+
opacity: 0;
|
|
212
|
+
height: var(--_icon-size);
|
|
213
|
+
width: var(--_icon-size);
|
|
214
|
+
margin: 0;
|
|
215
|
+
padding: 0;
|
|
216
|
+
|
|
217
|
+
&:hover {
|
|
218
|
+
cursor: pointer;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
</style>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ToggleSwitchWithLabelInline v-model="currentColourScheme" :name :label labelWeight="normal" :size trueValue="dark" falseValue="light" :style-class-passthrough>
|
|
3
|
+
<template #iconOn>
|
|
4
|
+
<LazyIcon name="radix-icons:moon" class="icon" />
|
|
5
|
+
</template>
|
|
6
|
+
<template #iconOff>
|
|
7
|
+
<LazyIcon name="radix-icons:sun" class="icon" />
|
|
8
|
+
</template>
|
|
9
|
+
</ToggleSwitchWithLabelInline>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import propValidators from '../../forms/c12/prop-validators';
|
|
14
|
+
|
|
15
|
+
defineProps({
|
|
16
|
+
name: {
|
|
17
|
+
type: String,
|
|
18
|
+
required: true,
|
|
19
|
+
},
|
|
20
|
+
label: {
|
|
21
|
+
type: String,
|
|
22
|
+
required: true,
|
|
23
|
+
},
|
|
24
|
+
labelWeight: {
|
|
25
|
+
type: String as PropType<string>,
|
|
26
|
+
default: 'semi-bold',
|
|
27
|
+
validator(value: string) {
|
|
28
|
+
return propValidators.labelWeight.includes(value);
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
size: {
|
|
32
|
+
type: String as PropType<string>,
|
|
33
|
+
default: 'small',
|
|
34
|
+
validator(value: string) {
|
|
35
|
+
return propValidators.size.includes(value);
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
styleClassPassthrough: {
|
|
39
|
+
type: Array as PropType<string[]>,
|
|
40
|
+
default: () => [],
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// const displayMode = ref<'auto' | 'dark' | 'light'>('auto');
|
|
45
|
+
|
|
46
|
+
const { currentColourScheme } = useColourScheme();
|
|
47
|
+
</script>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class CustomError extends Error {
|
|
2
|
+
// name = "CustomError";
|
|
3
|
+
override name = 'CustomError';
|
|
4
|
+
extraProp = 'Error: test';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
async function useApiRequest<T, E extends new (message?: string) => Error>(promise: Promise<T>, errorsToCatch?: E[]): Promise<[undefined, T] | [InstanceType<E>]> {
|
|
8
|
+
return promise
|
|
9
|
+
.then((data) => {
|
|
10
|
+
return [undefined, data] as [undefined, T];
|
|
11
|
+
})
|
|
12
|
+
.catch((error) => {
|
|
13
|
+
if (errorsToCatch == undefined) {
|
|
14
|
+
return [error];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (errorsToCatch.some((errorType) => error instanceof errorType)) {
|
|
18
|
+
return [error];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
throw error;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default useApiRequest;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const useColourScheme = () => {
|
|
2
|
+
const currentColourScheme = ref<'auto' | 'dark' | 'light' | null>(null);
|
|
3
|
+
|
|
4
|
+
const returnSavedColourPreferenceFromLocalStorage = () => {
|
|
5
|
+
if (import.meta.client) {
|
|
6
|
+
return localStorage.getItem('colourScheme') as 'auto' | 'dark' | 'light' | null;
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
onMounted(() => {
|
|
11
|
+
currentColourScheme.value = returnSavedColourPreferenceFromLocalStorage() || 'auto';
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
watch(currentColourScheme, (newVal) => {
|
|
15
|
+
if (import.meta.client && newVal !== null) {
|
|
16
|
+
localStorage.setItem('colourScheme', newVal);
|
|
17
|
+
document.documentElement.dataset.colorScheme = newVal;
|
|
18
|
+
currentColourScheme.value = newVal;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
currentColourScheme,
|
|
24
|
+
};
|
|
25
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { IFormData } from '@/types/types.forms';
|
|
2
|
+
|
|
3
|
+
export function useErrorMessage(name: string, formData: Ref<IFormData>) {
|
|
4
|
+
const defaultError = ref('');
|
|
5
|
+
const errorMessages = ref(formData.value.errorMessages);
|
|
6
|
+
|
|
7
|
+
const hasCustomError = () => {
|
|
8
|
+
return errorMessages.value[name] !== undefined && errorMessages.value[name].useCustomError;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const errorMessage = computed(() => {
|
|
12
|
+
console.log(`errorMessage()`);
|
|
13
|
+
if (hasCustomError()) {
|
|
14
|
+
console.log(`errorMessage() | IF`);
|
|
15
|
+
return errorMessages.value[name].message;
|
|
16
|
+
} else {
|
|
17
|
+
return defaultError.value;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const setDefaultError = (newDefaultError: string) => {
|
|
22
|
+
defaultError.value = newDefaultError;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const fieldHasError = computed(() => {
|
|
26
|
+
// console.log(`fieldHasError() | name(${name})`);
|
|
27
|
+
nextTick();
|
|
28
|
+
if (formData.value.submitDisabled) {
|
|
29
|
+
console.log(`fieldHasError() | name(${name}) | IF`);
|
|
30
|
+
if (hasCustomError()) {
|
|
31
|
+
console.log(`fieldHasError() | name(${name}) | IF | IF`);
|
|
32
|
+
|
|
33
|
+
return true;
|
|
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
|
+
|
|
37
|
+
return !formData.value.validityState[name];
|
|
38
|
+
}
|
|
39
|
+
console.log(`fieldHasError() | name(${name}) | IF | ELSE`);
|
|
40
|
+
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const removeCustomError = (valid: boolean = false) => {
|
|
46
|
+
console.log(`useErrorMessage | removeCustomError | name(${name}) | valid(${valid})`);
|
|
47
|
+
// formData.value.validityState[name] = valid;
|
|
48
|
+
// await nextTick();
|
|
49
|
+
// delete formData.value.errorMessages[name];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
hasCustomError,
|
|
54
|
+
errorMessage,
|
|
55
|
+
setDefaultError,
|
|
56
|
+
fieldHasError,
|
|
57
|
+
removeCustomError,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -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
|
+
};
|