@nxgt/material-vue 0.38.0 → 0.40.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 (94) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +1 -0
  3. package/dist/{Button-5UyKA-x_.js → Button-BAIJ8YvT.js} +1 -1
  4. package/dist/{DropdownMenuTrigger-BsuyNVzV.js → DropdownMenuTrigger-C7uYPf_9.js} +36 -36
  5. package/dist/{HoverCardTrigger-CmCDiTn8.js → HoverCardTrigger-CKtL8teP.js} +122 -120
  6. package/dist/{camera-BWzF8sOW.js → camera-2UuZVfGS.js} +4 -4
  7. package/dist/components/base-content.css +64 -4
  8. package/dist/components/theme-tokens.css +7 -2
  9. package/dist/{components-B_s5oxUp.js → components-CCrK1Sbt.js} +13631 -13383
  10. package/dist/components.js +8 -8
  11. package/dist/composables.js +3 -3
  12. package/dist/dnd.js +1 -1
  13. package/dist/{form-scope-UzoU38db.js → form-scope-BLjSnqUf.js} +1 -1
  14. package/dist/{i18n-provider-BEvjls8b.js → i18n-provider-BpNC9SxV.js} +1 -1
  15. package/dist/i18n.js +4 -4
  16. package/dist/{icon-button-DhqJE6Ej.js → icon-button-5bl9odKW.js} +1 -1
  17. package/dist/in-dialog-Blhwwbyb.js +31 -0
  18. package/dist/lib/components/ui/collections/data-table/data-table-density.d.ts +15 -0
  19. package/dist/lib/components/ui/collections/data-table/data-table.stories.columns.d.ts +49 -0
  20. package/dist/lib/components/ui/collections/data-table/data-table.stories.template.d.ts +1 -0
  21. package/dist/lib/components/ui/collections/data-table/index.d.ts +1 -1
  22. package/dist/lib/components/ui/collections/data-table/types.d.ts +7 -0
  23. package/dist/lib/components/ui/collections/facet-group/facet-group-classes.d.ts +18 -0
  24. package/dist/lib/components/ui/collections/facet-group/facet-group.vue.d.ts +47 -0
  25. package/dist/lib/components/ui/collections/facet-group/index.d.ts +2 -0
  26. package/dist/lib/components/ui/collections/index.d.ts +1 -0
  27. package/dist/lib/components/ui/fields/contacts/consts.d.ts +1 -1
  28. package/dist/lib/components/ui/layout/sidebar/sidebar-menu-item.vue.d.ts +4 -3
  29. package/dist/lib/components/ui/menu-item-loading.d.ts +26 -0
  30. package/dist/lib/components/ui/overlays/context-menu/ContextMenuItem.vue.d.ts +4 -2
  31. package/dist/lib/components/ui/overlays/dialog/in-dialog.d.ts +12 -0
  32. package/dist/lib/components/ui/overlays/dropdown-menu/DropdownMenuItem.vue.d.ts +9 -3
  33. package/dist/lib/components/ui/overlays/menubar/MenubarItem.vue.d.ts +4 -2
  34. package/dist/lib/composables/use-hidden-input-name.d.ts +12 -0
  35. package/dist/lib/i18n/resources/en/en.d.ts +7 -0
  36. package/dist/lib/i18n/resources/en/material.json.d.ts +8 -1
  37. package/dist/lib/i18n/resources/fr/fr.d.ts +7 -0
  38. package/dist/lib/i18n/resources/fr/material.json.d.ts +8 -1
  39. package/dist/lib/i18n/resources/resources.d.ts +14 -0
  40. package/dist/lib/i18n/translator.d.ts +1 -1
  41. package/dist/lib/nuxt/component-names.d.ts +1 -1
  42. package/dist/lib.js +3 -3
  43. package/dist/main.js +17 -17
  44. package/dist/map.js +4 -4
  45. package/dist/material-vue.css +1 -1
  46. package/dist/{number.utils-Bpo9tUaJ.js → number.utils-C1eB3GDr.js} +427 -427
  47. package/dist/{pdf-outline-sidebar-BsePJlTi.js → pdf-outline-sidebar-BUvWHjiE.js} +2 -2
  48. package/dist/{pdf-page-Ct313mNF.js → pdf-page-B57B0uES.js} +1 -1
  49. package/dist/{pdf-page-list-JrRo2BeU.js → pdf-page-list-BHU7pW0b.js} +2 -2
  50. package/dist/{pdf-reader-tABBZMbZ.js → pdf-reader-CwTYGXq8.js} +8 -8
  51. package/dist/{pdf-sidebar-Caw9rzU-.js → pdf-sidebar-CtMy8Kw_.js} +3 -3
  52. package/dist/{pdf-thumbnail-sidebar-ZybLeUG7.js → pdf-thumbnail-sidebar-DS0iT3qo.js} +3 -3
  53. package/dist/{pdf-toolbar-BM8Sz_JN.js → pdf-toolbar-DL_vCyXG.js} +5 -5
  54. package/dist/pdf.js +2 -2
  55. package/dist/{print-format.utils-CQFIVeUA.js → print-format.utils-k5W0GBRF.js} +3 -3
  56. package/dist/{sortable-provider-CKkWYijj.js → sortable-provider-cYkCa6tm.js} +2 -2
  57. package/dist/{stored-language-CvOHC8AD.js → stored-language-DEyPmUzl.js} +16 -2
  58. package/dist/{stx-player-CsVKbwFk.js → stx-player-C_lPkXc6.js} +13 -13
  59. package/dist/{translator-k1rKIgC7.js → translator-B62GlCBp.js} +1 -1
  60. package/dist/{use-heading-spy-D-ipoQwl.js → use-heading-spy-T94lNeNA.js} +21 -21
  61. package/dist/{use-material-translation-1FdRx9I7.js → use-material-translation-DxU51--g.js} +3 -3
  62. package/docs/components/data-table-filter-and-lists.md +45 -1
  63. package/docs/components/feedback-and-status.md +7 -6
  64. package/docs/components/form-fields.md +1 -1
  65. package/docs/components/navigation-and-layout.md +6 -5
  66. package/docs/components/overlays-and-menus.md +3 -0
  67. package/docs/guides/csp.md +10 -0
  68. package/docs/guides/forms.md +55 -4
  69. package/docs/guides/i18n.md +3 -2
  70. package/docs/guides/ssr.md +8 -0
  71. package/docs/guides/theming.md +29 -6
  72. package/docs/previews/buttons-actions-button.webp +0 -0
  73. package/docs/previews/data-display-avatar.webp +0 -0
  74. package/docs/previews/data-display-badge.webp +0 -0
  75. package/docs/previews/data-display-chip.webp +0 -0
  76. package/docs/previews/data-display-statusindicator.webp +0 -0
  77. package/docs/previews/data-display-timeline.webp +0 -0
  78. package/docs/previews/data-table-filter-lists-facetgroup.webp +0 -0
  79. package/docs/previews/feedback-status-alert.webp +0 -0
  80. package/docs/previews/feedback-status-banner.webp +0 -0
  81. package/docs/previews/feedback-status-circularprogress.webp +0 -0
  82. package/docs/previews/feedback-status-emptystate.webp +0 -0
  83. package/docs/previews/feedback-status-progress.webp +0 -0
  84. package/docs/previews/feedback-status-spinner.webp +0 -0
  85. package/docs/previews/feedback-status-toast.webp +0 -0
  86. package/docs/previews/form-fields-passwordfield.webp +0 -0
  87. package/docs/previews/form-fields-ratingfield.webp +0 -0
  88. package/docs/roadmap.md +5 -0
  89. package/docs/troubleshooting.md +32 -5
  90. package/lib/components/base-content.css +64 -4
  91. package/lib/components/theme-tokens.css +7 -2
  92. package/lib/nuxt/component-names.ts +1 -0
  93. package/package.json +2 -2
  94. package/dist/use-inert-others-CtGqZKR8.js +0 -22
