@nxgt/material 2.7.1 → 2.9.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 (223) hide show
  1. package/CHANGELOG.md +201 -0
  2. package/README.md +9 -6
  3. package/dist/{_Uint8Array-JRUmWcRr.js → _Uint8Array-azThpjEP.js} +1 -1
  4. package/dist/{_overRest-DvC5rm5s.js → _overRest-DMn4Dt71.js} +1 -1
  5. package/dist/{abnfDiagram-O67JEVCF-Cl4ukf6a.js → abnfDiagram-O67JEVCF-D-rmT4BY.js} +4 -4
  6. package/dist/{architectureDiagram-NJMV4G6O-WmFlBQF5.js → architectureDiagram-NJMV4G6O-DOc_c_T9.js} +7 -7
  7. package/dist/{async-slice-OftEOtMA.js → async-slice-DhZDjEtw.js} +2 -2
  8. package/dist/{blockDiagram-BEXU5L5S-BM-gkWAO.js → blockDiagram-BEXU5L5S-BOsyK4E_.js} +11 -11
  9. package/dist/{c4Diagram-YGBWAQC7-D9xSt2QO.js → c4Diagram-YGBWAQC7-El4SkIzX.js} +8 -8
  10. package/dist/{channel-Dg92Hoym.js → channel-CnLB6790.js} +1 -1
  11. package/dist/{chunk-3FUC2YCW-Cf18L00V.js → chunk-3FUC2YCW-BCsKm3ZT.js} +8 -8
  12. package/dist/{chunk-5DYCD2WN-Cja1INgd.js → chunk-5DYCD2WN-Cd1zazNp.js} +3 -3
  13. package/dist/{chunk-6AEJRKK7-3YWRP4aV.js → chunk-6AEJRKK7-BLBlw8ui.js} +2 -2
  14. package/dist/{chunk-742MDFTN-Cs1qGiTK.js → chunk-742MDFTN-CsUjFyzK.js} +2 -2
  15. package/dist/{chunk-7INBJB4K-BY5n77ka.js → chunk-7INBJB4K-k-I67NSP.js} +6 -6
  16. package/dist/{chunk-7M6MHVWA-C3x6E3yT.js → chunk-7M6MHVWA-C3qpW8zM.js} +9 -9
  17. package/dist/{chunk-7PRAP22T-_Av9vcXz.js → chunk-7PRAP22T-0C6_H8Li.js} +1 -1
  18. package/dist/{chunk-GNY47TPC-D1E6DHDV.js → chunk-GNY47TPC-CzPNLW5k.js} +11 -11
  19. package/dist/{chunk-GTNCS2PH-BZ9WMD1v.js → chunk-GTNCS2PH-AJi9PVT7.js} +3 -3
  20. package/dist/{chunk-GWA4HPMP-Co-A6dtV.js → chunk-GWA4HPMP-B8jXI0Tl.js} +3 -3
  21. package/dist/{chunk-MBY4JIJT-C3RQP_Nk.js → chunk-MBY4JIJT-BFIqOe-8.js} +4 -4
  22. package/dist/{chunk-NETBCI7D-DJUuN4EY.js → chunk-NETBCI7D-tNinefdn.js} +1 -1
  23. package/dist/{chunk-O7XYJQB3-A66QSOPA.js → chunk-O7XYJQB3-BChweQ_V.js} +1 -1
  24. package/dist/{chunk-UA2S7LBM-BNeHeMx9.js → chunk-UA2S7LBM-ChuksCDg.js} +6 -6
  25. package/dist/{chunk-WEXAMYUT-CucJfehv.js → chunk-WEXAMYUT-CbFOhEve.js} +2 -2
  26. package/dist/{chunk-XXDRQBXY-FgXU14Bh.js → chunk-XXDRQBXY-dKXxu_aj.js} +1 -1
  27. package/dist/{chunk-Z7XXMR3K-DMTBxZxM.js → chunk-Z7XXMR3K-C9P-XjM2.js} +8 -8
  28. package/dist/{chunk-ZIGJFQKS-C3xUAXJq.js → chunk-ZIGJFQKS-9VXArDKF.js} +3 -3
  29. package/dist/{classDiagram-v2-NBCMYWYE-BDaxYvDy.js → classDiagram-v2-NBCMYWYE-D00ZobEC.js} +14 -14
  30. package/dist/clsx-CbxlCXZ3.js +16 -0
  31. package/dist/components/base-content.css +139 -0
  32. package/dist/components/base.css +150 -0
  33. package/dist/components/components.css +2 -0
  34. package/dist/components/reduced-motion.css +56 -0
  35. package/dist/components/theme-tokens.css +116 -0
  36. package/dist/components/ui/actions/button-group/button-group.css +1 -1
  37. package/dist/components/ui/feedback/toast/toast.css +21 -0
  38. package/dist/components/ui/primitives/label/label.css +33 -0
  39. package/dist/{components-DrStgM4J.js → components-km60bAdr.js} +34658 -34218
  40. package/dist/components.js +6 -6
  41. package/dist/{cose-bilkent-JH36ORCC-DRUT3qBb.js → cose-bilkent-JH36ORCC-P94vkHv_.js} +2 -2
  42. package/dist/{cynefinDiagram-VND7K2PF-B9HrdQW8.js → cynefinDiagram-VND7K2PF-EIpXbLXm.js} +4 -4
  43. package/dist/{dagre-6A5THRUB-DZF0nICj.js → dagre-6A5THRUB-D_F3_LlW.js} +20 -20
  44. package/dist/{diagram-22UHCM2B-CVZYpeev.js → diagram-22UHCM2B-CsxOExpW.js} +15 -15
  45. package/dist/{diagram-3UASUU5V-D7MmBJbM.js → diagram-3UASUU5V-Dl7AETV1.js} +7 -7
  46. package/dist/{diagram-ATOU4E4O-CNY-8kl0.js → diagram-ATOU4E4O-isL7nwg2.js} +3 -3
  47. package/dist/{diagram-CDSNMT55-C8Mk1smi.js → diagram-CDSNMT55-Bnhlo5at.js} +5 -5
  48. package/dist/{diagram-MLGK6HIB-CpfeILwW.js → diagram-MLGK6HIB-DvBcyz8v.js} +4 -4
  49. package/dist/{diagram-MPIPVDR6-Do7bUnTd.js → diagram-MPIPVDR6-iTevwmNE.js} +4 -4
  50. package/dist/{dist-C3YK_ean.js → dist-B6_-RPZ3.js} +1 -1
  51. package/dist/{dist-CRbPtqwD.js → dist-BvUQHUs_.js} +4 -4
  52. package/dist/dnd.js +1 -1
  53. package/dist/{ebnfDiagram-ZINNZB2B-1GewX9Zq.js → ebnfDiagram-ZINNZB2B-RVuQIpm0.js} +4 -4
  54. package/dist/{elk-276RUBZZ-DI4eicFj.js → elk-276RUBZZ-BK-NMLiI.js} +26 -26
  55. package/dist/{erDiagram-OPXOYQCR-CUbpVns3.js → erDiagram-OPXOYQCR-DC1GgS4l.js} +14 -14
  56. package/dist/field-blur--x_n3oMy.js +3637 -0
  57. package/dist/flowDiagram-KWPJA3E3-BUSlDNQP.js +17 -0
  58. package/dist/{ganttDiagram-FUAMR5RP-D3vmspam.js → ganttDiagram-FUAMR5RP-DOp1p_x0.js} +6 -6
  59. package/dist/{gitGraphDiagram-X574FWY7-80yOq8dE.js → gitGraphDiagram-X574FWY7-DerJL1Q-.js} +3 -3
  60. package/dist/{graphlib-DLJn7qS3.js → graphlib-DLjE3QPd.js} +6 -6
  61. package/dist/hooks.js +3 -3
  62. package/dist/{i18n-provider-DBFRz5uH.js → i18n-provider-CQL7fqLy.js} +1 -1
  63. package/dist/i18n.js +6 -6
  64. package/dist/icon-button-G9zdQ3Ix.js +2753 -0
  65. package/dist/{infoDiagram-VRGFBTTK-Cboh-B9n.js → infoDiagram-VRGFBTTK-CtVsLyCQ.js} +3 -3
  66. package/dist/{isArrayLikeObject-CO5xeuaJ.js → isArrayLikeObject-CO-5KAzG.js} +3 -3
  67. package/dist/{isEmpty-DOC38Ces.js → isEmpty-Bl1drJE7.js} +3 -3
  68. package/dist/{ishikawaDiagram-OU5B5YK6-DwTd1YxL.js → ishikawaDiagram-OU5B5YK6-D1n8vp8C.js} +4 -4
  69. package/dist/{journeyDiagram-ZHPQQLJL-Dxvl3oeN.js → journeyDiagram-ZHPQQLJL-DuQ9Th0y.js} +4 -4
  70. package/dist/{kanban-definition-PNTS6WVX-S8plI4wn.js → kanban-definition-PNTS6WVX-D1-Owjil.js} +10 -10
  71. package/dist/lib/components/ui/actions/button/busy-click-capture.d.ts +11 -0
  72. package/dist/lib/components/ui/actions/button/button-tones.d.ts +36 -0
  73. package/dist/lib/components/ui/actions/button/button.d.ts +19 -0
  74. package/dist/lib/components/ui/actions/button/button.wrap.d.ts +8 -0
  75. package/dist/lib/components/ui/actions/button/label-text.d.ts +10 -0
  76. package/dist/lib/components/ui/actions/button/use-click-lock.d.ts +18 -0
  77. package/dist/lib/components/ui/actions/copy-button/copy-button.d.ts +5 -1
  78. package/dist/lib/components/ui/actions/icon-button/icon-button.compound-variants.d.ts +114 -0
  79. package/dist/lib/components/ui/collections/data-table/hooks.d.ts +13 -13
  80. package/dist/lib/components/ui/collections/data-table/partials/data-table-empty-row.d.ts +5 -1
  81. package/dist/lib/components/ui/collections/data-table/partials/data-table-scroll-region.d.ts +16 -0
  82. package/dist/lib/components/ui/collections/data-table/partials/data-table-toolbar.d.ts +24 -0
  83. package/dist/lib/components/ui/collections/data-table/select-checkbox.d.ts +10 -0
  84. package/dist/lib/components/ui/collections/data-table/types.d.ts +9 -0
  85. package/dist/lib/components/ui/collections/data-table/use-data-table.d.ts +12 -1
  86. package/dist/lib/components/ui/collections/data-table/use-scroll-edges.d.ts +22 -0
  87. package/dist/lib/components/ui/dates/calendar/calendar-day.d.ts +8 -0
  88. package/dist/lib/components/ui/dates/calendar/calendar.d.ts +3 -3
  89. package/dist/lib/components/ui/dates/calendar/calendar.utils.d.ts +8 -0
  90. package/dist/lib/components/ui/dates/calendar/use-calendar-labels.d.ts +22 -0
  91. package/dist/lib/components/ui/display/description/description.d.ts +2 -1
  92. package/dist/lib/components/ui/display/list-tile/list-tile.d.ts +9 -1
  93. package/dist/lib/components/ui/display/list-tile/partials/list-tile-body.d.ts +11 -0
  94. package/dist/lib/components/ui/display/list-tile/partials/list-tile-classes.d.ts +10 -0
  95. package/dist/lib/components/ui/feedback/alert-dialog/alert-dialog.d.ts +1 -1
  96. package/dist/lib/components/ui/feedback/toast/partials/toast-actions.d.ts +3 -0
  97. package/dist/lib/components/ui/feedback/toast/toast.d.ts +27 -3
  98. package/dist/lib/components/ui/feedback/toast/toaster.d.ts +19 -0
  99. package/dist/lib/components/ui/feedback/toast/toasts.d.ts +3 -0
  100. package/dist/lib/components/ui/forms/field-blur.d.ts +7 -2
  101. package/dist/lib/components/ui/forms/use-field-blur.d.ts +8 -1
  102. package/dist/lib/components/ui/layout/app-shell/activity-content.d.ts +10 -1
  103. package/dist/lib/components/ui/layout/app-shell/activity-layout.d.ts +26 -2
  104. package/dist/lib/components/ui/layout/app-shell/app-header.d.ts +8 -2
  105. package/dist/lib/components/ui/layout/app-shell/app-sidebar.d.ts +8 -2
  106. package/dist/lib/components/ui/layout/app-shell/types.d.ts +3 -0
  107. package/dist/lib/components/ui/layout/app-shell/use-content-scroll.d.ts +10 -0
  108. package/dist/lib/components/ui/layout/app-shell/use-navigation-sheet.d.ts +19 -0
  109. package/dist/lib/components/ui/layout/sidebar/index.d.ts +5 -0
  110. package/dist/lib/components/ui/layout/sidebar/sidebar-content.d.ts +2 -0
  111. package/dist/lib/components/ui/layout/sidebar/sidebar-context.d.ts +7 -0
  112. package/dist/lib/components/ui/layout/sidebar/sidebar-footer.d.ts +2 -0
  113. package/dist/lib/components/ui/layout/sidebar/sidebar-header.d.ts +2 -0
  114. package/dist/lib/components/ui/layout/sidebar/sidebar-menu-item-tooltip.d.ts +7 -0
  115. package/dist/lib/components/ui/layout/sidebar/sidebar-menu-item.d.ts +31 -0
  116. package/dist/lib/components/ui/layout/sidebar/sidebar-menu-label.d.ts +4 -0
  117. package/dist/lib/components/ui/layout/sidebar/sidebar.d.ts +1 -12
  118. package/dist/lib/components/ui/overlays/context-menu/context-menu-items.d.ts +10 -0
  119. package/dist/lib/components/ui/overlays/context-menu/context-menu-sub.d.ts +8 -0
  120. package/dist/lib/components/ui/overlays/context-menu/context-menu.d.ts +4 -14
  121. package/dist/lib/components/ui/overlays/dialog/dialog-outside.d.ts +6 -0
  122. package/dist/lib/components/ui/overlays/dialog/dialog-scroll-content.d.ts +1 -1
  123. package/dist/lib/components/ui/overlays/dialog/dialog.d.ts +1 -1
  124. package/dist/lib/components/ui/overlays/dialog/use-return-focus.d.ts +3 -2
  125. package/dist/lib/components/ui/overlays/drawer/drawer.d.ts +1 -1
  126. package/dist/lib/components/ui/overlays/dropdown-menu/dropdown-menu-items.d.ts +11 -0
  127. package/dist/lib/components/ui/overlays/dropdown-menu/dropdown-menu-sub.d.ts +8 -0
  128. package/dist/lib/components/ui/overlays/dropdown-menu/dropdown-menu.d.ts +6 -21
  129. package/dist/lib/components/ui/overlays/popover/popover.d.ts +1 -1
  130. package/dist/lib/components/ui/overlays/sheet/sheet.d.ts +1 -1
  131. package/dist/lib/components/ui/primitives/card/card.d.ts +10 -1
  132. package/dist/lib/components/ui/primitives/link/link-component.d.ts +14 -0
  133. package/dist/lib/components/ui/primitives/tabs/partials/tabs-content.d.ts +3 -0
  134. package/dist/lib/components/ui/primitives/tabs/partials/tabs-indicator.d.ts +13 -0
  135. package/dist/lib/components/ui/primitives/tabs/partials/tabs-list.d.ts +3 -0
  136. package/dist/lib/components/ui/primitives/tabs/partials/tabs-trigger.d.ts +6 -0
  137. package/dist/lib/components/ui/primitives/tabs/tabs-classes.d.ts +27 -0
  138. package/dist/lib/components/ui/primitives/tabs/tabs-variant.d.ts +5 -0
  139. package/dist/lib/components/ui/primitives/tabs/tabs.d.ts +11 -7
  140. package/dist/lib/components/ui/selection/select/select.d.ts +1 -1
  141. package/dist/lib/components/ui/selection/select-chip-field/select-chip-field.d.ts +1 -1
  142. package/dist/lib/components/ui/widgets/emoji/types.d.ts +1 -6
  143. package/dist/lib/i18n/resources/en/en.d.ts +23 -0
  144. package/dist/lib/i18n/resources/en/material.json.d.ts +28 -5
  145. package/dist/lib/i18n/resources/fr/fr.d.ts +23 -0
  146. package/dist/lib/i18n/resources/fr/material.json.d.ts +28 -5
  147. package/dist/lib/i18n/resources/resources.d.ts +46 -0
  148. package/dist/lib/i18n/translator.d.ts +1 -1
  149. package/dist/lib/lib/redux/status-slice.d.ts +1 -1
  150. package/dist/lib.js +39 -55
  151. package/dist/main.js +13 -13
  152. package/dist/map.js +35 -35
  153. package/dist/material.css +1 -1
  154. package/dist/{merge-CuDTNgpm.js → merge-CwwtONfp.js} +4 -4
  155. package/dist/{mermaid.core-O7mtb1-p.js → mermaid.core-Dfr4zfuP.js} +49 -49
  156. package/dist/{mindmap-definition-NLK3R4M7-CO0E97EZ.js → mindmap-definition-NLK3R4M7-_qKWGSrk.js} +13 -13
  157. package/dist/{pegDiagram-GJSIUBJH-CAO6aYvj.js → pegDiagram-GJSIUBJH-CWKobqLl.js} +4 -4
  158. package/dist/{pieDiagram-5QR66LMP-BXxVfv5K.js → pieDiagram-5QR66LMP-B6at48Bd.js} +5 -5
  159. package/dist/print.js +2 -2
  160. package/dist/{quadrantDiagram-O4NWA36T-BbidZDM8.js → quadrantDiagram-O4NWA36T-Cr9bA0xJ.js} +3 -3
  161. package/dist/{railroadDiagram-XR7U4H2S-DmKDXg8V.js → railroadDiagram-XR7U4H2S-fsZ7oqPG.js} +4 -4
  162. package/dist/{requirementDiagram-PLB6GJNP-wFvgSWJP.js → requirementDiagram-PLB6GJNP-BBs-lByV.js} +13 -13
  163. package/dist/rolldown-runtime-BFFgcFxR.js +30 -0
  164. package/dist/{sankeyDiagram-IPEJSGJF-Cka5t09q.js → sankeyDiagram-IPEJSGJF-BzX37jGY.js} +3 -3
  165. package/dist/{sequenceDiagram-PO4LG4MO-B2qb55A5.js → sequenceDiagram-PO4LG4MO-Bgx1kuGA.js} +5 -5
  166. package/dist/{sortable-provider-C5VYhY8u.js → sortable-provider-WZT5I34N.js} +24 -24
  167. package/dist/{src-C9kQSo6K.js → src-CsLQInsN.js} +1 -1
  168. package/dist/{stateDiagram-v2-GCMORJYK-EMa7q3ju.js → stateDiagram-v2-GCMORJYK-Chc9Z6Mr.js} +14 -14
  169. package/dist/{stored-language-B0o4cBh8.js → stored-language-BW32owje.js} +56 -10
  170. package/dist/styles.css +10 -368
  171. package/dist/{swimlanes-2SLR337P-BtZ7Di5u.js → swimlanes-2SLR337P-D0yLvfot.js} +12 -12
  172. package/dist/swimlanesDiagram-TC7HE7FX-BOd0Czfs.js +28 -0
  173. package/dist/{timeline-definition-EJHVYXUP-2Z6UABw8.js → timeline-definition-EJHVYXUP-nDNR-Dt-.js} +5 -5
  174. package/dist/{translate-material-BCwQrt2G.js → translate-material-ZEzF-LKH.js} +2 -2
  175. package/dist/{translator-CwpmpI24.js → translator-BfqVlNGW.js} +1 -1
  176. package/dist/{use-heading-spy-BtBJiU63.js → use-heading-spy-DvZVu15k.js} +37 -37
  177. package/dist/{use-material-translation-DG_7z3Q7.js → use-material-translation-B2GUtxUY.js} +2 -2
  178. package/dist/{usecaseDiagram-POWQR4AR-DT56cp_B.js → usecaseDiagram-POWQR4AR-Bwen99ay.js} +13 -13
  179. package/dist/{utils-UBn-46YY.js → utils-DC8bw9A_.js} +346 -360
  180. package/dist/{vennDiagram-UO4OBE2U-CO0yFCI8.js → vennDiagram-UO4OBE2U-BpgP__1T.js} +4 -4
  181. package/dist/{wardleyDiagram-VNRHLVJA-QkwghAYo.js → wardleyDiagram-VNRHLVJA-CYqKMb2f.js} +4 -4
  182. package/dist/{xychartDiagram-PMCCYNJV-B2aZlrno.js → xychartDiagram-PMCCYNJV-b-Quuxoi.js} +7 -7
  183. package/docs/components/buttons-and-actions.md +60 -10
  184. package/docs/components/data-display.md +24 -2
  185. package/docs/components/data-table-filter-and-lists.md +88 -10
  186. package/docs/components/date-and-time.md +24 -0
  187. package/docs/components/feedback-and-status.md +85 -8
  188. package/docs/components/form-fields.md +14 -5
  189. package/docs/components/media-and-files.md +7 -0
  190. package/docs/components/navigation-and-layout.md +21 -3
  191. package/docs/components/overlays-and-menus.md +21 -6
  192. package/docs/components/primitives.md +96 -7
  193. package/docs/components/rich-widgets.md +10 -0
  194. package/docs/components/selection-and-choice.md +13 -2
  195. package/docs/getting-started.md +4 -2
  196. package/docs/guides/app-shell.md +97 -4
  197. package/docs/guides/forms.md +54 -5
  198. package/docs/guides/helpers.md +2 -2
  199. package/docs/guides/i18n.md +24 -4
  200. package/docs/guides/ssr.md +1 -1
  201. package/docs/guides/theming.md +147 -5
  202. package/docs/previews/data-display-codegroup.webp +0 -0
  203. package/docs/previews/data-display-description.webp +0 -0
  204. package/docs/previews/feedback-status-toast.webp +0 -0
  205. package/docs/previews/primitives-tabs.webp +0 -0
  206. package/docs/previews/rich-widgets-emojipicker.webp +0 -0
  207. package/docs/roadmap.md +29 -1
  208. package/docs/troubleshooting.md +937 -11
  209. package/lib/components/base-content.css +139 -0
  210. package/lib/components/base.css +150 -0
  211. package/lib/components/components.css +2 -0
  212. package/lib/components/reduced-motion.css +56 -0
  213. package/lib/components/theme-tokens.css +116 -0
  214. package/lib/components/ui/actions/button-group/button-group.css +1 -1
  215. package/lib/components/ui/feedback/toast/toast.css +21 -0
  216. package/lib/components/ui/primitives/label/label.css +33 -0
  217. package/lib/styles.css +10 -368
  218. package/package.json +2 -2
  219. package/dist/field-blur-DWQtxiAe.js +0 -3483
  220. package/dist/flowDiagram-KWPJA3E3-DZxumFfu.js +0 -17
  221. package/dist/icon-button-BxySFfrr.js +0 -2718
  222. package/dist/lib/components/ui/widgets/emoji/emoji.groups.d.ts +0 -6
  223. package/dist/swimlanesDiagram-TC7HE7FX-CROh8GTF.js +0 -28
