@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 +1 -1
- package/docs/generated-docs/maz-table-virtualizer.doc.md +20 -0
- package/docs/generated-docs/maz-table.doc.md +43 -39
- package/docs/src/components/maz-date-picker.md +54 -0
- package/docs/src/components/maz-table.md +82 -1
- package/docs/src/composables/use-form-validator.md +247 -5
- package/docs/src/guide/migration-v5.md +44 -1
- package/docs/src/index.md +1 -1
- package/package.json +6 -6
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.
|
|
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
|
|
4
|
-
|
|
|
5
|
-
| **table-class**
|
|
6
|
-
| **table-style**
|
|
7
|
-
| **model-value**
|
|
8
|
-
| **size**
|
|
9
|
-
| **input-size**
|
|
10
|
-
| **title**
|
|
11
|
-
| **headers**
|
|
12
|
-
| **sortable**
|
|
13
|
-
| **headers-align**
|
|
14
|
-
| **rows**
|
|
15
|
-
| **hoverable**
|
|
16
|
-
| **search**
|
|
17
|
-
| **hide-search-in-row**
|
|
18
|
-
| **hide-search-by**
|
|
19
|
-
| **search-query**
|
|
20
|
-
| **background-odd**
|
|
21
|
-
| **background-even**
|
|
22
|
-
| **elevation**
|
|
23
|
-
| **divider**
|
|
24
|
-
| **caption**
|
|
25
|
-
| **caption-side**
|
|
26
|
-
| **pagination**
|
|
27
|
-
| **page**
|
|
28
|
-
| **page-size**
|
|
29
|
-
| **total-pages**
|
|
30
|
-
| **paginate-rows**
|
|
31
|
-
| **total-items**
|
|
32
|
-
| **loading**
|
|
33
|
-
| **selectable**
|
|
34
|
-
| **selected-key**
|
|
35
|
-
| **table-layout**
|
|
36
|
-
| **color**
|
|
37
|
-
| **translations**
|
|
38
|
-
| **rounded-size**
|
|
39
|
-
| **scrollable**
|
|
40
|
-
| **row-key**
|
|
41
|
-
| **animated-rows**
|
|
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="
|
|
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
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
46
|
-
"maz-ui": "5.0.0-beta.
|
|
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.
|
|
49
|
+
"@maz-ui/eslint-config": "5.0.0-beta.47",
|
|
50
50
|
"@maz-ui/node": "5.0.0-beta.44",
|
|
51
|
-
"@modelcontextprotocol/inspector": "^2.
|
|
52
|
-
"@swc/core": "1.16.
|
|
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",
|