@@ -293,6 +293,55 @@ in `[data-slot="form-actions"]`) and put each group of fields in a
293
293
  fields still take a row of their own. A rule separates each section from the
294
294
  one before it (`:separated="false"` drops it).
295
295
 
296
+ ### One scroll, sticky actions
297
+
298
+ The form grid has no height of its own and its fields no scroll box: a long
299
+ form grows with the page, and the page is the one scroll. The actions
300
+ (`[data-slot="form-actions"]`) are sticky at the bottom of whatever scrolls
301
+ the form, on a translucent, blurred background, so Cancel and Save stay in
302
+ reach while the fields scroll under them; a form shorter than the screen
303
+ shows them right under its fields.
304
+
305
+ - In a `DialogContent` or a `SheetContent`, which are capped to the screen
306
+ and scroll themselves, the actions stick to the dialog's or the sheet's
307
+ bottom. A `DrawerContent` does not scroll: a form that is its direct child
308
+ is the scroll box there.
309
+ - In an `AlertDialogContent` (the rich text editor's edit dialogs use one),
310
+ the actions stick to its bottom the same way, over its padding.
311
+ - `ConfirmationDialog` holds a form when you pass it `form-options` (a schema and its `defaultValues`) and the `#form` slot: it renders the
312
+ fields in a `.form-grid-layout` with no `form-actions`, so its footer
313
+ (Cancel, Confirm) is what sticks to the dialog's bottom. Any `Dialog` or
314
+ `AlertDialog` whose content holds a `.form-grid-layout` without
315
+ `form-actions` gets the same sticky footer.
316
+ - A focused control or link is scrolled into view clear of the actions and
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.
322
+ - Taller actions (a third button, labels that wrap) or helper text on
323
+ several lines need more room: raise `--form-actions-clearance` on the
324
+ form. A dialog whose bottom padding is not the default 1.5rem (`p-0` and
325
+ `p-4` are recognised) sets `--form-actions-fill` to it:
326
+
327
+ ```css
328
+ .edit-school-form {
329
+ --form-actions-clearance: 9rem;
330
+ }
331
+ .edit-school-dialog {
332
+ --form-actions-fill: 2rem; /* its pb-8 */
333
+ }
334
+ ```
335
+
336
+ ```vue
337
+ <form class="form-grid-layout edit-school-form">
338
+ ```
339
+ - To scroll a form in a box of your own, give it a height and the scroll
340
+ (`class="form-grid-layout max-h-[60vh] overflow-y-auto"`); to keep the
341
+ actions in place, give them `class="static"`. The
342
+ [troubleshooting entry](../troubleshooting.md#a-forms-fields-no-longer-scroll-in-their-own-box-after-upgrading)
343
+ has the details.
344
+
296
345
  ## Validation messages
297
346
 
298
347
  `zodLocaleError()` from `@nxgt/material-vue/lib` is a Zod v4 error map that
@@ -902,10 +951,12 @@ const onSubmit = handleSubmit(() => {
902
951
 
903
952
  - `meta.valid` keeps the button disabled until the schema passes; the form
904
953
  runs a silent validation when it mounts, so it starts disabled.
905
- - `ConfirmationDialog` has no form of its own in `@nxgt/material-vue`: it
906
- renders its default slot above Cancel / Confirm and emits `confirm`, then
907
- closes. Use it for a plain confirmation, and the pattern above when the
908
- user has to type something.
954
+ - `ConfirmationDialog` renders its default slot above Cancel / Confirm and
955
+ emits `confirm`, then closes. For a small form (a name to type before a
956
+ delete), pass `form-options` (a schema and its `defaultValues`) and render
957
+ the fields in its `#form` slot: Confirm stays disabled until the form is
958
+ valid, and `@confirm` receives the schema's output. Use the pattern above
959
+ for a larger form with its own submit.
909
960
  - `ConfirmDialog` (controlled: `open` is required) awaits its `@confirm`
910
961
  handler: while the promise is pending both buttons are disabled and the
911
962
  confirm button reads "Processing..." (`material.confirm-dialog.processing`);
@@ -281,7 +281,8 @@ checkboxes, the filter's presets and `Min`/`Max` fields, the `variant="4"` spinn
281
281
  `DialogFooter`. `CopyButton` reads `material.copy.copy`, `copied` and `failed` ("Copy", "Copied",
282
282
  "Copy failed"). The other icon-only buttons are named from it too: the
283
283
  `Sidebar` toggle (`material.sidebar.toggle`), the data table's row expander
284
- (`material.table.labels.toggle-row`), its loading status, "Loading data…" /
284
+ (`material.table.labels.toggle-row`) and its header, a hidden "Expand" / "Déplier"
285
+ (`material.table.labels.expand`), its loading status, "Loading data…" /
285
286
  "Chargement des données…" (`material.table.loading`), its scroll region,
286
287
  "Scrollable table" / "Tableau défilant"
287
288
  (`material.table.labels.scroll-region`, or the `scroll-label` prop), a `Chip`'s remove button, which the
@@ -291,7 +292,7 @@ the `LocationPreview` trigger (`material.map.preview` / `preview-named`),
291
292
  (`material['duration-field']['hours-label']` / `minutes-label`), and
292
293
  the delete button of the deprecated `ContactItem` / `OpeningHoursItem`
293
294
  (`material.contacts.buttons.remove`,
294
- `material['opening-hours'].buttons.remove`). 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`). When the form of a
295
296
  `ConfirmationDialog` with `form-options` cannot be loaded, it says so in the
296
297
  form's place: "The form could not be loaded." / "Le formulaire n’a pas pu être
297
298
  chargé." (`material.confirm-dialog.form-failed`). The keys and the French are the
@@ -179,6 +179,14 @@ function clientOnly<P extends object>(component: Component<P>): Component<P>;
179
179
  `@nxgt/material-vue/composables` is a `Ref<boolean>` that is `false` on the
180
180
  server and the first render, then `true`.
181
181
 
182
+ ## Hidden form inputs
183
+
184
+ `Checkbox`, `Switch`, `RadioGroup` and `Select` create their hidden form input
185
+ after mount, never in the server HTML (Reka put an inline `style` on it, which a
186
+ strict CSP blocks). Before hydration a native form post therefore carries none
187
+ of these four controls' values; submit through the client, or wait for
188
+ hydration. See [csp.md](csp.md#the-hidden-form-inputs).
189
+
182
190
  ## Hydration
183
191
 
184
192
  **Never name an SVG with `<svg><title>`.** Some SSR setups treat `<title>` as
@@ -134,7 +134,7 @@ import { Button } from '@nxgt/material-vue/components';
134
134
 
135
135
  **Contrast.** The shipped light palette keeps the text of every filled status
136
136
  colour at 4.5:1 or more (WCAG 1.4.3): `--secondary` 5.7:1, `--success` 5.6:1,
137
- `--info` 5.8:1 and `--warning` 5.3:1 against their `-foreground`, and
137
+ `--info` 5.8:1 and `--warning` 5.5:1 against their `-foreground`, and
138
138
  `--secondary` as text (a `tonal`, `outlined` or `link` `Button`) 6.0:1 on
139
139
  `--background`, 4.8:1 on its own 15 % `tonal` tint.
140
140
 
@@ -144,8 +144,8 @@ and 5.8:1 on `--card` (a `tonal` secondary `Button` 5.4:1 and 4.6:1). Under
144
144
  near-white text those brights are too light, so every filled surface built on
145
145
  them is tinted in the dark theme: a filled `secondary` `Button`, `IconButton`
146
146
  or `Badge` at 70 % (5.2:1 on `--background`, 4.9:1 on `--card`), a filled
147
- `success`, `info` or `warning` one at 60 % (7.3:1, 5.7:1 and 7.5:1 on
148
- `--background`; 6.7:1, 5.3:1 and 7.0:1 on `--card`). Their hover, focus and
147
+ `success`, `info` or `warning` one at 60 % (7.3:1, 5.7:1 and 6.0:1 on
148
+ `--background`; 6.7:1, 5.3:1 and 5.6:1 on `--card`; the `warning` `Button` 6.5:1). Their hover, focus and
149
149
  pressed states are tinted further down, so they only gain contrast.
150
150
 
151
151
  In the light theme the filled `primary`, `secondary`, `success`, `info`,
@@ -161,9 +161,9 @@ their text and icon in one tone per colour, read on `--background`, on
161
161
  `--card` and on the variant's own tint (15 % at most, at rest or focused). In
162
162
  the light theme `primary`, `secondary`, `info` and `success` use the token
163
163
  itself (5.8, 4.8, 4.9 and 4.7:1 at their lowest) and `warning` and `error` the
164
- token mixed with 15 % black (6.0 and 5.4:1). In the dark theme `primary` is
164
+ token mixed with 15 % black (6.4 and 5.4:1). In the dark theme `primary` is
165
165
  mixed with 60 % white (8.1:1 on `--card`: the token itself is a dark indigo,
166
- 2.2:1), `success` and `warning` with 15 % white (5.0 and 5.4:1), `error` with
166
+ 2.2:1), `success` and `warning` with 15 % white (5.0 and 6.3:1), `error` with
167
167
  30 % white (5.9:1), and `secondary` and `info` use the token (4.6 and 5.3:1).
168
168
  The tones are mixed from the tokens (`color-mix(in oklab, var(--color-error)
169
169
  85%, black)`), so an override of a token moves its tone with it; a text colour
@@ -179,6 +179,29 @@ darker than the dark theme's: a filled `error` button's near-white text reaches
179
179
  4.8:1 (it was 4.6:1). The dark `--error` keeps `0.577`: a lighter value drops
180
180
  the filled surface's near-white text below 4.5:1 (`0.62` gives 3.85:1).
181
181
 
182
+ **`--warning` is amber**, a hue apart from `--error` (62 against 27 degrees in
183
+ the light theme, 70 in the dark one), so a warning `Alert`, `Banner`, `Badge`
184
+ or toast does not read as a failure. Light `oklch(0.52 0.13 62)` is 5.5:1
185
+ under its near-white `-foreground`; dark `oklch(0.7 0.15 70)` reads as text at
186
+ 7.3:1 on `--background` and 6.5:1 on `--card` (6.6 and 5.8:1 on its 10 % tint). An app that overrides `--warning` keeps its own
187
+ value; one that keeps the shipped value sees its `bg-warning` and
188
+ `text-warning` surfaces change hue, and re-checks its custom pairs.
189
+
190
+ **A selected `DataTable` row** (`TableRow`, `data-state="selected"`) is a 15 %
191
+ primary tint with `foreground` text, a bar on the inline-start edge of its
192
+ first cell (`before:`, primary in the light theme, primary mixed with 60 %
193
+ white in the dark one: 5.9:1 in light and 8.2:1 or more in dark against the
194
+ row), and the checkbox, drawn in the same dark colour (8.2:1 or more). The
195
+ tint is too light to be told from its neighbour alone (1.2:1 in light, 1.06 to
196
+ 1.11:1 in dark), which is what keeps the text at 4.5:1 or more on a page and on
197
+ a `Card`: 15.9:1 in light, and in dark 17.5:1 on a page, 17.1:1 in a `DataTable`
198
+ on a `Card` and 15.4:1 for a bare `TableRow` on a `Card`. Links and
199
+ `text-muted-foreground` texts (drawn as `foreground` at 70 %) are 7.0:1 in
200
+ light and 8.15:1 in dark for a bare row on a `Card`. A `text-error-text` text
201
+ keeps its colour. Do not
202
+ put `text-primary-foreground` in a cell for a selected row: it is near-white
203
+ on that tint.
204
+
182
205
  **Error text has a token of its own**, `--error-text` (`text-error-text`), so
183
206
  the fill can stay dark while the text is light enough to read. The light value
184
207
  is `color-mix(in oklab, var(--error) 94%, black)` (5.7:1 on `--background`
@@ -212,7 +235,7 @@ the surfaces' text, the error text (`HelperText` and a field's message on
212
235
  `--background` and `--card`), the `Alert`s and the `Timeline`, `Button` and
213
236
  `IconButton` in every variant and colour, and a `Badge` link in every colour,
214
237
  at rest, hovered, focused and pressed, the tracks and the tabs;
215
- `Data display/ListTile` measures the selected tile. Run them against your own
238
+ `Data display/ListTile` measures the selected tile, `Data table, filter & lists/DataTable/On a card` the header, the cells, the links and the selected rows on a page and on a `Card` (also right-to-left), and `Feedback & status/Alert/Tones contrast` the warning, error and success alerts there. Run them against your own
216
239
  tokens after an override.
217
240
 
218
241
  **Tabs.** An `underline` tab's active label is `--primary` in the light
package/docs/roadmap.md CHANGED
@@ -90,9 +90,14 @@ version they shipped in, and planned entries may name the major they target.
90
90
  Vue only: `@nxgt/material`'s print blocks already produce a PDF.
91
91
  - **`StepperIndicator`, as in `@nxgt/material`** — the step markers on their
92
92
  own, for a stepper laid out your way.
93
+ - **Facets panel: `Filter variant="facets"`** — collapsible FacetGroups sharing the filter bar's state, a sheet on a phone
93
94
 
94
95
  ## Later
95
96
 
97
+ - **A CSP without `style-src-attr 'unsafe-inline'`** — the four form controls'
98
+ hidden inputs no longer need it; the checked `Checkbox` indicator,
99
+ `SelectValue`, the `RadioGroup` root, `Slider` and popper positioning still
100
+ do. Inventory: softistx/nxgt-material#596.
96
101
  - **Next major: an error `Banner` interrupts** — `tone="error"` becomes
97
102
  `role="alert"` `aria-live="assertive"`, so a screen reader reads it out at
98
103
  once. Every tone is `role="status"` `aria-live="polite"` today; pass
@@ -27,6 +27,7 @@ words in your error.
27
27
  - [A Content-Security-Policy reports a `data:` image on every form with a required field](#a-content-security-policy-reports-a-data-image-on-every-form-with-a-required-field)
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
+ - [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)
30
31
  - [`JsBarcode is not a function`](#jsbarcode-is-not-a-function)
31
32
  - [A `LinkButton` selected as a `button` stops matching](#a-linkbutton-selected-as-a-button-stops-matching)
32
33
  - [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)
@@ -1284,6 +1285,32 @@ with `class="max-h-none overflow-visible"`; to change the cap, pass your own
1284
1285
  For a dialog that should grow taller than the screen and scroll inside the
1285
1286
  overlay instead of capping, use `DialogScrollContent`.
1286
1287
 
1288
+ ### A form's fields no longer scroll in their own box after upgrading
1289
+
1290
+ **When:** after upgrading, a `.form-grid-layout` form grows with the page
1291
+ instead of scrolling its fields in a box of its own, Cancel and Save stay at
1292
+ the bottom of the screen, or a form inside a box you gave a fixed height
1293
+ overflows it.
1294
+ **Why:** the form grid used to cap its height
1295
+ (`max-h-[calc(100lvh-184px)]`, more from `md`) and scroll
1296
+ `[data-slot="form-fields"]` inside it, so a page had two scrolls, one inside
1297
+ the other. It now has no cap and no scroll of its own: the page, or the
1298
+ `DialogContent` / `SheetContent` around it, is the one scroll, and
1299
+ `[data-slot="form-actions"]` is sticky at its bottom on a blurred background.
1300
+ A `Drawer`'s content does not scroll, so a form that is a direct child of
1301
+ `DrawerContent` is the scroll box there.
1302
+ **Fix:** to scroll a form inside a box again, give the box (or the form) a
1303
+ height and the scroll; the actions then stick to its bottom. To keep the
1304
+ actions where they are, make them static:
1305
+
1306
+ ```vue
1307
+ <form class="form-grid-layout max-h-[60vh] overflow-y-auto">
1308
+ <div data-slot="form-actions" class="static">
1309
+ ```
1310
+
1311
+ An app's own fixed bottom bar or floating action button now sits under the
1312
+ sticky actions (`z-index: 10`): give it a z-index above 10 (`class="z-20"`).
1313
+
1287
1314
  ## Feedback
1288
1315
 
1289
1316
  ### A toast with an action never goes away
@@ -1318,12 +1345,12 @@ no `position`; it still covers the bottom when the app mounts vue-sonner's own
1318
1345
  `position`, which applies on every screen.
1319
1346
  **Fix:** mount the `Toaster` from `@nxgt/material-vue/components`, and drop a
1320
1347
  bottom `position` unless the bottom is what you want on phones too. When a
1321
- sticky header must stay visible above the toasts, raise the phone offset's
1322
- `top`:
1348
+ sticky header is taller than the app shell's (72px with its margin), raise the
1349
+ phone offset's `top` (80 by default, so toasts start below that bar):
1323
1350
 
1324
1351
  ```vue
1325
1352
  <Toaster /> <!-- top-center on a phone -->
1326
- <Toaster :mobile-offset="{ top: 72 }" />
1353
+ <Toaster :mobile-offset="{ top: 96 }" />
1327
1354
  <Toaster position="bottom-center" /> <!-- bottom-center on every screen -->
1328
1355
  ```
1329
1356
 
@@ -3306,8 +3333,8 @@ while a `Switch`, `SwitchField` or `SwitchFormField` with `required` and a
3306
3333
  proceed".
3307
3334
  **Why:** `required` used to draw only the label's asterisk. It now reaches the
3308
3335
  switch, which is announced as required (`aria-required`), and its hidden input
3309
- is `required`, so the browser's native validation blocks the submit, as for a
3310
- required checkbox.
3336
+ is `required`, so once hydrated the browser's native validation blocks the
3337
+ submit, as for a required checkbox.
3311
3338
  **Fix:** leave `required` on a switch that must be on (an agreement); drop it
3312
3339
  from one that may stay off. A form that validates itself (vee-validate) can
3313
3340
  turn native validation off with `novalidate`:
@@ -97,7 +97,7 @@
97
97
  .form-grid-layout {
98
98
  @apply relative flex flex-col gap-2 @container;
99
99
  [data-slot="form-fields"] {
100
- @apply grid @2xl:grid-cols-2 @4xl:grid-cols-3 @6xl:grid-cols-4 gap-2 bg-background/80 p-4 place-items-start overflow-auto;
100
+ @apply grid @2xl:grid-cols-2 @4xl:grid-cols-3 @6xl:grid-cols-4 gap-2 bg-background/80 p-4 place-items-start;
101
101
 
102
102
  [data-slot="separator"] {
103
103
  @apply col-start-full;
@@ -125,13 +125,73 @@
125
125
  @apply col-start-full;
126
126
  }
127
127
  }
128
+ /* A focused control scrolls into view clear of the actions and a line
129
+ of helper text, in any scroll box (WCAG 2.4.11 Focus Not Obscured). */
130
+ [data-slot="form-fields"]
131
+ :is(
132
+ input,
133
+ textarea,
134
+ select,
135
+ button,
136
+ a[href],
137
+ summary,
138
+ [tabindex],
139
+ [contenteditable]
140
+ ) {
141
+ scroll-margin-bottom: calc(
142
+ var(--form-actions-clearance, 6rem) +
143
+ var(--form-actions-fill, 0px)
144
+ );
145
+ }
146
+ /* Sticky at the bottom of whatever scrolls the form: in reach. */
128
147
  [data-slot="form-actions"] {
129
- @apply flex w-full justify-end gap-2 bottom-2 p-4 max-sm:*:flex-1;
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;
130
149
  }
131
150
  }
132
151
 
133
- .form-grid-layout {
134
- @apply max-h-[calc(100lvh-184px)] md:max-h-[calc(100lvh-242px)] lg:max-h-[calc(100lvh-280px)];
152
+ /* No height cap and no scroll of its own: the page (or the overlay) is
153
+ the one scroll. A Drawer's content does not scroll: the form does. */
154
+ [data-slot="drawer-content"] > .form-grid-layout {
155
+ @apply min-h-0 overflow-y-auto;
156
+ }
157
+
158
+ /* In a dialog, the stuck actions sit above its bottom padding
159
+ (`--form-actions-fill`: p-6 by default; set it for another one). */
160
+ :is([data-slot="dialog-content"], [data-slot="alert-dialog-content"]) {
161
+ --form-actions-fill: 1.5rem;
162
+ &:is(.p-0, .py-0, .pb-0) {
163
+ --form-actions-fill: 0px;
164
+ }
165
+ &:is(.p-4, .py-4, .pb-4) {
166
+ --form-actions-fill: 1rem;
167
+ }
168
+ }
169
+
170
+ /* ConfirmationDialog's form has no actions of its own: the dialog's
171
+ footer (two stacked buttons on a phone) sticks instead. */
172
+ :is([data-slot="dialog-content"], [data-slot="alert-dialog-content"]):has(
173
+ .form-grid-layout
174
+ ):not(:has([data-slot="form-actions"])) {
175
+ --form-actions-clearance: 7.5rem;
176
+ > :is([data-slot="dialog-footer"], [data-slot="alert-dialog-footer"]) {
177
+ @apply sticky bottom-0 z-10 bg-background/90 backdrop-blur-sm;
178
+ }
179
+ }
180
+
181
+ /* A sticky element stops at the scroll box's padding: in a dialog, an
182
+ `::after` as tall as that padding fills the strip below the stuck
183
+ actions, so no field shows there, and adds no overflow. */
184
+ :is([data-slot="dialog-content"], [data-slot="alert-dialog-content"])
185
+ .form-grid-layout
186
+ > [data-slot="form-actions"]::after,
187
+ :is([data-slot="dialog-content"], [data-slot="alert-dialog-content"]):has(
188
+ .form-grid-layout
189
+ ):not(:has([data-slot="form-actions"]))
190
+ > :is(
191
+ [data-slot="dialog-footer"],
192
+ [data-slot="alert-dialog-footer"]
193
+ )::after {
194
+ @apply absolute inset-x-0 top-full h-(--form-actions-fill) bg-background content-[""];
135
195
  }
136
196
 
137
197
  /* No height cap on `DataTable` here (it nested a scroll in the page's):
@@ -38,7 +38,9 @@
38
38
  --success-foreground: oklch(0.984 0.003 247.858);
39
39
  --info: oklch(0.5 0.165 254.624);
40
40
  --info-foreground: oklch(0.984 0.003 247.858);
41
- --warning: oklch(0.54 0.222 41.116);
41
+ /* Amber, a hue apart from --error (27): a warning must not read as a
42
+ failure. 5.7:1 as text on white, 5.0:1 on its 10% tint, 5.5:1 under near-white text. */
43
+ --warning: oklch(0.52 0.13 62);
42
44
  --warning-foreground: oklch(0.984 0.003 247.858);
43
45
  --destructive: var(--error);
44
46
  --border: oklch(0.929 0.013 255.508);
@@ -94,7 +96,10 @@
94
96
  --success-foreground: oklch(0.984 0.003 247.858);
95
97
  --info: oklch(0.707 0.165 254.624);
96
98
  --info-foreground: oklch(0.984 0.003 247.858);
97
- --warning: oklch(0.646 0.222 41.116);
99
+ /* Amber too, as text: 7.3:1 on --background and 6.5:1 on --card, 6.6:1 and
100
+ 5.8:1 on its 10% tint; the badge's 60% fill under near-white text is
101
+ 6.0:1 on a page and 5.6:1 on a Card. */
102
+ --warning: oklch(0.7 0.15 70);
98
103
  --warning-foreground: oklch(0.984 0.003 247.858);
99
104
  --destructive: oklch(0.704 0.191 22.216);
100
105
  --border: oklch(1 0 0 / 10%);
@@ -241,6 +241,7 @@ export const COMPONENT_NAMES = [
241
241
  'EventCard',
242
242
  'EventChip',
243
243
  'ExtendedLabel',
244
+ 'FacetGroup',
244
245
  'FeatureCard',
245
246
  'Field',
246
247
  'Figure',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/material-vue",
3
- "version": "0.38.0",
3
+ "version": "0.40.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.0",
127
+ "@nxgt/material-core": "^0.11.1",
128
128
  "@floating-ui/dom": "^1.8.0",
129
129
  "@fortawesome/fontawesome-common-types": "7.3.1",
130
130
  "@lucide/vue": "^1.45.0",
@@ -1,22 +0,0 @@
1
- import { nextTick as e, useTemplateRef as t, watch as n } from "vue";
2
- import { unrefElement as r } from "@vueuse/core";
3
- import { inertOthers as i } from "@nxgt/material-core/inert-others";
4
- //#region lib/components/ui/overlays/dialog/use-inert-others.ts
5
- function a(a, o) {
6
- let s = typeof a == "string" ? t(a) : a;
7
- n(o, (t, n, a) => {
8
- if (!t || typeof document > "u") return;
9
- let o, c = !1;
10
- e(() => {
11
- let e = r(s);
12
- !c && e instanceof Element && (o = i(e));
13
- }), a(() => {
14
- c = !0, o?.();
15
- });
16
- }, {
17
- immediate: !0,
18
- flush: "post"
19
- });
20
- }
21
- //#endregion
22
- export { a as t };