@nxgt/material 3.18.0 → 3.20.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 (72) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +4 -4
  3. package/dist/{async-slice-gqif7FX9.js → async-slice-nTRgm_ct.js} +2 -2
  4. package/dist/{button-Ck1Bif-Q.js → button-rdIhY2BT.js} +1 -1
  5. package/dist/charts.js +3 -3
  6. package/dist/{chip-B9pZFWbi.js → chip-CEau88ZV.js} +1 -1
  7. package/dist/{code-editor-M4DcaX-P.js → code-editor-DHB5ytUz.js} +4 -4
  8. package/dist/code-editor.js +4 -4
  9. package/dist/{code-language-items-Bnm_WDVg.js → code-language-items-Dww9amx_.js} +2 -2
  10. package/dist/{components-nq1rUKCK.js → components-SJxc3fpp.js} +14829 -14693
  11. package/dist/components.js +15 -15
  12. package/dist/{dialog-jYcWDh9G.js → dialog-B2GXKBp3.js} +1 -1
  13. package/dist/dnd.js +1 -1
  14. package/dist/{dropdown-menu-BEZhNnhr.js → dropdown-menu-mTDyFwbQ.js} +1 -1
  15. package/dist/editor.js +7 -7
  16. package/dist/{empty-state-BBUsR0_X.js → empty-state-CJh9f2pc.js} +1 -1
  17. package/dist/hooks.js +2 -2
  18. package/dist/{i18n-provider-3TsMIGn1.js → i18n-provider-BpZOKh7U.js} +1 -1
  19. package/dist/i18n.js +5 -5
  20. package/dist/{icon-badge-92uhu2o6.js → icon-badge-CiWXvKCs.js} +1 -1
  21. package/dist/lib/components/ui/collections/data-table/partials/data-table-page-control.d.ts +3 -1
  22. package/dist/lib/components/ui/collections/data-table/server-pagination.d.ts +5 -0
  23. package/dist/lib/components/ui/collections/list-empty/empty-icon-style.d.ts +11 -0
  24. package/dist/lib/components/ui/collections/list-empty/list-empty-page.d.ts +9 -0
  25. package/dist/lib/components/ui/collections/list-empty/list-empty.d.ts +29 -6
  26. package/dist/lib/components/ui/collections/server-table/partials/server-table-band-classes.d.ts +9 -0
  27. package/dist/lib/components/ui/collections/server-table/partials/server-table-band.d.ts +5 -1
  28. package/dist/lib/components/ui/collections/server-table/partials/server-table-empty-page.d.ts +10 -0
  29. package/dist/lib/components/ui/collections/server-table/partials/server-table-passed-through.d.ts +2 -0
  30. package/dist/lib/components/ui/collections/server-table/server-table.d.ts +1 -1
  31. package/dist/lib/components/ui/collections/server-table/types.d.ts +43 -8
  32. package/dist/lib/components/ui/display/entity-header/entity-header.d.ts +12 -1
  33. package/dist/lib/components/ui/display/entity-header/partials/entity-header-leading.d.ts +10 -0
  34. package/dist/lib/components/ui/feedback/empty-state/empty-state.d.ts +2 -0
  35. package/dist/lib/components/ui/selection/switcher/partials/switcher-footer.d.ts +1 -1
  36. package/dist/lib/components/ui/selection/switcher/partials/switcher-popover.d.ts +3 -1
  37. package/dist/lib/components/ui/selection/switcher/partials/switcher-sheet.d.ts +3 -1
  38. package/dist/lib/components/ui/selection/switcher/partials/switcher-subtitle.d.ts +9 -0
  39. package/dist/lib/components/ui/selection/switcher/partials/switcher-trigger.d.ts +4 -1
  40. package/dist/lib/components/ui/selection/switcher/switcher.d.ts +1 -1
  41. package/dist/lib/components/ui/selection/switcher/types.d.ts +6 -2
  42. package/dist/lib/i18n/resources/en/en.d.ts +2 -0
  43. package/dist/lib/i18n/resources/en/material.json.d.ts +2 -0
  44. package/dist/lib/i18n/resources/fr/fr.d.ts +2 -0
  45. package/dist/lib/i18n/resources/fr/material.json.d.ts +2 -0
  46. package/dist/lib/i18n/resources/resources.d.ts +4 -0
  47. package/dist/lib/i18n/translator.d.ts +1 -1
  48. package/dist/lib.js +1 -1
  49. package/dist/main.js +21 -21
  50. package/dist/map.js +4 -4
  51. package/dist/material.css +1 -1
  52. package/dist/pdf-reader.js +1 -1
  53. package/dist/print.js +2 -2
  54. package/dist/{public-BLw13izR.js → public-DWuBn4t_.js} +6 -6
  55. package/dist/{rich-code-block-DPe5USBG.js → rich-code-block-CLkufAhI.js} +4 -4
  56. package/dist/{sortable-provider-CtAb1Q02.js → sortable-provider-BofwO9zZ.js} +1 -1
  57. package/dist/{spinner-CcmXeIHf.js → spinner-B7HCQMFr.js} +1 -1
  58. package/dist/{stored-language-CsDEJDR7.js → stored-language-CJn_iWwG.js} +4 -0
  59. package/dist/theme-picker.js +2 -2
  60. package/dist/{theme-toggle-CWovcIQY.js → theme-toggle-mQzIR1hw.js} +1 -1
  61. package/dist/{toolbar-items-CrATPuOX.js → toolbar-items-Tn3MMu-s.js} +2 -2
  62. package/dist/{translate-material-X1daoGxE.js → translate-material-CCyVEi_f.js} +2 -2
  63. package/dist/{translator-BUVKAo4l.js → translator-1THr41fc.js} +1 -1
  64. package/dist/{use-chat-composer-UZVFm8Tw.js → use-chat-composer-BBFuKNqC.js} +12 -12
  65. package/dist/{use-heading-spy-ByraZUf9.js → use-heading-spy-DNFfHQJs.js} +1 -1
  66. package/dist/{use-material-translation-tD9XqIBm.js → use-material-translation-BFQODuQz.js} +2 -2
  67. package/docs/components/data-display.md +16 -2
  68. package/docs/components/data-table-filter-and-lists.md +75 -10
  69. package/docs/components/selection-and-choice.md +13 -1
  70. package/docs/roadmap.md +1 -1
  71. package/docs/troubleshooting.md +31 -0
  72. package/package.json +2 -2
@@ -1,3 +1,3 @@
1
- import { t as e } from "./public-BLw13izR.js";
1
+ import { t as e } from "./public-DWuBn4t_.js";
2
2
  import { configurePdfReader as t } from "@nxgt/material-core/pdf-reader";
