@nxgt/material 3.49.0 → 3.51.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 (99) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +1 -1
  3. package/dist/{async-slice-DlvKCKAa.js → async-slice-BGtmF3K5.js} +2 -2
  4. package/dist/{button-DJgaQbBD.js → button-BWcr2b49.js} +45 -76
  5. package/dist/charts.js +3 -3
  6. package/dist/{chip-DN44R1vW.js → chip-CmLW6ZrT.js} +3 -3
  7. package/dist/{code-editor-BJ9ewxSC.js → code-editor-BKMaeIF6.js} +6 -6
  8. package/dist/code-editor.js +5 -5
  9. package/dist/{code-language-items-BJE3VuQg.js → code-language-items-Cs1Pwd1h.js} +2 -2
  10. package/dist/components/ui/layout/app-shell/activity-backdrop-shapes.css +14 -9
  11. package/dist/components/ui/layout/auth-card/auth-card-backdrop.css +2 -2
  12. package/dist/{components-AtBccCir.js → components-CuwU9Sux.js} +11878 -11614
  13. package/dist/components.js +26 -27
  14. package/dist/{dialog-ODql4vH7.js → dialog-TdT_5iUc.js} +7 -7
  15. package/dist/dnd.js +1 -1
  16. package/dist/{dropdown-menu-DRXYP9ta.js → dropdown-menu-BozV6Ikw.js} +25 -26
  17. package/dist/editor.js +9 -9
  18. package/dist/{empty-state-C9QqvBU1.js → empty-state-gzBPimAo.js} +2 -2
  19. package/dist/{escape-owner-DCrlERLM.js → escape-owner-Bu-aDEEO.js} +1 -1
  20. package/dist/{field-blur-DYGyIdfD.js → field-blur-47zmuDkH.js} +1 -1
  21. package/dist/{field-blur-context-B-fSpl1H.js → field-blur-context-6McYOnM8.js} +1 -1
  22. package/dist/hooks.js +2 -2
  23. package/dist/{hover-card-EO_CckIx.js → hover-card-zHdZJpBG.js} +14 -14
  24. package/dist/{i18n-provider-C0fB0sEL.js → i18n-provider-Ang2f31m.js} +1 -1
  25. package/dist/i18n.js +5 -5
  26. package/dist/{icon-badge-BQYj_LE6.js → icon-badge-CRFGIQPX.js} +3 -3
  27. package/dist/{icon-button-B481L2TP.js → icon-button-CtG3Snfm.js} +119 -87
  28. package/dist/lib/components/ui/actions/icon-button/icon-button-busy.d.ts +22 -0
  29. package/dist/lib/components/ui/actions/icon-button/icon-button-shape.d.ts +2 -0
  30. package/dist/lib/components/ui/actions/icon-button/icon-button.d.ts +7 -0
  31. package/dist/lib/components/ui/collections/data-table/data-table-selection-host.d.ts +7 -0
  32. package/dist/lib/components/ui/collections/data-table/internal.d.ts +1 -0
  33. package/dist/lib/components/ui/collections/data-table/partials/layouts/data-table-list-bar.d.ts +2 -1
  34. package/dist/lib/components/ui/collections/filter/bar/partials/filter-quick-toggle.d.ts +3 -2
  35. package/dist/lib/components/ui/collections/filter/sections/filter-sections-classes.d.ts +7 -1
  36. package/dist/lib/components/ui/collections/page-footer/page-footer.d.ts +6 -1
  37. package/dist/lib/components/ui/collections/row-menu/row-menu-trigger.d.ts +5 -2
  38. package/dist/lib/components/ui/collections/server-table/index.d.ts +1 -1
  39. package/dist/lib/components/ui/collections/server-table/partials/filtered-server-table.d.ts +8 -0
  40. package/dist/lib/components/ui/collections/server-table/partials/server-table-band.d.ts +10 -1
  41. package/dist/lib/components/ui/collections/server-table/partials/server-table-bulk-buttons.d.ts +3 -2
  42. package/dist/lib/components/ui/collections/server-table/partials/server-table-bulk-more.d.ts +4 -2
  43. package/dist/lib/components/ui/collections/server-table/partials/server-table-bulk.d.ts +2 -1
  44. package/dist/lib/components/ui/collections/server-table/partials/server-table-controls.d.ts +16 -0
  45. package/dist/lib/components/ui/collections/server-table/partials/server-table-empty-chips.d.ts +5 -3
  46. package/dist/lib/components/ui/collections/server-table/partials/server-table-grid.d.ts +3 -1
  47. package/dist/lib/components/ui/collections/server-table/partials/server-table-selection-bar.d.ts +17 -0
  48. package/dist/lib/components/ui/collections/server-table/partials/server-table-view.d.ts +15 -0
  49. package/dist/lib/components/ui/collections/server-table/partials/use-server-table-phone-row.d.ts +14 -0
  50. package/dist/lib/components/ui/collections/server-table/partials/use-server-table-selection-slots.d.ts +24 -0
  51. package/dist/lib/components/ui/collections/server-table/types-paging.d.ts +17 -0
  52. package/dist/lib/components/ui/collections/server-table/types.d.ts +3 -10
  53. package/dist/lib/components/ui/collections/server-table/use-selection-band.d.ts +14 -0
  54. package/dist/lib/components/ui/feedback/step-up-dialog/partials/step-up-body.d.ts +5 -3
  55. package/dist/lib/components/ui/feedback/step-up-dialog/partials/step-up-request-error.d.ts +15 -0
  56. package/dist/lib/components/ui/feedback/step-up-dialog/step-up-dialog.d.ts +6 -3
  57. package/dist/lib/components/ui/feedback/step-up-dialog/types.d.ts +12 -1
  58. package/dist/lib/components/ui/feedback/step-up-dialog/use-retry-focus.d.ts +27 -0
  59. package/dist/lib/components/ui/feedback/step-up-dialog/use-retry.d.ts +11 -0
  60. package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
  61. package/dist/lib/components/ui/fields/verification-code/use-settled-focus.d.ts +10 -0
  62. package/dist/lib/components/ui/layout/app-shell/use-keep-shapes-inside.d.ts +12 -1
  63. package/dist/lib/i18n/resources/en/en.d.ts +4 -0
  64. package/dist/lib/i18n/resources/en/material.json.d.ts +6 -2
  65. package/dist/lib/i18n/resources/fr/fr.d.ts +4 -0
  66. package/dist/lib/i18n/resources/fr/material.json.d.ts +6 -2
  67. package/dist/lib/i18n/resources/resources.d.ts +8 -0
  68. package/dist/lib/i18n/translator.d.ts +1 -1
  69. package/dist/lib.js +1 -1
  70. package/dist/main.js +43 -44
  71. package/dist/map.js +8 -8
  72. package/dist/material.css +1 -1
  73. package/dist/pdf-reader.js +1 -1
  74. package/dist/print.js +2 -2
  75. package/dist/{public-CZBz4JVu.js → public-CTZ5RK_Y.js} +20 -20
  76. package/dist/{rich-code-block-597e0_Qm.js → rich-code-block-oTgL-s4W.js} +5 -5
  77. package/dist/{sortable-provider-CK_xAk2H.js → sortable-provider-CpA5NYgD.js} +2 -2
  78. package/dist/{dist-B61_o9Mg.js → spinner-UvqlDsV8.js} +478 -256
  79. package/dist/{stored-language-IaL1Ftn_.js → stored-language-Cb9FMEwk.js} +12 -4
  80. package/dist/theme-picker.js +2 -2
  81. package/dist/{theme-toggle-Dnjn83TJ.js → theme-toggle-BZVz6O5q.js} +1 -1
  82. package/dist/{toolbar-items-CCwIu7fE.js → toolbar-items-C09bJphe.js} +106 -106
  83. package/dist/{translate-material-v9u-e7Kd.js → translate-material-D6KCK21i.js} +2 -2
  84. package/dist/{translator-Dk4_qE6U.js → translator-BnQXtpSZ.js} +1 -1
  85. package/dist/{use-chat-composer-Ot5XUbdn.js → use-chat-composer-AZAj4tFJ.js} +4881 -4882
  86. package/dist/{button-tones-BwuVvAmd.js → use-click-lock-BZYtTmVt.js} +37 -7
  87. package/dist/{use-heading-spy-DjKixjLm.js → use-heading-spy-C9-A2md2.js} +1 -1
  88. package/dist/{use-material-translation-DOkTaWK_.js → use-material-translation-pLgSqqLw.js} +2 -2
  89. package/docs/components/buttons-and-actions.md +2 -1
  90. package/docs/components/data-table-filter-and-lists.md +67 -14
  91. package/docs/components/feedback-and-status.md +22 -3
  92. package/docs/components/form-fields.md +7 -0
  93. package/docs/guides/backgrounds.md +3 -1
  94. package/docs/guides/cards-and-tiles.md +3 -0
  95. package/docs/guides/server-lists-url.md +3 -2
  96. package/lib/components/ui/layout/app-shell/activity-backdrop-shapes.css +14 -9
  97. package/lib/components/ui/layout/auth-card/auth-card-backdrop.css +2 -2
  98. package/package.json +1 -1
  99. package/dist/spinner-BhuZuHK1.js +0 -229
