@nxgt/material-vue 0.40.1 → 0.42.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.
Files changed (96) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +3 -2
  3. package/dist/{Button-teS2p_af.js → Button-CWmzvv9p.js} +132 -94
  4. package/dist/{HoverCardTrigger-BEzEsgBo.js → HoverCardTrigger-C5AbAyHE.js} +2 -2
  5. package/dist/{camera-1QRTmpnM.js → camera-C-9slBUV.js} +4 -4
  6. package/dist/components/base-content.css +14 -4
  7. package/dist/{components-XJ95qxB1.js → components-DX4EycMi.js} +10131 -9800
  8. package/dist/components.js +8 -8
  9. package/dist/composables.js +2 -2
  10. package/dist/dnd.js +1 -1
  11. package/dist/{form-scope-BtEP6yC2.js → form-scope-CN68PTXc.js} +1 -1
  12. package/dist/{i18n-provider-BpNC9SxV.js → i18n-provider-DmZSTBnp.js} +1 -1
  13. package/dist/i18n.js +4 -4
  14. package/dist/{icon-button-Vz4NECf1.js → icon-button-DErh1aJS.js} +43 -5
  15. package/dist/lib/components/ui/actions/button/Button.vue.d.ts +1 -1
  16. package/dist/lib/components/ui/actions/button/button.compound-variants.d.ts +28 -0
  17. package/dist/lib/components/ui/actions/button/button.variants.d.ts +8 -2
  18. package/dist/lib/components/ui/actions/copy-button/copy-button.vue.d.ts +1 -1
  19. package/dist/lib/components/ui/actions/copy-button/use-copy-status.d.ts +3 -1
  20. package/dist/lib/components/ui/actions/icon-button/icon-button.compound-variants.d.ts +28 -0
  21. package/dist/lib/components/ui/actions/icon-button/icon-button.variants.d.ts +8 -2
  22. package/dist/lib/components/ui/actions/icon-button/icon-button.vue.d.ts +1 -1
  23. package/dist/lib/components/ui/actions/link/icon-link-button.vue.d.ts +1 -1
  24. package/dist/lib/components/ui/actions/link/link-button.vue.d.ts +1 -1
  25. package/dist/lib/components/ui/actions/link/responsive-link-button.vue.d.ts +1 -1
  26. package/dist/lib/components/ui/actions/load-more-button/load-more-button.vue.d.ts +1 -1
  27. package/dist/lib/components/ui/actions/scroll-to-top/scroll-to-top.vue.d.ts +1 -1
  28. package/dist/lib/components/ui/display/cards/cards.gallery.d.ts +3 -3
  29. package/dist/lib/components/ui/display/chip/chip.utils.d.ts +1 -1
  30. package/dist/lib/components/ui/display/enrolment-secret/enrolment-secret.vue.d.ts +17 -1
  31. package/dist/lib/components/ui/display/qrcode/qrcode.vue.d.ts +6 -0
  32. package/dist/lib/components/ui/display/recovery-codes/index.d.ts +1 -1
  33. package/dist/lib/components/ui/display/recovery-codes/partials/recovery-codes-actions.vue.d.ts +20 -2
  34. package/dist/lib/components/ui/display/recovery-codes/recovery-codes.vue.d.ts +9 -1
  35. package/dist/lib/components/ui/display/recovery-codes/types.d.ts +4 -0
  36. package/dist/lib/components/ui/display/recovery-codes/use-recovery-codes-labels.d.ts +1 -0
  37. package/dist/lib/components/ui/fields/contacts/consts.d.ts +1 -1
  38. package/dist/lib/components/ui/fields/otp-field/otp-field.vue.d.ts +2 -0
  39. package/dist/lib/components/ui/layout/account-menu/account-menu-avatar.vue.d.ts +9 -0
  40. package/dist/lib/components/ui/layout/account-menu/account-menu.vue.d.ts +38 -0
  41. package/dist/lib/components/ui/layout/account-menu/index.d.ts +3 -0
  42. package/dist/lib/components/ui/layout/account-menu/types.d.ts +10 -0
  43. package/dist/lib/components/ui/layout/app-shell/activity-layout.vue.d.ts +21 -5
  44. package/dist/lib/components/ui/layout/app-shell/app-brand-logo.vue.d.ts +20 -0
  45. package/dist/lib/components/ui/layout/app-shell/app-brand.vue.d.ts +30 -0
  46. package/dist/lib/components/ui/layout/app-shell/app-header.vue.d.ts +13 -5
  47. package/dist/lib/components/ui/layout/app-shell/app-sidebar.vue.d.ts +12 -2
  48. package/dist/lib/components/ui/layout/app-shell/index.d.ts +1 -1
  49. package/dist/lib/components/ui/layout/app-shell/types.d.ts +10 -0
  50. package/dist/lib/components/ui/layout/index.d.ts +1 -0
  51. package/dist/lib/components/ui/layout/stepper/stepper.vue.d.ts +7 -2
  52. package/dist/lib/components/ui/media/carousel/carousel-next.vue.d.ts +1 -1
  53. package/dist/lib/components/ui/media/carousel/carousel-previous.vue.d.ts +1 -1
  54. package/dist/lib/components/ui/primitives/input-group/input-group-button.vue.d.ts +1 -1
  55. package/dist/lib/i18n/resources/en/en.d.ts +5 -0
  56. package/dist/lib/i18n/resources/en/material.json.d.ts +5 -0
  57. package/dist/lib/i18n/resources/fr/fr.d.ts +5 -0
  58. package/dist/lib/i18n/resources/fr/material.json.d.ts +7 -2
  59. package/dist/lib/i18n/resources/resources.d.ts +10 -0
  60. package/dist/lib/i18n/translator.d.ts +1 -1
  61. package/dist/lib/nuxt/component-names.d.ts +1 -1
  62. package/dist/lib.js +1 -1
  63. package/dist/main.js +17 -17
  64. package/dist/map.js +4 -4
  65. package/dist/material-vue.css +1 -1
  66. package/dist/{pdf-outline-sidebar-DINCjWKk.js → pdf-outline-sidebar-BU4o_hBI.js} +2 -2
  67. package/dist/{pdf-page-BLJ0xkS3.js → pdf-page-anNe78Lc.js} +1 -1
  68. package/dist/{pdf-page-list-BNUPVA9x.js → pdf-page-list-CSY76HH6.js} +2 -2
  69. package/dist/{pdf-reader-BcB_Kd_F.js → pdf-reader-BhZNrW7R.js} +8 -8
  70. package/dist/{pdf-sidebar-B7WgcY77.js → pdf-sidebar-DcIpG7eh.js} +3 -3
  71. package/dist/{pdf-thumbnail-sidebar-BRtyyBPt.js → pdf-thumbnail-sidebar-CUC0V4if.js} +3 -3
  72. package/dist/{pdf-toolbar-_CBHSMnH.js → pdf-toolbar-C373S-ul.js} +4 -4
  73. package/dist/pdf.js +2 -2
  74. package/dist/{print-format.utils-k5W0GBRF.js → print-format.utils-C81c0weJ.js} +2 -2
  75. package/dist/{sortable-provider-Dw9TBAlq.js → sortable-provider-CL2UGGn8.js} +2 -2
  76. package/dist/{stored-language-DEyPmUzl.js → stored-language-Wtcj2HsU.js} +12 -2
  77. package/dist/{stx-player-CgwzX7_k.js → stx-player-CMmM3F5Z.js} +12 -12
  78. package/dist/{translator-B62GlCBp.js → translator-D5Dp-dob.js} +1 -1
  79. package/dist/{use-heading-spy-T94lNeNA.js → use-heading-spy-BIBHtUXN.js} +1 -1
  80. package/dist/{use-material-translation-DxU51--g.js → use-material-translation-COoBTGpB.js} +3 -3
  81. package/docs/components/README.md +2 -1
  82. package/docs/components/buttons-and-actions.md +2 -2
  83. package/docs/components/data-display.md +14 -7
  84. package/docs/components/data-table-filter-and-lists.md +3 -1
  85. package/docs/components/feedback-and-status.md +7 -2
  86. package/docs/components/form-fields.md +2 -1
  87. package/docs/components/navigation-and-layout.md +62 -2
  88. package/docs/guides/app-shell.md +99 -19
  89. package/docs/guides/forms.md +10 -4
  90. package/docs/guides/i18n.md +1 -1
  91. package/docs/previews/navigation-layout-accountmenu.webp +0 -0
  92. package/docs/roadmap.md +9 -0
  93. package/docs/troubleshooting.md +31 -0
  94. package/lib/components/base-content.css +14 -4
  95. package/lib/nuxt/component-names.ts +1 -0
  96. package/package.json +2 -2
