@nxgt/material-vue 0.26.1 → 0.28.0
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/CHANGELOG.md +202 -0
- package/README.md +10 -7
- package/dist/Button-D1uzdbOJ.js +728 -0
- package/dist/{DropdownMenuTrigger-BHAhXGUO.js → DropdownMenuTrigger-BsuyNVzV.js} +48 -41
- package/dist/HoverCardTrigger-DaM5d4hE.js +510 -0
- package/dist/{camera-DvKuqT4L.js → camera-CxXijySa.js} +4 -4
- package/dist/components/base-content.css +139 -0
- package/dist/components/base.css +150 -0
- package/dist/components/components.css +2 -0
- package/dist/components/reduced-motion.css +56 -0
- package/dist/components/theme-tokens.css +116 -0
- package/dist/components/ui/actions/button-group/button-group.css +1 -1
- package/dist/components/ui/feedback/toast/toast.css +21 -0
- package/dist/components/ui/primitives/label/label.css +33 -0
- package/dist/{components-CCWod7cJ.js → components-Db9OdMfQ.js} +19053 -18364
- package/dist/components.js +7 -7
- package/dist/composables.js +3 -3
- package/dist/dnd.js +1 -1
- package/dist/field-name-attribute-Bl7LSVK0.js +4 -0
- package/dist/{form-scope-x-7TYn2s.js → form-scope-BILi3ke-.js} +1 -1
- package/dist/{i18n-provider-CaaZCmaR.js → i18n-provider-BPK68eqM.js} +1 -1
- package/dist/i18n.js +4 -4
- package/dist/icon-button-D60G5B_Z.js +262 -0
- package/dist/lib/components/ui/actions/button/Button.vue.d.ts +22 -4
- package/dist/lib/components/ui/actions/button/button-tones.d.ts +36 -0
- package/dist/lib/components/ui/actions/button/button.variants.d.ts +1 -1
- package/dist/lib/components/ui/actions/button/button.wrap.d.ts +8 -0
- package/dist/lib/components/ui/actions/button/label-text.d.ts +9 -0
- package/dist/lib/components/ui/actions/button/prepend-to-child.d.ts +18 -0
- package/dist/lib/components/ui/actions/button/use-click-lock.d.ts +14 -0
- package/dist/lib/components/ui/actions/copy-button/copy-button.vue.d.ts +6 -2
- package/dist/lib/components/ui/actions/icon-button/icon-button.compound-variants.d.ts +28 -0
- package/dist/lib/components/ui/actions/icon-button/icon-button.vue.d.ts +1 -1
- package/dist/lib/components/ui/actions/responsive-button/responsive-button.vue.d.ts +1 -3
- package/dist/lib/components/ui/collections/data-table/partials/data-table-empty.vue.d.ts +5 -1
- package/dist/lib/components/ui/collections/data-table/partials/data-table-scroll-region.vue.d.ts +28 -0
- package/dist/lib/components/ui/collections/data-table/partials/data-table-toolbar.d.ts +60 -0
- package/dist/lib/components/ui/collections/data-table/types.d.ts +9 -0
- package/dist/lib/components/ui/collections/data-table/use-scroll-edges.d.ts +22 -0
- package/dist/lib/components/ui/collections/pagination/PaginationFirst.vue.d.ts +1 -1
- package/dist/lib/components/ui/collections/pagination/PaginationItem.vue.d.ts +1 -1
- package/dist/lib/components/ui/collections/pagination/PaginationLast.vue.d.ts +1 -1
- package/dist/lib/components/ui/collections/pagination/PaginationNext.vue.d.ts +1 -1
- package/dist/lib/components/ui/collections/pagination/PaginationPrevious.vue.d.ts +1 -1
- package/dist/lib/components/ui/dates/calendar/Calendar.vue.d.ts +4 -4
- package/dist/lib/components/ui/dates/calendar/calendar-day-label.d.ts +17 -0
- package/dist/lib/components/ui/dates/calendar/calendar-years.d.ts +11 -0
- package/dist/lib/components/ui/dates/calendar/partials/calendar-month-select.vue.d.ts +18 -0
- package/dist/lib/components/ui/dates/calendar/partials/calendar-year-select.vue.d.ts +19 -0
- package/dist/lib/components/ui/dates/calendar/use-calendar-locale.d.ts +7 -0
- package/dist/lib/components/ui/dates/calendar/use-calendar-selected-hint.d.ts +17 -0
- package/dist/lib/components/ui/dates/date-range-field/partials/date-range-day.vue.d.ts +8 -0
- package/dist/lib/components/ui/display/cards/cards.gallery.d.ts +3 -3
- package/dist/lib/components/ui/display/list-tile/list-tile.vue.d.ts +10 -6
- package/dist/lib/components/ui/feedback/alert-dialog/AlertDialogContent.vue.d.ts +2 -2
- package/dist/lib/components/ui/feedback/sonner/index.d.ts +1 -0
- package/dist/lib/components/ui/feedback/toast/toast.d.ts +7 -0
- package/dist/lib/components/ui/feedback/toast/toast.vue.d.ts +18 -2
- package/dist/lib/components/ui/fields/contacts/consts.d.ts +1 -1
- package/dist/lib/components/ui/forms/use-field-blur.d.ts +3 -0
- package/dist/lib/components/ui/layout/app-shell/activity-content.vue.d.ts +9 -3
- package/dist/lib/components/ui/layout/app-shell/activity-layout.vue.d.ts +21 -4
- package/dist/lib/components/ui/layout/app-shell/app-header.vue.d.ts +7 -5
- package/dist/lib/components/ui/layout/app-shell/app-sidebar.vue.d.ts +4 -1
- package/dist/lib/components/ui/layout/app-shell/index.d.ts +2 -2
- package/dist/lib/components/ui/layout/app-shell/router-menu-link.d.ts +7 -0
- package/dist/lib/components/ui/layout/app-shell/types.d.ts +8 -0
- package/dist/lib/components/ui/layout/app-shell/use-content-scroll.d.ts +13 -0
- package/dist/lib/components/ui/layout/app-shell/use-navigation-sheet.d.ts +17 -0
- package/dist/lib/components/ui/layout/sidebar/sidebar-context.d.ts +15 -0
- package/dist/lib/components/ui/layout/sidebar/sidebar-menu-item.vue.d.ts +13 -4
- package/dist/lib/components/ui/media/carousel/carousel-next.vue.d.ts +1 -1
- package/dist/lib/components/ui/media/carousel/carousel-previous.vue.d.ts +1 -1
- package/dist/lib/components/ui/menu-item-variant.d.ts +4 -4
- package/dist/lib/components/ui/overlays/context-menu/ContextMenuContent.vue.d.ts +2 -2
- package/dist/lib/components/ui/overlays/dialog/DialogContent.vue.d.ts +2 -2
- package/dist/lib/components/ui/overlays/dialog/DialogScrollContent.vue.d.ts +2 -2
- package/dist/lib/components/ui/overlays/dialog/use-escape-forward.d.ts +17 -0
- package/dist/lib/components/ui/overlays/dialog/use-inert-others.d.ts +17 -0
- package/dist/lib/components/ui/overlays/dialog/use-modal-content.d.ts +32 -0
- package/dist/lib/components/ui/overlays/drawer/DrawerContent.vue.d.ts +2 -2
- package/dist/lib/components/ui/overlays/dropdown-menu/DropdownMenu.vue.d.ts +3 -1
- package/dist/lib/components/ui/overlays/dropdown-menu/DropdownMenuContent.vue.d.ts +2 -2
- package/dist/lib/components/ui/overlays/popover/PopoverContent.vue.d.ts +2 -2
- package/dist/lib/components/ui/overlays/sheet/SheetContent.vue.d.ts +2 -2
- package/dist/lib/components/ui/primitives/card/CardTitle.vue.d.ts +11 -4
- package/dist/lib/components/ui/primitives/card/index.d.ts +1 -0
- package/dist/lib/components/ui/primitives/tabs/Tabs.vue.d.ts +6 -2
- package/dist/lib/components/ui/primitives/tabs/TabsList.vue.d.ts +2 -2
- package/dist/lib/components/ui/primitives/tabs/TabsTrigger.vue.d.ts +6 -4
- package/dist/lib/components/ui/primitives/tabs/tabs-classes.d.ts +27 -0
- package/dist/lib/components/ui/primitives/tabs/tabs-indicator.vue.d.ts +25 -0
- package/dist/lib/components/ui/primitives/tabs/tabs-variant.d.ts +5 -8
- package/dist/lib/components/ui/primitives/toggle/Toggle.vue.d.ts +1 -1
- package/dist/lib/components/ui/primitives/toggle/index.d.ts +1 -1
- package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-box.vue.d.ts +2 -0
- package/dist/lib/components/ui/selection/select/SelectContent.vue.d.ts +2 -2
- package/dist/lib/components/ui/selection/select-chip-field/select-chip-field.vue.d.ts +1 -0
- package/dist/lib/components/ui/widgets/emoji/types.d.ts +1 -6
- package/dist/lib/i18n/resources/en/en.d.ts +23 -0
- package/dist/lib/i18n/resources/en/material.json.d.ts +28 -5
- package/dist/lib/i18n/resources/fr/fr.d.ts +23 -0
- package/dist/lib/i18n/resources/fr/material.json.d.ts +28 -5
- package/dist/lib/i18n/resources/resources.d.ts +46 -0
- package/dist/lib/i18n/translator.d.ts +1 -1
- package/dist/lib/lib/field-name-attribute.d.ts +7 -0
- package/dist/lib/lib/focus-field.d.ts +17 -0
- package/dist/lib/lib/index.d.ts +1 -0
- package/dist/lib.js +76 -74
- package/dist/main.js +13 -13
- package/dist/map.js +4 -4
- package/dist/material-vue.css +1 -1
- package/dist/{pdf-outline-sidebar-D3vq2qfS.js → pdf-outline-sidebar-BXGTDISI.js} +2 -2
- package/dist/{pdf-page-BrJbyjQj.js → pdf-page-BR4G03kN.js} +1 -1
- package/dist/{pdf-page-list-BCgYEszV.js → pdf-page-list-bjQm6_kf.js} +2 -2
- package/dist/{pdf-reader-BAzevSJU.js → pdf-reader-DGVEFNc0.js} +9 -9
- package/dist/{pdf-sidebar-DSh2Mend.js → pdf-sidebar-CiM6dwAm.js} +3 -3
- package/dist/{pdf-thumbnail-sidebar-D_EMcbKF.js → pdf-thumbnail-sidebar-fM3DGJzD.js} +3 -3
- package/dist/{pdf-toolbar-BcL7HlfS.js → pdf-toolbar-mWlGTWU1.js} +5 -5
- package/dist/pdf.js +2 -2
- package/dist/{sortable-provider-DMpmM7Ke.js → sortable-provider-Dml11nQY.js} +2 -2
- package/dist/{stored-language-Bxhsx4iB.js → stored-language-UnInBB6T.js} +56 -10
- package/dist/{stx-player-MldK_ZAZ.js → stx-player-BS7-CXFx.js} +2 -2
- package/dist/styles.css +10 -368
- package/dist/{translator-CLI_7Crg.js → translator-J3_vaV_A.js} +1 -1
- package/dist/{use-heading-spy-VPuFj2Gt.js → use-heading-spy-_dk8kGwL.js} +19 -19
- package/dist/use-inert-others-CtGqZKR8.js +22 -0
- package/dist/{use-material-translation-CONqD9wJ.js → use-material-translation-Do6ztdtV.js} +3 -3
- package/docs/components/buttons-and-actions.md +71 -16
- package/docs/components/data-display.md +13 -2
- package/docs/components/data-table-filter-and-lists.md +85 -5
- package/docs/components/date-and-time.md +25 -1
- package/docs/components/feedback-and-status.md +86 -11
- package/docs/components/form-fields.md +13 -4
- package/docs/components/media-and-files.md +7 -0
- package/docs/components/navigation-and-layout.md +19 -1
- package/docs/components/overlays-and-menus.md +20 -4
- package/docs/components/primitives.md +97 -20
- package/docs/components/rich-widgets.md +10 -0
- package/docs/components/selection-and-choice.md +12 -1
- package/docs/getting-started.md +2 -2
- package/docs/guides/app-shell.md +102 -6
- package/docs/guides/forms.md +58 -5
- package/docs/guides/helpers.md +2 -1
- package/docs/guides/i18n.md +24 -4
- package/docs/guides/ssr.md +1 -1
- package/docs/guides/theming.md +148 -6
- package/docs/previews/data-display-codegroup.webp +0 -0
- package/docs/previews/data-display-description.webp +0 -0
- package/docs/previews/feedback-status-toast.webp +0 -0
- package/docs/previews/primitives-tabs.webp +0 -0
- package/docs/previews/rich-widgets-emojipicker.webp +0 -0
- package/docs/roadmap.md +30 -1
- package/docs/troubleshooting.md +889 -12
- package/lib/components/base-content.css +139 -0
- package/lib/components/base.css +150 -0
- package/lib/components/components.css +2 -0
- package/lib/components/reduced-motion.css +56 -0
- package/lib/components/theme-tokens.css +116 -0
- package/lib/components/ui/actions/button-group/button-group.css +1 -1
- package/lib/components/ui/feedback/toast/toast.css +21 -0
- package/lib/components/ui/primitives/label/label.css +33 -0
- package/lib/styles.css +10 -368
- package/package.json +2 -2
- package/dist/Button-16J7vTpE.js +0 -665
- package/dist/HoverCardTrigger-C41002JS.js +0 -455
- package/dist/icon-button-CDdNsZuZ.js +0 -227
- package/dist/lib/components/ui/widgets/emoji/emoji.groups.d.ts +0 -6
|
@@ -163,7 +163,9 @@ import {
|
|
|
163
163
|
|
|
164
164
|
Parts: `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardAction`,
|
|
165
165
|
`CardContent`, `CardFooter` — each a styled `<div>` taking `class` and the
|
|
166
|
-
element's attributes
|
|
166
|
+
element's attributes, except `CardTitle`, a heading: an `h3` by default,
|
|
167
|
+
another level or a `div` with `as` (`<CardTitle as="h2">`), so
|
|
168
|
+
`getByRole('heading', { name })` finds it. `CardAction` sits top-right of the header.
|
|
167
169
|
|
|
168
170
|
**Storybook:** `Primitives/Card` · **React:** `Card` (same parts)
|
|
169
171
|
|
|
@@ -246,12 +248,24 @@ import { Input, Label } from '@nxgt/material-vue/components';
|
|
|
246
248
|
|
|
247
249
|
| Prop | Type | Default | Effect |
|
|
248
250
|
| --- | --- | --- | --- |
|
|
249
|
-
| `with-asterisk` | `boolean` | — |
|
|
251
|
+
| `with-asterisk` | `boolean` | — | draws a red `*` with CSS after the label, so it is in neither the label's text nor its accessible name (`getByLabel('Email', { exact: true })` and `getByLabelText` match "Email"): mark the field itself `required` (or `aria-required`), which every field of this library does for you |
|
|
250
252
|
| `for` | `string` | — | the control it labels; clicking the label focuses it |
|
|
251
253
|
|
|
252
254
|
Fields on the [Form fields](form-fields.md) page render it for you through
|
|
253
255
|
their `label` prop.
|
|
254
256
|
|
|
257
|
+
To restyle the mark, target `[data-slot="label-text"][data-required]::after`:
|
|
258
|
+
`background-color` sets its colour (default `var(--color-error)`), `width` and
|
|
259
|
+
`height` its size. It stays visible in forced-colors mode and in print.
|
|
260
|
+
|
|
261
|
+
```css
|
|
262
|
+
[data-slot="label-text"][data-required]::after {
|
|
263
|
+
background-color: #b45309;
|
|
264
|
+
width: 0.75em;
|
|
265
|
+
height: 0.75em;
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
255
269
|
**Storybook:** `Primitives/Label` · **React:** `Label`
|
|
256
270
|
|
|
257
271
|
---
|
|
@@ -570,12 +584,12 @@ use `DataTable` on the [Data table, filter & lists](data-table-filter-and-lists.
|
|
|
570
584
|
|
|
571
585
|

|
|
572
586
|
|
|
573
|
-
Tabs whose triggers can take an icon
|
|
587
|
+
Tabs whose triggers can take an icon, in two looks: `underline` (the default) and the `segmented` pill.
|
|
574
588
|
|
|
575
589
|
```vue
|
|
576
590
|
<script setup lang="ts">
|
|
591
|
+
import { Calendar, User } from '@lucide/vue';
|
|
577
592
|
import {
|
|
578
|
-
Icon,
|
|
579
593
|
Tabs,
|
|
580
594
|
TabsContent,
|
|
581
595
|
TabsList,
|
|
@@ -586,8 +600,11 @@ import {
|
|
|
586
600
|
<template>
|
|
587
601
|
<Tabs default-value="account" class="w-md">
|
|
588
602
|
<TabsList>
|
|
589
|
-
<TabsTrigger value="account"
|
|
590
|
-
<TabsTrigger value="calendar"
|
|
603
|
+
<TabsTrigger value="account" :icon="User">Account</TabsTrigger>
|
|
604
|
+
<TabsTrigger value="calendar">
|
|
605
|
+
<template #icon><Calendar /></template>
|
|
606
|
+
Calendar
|
|
607
|
+
</TabsTrigger>
|
|
591
608
|
</TabsList>
|
|
592
609
|
<TabsContent value="account">Your profile.</TabsContent>
|
|
593
610
|
<TabsContent value="calendar">Your events.</TabsContent>
|
|
@@ -595,36 +612,96 @@ import {
|
|
|
595
612
|
</template>
|
|
596
613
|
```
|
|
597
614
|
|
|
598
|
-
Parts: `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`.
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
`
|
|
602
|
-
|
|
603
|
-
|
|
615
|
+
Parts: `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`. `TabsList` and
|
|
616
|
+
`TabsTrigger` read the look from `Tabs`. A trigger's icon goes before its
|
|
617
|
+
label, as `@nxgt/material`'s `icon`: pass a component to the `icon` prop, or
|
|
618
|
+
fill the `#icon` slot. An icon written in the default slot still renders
|
|
619
|
+
beside the label.
|
|
620
|
+
|
|
621
|
+
**`underline`** (the default) lays the tabs over a border; the active one
|
|
622
|
+
takes the primary colour, and a bar under it slides to the tab clicked or
|
|
623
|
+
reached with the arrow keys. The list scrolls sideways when the tabs do not
|
|
624
|
+
fit (its scrollbar hidden), which suits a phone or a narrow popover.
|
|
625
|
+
|
|
626
|
+
**`segmented`** is a neutral pill track: the active tab sits on a raised
|
|
627
|
+
surface that slides between tabs, and its label turns to the foreground
|
|
628
|
+
colour and semibold — the state never rests on the surface alone. It suits a
|
|
629
|
+
few short choices, such as a view switch.
|
|
630
|
+
|
|
631
|
+
```vue
|
|
632
|
+
<template>
|
|
633
|
+
<Tabs default-value="week" variant="segmented">
|
|
634
|
+
<TabsList>
|
|
635
|
+
<TabsTrigger value="day">Day</TabsTrigger>
|
|
636
|
+
<TabsTrigger value="week">Week</TabsTrigger>
|
|
637
|
+
<TabsTrigger value="month">Month</TabsTrigger>
|
|
638
|
+
</TabsList>
|
|
639
|
+
<TabsContent value="day">Today.</TabsContent>
|
|
640
|
+
<TabsContent value="week">This week.</TabsContent>
|
|
641
|
+
<TabsContent value="month">This month.</TabsContent>
|
|
642
|
+
</Tabs>
|
|
643
|
+
</template>
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
`orientation="vertical"` lays the tabs in a column, the indicator on their
|
|
647
|
+
left side; the arrow keys become Up and Down.
|
|
604
648
|
|
|
605
649
|
```vue
|
|
606
650
|
<template>
|
|
607
|
-
<Tabs default-value="
|
|
651
|
+
<Tabs default-value="profile" orientation="vertical">
|
|
608
652
|
<TabsList>
|
|
609
|
-
<TabsTrigger value="
|
|
610
|
-
<TabsTrigger value="
|
|
653
|
+
<TabsTrigger value="profile">Profile</TabsTrigger>
|
|
654
|
+
<TabsTrigger value="security">Security</TabsTrigger>
|
|
611
655
|
</TabsList>
|
|
612
|
-
<TabsContent value="
|
|
613
|
-
<TabsContent value="
|
|
656
|
+
<TabsContent value="profile">Your profile.</TabsContent>
|
|
657
|
+
<TabsContent value="security">Your sessions.</TabsContent>
|
|
614
658
|
</Tabs>
|
|
615
659
|
</template>
|
|
616
660
|
```
|
|
617
661
|
|
|
662
|
+
The sliding indicator is reka's `TabsIndicator`, which renders once mounted:
|
|
663
|
+
server HTML, and a page without JavaScript, show a static indicator under (or
|
|
664
|
+
behind) the active tab instead, and hydrating swaps one for the other without
|
|
665
|
+
a mismatch. The list then carries `data-indicator="ready"`. The focus ring is
|
|
666
|
+
an outline in the primary colour inside the tab, so a scrolling list never
|
|
667
|
+
clips it.
|
|
668
|
+
|
|
669
|
+
With `TabsList asChild`, only the static indicator is drawn.
|
|
670
|
+
|
|
671
|
+
`dir="rtl"` on `Tabs` (or reka's `ConfigProvider`) runs the tabs from the
|
|
672
|
+
right: the indicator follows them, and the Left arrow key moves to the next
|
|
673
|
+
tab. Under `prefers-reduced-motion: reduce` the indicator jumps to the active
|
|
674
|
+
tab instead of sliding.
|
|
675
|
+
|
|
676
|
+
**A tab with only an icon needs a name.** A screen reader announces a
|
|
677
|
+
`TabsTrigger` by its text; with an icon and no label it is only "tab". Give it
|
|
678
|
+
`aria-label`, and `title` for sighted users who hover it:
|
|
679
|
+
|
|
680
|
+
```vue
|
|
681
|
+
<TabsTrigger value="recent" :icon="Clock" aria-label="Recent" title="Recent" />
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
The emoji picker's group tabs are named this way, from
|
|
685
|
+
`material.emoji.groups.*`.
|
|
686
|
+
|
|
687
|
+
**The default look changed.** A `Tabs` with no `variant` used to render the
|
|
688
|
+
`bar` look (one muted pill with the active trigger raised); it now renders
|
|
689
|
+
`underline`. `variant="bar"` and `variant="line"` are deprecated aliases:
|
|
690
|
+
`bar` renders as `underline`, `line` as `segmented`. Both still type-check and
|
|
691
|
+
render; write `underline` or `segmented` instead.
|
|
692
|
+
|
|
618
693
|
| Prop | Type | Default | Effect |
|
|
619
694
|
| --- | --- | --- | --- |
|
|
620
|
-
| `variant` (`Tabs`) | `'bar' \| 'line'` | `'
|
|
695
|
+
| `variant` (`Tabs`) | `'underline' \| 'segmented' \| 'bar' \| 'line'` | `'underline'` | the look; `bar` and `line` are deprecated aliases of `underline` and `segmented` |
|
|
621
696
|
| `default-value` / `v-model` (`Tabs`) | `string \| number` | — | the selected tab, uncontrolled / controlled |
|
|
622
|
-
| `orientation` (`Tabs`) | `'horizontal' \| 'vertical'` | `'horizontal'` | arrow
|
|
697
|
+
| `orientation` (`Tabs`) | `'horizontal' \| 'vertical'` | `'horizontal'` | a row or a column, and the arrow keys' direction |
|
|
698
|
+
| `dir` (`Tabs`) | `'ltr' \| 'rtl'` | `'ltr'` | the reading direction: which way the tabs run and the arrow keys move |
|
|
623
699
|
| `activation-mode` (`Tabs`) | `'automatic' \| 'manual'` | `'automatic'` | select on focus, or only on Enter / click |
|
|
624
700
|
| `value` (`TabsTrigger`, `TabsContent`) | `string \| number` | — (required) | pairs a trigger with its panel |
|
|
701
|
+
| `icon` (`TabsTrigger`) | `Component` | — | shown before the label; or the `#icon` slot |
|
|
625
702
|
|
|
626
703
|
**Emits:** `update:modelValue` on `Tabs` — `string | number`.
|
|
627
|
-
**Storybook:** `Primitives/Tabs` (
|
|
704
|
+
**Storybook:** `Primitives/Tabs` (stories `Underline`, `Segmented`, `WithIcons`, `Scrolling`, `RightToLeft`, `Vertical`, `DeprecatedAliases`, `Hydrated`) · **React:** `Tabs` (same parts, the same variants)
|
|
628
705
|
|
|
629
706
|
---
|
|
630
707
|
|
|
@@ -272,6 +272,16 @@ const emojis: EmojiItem[] = [
|
|
|
272
272
|
Parts: `EmojiPicker` (a button opening a popover), `EmojiList` (the searchable
|
|
273
273
|
grid on its own, same `emojis` / `@select`), `expandEmojiShortcodes`.
|
|
274
274
|
|
|
275
|
+
The category tabs are icon-only. Each is named from
|
|
276
|
+
`material.emoji.groups.<group>` (for example `people_body`, `flags`): that name
|
|
277
|
+
is its `aria-label` and `title`, in the provider's language (see
|
|
278
|
+
[i18n](../guides/i18n.md)). An `EmojiItem` with no `group` falls under
|
|
279
|
+
`general`; a `group` is lowercased, with ` & ` and spaces turned into `_`
|
|
280
|
+
("Food & Drink" becomes `food_drink`). A group the bundles do not name gets
|
|
281
|
+
its words, capitalised, as an English name (`hand_signs` becomes "Hand
|
|
282
|
+
signs"), and an icon of its own is not provided: it shows the generic grid
|
|
283
|
+
icon.
|
|
284
|
+
|
|
275
285
|
| Prop | Type | Default | Effect |
|
|
276
286
|
| --- | --- | --- | --- |
|
|
277
287
|
| `emojis` | `EmojiItem[]` | — (required) | the set offered; `group` drives the category tabs, `shortcodes` the search |
|
|
@@ -31,6 +31,8 @@ value type each one writes. Everything is imported from
|
|
|
31
31
|

|
|
32
32
|
|
|
33
33
|
Select primitives with groups, labels and scroll buttons, plus a styled native `<select>`.
|
|
34
|
+
Its list is modal: while it is open the rest of the page is `inert` (a
|
|
35
|
+
`Select` in a `Dialog` makes the dialog inert too, until the list closes).
|
|
34
36
|
|
|
35
37
|
```vue
|
|
36
38
|
<script setup lang="ts">
|
|
@@ -140,7 +142,7 @@ const framework = ref('nuxt');
|
|
|
140
142
|
| `options` | `{ value: string; label: string }[]` | — | required; the items |
|
|
141
143
|
| `v-model` | `string` | `''` | the selected `value`; `''` for nothing selected |
|
|
142
144
|
| `placeholder` | `string` | `label` | the trigger's text while nothing is selected |
|
|
143
|
-
| `required` | `boolean` | — | an asterisk after the label, and the select's `required
|
|
145
|
+
| `required` | `boolean` | — | an asterisk after the label, and the select's `required`; the trigger is named by the label |
|
|
144
146
|
| `label`, `helper-text`, `error`, `disabled` | | — | the field's text and state |
|
|
145
147
|
|
|
146
148
|
`#option` renders each option's content in the list (React's
|
|
@@ -161,6 +163,10 @@ the picked option's text.
|
|
|
161
163
|
|
|
162
164
|
`SelectFormField` passes the slot through.
|
|
163
165
|
|
|
166
|
+
**Accessibility:** the trigger is named by `label`, described by the help or
|
|
167
|
+
error text (`aria-describedby`) and marked `aria-invalid` on error, so
|
|
168
|
+
`getByRole('combobox', { name: 'Framework' })` finds it and
|
|
169
|
+
`toHaveAccessibleDescription(…)` / `toBeInvalid()` match.
|
|
164
170
|
**Emits:** `update:modelValue` with `string`.
|
|
165
171
|
**Form wrapper:** `SelectFormField` — `name` inside a vee-validate form, see
|
|
166
172
|
[forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
|
|
@@ -571,6 +577,11 @@ const picked = ref<string[]>([]);
|
|
|
571
577
|
| `options` | `{ value: string; label: string }[]` | — | required; the chips |
|
|
572
578
|
| `label`, `helper-text`, `error`, `disabled` | | — | the field's text and state |
|
|
573
579
|
|
|
580
|
+
**Accessibility:** the chips are toggle buttons in a `role="group"` named by
|
|
581
|
+
`label` and described by the help text: a chosen chip is
|
|
582
|
+
`aria-pressed="true"` (never `aria-selected`), in `single` mode too.
|
|
583
|
+
Query `getByRole('group', { name: 'Topics' })`, then
|
|
584
|
+
`getByRole('button', { name: 'Design' })`; there is no `option` role.
|
|
574
585
|
**Emits:** `update:modelValue` with `string[]`. `SelectChipFormField` stores
|
|
575
586
|
`string[]` in both modes (React stores `string | null` in `single` mode).
|
|
576
587
|
**Form wrapper:** `SelectChipFormField` — `name` inside a vee-validate form, see
|
package/docs/getting-started.md
CHANGED
|
@@ -21,7 +21,7 @@ the **required peers**. The app must resolve a single copy of each, otherwise
|
|
|
21
21
|
| `vue-router` | `^4.5 \|\| ^5` | the app shell, `LinkButton`, `useSearchParam`, `useNavigationHandler` |
|
|
22
22
|
| `vee-validate` | `^4.15` | every `*FormField` wrapper and the built-in forms ([forms](guides/forms.md)) |
|
|
23
23
|
| `@tanstack/vue-table` | `^9` | `DataTable` |
|
|
24
|
-
| `@vueuse/core` | `^13 \|\| ^14` | `useTheme`, `useIsMobile`, `ScrollProgress`,
|
|
24
|
+
| `@vueuse/core` | `^13 \|\| ^14` | `useTheme`, `useIsMobile`, `ScrollProgress`, prop forwarding in primitives |
|
|
25
25
|
|
|
26
26
|
**Optional peers** are only needed by the entry that uses them. An app that
|
|
27
27
|
does not import that entry never has them in its tree.
|
|
@@ -350,7 +350,7 @@ touched, and validated, when focus leaves it, so a required field shows its
|
|
|
350
350
|
error as the user moves past it rather than only on submit
|
|
351
351
|
([details](troubleshooting.md#metatouched-stays-false-after-leaving-a-field)).
|
|
352
352
|
An `AlertDialogContent` whose `@open-auto-focus` calls `event.preventDefault()`
|
|
353
|
-
now keeps focus
|
|
353
|
+
now keeps focus off its Cancel, as the other modals do
|
|
354
354
|
([details](troubleshooting.md#an-alertdialog-focuses-its-cancel-although-open-auto-focus-prevented-it)).
|
|
355
355
|
A `DrawerContent` hands focus back, on close, to the element that opened it,
|
|
356
356
|
where it used to fall to `<body>`
|
package/docs/guides/app-shell.md
CHANGED
|
@@ -66,7 +66,7 @@ and `definePageMeta({ breadcrumb: 'Invoices' })` in each page.
|
|
|
66
66
|
|
|
67
67
|
<a id="it-needs-a-data-router"></a>
|
|
68
68
|
|
|
69
|
-
The sidebar
|
|
69
|
+
The sidebar entries are `RouterLink`s, and the sidebar and
|
|
70
70
|
`AppBreadcrumb` read `useRoute()`. Install vue-router before mount
|
|
71
71
|
(`app.use(router)`, see [getting started](../getting-started.md#pinia-and-router));
|
|
72
72
|
without it the shell cannot render. Any history works: `createWebHistory`,
|
|
@@ -175,12 +175,39 @@ partitionMenu(items); // { content, footer }
|
|
|
175
175
|
| --- | --- | --- | --- |
|
|
176
176
|
| `items` | `MenuItem[]` | — (required) | Sidebar entries, and the mobile menu sheet |
|
|
177
177
|
| `avatar-src` | `string` | — | End of the header: the image URL of an `Avatar` |
|
|
178
|
-
| `
|
|
178
|
+
| `link-component` | `AppShellLinkComponent` (a `Component`) | `RouterLink` | The link each menu entry renders, given `href` (see below) |
|
|
179
|
+
| `content-as` | `'main' \| 'div'` | `'main'` | The content region's element: the page's `<main>` landmark, or a `div` when your app renders its own `<main>` |
|
|
180
|
+
| `scroll` | `'document' \| 'container'` | `'document'` | Who scrolls: the page (header and sidebar sticky), or the content region inside a shell one screen high |
|
|
181
|
+
| `class` | `string` | — | Classes for the outer element (`[data-slot=activity-layout]`), which takes the other attributes too |
|
|
179
182
|
| `#brand` | slot | an atom icon | Replaces the sidebar header: a logo, a product name. Always fill it; the default is a placeholder |
|
|
180
183
|
| `#header-actions` | slot | — | In the header, before the avatar: notifications, help, `LanguageSwitcher`, `ThemeToggle` |
|
|
181
184
|
| `#avatar` | slot | the `avatar-src` image | Replaces the avatar entirely |
|
|
182
185
|
| default | slot | — | The routed content, usually `<RouterView />` |
|
|
183
186
|
|
|
187
|
+
Each menu entry is a real link, `<a href="/invoices">`, so it works before
|
|
188
|
+
hydration on a server-rendered page, opens in a new tab on middle-click, and
|
|
189
|
+
is announced as a link, named by its label alone ("Identities": the icon beside
|
|
190
|
+
it is decorative); the current page's entry carries `aria-current="page"`.
|
|
191
|
+
By default it is a `RouterLink` (client-side navigation, no reload), so
|
|
192
|
+
`link-component` is only needed for another router or a custom link: pass it a
|
|
193
|
+
component that takes `href` and renders a single root element with its default slot. Vue falls the rest of the
|
|
194
|
+
attributes (`class`, `aria-current`, the tooltip's handlers, `data-state`)
|
|
195
|
+
through to that root, so the root must be one element, not a fragment, and must
|
|
196
|
+
take them: do not set `inheritAttrs: false` without binding `$attrs` yourself.
|
|
197
|
+
A link that forwards only a `DataTable`'s link props is not enough here.
|
|
198
|
+
|
|
199
|
+
The prop's type is exported as `AppShellLinkComponent` (any `Component`):
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import type { AppShellLinkComponent } from '@nxgt/material-vue/components';
|
|
203
|
+
import { defineComponent, h } from 'vue';
|
|
204
|
+
|
|
205
|
+
export const MyLink: AppShellLinkComponent = defineComponent({
|
|
206
|
+
props: { href: { type: String, required: true } },
|
|
207
|
+
setup: (props, { slots }) => () => h('a', { href: props.href }, slots.default?.()),
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
184
211
|
The shell holds no session: read the user wherever your app keeps it and fill
|
|
185
212
|
the slots.
|
|
186
213
|
|
|
@@ -219,10 +246,56 @@ defineProps<{ items: MenuItem[]; user: { name: string; picture?: string } }>();
|
|
|
219
246
|
</template>
|
|
220
247
|
```
|
|
221
248
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
249
|
+
On screens below 768 px the sidebar is hidden and the header shows a menu
|
|
250
|
+
button ("Open menu") that opens the same entries in a sheet. Breadcrumbs are
|
|
251
|
+
hidden there. The sheet is a dialog named "Navigation"
|
|
252
|
+
(`material.app-shell.navigation`) with a visible "Close" button. Following an
|
|
253
|
+
entry closes it — so does any route change, Back included — and focus goes to
|
|
254
|
+
the page's `<main>`; closed with Escape, the close button or the overlay,
|
|
255
|
+
focus returns to the menu button. Its entries show their labels, so they have
|
|
256
|
+
no tooltip: none opens over the first entry as focus moves in.
|
|
257
|
+
|
|
258
|
+
## Landmarks, headings and scrolling
|
|
259
|
+
|
|
260
|
+
- **One `<main>`.** The content region is the page's `<main>` landmark
|
|
261
|
+
(`getByRole('main')`). If your app already renders a `<main>` around the
|
|
262
|
+
shell, pass `content-as="div"`: a page has only one.
|
|
263
|
+
- **The screen's title is the `h1`.** `ActivityContent`'s `title` is a heading,
|
|
264
|
+
level 1 by default (`title-as`); the shell itself renders no heading, so a
|
|
265
|
+
screen's title is the page's top one. A `CardTitle` is an `h3` (its `as`
|
|
266
|
+
changes the level).
|
|
267
|
+
- **The document scrolls.** The shell sits on `Background` but is `100%` wide
|
|
268
|
+
and at least `100dvh` high, so it never adds a scrollbar's width of
|
|
269
|
+
horizontal scroll and its bottom is never under a phone's toolbar. The page
|
|
270
|
+
grows with the screen and the browser scrolls it; the header and the
|
|
271
|
+
desktop sidebar are sticky. `scroll="container"` is the opt-in for a screen
|
|
272
|
+
that manages its own height (a full-height map, a board): the shell is then
|
|
273
|
+
exactly `100dvh` high and the content region scrolls inside it.
|
|
274
|
+
- **A new screen starts at the top.** With the document scrolling, the
|
|
275
|
+
router's `scrollBehavior` handles positions as for any page. With
|
|
276
|
+
`scroll="container"` the content region outlives navigations, and
|
|
277
|
+
`scrollBehavior` only knows the window, so the shell does it for that box: a
|
|
278
|
+
new path opens at the top, and Back or Forward returns to where that entry
|
|
279
|
+
was left (kept per history entry — `history.state.position`, or the
|
|
280
|
+
`fullPath` with a memory history — for the session). A change of query or
|
|
281
|
+
hash only (a filter, a tab, an `#anchor`) keeps the position.
|
|
282
|
+
|
|
283
|
+
```vue
|
|
284
|
+
<script setup lang="ts">
|
|
285
|
+
import { ActivityLayout, type MenuItem } from '@nxgt/material-vue/components';
|
|
286
|
+
|
|
287
|
+
defineProps<{ items: MenuItem[] }>();
|
|
288
|
+
</script>
|
|
289
|
+
|
|
290
|
+
<template>
|
|
291
|
+
<!-- The app renders its own <main>, and the board below needs a fixed height. -->
|
|
292
|
+
<main>
|
|
293
|
+
<ActivityLayout :items="items" content-as="div" scroll="container">
|
|
294
|
+
<RouterView />
|
|
295
|
+
</ActivityLayout>
|
|
296
|
+
</main>
|
|
297
|
+
</template>
|
|
298
|
+
```
|
|
226
299
|
|
|
227
300
|
## `ActivityContent`
|
|
228
301
|
|
|
@@ -231,6 +304,7 @@ Breadcrumbs are hidden there.
|
|
|
231
304
|
| `title` | `string` | — | Header card title |
|
|
232
305
|
| `subtitle` | `string` | — | Under the title |
|
|
233
306
|
| `hide-header` | `boolean` | `false` | No header card |
|
|
307
|
+
| `title-as` | `'h1'` … `'h6'` \| `'div'` | `'h1'` | The title's element: the screen's heading, `'h2'` under a heading of yours, `'div'` for none |
|
|
234
308
|
| `#actions` | slot | — | Right side of the header card: buttons |
|
|
235
309
|
| `#header` | slot | — | Replaces the header card entirely |
|
|
236
310
|
| default | slot | — | The screen's content |
|
|
@@ -250,6 +324,9 @@ import { ActivityContent, Button } from '@nxgt/material-vue/components';
|
|
|
250
324
|
</template>
|
|
251
325
|
```
|
|
252
326
|
|
|
327
|
+
The header card wraps: on a narrow screen the actions go under the title
|
|
328
|
+
instead of squeezing it, and a long title wraps.
|
|
329
|
+
|
|
253
330
|
## Breadcrumbs
|
|
254
331
|
|
|
255
332
|
`AppBreadcrumb` (rendered by `ActivityLayout`) starts with a home icon linking
|
|
@@ -302,3 +379,22 @@ takes `expanded` (`v-model:expanded`) and `with-toggle-icon`, and has the
|
|
|
302
379
|
bar. The lower-level `Sidebar`, `TopAppBar`, `NavigationRail` and `Breadcrumb`
|
|
303
380
|
primitives are on the
|
|
304
381
|
[navigation & layout](../components/navigation-and-layout.md) page.
|
|
382
|
+
|
|
383
|
+
`AppHeader` and `AppSidebar` also work without Vue Router, given a
|
|
384
|
+
`link-component` of your own (the default, `RouterLink`, needs one). Without a
|
|
385
|
+
router no entry is marked active, and the header's menu sheet closes only when
|
|
386
|
+
an entry is followed rather than on every route change. `AppBreadcrumb` is
|
|
387
|
+
built from the route and needs Vue Router.
|
|
388
|
+
|
|
389
|
+
```vue
|
|
390
|
+
<script setup lang="ts">
|
|
391
|
+
import { AppHeader, type MenuItem } from '@nxgt/material-vue/components';
|
|
392
|
+
import PlainLink from './plain-link.vue'; // renders <a :href="href"><slot /></a>
|
|
393
|
+
|
|
394
|
+
defineProps<{ items: MenuItem[] }>();
|
|
395
|
+
</script>
|
|
396
|
+
|
|
397
|
+
<template>
|
|
398
|
+
<AppHeader :items="items" :link-component="PlainLink" />
|
|
399
|
+
</template>
|
|
400
|
+
```
|
package/docs/guides/forms.md
CHANGED
|
@@ -52,7 +52,7 @@ What a wrapper does for you, through `useField(() => props.name)`:
|
|
|
52
52
|
| `error` | `true` when the field has an error message, or is touched and invalid (the red border) |
|
|
53
53
|
| `helperText` | the field's error message, or your own `helperText` when there is none |
|
|
54
54
|
| `name` | the `name` you passed, also used as the native `name` |
|
|
55
|
-
| `id` | passed through; `
|
|
55
|
+
| `id` | passed through; when absent, every field generates one (`useId`), never the field `name`, so two forms on one page sharing a field name keep their labels apart; a group's option *i* is `` `${id}-${i}` `` |
|
|
56
56
|
| everything else | your props, passed through unchanged (`label`, `disabled`, `options`, …) |
|
|
57
57
|
|
|
58
58
|
`RadioGroupFormField` and `CheckboxGroupFormField` need nothing more for a
|
|
@@ -201,8 +201,17 @@ const onSubmit = handleSubmit((values) => props.onSave(values));
|
|
|
201
201
|
validates, and calls `fn` only when the form is valid. `isSubmitting` stays
|
|
202
202
|
`true` until the promise `fn` returns settles.
|
|
203
203
|
- `Button` defaults to `type="button"`: the submit button needs
|
|
204
|
-
`type="submit"`.
|
|
205
|
-
`
|
|
204
|
+
`type="submit"`. With `:loading="isSubmitting"` it is busy (`aria-busy`,
|
|
205
|
+
`aria-disabled`, a spinner) from the first click until the promise settles,
|
|
206
|
+
and cancels every click meanwhile — a double click, or Enter pressed twice
|
|
207
|
+
in a field, submits once. It stays focusable. For a `loading` that turns
|
|
208
|
+
true only after an `await`, add `lock-on-click`
|
|
209
|
+
([Button](../components/buttons-and-actions.md#button)). The guard sees
|
|
210
|
+
clicks, Space and Enter on the button, and Enter in a field when it is the
|
|
211
|
+
form's first submit button; `form.requestSubmit()` and `form.submit()` fire
|
|
212
|
+
no click, so a submit your code starts (an OTP step that submits on its
|
|
213
|
+
last digit, a shortcut) checks `isSubmitting` itself. A `lock-on-click`
|
|
214
|
+
whose `loading` never turns true releases after one second.
|
|
206
215
|
- Initial values must have the type the field emits. See
|
|
207
216
|
[What each field emits](#what-each-field-emits).
|
|
208
217
|
|
|
@@ -273,8 +282,9 @@ Then, for these schemas:
|
|
|
273
282
|
|
|
274
283
|
| Input | English | French |
|
|
275
284
|
| --- | --- | --- |
|
|
276
|
-
| `email: 'nope'` for `z.email()` | `Invalid email address.` | `
|
|
277
|
-
| `name: 'ab'` for `z.string().min(3)` | `
|
|
285
|
+
| `email: 'nope'` for `z.email()` | `Invalid email address.` | `Adresse e-mail invalide.` |
|
|
286
|
+
| `name: 'ab'` for `z.string().min(3)` | `Must contain at least 3 characters.` | `Doit contenir au moins 3 caractères.` |
|
|
287
|
+
| `bio` too long for `z.string().max(80)` | `Must contain at most 80 characters.` | `Doit contenir au plus 80 caractères.` |
|
|
278
288
|
| a missing key, or `null` / `''` / `0` given to a schema of another type (`seats: null` for `z.number()`) | `This field is required.` | `Ce champ est requis.` |
|
|
279
289
|
|
|
280
290
|
A message set on the schema wins over the error map, so override one field
|
|
@@ -313,6 +323,13 @@ How it picks the language, and what that implies:
|
|
|
313
323
|
- The message is computed when validation runs. An error already on screen
|
|
314
324
|
keeps its language after a switch until the field is validated again
|
|
315
325
|
(`validate()` from `useForm` re-runs it).
|
|
326
|
+
- A string length reads as a length (`Must contain at most 80 characters.`);
|
|
327
|
+
a number or a list keeps the size wording (`This value is too large: it
|
|
328
|
+
must be at most 2 items.`). The strings are `material.zod.too_big_string_*`
|
|
329
|
+
and `material.zod.too_small_string_*` (`inclusive` / `exclusive`): an app
|
|
330
|
+
that overrode `too_big_inclusive` for strings overrides these now.
|
|
331
|
+
- A format message that opens with its noun (`Invalid email address.`,
|
|
332
|
+
`Adresse e-mail invalide.`) starts with a capital letter in every language.
|
|
316
333
|
- `invalid_type` with an empty input (`undefined`, `null`, `''`, `0`,
|
|
317
334
|
`false`) says `This field is required.`; with any other input it says
|
|
318
335
|
what was expected (`z.number()` given `'x'` gives
|
|
@@ -576,6 +593,42 @@ props, and `Field` does neither.
|
|
|
576
593
|
barrel cannot render it. Use `LocationFormField` from
|
|
577
594
|
`@nxgt/material-vue/map`.
|
|
578
595
|
|
|
596
|
+
## Focusing the first invalid field
|
|
597
|
+
|
|
598
|
+
vee-validate never moves focus: a failed `handleSubmit` reports
|
|
599
|
+
`{ errors }` to its second callback and that is all (React Hook Form's
|
|
600
|
+
`shouldFocusError` has no equivalent). Every `*FormField` marks the element it
|
|
601
|
+
renders with `data-field-name="<name>"`, and two helpers from
|
|
602
|
+
`@nxgt/material-vue/lib` use it to focus the first control a user can reach in
|
|
603
|
+
a field, whatever its shape (an input, a select trigger, the first chip, a
|
|
604
|
+
file picker's button, the first of two date inputs):
|
|
605
|
+
|
|
606
|
+
```vue
|
|
607
|
+
<script setup lang="ts">
|
|
608
|
+
import { focusField, focusFirstInvalid } from '@nxgt/material-vue/lib';
|
|
609
|
+
import { useForm } from 'vee-validate';
|
|
610
|
+
|
|
611
|
+
const { handleSubmit, setFieldError } = useForm<Values>();
|
|
612
|
+
|
|
613
|
+
// A failed submit: the first field, in page order, that has an error.
|
|
614
|
+
const onSubmit = handleSubmit(save, ({ errors }) => focusFirstInvalid(errors));
|
|
615
|
+
|
|
616
|
+
// A server error on one field:
|
|
617
|
+
function reject() {
|
|
618
|
+
setFieldError('email', 'Already used');
|
|
619
|
+
focusField('email');
|
|
620
|
+
}
|
|
621
|
+
</script>
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
- `focusFirstInvalid(errors, scope?)` takes the first field in **page order**
|
|
625
|
+
whose name is in `errors`, not the first key of `errors`. Pass the form
|
|
626
|
+
element as `scope` on a page with several forms.
|
|
627
|
+
- `focusField(name, scope?)` focuses one field. Both return whether a control
|
|
628
|
+
took focus: a field that is not mounted (a closed tab) cannot.
|
|
629
|
+
- A field of your own that wants the same calls `useFieldBlur` (it stamps the
|
|
630
|
+
`name` prop) or sets `data-field-name` itself.
|
|
631
|
+
|
|
579
632
|
## Server errors
|
|
580
633
|
|
|
581
634
|
Map an API's field errors onto the form with `setErrors` (or `setFieldError`
|
package/docs/guides/helpers.md
CHANGED
|
@@ -41,6 +41,7 @@ differences are listed under each table. React's `/hooks` is
|
|
|
41
41
|
| `STYLES_UTILS.cssSize(value)` | `240` → `'240px'`, a string unchanged, `undefined` for empty. Vue does not add `px` to a numeric inline style the way React does. |
|
|
42
42
|
| `searchParams(request)`, `searchParam(request, key)` | Reads the query string of a Fetch `Request` (a server handler, an edge function). |
|
|
43
43
|
| `isExternalHref(href)` | True for `http(s):`, protocol-relative `//` and `mailto:`: open in a new tab, not in-app. |
|
|
44
|
+
| `focusFirstInvalid(errors, scope?)`, `focusField(name, scope?)` | Focus the first control a user can reach in the first field, in page order, that has an error in `errors` (vee-validate's `onInvalid` `{ errors }`), or in the field `name`; each `*FormField` marks its element with its name for them. `scope` limits the lookup to one form; both return whether a control took focus. See [forms](forms.md#focusing-the-first-invalid-field). |
|
|
44
45
|
| `buildFieldOptions(values, { valueKey, label })` | Maps records to `{ value, label }` options; `valueKey` and a string `label` are paths (`'owner.name'`), or `label` is a function. |
|
|
45
46
|
| `zodLocaleError(options?)` | Zod v4 `customError` that translates issues through `material.zod.*`: `z.config({ customError: zodLocaleError() })`. Options `storageKey`, `language`, `translate` (type `ZodLocaleErrorOptions`): see [below](#zodlocaleerror-limits). |
|
|
46
47
|
| `parsedType(data)` | Runtime type name used for Zod's `{received}` placeholder. |
|
|
@@ -56,7 +57,7 @@ defineProps<{
|
|
|
56
57
|
</script>
|
|
57
58
|
|
|
58
59
|
<template>
|
|
59
|
-
<tr :class="cn('border-b', order.late && 'text-error')">
|
|
60
|
+
<tr :class="cn('border-b', order.late && 'text-error-text')">
|
|
60
61
|
<td>
|
|
61
62
|
<template v-for="(part, i) in splitMatch(order.ref, query)" :key="i">
|
|
62
63
|
<mark v-if="part.match">{{ part.text }}</mark>
|
package/docs/guides/i18n.md
CHANGED
|
@@ -278,10 +278,13 @@ previous and next buttons, `DateRangeField`'s included (`material.calendar.*`),
|
|
|
278
278
|
checkboxes, the filter's presets and `Min`/`Max` fields, the `variant="4"` spinner's label,
|
|
279
279
|
`ScrollToTop`, the network upload field's `Retry`/`Remove`, the map's
|
|
280
280
|
`You are here` and zoom buttons, and the close button of dialogs, sheets and
|
|
281
|
-
`DialogFooter`.
|
|
281
|
+
`DialogFooter`. `CopyButton` reads `material.copy.copy`, `copied` and `failed` ("Copy", "Copied",
|
|
282
|
+
"Copy failed"). The other icon-only buttons are named from it too: the
|
|
282
283
|
`Sidebar` toggle (`material.sidebar.toggle`), the data table's row expander
|
|
283
284
|
(`material.table.labels.toggle-row`), its loading status, "Loading data…" /
|
|
284
|
-
"Chargement des données…" (`material.table.loading`),
|
|
285
|
+
"Chargement des données…" (`material.table.loading`), its scroll region,
|
|
286
|
+
"Scrollable table" / "Tableau défilant"
|
|
287
|
+
(`material.table.labels.scroll-region`, or the `scroll-label` prop), a `Chip`'s remove button, which the
|
|
285
288
|
`Autocomplete` chips use ("Remove {label}", `material.chip.dismiss-label`),
|
|
286
289
|
the `LocationPreview` trigger (`material.map.preview` / `preview-named`),
|
|
287
290
|
`DurationField`'s inputs, "Hours" and "Minutes"
|
|
@@ -402,10 +405,27 @@ and description and against the English ones, ignoring accents: in French,
|
|
|
402
405
|
|
|
403
406
|
Some strings are still English in every language, among them the filter's
|
|
404
407
|
validation messages, the camera's error messages, the editor's callout type
|
|
405
|
-
names and default block content
|
|
406
|
-
tabs. File sizes (`2.0 KB`) and the filter chips' dates are not formatted for
|
|
408
|
+
names and default block content. File sizes (`2.0 KB`) and the filter chips' dates are not formatted for
|
|
407
409
|
the provider's language.
|
|
408
410
|
|
|
411
|
+
The emoji picker's category tabs are icon-only; their name, read by screen
|
|
412
|
+
readers and shown as a tooltip, comes from `material.emoji.groups.<group>`.
|
|
413
|
+
Both languages ship `all`, `recent`, `general`, `people_body`, `components`,
|
|
414
|
+
`animals_nature`, `food_drink`, `travel_places`, `activities`, `objects`,
|
|
415
|
+
`symbols` and `flags`:
|
|
416
|
+
|
|
417
|
+
```ts
|
|
418
|
+
// English / French values of the shipped keys
|
|
419
|
+
material.emoji.groups.people_body; // "People & body" / "Personnes et corps"
|
|
420
|
+
material.emoji.groups.food_drink; // "Food & drink" / "Nourriture et boissons"
|
|
421
|
+
material.emoji.groups.flags; // "Flags" / "Drapeaux"
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
A group your `EmojiItem`s name that is not in this list (`hand_signs`) reads
|
|
425
|
+
as its words in English (`Hand signs`). As for every `material.*` key above,
|
|
426
|
+
the components read their own bundle: your own translator sees a key you add,
|
|
427
|
+
the tab does not.
|
|
428
|
+
|
|
409
429
|
That override is visible only through **your** translator. The components
|
|
410
430
|
translate through the package's own bundle, so defining
|
|
411
431
|
`material.command.title` in your resources changes what
|
package/docs/guides/ssr.md
CHANGED
|
@@ -275,7 +275,7 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
275
275
|
| `getActivePinia()` was called but there was no active Pinia | a widget rendered on the server before Pinia was installed | `app.use(createPinia())` in the server entry too; in Nuxt the module does it |
|
|
276
276
|
| Text flips language right after load | no `initial-language` | Pass the request's language ([i18n](i18n.md#server-rendering)) |
|
|
277
277
|
| Hydration mismatch around an icon | an `<svg><title>` in your markup | `role="img"` + `aria-label` |
|
|
278
|
-
| Hydration mismatch on a phone around
|
|
278
|
+
| Hydration mismatch on a phone around the app shell or a `DataTable`, or on a dark screen around a code block, 0.21.0 or earlier | the server answered "desktop, light", the phone's first render its own screen | upgrade ([troubleshooting](../troubleshooting.md#hydration-completed-but-contains-mismatches)) |
|
|
279
279
|
| Hydration mismatch on a page with a `TimeField`, 0.21.0 or earlier | the server rendered the phone (dialog) trigger, a desktop client the popover one | upgrade ([troubleshooting](../troubleshooting.md#hydration-completed-but-contains-mismatches)) |
|
|
280
280
|
|
|
281
281
|
More entries, with the full message and fix, are in
|