@volverjs/form-vue 1.0.0-beta.9 → 1.1.0-beta.1
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 +1 -1
- package/README.md +442 -251
- package/dist/VvForm.d.ts +134 -0
- package/dist/VvFormField.d.ts +129 -0
- package/dist/VvFormFieldsGroup.d.ts +108 -0
- package/dist/VvFormTemplate.d.ts +39 -0
- package/dist/VvFormWrapper.d.ts +64 -0
- package/dist/{src/enums.d.ts → enums.d.ts} +1 -0
- package/dist/index.d.ts +972 -1
- package/dist/index.es.js +4285 -576
- package/dist/index.umd.js +3 -1
- package/dist/types.d.ts +101 -0
- package/dist/utils.d.ts +11 -0
- package/package.json +61 -60
- package/src/VvForm.ts +361 -301
- package/src/VvFormField.ts +367 -334
- package/src/VvFormFieldsGroup.ts +382 -0
- package/src/VvFormTemplate.ts +191 -171
- package/src/VvFormWrapper.ts +199 -161
- package/src/enums.ts +27 -26
- package/src/index.ts +154 -136
- package/src/types.ts +194 -125
- package/src/utils.ts +283 -102
- package/dist/src/VvForm.d.ts +0 -202
- package/dist/src/VvFormField.d.ts +0 -116
- package/dist/src/VvFormTemplate.d.ts +0 -60
- package/dist/src/VvFormWrapper.d.ts +0 -107
- package/dist/src/index.d.ts +0 -1498
- package/dist/src/types.d.ts +0 -70
- package/dist/src/utils.d.ts +0 -3
- package/dist/test-playwright/VvForm.spec.d.ts +0 -1
- package/dist/test-playwright/VvFormField.spec.d.ts +0 -1
- package/dist/test-playwright/VvFormWrapper.spec.d.ts +0 -1
- package/dist/test-vitest/defaultObjectBySchema.test.d.ts +0 -1
- package/dist/test-vitest/useForm.test.d.ts +0 -1
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
[](https://volverjs.github.io/form-vue)
|
|
4
4
|
|
|
5
5
|
## @volverjs/form-vue
|
|
@@ -44,20 +44,24 @@ npm install @volverjs/form-vue --save
|
|
|
44
44
|
```typescript
|
|
45
45
|
import { createApp } from 'vue'
|
|
46
46
|
import { createForm } from '@volverjs/form-vue'
|
|
47
|
-
import { z } from 'zod'
|
|
47
|
+
import { z } from 'zod/v3'
|
|
48
48
|
|
|
49
49
|
const schema = z.object({
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
firstName: z.string(),
|
|
51
|
+
lastName: z.string()
|
|
52
52
|
})
|
|
53
53
|
|
|
54
54
|
const app = createApp(App)
|
|
55
55
|
const form = createForm({
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
schema
|
|
57
|
+
// lazyLoad: boolean - default false
|
|
58
|
+
// updateThrottle: number - default 500
|
|
59
|
+
// continuousValidation: boolean - default false
|
|
60
|
+
// sideEffects?: (data: any) => void
|
|
61
|
+
// scope?: string - Defines a unique scope for the form instance (singletons)
|
|
62
|
+
// class?: new (data?: any) => Type - Type constructor for form data
|
|
63
|
+
// Example:
|
|
64
|
+
// class: class User { constructor(data?: any) { Object.assign(this, data) } }
|
|
61
65
|
})
|
|
62
66
|
|
|
63
67
|
app.use(form)
|
|
@@ -73,19 +77,21 @@ A `valid` or `invalid` event is emitted when the form status changes.
|
|
|
73
77
|
|
|
74
78
|
```vue
|
|
75
79
|
<script lang="ts" setup>
|
|
76
|
-
|
|
80
|
+
function onSubmit(formData) {
|
|
77
81
|
// ...
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
}
|
|
83
|
+
function onInvalid(errors) {
|
|
80
84
|
// ...
|
|
81
|
-
|
|
85
|
+
}
|
|
82
86
|
</script>
|
|
83
87
|
|
|
84
88
|
<template>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
<VvForm @submit="onSubmit" @invalid="onInvalid">
|
|
90
|
+
<!-- ... -->
|
|
91
|
+
<button type="submit">
|
|
92
|
+
Submit
|
|
93
|
+
</button>
|
|
94
|
+
</VvForm>
|
|
89
95
|
</template>
|
|
90
96
|
```
|
|
91
97
|
|
|
@@ -93,23 +99,25 @@ The submit can be triggered programmatically with the `submit()` method.
|
|
|
93
99
|
|
|
94
100
|
```vue
|
|
95
101
|
<script lang="ts" setup>
|
|
96
|
-
|
|
97
|
-
|
|
102
|
+
import { ref } from 'vue'
|
|
103
|
+
import type { FormComponent } from '@volverjs/form-vue'
|
|
98
104
|
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
const formEl = ref<InstanceType<FormComponent>>(null)
|
|
106
|
+
function onSubmit(formData) {
|
|
101
107
|
// ...
|
|
102
|
-
|
|
103
|
-
|
|
108
|
+
}
|
|
109
|
+
function submitForm() {
|
|
104
110
|
formEl.value.submit()
|
|
105
|
-
|
|
111
|
+
}
|
|
106
112
|
</script>
|
|
107
113
|
|
|
108
114
|
<template>
|
|
109
|
-
|
|
115
|
+
<VvForm ref="formEl" @submit="onSubmit">
|
|
110
116
|
<!-- ... -->
|
|
111
|
-
|
|
112
|
-
|
|
117
|
+
</VvForm>
|
|
118
|
+
<button type="button" @click.stop="submitForm">
|
|
119
|
+
Submit
|
|
120
|
+
</button>
|
|
113
121
|
</template>
|
|
114
122
|
```
|
|
115
123
|
|
|
@@ -118,22 +126,22 @@ Use the `v-model` directive (or only `:model-value` to set the initial value of
|
|
|
118
126
|
The form data two way binding is **throttled** by default (500ms) to avoid performance issues. The throttle can be changed with the `updateThrottle` option or prop.
|
|
119
127
|
|
|
120
128
|
By default form validation **stops** when a **valid state** is reached.
|
|
121
|
-
To activate **
|
|
129
|
+
To activate **continuous validation** use the `continuousValidation` option or prop.
|
|
122
130
|
|
|
123
131
|
```vue
|
|
124
132
|
<script lang="ts" setup>
|
|
125
|
-
|
|
133
|
+
import { ref } from 'vue'
|
|
126
134
|
|
|
127
|
-
|
|
135
|
+
const formData = ref({
|
|
128
136
|
firstName: '',
|
|
129
137
|
lastName: ''
|
|
130
|
-
|
|
138
|
+
})
|
|
131
139
|
</script>
|
|
132
140
|
|
|
133
141
|
<template>
|
|
134
|
-
|
|
142
|
+
<VvForm v-model="formData" :update-throttle="1000" continuous-validation>
|
|
135
143
|
<!-- ... -->
|
|
136
|
-
|
|
144
|
+
</VvForm>
|
|
137
145
|
</template>
|
|
138
146
|
```
|
|
139
147
|
|
|
@@ -144,27 +152,29 @@ The **default settings** are **inherited** from the plugin (if it's installed).
|
|
|
144
152
|
|
|
145
153
|
```vue
|
|
146
154
|
<script lang="ts" setup>
|
|
147
|
-
|
|
148
|
-
|
|
155
|
+
import { useForm } from '@volverjs/form-vue'
|
|
156
|
+
import { z } from 'zod/v3'
|
|
149
157
|
|
|
150
|
-
|
|
158
|
+
const schema = z.object({
|
|
151
159
|
firstName: z.string(),
|
|
152
160
|
lastName: z.string()
|
|
153
|
-
|
|
161
|
+
})
|
|
154
162
|
|
|
155
|
-
|
|
163
|
+
const { VvForm, VvFormWrapper, VvFormField } = useForm(schema, {
|
|
156
164
|
// lazyLoad: boolean - default false
|
|
157
165
|
// updateThrottle: number - default 500
|
|
158
|
-
//
|
|
166
|
+
// continuousValidation: boolean - default false
|
|
159
167
|
// sideEffects?: (formData: any) => void
|
|
160
|
-
|
|
168
|
+
// scope?: string
|
|
169
|
+
// class?: new (data?: any) => Type
|
|
170
|
+
})
|
|
161
171
|
</script>
|
|
162
172
|
|
|
163
173
|
<template>
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
174
|
+
<VvForm>
|
|
175
|
+
<VvFormField type="text" name="firstName" label="First Name" />
|
|
176
|
+
<VvFormField type="text" name="lastName" label="Last Name" />
|
|
177
|
+
</VvForm>
|
|
168
178
|
</template>
|
|
169
179
|
```
|
|
170
180
|
|
|
@@ -174,33 +184,37 @@ The **default settings** are **inherited** from the plugin (if it's installed).
|
|
|
174
184
|
|
|
175
185
|
```ts
|
|
176
186
|
import { useForm } from '@volverjs/form-vue'
|
|
177
|
-
import { z } from 'zod'
|
|
187
|
+
import { z } from 'zod/v3'
|
|
178
188
|
|
|
179
189
|
const schema = z.object({
|
|
180
|
-
|
|
181
|
-
|
|
190
|
+
firstName: z.string(),
|
|
191
|
+
lastName: z.string()
|
|
182
192
|
})
|
|
183
193
|
|
|
184
194
|
const {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
195
|
+
VvForm,
|
|
196
|
+
VvFormWrapper,
|
|
197
|
+
VvFormField,
|
|
198
|
+
VvFormFieldsGroup,
|
|
199
|
+
VvFormTemplate,
|
|
200
|
+
formData,
|
|
201
|
+
status,
|
|
202
|
+
errors,
|
|
203
|
+
wrappers
|
|
192
204
|
} = useForm(schema, {
|
|
193
|
-
|
|
205
|
+
lazyLoad: true
|
|
194
206
|
})
|
|
195
207
|
|
|
196
208
|
export default {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
209
|
+
VvForm,
|
|
210
|
+
VvFormWrapper,
|
|
211
|
+
VvFormField,
|
|
212
|
+
VvFormFieldsGroup,
|
|
213
|
+
VvFormTemplate,
|
|
214
|
+
formData,
|
|
215
|
+
status,
|
|
216
|
+
errors,
|
|
217
|
+
wrappers
|
|
204
218
|
}
|
|
205
219
|
```
|
|
206
220
|
|
|
@@ -211,20 +225,20 @@ The wrapper status is invalid if at least one of the fields inside it is invalid
|
|
|
211
225
|
|
|
212
226
|
```vue
|
|
213
227
|
<template>
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
+
<VvForm>
|
|
229
|
+
<VvFormWrapper v-slot="{ invalid }" name="firstSection">
|
|
230
|
+
<div class="form-section-1">
|
|
231
|
+
<span v-if="invalid">There is a validation error</span>
|
|
232
|
+
<!-- form fields of section 1 -->
|
|
233
|
+
</div>
|
|
234
|
+
</VvFormWrapper>
|
|
235
|
+
<VvFormWrapper v-slot="{ invalid }" name="secondSection">
|
|
236
|
+
<div class="form-section-2">
|
|
237
|
+
<span v-if="invalid">There is a validation error</span>
|
|
238
|
+
<!-- form fields of the section 2 -->
|
|
239
|
+
</div>
|
|
240
|
+
</VvFormWrapper>
|
|
241
|
+
</VvForm>
|
|
228
242
|
</template>
|
|
229
243
|
```
|
|
230
244
|
|
|
@@ -232,23 +246,37 @@ The wrapper status is invalid if at least one of the fields inside it is invalid
|
|
|
232
246
|
|
|
233
247
|
```vue
|
|
234
248
|
<template>
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
249
|
+
<VvForm>
|
|
250
|
+
<!-- main VvFormWrapper -->
|
|
251
|
+
<VvFormWrapper v-slot="{ invalid }" name="firstSection">
|
|
252
|
+
<!-- add VvFormFields to wrapper -->
|
|
253
|
+
<div class="form-section">
|
|
254
|
+
<span v-if="invalid">There is a validation error</span>
|
|
255
|
+
<!-- nested VvFormWrapper -->
|
|
256
|
+
<VvFormWrapper v-slot="{ invalid: groupInvalid }">
|
|
257
|
+
<div class="form-section__group">
|
|
258
|
+
<span v-if="groupInvalid">There is a validation error</span>
|
|
259
|
+
<!-- add VvFormFields to nested wrapper -->
|
|
260
|
+
</div>
|
|
261
|
+
</VvFormWrapper>
|
|
262
|
+
</div>
|
|
245
263
|
</VvFormWrapper>
|
|
246
|
-
|
|
247
|
-
</VvFormWrapper>
|
|
248
|
-
</VvForm>
|
|
264
|
+
</VvForm>
|
|
249
265
|
</template>
|
|
250
266
|
```
|
|
251
267
|
|
|
268
|
+
The `wrappers` map provides access to form wrapper data.
|
|
269
|
+
This allows for better control over form validation state and data management.
|
|
270
|
+
|
|
271
|
+
```vue
|
|
272
|
+
<script setup>
|
|
273
|
+
const { wrappers } = useForm(schema)
|
|
274
|
+
|
|
275
|
+
// Access wrapper data
|
|
276
|
+
const isFirstSectionInvalid = computed(() => wrappers.get('firstSection').invalid)
|
|
277
|
+
</script>
|
|
278
|
+
```
|
|
279
|
+
|
|
252
280
|
### VvFormField
|
|
253
281
|
|
|
254
282
|
`VvFormField` allow you to render a form field or a [`@volverjs/ui-vue`](https://github.com/volverjs/ui-vue) input component inside a form.
|
|
@@ -257,26 +285,26 @@ It automatically bind the form data through the `name` attribute. For nested obj
|
|
|
257
285
|
|
|
258
286
|
```vue
|
|
259
287
|
<template>
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
288
|
+
<VvForm>
|
|
289
|
+
<VvFormField
|
|
290
|
+
v-slot="{ modelValue, invalid, invalidLabel, onUpdate }"
|
|
291
|
+
name="lastName"
|
|
292
|
+
>
|
|
293
|
+
<label for="lastName">Last Name</label>
|
|
294
|
+
<input
|
|
295
|
+
id="lastName"
|
|
296
|
+
type="text"
|
|
297
|
+
name="lastName"
|
|
298
|
+
:value="modelValue"
|
|
299
|
+
:aria-invalid="invalid"
|
|
300
|
+
:aria-errormessage="invalid ? 'last-name-alert' : undefined"
|
|
301
|
+
@input="onUpdate"
|
|
302
|
+
>
|
|
303
|
+
<small v-if="invalid" id="last-name-alert" role="alert">
|
|
304
|
+
{{ invalidLabel }}
|
|
305
|
+
</small>
|
|
306
|
+
</VvFormField>
|
|
307
|
+
</VvForm>
|
|
280
308
|
</template>
|
|
281
309
|
```
|
|
282
310
|
|
|
@@ -285,83 +313,218 @@ By default UI components must be installed globally, they can be lazy-loaded wit
|
|
|
285
313
|
|
|
286
314
|
```vue
|
|
287
315
|
<template>
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
316
|
+
<VvForm>
|
|
317
|
+
<VvFormField type="text" name="username" label="Username" lazy-load />
|
|
318
|
+
<VvFormField type="password" name="password" label="Password" lazy-load />
|
|
319
|
+
</VvForm>
|
|
292
320
|
</template>
|
|
293
321
|
```
|
|
294
322
|
|
|
295
323
|
Check the [`VvFormField` documentation](./docs/VvFormField.md) to learn more about form fields.
|
|
296
324
|
|
|
325
|
+
### VvFormFieldsGroup
|
|
326
|
+
|
|
327
|
+
`VvFormFieldsGroup` allow you to render a group of form fields inside a form.
|
|
328
|
+
|
|
329
|
+
It automatically bind the form data through the `names` attribute. For nested objects, use the `names` attribute with **dot notation**.
|
|
330
|
+
|
|
331
|
+
```vue
|
|
332
|
+
<template>
|
|
333
|
+
<VvForm>
|
|
334
|
+
<VvFormFieldsGroup
|
|
335
|
+
v-slot="{ modelValue, invalids, invalidLabels, onUpdateField }"
|
|
336
|
+
:names="['firstName', 'lastName']"
|
|
337
|
+
>
|
|
338
|
+
<fieldset>
|
|
339
|
+
<p>
|
|
340
|
+
<label for="firstName">First Name</label>
|
|
341
|
+
<input
|
|
342
|
+
id="firstName"
|
|
343
|
+
type="text"
|
|
344
|
+
name="firstName"
|
|
345
|
+
:value="modelValue.firstName"
|
|
346
|
+
:aria-invalid="invalids.firstName"
|
|
347
|
+
:aria-errormessage="invalids.firstName ? 'first-name-alert' : undefined"
|
|
348
|
+
@input="onUpdateField('firstName', $event)"
|
|
349
|
+
>
|
|
350
|
+
<small v-if="invalids.firstName" id="first-name-alert" role="alert">
|
|
351
|
+
{{ invalidLabels?.firstName }}
|
|
352
|
+
</small>
|
|
353
|
+
</p>
|
|
354
|
+
<p>
|
|
355
|
+
<label for="lastName">Last Name</label>
|
|
356
|
+
<input
|
|
357
|
+
id="lastName"
|
|
358
|
+
type="text"
|
|
359
|
+
name="lastName"
|
|
360
|
+
:value="modelValue.lastName"
|
|
361
|
+
:aria-invalid="invalids.lastName"
|
|
362
|
+
:aria-errormessage="invalids.lastName ? 'last-name-alert' : undefined"
|
|
363
|
+
@input="onUpdateField('lastName', $event)"
|
|
364
|
+
>
|
|
365
|
+
<small v-if="invalids.lastName" id="last-name-alert" role="alert">
|
|
366
|
+
{{ invalidLabels?.lastName }}
|
|
367
|
+
</small>
|
|
368
|
+
</p>
|
|
369
|
+
</fieldset>
|
|
370
|
+
</VvFormFieldsGroup>
|
|
371
|
+
</VvForm>
|
|
372
|
+
</template>
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Alternatively, you can create a custom component to render the group of form fields.
|
|
376
|
+
|
|
377
|
+
```vue
|
|
378
|
+
// MyFieldsGroup.vue
|
|
379
|
+
<script lang="ts" setup>
|
|
380
|
+
defineProps<{
|
|
381
|
+
invalids: Record<string, boolean>
|
|
382
|
+
invalidLabels?: Record<string, string[]>
|
|
383
|
+
}>()
|
|
384
|
+
// v-model:first-name
|
|
385
|
+
const firstName = defineModel<string>('firstName', { default: '' })
|
|
386
|
+
// v-model:last-name
|
|
387
|
+
const lastName = defineModel<string>('lastName', { default: '' })
|
|
388
|
+
</script>
|
|
389
|
+
|
|
390
|
+
<template>
|
|
391
|
+
<fieldset>
|
|
392
|
+
<p>
|
|
393
|
+
<label for="firstName">First Name</label>
|
|
394
|
+
<input
|
|
395
|
+
id="firstName"
|
|
396
|
+
v-model="firstName"
|
|
397
|
+
type="text"
|
|
398
|
+
name="firstName"
|
|
399
|
+
:aria-invalid="invalids.firstName"
|
|
400
|
+
:aria-errormessage="invalids.firstName ? 'first-name-alert' : undefined"
|
|
401
|
+
>
|
|
402
|
+
<small v-if="invalids.firstName" id="first-name-alert" role="alert">
|
|
403
|
+
{{ invalidLabels?.firstName }}
|
|
404
|
+
</small>
|
|
405
|
+
</p>
|
|
406
|
+
<p>
|
|
407
|
+
<label for="lastName">Last Name</label>
|
|
408
|
+
<input
|
|
409
|
+
id="lastName"
|
|
410
|
+
v-model="lastName"
|
|
411
|
+
type="text"
|
|
412
|
+
name="lastName"
|
|
413
|
+
:aria-invalid="invalids.lastName"
|
|
414
|
+
:aria-errormessage="invalids.lastName ? 'last-name-alert' : undefined"
|
|
415
|
+
>
|
|
416
|
+
<small v-if="invalids.lastName" id="last-name-alert" role="alert">
|
|
417
|
+
{{ invalidLabels?.lastName }}
|
|
418
|
+
</small>
|
|
419
|
+
</p>
|
|
420
|
+
</fieldset>
|
|
421
|
+
</template>
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
An than use it inside the `VvFormFieldsGroup` with the `:is` attribute.
|
|
425
|
+
|
|
426
|
+
```vue
|
|
427
|
+
<script>
|
|
428
|
+
import MyFieldsGroup from './MyFieldsGroup.vue'
|
|
429
|
+
</script>
|
|
430
|
+
|
|
431
|
+
<template>
|
|
432
|
+
<VvForm>
|
|
433
|
+
<VvFormFieldsGroup
|
|
434
|
+
:is="MyFieldsGroup"
|
|
435
|
+
:names="['firstName', 'lastName']"
|
|
436
|
+
/>
|
|
437
|
+
</VvForm>
|
|
438
|
+
</template>
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
You can also map the form fields to the components v-models. The `:names` attribute can be an object with the component v-models as keys and the form fields names as values.
|
|
442
|
+
|
|
443
|
+
```vue
|
|
444
|
+
<script>
|
|
445
|
+
import MyCustomComponent from './MyCustomComponent.vue'
|
|
446
|
+
</script>
|
|
447
|
+
|
|
448
|
+
<template>
|
|
449
|
+
<VvForm>
|
|
450
|
+
<VvFormFieldsGroup
|
|
451
|
+
:is="MyCustomComponent"
|
|
452
|
+
:names="{
|
|
453
|
+
myCustomComponentVModel: 'path.to.form.field',
|
|
454
|
+
}"
|
|
455
|
+
/>
|
|
456
|
+
</VvForm>
|
|
457
|
+
</template>
|
|
458
|
+
```
|
|
459
|
+
|
|
297
460
|
## VvFormTemplate
|
|
298
461
|
|
|
299
462
|
Forms can also be created using a template. A template is an **array of objects** that describes the form fields. All properties that are **not listed** below are passed to the component **as props**.
|
|
300
463
|
|
|
301
464
|
```vue
|
|
302
465
|
<script lang="ts" setup>
|
|
303
|
-
|
|
304
|
-
|
|
466
|
+
import { useForm } from '@volverjs/form-vue'
|
|
467
|
+
import { z } from 'zod/v3'
|
|
305
468
|
|
|
306
|
-
|
|
469
|
+
const schema = z.object({
|
|
307
470
|
firstName: z.string(),
|
|
308
471
|
lastName: z.string(),
|
|
309
472
|
address: z.object({
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
473
|
+
street: z.string(),
|
|
474
|
+
number: z.string(),
|
|
475
|
+
city: z.string(),
|
|
476
|
+
zip: z.number()
|
|
314
477
|
})
|
|
315
|
-
|
|
478
|
+
})
|
|
316
479
|
|
|
317
|
-
|
|
480
|
+
const templateSchema = [
|
|
318
481
|
{
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
482
|
+
vvName: 'firstName',
|
|
483
|
+
vvType: 'text',
|
|
484
|
+
label: 'First Name'
|
|
322
485
|
},
|
|
323
486
|
{
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
487
|
+
vvName: 'lastName',
|
|
488
|
+
vvType: 'text',
|
|
489
|
+
label: 'Last Name'
|
|
327
490
|
},
|
|
328
491
|
{
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
492
|
+
vvIs: 'div',
|
|
493
|
+
class: 'grid grid-col-3 gap-4',
|
|
494
|
+
vvChildren: [
|
|
495
|
+
{
|
|
496
|
+
vvName: 'address.street',
|
|
497
|
+
vvType: 'text',
|
|
498
|
+
label: 'Street',
|
|
499
|
+
class: 'col-span-2'
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
vvName: 'address.number',
|
|
503
|
+
vvType: 'text',
|
|
504
|
+
label: 'Number'
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
vvName: 'address.city',
|
|
508
|
+
vvType: 'text',
|
|
509
|
+
label: 'City',
|
|
510
|
+
class: 'col-span-2',
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
vvName: 'address.zip',
|
|
514
|
+
vvType: 'number',
|
|
515
|
+
label: 'Zip'
|
|
516
|
+
}
|
|
517
|
+
]
|
|
355
518
|
}
|
|
356
|
-
|
|
519
|
+
]
|
|
357
520
|
|
|
358
|
-
|
|
521
|
+
const { VvForm, VvFormTemplate } = useForm(schema)
|
|
359
522
|
</script>
|
|
360
523
|
|
|
361
524
|
<template>
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
525
|
+
<VvForm>
|
|
526
|
+
<VvFormTemplate :schema="templateSchema" />
|
|
527
|
+
</VvForm>
|
|
365
528
|
</template>
|
|
366
529
|
```
|
|
367
530
|
|
|
@@ -378,69 +541,69 @@ Conditional rendering can be achieved using the `vvIf` and `vvElseIf` properties
|
|
|
378
541
|
|
|
379
542
|
```vue
|
|
380
543
|
<script lang="ts" setup>
|
|
381
|
-
|
|
382
|
-
|
|
544
|
+
import { useForm } from '@volverjs/form-vue'
|
|
545
|
+
import { z } from 'zod/v3'
|
|
383
546
|
|
|
384
|
-
|
|
547
|
+
const schema = z.object({
|
|
385
548
|
firstName: z.string(),
|
|
386
549
|
lastName: z.string(),
|
|
387
550
|
hasUsername: z.boolean(),
|
|
388
|
-
username: z.string().optional()
|
|
551
|
+
username: z.string().optional(),
|
|
389
552
|
email: z.string().email().optional()
|
|
390
|
-
|
|
553
|
+
}).superRefine((value, ctx) => {
|
|
391
554
|
if (value.hasUsername && !value.username) {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
555
|
+
ctx.addIssue({
|
|
556
|
+
code: z.ZodIssueCode.custom,
|
|
557
|
+
message: 'Username is required'
|
|
558
|
+
})
|
|
396
559
|
}
|
|
397
560
|
if (!value.hasUsername && !value.email) {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
561
|
+
ctx.addIssue({
|
|
562
|
+
code: z.ZodIssueCode.custom,
|
|
563
|
+
message: 'Email is required'
|
|
564
|
+
})
|
|
402
565
|
}
|
|
403
|
-
|
|
566
|
+
})
|
|
404
567
|
|
|
405
|
-
|
|
568
|
+
const templateSchema = [
|
|
406
569
|
{
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
570
|
+
vvName: 'firstName',
|
|
571
|
+
vvType: 'text',
|
|
572
|
+
label: 'First Name'
|
|
410
573
|
},
|
|
411
574
|
{
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
575
|
+
vvName: 'lastName',
|
|
576
|
+
vvType: 'text',
|
|
577
|
+
label: 'Last Name'
|
|
415
578
|
},
|
|
416
579
|
{
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
580
|
+
vvName: 'hasUsername',
|
|
581
|
+
vvType: 'checkbox',
|
|
582
|
+
label: 'Has Username',
|
|
583
|
+
value: true,
|
|
584
|
+
uncheckedValue: false
|
|
422
585
|
},
|
|
423
586
|
{
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
587
|
+
vvIf: 'hasUsername',
|
|
588
|
+
vvName: 'username',
|
|
589
|
+
vvType: 'text',
|
|
590
|
+
label: 'Username'
|
|
428
591
|
},
|
|
429
592
|
{
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
593
|
+
vvElseIf: true,
|
|
594
|
+
vvName: 'email',
|
|
595
|
+
vvType: 'email',
|
|
596
|
+
label: 'Email'
|
|
434
597
|
}
|
|
435
|
-
|
|
598
|
+
]
|
|
436
599
|
|
|
437
|
-
|
|
600
|
+
const { VvForm, VvFormTemplate } = useForm(schema)
|
|
438
601
|
</script>
|
|
439
602
|
|
|
440
603
|
<template>
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
604
|
+
<VvForm>
|
|
605
|
+
<VvFormTemplate :schema="templateSchema" />
|
|
606
|
+
</VvForm>
|
|
444
607
|
</template>
|
|
445
608
|
```
|
|
446
609
|
|
|
@@ -449,107 +612,135 @@ Conditional rendering can be achieved using the `vvIf` and `vvElseIf` properties
|
|
|
449
612
|
`vvIf` and `vvElseIf` can be a string or a function. If it is a string it will be evaluated as a **property** of the form data. If it is a function it will be called with the **form context** as the **first argument** and must return a boolean.
|
|
450
613
|
|
|
451
614
|
```ts
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
615
|
+
const templateSchema = [
|
|
616
|
+
{
|
|
617
|
+
vvIf: ctx => ctx.formData.value.hasUsername,
|
|
618
|
+
vvName: 'username',
|
|
619
|
+
vvType: 'text',
|
|
620
|
+
label: 'Username'
|
|
621
|
+
}
|
|
622
|
+
]
|
|
458
623
|
```
|
|
459
624
|
|
|
460
625
|
Also the template schema and all template items can be a function.
|
|
461
626
|
The function will be called with the **form context** as the **first argument**.
|
|
462
627
|
|
|
463
628
|
```ts
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
629
|
+
function templateSchema(ctx) {
|
|
630
|
+
return [
|
|
631
|
+
{
|
|
632
|
+
vvName: 'firstName',
|
|
633
|
+
vvType: 'text',
|
|
634
|
+
label: `Hi ${ctx.formData.value.firstName}!`
|
|
635
|
+
}
|
|
636
|
+
]
|
|
637
|
+
}
|
|
471
638
|
```
|
|
472
639
|
|
|
473
640
|
```ts
|
|
474
641
|
const templateSchema = [
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
642
|
+
ctx => ({
|
|
643
|
+
vvName: 'firstName',
|
|
644
|
+
vvType: 'text',
|
|
645
|
+
label: `Hi ${ctx.formData.value.firstName}!`
|
|
646
|
+
}),
|
|
647
|
+
{
|
|
648
|
+
vvName: 'username',
|
|
649
|
+
type: 'text',
|
|
650
|
+
label: 'username'
|
|
651
|
+
}
|
|
485
652
|
]
|
|
486
653
|
```
|
|
487
654
|
|
|
488
|
-
## Default Object by Zod
|
|
655
|
+
## Default Object by Zod Schema
|
|
489
656
|
|
|
490
657
|
`defaultObjectBySchema` creates an object by a [Zod Object Schema](https://zod.dev/?id=objects).
|
|
491
658
|
It can be useful to create a **default object** for a **form**. The default object is created by the default values of the schema and can be merged with an other object passed as parameter.
|
|
492
659
|
|
|
493
660
|
```ts
|
|
494
|
-
import { z } from 'zod'
|
|
661
|
+
import { z } from 'zod/v3'
|
|
495
662
|
import { defaultObjectBySchema } from '@volverjs/form-vue'
|
|
496
663
|
|
|
497
664
|
const schema = z.object({
|
|
498
|
-
|
|
499
|
-
|
|
665
|
+
firstName: z.string().default('John'),
|
|
666
|
+
lastName: z.string().default('Doe')
|
|
500
667
|
})
|
|
501
668
|
|
|
502
669
|
const defaultObject = defaultObjectBySchema(schema)
|
|
503
|
-
// defaultObject = {
|
|
670
|
+
// defaultObject = { firstName: 'John', lastName: 'Doe' }
|
|
504
671
|
|
|
505
672
|
const defaultObject = defaultObjectBySchema(schema, { name: 'Jane' })
|
|
506
|
-
// defaultObject = {
|
|
673
|
+
// defaultObject = { firstName: 'Jane', lastName: 'Doe' }
|
|
507
674
|
```
|
|
508
675
|
|
|
509
676
|
`defaultObjectBySchema` can be used with nested objects.
|
|
510
677
|
|
|
511
678
|
```ts
|
|
512
|
-
import { z } from 'zod'
|
|
679
|
+
import { z } from 'zod/v3'
|
|
513
680
|
import { defaultObjectBySchema } from '@volverjs/form-vue'
|
|
514
681
|
|
|
515
682
|
const schema = z.object({
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
683
|
+
firstName: z.string().default('John'),
|
|
684
|
+
lastName: z.string().default('Doe'),
|
|
685
|
+
address: z.object({
|
|
686
|
+
street: z.string().default('Main Street'),
|
|
687
|
+
number: z.number().default(1)
|
|
688
|
+
})
|
|
522
689
|
})
|
|
523
690
|
|
|
524
691
|
const defaultObject = defaultObjectBySchema(schema)
|
|
525
|
-
// defaultObject = {
|
|
692
|
+
// defaultObject = { firstName: 'John', lastName: 'Doe', address: { street: 'Main Street', number: 1 } }
|
|
526
693
|
```
|
|
527
694
|
|
|
528
695
|
Other Zod methods are also supported: [`z.nullable()`](https://github.com/colinhacks/zod#nullable), [`z.coerce`](https://github.com/colinhacks/zod#coercion-for-primitives) and [`z.passthrough()`](https://github.com/colinhacks/zod#passthrough).
|
|
529
696
|
|
|
530
697
|
```ts
|
|
531
|
-
import { z } from 'zod'
|
|
698
|
+
import { z } from 'zod/v3'
|
|
532
699
|
import { defaultObjectBySchema } from '@volverjs/form-vue'
|
|
533
700
|
|
|
534
701
|
const schema = z
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
702
|
+
.object({
|
|
703
|
+
firstName: z.string().default('John'),
|
|
704
|
+
lastName: z.string().default('Doe'),
|
|
705
|
+
address: z.object({
|
|
706
|
+
street: z.string().default('Main Street'),
|
|
707
|
+
number: z.number().default(1)
|
|
708
|
+
}),
|
|
709
|
+
age: z.number().nullable().default(null),
|
|
710
|
+
height: z.coerce.number().default(1.8),
|
|
711
|
+
weight: z.number().default(80)
|
|
712
|
+
})
|
|
713
|
+
.passthrough()
|
|
547
714
|
|
|
548
715
|
const defaultObject = defaultObjectBySchema(schema, {
|
|
549
|
-
|
|
550
|
-
|
|
716
|
+
height: '1.9',
|
|
717
|
+
email: 'john.doe@test.com'
|
|
718
|
+
})
|
|
719
|
+
// defaultObject = { firstName: 'John', lastName: 'Doe', address: { street: 'Main Street', number: 1 }, age: null, height: 1.9, weight: 80, email: 'john.doe@test.com' }
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
## Zod 4
|
|
723
|
+
`@volverjs/form-vue` supports Zod 4 from `zod@3.25.x` and `zod@4.x.x`. All features and methods are compatible and the usage remains the same, the library automatically detects the Zod version and adapts accordingly.
|
|
724
|
+
|
|
725
|
+
```typescript
|
|
726
|
+
import * as z from 'zod/v4'
|
|
727
|
+
import { createForm, useForm, defaultObjectBySchema } from '@volverjs/form-vue'
|
|
728
|
+
|
|
729
|
+
const schema = z.object({
|
|
730
|
+
firstName: z.string(),
|
|
731
|
+
lastName: z.string()
|
|
732
|
+
})
|
|
733
|
+
|
|
734
|
+
// Plugin
|
|
735
|
+
const form = createForm({
|
|
736
|
+
schema
|
|
551
737
|
})
|
|
552
|
-
|
|
738
|
+
|
|
739
|
+
// Composable
|
|
740
|
+
const { VvForm, VvFormWrapper, VvFormField } = useForm(schema)
|
|
741
|
+
|
|
742
|
+
// Default Object by Zod Schema
|
|
743
|
+
const defaultObject = defaultObjectBySchema(schema, { firstName: 'Jane' })
|
|
553
744
|
```
|
|
554
745
|
|
|
555
746
|
## License
|