srcdev-nuxt-forms 0.0.23 → 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/.prettierrc +2 -1
- package/LICENSE +21 -0
- package/assets/styles/forms/index.css +1 -0
- package/assets/styles/forms/themes/_error.css +9 -0
- package/assets/styles/forms/themes/_ghost.css +11 -0
- package/assets/styles/forms/themes/_primary.css +11 -1
- package/assets/styles/forms/themes/_secondary.css +13 -0
- package/assets/styles/forms/themes/_success.css +12 -0
- package/assets/styles/forms/themes/_tertiary.css +11 -0
- package/assets/styles/forms/themes/_warning.css +11 -0
- package/assets/styles/forms/themes/index.css +5 -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 +56 -2
- package/assets/styles/variables/colors/_gray.css +1 -0
- package/assets/styles/variables/colors/_orange.css +1 -1
- package/assets/styles/variables/colors/_red.css +1 -1
- package/components/forms/c12/prop-validators/index.ts +13 -0
- package/components/forms/c12/utils.ts +14 -0
- package/components/forms/c12/validation-patterns/en.json +13 -1
- package/components/forms/form-errors/InputError.vue +132 -0
- package/components/forms/input-button/InputButtonCore.vue +370 -0
- package/components/forms/input-button/variants/InputButtonConfirm.vue +78 -0
- package/components/forms/input-button/variants/InputButtonSubmit.vue +74 -0
- 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 +115 -79
- package/components/forms/input-text/variants/material/InputEmailMaterial.vue +72 -0
- package/components/forms/input-text/variants/material/InputPasswordMaterial.vue +114 -0
- package/components/forms/input-text/variants/material/InputTextMaterial.vue +68 -0
- package/components/forms/input-text/variants/material/InputTextMaterialCore.vue +313 -0
- 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/forms/ui/FormField.vue +7 -2
- package/components/forms/ui/FormWrapper.vue +2 -2
- package/components/ui/content-grid/ContentGrid.vue +85 -0
- package/composables/useErrorMessages.ts +21 -9
- package/composables/useFormControl.ts +171 -41
- package/layouts/default.vue +28 -3
- package/nuxt.config.ts +26 -3
- package/package.json +9 -6
- package/pages/forms/examples/buttons/index.vue +155 -0
- package/pages/forms/examples/material/text-fields.vue +372 -0
- package/pages/index.vue +2 -70
- 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 +38 -13
- package/types/types.places.ts +8 -0
- package/components/forms/input-text/InputTextField.vue +0 -22
- package/components/forms/input-text/variants/InputTextMaterial.vue +0 -192
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<NuxtLayout name="default">
|
|
4
|
+
<template #layout-content>
|
|
5
|
+
<div>
|
|
6
|
+
<h1>Material UI text fields ({{ compact ? 'compact' : 'default' }})</h1>
|
|
7
|
+
<ul class="flex-group">
|
|
8
|
+
<li>
|
|
9
|
+
<InputButtonSubmit type="button" @click.stop.prevent="swapCompact(false)" button-text="Use Default UI" theme="secondary" size="normal" />
|
|
10
|
+
</li>
|
|
11
|
+
<li>
|
|
12
|
+
<InputButtonSubmit type="button" @click.stop.prevent="swapCompact(true)" button-text="Use Compact UI" theme="secondary" size="normal" />
|
|
13
|
+
</li>
|
|
14
|
+
</ul>
|
|
15
|
+
|
|
16
|
+
<p>Example test fields in default material UI</p>
|
|
17
|
+
<p>Use 'test@test.com' to trigger server errors</p>
|
|
18
|
+
</div>
|
|
19
|
+
<ContentGrid>
|
|
20
|
+
<template #slot1>
|
|
21
|
+
<FormWrapper width="medium">
|
|
22
|
+
<template #default>
|
|
23
|
+
<ClientOnly>
|
|
24
|
+
<form class="form-wrapper" @submit.stop.prevent="submitForm()">
|
|
25
|
+
<div aria-live="assertive" id="aria-live-message"></div>
|
|
26
|
+
<FormField width="wide" :has-gutter="false">
|
|
27
|
+
<template #default>
|
|
28
|
+
<InputEmailMaterial
|
|
29
|
+
id="emailAddress"
|
|
30
|
+
name="emailAddress"
|
|
31
|
+
validation="emailaddress"
|
|
32
|
+
:required="true"
|
|
33
|
+
:c12="{
|
|
34
|
+
label: 'Your Email Address',
|
|
35
|
+
placeholder: 'eg. joe@example.com',
|
|
36
|
+
errorMessage: 'Please enter a valid email address',
|
|
37
|
+
}"
|
|
38
|
+
v-model="formData"
|
|
39
|
+
theme="secondary"
|
|
40
|
+
:compact
|
|
41
|
+
/>
|
|
42
|
+
</template>
|
|
43
|
+
</FormField>
|
|
44
|
+
|
|
45
|
+
<FormField width="wide" :has-gutter="false">
|
|
46
|
+
<template #default>
|
|
47
|
+
<InputTextMaterial
|
|
48
|
+
id="username"
|
|
49
|
+
name="username"
|
|
50
|
+
validation="username"
|
|
51
|
+
:required="true"
|
|
52
|
+
:c12="{
|
|
53
|
+
label: 'Your Username',
|
|
54
|
+
placeholder: 'eg. YourUserName',
|
|
55
|
+
errorMessage: 'Please enter a valid username',
|
|
56
|
+
}"
|
|
57
|
+
v-model="formData"
|
|
58
|
+
theme="secondary"
|
|
59
|
+
:compact
|
|
60
|
+
/>
|
|
61
|
+
</template>
|
|
62
|
+
</FormField>
|
|
63
|
+
|
|
64
|
+
<FormField width="wide" :has-gutter="false">
|
|
65
|
+
<template #default>
|
|
66
|
+
<InputPasswordMaterial
|
|
67
|
+
id="password"
|
|
68
|
+
name="password"
|
|
69
|
+
validation="password"
|
|
70
|
+
:required="true"
|
|
71
|
+
:c12="{
|
|
72
|
+
label: 'Password',
|
|
73
|
+
placeholder: 'eg. Your5illYPa55w0rd',
|
|
74
|
+
errorMessage: 'Please enter a valid password',
|
|
75
|
+
}"
|
|
76
|
+
v-model="formData"
|
|
77
|
+
theme="secondary"
|
|
78
|
+
:compact
|
|
79
|
+
/>
|
|
80
|
+
</template>
|
|
81
|
+
</FormField>
|
|
82
|
+
|
|
83
|
+
<FormField width="wide" :has-gutter="false">
|
|
84
|
+
<template #default>
|
|
85
|
+
<InputTextareaMaterial
|
|
86
|
+
id="message"
|
|
87
|
+
name="message"
|
|
88
|
+
validation="message"
|
|
89
|
+
:required="true"
|
|
90
|
+
:c12="{
|
|
91
|
+
label: 'Message',
|
|
92
|
+
placeholder: 'eg. Type something here',
|
|
93
|
+
errorMessage: 'Bad characters in message',
|
|
94
|
+
}"
|
|
95
|
+
v-model="formData"
|
|
96
|
+
theme="secondary"
|
|
97
|
+
:compact
|
|
98
|
+
/>
|
|
99
|
+
</template>
|
|
100
|
+
</FormField>
|
|
101
|
+
|
|
102
|
+
<FormField width="wide" :has-gutter="false">
|
|
103
|
+
<template #default>
|
|
104
|
+
<InputRangeDefault
|
|
105
|
+
id="score"
|
|
106
|
+
name="score"
|
|
107
|
+
:min="0"
|
|
108
|
+
:max="100"
|
|
109
|
+
:step="1"
|
|
110
|
+
:required="true"
|
|
111
|
+
validation="positiveNumber0to100"
|
|
112
|
+
:c12="{
|
|
113
|
+
label: 'Score between 0 & 100',
|
|
114
|
+
placeholder: 'eg. What\'s your score?',
|
|
115
|
+
errorMessage: 'Score between 0 & 100',
|
|
116
|
+
}"
|
|
117
|
+
v-model="formData"
|
|
118
|
+
theme="secondary"
|
|
119
|
+
>
|
|
120
|
+
<template #description>
|
|
121
|
+
<p class="label-description">This is a description of what the user is required to do</p>
|
|
122
|
+
</template>
|
|
123
|
+
<template #left><</template>
|
|
124
|
+
<template #right>></template>
|
|
125
|
+
</InputRangeDefault>
|
|
126
|
+
</template>
|
|
127
|
+
</FormField>
|
|
128
|
+
|
|
129
|
+
<FormField v-if="citiesData !== null" width="wide" :has-gutter="false">
|
|
130
|
+
<template #default>
|
|
131
|
+
<MultipleCheckboxes
|
|
132
|
+
id="cities"
|
|
133
|
+
name="cities"
|
|
134
|
+
legend="Choose a location"
|
|
135
|
+
:required="true"
|
|
136
|
+
:c12="{
|
|
137
|
+
label: 'Check all Cities you like',
|
|
138
|
+
placeholder: 'eg. Type something here',
|
|
139
|
+
errorMessage: 'Please choose at least 1 location',
|
|
140
|
+
}"
|
|
141
|
+
v-model="formData"
|
|
142
|
+
v-model:fieldData="citiesData"
|
|
143
|
+
theme="secondary"
|
|
144
|
+
size="normal"
|
|
145
|
+
checkbox-style="check"
|
|
146
|
+
checkbox-appearance="with-decorator"
|
|
147
|
+
>
|
|
148
|
+
<template #description>
|
|
149
|
+
<p class="label-description">This is description: optionsLayout = 'equal-widths'</p>
|
|
150
|
+
</template>
|
|
151
|
+
</MultipleCheckboxes>
|
|
152
|
+
</template>
|
|
153
|
+
</FormField>
|
|
154
|
+
|
|
155
|
+
<FormField v-if="countriesData !== null" width="wide" :has-gutter="false">
|
|
156
|
+
<template #default>
|
|
157
|
+
<MultipleCheckboxes
|
|
158
|
+
id="countries"
|
|
159
|
+
name="countries"
|
|
160
|
+
legend="Choose a country"
|
|
161
|
+
:required="true"
|
|
162
|
+
:c12="{
|
|
163
|
+
label: 'Check all countries you like',
|
|
164
|
+
placeholder: 'eg. Choose some locations',
|
|
165
|
+
errorMessage: 'Please select a country',
|
|
166
|
+
}"
|
|
167
|
+
v-model="formData"
|
|
168
|
+
v-model:fieldData="countriesData"
|
|
169
|
+
theme="secondary"
|
|
170
|
+
size="normal"
|
|
171
|
+
options-layout="inline"
|
|
172
|
+
checkbox-style="cross"
|
|
173
|
+
checkbox-appearance="with-decorator"
|
|
174
|
+
>
|
|
175
|
+
<template #description>
|
|
176
|
+
<p class="label-description">This is description: optionsLayout = 'inline'</p>
|
|
177
|
+
</template>
|
|
178
|
+
</MultipleCheckboxes>
|
|
179
|
+
</template>
|
|
180
|
+
</FormField>
|
|
181
|
+
|
|
182
|
+
<FormField v-if="titleData !== null" width="wide" :has-gutter="false">
|
|
183
|
+
<template #default>
|
|
184
|
+
<MultipleRadio
|
|
185
|
+
id="title"
|
|
186
|
+
name="title"
|
|
187
|
+
legend="Choose a title"
|
|
188
|
+
:required="true"
|
|
189
|
+
:c12="{
|
|
190
|
+
label: 'Check all title you like',
|
|
191
|
+
placeholder: 'eg. Choose some title',
|
|
192
|
+
errorMessage: 'Please select a title',
|
|
193
|
+
}"
|
|
194
|
+
v-model="formData"
|
|
195
|
+
v-model:fieldData="titleData"
|
|
196
|
+
theme="secondary"
|
|
197
|
+
size="normal"
|
|
198
|
+
options-layout="equal-widths"
|
|
199
|
+
radio-appearance="with-decorator"
|
|
200
|
+
>
|
|
201
|
+
<template #description>
|
|
202
|
+
<p class="label-description">This is description: optionsLayout = 'equal-widths'</p>
|
|
203
|
+
</template>
|
|
204
|
+
</MultipleRadio>
|
|
205
|
+
</template>
|
|
206
|
+
</FormField>
|
|
207
|
+
|
|
208
|
+
<FormField width="wide" :has-gutter="false">
|
|
209
|
+
<template #default>
|
|
210
|
+
<SingleCheckbox
|
|
211
|
+
id="terms"
|
|
212
|
+
name="terms"
|
|
213
|
+
legend="Accept terms and conditions"
|
|
214
|
+
:required="true"
|
|
215
|
+
:c12="{
|
|
216
|
+
label: 'Accept terms and conditions',
|
|
217
|
+
placeholder: 'eg. Type something here',
|
|
218
|
+
errorMessage: 'Please accept our terms and conditions',
|
|
219
|
+
}"
|
|
220
|
+
v-model="formData"
|
|
221
|
+
theme="secondary"
|
|
222
|
+
size="normal"
|
|
223
|
+
checkbox-appearance="with-decorator"
|
|
224
|
+
checkbox-style="check"
|
|
225
|
+
>
|
|
226
|
+
<template #description>
|
|
227
|
+
<p class="label-description">This is a description of what the user is required to do</p>
|
|
228
|
+
</template>
|
|
229
|
+
</SingleCheckbox>
|
|
230
|
+
</template>
|
|
231
|
+
</FormField>
|
|
232
|
+
|
|
233
|
+
<FormField width="wide" :has-gutter="false">
|
|
234
|
+
<template #default>
|
|
235
|
+
<InputButtonSubmit type="button" @click.stop.prevent="submitForm()" :is-pending="false" :readonly="submitDisabled" button-text="Submit" theme="secondary" size="medium" />
|
|
236
|
+
</template>
|
|
237
|
+
</FormField>
|
|
238
|
+
</form>
|
|
239
|
+
</ClientOnly>
|
|
240
|
+
</template>
|
|
241
|
+
</FormWrapper>
|
|
242
|
+
</template>
|
|
243
|
+
<template #slot2>
|
|
244
|
+
<ClientOnly>
|
|
245
|
+
<p>Client only content</p>
|
|
246
|
+
<pre>
|
|
247
|
+
{{ formData }}
|
|
248
|
+
</pre>
|
|
249
|
+
</ClientOnly>
|
|
250
|
+
</template>
|
|
251
|
+
</ContentGrid>
|
|
252
|
+
</template>
|
|
253
|
+
</NuxtLayout>
|
|
254
|
+
</div>
|
|
255
|
+
</template>
|
|
256
|
+
|
|
257
|
+
<script setup lang="ts">
|
|
258
|
+
import type { IFieldsInitialState, IOptionsConfig, IFormMultipleOptions } from '@/types/types.forms';
|
|
259
|
+
|
|
260
|
+
definePageMeta({
|
|
261
|
+
layout: false,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
useHead({
|
|
265
|
+
title: 'Text Field Example',
|
|
266
|
+
meta: [{ name: 'description', content: 'Homepage' }],
|
|
267
|
+
bodyAttrs: {
|
|
268
|
+
class: '',
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const compact = ref(false);
|
|
273
|
+
const swapCompact = (newStyle: boolean) => {
|
|
274
|
+
compact.value = newStyle;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
/*
|
|
278
|
+
* Fetch some sample data
|
|
279
|
+
**/
|
|
280
|
+
const { data: citiesData } = await useFetch<IFormMultipleOptions>('/api/places/list?category=cities');
|
|
281
|
+
const { data: countriesData } = await useFetch<IFormMultipleOptions>('/api/places/list?category=countries');
|
|
282
|
+
const { data: titleData } = await useFetch<IFormMultipleOptions>('/api/utils?category=title');
|
|
283
|
+
|
|
284
|
+
/*
|
|
285
|
+
* Setup forms
|
|
286
|
+
*/
|
|
287
|
+
const fieldsInitialState = ref<IFieldsInitialState>({
|
|
288
|
+
// emailAddress: "simon@simon.com",
|
|
289
|
+
// emailAddress: "test@test.com",
|
|
290
|
+
emailAddress: '',
|
|
291
|
+
// username: "",
|
|
292
|
+
username: '',
|
|
293
|
+
// password: "!+Password123",
|
|
294
|
+
password: '',
|
|
295
|
+
message: '',
|
|
296
|
+
// message: 'This is test 1234567890,.<>?@;:',
|
|
297
|
+
score: 50,
|
|
298
|
+
cities: [],
|
|
299
|
+
countries: [],
|
|
300
|
+
title: [],
|
|
301
|
+
terms: false,
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Setup formData
|
|
305
|
+
const { formData, initFormData, getErrorCount, updateErrorMessages, formIsValid, submitDisabled, useApiErrors } = useFormControl();
|
|
306
|
+
await initFormData(fieldsInitialState);
|
|
307
|
+
|
|
308
|
+
async function postFormData() {
|
|
309
|
+
try {
|
|
310
|
+
const data = await $fetch('/api/textFields', {
|
|
311
|
+
method: 'post',
|
|
312
|
+
body: formData.value.data,
|
|
313
|
+
onResponse({ response }) {
|
|
314
|
+
if (response.status === 400) {
|
|
315
|
+
console.log('onResponse', response);
|
|
316
|
+
console.log(response.status);
|
|
317
|
+
|
|
318
|
+
useApiErrors(response._data.data.errors);
|
|
319
|
+
// for (const [key, message] of Object.entries(response._data.data.errors)) {
|
|
320
|
+
// console.log(`${key}: ${message}`);
|
|
321
|
+
// updateErrorMessages(key, message);
|
|
322
|
+
// }
|
|
323
|
+
}
|
|
324
|
+
if (response.status === 200) {
|
|
325
|
+
formData.value.isPending = false;
|
|
326
|
+
formData.value.submitSuccess = true;
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
console.log('3: Finished data', data);
|
|
331
|
+
// return data;
|
|
332
|
+
} catch (error) {
|
|
333
|
+
console.warn('2: An error occured posting form data', error);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const submitForm = async () => {
|
|
338
|
+
getErrorCount(true);
|
|
339
|
+
|
|
340
|
+
if (formIsValid.value) {
|
|
341
|
+
formData.value.isPending = true;
|
|
342
|
+
formData.value.submitDisabled = true;
|
|
343
|
+
console.log('Form is good - post it!');
|
|
344
|
+
|
|
345
|
+
postFormData();
|
|
346
|
+
|
|
347
|
+
// formData.value.errorMessages['emailAddress'] = {
|
|
348
|
+
// useCustomError: true,
|
|
349
|
+
// message: 'This is a custom error message',
|
|
350
|
+
// };
|
|
351
|
+
|
|
352
|
+
// executePost();
|
|
353
|
+
} else {
|
|
354
|
+
console.warn('Form has errors');
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
</script>
|
|
358
|
+
|
|
359
|
+
<style lang="css">
|
|
360
|
+
.flex-group {
|
|
361
|
+
align-items: flex-start;
|
|
362
|
+
display: flex;
|
|
363
|
+
flex-wrap: wrap;
|
|
364
|
+
gap: 24px;
|
|
365
|
+
margin-bottom: 32px;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
ul.flex-group {
|
|
369
|
+
list-style-type: none;
|
|
370
|
+
padding: 0;
|
|
371
|
+
}
|
|
372
|
+
</style>
|
package/pages/index.vue
CHANGED
|
@@ -3,53 +3,16 @@
|
|
|
3
3
|
<NuxtLayout name="default">
|
|
4
4
|
<template #layout-content>
|
|
5
5
|
<div>
|
|
6
|
-
<h1>Sample form page</h1>
|
|
6
|
+
<h1>Sample form page pages</h1>
|
|
7
7
|
|
|
8
|
-
<
|
|
9
|
-
<template #default>
|
|
10
|
-
<form @submit.prevent="isPending">
|
|
11
|
-
<p>Form content</p>
|
|
12
|
-
<FormField width="wide" :has-gutter="true">
|
|
13
|
-
<template #default>
|
|
14
|
-
<p>Input text</p>
|
|
15
|
-
<InputTextMaterial
|
|
16
|
-
id="username"
|
|
17
|
-
name="username"
|
|
18
|
-
type="text"
|
|
19
|
-
validation="username"
|
|
20
|
-
:required="true"
|
|
21
|
-
:c12="{
|
|
22
|
-
label: 'Choose Username',
|
|
23
|
-
placeholder: 'eg. YourUserName',
|
|
24
|
-
errorMessage: 'Please enter a valid username',
|
|
25
|
-
}"
|
|
26
|
-
v-model="formData"
|
|
27
|
-
/>
|
|
28
|
-
</template>
|
|
29
|
-
</FormField>
|
|
30
|
-
<input
|
|
31
|
-
type="submit"
|
|
32
|
-
@click.prevent="isPending"
|
|
33
|
-
value="Submit"
|
|
34
|
-
/>
|
|
35
|
-
</form>
|
|
36
|
-
</template>
|
|
37
|
-
</FormWrapper>
|
|
8
|
+
<p>Example test fields in default material UI</p>
|
|
38
9
|
</div>
|
|
39
|
-
<ClientOnly>
|
|
40
|
-
<p>Client only content</p>
|
|
41
|
-
<pre>
|
|
42
|
-
{{ formData }}
|
|
43
|
-
</pre>
|
|
44
|
-
</ClientOnly>
|
|
45
10
|
</template>
|
|
46
11
|
</NuxtLayout>
|
|
47
12
|
</div>
|
|
48
13
|
</template>
|
|
49
14
|
|
|
50
15
|
<script setup lang="ts">
|
|
51
|
-
import type { IFieldsInitialState, IOptionsConfig } from '@/types/types.forms';
|
|
52
|
-
|
|
53
16
|
definePageMeta({
|
|
54
17
|
layout: false,
|
|
55
18
|
});
|
|
@@ -61,37 +24,6 @@ useHead({
|
|
|
61
24
|
class: '',
|
|
62
25
|
},
|
|
63
26
|
});
|
|
64
|
-
|
|
65
|
-
/*
|
|
66
|
-
* Setup forms
|
|
67
|
-
*/
|
|
68
|
-
const fieldsInitialState = ref<IFieldsInitialState>({
|
|
69
|
-
username: '',
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// Setup formData
|
|
73
|
-
const {
|
|
74
|
-
formData,
|
|
75
|
-
initFormData,
|
|
76
|
-
getErrorCount,
|
|
77
|
-
updateCustomErrors,
|
|
78
|
-
resetForm,
|
|
79
|
-
formIsValid,
|
|
80
|
-
showErrors,
|
|
81
|
-
} = useFormControl(fieldsInitialState);
|
|
82
|
-
|
|
83
|
-
await initFormData();
|
|
84
|
-
|
|
85
|
-
const isPending = async () => {
|
|
86
|
-
formData.value.isPending = true;
|
|
87
|
-
await getErrorCount();
|
|
88
|
-
|
|
89
|
-
if (formIsValid.value) {
|
|
90
|
-
console.log('Form is good - post it!');
|
|
91
|
-
} else {
|
|
92
|
-
console.warn('Form has errors');
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
27
|
</script>
|
|
96
28
|
|
|
97
29
|
<style lang="css">
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="wrapper">
|
|
3
|
+
<div class="text-wrapper">
|
|
4
|
+
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit.</p>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="text-wrapper">
|
|
7
|
+
<p>Nisi harum impedit, expedita eius doloremque dicta obcaecati tempora dolorem eum fuga deserunt minus facere error mollitia pariatur cum tempore, reiciendis molestiae?</p>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="text-wrapper">
|
|
10
|
+
<p>
|
|
11
|
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Nisi harum impedit, expedita eius doloremque dicta obcaecati tempora dolorem eum fuga deserunt minus facere error mollitia pariatur cum
|
|
12
|
+
tempore, reiciendis molestiae?
|
|
13
|
+
</p>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
</template>
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
definePageMeta({
|
|
19
|
+
layout: false,
|
|
20
|
+
});
|
|
21
|
+
</script>
|
|
22
|
+
<style lang="css">
|
|
23
|
+
.wrapper {
|
|
24
|
+
display: grid;
|
|
25
|
+
grid-template-columns: 1fr 1fr 1fr;
|
|
26
|
+
gap: 20px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.text-wrapper {
|
|
30
|
+
max-height: 100px;
|
|
31
|
+
width: 200px;
|
|
32
|
+
padding: 20px;
|
|
33
|
+
outline: 1px solid black;
|
|
34
|
+
|
|
35
|
+
p {
|
|
36
|
+
display: -webkit-box;
|
|
37
|
+
-webkit-box-orient: vertical;
|
|
38
|
+
line-clamp: 3;
|
|
39
|
+
-webkit-line-clamp: 3;
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import cities from '../../data/places/cities.json';
|
|
2
|
+
import countries from '../../data/places/countries.json';
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const sleep = async (ms: number) => {
|
|
6
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const query = getQuery(event);
|
|
10
|
+
|
|
11
|
+
let timeout = 0;
|
|
12
|
+
if (typeof query.delay !== 'undefined') {
|
|
13
|
+
timeout = Number(query.delay);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
await sleep(timeout);
|
|
17
|
+
|
|
18
|
+
if (query.category === 'cities') {
|
|
19
|
+
return cities;
|
|
20
|
+
} else if (query.category === 'countries') {
|
|
21
|
+
return countries;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createError } from 'h3';
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event: any) => {
|
|
4
|
+
const body = await readBody<{ emailAddress: string; password: string; username: string }>(event);
|
|
5
|
+
|
|
6
|
+
const { emailAddress, password, username } = body;
|
|
7
|
+
|
|
8
|
+
let throwError = false;
|
|
9
|
+
|
|
10
|
+
if (emailAddress === 'test@test.com') {
|
|
11
|
+
throwError = true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const response = {
|
|
15
|
+
status: 200,
|
|
16
|
+
statusText: 'success',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// set some other response status cde
|
|
20
|
+
// setResponseStatus(event, 202)
|
|
21
|
+
|
|
22
|
+
// Throw some server side errors
|
|
23
|
+
if (throwError) {
|
|
24
|
+
throw createError({
|
|
25
|
+
statusCode: 400,
|
|
26
|
+
statusMessage: 'error',
|
|
27
|
+
data: {
|
|
28
|
+
errors: {
|
|
29
|
+
emailAddress: 'Email address already registered',
|
|
30
|
+
username: 'Username already registered',
|
|
31
|
+
password: ['Password is too weak', 'Password is too short'],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return response;
|
|
37
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import title from '../../data/utils/title.json';
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const sleep = async (ms: number) => {
|
|
5
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const query = getQuery(event);
|
|
9
|
+
|
|
10
|
+
let timeout = 0;
|
|
11
|
+
if (typeof query.delay !== 'undefined') {
|
|
12
|
+
timeout = Number(query.delay);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
await sleep(timeout);
|
|
16
|
+
|
|
17
|
+
if (query.category === 'title') {
|
|
18
|
+
return title;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": [
|
|
3
|
+
{
|
|
4
|
+
"id": "bath",
|
|
5
|
+
"name": "cities",
|
|
6
|
+
"value": "cities-12",
|
|
7
|
+
"label": "Bath"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "bristol",
|
|
11
|
+
"name": "cities",
|
|
12
|
+
"value": "cities-23",
|
|
13
|
+
"label": "Bristol"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "london",
|
|
17
|
+
"name": "cities",
|
|
18
|
+
"value": "cities-42",
|
|
19
|
+
"label": "London"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "sunderland",
|
|
23
|
+
"name": "cities",
|
|
24
|
+
"value": "cities-56",
|
|
25
|
+
"label": "Sunderland"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "penzance",
|
|
29
|
+
"name": "cities",
|
|
30
|
+
"value": "cities-09",
|
|
31
|
+
"label": "Penzance"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"total": 5,
|
|
35
|
+
"skip": 0,
|
|
36
|
+
"limit": 10
|
|
37
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": [
|
|
3
|
+
{
|
|
4
|
+
"id": "uk",
|
|
5
|
+
"name": "countries",
|
|
6
|
+
"value": "countries-12",
|
|
7
|
+
"label": "United Kingdom"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "france",
|
|
11
|
+
"name": "countries",
|
|
12
|
+
"value": "countries-23",
|
|
13
|
+
"label": "France"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "spain",
|
|
17
|
+
"name": "countries",
|
|
18
|
+
"value": "countries-42",
|
|
19
|
+
"label": "Spain"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "italy",
|
|
23
|
+
"name": "countries",
|
|
24
|
+
"value": "countries-56",
|
|
25
|
+
"label": "Italy"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "greece",
|
|
29
|
+
"name": "countries",
|
|
30
|
+
"value": "countries-09",
|
|
31
|
+
"label": "Greece"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "turkey",
|
|
35
|
+
"name": "countries",
|
|
36
|
+
"value": "countries-13",
|
|
37
|
+
"label": "Turkey"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "germany",
|
|
41
|
+
"name": "countries",
|
|
42
|
+
"value": "countries-76",
|
|
43
|
+
"label": "Germany"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "portugal",
|
|
47
|
+
"name": "countries",
|
|
48
|
+
"value": "countries-34",
|
|
49
|
+
"label": "Portugal"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"total": 5,
|
|
53
|
+
"skip": 0,
|
|
54
|
+
"limit": 10
|
|
55
|
+
}
|