@helixui/library 3.8.0 → 3.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.
- package/README.md +41 -0
- package/aaa-verdicts.json +2036 -0
- package/custom-elements.json +532 -569
- package/dist/components/hx-accordion/hx-accordion-item.d.ts.map +1 -1
- package/dist/components/hx-accordion/hx-accordion-item.styles.d.ts.map +1 -1
- package/dist/components/hx-accordion/index.js +1 -1
- package/dist/components/hx-alert/hx-alert.d.ts +0 -8
- package/dist/components/hx-alert/hx-alert.d.ts.map +1 -1
- package/dist/components/hx-alert/hx-alert.styles.d.ts.map +1 -1
- package/dist/components/hx-alert/index.js +1 -1
- package/dist/components/hx-avatar/hx-avatar.d.ts +4 -1
- package/dist/components/hx-avatar/hx-avatar.d.ts.map +1 -1
- package/dist/components/hx-avatar/hx-avatar.styles.d.ts.map +1 -1
- package/dist/components/hx-avatar/index.js +1 -1
- package/dist/components/hx-badge/hx-badge.d.ts.map +1 -1
- package/dist/components/hx-badge/hx-badge.styles.d.ts.map +1 -1
- package/dist/components/hx-badge/index.js +1 -1
- package/dist/components/hx-banner/hx-banner.d.ts +0 -8
- package/dist/components/hx-banner/hx-banner.d.ts.map +1 -1
- package/dist/components/hx-banner/hx-banner.styles.d.ts.map +1 -1
- package/dist/components/hx-banner/index.js +1 -1
- package/dist/components/hx-carousel/hx-carousel.d.ts.map +1 -1
- package/dist/components/hx-carousel/hx-carousel.styles.d.ts.map +1 -1
- package/dist/components/hx-carousel/index.js +1 -1
- package/dist/components/hx-checkbox/hx-checkbox.d.ts.map +1 -1
- package/dist/components/hx-checkbox/hx-checkbox.styles.d.ts.map +1 -1
- package/dist/components/hx-checkbox/index.js +1 -1
- package/dist/components/hx-clinical-status/hx-clinical-status.d.ts +7 -9
- package/dist/components/hx-clinical-status/hx-clinical-status.d.ts.map +1 -1
- package/dist/components/hx-clinical-status/hx-clinical-status.styles.d.ts.map +1 -1
- package/dist/components/hx-clinical-status/index.js +1 -1
- package/dist/components/hx-combobox/hx-combobox.d.ts.map +1 -1
- package/dist/components/hx-combobox/hx-combobox.styles.d.ts.map +1 -1
- package/dist/components/hx-combobox/index.js +1 -1
- package/dist/components/hx-date-picker/hx-date-picker.d.ts.map +1 -1
- package/dist/components/hx-date-picker/hx-date-picker.styles.d.ts.map +1 -1
- package/dist/components/hx-date-picker/index.js +1 -1
- package/dist/components/hx-drawer/hx-drawer.d.ts.map +1 -1
- package/dist/components/hx-drawer/hx-drawer.styles.d.ts.map +1 -1
- package/dist/components/hx-drawer/index.js +1 -1
- package/dist/components/hx-dropdown/hx-dropdown.styles.d.ts.map +1 -1
- package/dist/components/hx-dropdown/index.js +1 -1
- package/dist/components/hx-file-upload/hx-file-upload.d.ts +28 -0
- package/dist/components/hx-file-upload/hx-file-upload.d.ts.map +1 -1
- package/dist/components/hx-file-upload/hx-file-upload.styles.d.ts.map +1 -1
- package/dist/components/hx-file-upload/index.js +1 -1
- package/dist/components/hx-help-text/hx-help-text.d.ts.map +1 -1
- package/dist/components/hx-help-text/hx-help-text.styles.d.ts.map +1 -1
- package/dist/components/hx-help-text/index.js +1 -1
- package/dist/components/hx-icon/hx-icon.d.ts +108 -12
- package/dist/components/hx-icon/hx-icon.d.ts.map +1 -1
- package/dist/components/hx-icon/hx-icon.styles.d.ts.map +1 -1
- package/dist/components/hx-icon/index.js +1 -1
- package/dist/components/hx-link/hx-link.d.ts.map +1 -1
- package/dist/components/hx-link/hx-link.styles.d.ts.map +1 -1
- package/dist/components/hx-link/index.js +1 -1
- package/dist/components/hx-menu/hx-menu-item.d.ts.map +1 -1
- package/dist/components/hx-menu/hx-menu-item.styles.d.ts.map +1 -1
- package/dist/components/hx-menu/index.js +1 -1
- package/dist/components/hx-nav/hx-nav.d.ts.map +1 -1
- package/dist/components/hx-nav/hx-nav.styles.d.ts.map +1 -1
- package/dist/components/hx-nav/index.js +1 -1
- package/dist/components/hx-number-input/hx-number-input.d.ts.map +1 -1
- package/dist/components/hx-number-input/hx-number-input.styles.d.ts.map +1 -1
- package/dist/components/hx-number-input/index.js +1 -1
- package/dist/components/hx-overflow-menu/hx-overflow-menu.d.ts +5 -1
- package/dist/components/hx-overflow-menu/hx-overflow-menu.d.ts.map +1 -1
- package/dist/components/hx-overflow-menu/hx-overflow-menu.styles.d.ts.map +1 -1
- package/dist/components/hx-overflow-menu/index.js +1 -1
- package/dist/components/hx-phi-field/hx-phi-field.d.ts.map +1 -1
- package/dist/components/hx-phi-field/hx-phi-field.styles.d.ts.map +1 -1
- package/dist/components/hx-phi-field/index.js +1 -1
- package/dist/components/hx-radio-group/hx-radio-group.d.ts.map +1 -1
- package/dist/components/hx-radio-group/index.js +1 -1
- package/dist/components/hx-rating/hx-rating.d.ts.map +1 -1
- package/dist/components/hx-rating/hx-rating.styles.d.ts.map +1 -1
- package/dist/components/hx-rating/index.js +1 -1
- package/dist/components/hx-side-nav/hx-nav-item.d.ts.map +1 -1
- package/dist/components/hx-side-nav/hx-nav-item.styles.d.ts.map +1 -1
- package/dist/components/hx-side-nav/hx-side-nav.d.ts.map +1 -1
- package/dist/components/hx-side-nav/hx-side-nav.styles.d.ts.map +1 -1
- package/dist/components/hx-side-nav/index.js +1 -1
- package/dist/components/hx-slider/hx-slider.d.ts +28 -0
- package/dist/components/hx-slider/hx-slider.d.ts.map +1 -1
- package/dist/components/hx-slider/index.js +1 -1
- package/dist/components/hx-split-button/hx-split-button.d.ts.map +1 -1
- package/dist/components/hx-split-button/hx-split-button.styles.d.ts.map +1 -1
- package/dist/components/hx-split-button/index.js +1 -1
- package/dist/components/hx-stat/hx-stat.d.ts.map +1 -1
- package/dist/components/hx-stat/hx-stat.styles.d.ts.map +1 -1
- package/dist/components/hx-stat/index.js +1 -1
- package/dist/components/hx-steps/hx-step.d.ts.map +1 -1
- package/dist/components/hx-steps/hx-step.styles.d.ts.map +1 -1
- package/dist/components/hx-steps/index.js +1 -1
- package/dist/components/hx-tag/hx-tag.d.ts.map +1 -1
- package/dist/components/hx-tag/hx-tag.styles.d.ts.map +1 -1
- package/dist/components/hx-tag/index.js +1 -1
- package/dist/components/hx-time-picker/hx-time-picker.d.ts.map +1 -1
- package/dist/components/hx-time-picker/hx-time-picker.styles.d.ts.map +1 -1
- package/dist/components/hx-time-picker/index.js +1 -1
- package/dist/components/hx-toast/hx-toast.d.ts +0 -8
- package/dist/components/hx-toast/hx-toast.d.ts.map +1 -1
- package/dist/components/hx-toast/hx-toast.styles.d.ts.map +1 -1
- package/dist/components/hx-toast/index.js +1 -1
- package/dist/components/hx-top-nav/hx-top-nav.d.ts.map +1 -1
- package/dist/components/hx-top-nav/hx-top-nav.styles.d.ts.map +1 -1
- package/dist/components/hx-top-nav/index.js +1 -1
- package/dist/components/hx-tree-view/hx-tree-item.d.ts.map +1 -1
- package/dist/components/hx-tree-view/hx-tree-item.styles.d.ts.map +1 -1
- package/dist/components/hx-tree-view/index.js +1 -1
- package/dist/css/helix-all.css +116 -54
- package/dist/css/helix-core.css +19 -4
- package/dist/css/helix-feedback.css +15 -18
- package/dist/css/helix-forms.css +39 -12
- package/dist/css/helix-media.css +6 -3
- package/dist/css/helix-navigation.css +16 -7
- package/dist/css/helix-overlay.css +10 -0
- package/dist/css/helix-tokens.css +3 -2
- package/dist/css/helix-utility.css +5 -0
- package/dist/css/hx-alert.css +4 -8
- package/dist/css/hx-avatar.css +1 -2
- package/dist/css/hx-badge.css +5 -0
- package/dist/css/hx-banner.css +4 -8
- package/dist/css/hx-carousel.css +6 -3
- package/dist/css/hx-checkbox.css +4 -9
- package/dist/css/hx-clinical-status.css +4 -7
- package/dist/css/hx-combobox.css +8 -0
- package/dist/css/hx-date-picker.css +5 -0
- package/dist/css/hx-drawer.css +5 -0
- package/dist/css/hx-dropdown.css +5 -0
- package/dist/css/hx-file-upload.css +4 -0
- package/dist/css/hx-help-text.css +5 -0
- package/dist/css/hx-icon.css +7 -0
- package/dist/css/hx-link.css +1 -2
- package/dist/css/hx-nav.css +7 -0
- package/dist/css/hx-number-input.css +2 -3
- package/dist/css/hx-overflow-menu.css +5 -0
- package/dist/css/hx-phi-field.css +2 -3
- package/dist/css/hx-rating.css +6 -0
- package/dist/css/hx-side-nav.css +3 -5
- package/dist/css/hx-split-button.css +5 -0
- package/dist/css/hx-stat.css +1 -2
- package/dist/css/hx-tag.css +5 -0
- package/dist/css/hx-time-picker.css +5 -0
- package/dist/css/hx-toast.css +6 -0
- package/dist/css/hx-top-nav.css +1 -2
- package/dist/css/index.css +1 -1
- package/dist/css/manifest.json +4 -1
- package/dist/index.js +33 -33
- package/dist/shared/{hx-accordion-ZVzgDzTG.js → hx-accordion-DR--Ev4t.js} +48 -54
- package/dist/shared/hx-accordion-DR--Ev4t.js.map +1 -0
- package/dist/shared/{hx-alert-Bto8-TIi.js → hx-alert-C0axS32J.js} +40 -79
- package/dist/shared/hx-alert-C0axS32J.js.map +1 -0
- package/dist/shared/{hx-avatar-C9hOmlAb.js → hx-avatar-ChAYWnK8.js} +22 -24
- package/dist/shared/hx-avatar-ChAYWnK8.js.map +1 -0
- package/dist/shared/{hx-badge-DFL35nzi.js → hx-badge-vX-1cuLA.js} +16 -11
- package/dist/shared/hx-badge-vX-1cuLA.js.map +1 -0
- package/dist/shared/{hx-banner-fpRnciIO.js → hx-banner-PbHwFNSb.js} +51 -90
- package/dist/shared/hx-banner-PbHwFNSb.js.map +1 -0
- package/dist/shared/{hx-carousel-item-z1Lc24op.js → hx-carousel-item-BVIKgQ4i.js} +72 -102
- package/dist/shared/hx-carousel-item-BVIKgQ4i.js.map +1 -0
- package/dist/shared/{hx-checkbox-DcgyGS9V.js → hx-checkbox-DDSXXhps.js} +31 -38
- package/dist/shared/hx-checkbox-DDSXXhps.js.map +1 -0
- package/dist/shared/{hx-clinical-status-D3XQIOqX.js → hx-clinical-status-ZSVEc3Qg.js} +68 -87
- package/dist/shared/hx-clinical-status-ZSVEc3Qg.js.map +1 -0
- package/dist/shared/{hx-combobox-NgJaLbs2.js → hx-combobox-Be-mqOv4.js} +35 -45
- package/dist/shared/hx-combobox-Be-mqOv4.js.map +1 -0
- package/dist/shared/{hx-date-picker-0PtEav0K.js → hx-date-picker-CziP3Hm1.js} +15 -22
- package/dist/shared/hx-date-picker-CziP3Hm1.js.map +1 -0
- package/dist/shared/{hx-drawer-CM_upadk.js → hx-drawer-BlU2oX8-.js} +32 -36
- package/dist/shared/hx-drawer-BlU2oX8-.js.map +1 -0
- package/dist/shared/{hx-dropdown-xHwTJecv.js → hx-dropdown-DREqpIpm.js} +16 -11
- package/dist/shared/hx-dropdown-DREqpIpm.js.map +1 -0
- package/dist/shared/{hx-file-upload-D3rKROK5.js → hx-file-upload-CU5QGZSP.js} +137 -80
- package/dist/shared/hx-file-upload-CU5QGZSP.js.map +1 -0
- package/dist/shared/hx-help-text-CNaZ82LT.js +137 -0
- package/dist/shared/hx-help-text-CNaZ82LT.js.map +1 -0
- package/dist/shared/hx-icon-bxz9eB9a.js +386 -0
- package/dist/shared/hx-icon-bxz9eB9a.js.map +1 -0
- package/dist/shared/{hx-link-CMnZRUtQ.js → hx-link-BURSdYLp.js} +19 -26
- package/dist/shared/hx-link-BURSdYLp.js.map +1 -0
- package/dist/shared/{hx-menu-divider-A6Guuzi_.js → hx-menu-divider-g0grbWV9.js} +19 -31
- package/dist/shared/hx-menu-divider-g0grbWV9.js.map +1 -0
- package/dist/shared/{hx-nav-ChMTfn7o.js → hx-nav-GTsAZGOx.js} +46 -59
- package/dist/shared/hx-nav-GTsAZGOx.js.map +1 -0
- package/dist/shared/{hx-nav-item-ClN17f1y.js → hx-nav-item-CxE7Mp3M.js} +62 -64
- package/dist/shared/hx-nav-item-CxE7Mp3M.js.map +1 -0
- package/dist/shared/{hx-number-input-MggsT7F0.js → hx-number-input-Bvyc9kOi.js} +48 -53
- package/dist/shared/hx-number-input-Bvyc9kOi.js.map +1 -0
- package/dist/shared/{hx-overflow-menu-DFjJAziP.js → hx-overflow-menu-LrTteeR1.js} +32 -39
- package/dist/shared/hx-overflow-menu-LrTteeR1.js.map +1 -0
- package/dist/shared/{hx-phi-field-C19oxlrr.js → hx-phi-field-sZt_rYIL.js} +46 -66
- package/dist/shared/hx-phi-field-sZt_rYIL.js.map +1 -0
- package/dist/shared/{hx-radio-BY4zpwdh.js → hx-radio-BD_c9NJy.js} +51 -56
- package/dist/shared/{hx-radio-BY4zpwdh.js.map → hx-radio-BD_c9NJy.js.map} +1 -1
- package/dist/shared/{hx-rating-C3QP53k9.js → hx-rating-BGK4AxvI.js} +45 -71
- package/dist/shared/hx-rating-BGK4AxvI.js.map +1 -0
- package/dist/shared/{hx-slider-Blmv_rwS.js → hx-slider-CkOk5BCY.js} +83 -23
- package/dist/shared/{hx-slider-Blmv_rwS.js.map → hx-slider-CkOk5BCY.js.map} +1 -1
- package/dist/shared/{hx-split-button-CdNz1XAu.js → hx-split-button-Bg9FHrFK.js} +12 -16
- package/dist/shared/hx-split-button-Bg9FHrFK.js.map +1 -0
- package/dist/shared/{hx-stat-Gtw_SpK8.js → hx-stat-wKxbyep6.js} +22 -55
- package/dist/shared/hx-stat-wKxbyep6.js.map +1 -0
- package/dist/shared/{hx-step-CUzliIK_.js → hx-step-CyGQAuiB.js} +5 -25
- package/dist/shared/hx-step-CyGQAuiB.js.map +1 -0
- package/dist/shared/{hx-tag-C5aCUpVi.js → hx-tag-BqO6HY6V.js} +26 -21
- package/dist/shared/hx-tag-BqO6HY6V.js.map +1 -0
- package/dist/shared/{hx-time-picker-DfJkBwcX.js → hx-time-picker-if5Cl0Ei.js} +32 -38
- package/dist/shared/hx-time-picker-if5Cl0Ei.js.map +1 -0
- package/dist/shared/{hx-top-nav-CsTxOtVI.js → hx-top-nav-vP6oDWMV.js} +24 -38
- package/dist/shared/hx-top-nav-vP6oDWMV.js.map +1 -0
- package/dist/shared/{hx-tree-item-CXyspGxI.js → hx-tree-item-D8hwKd5m.js} +54 -57
- package/dist/shared/hx-tree-item-D8hwKd5m.js.map +1 -0
- package/dist/shared/{toast-factory-Dht3pVsw.js → toast-factory-DgnbFxVs.js} +127 -153
- package/dist/shared/toast-factory-DgnbFxVs.js.map +1 -0
- package/figma-inventory.json +283 -304
- package/package.json +8 -4
- package/dist/shared/hx-accordion-ZVzgDzTG.js.map +0 -1
- package/dist/shared/hx-alert-Bto8-TIi.js.map +0 -1
- package/dist/shared/hx-avatar-C9hOmlAb.js.map +0 -1
- package/dist/shared/hx-badge-DFL35nzi.js.map +0 -1
- package/dist/shared/hx-banner-fpRnciIO.js.map +0 -1
- package/dist/shared/hx-carousel-item-z1Lc24op.js.map +0 -1
- package/dist/shared/hx-checkbox-DcgyGS9V.js.map +0 -1
- package/dist/shared/hx-clinical-status-D3XQIOqX.js.map +0 -1
- package/dist/shared/hx-combobox-NgJaLbs2.js.map +0 -1
- package/dist/shared/hx-date-picker-0PtEav0K.js.map +0 -1
- package/dist/shared/hx-drawer-CM_upadk.js.map +0 -1
- package/dist/shared/hx-dropdown-xHwTJecv.js.map +0 -1
- package/dist/shared/hx-file-upload-D3rKROK5.js.map +0 -1
- package/dist/shared/hx-help-text-Xb2Yr8x2.js +0 -156
- package/dist/shared/hx-help-text-Xb2Yr8x2.js.map +0 -1
- package/dist/shared/hx-icon-fuVm4-bk.js +0 -283
- package/dist/shared/hx-icon-fuVm4-bk.js.map +0 -1
- package/dist/shared/hx-link-CMnZRUtQ.js.map +0 -1
- package/dist/shared/hx-menu-divider-A6Guuzi_.js.map +0 -1
- package/dist/shared/hx-nav-ChMTfn7o.js.map +0 -1
- package/dist/shared/hx-nav-item-ClN17f1y.js.map +0 -1
- package/dist/shared/hx-number-input-MggsT7F0.js.map +0 -1
- package/dist/shared/hx-overflow-menu-DFjJAziP.js.map +0 -1
- package/dist/shared/hx-phi-field-C19oxlrr.js.map +0 -1
- package/dist/shared/hx-rating-C3QP53k9.js.map +0 -1
- package/dist/shared/hx-split-button-CdNz1XAu.js.map +0 -1
- package/dist/shared/hx-stat-Gtw_SpK8.js.map +0 -1
- package/dist/shared/hx-step-CUzliIK_.js.map +0 -1
- package/dist/shared/hx-tag-C5aCUpVi.js.map +0 -1
- package/dist/shared/hx-time-picker-DfJkBwcX.js.map +0 -1
- package/dist/shared/hx-top-nav-CsTxOtVI.js.map +0 -1
- package/dist/shared/hx-tree-item-CXyspGxI.js.map +0 -1
- package/dist/shared/toast-factory-Dht3pVsw.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hx-radio-BY4zpwdh.js","sources":["../../src/components/hx-radio-group/hx-radio-group.styles.ts","../../src/components/hx-radio-group/hx-radio-group.ts","../../src/components/hx-radio-group/hx-radio.styles.ts","../../src/components/hx-radio-group/hx-radio.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const helixRadioGroupStyles = css`\n :host {\n display: block;\n }\n\n :host([disabled]) {\n opacity: var(--hx-opacity-disabled, 0.5);\n pointer-events: none;\n }\n\n /*\n * AAA 2.4.13 Focus Appearance — host-level focus ring for the group host\n * when it carries focus via roving tabindex. Token-driven: ≥2px width.\n */\n :host(:focus-visible) {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-radio-group-focus-ring-color, var(--hx-focus-ring-color, #0f7078));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n }\n\n * {\n box-sizing: border-box;\n }\n\n /* ─── Fieldset ─── */\n\n .fieldset {\n border: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: var(--hx-space-2, 0.5rem);\n font-family: var(--hx-radio-group-font-family, var(--hx-font-family-sans, sans-serif));\n }\n\n /* ─── Legend ─── */\n\n .fieldset__legend {\n display: flex;\n align-items: baseline;\n gap: var(--hx-space-1, 0.25rem);\n font-size: var(--hx-font-size-sm, 0.875rem);\n font-weight: var(--hx-font-weight-medium, 500);\n color: var(--hx-radio-group-label-color, var(--hx-color-text-strong, #202b39));\n line-height: var(--hx-line-height-normal, 1.5);\n padding: 0;\n margin-bottom: var(--hx-space-1, 0.25rem);\n }\n\n .fieldset__required-marker {\n color: var(--hx-radio-group-error-color, var(--hx-color-error-text, #c92a2a));\n font-weight: var(--hx-font-weight-bold, 700);\n }\n\n /* ─── Group Container ─── */\n\n .fieldset__group {\n display: flex;\n flex-direction: column;\n gap: var(--hx-radio-group-gap, var(--hx-space-3, 0.75rem));\n }\n\n :host([orientation='horizontal']) .fieldset__group {\n flex-direction: row;\n flex-wrap: wrap;\n }\n\n /* ─── Error State ─── */\n\n .fieldset--error .fieldset__legend {\n color: var(--hx-radio-group-error-color, var(--hx-color-error-text, #c92a2a));\n }\n\n /* ─── Help Text & Error Messages ─── */\n\n .fieldset__help-text {\n font-size: var(--hx-font-size-xs, 0.75rem);\n color: var(--hx-radio-group-help-text-color, var(--hx-color-text-muted, #66787b));\n line-height: var(--hx-line-height-normal, 1.5);\n }\n\n .fieldset__error {\n font-size: var(--hx-font-size-xs, 0.75rem);\n color: var(--hx-radio-group-error-color, var(--hx-color-error-text, #c92a2a));\n line-height: var(--hx-line-height-normal, 1.5);\n }\n\n /* ─── High Contrast Mode (forced-colors) ─── */\n\n @media (forced-colors: active) {\n .fieldset {\n border: none;\n }\n\n .fieldset__legend {\n color: CanvasText;\n }\n\n .fieldset--error .fieldset__legend {\n color: LinkText;\n }\n\n :host([disabled]) {\n opacity: 1;\n }\n\n :host([disabled]) .fieldset__legend {\n color: GrayText;\n }\n\n .fieldset__help-text {\n color: GrayText;\n }\n\n .fieldset__error {\n color: LinkText;\n }\n }\n`;\n","import { html, nothing, type PropertyValues } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { devWarn } from '../../utils/dev-warn.js';\nimport { HelixElement, createIdCounter } from '../../base/index.js';\nimport { FormMixin } from '../../mixins/FormMixin.js';\nimport { helixRadioGroupStyles } from './hx-radio-group.styles.js';\nimport { forcedColorsField } from '../../styles/forced-colors.js';\nimport type { HelixRadio } from './hx-radio.js';\nimport {\n installAriaIdrefMirror,\n resolveIdrefTokens,\n supportsIdrefElementReferences,\n type AriaIdrefMirrorHandle,\n} from '../../utils/aria-idref.js';\n\nconst _nextRadioGroupId = createIdCounter('hx-radio-group');\n\n/**\n * Reads visible text from a shadow wrapper that contains a `<slot>`. Prefers\n * the slot's flattened assigned-nodes text when light DOM is projected,\n * otherwise falls back to the wrapper's own `textContent` (so property-driven\n * fallback content rendered inside the slot is still readable). Codex\n * round-23 P2 (Finding C): a wrapper-element `textContent` read does NOT\n * cross the shadow → light-DOM slot boundary, so the previous direct\n * `textContent` mirror returned empty when a consumer slotted help/error\n * content instead of using the property.\n */\nfunction readSlottedOrShadowText(wrapper: Element): string {\n const slot = wrapper.querySelector('slot');\n if (slot) {\n const assigned = (slot as HTMLSlotElement).assignedNodes({ flatten: true });\n if (assigned.length > 0) {\n return assigned\n .map((node) => node.textContent ?? '')\n .join('')\n .trim();\n }\n }\n return (wrapper.textContent ?? '').trim();\n}\n\n/** Detail for the hx-change event dispatched by hx-radio-group. */\nexport interface HxRadioGroupChangeDetail {\n value: string;\n checked: boolean;\n}\n\n/**\n * A form-associated radio group that manages a set of `<hx-radio>` children.\n *\n * @summary Form-associated radio group with label, validation, help text, and keyboard navigation.\n *\n * @tag hx-radio-group\n *\n * @slot - `<hx-radio>` elements.\n * @slot error - Custom error content (overrides the error property).\n * @slot help-text - Custom help text content (overrides the helpText property).\n *\n * @fires {CustomEvent<{value: string, checked: boolean}>} hx-change - Dispatched when the selected radio changes.\n * @fires {CustomEvent<{value: string}>} hx-radio-select - Internal event dispatched by `hx-radio` when selected; consumed by the group.\n *\n * @csspart fieldset - The fieldset wrapper.\n * @csspart legend - The legend/label.\n * @csspart group - The container for radio items.\n * @csspart error - The error message.\n * @csspart help-text - The help text.\n *\n * @cssprop [--hx-radio-group-gap=var(--hx-space-3, 0.75rem)] - Gap between radio items.\n * @cssprop [--hx-radio-group-label-color=var(--hx-color-neutral-700, #313E4B)] - Label text color.\n * @cssprop [--hx-radio-group-error-color=var(--hx-color-error-500, #E5493E)] - Error message color.\n * @cssprop [--hx-radio-group-help-text-color=var(--hx-color-neutral-500, #66787B)] - Help text color.\n * @cssprop [--hx-opacity-disabled] - Opacity.\n * @cssprop [--hx-space-2] - Spacing token.\n * @cssprop [--hx-radio-group-font-family=var(--hx-font-family-sans)] - CSS custom property.\n * @cssprop [--hx-font-family-sans] - Font family.\n * @cssprop [--hx-space-1] - Spacing token.\n * @cssprop [--hx-font-size-sm] - Font size.\n * @cssprop [--hx-font-weight-medium] - Font weight.\n * @cssprop [--hx-color-neutral-700] - Color.\n * @cssprop [--hx-line-height-normal] - Line height.\n * @cssprop [--hx-color-error-text] - Color.\n * @cssprop [--hx-font-weight-bold] - Font weight.\n * @cssprop [--hx-space-3] - Spacing token.\n * @cssprop [--hx-font-size-xs] - Font size.\n * @cssprop [--hx-color-neutral-500] - Color.\n * @aaa-certified 2026-05-08\n * @aaa-criteria 1.4.6, 1.4.9, 2.1.3, 2.3.3, 2.4.12, 2.4.13, 2.5.5, 3.2.5, 3.3.6, forced-colors, apg-keyboard\n * @aaa-audit src/components/hx-radio-group/AAA-AUDIT.md\n * @keyboard-contract navigate=Arrow; activate=Space; disabled-suppresses=true\n * @aria-pattern radiogroup\n * @aria-pattern-source https://www.w3.org/WAI/ARIA/apg/patterns/radio/\n * @forced-colors-supported true\n * @stability stable\n * @since 3.7.0\n * @form-associated true\n * @theme-aware true\n * @brand-aware true\n * @drupal-sdc-eligible true\n * @react-wrapper-status complete\n * @figma-component-name hx-radio-group\n * @priority-tier P0\n * @phi-handles false\n * @clinical-context none\n */\n@customElement('hx-radio-group')\nexport class HelixRadioGroup extends FormMixin(HelixElement) {\n static override styles = [helixRadioGroupStyles, forcedColorsField];\n\n // ─── Form Association ───\n\n /**\n * Enables ElementInternals form association for this component.\n * @internal\n */\n static override formAssociated = true;\n\n // ─── Properties ───\n\n /**\n * The selected radio's value.\n * @attr value\n */\n @property({ type: String, reflect: true })\n value = '';\n\n /**\n * The name used for form submission.\n * @attr name\n */\n @property({ type: String, reflect: true })\n name = '';\n\n /**\n * The fieldset legend/label text.\n * @attr label\n */\n @property({ type: String })\n label = '';\n\n /**\n * Whether a selection is required for form submission.\n * @attr required\n */\n @property({ type: Boolean, reflect: true })\n required = false;\n\n /**\n * Whether the entire group is disabled.\n * @attr disabled\n */\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n /**\n * Error message to display. When set, the group enters an error state.\n * @attr error\n */\n @property({ type: String })\n error = '';\n\n /**\n * Help text displayed below the group for guidance.\n * @attr help-text\n */\n @property({ type: String, attribute: 'help-text' })\n helpText = '';\n\n /**\n * Layout orientation of the radio items.\n * @attr orientation\n */\n @property({ type: String, reflect: true })\n orientation: 'vertical' | 'horizontal' = 'vertical';\n\n /**\n * Queries the rendered group container element within the shadow root.\n * @internal\n */\n private get _groupEl(): HTMLElement | null {\n return this.renderRoot?.querySelector('.fieldset__group') ?? null;\n }\n\n /**\n * Tracks whether the error slot has assigned content.\n * @internal\n */\n @state() private _hasErrorSlot = false;\n\n /**\n * Tracks whether the help-text slot has assigned content.\n * @internal\n */\n @state() private _hasHelpSlot = false;\n\n /**\n * Whether the platform supports IDL element references on `ElementInternals`.\n * Drives the render-time branch between modern (host-canonical via\n * internals) and fallback (inner fieldset is the announced surface).\n * Codex round-17 P1.\n * @internal\n */\n @state() private _supportsIdrefRefs = true;\n\n // ─── Internal IDs ───\n\n /**\n * Unique identifier for this radio group instance used in ARIA attributes.\n * @internal\n */\n private _groupId = _nextRadioGroupId();\n /**\n * Unique identifier for the help text element, used in aria-describedby.\n * @internal\n */\n private _helpTextId = `${this._groupId}-help`;\n /**\n * Unique identifier for the error element, used in aria-describedby.\n * @internal\n */\n private _errorId = `${this._groupId}-error`;\n\n // ─── Slot Handlers ───\n\n /**\n * Handles slotchange events on the error slot to detect assigned content.\n * @internal\n */\n private _handleErrorSlotChange(e: Event): void {\n if (!(e.target instanceof HTMLSlotElement)) return;\n this._hasErrorSlot = e.target.assignedNodes({ flatten: true }).length > 0;\n // Codex round-23 P2 (Finding C): re-tune the in-place text observer over\n // the new assigned-node set so in-place `textContent` rewrites of slotted\n // error nodes resync `internals.ariaDescription` on the no-IDL-ref\n // fallback path. `slotchange` only fires when the *node set* changes;\n // mutating an already-assigned node's text does not, so a separate\n // observer is required. Mirrors the round-21 P3 label-slot observer in\n // hx-checkbox-group.\n this._installErrorSlotTextObserver(e.target);\n this._syncHostAriaSemantics();\n }\n\n /**\n * Handles slotchange events on the help-text slot to detect assigned content.\n * Codex aria-group-2 finding: slot-only help text was not contributing to\n * `aria-describedby` because the wrapper was conditionally rendered on the\n * `helpText` property alone.\n * @internal\n */\n private _handleHelpSlotChange(e: Event): void {\n if (!(e.target instanceof HTMLSlotElement)) return;\n this._hasHelpSlot = e.target.assignedNodes({ flatten: true }).length > 0;\n // Codex round-23 P2 (Finding C): same pattern as the error slot — keep\n // `internals.ariaDescription` in sync with in-place text edits on already\n // assigned help-text nodes.\n this._installHelpSlotTextObserver(e.target);\n this._syncHostAriaSemantics();\n }\n\n /**\n * Watches assigned `<slot name=\"help-text\">` nodes for in-place text\n * mutations so the no-IDL-ref fallback `internals.ariaDescription` stays in\n * sync when a framework rewrites `textContent` of an already-assigned node\n * without replacing it. `slotchange` does NOT fire for those mutations, so\n * a separate observer is required. Codex round-23 P2 (Finding C).\n * @internal\n */\n private _helpSlotTextObserver: MutationObserver | null = null;\n\n /**\n * Watches assigned `<slot name=\"error\">` nodes for in-place text mutations\n * so the no-IDL-ref fallback `internals.ariaDescription` stays in sync when\n * a framework rewrites `textContent` of an already-assigned node without\n * replacing it. Codex round-23 P2 (Finding C).\n * @internal\n */\n private _errorSlotTextObserver: MutationObserver | null = null;\n\n /**\n * (Re-)installs the mutation observer over the current set of assigned\n * help-text-slot nodes. Codex round-23 P2 (Finding C).\n * @internal\n */\n private _installHelpSlotTextObserver(slot: HTMLSlotElement | null): void {\n this._helpSlotTextObserver?.disconnect();\n if (!slot) {\n this._helpSlotTextObserver = null;\n return;\n }\n const observer = new MutationObserver(() => {\n this._syncHostAriaSemantics();\n });\n slot.assignedNodes().forEach((node) => {\n observer.observe(node, {\n characterData: true,\n childList: true,\n subtree: true,\n });\n });\n this._helpSlotTextObserver = observer;\n }\n\n /**\n * (Re-)installs the mutation observer over the current set of assigned\n * error-slot nodes. Codex round-23 P2 (Finding C).\n * @internal\n */\n private _installErrorSlotTextObserver(slot: HTMLSlotElement | null): void {\n this._errorSlotTextObserver?.disconnect();\n if (!slot) {\n this._errorSlotTextObserver = null;\n return;\n }\n const observer = new MutationObserver(() => {\n this._syncHostAriaSemantics();\n });\n slot.assignedNodes().forEach((node) => {\n observer.observe(node, {\n characterData: true,\n childList: true,\n subtree: true,\n });\n });\n this._errorSlotTextObserver = observer;\n }\n\n /**\n * Handle for the shared IDREF observer. See `installAriaIdrefMirror()`.\n * @internal\n */\n private _ariaMirror: AriaIdrefMirrorHandle | null = null;\n\n /**\n * Deferred copy of `error` driven through reactive state so the persistent\n * live region can re-announce on transitions without direct DOM mutation.\n * Codex round-1 finding #10.\n * @internal\n */\n @state() private _announcedError = '';\n\n /**\n * Tracks group-suppressed child radios so detached children can have the\n * flag cleared. Defense-in-depth symmetry with `hx-checkbox-group` —\n * `hx-radio` is not form-associated today, so the flag is inert, but the\n * parity keeps the contract identical between the two group/child families\n * for any future form-association on `hx-radio`. Codex round-3 finding #1.\n * @internal\n */\n private _suppressedChildren = new WeakSet<HelixRadio>();\n\n /**\n * Snapshot of children captured before each `slotchange` so removed\n * children can be released from suppression (WeakSet is non-enumerable).\n * @internal\n */\n private _previousRadios: HelixRadio[] = [];\n\n /**\n * Last value of `aria-labelledby` we wrote to the host. Used to distinguish\n * external (consumer) attribute mutations from our own internal augmentation\n * writes when refreshing the host-attribute fallback. Codex round-10 P2:\n * without this guard, an internal mutation observer fire would re-read the\n * already-augmented host attribute as if it were consumer-supplied, causing\n * legend/help/error ids to leak forward as \"consumer tokens\" forever.\n * @internal\n */\n private _lastWrittenLabelledBy: string | null = null;\n /** @internal — see `_lastWrittenLabelledBy`. */\n private _lastWrittenDescribedBy: string | null = null;\n /**\n * Most recently observed *consumer-supplied* `aria-labelledby` baseline (the\n * set of tokens the consumer themselves wrote on the host). Refreshed only\n * when the host attribute changes via an external write — internal writes\n * leave the baseline untouched.\n * @internal\n */\n private _consumerLabelledBy: string | null = null;\n /** @internal — see `_consumerLabelledBy`. */\n private _consumerDescribedBy: string | null = null;\n\n // ─── Lifecycle ───\n\n override connectedCallback(): void {\n super.connectedCallback();\n // Codex round-17 P1: detect IDL element-references API support so\n // render() can branch the fieldset between presentational (modern) and\n // group-with-aria (fallback) treatments.\n this._supportsIdrefRefs = supportsIdrefElementReferences(this._internals);\n this.addEventListener('hx-radio-select', this._handleRadioSelect);\n this.addEventListener('keydown', this._handleKeydown);\n // Seed root-independent semantics from connect so the host announces the\n // radiogroup role before first paint.\n this._syncHostAriaSemantics();\n this._ariaMirror = installAriaIdrefMirror(this, () => {\n this._syncHostAriaSemantics();\n });\n // Codex round-10 P2 (parity with hx-checkbox-group): re-apply child\n // suppression on reattach so a detached-then-reinserted group with the\n // same `<hx-radio>` children still claims them. `_groupedSuppress` is\n // currently inert on `hx-radio`, but the lifecycle parity keeps the\n // contract identical for any future form-association on the radio child.\n const existing = this._getRadios();\n if (existing.length > 0) {\n existing.forEach((radio) => {\n radio._groupedSuppress = true;\n this._suppressedChildren.add(radio);\n });\n this._previousRadios = existing;\n }\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener('hx-radio-select', this._handleRadioSelect);\n this.removeEventListener('keydown', this._handleKeydown);\n this._ariaMirror?.disconnect();\n this._ariaMirror = null;\n // Codex round-7 finding #6: tear down the per-child disabled observer so\n // detached radios don't keep a strong reference back into a group whose\n // host is being torn down.\n this._childDisabledObserver?.disconnect();\n this._childDisabledObserver = null;\n // Codex round-23 P2 (Finding C): tear down the help/error slot text\n // observers so detached assigned nodes stop firing into a torn-down host.\n this._helpSlotTextObserver?.disconnect();\n this._helpSlotTextObserver = null;\n this._errorSlotTextObserver?.disconnect();\n this._errorSlotTextObserver = null;\n // Release suppression on every previously-tracked child so they regain\n // stand-alone behaviour if re-parented or kept in the document after the\n // group is removed. Codex round-3 finding #1 (defense-in-depth).\n this._previousRadios.forEach((radio) => {\n if (this._suppressedChildren.has(radio)) {\n radio._groupedSuppress = false;\n this._suppressedChildren.delete(radio);\n }\n });\n this._previousRadios = [];\n }\n\n override updated(changedProperties: PropertyValues<this>): void {\n super.updated(changedProperties);\n if (changedProperties.has('value')) {\n this._internals.setFormValue(this.value || null);\n this._syncRadios();\n }\n if (changedProperties.has('disabled')) {\n this._syncRadios();\n }\n // Host-elevated ARIA semantics — see _syncHostAriaSemantics.\n this._syncHostAriaSemantics();\n // Codex round-1 finding #10: drive re-announcement from reactive state\n // so the persistent live region stays in the shadow tree across error\n // transitions. Direct `textContent` mutation would delete the slot\n // subtree the renderer just produced.\n if (changedProperties.has('error')) {\n const previousError = changedProperties.get('error') as string;\n if (previousError && this.error) {\n // Error→error: clear then re-set after rAF so AT re-announces.\n this._announcedError = '';\n requestAnimationFrame(() => {\n this._announcedError = this.error;\n });\n } else {\n this._announcedError = this.error;\n }\n }\n }\n\n /**\n * Mirrors radiogroup semantics onto the host via ElementInternals so that\n * consumer-supplied `aria-label`, `aria-labelledby`, and `aria-describedby`\n * on `<hx-radio-group>` reach the announced control. The codex aria-group-2\n * finding identified that the inner `<fieldset>` was the announced node and\n * the host's external IDREF tokens could not cross the shadow boundary.\n * @internal\n */\n private _syncHostAriaSemantics(): void {\n const internals = this._internals;\n internals.role = 'radiogroup';\n internals.ariaRequired = this.required ? 'true' : 'false';\n internals.ariaInvalid = !internals.validity.valid ? 'true' : 'false';\n internals.ariaDisabled = this.disabled ? 'true' : 'false';\n internals.ariaOrientation = this.orientation === 'horizontal' ? 'horizontal' : 'vertical';\n\n const hostAriaLabel = this.getAttribute('aria-label')?.trim() || '';\n\n // Resolve the candidate label/desc element references once — the IDL-ref\n // path consumes them as `Element[]`, the fallback path mirrors their `id`\n // tokens onto the host's `aria-labelledby` / `aria-describedby` attributes\n // so that AT can still locate the shadow help/error wrappers via the\n // stable shadow-internal ids.\n const internalLegend = this.shadowRoot?.getElementById(`${this._groupId}-legend`);\n const helpEl = this.shadowRoot?.getElementById(this._helpTextId);\n const errorEl = this.shadowRoot?.getElementById(this._errorId);\n\n // Codex round-10 P2: refresh the consumer baseline only when the host\n // attribute moved due to an *external* write. Compare the live attribute\n // against our last-written snapshot — if it differs, the consumer wrote.\n // This prevents internally-augmented values (containing legend/help/error\n // ids we appended) from being re-read as if they were consumer tokens.\n const liveLabelledBy = this.getAttribute('aria-labelledby');\n if (liveLabelledBy !== this._lastWrittenLabelledBy) {\n this._consumerLabelledBy = liveLabelledBy;\n }\n const liveDescribedBy = this.getAttribute('aria-describedby');\n if (liveDescribedBy !== this._lastWrittenDescribedBy) {\n this._consumerDescribedBy = liveDescribedBy;\n }\n const externalLabelTokens = this._consumerLabelledBy;\n const externalDescTokens = this._consumerDescribedBy;\n\n const labelEls = resolveIdrefTokens(this, externalLabelTokens);\n // Codex round-35 finding (CR major + codex follow-up): `aria-labelledby`\n // is only \"effective\" when at least one IDREF resolves. A typo or\n // transiently-missing target must NOT erase the visible label — fall back\n // to `label` so the radiogroup keeps a name on both paths.\n const hasEffectiveLabelledBy = labelEls.length > 0;\n if (hostAriaLabel) {\n internals.ariaLabel = hostAriaLabel;\n } else if (!hasEffectiveLabelledBy) {\n internals.ariaLabel = this.label || null;\n } else {\n internals.ariaLabel = null;\n }\n if (labelEls.length === 0 && !hostAriaLabel && this.label && internalLegend) {\n labelEls.push(internalLegend);\n }\n\n const descEls = resolveIdrefTokens(this, externalDescTokens);\n const hasError = !!(this.error || this._hasErrorSlot);\n // Codex round-16 P2: drop help text from the describedby chain while an\n // error is active. The render path hides the help wrapper in that state\n // (`?hidden=${!hasHelp || hasError}`); appending the hidden node to host\n // semantics would have AT announce stale guidance ahead of the\n // validation error. Mirrors hx-checkbox-group, hx-switch, hx-checkbox.\n if (helpEl && !hasError && (this.helpText || this._hasHelpSlot)) {\n descEls.push(helpEl);\n }\n if (errorEl && hasError) {\n descEls.push(errorEl);\n }\n\n // Branch off the cached `_supportsIdrefRefs` (seeded at connect by the\n // platform probe) so tests can force the fallback branch by flipping the\n // flag. TODO(codex round-19 follow-up): re-probe on `adoptedCallback` if\n // we ever support cross-document moves; today the cached value is set\n // once at connect.\n if (this._supportsIdrefRefs) {\n type InternalsWithRefs = ElementInternals & {\n ariaLabelledByElements: Element[] | null;\n ariaDescribedByElements: Element[] | null;\n };\n const refsInternals = internals as InternalsWithRefs;\n refsInternals.ariaLabelledByElements = labelEls.length > 0 ? labelEls : null;\n refsInternals.ariaDescribedByElements = descEls.length > 0 ? descEls : null;\n // Clear any stale fallback `ariaDescription` string in case a prior sync\n // ran on the fallback path (e.g. tests flipping `_supportsIdrefRefs`).\n // The modern path uses element references exclusively; coexisting strings\n // would cause AT to announce the description twice.\n internals.ariaDescription = null;\n } else {\n // ─── No-IDL-ref fallback (codex round-19 P1) ───\n // The IDL element-references API is unavailable, so internal shadow\n // help/error/legend wrappers cannot be projected onto the host\n // accessibility node via `internals.aria*Elements`.\n //\n // Codex round-19 P1: keep the host as the canonical accessible-container\n // surface on BOTH modern and fallback paths. Earlier rounds promoted the\n // inner fieldset to `role=\"group\"` here and tried to splice consumer\n // light-DOM ids together with shadow-internal ids on the fieldset's\n // `aria-labelledby` / `aria-describedby`. That created two nested\n // accessible containers (host radiogroup → inner group → controls) AND\n // the spliced shadow IDREFs could never resolve to consumer light-DOM\n // targets, so the inner fieldset's \"external label\" was silently broken.\n //\n // The correct trade-off on legacy engines (notably Firefox today): the\n // host owns the role + ARIA strings (via `internals.role`,\n // `internals.ariaLabel`, and the host attribute mirror below); we accept\n // a documented loss of *internal* legend/help/error references and rely\n // on consumer-supplied light-DOM tokens, which resolve correctly in the\n // host's containing root. The inner fieldset stays presentational on\n // both paths so AT announces a single accessible container.\n const consumerLabelIds = new Set((externalLabelTokens?.split(/\\s+/) ?? []).filter(Boolean));\n const consumerDescIds = new Set((externalDescTokens?.split(/\\s+/) ?? []).filter(Boolean));\n\n // Host attributes: ONLY consumer tokens (never shadow-internal ids —\n // those cannot resolve across the shadow boundary).\n //\n // Codex round-35 (medium): mirror consumer tokens to the host attribute\n // ONLY when at least one token resolves to a real element. A broken\n // `aria-labelledby` (typo, target not yet attached) would otherwise\n // erase the accessible name on legacy engines per ARIA priority\n // (aria-labelledby > aria-label > internals.ariaLabel). When tokens\n // don't resolve, clear the host attribute so the `internals.ariaLabel`\n // fallback the modern path set above (`this.label`) wins.\n const hostLabel = hasEffectiveLabelledBy\n ? [...consumerLabelIds].filter(Boolean).join(' ')\n : '';\n const liveLabel = this.getAttribute('aria-labelledby');\n if (hostLabel) {\n if (liveLabel !== hostLabel) {\n this.setAttribute('aria-labelledby', hostLabel);\n }\n this._lastWrittenLabelledBy = hostLabel;\n } else if (liveLabel !== null) {\n // Codex round-36 (medium): when consumer-supplied tokens don't\n // resolve (`!hasEffectiveLabelledBy`), actively clear the host\n // attribute even if WE didn't write it. Per ARIA priority\n // (aria-labelledby > aria-label > internals.ariaLabel), a broken\n // consumer-authored attribute on the announced surface erases the\n // legend on legacy engines. The original tokens remain cached in\n // `_consumerLabelledBy` so they replay on a future sync if the\n // target later attaches and `hasEffectiveLabelledBy` flips back to\n // true.\n this.removeAttribute('aria-labelledby');\n this._lastWrittenLabelledBy = null;\n }\n\n const hostDesc = [...consumerDescIds].filter(Boolean).join(' ') || '';\n const liveDesc = this.getAttribute('aria-describedby');\n if (hostDesc) {\n if (liveDesc !== hostDesc) {\n this.setAttribute('aria-describedby', hostDesc);\n }\n this._lastWrittenDescribedBy = hostDesc;\n } else if (liveDesc !== null && this._lastWrittenDescribedBy !== null) {\n this.removeAttribute('aria-describedby');\n this._lastWrittenDescribedBy = null;\n }\n\n // Codex round-22 P1 #2: on the no-IDL-ref fallback path, consumer-supplied\n // describedby tokens reach the host (above) but the *internal* shadow\n // help/error wrappers cannot be referenced from light-DOM IDREFs. Mirror\n // their `textContent` into `internals.ariaDescription` so the host's\n // accessible description still surfaces the live help/error strings on\n // legacy engines (Firefox today). The string-form description hook is\n // independent of element references and survives the shadow boundary.\n // Empty strings are normalized to `null` so AT does not announce an\n // empty description.\n // Codex round-23 P2 (Finding C): use a slot-aware text read so slotted\n // help/error content surfaces into `internals.ariaDescription` —\n // wrapper `textContent` does NOT cross the shadow → light-DOM slot\n // boundary. The new help/error slot text observers (above) keep this\n // in sync when a framework rewrites the slotted node's textContent in\n // place.\n const helpText =\n helpEl && !hasError && (this.helpText || this._hasHelpSlot)\n ? readSlottedOrShadowText(helpEl)\n : '';\n const errorText = errorEl && hasError ? readSlottedOrShadowText(errorEl) : '';\n const internalDescriptionText = [helpText, errorText].filter(Boolean).join(' ');\n internals.ariaDescription = internalDescriptionText || null;\n }\n }\n\n override firstUpdated(changedProperties: PropertyValues<this>): void {\n super.firstUpdated(changedProperties);\n this._syncRadios();\n this._previousRadios = this._getRadios();\n // Codex round-7 finding #6: observe in-place `disabled` mutations on the\n // initial child set. The slotchange handler refreshes this on every slot\n // pass, but firstUpdated covers the case where the initial children are\n // never re-slotted (e.g. static markup mounted once).\n this._installChildDisabledObservers();\n // WCAG 4.1.2: warn when no accessible name is available for the radio group.\n // The fieldset needs either a label prop (rendered as <legend>) or an aria-label\n // attribute on the host element so screen readers can identify the group.\n if (!this.label && !this.getAttribute('aria-label')) {\n devWarn(\n 'hx-radio-group',\n 'No accessible label provided. Set the `label` attribute or add `aria-label` to the host element. An unlabeled radio group violates WCAG 2.1 AA (4.1.2 Name, Role, Value).',\n );\n }\n }\n\n // ─── Radio Management ───\n\n /**\n * Cached list of child hx-radio elements; invalidated on slot change.\n * @internal\n */\n private _cachedRadios: HelixRadio[] | null = null;\n /**\n * Stores each radio's individual disabled state before group-level disabling overrides it.\n * @internal\n */\n private _individualDisabledStates = new WeakMap<HelixRadio, boolean>();\n\n /**\n * Returns all child hx-radio elements, using the cache when available.\n * @internal\n */\n private _getRadios(): HelixRadio[] {\n if (!this._cachedRadios) {\n this._cachedRadios = Array.from(this.querySelectorAll('hx-radio')) as HelixRadio[];\n }\n return this._cachedRadios;\n }\n\n /**\n * Returns only the child hx-radio elements that are not disabled.\n * @internal\n */\n private _getEnabledRadios(): HelixRadio[] {\n return this._getRadios().filter((radio) => !radio.disabled && !this.disabled);\n }\n\n /**\n * Synchronizes checked state, disabled state, and roving tabindex across all child radios.\n * @internal\n */\n private _syncRadios(): void {\n const radios = this._getRadios();\n const enabledRadios = this._getEnabledRadios();\n\n radios.forEach((radio) => {\n // Codex round-3 finding #1 (defense-in-depth symmetry with hx-checkbox):\n // mark every group-managed child so any future form-association change\n // on `hx-radio` is automatically suppressed inside the group. The flag\n // is currently inert on `hx-radio` (which is not form-associated).\n radio._groupedSuppress = true;\n this._suppressedChildren.add(radio);\n\n const isChecked = radio.value === this.value && this.value !== '';\n radio.checked = isChecked;\n\n if (this.disabled) {\n // Store individual disabled state before overriding with group disabled\n if (!this._individualDisabledStates.has(radio)) {\n this._individualDisabledStates.set(radio, radio.disabled);\n }\n radio.disabled = true;\n } else {\n // Restore individual disabled state when group is re-enabled\n const originalDisabled = this._individualDisabledStates.get(radio);\n if (originalDisabled !== undefined) {\n radio.disabled = originalDisabled;\n this._individualDisabledStates.delete(radio);\n }\n }\n });\n\n // Roving tabindex management\n const checkedRadio = enabledRadios.find((r) => r.checked);\n radios.forEach((radio) => {\n radio.tabIndex = -1;\n });\n\n if (checkedRadio) {\n checkedRadio.tabIndex = 0;\n } else if (enabledRadios.length > 0) {\n const firstRadio = enabledRadios[0];\n if (firstRadio) {\n firstRadio.tabIndex = 0;\n }\n }\n }\n\n // ─── Event Handling ───\n\n /**\n * Handles the internal hx-radio-select event to update the group's selected value.\n * @internal\n */\n private _handleRadioSelect = (e: Event): void => {\n if (!(e instanceof CustomEvent)) return;\n e.stopPropagation();\n\n const newValue = (e.detail as { value: string }).value;\n if (newValue === this.value) {\n return;\n }\n\n this.value = newValue;\n this._handleInteractionInput();\n // Reactive update in updated() will call setFormValue, _syncRadios, _updateValidity\n\n /**\n * Dispatched when the selected radio changes.\n * @event hx-change\n */\n this.dispatchEvent(\n new CustomEvent<{ value: string; checked: boolean }>('hx-change', {\n bubbles: true,\n composed: true,\n detail: { value: this.value, checked: true },\n }),\n );\n };\n\n /**\n * Handles keyboard navigation (arrow keys, Home, End, Space) within the radio group.\n * @internal\n */\n private _handleKeydown = (e: KeyboardEvent): void => {\n const enabledRadios = this._getEnabledRadios();\n if (enabledRadios.length === 0) {\n return;\n }\n\n const isHandledKey = [\n 'ArrowUp',\n 'ArrowDown',\n 'ArrowLeft',\n 'ArrowRight',\n ' ',\n 'Home',\n 'End',\n ].includes(e.key);\n if (!isHandledKey) {\n return;\n }\n\n e.preventDefault();\n\n // Space: select the currently focused radio without moving focus\n if (e.key === ' ') {\n const targetRadio = (e.target as Element)?.closest?.('hx-radio') as HelixRadio | null;\n if (targetRadio && !targetRadio.disabled) {\n targetRadio.dispatchEvent(\n new CustomEvent<{ value: string }>('hx-radio-select', {\n bubbles: true,\n composed: true,\n detail: { value: targetRadio.value },\n }),\n );\n }\n return;\n }\n\n const targetRadio = (e.target as Element)?.closest?.('hx-radio') as HelixRadio | null;\n const currentIndex = targetRadio\n ? enabledRadios.indexOf(targetRadio)\n : enabledRadios.findIndex((radio) => radio.checked);\n\n let nextIndex: number;\n if (e.key === 'Home') {\n nextIndex = 0;\n } else if (e.key === 'End') {\n nextIndex = enabledRadios.length - 1;\n } else if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {\n nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % enabledRadios.length;\n } else {\n nextIndex = currentIndex <= 0 ? enabledRadios.length - 1 : currentIndex - 1;\n }\n\n const nextRadio = enabledRadios[nextIndex];\n if (nextRadio) {\n nextRadio.focus();\n nextRadio.dispatchEvent(\n new CustomEvent<{ value: string }>('hx-radio-select', {\n bubbles: true,\n composed: true,\n detail: { value: nextRadio.value },\n }),\n );\n }\n };\n\n /**\n * Handles slotchange events on the default slot. Refreshes the cached\n * radio list, reconciles `value`/`setFormValue`/validity against the new\n * children, then re-tunes the per-child disabled observer so in-place\n * `radio.disabled = true` mutations trigger the same reconcile pass.\n *\n * Codex round-2 finding #3: previously this handler only invalidated the\n * cache and re-synced child state, so removing the currently-selected\n * radio (or disabling it, or adding a new `checked` radio) left\n * `this.value` and the submitted form value pointing at stale state and\n * `_updateValidity` was never re-run.\n *\n * Codex round-7 finding #6: an in-place `selectedRadio.disabled = true`\n * never re-entered this reconciler (the radio remained slotted), so the\n * group could keep submitting a disabled value and stay valid until some\n * unrelated slot mutation kicked things over. The per-child observer\n * installed at the end of this method re-runs the same reconcile logic on\n * every `disabled` attribute mutation.\n * @internal\n */\n private _handleSlotChange(): void {\n this._cachedRadios = null;\n this._reconcileChildren();\n this._installChildDisabledObservers();\n }\n\n /**\n * Per-child `disabled` attribute observer. Mirrors the slotchange\n * reconcile pipeline so an in-place `radio.disabled = true` collapses the\n * group's `value`/form participation/validity. Round-7 finding #6.\n *\n * One observer is shared across all currently-slotted radios; it is\n * disconnected and re-attached on every slotchange so children that have\n * left the group stop firing reconcile passes.\n * @internal\n */\n private _childDisabledObserver: MutationObserver | null = null;\n\n /**\n * Re-runs the slotchange reconcile pass without touching the disabled\n * observer wiring. Called from the slotchange handler and from the\n * per-child disabled observer; factored out so both entry points share\n * the same value/formValue/validity reconciliation path. Round-7 #6.\n * @internal\n */\n private _reconcileChildren(): void {\n // Snapshot of the previous slot pass so we can release `_groupedSuppress`\n // on any radio that has left this group. Codex round-3 finding #1\n // (defense-in-depth symmetry).\n const previous = this._previousRadios;\n\n // Reconcile against currently-slotted children BEFORE _syncRadios runs.\n // _syncRadios() unconditionally overwrites `radio.checked` from `this.value`,\n // which would clobber an externally-set `checked=true` on a newly-added\n // radio when the group's current value is empty. We therefore peek at the\n // raw children first to detect \"pre-checked\" adoption, then commit through\n // the normal sync pipeline.\n //\n // Codex round-12 P2: prefer a checked radio whose value differs from the\n // current group value — that is the \"newly externally checked\" candidate.\n // The naive `.find(r.checked && !r.disabled)` returned the first checked\n // radio in DOM order, which is typically the previously-selected one whose\n // value already matches `this.value`, so an appended `<hx-radio checked>`\n // with a different value would be rejected by the next `_syncRadios()`\n // pass. Falling back to \"any checked radio\" still handles the\n // empty-group / first-mount case.\n const candidates = this._getRadios().filter((r) => r.checked && !r.disabled);\n const externallyChecked = candidates.find((r) => r.value !== this.value) ?? candidates[0];\n if (externallyChecked && externallyChecked.value !== this.value) {\n this.value = externallyChecked.value;\n }\n\n this._syncRadios();\n\n // After sync, the group's `value` and the children's `checked` flags are\n // canonical. If the previously-selected radio was removed or disabled,\n // `selected` becomes undefined and `reconciled` collapses to '', clearing\n // form participation.\n //\n // Codex round-10 P1: when the group itself is disabled, every radio is\n // force-disabled by `_syncRadios`. The post-sync `selected` lookup excludes\n // disabled radios, so it would always collapse to '' and wipe the user's\n // selection across a disable→enable cycle. Group-level disable is a\n // visual/interaction lock — it must preserve `value` and form participation\n // (matching native `<fieldset disabled>` semantics where the form value is\n // simply omitted from submission while disabled). When `this.disabled` is\n // true we leave `value` and form state untouched; the next disabled→false\n // pass through `updated()` runs `_syncRadios` again, which restores each\n // radio's individual disabled flag and re-checks the matching value.\n if (this.disabled) {\n // Codex round-18 P2: even while disabled, if the previously-selected\n // radio has been *removed* from the DOM (vs merely disabled by the\n // group), `this.value` references a value that no slotted child\n // carries, so the group would resubmit a stale option after re-enable.\n // We must clear `value` in that case while still preserving selection\n // across pure disable→enable cycles where the radio still exists.\n // Detect \"selected option still in DOM\" by membership lookup on the\n // raw children, ignoring their disabled flag (since group-level\n // disable forces them all disabled by _syncRadios).\n const presentValues = this._getRadios().map((r) => r.value);\n if (this.value && !presentValues.includes(this.value)) {\n this.value = '';\n }\n // Validity reflects the (possibly-cleared) value; setFormValue is\n // omitted because a disabled control shouldn't contribute to form\n // submission.\n this._updateValidity();\n } else {\n const selected = this._getRadios().find((r) => r.checked && !r.disabled);\n const reconciled = selected?.value ?? '';\n if (reconciled !== this.value) {\n this.value = reconciled;\n }\n // Always re-run setFormValue + validity. `value` may be stable while\n // membership/disabled-state changed, and a freshly-adopted pre-checked\n // radio still needs its value pushed to the form.\n this._internals.setFormValue(this.value || null);\n this._updateValidity();\n }\n\n // Release suppression on any radio that left the group. Then refresh the\n // snapshot for the next slotchange. Codex round-3 finding #1.\n const current = new Set(this._getRadios());\n previous.forEach((radio) => {\n if (!current.has(radio) && this._suppressedChildren.has(radio)) {\n radio._groupedSuppress = false;\n this._suppressedChildren.delete(radio);\n }\n });\n this._previousRadios = this._getRadios();\n }\n\n /**\n * Installs (or re-installs) a single MutationObserver across the current\n * set of slotted `<hx-radio>` children, listening for `disabled` attribute\n * mutations. Round-7 finding #6: in-place `disabled = true` never reaches\n * the slotchange-driven reconciler otherwise.\n * @internal\n */\n private _installChildDisabledObservers(): void {\n this._childDisabledObserver?.disconnect();\n const radios = this._getRadios();\n if (radios.length === 0) {\n this._childDisabledObserver = null;\n return;\n }\n const observer = new MutationObserver((mutations) => {\n // Filter to disabled-attribute mutations only — the observer is scoped\n // to attributeFilter:['disabled'] but the callback still fires per\n // mutation record, so guard cheaply against noise.\n if (!mutations.some((m) => m.attributeName === 'disabled')) return;\n this._reconcileChildren();\n });\n radios.forEach((radio) => {\n observer.observe(radio, {\n attributes: true,\n attributeFilter: ['disabled'],\n });\n });\n this._childDisabledObserver = observer;\n }\n\n // ─── Form Integration ───\n\n /**\n * Updates the ElementInternals validity state based on the required constraint and current value.\n * @internal\n */\n override _updateValidity(): void {\n if (this.required && !this.value) {\n // Codex round-35 finding (CR major): anchor `setValidity()` to an actual\n // radio. The presentational `.fieldset__group` div is `role=\"none\"` and\n // not focusable, so anchoring there leaves UA validation UI / error\n // recovery disconnected from the interactive surface.\n //\n // Codex round-35 follow-up (Low #4): drop the unconditional `radios[0]`\n // tier — a disabled radio is not focusable and cannot host UA validation\n // UI either. Prefer checked-enabled, then any enabled, then the\n // presentational group as a last resort (still better than `undefined`\n // because it pins UA error positioning to the visible group bounds).\n const anchor =\n this._getRadios().find((radio) => radio.checked && !radio.disabled) ??\n this._getEnabledRadios()[0] ??\n this._groupEl ??\n undefined;\n this._internals.setValidity(\n { valueMissing: true },\n this.error || 'Please select an option.',\n anchor,\n );\n } else {\n this._internals.setValidity({});\n }\n // Codex round-1 finding #6: re-sync host ARIA after every setValidity().\n this._syncHostAriaSemantics();\n }\n\n /** @internal */\n protected override _onFormReset(): void {\n this.value = '';\n this._internals.setFormValue(null);\n this._syncRadios();\n this._resetInteractionState();\n }\n\n /** @internal */\n protected override _onFormStateRestore(\n state: File | string | FormData | null,\n _mode: 'restore' | 'autocomplete',\n ): void {\n if (typeof state === 'string') {\n this.value = state;\n }\n }\n\n /** @internal */\n protected override _onFormDisabled(disabled: boolean): void {\n this.disabled = disabled;\n }\n\n // ─── Render ───\n\n override render() {\n const hasError = !!this.error || this._hasErrorSlot;\n const hasHelp = !!this.helpText || this._hasHelpSlot;\n const legendId = `${this._groupId}-legend`;\n\n const fieldsetClasses = {\n fieldset: true,\n 'fieldset--error': hasError,\n 'fieldset--disabled': this.disabled,\n 'fieldset--required': this.required,\n };\n\n // Codex round-19 P1: inner fieldset is presentational on BOTH the modern\n // and no-IDL-ref fallback paths so AT announces exactly one accessible\n // container (the host). Earlier rounds promoted the fieldset to\n // `role=\"group\"` on the fallback branch and spliced shadow-internal ids\n // into its aria-* attributes; that produced nested host→fieldset groups\n // and broke external IDREFs (shadow ids cannot resolve across the\n // boundary). The host carries the radiogroup role via ElementInternals\n // on both paths.\n return html`\n <fieldset\n part=\"fieldset\"\n class=${classMap(fieldsetClasses)}\n role=\"presentation\"\n aria-orientation=${this.orientation === 'horizontal' ? 'horizontal' : nothing}\n >\n ${this.label\n ? html`\n <legend part=\"legend\" class=\"fieldset__legend\" id=${legendId}>\n ${this.label}\n ${this.required\n ? html`<span class=\"fieldset__required-marker\" aria-hidden=\"true\">*</span>`\n : nothing}\n </legend>\n `\n : nothing}\n\n <div part=\"group\" class=\"fieldset__group\" role=\"none\">\n <slot @slotchange=${this._handleSlotChange}></slot>\n </div>\n\n <!--\n Persistent error live region. role=\"alert\" is set from first paint\n so the WAI-ARIA contract for live updates is honoured: content\n changes in place rather than the container being toggled.\n -->\n <div\n part=\"error\"\n class=\"fieldset__error\"\n id=${this._errorId}\n role=\"alert\"\n ?hidden=${!hasError}\n >\n <slot name=\"error\" @slotchange=${this._handleErrorSlotChange}\n >${this._announcedError}</slot\n >\n </div>\n\n <!--\n Persistent help-text container. Rendered whenever the property OR\n the slot has content; hidden when an error is present so guidance\n does not compete with validation feedback. Always in the shadow\n tree so the host's aria-describedby chain is stable.\n -->\n <div\n part=\"help-text\"\n class=\"fieldset__help-text\"\n id=${this._helpTextId}\n ?hidden=${!hasHelp || hasError}\n >\n <slot name=\"help-text\" @slotchange=${this._handleHelpSlotChange}>${this.helpText}</slot>\n </div>\n </fieldset>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-radio-group': HelixRadioGroup;\n }\n}\n\n/** Canonical type alias for the hx-radio-group component. */\nexport type HxRadioGroup = HelixRadioGroup;\n","import { css } from 'lit';\n\nexport const helixRadioStyles = css`\n :host {\n display: block;\n /*\n * Suppress the browser default ~1px host outline. Without this the formal\n * AAA audit harness records a sub-2px outline on the focused host and\n * reports WCAG 2.4.13 Partially Supports — even though the explicit 2px\n * focus ring is correctly painted on the inner .radio__control element.\n * Mirrors the hx-checkbox / hx-switch / hx-toggle-button fix.\n */\n outline: none;\n }\n\n :host([disabled]) {\n opacity: var(--hx-opacity-disabled, 0.5);\n pointer-events: none;\n }\n\n * {\n box-sizing: border-box;\n }\n\n .radio {\n display: inline-flex;\n align-items: center;\n gap: var(--hx-space-2, 0.5rem);\n /* WCAG 2.5.5 (healthcare mandate): minimum 44px touch target height */\n min-height: var(--hx-touch-target-min, 2.75rem);\n cursor: pointer;\n position: relative;\n font-family: var(--hx-radio-font-family, var(--hx-font-family-sans, sans-serif));\n }\n\n .radio--disabled {\n cursor: not-allowed;\n }\n\n /* ─── Hidden Native Input ─── */\n\n .radio__input {\n position: absolute;\n width: var(--hx-space-px);\n height: var(--hx-space-px);\n padding: 0;\n margin: calc(var(--hx-space-px) * -1);\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n }\n\n /* ─── Visual Radio Circle ─── */\n\n .radio__control {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--hx-radio-size, var(--hx-size-5, 1.25rem));\n height: var(--hx-radio-size, var(--hx-size-5, 1.25rem));\n border: var(--hx-border-width-medium, 2px) solid\n var(--hx-radio-border-color, var(--hx-color-border-strong, #66787b));\n border-radius: var(--hx-border-radius-full, 9999px);\n background-color: var(--hx-radio-bg, var(--hx-color-surface-default, #ffffff));\n transition:\n border-color var(--hx-transition-fast, 150ms ease),\n background-color var(--hx-transition-fast, 150ms ease),\n box-shadow var(--hx-transition-fast, 150ms ease);\n flex-shrink: 0;\n }\n\n /* ─── Inner Dot ─── */\n\n .radio__dot {\n width: calc(var(--hx-radio-size, var(--hx-size-5, 1.25rem)) * 0.4);\n height: calc(var(--hx-radio-size, var(--hx-size-5, 1.25rem)) * 0.4);\n border-radius: var(--hx-border-radius-full, 9999px);\n background-color: var(--hx-radio-dot-color, var(--hx-color-text-on-primary, #ffffff));\n transform: scale(0);\n transition: transform var(--hx-transition-fast, 150ms ease);\n }\n\n /* ─── Checked State ─── */\n\n .radio--checked .radio__control {\n border-color: var(--hx-radio-checked-border-color, var(--hx-color-action-primary-bg, #0f7078));\n background-color: var(--hx-radio-checked-bg, var(--hx-color-action-primary-bg, #0f7078));\n }\n\n .radio--checked .radio__dot {\n transform: scale(1);\n }\n\n /* ─── Focus State ─── */\n\n :host(:focus-visible) .radio__control {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-radio-focus-ring-color, var(--hx-focus-ring-color, #0f7078));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n }\n\n /* ─── Hover State ─── */\n\n .radio:not(.radio--disabled):not(.radio--checked):hover .radio__control {\n border-color: var(--hx-radio-hover-border-color, var(--hx-color-border-strong, #66787b));\n }\n\n /* ─── Label ─── */\n\n .radio__label {\n font-size: var(--hx-font-size-md, 1rem);\n color: var(--hx-radio-label-color, var(--hx-color-text-strong, #202b39));\n line-height: var(--hx-line-height-normal, 1.5);\n user-select: none;\n -webkit-user-select: none;\n }\n\n /* ─── Reduced Motion ─── */\n\n @media (prefers-reduced-motion: reduce) {\n .radio__control,\n .radio__dot {\n transition: none;\n }\n }\n\n /* ─── High Contrast Mode (forced-colors) ─── */\n\n @media (forced-colors: active) {\n .radio__control {\n forced-color-adjust: none;\n background-color: ButtonFace;\n border: 2px solid ButtonText;\n }\n\n :host(:focus-visible) .radio__control {\n outline: 3px solid Highlight;\n outline-offset: 2px;\n }\n\n .radio--checked .radio__control {\n background-color: Highlight;\n border-color: Highlight;\n }\n\n .radio--checked .radio__dot {\n background-color: HighlightText;\n }\n\n :host([disabled]) {\n opacity: 1;\n }\n\n :host([disabled]) .radio__control {\n border-color: GrayText;\n background-color: ButtonFace;\n }\n\n :host([disabled]) .radio--checked .radio__control {\n background-color: GrayText;\n border-color: GrayText;\n }\n\n :host([disabled]) .radio__label {\n color: GrayText;\n }\n\n .radio__label {\n color: CanvasText;\n }\n }\n`;\n","import { html, type PropertyValues } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property } from 'lit/decorators.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { HelixElement, createIdCounter } from '../../base/index.js';\nimport { helixRadioStyles } from './hx-radio.styles.js';\nimport { forcedColorsField } from '../../styles/forced-colors.js';\n\nconst _nextRadioId = createIdCounter('hx-radio');\n\n/**\n * An individual radio button, designed to be used inside a `<hx-radio-group>`.\n *\n * @summary Presentational radio button managed by its parent radio group.\n *\n * @tag hx-radio\n *\n * @slot - Custom label content (overrides the label property).\n *\n * @csspart radio - The visual radio circle.\n * @csspart label - The label text.\n *\n * @cssprop [--hx-radio-size=var(--hx-size-5, 1.25rem)] - Radio circle size.\n * @cssprop [--hx-radio-border-color=var(--hx-color-neutral-300, #B6BFB9)] - Radio border color.\n * @cssprop [--hx-radio-checked-bg=var(--hx-color-primary-500, #429797)] - Checked background color.\n * @cssprop [--hx-radio-checked-border-color=var(--hx-color-primary-500, #429797)] - Checked border color.\n * @cssprop [--hx-radio-dot-color=var(--hx-color-neutral-0, #ffffff)] - Inner dot color when checked.\n * @cssprop [--hx-radio-focus-ring-color=var(--hx-focus-ring-color, #6AB1B1)] - Focus ring color.\n * @cssprop [--hx-radio-label-color=var(--hx-color-neutral-700, #313E4B)] - Label text color.\n */\n@customElement('hx-radio')\nexport class HelixRadio extends HelixElement {\n static override styles = [helixRadioStyles, forcedColorsField];\n\n // ─── Properties ───\n\n /**\n * The value this radio represents.\n * @attr value\n */\n @property({ type: String })\n value = '';\n\n /**\n * Visible label text for the radio.\n * @attr label\n */\n @property({ type: String })\n label = '';\n\n /**\n * Whether this radio is disabled.\n * @attr disabled\n */\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n /**\n * Whether this radio is checked. Managed by the parent group.\n * @attr checked\n */\n @property({ type: Boolean, reflect: true })\n checked = false;\n\n /**\n * Set by `hx-radio-group` to mark this child as group-managed. `hx-radio` is\n * not form-associated (the group is the sole form participant), so this\n * flag is currently inert on this element. It exists for symmetry with\n * `hx-checkbox._groupedSuppress` so the group/child contract is identical\n * across both selection-control families and any future form-association\n * change on `hx-radio` is automatically suppressed inside a group. Codex\n * round-3 finding #1 (defense-in-depth).\n * @internal\n */\n set _groupedSuppress(value: boolean) {\n this.__groupedSuppress = value;\n }\n get _groupedSuppress(): boolean {\n return this.__groupedSuppress;\n }\n /** @internal */\n private __groupedSuppress = false;\n\n // ─── Lifecycle ───\n\n override connectedCallback(): void {\n super.connectedCallback();\n // ARIA role and state are projected to the accessibility tree via\n // ElementInternals rather than imperative setAttribute calls. This keeps\n // the host element's attribute surface clean — consumers cannot accidentally\n // override role/aria-* — and aligns with the standards pattern for\n // custom elements. See https://wicg.github.io/aom/spec/aria-reflection.html.\n this._internals.role = 'radio';\n this._syncAriaState();\n }\n\n override updated(changedProperties: PropertyValues<this>): void {\n super.updated(changedProperties);\n if (\n changedProperties.has('checked') ||\n changedProperties.has('label') ||\n changedProperties.has('disabled')\n ) {\n this._syncAriaState();\n }\n }\n\n /**\n * Mirror reactive ARIA state onto ElementInternals. Setting a value to `null`\n * removes it from the accessibility tree (matching the previous\n * removeAttribute behavior).\n *\n * WCAG 4.1.2: expose the label text as ariaLabel on the host so assistive\n * technology can associate the visible label with the radio role. The label\n * span lives inside Shadow DOM and aria-labelledby cannot cross shadow\n * boundaries, so ariaLabel on the host is the correct pattern here.\n *\n * WCAG 4.1.2: omit ariaDisabled entirely when not disabled. Setting\n * aria-disabled=\"false\" is verbose and unnecessary — omission is preferred.\n *\n * @internal\n */\n private _syncAriaState(): void {\n this._internals.ariaChecked = String(this.checked);\n this._internals.ariaLabel = this.label || null;\n this._internals.ariaDisabled = this.disabled ? 'true' : null;\n }\n\n // ─── Internal IDs ───\n\n /** @internal */\n private _inputId = _nextRadioId();\n\n // ─── Event Handling ───\n\n /** @internal */\n private _handleClick(): void {\n if (this.disabled) {\n return;\n }\n\n /**\n * Internal event dispatched to signal selection to the parent group.\n * Not part of the public API.\n * @internal\n */\n this.dispatchEvent(\n new CustomEvent<{ value: string }>('hx-radio-select', {\n bubbles: true,\n composed: true,\n detail: { value: this.value },\n }),\n );\n }\n\n // ─── Render ───\n\n override render() {\n const classes = {\n radio: true,\n 'radio--checked': this.checked,\n 'radio--disabled': this.disabled,\n };\n\n return html`\n <div class=${classMap(classes)} @click=${this._handleClick}>\n <input\n class=\"radio__input\"\n type=\"radio\"\n id=${this._inputId}\n .checked=${this.checked}\n ?disabled=${this.disabled}\n tabindex=\"-1\"\n aria-hidden=\"true\"\n />\n <span part=\"radio\" class=\"radio__control\" aria-hidden=\"true\">\n <span class=\"radio__dot\"></span>\n </span>\n <span part=\"label\" class=\"radio__label\">\n <slot>${this.label}</slot>\n </span>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-radio': HelixRadio;\n }\n}\n\n/** Canonical type alias for the hx-radio component. */\nexport type HxRadio = HelixRadio;\n"],"names":["helixRadioGroupStyles","css","_nextRadioGroupId","createIdCounter","readSlottedOrShadowText","wrapper","slot","assigned","node","HelixRadioGroup","FormMixin","HelixElement","newValue","enabledRadios","targetRadio","_b","_a","_d","_c","currentIndex","radio","nextIndex","nextRadio","observer","supportsIdrefElementReferences","installAriaIdrefMirror","existing","changedProperties","internals","hostAriaLabel","internalLegend","helpEl","errorEl","liveLabelledBy","liveDescribedBy","externalLabelTokens","externalDescTokens","labelEls","resolveIdrefTokens","hasEffectiveLabelledBy","descEls","hasError","refsInternals","consumerLabelIds","consumerDescIds","hostLabel","liveLabel","hostDesc","liveDesc","helpText","errorText","internalDescriptionText","radios","isChecked","originalDisabled","checkedRadio","firstRadio","previous","candidates","r","externallyChecked","presentValues","selected","reconciled","current","mutations","m","anchor","state","_mode","disabled","hasHelp","legendId","fieldsetClasses","html","classMap","nothing","forcedColorsField","__decorateClass","property","customElement","helixRadioStyles","_nextRadioId","HelixRadio","value","classes"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hx-radio-BD_c9NJy.js","sources":["../../src/components/hx-radio-group/hx-radio-group.styles.ts","../../src/components/hx-radio-group/hx-radio-group.ts","../../src/components/hx-radio-group/hx-radio.styles.ts","../../src/components/hx-radio-group/hx-radio.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const helixRadioGroupStyles = css`\n :host {\n display: block;\n }\n\n :host([disabled]) {\n opacity: var(--hx-opacity-disabled, 0.5);\n pointer-events: none;\n }\n\n /*\n * AAA 2.4.13 Focus Appearance — host-level focus ring for the group host\n * when it carries focus via roving tabindex. Token-driven: ≥2px width.\n */\n :host(:focus-visible) {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-radio-group-focus-ring-color, var(--hx-focus-ring-color, #0f7078));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n }\n\n * {\n box-sizing: border-box;\n }\n\n /* ─── Fieldset ─── */\n\n .fieldset {\n border: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: var(--hx-space-2, 0.5rem);\n font-family: var(--hx-radio-group-font-family, var(--hx-font-family-sans, sans-serif));\n }\n\n /* ─── Legend ─── */\n\n .fieldset__legend {\n display: flex;\n align-items: baseline;\n gap: var(--hx-space-1, 0.25rem);\n font-size: var(--hx-font-size-sm, 0.875rem);\n font-weight: var(--hx-font-weight-medium, 500);\n color: var(--hx-radio-group-label-color, var(--hx-color-text-strong, #202b39));\n line-height: var(--hx-line-height-normal, 1.5);\n padding: 0;\n margin-bottom: var(--hx-space-1, 0.25rem);\n }\n\n .fieldset__required-marker {\n color: var(--hx-radio-group-error-color, var(--hx-color-error-text, #c92a2a));\n font-weight: var(--hx-font-weight-bold, 700);\n }\n\n /* ─── Group Container ─── */\n\n .fieldset__group {\n display: flex;\n flex-direction: column;\n gap: var(--hx-radio-group-gap, var(--hx-space-3, 0.75rem));\n }\n\n :host([orientation='horizontal']) .fieldset__group {\n flex-direction: row;\n flex-wrap: wrap;\n }\n\n /* ─── Error State ─── */\n\n .fieldset--error .fieldset__legend {\n color: var(--hx-radio-group-error-color, var(--hx-color-error-text, #c92a2a));\n }\n\n /* ─── Help Text & Error Messages ─── */\n\n .fieldset__help-text {\n font-size: var(--hx-font-size-xs, 0.75rem);\n color: var(--hx-radio-group-help-text-color, var(--hx-color-text-muted, #66787b));\n line-height: var(--hx-line-height-normal, 1.5);\n }\n\n .fieldset__error {\n font-size: var(--hx-font-size-xs, 0.75rem);\n color: var(--hx-radio-group-error-color, var(--hx-color-error-text, #c92a2a));\n line-height: var(--hx-line-height-normal, 1.5);\n }\n\n /* ─── High Contrast Mode (forced-colors) ─── */\n\n @media (forced-colors: active) {\n .fieldset {\n border: none;\n }\n\n .fieldset__legend {\n color: CanvasText;\n }\n\n .fieldset--error .fieldset__legend {\n color: LinkText;\n }\n\n :host([disabled]) {\n opacity: 1;\n }\n\n :host([disabled]) .fieldset__legend {\n color: GrayText;\n }\n\n .fieldset__help-text {\n color: GrayText;\n }\n\n .fieldset__error {\n color: LinkText;\n }\n }\n`;\n","import { html, nothing, type PropertyValues } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { devWarn } from '../../utils/dev-warn.js';\nimport { HelixElement, createIdCounter } from '../../base/index.js';\nimport { FormMixin } from '../../mixins/FormMixin.js';\nimport { helixRadioGroupStyles } from './hx-radio-group.styles.js';\nimport { forcedColorsField } from '../../styles/forced-colors.js';\nimport type { HelixRadio } from './hx-radio.js';\nimport {\n installAriaIdrefMirror,\n resolveIdrefTokens,\n supportsIdrefElementReferences,\n type AriaIdrefMirrorHandle,\n} from '../../utils/aria-idref.js';\n\nconst _nextRadioGroupId = createIdCounter('hx-radio-group');\n\n/**\n * Reads visible text from a shadow wrapper that contains a `<slot>`. Prefers\n * the slot's flattened assigned-nodes text when light DOM is projected,\n * otherwise falls back to the wrapper's own `textContent` (so property-driven\n * fallback content rendered inside the slot is still readable). Codex\n * round-23 P2 (Finding C): a wrapper-element `textContent` read does NOT\n * cross the shadow → light-DOM slot boundary, so the previous direct\n * `textContent` mirror returned empty when a consumer slotted help/error\n * content instead of using the property.\n */\nfunction readSlottedOrShadowText(wrapper: Element): string {\n const slot = wrapper.querySelector('slot');\n if (slot) {\n const assigned = (slot as HTMLSlotElement).assignedNodes({ flatten: true });\n if (assigned.length > 0) {\n return assigned\n .map((node) => node.textContent ?? '')\n .join('')\n .trim();\n }\n }\n return (wrapper.textContent ?? '').trim();\n}\n\n/** Detail for the hx-change event dispatched by hx-radio-group. */\nexport interface HxRadioGroupChangeDetail {\n value: string;\n checked: boolean;\n}\n\n/**\n * A form-associated radio group that manages a set of `<hx-radio>` children.\n *\n * @summary Form-associated radio group with label, validation, help text, and keyboard navigation.\n *\n * @tag hx-radio-group\n *\n * @slot - `<hx-radio>` elements.\n * @slot error - Custom error content (overrides the error property).\n * @slot help-text - Custom help text content (overrides the helpText property).\n *\n * @fires {CustomEvent<{value: string, checked: boolean}>} hx-change - Dispatched when the selected radio changes.\n * @fires {CustomEvent<{value: string}>} hx-radio-select - Internal event dispatched by `hx-radio` when selected; consumed by the group.\n *\n * @csspart fieldset - The fieldset wrapper.\n * @csspart legend - The legend/label.\n * @csspart group - The container for radio items.\n * @csspart error - The error message.\n * @csspart help-text - The help text.\n *\n * @cssprop [--hx-radio-group-gap=var(--hx-space-3, 0.75rem)] - Gap between radio items.\n * @cssprop [--hx-radio-group-label-color=var(--hx-color-neutral-700, #313E4B)] - Label text color.\n * @cssprop [--hx-radio-group-error-color=var(--hx-color-error-500, #E5493E)] - Error message color.\n * @cssprop [--hx-radio-group-help-text-color=var(--hx-color-neutral-500, #66787B)] - Help text color.\n * @cssprop [--hx-opacity-disabled] - Opacity.\n * @cssprop [--hx-space-2] - Spacing token.\n * @cssprop [--hx-radio-group-font-family=var(--hx-font-family-sans)] - CSS custom property.\n * @cssprop [--hx-font-family-sans] - Font family.\n * @cssprop [--hx-space-1] - Spacing token.\n * @cssprop [--hx-font-size-sm] - Font size.\n * @cssprop [--hx-font-weight-medium] - Font weight.\n * @cssprop [--hx-color-neutral-700] - Color.\n * @cssprop [--hx-line-height-normal] - Line height.\n * @cssprop [--hx-color-error-text] - Color.\n * @cssprop [--hx-font-weight-bold] - Font weight.\n * @cssprop [--hx-space-3] - Spacing token.\n * @cssprop [--hx-font-size-xs] - Font size.\n * @cssprop [--hx-color-neutral-500] - Color.\n * @aaa-certified 2026-05-08\n * @aaa-criteria 1.4.6, 1.4.9, 2.1.3, 2.3.3, 2.4.12, 2.4.13, 2.5.5, 3.2.5, 3.3.6, forced-colors, apg-keyboard\n * @aaa-audit src/components/hx-radio-group/AAA-AUDIT.md\n * @keyboard-contract navigate=Arrow; activate=Space; disabled-suppresses=true\n * @aria-pattern radiogroup\n * @aria-pattern-source https://www.w3.org/WAI/ARIA/apg/patterns/radio/\n * @forced-colors-supported true\n * @stability stable\n * @since 3.7.0\n * @form-associated true\n * @theme-aware true\n * @brand-aware true\n * @drupal-sdc-eligible true\n * @react-wrapper-status complete\n * @figma-component-name hx-radio-group\n * @priority-tier P0\n * @phi-handles false\n * @clinical-context none\n */\n@customElement('hx-radio-group')\nexport class HelixRadioGroup extends FormMixin(HelixElement) {\n static override styles = [helixRadioGroupStyles, forcedColorsField];\n\n // ─── Form Association ───\n\n /**\n * Enables ElementInternals form association for this component.\n * @internal\n */\n static override formAssociated = true;\n\n // ─── Properties ───\n\n /**\n * The selected radio's value.\n * @attr value\n */\n @property({ type: String, reflect: true })\n value = '';\n\n /**\n * The name used for form submission.\n * @attr name\n */\n @property({ type: String, reflect: true })\n name = '';\n\n /**\n * The fieldset legend/label text.\n * @attr label\n */\n @property({ type: String })\n label = '';\n\n /**\n * Whether a selection is required for form submission.\n * @attr required\n */\n @property({ type: Boolean, reflect: true })\n required = false;\n\n /**\n * Whether the entire group is disabled.\n * @attr disabled\n */\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n /**\n * Error message to display. When set, the group enters an error state.\n * @attr error\n */\n @property({ type: String })\n error = '';\n\n /**\n * Help text displayed below the group for guidance.\n * @attr help-text\n */\n @property({ type: String, attribute: 'help-text' })\n helpText = '';\n\n /**\n * Layout orientation of the radio items.\n * @attr orientation\n */\n @property({ type: String, reflect: true })\n orientation: 'vertical' | 'horizontal' = 'vertical';\n\n /**\n * Queries the rendered group container element within the shadow root.\n * @internal\n */\n private get _groupEl(): HTMLElement | null {\n return this.renderRoot?.querySelector('.fieldset__group') ?? null;\n }\n\n /**\n * Tracks whether the error slot has assigned content.\n * @internal\n */\n @state() private _hasErrorSlot = false;\n\n /**\n * Tracks whether the help-text slot has assigned content.\n * @internal\n */\n @state() private _hasHelpSlot = false;\n\n /**\n * Whether the platform supports IDL element references on `ElementInternals`.\n * Drives the render-time branch between modern (host-canonical via\n * internals) and fallback (inner fieldset is the announced surface).\n * Codex round-17 P1.\n * @internal\n */\n @state() private _supportsIdrefRefs = true;\n\n // ─── Internal IDs ───\n\n /**\n * Unique identifier for this radio group instance used in ARIA attributes.\n * @internal\n */\n private _groupId = _nextRadioGroupId();\n /**\n * Unique identifier for the help text element, used in aria-describedby.\n * @internal\n */\n private _helpTextId = `${this._groupId}-help`;\n /**\n * Unique identifier for the error element, used in aria-describedby.\n * @internal\n */\n private _errorId = `${this._groupId}-error`;\n\n // ─── Slot Handlers ───\n\n /**\n * Handles slotchange events on the error slot to detect assigned content.\n * @internal\n */\n private _handleErrorSlotChange(e: Event): void {\n if (!(e.target instanceof HTMLSlotElement)) return;\n this._hasErrorSlot = e.target.assignedNodes({ flatten: true }).length > 0;\n // Codex round-23 P2 (Finding C): re-tune the in-place text observer over\n // the new assigned-node set so in-place `textContent` rewrites of slotted\n // error nodes resync `internals.ariaDescription` on the no-IDL-ref\n // fallback path. `slotchange` only fires when the *node set* changes;\n // mutating an already-assigned node's text does not, so a separate\n // observer is required. Mirrors the round-21 P3 label-slot observer in\n // hx-checkbox-group.\n this._installErrorSlotTextObserver(e.target);\n this._syncHostAriaSemantics();\n }\n\n /**\n * Handles slotchange events on the help-text slot to detect assigned content.\n * Codex aria-group-2 finding: slot-only help text was not contributing to\n * `aria-describedby` because the wrapper was conditionally rendered on the\n * `helpText` property alone.\n * @internal\n */\n private _handleHelpSlotChange(e: Event): void {\n if (!(e.target instanceof HTMLSlotElement)) return;\n this._hasHelpSlot = e.target.assignedNodes({ flatten: true }).length > 0;\n // Codex round-23 P2 (Finding C): same pattern as the error slot — keep\n // `internals.ariaDescription` in sync with in-place text edits on already\n // assigned help-text nodes.\n this._installHelpSlotTextObserver(e.target);\n this._syncHostAriaSemantics();\n }\n\n /**\n * Watches assigned `<slot name=\"help-text\">` nodes for in-place text\n * mutations so the no-IDL-ref fallback `internals.ariaDescription` stays in\n * sync when a framework rewrites `textContent` of an already-assigned node\n * without replacing it. `slotchange` does NOT fire for those mutations, so\n * a separate observer is required. Codex round-23 P2 (Finding C).\n * @internal\n */\n private _helpSlotTextObserver: MutationObserver | null = null;\n\n /**\n * Watches assigned `<slot name=\"error\">` nodes for in-place text mutations\n * so the no-IDL-ref fallback `internals.ariaDescription` stays in sync when\n * a framework rewrites `textContent` of an already-assigned node without\n * replacing it. Codex round-23 P2 (Finding C).\n * @internal\n */\n private _errorSlotTextObserver: MutationObserver | null = null;\n\n /**\n * (Re-)installs the mutation observer over the current set of assigned\n * help-text-slot nodes. Codex round-23 P2 (Finding C).\n * @internal\n */\n private _installHelpSlotTextObserver(slot: HTMLSlotElement | null): void {\n this._helpSlotTextObserver?.disconnect();\n if (!slot) {\n this._helpSlotTextObserver = null;\n return;\n }\n const observer = new MutationObserver(() => {\n this._syncHostAriaSemantics();\n });\n slot.assignedNodes().forEach((node) => {\n observer.observe(node, {\n characterData: true,\n childList: true,\n subtree: true,\n });\n });\n this._helpSlotTextObserver = observer;\n }\n\n /**\n * (Re-)installs the mutation observer over the current set of assigned\n * error-slot nodes. Codex round-23 P2 (Finding C).\n * @internal\n */\n private _installErrorSlotTextObserver(slot: HTMLSlotElement | null): void {\n this._errorSlotTextObserver?.disconnect();\n if (!slot) {\n this._errorSlotTextObserver = null;\n return;\n }\n const observer = new MutationObserver(() => {\n this._syncHostAriaSemantics();\n });\n slot.assignedNodes().forEach((node) => {\n observer.observe(node, {\n characterData: true,\n childList: true,\n subtree: true,\n });\n });\n this._errorSlotTextObserver = observer;\n }\n\n /**\n * Handle for the shared IDREF observer. See `installAriaIdrefMirror()`.\n * @internal\n */\n private _ariaMirror: AriaIdrefMirrorHandle | null = null;\n\n /**\n * Deferred copy of `error` driven through reactive state so the persistent\n * live region can re-announce on transitions without direct DOM mutation.\n * Codex round-1 finding #10.\n * @internal\n */\n @state() private _announcedError = '';\n\n /**\n * Tracks group-suppressed child radios so detached children can have the\n * flag cleared. Defense-in-depth symmetry with `hx-checkbox-group` —\n * `hx-radio` is not form-associated today, so the flag is inert, but the\n * parity keeps the contract identical between the two group/child families\n * for any future form-association on `hx-radio`. Codex round-3 finding #1.\n * @internal\n */\n private _suppressedChildren = new WeakSet<HelixRadio>();\n\n /**\n * Snapshot of children captured before each `slotchange` so removed\n * children can be released from suppression (WeakSet is non-enumerable).\n * @internal\n */\n private _previousRadios: HelixRadio[] = [];\n\n /**\n * Last value of `aria-labelledby` we wrote to the host. Used to distinguish\n * external (consumer) attribute mutations from our own internal augmentation\n * writes when refreshing the host-attribute fallback. Codex round-10 P2:\n * without this guard, an internal mutation observer fire would re-read the\n * already-augmented host attribute as if it were consumer-supplied, causing\n * legend/help/error ids to leak forward as \"consumer tokens\" forever.\n * @internal\n */\n private _lastWrittenLabelledBy: string | null = null;\n /** @internal — see `_lastWrittenLabelledBy`. */\n private _lastWrittenDescribedBy: string | null = null;\n /**\n * Most recently observed *consumer-supplied* `aria-labelledby` baseline (the\n * set of tokens the consumer themselves wrote on the host). Refreshed only\n * when the host attribute changes via an external write — internal writes\n * leave the baseline untouched.\n * @internal\n */\n private _consumerLabelledBy: string | null = null;\n /** @internal — see `_consumerLabelledBy`. */\n private _consumerDescribedBy: string | null = null;\n\n // ─── Lifecycle ───\n\n override connectedCallback(): void {\n super.connectedCallback();\n // Codex round-17 P1: detect IDL element-references API support so\n // render() can branch the fieldset between presentational (modern) and\n // group-with-aria (fallback) treatments.\n this._supportsIdrefRefs = supportsIdrefElementReferences(this._internals);\n this.addEventListener('hx-radio-select', this._handleRadioSelect);\n this.addEventListener('keydown', this._handleKeydown);\n // Seed root-independent semantics from connect so the host announces the\n // radiogroup role before first paint.\n this._syncHostAriaSemantics();\n this._ariaMirror = installAriaIdrefMirror(this, () => {\n this._syncHostAriaSemantics();\n });\n // Codex round-10 P2 (parity with hx-checkbox-group): re-apply child\n // suppression on reattach so a detached-then-reinserted group with the\n // same `<hx-radio>` children still claims them. `_groupedSuppress` is\n // currently inert on `hx-radio`, but the lifecycle parity keeps the\n // contract identical for any future form-association on the radio child.\n const existing = this._getRadios();\n if (existing.length > 0) {\n existing.forEach((radio) => {\n radio._groupedSuppress = true;\n this._suppressedChildren.add(radio);\n });\n this._previousRadios = existing;\n }\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener('hx-radio-select', this._handleRadioSelect);\n this.removeEventListener('keydown', this._handleKeydown);\n this._ariaMirror?.disconnect();\n this._ariaMirror = null;\n // Codex round-7 finding #6: tear down the per-child disabled observer so\n // detached radios don't keep a strong reference back into a group whose\n // host is being torn down.\n this._childDisabledObserver?.disconnect();\n this._childDisabledObserver = null;\n // Codex round-23 P2 (Finding C): tear down the help/error slot text\n // observers so detached assigned nodes stop firing into a torn-down host.\n this._helpSlotTextObserver?.disconnect();\n this._helpSlotTextObserver = null;\n this._errorSlotTextObserver?.disconnect();\n this._errorSlotTextObserver = null;\n // Release suppression on every previously-tracked child so they regain\n // stand-alone behaviour if re-parented or kept in the document after the\n // group is removed. Codex round-3 finding #1 (defense-in-depth).\n this._previousRadios.forEach((radio) => {\n if (this._suppressedChildren.has(radio)) {\n radio._groupedSuppress = false;\n this._suppressedChildren.delete(radio);\n }\n });\n this._previousRadios = [];\n }\n\n override updated(changedProperties: PropertyValues<this>): void {\n super.updated(changedProperties);\n if (changedProperties.has('value')) {\n this._internals.setFormValue(this.value || null);\n this._syncRadios();\n }\n if (changedProperties.has('disabled')) {\n this._syncRadios();\n }\n // Host-elevated ARIA semantics — see _syncHostAriaSemantics.\n this._syncHostAriaSemantics();\n // Codex round-1 finding #10: drive re-announcement from reactive state\n // so the persistent live region stays in the shadow tree across error\n // transitions. Direct `textContent` mutation would delete the slot\n // subtree the renderer just produced.\n if (changedProperties.has('error')) {\n const previousError = changedProperties.get('error') as string;\n if (previousError && this.error) {\n // Error→error: clear then re-set after rAF so AT re-announces.\n this._announcedError = '';\n requestAnimationFrame(() => {\n this._announcedError = this.error;\n });\n } else {\n this._announcedError = this.error;\n }\n }\n }\n\n /**\n * Mirrors radiogroup semantics onto the host via ElementInternals so that\n * consumer-supplied `aria-label`, `aria-labelledby`, and `aria-describedby`\n * on `<hx-radio-group>` reach the announced control. The codex aria-group-2\n * finding identified that the inner `<fieldset>` was the announced node and\n * the host's external IDREF tokens could not cross the shadow boundary.\n * @internal\n */\n private _syncHostAriaSemantics(): void {\n const internals = this._internals;\n internals.role = 'radiogroup';\n internals.ariaRequired = this.required ? 'true' : 'false';\n internals.ariaInvalid = !internals.validity.valid ? 'true' : 'false';\n internals.ariaDisabled = this.disabled ? 'true' : 'false';\n internals.ariaOrientation = this.orientation === 'horizontal' ? 'horizontal' : 'vertical';\n\n const hostAriaLabel = this.getAttribute('aria-label')?.trim() || '';\n\n // Resolve the candidate label/desc element references once — the IDL-ref\n // path consumes them as `Element[]`, the fallback path mirrors their `id`\n // tokens onto the host's `aria-labelledby` / `aria-describedby` attributes\n // so that AT can still locate the shadow help/error wrappers via the\n // stable shadow-internal ids.\n const internalLegend = this.shadowRoot?.getElementById(`${this._groupId}-legend`);\n const helpEl = this.shadowRoot?.getElementById(this._helpTextId);\n const errorEl = this.shadowRoot?.getElementById(this._errorId);\n\n // Codex round-10 P2: refresh the consumer baseline only when the host\n // attribute moved due to an *external* write. Compare the live attribute\n // against our last-written snapshot — if it differs, the consumer wrote.\n // This prevents internally-augmented values (containing legend/help/error\n // ids we appended) from being re-read as if they were consumer tokens.\n const liveLabelledBy = this.getAttribute('aria-labelledby');\n if (liveLabelledBy !== this._lastWrittenLabelledBy) {\n this._consumerLabelledBy = liveLabelledBy;\n }\n const liveDescribedBy = this.getAttribute('aria-describedby');\n if (liveDescribedBy !== this._lastWrittenDescribedBy) {\n this._consumerDescribedBy = liveDescribedBy;\n }\n const externalLabelTokens = this._consumerLabelledBy;\n const externalDescTokens = this._consumerDescribedBy;\n\n const labelEls = resolveIdrefTokens(this, externalLabelTokens);\n // Codex round-35 finding (CR major + codex follow-up): `aria-labelledby`\n // is only \"effective\" when at least one IDREF resolves. A typo or\n // transiently-missing target must NOT erase the visible label — fall back\n // to `label` so the radiogroup keeps a name on both paths.\n const hasEffectiveLabelledBy = labelEls.length > 0;\n if (hostAriaLabel) {\n internals.ariaLabel = hostAriaLabel;\n } else if (!hasEffectiveLabelledBy) {\n internals.ariaLabel = this.label || null;\n } else {\n internals.ariaLabel = null;\n }\n if (labelEls.length === 0 && !hostAriaLabel && this.label && internalLegend) {\n labelEls.push(internalLegend);\n }\n\n const descEls = resolveIdrefTokens(this, externalDescTokens);\n const hasError = !!(this.error || this._hasErrorSlot);\n // Codex round-16 P2: drop help text from the describedby chain while an\n // error is active. The render path hides the help wrapper in that state\n // (`?hidden=${!hasHelp || hasError}`); appending the hidden node to host\n // semantics would have AT announce stale guidance ahead of the\n // validation error. Mirrors hx-checkbox-group, hx-switch, hx-checkbox.\n if (helpEl && !hasError && (this.helpText || this._hasHelpSlot)) {\n descEls.push(helpEl);\n }\n if (errorEl && hasError) {\n descEls.push(errorEl);\n }\n\n // Branch off the cached `_supportsIdrefRefs` (seeded at connect by the\n // platform probe) so tests can force the fallback branch by flipping the\n // flag. TODO(codex round-19 follow-up): re-probe on `adoptedCallback` if\n // we ever support cross-document moves; today the cached value is set\n // once at connect.\n if (this._supportsIdrefRefs) {\n type InternalsWithRefs = ElementInternals & {\n ariaLabelledByElements: Element[] | null;\n ariaDescribedByElements: Element[] | null;\n };\n const refsInternals = internals as InternalsWithRefs;\n refsInternals.ariaLabelledByElements = labelEls.length > 0 ? labelEls : null;\n refsInternals.ariaDescribedByElements = descEls.length > 0 ? descEls : null;\n // Clear any stale fallback `ariaDescription` string in case a prior sync\n // ran on the fallback path (e.g. tests flipping `_supportsIdrefRefs`).\n // The modern path uses element references exclusively; coexisting strings\n // would cause AT to announce the description twice.\n internals.ariaDescription = null;\n } else {\n // ─── No-IDL-ref fallback (codex round-19 P1) ───\n // The IDL element-references API is unavailable, so internal shadow\n // help/error/legend wrappers cannot be projected onto the host\n // accessibility node via `internals.aria*Elements`.\n //\n // Codex round-19 P1: keep the host as the canonical accessible-container\n // surface on BOTH modern and fallback paths. Earlier rounds promoted the\n // inner fieldset to `role=\"group\"` here and tried to splice consumer\n // light-DOM ids together with shadow-internal ids on the fieldset's\n // `aria-labelledby` / `aria-describedby`. That created two nested\n // accessible containers (host radiogroup → inner group → controls) AND\n // the spliced shadow IDREFs could never resolve to consumer light-DOM\n // targets, so the inner fieldset's \"external label\" was silently broken.\n //\n // The correct trade-off on legacy engines (notably Firefox today): the\n // host owns the role + ARIA strings (via `internals.role`,\n // `internals.ariaLabel`, and the host attribute mirror below); we accept\n // a documented loss of *internal* legend/help/error references and rely\n // on consumer-supplied light-DOM tokens, which resolve correctly in the\n // host's containing root. The inner fieldset stays presentational on\n // both paths so AT announces a single accessible container.\n const consumerLabelIds = new Set((externalLabelTokens?.split(/\\s+/) ?? []).filter(Boolean));\n const consumerDescIds = new Set((externalDescTokens?.split(/\\s+/) ?? []).filter(Boolean));\n\n // Host attributes: ONLY consumer tokens (never shadow-internal ids —\n // those cannot resolve across the shadow boundary).\n //\n // Codex round-35 (medium): mirror consumer tokens to the host attribute\n // ONLY when at least one token resolves to a real element. A broken\n // `aria-labelledby` (typo, target not yet attached) would otherwise\n // erase the accessible name on legacy engines per ARIA priority\n // (aria-labelledby > aria-label > internals.ariaLabel). When tokens\n // don't resolve, clear the host attribute so the `internals.ariaLabel`\n // fallback the modern path set above (`this.label`) wins.\n const hostLabel = hasEffectiveLabelledBy\n ? [...consumerLabelIds].filter(Boolean).join(' ')\n : '';\n const liveLabel = this.getAttribute('aria-labelledby');\n if (hostLabel) {\n if (liveLabel !== hostLabel) {\n this.setAttribute('aria-labelledby', hostLabel);\n }\n this._lastWrittenLabelledBy = hostLabel;\n } else if (liveLabel !== null) {\n // Codex round-36 (medium): when consumer-supplied tokens don't\n // resolve (`!hasEffectiveLabelledBy`), actively clear the host\n // attribute even if WE didn't write it. Per ARIA priority\n // (aria-labelledby > aria-label > internals.ariaLabel), a broken\n // consumer-authored attribute on the announced surface erases the\n // legend on legacy engines. The original tokens remain cached in\n // `_consumerLabelledBy` so they replay on a future sync if the\n // target later attaches and `hasEffectiveLabelledBy` flips back to\n // true.\n this.removeAttribute('aria-labelledby');\n this._lastWrittenLabelledBy = null;\n }\n\n const hostDesc = [...consumerDescIds].filter(Boolean).join(' ') || '';\n const liveDesc = this.getAttribute('aria-describedby');\n if (hostDesc) {\n if (liveDesc !== hostDesc) {\n this.setAttribute('aria-describedby', hostDesc);\n }\n this._lastWrittenDescribedBy = hostDesc;\n } else if (liveDesc !== null && this._lastWrittenDescribedBy !== null) {\n this.removeAttribute('aria-describedby');\n this._lastWrittenDescribedBy = null;\n }\n\n // Codex round-22 P1 #2: on the no-IDL-ref fallback path, consumer-supplied\n // describedby tokens reach the host (above) but the *internal* shadow\n // help/error wrappers cannot be referenced from light-DOM IDREFs. Mirror\n // their `textContent` into `internals.ariaDescription` so the host's\n // accessible description still surfaces the live help/error strings on\n // legacy engines (Firefox today). The string-form description hook is\n // independent of element references and survives the shadow boundary.\n // Empty strings are normalized to `null` so AT does not announce an\n // empty description.\n // Codex round-23 P2 (Finding C): use a slot-aware text read so slotted\n // help/error content surfaces into `internals.ariaDescription` —\n // wrapper `textContent` does NOT cross the shadow → light-DOM slot\n // boundary. The new help/error slot text observers (above) keep this\n // in sync when a framework rewrites the slotted node's textContent in\n // place.\n const helpText =\n helpEl && !hasError && (this.helpText || this._hasHelpSlot)\n ? readSlottedOrShadowText(helpEl)\n : '';\n const errorText = errorEl && hasError ? readSlottedOrShadowText(errorEl) : '';\n const internalDescriptionText = [helpText, errorText].filter(Boolean).join(' ');\n internals.ariaDescription = internalDescriptionText || null;\n }\n }\n\n override firstUpdated(changedProperties: PropertyValues<this>): void {\n super.firstUpdated(changedProperties);\n this._syncRadios();\n this._previousRadios = this._getRadios();\n // Codex round-7 finding #6: observe in-place `disabled` mutations on the\n // initial child set. The slotchange handler refreshes this on every slot\n // pass, but firstUpdated covers the case where the initial children are\n // never re-slotted (e.g. static markup mounted once).\n this._installChildDisabledObservers();\n // WCAG 4.1.2: warn when no accessible name is available for the radio group.\n // The fieldset needs either a label prop (rendered as <legend>) or an aria-label\n // attribute on the host element so screen readers can identify the group.\n if (!this.label && !this.getAttribute('aria-label')) {\n devWarn(\n 'hx-radio-group',\n 'No accessible label provided. Set the `label` attribute or add `aria-label` to the host element. An unlabeled radio group violates WCAG 2.1 AA (4.1.2 Name, Role, Value).',\n );\n }\n }\n\n // ─── Radio Management ───\n\n /**\n * Cached list of child hx-radio elements; invalidated on slot change.\n * @internal\n */\n private _cachedRadios: HelixRadio[] | null = null;\n /**\n * Stores each radio's individual disabled state before group-level disabling overrides it.\n * @internal\n */\n private _individualDisabledStates = new WeakMap<HelixRadio, boolean>();\n\n /**\n * Returns all child hx-radio elements, using the cache when available.\n * @internal\n */\n private _getRadios(): HelixRadio[] {\n if (!this._cachedRadios) {\n this._cachedRadios = Array.from(this.querySelectorAll('hx-radio')) as HelixRadio[];\n }\n return this._cachedRadios;\n }\n\n /**\n * Returns only the child hx-radio elements that are not disabled.\n * @internal\n */\n private _getEnabledRadios(): HelixRadio[] {\n return this._getRadios().filter((radio) => !radio.disabled && !this.disabled);\n }\n\n /**\n * Synchronizes checked state, disabled state, and roving tabindex across all child radios.\n * @internal\n */\n private _syncRadios(): void {\n const radios = this._getRadios();\n const enabledRadios = this._getEnabledRadios();\n\n radios.forEach((radio) => {\n // Codex round-3 finding #1 (defense-in-depth symmetry with hx-checkbox):\n // mark every group-managed child so any future form-association change\n // on `hx-radio` is automatically suppressed inside the group. The flag\n // is currently inert on `hx-radio` (which is not form-associated).\n radio._groupedSuppress = true;\n this._suppressedChildren.add(radio);\n\n const isChecked = radio.value === this.value && this.value !== '';\n radio.checked = isChecked;\n\n if (this.disabled) {\n // Store individual disabled state before overriding with group disabled\n if (!this._individualDisabledStates.has(radio)) {\n this._individualDisabledStates.set(radio, radio.disabled);\n }\n radio.disabled = true;\n } else {\n // Restore individual disabled state when group is re-enabled\n const originalDisabled = this._individualDisabledStates.get(radio);\n if (originalDisabled !== undefined) {\n radio.disabled = originalDisabled;\n this._individualDisabledStates.delete(radio);\n }\n }\n });\n\n // Roving tabindex management\n const checkedRadio = enabledRadios.find((r) => r.checked);\n radios.forEach((radio) => {\n radio.tabIndex = -1;\n });\n\n if (checkedRadio) {\n checkedRadio.tabIndex = 0;\n } else if (enabledRadios.length > 0) {\n const firstRadio = enabledRadios[0];\n if (firstRadio) {\n firstRadio.tabIndex = 0;\n }\n }\n }\n\n // ─── Event Handling ───\n\n /**\n * Handles the internal hx-radio-select event to update the group's selected value.\n * @internal\n */\n private _handleRadioSelect = (e: Event): void => {\n if (!(e instanceof CustomEvent)) return;\n e.stopPropagation();\n\n const newValue = (e.detail as { value: string }).value;\n if (newValue === this.value) {\n return;\n }\n\n this.value = newValue;\n this._handleInteractionInput();\n // Reactive update in updated() will call setFormValue, _syncRadios, _updateValidity\n\n /**\n * Dispatched when the selected radio changes.\n * @event hx-change\n */\n this.dispatchEvent(\n new CustomEvent<{ value: string; checked: boolean }>('hx-change', {\n bubbles: true,\n composed: true,\n detail: { value: this.value, checked: true },\n }),\n );\n };\n\n /**\n * Handles keyboard navigation (arrow keys, Home, End, Space) within the radio group.\n * @internal\n */\n private _handleKeydown = (e: KeyboardEvent): void => {\n const enabledRadios = this._getEnabledRadios();\n if (enabledRadios.length === 0) {\n return;\n }\n\n const isHandledKey = [\n 'ArrowUp',\n 'ArrowDown',\n 'ArrowLeft',\n 'ArrowRight',\n ' ',\n 'Home',\n 'End',\n ].includes(e.key);\n if (!isHandledKey) {\n return;\n }\n\n e.preventDefault();\n\n // Space: select the currently focused radio without moving focus\n if (e.key === ' ') {\n const targetRadio = (e.target as Element)?.closest?.('hx-radio') as HelixRadio | null;\n if (targetRadio && !targetRadio.disabled) {\n targetRadio.dispatchEvent(\n new CustomEvent<{ value: string }>('hx-radio-select', {\n bubbles: true,\n composed: true,\n detail: { value: targetRadio.value },\n }),\n );\n }\n return;\n }\n\n const targetRadio = (e.target as Element)?.closest?.('hx-radio') as HelixRadio | null;\n const currentIndex = targetRadio\n ? enabledRadios.indexOf(targetRadio)\n : enabledRadios.findIndex((radio) => radio.checked);\n\n let nextIndex: number;\n if (e.key === 'Home') {\n nextIndex = 0;\n } else if (e.key === 'End') {\n nextIndex = enabledRadios.length - 1;\n } else if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {\n nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % enabledRadios.length;\n } else {\n nextIndex = currentIndex <= 0 ? enabledRadios.length - 1 : currentIndex - 1;\n }\n\n const nextRadio = enabledRadios[nextIndex];\n if (nextRadio) {\n nextRadio.focus();\n nextRadio.dispatchEvent(\n new CustomEvent<{ value: string }>('hx-radio-select', {\n bubbles: true,\n composed: true,\n detail: { value: nextRadio.value },\n }),\n );\n }\n };\n\n /**\n * Handles slotchange events on the default slot. Refreshes the cached\n * radio list, reconciles `value`/`setFormValue`/validity against the new\n * children, then re-tunes the per-child disabled observer so in-place\n * `radio.disabled = true` mutations trigger the same reconcile pass.\n *\n * Codex round-2 finding #3: previously this handler only invalidated the\n * cache and re-synced child state, so removing the currently-selected\n * radio (or disabling it, or adding a new `checked` radio) left\n * `this.value` and the submitted form value pointing at stale state and\n * `_updateValidity` was never re-run.\n *\n * Codex round-7 finding #6: an in-place `selectedRadio.disabled = true`\n * never re-entered this reconciler (the radio remained slotted), so the\n * group could keep submitting a disabled value and stay valid until some\n * unrelated slot mutation kicked things over. The per-child observer\n * installed at the end of this method re-runs the same reconcile logic on\n * every `disabled` attribute mutation.\n * @internal\n */\n private _handleSlotChange(): void {\n this._cachedRadios = null;\n this._reconcileChildren();\n this._installChildDisabledObservers();\n }\n\n /**\n * Per-child `disabled` attribute observer. Mirrors the slotchange\n * reconcile pipeline so an in-place `radio.disabled = true` collapses the\n * group's `value`/form participation/validity. Round-7 finding #6.\n *\n * One observer is shared across all currently-slotted radios; it is\n * disconnected and re-attached on every slotchange so children that have\n * left the group stop firing reconcile passes.\n * @internal\n */\n private _childDisabledObserver: MutationObserver | null = null;\n\n /**\n * Re-runs the slotchange reconcile pass without touching the disabled\n * observer wiring. Called from the slotchange handler and from the\n * per-child disabled observer; factored out so both entry points share\n * the same value/formValue/validity reconciliation path. Round-7 #6.\n * @internal\n */\n private _reconcileChildren(): void {\n // Snapshot of the previous slot pass so we can release `_groupedSuppress`\n // on any radio that has left this group. Codex round-3 finding #1\n // (defense-in-depth symmetry).\n const previous = this._previousRadios;\n\n // Reconcile against currently-slotted children BEFORE _syncRadios runs.\n // _syncRadios() unconditionally overwrites `radio.checked` from `this.value`,\n // which would clobber an externally-set `checked=true` on a newly-added\n // radio when the group's current value is empty. We therefore peek at the\n // raw children first to detect \"pre-checked\" adoption, then commit through\n // the normal sync pipeline.\n //\n // Codex round-12 P2: prefer a checked radio whose value differs from the\n // current group value — that is the \"newly externally checked\" candidate.\n // The naive `.find(r.checked && !r.disabled)` returned the first checked\n // radio in DOM order, which is typically the previously-selected one whose\n // value already matches `this.value`, so an appended `<hx-radio checked>`\n // with a different value would be rejected by the next `_syncRadios()`\n // pass. Falling back to \"any checked radio\" still handles the\n // empty-group / first-mount case.\n const candidates = this._getRadios().filter((r) => r.checked && !r.disabled);\n const externallyChecked = candidates.find((r) => r.value !== this.value) ?? candidates[0];\n if (externallyChecked && externallyChecked.value !== this.value) {\n this.value = externallyChecked.value;\n }\n\n this._syncRadios();\n\n // After sync, the group's `value` and the children's `checked` flags are\n // canonical. If the previously-selected radio was removed or disabled,\n // `selected` becomes undefined and `reconciled` collapses to '', clearing\n // form participation.\n //\n // Codex round-10 P1: when the group itself is disabled, every radio is\n // force-disabled by `_syncRadios`. The post-sync `selected` lookup excludes\n // disabled radios, so it would always collapse to '' and wipe the user's\n // selection across a disable→enable cycle. Group-level disable is a\n // visual/interaction lock — it must preserve `value` and form participation\n // (matching native `<fieldset disabled>` semantics where the form value is\n // simply omitted from submission while disabled). When `this.disabled` is\n // true we leave `value` and form state untouched; the next disabled→false\n // pass through `updated()` runs `_syncRadios` again, which restores each\n // radio's individual disabled flag and re-checks the matching value.\n if (this.disabled) {\n // Codex round-18 P2: even while disabled, if the previously-selected\n // radio has been *removed* from the DOM (vs merely disabled by the\n // group), `this.value` references a value that no slotted child\n // carries, so the group would resubmit a stale option after re-enable.\n // We must clear `value` in that case while still preserving selection\n // across pure disable→enable cycles where the radio still exists.\n // Detect \"selected option still in DOM\" by membership lookup on the\n // raw children, ignoring their disabled flag (since group-level\n // disable forces them all disabled by _syncRadios).\n const presentValues = this._getRadios().map((r) => r.value);\n if (this.value && !presentValues.includes(this.value)) {\n this.value = '';\n }\n // Validity reflects the (possibly-cleared) value; setFormValue is\n // omitted because a disabled control shouldn't contribute to form\n // submission.\n this._updateValidity();\n } else {\n const selected = this._getRadios().find((r) => r.checked && !r.disabled);\n const reconciled = selected?.value ?? '';\n if (reconciled !== this.value) {\n this.value = reconciled;\n }\n // Always re-run setFormValue + validity. `value` may be stable while\n // membership/disabled-state changed, and a freshly-adopted pre-checked\n // radio still needs its value pushed to the form.\n this._internals.setFormValue(this.value || null);\n this._updateValidity();\n }\n\n // Release suppression on any radio that left the group. Then refresh the\n // snapshot for the next slotchange. Codex round-3 finding #1.\n const current = new Set(this._getRadios());\n previous.forEach((radio) => {\n if (!current.has(radio) && this._suppressedChildren.has(radio)) {\n radio._groupedSuppress = false;\n this._suppressedChildren.delete(radio);\n }\n });\n this._previousRadios = this._getRadios();\n }\n\n /**\n * Installs (or re-installs) a single MutationObserver across the current\n * set of slotted `<hx-radio>` children, listening for `disabled` attribute\n * mutations. Round-7 finding #6: in-place `disabled = true` never reaches\n * the slotchange-driven reconciler otherwise.\n * @internal\n */\n private _installChildDisabledObservers(): void {\n this._childDisabledObserver?.disconnect();\n const radios = this._getRadios();\n if (radios.length === 0) {\n this._childDisabledObserver = null;\n return;\n }\n const observer = new MutationObserver((mutations) => {\n // Filter to disabled-attribute mutations only — the observer is scoped\n // to attributeFilter:['disabled'] but the callback still fires per\n // mutation record, so guard cheaply against noise.\n if (!mutations.some((m) => m.attributeName === 'disabled')) return;\n this._reconcileChildren();\n });\n radios.forEach((radio) => {\n observer.observe(radio, {\n attributes: true,\n attributeFilter: ['disabled'],\n });\n });\n this._childDisabledObserver = observer;\n }\n\n // ─── Form Integration ───\n\n /**\n * Updates the ElementInternals validity state based on the required constraint and current value.\n * @internal\n */\n override _updateValidity(): void {\n if (this.required && !this.value) {\n // Codex round-35 finding (CR major): anchor `setValidity()` to an actual\n // radio. The presentational `.fieldset__group` div is `role=\"none\"` and\n // not focusable, so anchoring there leaves UA validation UI / error\n // recovery disconnected from the interactive surface.\n //\n // Codex round-35 follow-up (Low #4): drop the unconditional `radios[0]`\n // tier — a disabled radio is not focusable and cannot host UA validation\n // UI either. Prefer checked-enabled, then any enabled, then the\n // presentational group as a last resort (still better than `undefined`\n // because it pins UA error positioning to the visible group bounds).\n const anchor =\n this._getRadios().find((radio) => radio.checked && !radio.disabled) ??\n this._getEnabledRadios()[0] ??\n this._groupEl ??\n undefined;\n this._internals.setValidity(\n { valueMissing: true },\n this.error || 'Please select an option.',\n anchor,\n );\n } else {\n this._internals.setValidity({});\n }\n // Codex round-1 finding #6: re-sync host ARIA after every setValidity().\n this._syncHostAriaSemantics();\n }\n\n /** @internal */\n protected override _onFormReset(): void {\n this.value = '';\n this._internals.setFormValue(null);\n this._syncRadios();\n this._resetInteractionState();\n }\n\n /** @internal */\n protected override _onFormStateRestore(\n state: File | string | FormData | null,\n _mode: 'restore' | 'autocomplete',\n ): void {\n if (typeof state === 'string') {\n this.value = state;\n }\n }\n\n /** @internal */\n protected override _onFormDisabled(disabled: boolean): void {\n this.disabled = disabled;\n }\n\n // ─── Render ───\n\n override render() {\n const hasError = !!this.error || this._hasErrorSlot;\n const hasHelp = !!this.helpText || this._hasHelpSlot;\n const legendId = `${this._groupId}-legend`;\n\n const fieldsetClasses = {\n fieldset: true,\n 'fieldset--error': hasError,\n 'fieldset--disabled': this.disabled,\n 'fieldset--required': this.required,\n };\n\n // Codex round-19 P1: inner fieldset is presentational on BOTH the modern\n // and no-IDL-ref fallback paths so AT announces exactly one accessible\n // container (the host). Earlier rounds promoted the fieldset to\n // `role=\"group\"` on the fallback branch and spliced shadow-internal ids\n // into its aria-* attributes; that produced nested host→fieldset groups\n // and broke external IDREFs (shadow ids cannot resolve across the\n // boundary). The host carries the radiogroup role via ElementInternals\n // on both paths.\n // axe rule `aria-allowed-attr` forbids aria-orientation on\n // role=\"presentation\" — the host carries `ariaOrientation` via\n // ElementInternals (see _syncHostAriaSemantics), so the inner\n // fieldset must not duplicate it.\n return html`\n <fieldset part=\"fieldset\" class=${classMap(fieldsetClasses)} role=\"presentation\">\n ${this.label\n ? html`\n <legend part=\"legend\" class=\"fieldset__legend\" id=${legendId}>\n ${this.label}\n ${this.required\n ? html`<span class=\"fieldset__required-marker\" aria-hidden=\"true\">*</span>`\n : nothing}\n </legend>\n `\n : nothing}\n\n <div part=\"group\" class=\"fieldset__group\" role=\"none\">\n <slot @slotchange=${this._handleSlotChange}></slot>\n </div>\n\n <!--\n Persistent error live region. role=\"alert\" is set from first paint\n so the WAI-ARIA contract for live updates is honoured: content\n changes in place rather than the container being toggled.\n -->\n <div\n part=\"error\"\n class=\"fieldset__error\"\n id=${this._errorId}\n role=\"alert\"\n ?hidden=${!hasError}\n >\n <slot name=\"error\" @slotchange=${this._handleErrorSlotChange}\n >${this._announcedError}</slot\n >\n </div>\n\n <!--\n Persistent help-text container. Rendered whenever the property OR\n the slot has content; hidden when an error is present so guidance\n does not compete with validation feedback. Always in the shadow\n tree so the host's aria-describedby chain is stable.\n -->\n <div\n part=\"help-text\"\n class=\"fieldset__help-text\"\n id=${this._helpTextId}\n ?hidden=${!hasHelp || hasError}\n >\n <slot name=\"help-text\" @slotchange=${this._handleHelpSlotChange}>${this.helpText}</slot>\n </div>\n </fieldset>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-radio-group': HelixRadioGroup;\n }\n}\n\n/** Canonical type alias for the hx-radio-group component. */\nexport type HxRadioGroup = HelixRadioGroup;\n","import { css } from 'lit';\n\nexport const helixRadioStyles = css`\n :host {\n display: block;\n /*\n * Suppress the browser default ~1px host outline. Without this the formal\n * AAA audit harness records a sub-2px outline on the focused host and\n * reports WCAG 2.4.13 Partially Supports — even though the explicit 2px\n * focus ring is correctly painted on the inner .radio__control element.\n * Mirrors the hx-checkbox / hx-switch / hx-toggle-button fix.\n */\n outline: none;\n }\n\n :host([disabled]) {\n opacity: var(--hx-opacity-disabled, 0.5);\n pointer-events: none;\n }\n\n * {\n box-sizing: border-box;\n }\n\n .radio {\n display: inline-flex;\n align-items: center;\n gap: var(--hx-space-2, 0.5rem);\n /* WCAG 2.5.5 (healthcare mandate): minimum 44px touch target height */\n min-height: var(--hx-touch-target-min, 2.75rem);\n cursor: pointer;\n position: relative;\n font-family: var(--hx-radio-font-family, var(--hx-font-family-sans, sans-serif));\n }\n\n .radio--disabled {\n cursor: not-allowed;\n }\n\n /* ─── Hidden Native Input ─── */\n\n .radio__input {\n position: absolute;\n width: var(--hx-space-px);\n height: var(--hx-space-px);\n padding: 0;\n margin: calc(var(--hx-space-px) * -1);\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n }\n\n /* ─── Visual Radio Circle ─── */\n\n .radio__control {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--hx-radio-size, var(--hx-size-5, 1.25rem));\n height: var(--hx-radio-size, var(--hx-size-5, 1.25rem));\n border: var(--hx-border-width-medium, 2px) solid\n var(--hx-radio-border-color, var(--hx-color-border-strong, #66787b));\n border-radius: var(--hx-border-radius-full, 9999px);\n background-color: var(--hx-radio-bg, var(--hx-color-surface-default, #ffffff));\n transition:\n border-color var(--hx-transition-fast, 150ms ease),\n background-color var(--hx-transition-fast, 150ms ease),\n box-shadow var(--hx-transition-fast, 150ms ease);\n flex-shrink: 0;\n }\n\n /* ─── Inner Dot ─── */\n\n .radio__dot {\n width: calc(var(--hx-radio-size, var(--hx-size-5, 1.25rem)) * 0.4);\n height: calc(var(--hx-radio-size, var(--hx-size-5, 1.25rem)) * 0.4);\n border-radius: var(--hx-border-radius-full, 9999px);\n background-color: var(--hx-radio-dot-color, var(--hx-color-text-on-primary, #ffffff));\n transform: scale(0);\n transition: transform var(--hx-transition-fast, 150ms ease);\n }\n\n /* ─── Checked State ─── */\n\n .radio--checked .radio__control {\n border-color: var(--hx-radio-checked-border-color, var(--hx-color-action-primary-bg, #0f7078));\n background-color: var(--hx-radio-checked-bg, var(--hx-color-action-primary-bg, #0f7078));\n }\n\n .radio--checked .radio__dot {\n transform: scale(1);\n }\n\n /* ─── Focus State ─── */\n\n :host(:focus-visible) .radio__control {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-radio-focus-ring-color, var(--hx-focus-ring-color, #0f7078));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n }\n\n /* ─── Hover State ─── */\n\n .radio:not(.radio--disabled):not(.radio--checked):hover .radio__control {\n border-color: var(--hx-radio-hover-border-color, var(--hx-color-border-strong, #66787b));\n }\n\n /* ─── Label ─── */\n\n .radio__label {\n font-size: var(--hx-font-size-md, 1rem);\n color: var(--hx-radio-label-color, var(--hx-color-text-strong, #202b39));\n line-height: var(--hx-line-height-normal, 1.5);\n user-select: none;\n -webkit-user-select: none;\n }\n\n /* ─── Reduced Motion ─── */\n\n @media (prefers-reduced-motion: reduce) {\n .radio__control,\n .radio__dot {\n transition: none;\n }\n }\n\n /* ─── High Contrast Mode (forced-colors) ─── */\n\n @media (forced-colors: active) {\n .radio__control {\n forced-color-adjust: none;\n background-color: ButtonFace;\n border: 2px solid ButtonText;\n }\n\n :host(:focus-visible) .radio__control {\n outline: 3px solid Highlight;\n outline-offset: 2px;\n }\n\n .radio--checked .radio__control {\n background-color: Highlight;\n border-color: Highlight;\n }\n\n .radio--checked .radio__dot {\n background-color: HighlightText;\n }\n\n :host([disabled]) {\n opacity: 1;\n }\n\n :host([disabled]) .radio__control {\n border-color: GrayText;\n background-color: ButtonFace;\n }\n\n :host([disabled]) .radio--checked .radio__control {\n background-color: GrayText;\n border-color: GrayText;\n }\n\n :host([disabled]) .radio__label {\n color: GrayText;\n }\n\n .radio__label {\n color: CanvasText;\n }\n }\n`;\n","import { html, type PropertyValues } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property } from 'lit/decorators.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { HelixElement, createIdCounter } from '../../base/index.js';\nimport { helixRadioStyles } from './hx-radio.styles.js';\nimport { forcedColorsField } from '../../styles/forced-colors.js';\n\nconst _nextRadioId = createIdCounter('hx-radio');\n\n/**\n * An individual radio button, designed to be used inside a `<hx-radio-group>`.\n *\n * @summary Presentational radio button managed by its parent radio group.\n *\n * @tag hx-radio\n *\n * @slot - Custom label content (overrides the label property).\n *\n * @csspart radio - The visual radio circle.\n * @csspart label - The label text.\n *\n * @cssprop [--hx-radio-size=var(--hx-size-5, 1.25rem)] - Radio circle size.\n * @cssprop [--hx-radio-border-color=var(--hx-color-neutral-300, #B6BFB9)] - Radio border color.\n * @cssprop [--hx-radio-checked-bg=var(--hx-color-primary-500, #429797)] - Checked background color.\n * @cssprop [--hx-radio-checked-border-color=var(--hx-color-primary-500, #429797)] - Checked border color.\n * @cssprop [--hx-radio-dot-color=var(--hx-color-neutral-0, #ffffff)] - Inner dot color when checked.\n * @cssprop [--hx-radio-focus-ring-color=var(--hx-focus-ring-color, #6AB1B1)] - Focus ring color.\n * @cssprop [--hx-radio-label-color=var(--hx-color-neutral-700, #313E4B)] - Label text color.\n */\n@customElement('hx-radio')\nexport class HelixRadio extends HelixElement {\n static override styles = [helixRadioStyles, forcedColorsField];\n\n // ─── Properties ───\n\n /**\n * The value this radio represents.\n * @attr value\n */\n @property({ type: String })\n value = '';\n\n /**\n * Visible label text for the radio.\n * @attr label\n */\n @property({ type: String })\n label = '';\n\n /**\n * Whether this radio is disabled.\n * @attr disabled\n */\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n /**\n * Whether this radio is checked. Managed by the parent group.\n * @attr checked\n */\n @property({ type: Boolean, reflect: true })\n checked = false;\n\n /**\n * Set by `hx-radio-group` to mark this child as group-managed. `hx-radio` is\n * not form-associated (the group is the sole form participant), so this\n * flag is currently inert on this element. It exists for symmetry with\n * `hx-checkbox._groupedSuppress` so the group/child contract is identical\n * across both selection-control families and any future form-association\n * change on `hx-radio` is automatically suppressed inside a group. Codex\n * round-3 finding #1 (defense-in-depth).\n * @internal\n */\n set _groupedSuppress(value: boolean) {\n this.__groupedSuppress = value;\n }\n get _groupedSuppress(): boolean {\n return this.__groupedSuppress;\n }\n /** @internal */\n private __groupedSuppress = false;\n\n // ─── Lifecycle ───\n\n override connectedCallback(): void {\n super.connectedCallback();\n // ARIA role and state are projected to the accessibility tree via\n // ElementInternals rather than imperative setAttribute calls. This keeps\n // the host element's attribute surface clean — consumers cannot accidentally\n // override role/aria-* — and aligns with the standards pattern for\n // custom elements. See https://wicg.github.io/aom/spec/aria-reflection.html.\n this._internals.role = 'radio';\n this._syncAriaState();\n }\n\n override updated(changedProperties: PropertyValues<this>): void {\n super.updated(changedProperties);\n if (\n changedProperties.has('checked') ||\n changedProperties.has('label') ||\n changedProperties.has('disabled')\n ) {\n this._syncAriaState();\n }\n }\n\n /**\n * Mirror reactive ARIA state onto ElementInternals. Setting a value to `null`\n * removes it from the accessibility tree (matching the previous\n * removeAttribute behavior).\n *\n * WCAG 4.1.2: expose the label text as ariaLabel on the host so assistive\n * technology can associate the visible label with the radio role. The label\n * span lives inside Shadow DOM and aria-labelledby cannot cross shadow\n * boundaries, so ariaLabel on the host is the correct pattern here.\n *\n * WCAG 4.1.2: omit ariaDisabled entirely when not disabled. Setting\n * aria-disabled=\"false\" is verbose and unnecessary — omission is preferred.\n *\n * @internal\n */\n private _syncAriaState(): void {\n this._internals.ariaChecked = String(this.checked);\n this._internals.ariaLabel = this.label || null;\n this._internals.ariaDisabled = this.disabled ? 'true' : null;\n }\n\n // ─── Internal IDs ───\n\n /** @internal */\n private _inputId = _nextRadioId();\n\n // ─── Event Handling ───\n\n /** @internal */\n private _handleClick(): void {\n if (this.disabled) {\n return;\n }\n\n /**\n * Internal event dispatched to signal selection to the parent group.\n * Not part of the public API.\n * @internal\n */\n this.dispatchEvent(\n new CustomEvent<{ value: string }>('hx-radio-select', {\n bubbles: true,\n composed: true,\n detail: { value: this.value },\n }),\n );\n }\n\n // ─── Render ───\n\n override render() {\n const classes = {\n radio: true,\n 'radio--checked': this.checked,\n 'radio--disabled': this.disabled,\n };\n\n return html`\n <div class=${classMap(classes)} @click=${this._handleClick}>\n <input\n class=\"radio__input\"\n type=\"radio\"\n id=${this._inputId}\n .checked=${this.checked}\n ?disabled=${this.disabled}\n tabindex=\"-1\"\n aria-hidden=\"true\"\n />\n <span part=\"radio\" class=\"radio__control\" aria-hidden=\"true\">\n <span class=\"radio__dot\"></span>\n </span>\n <span part=\"label\" class=\"radio__label\">\n <slot>${this.label}</slot>\n </span>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-radio': HelixRadio;\n }\n}\n\n/** Canonical type alias for the hx-radio component. */\nexport type HxRadio = HelixRadio;\n"],"names":["helixRadioGroupStyles","css","_nextRadioGroupId","createIdCounter","readSlottedOrShadowText","wrapper","slot","assigned","node","HelixRadioGroup","FormMixin","HelixElement","newValue","enabledRadios","targetRadio","_b","_a","_d","_c","currentIndex","radio","nextIndex","nextRadio","observer","supportsIdrefElementReferences","installAriaIdrefMirror","existing","changedProperties","internals","hostAriaLabel","internalLegend","helpEl","errorEl","liveLabelledBy","liveDescribedBy","externalLabelTokens","externalDescTokens","labelEls","resolveIdrefTokens","hasEffectiveLabelledBy","descEls","hasError","refsInternals","consumerLabelIds","consumerDescIds","hostLabel","liveLabel","hostDesc","liveDesc","helpText","errorText","internalDescriptionText","radios","isChecked","originalDisabled","checkedRadio","firstRadio","previous","candidates","r","externallyChecked","presentValues","selected","reconciled","current","mutations","m","anchor","state","_mode","disabled","hasHelp","legendId","fieldsetClasses","html","classMap","nothing","forcedColorsField","__decorateClass","property","customElement","helixRadioStyles","_nextRadioId","HelixRadio","value","classes"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { css as f, html as d, nothing as p } from "lit";
|
|
2
|
-
import { property as h, state as
|
|
2
|
+
import { property as h, state as y, customElement as v } from "lit/decorators.js";
|
|
3
3
|
import { F as _ } from "./FormMixin-B8PXk5RQ.js";
|
|
4
|
-
import { f as
|
|
5
|
-
import { H as
|
|
6
|
-
const
|
|
4
|
+
import { f as x } from "./forced-colors-CTEDFRGa.js";
|
|
5
|
+
import { H as g } from "./helix-element-BNEYeiys.js";
|
|
6
|
+
const S = f`
|
|
7
7
|
:host {
|
|
8
8
|
display: inline-block;
|
|
9
9
|
}
|
|
@@ -65,6 +65,12 @@ const w = f`
|
|
|
65
65
|
color: var(--hx-rating-hover-color, var(--hx-color-warning-300, #eeb383));
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/* ─── Star Glyph (hx-icon) Sizing ─── */
|
|
69
|
+
|
|
70
|
+
.star-glyph {
|
|
71
|
+
--hx-icon-size: 1em;
|
|
72
|
+
}
|
|
73
|
+
|
|
68
74
|
/* ─── Half-Star Layout ─── */
|
|
69
75
|
|
|
70
76
|
.star-half {
|
|
@@ -140,12 +146,12 @@ const w = f`
|
|
|
140
146
|
}
|
|
141
147
|
}
|
|
142
148
|
`;
|
|
143
|
-
var
|
|
144
|
-
for (var r = o > 1 ? void 0 : o ?
|
|
149
|
+
var $ = Object.defineProperty, V = Object.getOwnPropertyDescriptor, n = (e, t, s, o) => {
|
|
150
|
+
for (var r = o > 1 ? void 0 : o ? V(t, s) : t, a = e.length - 1, i; a >= 0; a--)
|
|
145
151
|
(i = e[a]) && (r = (o ? i(t, s, r) : i(r)) || r);
|
|
146
|
-
return o && r &&
|
|
152
|
+
return o && r && $(t, s, r), r;
|
|
147
153
|
};
|
|
148
|
-
let l = class extends _(
|
|
154
|
+
let l = class extends _(g) {
|
|
149
155
|
constructor() {
|
|
150
156
|
super(...arguments), this.value = 0, this.max = 5, this.precision = 1, this.readonly = !1, this.disabled = !1, this.name = "", this.label = "", this.required = !1, this.labelStar = (e) => e === 1 ? "1 star" : `${e} stars`, this.labelValueText = (e, t) => `${e} out of ${t} stars`, this._hoverValue = null, this._defaultValue = 0;
|
|
151
157
|
}
|
|
@@ -285,72 +291,40 @@ let l = class extends _(x) {
|
|
|
285
291
|
// ─── SVG Star Icons ───
|
|
286
292
|
/** @internal */
|
|
287
293
|
_renderFullStar() {
|
|
288
|
-
return d
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
width="1em"
|
|
295
|
-
height="1em"
|
|
296
|
-
>
|
|
297
|
-
<path
|
|
298
|
-
d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"
|
|
299
|
-
/>
|
|
300
|
-
</svg>
|
|
301
|
-
`;
|
|
294
|
+
return d`<hx-icon
|
|
295
|
+
class="star-glyph"
|
|
296
|
+
library="helix"
|
|
297
|
+
name="star-filled"
|
|
298
|
+
aria-hidden="true"
|
|
299
|
+
></hx-icon>`;
|
|
302
300
|
}
|
|
303
301
|
/** @internal */
|
|
304
302
|
_renderHalfStar() {
|
|
305
303
|
return d`
|
|
306
304
|
<span class="star-half" aria-hidden="true">
|
|
307
|
-
<
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
<svg
|
|
320
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
321
|
-
viewBox="0 0 24 24"
|
|
322
|
-
fill="none"
|
|
323
|
-
stroke="currentColor"
|
|
324
|
-
stroke-width="1.5"
|
|
325
|
-
width="1em"
|
|
326
|
-
height="1em"
|
|
327
|
-
class="star-half__empty"
|
|
328
|
-
>
|
|
329
|
-
<path
|
|
330
|
-
d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"
|
|
331
|
-
/>
|
|
332
|
-
</svg>
|
|
305
|
+
<hx-icon
|
|
306
|
+
class="star-glyph star-half__filled"
|
|
307
|
+
library="helix"
|
|
308
|
+
name="star-filled"
|
|
309
|
+
aria-hidden="true"
|
|
310
|
+
></hx-icon>
|
|
311
|
+
<hx-icon
|
|
312
|
+
class="star-glyph star-half__empty"
|
|
313
|
+
library="helix"
|
|
314
|
+
name="star-outline"
|
|
315
|
+
aria-hidden="true"
|
|
316
|
+
></hx-icon>
|
|
333
317
|
</span>
|
|
334
318
|
`;
|
|
335
319
|
}
|
|
336
320
|
/** @internal */
|
|
337
321
|
_renderEmptyStar() {
|
|
338
|
-
return d
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
stroke-width="1.5"
|
|
345
|
-
aria-hidden="true"
|
|
346
|
-
width="1em"
|
|
347
|
-
height="1em"
|
|
348
|
-
>
|
|
349
|
-
<path
|
|
350
|
-
d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"
|
|
351
|
-
/>
|
|
352
|
-
</svg>
|
|
353
|
-
`;
|
|
322
|
+
return d`<hx-icon
|
|
323
|
+
class="star-glyph"
|
|
324
|
+
library="helix"
|
|
325
|
+
name="star-outline"
|
|
326
|
+
aria-hidden="true"
|
|
327
|
+
></hx-icon>`;
|
|
354
328
|
}
|
|
355
329
|
/** @internal */
|
|
356
330
|
_renderStarIcon(e) {
|
|
@@ -415,15 +389,15 @@ let l = class extends _(x) {
|
|
|
415
389
|
@mouseleave="${this._handleMouseLeave}"
|
|
416
390
|
>
|
|
417
391
|
${Array.from({ length: this.max }, (o, r) => {
|
|
418
|
-
const a = r + 1, i = this._getStarState(a), u = this._isChecked(a),
|
|
392
|
+
const a = r + 1, i = this._getStarState(a), u = this._isChecked(a), b = this.labelStar(a), m = this.value > 0 ? Math.ceil(this.value) === a : a === 1;
|
|
419
393
|
return d`
|
|
420
394
|
<span
|
|
421
395
|
part="symbol"
|
|
422
396
|
class="symbol symbol--${i}${this.disabled ? " symbol--disabled" : ""}"
|
|
423
397
|
role="radio"
|
|
424
|
-
aria-label="${
|
|
398
|
+
aria-label="${b}"
|
|
425
399
|
aria-checked="${u ? "true" : "false"}"
|
|
426
|
-
tabindex="${!this.disabled &&
|
|
400
|
+
tabindex="${!this.disabled && m ? "0" : "-1"}"
|
|
427
401
|
data-index="${a}"
|
|
428
402
|
@click="${(c) => this._handleSymbolClick(c, a)}"
|
|
429
403
|
@mouseenter="${(c) => this._handleSymbolMouseEnter(c, a)}"
|
|
@@ -437,7 +411,7 @@ let l = class extends _(x) {
|
|
|
437
411
|
`;
|
|
438
412
|
}
|
|
439
413
|
};
|
|
440
|
-
l.styles = [
|
|
414
|
+
l.styles = [S, x];
|
|
441
415
|
l.formAssociated = !0;
|
|
442
416
|
n([
|
|
443
417
|
h({ type: Number, reflect: !0 })
|
|
@@ -470,12 +444,12 @@ n([
|
|
|
470
444
|
h({ attribute: !1 })
|
|
471
445
|
], l.prototype, "labelValueText", 2);
|
|
472
446
|
n([
|
|
473
|
-
|
|
447
|
+
y()
|
|
474
448
|
], l.prototype, "_hoverValue", 2);
|
|
475
449
|
l = n([
|
|
476
|
-
|
|
450
|
+
v("hx-rating")
|
|
477
451
|
], l);
|
|
478
452
|
export {
|
|
479
453
|
l as H
|
|
480
454
|
};
|
|
481
|
-
//# sourceMappingURL=hx-rating-
|
|
455
|
+
//# sourceMappingURL=hx-rating-BGK4AxvI.js.map
|