3
3
  export { e as PdfReader, t as configurePdfReader };
package/dist/print.js CHANGED
@@ -1,5 +1,5 @@
1
- import { r as e } from "./i18n-provider-3TsMIGn1.js";
2
- import { t } from "./translate-material-X1daoGxE.js";
1
+ import { r as e } from "./i18n-provider-BpZOKh7U.js";
2
+ import { t } from "./translate-material-CCyVEi_f.js";
3
3
  import { Children as n, createContext as r, isValidElement as i, useCallback as a, useContext as o, useEffect as s, useMemo as c, useState as l } from "react";
4
4
  import { jsx as u, jsxs as d } from "react/jsx-runtime";
5
5
  import { resolvePdfFileName as f } from "@nxgt/material-core/pdf-file-name";
@@ -6,18 +6,18 @@ import { c as i, i as a, l as o, n as s, o as c, s as l } from "./dist-lmHmz_bu.
6
6
  import { a as u, o as d } from "./tooltip-CkttDIab.js";
7
7
  import { i as f } from "./dist-C4wtXCjf.js";
8
8
  import { r as p, t as m } from "./in-dialog-Vwkyp-M5.js";
9
- import { C as h, D as g, E as _, O as v, S as y, T as b, _ as x, f as S, g as ee, k as te, r as ne, t as C, u as re, w } from "./dialog-jYcWDh9G.js";
10
- import { c as T, d as ie, f as E, i as ae, l as oe, n as se, o as D, t as ce, u as O } from "./dropdown-menu-BEZhNnhr.js";
11
- import { S as le, a as k, d as A, g as ue, h as de, i as fe, m as pe, n as j, r as me, t as M, u as he, w as ge } from "./toolbar-items-CrATPuOX.js";
12
- import { t as N } from "./use-material-translation-tD9XqIBm.js";
13
- import { t as _e } from "./button-Ck1Bif-Q.js";
9
+ import { C as h, D as g, E as _, O as v, S as y, T as b, _ as x, f as S, g as ee, k as te, r as ne, t as C, u as re, w } from "./dialog-B2GXKBp3.js";
10
+ import { c as T, d as ie, f as E, i as ae, l as oe, n as se, o as D, t as ce, u as O } from "./dropdown-menu-mTDyFwbQ.js";
11
+ import { S as le, a as k, d as A, g as ue, h as de, i as fe, m as pe, n as j, r as me, t as M, u as he, w as ge } from "./toolbar-items-Tn3MMu-s.js";
12
+ import { t as N } from "./use-material-translation-BFQODuQz.js";
13
+ import { t as _e } from "./button-rdIhY2BT.js";
14
14
  import { n as ve, t as ye } from "./icon-button-ifTpXt0A.js";
15
15
  import { t as be } from "./useEvent-2fKq-6w8.js";
16
16
  import { n as xe, t as Se } from "./toggle-group-BMJNcFAC.js";
17
17
  import { o as Ce } from "./escape-owner-C4W_sOAU.js";
18
18
  import { n as we } from "./use-slice-reducer-BGkeGtML.js";
19
19
  import { r as Te } from "./use-csp-nonce-ZKs9k-ov.js";
20
- import { n as Ee, t as De } from "./empty-state-BBUsR0_X.js";
20
+ import { n as Ee, t as De } from "./empty-state-CJh9f2pc.js";
21
21
  import { t as Oe } from "./client-only.utils-3nHNzCki.js";
22
22
  import { t as ke } from "./use-scroll-away-PQRFZkT2.js";
23
23
  import { ArrowLeft as Ae, BookOpenText as je, ChevronDown as Me, ChevronLeft as Ne, ChevronRight as Pe, ChevronUp as Fe, Download as Ie, Eraser as Le, FileText as Re, FileWarning as ze, FileX2 as Be, Highlighter as Ve, KeyRound as He, LayoutGrid as Ue, List as We, Loader2 as Ge, Maximize as Ke, MessageSquareText as qe, Minimize as Je, MoreVertical as Ye, MousePointer2 as Xe, MoveHorizontal as Ze, PanelLeft as Qe, Pen as $e, PencilLine as et, Printer as tt, RotateCw as nt, Search as rt, StickyNote as it, Type as at, Underline as ot, Undo2 as st, Volume2 as ct, X as lt, XIcon as ut, ZoomIn as dt, ZoomOut as ft } from "lucide-react";
@@ -1,7 +1,7 @@
1
1
  import { t as e } from "./tooltip-CkttDIab.js";
2
- import { c as t, n, t as r } from "./dropdown-menu-BEZhNnhr.js";
3
- import { t as i } from "./use-material-translation-tD9XqIBm.js";
4
- import { a, i as o, n as s, o as c, t as l } from "./code-language-items-Bnm_WDVg.js";
2
+ import { c as t, n, t as r } from "./dropdown-menu-mTDyFwbQ.js";
3
+ import { t as i } from "./use-material-translation-BFQODuQz.js";
4
+ import { a, i as o, n as s, o as c, t as l } from "./code-language-items-Dww9amx_.js";
5
5
  import { i as u, n as d, r as f, s as p } from "./dist-C3GR7kJd.js";
6
6
  import { t as m } from "./code-block-spec-CGLKRhwD.js";
7
7
  import { Check as h, ChevronDown as g, Copy as _, Maximize2 as v, WrapText as y } from "lucide-react";