@@ -17,10 +17,7 @@ const items: MenuItem[] = [
17
17
  </script>
18
18
 
19
19
  <template>
20
- <ActivityLayout :items="items" avatar-src="/me.png">
21
- <template #brand>
22
- <strong>Acme</strong>
23
- </template>
20
+ <ActivityLayout :items="items" logo="/logo.svg" name="Acme" avatar-src="/me.png">
24
21
  <RouterView />
25
22
  </ActivityLayout>
26
23
  </template>
@@ -179,7 +176,11 @@ partitionMenu(items); // { content, footer }
179
176
  | `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
177
  | `scroll` | `'document' \| 'container'` | `'document'` | Who scrolls: the page (header and sidebar sticky), or the content region inside a shell one screen high |
181
178
  | `class` | `string` | — | Classes for the outer element (`[data-slot=activity-layout]`), which takes the other attributes too |
182
- | `#brand` | slot | an atom icon | Replaces the sidebar header: a logo, a product name. Always fill it; the default is a placeholder |
179
+ | `logo` | `string \| { src, alt? }` | an atom badge | The app's logo, an image URL: alone in the collapsed rail, beside `name` in the phone's menu sheet (see [The brand](#the-brand-logo-and-name)) |
180
+ | `#logo` | slot | — | The logo as a node (an inline SVG), in place of `logo`; it wins when both are given |
181
+ | `name` | `string` | — | The app's name, already translated: the logo's accessible name and tooltip in the rail, shown beside it in the sheet |
182
+ | `#brand` | slot | — | Replaces the sidebar header entirely, in the rail and in the menu sheet; wins over `logo` and `name` |
183
+ | `#header-title` | slot | — | The header's leading title: the context the app is in (the active organisation's name), right after the phone's menu button, truncated when long |
183
184
  | `#header-actions` | slot | — | In the header, before the avatar: notifications, help, `LanguageSwitcher`, `ThemeToggle` |
184
185
  | `#avatar` | slot | the `avatar-src` image | Replaces the avatar entirely |
185
186
  | default | slot | — | The routed content, usually `<RouterView />` |
@@ -214,32 +215,26 @@ the slots.
214
215
  ```vue
215
216
  <script setup lang="ts">
216
217
  import {
218
+ AccountMenu,
217
219
  ActivityLayout,
218
- Avatar,
219
- AvatarFallback,
220
- AvatarImage,
221
220
  LanguageSwitcher,
222
221
  type MenuItem,
223
222
  ThemeToggle,
224
223
  } from '@nxgt/material-vue/components';
225
224
 
226
- defineProps<{ items: MenuItem[]; user: { name: string; picture?: string } }>();
225
+ defineProps<{ items: MenuItem[]; user: { name: string; email: string; picture?: string } }>();
226
+ const emit = defineEmits<{ 'sign-out': [] }>();
227
227
  </script>
228
228
 
229
229
  <template>
230
- <ActivityLayout :items="items">
231
- <template #brand>
232
- <img src="/logo.svg" alt="Acme" class="h-8" />
233
- </template>
230
+ <ActivityLayout :items="items" logo="/logo.svg" name="Acme">
231
+ <template #header-title>Northwind school</template>
234
232
  <template #header-actions>
235
233
  <LanguageSwitcher />
236
234
  <ThemeToggle />
237
235
  </template>
238
236
  <template #avatar>
239
- <Avatar>
240
- <AvatarImage :src="user.picture ?? ''" :alt="user.name" />
241
- <AvatarFallback>{{ user.name.slice(0, 2) }}</AvatarFallback>
242
- </Avatar>
237
+ <AccountMenu :name="user.name" :secondary="user.email" :src="user.picture" @sign-out="emit('sign-out')" />
243
238
  </template>
244
239
  <RouterView />
245
240
  </ActivityLayout>
@@ -254,7 +249,92 @@ entry closes it — so does any route change, Back included — and focus goes t
254
249
  the page's `<main>`, unless the new screen already focused an element inside it
255
250
  (an error page's `h1`), which keeps it; closed with Escape, the close button or the overlay,
256
251
  focus returns to the menu button. Its entries show their labels, so they have
257
- no tooltip: none opens over the first entry as focus moves in.
252
+ no tooltip: none opens over the first entry as focus moves in. It shows the
253
+ app's `logo` and `name` (or `#brand`) above the entries, as the expanded
254
+ sidebar does.
255
+
256
+ ### The app bar: a title, the actions, the account
257
+
258
+ The header reads, from the start: the phone's menu button, `#header-title`
259
+ (the context the app is in, the active school or organisation), then
260
+ `#header-actions` (language, notifications) and `#avatar` at the end. The
261
+ title takes the room left and ends in an ellipsis when it does not fit, so
262
+ the avatar always stays on screen. It is not a heading: the screen's `h1` is
263
+ `ActivityContent`'s title.
264
+
265
+ `AccountMenu` is the `#avatar` most apps want: an avatar button named
266
+ "Account menu, Ada Lovelace" (`material.account-menu.trigger`) that opens a
267
+ menu aligned to its end, headed by the avatar, the name and `secondary`, then
268
+ the app's `items` and a destructive Sign out
269
+ (`material.account-menu.sign-out`). Choosing Sign out emits `sign-out` and
270
+ keeps the menu open: pass `signing-out` while the request runs, and the item
271
+ shows a spinner and ignores a second selection. Without `src`, or until it
272
+ loads, the avatar shows the initials on `bg-primary`.
273
+
274
+ ```vue
275
+ <AccountMenu
276
+ :name="user.name"
277
+ :secondary="user.email"
278
+ :src="user.picture"
279
+ :items="[
280
+ { label: t('menu.profile'), url: '/profile', icon: 'user' },
281
+ { label: t('menu.settings'), url: '/settings', icon: 'gear' },
282
+ ]"
283
+ :signing-out="pending"
284
+ @sign-out="signOut"
285
+ />
286
+ ```
287
+
288
+ Each entry is an `AccountMenuItem`, exported with it:
289
+
290
+ ```ts
291
+ type AccountMenuItem = {
292
+ label: string; // already translated
293
+ url: string; // rendered as a real link through `link-component`
294
+ icon?: IconName; // optional, decorative: the label names the entry
295
+ };
296
+ ```
297
+
298
+ `icon` is an `IconName`, a symbol id of the Font Awesome duotone sprite you serve (`user`, `gear`, `arrow-right-from-bracket`; browse the names at <https://fontawesome.com/icons>, and see [getting started](../getting-started.md#icons) for serving the sprite).
299
+
300
+ ### The brand: logo and name
301
+
302
+ `logo` (or the `#logo` slot) and `name` put the app's brand in the sidebar
303
+ header, in the shape each place has room for. `logo` is typed `AppLogo`
304
+ (`string | AppLogoImage`, where `AppLogoImage` is `{ src: string; alt?: string }`
305
+ and a string is the image URL), both exported from
306
+ `@nxgt/material-vue/components`:
307
+
308
+ - **The collapsed rail** (desktop, about 70 px wide) shows the **logo alone**.
309
+ `name` is its accessible name (an `<img alt>`, or `role="img"` with an
310
+ `aria-label` around the `#logo` node) and shows as a tooltip on hover, to
311
+ its right.
312
+ - **The expanded sidebar and the phone's menu sheet** show the **logo and the
313
+ name side by side**. The name is on screen there, so the logo is decorative
314
+ (`alt=""` or `aria-hidden`), and no tooltip repeats it.
315
+
316
+ ```vue
317
+ <ActivityLayout :items="items" logo="/logo.svg" :name="t('app.name')">…</ActivityLayout>
318
+ <!-- An image with its own alt, used when there is no name: -->
319
+ <ActivityLayout :items="items" :logo="{ src: '/logo.svg', alt: 'Acme' }">…</ActivityLayout>
320
+ <!-- A node, an inline SVG for instance: -->
321
+ <ActivityLayout :items="items" name="Acme">
322
+ <template #logo><AcmeMark class="size-8" /></template>
323
+ …
324
+ </ActivityLayout>
325
+ ```
326
+
327
+ Without a logo the header keeps its placeholder atom badge (named by `name`
328
+ in the rail when there is one); fill `logo` in a real app. `#brand` is the
329
+ full override: one node rendered as given, collapsed or expanded alike, in the
330
+ rail and in the menu sheet. `AppSidebar` takes the same props and slots, and
331
+ `AppHeader` takes them for its menu sheet.
332
+
333
+ `#brand` is rendered twice, once in the rail and once in the menu sheet (the
334
+ rail stays in the page on a phone, hidden): an `id` inside it appears twice
335
+ while the sheet is open, so leave ids out of it. A link in `#brand` (a home
336
+ link around the logo) is the sheet's first focusable element, so it takes
337
+ the focus when the sheet opens.
258
338
 
259
339
  ## Landmarks, headings and scrolling
260
340
 
@@ -375,7 +455,7 @@ home included, is a `RouterLink`, so it goes through the router's base: under
375
455
  `AppSidebar`, `AppHeader` and `AppBreadcrumb` are exported for a layout of your
376
456
  own. `AppSidebar` and `AppHeader` take the same `items`; `AppSidebar` also
377
457
  takes `expanded` (`v-model:expanded`) and `with-toggle-icon`, and has the
378
- `#brand` slot; `AppHeader` takes `avatar-src` and `show-menu-icon` (default
458
+ `#brand` slot, `logo`, `name` and `#logo`; `AppHeader` takes `avatar-src`, `logo`, `name` and `show-menu-icon` (default
379
459
  `true`), with `#actions`, `#avatar` and a default slot for the middle of the
380
460
  bar. The lower-level `Sidebar`, `TopAppBar`, `NavigationRail` and `Breadcrumb`
381
461
  primitives are on the
@@ -315,10 +315,16 @@ shows them right under its fields.
315
315
  `form-actions` gets the same sticky footer.
316
316
  - A focused control or link is scrolled into view clear of the actions and
317
317
  a line of helper text below it (`scroll-margin-bottom` on the fields'
318
- controls: 6rem, plus a dialog's bottom padding, 7.5rem above a
319
- `ConfirmationDialog`'s stacked footer), so a field you tab to is never
320
- hidden behind them (WCAG 2.4.11). It is set on the controls, so it holds
321
- in a scroll box of your own too.
318
+ controls: 6rem, 8rem on a phone, plus a dialog's bottom padding, plus the
319
+ safe-area inset beyond 1rem, 7.5rem above a `ConfirmationDialog`'s stacked
320
+ footer), so a field you tab to is never hidden behind them (WCAG 2.4.11).
321
+ It is set on the controls, so it holds in a scroll box of your own too.
322
+ - The actions pad their bottom for the safe area
323
+ (`max(1rem, env(safe-area-inset-bottom))`): in an installed app with
324
+ `viewport-fit=cover`, they clear the home indicator. The phone default
325
+ (below `sm`, where the buttons share the row) comes from
326
+ `--form-actions-clearance-default`, which is internal: override
327
+ `--form-actions-clearance` instead.
322
328
  - Taller actions (a third button, labels that wrap) or helper text on
323
329
  several lines need more room: raise `--form-actions-clearance` on the
324
330
  form. A dialog whose bottom padding is not the default 1.5rem (`p-0` and
@@ -292,7 +292,7 @@ the `LocationPreview` trigger (`material.map.preview` / `preview-named`),
292
292
  (`material['duration-field']['hours-label']` / `minutes-label`), and
293
293
  the delete button of the deprecated `ContactItem` / `OpeningHoursItem`
294
294
  (`material.contacts.buttons.remove`,
295
- `material['opening-hours'].buttons.remove`). `FacetGroup`'s toggle is named "Level, 2 selected" / "Level, 2 sélectionnés" (`material.facet-group.name`, an ICU plural over `{title}` and `{count}`) and its Clear button "Clear Level" / "Effacer Level" (`material.facet-group.clear-label`, with the visible `material.facet-group.clear`). When the form of a
295
+ `material['opening-hours'].buttons.remove`). `FacetGroup`'s toggle is named "Level, 2 selected" / "Level, 2 sélectionnés" (`material.facet-group.name`, an ICU plural over `{title}` and `{count}`) and its Clear button "Clear Level" / "Effacer Level" (`material.facet-group.clear-label`, with the visible `material.facet-group.clear`). `AccountMenu`'s avatar button is named "Account menu, {name}" / "Menu du compte, {name}" (`material.account-menu.trigger`, over `{name}`) and its last item reads "Sign out" / "Se déconnecter" (`material.account-menu.sign-out`), unless you pass `sign-out-label`. When the form of a
296
296
  `ConfirmationDialog` with `form-options` cannot be loaded, it says so in the
297
297
  form's place: "The form could not be loaded." / "Le formulaire n’a pas pu être
298
298
  chargé." (`material.confirm-dialog.form-failed`). The keys and the French are the
package/docs/roadmap.md CHANGED
@@ -167,6 +167,15 @@ version they shipped in, and planned entries may name the major they target.
167
167
 
168
168
  ## Shipped
169
169
 
170
+ - **The app's brand, a header title and an account menu** — the app shell
171
+ takes the app's `logo` and `name`: the logo alone in the collapsed rail,
172
+ named by the app's name and shown as its tooltip, the logo and the name side
173
+ by side expanded and in the phone's menu sheet, which now shows `brand` too;
174
+ `#header-title`, the context the app is in, right after the phone's menu
175
+ button and truncated when long; and `AccountMenu`, the avatar button named
176
+ "Account menu, <name>" that opens the account's name and e-mail, the app's
177
+ links and a busy-aware Sign out (`sign-out`).
178
+
170
179
  - **Rows a user adds, removes and reorders** — `RepeatableGroup` and
171
180
  `RepeatableGroupFormField` over the form library's field array: rows from
172
181
  the `row` slot, a list for assistive tech, Remove and move buttons named after
@@ -28,6 +28,7 @@ words in your error.
28
28
  - [A Button's label overflows a card on a phone](#a-buttons-label-overflows-a-card-on-a-phone)
29
29
  - [A dialog or sheet scrolls, or clips a child, after upgrading](#a-dialog-or-sheet-scrolls-or-clips-a-child-after-upgrading)
30
30
  - [A form's fields no longer scroll in their own box after upgrading](#a-forms-fields-no-longer-scroll-in-their-own-box-after-upgrading)
31
+ - [axe reports `target-size` on a control under a form's sticky actions](#axe-reports-target-size-on-a-control-under-a-forms-sticky-actions)
31
32
  - [`JsBarcode is not a function`](#jsbarcode-is-not-a-function)
32
33
  - [A `LinkButton` selected as a `button` stops matching](#a-linkbutton-selected-as-a-button-stops-matching)
33
34
  - [A `Toc`, `Pager`, `SeeAlso` or `ScrollProgress` inside `Doc` selected by `data-slot` stops matching](#a-toc-pager-seealso-or-scrollprogress-inside-doc-selected-by-data-slot-stops-matching)
@@ -190,6 +191,7 @@ words in your error.
190
191
  - [A test's `getByRole('region')` finds more than one region](#a-tests-getbyroleregion-finds-more-than-one-region)
191
192
  - [A `DataTable` toolbar is gone](#a-datatable-toolbar-is-gone)
192
193
  - [axe reports `target-size` on a `DataTable`'s "Select all"](#axe-reports-target-size-on-a-datatables-select-all)
194
+ - [A French test no longer finds the sort menu's "Asc" or "Desc"](#a-french-test-no-longer-finds-the-sort-menus-asc-or-desc)
193
195
 
194
196
  **PDF reader**
195
197
 
@@ -1311,6 +1313,26 @@ actions where they are, make them static:
1311
1313
  An app's own fixed bottom bar or floating action button now sits under the
1312
1314
  sticky actions (`z-index: 10`): give it a z-index above 10 (`class="z-20"`).
1313
1315
 
1316
+ ### axe reports `target-size` on a control under a form's sticky actions
1317
+
1318
+ **When:** since the form's actions stick to the bottom (see above), axe's
1319
+ `target-size` (WCAG 2.5.8) flags a field's control, a link or a button
1320
+ that sits half under the sticky `[data-slot="form-actions"]` bar, wherever
1321
+ the page happens to stop scrolling when the scan runs. The same control passes once scrolled into view.
1322
+ **Why:** axe measures what is visible of a target: the sticky bar covers part
1323
+ of the control, so axe sees a target smaller than 24 by 24 px, or one whose
1324
+ circle meets Cancel or Save. A user scrolls the control out from under the
1325
+ bar; a scan does not.
1326
+ **Fix:** scan with the actions put back in the flow, so nothing covers the
1327
+ fields, and keep the bar's own buttons in the scan:
1328
+
1329
+ ```ts
1330
+ await page.addStyleTag({
1331
+ content: '[data-slot="form-actions"] { position: static !important; }',
1332
+ });
1333
+ await expectAccessible(page);
1334
+ ```
1335
+
1314
1336
  ## Feedback
1315
1337
 
1316
1338
  ### A toast with an action never goes away
@@ -3580,6 +3602,15 @@ page.getByRole('region', { name: 'Invoices' });
3580
3602
  **Why:** axe measures the 16px box itself, not its 24px `::before`, so it asks for a 24px circle around the box clear of every other control. The sort button reaches 12px back into its cell's padding (`-ml-3`), and the `select` cell had no padding after its checkbox: the circle met the button.
3581
3603
  **Fix:** upgrade: the `select` column's checkboxes now keep a 12px margin after them. Drop any axe `exclude` of `thead [data-slot="checkbox-field"]` you kept for it. A `select` column of your own (not `predefinedColumns().select`) needs the same: `me-3` on its checkboxes.
3582
3604
 
3605
+ ### A French test no longer finds the sort menu's "Asc" or "Desc"
3606
+
3607
+ **When:** after upgrading, a test running in French clicks the
3608
+ `DataTableColumnHeader` menu item "Asc" or "Desc" and finds nothing.
3609
+ **Why:** `material.table.labels.asc` / `desc` are now translated in French,
3610
+ "Croissant" / "Décroissant" (#612); English keeps "Asc" / "Desc".
3611
+ **Fix:** look for `getByRole('menuitem', { name: 'Croissant' })` (or
3612
+ `'Décroissant'`) in French.
3613
+
3583
3614
  ---
3584
3615
 
3585
3616
  ---
@@ -139,13 +139,23 @@
139
139
  [contenteditable]
140
140
  ) {
141
141
  scroll-margin-bottom: calc(
142
- var(--form-actions-clearance, 6rem) +
143
- var(--form-actions-fill, 0px)
142
+ var(
143
+ --form-actions-clearance,
144
+ var(--form-actions-clearance-default, 6rem)
145
+ ) +
146
+ var(--form-actions-fill, 0px) +
147
+ max(0px, calc(env(safe-area-inset-bottom, 0px) - 1rem))
144
148
  );
145
149
  }
146
- /* Sticky at the bottom of whatever scrolls the form: in reach. */
150
+ /* A phone's actions wrap their labels over two lines and a field has an
151
+ error line: 8rem covers both (`--form-actions-clearance` still wins). */
152
+ @variant max-sm {
153
+ --form-actions-clearance-default: 8rem;
154
+ }
155
+ /* Sticky at the bottom of whatever scrolls the form: in reach. Padded
156
+ for the safe area (`viewport-fit=cover`: the home indicator). */
147
157
  [data-slot="form-actions"] {
148
- @apply sticky bottom-0 z-10 flex w-full justify-end gap-2 bg-background/90 p-4 backdrop-blur-sm max-sm:*:flex-1;
158
+ @apply sticky bottom-0 z-10 flex w-full justify-end gap-2 bg-background/90 p-4 pb-[max(1rem,env(safe-area-inset-bottom))] backdrop-blur-sm max-sm:*:flex-1;
149
159
  }
150
160
  }
151
161
 
@@ -4,6 +4,7 @@ export const COMPONENT_NAMES = [
4
4
  'AccordionContent',
5
5
  'AccordionItem',
6
6
  'AccordionTrigger',
7
+ 'AccountMenu',
7
8
  'ActionCard',
8
9
  'Activity',
9
10
  'ActivityContent',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/material-vue",
3
- "version": "0.40.1",
3
+ "version": "0.42.0",
4
4
  "license": "MIT",
5
5
  "bugs": {
6
6
  "email": "stevegires@gmail.com"
@@ -124,7 +124,7 @@
124
124
  "docs:previews": "vitest run --project previews"
125
125
  },
126
126
  "dependencies": {
127
- "@nxgt/material-core": "^0.11.1",
127
+ "@nxgt/material-core": "^0.12.0",
128
128
  "@floating-ui/dom": "^1.8.0",
129
129
  "@fortawesome/fontawesome-common-types": "7.3.1",
130
130
  "@lucide/vue": "^1.45.0",