@volverjs/form-vue 0.0.10-beta.1 → 0.0.10-beta.10

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/README.md CHANGED
@@ -33,11 +33,11 @@ npm install @volverjs/form-vue --save
33
33
 
34
34
  ## Usage
35
35
 
36
- `@volverjs/form-vue` allow you to create a Vue 3 form with [`@volverjs/ui-vue`](https://github.com/volverjs/ui-vue) components from a [Zod Object](https://zod.dev/?id=objects) schema. It provides three functions: `createForm`, `useForm` and `formFactory`.
36
+ `@volverjs/form-vue` allow you to create a Vue 3 form with [`@volverjs/ui-vue`](https://github.com/volverjs/ui-vue) components from a [Zod Object](https://zod.dev/?id=objects) schema. It provides two functions: `createForm()` and `useForm()`.
37
37
 
38
38
  ## Plugin
39
39
 
40
- `createForm` defines globally three components `VvForm`, `VvFormWrapper`, and `VvFormField` through a [Vue 3 Plugin](https://vuejs.org/guide/reusability/plugins.html).
40
+ `createForm()` defines globally three components `VvForm`, `VvFormWrapper`, and `VvFormField` through a [Vue 3 Plugin](https://vuejs.org/guide/reusability/plugins.html).
41
41
 
42
42
  ```typescript
43
43
  import { createApp } from 'vue'
@@ -45,8 +45,8 @@ import { createForm } from '@volverjs/form-vue'
45
45
  import { z } from 'zod'
46
46
 
47
47
  const schema = z.object({
48
- name: z.string(),
49
- surname: z.string()
48
+ firstName: z.string(),
49
+ lastName: z.string()
50
50
  })
51
51
 
52
52
  const app = createApp(App)
@@ -62,7 +62,7 @@ app.use(form)
62
62
  app.mount('#app')
63
63
  ```
64
64
 
65
- By default [`@volverjs/ui-vue`](https://github.com/volverjs/ui-vue) components must be defined globally but can be lazy loaded with `lazyLoad` option. If the schema is omitted, the plugin only share the options to the forms created with the [composable](https://github.com/volverjs/form-vue/#composable).
65
+ If the schema is omitted, the plugin only share the options to the forms created with the [composable](https://github.com/volverjs/form-vue/#composable).
66
66
 
67
67
  ### VvForm
68
68
 
@@ -72,16 +72,16 @@ A `valid` or `invalid` event is emitted when the form status changes.
72
72
  ```vue
73
73
  <script lang="ts" setup>
74
74
  const onSubmit = (formData) => {
75
- // Do something with the form data
75
+ // ...
76
76
  }
77
77
  const onInvalid = (errors) => {
78
- // Do something with the errors
78
+ // ...
79
79
  }
80
80
  </script>
81
81
 
82
82
  <template>
83
83
  <VvForm @submit="onSubmit" @invalid="onInvalid">
84
- <!-- form fields -->
84
+ <!-- ... -->
85
85
  <button type="submit">Submit</button>
86
86
  </VvForm>
87
87
  </template>
@@ -96,7 +96,7 @@ The submit can be triggered programmatically with the `submit()` method.
96
96
 
97
97
  const formEl = ref<InstanceType<FormComponent>>(null)
98
98
  const onSubmit = (formData) => {
99
- // Do something with the form data
99
+ // ...
100
100
  }
101
101
  const submitForm = () => {
102
102
  formEl.value.submit()
@@ -105,58 +105,103 @@ The submit can be triggered programmatically with the `submit()` method.
105
105
 
106
106
  <template>
107
107
  <VvForm @submit="onSubmit" ref="formEl">
108
- <!-- form fields -->
108
+ <!-- ... -->
109
109
  </VvForm>
110
110
  <button type="button" @click.stop="submitForm">Submit</button>
111
111
  </template>
112
112
  ```
113
113
 
114
- Use the `v-model` directive (or only `:model-value` to set the initial value of form data) to bind the form data.
115
- The form data two way binding is throttled by default (500ms) to avoid performance issues.
116
- The throttle can be changed with the `updateThrottle` option.
114
+ Use the `v-model` directive (or only `:model-value` to set the initial value of form data) or bind the form data.
115
+
116
+ 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.
117
+
118
+ By default form validation **stops** when a **valid state** is reached.
119
+ To activate **continuos validation** use the `continuosValidation` option or prop.
117
120
 
118
121
  ```vue
119
122
  <script lang="ts" setup>
120
123
  import { ref } from 'vue'
121
124
 
122
125
  const formData = ref({
123
- name: '',
124
- surname: ''
126
+ firstName: '',
127
+ lastName: ''
125
128
  })
126
129
  </script>
127
130
 
128
131
  <template>
129
- <VvForm v-model="formData">
130
- <!-- form fields -->
132
+ <VvForm v-model="formData" :update-throttle="1000" continuos-validation>
133
+ <!-- ... -->
131
134
  </VvForm>
132
135
  </template>
133
136
  ```
134
137
 
135
- The `continuosValidation` can be passed through options or with VvForm prop.
136
- With this field the validation doesn't stop and continue also after a validaton success.
138
+ ## Composable
139
+
140
+ `useForm()` can be used to create a form programmatically inside a Vue 3 Component.
141
+ The **default settings** are **inherited** from the plugin (if it's installed).
137
142
 
138
143
  ```vue
139
144
  <script lang="ts" setup>
140
- import { ref } from 'vue'
145
+ import { useForm } from '@volverjs/form-vue'
146
+ import { z } from 'zod'
141
147
 
142
- const { VvForm, VvFormField } = useForm(MyZodSchema, {
143
- lazyLoad: true
144
- // continuosValidation: true
148
+ const schema = z.object({
149
+ firstName: z.string(),
150
+ lastName: z.string()
145
151
  })
146
152
 
147
- const formData = ref({
148
- name: '',
149
- surname: ''
153
+ const { VvForm, VvFormWrapper, VvFormField } = useForm(schema, {
154
+ // lazyLoad: boolean - default false
155
+ // updateThrottle: number - default 500
156
+ // continuosValidation: boolean - default false
157
+ // sideEffects?: (formData: any) => void
150
158
  })
151
159
  </script>
152
160
 
153
161
  <template>
154
- <VvForm v-model="formData" :continuosValidation="true">
155
- <!-- form fields -->
162
+ <VvForm>
163
+ <VvFormField type="text" name="firstName" label="First Name" />
164
+ <VvFormField type="text" name="lastName" label="Last Name" />
156
165
  </VvForm>
157
166
  </template>
158
167
  ```
159
168
 
169
+ ### Outside a Vue 3 Component
170
+
171
+ `useForm()` can create a form also outside a Vue 3 Component, plugin settings are **not inherited**.
172
+
173
+ ```ts
174
+ import { formFactory } from '@volverjs/form-vue'
175
+ import { z } from 'zod'
176
+
177
+ const schema = z.object({
178
+ name: z.string(),
179
+ surname: z.string()
180
+ })
181
+
182
+ const {
183
+ VvForm,
184
+ VvFormWrapper,
185
+ VvFormField,
186
+ VvFormTemplate,
187
+ formData,
188
+ status,
189
+ errors
190
+ } = formFactory(schema, {
191
+ lazyLoad: true
192
+ })
193
+
194
+ export default {
195
+ VvForm,
196
+ VvFormWrapper,
197
+ VvFormField,
198
+ VvFormTemplate,
199
+ formData,
200
+ status,
201
+ errors
202
+ }
203
+ ```
204
+
160
205
  ### VvFormWrapper
161
206
 
162
207
  `VvFormWrapper` gives you the validation status of a part of your form.
@@ -181,7 +226,7 @@ The wrapper status is invalid if at least one of the fields inside it is invalid
181
226
  </template>
182
227
  ```
183
228
 
184
- `VvFormWrapper` can be used recursively to create a validation tree. The wrapper status is invalid if at least one of the fields inside it or one of its children is invalid.
229
+ `VvFormWrapper` can be used recursively to create a validation tree. The wrapper status is invalid if **at least one of the fields** inside it or one of its children **is invalid**.
185
230
 
186
231
  ```vue
187
232
  <template>
@@ -204,57 +249,28 @@ The wrapper status is invalid if at least one of the fields inside it is invalid
204
249
 
205
250
  ### VvFormField
206
251
 
207
- `VvFormField` allow you to render a [`@volverjs/ui-vue`](https://github.com/volverjs/ui-vue) input component inside a form. It automatically bind the form data through the `name` attribute.
208
-
209
- ```vue
210
- <template>
211
- <VvForm>
212
- <VvFormField type="text" name="name" label="Name" />
213
- <VvFormField type="text" name="surname" label="Surname" />
214
- </VvForm>
215
- </template>
216
- ```
217
-
218
- For nested objects, use the `name` attribute with dot notation.
219
-
220
- ```vue
221
- <template>
222
- <VvForm>
223
- <VvFormField type="text" name="shipping.address" label="Shipping address" />
224
- </VvForm>
225
- </template>
226
- ```
227
-
228
- The type of input component is defined by the `type` attribute.
229
- All the available input types are listed in the [VvFormField documentation](/docs/VvFormField.md).
252
+ `VvFormField` allow you to render a form field or a [`@volverjs/ui-vue`](https://github.com/volverjs/ui-vue) input component inside a form.
230
253
 
231
- You can also use the `VvFormField` component to render a default slot without a `type` (default `type` is `custom`).
254
+ It automatically bind the form data through the `name` attribute. For nested objects, use the `name` attribute with **dot notation**.
232
255
 
233
256
  ```vue
234
257
  <template>
235
258
  <VvForm>
236
259
  <VvFormField
237
- v-slot="{
238
- modelValue,
239
- invalid,
240
- invalidLabel,
241
- formData,
242
- formErrors,
243
- errors,
244
- onUpdate
245
- }"
246
- name="surname"
260
+ v-slot="{ modelValue, invalid, invalidLabel, onUpdate }"
261
+ name="lastName"
247
262
  >
248
- <label for="surname">Surname</label>
263
+ <label for="lastName">Last Name</label>
249
264
  <input
250
- id="surname"
265
+ id="lastName"
251
266
  type="text"
267
+ name="lastName"
252
268
  :value="modelValue"
253
269
  :aria-invalid="invalid"
254
- :aria-errormessage="invalid ? 'surname-alert' : undefined"
270
+ :aria-errormessage="invalid ? 'last-name-alert' : undefined"
255
271
  @input="onUpdate"
256
272
  />
257
- <small v-if="invalid" role="alert" id="surname-alert">
273
+ <small v-if="invalid" role="alert" id="last-name-alert">
258
274
  {{ invalidLabel }}
259
275
  </small>
260
276
  </VvFormField>
@@ -262,104 +278,101 @@ You can also use the `VvFormField` component to render a default slot without a
262
278
  </template>
263
279
  ```
264
280
 
265
- Or a custom component.
281
+ To render a [`@volverjs/ui-vue`](https://github.com/volverjs/ui-vue) input component, use the `type` attribute.
282
+ By default UI components must be installed globally, they can be lazy-loaded with `lazyLoad` option or prop.
266
283
 
267
284
  ```vue
268
- <script lang="ts" setup>
269
- import MyInput from './MyInput.vue'
270
- </script>
271
-
272
285
  <template>
273
286
  <VvForm>
274
- <VvFormField name="surname" :is="MyInput" />
287
+ <VvFormField type="text" name="username" label="Username" lazy-load />
288
+ <VvFormField type="password" name="password" label="Password" lazy-load />
275
289
  </VvForm>
276
290
  </template>
277
291
  ```
278
292
 
279
- ## Nested VvFormField
293
+ Check the [`VvFormField` documentation](./docs/VvFormField.md) to learn more about form fields.
280
294
 
281
- In some use cases can be usefull nest `VvFormField`.
282
- For example let's assume:
295
+ ## VvFormTemplate
283
296
 
284
- - a shopping list that is a field of our model (ex: ToDo list)
285
- - the sum of all products of the shopping list cannot be 0
286
- - we don't know all the products a priori
287
-
288
- So our ToDo model and shopping list are structured like:
289
-
290
- ```javascript
291
- const toDo = {
292
- shoppingList: {
293
- bread: 0,
294
- milk: 0,
295
- tomato: 0,
296
- potato: 0,
297
- ...
298
- }
299
- }
300
- ```
297
+ 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**.
301
298
 
302
- Our Zod schema can be:
299
+ ```vue
300
+ <script lang="ts" setup>
301
+ import { useForm } from '@volverjs/form-vue'
302
+ import { z } from 'zod'
303
303
 
304
- ```typescript
305
- const toDoSchema = z.object({
306
- shoppingList: z
307
- .object({})
308
- .default({})
309
- .superRefine((value, ctx) => {
310
- const shoppingList = value as Record<string, number>
311
- if (
312
- Object.keys(value).length &&
313
- !Object.keys(value).find((key) => shoppingList[key] > 0)
314
- ) {
315
- ctx.addIssue({
316
- code: z.ZodIssueCode.custom,
317
- message: i18n.global.t('atLeastOneProduct')
318
- })
319
- }
304
+ const schema = z.object({
305
+ firstName: z.string(),
306
+ lastName: z.string(),
307
+ address: z.object({
308
+ street: z.string(),
309
+ number: z.string(),
310
+ city: z.string(),
311
+ zip: z.number()
320
312
  })
321
- })
322
- ```
323
-
324
- And the Vue component:
325
-
326
- ```vue
327
- <script setup lang="ts">
328
- const { VvForm, VvFormField } = useForm(toDoSchema, {
329
- lazyLoad: true,
330
- continuosValidation: true
331
313
  })
332
314
 
333
- // shopping list data, const or async
334
- const shoppingList = {
335
- bread: 0,
336
- milk: 0,
337
- tomato: 0,
338
- potato: 0
339
- }
315
+ const templateSchema = [
316
+ {
317
+ vvName: 'firstName',
318
+ vvType: 'text',
319
+ label: 'First Name'
320
+ },
321
+ {
322
+ vvName: 'lastName',
323
+ vvType: 'text',
324
+ label: 'Last Name'
325
+ },
326
+ {
327
+ vvIs: 'div',
328
+ class: 'grid grid-col-3 gap-4',
329
+ vvChildren: [
330
+ {
331
+ vvName: 'address.street',
332
+ vvType: 'text',
333
+ label: 'Street',
334
+ class: 'col-span-2'
335
+ },
336
+ {
337
+ vvName: 'address.number',
338
+ vvType: 'text',
339
+ label: 'Number'
340
+ },
341
+ {
342
+ vvName: 'address.city',
343
+ vvType: 'text',
344
+ label: 'City'
345
+ class: 'col-span-2',
346
+ },
347
+ {
348
+ vvName: 'address.zip',
349
+ vvType: 'number',
350
+ label: 'Zip'
351
+ }
352
+ ]
353
+ }
354
+ ]
355
+
356
+ const { VvForm, VvFormTemplate } = useForm(schema)
340
357
  </script>
341
358
 
342
359
  <template>
343
360
  <VvForm>
344
- <VvFormField v-slot="{ invalid, invalidLabel }" name="shoppingList">
345
- <VvFormField
346
- v-for="key in Object.keys(shoppingList)"
347
- :key="key"
348
- :name="`shoppingList.${key}`"
349
- :label="$t(key)"
350
- />
351
- <small v-if="invalid" class="input-counter__hint">{{
352
- invalidLabel[0]
353
- }}</small>
354
- </VvFormField>
361
+ <VvFormTemplate :schema="templateSchema" />
355
362
  </VvForm>
356
363
  </template>
357
364
  ```
358
365
 
359
- ## Composable
366
+ Template items, by default, are rendered as a `VvFormField` component but this can be changed using the `vvIs` property. The `vvIs` property can be a string or a component.
360
367
 
361
- `useForm` can be used to create a form programmatically inside a Vue 3 Component.
362
- The default settings are inherited from the plugin (if it was defined).
368
+ `vvName` refers to the name of the field in the schema and can be a nested property using **dot notation**.
369
+ `vvType` refers to the type of the field and can be any of the supported types.
370
+ `vvDefaultValue` can be used to set default values for the form item.
371
+ `vvShowValid` can be used to show the valid state of the form item.
372
+ `vvSlots` can be used to pass a slots to the template item.
373
+ `vvChildren` is an array of template items which will be wrapped in the parent item.
374
+
375
+ Conditional rendering can be achieved using the `vvIf` and `vvElseIf` properties.
363
376
 
364
377
  ```vue
365
378
  <script lang="ts" setup>
@@ -367,54 +380,113 @@ The default settings are inherited from the plugin (if it was defined).
367
380
  import { z } from 'zod'
368
381
 
369
382
  const schema = z.object({
370
- name: z.string(),
371
- surname: z.string()
383
+ firstName: z.string(),
384
+ lastName: z.string(),
385
+ hasUsername: z.boolean(),
386
+ username: z.string().optional()
387
+ email: z.string().email().optional()
388
+ }).superRefine((value, ctx) => {
389
+ if (value.hasUsername && !value.username) {
390
+ ctx.addIssue({
391
+ code: z.ZodIssueCode.custom,
392
+ message: 'Username is required'
393
+ })
394
+ }
395
+ if (!value.hasUsername && !value.email) {
396
+ ctx.addIssue({
397
+ code: z.ZodIssueCode.custom,
398
+ message: 'Email is required'
399
+ })
400
+ }
372
401
  })
373
402
 
374
- const { VvForm, VvFormWrapper, VvFormField } = useForm(schema, {
375
- // lazyLoad: boolean - default false
376
- // updateThrottle: number - default 500
377
- // continuosValidation: true - default false
378
- // sideEffects?: (formData: any) => void
379
- })
403
+ const templateSchema = [
404
+ {
405
+ vvName: 'firstName',
406
+ vvType: 'text',
407
+ label: 'First Name'
408
+ },
409
+ {
410
+ vvName: 'lastName',
411
+ vvType: 'text',
412
+ label: 'Last Name'
413
+ },
414
+ {
415
+ vvName: 'hasUsername',
416
+ vvType: 'checkbox',
417
+ label: 'Has Username'
418
+ value: true,
419
+ uncheckedValue: false
420
+ },
421
+ {
422
+ vvIf: 'hasUsername',
423
+ vvName: 'username',
424
+ vvType: 'text',
425
+ label: 'Username'
426
+ },
427
+ {
428
+ vvElseIf: true,
429
+ vvName: 'email',
430
+ vvType: 'email',
431
+ label: 'Email'
432
+ }
433
+ ]
434
+
435
+ const { VvForm, VvFormTemplate } = useForm(schema)
380
436
  </script>
381
437
 
382
438
  <template>
383
439
  <VvForm>
384
- <VvFormField type="text" name="name" label="Name" />
385
- <VvFormField type="text" name="surname" label="Surname" />
440
+ <VvFormTemplate :schema="templateSchema" />
386
441
  </VvForm>
387
442
  </template>
388
443
  ```
389
444
 
390
- ## Outside a Vue 3 Component
445
+ `vvElseIf` can be used multiple times. `vvElseIf: true` is like an `else` statement and will be rendered if all previous `vvIf` and `vvElseIf` conditions are false.
391
446
 
392
- `formFactory` can be used to create a form outside a Vue 3 Component.
393
- No settings are inherited.
447
+ `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.
394
448
 
395
449
  ```ts
396
- import { formFactory } from '@volverjs/form-vue'
397
- import { z } from 'zod'
450
+ {
451
+ vvIf: (ctx) => ctx.formData.value.hasUsername,
452
+ vvName: 'username',
453
+ vvType: 'text',
454
+ label: 'Username'
455
+ }
456
+ ```
398
457
 
399
- const schema = z.object({
400
- name: z.string(),
401
- surname: z.string()
402
- })
458
+ Also the template schema and all template items can be a function.
459
+ The function will be called with the **form context** as the **first argument**.
403
460
 
404
- const { VvForm, VvFormWrapper, VvFormField } = formFactory(schema, {
405
- // lazyLoad: boolean - default false
406
- // updateThrottle: number - default 500
407
- // continuosValidation: true - default false
408
- // sideEffects?: (data: any) => void
409
- })
461
+ ```ts
462
+ const templateSchema = (ctx) => [
463
+ {
464
+ vvName: 'firstName',
465
+ vvType: 'text',
466
+ label: `Hi ${ctx.formData.value.firstName}!`
467
+ }
468
+ ]
469
+ ```
410
470
 
411
- export default { VvForm, VvFormWrapper, VvFormField }
471
+ ```ts
472
+ const templateSchema = [
473
+ (ctx) => ({
474
+ vvName: 'firstName',
475
+ vvType: 'text',
476
+ label: `Hi ${ctx.formData.value.firstName}!`
477
+ }),
478
+ {
479
+ vvName: 'username',
480
+ type: 'text',
481
+ label: 'username'
482
+ }
483
+ ]
412
484
  ```
413
485
 
414
486
  ## Default Object by Zod Object Schema
415
487
 
416
- `defaultObjectBySchema` creates an object by a Zod Object Schema.
417
- 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.
488
+ `defaultObjectBySchema` creates an object by a [Zod Object Schema](https://zod.dev/?id=objects).
489
+ 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.
418
490
 
419
491
  ```ts
420
492
  import { z } from 'zod'