@maz-ui/mcp 5.0.0-beta.5 → 5.0.0-beta.50
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 +1 -1
- package/dist/mcp.d.mts +22 -9
- package/dist/mcp.d.ts +22 -9
- package/dist/mcp.mjs +160 -83
- package/docs/generated-docs/maz-avatar.doc.md +30 -30
- package/docs/generated-docs/maz-bottom-sheet.doc.md +16 -13
- package/docs/generated-docs/maz-btn.doc.md +21 -19
- package/docs/generated-docs/maz-checkbox.doc.md +16 -17
- package/docs/generated-docs/maz-circular-progress-bar.doc.md +1 -1
- package/docs/generated-docs/maz-code-highlight.doc.md +11 -0
- package/docs/generated-docs/maz-container.doc.md +1 -0
- package/docs/generated-docs/maz-date-picker.doc.md +41 -41
- package/docs/generated-docs/maz-drawer.doc.md +7 -8
- package/docs/generated-docs/maz-dropdown.doc.md +3 -0
- package/docs/generated-docs/maz-expand-animation.doc.md +4 -4
- package/docs/generated-docs/maz-fullscreen-loader.doc.md +5 -5
- package/docs/generated-docs/maz-gallery.doc.md +15 -15
- package/docs/generated-docs/maz-input-code.doc.md +16 -16
- package/docs/generated-docs/maz-input-price.doc.md +14 -14
- package/docs/generated-docs/maz-input-tags.doc.md +16 -16
- package/docs/generated-docs/maz-input.doc.md +33 -33
- package/docs/generated-docs/maz-lazy-img.doc.md +14 -14
- package/docs/generated-docs/maz-loading-bar.doc.md +4 -4
- package/docs/generated-docs/maz-markdown-editor.doc.md +65 -0
- package/docs/generated-docs/maz-popover.doc.md +1 -1
- package/docs/generated-docs/maz-pull-to-refresh.doc.md +39 -20
- package/docs/generated-docs/maz-radio-buttons.doc.md +17 -17
- package/docs/generated-docs/maz-radio.doc.md +16 -16
- package/docs/generated-docs/maz-reading-progress-bar.doc.md +4 -4
- package/docs/generated-docs/maz-sidebar-content.doc.md +5 -0
- package/docs/generated-docs/maz-sidebar-footer.doc.md +5 -0
- package/docs/generated-docs/maz-sidebar-group.doc.md +11 -0
- package/docs/generated-docs/maz-sidebar-header.doc.md +5 -0
- package/docs/generated-docs/maz-sidebar-menu-button.doc.md +27 -0
- package/docs/generated-docs/maz-sidebar-menu-item.doc.md +5 -0
- package/docs/generated-docs/maz-sidebar-menu-sub.doc.md +16 -0
- package/docs/generated-docs/maz-sidebar-menu.doc.md +5 -0
- package/docs/generated-docs/maz-sidebar-separator.doc.md +0 -0
- package/docs/generated-docs/maz-sidebar-trigger.doc.md +5 -0
- package/docs/generated-docs/maz-sidebar.doc.md +36 -0
- package/docs/generated-docs/maz-slider.doc.md +1 -1
- package/docs/generated-docs/maz-spinner.doc.md +4 -4
- package/docs/generated-docs/maz-switch.doc.md +14 -14
- package/docs/generated-docs/maz-table-virtualizer.doc.md +20 -0
- package/docs/generated-docs/maz-table.doc.md +43 -37
- package/docs/generated-docs/maz-tabs-bar.doc.md +20 -9
- package/docs/generated-docs/maz-tabs.doc.md +6 -3
- package/docs/generated-docs/maz-textarea.doc.md +27 -25
- package/docs/generated-docs/maz-ticker.doc.md +1 -1
- package/docs/generated-docs/maz-timeline.doc.md +4 -4
- package/docs/generated-docs/maz-window-mockup.doc.md +23 -0
- package/docs/src/blog/v4.md +1 -1
- package/docs/src/blog/v5.md +5 -7
- package/docs/src/components/maz-avatar.md +52 -4
- package/docs/src/components/maz-bottom-sheet.md +300 -169
- package/docs/src/components/maz-btn.md +1 -1
- package/docs/src/components/maz-checklist.md +10 -10
- package/docs/src/components/maz-code-highlight.md +233 -0
- package/docs/src/components/maz-date-picker.md +55 -1
- package/docs/src/components/maz-dialog.md +46 -0
- package/docs/src/components/maz-dropdown.md +32 -16
- package/docs/src/components/maz-icon.md +16 -5
- package/docs/src/components/maz-lazy-img.md +64 -0
- package/docs/src/components/maz-markdown-editor.md +369 -0
- package/docs/src/components/maz-pull-to-refresh.md +176 -17
- package/docs/src/components/maz-sidebar.md +719 -0
- package/docs/src/components/maz-table.md +156 -1
- package/docs/src/components/maz-tabs.md +110 -3
- package/docs/src/components/maz-textarea.md +58 -0
- package/docs/src/components/maz-timeline.md +60 -0
- package/docs/src/components/maz-window-mockup.md +249 -0
- package/docs/src/composables/use-drag.md +239 -0
- package/docs/src/composables/use-form-validator.md +297 -16
- package/docs/src/composables/use-pull-to-refresh.md +79 -0
- package/docs/src/composables/use-swipe.md +26 -11
- package/docs/src/directives/click-outside.md +8 -15
- package/docs/src/directives/fullscreen-img.md +1 -1
- package/docs/src/directives/lazy-img.md +44 -11
- package/docs/src/directives/tooltip.md +24 -1
- package/docs/src/directives/zoom-img.md +1 -1
- package/docs/src/ecosystem/eslint-config.md +95 -1
- package/docs/src/{guide/icons.md → ecosystem/icons/index.md} +1 -1
- package/docs/src/ecosystem/node/exec-promise.md +87 -0
- package/docs/src/ecosystem/node/index.md +53 -0
- package/docs/src/ecosystem/node/logger.md +146 -0
- package/docs/src/ecosystem/node/print-banner.md +93 -0
- package/docs/src/{guide → ecosystem}/nuxt.md +82 -47
- package/docs/src/{guide → ecosystem}/themes.md +152 -71
- package/docs/src/{guide → ecosystem}/translations.md +1 -1
- package/docs/src/ecosystem/utils/camel-case.md +31 -0
- package/docs/src/{helpers → ecosystem/utils}/capitalize.md +2 -3
- package/docs/src/ecosystem/utils/check-availability.md +79 -0
- package/docs/src/ecosystem/utils/cookie.md +80 -0
- package/docs/src/ecosystem/utils/debounce-callback.md +38 -0
- package/docs/src/ecosystem/utils/debounce-id.md +69 -0
- package/docs/src/ecosystem/utils/debounce.md +65 -0
- package/docs/src/ecosystem/utils/fetch-locale-ip.md +33 -0
- package/docs/src/ecosystem/utils/format-json.md +33 -0
- package/docs/src/ecosystem/utils/format-phone-number.md +37 -0
- package/docs/src/ecosystem/utils/get-browser-locale.md +29 -0
- package/docs/src/ecosystem/utils/get-error-message.md +39 -0
- package/docs/src/ecosystem/utils/idle-timeout.md +90 -0
- package/docs/src/ecosystem/utils/index.md +60 -0
- package/docs/src/ecosystem/utils/is-client.md +32 -0
- package/docs/src/ecosystem/utils/is-equal.md +38 -0
- package/docs/src/ecosystem/utils/is-server.md +31 -0
- package/docs/src/ecosystem/utils/is-standalone-mode.md +43 -0
- package/docs/src/ecosystem/utils/kebab-case.md +36 -0
- package/docs/src/ecosystem/utils/normalize-string.md +77 -0
- package/docs/src/ecosystem/utils/pascal-case.md +35 -0
- package/docs/src/ecosystem/utils/script-loader.md +77 -0
- package/docs/src/ecosystem/utils/sleep.md +59 -0
- package/docs/src/ecosystem/utils/snake-case.md +36 -0
- package/docs/src/ecosystem/utils/swipe-handler.md +91 -0
- package/docs/src/ecosystem/utils/textarea-autogrow.md +41 -0
- package/docs/src/ecosystem/utils/throttle-id.md +48 -0
- package/docs/src/ecosystem/utils/throttle.md +57 -0
- package/docs/src/ecosystem/utils/truthy-filter.md +31 -0
- package/docs/src/ecosystem/utils/types/deep-key-of.md +48 -0
- package/docs/src/ecosystem/utils/types/deep-partial.md +42 -0
- package/docs/src/ecosystem/utils/types/deep-required.md +39 -0
- package/docs/src/ecosystem/utils/types/flatten-object-keys.md +44 -0
- package/docs/src/ecosystem/utils/types/generic-instance-type.md +42 -0
- package/docs/src/ecosystem/utils/types/infer-maybe-ref.md +35 -0
- package/docs/src/ecosystem/utils/upper-first.md +32 -0
- package/docs/src/ecosystem/utils/user-visibility.md +69 -0
- package/docs/src/guide/getting-started.md +15 -13
- package/docs/src/guide/global-defaults.md +101 -0
- package/docs/src/guide/maz-ui-provider.md +6 -3
- package/docs/src/guide/migration-v4.md +13 -9
- package/docs/src/guide/migration-v5.md +211 -15
- package/docs/src/guide/resolvers.md +8 -8
- package/docs/src/guide/tailwind.md +4 -0
- package/docs/src/guide/vue.md +4 -4
- package/docs/src/index.md +13 -13
- package/docs/src/plugins/aos.md +1 -1
- package/package.json +11 -10
- package/docs/src/helpers/camel-case.md +0 -14
- package/docs/src/helpers/check-availability.md +0 -14
- package/docs/src/helpers/debounce-callback.md +0 -14
- package/docs/src/helpers/debounce-id.md +0 -14
- package/docs/src/helpers/debounce.md +0 -14
- package/docs/src/helpers/is-client.md +0 -14
- package/docs/src/helpers/is-equal.md +0 -14
- package/docs/src/helpers/is-standalone-mode.md +0 -14
- package/docs/src/helpers/kebab-case.md +0 -14
- package/docs/src/helpers/normalize-string.md +0 -14
- package/docs/src/helpers/pascal-case.md +0 -14
- package/docs/src/helpers/script-loader.md +0 -14
- package/docs/src/helpers/sleep.md +0 -14
- package/docs/src/helpers/snake-case.md +0 -14
- package/docs/src/helpers/throttle-id.md +0 -14
- package/docs/src/helpers/throttle.md +0 -14
- /package/docs/src/{guide → ecosystem/icons}/icon-set.md +0 -0
- /package/docs/src/{guide → ecosystem}/mcp.md +0 -0
- /package/docs/src/{helpers → ecosystem/utils}/country-code-to-unicode-flag.md +0 -0
- /package/docs/src/{helpers → ecosystem/utils}/currency.md +0 -0
- /package/docs/src/{helpers → ecosystem/utils}/date.md +0 -0
- /package/docs/src/{helpers → ecosystem/utils}/get-country-flag-url.md +0 -0
- /package/docs/src/{helpers → ecosystem/utils}/number.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: useFormValidator
|
|
3
|
-
description: Vue composables for form validation with Valibot - useFormValidator and useFormField provide a flexible and typed approach to handle form validation in your Vue applications.
|
|
3
|
+
description: Vue composables for form validation with any Standard Schema library (Valibot, Zod, ArkType...) - useFormValidator and useFormField provide a flexible and typed approach to handle form validation in your Vue applications.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# {{ $frontmatter.title }}
|
|
@@ -9,7 +9,7 @@ description: Vue composables for form validation with Valibot - useFormValidator
|
|
|
9
9
|
|
|
10
10
|
## Introduction
|
|
11
11
|
|
|
12
|
-
`useFormValidator` and `useFormField` are two Vue composables that work together to provide powerful form validation
|
|
12
|
+
`useFormValidator` and `useFormField` are two Vue composables that work together to provide powerful form validation. They are **validation library agnostic**: any library implementing [Standard Schema](https://standardschema.dev) works out of the box, like [Valibot](https://valibot.dev), [Zod](https://zod.dev) or [ArkType](https://arktype.io). See [Validation Libraries](#validation-libraries).
|
|
13
13
|
|
|
14
14
|
- **useFormValidator**: Initializes form validation for your entire form. Use it in your form's parent component.
|
|
15
15
|
- **useFormField**: Manages individual field validation states. Use it when you need fine-grained control over a field or when fields are in child components.
|
|
@@ -23,6 +23,24 @@ description: Vue composables for form validation with Valibot - useFormValidator
|
|
|
23
23
|
|
|
24
24
|
## Quick Start
|
|
25
25
|
|
|
26
|
+
Install the validation library of your choice (Valibot is used in most examples of this page):
|
|
27
|
+
|
|
28
|
+
::: code-group
|
|
29
|
+
|
|
30
|
+
```bash [valibot]
|
|
31
|
+
pnpm add valibot
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```bash [zod]
|
|
35
|
+
pnpm add zod
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```bash [arktype]
|
|
39
|
+
pnpm add arktype
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
:::
|
|
43
|
+
|
|
26
44
|
Here's the simplest form you can create with `useFormValidator`:
|
|
27
45
|
|
|
28
46
|
<ComponentDemo>
|
|
@@ -104,6 +122,186 @@ const onSubmit = handleSubmit((data) => {
|
|
|
104
122
|
</template>
|
|
105
123
|
</ComponentDemo>
|
|
106
124
|
|
|
125
|
+
## Validation Libraries
|
|
126
|
+
|
|
127
|
+
`useFormValidator` relies on [Standard Schema](https://standardschema.dev), a common interface shared by the main validation libraries. Maz-UI does not ship any validation library: install the one you prefer and pass its schemas. Valibot, Zod and ArkType are declared as optional peer dependencies of `maz-ui`, so your package manager warns you if the installed version is not supported.
|
|
128
|
+
|
|
129
|
+
| Library | Supported versions |
|
|
130
|
+
|---------|--------------------|
|
|
131
|
+
| [Valibot](https://valibot.dev) | `>=1.0.0 <2.0.0` |
|
|
132
|
+
| [Zod](https://zod.dev) | `>=3.24.0 <5.0.0` (v3 and v4) |
|
|
133
|
+
| [ArkType](https://arktype.io) | `>=2.0.0 <3.0.0` |
|
|
134
|
+
| [Others](https://standardschema.dev/#what-schema-libraries-implement-the-spec) | Any library implementing Standard Schema V1 |
|
|
135
|
+
|
|
136
|
+
The `schema` option is always a **plain object** where each key is a field name and each value is a schema of your library. Do not wrap it with `object()` / `z.object()`.
|
|
137
|
+
|
|
138
|
+
### Same form, different libraries
|
|
139
|
+
|
|
140
|
+
::: code-group
|
|
141
|
+
|
|
142
|
+
```ts [valibot]
|
|
143
|
+
import { useFormValidator } from 'maz-ui/composables'
|
|
144
|
+
import { email, minLength, nonEmpty, pipe, string } from 'valibot'
|
|
145
|
+
|
|
146
|
+
const { model, errorMessages, handleSubmit } = useFormValidator({
|
|
147
|
+
schema: {
|
|
148
|
+
email: pipe(string(), nonEmpty('Email is required'), email('Invalid email')),
|
|
149
|
+
password: pipe(string(), minLength(8, 'Min 8 characters')),
|
|
150
|
+
},
|
|
151
|
+
})
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```ts [zod]
|
|
155
|
+
import { useFormValidator } from 'maz-ui/composables'
|
|
156
|
+
import { z } from 'zod'
|
|
157
|
+
|
|
158
|
+
const { model, errorMessages, handleSubmit } = useFormValidator({
|
|
159
|
+
schema: {
|
|
160
|
+
email: z.string().min(1, 'Email is required').pipe(z.email('Invalid email')),
|
|
161
|
+
password: z.string().min(8, 'Min 8 characters'),
|
|
162
|
+
},
|
|
163
|
+
})
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```ts [arktype]
|
|
167
|
+
import { type } from 'arktype'
|
|
168
|
+
import { useFormValidator } from 'maz-ui/composables'
|
|
169
|
+
|
|
170
|
+
const { model, errorMessages, handleSubmit } = useFormValidator({
|
|
171
|
+
schema: {
|
|
172
|
+
email: type('string.email'),
|
|
173
|
+
password: type('string >= 8'),
|
|
174
|
+
},
|
|
175
|
+
})
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
:::
|
|
179
|
+
|
|
180
|
+
### Live example with Zod
|
|
181
|
+
|
|
182
|
+
<ComponentDemo>
|
|
183
|
+
<form class="maz:flex maz:flex-col maz:gap-4" @submit="onSubmitZod">
|
|
184
|
+
<MazInput
|
|
185
|
+
v-model="zodModel.username"
|
|
186
|
+
label="Username"
|
|
187
|
+
:hint="zodErrors.username"
|
|
188
|
+
:error="!!zodErrors.username"
|
|
189
|
+
:success="zodStates.username.valid"
|
|
190
|
+
/>
|
|
191
|
+
<MazInput
|
|
192
|
+
v-model="zodModel.age"
|
|
193
|
+
label="Age"
|
|
194
|
+
type="number"
|
|
195
|
+
:hint="zodErrors.age"
|
|
196
|
+
:error="!!zodErrors.age"
|
|
197
|
+
:success="zodStates.age.valid"
|
|
198
|
+
/>
|
|
199
|
+
<MazBtn type="submit" :loading="zodSubmitting">
|
|
200
|
+
Submit
|
|
201
|
+
</MazBtn>
|
|
202
|
+
</form>
|
|
203
|
+
|
|
204
|
+
<template #code>
|
|
205
|
+
|
|
206
|
+
```vue
|
|
207
|
+
<script lang="ts" setup>
|
|
208
|
+
import { useFormValidator } from 'maz-ui/composables'
|
|
209
|
+
import { z } from 'zod'
|
|
210
|
+
|
|
211
|
+
const schema = {
|
|
212
|
+
username: z.string().min(1, 'Username is required').min(3, 'Min 3 characters'),
|
|
213
|
+
age: z.number({ error: 'Age is required' }).min(18, 'Min 18').max(100, 'Max 100'),
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const {
|
|
217
|
+
model,
|
|
218
|
+
errorMessages,
|
|
219
|
+
fieldsStates,
|
|
220
|
+
isSubmitting,
|
|
221
|
+
handleSubmit,
|
|
222
|
+
} = useFormValidator({ schema })
|
|
223
|
+
|
|
224
|
+
const onSubmit = handleSubmit((data) => {
|
|
225
|
+
// data is typed as { username: string, age: number }
|
|
226
|
+
console.log('Form submitted:', data)
|
|
227
|
+
})
|
|
228
|
+
</script>
|
|
229
|
+
|
|
230
|
+
<template>
|
|
231
|
+
<form @submit="onSubmit">
|
|
232
|
+
<MazInput
|
|
233
|
+
v-model="model.username"
|
|
234
|
+
label="Username"
|
|
235
|
+
:hint="errorMessages.username"
|
|
236
|
+
:error="!!errorMessages.username"
|
|
237
|
+
:success="fieldsStates.username.valid"
|
|
238
|
+
/>
|
|
239
|
+
<MazInput
|
|
240
|
+
v-model="model.age"
|
|
241
|
+
label="Age"
|
|
242
|
+
type="number"
|
|
243
|
+
:hint="errorMessages.age"
|
|
244
|
+
:error="!!errorMessages.age"
|
|
245
|
+
:success="fieldsStates.age.valid"
|
|
246
|
+
/>
|
|
247
|
+
<MazBtn type="submit" :loading="isSubmitting">
|
|
248
|
+
Submit
|
|
249
|
+
</MazBtn>
|
|
250
|
+
</form>
|
|
251
|
+
</template>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
</template>
|
|
255
|
+
</ComponentDemo>
|
|
256
|
+
|
|
257
|
+
### Mixing libraries
|
|
258
|
+
|
|
259
|
+
Each field is validated by its own schema, so you can mix libraries in the same form (useful during a progressive migration from one library to another):
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
import { useFormValidator } from 'maz-ui/composables'
|
|
263
|
+
import { email, pipe, string } from 'valibot'
|
|
264
|
+
import { z } from 'zod'
|
|
265
|
+
|
|
266
|
+
const { model } = useFormValidator({
|
|
267
|
+
schema: {
|
|
268
|
+
email: pipe(string(), email('Invalid email')),
|
|
269
|
+
age: z.number().min(18, 'Min 18'),
|
|
270
|
+
},
|
|
271
|
+
})
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Custom schema
|
|
275
|
+
|
|
276
|
+
You can also write your own validator by implementing the `StandardSchemaV1` interface (exported by `maz-ui/composables`). `validate` can be synchronous or return a `Promise`:
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
import type { StandardSchemaV1 } from 'maz-ui/composables'
|
|
280
|
+
import { useFormValidator } from 'maz-ui/composables'
|
|
281
|
+
|
|
282
|
+
const frenchZipCode: StandardSchemaV1<string, string> = {
|
|
283
|
+
'~standard': {
|
|
284
|
+
version: 1,
|
|
285
|
+
vendor: 'my-app',
|
|
286
|
+
validate: value =>
|
|
287
|
+
typeof value === 'string' && /^\d{5}$/.test(value)
|
|
288
|
+
? { value }
|
|
289
|
+
: { issues: [{ message: 'Invalid zip code' }] },
|
|
290
|
+
},
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const { model } = useFormValidator({
|
|
294
|
+
schema: { zipCode: frenchZipCode },
|
|
295
|
+
})
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Good to know
|
|
299
|
+
|
|
300
|
+
- **Empty values**: `undefined` and `null` field values are validated as an empty string (`''`). To make a field required, use a length rule (`nonEmpty()` with Valibot, `.min(1)` with Zod). An optional field with a format rule (email, URL...) has to accept the empty string explicitly, for example `z.email().or(z.literal(''))` with Zod or `union([literal(''), pipe(string(), email())])` with Valibot.
|
|
301
|
+
- **Error messages**: `errorMessages` contains the message of the first issue of each field, whatever the library.
|
|
302
|
+
- **Issues type**: `errors` and `fieldsStates[field].errors` are typed as `ValidationIssues`, the Standard Schema issue shape (`message` and `path`). At runtime, they are the native issues of your library, so you can cast them to access library specific properties (for example `BaseIssue<unknown>` from Valibot or `z.core.$ZodIssue` from Zod).
|
|
303
|
+
- **Type inference**: the `model` type (input) and the `handleSubmit` payload type (output, after transformations) are inferred from the schemas whatever the library.
|
|
304
|
+
|
|
107
305
|
## Understanding Form State
|
|
108
306
|
|
|
109
307
|
`useFormValidator` returns several reactive values to help you manage your form:
|
|
@@ -151,7 +349,7 @@ Each field in `fieldsStates` contains:
|
|
|
151
349
|
v-model="stateModel.age"
|
|
152
350
|
label="Age (18-100)"
|
|
153
351
|
type="number"
|
|
154
|
-
:hint="stateErrors.age"
|
|
352
|
+
:hint="stateErrors.age"
|
|
155
353
|
:error="!!stateErrors.age"
|
|
156
354
|
:success="stateFields.age.valid"
|
|
157
355
|
/>
|
|
@@ -365,7 +563,7 @@ Requires `useFormField` with `ref` option or `validationEvents`.
|
|
|
365
563
|
:success="eagerEmailValid"
|
|
366
564
|
:class="{ 'has-error-eager': eagerEmailHasError }"
|
|
367
565
|
/>
|
|
368
|
-
<MazBtn type="submit" :loading="eagerSubmitting">Submit</MazBtn>
|
|
566
|
+
<MazBtn type="submit" :loading="eagerSubmitting" :disabled="!eagerIsValid">Submit</MazBtn>
|
|
369
567
|
</form>
|
|
370
568
|
|
|
371
569
|
<template #code>
|
|
@@ -381,7 +579,7 @@ const schema = {
|
|
|
381
579
|
email: pipe(string(), nonEmpty('Required'), email('Invalid email')),
|
|
382
580
|
}
|
|
383
581
|
|
|
384
|
-
const { isSubmitting, handleSubmit } = useFormValidator({
|
|
582
|
+
const { isSubmitting, handleSubmit, isValid } = useFormValidator({
|
|
385
583
|
schema,
|
|
386
584
|
options: {
|
|
387
585
|
mode: 'eager',
|
|
@@ -430,6 +628,8 @@ const {
|
|
|
430
628
|
:error="emailHasError"
|
|
431
629
|
:success="emailValid"
|
|
432
630
|
/>
|
|
631
|
+
|
|
632
|
+
<MazBtn type="submit" :loading="isSubmitting" :disabled="!isValid">Submit</MazBtn>
|
|
433
633
|
</form>
|
|
434
634
|
</template>
|
|
435
635
|
```
|
|
@@ -614,6 +814,8 @@ const { value: name, hasError, errorMessage, isValid } = useFormField<string>('n
|
|
|
614
814
|
|
|
615
815
|
Pass a template ref to `useFormField`. It will automatically detect interactive elements and attach blur listeners.
|
|
616
816
|
|
|
817
|
+
The `ref` option is **reactive**: if the field is rendered conditionally (e.g. with `v-if`), the blur listeners are attached automatically as soon as the element appears in the DOM, and removed when it is unmounted. You don't need to handle anything special.
|
|
818
|
+
|
|
617
819
|
```vue
|
|
618
820
|
<script setup>
|
|
619
821
|
import { useFormField } from 'maz-ui/composables'
|
|
@@ -635,6 +837,41 @@ const { value, errorMessage, hasError } = useFormField<string>('email', {
|
|
|
635
837
|
</template>
|
|
636
838
|
```
|
|
637
839
|
|
|
840
|
+
Because it is reactive, the same code works even when the input is wrapped in a `v-if`:
|
|
841
|
+
|
|
842
|
+
```vue
|
|
843
|
+
<script setup>
|
|
844
|
+
import { useFormField } from 'maz-ui/composables'
|
|
845
|
+
import { useTemplateRef, ref } from 'vue'
|
|
846
|
+
|
|
847
|
+
const isVisible = ref(false)
|
|
848
|
+
|
|
849
|
+
const { value, errorMessage, hasError } = useFormField<string>('email', {
|
|
850
|
+
ref: useTemplateRef('emailRef'),
|
|
851
|
+
formIdentifier: 'my-form',
|
|
852
|
+
})
|
|
853
|
+
</script>
|
|
854
|
+
|
|
855
|
+
<template>
|
|
856
|
+
<MazInput
|
|
857
|
+
v-if="isVisible"
|
|
858
|
+
ref="emailRef"
|
|
859
|
+
v-model="value"
|
|
860
|
+
:hint="errorMessage"
|
|
861
|
+
:error="hasError"
|
|
862
|
+
/>
|
|
863
|
+
</template>
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
You can also pass a raw `HTMLElement` directly. In that case it won't be reactive, so the element must already exist in the DOM:
|
|
867
|
+
|
|
868
|
+
```ts
|
|
869
|
+
const { value } = useFormField<string>('email', {
|
|
870
|
+
ref: document.querySelector('input'),
|
|
871
|
+
formIdentifier: 'my-form',
|
|
872
|
+
})
|
|
873
|
+
```
|
|
874
|
+
|
|
638
875
|
#### Option 2: Using `validationEvents`
|
|
639
876
|
|
|
640
877
|
If your component emits a `blur` event, you can use `v-bind` with `validationEvents`.
|
|
@@ -660,7 +897,7 @@ const { value, errorMessage, hasError, validationEvents } = useFormField<string>
|
|
|
660
897
|
|
|
661
898
|
## TypeScript Type Inference
|
|
662
899
|
|
|
663
|
-
The form model is automatically typed based on your schema:
|
|
900
|
+
The form model is automatically typed based on your schema, whatever the validation library:
|
|
664
901
|
|
|
665
902
|
```ts
|
|
666
903
|
const schema = {
|
|
@@ -706,7 +943,23 @@ const { value: email } = useFormField<string>('email', {
|
|
|
706
943
|
|
|
707
944
|
## Async Validation
|
|
708
945
|
|
|
709
|
-
|
|
946
|
+
Async validations (like checking username availability) are supported by every library: Valibot's `pipeAsync` and `checkAsync`, Zod's async `refine`, etc. The example below uses Valibot, here is the Zod equivalent of the schema:
|
|
947
|
+
|
|
948
|
+
```ts
|
|
949
|
+
import { z } from 'zod'
|
|
950
|
+
|
|
951
|
+
const schema = {
|
|
952
|
+
username: z
|
|
953
|
+
.string()
|
|
954
|
+
.min(1, 'Username is required')
|
|
955
|
+
.min(3, 'Min 3 characters')
|
|
956
|
+
.refine(async (value) => {
|
|
957
|
+
// Simulate API call
|
|
958
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
959
|
+
return value !== 'taken'
|
|
960
|
+
}, 'Username is already taken'),
|
|
961
|
+
}
|
|
962
|
+
```
|
|
710
963
|
|
|
711
964
|
<ComponentDemo>
|
|
712
965
|
<div class="maz:mb-4">
|
|
@@ -1026,7 +1279,7 @@ If your custom component isn't detected for blur events, add `data-interactive`:
|
|
|
1026
1279
|
|
|
1027
1280
|
```ts
|
|
1028
1281
|
useFormValidator<TSchema>({
|
|
1029
|
-
schema: TSchema, // Valibot
|
|
1282
|
+
schema: TSchema, // Object of Standard Schema field schemas: Valibot, Zod, ArkType... (required)
|
|
1030
1283
|
model?: Ref<Model>, // External model ref (optional)
|
|
1031
1284
|
defaultValues?: DeepPartial<Model>, // Initial values (optional)
|
|
1032
1285
|
options?: {
|
|
@@ -1074,7 +1327,7 @@ useFormField<FieldType>(
|
|
|
1074
1327
|
options?: {
|
|
1075
1328
|
defaultValue?: FieldType, // Default value for this field
|
|
1076
1329
|
mode?: 'lazy' | 'aggressive' | 'eager' | 'blur' | 'progressive', // Override form mode
|
|
1077
|
-
ref?: Ref<HTMLElement | ComponentInstance
|
|
1330
|
+
ref?: Ref<HTMLElement | ComponentInstance> | HTMLElement, // Reactive template ref (v-if friendly) or raw element
|
|
1078
1331
|
formIdentifier?: string | symbol, // Must match useFormValidator's identifier
|
|
1079
1332
|
}
|
|
1080
1333
|
)
|
|
@@ -1101,6 +1354,14 @@ useFormField<FieldType>(
|
|
|
1101
1354
|
### Types
|
|
1102
1355
|
|
|
1103
1356
|
```ts
|
|
1357
|
+
// Standard Schema issue (https://standardschema.dev)
|
|
1358
|
+
interface ValidationIssue {
|
|
1359
|
+
readonly message: string
|
|
1360
|
+
readonly path?: ReadonlyArray<PropertyKey | { readonly key: PropertyKey }>
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
type ValidationIssues = ValidationIssue[]
|
|
1364
|
+
|
|
1104
1365
|
interface FormValidatorOptions<Model> {
|
|
1105
1366
|
mode?: 'eager' | 'lazy' | 'aggressive' | 'blur' | 'progressive'
|
|
1106
1367
|
throttledFields?: Partial<Record<keyof Model, number | true>>
|
|
@@ -1113,7 +1374,7 @@ interface FormValidatorOptions<Model> {
|
|
|
1113
1374
|
interface FormFieldOptions<FieldType> {
|
|
1114
1375
|
defaultValue?: FieldType
|
|
1115
1376
|
mode?: 'eager' | 'lazy' | 'aggressive' | 'blur' | 'progressive'
|
|
1116
|
-
ref?: Ref<HTMLElement | ComponentInstance>
|
|
1377
|
+
ref?: Ref<HTMLElement | ComponentInstance> | HTMLElement
|
|
1117
1378
|
formIdentifier?: string | symbol
|
|
1118
1379
|
}
|
|
1119
1380
|
|
|
@@ -1166,14 +1427,13 @@ const { value, validationEvents } = useFormField<string>('name')
|
|
|
1166
1427
|
// Then: v-bind="validationEvents" on your input
|
|
1167
1428
|
```
|
|
1168
1429
|
|
|
1169
|
-
###
|
|
1430
|
+
### Conditionally Rendered Fields (`v-if`)
|
|
1170
1431
|
|
|
1171
|
-
|
|
1432
|
+
The `ref` option is reactive, so a field wrapped in `v-if` works out of the box: blur listeners are attached as soon as the element is rendered and removed when it is unmounted.
|
|
1172
1433
|
|
|
1173
|
-
|
|
1174
|
-
1. Ensure the ref is bound to an HTML element or Vue component
|
|
1175
|
-
2.
|
|
1176
|
-
3. For custom components, add `data-interactive` attribute
|
|
1434
|
+
If blur validation still doesn't trigger:
|
|
1435
|
+
1. Ensure the `ref` is bound to an HTML element or a Vue component exposing `$el`
|
|
1436
|
+
2. For custom components, add the `data-interactive` attribute
|
|
1177
1437
|
|
|
1178
1438
|
### Mismatched Form Identifiers
|
|
1179
1439
|
|
|
@@ -1214,9 +1474,29 @@ import {
|
|
|
1214
1474
|
pipeAsync,
|
|
1215
1475
|
checkAsync,
|
|
1216
1476
|
} from 'valibot'
|
|
1477
|
+
import { z } from 'zod'
|
|
1217
1478
|
|
|
1218
1479
|
const toast = useToast()
|
|
1219
1480
|
|
|
1481
|
+
// Zod Demo
|
|
1482
|
+
const zodSchema = {
|
|
1483
|
+
username: z.string().min(1, 'Username is required').min(3, 'Min 3 characters'),
|
|
1484
|
+
age: z.number({ error: 'Age is required' }).min(18, 'Min 18').max(100, 'Max 100'),
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
const {
|
|
1488
|
+
model: zodModel,
|
|
1489
|
+
errorMessages: zodErrors,
|
|
1490
|
+
fieldsStates: zodStates,
|
|
1491
|
+
isSubmitting: zodSubmitting,
|
|
1492
|
+
handleSubmit: handleZod,
|
|
1493
|
+
} = useFormValidator({ schema: zodSchema })
|
|
1494
|
+
|
|
1495
|
+
const onSubmitZod = handleZod(async (data) => {
|
|
1496
|
+
await sleep(1000)
|
|
1497
|
+
toast.success(`Welcome ${data.username} (${data.age})!`, { position: 'top' })
|
|
1498
|
+
})
|
|
1499
|
+
|
|
1220
1500
|
// Quick Start Demo
|
|
1221
1501
|
const quickStartSchema = {
|
|
1222
1502
|
email: pipe(string(), nonEmpty('Email is required'), email('Invalid email')),
|
|
@@ -1310,6 +1590,7 @@ const eagerSchema = {
|
|
|
1310
1590
|
const {
|
|
1311
1591
|
isSubmitting: eagerSubmitting,
|
|
1312
1592
|
handleSubmit: handleEager,
|
|
1593
|
+
isValid: eagerIsValid,
|
|
1313
1594
|
} = useFormValidator({
|
|
1314
1595
|
schema: eagerSchema,
|
|
1315
1596
|
options: { mode: 'eager', scrollToError: '.has-error-eager', identifier: 'form-eager' },
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: usePullToRefresh
|
|
3
|
+
description: usePullToRefresh is a headless Vue composable for the pull to refresh gesture on the window or any scroll container.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# {{ $frontmatter.title }}
|
|
7
|
+
|
|
8
|
+
{{ $frontmatter.description }}
|
|
9
|
+
|
|
10
|
+
## Introduction
|
|
11
|
+
|
|
12
|
+
`usePullToRefresh` tracks the pull to refresh gesture and gives you its state (distance, progress, ready, refreshing) to build your own indicator. It powers [MazPullToRefresh](./../components/maz-pull-to-refresh.md): use the component for a ready-made indicator, the composable when you need full control over the rendering.
|
|
13
|
+
|
|
14
|
+
The pull only starts when the target is scrolled to the top, follows a rubber band beyond the threshold, ignores horizontal and multi-touch gestures, and prevents the native bounce while pulling.
|
|
15
|
+
|
|
16
|
+
## Basic usage
|
|
17
|
+
|
|
18
|
+
```vue
|
|
19
|
+
<script lang="ts" setup>
|
|
20
|
+
import { usePullToRefresh } from 'maz-ui/composables'
|
|
21
|
+
import { useTemplateRef } from 'vue'
|
|
22
|
+
|
|
23
|
+
const scroller = useTemplateRef<HTMLElement>('scroller')
|
|
24
|
+
|
|
25
|
+
const { distance, progress, isReady, isRefreshing } = usePullToRefresh({
|
|
26
|
+
target: scroller,
|
|
27
|
+
onRefresh: () => fetchData(),
|
|
28
|
+
})
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<template>
|
|
32
|
+
<div ref="scroller" class="maz:h-screen maz:overflow-y-auto">
|
|
33
|
+
<div class="maz:flex maz:items-end maz:justify-center maz:overflow-hidden" :style="{ height: `${distance}px` }">
|
|
34
|
+
<span v-if="isRefreshing">Refreshing…</span>
|
|
35
|
+
<span v-else-if="isReady">Release to refresh</span>
|
|
36
|
+
<span v-else :style="{ opacity: progress }">Pull to refresh</span>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<p>Content</p>
|
|
40
|
+
</div>
|
|
41
|
+
</template>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Without `target`, the gesture is tracked on the window.
|
|
45
|
+
|
|
46
|
+
## Options
|
|
47
|
+
|
|
48
|
+
| Option | Type | Default | Description |
|
|
49
|
+
| ------------- | -------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------ |
|
|
50
|
+
| `onRefresh` | `() => unknown` | - | Called on release past the threshold, or by `refresh()` |
|
|
51
|
+
| `target` | `MaybeRefOrGetter<HTMLElement \| Window \| null>` | `window` | Scroll container receiving the gesture |
|
|
52
|
+
| `threshold` | `MaybeRefOrGetter<number>` | `80` | Pull distance (px, after resistance) needed to trigger a refresh |
|
|
53
|
+
| `maxDistance` | `MaybeRefOrGetter<number>` | `threshold * 1.6` | Upper bound of the rubber band |
|
|
54
|
+
| `resistance` | `MaybeRefOrGetter<number>` | `0.5` | Ratio between the finger movement and the pull distance |
|
|
55
|
+
| `minDuration` | `MaybeRefOrGetter<number>` | `400` | Minimum duration (ms) of the refreshing state |
|
|
56
|
+
| `haptic` | `MaybeRefOrGetter<boolean>` | `true` | Vibrate briefly when the threshold is reached |
|
|
57
|
+
| `disabled` | `MaybeRefOrGetter<boolean>` | `false` | Disable the gesture |
|
|
58
|
+
| `onError` | `(error: unknown) => void` | - | Called when `onRefresh` throws or rejects (the error is also exposed in `error`) |
|
|
59
|
+
|
|
60
|
+
## Return
|
|
61
|
+
|
|
62
|
+
| Property | Type | Description |
|
|
63
|
+
| -------------- | --------------------------- | ------------------------------------------------------------ |
|
|
64
|
+
| `distance` | `Readonly<Ref<number>>` | Current pull distance in px |
|
|
65
|
+
| `progress` | `ComputedRef<number>` | Progress towards the threshold, from 0 to 1 |
|
|
66
|
+
| `isPulling` | `Readonly<Ref<boolean>>` | The user is pulling |
|
|
67
|
+
| `isReady` | `ComputedRef<boolean>` | Releasing now triggers a refresh |
|
|
68
|
+
| `isRefreshing` | `Readonly<Ref<boolean>>` | `onRefresh` is running |
|
|
69
|
+
| `error` | `Readonly<Ref<unknown>>` | Error of the last refresh, reset on each refresh |
|
|
70
|
+
| `refresh` | `() => Promise<void>` | Run the refresh programmatically (for a keyboard-usable button) |
|
|
71
|
+
| `start` | `() => void` | Attach the listeners (done automatically on mount) |
|
|
72
|
+
| `stop` | `() => void` | Detach the listeners (done automatically on unmount) |
|
|
73
|
+
|
|
74
|
+
## Notes
|
|
75
|
+
|
|
76
|
+
- Inside a component, listeners are attached on mount and removed when the scope is disposed. Outside of a component, they are attached right away.
|
|
77
|
+
- When `target` changes (for example a ref resolved later), the listeners move to the new element.
|
|
78
|
+
- `refresh()` never throws: errors go to `error` and `onError`. Calling it while a refresh is running does nothing.
|
|
79
|
+
- SSR safe: nothing touches the DOM before mount.
|
|
@@ -12,11 +12,19 @@ description: useSwipe is a Vue composable that simplifies the management of "swi
|
|
|
12
12
|
`useSwipe` allows you to detect and react to swiping movements on an HTML element. It provides you with various information about the swipe movement, such as the direction, distance, start, and end coordinates.
|
|
13
13
|
You can use this information to implement specific interactions in your application, such as scrolling a carousel, opening a side menu, etc.
|
|
14
14
|
|
|
15
|
+
It is built on top of [Pointer Events](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events), so it works with **touch, mouse and pen** out of the box.
|
|
16
|
+
|
|
17
|
+
::: tip
|
|
18
|
+
For a real-time drag gesture (follow the pointer, snap back, drag-to-dismiss…) rather than discrete directional detection, use [useDrag](./use-drag.md).
|
|
19
|
+
:::
|
|
20
|
+
|
|
15
21
|
## Key Features
|
|
16
22
|
|
|
17
23
|
- Detects swipes in all 4 directions (left, right, up, down)
|
|
24
|
+
- Works with touch, mouse and pen (Pointer Events)
|
|
18
25
|
- Provides key information about the swipe movement (start/end coordinates, horizontal/vertical distance)
|
|
19
26
|
- Allows you to configure callbacks for each swipe direction
|
|
27
|
+
- Restrict the gesture to specific pointer types
|
|
20
28
|
- Possibility to customize the swipe detection threshold
|
|
21
29
|
- Automatically handles the addition and removal of event listeners
|
|
22
30
|
- Can be used with any HTML element
|
|
@@ -27,7 +35,7 @@ You can use this information to implement specific interactions in your applicat
|
|
|
27
35
|
<p>
|
|
28
36
|
Swipe in any direction<br>
|
|
29
37
|
<span class="maz:text-xs maz:text-muted">
|
|
30
|
-
(
|
|
38
|
+
(works with touch, mouse and pen)
|
|
31
39
|
</span>
|
|
32
40
|
<br><br>
|
|
33
41
|
Last swipe direction: {{lastSwipeDirection || 'None'}}
|
|
@@ -74,7 +82,7 @@ onUnmounted(() => {
|
|
|
74
82
|
<p>
|
|
75
83
|
Swipe in any direction<br>
|
|
76
84
|
<span class="maz:text-sm maz:text-muted">
|
|
77
|
-
(
|
|
85
|
+
(works with touch, mouse and pen)
|
|
78
86
|
</span>
|
|
79
87
|
<br><br>
|
|
80
88
|
Last swipe direction: {{ lastSwipeDirection || 'None' }}
|
|
@@ -153,35 +161,41 @@ interface UseSwipeOptions {
|
|
|
153
161
|
*/
|
|
154
162
|
element: HTMLElement | string | Ref<HTMLElement>
|
|
155
163
|
/** Callback executed when a left swipe is detected. */
|
|
156
|
-
onLeft?: (event:
|
|
164
|
+
onLeft?: (event: PointerEvent) => void
|
|
157
165
|
/** Callback executed when a right swipe is detected. */
|
|
158
|
-
onRight?: (event:
|
|
166
|
+
onRight?: (event: PointerEvent) => void
|
|
159
167
|
/** Callback executed when an up swipe is detected. */
|
|
160
|
-
onUp?: (event:
|
|
168
|
+
onUp?: (event: PointerEvent) => void
|
|
161
169
|
/** Callback executed when a down swipe is detected. */
|
|
162
|
-
onDown?: (event:
|
|
170
|
+
onDown?: (event: PointerEvent) => void
|
|
163
171
|
/**
|
|
164
172
|
* The minimum distance the swipe must travel to be considered valid.
|
|
165
173
|
* @default 50
|
|
166
174
|
*/
|
|
167
175
|
threshold?: number
|
|
168
176
|
/**
|
|
169
|
-
*
|
|
177
|
+
* Pointer types that can trigger the swipe.
|
|
178
|
+
* @default ['mouse', 'touch', 'pen']
|
|
179
|
+
*/
|
|
180
|
+
pointerTypes?: ('mouse' | 'touch' | 'pen')[]
|
|
181
|
+
/**
|
|
182
|
+
* Whether to prevent the default behavior of the pointer move event
|
|
183
|
+
* (the move listener becomes non-passive when enabled).
|
|
170
184
|
* @default false
|
|
171
185
|
*/
|
|
172
|
-
|
|
186
|
+
preventDefaultOnMove?: boolean
|
|
173
187
|
/**
|
|
174
188
|
* Whether to prevent the default behavior of the mousewheel event.
|
|
175
189
|
* @default false
|
|
176
190
|
*/
|
|
177
191
|
preventDefaultOnMouseWheel?: boolean
|
|
178
192
|
/**
|
|
179
|
-
* Whether to
|
|
193
|
+
* Whether to start listening immediately on instantiation.
|
|
180
194
|
* @default false
|
|
181
195
|
*/
|
|
182
196
|
immediate?: boolean
|
|
183
197
|
/**
|
|
184
|
-
* Whether to trigger the swipe event only on
|
|
198
|
+
* Whether to trigger the swipe event only on pointer up.
|
|
185
199
|
* @default false
|
|
186
200
|
*/
|
|
187
201
|
triggerOnEnd?: boolean
|
|
@@ -220,4 +234,5 @@ interface UseSwipeReturn {
|
|
|
220
234
|
- If you use the composable in a Vue component, make sure to call it in the `setup()` and clean up the event listeners in the `onUnmounted()`.
|
|
221
235
|
- The composable automatically handles the addition and removal of event listeners based on the provided options.
|
|
222
236
|
- You can customize the swipe detection threshold by modifying the `threshold` option.
|
|
223
|
-
- If you want to prevent the default behavior of
|
|
237
|
+
- If you want to prevent the default behavior of pointer move or mousewheel events, you can set the `preventDefaultOnMove` and `preventDefaultOnMouseWheel` options, respectively.
|
|
238
|
+
- Use the `pointerTypes` option to restrict the gesture to specific input types (e.g. `['touch']` for touch-only).
|