@nxgt/material-vue 0.26.1 → 0.27.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 +175 -0
- package/README.md +8 -5
- package/dist/Button-DEUkxkrJ.js +728 -0
- package/dist/{DropdownMenuTrigger-BHAhXGUO.js → DropdownMenuTrigger-BsuyNVzV.js} +48 -41
- package/dist/HoverCardTrigger-TDll55LP.js +510 -0
- package/dist/{camera-DvKuqT4L.js → camera-UJXnnFeb.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-BZ1XAK37.js} +19198 -18568
- 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-BFGca0h6.js} +1 -1
- package/dist/{i18n-provider-CaaZCmaR.js → i18n-provider-DBusa725.js} +1 -1
- package/dist/i18n.js +4 -4
- package/dist/icon-button-jHB4MhQ5.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/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/i18n/resources/en/en.d.ts +9 -0
- package/dist/lib/i18n/resources/en/material.json.d.ts +13 -4
- package/dist/lib/i18n/resources/fr/fr.d.ts +9 -0
- package/dist/lib/i18n/resources/fr/material.json.d.ts +13 -4
- package/dist/lib/i18n/resources/resources.d.ts +18 -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-ChjAXoUX.js} +2 -2
- package/dist/{pdf-page-BrJbyjQj.js → pdf-page-D4OTPcpF.js} +1 -1
- package/dist/{pdf-page-list-BCgYEszV.js → pdf-page-list-CFgKYLur.js} +2 -2
- package/dist/{pdf-reader-BAzevSJU.js → pdf-reader-B1hw81cG.js} +9 -9
- package/dist/{pdf-sidebar-DSh2Mend.js → pdf-sidebar-Do5HzrDq.js} +3 -3
- package/dist/{pdf-thumbnail-sidebar-D_EMcbKF.js → pdf-thumbnail-sidebar-CMge0C9-.js} +3 -3
- package/dist/{pdf-toolbar-BcL7HlfS.js → pdf-toolbar-BBio9baU.js} +5 -5
- package/dist/pdf.js +2 -2
- package/dist/{sortable-provider-DMpmM7Ke.js → sortable-provider-DMVbbmUU.js} +2 -2
- package/dist/{stored-language-Bxhsx4iB.js → stored-language-AJpwOMsA.js} +26 -8
- package/dist/{stx-player-MldK_ZAZ.js → stx-player-gtyStl7e.js} +2 -2
- package/dist/styles.css +10 -368
- package/dist/{translator-CLI_7Crg.js → translator-D2_swsMz.js} +1 -1
- package/dist/{use-heading-spy-VPuFj2Gt.js → use-heading-spy-BbEbAfPK.js} +19 -19
- package/dist/use-inert-others-CtGqZKR8.js +22 -0
- package/dist/{use-material-translation-CONqD9wJ.js → use-material-translation-BHtgyMz3.js} +3 -3
- package/docs/components/buttons-and-actions.md +71 -16
- package/docs/components/data-display.md +12 -1
- 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 +16 -2
- 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 +5 -2
- package/docs/guides/ssr.md +1 -1
- package/docs/guides/theming.md +134 -6
- package/docs/previews/data-display-description.webp +0 -0
- package/docs/previews/feedback-status-toast.webp +0 -0
- package/docs/roadmap.md +21 -0
- package/docs/troubleshooting.md +854 -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
|
@@ -751,6 +751,13 @@ Playback uses the browser's `<video>` / `<audio>`: a format the browser cannot
|
|
|
751
751
|
decode (H.264 MP4 in a Chromium build without proprietary codecs) shows the
|
|
752
752
|
player's error state with a Retry button. `StxPlayer` mounts on the client only.
|
|
753
753
|
|
|
754
|
+
The speed and captions menus are radio groups: each entry is a
|
|
755
|
+
`menuitemradio` and the chosen one `aria-checked="true"` (captions: "Off" or
|
|
756
|
+
one track's `label`). `c` toggles the captions; turned on before a language
|
|
757
|
+
was chosen, they show the current track's first caption track, which the menu
|
|
758
|
+
then checks. In a test, pick one with
|
|
759
|
+
`getByRole('menuitemradio', { name: 'English' })`.
|
|
760
|
+
|
|
754
761
|
**Storybook:** `Media & files/StxPlayer` · **React:** `StxPlayer` (same parts)
|
|
755
762
|
|
|
756
763
|
---
|
|
@@ -72,11 +72,14 @@ which work alone; `findActiveMenuItem`, `isMenuItemActive` and
|
|
|
72
72
|
| Prop | Type | Default | Effect |
|
|
73
73
|
| --- | --- | --- | --- |
|
|
74
74
|
| `items` | `MenuItem[]` | — | `label` (already translated), `url`, `icon` (a Font Awesome name), `footer` |
|
|
75
|
+
| `linkComponent` | `Component` | `RouterLink` | The link each menu entry renders, given `href`; pass it as `link-component` (see the [app shell guide](../guides/app-shell.md)) |
|
|
75
76
|
| `avatarSrc` | `string` | — | avatar image in the header |
|
|
76
77
|
| `#brand` | slot | — | sidebar header |
|
|
77
78
|
| `#header-actions`, `#avatar` | slots | — | header, before the avatar; `#avatar` replaces it |
|
|
78
79
|
| `title`, `subtitle`, `#actions` | `string`, `string`, slot | — | `ActivityContent`'s header card |
|
|
79
80
|
| `#header` / `hideHeader` | slot / `boolean` | — / `false` | `ActivityContent`: replace or drop the header card |
|
|
81
|
+
| `contentAs` / `scroll` | `'main' \| 'div'` / `'document' \| 'container'` | `'main'` / `'document'` | the content region is the page's `<main>`; the document scrolls (see the [app shell guide](../guides/app-shell.md#landmarks-headings-and-scrolling)) |
|
|
82
|
+
| `titleAs` | `'h1'` … `'h6'` \| `'div'` | `'h1'` | `ActivityContent`: the title's heading level |
|
|
80
83
|
|
|
81
84
|
The active entry is the longest match across the whole menu, segment-aware.
|
|
82
85
|
`AppBreadcrumb` reads `meta.breadcrumb` (a string, or a function of the
|
|
@@ -86,6 +89,11 @@ to the router's root, so a history base is kept; a crumb with params links to
|
|
|
86
89
|
its path with them filled in) and the current page is text with
|
|
87
90
|
`aria-current="page"`.
|
|
88
91
|
|
|
92
|
+
`AppSidebar` gives each entry its label as a tooltip **only while it is
|
|
93
|
+
collapsed**: expanded (and in the phone menu sheet) the label is on screen
|
|
94
|
+
and no tooltip opens. A `SidebarMenuItem` of your own shows the `tooltip` you
|
|
95
|
+
pass, collapsed or expanded.
|
|
96
|
+
|
|
89
97
|
**Storybook:** `Navigation & layout/AppShell` · **React:** `ActivityLayout`, `ActivityContent`, `AppSidebar`, `AppHeader`, `AppBreadcrumb`
|
|
90
98
|
|
|
91
99
|
---
|
|
@@ -135,9 +143,19 @@ const expanded = ref(false);
|
|
|
135
143
|
```
|
|
136
144
|
|
|
137
145
|
Parts: `Sidebar`, `SidebarHeader`, `SidebarContent`, `SidebarFooter`,
|
|
138
|
-
`SidebarMenuLabel` (`#actions` slot), `SidebarMenuItem` (a button
|
|
146
|
+
`SidebarMenuLabel` (`#actions` slot), `SidebarMenuItem` (a button, or a link with `href` and `link-component`: `#icon`
|
|
139
147
|
slot, `active`, `tooltip`, `@click`).
|
|
140
148
|
|
|
149
|
+
A `SidebarMenuItem` stays a `<button>` without `href`. With `href` it renders a
|
|
150
|
+
real link, a plain `<a>` unless you pass `link-component` (given the `href`):
|
|
151
|
+
|
|
152
|
+
```vue
|
|
153
|
+
<SidebarMenuItem href="/inbox" :link-component="MyLink">
|
|
154
|
+
<template #icon><Icon name="inbox" /></template>
|
|
155
|
+
Inbox
|
|
156
|
+
</SidebarMenuItem>
|
|
157
|
+
```
|
|
158
|
+
|
|
141
159
|
| Prop | Type | Default | Effect |
|
|
142
160
|
| --- | --- | --- | --- |
|
|
143
161
|
| `v-model:expanded` | `boolean` | `false` | labels beside the icons; collapsed shows icons with tooltips |
|
|
@@ -9,6 +9,19 @@ uncontrolled. Dialogs that exist to confirm something live on
|
|
|
9
9
|
[Feedback & status](feedback-and-status.md). Everything is imported from
|
|
10
10
|
`@nxgt/material-vue/components`.
|
|
11
11
|
|
|
12
|
+
**The page behind a modal layer is inert.** While a `Dialog`, `Sheet`,
|
|
13
|
+
`Drawer`, `AlertDialog` or `CommandDialog` is open — and a `Select` list, a
|
|
14
|
+
`ContextMenu`, or a `DropdownMenu` or `Popover` given `modal` — everything else
|
|
15
|
+
on the page is `inert` as well as `aria-hidden`: its controls leave the tab
|
|
16
|
+
order and the accessibility tree, so a keyboard or screen-reader user stays in
|
|
17
|
+
the layer and axe's `aria-hidden-focus` passes. Live regions stay reachable:
|
|
18
|
+
a toast shown over a dialog is announced, and its action can be used. Layers
|
|
19
|
+
stack: a `Select` open in a `Dialog` makes the dialog inert too, until it
|
|
20
|
+
closes. Focus left on the page when the layer opens (an `@open-auto-focus`
|
|
21
|
+
that prevents the default and focuses nothing) goes to the layer's content.
|
|
22
|
+
Non-modal layers (`:modal="false"`, and `DropdownMenu` and `Popover` by
|
|
23
|
+
default) leave the page as it is.
|
|
24
|
+
|
|
12
25
|
| | Component | What it does |
|
|
13
26
|
| --- | --- | --- |
|
|
14
27
|
| <img src="../previews/overlays-menus-dialog.webp" width="96" alt=""> | [Dialog](#dialog) | Modal dialog with a close button |
|
|
@@ -74,7 +87,7 @@ overlay, for a dialog taller than the screen).
|
|
|
74
87
|
| Prop | Type | Default | Effect |
|
|
75
88
|
| --- | --- | --- | --- |
|
|
76
89
|
| `default-open` / `v-model:open` (`Dialog`) | `boolean` | `false` | open state, uncontrolled / controlled |
|
|
77
|
-
| `modal` (`Dialog`) | `boolean` | `true` | `false` drops the backdrop and lets the page stay interactive |
|
|
90
|
+
| `modal` (`Dialog`) | `boolean` | `true` | `false` drops the backdrop and lets the page stay interactive; `true` makes the page behind `inert` while it is open |
|
|
78
91
|
| `as-child` (`DialogTrigger`) | `boolean` | `false` | the trigger is its child — pass it with a `Button`, or the trigger renders its own `<button>` around it |
|
|
79
92
|
| `show-close-button` (`DialogContent`) | `boolean` | `true` | the top-right X, named "Close" (`material.buttons.close`, translated) |
|
|
80
93
|
| `show-close-button` (`DialogFooter`) | `boolean` | `false` | adds an outlined "Close" button (`material.buttons.close`, translated) |
|
|
@@ -86,7 +99,10 @@ Unlike React, `DialogContent` renders the X by itself (turn it off with
|
|
|
86
99
|
On open, `DialogContent` moves focus into the dialog: to its first control
|
|
87
100
|
(the built-in X when there is nothing before it), or to the content itself
|
|
88
101
|
when it has none. An `@open-auto-focus` that calls `event.preventDefault()`
|
|
89
|
-
keeps focus
|
|
102
|
+
keeps focus off that control: a modal dialog then focuses its content, as the
|
|
103
|
+
page behind it is inert, and a non-modal one leaves focus where it was
|
|
104
|
+
([details](../troubleshooting.md#focus-moves-into-a-dialog-although-open-auto-focus-prevented-it)).
|
|
105
|
+
`SheetContent`, `DrawerContent` and
|
|
90
106
|
`AlertDialogContent` do the same; an alert dialog focuses its
|
|
91
107
|
`AlertDialogCancel`.
|
|
92
108
|
|
|
@@ -371,7 +387,7 @@ shows.
|
|
|
371
387
|
|
|
372
388
|

|
|
373
389
|
|
|
374
|
-
Menu with checkbox and radio items, submenus, shortcuts and a destructive item.
|
|
390
|
+
Menu with checkbox and radio items, submenus, shortcuts and a destructive item. It is non-modal by default: Reka's modal menu marks the rest of the page `aria-hidden` while its buttons and links stay focusable, which accessibility audits (axe `aria-hidden-focus`) report; a non-modal menu hides nothing and still closes on Escape, an outside click or Tab. Pass `modal` for a modal one: the page behind it is then `inert` too, so its controls leave the tab order and axe passes.
|
|
375
391
|
|
|
376
392
|
```vue
|
|
377
393
|
<script setup lang="ts">
|
|
@@ -421,7 +437,7 @@ Parts: `DropdownMenu`, `DropdownMenuTrigger`, `DropdownMenuContent`,
|
|
|
421
437
|
| `variant` (`DropdownMenuItem`) | `'default' \| 'error'` | `'default'` | error colour for a destructive item; `'destructive'` is a deprecated alias of `'error'`; both render `data-variant="error"`, so style or test with `[data-variant=error]` |
|
|
422
438
|
| `inset` (item, label, sub-trigger) | `boolean` | — | indents to line up with checkbox and radio items |
|
|
423
439
|
| `@select` (`DropdownMenuItem`) | `(event: Event) => void` | — | the item's action; `event.preventDefault()` keeps the menu open |
|
|
424
|
-
| `default-open` / `v-model:open`, `modal` (`DropdownMenu`) | `boolean` | —, `
|
|
440
|
+
| `default-open` / `v-model:open`, `modal` (`DropdownMenu`) | `boolean` | —, `false` | open state; `modal` brings back the scroll lock, swallowed outside clicks and an `aria-hidden`, `inert` page behind the menu |
|
|
425
441
|
| `to` (`DropdownMenuContent`) | `string \| HTMLElement` | `'body'` | where the menu is teleported; for a menu opened in fullscreen, pass the fullscreen element, read when the menu opens (see [troubleshooting](../troubleshooting.md#a-dropdownmenu-opened-in-fullscreen-does-not-show)) |
|
|
426
442
|
|
|
427
443
|
Vue's `DropdownMenuItem` has no `active` prop. CSS or a test that selected
|
|
@@ -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
|
---
|
|
@@ -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"
|
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
|