@@ -1,8 +1,8 @@
1
- import { t as e } from "./src-C9kQSo6K.js";
2
- import { i as t } from "./chunk-ZIGJFQKS-C3xUAXJq.js";
1
+ import { t as e } from "./src-CsLQInsN.js";
2
+ import { i as t } from "./chunk-ZIGJFQKS-9VXArDKF.js";
3
3
  import { n } from "./chunk-Y2CYZVJY-BAad7w_K.js";
4
- import { H as r, U as i, a, b as o, c as s, f as c, it as l, nt as u, q as d, rt as f, tt as p, v as m, w as h, y as g } from "./chunk-O7XYJQB3-A66QSOPA.js";
5
- import { t as _ } from "./chunk-6AEJRKK7-3YWRP4aV.js";
4
+ import { H as r, U as i, a, b as o, c as s, f as c, it as l, nt as u, q as d, rt as f, tt as p, v as m, w as h, y as g } from "./chunk-O7XYJQB3-BChweQ_V.js";
5
+ import { t as _ } from "./chunk-6AEJRKK7-BLBlw8ui.js";
6
6
  import { t as v } from "./rough.esm-Cvc79rxE.js";
7
7
  //#region ../../node_modules/khroma/dist/methods/transparentize.js
8
8
  var y = (e, t) => f(e, "a", -t), b = 1e-10;
