@helixui/library 3.3.1-next.118 → 3.4.0-next.121

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 (249) hide show
  1. package/custom-elements.json +422 -322
  2. package/dist/components/hx-alert/hx-alert.d.ts +11 -0
  3. package/dist/components/hx-alert/hx-alert.d.ts.map +1 -1
  4. package/dist/components/hx-alert/hx-alert.styles.d.ts.map +1 -1
  5. package/dist/components/hx-alert/index.js +1 -1
  6. package/dist/components/hx-badge/hx-badge.styles.d.ts.map +1 -1
  7. package/dist/components/hx-badge/index.js +1 -1
  8. package/dist/components/hx-banner/hx-banner.d.ts +9 -1
  9. package/dist/components/hx-banner/hx-banner.d.ts.map +1 -1
  10. package/dist/components/hx-banner/index.js +1 -1
  11. package/dist/components/hx-button/hx-button.d.ts +11 -1
  12. package/dist/components/hx-button/hx-button.d.ts.map +1 -1
  13. package/dist/components/hx-button/index.js +1 -1
  14. package/dist/components/hx-button-group/hx-button-group.d.ts +13 -0
  15. package/dist/components/hx-button-group/hx-button-group.d.ts.map +1 -1
  16. package/dist/components/hx-button-group/index.js +1 -1
  17. package/dist/components/hx-checkbox/hx-checkbox.d.ts.map +1 -1
  18. package/dist/components/hx-checkbox/index.js +1 -1
  19. package/dist/components/hx-checkbox-group/index.js +1 -1
  20. package/dist/components/hx-color-picker/index.js +1 -1
  21. package/dist/components/hx-combobox/index.js +1 -1
  22. package/dist/components/hx-data-table/hx-data-table.d.ts.map +1 -1
  23. package/dist/components/hx-data-table/index.js +1 -1
  24. package/dist/components/hx-date-picker/index.js +1 -1
  25. package/dist/components/hx-dialog/index.js +1 -1
  26. package/dist/components/hx-drawer/hx-drawer.d.ts +201 -0
  27. package/dist/components/hx-drawer/hx-drawer.d.ts.map +1 -1
  28. package/dist/components/hx-drawer/hx-drawer.styles.d.ts.map +1 -1
  29. package/dist/components/hx-drawer/index.js +1 -1
  30. package/dist/components/hx-dropdown/hx-dropdown.d.ts +96 -8
  31. package/dist/components/hx-dropdown/hx-dropdown.d.ts.map +1 -1
  32. package/dist/components/hx-dropdown/index.js +1 -1
  33. package/dist/components/hx-icon-button/hx-icon-button.d.ts +16 -3
  34. package/dist/components/hx-icon-button/hx-icon-button.d.ts.map +1 -1
  35. package/dist/components/hx-icon-button/hx-icon-button.styles.d.ts.map +1 -1
  36. package/dist/components/hx-icon-button/index.js +1 -1
  37. package/dist/components/hx-link/hx-link.d.ts +10 -1
  38. package/dist/components/hx-link/hx-link.d.ts.map +1 -1
  39. package/dist/components/hx-link/index.js +1 -1
  40. package/dist/components/hx-list/hx-list-item.d.ts +27 -1
  41. package/dist/components/hx-list/hx-list-item.d.ts.map +1 -1
  42. package/dist/components/hx-list/hx-list.d.ts +28 -0
  43. package/dist/components/hx-list/hx-list.d.ts.map +1 -1
  44. package/dist/components/hx-list/index.js +1 -1
  45. package/dist/components/hx-menu/hx-menu-divider.d.ts +10 -0
  46. package/dist/components/hx-menu/hx-menu-divider.d.ts.map +1 -1
  47. package/dist/components/hx-menu/hx-menu-item.d.ts +99 -2
  48. package/dist/components/hx-menu/hx-menu-item.d.ts.map +1 -1
  49. package/dist/components/hx-menu/hx-menu-item.styles.d.ts.map +1 -1
  50. package/dist/components/hx-menu/hx-menu.d.ts +117 -2
  51. package/dist/components/hx-menu/hx-menu.d.ts.map +1 -1
  52. package/dist/components/hx-menu/index.js +1 -1
  53. package/dist/components/hx-meter/hx-meter.d.ts +39 -0
  54. package/dist/components/hx-meter/hx-meter.d.ts.map +1 -1
  55. package/dist/components/hx-meter/hx-meter.styles.d.ts.map +1 -1
  56. package/dist/components/hx-meter/index.js +1 -1
  57. package/dist/components/hx-overflow-menu/hx-overflow-menu.d.ts +132 -1
  58. package/dist/components/hx-overflow-menu/hx-overflow-menu.d.ts.map +1 -1
  59. package/dist/components/hx-overflow-menu/index.js +1 -1
  60. package/dist/components/hx-phi-field/hx-phi-field.d.ts +0 -1
  61. package/dist/components/hx-phi-field/hx-phi-field.d.ts.map +1 -1
  62. package/dist/components/hx-popover/index.js +1 -1
  63. package/dist/components/hx-progress-bar/hx-progress-bar.d.ts +33 -0
  64. package/dist/components/hx-progress-bar/hx-progress-bar.d.ts.map +1 -1
  65. package/dist/components/hx-progress-bar/index.js +1 -1
  66. package/dist/components/hx-radio-group/index.js +1 -1
  67. package/dist/components/hx-select/hx-select.d.ts +1 -0
  68. package/dist/components/hx-select/hx-select.d.ts.map +1 -1
  69. package/dist/components/hx-select/index.js +1 -1
  70. package/dist/components/hx-spinner/hx-spinner.d.ts +14 -0
  71. package/dist/components/hx-spinner/hx-spinner.d.ts.map +1 -1
  72. package/dist/components/hx-spinner/index.js +1 -1
  73. package/dist/components/hx-split-button/hx-split-button.d.ts +94 -7
  74. package/dist/components/hx-split-button/hx-split-button.d.ts.map +1 -1
  75. package/dist/components/hx-split-button/index.js +1 -1
  76. package/dist/components/hx-stat/hx-stat.d.ts +28 -0
  77. package/dist/components/hx-stat/hx-stat.d.ts.map +1 -1
  78. package/dist/components/hx-stat/index.js +1 -1
  79. package/dist/components/hx-switch/index.js +1 -1
  80. package/dist/components/hx-table/hx-td.d.ts +30 -3
  81. package/dist/components/hx-table/hx-td.d.ts.map +1 -1
  82. package/dist/components/hx-table/hx-th.d.ts +39 -3
  83. package/dist/components/hx-table/hx-th.d.ts.map +1 -1
  84. package/dist/components/hx-table/hx-tr.d.ts +26 -0
  85. package/dist/components/hx-table/hx-tr.d.ts.map +1 -1
  86. package/dist/components/hx-table/index.js +1 -1
  87. package/dist/components/hx-tabs/hx-tab-panel.d.ts +34 -0
  88. package/dist/components/hx-tabs/hx-tab-panel.d.ts.map +1 -1
  89. package/dist/components/hx-tabs/hx-tab.d.ts +45 -2
  90. package/dist/components/hx-tabs/hx-tab.d.ts.map +1 -1
  91. package/dist/components/hx-tabs/hx-tab.styles.d.ts.map +1 -1
  92. package/dist/components/hx-tabs/hx-tabs.d.ts +32 -2
  93. package/dist/components/hx-tabs/hx-tabs.d.ts.map +1 -1
  94. package/dist/components/hx-tabs/index.js +1 -1
  95. package/dist/components/hx-tag/hx-tag.styles.d.ts.map +1 -1
  96. package/dist/components/hx-tag/index.js +1 -1
  97. package/dist/components/hx-theme/hx-theme.d.ts +10 -5
  98. package/dist/components/hx-theme/hx-theme.d.ts.map +1 -1
  99. package/dist/components/hx-time-picker/hx-time-picker.d.ts +210 -2
  100. package/dist/components/hx-time-picker/hx-time-picker.d.ts.map +1 -1
  101. package/dist/components/hx-time-picker/hx-time-picker.styles.d.ts.map +1 -1
  102. package/dist/components/hx-time-picker/index.js +1 -1
  103. package/dist/components/hx-toast/hx-toast-stack.d.ts +14 -0
  104. package/dist/components/hx-toast/hx-toast-stack.d.ts.map +1 -1
  105. package/dist/components/hx-toast/hx-toast.d.ts +22 -3
  106. package/dist/components/hx-toast/hx-toast.d.ts.map +1 -1
  107. package/dist/components/hx-toast/index.js +1 -1
  108. package/dist/components/hx-toast/toast-factory.d.ts.map +1 -1
  109. package/dist/components/hx-toggle-button/index.js +1 -1
  110. package/dist/components/hx-tree-view/hx-tree-item.d.ts +117 -12
  111. package/dist/components/hx-tree-view/hx-tree-item.d.ts.map +1 -1
  112. package/dist/components/hx-tree-view/hx-tree-view.d.ts +87 -7
  113. package/dist/components/hx-tree-view/hx-tree-view.d.ts.map +1 -1
  114. package/dist/components/hx-tree-view/index.js +1 -1
  115. package/dist/css/helix-all.css +123 -0
  116. package/dist/css/helix-core.css +81 -0
  117. package/dist/css/helix-feedback.css +14 -0
  118. package/dist/css/helix-forms.css +11 -0
  119. package/dist/css/helix-overlay.css +17 -0
  120. package/dist/css/hx-alert.css +9 -0
  121. package/dist/css/hx-badge.css +28 -0
  122. package/dist/css/hx-drawer.css +17 -0
  123. package/dist/css/hx-icon-button.css +30 -0
  124. package/dist/css/hx-meter.css +5 -0
  125. package/dist/css/hx-tag.css +23 -0
  126. package/dist/css/hx-time-picker.css +11 -0
  127. package/dist/css/index.css +1 -1
  128. package/dist/css/manifest.json +3 -1
  129. package/dist/index.js +35 -35
  130. package/dist/shared/aria-idref-CxvyzfQS.js +126 -0
  131. package/dist/shared/aria-idref-CxvyzfQS.js.map +1 -0
  132. package/dist/shared/{hx-alert-CLn7CstP.js → hx-alert-Bto8-TIi.js} +55 -37
  133. package/dist/shared/hx-alert-Bto8-TIi.js.map +1 -0
  134. package/dist/shared/{hx-badge-CQXgOXJM.js → hx-badge-JlFtAdxS.js} +37 -9
  135. package/dist/shared/hx-badge-JlFtAdxS.js.map +1 -0
  136. package/dist/shared/{hx-banner-D3DzpfcP.js → hx-banner-fpRnciIO.js} +13 -5
  137. package/dist/shared/hx-banner-fpRnciIO.js.map +1 -0
  138. package/dist/shared/{hx-button-DPY6SPVT.js → hx-button-BOwAEcF1.js} +108 -85
  139. package/dist/shared/{hx-button-DPY6SPVT.js.map → hx-button-BOwAEcF1.js.map} +1 -1
  140. package/dist/shared/{hx-button-group-BI-QBqmO.js → hx-button-group-DcHP5MBv.js} +15 -16
  141. package/dist/shared/{hx-button-group-BI-QBqmO.js.map → hx-button-group-DcHP5MBv.js.map} +1 -1
  142. package/dist/shared/{hx-checkbox-BdgoUeWi.js → hx-checkbox-C48KYKFq.js} +87 -87
  143. package/dist/shared/hx-checkbox-C48KYKFq.js.map +1 -0
  144. package/dist/shared/{hx-checkbox-group-LWezHrvS.js → hx-checkbox-group-BJIAX3zU.js} +2 -2
  145. package/dist/shared/{hx-checkbox-group-LWezHrvS.js.map → hx-checkbox-group-BJIAX3zU.js.map} +1 -1
  146. package/dist/shared/{hx-color-picker-DVhZl88b.js → hx-color-picker-Dk4cBwYQ.js} +2 -2
  147. package/dist/shared/{hx-color-picker-DVhZl88b.js.map → hx-color-picker-Dk4cBwYQ.js.map} +1 -1
  148. package/dist/shared/{hx-combobox-DvlezcDV.js → hx-combobox-BTLO9qiK.js} +2 -2
  149. package/dist/shared/{hx-combobox-DvlezcDV.js.map → hx-combobox-BTLO9qiK.js.map} +1 -1
  150. package/dist/shared/{hx-data-table-CLqVqdxr.js → hx-data-table-Ct3gQ6ya.js} +3 -2
  151. package/dist/shared/{hx-data-table-CLqVqdxr.js.map → hx-data-table-Ct3gQ6ya.js.map} +1 -1
  152. package/dist/shared/{hx-date-picker-N-0aG5XL.js → hx-date-picker-CiR7FVnR.js} +2 -2
  153. package/dist/shared/{hx-date-picker-N-0aG5XL.js.map → hx-date-picker-CiR7FVnR.js.map} +1 -1
  154. package/dist/shared/{hx-dialog-DzB7VytW.js → hx-dialog-AOZpHSuF.js} +2 -2
  155. package/dist/shared/{hx-dialog-DzB7VytW.js.map → hx-dialog-AOZpHSuF.js.map} +1 -1
  156. package/dist/shared/{hx-drawer-Y1Ui2IWJ.js → hx-drawer-DH6CdAN1.js} +300 -98
  157. package/dist/shared/hx-drawer-DH6CdAN1.js.map +1 -0
  158. package/dist/shared/hx-dropdown-DiLd40Lm.js +401 -0
  159. package/dist/shared/hx-dropdown-DiLd40Lm.js.map +1 -0
  160. package/dist/shared/{hx-icon-button-CGNdQSFM.js → hx-icon-button-a6OpeQz5.js} +149 -68
  161. package/dist/shared/hx-icon-button-a6OpeQz5.js.map +1 -0
  162. package/dist/shared/{hx-link-C-O6vq0Q.js → hx-link-CMnZRUtQ.js} +55 -43
  163. package/dist/shared/hx-link-CMnZRUtQ.js.map +1 -0
  164. package/dist/shared/{hx-list-MyEhh8c7.js → hx-list-De66EtAP.js} +163 -107
  165. package/dist/shared/hx-list-De66EtAP.js.map +1 -0
  166. package/dist/shared/hx-menu-divider-BjiRIWKq.js +797 -0
  167. package/dist/shared/hx-menu-divider-BjiRIWKq.js.map +1 -0
  168. package/dist/shared/{hx-meter-BPscsw5t.js → hx-meter-BJdh6nrF.js} +105 -64
  169. package/dist/shared/hx-meter-BJdh6nrF.js.map +1 -0
  170. package/dist/shared/hx-overflow-menu-BQ4fiMYu.js +492 -0
  171. package/dist/shared/hx-overflow-menu-BQ4fiMYu.js.map +1 -0
  172. package/dist/shared/hx-phi-field-C19oxlrr.js.map +1 -1
  173. package/dist/shared/{hx-popover-CHxWY_cd.js → hx-popover-B9W8-tC0.js} +2 -2
  174. package/dist/shared/{hx-popover-CHxWY_cd.js.map → hx-popover-B9W8-tC0.js.map} +1 -1
  175. package/dist/shared/hx-progress-bar-C8nDMdYa.js +290 -0
  176. package/dist/shared/hx-progress-bar-C8nDMdYa.js.map +1 -0
  177. package/dist/shared/{hx-radio-CeGzARNk.js → hx-radio-Z1lV1zTO.js} +2 -2
  178. package/dist/shared/{hx-radio-CeGzARNk.js.map → hx-radio-Z1lV1zTO.js.map} +1 -1
  179. package/dist/shared/{hx-select-DrcS-YRJ.js → hx-select-D18CnJ0e.js} +2 -2
  180. package/dist/shared/hx-select-D18CnJ0e.js.map +1 -0
  181. package/dist/shared/{hx-spinner-DL5AYr16.js → hx-spinner-BB0h2hKZ.js} +62 -34
  182. package/dist/shared/hx-spinner-BB0h2hKZ.js.map +1 -0
  183. package/dist/shared/{hx-split-button-Djnc5Aeg.js → hx-split-button-BoABoEm5.js} +153 -82
  184. package/dist/shared/hx-split-button-BoABoEm5.js.map +1 -0
  185. package/dist/shared/{hx-stat-WOcNV1Ry.js → hx-stat-Dtf9lz-O.js} +77 -47
  186. package/dist/shared/hx-stat-Dtf9lz-O.js.map +1 -0
  187. package/dist/shared/{hx-switch-BX_8uNUs.js → hx-switch-B6kr-EwE.js} +2 -2
  188. package/dist/shared/{hx-switch-BX_8uNUs.js.map → hx-switch-B6kr-EwE.js.map} +1 -1
  189. package/dist/shared/{hx-tab-panel-DspCrKqo.js → hx-tab-panel-BQtBXKLD.js} +255 -131
  190. package/dist/shared/hx-tab-panel-BQtBXKLD.js.map +1 -0
  191. package/dist/shared/{hx-tag-CNSmdyaK.js → hx-tag-C5aCUpVi.js} +63 -40
  192. package/dist/shared/hx-tag-C5aCUpVi.js.map +1 -0
  193. package/dist/shared/{hx-td-DnnEMIuA.js → hx-td-BGkFOJEK.js} +267 -123
  194. package/dist/shared/hx-td-BGkFOJEK.js.map +1 -0
  195. package/dist/shared/hx-theme-BsefFWTO.js.map +1 -1
  196. package/dist/shared/hx-time-picker-iwCD7rzW.js +1038 -0
  197. package/dist/shared/hx-time-picker-iwCD7rzW.js.map +1 -0
  198. package/dist/shared/{hx-toggle-button-Dcz9IlUm.js → hx-toggle-button-BQ81EDkl.js} +2 -2
  199. package/dist/shared/{hx-toggle-button-Dcz9IlUm.js.map → hx-toggle-button-BQ81EDkl.js.map} +1 -1
  200. package/dist/shared/hx-tree-item-CHrUhuZL.js +925 -0
  201. package/dist/shared/hx-tree-item-CHrUhuZL.js.map +1 -0
  202. package/dist/shared/menu-roving-DmMnzJhn.js +14 -0
  203. package/dist/shared/menu-roving-DmMnzJhn.js.map +1 -0
  204. package/dist/shared/menu-tree-BNM0SYYq.js +42 -0
  205. package/dist/shared/menu-tree-BNM0SYYq.js.map +1 -0
  206. package/dist/shared/{toast-factory-YSznocIV.js → toast-factory-CL2BzdSB.js} +128 -77
  207. package/dist/shared/toast-factory-CL2BzdSB.js.map +1 -0
  208. package/dist/utils/aria-idref.d.ts.map +1 -1
  209. package/dist/utils/menu-label.d.ts +18 -0
  210. package/dist/utils/menu-label.d.ts.map +1 -0
  211. package/dist/utils/menu-roving.d.ts +28 -0
  212. package/dist/utils/menu-roving.d.ts.map +1 -0
  213. package/dist/utils/menu-tree.d.ts +41 -0
  214. package/dist/utils/menu-tree.d.ts.map +1 -0
  215. package/dist/utils/tree-walk.d.ts +53 -0
  216. package/dist/utils/tree-walk.d.ts.map +1 -0
  217. package/figma-inventory.json +69 -20
  218. package/package.json +2 -2
  219. package/dist/shared/aria-idref-Q0yiSR3p.js +0 -104
  220. package/dist/shared/aria-idref-Q0yiSR3p.js.map +0 -1
  221. package/dist/shared/hx-alert-CLn7CstP.js.map +0 -1
  222. package/dist/shared/hx-badge-CQXgOXJM.js.map +0 -1
  223. package/dist/shared/hx-banner-D3DzpfcP.js.map +0 -1
  224. package/dist/shared/hx-checkbox-BdgoUeWi.js.map +0 -1
  225. package/dist/shared/hx-drawer-Y1Ui2IWJ.js.map +0 -1
  226. package/dist/shared/hx-dropdown-DJWlF94E.js +0 -316
  227. package/dist/shared/hx-dropdown-DJWlF94E.js.map +0 -1
  228. package/dist/shared/hx-icon-button-CGNdQSFM.js.map +0 -1
  229. package/dist/shared/hx-link-C-O6vq0Q.js.map +0 -1
  230. package/dist/shared/hx-list-MyEhh8c7.js.map +0 -1
  231. package/dist/shared/hx-menu-divider-C2omnPtj.js +0 -558
  232. package/dist/shared/hx-menu-divider-C2omnPtj.js.map +0 -1
  233. package/dist/shared/hx-meter-BPscsw5t.js.map +0 -1
  234. package/dist/shared/hx-overflow-menu-DCLsdIBy.js +0 -374
  235. package/dist/shared/hx-overflow-menu-DCLsdIBy.js.map +0 -1
  236. package/dist/shared/hx-progress-bar-Bn3JEPUf.js +0 -258
  237. package/dist/shared/hx-progress-bar-Bn3JEPUf.js.map +0 -1
  238. package/dist/shared/hx-select-DrcS-YRJ.js.map +0 -1
  239. package/dist/shared/hx-spinner-DL5AYr16.js.map +0 -1
  240. package/dist/shared/hx-split-button-Djnc5Aeg.js.map +0 -1
  241. package/dist/shared/hx-stat-WOcNV1Ry.js.map +0 -1
  242. package/dist/shared/hx-tab-panel-DspCrKqo.js.map +0 -1
  243. package/dist/shared/hx-tag-CNSmdyaK.js.map +0 -1
  244. package/dist/shared/hx-td-DnnEMIuA.js.map +0 -1
  245. package/dist/shared/hx-time-picker-BoEIZwzv.js +0 -688
  246. package/dist/shared/hx-time-picker-BoEIZwzv.js.map +0 -1
  247. package/dist/shared/hx-tree-item-C2CiWuDE.js +0 -703
  248. package/dist/shared/hx-tree-item-C2CiWuDE.js.map +0 -1
  249. package/dist/shared/toast-factory-YSznocIV.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hx-dropdown-DiLd40Lm.js","sources":["../../src/components/hx-dropdown/hx-dropdown.styles.ts","../../src/components/hx-dropdown/hx-dropdown.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const helixDropdownStyles = css`\n :host {\n display: inline-block;\n position: relative;\n }\n\n :host([disabled]) {\n pointer-events: none;\n opacity: var(--hx-opacity-disabled, 0.5);\n }\n\n .trigger-wrapper {\n display: inline-block;\n }\n\n [part='panel'] {\n position: fixed;\n z-index: var(--hx-dropdown-panel-z-index, 1000);\n min-width: var(--hx-dropdown-panel-min-width, 160px);\n background: var(--hx-dropdown-panel-bg, var(--hx-color-surface-default, #ffffff));\n border: 1px solid var(--hx-dropdown-panel-border-color, var(--hx-color-border-default, #d6dbd5));\n border-radius: var(--hx-dropdown-panel-border-radius, var(--hx-border-radius-md, 0.375rem));\n box-shadow: var(\n --hx-dropdown-panel-shadow,\n 0 4px 16px var(--hx-overlay-black-12, rgba(0, 0, 0, 0.12))\n );\n visibility: hidden;\n opacity: 0;\n pointer-events: none;\n transition:\n opacity var(--hx-transition-fast, 150ms ease),\n visibility var(--hx-transition-fast, 150ms ease);\n outline: none;\n }\n\n [part='panel'].panel--visible {\n visibility: visible;\n opacity: 1;\n pointer-events: auto;\n }\n\n @media (prefers-reduced-motion: reduce) {\n [part='panel'] {\n transition: none;\n }\n }\n\n /* ─── High Contrast Mode (forced-colors) ─── */\n\n @media (forced-colors: active) {\n [part='panel'] {\n background-color: Canvas;\n border: 2px solid CanvasText;\n }\n }\n`;\n","import { html, nothing, type PropertyValues } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property, state, query } from 'lit/decorators.js';\nimport { devWarn } from '../../utils/dev-warn.js';\nimport { HelixElement } from '../../base/index.js';\nimport type { Placement as FloatingPlacement } from '@floating-ui/dom';\nimport { createIdCounter } from '../../base/index.js';\nimport { forcedColorsInteractive } from '../../styles/forced-colors.js';\nimport { helixDropdownStyles } from './hx-dropdown.styles.js';\nimport { flattenAccName } from '../../utils/aria-flatten.js';\nimport { getMenuItemTypeaheadLabel } from '../../utils/menu-label.js';\nimport { writeMenuItemRovingTabIndex } from '../../utils/menu-roving.js';\nimport { findClosestMenuAncestor } from '../../utils/menu-tree.js';\nimport {\n installAriaIdrefMirror,\n resolveIdrefTokens,\n type AriaIdrefMirrorHandle,\n} from '../../utils/aria-idref.js';\n\n// P2-03: Export so TypeScript consumers can import this type for prop typing.\nexport type DropdownPlacement =\n | 'top'\n | 'top-start'\n | 'top-end'\n | 'bottom'\n | 'bottom-start'\n | 'bottom-end'\n | 'start'\n | 'end';\n\nconst _nextDropdownId = createIdCounter('hx-dropdown');\n\n/**\n * A dropdown component — a button that opens a floating panel on click.\n *\n * ## Architecture Note: Host-Attribute Label Mirror (group-4 round-1)\n *\n * The announced surface is the inner `[part=\"panel\"]` element, which carries\n * `role=\"menu\"`. The host wraps a slotted trigger and the floating panel and\n * does NOT claim a role itself (apart from the round-35-style host\n * `aria-expanded` fallback used only when the trigger slot is empty).\n *\n * Because the panel lives in shadow DOM and `ElementInternals` IDL refs on\n * the host project semantics OUTWARD (host → AT) rather than INWARD\n * (host → shadow descendant), we use the **host-attribute mirror** pattern:\n * resolve consumer `aria-labelledby` IDREFs against the host's composed-tree\n * roots, text-flatten via `flattenAccName`, and write the result to the\n * panel's `aria-label`. Host `aria-label` outranks the `label` property in\n * the same precedence used by every host-canonical hx-* control.\n *\n * Naming precedence (W3C AccName 1.2 §4.3.1):\n * 1. Host `aria-labelledby` (resolved IDREFs, text-flattened)\n * 2. Host `aria-label`\n * 3. `label` property\n * 4. Hard-coded literal `\"Menu\"` (last-resort accessible name)\n *\n * **Group 4b → Group 5b boundary:** Group 4b added the host-attribute\n * label mirror **only** — additive on top of the existing dropdown\n * behaviour. Group 5b (this commit) adds the composite-navigation\n * portion that 4b explicitly deferred:\n * - **Roving tabindex** inside the panel (`_applyRovingTabIndex` +\n * `_rovingIndex`). Only the focused item carries `tabindex=0`.\n * - **First-character typeahead** with 500ms timeout (`_handleTypeahead`)\n * matching `hx-menu`, `hx-overflow-menu`, `hx-split-button`.\n * - Submenu auto-handling is delegated to slotted `hx-menu` /\n * `hx-menu-item` (whose `hx-item-submenu-open` / `hx-item-submenu-close`\n * events are auto-handled by the parent `hx-menu` after Group 5b).\n *\n * The panel's inner-div `role=\"menu\"` is intentionally NOT migrated to\n * the host: the host wraps a slotted consumer trigger AND the panel,\n * so it cannot canonically carry the menu role. Slotted `hx-menu-item`\n * children carry `role=\"menuitem\"` on their HOST after Group 5b's menu\n * migration, which fixes the cross-shadow walk concern from the\n * consumer's perspective.\n *\n * `aria-controls` is intentionally omitted on the trigger: the panel lives\n * in shadow DOM and IDREF values cannot be resolved across shadow\n * boundaries by assistive technology (axe-core flags this as a critical\n * violation if attempted). See `_setupTriggerAria` for the inline note.\n *\n * @summary Button that opens a floating menu panel on click.\n *\n * @tag hx-dropdown\n *\n * @slot trigger - The element that opens the dropdown (e.g. hx-button).\n * @slot - Default slot for dropdown panel content (e.g. menu items).\n *\n * @fires {CustomEvent<void>} hx-show - Dispatched when the dropdown is opened.\n * @fires {CustomEvent<void>} hx-hide - Dispatched when the dropdown is closed.\n * @fires {CustomEvent<{value: string | null; label: string}>} hx-select - Dispatched when a menu item is selected.\n *\n * @csspart trigger - The trigger wrapper element.\n * @csspart panel - The floating panel element.\n *\n * @cssprop [--hx-dropdown-panel-bg=var(--hx-color-neutral-0)] - Panel background color.\n * @cssprop [--hx-dropdown-panel-border-color=var(--hx-color-neutral-200)] - Panel border color.\n * @cssprop [--hx-dropdown-panel-border-radius=var(--hx-border-radius-md)] - Panel border radius.\n * @cssprop [--hx-dropdown-panel-shadow=0 4px 16px rgba(0,0,0,0.12)] - Panel box shadow.\n * @cssprop [--hx-dropdown-panel-z-index=1000] - Panel z-index.\n * @cssprop [--hx-dropdown-panel-min-width=160px] - Panel minimum width.\n *\n * @example\n * ```html\n * <hx-dropdown>\n * <button slot=\"trigger\">Open Menu</button>\n * <ul>\n * <li data-value=\"edit\">Edit</li>\n * <li data-value=\"delete\">Delete</li>\n * </ul>\n * </hx-dropdown>\n * ```\n * @cssprop [--hx-opacity-disabled] - Opacity.\n * @cssprop [--hx-color-neutral-0] - Color.\n * @cssprop [--hx-color-neutral-200] - Color.\n * @cssprop [--hx-border-radius-md] - CSS custom property.\n * @cssprop [--hx-overlay-black-12] - Overlay color.\n * @cssprop [--hx-transition-fast] - Transition timing.\n */\n@customElement('hx-dropdown')\nexport class HelixDropdown extends HelixElement {\n static override styles = [helixDropdownStyles, forcedColorsInteractive];\n\n // ─── Public Properties ───\n\n /**\n * Whether the dropdown panel is open.\n * @attr open\n */\n @property({ type: Boolean, reflect: true })\n open = false;\n\n /**\n * Preferred placement of the panel relative to the trigger.\n * @attr placement\n */\n @property({ type: String, reflect: true })\n placement:\n | 'top'\n | 'top-start'\n | 'top-end'\n | 'bottom'\n | 'bottom-start'\n | 'bottom-end'\n | 'start'\n | 'end' = 'bottom-start';\n\n /**\n * Accessible label for the dropdown menu panel. Override for i18n.\n * @attr label\n */\n @property() label = 'Menu';\n\n /**\n * Whether the dropdown is disabled. Prevents opening.\n * @attr disabled\n */\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n /**\n * Gap in pixels between the trigger and the panel.\n * @attr distance\n */\n @property({ type: Number })\n distance = 4;\n\n // ─── Internal State ───\n\n /**\n * Whether the dropdown panel is currently visible.\n * @internal\n */\n @state() private _panelVisible = false;\n\n /**\n * Index within the panel's focusable menu items of the item currently\n * holding the roving tabindex (and thus visual focus). −1 means the\n * panel has not been keyboard-focused yet.\n * @internal\n */\n private _rovingIndex = -1;\n\n /**\n * Accumulated character buffer for typeahead search within the panel's\n * menu items. Cleared after 500ms of inactivity.\n * @internal\n */\n private _typeaheadBuffer = '';\n\n /**\n * Timer handle that clears the typeahead buffer after a period of inactivity.\n * @internal\n */\n private _typeaheadTimer: ReturnType<typeof setTimeout> | null = null;\n\n /**\n * Resolved accessible name for the menu panel — the value written to the\n * inner `[part=\"panel\"]` `aria-label`. Recomputed on every sync per\n * AccName 1.2 §4.3.1 precedence: host `aria-labelledby` (flattened) >\n * host `aria-label` > `label` property > literal `\"Menu\"`.\n * @internal\n */\n @state() private _resolvedLabel = '';\n\n /**\n * Most recently observed consumer-supplied `aria-labelledby` token list on\n * the host. Refreshed every sync via `getAttribute()`.\n * @internal\n */\n private _consumerLabelledBy: string | null = null;\n\n /**\n * Handle for the shared host attribute / root id observer.\n * @internal\n */\n private _ariaMirror: AriaIdrefMirrorHandle | null = null;\n\n /**\n * Watches in-place text / visibility mutations on consumer light-DOM\n * elements resolved from the host's `aria-labelledby`.\n * @internal\n */\n private _externalRefsObserver: MutationObserver | null = null;\n\n /**\n * Guards against accumulating multiple document click listeners when open state\n * changes faster than the microtask queue can process removeEventListener calls.\n * @internal\n */\n private _documentListenerAttached = false;\n\n // P1-02: Unique panel ID for aria-controls.\n /**\n * Unique ID assigned to the floating panel element, referenced by `aria-controls` on the trigger.\n * @internal\n */\n private _panelId = `${_nextDropdownId()}-panel`;\n\n /**\n * Reference to the floating panel element inside the shadow DOM.\n * @internal\n */\n @query('[part=\"panel\"]') private _panel: HTMLElement | undefined;\n /**\n * Reference to the trigger wrapper element inside the shadow DOM.\n * @internal\n */\n @query('[part=\"trigger\"]') private _triggerWrapper: HTMLElement | undefined;\n\n // ─── Lifecycle ───\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener('keydown', this._handleKeydown);\n // Seed the host-attribute label mirror BEFORE first paint so the panel's\n // `aria-label` carries the resolved name on its very first render.\n this._syncResolvedLabel();\n this._ariaMirror = installAriaIdrefMirror(this, () => {\n this._syncResolvedLabel();\n });\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener('keydown', this._handleKeydown);\n if (this._documentListenerAttached) {\n document.removeEventListener('click', this._handleOutsideClick, { capture: true });\n this._documentListenerAttached = false;\n }\n if (this._typeaheadTimer !== null) {\n clearTimeout(this._typeaheadTimer);\n this._typeaheadTimer = null;\n }\n this._ariaMirror?.disconnect();\n this._ariaMirror = null;\n this._externalRefsObserver?.disconnect();\n this._externalRefsObserver = null;\n }\n\n // ─── Open/Close ───\n\n /** @internal */\n private async _show(): Promise<void> {\n if (this.open || this.disabled) return;\n this.open = true;\n this._panelVisible = true;\n // Add outside-click listener synchronously before any await so it is registered\n // by the time the test fires an outside click after a single await el.updateComplete.\n if (!this._documentListenerAttached) {\n document.addEventListener('click', this._handleOutsideClick, { capture: true });\n this._documentListenerAttached = true;\n }\n await this.updateComplete;\n // P0-01: Fix focus management — use slot.assignedElements() to traverse slotted (light DOM) content.\n // Focus is set after updateComplete (panel is rendered) but before _updatePosition so\n // it executes in the same microtask as the test's await-continuation.\n const panel = this._panel;\n if (panel) {\n // Group 5b: initialize roving tabindex on slotted menu items\n // before focusing the first one. Tab from outside lands on the\n // same item that has visual focus.\n const items = this._getFocusableMenuItems();\n if (items.length > 0) {\n this._rovingIndex = 0;\n this._applyRovingTabIndex(items);\n }\n const firstFocusable = this._getFirstFocusableItem();\n firstFocusable?.focus();\n }\n await this._updatePosition();\n this.dispatchEvent(new CustomEvent<void>('hx-show', { bubbles: true, composed: true }));\n }\n\n // P2-02: returnFocus=true only on Escape; Tab should let focus advance naturally.\n /** @internal */\n private _hide(returnFocus = true): void {\n if (!this.open) return;\n this.open = false;\n this._panelVisible = false;\n this._rovingIndex = -1;\n if (this._typeaheadTimer !== null) {\n clearTimeout(this._typeaheadTimer);\n this._typeaheadTimer = null;\n }\n this._typeaheadBuffer = '';\n if (this._documentListenerAttached) {\n document.removeEventListener('click', this._handleOutsideClick, { capture: true });\n this._documentListenerAttached = false;\n }\n this.dispatchEvent(new CustomEvent<void>('hx-hide', { bubbles: true, composed: true }));\n if (returnFocus) {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot[name=\"trigger\"]');\n const trigger = slot?.assignedElements()[0] as HTMLElement | undefined;\n trigger?.focus();\n }\n }\n\n // ─── Positioning ───\n\n /** @internal */\n private async _updatePosition(): Promise<void> {\n const reference = this._triggerWrapper;\n const panel = this._panel;\n if (!reference || !panel) return;\n\n // Map 'start' and 'end' to floating-ui's 'left'/'right'\n const floatingPlacement = this.placement\n .replace(/^start$/, 'left')\n .replace(/^end$/, 'right') as FloatingPlacement;\n\n const { computePosition, flip, shift, offset } = await import('@floating-ui/dom');\n const { x, y } = await computePosition(reference, panel, {\n placement: floatingPlacement,\n strategy: 'fixed',\n middleware: [offset(this.distance), flip(), shift({ padding: 8 })],\n });\n\n Object.assign(panel.style, {\n left: `${x}px`,\n top: `${y}px`,\n });\n }\n\n // ─── Event Handlers ───\n\n /** @internal */\n private _handleTriggerClick(e: MouseEvent): void {\n e.stopPropagation();\n if (this.open) {\n this._hide();\n } else {\n void this._show();\n }\n }\n\n /** @internal */\n private _handleTriggerKeydown(e: KeyboardEvent): void {\n if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {\n e.preventDefault();\n void this._show();\n }\n }\n\n /** @internal */\n private _handleKeydown = (e: KeyboardEvent): void => {\n if (e.key === 'Escape' && this.open) {\n e.stopPropagation();\n this._hide(true); // return focus to trigger on Escape\n } else if (e.key === 'Tab' && this.open) {\n // P2-02: Do not return focus to trigger on Tab — let focus advance naturally to next page element.\n this._hide(false);\n } else if (\n this.open &&\n (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Home' || e.key === 'End')\n ) {\n // P2-01: Arrow key roving within panel per APG Menu Button pattern.\n e.preventDefault();\n this._handleMenuNavigation(e.key);\n } else if (\n this.open &&\n e.key.length === 1 &&\n e.key !== ' ' &&\n !e.ctrlKey &&\n !e.metaKey &&\n !e.altKey\n ) {\n // Group 5b: first-character typeahead within the panel's menu\n // items. 500ms timeout matching hx-menu / hx-overflow-menu.\n this._handleTypeahead(e.key);\n }\n };\n\n // P2-01: Move focus among menuitem elements using arrow keys.\n /** @internal */\n private _handleMenuNavigation(key: string): void {\n const items = this._getFocusableMenuItems();\n if (items.length === 0) return;\n const currentIndex = items.indexOf(document.activeElement as HTMLElement);\n let nextIndex: number;\n if (key === 'ArrowDown') {\n nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;\n } else if (key === 'ArrowUp') {\n nextIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;\n } else if (key === 'Home') {\n nextIndex = 0;\n } else {\n nextIndex = items.length - 1;\n }\n this._rovingIndex = nextIndex;\n this._applyRovingTabIndex(items);\n items[nextIndex]?.focus();\n }\n\n /**\n * Roving tabindex inside the panel: only the focused item carries\n * tabindex=0; the rest are tabindex=-1. APG-compliant for the menu\n * button pattern. Closing-Tab semantics are preserved by the\n * `_handleKeydown` Tab branch above (which lets focus advance\n * naturally and closes the panel).\n *\n * Group 5b: introduced to align hx-dropdown's panel keyboard contract\n * with hx-menu / hx-overflow-menu / hx-split-button. Group 4b only\n * added the host-attribute label mirror (additive); this is the\n * keyboard portion deferred until Group 5.\n *\n * Codex push-gate round-8 finding 2: route through\n * `writeMenuItemRovingTabIndex` so host-canonical `hx-menu-item` items\n * land their roving tabindex on the correct surface (host on the\n * modern path, inner `.menu-item` on the fallback path). A direct\n * `item.tabIndex = value` write on the host fails on the fallback\n * path because the host is forced to `tabindex=-1` to keep exactly\n * one focusable surface per item.\n * @internal\n */\n private _applyRovingTabIndex(items: HTMLElement[]): void {\n items.forEach((item, i) => {\n writeMenuItemRovingTabIndex(item, i === this._rovingIndex ? 0 : -1);\n });\n }\n\n /** @internal */\n private _handleTypeahead(char: string): void {\n if (this._typeaheadTimer !== null) {\n clearTimeout(this._typeaheadTimer);\n }\n this._typeaheadBuffer += char.toLowerCase();\n this._typeaheadTimer = setTimeout(() => {\n this._typeaheadBuffer = '';\n this._typeaheadTimer = null;\n }, 500);\n\n const items = this._getFocusableMenuItems();\n // Codex push-gate round-7 finding 3: read item label via the shared\n // submenu-aware extractor so a parent menuitem with a nested\n // `<hx-menu slot=\"submenu\">` does not match grandchild text and steal\n // focus from a sibling. Single source of truth across hx-menu /\n // hx-dropdown / hx-overflow-menu / hx-split-button.\n const match = items.findIndex((item) => {\n const text = getMenuItemTypeaheadLabel(item).toLowerCase();\n return text.startsWith(this._typeaheadBuffer);\n });\n\n if (match !== -1) {\n this._rovingIndex = match;\n this._applyRovingTabIndex(items);\n items[match]?.focus();\n }\n }\n\n // P0-01 / P2-01: Get focusable menu items from slotted content.\n /** @internal */\n private _getFocusableMenuItems(): HTMLElement[] {\n const panel = this._panel;\n if (!panel) return [];\n const slot = panel.querySelector<HTMLSlotElement>('slot');\n const assignedNodes = slot?.assignedElements({ flatten: true }) ?? [];\n const items: HTMLElement[] = [];\n // `hx-menu-item` carries `role=\"menuitem\"` (or menuitemcheckbox /\n // menuitemradio) via `_internals.role` — AT-only, not a DOM attribute —\n // so `[role=\"menuitem\"]` selectors miss the host. Match the tag name in\n // tandem with the legacy attribute selector so both shapes traverse.\n const isHostCanonicalMenuItem = (el: Element): boolean => el.localName === 'hx-menu-item';\n const collectFrom = (root: ParentNode): HTMLElement[] => {\n const found: HTMLElement[] = [];\n root.querySelectorAll<HTMLElement>('[role=\"menuitem\"]').forEach((item) => found.push(item));\n root.querySelectorAll<HTMLElement>('hx-menu-item').forEach((item) => found.push(item));\n return found;\n };\n for (const node of assignedNodes) {\n if (!(node instanceof HTMLElement)) continue;\n if (node.matches('[role=\"menuitem\"]') || isHostCanonicalMenuItem(node)) {\n items.push(node);\n } else {\n collectFrom(node).forEach((item) => items.push(item));\n }\n }\n return items;\n }\n\n // P0-01: Find the first focusable element in slotted panel content.\n /** @internal */\n private _getFirstFocusableItem(): HTMLElement | null {\n const panel = this._panel;\n if (!panel) return null;\n const slot = panel.querySelector<HTMLSlotElement>('slot');\n const assignedNodes = slot?.assignedElements({ flatten: true }) ?? [];\n // `hx-menu-item` host carries `role=\"menuitem\"` via `_internals.role`\n // (invisible to attribute selectors). Add the literal tag name to the\n // focusable selector so the host-canonical menu-item is found.\n const focusableSelector =\n 'hx-menu-item, [role=\"menuitem\"], button, [tabindex]:not([tabindex=\"-1\"]), a[href], input, select, textarea';\n for (const node of assignedNodes) {\n if (!(node instanceof HTMLElement)) continue;\n if (node.matches(focusableSelector)) return node;\n const found = node.querySelector<HTMLElement>(focusableSelector);\n if (found) return found;\n }\n return null;\n }\n\n /** @internal */\n private _handleOutsideClick = (e: MouseEvent): void => {\n const path = e.composedPath();\n if (!path.includes(this)) {\n this._hide();\n }\n };\n\n /** @internal */\n private _handlePanelClick(e: MouseEvent): void {\n const target = e.target as HTMLElement;\n // P2-06: Narrow selector — bare 'li' and 'button' cause spurious hx-select events.\n // Group 5b round-3 (codex): bail FIRST on host-canonical `hx-menu-item`,\n // independently of what `closest()` resolves with the legacy selectors.\n // If a consumer slots `<hx-menu-item><span data-value=\"…\">…</span></hx-menu-item>`\n // and the click lands on the inner span, `closest('hx-menu-item, …, [data-value]')`\n // resolves to the inner span (nearest match) — the legacy localName guard\n // misses, and we'd dispatch `hx-select` here AND again from\n // `_handlePanelItemSelect` when the host's bubbled `hx-item-select` arrives.\n // The host owns its own dispatch path; descendants of the host must defer.\n if (target.closest('hx-menu-item')) return;\n const item = target.closest<HTMLElement>('[role=\"menuitem\"], [data-value]');\n if (!item) return;\n\n const value = item.dataset['value'] ?? item.getAttribute('value') ?? null;\n const label = item.textContent?.trim() ?? '';\n\n this.dispatchEvent(\n new CustomEvent<{ value: string | null; label: string }>('hx-select', {\n bubbles: true,\n composed: true,\n detail: { value, label },\n }),\n );\n\n this._hide();\n }\n\n /**\n * Bubbled `hx-item-select` from a slotted `hx-menu-item` host. Forwards\n * the activation through the composite's `hx-select` contract using the\n * item's `value` property and label text. Disabled items never emit\n * `hx-item-select`, so no disabled-guard is needed here.\n * @internal\n */\n private _handlePanelItemSelect(e: Event): void {\n const detail = (e as CustomEvent<{ item: HTMLElement; value: string }>).detail;\n const item = detail?.item;\n const value = detail?.value ?? null;\n const label = item?.textContent?.trim() ?? '';\n this.dispatchEvent(\n new CustomEvent<{ value: string | null; label: string }>('hx-select', {\n bubbles: true,\n composed: true,\n detail: { value, label },\n }),\n );\n this._hide();\n }\n\n /**\n * Bubbled `hx-item-submenu-open` from a slotted `hx-menu-item` host.\n * Codex push-gate round-9 P1: when slotted `hx-menu-item`s open / close\n * a nested submenu inside this composite's panel (no enclosing\n * `hx-menu`), the events fly past with no handler. Match the round-4\n * `hx-menu._handleSubmenuOpen` shape so APG behaviour holds.\n *\n * If the dispatching item is enclosed by an inner `hx-menu` (a true\n * nested submenu inside the panel), that menu owns the toggle — defer.\n * Otherwise this composite's panel is the enclosing menu surface, so\n * call `setSubmenuOpen(true)` on the item and focus the first child.\n * @internal\n */\n private _handlePanelSubmenuOpen = (e: Event): void => {\n if (!(e instanceof CustomEvent)) return;\n const detail = (e as CustomEvent<{ item: HTMLElement }>).detail;\n const item = detail?.item;\n if (!item) return;\n // Defer to a closer enclosing `hx-menu` (a nested submenu) when one\n // exists — that menu's own handler will own the toggle.\n if (findClosestMenuAncestor(item) !== null) return;\n queueMicrotask(() => {\n if (e.defaultPrevented) return;\n const setter = (item as HTMLElement & { setSubmenuOpen?: (v: boolean) => void })\n .setSubmenuOpen;\n if (typeof setter !== 'function') return;\n setter.call(item, true);\n const updateComplete = (item as HTMLElement & { updateComplete?: Promise<unknown> })\n .updateComplete;\n if (updateComplete) {\n void updateComplete\n .then(() => {\n const submenuSlot = (\n item as HTMLElement & { shadowRoot?: ShadowRoot | null }\n ).shadowRoot?.querySelector<HTMLSlotElement>('slot[name=\"submenu\"]');\n const nested = submenuSlot\n ?.assignedElements({ flatten: true })\n .find((el) => el.tagName.toLowerCase() === 'hx-menu') as\n | (HTMLElement & { focusFirst?: () => void })\n | undefined;\n nested?.focusFirst?.();\n })\n .catch(() => undefined);\n }\n });\n };\n\n /**\n * Bubbled `hx-item-submenu-close` from a slotted `hx-menu-item` host.\n * Codex push-gate round-9 P1: routes the close to the right surface.\n *\n * - Nested submenu close (the dispatching item lives inside an inner\n * `hx-menu` slotted into a parent's `slot=\"submenu\"`): defer to that\n * inner menu's own handler. The composite's panel must NOT close.\n * - Top-level item ArrowLeft (no enclosing `hx-menu` between the item\n * and this composite): there is no parent submenu to close, so\n * collapse the composite's panel and return focus to the trigger,\n * matching APG menu-button behaviour.\n * @internal\n */\n private _handlePanelSubmenuClose = (e: Event): void => {\n if (!(e instanceof CustomEvent)) return;\n const detail = (e as CustomEvent<{ item: HTMLElement }>).detail;\n const item = detail?.item;\n if (!item) return;\n // A closer enclosing `hx-menu` owns the close — defer.\n if (findClosestMenuAncestor(item) !== null) return;\n if (e.defaultPrevented) return;\n this._hide(true);\n };\n\n // ─── Render ───\n\n override render() {\n return html`\n <div\n part=\"trigger\"\n class=\"trigger-wrapper\"\n @click=${this._handleTriggerClick}\n @keydown=${this._handleTriggerKeydown}\n >\n <slot name=\"trigger\" @slotchange=${this._onTriggerSlotChange}></slot>\n </div>\n <div\n part=\"panel\"\n id=${this._panelId}\n role=\"menu\"\n aria-hidden=${this._panelVisible ? nothing : 'true'}\n aria-label=${this._resolvedLabel}\n class=${this._panelVisible ? 'panel panel--visible' : 'panel'}\n @click=${this._handlePanelClick}\n @hx-item-select=${this._handlePanelItemSelect}\n @hx-item-submenu-open=${this._handlePanelSubmenuOpen}\n @hx-item-submenu-close=${this._handlePanelSubmenuClose}\n >\n <slot @slotchange=${this._onPanelSlotChange}></slot>\n </div>\n `;\n }\n\n // ─── Panel slot validation ───\n\n /** @internal */\n private _onPanelSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n const assigned = slot.assignedElements({ flatten: true });\n const nonItems = assigned.filter((el) => el.tagName.toLowerCase() !== 'hx-dropdown-item');\n if (nonItems.length > 0) {\n devWarn(\n 'hx-dropdown',\n `Default slot should contain only hx-dropdown-item elements. Found unexpected: ${nonItems.map((el) => `<${el.tagName.toLowerCase()}>`).join(', ')}. Non-hx-dropdown-item children will be included in keyboard navigation incorrectly.`,\n );\n }\n }\n\n // ─── ARIA setup for trigger slot ───\n\n /** @internal */\n private _onTriggerSlotChange(): void {\n this._setupTriggerAria();\n }\n\n override firstUpdated(): void {\n this._setupTriggerAria();\n }\n\n /** @internal */\n private _setupTriggerAria(): void {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot[name=\"trigger\"]');\n if (!slot) return;\n const trigger = slot.assignedElements()[0] as HTMLElement | undefined;\n if (trigger) {\n // P1-01: Use aria-haspopup=\"menu\" per ARIA 1.1+ / APG Menu Button pattern.\n trigger.setAttribute('aria-haspopup', 'menu');\n trigger.setAttribute('aria-expanded', String(this.open));\n // aria-controls is intentionally omitted: the panel lives in Shadow DOM and\n // IDREF values cannot be resolved across shadow boundaries by assistive technology.\n // P2-06: Remove host fallback when a trigger element is present.\n this.removeAttribute('aria-expanded');\n } else {\n // P2-06: Fallback — set aria-expanded on host when trigger slot is empty or unassigned.\n this.setAttribute('aria-expanded', String(this.open));\n }\n }\n\n override willUpdate(changedProperties: PropertyValues<this>): void {\n super.willUpdate(changedProperties);\n // `label` property changes must flow into the resolved name BEFORE\n // render so the new fallback is in place on the same paint. See\n // `hx-popover.willUpdate()` for the same rationale.\n if (changedProperties.has('label')) {\n this._syncResolvedLabel();\n }\n }\n\n override updated(changedProperties: PropertyValues<this>): void {\n super.updated(changedProperties);\n if (changedProperties.has('open')) {\n // Keep aria-expanded in sync\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot[name=\"trigger\"]');\n const trigger = slot?.assignedElements()[0] as HTMLElement | undefined;\n if (trigger) {\n trigger.setAttribute('aria-expanded', String(this.open));\n } else {\n // P2-06: Fallback — keep host aria-expanded in sync when trigger slot is empty.\n this.setAttribute('aria-expanded', String(this.open));\n }\n }\n }\n\n // ─── Host-attribute label mirror ───\n\n /**\n * (Re-)installs a `MutationObserver` over the deduped union of\n * consumer-resolved label elements, watching for in-place text /\n * visibility mutations so the panel's `aria-label` tracks live consumer\n * text. See `hx-popover._installExternalRefsObserver` for the matching\n * shape used across the host-attribute-mirror family.\n * @internal\n */\n private _installExternalRefsObserver(elements: Element[]): void {\n if (this._externalRefsObserver) {\n this._externalRefsObserver.disconnect();\n this._externalRefsObserver = null;\n }\n if (elements.length === 0) return;\n const unique = new Set<Element>(elements);\n const observer = new MutationObserver(() => {\n this._syncResolvedLabel();\n });\n for (const el of unique) {\n observer.observe(el, {\n characterData: true,\n subtree: true,\n childList: true,\n attributes: true,\n attributeFilter: ['aria-hidden', 'hidden'],\n });\n }\n this._externalRefsObserver = observer;\n }\n\n /**\n * Resolves the menu panel's accessible name from host attributes and the\n * `label` property. AccName 1.2 §4.3.1 precedence:\n * 1. Host `aria-labelledby` (resolved IDREFs, flattened)\n * 2. Host `aria-label`\n * 3. `label` property\n * 4. Literal `\"Menu\"` (last-resort)\n * @internal\n */\n private _syncResolvedLabel(): void {\n const liveLabelledBy = this.getAttribute('aria-labelledby');\n this._consumerLabelledBy = liveLabelledBy;\n const consumerLabelEls = resolveIdrefTokens(this, liveLabelledBy);\n\n this._installExternalRefsObserver(consumerLabelEls);\n\n const isVisibleForAccName = (el: Element): boolean =>\n el.getAttribute('aria-hidden') !== 'true' && !el.hasAttribute('hidden');\n\n const flattenedFromIdrefs = consumerLabelEls\n .filter(isVisibleForAccName)\n .map((el) => flattenAccName(el))\n .filter((t) => t.length > 0)\n .join(' ')\n .replace(/\\s+/g, ' ')\n .trim();\n\n const liveAriaLabel = this.getAttribute('aria-label');\n const hostAriaLabel = liveAriaLabel !== null ? liveAriaLabel.trim() : '';\n\n let resolved = '';\n if (flattenedFromIdrefs) {\n resolved = flattenedFromIdrefs;\n } else if (hostAriaLabel) {\n resolved = hostAriaLabel;\n } else if (this.label) {\n resolved = this.label;\n } else {\n resolved = 'Menu';\n }\n\n this._resolvedLabel = resolved;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-dropdown': HelixDropdown;\n }\n interface HTMLElementEventMap {\n 'hx-show': CustomEvent<void>;\n 'hx-hide': CustomEvent<void>;\n 'hx-select': CustomEvent<{ value: string | null; label: string }>;\n }\n}\n"],"names":["helixDropdownStyles","css","_nextDropdownId","createIdCounter","HelixDropdown","HelixElement","detail","item","findClosestMenuAncestor","setter","updateComplete","submenuSlot","_a","nested","el","_b","installAriaIdrefMirror","items","firstFocusable","returnFocus","slot","trigger","reference","panel","floatingPlacement","computePosition","flip","shift","offset","x","y","key","currentIndex","nextIndex","writeMenuItemRovingTabIndex","char","match","getMenuItemTypeaheadLabel","assignedNodes","isHostCanonicalMenuItem","collectFrom","root","found","node","focusableSelector","target","value","label","html","nothing","nonItems","devWarn","changedProperties","elements","unique","observer","liveLabelledBy","consumerLabelEls","resolveIdrefTokens","isVisibleForAccName","flattenedFromIdrefs","flattenAccName","t","liveAriaLabel","hostAriaLabel","resolved","forcedColorsInteractive","__decorateClass","property","state","query","customElement"],"mappings":"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"}
