@maz-ui/mcp 5.0.0-beta.46 → 5.0.0-beta.48

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/dist/mcp.mjs CHANGED
@@ -7,7 +7,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
7
7
  import { resolve, join, dirname } from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
9
 
10
- const version = "5.0.0-beta.44";
10
+ const version = "5.0.0-beta.47";
11
11
 
12
12
  class MetadataExtractor {
13
13
  extract(name, type, content, manualTags = []) {
@@ -0,0 +1,20 @@
1
+ ## Props
2
+
3
+ | Name | Description | Type | Required |
4
+ | ------------------ | ------------------------------------------------------------- | --------------------- | -------- |
5
+ | **count** | Total number of rows to virtualize. | `number` | Yes |
6
+ | **scroll-element** | Scrollable viewport element the virtualizer observes. | `HTMLElement \| null` | No |
7
+ | **estimate-size** | Fixed per-row height used to place rows and size the spacers. | `number` | No |
8
+ | **overscan** | Rows rendered outside the viewport on each side. | `number` | No |
9
+
10
+ ## Events
11
+
12
+ | Event name | Properties | Description |
13
+ | ---------- | ---------- | ----------- |
14
+ | change | |
15
+
16
+ ## Slots
17
+
18
+ | Name | Description | Bindings |
19
+ | ------- | ----------- | -------- |
20
+ | default | | |
@@ -1,44 +1,48 @@
1
1
  ## Props
2
2
 
3
- | Name | Description | Type | Required | Default | Possible values |
4
- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | -------- | ---------------------------------------- | ---------------------------- |
5
- | **table-class** | CSS class of the table element | `HTMLAttributes['class']` | No | `undefined` | - |
6
- | **table-style** | CSS style of the table element | `HTMLAttributes['style']` | No | `undefined` | - |
7
- | **model-value** | `v-model` <br/>List of selected rows | `(string \| boolean \| number)[]` | No | `undefined` | - |
8
- | **size** | Size of the table | `MazSize` | No | `md` | `xl, lg, md, sm, xs, mini` |
9
- | **input-size** | Size of the search inputs | `MazSize` | No | `undefined` | `xl, lg, md, sm, xs, mini` |
10
- | **title** | Title of the table | `string` | No | `undefined` | - |
11
- | **headers** | Headers of the table | `MazTableHeader[]` | No | `undefined` | - |
12
- | **sortable** | Enable sort feature on all columns | `boolean` | No | `false` | - |
13
- | **headers-align** | Align all headers | `string` | No | `left` | - |
14
- | **rows** | Rows of the table | `T[]` | No | `undefined` | - |
15
- | **hoverable** | Add hover effect on rows | `boolean` | No | `false` | - |
16
- | **search** | Enable search feature in table header | `boolean` | No | `false` | - |
17
- | **hide-search-in-row** | Disable search in rows - useful to filter data yourself or make search request to server | `boolean` | No | `false` | - |
18
- | **hide-search-by** | Disable search by column (remove select input "search by") | `boolean` | No | `false` | - |
19
- | **search-query** | `v-model:search-query` <br/>Search query in input | `string` | No | `undefined` | - |
20
- | **background-odd** | Add background color to odd rows | `boolean` | No | `false` | - |
21
- | **background-even** | Add background color to even rows | `boolean` | No | `false` | - |
22
- | **elevation** | Add shadow to the table | `boolean` | No | `false` | - |
23
- | **divider** | add divider between rows | `boolean` | No | `false` | - |
24
- | **caption** | Caption of the table | `string` | No | `undefined` | - |
25
- | **caption-side** | Caption side | `string` | No | `bottom` | `top, bottom` |
26
- | **pagination** | Add pagination in table footer | `boolean` | No | `false` | - |
27
- | **page** | `v-model:page` <br/>Current page of pagination | `number` | No | `1` | - |
28
- | **page-size** | `v-model:page-size` <br/>Number of items per page | `number` | No | `20` | - |
29
- | **total-pages** | Total number of pages | `number` | No | `undefined` | - |
30
- | **paginate-rows** | Don't paginate rows - useful to make pagination request with your server | `boolean` | No | `true` | - |
31
- | **total-items** | Total number of items | `number` | No | `undefined` | - |
32
- | **loading** | Loading state | `boolean` | No | `false` | - |
33
- | **selectable** | Enable selection of rows | `boolean` | No | `false` | - |
34
- | **selected-key** | Enable selection of rows - key of the selected row | `string` | No | `undefined` | - |
35
- | **table-layout** | Table layout | `string` | No | `undefined` | `auto, fixed` |
36
- | **color** | Color of the component | `MazColor` | No | `primary` | - |
37
- | **translations** | Translations of the table | `DeepPartial<MazUiTranslationsNestedSchema['table']>` | No | `Translations from @maz-ui/translations` | - |
38
- | **rounded-size** | Size radius of the component's border | `MazRoundedSize` | No | `md` | `none, sm, md, lg, xl, full` |
39
- | **scrollable** | Enable scrollable on table | `boolean` | No | `false` | - |
40
- | **row-key** | Field name used as a stable key for each row (e.g. an id). Gives every row a stable identity so reordering is tracked correctly - required for `animatedRows` to look right. Falls back to the row index when not provided. | `string` | No | `undefined` | - |
41
- | **animated-rows** | Animate row reordering with a FLIP transition (rows slide to their new position). Provide a stable `rowKey` for meaningful results. Respects `prefers-reduced-motion`. | `boolean` | No | `false` | - |
3
+ | Name | Description | Type | Required | Default | Possible values |
4
+ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | -------- | ---------------------------------------- | ---------------------------- |
5
+ | **table-class** | CSS class of the table element | `HTMLAttributes['class']` | No | `undefined` | - |
6
+ | **table-style** | CSS style of the table element | `HTMLAttributes['style']` | No | `undefined` | - |
7
+ | **model-value** | `v-model` <br/>List of selected rows | `(string \| boolean \| number)[]` | No | `undefined` | - |
8
+ | **size** | Size of the table | `MazSize` | No | `md` | `xl, lg, md, sm, xs, mini` |
9
+ | **input-size** | Size of the search inputs | `MazSize` | No | `undefined` | `xl, lg, md, sm, xs, mini` |
10
+ | **title** | Title of the table | `string` | No | `undefined` | - |
11
+ | **headers** | Headers of the table | `MazTableHeader[]` | No | `undefined` | - |
12
+ | **sortable** | Enable sort feature on all columns | `boolean` | No | `false` | - |
13
+ | **headers-align** | Align all headers | `string` | No | `left` | - |
14
+ | **rows** | Rows of the table | `T[]` | No | `undefined` | - |
15
+ | **hoverable** | Add hover effect on rows | `boolean` | No | `false` | - |
16
+ | **search** | Enable search feature in table header | `boolean` | No | `false` | - |
17
+ | **hide-search-in-row** | Disable search in rows - useful to filter data yourself or make search request to server | `boolean` | No | `false` | - |
18
+ | **hide-search-by** | Disable search by column (remove select input "search by") | `boolean` | No | `false` | - |
19
+ | **search-query** | `v-model:search-query` <br/>Search query in input | `string` | No | `undefined` | - |
20
+ | **background-odd** | Add background color to odd rows | `boolean` | No | `false` | - |
21
+ | **background-even** | Add background color to even rows | `boolean` | No | `false` | - |
22
+ | **elevation** | Add shadow to the table | `boolean` | No | `false` | - |
23
+ | **divider** | add divider between rows | `boolean` | No | `false` | - |
24
+ | **caption** | Caption of the table | `string` | No | `undefined` | - |
25
+ | **caption-side** | Caption side | `string` | No | `bottom` | `top, bottom` |
26
+ | **pagination** | Add pagination in table footer | `boolean` | No | `false` | - |
27
+ | **page** | `v-model:page` <br/>Current page of pagination | `number` | No | `1` | - |
28
+ | **page-size** | `v-model:page-size` <br/>Number of items per page | `number` | No | `20` | - |
29
+ | **total-pages** | Total number of pages | `number` | No | `undefined` | - |
30
+ | **paginate-rows** | Don't paginate rows - useful to make pagination request with your server | `boolean` | No | `true` | - |
31
+ | **total-items** | Total number of items | `number` | No | `undefined` | - |
32
+ | **loading** | Loading state | `boolean` | No | `false` | - |
33
+ | **selectable** | Enable selection of rows | `boolean` | No | `false` | - |
34
+ | **selected-key** | Enable selection of rows - key of the selected row | `string` | No | `undefined` | - |
35
+ | **table-layout** | Table layout | `string` | No | `undefined` | `auto, fixed` |
36
+ | **color** | Color of the component | `MazColor` | No | `primary` | - |
37
+ | **translations** | Translations of the table | `DeepPartial<MazUiTranslationsNestedSchema['table']>` | No | `Translations from @maz-ui/translations` | - |
38
+ | **rounded-size** | Size radius of the component's border | `MazRoundedSize` | No | `md` | `none, sm, md, lg, xl, full` |
39
+ | **scrollable** | Enable scrollable on table | `boolean` | No | `false` | - |
40
+ | **row-key** | Field name used as a stable key for each row (e.g. an id). Gives every row a stable identity so reordering is tracked correctly - required for `animatedRows` to look right. Falls back to the row index when not provided. | `string` | No | `undefined` | - |
41
+ | **animated-rows** | Animate row reordering with a FLIP transition (rows slide to their new position). Provide a stable `rowKey` for meaningful results. Respects `prefers-reduced-motion`. | `boolean` | No | `false` | - |
42
+ | **virtualized** | Only render the rows visible in the viewport (plus an overscan) using `@tanstack/vue-virtual`. Keeps the table fast on thousands of rows. Requires `maxHeight` to bound the scroll viewport. Ignored when `animatedRows` is on or when a custom default slot is provided. Rows render client-side only. | `boolean` | No | `false` | - |
43
+ | **max-height** | Max height of the scroll viewport (any CSS length, e.g. `'400px'`, `'70vh'`). Enables scrolling and, when `virtualized`, bounds the virtualized viewport. | `string` | No | `undefined` | - |
44
+ | **estimated-row-height** | Fixed row height in pixels used by the virtualizer to place rows and size the spacers. Set it to your real row height (virtualized rows should be uniform). | `number` | No | `44` | - |
45
+ | **overscan** | Number of rows rendered outside the viewport on each side when `virtualized`. | `number` | No | `10` | - |
42
46
 
43
47
  ## Events
44
48
 
@@ -16,6 +16,7 @@ description: A powerful and flexible date picker component with support for sing
16
16
  - [Basic Usage](#basic-usage)
17
17
  - [Date Formats](#date-formats)
18
18
  - [Date Selection](#date-selection)
19
+ - [Custom Trigger](#custom-trigger)
19
20
  - [Time Selection](#time-selection)
20
21
  - [Range Selection](#range-selection)
21
22
  - [Internationalization](#internationalization)
@@ -273,6 +274,58 @@ const date = ref()
273
274
  </template>
274
275
  </ComponentDemo>
275
276
 
277
+ ## Custom Trigger
278
+
279
+ By default MazDatePicker renders a readonly `MazInput` as the field that opens the calendar. Use the `#trigger` slot to replace it with your own element - a button, a custom input, an icon... Clicking the slotted element opens the calendar automatically (no `@click` needed), and the slot exposes the current open state plus functions for programmatic control.
280
+
281
+ ### Slot bindings
282
+
283
+ | Binding | Type | Description |
284
+ | --- | --- | --- |
285
+ | `is-open` | `boolean` | Whether the calendar is currently open |
286
+ | `open` | `() => void` | Open the calendar programmatically |
287
+ | `close` | `() => void` | Close the calendar programmatically |
288
+ | `toggle` | `() => void` | Toggle the calendar programmatically |
289
+
290
+ <ComponentDemo>
291
+ <MazDatePicker v-model="triggerDate" auto-close hide-header>
292
+ <template #trigger="{ isOpen }">
293
+ <MazBtn color="secondary" :class="{ 'maz:ring-2 maz:ring-offset-2 maz:ring-secondary': isOpen }">
294
+ {{ triggerDate || 'Pick a date' }}
295
+ </MazBtn>
296
+ </template>
297
+ </MazDatePicker>
298
+
299
+ <template #code>
300
+
301
+ ```vue
302
+ <template>
303
+ <MazDatePicker v-model="date" auto-close>
304
+ <!-- Clicking the button opens the picker automatically. -->
305
+ <template #trigger="{ isOpen }">
306
+ <MazBtn color="secondary" :class="{ 'maz:ring-2 maz:ring-secondary': isOpen }">
307
+ {{ date || 'Pick a date' }}
308
+ </MazBtn>
309
+ </template>
310
+ </MazDatePicker>
311
+ </template>
312
+
313
+ <script setup>
314
+ import { ref } from 'vue'
315
+
316
+ const date = ref()
317
+ </script>
318
+ ```
319
+
320
+ </template>
321
+ </ComponentDemo>
322
+
323
+ ::: tip
324
+ The model value is not passed through the slot - use your own `v-model` ref (here `date`) to display the selected value in your custom trigger. The exposed `open` / `close` / `toggle` functions are only needed when you want to drive the picker from your own logic.
325
+ :::
326
+
327
+ > In `inline` mode the calendar is always visible, so the `trigger` slot is not rendered. To open the picker from an element rendered outside the component, use the `custom-element-selector` prop instead.
328
+
276
329
  ## Time Selection
277
330
 
278
331
  ### Date + Time (24h format)
@@ -1021,6 +1074,7 @@ const formatExample2 = ref('03/15/2024')
1021
1074
  const formatExample3 = ref('15-03-2024')
1022
1075
  const transformedDate = ref()
1023
1076
  const newDateValue = ref()
1077
+ const triggerDate = ref()
1024
1078
  // Date selection
1025
1079
  const dateSelection = ref()
1026
1080
  const inlineDate = ref()
@@ -20,6 +20,7 @@ description: MazTable is designed to be a reusable data table with advanced feat
20
20
  5. Customizable Page Size: Users can choose the number of items to display per page using a dropdown list.
21
21
  6. Loading Indicator (prop `loading`): A loading indicator (MazLoadingBar) is displayed when data is being loaded.
22
22
  7. Animated rows (props `animated-rows` + `row-key`): rows slide to their new position when the order changes (FLIP animation). Respects `prefers-reduced-motion`.
23
+ 8. Virtualization (prop `virtualized`): render only the rows visible in the viewport (plus an overscan) so the table stays fast with thousands of rows.
23
24
 
24
25
  ## Available models
25
26
 
@@ -156,7 +157,7 @@ v-model:page-size="{{pageSize ?? 'undefined'}}"
156
157
  </template>
157
158
  <template #cell-name="{ row, value }">
158
159
  <div class="maz:flex maz:items-center maz:gap-2">
159
- <MazAvatar :src="row.logoUrl" size="0.5rem"></MazAvatar>
160
+ <MazAvatar :src="row.logoUrl" size="mini"></MazAvatar>
160
161
  <span>{{value}}</span>
161
162
  </div>
162
163
  </template>
@@ -431,6 +432,79 @@ Enable `animated-rows` to make rows slide to their new position when the order c
431
432
 
432
433
  </ComponentDemo>
433
434
 
435
+ ## Virtualization
436
+
437
+ Enable `virtualized` to render only the rows inside the viewport (plus an overscan) instead of every row. This keeps the table fast and the DOM light even with tens of thousands of rows. It works in `rows` mode (data-driven), and `@tanstack/vue-virtual` is loaded on demand - pages that never virtualize a table never ship it.
438
+
439
+ The example below renders **10 000 rows** but only keeps ~20 `<tr>` in the DOM at any time.
440
+
441
+ <ComponentDemo>
442
+ <MazTable
443
+ class="vp-raw"
444
+ size="sm"
445
+ virtualized
446
+ max-height="300px"
447
+ :estimated-row-height="38"
448
+ :headers="[
449
+ { label: '#', key: 'id', width: '5rem' },
450
+ { label: 'Name', key: 'name', width: '12rem' },
451
+ { label: 'Email', key: 'email' },
452
+ { label: 'Score', key: 'score', align: 'center', width: '6rem' },
453
+ ]"
454
+ :rows="virtualizedRows"
455
+ />
456
+
457
+ <template #code>
458
+
459
+ ```vue
460
+ <template>
461
+ <MazTable
462
+ virtualized
463
+ max-height="400px"
464
+ :estimated-row-height="38"
465
+ :headers="[
466
+ { label: '#', key: 'id', width: '5rem' },
467
+ { label: 'Name', key: 'name', width: '12rem' },
468
+ { label: 'Email', key: 'email' },
469
+ { label: 'Score', key: 'score', align: 'center', width: '6rem' },
470
+ ]"
471
+ :rows="rows"
472
+ />
473
+ </template>
474
+
475
+ <script lang="ts" setup>
476
+ import { MazTable } from 'maz-ui/components'
477
+
478
+ // 10 000 rows - only the visible window is rendered to the DOM
479
+ const rows = Array.from({ length: 10_000 }, (_, i) => ({
480
+ id: i + 1,
481
+ name: `User ${i + 1}`,
482
+ email: `user${i + 1}@maz-ui.com`,
483
+ score: Math.round(Math.random() * 100),
484
+ }))
485
+ </script>
486
+ ```
487
+
488
+ </template>
489
+
490
+ </ComponentDemo>
491
+
492
+ ### Options
493
+
494
+ | Prop | Default | Description |
495
+ | --- | --- | --- |
496
+ | `virtualized` | `false` | Turns virtualization on. |
497
+ | `max-height` | - | **Required.** Bounds the scroll viewport (any CSS length, e.g. `'400px'`, `'70vh'`). Scrolling is enabled automatically and the header stays sticky. |
498
+ | `estimated-row-height` | `44` | Fixed row height (in px) used to place rows and size the spacers. Set it to your real row height; virtualized rows should be uniform (single line). |
499
+ | `overscan` | `10` | Rows rendered outside the viewport on each side. |
500
+
501
+ ### Good to know
502
+
503
+ * **Set a `width` (or `minWidth`) on your headers.** Virtualization forces `table-layout: fixed` so columns stay aligned across the rendered window; without widths the browser distributes them equally.
504
+ * **Accessibility:** the table exposes `aria-rowcount` and each rendered row an `aria-rowindex`, so screen readers still announce "row X of N".
505
+ * **Ignored** (with a dev warning) when `animated-rows` is enabled or when you provide a custom default slot - those paths render every row.
506
+ * **Rendered client-side:** the virtualized body is empty during SSR and fills in on hydration.
507
+
434
508
  ## Loading
435
509
 
436
510
  Enable the loading state with the prop `loading`
@@ -589,6 +663,13 @@ Available sizes: `'mini' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'`
589
663
  function shuffleRows() {
590
664
  players.value = [...players.value].sort(() => Math.random() - 0.5)
591
665
  }
666
+
667
+ const virtualizedRows = Array.from({ length: 10_000 }, (_, i) => ({
668
+ id: i + 1,
669
+ name: `User ${i + 1}`,
670
+ email: `user${i + 1}@maz-ui.com`,
671
+ score: Math.round(Math.random() * 100),
672
+ }))
592
673
  </script>
593
674
 
594
675
  ## Types
@@ -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 using [Valibot](https://valibot.dev/guides/introduction/).
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:
@@ -699,7 +897,7 @@ const { value, errorMessage, hasError, validationEvents } = useFormField<string>
699
897
 
700
898
  ## TypeScript Type Inference
701
899
 
702
- 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:
703
901
 
704
902
  ```ts
705
903
  const schema = {
@@ -745,7 +943,23 @@ const { value: email } = useFormField<string>('email', {
745
943
 
746
944
  ## Async Validation
747
945
 
748
- Use Valibot's `pipeAsync` and `checkAsync` for async validations like checking username availability:
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
+ ```
749
963
 
750
964
  <ComponentDemo>
751
965
  <div class="maz:mb-4">
@@ -1065,7 +1279,7 @@ If your custom component isn't detected for blur events, add `data-interactive`:
1065
1279
 
1066
1280
  ```ts
1067
1281
  useFormValidator<TSchema>({
1068
- schema: TSchema, // Valibot validation schema (required)
1282
+ schema: TSchema, // Object of Standard Schema field schemas: Valibot, Zod, ArkType... (required)
1069
1283
  model?: Ref<Model>, // External model ref (optional)
1070
1284
  defaultValues?: DeepPartial<Model>, // Initial values (optional)
1071
1285
  options?: {
@@ -1140,6 +1354,14 @@ useFormField<FieldType>(
1140
1354
  ### Types
1141
1355
 
1142
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
+
1143
1365
  interface FormValidatorOptions<Model> {
1144
1366
  mode?: 'eager' | 'lazy' | 'aggressive' | 'blur' | 'progressive'
1145
1367
  throttledFields?: Partial<Record<keyof Model, number | true>>
@@ -1252,9 +1474,29 @@ import {
1252
1474
  pipeAsync,
1253
1475
  checkAsync,
1254
1476
  } from 'valibot'
1477
+ import { z } from 'zod'
1255
1478
 
1256
1479
  const toast = useToast()
1257
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
+
1258
1500
  // Quick Start Demo
1259
1501
  const quickStartSchema = {
1260
1502
  email: pipe(string(), nonEmpty('Email is required'), email('Invalid email')),
@@ -86,7 +86,8 @@ The MCP server is read-only — it ships docs, not code edits — so the assista
86
86
  9. **`MazAvatar` size scale fixed.** A CSS-unit `size` now renders at its real value (`size="2rem"` is a 32px avatar, it was ~96px before). Multiply your unit values by 3 to keep the same render, or switch to a `MazSize` keyword (`'mini' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'`). The upgrade tool does the ×3 rewrite for you on static values.
87
87
  10. Rename **`MazPullToRefresh`**'s `on-click` prop to `on-refresh` (handled by the upgrade tool).
88
88
  11. Replace bare **`from 'maz-ui'`** utility imports with **`from '@maz-ui/utils'`** (the root re-export of utils was removed; handled by the upgrade tool). Components/composables/directives/plugins stay on their subpaths; types remain importable from `maz-ui`.
89
- 12. That's it for most apps. Everything else is opt-in.
89
+ 12. If you use **`useFormValidator` / `useFormField`** with Valibot, add **`valibot`** to your own dependencies (it is no longer installed by `maz-ui`). The composables now accept any [Standard Schema](https://standardschema.dev) library (Valibot, Zod, ArkType...).
90
+ 13. That's it for most apps. Everything else is opt-in.
90
91
 
91
92
  ## Prerequisites
92
93
 
@@ -642,6 +643,48 @@ import type { MazColor, MazSize } from 'maz-ui'
642
643
 
643
644
  `maz-ui` and its `@maz-ui/*` packages ship **ESM only** (no CommonJS build). This was already the case in v4, but if you consume the library from a CommonJS context (old Jest without ESM, `require()`), use a bundler/test runner with ESM support (Vitest, Jest ≥ 29 with ESM, or native `import`).
644
645
 
646
+ ### 20. `useFormValidator`: validation library agnostic (Standard Schema)
647
+
648
+ `useFormValidator` and `useFormField` no longer depend on Valibot. They accept any library implementing [Standard Schema](https://standardschema.dev): Valibot (>= 1.0), Zod (>= 3.24), ArkType (>= 2.0), or your own validator. Your existing Valibot schemas keep working as is: same validation, same error messages, same inferred types for `model` and the `handleSubmit` payload.
649
+
650
+ ```ts
651
+ // v4 and v5 - unchanged
652
+ import { pipe, string, minLength } from 'valibot'
653
+ useFormValidator({ schema: { name: pipe(string(), minLength(3)) } })
654
+
655
+ // v5 - Zod (or mix both in the same schema)
656
+ import { z } from 'zod'
657
+ useFormValidator({ schema: { name: z.string().min(3) } })
658
+ ```
659
+
660
+ What can break:
661
+
662
+ **`valibot` is no longer a dependency of `maz-ui`.** Valibot, Zod and ArkType are now declared as optional peer dependencies (`valibot >=1.0.0 <2.0.0`, `zod >=3.24.0 <5.0.0`, `arktype >=2.0.0 <3.0.0`). If your app imports `valibot` without declaring it (it was installed transitively), add it:
663
+
664
+ ```bash
665
+ pnpm add valibot
666
+ ```
667
+
668
+ **Issue types are now the Standard Schema ones.** `ValidationIssues` (returned by `errors`, `fieldsStates[field].errors`, `useFormField().errors` and the `onError` payload of `handleSubmit`) is now typed as an array of `{ message: string, path?: ... }` instead of Valibot's `BaseIssue<unknown>`. Only TypeScript is affected: at runtime, the issues are still the native Valibot objects. If you read Valibot specific properties (`kind`, `type`, `input`, `expected`, `received`, `requirement`...), cast the issue:
669
+
670
+ ```ts
671
+ import type { BaseIssue } from 'valibot'
672
+
673
+ // v4
674
+ const type = errors.value.email[0].type
675
+
676
+ // v5
677
+ const type = (errors.value.email[0] as BaseIssue<unknown>).type
678
+ ```
679
+
680
+ **`FormSchema` values are typed as `StandardSchemaV1`.** If you typed a schema object with `FormSchema<Model>` and then read Valibot specific properties of its entries (`schema.email.type`, `schema.email.pipe`...), declare the object with `as const satisfies FormSchema<Model>` or without annotation, so TypeScript keeps the Valibot types.
681
+
682
+ ```bash
683
+ # Find usages to check
684
+ rg "useFormValidator|useFormField" src/
685
+ rg "from 'valibot'" src/
686
+ ```
687
+
645
688
  ## Informational changes (probably no action needed)
646
689
 
647
690
  ### Theme colors are now emitted as OKLCh
package/docs/src/index.md CHANGED
@@ -465,7 +465,7 @@ description: Build amazing interfaces with Maz-UI - standalone components & tool
465
465
  </div>
466
466
  <h3 class="maz:text-base maz:font-semibold">useFormValidator</h3>
467
467
  <p class="maz:dark:text-gray-300 maz:text-muted maz:text-sm">
468
- A Vue composable designed to simplify form validation using Valibot as the validation library. Offers a flexible and typed approach to handle form validation.
468
+ A Vue composable designed to simplify form validation with any Standard Schema library (Valibot, Zod, ArkType...). Offers a flexible and typed approach to handle form validation.
469
469
  </p>
470
470
  <div class="maz:flex-1"></div>
471
471
  <MazBtn color="surface" outlined size="sm" block href="/composables/use-form-validator">Discover</MazBtn>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@maz-ui/mcp",
3
3
  "type": "module",
4
- "version": "5.0.0-beta.46",
4
+ "version": "5.0.0-beta.48",
5
5
  "description": "Maz-UI ModelContextProtocol Client",
6
6
  "author": "Louis Mazel <me@loicmazuel.com>",
7
7
  "license": "MIT",
@@ -42,14 +42,14 @@
42
42
  ],
43
43
  "dependencies": {
44
44
  "@maz-ui/utils": "5.0.0-beta.46",
45
- "@modelcontextprotocol/sdk": "^1.30.1",
46
- "maz-ui": "5.0.0-beta.46"
45
+ "@modelcontextprotocol/sdk": "^1.32.0",
46
+ "maz-ui": "5.0.0-beta.48"
47
47
  },
48
48
  "devDependencies": {
49
- "@maz-ui/eslint-config": "5.0.0-beta.44",
49
+ "@maz-ui/eslint-config": "5.0.0-beta.47",
50
50
  "@maz-ui/node": "5.0.0-beta.44",
51
- "@modelcontextprotocol/inspector": "^2.8.0",
52
- "@swc/core": "1.16.2",
51
+ "@modelcontextprotocol/inspector": "^2.9.0",
52
+ "@swc/core": "1.16.13",
53
53
  "prettier": "^3.9.9",
54
54
  "ts-node-maintained": "^10.9.5",
55
55
  "tsx": "^4.23.15",