@@ -1,6 +1,20 @@
1
- import { createContext as e } from "react";
1
+ import { createContext as e, useCallback as t, useEffect as n, useRef as r, useState as i } from "react";
2
2
  //#region lib/components/ui/primitives/icon/icon-context.ts
3
- var t = e(!1), n = {
3
+ var a = e(!1);
4
+ //#endregion
5
+ //#region lib/components/ui/actions/button/busy-click-capture.ts
6
+ function o(e, t, n) {
7
+ return (r) => {
8
+ if (e || t.isLocked()) {
9
+ r.preventDefault(), r.stopPropagation();
10
+ return;
11
+ }
12
+ t.lock(), n?.(r);
13
+ };
14
+ }
15
+ //#endregion
16
+ //#region lib/components/ui/actions/button/button-tones.ts
17
+ var s = {
4
18
  primary: "text-primary-text",
5
19
  secondary: "text-secondary",
6
20
  success: "text-success dark:text-[color-mix(in_oklab,var(--color-success)_85%,white)]",
@@ -8,7 +22,7 @@ var t = e(!1), n = {
8
22
  warning: "text-[color-mix(in_oklab,var(--color-warning)_85%,black)] dark:text-[color-mix(in_oklab,var(--color-warning)_85%,white)]",
9
23
  error: "text-[color-mix(in_oklab,var(--color-error)_85%,black)] dark:text-[color-mix(in_oklab,var(--color-error)_70%,white)]",
10
24
  default: "text-foreground"
11
- }, r = {
25
+ }, c = {
12
26
  primary: "[&_svg]:data-[slot=icon]:text-primary-text",
13
27
  secondary: "[&_svg]:data-[slot=icon]:text-secondary",
14
28
  success: "[&_svg]:data-[slot=icon]:text-success dark:[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-success)_85%,white)]",
@@ -16,7 +30,7 @@ var t = e(!1), n = {
16
30
  warning: "[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-warning)_85%,black)] dark:[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-warning)_85%,white)]",
17
31
  error: "[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-error)_85%,black)] dark:[&_svg]:data-[slot=icon]:text-[color-mix(in_oklab,var(--color-error)_70%,white)]",
18
32
  default: "[&_svg]:data-[slot=icon]:text-foreground"
19
- }, i = {
33
+ }, l = {
20
34
  primary: "[&_svg]:stroke-primary-text",
21
35
  secondary: "[&_svg]:stroke-secondary",
22
36
  success: "[&_svg]:stroke-success dark:[&_svg]:stroke-[color-mix(in_oklab,var(--color-success)_85%,white)]",
@@ -24,7 +38,7 @@ var t = e(!1), n = {
24
38
  warning: "[&_svg]:stroke-[color-mix(in_oklab,var(--color-warning)_85%,black)] dark:[&_svg]:stroke-[color-mix(in_oklab,var(--color-warning)_85%,white)]",
25
39
  error: "[&_svg]:stroke-[color-mix(in_oklab,var(--color-error)_85%,black)] dark:[&_svg]:stroke-[color-mix(in_oklab,var(--color-error)_70%,white)]",
26
40
  default: "[&_svg]:stroke-foreground"
27
- }, a = {
41
+ }, u = {
28
42
  primary: "[--morph-on:var(--color-primary)] [--morph-on-fg:var(--color-primary-foreground)] [--btn-focus:var(--color-primary-text)] [--btn-line:var(--color-primary)]",
29
43
  secondary: "[--morph-on:var(--color-secondary)] dark:[--morph-on:color-mix(in_oklab,var(--color-secondary)_70%,var(--color-background))] [--morph-on-fg:var(--color-secondary-foreground)] [--btn-focus:var(--color-secondary)] [--btn-line:var(--color-secondary)]",
30
44
  success: "[--morph-on:var(--color-success)] dark:[--morph-on:color-mix(in_oklab,var(--color-success)_60%,var(--color-background))] [--morph-on-fg:var(--color-success-foreground)] [--btn-focus:var(--color-success)] dark:[--btn-focus:color-mix(in_oklab,var(--color-success)_85%,white)] [--btn-line:var(--color-success)]",
@@ -32,6 +46,22 @@ var t = e(!1), n = {
32
46
  warning: "[--morph-on:var(--color-warning)] dark:[--morph-on:color-mix(in_oklab,var(--color-warning)_60%,var(--color-background))] [--morph-on-fg:var(--color-warning-foreground)] [--btn-focus:color-mix(in_oklab,var(--color-warning)_85%,black)] dark:[--btn-focus:color-mix(in_oklab,var(--color-warning)_85%,white)] [--btn-line:var(--color-warning)]",
33
47
  error: "[--morph-on:var(--color-error)] [--morph-on-fg:var(--color-error-foreground)] [--btn-focus:color-mix(in_oklab,var(--color-error)_85%,black)] dark:[--btn-focus:color-mix(in_oklab,var(--color-error)_70%,white)] [--btn-line:var(--color-error)]",
34
48
  default: "[--morph-on:var(--color-foreground)] [--morph-on-fg:var(--color-background)] [--btn-focus:var(--color-foreground)] [--btn-line:var(--color-foreground)]"
35
- };
49
+ }, d = 1e3;
50
+ function f(e, a) {
51
+ let o = r(!1), s = r(!1), c = r(void 0), [l, u] = i(!1), f = t(() => {
52
+ o.current = !1, s.current = !1, clearTimeout(c.current), u(!1);
53
+ }, []), p = t(() => {
54
+ e && (o.current = !0, u(!0), clearTimeout(c.current), c.current = setTimeout(() => {
55
+ s.current || f();
56
+ }, d));
57
+ }, [e, f]);
58
+ return n(() => {
59
+ o.current && (a ? s.current = !0 : s.current && f());
60
+ }, [a, f]), n(() => () => clearTimeout(c.current), []), {
61
+ locked: l,
62
+ lock: p,
63
+ isLocked: t(() => o.current, [])
64
+ };
65
+ }
36
66
  //#endregion
37
- export { t as a, a as i, i as n, n as r, r as t };
67
+ export { u as a, s as i, c as n, o, l as r, a as s, f as t };
@@ -1,4 +1,4 @@
1
- import { r as e } from "./i18n-provider-C0fB0sEL.js";
1
+ import { r as e } from "./i18n-provider-Ang2f31m.js";
2
2
  import { _ as t, g as n, h as r, n as i, v as a, y as o } from "./number.utils-BG_eEt5g.js";
3
3
  import { useCallback as s, useEffect as c, useMemo as l, useRef as u, useState as d } from "react";
4
4
  import { busySettler as f, busySettler as p, createSubmitGuard as m, createSubmitGuard as h } from "@nxgt/material-core/submit-guard";
@@ -1,5 +1,5 @@
1
- import { r as e } from "./i18n-provider-C0fB0sEL.js";
2
- import { t } from "./translate-material-v9u-e7Kd.js";
1
+ import { r as e } from "./i18n-provider-Ang2f31m.js";
2
+ import { t } from "./translate-material-D6KCK21i.js";
3
3
  import { useCallback as n } from "react";
4
4
  //#region lib/i18n/use-material-translation.ts
5
5
  function r() {
@@ -7,7 +7,7 @@ and language switches. Everything is imported from `@nxgt/material/components`.
7
7
  | | Component | What it does |
8
8
  | --- | --- | --- |
9
9
  | <img src="../previews/buttons-actions-button.webp" width="96" alt=""> | [Button](#button) | Five variants in seven colours, six sizes, a round or square shape and a selected state, with a loading state |
10
- | <img src="../previews/buttons-actions-iconbutton.webp" width="96" alt=""> | [IconButton](#iconbutton) | Icon-only button in five sizes and three widths, with an optional tooltip |
10
+ | <img src="../previews/buttons-actions-iconbutton.webp" width="96" alt=""> | [IconButton](#iconbutton) | Icon-only button in five sizes and three widths, with an optional tooltip and a loading state |
11
11
  | <img src="../previews/buttons-actions-buttongroup.webp" width="96" alt=""> | [ButtonGroup](#buttongroup) | Joins adjacent buttons into one control; the expressive look widens the pressed one |
12
12
  | <img src="../previews/buttons-actions-splitbutton.webp" width="96" alt=""> | [SplitButton](#splitbutton) | Main action plus a menu of related ones, Material 3 Expressive |
13
13
  | <img src="../previews/buttons-actions-fab.webp" width="96" alt=""> | [Fab](#fab) | The screen's main action, floating; extended with its label, folds on scroll |
@@ -180,6 +180,7 @@ import { Pencil, Trash2 } from 'lucide-react';
180
180
  | `morph` | `'round' \| 'pill' \| 'none'` | `'pill'` | what a **toggle** (`aria-pressed` set) does when pressed: `pill` fills with its colour and widens by 14 px into a pill, `round` fills and stays a circle, `none` keeps the look your classes give it. Not set on a button without `aria-pressed`, one with a `shape`, or a Toolbar item (the Toolbar's own look) |
181
181
  | `badge` | `number` | — | a count on the top end corner (top left in a right-to-left page): "99+" past 99, nothing at 0 or unset. It takes no room, so the box and the 44 px hit area are unchanged. It is **decorative** (`aria-hidden`): put the number in the accessible name yourself, e.g. `aria-label="Publish 2 items"` |
182
182
  | `badgeTone` | `'primary' \| 'error' \| 'neutral'` | `'primary'` | the badge's colour: a count is not an alert, so `primary`; `error` for a notification; `neutral` is the page's text colour on its background |
183
+ | `loading` | `boolean` | `false` | busy, as `Button`'s: `aria-busy` and `aria-disabled`, a spinner in place of the icon (the button keeps its size) and every click blocked. It stays focusable (no native `disabled`), so the focus is not lost while the work runs |
183
184
 
184
185
  **A count badge** (`badge`) is a primary pill (`badgeTone="error"` is the menu-item badge's red), ringed in the page background so it reads over any variant, and sits half outside the corner, over the neighbours' gap (leave 4 px; a `Toolbar` clips 4 px outside its items, so leave room there too). It does not dim when the button is disabled. It never names itself: `<IconButton badge={2} aria-label="Publish 2 items">`. `Button` has no `badge`: its label can be cut with an ellipsis and an `asChild` one clips, so compose a `CountBadge` around it.
185
186
 
@@ -15,9 +15,9 @@ either can be used alone.
15
15
  | --- | --- | --- |
16
16
  | <img src="../previews/data-table-filter-lists-datatable.webp" width="96" alt=""> | [DataTable](#datatable) | TanStack Table v9 with sorting, selection, expansion and pagination, client-side or one server page at a time |
17
17
  | <img src="../previews/data-table-filter-lists-servertable.webp" width="96" alt=""> | [ServerTable](#servertable) | A server-mode `DataTable` driven by the URL, with the Filter in its top band (a title and a description, or neither): sort, cursor paging, the empty page with a scene, an action and removable chips, one count announced |
18
- | <img src="../previews/data-table-filter-lists-rowmenu.webp" width="96" alt=""> | [RowMenu](#rowmenu) | A row's "..." button named after its row ("Actions for Ada"), its menu aligned to the end with the destructive item last, and `rowMenuColumn` to drop both in a table |
18
+ | <img src="../previews/data-table-filter-lists-rowmenu.webp" width="96" alt=""> | [RowMenu](#rowmenu) | A row's "..." button named after its row ("Actions for Ada"), its menu aligned to the end with the destructive item last, a loading state while an action runs, and `rowMenuColumn` to drop both in a table |
19
19
  | <img src="../previews/data-table-filter-lists-listempty.webp" width="96" alt=""> | [ListEmpty](#listempty) | A list's empty page: nothing yet, with a scene and an action, or nothing matches and the way out |
20
- | <img src="../previews/data-table-filter-lists-pagefooter.webp" width="96" alt=""> | [PageFooter](#pagefooter) | The page footer of a feed that is not a table (cards, a news feed) under a FilterBar: the range and Previous / Next as links from the URL's cursor trail, `pageControls="icon"`; no `totalCount` for a feed with no total (no range, Previous / Next only) |
20
+ | <img src="../previews/data-table-filter-lists-pagefooter.webp" width="96" alt=""> | [PageFooter](#pagefooter) | The page footer of a feed that is not a table (cards, a news feed) under a FilterBar: the range and Previous / Next as links from the URL's cursor trail, `pageControls="icon"`; no `totalCount` for a feed with no total (no range, Previous / Next only); `hideOnSinglePage` hides it on a single page |
21
21
  | <img src="../previews/data-table-filter-lists-datagrid.webp" width="96" alt=""> | [DataGrid](#datagrid) | An editable grid: spreadsheet keys, paste, a fill handle with series, undo, totals, saving with versions and conflicts (lazy ones fetch theirs on "Keep mine"), `onRefuse` for a refused paste or fill, row selection (`enableRowSelection`), and resizable, hideable and reorderable columns (`columnControls`, `columnLayout`, `DataGridColumnsMenu`) |
22
22
  | <img src="../previews/data-table-filter-lists-daygroupedlist.webp" width="96" alt=""> | [DayGroupedList](#daygroupedlist) | A list split by day under sticky Today / Yesterday / Earlier headings |
23
23
  | <img src="../previews/data-table-filter-lists-filter.webp" width="96" alt=""> | [Filter](#filter) | Schema-driven filters with chips, presets and persistence |
@@ -244,8 +244,10 @@ with a `select` column and `actions`, the toolbar keeps its band while
244
244
  nothing is selected, so the first selection does not move the rows (on a
245
245
  wide screen; on a phone the extra lines still push them down). Below 768px
246
246
  the declarative `bulkActions` fold past the third into a "More" menu in the
247
- toolbar too, so a list that stays a table on a phone (`layout="table"`) shows
248
- three actions and "More", as the cards and tiles do (#1797).
247
+ toolbar (a `ServerTable` without `filter`, or with the facets panel) and in a
248
+ phone's second row (#1719), as the cards and tiles do (#1797). The
249
+ [selection band](#the-selection-band) keeps one action and puts the rest in
250
+ "More".
249
251
 
250
252
  ```tsx
251
253
  <DataTable
@@ -293,6 +295,33 @@ press elsewhere is never pulled back. The footer's selected count is a polite
293
295
  status, so the first selection is announced ("1 row selected") when its bar
294
296
  shows up.
295
297
 
298
+ ### The selection band
299
+
300
+ With `bulkActions` and a `filter` (not `variant: 'facets'`), the first
301
+ selected row turns the band that holds the filter bar into a selection bar, in
302
+ place and on the same row: "N selected", the bulk actions, and a tonal
303
+ "Cancel" that clears the selection (#1718). The search and the page's
304
+ `actions` stay mounted, hidden (`invisible`, `inert`) and keep their size, so
305
+ the table does not move; clearing the selection brings them back. The bar's
306
+ height is the row's (40px, 44px on a coarse pointer). From 768px every
307
+ declarative action keeps its button; below it the bar is one row: the first
308
+ action keeps its button, the others go into "More" (an icon, still named),
309
+ and "Cancel" is an icon (its name and tooltip stay). Bulk actions written as a
310
+ function are rendered as they are, in the same place.
311
+
312
+ After "Cancel" the focus goes back to the control that started the selection
313
+ (the row's or the header's checkbox) when it is still on the page, else to the
314
+ list; a selection cleared another way (a bulk action's `selection.clear()`, a
315
+ new query) keeps its own focus rule. While rows are selected the search is
316
+ `inert`, so `filterRef.focus()` does nothing: cancel the selection first. The count is told once, politely, by the
317
+ footer's status ("N rows selected"); the bar's own count is plain text. The
318
+ cards' and tiles' list bar leaves its count and "Clear selection" to the bar.
319
+ A phone's second row of a list in cards or tiles with `actions` (#1719) is the
320
+ selection bar there, as before. Without `filter` (or with the facets panel) the
321
+ band has no filter bar to replace, and the bulk actions stay in the table's
322
+ toolbar. Text keys: `material.server-table.bulk-selected`, `bulk-cancel`,
323
+ `bulk-bar` (English and French).
324
+
296
325
  ### Focus after a bulk action
297
326
 
298
327
  When a bulk action clears the selection, its buttons unmount and the focus
@@ -410,7 +439,9 @@ grow the toolbar. Without the `select` column, or without `actions`, no band
410
439
  is kept above the table. `reserveActions={false}` keeps none either: the bar
411
440
  arrives on the first selection with a short grow (none under reduced motion),
412
441
  and the rows move down by its height. `ServerTable` passes `false`, so its
413
- bulk bar leaves no empty row under the band while nothing is selected.
442
+ bulk bar leaves no empty row under the band while nothing is selected. With
443
+ `filter` it has no toolbar row at all: the band's selection bar holds the bulk
444
+ actions ([the selection band](#the-selection-band)).
414
445
 
415
446
  ```tsx
416
447
  <DataTable
@@ -818,8 +849,8 @@ export default function Students() {
818
849
  - **Nothing matches.** The empty page is [`ListEmpty`](#listempty), with no
819
850
  header row and no footer. With `filter`, it lists the user's filters as
820
851
  removable chips (the band's row keeps the locked ones only, so none is
821
- listed twice), then "Clear all" while two of them or a search remain;
822
- removing one moves the focus to the next chip, then "Clear all", then the
852
+ listed twice), then "Clear all" (worded by `empty.clear`) whenever a chip or a search
853
+ remains, a single chip included, the locked conditions staying; removing one moves the focus to the next chip, then "Clear all", then the
823
854
  search. When rows come back while the focus was on the empty page, it
824
855
  goes to the search; rows that come back on their own leave the focus
825
856
  alone. The empty page stays while the next answer loads (`data-busy`).
@@ -843,7 +874,8 @@ export default function Students() {
843
874
  onRetry={() => fetcher.reload()}
844
875
  />
845
876
  ```
846
- - **Selection.** With `bulkActions(ids, selection)`, rows are selectable;
877
+ - **Selection.** With `bulkActions(ids, selection)`, rows are selectable
878
+ (see [the selection band](#the-selection-band) for where the bulk actions show);
847
879
  the selection is kept across pages and drops on a new query.
848
880
  `selection.clear()` empties it and moves the focus to the table
849
881
  (`tabindex="-1"`, by script only; the list of cards or tiles in those layouts); `selection.deselect(ids)` drops rows
@@ -995,6 +1027,7 @@ Its props are `ServerTableProps<T>`; `filter` is a `ServerTableFilter`,
995
1027
  | `actions` | `ReactNode` | — | the page's actions, at the band's end |
996
1028
  | `bandDensity` | `'comfortable' \| 'compact'` | `'comfortable'` | (the same prop as [`Filter`](#a-compact-band-for-a-list-inside-a-page-section)'s bar) `'compact'` halves the room above the band (16px to 8px) and under it (12px to 6px), for a list under a sticky app bar and a breadcrumb; the controls keep their size (the Filter's own `density` is the controls') |
997
1029
  | `pageControls` | `'labelled' \| 'icon'` | `'labelled'` | the footer's Previous / Next: `'icon'` is icon-only, with a tooltip and the names "Previous page" / "Next page" (`DataTable`'s default is unchanged); on a coarse pointer the icon controls are 44x44 boxes (still painted 32px) |
1030
+ | `hideOnSinglePage` | `boolean` | `false` | hides the footer's range and Previous / Next when the result is one page (no previous, no next), in the table, cards and tiles layouts; the count is still announced by the live region but no longer shown, so put it in `description` or keep the footer. With `bulkActions` the footer stays for "N rows selected", without its pager |
998
1031
  | `bandActions` | `'stacked' \| 'inline'` | `'stacked'` | with `filter`, where `actions` sit below 768px: `'stacked'` gives them a row above the search; `'inline'` keeps them on the search's row (the search takes the room left), for a lone icon action such as a "+". An action wider than 3.5rem wraps under the search. From 768px both are the same. With a cards or tiles list on a phone, neither applies: [the two-row header](#the-phone-header-of-a-list-in-cards-or-tiles) puts the actions on its second row |
999
1032
  | `filterRef` | `Ref<ServerTableFilterHandle>` | — | with `filter`: `filterRef.current.focus()` focuses the Filter's first control, the search, else the first quick filter, "Add filter" or "Filters (N)" the width shows |
1000
1033
  | `sorting` | `createSorting(…)` | — | headers sorted from the URL |
@@ -1035,7 +1068,8 @@ rows (the bulk bar is not drawn under it). With the declarative list form of
1035
1068
  (2)", named "Publish 2 rows"), hidden at 0 rows; the buttons wrap onto a second
1036
1069
  line rather than scroll, and past the third action the rest go into a "More"
1037
1070
  menu (`material.server-table.bulk-more`), each item keeping its count (the same
1038
- fold applies below 768px to every layout, a phone table included). From
1071
+ fold applies below 768px to every layout in the toolbar and in this row; the
1072
+ [selection band](#the-selection-band) of a list with `filter` keeps one action). From
1039
1073
  768px the buttons read "Label (n)" as before. When the focused button
1040
1074
  disappears the focus goes to the next one, else to "More", else to "Select all"; an action past `max` shows its notice there (an inline text, or a disabled More item). The function
1041
1075
  form (`bulkActions={(ids) => node}`) is drawn whole in the row, so keep it to
@@ -1168,6 +1202,7 @@ and `ref`, so it is the child of a `DropdownMenuTrigger asChild`.
1168
1202
  | Prop | Type | Default | Effect |
1169
1203
  | --- | --- | --- | --- |
1170
1204
  | `label` (`RowMenu`, `RowMenuTrigger`) | `string` | — | the row's name; the button's name is "Actions for {label}" (translated). An `aria-label` of your own wins |
1205
+ | `loading` (`RowMenuTrigger`, `triggerProps.loading` of `RowMenu`) | `boolean` | `false` | an action chosen from this row's menu is running: `aria-busy`, a spinner in place of the dots (the button keeps its size), and the menu does not open again until it is over. The button stays focusable and keeps the focus. As `Button`'s `loading` |
1171
1206
  | `items` (`RowMenu`) | `{ label, icon?, onSelect?, disabled?, destructive?, id? }[]` | `[]` | the actions; `destructive` ones are drawn in the error colour and moved last, after a separator |
1172
1207
  | `children` (`RowMenu`) | `ReactNode` | — | menu content of your own, above `items` (put a destructive item last yourself); its menu items are 44px high on a coarse pointer too |
1173
1208
  | `triggerProps` (`RowMenu`) | button props | — | `disabled`, `className`, `data-*` on the button |
@@ -1175,6 +1210,17 @@ and `ref`, so it is the child of a `DropdownMenuTrigger asChild`.
1175
1210
  | `label`, `items`, `menu` (`rowMenuColumn`) | `(row) => string` / `(row) => RowMenuItem[]` / `(row) => ReactNode` | — | the row's name, its actions as data, or its menu content as nodes |
1176
1211
  | `id`, `className` (`rowMenuColumn`) | `string` | `'actions'`, `'w-12'` | the column's id and its cell classes |
1177
1212
 
1213
+ While an action runs, set `loading` on its row's button. The button keeps its size and
1214
+ the focus, so the focus does not fall to the page when the menu closes:
1215
+
1216
+ ```tsx
1217
+ <RowMenu
1218
+ label={person.name}
1219
+ items={[{ label: 'Archive', onSelect: () => archive.mutate(person.id) }]}
1220
+ triggerProps={{ loading: archive.isPending && archive.variables === person.id }}
1221
+ />
1222
+ ```
1223
+
1178
1224
  Focus returns to the button when the menu closes, and when a dialog opened from
1179
1225
  an item (a `ConfirmDialog`, a `Dialog`, a `Sheet`) closes: the item is gone by
1180
1226
  then, so the dialog hands the focus back to the menu's button.
@@ -1249,6 +1295,12 @@ A cursor feed whose API answers `pageInfo` and the items but no total (a news or
1249
1295
  <PageFooter pageInfo={pageInfo} params={params} pageSize={20} count={items.length} pageControls="icon" />
1250
1296
  ```
1251
1297
 
1298
+ A list that fits one page shows "1–5 of 5" by default. `hideOnSinglePage` renders nothing when there is no previous and no next page (`ServerTable` takes the same prop, in every layout). The range is gone from the screen then: tell the count elsewhere, a heading or a status line, or keep the footer.
1299
+
1300
+ ```tsx
1301
+ <PageFooter pageInfo={pageInfo} totalCount={5} params={params} pageSize={20} count={5} hideOnSinglePage />
1302
+ ```
1303
+
1252
1304
  | Prop | Type | Default | Effect |
1253
1305
  | --- | --- | --- | --- |
1254
1306
  | `params` | `URLSearchParams` | — | the page's search params: the trail is read from them and every other param is kept in the hrefs |
@@ -1259,6 +1311,7 @@ A cursor feed whose API answers `pageInfo` and the items but no total (a news or
1259
1311
  | `pageHref` | `(params) => string` | ``?${params}`` | a page's href |
1260
1312
  | `pagingNames` | `{ after?, trail? }` | `after`, `trail` | the cursor's search param names |
1261
1313
  | `pageControls` | `'labelled' \| 'icon'` | `'labelled'` | icon-only controls with a tooltip; 44x44 boxes on a coarse pointer |
1314
+ | `hideOnSinglePage` | `boolean` | `false` | renders nothing when there is no previous and no next page; the range is then not shown, so tell the count elsewhere (a heading, a status line) or keep the footer |
1262
1315
  | `live` | `boolean` | `true` | `false` renders the range as plain text instead of a polite live region |
1263
1316
  | `disabled` | `boolean` | `false` | makes Previous and Next inert while the feed loads |
1264
1317
  | `linkComponent` | `DataTableLinkComponent` | `<a>` | your router's link; it must forward `className` and `ref` |
@@ -1484,7 +1537,7 @@ filter values become one form value. See [forms](../guides/forms.md).
1484
1537
  `variant="bar"` is a query-driven bar over
1485
1538
  `@nxgt/material-core/filter`. So far: the search; the quick filters, a group
1486
1539
  named "Quick filters" (a dashed "+ Status" pill that opens the field's
1487
- editor, or a toggle for a switch or checkbox field); "Add filter", a menu
1540
+ editor, or a toggle chip for a switch or checkbox field: its label alone while off, the chip's check mark once on); "Add filter", a menu
1488
1541
  that picks a field, then an operator (skipped when the field has one), then
1489
1542
  its value, applied as it changes, Done (`aria-disabled`, with its reason described, while the value cannot be read) returning the focus to "Add filter"
1490
1543
  and announcing the filter ("Level is CE1, 3 results"), Escape or a click
@@ -1492,7 +1545,7 @@ elsewhere dropping it; the active filters as chips in a list named "Active
1492
1545
  filters" (each opens its editor, titled with its field and operator: select,
1493
1546
  multi-select, radio group, chips, text, switch, checkbox, autocomplete, date, date range, time, time range, a custom field the page renders; "is empty" takes
1494
1547
  no value); the page's locked conditions (a lock, no ✕); "Clear all" from two
1495
- filters; live apply and one polite live region telling the settled count.
1548
+ filters (on the empty page, from one); live apply and one polite live region telling the settled count.
1496
1549
  `/` focuses the search and `F` opens "Add filter" (never while typing, with
1497
1550
  a modifier, or while a menu or dialog is open; on a phone, `F` opens the
1498
1551
  sheet). Below 768px, a "Filters (N)" button opens the phone sheet (below). That button is `FilterSheetTrigger`, exported: to place it elsewhere, render `<FilterSheetTrigger />` inside `ServerTable` (in its `actions`, or anywhere else in its tree) and hide the bar's own with `filter.sheetTrigger: false`; `Filter` takes no children, and outside a filter scope the trigger throws (see the troubleshooting page). `filter.variant: 'facets'` keeps its own. It opens the one sheet the bar mounts, `F` still opens it, and the focus comes back to the button that opened it.
@@ -1884,7 +1937,7 @@ a word, never colour alone): a chip, a quick pill, a scope pill (on a phone
1884
1937
  too), a quick switch or checkbox toggle, and the default chip. It is described "Invalid: <message>"; its
1885
1938
  editor and its facets section show the message under the title; and the
1886
1939
  polite live region tells it ("Period: The period cannot exceed 366 days."),
1887
- once, after the count, also when the page opens with it; the next count replaces it. A dashed control (a toggle, the default chip) keeps its dashes
1940
+ once, after the count, also when the page opens with it; the next count replaces it. A dashed control (the default chip) keeps its dashes
1888
1941
  and takes the error tone on its line and text. Only a field of the model that the page does not lock shows
1889
1942
  one. The page clears it when the condition changes, by passing no error. The
1890
1943
  words are `material.filter.error.*`; `fieldErrors(errors, model, locked)` in
@@ -1948,7 +2001,7 @@ field's, once and after the count: "Search: Not a valid item." and the
1948
2001
  search or the query changes, by passing nothing, as it does for `errors`.
1949
2002
 
1950
2003
  When the loader answers one flat record, `splitFilterErrors(refusal, model)`
1951
- from `@nxgt/material-core/filter` splits it: the key equal to the model's
2004
+ (from `@nxgt/material/components`, usable in a loader) splits it: the key equal to the model's
1952
2005
  search param becomes `searchError`, the key `FILTER_FORM_ERROR_KEY`
1953
2006
  (`'$form'`, never a field id) becomes `formError`, and the rest stays in
1954
2007
  `errors`. Messages are trimmed and an empty one is dropped:
@@ -1971,7 +2024,7 @@ search param, so `q` is free for a field. `model.search.enabled` tells.
1971
2024
 
1972
2025
  Fields declared `quick: true` in the model (or listed in `quick`) show as
1973
2026
  pills beside the search, before any chip: a dashed "+ Level" pill opens the
1974
- field's editor, and a switch or checkbox field is a toggle.
2027
+ field's editor, and a switch or checkbox field is a toggle chip (`aria-pressed`): its label alone, not dashed and with no plus while off, the chip's check mark once on.
1975
2028
 
1976
2029
  ```tsx
1977
2030
  <Filter
@@ -418,20 +418,39 @@ export function ConfirmItsYou({
418
418
  | `via` | `'email' \| 'secondFactor'` | — | how the code was issued; left out, the code is still being requested: no field, a status line. Changing it starts the form again (typed value, recovery switch) |
419
419
  | `destination` | `ReactNode` | — | where an e-mailed code went; named in the description |
420
420
  | `requestError` | `ReactNode` | — | the request for a code failed: an alert in place of the field |
421
+ | `onRetry` | `() => unknown` | — | with `requestError`, the alert holds a "Retry" button (`labels.retry`, "Réessayer" in French) that calls it. While the returned promise is pending the button is busy and keeps the focus. Clear `requestError` and set `via` and the code field takes the focus; set a new `requestError` and the focus stays on "Retry" and the new text is announced. Left out, there is no button |
421
422
  | `attemptsLeft`, `locked`, `error`, `retryAfter`, `pending` | as `VerificationCode` | — | the answer to the last code (`error="throttled"` with `retryAfter` seconds disables "Resend the code" for that long and never locks; the footer is unchanged). No tries left: an e-mailed code reads "expired, ask for a new code"; an app or recovery code shows the locked alert and writes the page-wide lock |
422
423
  | `length`, `allowRecovery` | `number`, `boolean` | `6`, per `via` | the slots in the code field, and "Use a recovery code" (on for `via="secondFactor"`), passed to the `VerificationCode` |
424
+ | `autoFocus` | `boolean` | — | the code field, not the title, takes the focus on open (passed to the `VerificationCode`) |
423
425
  | `onSubmit`, `onResend`, `resent` | as `VerificationCode` | — | your calls; "Resend the code" sits alone on its own line |
424
426
  | `confirming`, `confirmedLabel` | `boolean`, `ReactNode` | — | the code was accepted and you re-run the action: the field goes, a status line shows, Cancel is disabled and Escape does nothing until you close it |
425
427
  | `title`, `description` | `ReactNode` | built-in | the title ("Confirm it's you") and the line under it, which follows `via` |
426
- | `labels`, `icons` | partial objects | built-in | every string of the dialog (`title`, `description`, `descriptionApp`, `descriptionEmail`, `descriptionEmailTo`, `requesting`, `cancel`, `confirmed`) and of `VerificationCode` (`incomplete`, `incompleteRecovery` among them) |
428
+ | `labels`, `icons` | partial objects | built-in | every string of the dialog (`title`, `description`, `descriptionApp`, `descriptionEmail`, `descriptionEmailTo`, `requesting`, `cancel`, `confirmed`, `retry`) and of `VerificationCode` (`incomplete`, `incompleteRecovery` among them) |
429
+
430
+ **Retrying a failed request.** Give `onRetry` and the alert offers a button
431
+ instead of leaving the user to cancel and start over:
432
+
433
+ ```tsx
434
+ <StepUpDialog
435
+ open
436
+ onCancel={close}
437
+ requestError={step.requestError} // "We could not send a code."
438
+ onRetry={async () => {
439
+ // clear requestError and set via on success; set a new requestError on failure
440
+ await requestCode();
441
+ }}
442
+ onSubmit={(code, { kind }) => check(code, kind)}
443
+ />
444
+ ```
427
445
 
428
446
  Confirm is a footer button that submits the form. Pressed with too few digits
429
447
  (or an empty recovery code) it focuses the field and says "Enter all 6 digits."
430
448
  instead of calling `onSubmit`.
431
449
 
432
450
  **Focus.** The title takes the focus on open and whenever the field goes away
433
- (a code being requested, a code accepted); the field takes it after a refusal;
434
- on close it goes back to the button that opened the dialog. A locked code
451
+ (a code being requested, a code accepted), unless `autoFocus` sends it to the
452
+ field on open; "Retry" keeps it while a retry runs and when the request fails again, and the code field takes it once the request succeeds; the field takes it after a refusal and once a resend settles
453
+ (answered or throttled), when "Resend" held the focus as it started; on close it goes back to the button that opened the dialog. A locked code
435
454
  leaves Cancel usable.
436
455
 
437
456
  **On a phone** the footer stays one row, Cancel on the left and Confirm on the
@@ -792,6 +792,7 @@ export function SecondStep({ answer, pending, send }: Props) {
792
792
  | `onResend`, `resent` | `() => unknown`, `boolean` | — | "Resend the code" alone on its own line (an e-mailed code), "New code" / "Try again" once a code expired; `resent` says a new code was sent |
793
793
  | `allowRecovery` | `boolean` | `via === 'secondFactor'` | "Use a recovery code", which swaps the field for a text field and back |
794
794
  | `length` | `number` | `6` | slots in the code field |
795
+ | `autoFocus` | `boolean` | — | puts the focus in the code field on mount |
795
796
  | `id` | `string` | generated | the form's id; the buttons of `actions="none"` point at it with `form={id}`. Other `<form>` props pass through |
796
797
  | `actions` | `'inline' \| 'none'` | `'inline'` | `'none'` leaves the buttons to you; point them at the form with `form={id}` |
797
798
  | `submitLabel`, `secondaryAction` | `ReactNode` | "Confirm" | the button, and what sits beside it (Cancel), which stays usable when locked |
@@ -836,6 +837,12 @@ is described by the line, and the live region leaves it out. The words are
836
837
  `labels.throttled`, `throttledMinutes(n)` and `throttledSeconds(n)` (`throttledMinutesApp(n)` and `throttledSecondsApp(n)` for `via="secondFactor"`). On the
837
838
  server nothing waits and no timer runs.
838
839
 
840
+ **After a resend settles** (answered with or without a throttle), the focus
841
+ returns to the code field when "Resend" held it, or the page did, as the resend
842
+ started (a host that sets `pending` disables the button, which drops the focus
843
+ to the page): WCAG 2.4.3. A focus the user moved elsewhere in the meantime is
844
+ left alone.
845
+
839
846
  **With no tries left** (`attemptsLeft === 0`) the form reads the answer by what
840
847
  the code was: an e-mailed code shows "expired, ask for a new code" (nothing is
841
848
  held against it), and an app or recovery code shows the locked alert, because
@@ -545,7 +545,9 @@ good.
545
545
  - The server renders every glyph. The drop happens after the first measure, on the client, in one frame (boxes read
546
546
  before any write), so the markup never differs between server and client.
547
547
  - A dropped glyph fades out in 150 ms (none under `prefers-reduced-motion`) and fades back when the content moves
548
- away. It runs again on resize and when the content changes.
548
+ away. It runs again on resize and when the content changes. A glyph the first pass drops is hidden at once: the fade
549
+ starts a frame later (`data-settled` on the scene), so no glyph is seen over a paragraph for the length of a fade on
550
+ a reload.
549
551
  - The open areas keep their glyphs; the space behind and between cards is empty. `keepClear={false}` on an `AuthCard`
550
552
  lets the glyphs run under the card.
551
553
  - **`keepClear="gaps"`** (on `ActivityLayout` and `AuthCard`, opt-in; `true` stays the default) lets a glyph stay between
@@ -144,6 +144,9 @@ is not `focusable`). On a narrow list (under 30rem) the bar stays on one row and
144
144
  Clear shrinks to an icon button; its label stays its accessible name, and it has
145
145
  a tooltip. Do not add your own in `actions`: that one is the bulk bar's.
146
146
  Once a bulk action clears the selection and the focus is lost, the list takes it.
147
+ In a `ServerTable` with `filter` and `bulkActions`, the band's own selection bar
148
+ tells the count and has "Cancel" (#1718), so the list bar leaves its count and
149
+ "Clear selection" out ([the selection band](../components/data-table-filter-and-lists.md#the-selection-band)).
147
150
 
148
151
  Inside a `ServerTable` with `filter` and `actions`, below 768px, the bar has no
149
152
  row of its own: its "Select all" (with a short visible label, "Select all"),
@@ -6,7 +6,8 @@ and an edit in the bar writes the URL back. A shared link, a reload and the
6
6
  browser's Back button all land on the same list.
7
7
 
8
8
  It uses the URL codec (`defineFilterModel`, `parseFilterQuery`,
9
- `serializeFilterQuery`), which `@nxgt/material/components` re-exports (it is
9
+ `serializeFilterQuery`, plus `effectiveConditions` and `splitFilterErrors` for
10
+ a loader), which `@nxgt/material/components` re-exports (it is
10
11
  also in `@nxgt/material-core/filter`, so the examples below work with either
11
12
  import) and React Router's
12
13
  framework mode. Any router works the same way: the codec takes and returns
@@ -71,7 +72,7 @@ a condition with no readable value left is still dropped.
71
72
 
72
73
  ```ts
73
74
  // app/routes/students.tsx
74
- import { effectiveConditions, parseFilterQuery } from '@nxgt/material-core/filter';
75
+ import { effectiveConditions, parseFilterQuery } from '@nxgt/material/components';
75
76
  import type { Route } from './+types/students';
76
77
  import { STUDENT_FILTERS } from './students.filters';
77
78
  import { LOCKED } from './students.locked';
@@ -18,15 +18,20 @@
18
18
  visibility: hidden;
19
19
  }
20
20
 
21
- /* A shape over the page's text, controls or surfaces (cards, tables, lists) fades out and comes back once
22
- * clear (watch-content.ts): it is hidden, never moved. A compound body (the
23
- * moon, the planet, the eclipse: `.pt.mg`) is hidden whole, with its halo
24
- * (`.pt.mh`). The scene stays invisible until the first check, so nothing
25
- * flashes over the content. */
26
- [data-keep-inside] .mo > *,
27
- [data-keep-inside] .pt,
28
- [data-slot="activity-layout-backdrop"] [data-background] .mo > *,
29
- [data-slot="activity-layout-backdrop"] [data-background] .pt {
21
+ /* A shape over the page's text, controls or surfaces (cards, tables, lists)
22
+ * fades out and comes back once clear (watch-content.ts): it is hidden,
23
+ * never moved. A compound body (the moon, the planet, the eclipse: `.pt.mg`)
24
+ * is hidden whole, with its halo (`.pt.mh`). The scene stays invisible until
25
+ * the first check, so nothing flashes over the content. The fade waits for
26
+ * `data-settled`, set a frame after the first pass: a shape that pass drops
27
+ * is hidden at once, never seen fading out over the text on a reload. */
28
+ [data-keep-inside][data-settled] .mo > *,
29
+ [data-keep-inside][data-settled] .pt,
30
+ [data-slot="activity-layout-backdrop"]
31
+ [data-background][data-settled]
32
+ .mo
33
+ > *,
34
+ [data-slot="activity-layout-backdrop"] [data-background][data-settled] .pt {
30
35
  transition: opacity 150ms ease;
31
36
  }
32
37
  /* Important: a preset sets its own opacity on a body (`.md`, the dark
@@ -13,8 +13,8 @@
13
13
  visibility: hidden;
14
14
  }
15
15
  /* A shape over the card or the brand row fades out (watch-content.ts). */
16
- [data-slot="auth-card-backdrop"] [data-background] .mo > *,
17
- [data-slot="auth-card-backdrop"] [data-background] .pt {
16
+ [data-slot="auth-card-backdrop"] [data-background][data-settled] .mo > *,
17
+ [data-slot="auth-card-backdrop"] [data-background][data-settled] .pt {
18
18
  transition: opacity 150ms ease;
19
19
  }
20
20
  [data-slot="auth-card-backdrop"] [data-background] .mo > [data-hidden],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/material",
3
- "version": "3.49.0",
3
+ "version": "3.51.0",
4
4
  "license": "MIT",
5
5
  "bugs": {
6
6
  "email": "stevegires@gmail.com"