@@ -1,10 +1,11 @@
1
- import { css as f, html as c, nothing as u } from "lit";
2
- import { property as e, customElement as x } from "lit/decorators.js";
3
- import { classMap as d } from "lit/directives/class-map.js";
4
- import { ifDefined as b } from "lit/directives/if-defined.js";
5
- import { f as p } from "./forced-colors-CTEDFRGa.js";
6
- import { H as m } from "./helix-element-BNEYeiys.js";
7
- const v = f`
1
+ import { css as p, html as u, nothing as d } from "lit";
2
+ import { property as n, customElement as x } from "lit/decorators.js";
3
+ import { classMap as b } from "lit/directives/class-map.js";
4
+ import { ifDefined as t } from "lit/directives/if-defined.js";
5
+ import { f } from "./forced-colors-CTEDFRGa.js";
6
+ import { m } from "./aria-delegation-Doq6RRUy.js";
7
+ import { H as v } from "./helix-element-BNEYeiys.js";
8
+ const g = p`
8
9
  :host {
9
10
  display: inline-block;
10
11
  }
@@ -157,12 +158,37 @@ const v = f`
157
158
  multiplicative stacking (0.5 * 0.5 = 0.25). Do not add opacity here. */
158
159
  }
159
160
 
161
+ /* ─── Loading State ─── */
162
+
163
+ .button--loading {
164
+ position: relative;
165
+ cursor: wait;
166
+ }
167
+
168
+ .button__spinner {
169
+ width: 1em;
170
+ height: 1em;
171
+ flex-shrink: 0;
172
+ animation: hx-icon-button-spin var(--hx-duration-spinner, 750ms) linear infinite;
173
+ }
174
+
175
+ @keyframes hx-icon-button-spin {
176
+ to {
177
+ transform: rotate(360deg);
178
+ }
179
+ }
180
+
160
181
  /* ─── Reduced Motion ─── */
161
182
 
162
183
  @media (prefers-reduced-motion: reduce) {
163
184
  .button {
164
185
  transition: none;
165
186
  }
187
+
188
+ .button__spinner {
189
+ animation: none;
190
+ opacity: var(--hx-opacity-muted, 0.6);
191
+ }
166
192
  }
167
193
 
168
194
  /* ─── High Contrast Mode (forced-colors) ─── */
@@ -190,32 +216,37 @@ const v = f`
190
216
  :host([disabled]) {
191
217
  opacity: 1;
192
218
  }