@@ -1,9 +1,9 @@
1
- import { n as e } from "./src-C9kQSo6K.js";
2
- import { i as t } from "./chunk-ZIGJFQKS-C3xUAXJq.js";
1
+ import { n as e } from "./src-CsLQInsN.js";
2
+ import { i as t } from "./chunk-ZIGJFQKS-9VXArDKF.js";
3
3
  import { n } from "./mermaid-parser.core-BXD_TD1T.js";
4
4
  import { n as r } from "./chunk-Y2CYZVJY-BAad7w_K.js";
5
- import { D as i, H as a, U as o, a as s, b as c, c as l, q as u, v as d, w as f, x as p, y as m } from "./chunk-O7XYJQB3-A66QSOPA.js";
6
- import { t as h } from "./chunk-6AEJRKK7-3YWRP4aV.js";
5
+ import { D as i, H as a, U as o, a as s, b as c, c as l, q as u, v as d, w as f, x as p, y as m } from "./chunk-O7XYJQB3-BChweQ_V.js";
6
+ import { t as h } from "./chunk-6AEJRKK7-BLBlw8ui.js";
7
7
  import { t as g } from "./chunk-JWPE2WC7-BGVSGRTC.js";
8
8
  //#region ../../node_modules/mermaid/dist/chunks/mermaid.core/wardleyDiagram-VNRHLVJA.mjs