@@ -143,7 +143,7 @@ function M({ node: e, editor: t, updateAttributes: n, editable: r, wrap: a, onWr
143
143
  }
144
144
  //#endregion
145
145
  //#region lib/components/ui/widgets/editor/extensions/code/code-block-open.tsx
146
- var N = x(() => import("./code-editor-M4DcaX-P.js").then((e) => e.n).then((e) => ({ default: e.CodeEditor })));
146
+ var N = x(() => import("./code-editor-DHB5ytUz.js").then((e) => e.n).then((e) => ({ default: e.CodeEditor })));
147
147
  function P() {
148
148
  let { t: e } = i();
149
149
  return /* @__PURE__ */ T("div", {
@@ -1,6 +1,6 @@
1
1
  import { t as e } from "./utils-BLfntITJ.js";
2
2
  import { t } from "./dist-DVRQkaTK.js";
3
- import { t as n } from "./use-material-translation-tD9XqIBm.js";
3
+ import { t as n } from "./use-material-translation-BFQODuQz.js";
4
4
  import { t as r } from "./icon-button-ifTpXt0A.js";
5
5
  import { t as i } from "./compose-refs-DZ0iIVF-.js";
6
6
  import { GripVertical as a } from "lucide-react";
@@ -1,6 +1,6 @@
1
1
  import { t as e } from "./utils-BLfntITJ.js";
2
2
  import { o as t } from "./tooltip-CkttDIab.js";
3
- import { t as n } from "./use-material-translation-tD9XqIBm.js";
3
+ import { t as n } from "./use-material-translation-BFQODuQz.js";
4
4
  import { jsx as r } from "react/jsx-runtime";
5
5
  //#region lib/components/ui/feedback/spinner/spinner.tsx
6
6
  var i = t("transition-all duration-500", {
@@ -505,6 +505,8 @@ var t = {
505
505
  pagination: {
506
506
  previous: "Previous",
507
507
  next: "Next",
508
+ "previous-page": "Previous page",
509
+ "next-page": "Next page",
508
510
  "go-to-next-page": "Go to next page",
509
511
  "go-to-previous-page": "Go to previous page",
510
512
  "go-to-first-page": "Go to first page",
@@ -3115,6 +3117,8 @@ var t = {
3115
3117
  pagination: {
3116
3118
  previous: "Précédent",
3117
3119
  next: "Suivant",
3120
+ "previous-page": "Page précédente",
3121
+ "next-page": "Page suivante",
3118
3122
  "go-to-next-page": "Aller à la page suivante",
3119
3123
  "go-to-previous-page": "Aller à la page précédente",
3120
3124
  "go-to-first-page": "Aller à la première page",
@@ -1,10 +1,10 @@
1
1
  import { t as e } from "./utils-BLfntITJ.js";
2
2
  import { a as t, i as n, n as r, o as i, r as a, t as o } from "./bubble-input-B7BVd9fX.js";
3
- import { t as s } from "./use-material-translation-tD9XqIBm.js";
3
+ import { t as s } from "./use-material-translation-BFQODuQz.js";
4
4
  import { n as c, r as l } from "./icon-sprites-FxHyqyhg.js";
5
5
  import { t as u } from "./use-theme-DMcyJ52N.js";
6
6
  import { a as d, r as f } from "./theme-context-Cofk6vgs.js";
7
- import { t as p } from "./theme-toggle-CWovcIQY.js";
7
+ import { t as p } from "./theme-toggle-mQzIR1hw.js";
8
8
  import { n as m, t as h } from "./nxgt-theme-ZS945iMP.js";
9
9
  import { Check as g } from "lucide-react";
10
10
  import { useContext as _ } from "react";
@@ -1,4 +1,4 @@
1
- import { t as e } from "./use-material-translation-tD9XqIBm.js";
1
+ import { t as e } from "./use-material-translation-BFQODuQz.js";
2
2
  import { i as t } from "./theme-store-CSl51uNH.js";
3
3
  import { n, t as r } from "./use-theme-DMcyJ52N.js";
4
4
  import { r as i } from "./theme-context-Cofk6vgs.js";
@@ -4,9 +4,9 @@ import { a as i, c as a, i as o, l as s, r as c, s as l } from "./dist-lmHmz_bu.
4
4
  import { b as u, c as d, d as f, f as p, l as m, t as h, u as g, x as _ } from "./tooltip-CkttDIab.js";
5
5
  import { i as v, n as y, r as b, t as x } from "./dist-C4wtXCjf.js";
6
6
  import { a as S, c as C, l as w, o as T, r as ee, s as te, t as E } from "./in-dialog-Vwkyp-M5.js";
7
- import { A as ne, M as re, N as ie, b as ae, d as oe, f as se, i as ce, j as le, l as ue, n as de, o as fe, r as pe, t as me, u as he, y as ge } from "./dropdown-menu-BEZhNnhr.js";
7
+ import { A as ne, M as re, N as ie, b as ae, d as oe, f as se, i as ce, j as le, l as ue, n as de, o as fe, r as pe, t as me, u as he, y as ge } from "./dropdown-menu-mTDyFwbQ.js";
8
8
  import { a as _e, n as ve, r as ye, t as be } from "./dist-B7RjY7SL.js";
9
- import { t as xe } from "./use-material-translation-tD9XqIBm.js";
9
+ import { t as xe } from "./use-material-translation-BFQODuQz.js";
10
10
  import { t as Se } from "./warn-once-C_e47JM8.js";
11
11
  import { o as Ce } from "./escape-owner-C4W_sOAU.js";
12
12
  import { ChevronLeft as we, ChevronRightIcon as Te, MoreVertical as Ee } from "lucide-react";
@@ -1,5 +1,5 @@
1
- import "./stored-language-CsDEJDR7.js";
2
- import { a as e, r as t } from "./translator-BUVKAo4l.js";
1
+ import "./stored-language-CJn_iWwG.js";
2
+ import { a as e, r as t } from "./translator-1THr41fc.js";
3
3
  //#region lib/i18n/translate-material.ts
4
4
  function n(n, r, i, a) {
5
5
  if (n) for (let e of /* @__PURE__ */ new Set([n, n.split("-")[0]])) {
@@ -1,5 +1,5 @@
1
1
  import { t as e } from "./is-production-GLw59WU8.js";
2
- import { a as t, i as n, r, t as i } from "./stored-language-CsDEJDR7.js";
2
+ import { a as t, i as n, r, t as i } from "./stored-language-CJn_iWwG.js";
3
3
  import { t as a } from "./get-CTzJAwfc.js";
4
4
  //#region ../../node_modules/@formatjs/fast-memoize/index.js
5
5
  function o(e, t) {
@@ -7,21 +7,21 @@ import { a as o, c as s, i as c, l, n as u, o as d, r as f, s as p, t as m } fro
7
7
  import { _ as h, a as g, g as _, h as v, m as y, o as b, p as x, t as S, v as C, y as w } from "./tooltip-CkttDIab.js";
8
8
  import { a as T, i as E, n as D, r as O, t as k } from "./dist-C4wtXCjf.js";
9
9
  import { i as A, n as ee, t as j } from "./in-dialog-Vwkyp-M5.js";
10
- import { A as M, C as N, D as te, E as ne, O as P, S as re, T as ie, _ as F, b as ae, f as oe, g as se, k as I, l as ce, m as le, n as ue, o as de, r as fe, s as pe, t as me, u as he, v as ge, w as _e, x as ve, y as ye } from "./dialog-jYcWDh9G.js";
10
+ import { A as M, C as N, D as te, E as ne, O as P, S as re, T as ie, _ as F, b as ae, f as oe, g as se, k as I, l as ce, m as le, n as ue, o as de, r as fe, s as pe, t as me, u as he, v as ge, w as _e, x as ve, y as ye } from "./dialog-B2GXKBp3.js";
11
11
  import { A as be, C as xe, D as Se, E as Ce, O as we, S as Te, T as Ee, _ as De, a as Oe, b as ke, d as Ae, f as je, g as Me, h as Ne, i as Pe, k as Fe, l as Ie, m as Le, n as Re, o as ze, p as Be, r as Ve, s as He, t as Ue, u as We, v as Ge, w as Ke, x as qe, y as Je } from "./floating-ui.dom-KkZSW8_E.js";
12
- import { D as Ye, E as Xe, N as Ze, O as Qe, c as $e, d as et, f as tt, i as nt, k as rt, m as it, n as at, o as ot, p as st, t as ct, u as lt } from "./dropdown-menu-BEZhNnhr.js";
12
+ import { D as Ye, E as Xe, N as Ze, O as Qe, c as $e, d as et, f as tt, i as nt, k as rt, m as it, n as at, o as ot, p as st, t as ct, u as lt } from "./dropdown-menu-mTDyFwbQ.js";
13
13
  import { a as ut, i as dt, n as ft } from "./field-blur-context-Cft9s7Hg.js";
14
- import { C as pt, S as mt, a as ht, c as gt, d as _t, l as vt, n as yt, o as bt, s as xt, u as St } from "./toolbar-items-CrATPuOX.js";
14
+ import { C as pt, S as mt, a as ht, c as gt, d as _t, l as vt, n as yt, o as bt, s as xt, u as St } from "./toolbar-items-Tn3MMu-s.js";
15
15
  import { a as Ct, i as wt, n as Tt, o as Et, t as Dt } from "./bubble-input-B7BVd9fX.js";
16
16
  import { i as Ot } from "./dist-B7RjY7SL.js";
17
- import "./stored-language-CsDEJDR7.js";
18
- import { a as kt } from "./translator-BUVKAo4l.js";
17
+ import "./stored-language-CJn_iWwG.js";
18
+ import { a as kt } from "./translator-1THr41fc.js";
19
19
  import { h as At } from "./get-CTzJAwfc.js";
20
20
  import { t as jt } from "./isEmpty-F_g3jLf8.js";
21
- import { t as L } from "./use-material-translation-tD9XqIBm.js";
22
- import { t as Mt } from "./spinner-CcmXeIHf.js";
21
+ import { t as L } from "./use-material-translation-BFQODuQz.js";
22
+ import { t as Mt } from "./spinner-B7HCQMFr.js";
23
23
  import { a as Nt } from "./button-tones-BwuVvAmd.js";
24
- import { a as Pt, i as Ft, n as It, r as Lt, t as Rt } from "./button-Ck1Bif-Q.js";
24
+ import { a as Pt, i as Ft, n as It, r as Lt, t as Rt } from "./button-rdIhY2BT.js";
25
25
  import { t as R } from "./icon-button-ifTpXt0A.js";
26
26
  import { n as zt, r as Bt } from "./icon-sprites-FxHyqyhg.js";
27
27
  import { t as Vt } from "./use-ssr-media-query-C6M5m8sf.js";
@@ -35,10 +35,10 @@ import { a as tn, i as nn, o as rn } from "./escape-owner-C4W_sOAU.js";
35
35
  import { n as an } from "./use-slice-reducer-BGkeGtML.js";
36
36
  import { t as on } from "./browser-only-DQ94lWF8.js";
37
37
  import { t as sn } from "./compose-refs-DZ0iIVF-.js";
38
- import { n as cn, r as ln, t as un } from "./chip-B9pZFWbi.js";
39
- import { n as dn, t as fn } from "./icon-badge-92uhu2o6.js";
38
+ import { n as cn, r as ln, t as un } from "./chip-CEau88ZV.js";
39
+ import { n as dn, t as fn } from "./icon-badge-CiWXvKCs.js";
40
40
  import { r as pn } from "./use-csp-nonce-ZKs9k-ov.js";
41
- import { t as mn } from "./empty-state-BBUsR0_X.js";
41
+ import { t as mn } from "./empty-state-CJh9f2pc.js";
42
42
  import { t as hn } from "./skeleton-DSdtr2qc.js";
43
43
  import { H as gn, V as _n, X as vn, bt as yn, it as bn, lt as xn, n as Sn, t as Cn, xt as wn, yt as Tn } from "./dist-moutbfbd.js";
44
44
  import { t as En } from "./markdown-Dw_uH7j7.js";
@@ -10782,7 +10782,7 @@ var CS = (e = {}) => Dn({
10782
10782
  let [{ default: t }, { lowlight: n }, { richCodeBlock: r }] = await Promise.all([
10783
10783
  import("./dist-CRMFGjng.js"),
10784
10784
  import("./lowlight-OtcjU2Sa.js"),
10785
- import("./rich-code-block-DPe5USBG.js")
10785
+ import("./rich-code-block-CLkufAhI.js")
10786
10786
  ]);
10787
10787
  return [r(t, {
10788
10788
  lowlight: n,
@@ -1,4 +1,4 @@
1
- import { r as e } from "./i18n-provider-3TsMIGn1.js";
1
+ import { r as e } from "./i18n-provider-BpZOKh7U.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 } from "react";
4
4
  //#region ../../node_modules/date-fns/locale/fr/_lib/formatDistance.js
@@ -1,5 +1,5 @@
1
- import { r as e } from "./i18n-provider-3TsMIGn1.js";
2
- import { t } from "./translate-material-X1daoGxE.js";
1
+ import { r as e } from "./i18n-provider-BpZOKh7U.js";
2
+ import { t } from "./translate-material-CCyVEi_f.js";
3
3
  import { useCallback as n } from "react";
4
4
  //#region lib/i18n/use-material-translation.ts
5
5
  function r() {
@@ -633,7 +633,7 @@ the grid it is in; pass `className="col-start-full"` for a full-width row.
633
633
 
634
634
  ![EntityHeader](../previews/data-display-entityheader.webp)
635
635
 
636
- Page header with icon, title, status, metadata and actions.
636
+ Page header with icon (or a `leading` control), title, status, metadata and actions.
637
637
 
638
638
  ```tsx
639
639
  import { Edit } from 'lucide-react';
@@ -651,13 +651,27 @@ import { Badge, EntityHeader } from '@nxgt/material/components';
651
651
  | --- | --- | --- | --- |
652
652
  | `title` | `ReactNode` | — | the heading (an `h1` unless `headingLevel` says otherwise); optional: without it no heading is rendered (see below) |
653
653
  | `headingLevel` | `1 \| 2 \| 3` | `1` | the title's heading level: pass `2` or `3` when the page already has its `h1` (a list title beside a detail header); the look does not change |
654
- | `icon`, `status` | `ReactNode` | — | before and after the title; `icon` is decoration, shown in an `aria-hidden` badge, so put nothing meaningful or focusable there |
654
+ | `icon`, `status` | `ReactNode` | — | before and after the title; `icon` is decoration, shown in an `aria-hidden` badge, so put nothing meaningful or focusable there; for a control, use `leading` |
655
+ | `leading` | `ReactNode` | — | an interactive control at the band's start, such as a [Switcher](selection-and-choice.md#switcher) of either variant: drawn as it is, at its natural size (at most 18rem wide beside the title, from a 384px band; narrower, it takes the band's width), in the accessibility tree, with no badge and no `aria-hidden`; on a phone it takes the band's width above the title. **It replaces `icon` when both are given** (a development warning says so). Root `data-slot="entity-header-leading"` |
655
656
  | `metadata` | `{ label: string; value: ReactNode }[]` | `[]` | facts under the title, a description list: a muted label beside its value, no colon; a link in a value is drawn in the primary text colour (`text-primary-text`), not the prose link's info blue, and `nxgt-plain-link` still opts out |
656
657
  | `stats` | `EntityStat[]` | `[]` | figures (counts, a mean) as compact `size="sm"` [StatCard](#statcard) tiles in a `StatCardGroup` (2×2 on a phone, a wrapping row beside) under the metadata; a `tone` paints a tile, `0` is a figure, an empty value drops the tile (see Stats below) |
657
658
  | `actions` | `ResponsiveButtonProps[] \| ReactNode` | `[]` | an array becomes responsive buttons (icon-only on phones) |
658
659
 
659
660
  The root carries `data-slot="entity-header"`; the metadata is a `dl` with `data-slot="entity-metadata"` (a muted `dt` label beside its `dd` value, no colon), and the actions sit in `data-slot="entity-header-actions"` (absent when there are none). Inside an EntityHeader that replaces the old `summary-data` slot and its `Label:` text, so update a selector or a test that read "Label: value".
660
661
 
662
+ **A control at the start.** `leading` takes a control, never `icon`: the icon badge is `aria-hidden`, which squashes a `Switcher` and makes axe report `aria-hidden-focus` (see [Troubleshooting](../troubleshooting.md#a-control-in-an-entityheaders-icon-is-hidden-from-assistive-tech-squashed-and-reports-aria-hidden-focus)).
663
+
664
+ ```tsx
665
+ import { Badge, EntityHeader, Switcher } from '@nxgt/material/components';
666
+
667
+ <EntityHeader
668
+ leading={<Switcher variant="expressive" label="Group" value={group} onValueChange={setGroup} options={groups} />}
669
+ title="Attendance"
670
+ status={<Badge variant="success">Active</Badge>}
671
+ metadata={[{ label: 'Year', value: '2026-2027' }]}
672
+ />
673
+ ```
674
+
661
675
  **Stats.** Figures (counts, a mean) go in `stats`, shown as compact [StatCard](#statcard) tiles (`size="sm"`) in a `StatCardGroup` (2×2 on a phone, a row that wraps beside) under the metadata; facts (dates, names) stay in `metadata`. A `tone` paints a tile (tonal), none leaves it outlined; `0` is a figure, an empty value drops the tile. The row is `data-slot="entity-stats"`, and the type `EntityStat` is exported.
662
676
 
663
677
  ```tsx
@@ -14,8 +14,8 @@ either can be used alone.
14
14
  | | Component | What it does |
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
- | <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: sort, cursor paging, the empty page with removable chips, one count announced |
18
- | <img src="../previews/data-table-filter-lists-listempty.webp" width="96" alt=""> | [ListEmpty](#listempty) | A list's empty page: nothing yet, or nothing matches and the way out |
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-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 |
19
19
  | <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`) |
20
20
  | <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 |
21
21
  | <img src="../previews/data-table-filter-lists-filter.webp" width="96" alt=""> | [Filter](#filter) | Schema-driven filters with chips, presets and persistence |
@@ -115,9 +115,9 @@ table enables, for typing your own column defs.
115
115
  | `sorting` / `onSortingChange` | `SortingState` / `(sorting) => void` | held inside | controlled sorting; the callback gets the new state, not an updater, and is not called for an update that resolves to the current value |
116
116
  | `getRowId` | `(row, index) => string` | the index | the row's id, the key of `rowSelection` |
117
117
  | `enableRowSelection` | `boolean \| (row) => boolean` | `true` | which rows can be selected (`row` is the data). A row that cannot has a disabled checkbox; "Select all" skips it and its checked and indeterminate states count only selectable rows. A locked row already in the selection can be cleared by its own box (and only cleared) |
118
- | `rowSelectionDisabledReason` | `(row) => string \| undefined` | — | why a row cannot be selected: its checkbox is `aria-disabled` but stays focusable, shows the reason in a tooltip on hover and focus, and has it as its `aria-describedby`; without a reason it is a plain `disabled` box |
118
+ | `rowSelectionDisabledReason` | `(row) => string \| undefined` | — | why a row cannot be selected: its checkbox is `aria-disabled` but stays focusable, shows the reason in a tooltip on hover, focus and tap, and has it as its `aria-describedby`; without a reason it is a plain `disabled` box |
119
119
  | `rowSelection` / `onRowSelectionChange` | `RowSelectionState` / `(selection) => void` | held inside | controlled selection, by id; may hold rows of other pages; the callback is not called for an update that resolves to the current value |
120
- | `pagination` | `{ totalCount?, offset?, previous?, next?, live? }` | — | the server footer's range ("41–60 of 133") and Previous / Next: an href (a link) or a callback (a button); `live: false` renders the range as plain text instead of a polite live region (a page that tells a new query's count elsewhere) |
120
+ | `pagination` | `{ totalCount?, offset?, previous?, next?, live?, pageControls? }` | — | the server footer's range ("41–60 of 133") and Previous / Next: an href (a link) or a callback (a button); `live: false` renders the range as plain text instead of a polite live region (a page that tells a new query's count elsewhere); `pageControls: 'icon'` draws Previous and Next as icon-only buttons (default `'labelled'`, the tonal buttons with their word): a tooltip and the names "Previous page" / "Next page" (`material.table.pagination.previous-page`, `next-page`), `rel` and `data-slot` kept, a link staying a link, the disabled one in place |
121
121
  | `footer` | `false \| (table) => ReactNode` | the pagination | replaces the footer, or removes it |
122
122
  | `getRowHref` | `(row) => string \| undefined` | — | the first data cell links to the row's page; a click elsewhere on the row follows it |
123
123
  | `onRowClick` | `(row, event) => void` | — | a click on the row (not on a control); alone (without `getRowHref`), it makes the row focusable and Enter calls it |
@@ -824,7 +824,21 @@ export default function Students() {
824
824
  the selection is kept across pages and drops on a new query.
825
825
  `selection.clear()` empties it and moves the focus to the table
826
826
  (`tabindex="-1"`, by script only; the list of cards or tiles in those layouts); `selection.deselect(ids)` drops rows
827
- gone since the page was read.
827
+ gone since the page was read. **Locked rows:** `enableRowSelection` (`true`,
828
+ `false` or `(row) => boolean`, as DataTable's) leaves rows out of it, and
829
+ `rowSelectionDisabledReason` says why; a locked row's checkbox is
830
+ `aria-disabled` but focusable, described by the reason (`aria-describedby`)
831
+ and shown in a tooltip on hover, focus and tap. Space on it does nothing,
832
+ "Select all" skips it, and its state counts only the rows that can be
833
+ selected.
834
+ ```tsx
835
+ <ServerTable<Request>
836
+ {...props}
837
+ bulkActions={(ids) => <Button>{`Apply ${ids.length}`}</Button>}
838
+ enableRowSelection={(row) => !row.applied}
839
+ rowSelectionDisabledReason={() => 'Already applied'}
840
+ />
841
+ ```
828
842
  - **Your own controls instead.** Without `filter`, pass
829
843
  `filters={tableFilters(model)}`: whether the page is "nothing matches",
830
844
  and its "Clear search and filters" button
@@ -833,7 +847,8 @@ export default function Students() {
833
847
  - **Passed through** to `DataTable`: `columns`, `maxHeight`, `scrollLabel`,
834
848
  `showViewOptions`, `renderExpandedRow`, `options`, `loading`,
835
849
  `loadingRows`, `getRowHref`, `onRowClick`, `linkComponent`, `density`,
836
- `layout`, `renderCard`, `renderTile` (a list of cards or tiles instead of the
850
+ `layout`, `enableRowSelection`, `rowSelectionDisabledReason`,
851
+ `renderCard`, `renderTile` (a list of cards or tiles instead of the
837
852
  table: [Cards and tiles](../guides/cards-and-tiles.md)).
838
853
 
839
854
  Its props are `ServerTableProps<T>`; `filter` is a `ServerTableFilter`,
@@ -849,23 +864,72 @@ Its props are `ServerTableProps<T>`; `filter` is a `ServerTableFilter`,
849
864
  | `getRowId` | `(row) => string` | — | the selection's key |
850
865
  | `filter` | `ServerTableFilter` | — | `{ model, locked?, fieldProps?, variant?, sheetTrigger?, quick?, density?, shortcuts?, previewCount?, counts?, liveDelayMs?, label?, searchLabel?, searchPlaceholder?, searchApply?, searchMinLength?, codec? }` |
851
866
  | `filter.codec` | `{ keepKnownValues?, encoding? }` | — | how the page reads and writes the URL: pass the loader's `keepKnownValues` / `encoding` so chips and rows agree |
852
- | `title` | `ReactNode` | — | the band's title (a string is an `<h2>`) |
867
+ | `title` | `ReactNode` | — | the band's title (a string is an `<h2>`). Leave it out when the page's `h1` is elsewhere (the app bar's): the band then has no heading |
868
+ | `description` | `ReactNode` | — | a muted line under the `title`, or alone above the band's controls without one; the `actions` stay at the band's end |
853
869
  | `actions` | `ReactNode` | — | the page's actions, at the band's end |
870
+ | `bandDensity` | `'comfortable' \| 'compact'` | `'comfortable'` | `'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') |
871
+ | `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) |
872
+ | `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 |
854
873
  | `sorting` | `createSorting(…)` | — | headers sorted from the URL |
855
874
  | `filters` | `tableFilters(model)` | — | without `filter`: "nothing matches" and its clear |
856
- | `empty` | `ServerTableEmpty`: `{ none, noMatch, clear?, headingLevel? }` | — | the empty page's words; `headingLevel` (default 2) is the level of its title and of the error's |
875
+ | `empty` | `ServerTableEmpty`: `{ none, noMatch, clear?, headingLevel?, iconStyle? }` | — | the empty page: `none` and `noMatch` are a title or a `ListEmptyPage` (`{ title, description?, illustration?, action? }`); `headingLevel` (default 2) is the level of its title and of the error's; `iconStyle` styles the scenes (see below) |
857
876
  | `error` | `ServerTableError \| null` | — | the fetch failed: the failure and a Retry replace the rows |
858
877
  | `onRetry` | `() => unknown` | — | the error's Retry; a returned promise keeps it loading |
859
878
  | `onClearFilters` | `() => void` | focus the table | without `filter`, after "clear" |
860
879
  | `bulkActions` | `(ids, selection: ServerTableSelection) => ReactNode` | — | the selection's toolbar |
880
+ | `enableRowSelection`, `rowSelectionDisabledReason` | `boolean \| (row) => boolean`, `(row) => string \| undefined` | all rows, no reason | with `bulkActions`: which rows can be selected, and why one cannot ([DataTable](#datatable)) |
861
881
  | `pageHref` | `(params) => string` | `` `?${params}` `` | Previous / Next hrefs |
862
882
  | `pagingNames` | `{ after?, trail? }` | `after`, `trail` | the cursor's params |
863
883
  | `resultsLabel` | `(count) => string` | "N results" | without `filter`: what the list's region says. The page past the last has its own words: `material.server-table.past-end`, `past-end-hint`, `first-page` and `past-end-status` |
864
884
  | `className` | `string` | — | on the list's surface |
865
885
 
886
+ **Where the focus goes.** After "Clear" on an empty page, or the last chip
887
+ removed, the focus returns to the Filter's search; a bar without one falls to
888
+ its first shown control (a quick filter, "Add filter", or "Filters (N)" on a
889
+ phone, never a control hidden at the width). An app that moves the focus
890
+ itself holds `filterRef`:
891
+
892
+ ```tsx
893
+ const filterRef = useRef<ServerTableFilterHandle>(null);
894
+ <ServerTable {...props} filter={{ model }} filterRef={filterRef} bandDensity="compact" pageControls="icon" />;
895
+ filterRef.current?.focus();
896
+ ```
897
+
898
+ **A page whose heading is elsewhere, and a "nothing yet" with a scene.** Without
899
+ `title` the band has no heading; `description` is the line above its controls,
900
+ and `empty.none` takes the scene and the call to action (the same
901
+ `ListEmptyPage` as [ListEmpty](#listempty)):
902
+
903
+ ```tsx
904
+ // Once, above: <IllustrationDefaults sets={[EDUCATION_SCENES]}> (from
905
+ // '@nxgt/material-core/illustrations/education'), so 'no-students' resolves.
906
+ <ServerTable<Student>
907
+ {...page}
908
+ description="12 students this year"
909
+ actions={<Button>New student</Button>}
910
+ empty={{
911
+ none: {
912
+ title: 'No students yet',
913
+ description: 'Enrol the first one.',
914
+ illustration: 'no-students', // a scene of a registered set; decorative
915
+ action: <Button>New student</Button>,
916
+ },
917
+ noMatch: 'No student matches', // keeps the "no results" scene and "Clear"
918
+ iconStyle: 'solid', // the sprite file the app serves
919
+ }}
920
+ />
921
+ ```
922
+
923
+ A scene id from a set (`EDUCATION_SCENES`, `HEALTH_SCENES`) is registered once
924
+ with `IllustrationDefaults sets` above the table; `iconStyle` keeps that
925
+ provider's icons and sets and changes only the style (`{ family?, type? }`, a
926
+ sprite file's name, or a list of them). On "nothing matches", an `action` in
927
+ `noMatch` replaces "Clear search and filters". The live region speaks the
928
+ page's `title`.
929
+
866
930
  `ServerTable` uses `DataTable`'s `focusable`, `focusRef` (the table, or the list of cards or tiles), `announceLoading`, `announceEmpty`, `filtered` and `pagination.live` (see [DataTable](#datatable)).
867
931
 
868
- **Storybook:** `Data table, filter & lists/ServerTable` (`Error No Loading`, `Error Retry`), `…/ServerTable/Phone`, `…/ServerTable/Cards`, `…/ServerTable/Tiles`, `…/ServerTable/Own controls` (`Past The Last Page`, `Slow Router Keeps Search`) · **Vue:** `ServerTable`
932
+ **Storybook:** `Data table, filter & lists/ServerTable` (`Error No Loading`, `Error Retry`), `…/ServerTable/Band and empty page`, `…/ServerTable/Phone`, `…/ServerTable/Cards`, `…/ServerTable/Tiles`, `…/ServerTable/Own controls` (`Past The Last Page`, `Slow Router Keeps Search`), `…/ServerTable/Locked rows`, `…/ServerTable/Compact band and icon paging` · **Vue:** `ServerTable`
869
933
 
870
934
  ---
871
935
 
@@ -892,7 +956,8 @@ import { ListEmpty } from '@nxgt/material/components';
892
956
  | Prop | Type | Default | Effect |
893
957
  | --- | --- | --- | --- |
894
958
  | `filtered` | `boolean` | — | "nothing matches" rather than "nothing yet" |
895
- | `none`, `noMatch` | `ReactNode` | — | the two titles |
959
+ | `none`, `noMatch` | `ReactNode \| ListEmptyPage` | — | the two titles, or `{ title, description?, illustration?, action? }`: a line, a scene (a scene id, `true` for the kind's, `false` for none; decorative) and a call to action. On `noMatch`, `action` replaces the way out |
960
+ | `iconStyle` | `IconStyleInput` | — | the icon style of the scenes (`{ family?, type? }`, a sprite file's name, or a list), keeping the `IllustrationDefaults` above |
896
961
  | `children` | `ReactNode` | — | while nothing matches, above the way out (`ServerTable`'s removable chips) |
897
962
  | `clear` | `string` | "Clear search and filters" | the way out's label (`material.list-empty.clear`) |
898
963
  | `onClear` | `() => void` | — | the way out is a button |
@@ -1197,7 +1197,17 @@ const RouterLink: AppShellLinkComponent = ({ href, ...props }) => <Link to={href
1197
1197
  />
1198
1198
  ```
1199
1199
 
1200
- A title that does not fit the trigger is cut with an ellipsis: the span carries the full title in `aria-label` (what `expectNoTextOverflow` of `@nxgt/playwright` reads), hovering or focusing the trigger shows it with the subtitle in a tooltip, and the trigger's accessible name is the label, the full title and the subtitle.
1200
+ A title that does not fit the trigger is cut with an ellipsis: the span carries the full title in `aria-label` (what `expectNoTextOverflow` of `@nxgt/playwright` reads), hovering or focusing the trigger shows it with the subtitle in a tooltip, and the trigger's accessible name is the label, the full title and the subtitle. An option's subtitle that does not fit, in the trigger (default variant; the expressive pill keeps it for screen readers only), the menu or the panel, is treated the same way while it is cut: the full text in `aria-label` and, in the options, in `title`, and in the trigger's tooltip. The option's accessible name still reads the title and the subtitle once each.
1201
+
1202
+ **Choosing a link closes the panel.** In link mode the popover or the phone sheet closes when an option (or a footer link) is activated, as in select mode, and focus is **not** handed back to the trigger: the navigation owns it, so a route's own focus on the new page's title runs. `open` and `onOpenChange` control the panel from the app (close it from a route effect, or open it from a shortcut):
1203
+
1204
+ ```tsx
1205
+ const [open, setOpen] = useState(false);
1206
+
1207
+ <Switcher label="Group" value={id} options={groups} getHref={(g) => `/groups/${g.id}`} linkComponent={RouterLink} open={open} onOpenChange={setOpen} />;
1208
+ ```
1209
+
1210
+ To put a Switcher at the start of a page header, pass it as [EntityHeader](data-display.md#entityheader)'s `leading` (not `icon`).
1201
1211
 
1202
1212
  | Prop | Type | Default | Effect |
1203
1213
  | --- | --- | --- | --- |
@@ -1213,6 +1223,8 @@ A title that does not fit the trigger is cut with an ellipsis: the span carries
1213
1223
  | `footer` | `{ label?, onSelect?, href? }` | — | a "Manage…" button (a link with `href`) at the end of the panel; `label` defaults to `material.switcher.manage` |
1214
1224
  | `emptyLabel` | `ReactNode` | `material.switcher.empty` | shown when the search matches nothing |
1215
1225
  | `sheet` | `boolean` | on a phone | `true` always opens a sheet, `false` never |
1226
+ | `open` | `boolean` | uncontrolled | controls the panel (popover, menu or sheet); pair with `onOpenChange` |
1227
+ | `onOpenChange` | `(open: boolean) => void` | — | called when the panel opens or closes, by the user or after a pick |
1216
1228
  | `name`, button props | `ComponentProps<'button'>` | — | spread on the trigger; `name` is its `data-pw` |
1217
1229
 
1218
1230
  `SwitcherItem`: `{ id, title, subtitle?, icon?, group?, initial?, tone?, badge?, href?, disabled? }`.
package/docs/roadmap.md CHANGED
@@ -566,7 +566,7 @@ version they shipped in, and planned entries may name the major they target.
566
566
  `FilterChips` still work, now deprecated. Shipped in 2.37.0.
567
567
  [Filter bar](components/data-table-filter-and-lists.md#filter-bar) ·
568
568
  [Server lists in the URL](guides/server-lists-url.md)
569
- - **A server list driven by the URL: `ServerTable`** — a server-mode `DataTable` with the filter bar in its top band beside the page's title and actions, the query, sort and cursor in the URL for any router, the empty page listing the filters as removable chips, and one count announced. Shipped in 2.37.0.
569
+ - **A server list driven by the URL: `ServerTable`** — a server-mode `DataTable` with the filter bar in its top band beside the page's optional title, its description line and its actions, the query, sort and cursor in the URL for any router, the empty page listing the filters as removable chips (and, for "nothing yet", a scene and an action), and one count announced. Shipped in 2.37.0.
570
570
  - **LocalizedTextFields** — one text in several
571
571
  languages: a fieldset with an input per language, the default as a
572
572
  placeholder with a reset button, `lang` and `dir` on each input, an error
@@ -36,6 +36,8 @@ words in your error.
36
36
  - [`IconRefError` from `IconSpritesProvider`](#iconreferror-from-iconspritesprovider)
37
37
  - [An accessibility audit reports low contrast on a badge, a button, a toast or a menu entry](#an-accessibility-audit-reports-low-contrast-on-a-badge-a-button-a-toast-or-a-menu-entry)
38
38
  - [`ARIA hidden element must not be focusable or contain focusable elements`](#aria-hidden-element-must-not-be-focusable-or-contain-focusable-elements)
39
+ - [A control in an `EntityHeader`'s `icon` is hidden from assistive tech, squashed, and reports `aria-hidden-focus`](#a-control-in-an-entityheaders-icon-is-hidden-from-assistive-tech-squashed-and-reports-aria-hidden-focus)
40
+ - [A `Switcher` in link mode leaves focus on the trigger after a navigation, and the new page's title never gets it](#a-switcher-in-link-mode-leaves-focus-on-the-trigger-after-a-navigation-and-the-new-pages-title-never-gets-it)
39
41
  - [An accessibility audit reports low contrast on a tonal or outlined button, a selected list tile or a hovered badge link](#an-accessibility-audit-reports-low-contrast-on-a-tonal-or-outlined-button-a-selected-list-tile-or-a-hovered-badge-link)
40
42
  - [An accessibility audit reports low contrast on error text in the dark theme](#an-accessibility-audit-reports-low-contrast-on-error-text-in-the-dark-theme)
41
43
  - [An accessibility audit reports low contrast on primary text in the dark theme](#an-accessibility-audit-reports-low-contrast-on-primary-text-in-the-dark-theme)
@@ -956,6 +958,35 @@ Elements mounted after the layer opens are not made inert: mount them
956
958
  first, or pass them to `inertOthers` in `keep` when they must stay
957
959
  reachable.
958
960
 
961
+ ### A control in an `EntityHeader`'s `icon` is hidden from assistive tech, squashed, and reports `aria-hidden-focus`
962
+
963
+ **When:** an `EntityHeader` gets a `Switcher`, a button or a link as its `icon`.
964
+ The control is drawn small and clipped inside a filled square, a screen reader
965
+ skips it, and an accessibility audit (axe) reports `aria-hidden-focus`.
966
+ **Why:** `icon` is decoration: it is wrapped in a filled, icon-sized badge with
967
+ `aria-hidden="true"`, so anything focusable inside it is an `aria-hidden`
968
+ subtree the keyboard still reaches.
969
+ **Fix:** put the control in `leading`: it is drawn as it is, at its natural
970
+ size, beside the title, with no badge and no `aria-hidden`. When both are
971
+ given, `leading` replaces `icon` (and warns in development).
972
+
973
+ ```tsx
974
+ <EntityHeader
975
+ leading={<Switcher label="Group" value={group} onValueChange={setGroup} options={groups} />}
976
+ title="Attendance"
977
+ />
978
+ ```
979
+
980
+ ### A `Switcher` in link mode leaves focus on the trigger after a navigation, and the new page's title never gets it
981
+
982
+ **When:** a `Switcher` with `getHref` and your router's `linkComponent`: you pick an option, the URL changes, but focus sits on the Switcher's trigger (or on the clicked link while the panel is open) instead of moving to the new page's title.
983
+ **Why:** a closing overlay hands focus back to its trigger, which ran after your route's own focus handling. Older versions also gave you no way to close the panel yourself.
984
+ **Fix:** update `@nxgt/material`: activating a link option closes the popover or sheet and no longer returns focus to the trigger, so the page's focus runs. To close it from the app, control it with `open` and `onOpenChange`.
985
+
986
+ ```tsx
987
+ <Switcher label="Group" value={id} options={groups} getHref={(g) => `/groups/${g.id}`} linkComponent={RouterLink} open={open} onOpenChange={setOpen} />
988
+ ```
989
+
959
990
  ### An accessibility audit reports low contrast on a tonal or outlined button, a selected list tile or a hovered badge link
960
991
 
961
992
  **When:** axe (or Lighthouse) reports `color-contrast` on a `tonal` or
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/material",
3
- "version": "3.18.0",
3
+ "version": "3.20.0",
4
4
  "license": "MIT",
5
5
  "bugs": {
6
6
  "email": "stevegires@gmail.com"
@@ -357,7 +357,7 @@
357
357
  "docs:previews": "rm -rf node_modules/.cache/nxgt-preview-free/missing && NXGT_PREVIEW_FREE=1 vitest run --project previews && bun .storybook/preview-free-report.ts"
358
358
  },
359
359
  "dependencies": {
360
- "@nxgt/material-core": "^0.32.1",
360
+ "@nxgt/material-core": "^0.33.0",
361
361
  "@base-ui/react": "^1.6.0",
362
362
  "@dnd-kit/core": "^6.3.1",
363
363
  "@dnd-kit/sortable": "^10.0.0",