219
+
220
+ .button--loading .button__spinner {
221
+ stroke: ButtonText;
222
+ forced-color-adjust: none;
223
+ }
193
224
  }
194
225
  `;
195
- var g = Object.defineProperty, y = Object.getOwnPropertyDescriptor, o = (t, n, s, a) => {
196
- for (var i = a > 1 ? void 0 : a ? y(n, s) : n, h = t.length - 1, l; h >= 0; h--)
197
- (l = t[h]) && (i = (a ? l(n, s, i) : l(i)) || i);
198
- return a && i && g(n, s, i), i;
226
+ var y = Object.defineProperty, _ = Object.getOwnPropertyDescriptor, i = (r, a, h, s) => {
227
+ for (var e = s > 1 ? void 0 : s ? _(a, h) : a, c = r.length - 1, l; c >= 0; c--)
228
+ (l = r[c]) && (e = (s ? l(a, h, e) : l(e)) || e);
229
+ return s && e && y(a, h, e), e;
199
230
  };
200
- let r = class extends m {
231
+ let o = class extends m(v) {
201
232
  constructor() {
202
- super(...arguments), this.label = "", this.variant = "ghost", this.size = "md", this.type = "button", this.disabled = !1, this.href = void 0, this.name = void 0, this.value = void 0;
233
+ super(...arguments), this.label = "", this.variant = "ghost", this.size = "md", this.type = "button", this.disabled = !1, this.loading = !1, this.href = void 0, this.name = void 0, this.value = void 0;
203
234
  }
204
- _onFormDisabled(t) {
205
- this.disabled = t;
235
+ _onFormDisabled(r) {
236
+ this.disabled = r;
206
237
  }
207
238
  // ─── Event Handling ───
208
239
  /** @internal */
209
- _handleClick(t) {
210
- if (this.disabled) {
211
- t.preventDefault(), t.stopPropagation();
240
+ _handleClick(r) {
241
+ if (this.disabled || this.loading) {
242
+ r.preventDefault(), r.stopPropagation();
212
243
  return;
213
244
  }
214
245
  this.dispatchEvent(
215
246
  new CustomEvent("hx-click", {
216
247
  bubbles: !0,
217
248
  composed: !0,
218
- detail: { originalEvent: t }
249
+ detail: { originalEvent: r }
219
250
  })
220
251
  ), this.href || (this.type === "submit" && this._internals.form ? this._internals.form.requestSubmit() : this.type === "reset" && this._internals.form && this._internals.form.reset());
221
252
  }
@@ -229,76 +260,126 @@ let r = class extends m {
229
260
  return {
230
261
  button: !0,
231
262
  [`button--${this.variant}`]: !0,
232
- [`button--${this.size}`]: !0
263
+ [`button--${this.size}`]: !0,
264
+ "button--loading": this.loading
233
265
  };
234
266
  }
235
267
  /** @internal */
236
268
  _iconSlot() {
237
- return c`<span part="icon" class="icon"><slot></slot></span>`;
269
+ return u`<span part="icon" class="icon"><slot></slot></span>`;
270
+ }
271
+ /** @internal */
272
+ _renderSpinner() {
273
+ return u`
274
+ <svg
275
+ class="button__spinner"
276
+ part="spinner"
277
+ aria-hidden="true"
278
+ viewBox="0 0 24 24"
279
+ fill="none"
280
+ >
281
+ <circle
282
+ class="button__spinner-track"
283
+ cx="12"
284
+ cy="12"
285
+ r="10"
286
+ stroke="currentColor"
287
+ stroke-width="3"
288
+ opacity="0.3"
289
+ />
290
+ <path
291
+ class="button__spinner-arc"
292
+ d="M12 2a10 10 0 0 1 10 10"
293
+ stroke="currentColor"
294
+ stroke-width="3"
295
+ stroke-linecap="round"
296
+ />
297
+ </svg>
298
+ `;
238
299
  }
239
300
  // ─── Render ───
240
301
  render() {
241
- const t = this._normalizedLabel();
242
- return t ? this.href !== void 0 ? c`
302
+ const r = this._normalizedLabel();
303
+ if (!r)
304
+ return d;
305
+ const a = this.getAttribute("data-aria-describedby"), h = this.getAttribute("data-aria-pressed"), s = this.getAttribute("data-aria-expanded"), e = this.getAttribute("data-aria-haspopup"), c = this.getAttribute("data-aria-controls"), l = this.getAttribute("data-aria-current");
306
+ return this.href !== void 0 ? u`
243
307
  <a
244
308
  part="button"
245
- class=${d(this._classes())}
246
- href=${b(this.disabled ? void 0 : this.href)}
247
- aria-label=${t}
248
- title=${t}
249
- aria-disabled=${this.disabled ? "true" : u}
250
- tabindex=${this.disabled ? "-1" : u}
309
+ class=${b(this._classes())}
310
+ href=${t(this.disabled || this.loading ? void 0 : this.href)}
311
+ aria-label=${r}
312
+ title=${r}
313
+ aria-disabled=${this.disabled ? "true" : d}
314
+ aria-busy=${this.loading ? "true" : d}
315
+ aria-pressed=${t(h ?? void 0)}
316
+ aria-expanded=${t(s ?? void 0)}
317
+ aria-haspopup=${t(e ?? void 0)}
318
+ aria-controls=${t(c ?? void 0)}
319
+ aria-describedby=${t(a ?? void 0)}
320
+ aria-current=${t(l ?? void 0)}
321
+ tabindex=${this.disabled || this.loading ? "-1" : d}
251
322
  @click=${this._handleClick}
252
323
  >
253
- ${this._iconSlot()}
324
+ ${this.loading ? this._renderSpinner() : this._iconSlot()}
254
325
  </a>
255
- ` : c`
326
+ ` : u`
256
327
  <button
257
328
  part="button"
258
- class=${d(this._classes())}
329
+ class=${b(this._classes())}
259
330
  ?disabled=${this.disabled}
260
331
  type=${this.type}
261
- aria-label=${t}
262
- title=${t}
263
- name=${b(this.name)}
264
- value=${b(this.value)}
332
+ aria-label=${r}
333
+ title=${r}
334
+ aria-busy=${this.loading ? "true" : d}
335
+ aria-pressed=${t(h ?? void 0)}
336
+ aria-expanded=${t(s ?? void 0)}
337
+ aria-haspopup=${t(e ?? void 0)}
338
+ aria-controls=${t(c ?? void 0)}
339
+ aria-describedby=${t(a ?? void 0)}
340
+ aria-current=${t(l ?? void 0)}
341
+ name=${t(this.name)}
342
+ value=${t(this.value)}
265
343
  @click=${this._handleClick}
266
344
  >
267
- ${this._iconSlot()}
345
+ ${this.loading ? this._renderSpinner() : this._iconSlot()}
268
346
  </button>
269
- ` : u;
347
+ `;
270
348
  }
271
349
  };
272
- r.styles = [v, p];
273
- r.formAssociated = !0;
274
- o([
275
- e({ type: String })
276
- ], r.prototype, "label", 2);
277
- o([
278
- e({ type: String, reflect: !0 })
279
- ], r.prototype, "variant", 2);
280
- o([
281
- e({ type: String, reflect: !0, attribute: "hx-size" })
282
- ], r.prototype, "size", 2);
283
- o([
284
- e({ type: String })
285
- ], r.prototype, "type", 2);
286
- o([
287
- e({ type: Boolean, reflect: !0 })
288
- ], r.prototype, "disabled", 2);
289
- o([
290
- e({ type: String })
291
- ], r.prototype, "href", 2);
292
- o([
293
- e({ type: String })
294
- ], r.prototype, "name", 2);
295
- o([
296
- e({ type: String })
297
- ], r.prototype, "value", 2);
298
- r = o([
350
+ o.styles = [g, f];
351
+ o.formAssociated = !0;
352
+ i([
353
+ n({ type: String })
354
+ ], o.prototype, "label", 2);
355
+ i([
356
+ n({ type: String, reflect: !0 })
357
+ ], o.prototype, "variant", 2);
358
+ i([
359
+ n({ type: String, reflect: !0, attribute: "hx-size" })
360
+ ], o.prototype, "size", 2);
361
+ i([
362
+ n({ type: String })
363
+ ], o.prototype, "type", 2);
364
+ i([
365
+ n({ type: Boolean, reflect: !0 })
366
+ ], o.prototype, "disabled", 2);
367
+ i([
368
+ n({ type: Boolean, reflect: !0 })
369
+ ], o.prototype, "loading", 2);
370
+ i([
371
+ n({ type: String })
372
+ ], o.prototype, "href", 2);
373
+ i([
374
+ n({ type: String })
375
+ ], o.prototype, "name", 2);
376
+ i([
377
+ n({ type: String })
378
+ ], o.prototype, "value", 2);
379
+ o = i([
299
380
  x("hx-icon-button")
300
- ], r);
381
+ ], o);
301
382
  export {
302
- r as H
383
+ o as H
303
384
  };
304
- //# sourceMappingURL=hx-icon-button-CGNdQSFM.js.map
385
+ //# sourceMappingURL=hx-icon-button-a6OpeQz5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hx-icon-button-a6OpeQz5.js","sources":["../../src/components/hx-icon-button/hx-icon-button.styles.ts","../../src/components/hx-icon-button/hx-icon-button.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const helixIconButtonStyles = css`\n :host {\n display: inline-block;\n }\n\n :host([disabled]) {\n pointer-events: none;\n opacity: var(--hx-opacity-disabled, 0.5);\n }\n\n .button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: var(--hx-border-width-thin, 1px) solid var(--hx-icon-button-border-color, transparent);\n border-radius: var(--hx-icon-button-border-radius, var(--hx-border-radius-md, 0.375rem));\n background-color: var(--hx-icon-button-bg, transparent);\n color: var(--hx-icon-button-color, var(--hx-color-primary-500, #429797));\n cursor: pointer;\n transition:\n background-color var(--hx-transition-fast, 150ms ease),\n color var(--hx-transition-fast, 150ms ease),\n border-color var(--hx-transition-fast, 150ms ease),\n box-shadow var(--hx-transition-fast, 150ms ease);\n text-decoration: none;\n user-select: none;\n -webkit-user-select: none;\n flex-shrink: 0;\n }\n\n .button:focus-visible {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-icon-button-focus-ring-color, var(--hx-focus-ring-color, #0f7078));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n }\n\n .button:active {\n filter: brightness(var(--hx-filter-brightness-active, 0.8));\n }\n\n /* ─── Size Variants ─── */\n\n /* WCAG 2.5.5 (healthcare mandate): minimum 44x44px touch target for all sizes.\n min-width/min-height override the explicit size tokens when they fall below\n the 2.75rem (44px) threshold, preserving the visual icon size via font-size. */\n\n .button--sm {\n padding: var(--hx-space-1, 0.25rem);\n width: var(--hx-icon-button-size, var(--hx-size-8, 2rem));\n height: var(--hx-icon-button-size, var(--hx-size-8, 2rem));\n min-width: var(--hx-touch-target-min, 2.75rem);\n min-height: var(--hx-touch-target-min, 2.75rem);\n font-size: var(--hx-font-size-sm, 0.875rem);\n }\n\n .button--md {\n padding: var(--hx-space-2, 0.5rem);\n width: var(--hx-icon-button-size, var(--hx-size-10, 2.5rem));\n height: var(--hx-icon-button-size, var(--hx-size-10, 2.5rem));\n min-width: var(--hx-touch-target-min, 2.75rem);\n min-height: var(--hx-touch-target-min, 2.75rem);\n font-size: var(--hx-font-size-md, 1rem);\n }\n\n .button--lg {\n padding: var(--hx-space-3, 0.75rem);\n width: var(--hx-icon-button-size, var(--hx-size-12, 3rem));\n height: var(--hx-icon-button-size, var(--hx-size-12, 3rem));\n min-width: var(--hx-touch-target-min, 2.75rem);\n min-height: var(--hx-touch-target-min, 2.75rem);\n font-size: var(--hx-font-size-lg, 1.125rem);\n }\n\n /* ─── Style Variants ─── */\n\n .button--primary {\n --hx-icon-button-bg: var(--hx-color-primary-500, #429797);\n --hx-icon-button-color: var(--hx-color-text-on-primary, #ffffff);\n --hx-icon-button-border-color: transparent;\n }\n\n /* on-primary tokens are tuned for primary-500. primary-600 + on-primary\n drops icon contrast to 3.07:1 — fails the 4.5:1 floor for meaningful\n icons. Pin fg at neutral-0 (5.82:1 on primary-600). Mirrors hx-button. */\n .button--primary:hover {\n --hx-icon-button-bg: var(--hx-color-primary-600, #0f7078);\n --hx-icon-button-color: var(--hx-color-neutral-0, #ffffff);\n }\n\n .button--secondary {\n --hx-icon-button-bg: transparent;\n --hx-icon-button-color: var(--hx-color-primary-500, #429797);\n --hx-icon-button-border-color: var(--hx-color-primary-500, #429797);\n }\n\n .button--secondary:hover {\n --hx-icon-button-bg: var(--hx-color-primary-50, #ebf8f8);\n }\n\n .button--tertiary {\n --hx-icon-button-bg: transparent;\n --hx-icon-button-color: var(--hx-color-text-strong, #202b39);\n --hx-icon-button-border-color: var(--hx-color-border-strong, #66787b);\n }\n\n .button--tertiary:hover {\n --hx-icon-button-bg: var(--hx-color-surface-sunken, #ebeee9);\n }\n\n .button--danger {\n --hx-icon-button-bg: var(--hx-color-error-500, #e5493e);\n --hx-icon-button-color: var(--hx-color-text-on-error, #ffffff);\n --hx-icon-button-border-color: transparent;\n }\n\n /* on-error tokens are tuned for error-500. error-600 + on-error drops\n icon contrast to 2.25:1 — fails AA. Pin fg at neutral-0\n (6.47:1 on error-600). Mirrors hx-button danger:hover. */\n .button--danger:hover {\n --hx-icon-button-bg: var(--hx-color-error-600, #c92a2a);\n --hx-icon-button-color: var(--hx-color-neutral-0, #ffffff);\n }\n\n .button--ghost {\n --hx-icon-button-bg: transparent;\n --hx-icon-button-color: var(--hx-color-primary-500, #429797);\n --hx-icon-button-border-color: transparent;\n }\n\n .button--ghost:hover {\n --hx-icon-button-bg: var(--hx-color-surface-raised, #f5f8f3);\n }\n\n /* ─── Icon Container ─── */\n\n .icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1em;\n height: 1em;\n line-height: 1;\n pointer-events: none;\n }\n\n /* ─── Disabled ─── */\n\n .button[disabled] {\n cursor: not-allowed;\n /* P1-02 fix: opacity is set only on :host([disabled]) above to prevent\n multiplicative stacking (0.5 * 0.5 = 0.25). Do not add opacity here. */\n }\n\n /* ─── Loading State ─── */\n\n .button--loading {\n position: relative;\n cursor: wait;\n }\n\n .button__spinner {\n width: 1em;\n height: 1em;\n flex-shrink: 0;\n animation: hx-icon-button-spin var(--hx-duration-spinner, 750ms) linear infinite;\n }\n\n @keyframes hx-icon-button-spin {\n to {\n transform: rotate(360deg);\n }\n }\n\n /* ─── Reduced Motion ─── */\n\n @media (prefers-reduced-motion: reduce) {\n .button {\n transition: none;\n }\n\n .button__spinner {\n animation: none;\n opacity: var(--hx-opacity-muted, 0.6);\n }\n }\n\n /* ─── High Contrast Mode (forced-colors) ─── */\n\n @media (forced-colors: active) {\n .button {\n forced-color-adjust: none;\n background-color: ButtonFace;\n color: ButtonText;\n border: 2px solid ButtonText;\n }\n\n .button:focus-visible {\n outline: 3px solid Highlight;\n outline-offset: 2px;\n }\n\n .button[disabled] {\n background-color: ButtonFace;\n color: GrayText;\n border-color: GrayText;\n opacity: 1;\n }\n\n :host([disabled]) {\n opacity: 1;\n }\n\n .button--loading .button__spinner {\n stroke: ButtonText;\n forced-color-adjust: none;\n }\n }\n`;\n","import { html, nothing, type TemplateResult } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property } from 'lit/decorators.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { HelixElement } from '../../base/index.js';\nimport { mixinDelegatesAria } from '../../mixins/index.js';\nimport { helixIconButtonStyles } from './hx-icon-button.styles.js';\nimport { forcedColorsInteractive } from '../../styles/forced-colors.js';\nimport { devWarn } from '../../utils/dev-warn.js';\n\n/**\n * An icon-only button component for compact, accessible actions.\n * Renders a square button or anchor element containing a single icon.\n * The `label` property is required and provides the accessible name\n * via `aria-label` and a native tooltip via the `title` attribute.\n *\n * @summary Icon-only action button with full accessibility support.\n *\n * @tag hx-icon-button\n *\n * @slot - Icon element to display (hx-icon, svg, or img).\n *\n * @fires {CustomEvent<{originalEvent: MouseEvent}>} hx-click - Dispatched when the button is clicked (not disabled).\n *\n * @csspart button - The native button or anchor element.\n * @csspart icon - The icon container span wrapping the default slot.\n * @csspart spinner - The loading spinner SVG element shown when `loading` is true.\n *\n * @cssprop [--hx-icon-button-bg=transparent] - Button background color.\n * @cssprop [--hx-icon-button-color=var(--hx-color-primary-500)] - Icon color.\n * @cssprop [--hx-icon-button-border-color=transparent] - Button border color.\n * @cssprop [--hx-icon-button-border-radius=var(--hx-border-radius-md)] - Button border radius.\n * @cssprop [--hx-icon-button-size] - Explicit width and height override for the button.\n * @cssprop [--hx-icon-button-focus-ring-color=var(--hx-focus-ring-color)] - Focus ring color.\n * @cssprop [--hx-opacity-disabled] - Opacity.\n * @cssprop [--hx-border-width-thin] - Width.\n * @cssprop [--hx-border-radius-md] - CSS custom property.\n * @cssprop [--hx-color-primary-500] - Color.\n * @cssprop [--hx-transition-fast] - Transition timing.\n * @cssprop [--hx-focus-ring-width] - Width.\n * @cssprop [--hx-focus-ring-color] - Color.\n * @cssprop [--hx-focus-ring-offset] - CSS custom property.\n * @cssprop [--hx-filter-brightness-active] - CSS filter.\n * @cssprop [--hx-space-1] - Spacing token.\n * @cssprop [--hx-size-8] - Size token.\n * @cssprop [--hx-touch-target-min] - Minimum touch target size.\n * @cssprop [--hx-font-size-sm] - Font size.\n * @cssprop [--hx-space-2] - Spacing token.\n * @cssprop [--hx-size-10] - Size token.\n * @cssprop [--hx-font-size-md] - Font size.\n * @cssprop [--hx-space-3] - Spacing token.\n * @cssprop [--hx-size-12] - Size token.\n * @cssprop [--hx-font-size-lg] - Font size.\n * @cssprop [--hx-color-neutral-0] - Color.\n * @cssprop [--hx-color-primary-600] - Color.\n * @cssprop [--hx-color-primary-50] - Color.\n * @cssprop [--hx-color-neutral-700] - Color.\n * @cssprop [--hx-color-neutral-300] - Color.\n * @cssprop [--hx-color-neutral-100] - Color.\n * @cssprop [--hx-color-error-500] - Color.\n * @cssprop [--hx-color-error-600] - Color.\n */\n@customElement('hx-icon-button')\nexport class HelixIconButton extends mixinDelegatesAria(HelixElement) {\n static override styles = [helixIconButtonStyles, forcedColorsInteractive];\n\n /**\n * Accessible name for the button. Required. Rendered as `aria-label` and\n * `title` on the underlying element. The component renders nothing when absent,\n * and a console warning is emitted to alert developers during authoring.\n * @attr label\n */\n @property({ type: String })\n label = '';\n\n /**\n * Visual style variant of the button.\n * @attr variant\n */\n @property({ type: String, reflect: true })\n variant: 'primary' | 'secondary' | 'tertiary' | 'danger' | 'ghost' = 'ghost';\n\n /**\n * Size of the button.\n * @attr hx-size\n */\n @property({ type: String, reflect: true, attribute: 'hx-size' })\n size: 'sm' | 'md' | 'lg' = 'md';\n\n /**\n * The type attribute for the underlying button element.\n * Has no effect when `href` is set.\n * @attr type\n */\n @property({ type: String })\n type: 'button' | 'submit' | 'reset' = 'button';\n\n /**\n * Whether the button is disabled.\n * @attr disabled\n */\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n /**\n * Whether the button is in a loading state. Shows the spinner, prevents\n * activation, and sets `aria-busy=\"true\"` on the inner element. Does NOT\n * set the native `disabled` attribute (loading is transient; disabled is\n * persistent, and AT announces them differently).\n * @attr loading\n */\n @property({ type: Boolean, reflect: true })\n loading = false;\n\n /**\n * When set, renders an `<a>` element instead of a `<button>`.\n * @attr href\n */\n @property({ type: String })\n href: string | undefined = undefined;\n\n /**\n * Name submitted with form data. Only applicable when rendering as a button.\n * @attr name\n */\n @property({ type: String })\n name: string | undefined = undefined;\n\n /**\n * Value submitted with form data. Only applicable when rendering as a button.\n * @attr value\n */\n @property({ type: String })\n value: string | undefined = undefined;\n\n // ─── Form Association ───\n\n /** Marks this element as form-associated for ElementInternals support. @internal */\n static override formAssociated = true;\n\n protected override _onFormDisabled(disabled: boolean): void {\n this.disabled = disabled;\n }\n\n // ─── Event Handling ───\n\n /** @internal */\n private _handleClick(e: MouseEvent): void {\n if (this.disabled || this.loading) {\n e.preventDefault();\n e.stopPropagation();\n return;\n }\n\n /**\n * Dispatched when the button is clicked.\n * @event hx-click\n */\n this.dispatchEvent(\n new CustomEvent<{ originalEvent: MouseEvent }>('hx-click', {\n bubbles: true,\n composed: true,\n detail: { originalEvent: e },\n }),\n );\n\n // Handle form submission/reset if form-associated and not in href/link mode\n if (!this.href) {\n if (this.type === 'submit' && this._internals.form) {\n this._internals.form.requestSubmit();\n } else if (this.type === 'reset' && this._internals.form) {\n this._internals.form.reset();\n }\n }\n }\n\n // ─── Render Helpers ───\n\n /** @internal */\n private _normalizedLabel(): string {\n return this.label.trim();\n }\n\n /** @internal */\n private _classes() {\n return {\n button: true,\n [`button--${this.variant}`]: true,\n [`button--${this.size}`]: true,\n 'button--loading': this.loading,\n };\n }\n\n /** @internal */\n private _iconSlot() {\n return html`<span part=\"icon\" class=\"icon\"><slot></slot></span>`;\n }\n\n /** @internal */\n private _renderSpinner(): TemplateResult {\n return html`\n <svg\n class=\"button__spinner\"\n part=\"spinner\"\n aria-hidden=\"true\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n >\n <circle\n class=\"button__spinner-track\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n stroke-width=\"3\"\n opacity=\"0.3\"\n />\n <path\n class=\"button__spinner-arc\"\n d=\"M12 2a10 10 0 0 1 10 10\"\n stroke=\"currentColor\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n </svg>\n `;\n }\n\n // ─── Render ───\n\n override render() {\n const normalizedLabel = this._normalizedLabel();\n if (!normalizedLabel) {\n devWarn(\n 'hx-icon-button',\n 'The `label` property is required for accessibility. Render suppressed.',\n );\n return nothing;\n }\n\n // mixinDelegatesAria forwarding: consumer-set aria-* (aria-pressed,\n // aria-expanded, aria-haspopup, aria-controls, aria-describedby) lands\n // in data-aria-* on the host. Project them onto the inner element so the\n // a11y tree sees them on the role-bearing native element.\n const projectedDescribedBy = this.getAttribute('data-aria-describedby');\n const projectedPressed = this.getAttribute('data-aria-pressed');\n const projectedExpanded = this.getAttribute('data-aria-expanded');\n const projectedHasPopup = this.getAttribute('data-aria-haspopup');\n const projectedControls = this.getAttribute('data-aria-controls');\n const projectedCurrent = this.getAttribute('data-aria-current');\n\n if (this.href !== undefined) {\n // P1-03 fix: disabled anchor must set tabindex=\"-1\" explicitly — an <a>\n // without href is non-focusable by default in most browsers, but this is\n // browser-dependent. Explicit tabindex=\"-1\" guarantees keyboard exclusion\n // across all conforming browsers. Loading anchors are also tab-skipped\n // for consistency with disabled.\n // P1-07 note: aria-disabled IS required on the anchor branch because\n // <a> elements have no native disabled attribute; aria-disabled is the\n // only AT signal available.\n return html`\n <a\n part=\"button\"\n class=${classMap(this._classes())}\n href=${ifDefined(this.disabled || this.loading ? undefined : this.href)}\n aria-label=${normalizedLabel}\n title=${normalizedLabel}\n aria-disabled=${this.disabled ? 'true' : nothing}\n aria-busy=${this.loading ? 'true' : nothing}\n aria-pressed=${ifDefined(projectedPressed ?? undefined)}\n aria-expanded=${ifDefined(projectedExpanded ?? undefined)}\n aria-haspopup=${ifDefined(projectedHasPopup ?? undefined)}\n aria-controls=${ifDefined(projectedControls ?? undefined)}\n aria-describedby=${ifDefined(projectedDescribedBy ?? undefined)}\n aria-current=${ifDefined(projectedCurrent ?? undefined)}\n tabindex=${this.disabled || this.loading ? '-1' : nothing}\n @click=${this._handleClick}\n >\n ${this.loading ? this._renderSpinner() : this._iconSlot()}\n </a>\n `;\n }\n\n // P1-07 fix: aria-disabled is redundant on a natively disabled <button>.\n // The native disabled attribute already exposes aria-disabled=\"true\"\n // implicitly in the accessibility tree. Duplicate explicit aria-disabled\n // creates ambiguity about design intent. Keep only native ?disabled.\n return html`\n <button\n part=\"button\"\n class=${classMap(this._classes())}\n ?disabled=${this.disabled}\n type=${this.type}\n aria-label=${normalizedLabel}\n title=${normalizedLabel}\n aria-busy=${this.loading ? 'true' : nothing}\n aria-pressed=${ifDefined(projectedPressed ?? undefined)}\n aria-expanded=${ifDefined(projectedExpanded ?? undefined)}\n aria-haspopup=${ifDefined(projectedHasPopup ?? undefined)}\n aria-controls=${ifDefined(projectedControls ?? undefined)}\n aria-describedby=${ifDefined(projectedDescribedBy ?? undefined)}\n aria-current=${ifDefined(projectedCurrent ?? undefined)}\n name=${ifDefined(this.name)}\n value=${ifDefined(this.value)}\n @click=${this._handleClick}\n >\n ${this.loading ? this._renderSpinner() : this._iconSlot()}\n </button>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-icon-button': HelixIconButton;\n }\n interface HTMLElementEventMap {\n 'hx-click': CustomEvent<{ originalEvent: MouseEvent }>;\n }\n}\n"],"names":["helixIconButtonStyles","css","HelixIconButton","mixinDelegatesAria","HelixElement","disabled","e","html","normalizedLabel","nothing","projectedDescribedBy","projectedPressed","projectedExpanded","projectedHasPopup","projectedControls","projectedCurrent","classMap","ifDefined","forcedColorsInteractive","__decorateClass","property","customElement"],"mappings":";;;;;;;AAEO,MAAMA,IAAwBC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;;AC8D9B,IAAMC,IAAN,cAA8BC,EAAmBC,CAAY,EAAE;AAAA,EAA/D,cAAA;AAAA,UAAA,GAAA,SAAA,GAUL,KAAA,QAAQ,IAOR,KAAA,UAAqE,SAOrE,KAAA,OAA2B,MAQ3B,KAAA,OAAsC,UAOtC,KAAA,WAAW,IAUX,KAAA,UAAU,IAOV,KAAA,OAA2B,QAO3B,KAAA,OAA2B,QAO3B,KAAA,QAA4B;AAAA,EAAA;AAAA,EAOT,gBAAgBC,GAAyB;AAC1D,SAAK,WAAWA;AAAA,EAClB;AAAA;AAAA;AAAA,EAKQ,aAAaC,GAAqB;AACxC,QAAI,KAAK,YAAY,KAAK,SAAS;AACjC,MAAAA,EAAE,eAAA,GACFA,EAAE,gBAAA;AACF;AAAA,IACF;AAMA,SAAK;AAAA,MACH,IAAI,YAA2C,YAAY;AAAA,QACzD,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ,EAAE,eAAeA,EAAA;AAAA,MAAE,CAC5B;AAAA,IAAA,GAIE,KAAK,SACJ,KAAK,SAAS,YAAY,KAAK,WAAW,OAC5C,KAAK,WAAW,KAAK,cAAA,IACZ,KAAK,SAAS,WAAW,KAAK,WAAW,QAClD,KAAK,WAAW,KAAK,MAAA;AAAA,EAG3B;AAAA;AAAA;AAAA,EAKQ,mBAA2B;AACjC,WAAO,KAAK,MAAM,KAAA;AAAA,EACpB;AAAA;AAAA,EAGQ,WAAW;AACjB,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,CAAC,WAAW,KAAK,OAAO,EAAE,GAAG;AAAA,MAC7B,CAAC,WAAW,KAAK,IAAI,EAAE,GAAG;AAAA,MAC1B,mBAAmB,KAAK;AAAA,IAAA;AAAA,EAE5B;AAAA;AAAA,EAGQ,YAAY;AAClB,WAAOC;AAAA,EACT;AAAA;AAAA,EAGQ,iBAAiC;AACvC,WAAOA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0BT;AAAA;AAAA,EAIS,SAAS;AAChB,UAAMC,IAAkB,KAAK,iBAAA;AAC7B,QAAI,CAACA;AAKH,aAAOC;AAOT,UAAMC,IAAuB,KAAK,aAAa,uBAAuB,GAChEC,IAAmB,KAAK,aAAa,mBAAmB,GACxDC,IAAoB,KAAK,aAAa,oBAAoB,GAC1DC,IAAoB,KAAK,aAAa,oBAAoB,GAC1DC,IAAoB,KAAK,aAAa,oBAAoB,GAC1DC,IAAmB,KAAK,aAAa,mBAAmB;AAE9D,WAAI,KAAK,SAAS,SASTR;AAAA;AAAA;AAAA,kBAGKS,EAAS,KAAK,UAAU,CAAC;AAAA,iBAC1BC,EAAU,KAAK,YAAY,KAAK,UAAU,SAAY,KAAK,IAAI,CAAC;AAAA,uBAC1DT,CAAe;AAAA,kBACpBA,CAAe;AAAA,0BACP,KAAK,WAAW,SAASC,CAAO;AAAA,sBACpC,KAAK,UAAU,SAASA,CAAO;AAAA,yBAC5BQ,EAAUN,KAAoB,MAAS,CAAC;AAAA,0BACvCM,EAAUL,KAAqB,MAAS,CAAC;AAAA,0BACzCK,EAAUJ,KAAqB,MAAS,CAAC;AAAA,0BACzCI,EAAUH,KAAqB,MAAS,CAAC;AAAA,6BACtCG,EAAUP,KAAwB,MAAS,CAAC;AAAA,yBAChDO,EAAUF,KAAoB,MAAS,CAAC;AAAA,qBAC5C,KAAK,YAAY,KAAK,UAAU,OAAON,CAAO;AAAA,mBAChD,KAAK,YAAY;AAAA;AAAA,YAExB,KAAK,UAAU,KAAK,mBAAmB,KAAK,WAAW;AAAA;AAAA,UASxDF;AAAA;AAAA;AAAA,gBAGKS,EAAS,KAAK,UAAU,CAAC;AAAA,oBACrB,KAAK,QAAQ;AAAA,eAClB,KAAK,IAAI;AAAA,qBACHR,CAAe;AAAA,gBACpBA,CAAe;AAAA,oBACX,KAAK,UAAU,SAASC,CAAO;AAAA,uBAC5BQ,EAAUN,KAAoB,MAAS,CAAC;AAAA,wBACvCM,EAAUL,KAAqB,MAAS,CAAC;AAAA,wBACzCK,EAAUJ,KAAqB,MAAS,CAAC;AAAA,wBACzCI,EAAUH,KAAqB,MAAS,CAAC;AAAA,2BACtCG,EAAUP,KAAwB,MAAS,CAAC;AAAA,uBAChDO,EAAUF,KAAoB,MAAS,CAAC;AAAA,eAChDE,EAAU,KAAK,IAAI,CAAC;AAAA,gBACnBA,EAAU,KAAK,KAAK,CAAC;AAAA,iBACpB,KAAK,YAAY;AAAA;AAAA,UAExB,KAAK,UAAU,KAAK,mBAAmB,KAAK,WAAW;AAAA;AAAA;AAAA,EAG/D;AACF;AAvPaf,EACK,SAAS,CAACF,GAAuBkB,CAAuB;AAD7DhB,EA2EK,iBAAiB;AAjEjCiB,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GATflB,EAUX,WAAA,SAAA,CAAA;AAOAiB,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,QAAQ,SAAS,IAAM;AAAA,GAhB9BlB,EAiBX,WAAA,WAAA,CAAA;AAOAiB,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,QAAQ,SAAS,IAAM,WAAW,WAAW;AAAA,GAvBpDlB,EAwBX,WAAA,QAAA,CAAA;AAQAiB,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GA/BflB,EAgCX,WAAA,QAAA,CAAA;AAOAiB,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,SAAS,SAAS,IAAM;AAAA,GAtC/BlB,EAuCX,WAAA,YAAA,CAAA;AAUAiB,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,SAAS,SAAS,IAAM;AAAA,GAhD/BlB,EAiDX,WAAA,WAAA,CAAA;AAOAiB,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GAvDflB,EAwDX,WAAA,QAAA,CAAA;AAOAiB,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GA9DflB,EA+DX,WAAA,QAAA,CAAA;AAOAiB,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GArEflB,EAsEX,WAAA,SAAA,CAAA;AAtEWA,IAANiB,EAAA;AAAA,EADNE,EAAc,gBAAgB;AAAA,GAClBnB,CAAA;"}
@@ -1,10 +1,11 @@
1
- import { css as x, nothing as f, svg as v, html as h } from "lit";
2
- import { property as i, customElement as u } from "lit/decorators.js";
1
+ import { css as x, nothing as v, svg as f, html as h } from "lit";
2
+ import { property as n, customElement as u } from "lit/decorators.js";
3
3
  import { classMap as p } from "lit/directives/class-map.js";
4
- import { ifDefined as a } from "lit/directives/if-defined.js";
5
- import { c as k } from "./forced-colors-CTEDFRGa.js";
6
- import { H as m } from "./helix-element-BNEYeiys.js";
7
- const y = x`
4
+ import { ifDefined as r } from "lit/directives/if-defined.js";
5
+ import { c as b } from "./forced-colors-CTEDFRGa.js";
6
+ import { m as k } from "./aria-delegation-Doq6RRUy.js";
7
+ import { H as y } from "./helix-element-BNEYeiys.js";
8
+ const m = x`
8
9
  :host {
9
10
  display: inline;
10
11
  }
@@ -125,14 +126,14 @@ const y = x`
125
126
  border: 0;
126
127
  }
127
128
  `;
128
- var g = Object.defineProperty, b = Object.getOwnPropertyDescriptor, t = (e, n, s, l) => {
129
- for (var o = l > 1 ? void 0 : l ? b(n, s) : n, d = e.length - 1, c; d >= 0; d--)
130
- (c = e[d]) && (o = (l ? c(n, s, o) : c(o)) || o);
131
- return l && o && g(n, s, o), o;
129
+ var g = Object.defineProperty, w = Object.getOwnPropertyDescriptor, o = (e, a, s, l) => {
130
+ for (var t = l > 1 ? void 0 : l ? w(a, s) : a, d = e.length - 1, c; d >= 0; d--)
131
+ (c = e[d]) && (t = (l ? c(a, s, t) : c(t)) || t);
132
+ return l && t && g(a, s, t), t;
132
133
  };
133
- let r = class extends m {
134
+ let i = class extends k(y) {
134
135
  constructor() {
135
- super(...arguments), this.href = void 0, this.target = void 0, this.variant = "default", this.disabled = !1, this.download = void 0, this.rel = void 0;
136
+ super(...arguments), this.href = void 0, this.target = void 0, this.variant = "default", this.disabled = !1, this.download = void 0, this.rel = void 0, this.externalLabel = "(opens in new tab)";
136
137
  }
137
138
  // --- Event Handling ---
138
139
  /** @internal Blocks Enter and Space activation on disabled span. */
@@ -161,7 +162,7 @@ let r = class extends m {
161
162
  }
162
163
  /** @internal */
163
164
  _renderExternalIcon() {
164
- return this.target !== "_blank" ? f : h`
165
+ return this.target !== "_blank" ? v : h`
165
166
  <svg
166
167
  class="link__external-icon"
167
168
  part="external-icon"
@@ -173,9 +174,9 @@ let r = class extends m {
173
174
  stroke-linecap="round"
174
175
  stroke-linejoin="round"
175
176
  >
176
- ${v`<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /><polyline points="15 3 21 3 21 9" /><line x1="10" y1="14" x2="21" y2="3" />`}
177
+ ${f`<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /><polyline points="15 3 21 3 21 9" /><line x1="10" y1="14" x2="21" y2="3" />`}
177
178
  </svg>
178
- <span class="sr-only">(opens in new tab)</span>
179
+ <span class="sr-only">${this.externalLabel}</span>
179
180
  `;
180
181
  }
181
182
  // --- Render ---
@@ -184,13 +185,17 @@ let r = class extends m {
184
185
  link: !0,
185
186
  [`link--${this.variant}`]: this.variant !== "default",
186
187
  "link--disabled": this.disabled
187
- };
188
+ }, a = this.getAttribute("data-aria-label"), s = this.getAttribute("data-aria-labelledby"), l = this.getAttribute("data-aria-describedby"), t = this.getAttribute("data-aria-current");
188
189
  return this.disabled ? h`
189
190
  <span
190
191
  part="link"
191
192
  class=${p(e)}
192
193
  role="link"
193
194
  aria-disabled="true"
195
+ aria-label=${r(a ?? void 0)}
196
+ aria-labelledby=${r(s ?? void 0)}
197
+ aria-describedby=${r(l ?? void 0)}
198
+ aria-current=${r(t ?? void 0)}
194
199
  tabindex="0"
195
200
  @click=${this._handleClick}
196
201
  @keydown=${this._handleDisabledKeydown}
@@ -201,10 +206,14 @@ let r = class extends m {
201
206
  <a
202
207
  part="link"
203
208
  class=${p(e)}
204
- href=${a(this.href)}
205
- target=${a(this.target)}
206
- rel=${a(this._computeRel())}
207
- download=${a(this.download)}
209
+ href=${r(this.href)}
210
+ target=${r(this.target)}
211
+ rel=${r(this._computeRel())}
212
+ download=${r(this.download)}
213
+ aria-label=${r(a ?? void 0)}
214
+ aria-labelledby=${r(s ?? void 0)}
215
+ aria-describedby=${r(l ?? void 0)}
216
+ aria-current=${r(t ?? void 0)}
208
217
  @click=${this._handleClick}
209
218
  >
210
219
  <slot></slot>
@@ -213,29 +222,32 @@ let r = class extends m {
213
222
  `;
214
223
  }
215
224
  };
216
- r.styles = [y, k];
217
- t([
218
- i({ type: String })
219
- ], r.prototype, "href", 2);
220
- t([
221
- i({ type: String })
222
- ], r.prototype, "target", 2);
223
- t([
224
- i({ type: String, reflect: !0 })
225
- ], r.prototype, "variant", 2);
226
- t([
227
- i({ type: Boolean, reflect: !0 })
228
- ], r.prototype, "disabled", 2);
229
- t([
230
- i({ type: String })
231
- ], r.prototype, "download", 2);
232
- t([
233
- i({ type: String })
234
- ], r.prototype, "rel", 2);
235
- r = t([
225
+ i.styles = [m, b];
226
+ o([
227
+ n({ type: String })
228
+ ], i.prototype, "href", 2);
229
+ o([
230
+ n({ type: String })
231
+ ], i.prototype, "target", 2);
232
+ o([
233
+ n({ type: String, reflect: !0 })
234
+ ], i.prototype, "variant", 2);
235
+ o([
236
+ n({ type: Boolean, reflect: !0 })
237
+ ], i.prototype, "disabled", 2);
238
+ o([
239
+ n({ type: String })
240
+ ], i.prototype, "download", 2);
241
+ o([
242
+ n({ type: String })
243
+ ], i.prototype, "rel", 2);
244
+ o([
245
+ n({ type: String, attribute: "external-label" })
246
+ ], i.prototype, "externalLabel", 2);
247
+ i = o([
236
248
  u("hx-link")
237
- ], r);
249
+ ], i);
238
250
  export {
239
- r as H
251
+ i as H
240
252
  };
241
- //# sourceMappingURL=hx-link-C-O6vq0Q.js.map
253
+ //# sourceMappingURL=hx-link-CMnZRUtQ.js.map