9
9
  var _ = /* @__PURE__ */ r((e, t) => {
@@ -1,11 +1,11 @@
1
- import { at as e, rt as t, st as n } from "./components-DrStgM4J.js";
2
- import { n as r } from "./src-C9kQSo6K.js";
3
- import { i } from "./chunk-ZIGJFQKS-C3xUAXJq.js";
1
+ import { at as e, rt as t, st as n } from "./components-km60bAdr.js";
2
+ import { n as r } from "./src-CsLQInsN.js";
3
+ import { i } from "./chunk-ZIGJFQKS-9VXArDKF.js";
4
4
  import { n as a } from "./chunk-Y2CYZVJY-BAad7w_K.js";
5
- import { D as o, H as s, U as c, a as l, b as u, c as d, f, q as p, v as m, w as h, y as g, z as _ } from "./chunk-O7XYJQB3-A66QSOPA.js";
6
- import { t as v } from "./chunk-6AEJRKK7-3YWRP4aV.js";
7
- import "./chunk-742MDFTN-Cs1qGiTK.js";
8
- import { t as y } from "./chunk-MBY4JIJT-C3RQP_Nk.js";
5
+ import { D as o, H as s, U as c, a as l, b as u, c as d, f, q as p, v as m, w as h, y as g, z as _ } from "./chunk-O7XYJQB3-BChweQ_V.js";
6
+ import { t as v } from "./chunk-6AEJRKK7-BLBlw8ui.js";
7
+ import "./chunk-742MDFTN-CsUjFyzK.js";
8
+ import { t as y } from "./chunk-MBY4JIJT-BFIqOe-8.js";
9
9
  //#region ../../node_modules/mermaid/dist/chunks/mermaid.core/xychartDiagram-PMCCYNJV.mjs
10
10
  var b = (function() {
11
11
  var e = /* @__PURE__ */ a(function(e, t, n, r) {
@@ -50,15 +50,63 @@ import { Mail } from 'lucide-react';
50
50
  | `variant` | `'filled' \| 'tonal' \| 'outlined' \| 'ghost' \| 'link'` | `'filled'` | the style |
51
51
  | `color` | `'primary' \| 'secondary' \| 'success' \| 'info' \| 'warning' \| 'error' \| 'default'` | `'primary'` | the colour; `default` is the foreground colour |
52
52
  | `size` | `'xs' \| 'sm' \| 'default' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon' \| 'icon-lg'` | — | height: 24, 32, 36 and 40 px; the `icon*` sizes are square, for a button holding only an icon (give it an `aria-label`). Unset, the button keeps its content's height, as before sizes existed |
53
- | `loading` | `boolean` | — | shows a spinner and swallows clicks (the button stays focusable) |
53
+ | `loading` | `boolean` | — | busy: a spinner, `aria-busy="true"` and `aria-disabled="true"`, and every click cancelled — a submit button submits neither on a click nor on Enter in a field. It stays focusable (no native `disabled`), so focus is not lost |
54
+ | `lockOnClick` | `boolean` | — | a click makes the button busy at once, until `loading` has turned true and back (or for one second when it never does): for a `loading` that turns true only after an `await` |
54
55
  | `spinnerVariant` | `SpinnerProps['variant']` | `'2'` | which spinner `loading` shows |
56
+ | `wrap` | `boolean` | — | the label breaks onto as many lines as it needs, centred, instead of being cut with “…”: the button grows taller, never shorter than its `size`. Ignored by the `icon*` sizes |
55
57
  | `tooltip` | `string` | — | wraps the button in a `Tooltip` |
56
58
  | `asChild` | `boolean` | `false` | styles its only child instead of rendering a `<button>` |
57
59
  | `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | not `submit` by default — pass it inside a form |
58
60
 
59
61
  The classes are exported as `buttonVariants` for styling your own element.
60
62
 
61
- **Storybook:** `Buttons & actions/Button` · **Vue:** `Button`
63
+ **On a phone.** A button is never wider than its container, and keeps its
64
+ content's width in a column: a label too
65
+ long for it (a card on a phone) is cut with “…” on one line, and its
66
+ accessible name — and a `title` you give it — keep the whole text. Give a
67
+ label that must be read in full `wrap`, or a width of your own (`w-full`).
68
+ The text is put in a `<span data-slot="button-label">` (one per run of
69
+ text) that shrinks and ends in the ellipsis; it needs the text to be the button's own child; with `asChild`,
70
+ put the label in a `<span>` inside the child element.
71
+
72
+ ```tsx
73
+ <Button title="Continue to the payment details">Continue to the payment details</Button>;
74
+ <Button wrap>Continue to the payment details and confirm the order</Button>;
75
+ ```
76
+
77
+ **Submitting once.** Give a submit button `loading={formState.isSubmitting}`:
78
+ React Hook Form sets it as `handleSubmit` starts, so the button is busy from
79
+ the first click and a double click, or Enter pressed twice, submits once.
80
+ When `loading` comes from something that turns true later — a mutation's
81
+ `isPending` after an async resolver — the second click comes first: add
82
+ `lockOnClick`.
83
+
84
+ ```tsx
85
+ <form onSubmit={handleSubmit(onSubmit)}>
86
+ <EmailFormField control={control} name="email" label="Email" />
87
+ <Button type="submit" loading={formState.isSubmitting}>
88
+ Sign in
89
+ </Button>
90
+ </form>;
91
+
92
+ <Button type="submit" loading={mutation.isPending} lockOnClick>
93
+ Sign in
94
+ </Button>;
95
+ ```
96
+
97
+ **What the guard covers.** Clicks on this button, Space and Enter on it,
98
+ and Enter in a field when it is the form's first submit button (the default
99
+ one, which the browser clicks). `form.requestSubmit()` and `form.submit()`
100
+ fire no click, so a submit your code starts — an OTP step that submits on its
101
+ last digit, a keyboard shortcut — must check `formState.isSubmitting` itself.
102
+ With `lockOnClick`, a `loading` that never turns true releases the lock after
103
+ one second.
104
+
105
+ A test finds a busy button by its state, not by `disabled`:
106
+ `expect(button).toHaveAttribute('aria-busy', 'true')` (`toBeDisabled()` is
107
+ false: it is still focusable).
108
+
109
+ **Storybook:** `Buttons & actions/Button` (`Submits Once`, `Lock On Click`), `Buttons & actions/Button/Phone` · **Vue:** `Button`
62
110
 
63
111
  ---
64
112
 
@@ -179,7 +227,7 @@ import {
179
227
  | --- | --- | --- | --- |
180
228
  | `menu` | `ReactNode` | — (required) | the `DropdownMenu*` items shown by the chevron |
181
229
  | `menuLabel` | `string` | `"More actions"` (translated) | the chevron's `aria-label` |
182
- | `variant`, `color`, `loading`, `disabled` | as `Button` | — | applied to both halves; `loading` also disables the chevron |
230
+ | `variant`, `color`, `loading`, `disabled` | as `Button` | — | `variant`, `color` and `disabled` apply to both halves; `loading` makes the main half busy (spinner, clicks ignored, still focusable) and disables the chevron (native `disabled`) |
183
231
 
184
232
  Other `Button` props (`onClick`, `type`…) go to the main half.
185
233
 
@@ -191,7 +239,7 @@ Other `Button` props (`onClick`, `type`…) go to the main half.
191
239
 
192
240
  ![ResponsiveButton](../previews/buttons-actions-responsivebutton.webp)
193
241
 
194
- Shows its label on desktop and collapses to icon-only on mobile, keeping the label for screen readers.
242
+ Shows its label from 768 px up and collapses to icon-only below, keeping the label for screen readers. The collapse is a CSS media query, not a JS effect, so the server HTML is the same everywhere and nothing flashes.
195
243
 
196
244
  ```tsx
197
245
  import { ResponsiveButton } from '@nxgt/material/components';
@@ -203,9 +251,10 @@ import { Plus } from 'lucide-react';
203
251
  | Prop | Type | Default | Effect |
204
252
  | --- | --- | --- | --- |
205
253
  | `icon` | `ReactNode` | — (required) | always shown |
206
- | `expanded` | `boolean` | not mobile (`useIsMobile`) | shows the label, or keeps it as screen-reader text only |
254
+ | `expanded` | `boolean` | by width (`max-md` CSS) | `true` or `false` forces the label shown, or kept as screen-reader text only, at every width |
207
255
 
208
- Every `Button` prop applies. Collapsed, the label is still in the button as
256
+ Every `Button` prop applies. The label is always in the DOM, so the accessible
257
+ name is the same at every width; collapsed, it is
209
258
  `sr-only` text, so the icon-only button keeps its accessible name; no
210
259
  `aria-label` is needed. `expanded` is layout, not disclosure: the button has
211
260
  no `aria-expanded` of its own, so it is not announced as "collapsed". Wrapped
@@ -263,7 +312,7 @@ Needs a `react-router` router above it.
263
312
 
264
313
  ![CopyButton](../previews/buttons-actions-copybutton.webp)
265
314
 
266
- Copies a value to the clipboard and briefly shows a check mark. `CopyValue` displays the value next to the button.
315
+ Copies a value to the clipboard and briefly shows a check mark (or a cross when the clipboard refuses). `CopyValue` displays the value next to the button.
267
316
 
268
317
  ```tsx
269
318
  import { CopyButton, CopyValue } from '@nxgt/material/components';
@@ -277,12 +326,13 @@ import { CopyButton, CopyValue } from '@nxgt/material/components';
277
326
  | Prop | Type | Default | Effect |
278
327
  | --- | --- | --- | --- |
279
328
  | `value` | `string \| (() => string)` | — (required) | what is copied; a function is read at click time |
280
- | `label` / `copiedLabel` | `string` | `"Copy"` / `"Copied"` (translated) | tooltip and `aria-label`, before and after |
329
+ | `label` | `string` | `"Copy"` (translated) | the button's accessible name; it never changes after a click |
330
+ | `copiedLabel` / `failedLabel` | `string` | `"Copied"` / `"Copy failed"` (translated) | announced in the status region (and shown in the tooltip) once the write succeeded or failed |
281
331
  | `icon` | `ReactNode` | a copy icon | replaces the idle icon |
282
332
  | `variant`, `color` | as `IconButton` | `'ghost'`, `'default'` | the button style |
283
333
  | `display` (`CopyValue`) | `ReactNode` | `value` | what is shown, when it differs from what is copied |
284
334
 
285
- The check mark stays for 1.5 s.
335
+ The write is awaited: "Copied" appears only once the clipboard accepted the text, and a refusal (no permission, an insecure page, no Clipboard API) shows "Copy failed" instead. The outcome is announced through a visually hidden `role="status"` (polite) region rendered next to the button, not by renaming the button: `getByRole('button', { name: 'Copy' })` keeps matching, and a test reads the outcome with `within(button.parentElement!).getByRole('status')` (a bare `getByRole('status')` is ambiguous: every `CopyButton` renders a `status` region, and so do `Banner`, `DataTable` and the skeletons). The result stays for 1.5 s.
286
336
 
287
337
  **Storybook:** `Buttons & actions/CopyButton` · **Vue:** `CopyButton`, `CopyValue`
288
338
 
@@ -446,6 +496,6 @@ import { I18nProvider } from '@nxgt/material/i18n';
446
496
  | --- | --- | --- | --- |
447
497
  | `languages` | `Language[]` | the provider's `supportedLanguages` | which languages the menu lists |
448
498
 
449
- Disabled when no `I18nProvider` is mounted. See the [i18n guide](../guides/i18n.md).
499
+ Its menu is non-modal (see [DropdownMenu](overlays-and-menus.md#dropdownmenu)). Disabled when no `I18nProvider` is mounted. See the [i18n guide](../guides/i18n.md).
450
500
 
451
501
  **Storybook:** `Buttons & actions/LanguageSwitcher` · **Vue:** `LanguageSwitcher`
@@ -308,7 +308,9 @@ import { Description } from '@nxgt/material/components';
308
308
  ```
309
309
 
310
310
  `label` and `value` are its only props; the rest go to the `div`. A falsy
311
- `value` renders nothing, so optional fields need no guard.
311
+ `value` renders nothing, so optional fields need no guard. The label is a small
312
+ muted caption above the value (not a title). `Description` takes one cell of
313
+ the grid it is in; pass `className="col-start-full"` for a full-width row.
312
314
 
313
315
  **Storybook:** `Data display/Description` · **Vue:** `Description`
314
316
 
@@ -358,12 +360,32 @@ import { ListTile } from '@nxgt/material/components';
358
360
  | Prop | Type | Default | Effect |
359
361
  | --- | --- | --- | --- |
360
362
  | `href` | `string` | — | renders an `<a>` |
363
+ | `linkComponent` | `AppShellLinkComponent` | plain `<a>` | the link element, as in the app shell: takes `href`, spreads every other prop, forwards `ref` |
361
364
  | `onClick` | `MouseEventHandler` | — | renders a `<button>` |
362
365
  | `selected` | `boolean` | `false` | highlighted row |
363
366
  | `size` | `'sm' \| 'md'` | `'md'` | `sm` is the dense navigation row |
364
367
  | `leading`, `trailing`, `subtitle` | `ReactNode` | — | slots |
365
368
  | `disabled` | `boolean` | — | disables the link or button |
366
369
 
370
+ With a router, pass `linkComponent` so a click navigates on the client instead of reloading the page (React Router's `Link` takes `to`, so wrap it):
371
+
372
+ ```tsx
373
+ import { Link } from 'react-router';
374
+ import type { AppShellLinkComponent } from '@nxgt/material/components';
375
+ import { ListTile } from '@nxgt/material/components';
376
+
377
+ // Hoisted: one component, not a new one on every render.
378
+ const RouterLink: AppShellLinkComponent = ({ href, ...props }) => <Link to={href} {...props} />;
379
+
380
+ export function SettingsLink() {
381
+ return <ListTile title="Notifications" href="/settings/notifications" linkComponent={RouterLink} />;
382
+ }
383
+ ```
384
+
385
+ See also [A `ListTile` link reloads the whole page](../troubleshooting.md#a-listtile-link-reloads-the-whole-page). A disabled tile never renders the link component: it is a plain `<a>` without `href`, with `aria-disabled="true"`.
386
+
387
+ The root carries `data-slot="item-detail"` (a tile that is a button or a plain row too).
388
+
367
389
  **Storybook:** `Data display/ListTile` · **Vue:** `ListTile`
368
390
 
369
391
  ---
@@ -457,7 +479,7 @@ import { EventChip } from '@nxgt/material/components';
457
479
 
458
480
  ![CodeBlock](../previews/data-display-codeblock.webp)
459
481
 
460
- Highlighted code with line numbers and copy, a Monaco diff, a collapsible JSON tree, and tabbed samples (`Tabs variant="line"`).
482
+ Highlighted code with line numbers and copy, a Monaco diff, a collapsible JSON tree, and tabbed samples (`Tabs`, underlined).
461
483
 
462
484
  <img src="../previews/data-display-codegroup.webp" height="96" alt="CodeGroup">
463
485
  <img src="../previews/data-display-jsonviewer.webp" height="96" alt="JsonViewer">
@@ -23,7 +23,7 @@ each other, so either can be used alone.
23
23
 
24
24
  ![DataTable](../previews/data-table-filter-lists-datatable.webp)
25
25
 
26
- TanStack Table v9 with sorting, filtering, selection, expandable rows, pagination or load-more, column visibility, skeleton rows while loading and your own empty state — or one server page at a time, with controlled sorting and selection, cursor Previous / Next links and row links.
26
+ TanStack Table v9 with sorting, filtering, selection, expandable rows, pagination or load-more, column visibility, skeleton rows while loading and your own empty state — or one server page at a time, with controlled sorting and selection, cursor Previous / Next links and row links. On a phone it keeps its header row and scrolls sideways in a keyboard-reachable region; the page does not.
27
27
 
28
28
  ```tsx
29
29
  import {
@@ -72,14 +72,16 @@ table enables, for typing your own column defs.
72
72
  | `actions` | `(table) => ReactNode` | — | toolbar content with access to the table (selected rows…) |
73
73
  | `onLoadMore` / `isLoadingMore` | `() => void` / `boolean` | — | adds a "load more" button under the rows |
74
74
  | `options` | `{ showPage?: boolean; showSelected?: boolean }` | both `true` | parts of the footer |
75
- | `showHeader` | `boolean` | hidden on mobile | header row |
75
+ | `showHeader` | `boolean` | shown | header row (column names, sorting, "Select all") at every width; `false` hides it |
76
+ | `maxHeight` | `number \| string` | none | caps the table's height (`400`, `'60vh'`); the rows scroll inside it |
77
+ | `scrollLabel` | `string` | "Scrollable table" | the name of the scroll region the table becomes while it overflows |
76
78
  | `loading` | `boolean` | `false` | skeleton rows, or the current rows dimmed; `aria-busy`; controls disabled |
77
79
  | `loadingRows` | `number` | the page size | how many skeleton rows |
78
80
  | `empty` | `ReactNode \| ({ filtered }) => ReactNode` | icon and "No data available" | content of the empty row, spanning every visible column |
79
81
  | `manual` | `boolean` | `false` | `data` is one server page: no client filtering, sorting or paging; the footer is the server footer ([server-side data](#server-side-data)) |
80
- | `sorting` / `onSortingChange` | `SortingState` / `(sorting) => void` | held inside | controlled sorting; the callback gets the new state, not an updater |
82
+ | `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 |
81
83
  | `getRowId` | `(row, index) => string` | the index | the row's id, the key of `rowSelection` |
82
- | `rowSelection` / `onRowSelectionChange` | `RowSelectionState` / `(selection) => void` | held inside | controlled selection, by id; may hold rows of other pages |
84
+ | `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 |
83
85
  | `pagination` | `{ totalCount?, offset?, previous?, next? }` | — | the server footer's range ("41–60 of 133") and Previous / Next: an href (a link) or a callback (a button) |
84
86
  | `footer` | `false \| (table) => ReactNode` | the pagination | replaces the footer, or removes it |
85
87
  | `getRowHref` | `(row) => string \| undefined` | — | the first data cell links to the row's page; a click elsewhere on the row follows it |
@@ -138,6 +140,69 @@ default, or your own content, told whether rows exist but none matches the
138
140
  filters (`filtered`); content that renders nothing gets the default.
139
141
  `DataTableEmpty` and `DataTableEmptyState` type the prop and its argument.
140
142
 
143
+ ### Phones and narrow screens
144
+
145
+ ```tsx
146
+ // The rows scroll inside 60% of the viewport; the page scrolls otherwise.
147
+ <DataTable
148
+ data={invoices}
149
+ columns={columns}
150
+ maxHeight="60vh"
151
+ scrollLabel="Invoices"
152
+ />
153
+ ```
154
+
155
+ The table is as tall as its rows, and the page scrolls: there is no height
156
+ cap unless you pass `maxHeight`, so a phone never gets a scroll inside the
157
+ page's. The header row shows at every width (`showHeader` `false` hides it).
158
+ With `maxHeight`, the rows scroll under the header row, which stays in view
159
+ on an opaque background.
160
+
161
+ When the columns are wider than the table, the table scrolls sideways in
162
+ one box (`[data-slot="table-wrapper"]`), and the page does not. While it
163
+ overflows, that box is a `region` named "Scrollable table"
164
+ (`material.table.labels.scroll-region`, or `scrollLabel`) with
165
+ `tabindex="0"`, so a keyboard user can reach it and scroll it with the arrow
166
+ keys, and an edge fades on each side where columns are hidden
167
+ (`data-scroll-start` / `data-scroll-end` on its parent,
168
+ `[data-slot="table-scroll"]`). It is not a region while it fits, nor on the
169
+ server; once focused, it stays a tab stop until focus leaves, even if it
170
+ stops overflowing. The empty state is centred in the part of the table you
171
+ can see (the box's measured width, `--table-visible-width`), not across its
172
+ whole scroll width. The table needs no width from its parent: inside a
173
+ content-sized one (`w-fit`, `inline-flex`, an `auto` grid track, a popover)
174
+ it is as wide as its columns.
175
+
176
+ The `select` column's checkboxes keep their 16px box, with a 24px target
177
+ around it (WCAG 2.5.8). The toolbar renders only when it has content:
178
+ `actions` that render nothing (`null`, `false`, whitespace, an empty
179
+ fragment) leave no empty `<fieldset>` behind.
180
+
181
+ Selection actions do not move the rows. With `actions` and the `select`
182
+ column (`predefinedColumns().select`, id `select`), the toolbar's height is
183
+ reserved — an empty, hidden placeholder
184
+ (`[data-slot="header-placeholder"]`) while `actions` render nothing, the
185
+ same minimum height once they do — so the "Delete selected" that appears on
186
+ the first selection does not push the tapped row down. The reserved height
187
+ is one row of default-size buttons (`h-9`); taller or wrapping actions still
188
+ grow the toolbar.
189
+
190
+ ```tsx
191
+ <DataTable
192
+ data={people}
193
+ columns={[select, ...columns]}
194
+ actions={(table) =>
195
+ table.getSelectedRowModel().rows.length > 0 && (
196
+ <Button onClick={handleDelete(table.getSelectedRowModel().rows)}>
197
+ Delete selected
198
+ </Button>
199
+ )
200
+ }
201
+ />
202
+ ```
203
+
204
+ Rows stay rows on a phone; a card layout is not built in.
205
+
141
206
  ### Server-side data
142
207
 
143
208
  When the server filters, sorts and pages, hand the table **one page** and
@@ -249,12 +314,22 @@ export default function Students() {
249
314
  index of the page's first row) make the range: "41–60 of 133"
250
315
  (`material.table.pagination.range`), "41–60" without a total, "133 rows"
251
316
  without an offset. Cursor paging knows the offset from the trail's length
252
- times the page size; leave it out if pages can be short.
317
+ times the page size; leave it out if pages can be short. The range is a
318
+ polite live region (`role="status"`, `aria-atomic="true"`, named "Rows
319
+ shown" by `material.table.pagination.range-label`) that is in the DOM from
320
+ the first render whenever `pagination` is given, so a screen reader
321
+ announces "41–60 of 133" when the page changes. With neither `offset` nor
322
+ `totalCount` the region is empty, and is kept in the DOM so a later change
323
+ is still announced. The table already has a visually hidden status for
324
+ "Loading data…" (and the page may have a toast region), so a test finds the
325
+ range with `getByRole('status', { name: 'Rows shown' })`, not a bare
326
+ `getByRole('status')`.
253
327
  - **Sorting** is controlled: the headers show `sorting`, and
254
328
  `onSortingChange` receives the new `SortingState` (not TanStack's updater)
255
- for you to put in the URL. Filters work the same way: render them in
256
- `actions` and keep their values in the URL; `manual` turns client
257
- filtering off.
329
+ for you to put in the URL (neither it nor `onRowSelectionChange` is called
330
+ for an update that resolves to the current value). Filters work the same
331
+ way: render them in `actions` and keep their values in the URL; `manual`
332
+ turns client filtering off.
258
333
  - **Selection** is keyed by `getRowId`, so it survives a page change: ids
259
334
  of other pages stay in `rowSelection`, and the footer counts them all
260
335
  ("3 rows selected", `material.table.pagination.selected`). `actions(table)`
@@ -271,7 +346,10 @@ export default function Students() {
271
346
  navigation (opening a sheet). Alone, it makes the row focusable and Enter
272
347
  calls it; with `getRowHref`, the link stays the keyboard target, and
273
348
  calling `event.preventDefault()` in it stops a mouse click from following
274
- the link. A focusable row has no role of its own: for anything a screen
349
+ the link. The row itself is never a second control: with `getRowHref` it
350
+ has no role, no `tabindex` and no `aria-label`, and the one link is named
351
+ by the cell's text, so `getByRole('link', { name: 'Ada', exact: true })`
352
+ is unique. A focusable row has no role of its own: for anything a screen
275
353
  reader user must reach, prefer `getRowHref` or a button in a cell.
276
354
  - **Your own footer.** `footer={(table) => …}` replaces the footer —
277
355
  `<DataTableServerFooter table={table} pagination={…} />` is the default
@@ -280,7 +358,7 @@ export default function Students() {
280
358
  navigation is pending, and the current rows stay in place, dimmed.
281
359
 
282
360
  The server footer's strings are `material.table.pagination.*`
283
- (`previous`, `next`, `label`, `range`, `range-open`, `total`, `selected`),
361
+ (`previous`, `next`, `label`, `range`, `range-open`, `range-label`, `total`, `selected`),
284
362
  in English and French.
285
363
 
286
364
  For the TanStack API itself (column helpers, features, row models), see
@@ -46,6 +46,30 @@ month label opens the year view.
46
46
  | `startMonth`, `endMonth` | `Date` | — | navigation bounds |
47
47
  | `captionLayout` | `'label' \| 'dropdown' \| …` | `'label'` | caption as text or month / year dropdowns |
48
48
  | `showOutsideDays` | `boolean` | `true` | days of the adjacent months |
49
+ | `locale` | `Locale` (react-day-picker) | `I18nProvider` language, else `en` | month and weekday names, the heading, and the language of the day names |
50
+
51
+ **Day names.** Every day is named by its full date in the calendar's language
52
+ (`locale`, else the active `I18nProvider` language, else English): "Thursday,
53
+ October 1, 2026", "jeudi 1 octobre 2026". The name is on the button and, for
54
+ Testing Library, on the `gridcell` around it; "Today" and "selected" are not in
55
+ it. The chosen day has `aria-selected="true"` on its cell and its button is
56
+ described by "Selected" (`material.calendar.selected`, translated); today has
57
+ `aria-current="date"` on both. The days of the neighbouring months
58
+ (`showOutsideDays`) are named by their own dates, so a number alone matches no
59
+ day. "Selected" comes from the `Root` of the default `Calendar`: replacing
60
+ `components.Root` drops it. Pass `labels={{ labelDayButton }}` to override the
61
+ name on both:
62
+
63
+ ```ts
64
+ await page.getByRole('gridcell', { name: 'Thursday, October 1, 2026', exact: true }).getByRole('button').click();
65
+ ```
66
+
67
+ **Weekday row.** It follows the same language: "Su", "Mo"… in English, "lu",
68
+ "ma"… under a French `I18nProvider`, with the week starting on the locale's
69
+ first day (Monday in French). Each header is labelled by its full weekday
70
+ ("lundi"); react-day-picker hides the row from assistive tech
71
+ (`<thead aria-hidden>`), since every day's name already carries its weekday.
72
+ It reads at 4.5:1 or more on the popover in the light and the dark theme.
49
73
 
50
74
  **Storybook:** `Date & time/Calendar` · **Vue:** `Calendar` (Reka parts)
51
75
 
@@ -45,6 +45,8 @@ import { CircleAlert } from 'lucide-react';
45
45
  | `title` | `ReactNode` | — | bold first line |
46
46
  | `description` | `ReactNode` | — | muted body text |
47
47
 
48
+ The root carries `data-slot="alert"`, the title `data-slot="alert-title"` and the description `data-slot="alert-description"`, for tests and selectors.
49
+
48
50
  **Storybook:** `Feedback & status/Alert` · **Vue:** `Alert`
49
51
 
50
52
  ---
@@ -129,7 +131,9 @@ to `outlined`). Radix props (`open`, `defaultOpen`, `onOpenChange`) go on
129
131
  On open, `AlertDialogContent` focuses `AlertDialogCancel`, the least
130
132
  destructive action (its first control when there is no Cancel, the content
131
133
  when there is none); an `onOpenAutoFocus` that calls `event.preventDefault()`
132
- keeps focus where it was.
134
+ keeps focus off the Cancel, on the content: the page behind an alert dialog is
135
+ `inert` while it is open
136
+ ([Overlays & menus](overlays-and-menus.md)).
133
137
  It hands focus back on close as `DialogContent` does: to
134
138
  the element that had it when it opened (the button of yours that opened a
135
139
  controlled `AlertDialog`, too), unless focus already went elsewhere or your
@@ -219,6 +223,13 @@ const schema = z.object({ reason: z.string().min(3) });
219
223
  | `iconProps` | `IconButtonProps` | — | the badge's `variant`, `color`, `className` |
220
224
  | `confirmText` / `cancelText` | `ReactNode` | "Confirm" / "Cancel", translated (`material.confirm-dialog.confirm` / `.cancel`) | button text |
221
225
 
226
+ **Several steps, one dialog.** Confirm closes it; to show the next step, change
227
+ its `title` (and `description`) and set `open` back to `true`, at once (from
228
+ `onConfirm` or `onOpenChange`) or after an `await`. No `key` per step is needed:
229
+ the reopened overlay stays under the content, even while the previous one is
230
+ still fading out, so the dialog's buttons take the click. The same holds for
231
+ `ConfirmDialog`, `Dialog`, `AlertDialog` and `Sheet`.
232
+
222
233
  ![CustomAlertDialog](../previews/feedback-status-customalertdialog.webp)
223
234
 
224
235
  `CustomAlertDialog` is the alert-dialog twin of [CustomDialog](#customdialog):
@@ -415,15 +426,81 @@ toasts.dismiss(id);
415
426
  | --- | --- | --- | --- |
416
427
  | `title` / `description` | `ReactNode` | preset title for `success`, `error`, `loading` | toast text |
417
428
  | `icon` | `ReactNode` (`toast`) · icon name (`toasts.custom`) | preset icon | leading icon |
418
- | `action` | `ToastAction`: `{ label, onClick? }` or `(dismiss) => ReactNode` | a translated “Close” for `success`, `info`, `error` | trailing button: `onClick`, then the toast closes; or what the function renders, given `dismiss` |
419
- | `options` | Sonner `ExternalToast` | `duration: 5000` for presets, `Infinity` for `loading` | passed to Sonner (`id`, `duration`, `position`…) |
429
+ | `action` | `ToastAction`: `{ label, onClick? }` or `(dismiss) => ReactNode` | a translated “Close” for `success`, `info`, `error`; a preset keeps the caller's own | trailing button: `onClick`, then the toast closes; or what the function renders, given `dismiss`. Brings the X button and a persistent toast, see below |
430
+ | `dismissLabel` | `string` | the translated “Close” when an `action` is given | accessible name of the X icon button beside the action; `''` hides it |
431
+ | `role` | `'status' \| 'alert'` | none (`toasts.error`: `'alert'`) | live-region role. Toasts are already announced politely by the `Toaster`'s region; `alert` interrupts, and may be read twice by some screen readers |
432
+ | `options` | Sonner `ExternalToast` | `duration: 5000` for presets, `Infinity` for `loading` and for any toast with your own `action` (a preset's fallback "Close" still times out at 5000) unless a `duration` is passed | passed to Sonner (`id`, `duration`, `position`…) |
420
433
 
421
434
  The toast id defaults to the title, so two toasts with the same title
422
- replace each other; pass `options: { id }` to show both. `Toaster` is Sonner's
423
- and takes its props (`position`, `expand`, `richColors`…). `Toast` is the
424
- component each toast renders, for use with `sonnerToast.custom`.
435
+ replace each other; pass `options: { id }` to show both. `Toaster` wraps
436
+ Sonner's and takes its props (`position`, `expand`, `richColors`…), all
437
+ forwarded; mount the one from `@nxgt/material/components`, not Sonner's own,
438
+ or neither phone change below applies. `Toast` is the component each toast
439
+ renders, for use with `sonnerToast.custom`.
440
+
441
+ **Types:** `ToastAction`, `ToastProps`, `ToastMethodProps`, `ToasterProps`.
442
+
443
+ **On a phone** (under 600px wide, where Sonner lays toasts out across the
444
+ screen), a `Toaster` with no `position` shows its toasts at the top
445
+ (`top-center`): across the bottom they covered what a form keeps there, its
446
+ submit button first. A `position` you pass applies on every screen. The server
447
+ and the hydrating client render the desktop default (`bottom-right`), and a
448
+ phone moves the toasts to the top once hydrated. The phone offset is Sonner's
449
+ `mobileOffset` (16px by default); raise its `top` when a sticky header must
450
+ stay visible: `<Toaster mobileOffset={{ top: 72 }} />`.
451
+
452
+ ```tsx
453
+ <Toaster /> // bottom-right on a desktop, top-center on a phone
454
+ <Toaster position="bottom-center" /> // bottom-center everywhere
455
+ ```
456
+
457
+ **An action keeps the toast open.** `toast()` itself does it, so a preset and a
458
+ direct call behave alike: any toast with an `action` is persistent by default
459
+ (`options.duration: Infinity`, WCAG 2.2.1: a timed toast can vanish before a
460
+ keyboard or screen-reader user reaches its button) unless you pass
461
+ `options.duration`, and shows a small close (X) icon button beside the action,
462
+ named by `material.toast.buttons.close` ("Close" / "Fermer") unless you pass
463
+ your own `dismissLabel` (`''` hides it). The presets keep the `action` you pass
464
+ (`toasts.info({ description: 'A new version is available', action: { label:
465
+ 'Reload', onClick } })`); without an `action`, the preset's text "Close" button
466
+ is unchanged, on a timed toast. Sonner pauses its timer while a mouse hovers a
467
+ toast, while a finger is down on it and while the page is hidden, but not on
468
+ keyboard focus, which is why the default is persistent. A tap does not hover:
469
+ the mouse a browser emulates after a tap (which never leaves until the next tap
470
+ elsewhere) is ignored, so a tapped toast still goes after its duration. On a
471
+ touch screen a tap no longer fans out a stack of two or more toasts either: pass
472
+ `expand` to keep the stack open. Sonner does not close a toast on Escape (it
473
+ only collapses the stack); this `Toast` does when focus is inside it, with or
474
+ without an action. The close button is reached with Tab. **Focus after a
475
+ dismiss:** the dismissed toast leaves the page with the focused control in it,
476
+ so focus falls back to `document.body`; move it yourself (`onClick`, or after
477
+ `toasts.dismiss()`) when the flow needs it elsewhere.
478
+
479
+ **Announced to screen readers:** `Toaster` renders a polite live region
480
+ (`<section aria-live="polite">`) that is in the page before the first toast,
481
+ so every toast inserted into it is read out. A toast therefore has no `role`
482
+ of its own: one on each toast would nest a second live region. `toasts.error`
483
+ is `role="alert"` (assertive), so a failure interrupts; pass `role` to
484
+ `toast()` or to a preset to choose another (a caller's wins). Mount `Toaster`
485
+ once, early, and not conditionally after the first toast. A `Toast` rendered
486
+ outside a `Toaster` is not in a live region: give it its own.
487
+
488
+ **In tests:** find a toast by its text within the region, and an error toast
489
+ by role. A `status` or `alert` takes no name from its content, so read the
490
+ message with `toHaveTextContent`.
491
+
492
+ ```ts
493
+ const alert = await screen.findByRole('alert'); // toasts.error
494
+ expect(alert).toHaveTextContent('Save failed');
495
+ const saved = await screen.findByText('Item saved'); // toasts.success / info
496
+ expect(saved.closest('section[aria-live="polite"]')).not.toBeNull();
497
+ ```
498
+
499
+ Wait with `findBy*` or `waitFor`, never a fixed delay. A toast stays in the
500
+ DOM until it is dismissed or times out (a toast with an action never times
501
+ out), so queries in one test file can see the previous
502
+ test's toasts: call `toasts.dismiss()` first.
425
503
 
426
- **Types:** `ToastAction`, `ToastProps`, `ToastMethodProps`.
427
504
  **Storybook:** `Feedback & status/Toast` · **Vue:** `Toaster`, `toast`, `toasts`, `Toast` (`ToastAction` type)
428
505
 
429
506
  ---
@@ -475,7 +552,7 @@ import { HydrateLoading } from '@nxgt/material/components';
475
552
  | --- | --- | --- | --- |
476
553
  | `loadingIcon` | `ReactNode` | `<Spinner className="size-8" />` | replaces the spinner (`lodadingIcon`, the old misspelling, is a deprecated alias) |
477
554
  | `children` | `ReactNode` | — | shown above the spinner |
478
- | `className` | `string` | — | the root fills the viewport (`size-lv`); override to box it |
555
+ | `className` | `string` | — | the root fills the viewport (`w-lvw h-lvh`); override to box it |
479
556
 
480
557
  **Storybook:** `Feedback & status/HydrateLoading` · **Vue:** `HydrateLoading`
481
558
 
@@ -348,7 +348,7 @@ import { SearchField } from '@nxgt/material/components';
348
348
 
349
349
  | Prop | Type | Default | Effect |
350
350
  | --- | --- | --- | --- |
351
- | `onValueChange` | `(value: string) => void` | — | the text, debounced when `debounceMs` is set |
351
+ | `onValueChange` | `(value: string) => void` | — | the text, debounced when `debounceMs` is set; the latest `onValueChange` is called when the delay ends, so an inline arrow is safe |
352
352
  | `debounceMs` | `number` | `0` | delay before `onValueChange`; `0` calls it on every key |
353
353
  | `loading` | `boolean` | `false` | spinner in place of the clear button |
354
354
  | `onChange` | native handler | — | still fires on every key, undebounced |
@@ -410,8 +410,8 @@ export function Code() {
410
410
  | `length` | `number` | `6` | number of slots, split in two groups |
411
411
  | `value` / `onChange` | `string` / `(value: string) => void` | — | from `input-otp` |
412
412
  | `onComplete` | `(value: string) => void` | — | once every slot is filled |
413
- | `error` | `boolean` | — | error colour on the slots |
414
- | `id` | `string` | from `useId()` | the input's id, which the label points to; the generated one is the same on the server and the client |
413
+ | `error` | `boolean` | — | error colour on the slots (`data-invalid` on each); `aria-invalid` goes on the input only, the one element a screen reader reads |
414
+ | `id` | `string` | from `useId()` | the input's id, which the label points to; the generated one is the same on the server and the client, and differs per field — `OtpFormField` too (not the field `name`), so two code forms on one page each keep their name |
415
415
 
416
416
  `InputOTP`, `InputOTPGroup`, `InputOTPSlot` and `InputOTPSeparator` are the
417
417
  parts `OtpField` is built from (`input-otp`), for a layout of your own — no
@@ -444,10 +444,19 @@ export function Pin() {
444
444
  `index` it shows; `InputOTPSeparator` is a `separator` with a dash, or your
445
445
  children.
446
446
 
447
+ **On a phone.** The slots are 44 px squares that shrink with a narrow
448
+ field, kept square: six fit a 320 px-wide card, each above the 24 px
449
+ target of WCAG 2.5.8, and the gaps between them narrow under the `sm`
450
+ breakpoint. A slot you build yourself with `InputOTPSlot` does the same.
451
+
452
+ **In a test**, find the code by its input — `getByLabelText('Enter OTP')` —
453
+ and an error by `toHaveAttribute('aria-invalid', 'true')` on it; a slot
454
+ carries `data-invalid`, not `aria-invalid`.
455
+
447
456
  **Emits:** `string` (the digits typed so far).
448
457
  **Form wrapper:** `OtpFormField` — `control` and `name`, see
449
458
  [forms](../guides/forms.md#the-pair) and [what each field emits](../guides/forms.md#what-each-field-emits).
450
- **Storybook:** `Form fields/OtpField` · **Vue:** `OtpField`, `OtpFormField`, `InputOTP` (same parts)
459
+ **Storybook:** `Form fields/OtpField`, `Form fields/OtpField/Phone` · **Vue:** `OtpField`, `OtpFormField`, `InputOTP` (same parts)
451
460
 
452
461
  ---
453
462
 
@@ -693,7 +702,7 @@ switch, Email me when an order ships" and `getByRole('switch', { name:
693
702
  wins. The `helperText` (the validation message, under `SwitchFormField`)
694
703
  describes the switch too, and an `aria-describedby` you pass is kept beside
695
704
  these. With `required`, the switch has `aria-required` and the label's asterisk
696
- is hidden from screen readers, so the name stays "Terms", not "Terms*".
705
+ is drawn by CSS, so the name and the label text stay "Terms", not "Terms*".
697
706
  Without an `id` one is generated, under `SwitchFormField` too (it used to be
698
707
  the field's `name`, which two forms on one page shared). The row is tinted
699
708
  while the switch is on, from `checked` or, uncontrolled, from `defaultChecked` and each toggle, and carries
@@ -689,6 +689,13 @@ Playback uses the browser's `<video>` / `<audio>`: a format the browser cannot
689
689
  decode (H.264 MP4 in a Chromium build without proprietary codecs) shows the
690
690
  player's error state with a Retry button. The preview is the audio story.
691
691
 
692
+ The speed and captions menus are radio groups: each entry is a
693
+ `menuitemradio` and the chosen one `aria-checked="true"` (captions: "Off" or
694
+ one track's `label`). `c` toggles the captions; turned on before a language
695
+ was chosen, they show the current track's first caption track, which the menu
696
+ then checks. In a test, pick one with
697
+ `getByRole('menuitemradio', { name: 'English' })`.
698
+
692
699
  **Storybook:** `Media & files/StxPlayer` · **Vue:** `StxPlayer` (same parts)
693
700
 
694
701
  ---