@helixui/library 3.2.0 → 3.3.0-next.111
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/custom-elements.json +175 -326
- 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.styles.d.ts.map +1 -1
- package/dist/components/hx-alert/index.js +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-breadcrumb/hx-breadcrumb-item.styles.d.ts.map +1 -1
- package/dist/components/hx-breadcrumb/index.js +1 -1
- package/dist/components/hx-button/hx-button.d.ts +46 -49
- package/dist/components/hx-button/hx-button.d.ts.map +1 -1
- package/dist/components/hx-button/hx-button.styles.d.ts.map +1 -1
- package/dist/components/hx-button/index.js +1 -1
- package/dist/components/hx-card/hx-card.styles.d.ts.map +1 -1
- package/dist/components/hx-card/index.js +1 -1
- package/dist/components/hx-carousel/index.js +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.styles.d.ts.map +1 -1
- package/dist/components/hx-clinical-status/index.js +1 -1
- package/dist/components/hx-code-snippet/hx-code-snippet.styles.d.ts.map +1 -1
- package/dist/components/hx-code-snippet/index.js +1 -1
- package/dist/components/hx-color-picker/hx-color-picker.styles.d.ts.map +1 -1
- package/dist/components/hx-color-picker/index.js +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-data-table/hx-data-table.styles.d.ts.map +1 -1
- package/dist/components/hx-data-table/index.js +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-dialog/hx-dialog.styles.d.ts.map +1 -1
- package/dist/components/hx-dialog/index.js +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-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-icon-button/hx-icon-button.styles.d.ts.map +1 -1
- package/dist/components/hx-icon-button/index.js +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-list/hx-list-item.styles.d.ts.map +1 -1
- package/dist/components/hx-list/index.js +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-meter/hx-meter.styles.d.ts.map +1 -1
- package/dist/components/hx-meter/index.js +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-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-pagination/hx-pagination.styles.d.ts.map +1 -1
- package/dist/components/hx-pagination/index.js +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-popover/hx-popover.styles.d.ts.map +1 -1
- package/dist/components/hx-popover/index.js +1 -1
- package/dist/components/hx-radio-group/hx-radio.styles.d.ts.map +1 -1
- package/dist/components/hx-radio-group/index.js +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-select/hx-select.styles.d.ts.map +1 -1
- package/dist/components/hx-select/index.js +1 -1
- package/dist/components/hx-side-nav/hx-nav-item.d.ts +7 -5
- 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 +10 -23
- 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.styles.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 +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-split-panel/hx-split-panel.styles.d.ts.map +1 -1
- package/dist/components/hx-split-panel/index.js +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-switch/hx-switch.styles.d.ts.map +1 -1
- package/dist/components/hx-switch/index.js +1 -1
- package/dist/components/hx-table/hx-table.styles.d.ts.map +1 -1
- package/dist/components/hx-table/index.js +1 -1
- package/dist/components/hx-tabs/hx-tab-panel.styles.d.ts.map +1 -1
- package/dist/components/hx-tabs/hx-tab.styles.d.ts.map +1 -1
- package/dist/components/hx-tabs/index.js +1 -1
- package/dist/components/hx-text-input/hx-text-input.d.ts +18 -35
- package/dist/components/hx-text-input/hx-text-input.d.ts.map +1 -1
- package/dist/components/hx-text-input/hx-text-input.styles.d.ts.map +1 -1
- package/dist/components/hx-text-input/index.js +1 -1
- package/dist/components/hx-textarea/hx-textarea.styles.d.ts.map +1 -1
- package/dist/components/hx-textarea/index.js +1 -1
- package/dist/components/hx-theme/hx-theme.d.ts +14 -2
- package/dist/components/hx-theme/hx-theme.d.ts.map +1 -1
- package/dist/components/hx-theme/index.js +1 -1
- package/dist/components/hx-time-picker/index.js +1 -1
- package/dist/components/hx-toast/hx-toast.d.ts +12 -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-toggle-button/hx-toggle-button.styles.d.ts.map +1 -1
- package/dist/components/hx-toggle-button/index.js +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.styles.d.ts.map +1 -1
- package/dist/components/hx-tree-view/hx-tree-view.styles.d.ts.map +1 -1
- package/dist/components/hx-tree-view/index.js +1 -1
- package/dist/css/helix-all.css +351 -308
- package/dist/css/helix-core.css +180 -56
- package/dist/css/helix-data.css +23 -16
- package/dist/css/helix-feedback.css +30 -29
- package/dist/css/helix-forms.css +53 -120
- package/dist/css/helix-layout.css +2 -8
- package/dist/css/helix-media.css +3 -3
- package/dist/css/helix-navigation.css +49 -45
- package/dist/css/helix-overlay.css +3 -12
- package/dist/css/helix-tokens.css +60 -5
- package/dist/css/helix-utility.css +5 -11
- package/dist/css/hx-alert.css +1 -4
- package/dist/css/hx-banner.css +2 -8
- package/dist/css/hx-button.css +176 -43
- package/dist/css/hx-card.css +1 -4
- package/dist/css/hx-carousel.css +3 -3
- package/dist/css/hx-checkbox.css +2 -5
- package/dist/css/hx-clinical-status.css +2 -4
- package/dist/css/hx-code-snippet.css +20 -6
- package/dist/css/hx-color-picker.css +3 -13
- package/dist/css/hx-combobox.css +7 -13
- package/dist/css/hx-data-table.css +2 -8
- package/dist/css/hx-date-picker.css +7 -23
- package/dist/css/hx-dialog.css +1 -4
- package/dist/css/hx-drawer.css +1 -4
- package/dist/css/hx-file-upload.css +4 -13
- package/dist/css/hx-icon-button.css +2 -5
- package/dist/css/hx-link.css +1 -4
- package/dist/css/hx-meter.css +1 -2
- package/dist/css/hx-nav.css +2 -8
- package/dist/css/hx-overflow-menu.css +2 -8
- package/dist/css/hx-pagination.css +2 -8
- package/dist/css/hx-phi-field.css +1 -4
- package/dist/css/hx-popover.css +1 -4
- package/dist/css/hx-rating.css +3 -4
- package/dist/css/hx-select.css +3 -7
- package/dist/css/hx-side-nav.css +41 -15
- package/dist/css/hx-slider.css +4 -7
- package/dist/css/hx-split-button.css +5 -11
- package/dist/css/hx-split-panel.css +2 -8
- package/dist/css/hx-switch.css +3 -6
- package/dist/css/hx-table.css +1 -2
- package/dist/css/hx-text-input.css +8 -14
- package/dist/css/hx-textarea.css +2 -5
- package/dist/css/hx-time-picker.css +3 -3
- package/dist/css/hx-toast.css +26 -15
- package/dist/css/hx-toggle-button.css +4 -7
- package/dist/css/hx-top-nav.css +1 -4
- package/dist/css/hx-tree-view.css +1 -2
- package/dist/css/index.css +1 -1
- package/dist/css/manifest.json +63 -46
- package/dist/index.js +46 -46
- package/dist/shared/{hx-accordion-cnKg4_la.js → hx-accordion-ZVzgDzTG.js} +4 -5
- package/dist/shared/hx-accordion-ZVzgDzTG.js.map +1 -0
- package/dist/shared/{hx-alert-BZH8iHQf.js → hx-alert-CLn7CstP.js} +2 -5
- package/dist/shared/hx-alert-CLn7CstP.js.map +1 -0
- package/dist/shared/{hx-banner-DT7Zn9Bo.js → hx-banner-D3DzpfcP.js} +7 -13
- package/dist/shared/hx-banner-D3DzpfcP.js.map +1 -0
- package/dist/shared/{hx-breadcrumb-item-COeYcB2x.js → hx-breadcrumb-item-3tKppF9h.js} +2 -5
- package/dist/shared/{hx-breadcrumb-item-COeYcB2x.js.map → hx-breadcrumb-item-3tKppF9h.js.map} +1 -1
- package/dist/shared/{hx-button-modUSOpY.js → hx-button-DPY6SPVT.js} +201 -69
- package/dist/shared/hx-button-DPY6SPVT.js.map +1 -0
- package/dist/shared/{hx-card-CU1QnjNb.js → hx-card-qNAM2QNV.js} +6 -9
- package/dist/shared/hx-card-qNAM2QNV.js.map +1 -0
- package/dist/shared/{hx-carousel-item-BaE4hpLl.js → hx-carousel-item-z1Lc24op.js} +4 -4
- package/dist/shared/hx-carousel-item-z1Lc24op.js.map +1 -0
- package/dist/shared/{hx-checkbox-C46TyXhM.js → hx-checkbox-D7xma9YH.js} +7 -10
- package/dist/shared/hx-checkbox-D7xma9YH.js.map +1 -0
- package/dist/shared/{hx-clinical-status-BmSjfSEN.js → hx-clinical-status-D3XQIOqX.js} +3 -5
- package/dist/shared/hx-clinical-status-D3XQIOqX.js.map +1 -0
- package/dist/shared/{hx-code-snippet-CJ0FbQYG.js → hx-code-snippet-fVV3Z2DZ.js} +55 -41
- package/dist/shared/hx-code-snippet-fVV3Z2DZ.js.map +1 -0
- package/dist/shared/{hx-color-picker-DiDLZyvK.js → hx-color-picker-uRc865FJ.js} +23 -33
- package/dist/shared/hx-color-picker-uRc865FJ.js.map +1 -0
- package/dist/shared/{hx-combobox-DaA5dBC4.js → hx-combobox-DDzqNKEW.js} +11 -17
- package/dist/shared/hx-combobox-DDzqNKEW.js.map +1 -0
- package/dist/shared/{hx-data-table-Cq3t86Ic.js → hx-data-table-CLqVqdxr.js} +3 -9
- package/dist/shared/hx-data-table-CLqVqdxr.js.map +1 -0
- package/dist/shared/{hx-date-picker-DMqRQNSB.js → hx-date-picker-2iRG1p74.js} +14 -30
- package/dist/shared/hx-date-picker-2iRG1p74.js.map +1 -0
- package/dist/shared/{hx-dialog-eIS8tcDm.js → hx-dialog-DRN_1-Y-.js} +2 -5
- package/dist/shared/hx-dialog-DRN_1-Y-.js.map +1 -0
- package/dist/shared/{hx-drawer-DDhDz7RI.js → hx-drawer-Y1Ui2IWJ.js} +2 -5
- package/dist/shared/hx-drawer-Y1Ui2IWJ.js.map +1 -0
- package/dist/shared/{hx-file-upload-zTDbjsRw.js → hx-file-upload-D3rKROK5.js} +17 -26
- package/dist/shared/hx-file-upload-D3rKROK5.js.map +1 -0
- package/dist/shared/{hx-icon-button-BmV97nqz.js → hx-icon-button-CGNdQSFM.js} +3 -6
- package/dist/shared/hx-icon-button-CGNdQSFM.js.map +1 -0
- package/dist/shared/{hx-link-DmiV-mPw.js → hx-link-C-O6vq0Q.js} +5 -8
- package/dist/shared/hx-link-C-O6vq0Q.js.map +1 -0
- package/dist/shared/{hx-list-CkphGi9T.js → hx-list-MyEhh8c7.js} +3 -5
- package/dist/shared/hx-list-MyEhh8c7.js.map +1 -0
- package/dist/shared/{hx-menu-divider-j__TZjH2.js → hx-menu-divider-C2omnPtj.js} +2 -5
- package/dist/shared/hx-menu-divider-C2omnPtj.js.map +1 -0
- package/dist/shared/{hx-meter-Cm7k_Ro8.js → hx-meter-BPscsw5t.js} +2 -3
- package/dist/shared/hx-meter-BPscsw5t.js.map +1 -0
- package/dist/shared/{hx-nav-item-D8xHLVOs.js → hx-nav-item-xqRPOCWX.js} +172 -110
- package/dist/shared/hx-nav-item-xqRPOCWX.js.map +1 -0
- package/dist/shared/{hx-nav-LoyEKZQC.js → hx-nav-ldFM3Fle.js} +37 -43
- package/dist/shared/hx-nav-ldFM3Fle.js.map +1 -0
- package/dist/shared/{hx-overflow-menu-BmKyAp5D.js → hx-overflow-menu-DCLsdIBy.js} +3 -9
- package/dist/shared/hx-overflow-menu-DCLsdIBy.js.map +1 -0
- package/dist/shared/{hx-pagination-Dqw5dorC.js → hx-pagination-C7y8GVyU.js} +54 -60
- package/dist/shared/hx-pagination-C7y8GVyU.js.map +1 -0
- package/dist/shared/{hx-phi-field-Bf9TdtC1.js → hx-phi-field-C19oxlrr.js} +2 -5
- package/dist/shared/hx-phi-field-C19oxlrr.js.map +1 -0
- package/dist/shared/{hx-popover-B93rTAfr.js → hx-popover-B-FP3-wW.js} +8 -11
- package/dist/shared/hx-popover-B-FP3-wW.js.map +1 -0
- package/dist/shared/{hx-radio-N8xgDd_5.js → hx-radio-CJvNU2yP.js} +4 -7
- package/dist/shared/{hx-radio-N8xgDd_5.js.map → hx-radio-CJvNU2yP.js.map} +1 -1
- package/dist/shared/{hx-rating-i2FL1WUN.js → hx-rating-C3QP53k9.js} +4 -5
- package/dist/shared/hx-rating-C3QP53k9.js.map +1 -0
- package/dist/shared/{hx-select-vgaBo1Ai.js → hx-select-C8fEHQhC.js} +4 -8
- package/dist/shared/hx-select-C8fEHQhC.js.map +1 -0
- package/dist/shared/{hx-slider-ydBamYhd.js → hx-slider-Blmv_rwS.js} +26 -29
- package/dist/shared/hx-slider-Blmv_rwS.js.map +1 -0
- package/dist/shared/{hx-split-button-BeMsmS6N.js → hx-split-button-Djnc5Aeg.js} +6 -12
- package/dist/shared/hx-split-button-Djnc5Aeg.js.map +1 -0
- package/dist/shared/{hx-split-panel-BVG1VWNT.js → hx-split-panel-B-u0Z3mm.js} +3 -9
- package/dist/shared/hx-split-panel-B-u0Z3mm.js.map +1 -0
- package/dist/shared/{hx-step-DL3PbOzm.js → hx-step-R2rjp1fT.js} +2 -5
- package/dist/shared/hx-step-R2rjp1fT.js.map +1 -0
- package/dist/shared/{hx-switch-Dougzsgp.js → hx-switch-BrZFaRue.js} +8 -11
- package/dist/shared/hx-switch-BrZFaRue.js.map +1 -0
- package/dist/shared/{hx-tab-panel-CbkO9VKu.js → hx-tab-panel-DspCrKqo.js} +3 -9
- package/dist/shared/{hx-tab-panel-CbkO9VKu.js.map → hx-tab-panel-DspCrKqo.js.map} +1 -1
- package/dist/shared/{hx-td-1zwTFLRw.js → hx-td-DnnEMIuA.js} +2 -3
- package/dist/shared/hx-td-DnnEMIuA.js.map +1 -0
- package/dist/shared/{hx-text-input-B-caO5fI.js → hx-text-input-D6FlOZM-.js} +24 -31
- package/dist/shared/hx-text-input-D6FlOZM-.js.map +1 -0
- package/dist/shared/{hx-textarea-D9O4U8cb.js → hx-textarea-CNG590KY.js} +3 -6
- package/dist/shared/hx-textarea-CNG590KY.js.map +1 -0
- package/dist/shared/{hx-theme-BiyQ7UUK.js → hx-theme-BsefFWTO.js} +83 -113
- package/dist/shared/hx-theme-BsefFWTO.js.map +1 -0
- package/dist/shared/{hx-time-picker-m0z4nFB-.js → hx-time-picker-BoEIZwzv.js} +4 -4
- package/dist/shared/{hx-time-picker-m0z4nFB-.js.map → hx-time-picker-BoEIZwzv.js.map} +1 -1
- package/dist/shared/{hx-toggle-button-Dd8clXB4.js → hx-toggle-button-iLiYrMbD.js} +24 -27
- package/dist/shared/hx-toggle-button-iLiYrMbD.js.map +1 -0
- package/dist/shared/{hx-top-nav-CchPYaiV.js → hx-top-nav-DP6OFS8C.js} +11 -14
- package/dist/shared/hx-top-nav-DP6OFS8C.js.map +1 -0
- package/dist/shared/{hx-tree-item-DtMC3DTa.js → hx-tree-item-C2CiWuDE.js} +4 -11
- package/dist/shared/hx-tree-item-C2CiWuDE.js.map +1 -0
- package/dist/shared/{toast-factory-DvDRAh0l.js → toast-factory-YSznocIV.js} +66 -56
- package/dist/shared/toast-factory-YSznocIV.js.map +1 -0
- package/figma-inventory.json +283 -420
- package/package.json +2 -2
- package/dist/shared/hx-accordion-cnKg4_la.js.map +0 -1
- package/dist/shared/hx-alert-BZH8iHQf.js.map +0 -1
- package/dist/shared/hx-banner-DT7Zn9Bo.js.map +0 -1
- package/dist/shared/hx-button-modUSOpY.js.map +0 -1
- package/dist/shared/hx-card-CU1QnjNb.js.map +0 -1
- package/dist/shared/hx-carousel-item-BaE4hpLl.js.map +0 -1
- package/dist/shared/hx-checkbox-C46TyXhM.js.map +0 -1
- package/dist/shared/hx-clinical-status-BmSjfSEN.js.map +0 -1
- package/dist/shared/hx-code-snippet-CJ0FbQYG.js.map +0 -1
- package/dist/shared/hx-color-picker-DiDLZyvK.js.map +0 -1
- package/dist/shared/hx-combobox-DaA5dBC4.js.map +0 -1
- package/dist/shared/hx-data-table-Cq3t86Ic.js.map +0 -1
- package/dist/shared/hx-date-picker-DMqRQNSB.js.map +0 -1
- package/dist/shared/hx-dialog-eIS8tcDm.js.map +0 -1
- package/dist/shared/hx-drawer-DDhDz7RI.js.map +0 -1
- package/dist/shared/hx-file-upload-zTDbjsRw.js.map +0 -1
- package/dist/shared/hx-icon-button-BmV97nqz.js.map +0 -1
- package/dist/shared/hx-link-DmiV-mPw.js.map +0 -1
- package/dist/shared/hx-list-CkphGi9T.js.map +0 -1
- package/dist/shared/hx-menu-divider-j__TZjH2.js.map +0 -1
- package/dist/shared/hx-meter-Cm7k_Ro8.js.map +0 -1
- package/dist/shared/hx-nav-LoyEKZQC.js.map +0 -1
- package/dist/shared/hx-nav-item-D8xHLVOs.js.map +0 -1
- package/dist/shared/hx-overflow-menu-BmKyAp5D.js.map +0 -1
- package/dist/shared/hx-pagination-Dqw5dorC.js.map +0 -1
- package/dist/shared/hx-phi-field-Bf9TdtC1.js.map +0 -1
- package/dist/shared/hx-popover-B93rTAfr.js.map +0 -1
- package/dist/shared/hx-rating-i2FL1WUN.js.map +0 -1
- package/dist/shared/hx-select-vgaBo1Ai.js.map +0 -1
- package/dist/shared/hx-slider-ydBamYhd.js.map +0 -1
- package/dist/shared/hx-split-button-BeMsmS6N.js.map +0 -1
- package/dist/shared/hx-split-panel-BVG1VWNT.js.map +0 -1
- package/dist/shared/hx-step-DL3PbOzm.js.map +0 -1
- package/dist/shared/hx-switch-Dougzsgp.js.map +0 -1
- package/dist/shared/hx-td-1zwTFLRw.js.map +0 -1
- package/dist/shared/hx-text-input-B-caO5fI.js.map +0 -1
- package/dist/shared/hx-textarea-D9O4U8cb.js.map +0 -1
- package/dist/shared/hx-theme-BiyQ7UUK.js.map +0 -1
- package/dist/shared/hx-toggle-button-Dd8clXB4.js.map +0 -1
- package/dist/shared/hx-top-nav-CchPYaiV.js.map +0 -1
- package/dist/shared/hx-tree-item-DtMC3DTa.js.map +0 -1
- package/dist/shared/toast-factory-DvDRAh0l.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hx-file-upload-D3rKROK5.js","sources":["../../src/components/hx-file-upload/hx-file-upload.styles.ts","../../src/components/hx-file-upload/hx-file-upload.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const helixFileUploadStyles = 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 box-sizing: border-box;\n }\n\n .field {\n display: flex;\n flex-direction: column;\n gap: var(--hx-space-2, 0.5rem);\n font-family: var(--hx-file-upload-font-family, var(--hx-font-family-sans, sans-serif));\n }\n\n /* ─── Label ─── */\n\n .field__label {\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-color-text-strong, #202b39);\n line-height: var(--hx-line-height-normal, 1.5);\n }\n\n /* ─── Dropzone ─── */\n\n .dropzone {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--hx-space-2, 0.5rem);\n min-height: var(--hx-space-32, 8rem);\n padding: var(--hx-space-6, 1.5rem) var(--hx-space-4, 1rem);\n border: var(--hx-border-width-thin, 1px) dashed\n var(--hx-file-upload-dropzone-border-color, var(--hx-color-border-strong, #66787b));\n border-radius: var(--hx-file-upload-dropzone-border-radius, var(--hx-border-radius-lg, 0.5rem));\n background-color: var(--hx-file-upload-dropzone-bg, var(--hx-color-surface-raised, #f5f8f3));\n cursor: pointer;\n text-align: center;\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 user-select: none;\n color: var(--hx-color-text-secondary, #313e4b);\n font-size: var(--hx-font-size-sm, 0.875rem);\n }\n\n .dropzone:focus-visible {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-file-upload-focus-ring-color, var(--hx-focus-ring-color, #0f7078));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n border-color: var(--hx-file-upload-focus-ring-color, var(--hx-focus-ring-color, #0f7078));\n }\n\n .dropzone--drag-over {\n border-color: var(--hx-color-primary-500, #429797);\n background-color: var(\n --hx-file-upload-dropzone-active-bg,\n color-mix(\n in srgb,\n var(--hx-color-primary-500, #429797) 8%,\n var(--hx-color-surface-default, #ffffff)\n )\n );\n border-style: solid;\n }\n\n .dropzone--error {\n border-color: var(--hx-file-upload-error-color, var(--hx-color-error-500, #e5493e));\n }\n\n @media (prefers-reduced-motion: reduce) {\n .dropzone {\n transition: none;\n }\n }\n\n /* ─── Hidden file input ─── */\n\n .file-input {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n }\n\n /* ─── File list ─── */\n\n .file-list {\n display: flex;\n flex-direction: column;\n gap: var(--hx-space-2, 0.5rem);\n list-style: none;\n margin: 0;\n padding: 0;\n }\n\n .file-list:empty {\n display: none;\n }\n\n /* ─── File item ─── */\n\n .file-item {\n display: flex;\n flex-direction: column;\n gap: var(--hx-space-1, 0.25rem);\n padding: var(--hx-space-2, 0.5rem) var(--hx-space-3, 0.75rem);\n border: var(--hx-border-width-thin, 1px) solid var(--hx-color-border-default, #d6dbd5);\n border-radius: var(--hx-border-radius-md, 0.375rem);\n background-color: var(--hx-color-surface-default, #ffffff);\n }\n\n .file-item__row {\n display: flex;\n align-items: center;\n gap: var(--hx-space-2, 0.5rem);\n }\n\n .file-item__name {\n flex: 1;\n font-size: var(--hx-font-size-sm, 0.875rem);\n font-weight: var(--hx-font-weight-medium, 500);\n color: var(--hx-color-text-strong, #202b39);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .file-item__size {\n flex-shrink: 0;\n font-size: var(--hx-font-size-xs, 0.75rem);\n color: var(--hx-color-text-muted, #4a5362);\n }\n\n .file-item__remove {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 44px;\n min-height: 44px;\n padding: var(--hx-space-1, 0.25rem);\n border: none;\n border-radius: var(--hx-border-radius-sm, 0.25rem);\n background: transparent;\n color: var(--hx-color-text-muted, #4a5362);\n cursor: pointer;\n line-height: 1;\n transition:\n color var(--hx-transition-fast, 150ms ease),\n background-color var(--hx-transition-fast, 150ms ease);\n }\n\n .file-item__remove:hover {\n color: var(--hx-file-upload-error-color, var(--hx-color-error-text, #c92a2a));\n background-color: color-mix(in srgb, var(--hx-color-error-500, #e5493e) 8%, transparent);\n }\n\n .file-item__remove:focus-visible {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-file-upload-focus-ring-color, var(--hx-focus-ring-color, #0f7078));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .file-item__remove {\n transition: none;\n }\n }\n\n /* ─── Progress bar ─── */\n\n .progress-track {\n width: 100%;\n height: var(--hx-file-upload-progress-height, var(--hx-space-1, 0.25rem));\n background-color: var(--hx-color-border-default, #d6dbd5);\n border-radius: var(--hx-border-radius-full, 9999px);\n overflow: hidden;\n }\n\n .progress-bar {\n height: 100%;\n width: 100%;\n background-color: var(--hx-file-upload-progress-color, var(--hx-color-primary-500, #429797));\n border-radius: inherit;\n transform-origin: left center;\n transform: scaleX(var(--_progress-ratio, 0));\n transition: transform var(--hx-transition-fast, 150ms ease);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .progress-bar {\n transition: none;\n }\n }\n\n /* ─── Screen-reader only utility ─── */\n\n .sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n }\n\n /* ─── Error message ─── */\n\n .field__error {\n font-size: var(--hx-font-size-xs, 0.75rem);\n color: var(--hx-file-upload-error-color, var(--hx-color-error-text, #c92a2a));\n line-height: var(--hx-line-height-normal, 1.5);\n }\n\n /* ─── Forced colors (Windows High Contrast / Forced Colors Mode) ─── */\n\n @media (forced-colors: active) {\n .dropzone {\n border: 2px dashed ButtonText;\n background-color: Canvas;\n color: ButtonText;\n }\n\n .dropzone--drag-over {\n border: 2px solid Highlight;\n outline: none;\n background-color: Canvas;\n }\n\n .dropzone--error {\n border: 2px solid LinkText;\n }\n\n .dropzone:focus-visible {\n outline: 2px solid Highlight;\n outline-offset: 2px;\n }\n\n .progress-bar {\n background: Highlight;\n forced-color-adjust: none;\n }\n\n .file-item__remove:hover {\n outline: 2px solid Highlight;\n background-color: transparent;\n color: ButtonText;\n }\n\n .file-item__remove:focus-visible {\n outline: 2px solid Highlight;\n }\n\n :host([disabled]) .dropzone {\n border-color: GrayText;\n color: GrayText;\n opacity: 1;\n }\n }\n`;\n","import { html, nothing, type PropertyValues } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property, query, state } from 'lit/decorators.js';\nimport { HelixElement, createIdCounter } from '../../base/index.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { repeat } from 'lit/directives/repeat.js';\nimport { mixinDelegatesAria } from '../../mixins/index.js';\nimport { FormMixin } from '../../mixins/FormMixin.js';\nimport { helixFileUploadStyles } from './hx-file-upload.styles.js';\nimport { forcedColorsField } from '../../styles/forced-colors.js';\n\nconst _nextFileUploadId = createIdCounter('hx-file-upload');\n\ninterface FileEntry {\n file: File;\n progress: number;\n}\n\n/** Detail for the hx-upload event dispatched by hx-file-upload. */\nexport interface HxFileUploadDetail {\n files: File[];\n}\n\n/** Detail for the hx-remove event dispatched by hx-file-upload. */\nexport interface HxFileRemoveDetail {\n file: File;\n index: number;\n}\n\n/** Detail for the hx-error event dispatched by hx-file-upload. */\nexport interface HxFileErrorDetail {\n message: string;\n files: File[];\n}\n\n/**\n * A drag-and-drop file upload component with client-side validation,\n * file list management, per-file progress, and native form association.\n *\n * @summary Form-associated file upload dropzone with drag-and-drop, validation, and progress tracking.\n *\n * @tag hx-file-upload\n *\n * @slot - Default dropzone content. Replaces the built-in \"Drag files here or click to browse\" prompt.\n * @slot file-list - Custom file list display. When provided, the built-in file list is hidden.\n *\n * @fires {CustomEvent<{files: File[]}>} hx-upload - Dispatched when valid files are selected via drag-and-drop or the file picker.\n * @fires {CustomEvent<{file: File, index: number}>} hx-remove - Dispatched when a file is removed from the list.\n * @fires {CustomEvent<{message: string, files: File[]}>} hx-error - Dispatched when file validation fails (type or size constraint).\n *\n * @csspart dropzone - The drag-and-drop target area.\n * @csspart file-list - The container wrapping the list of selected files.\n * @csspart file-item - An individual file entry in the list.\n * @csspart progress - The progress bar track for a file item.\n * @csspart label - The visible label element.\n * @csspart error - The error message container below the dropzone.\n *\n * @cssprop [--hx-file-upload-dropzone-bg=var(--hx-color-neutral-50)] - Dropzone background color.\n * @cssprop [--hx-file-upload-dropzone-border-color=var(--hx-color-neutral-300)] - Dropzone border color.\n * @cssprop [--hx-file-upload-dropzone-border-radius=var(--hx-border-radius-lg)] - Dropzone border radius.\n * @cssprop [--hx-file-upload-dropzone-active-bg] - Dropzone background when a file is dragged over.\n * @cssprop [--hx-file-upload-progress-color=var(--hx-color-primary-500)] - Progress bar fill color.\n * @cssprop [--hx-file-upload-error-color=var(--hx-color-error-500)] - Error state and remove-button hover color.\n * @cssprop [--hx-opacity-disabled] - Opacity.\n * @cssprop [--hx-space-2] - Spacing token.\n * @cssprop [--hx-file-upload-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-space-32] - Spacing token.\n * @cssprop [--hx-space-6] - Spacing token.\n * @cssprop [--hx-space-4] - Spacing token.\n * @cssprop [--hx-border-width-thin] - Width.\n * @cssprop [--hx-color-neutral-300] - Color.\n * @cssprop [--hx-border-radius-lg] - CSS custom property.\n * @cssprop [--hx-color-neutral-50] - Color.\n * @cssprop [--hx-transition-fast] - Transition timing.\n * @cssprop [--hx-color-neutral-600] - Color.\n * @cssprop [--hx-focus-ring-width] - Width.\n * @cssprop [--hx-file-upload-focus-ring-color] - Color.\n * @cssprop [--hx-focus-ring-color] - Color.\n * @cssprop [--hx-color-primary-500] - Color.\n * @cssprop [--hx-focus-ring-offset] - CSS custom property.\n * @cssprop [--hx-color-neutral-0] - Color.\n * @cssprop [--hx-color-error-500] - Color.\n * @cssprop [--hx-space-3] - Spacing token.\n * @cssprop [--hx-color-neutral-200] - Color.\n * @cssprop [--hx-border-radius-md] - CSS custom property.\n * @cssprop [--hx-color-neutral-800] - Color.\n * @cssprop [--hx-font-size-xs] - Font size.\n * @cssprop [--hx-color-neutral-500] - Color.\n * @cssprop [--hx-border-radius-sm] - CSS custom property.\n * @cssprop [--hx-color-error-text] - Color.\n * @cssprop [--hx-file-upload-progress-height=var(--hx-space-1)] - Height.\n * @cssprop [--hx-border-radius-full] - CSS custom property.\n */\n@customElement('hx-file-upload')\nexport class HelixFileUpload extends FormMixin(mixinDelegatesAria(HelixElement)) {\n static override styles = [helixFileUploadStyles, forcedColorsField];\n\n // ─── Form Association ───\n\n /** Marks this element as form-associated for ElementInternals support. @internal */\n static override formAssociated = true;\n\n // ─── Properties ───\n\n /**\n * The form field name used during form submission.\n * @attr name\n */\n @property({ type: String, reflect: true })\n name = '';\n\n /**\n * Accepted file types as a comma-separated list of MIME types or extensions.\n * Mirrors the native `<input type=\"file\" accept>` attribute.\n * @attr accept\n */\n @property({ type: String })\n accept = '';\n\n /**\n * Maximum allowed file size in bytes. 0 means unlimited.\n * @attr max-size\n */\n @property({ type: Number, attribute: 'max-size' })\n maxSize = 0;\n\n /**\n * Maximum number of files that can be selected. 0 means unlimited.\n * @attr max-files\n */\n @property({ type: Number, attribute: 'max-files' })\n maxFiles = 0;\n\n /**\n * Whether multiple files may be selected at once.\n * @attr multiple\n */\n @property({ type: Boolean })\n multiple = false;\n\n /**\n * Visible label text for the dropzone.\n * @attr label\n */\n @property({ type: String })\n label = '';\n\n /**\n * Whether the component is disabled.\n * @attr disabled\n */\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n /**\n * Error message displayed below the dropzone. Also puts the dropzone in an error visual state.\n * @attr error\n */\n @property({ type: String })\n error = '';\n\n /**\n * Instructional text shown in the dropzone when no custom slot content is provided.\n * Also used as the accessible label for the dropzone.\n * @attr label-dropzone\n */\n @property({ type: String, attribute: 'label-dropzone' })\n labelDropzone = 'Drag files here or click to browse';\n\n /** Accessible label for the selected files list. */\n @property({ type: String, attribute: 'label-file-list' })\n labelFileList = 'Selected files';\n\n /**\n * Accessible label for the dropzone when no visible label text is provided.\n * Falls back to `label-dropzone` prop value, then a default string.\n *\n * Accepts both `accessible-label` and the standard `aria-label` HTML attribute.\n * `accessible-label` takes precedence when both are set.\n *\n * @attr accessible-label\n */\n @property({ type: String, attribute: 'accessible-label' })\n accessibleLabel: string = '';\n\n /**\n * Returns the effective label for the dropzone, checking accessible-label first,\n * then the aria-label attribute, falling back to empty string.\n * @internal\n */\n private get _effectiveLabel(): string {\n return this.accessibleLabel?.trim() || this.ariaLabel?.trim() || '';\n }\n\n /**\n * Generates upload progress description for screen readers.\n * @param name - file name\n * @param progress - progress percentage 0-100\n */\n @property({ attribute: false })\n labelUploadProgress: (name: string, progress: number) => string = (name, progress) =>\n `Upload progress for ${name}: ${progress}%`;\n\n /**\n * Screen reader announcement when file drag detected. Override for i18n.\n * @attr label-drag-detected\n */\n @property({ attribute: 'label-drag-detected' })\n labelDragDetected = 'File detected. Release to upload.';\n\n // ─── Internal State ───\n\n /** The list of currently selected file entries, each with a file reference and upload progress. @internal */\n @state() private _files: FileEntry[] = [];\n /** Whether a file is currently being dragged over the dropzone. @internal */\n @state() private _dragOver = false;\n /** Whether the named file-list slot contains projected content. @internal */\n @state() private _hasFileListSlot = false;\n\n // ─── Internal References ───\n\n /** Reference to the hidden native file input element used to open the OS file picker. @internal */\n @query('.file-input')\n private _fileInput: HTMLInputElement | null | undefined;\n\n // ─── Stable IDs ───\n\n /** @internal */\n private readonly _baseId = _nextFileUploadId();\n /** @internal */\n private readonly _labelId = `${this._baseId}-label`;\n /** @internal */\n private readonly _errorId = `${this._baseId}-error`;\n /** @internal */\n private readonly _dropzoneId = `${this._baseId}-dropzone`;\n /** @internal */\n private readonly _liveId = `${this._baseId}-live`;\n\n // ─── Slot Handling ───\n\n /** @internal */\n private _handleFileListSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n this._hasFileListSlot = slot.assignedElements({ flatten: true }).length > 0;\n }\n\n // ─── Lifecycle ───\n\n override updated(changedProperties: PropertyValues<this>): void {\n super.updated(changedProperties);\n if (changedProperties.has('_files' as keyof HelixFileUpload) || changedProperties.has('name')) {\n this._syncFormValue();\n }\n // Force screen reader re-announcement when error text changes (a11y-v3-005)\n if (changedProperties.has('error') && this.error) {\n const errorEl = this.shadowRoot?.querySelector('[role=\"alert\"]');\n if (errorEl) {\n const msg = this.error;\n requestAnimationFrame(() => {\n errorEl.textContent = '';\n requestAnimationFrame(() => {\n errorEl.textContent = msg;\n });\n });\n }\n }\n }\n\n // ─── Form Integration ───\n\n /** @internal */\n protected override _onFormReset(): void {\n this._files = [];\n this._internals.setFormValue(null);\n this._resetInteractionState();\n }\n\n /** @internal */\n protected override _onFormDisabled(disabled: boolean): void {\n this.disabled = disabled;\n }\n\n /** @internal */\n protected override _onFormStateRestore(\n _state: File | string | FormData | null,\n _mode: 'restore' | 'autocomplete',\n ): void {\n if (_mode === 'restore' || _mode === 'autocomplete') {\n this._files = [];\n this._internals.setFormValue(null);\n }\n }\n\n /** @internal */\n private _syncFormValue(): void {\n if (this._files.length === 0) {\n this._internals.setFormValue(null);\n return;\n }\n\n if (this._files.length === 1) {\n // Single file — pass directly as File (accepted by setFormValue)\n const firstEntry = this._files[0];\n if (firstEntry) {\n this._internals.setFormValue(firstEntry.file);\n }\n return;\n }\n\n // Multiple files — use FormData so all files are submitted under the same name\n const formData = new FormData();\n for (const entry of this._files) {\n formData.append(this.name, entry.file, entry.file.name);\n }\n this._internals.setFormValue(formData);\n }\n\n // ─── Validation ───\n\n /**\n * Validates a file against `accept` and `maxSize` constraints.\n * Returns null on success, or an error message string on failure.\n */\n /** @internal */\n private _validateFile(file: File): string | null {\n if (this.accept) {\n const accepted = this._isAccepted(file);\n if (!accepted) {\n return `\"${file.name}\" has an unsupported file type. Accepted types: ${this.accept}`;\n }\n }\n\n if (this.maxSize > 0 && file.size > this.maxSize) {\n const maxMb = (this.maxSize / (1024 * 1024)).toFixed(1);\n return `\"${file.name}\" exceeds the maximum size of ${maxMb} MB.`;\n }\n\n return null;\n }\n\n /**\n * Checks whether a file is accepted given the `accept` attribute value.\n * Handles MIME types (e.g. \"image/png\"), wildcard MIME types (e.g. \"image/*\"),\n * and extensions (e.g. \".pdf\").\n */\n /** @internal */\n private _isAccepted(file: File): boolean {\n const tokens = this.accept.split(',').map((t) => t.trim().toLowerCase());\n\n for (const token of tokens) {\n if (token.startsWith('.')) {\n // Extension match\n if (file.name.toLowerCase().endsWith(token)) return true;\n } else if (token.endsWith('/*')) {\n // Wildcard MIME type e.g. \"image/*\"\n const baseType = token.slice(0, -2);\n if (file.type.toLowerCase().startsWith(baseType)) return true;\n } else {\n // Exact MIME type\n if (file.type.toLowerCase() === token) return true;\n }\n }\n\n return false;\n }\n\n // ─── File Processing ───\n\n /** @internal */\n private _processFiles(rawFiles: File[]): void {\n if (this.disabled) return;\n\n const candidateFiles = this.multiple ? rawFiles : rawFiles.slice(0, 1);\n const validFiles: File[] = [];\n const invalidFiles: File[] = [];\n const errorMessages: string[] = [];\n\n for (const file of candidateFiles) {\n const validationError = this._validateFile(file);\n if (validationError) {\n invalidFiles.push(file);\n errorMessages.push(validationError);\n } else {\n validFiles.push(file);\n }\n }\n\n if (invalidFiles.length > 0) {\n this.dispatchEvent(\n new CustomEvent<{ message: string; files: File[] }>('hx-error', {\n bubbles: true,\n composed: true,\n detail: { message: errorMessages.join(' '), files: invalidFiles },\n }),\n );\n }\n\n if (validFiles.length === 0) return;\n\n // Enforce maxFiles limit (only in multiple mode — single-file mode always replaces)\n const currentCount = this.multiple ? this._files.length : 0;\n const capacity =\n this.maxFiles > 0 ? Math.max(0, this.maxFiles - currentCount) : validFiles.length;\n const allowedFiles = validFiles.slice(0, capacity);\n\n if (allowedFiles.length === 0 && this.maxFiles > 0) {\n this.dispatchEvent(\n new CustomEvent<{ message: string; files: File[] }>('hx-error', {\n bubbles: true,\n composed: true,\n detail: {\n message: `Maximum of ${this.maxFiles} file${this.maxFiles === 1 ? '' : 's'} allowed.`,\n files: validFiles,\n },\n }),\n );\n return;\n }\n\n if (allowedFiles.length > 0) {\n const newEntries: FileEntry[] = allowedFiles.map((file) => ({ file, progress: 0 }));\n\n if (this.multiple) {\n this._files = [...this._files, ...newEntries];\n } else {\n this._files = newEntries;\n }\n\n this._handleInteractionInput();\n\n this.dispatchEvent(\n new CustomEvent<{ files: File[] }>('hx-upload', {\n bubbles: true,\n composed: true,\n detail: { files: allowedFiles },\n }),\n );\n }\n\n // If remaining valid files were cut by maxFiles, report that too\n const overflow = validFiles.slice(capacity);\n if (overflow.length > 0 && this.maxFiles > 0) {\n this.dispatchEvent(\n new CustomEvent<{ message: string; files: File[] }>('hx-error', {\n bubbles: true,\n composed: true,\n detail: {\n message: `Maximum of ${this.maxFiles} file${this.maxFiles === 1 ? '' : 's'} allowed. ${overflow.length} file${overflow.length === 1 ? ' was' : 's were'} not added.`,\n files: overflow,\n },\n }),\n );\n }\n }\n\n // ─── Public Methods ───\n\n /**\n * Sets the upload progress for a file at the given index.\n * @param index - Zero-based index into the current file list.\n * @param percent - Progress percentage from 0 to 100.\n */\n setProgress(index: number, percent: number): void {\n if (index < 0 || index >= this._files.length) return;\n const clamped = Math.max(0, Math.min(100, percent));\n this._files = this._files.map((entry, i) =>\n i === index ? { ...entry, progress: clamped } : entry,\n );\n }\n\n /**\n * Returns a read-only copy of the currently selected files.\n */\n get files(): File[] {\n return this._files.map((e) => e.file);\n }\n\n // ─── Drag and Drop Handlers ───\n\n /** @internal */\n private _handleDragOver(e: DragEvent): void {\n e.preventDefault();\n if (this.disabled) return;\n this._dragOver = true;\n }\n\n /** @internal */\n private _handleDragLeave(e: DragEvent): void {\n // Only clear drag state when leaving the dropzone entirely\n const target = e.relatedTarget as Node | null;\n if (target && this.contains(target)) return;\n const dropzone = this.shadowRoot?.querySelector('.dropzone');\n if (dropzone && dropzone.contains(target)) return;\n this._dragOver = false;\n }\n\n /** @internal */\n private _handleDrop(e: DragEvent): void {\n e.preventDefault();\n this._dragOver = false;\n if (this.disabled) return;\n\n const dt = e.dataTransfer;\n if (!dt) return;\n\n const rawFiles = Array.from(dt.files);\n if (rawFiles.length === 0) return;\n\n this._processFiles(rawFiles);\n }\n\n // ─── Click / Keyboard Handlers ───\n\n /** @internal */\n private _handleDropzoneClick(): void {\n if (this.disabled) return;\n this._fileInput?.click();\n }\n\n /** @internal */\n private _handleDropzoneKeyDown(e: KeyboardEvent): void {\n if (this.disabled) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n this._fileInput?.click();\n }\n }\n\n /** @internal */\n private _handleFileInputChange(e: Event): void {\n const input = e.target as HTMLInputElement;\n if (!input.files || input.files.length === 0) return;\n\n const rawFiles = Array.from(input.files);\n // Reset the input so the same file can be re-selected after removal\n input.value = '';\n this._processFiles(rawFiles);\n }\n\n // ─── Remove Handler ───\n\n /** @internal */\n private _handleRemove(index: number): void {\n if (this.disabled) return;\n const entry = this._files[index];\n if (!entry) return;\n\n const removedFile = entry.file;\n this._files = this._files.filter((_, i) => i !== index);\n\n this.dispatchEvent(\n new CustomEvent<{ file: File; index: number }>('hx-remove', {\n bubbles: true,\n composed: true,\n detail: { file: removedFile, index },\n }),\n );\n\n // Restore focus after removal so keyboard users are not stranded.\n this.updateComplete\n .then(() => {\n if (this._files.length === 0) {\n // List is now empty — return focus to the dropzone.\n const dropzone = this.shadowRoot?.querySelector<HTMLElement>('[part=\"dropzone\"]');\n dropzone?.focus();\n } else {\n // Focus the remove button at the same position, or the previous one if\n // the removed item was the last in the list.\n const removeButtons =\n this.shadowRoot?.querySelectorAll<HTMLButtonElement>('.file-item__remove');\n if (removeButtons && removeButtons.length > 0) {\n const targetIndex = index < this._files.length ? index : this._files.length - 1;\n const targetButton = removeButtons[targetIndex];\n if (targetButton) {\n targetButton.focus();\n }\n }\n }\n })\n .catch(() => {\n // Focus restoration is best-effort; ignore errors.\n });\n }\n\n // ─── Formatters ───\n\n /** @internal */\n private _formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n }\n\n // ─── Render Helpers ───\n\n /** @internal */\n private _renderFileList() {\n if (this._hasFileListSlot) return nothing;\n if (this._files.length === 0) return nothing;\n\n return html`\n <ul part=\"file-list\" class=\"file-list\" aria-label=${this.labelFileList}>\n ${repeat(\n this._files,\n (entry) => entry.file.name + entry.file.size,\n (entry, index) => html`\n <li part=\"file-item\" class=\"file-item\">\n <div class=\"file-item__row\">\n <span class=\"file-item__name\" title=${entry.file.name}> ${entry.file.name} </span>\n <span class=\"file-item__size\">${this._formatSize(entry.file.size)}</span>\n <button\n type=\"button\"\n class=\"file-item__remove\"\n aria-label=${`Remove ${entry.file.name}`}\n @click=${() => this._handleRemove(index)}\n >\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 14 14\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path\n d=\"M1 1L13 13M13 1L1 13\"\n stroke=\"currentColor\"\n stroke-width=\"1.75\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n </div>\n <div\n part=\"progress\"\n class=\"progress-track\"\n role=\"progressbar\"\n aria-valuenow=${entry.progress}\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n aria-label=${this.labelUploadProgress(entry.file.name, entry.progress)}\n >\n <div\n class=\"progress-bar\"\n style=\"--_progress-ratio: ${String(entry.progress / 100)}\"\n ></div>\n </div>\n </li>\n `,\n )}\n </ul>\n `;\n }\n\n // ─── Render ───\n\n override render() {\n const hasError = !!this.error;\n\n const dropzoneClasses = {\n dropzone: true,\n 'dropzone--drag-over': this._dragOver,\n 'dropzone--error': hasError,\n };\n\n const dropzoneLabel = this.label ? `${this.label} — ${this.labelDropzone}` : this.labelDropzone;\n\n return html`\n <div class=\"field\">\n ${this.label\n ? html`\n <label part=\"label\" class=\"field__label\" id=${this._labelId} for=${this._dropzoneId}>\n ${this.label}\n </label>\n `\n : nothing}\n\n <div\n part=\"dropzone\"\n class=${classMap(dropzoneClasses)}\n id=${this._dropzoneId}\n role=\"button\"\n tabindex=${this.disabled ? '-1' : '0'}\n aria-label=${ifDefined(this._effectiveLabel || (!this.label ? dropzoneLabel : undefined))}\n aria-labelledby=${ifDefined(\n !this._effectiveLabel && this.label ? this._labelId : undefined,\n )}\n aria-disabled=${this.disabled ? 'true' : nothing}\n aria-invalid=${hasError ? 'true' : nothing}\n aria-describedby=${ifDefined(hasError ? this._errorId : undefined)}\n @click=${this._handleDropzoneClick}\n @keydown=${this._handleDropzoneKeyDown}\n @dragover=${this._handleDragOver}\n @dragleave=${this._handleDragLeave}\n @drop=${this._handleDrop}\n >\n <slot>${this.labelDropzone}</slot>\n </div>\n\n <input\n class=\"file-input\"\n type=\"file\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n accept=${ifDefined(this.accept || undefined)}\n ?multiple=${this.multiple}\n ?disabled=${this.disabled}\n @change=${this._handleFileInputChange}\n />\n\n <slot name=\"file-list\" @slotchange=${this._handleFileListSlotChange}></slot>\n\n ${this._renderFileList()}\n ${hasError\n ? html`\n <div part=\"error\" class=\"field__error\" id=${this._errorId} role=\"alert\">\n ${this.error}\n </div>\n `\n : nothing}\n\n <div id=${this._liveId} class=\"sr-only\" aria-live=\"polite\" aria-atomic=\"true\">\n ${this._dragOver ? this.labelDragDetected : ''}\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-file-upload': HelixFileUpload;\n }\n}\n"],"names":["helixFileUploadStyles","css","_nextFileUploadId","createIdCounter","HelixFileUpload","FormMixin","mixinDelegatesAria","HelixElement","name","progress","_a","_b","slot","changedProperties","errorEl","msg","disabled","_state","_mode","firstEntry","formData","entry","file","maxMb","tokens","token","baseType","rawFiles","candidateFiles","validFiles","invalidFiles","errorMessages","validationError","currentCount","capacity","allowedFiles","newEntries","overflow","index","percent","clamped","i","target","dropzone","dt","input","removedFile","_","removeButtons","targetIndex","targetButton","bytes","nothing","html","repeat","hasError","dropzoneClasses","dropzoneLabel","classMap","ifDefined","forcedColorsField","__decorateClass","property","state","query","customElement"],"mappings":";;;;;;;;;;AAEO,MAAMA,IAAwBC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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"}
|
|
@@ -36,10 +36,7 @@ const v = f`
|
|
|
36
36
|
|
|
37
37
|
.button:focus-visible {
|
|
38
38
|
outline: var(--hx-focus-ring-width, 2px) solid
|
|
39
|
-
var(
|
|
40
|
-
--hx-icon-button-focus-ring-color,
|
|
41
|
-
var(--hx-focus-ring-color, var(--hx-color-primary-500, #429797))
|
|
42
|
-
);
|
|
39
|
+
var(--hx-icon-button-focus-ring-color, var(--hx-focus-ring-color, #0f7078));
|
|
43
40
|
outline-offset: var(--hx-focus-ring-offset, 2px);
|
|
44
41
|
}
|
|
45
42
|
|
|
@@ -109,7 +106,7 @@ const v = f`
|
|
|
109
106
|
.button--tertiary {
|
|
110
107
|
--hx-icon-button-bg: transparent;
|
|
111
108
|
--hx-icon-button-color: var(--hx-color-text-strong, #202b39);
|
|
112
|
-
--hx-icon-button-border-color: var(--hx-color-border-strong, #
|
|
109
|
+
--hx-icon-button-border-color: var(--hx-color-border-strong, #66787b);
|
|
113
110
|
}
|
|
114
111
|
|
|
115
112
|
.button--tertiary:hover {
|
|
@@ -304,4 +301,4 @@ r = o([
|
|
|
304
301
|
export {
|
|
305
302
|
r as H
|
|
306
303
|
};
|
|
307
|
-
//# sourceMappingURL=hx-icon-button-
|
|
304
|
+
//# sourceMappingURL=hx-icon-button-CGNdQSFM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hx-icon-button-CGNdQSFM.js","sources":["../../src/components/hx-icon-button/hx-icon-button.styles.ts","../../src/components/hx-icon-button/hx-icon-button.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const helixIconButtonStyles = css`\n :host {\n display: inline-block;\n }\n\n :host([disabled]) {\n pointer-events: none;\n opacity: var(--hx-opacity-disabled, 0.5);\n }\n\n .button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: var(--hx-border-width-thin, 1px) solid var(--hx-icon-button-border-color, transparent);\n border-radius: var(--hx-icon-button-border-radius, var(--hx-border-radius-md, 0.375rem));\n background-color: var(--hx-icon-button-bg, transparent);\n color: var(--hx-icon-button-color, var(--hx-color-primary-500, #429797));\n cursor: pointer;\n transition:\n background-color var(--hx-transition-fast, 150ms ease),\n color var(--hx-transition-fast, 150ms ease),\n border-color var(--hx-transition-fast, 150ms ease),\n box-shadow var(--hx-transition-fast, 150ms ease);\n text-decoration: none;\n user-select: none;\n -webkit-user-select: none;\n flex-shrink: 0;\n }\n\n .button:focus-visible {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-icon-button-focus-ring-color, var(--hx-focus-ring-color, #0f7078));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n }\n\n .button:active {\n filter: brightness(var(--hx-filter-brightness-active, 0.8));\n }\n\n /* ─── Size Variants ─── */\n\n /* WCAG 2.5.5 (healthcare mandate): minimum 44x44px touch target for all sizes.\n min-width/min-height override the explicit size tokens when they fall below\n the 2.75rem (44px) threshold, preserving the visual icon size via font-size. */\n\n .button--sm {\n padding: var(--hx-space-1, 0.25rem);\n width: var(--hx-icon-button-size, var(--hx-size-8, 2rem));\n height: var(--hx-icon-button-size, var(--hx-size-8, 2rem));\n min-width: var(--hx-touch-target-min, 2.75rem);\n min-height: var(--hx-touch-target-min, 2.75rem);\n font-size: var(--hx-font-size-sm, 0.875rem);\n }\n\n .button--md {\n padding: var(--hx-space-2, 0.5rem);\n width: var(--hx-icon-button-size, var(--hx-size-10, 2.5rem));\n height: var(--hx-icon-button-size, var(--hx-size-10, 2.5rem));\n min-width: var(--hx-touch-target-min, 2.75rem);\n min-height: var(--hx-touch-target-min, 2.75rem);\n font-size: var(--hx-font-size-md, 1rem);\n }\n\n .button--lg {\n padding: var(--hx-space-3, 0.75rem);\n width: var(--hx-icon-button-size, var(--hx-size-12, 3rem));\n height: var(--hx-icon-button-size, var(--hx-size-12, 3rem));\n min-width: var(--hx-touch-target-min, 2.75rem);\n min-height: var(--hx-touch-target-min, 2.75rem);\n font-size: var(--hx-font-size-lg, 1.125rem);\n }\n\n /* ─── Style Variants ─── */\n\n .button--primary {\n --hx-icon-button-bg: var(--hx-color-primary-500, #429797);\n --hx-icon-button-color: var(--hx-color-text-on-primary, #ffffff);\n --hx-icon-button-border-color: transparent;\n }\n\n /* on-primary tokens are tuned for primary-500. primary-600 + on-primary\n drops icon contrast to 3.07:1 — fails the 4.5:1 floor for meaningful\n icons. Pin fg at neutral-0 (5.82:1 on primary-600). Mirrors hx-button. */\n .button--primary:hover {\n --hx-icon-button-bg: var(--hx-color-primary-600, #0f7078);\n --hx-icon-button-color: var(--hx-color-neutral-0, #ffffff);\n }\n\n .button--secondary {\n --hx-icon-button-bg: transparent;\n --hx-icon-button-color: var(--hx-color-primary-500, #429797);\n --hx-icon-button-border-color: var(--hx-color-primary-500, #429797);\n }\n\n .button--secondary:hover {\n --hx-icon-button-bg: var(--hx-color-primary-50, #ebf8f8);\n }\n\n .button--tertiary {\n --hx-icon-button-bg: transparent;\n --hx-icon-button-color: var(--hx-color-text-strong, #202b39);\n --hx-icon-button-border-color: var(--hx-color-border-strong, #66787b);\n }\n\n .button--tertiary:hover {\n --hx-icon-button-bg: var(--hx-color-surface-sunken, #ebeee9);\n }\n\n .button--danger {\n --hx-icon-button-bg: var(--hx-color-error-500, #e5493e);\n --hx-icon-button-color: var(--hx-color-text-on-error, #ffffff);\n --hx-icon-button-border-color: transparent;\n }\n\n /* on-error tokens are tuned for error-500. error-600 + on-error drops\n icon contrast to 2.25:1 — fails AA. Pin fg at neutral-0\n (6.47:1 on error-600). Mirrors hx-button danger:hover. */\n .button--danger:hover {\n --hx-icon-button-bg: var(--hx-color-error-600, #c92a2a);\n --hx-icon-button-color: var(--hx-color-neutral-0, #ffffff);\n }\n\n .button--ghost {\n --hx-icon-button-bg: transparent;\n --hx-icon-button-color: var(--hx-color-primary-500, #429797);\n --hx-icon-button-border-color: transparent;\n }\n\n .button--ghost:hover {\n --hx-icon-button-bg: var(--hx-color-surface-raised, #f5f8f3);\n }\n\n /* ─── Icon Container ─── */\n\n .icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1em;\n height: 1em;\n line-height: 1;\n pointer-events: none;\n }\n\n /* ─── Disabled ─── */\n\n .button[disabled] {\n cursor: not-allowed;\n /* P1-02 fix: opacity is set only on :host([disabled]) above to prevent\n multiplicative stacking (0.5 * 0.5 = 0.25). Do not add opacity here. */\n }\n\n /* ─── Reduced Motion ─── */\n\n @media (prefers-reduced-motion: reduce) {\n .button {\n transition: none;\n }\n }\n\n /* ─── High Contrast Mode (forced-colors) ─── */\n\n @media (forced-colors: active) {\n .button {\n forced-color-adjust: none;\n background-color: ButtonFace;\n color: ButtonText;\n border: 2px solid ButtonText;\n }\n\n .button:focus-visible {\n outline: 3px solid Highlight;\n outline-offset: 2px;\n }\n\n .button[disabled] {\n background-color: ButtonFace;\n color: GrayText;\n border-color: GrayText;\n opacity: 1;\n }\n\n :host([disabled]) {\n opacity: 1;\n }\n }\n`;\n","import { html, nothing } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property } from 'lit/decorators.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { HelixElement } from '../../base/index.js';\nimport { helixIconButtonStyles } from './hx-icon-button.styles.js';\nimport { forcedColorsInteractive } from '../../styles/forced-colors.js';\nimport { devWarn } from '../../utils/dev-warn.js';\n\n/**\n * An icon-only button component for compact, accessible actions.\n * Renders a square button or anchor element containing a single icon.\n * The `label` property is required and provides the accessible name\n * via `aria-label` and a native tooltip via the `title` attribute.\n *\n * @summary Icon-only action button with full accessibility support.\n *\n * @tag hx-icon-button\n *\n * @slot - Icon element to display (hx-icon, svg, or img).\n *\n * @fires {CustomEvent<{originalEvent: MouseEvent}>} hx-click - Dispatched when the button is clicked (not disabled).\n *\n * @csspart button - The native button or anchor element.\n * @csspart icon - The icon container span wrapping the default slot.\n *\n * @cssprop [--hx-icon-button-bg=transparent] - Button background color.\n * @cssprop [--hx-icon-button-color=var(--hx-color-primary-500)] - Icon color.\n * @cssprop [--hx-icon-button-border-color=transparent] - Button border color.\n * @cssprop [--hx-icon-button-border-radius=var(--hx-border-radius-md)] - Button border radius.\n * @cssprop [--hx-icon-button-size] - Explicit width and height override for the button.\n * @cssprop [--hx-icon-button-focus-ring-color=var(--hx-focus-ring-color)] - Focus ring color.\n * @cssprop [--hx-opacity-disabled] - Opacity.\n * @cssprop [--hx-border-width-thin] - Width.\n * @cssprop [--hx-border-radius-md] - CSS custom property.\n * @cssprop [--hx-color-primary-500] - Color.\n * @cssprop [--hx-transition-fast] - Transition timing.\n * @cssprop [--hx-focus-ring-width] - Width.\n * @cssprop [--hx-focus-ring-color] - Color.\n * @cssprop [--hx-focus-ring-offset] - CSS custom property.\n * @cssprop [--hx-filter-brightness-active] - CSS filter.\n * @cssprop [--hx-space-1] - Spacing token.\n * @cssprop [--hx-size-8] - Size token.\n * @cssprop [--hx-touch-target-min] - Minimum touch target size.\n * @cssprop [--hx-font-size-sm] - Font size.\n * @cssprop [--hx-space-2] - Spacing token.\n * @cssprop [--hx-size-10] - Size token.\n * @cssprop [--hx-font-size-md] - Font size.\n * @cssprop [--hx-space-3] - Spacing token.\n * @cssprop [--hx-size-12] - Size token.\n * @cssprop [--hx-font-size-lg] - Font size.\n * @cssprop [--hx-color-neutral-0] - Color.\n * @cssprop [--hx-color-primary-600] - Color.\n * @cssprop [--hx-color-primary-50] - Color.\n * @cssprop [--hx-color-neutral-700] - Color.\n * @cssprop [--hx-color-neutral-300] - Color.\n * @cssprop [--hx-color-neutral-100] - Color.\n * @cssprop [--hx-color-error-500] - Color.\n * @cssprop [--hx-color-error-600] - Color.\n */\n@customElement('hx-icon-button')\nexport class HelixIconButton extends HelixElement {\n static override styles = [helixIconButtonStyles, forcedColorsInteractive];\n\n /**\n * Accessible name for the button. Required. Rendered as `aria-label` and\n * `title` on the underlying element. The component renders nothing when absent,\n * and a console warning is emitted to alert developers during authoring.\n * @attr label\n */\n @property({ type: String })\n label = '';\n\n /**\n * Visual style variant of the button.\n * @attr variant\n */\n @property({ type: String, reflect: true })\n variant: 'primary' | 'secondary' | 'tertiary' | 'danger' | 'ghost' = 'ghost';\n\n /**\n * Size of the button.\n * @attr hx-size\n */\n @property({ type: String, reflect: true, attribute: 'hx-size' })\n size: 'sm' | 'md' | 'lg' = 'md';\n\n /**\n * The type attribute for the underlying button element.\n * Has no effect when `href` is set.\n * @attr type\n */\n @property({ type: String })\n type: 'button' | 'submit' | 'reset' = 'button';\n\n /**\n * Whether the button is disabled.\n * @attr disabled\n */\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n /**\n * When set, renders an `<a>` element instead of a `<button>`.\n * @attr href\n */\n @property({ type: String })\n href: string | undefined = undefined;\n\n /**\n * Name submitted with form data. Only applicable when rendering as a button.\n * @attr name\n */\n @property({ type: String })\n name: string | undefined = undefined;\n\n /**\n * Value submitted with form data. Only applicable when rendering as a button.\n * @attr value\n */\n @property({ type: String })\n value: string | undefined = undefined;\n\n // ─── Form Association ───\n\n /** Marks this element as form-associated for ElementInternals support. @internal */\n static override formAssociated = true;\n\n protected override _onFormDisabled(disabled: boolean): void {\n this.disabled = disabled;\n }\n\n // ─── Event Handling ───\n\n /** @internal */\n private _handleClick(e: MouseEvent): void {\n if (this.disabled) {\n e.preventDefault();\n e.stopPropagation();\n return;\n }\n\n /**\n * Dispatched when the button is clicked.\n * @event hx-click\n */\n this.dispatchEvent(\n new CustomEvent<{ originalEvent: MouseEvent }>('hx-click', {\n bubbles: true,\n composed: true,\n detail: { originalEvent: e },\n }),\n );\n\n // Handle form submission/reset if form-associated and not in href/link mode\n if (!this.href) {\n if (this.type === 'submit' && this._internals.form) {\n this._internals.form.requestSubmit();\n } else if (this.type === 'reset' && this._internals.form) {\n this._internals.form.reset();\n }\n }\n }\n\n // ─── Render Helpers ───\n\n /** @internal */\n private _normalizedLabel(): string {\n return this.label.trim();\n }\n\n /** @internal */\n private _classes() {\n return {\n button: true,\n [`button--${this.variant}`]: true,\n [`button--${this.size}`]: true,\n };\n }\n\n /** @internal */\n private _iconSlot() {\n return html`<span part=\"icon\" class=\"icon\"><slot></slot></span>`;\n }\n\n // ─── Render ───\n\n override render() {\n const normalizedLabel = this._normalizedLabel();\n if (!normalizedLabel) {\n devWarn(\n 'hx-icon-button',\n 'The `label` property is required for accessibility. Render suppressed.',\n );\n return nothing;\n }\n\n if (this.href !== undefined) {\n // P1-03 fix: disabled anchor must set tabindex=\"-1\" explicitly — an <a>\n // without href is non-focusable by default in most browsers, but this is\n // browser-dependent. Explicit tabindex=\"-1\" guarantees keyboard exclusion\n // across all conforming browsers.\n // P1-07 note: aria-disabled IS required on the anchor branch because\n // <a> elements have no native disabled attribute; aria-disabled is the\n // only AT signal available.\n return html`\n <a\n part=\"button\"\n class=${classMap(this._classes())}\n href=${ifDefined(this.disabled ? undefined : this.href)}\n aria-label=${normalizedLabel}\n title=${normalizedLabel}\n aria-disabled=${this.disabled ? 'true' : nothing}\n tabindex=${this.disabled ? '-1' : nothing}\n @click=${this._handleClick}\n >\n ${this._iconSlot()}\n </a>\n `;\n }\n\n // P1-07 fix: aria-disabled is redundant on a natively disabled <button>.\n // The native disabled attribute already exposes aria-disabled=\"true\"\n // implicitly in the accessibility tree. Duplicate explicit aria-disabled\n // creates ambiguity about design intent. Keep only native ?disabled.\n return html`\n <button\n part=\"button\"\n class=${classMap(this._classes())}\n ?disabled=${this.disabled}\n type=${this.type}\n aria-label=${normalizedLabel}\n title=${normalizedLabel}\n name=${ifDefined(this.name)}\n value=${ifDefined(this.value)}\n @click=${this._handleClick}\n >\n ${this._iconSlot()}\n </button>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-icon-button': HelixIconButton;\n }\n interface HTMLElementEventMap {\n 'hx-click': CustomEvent<{ originalEvent: MouseEvent }>;\n }\n}\n"],"names":["helixIconButtonStyles","css","HelixIconButton","HelixElement","disabled","e","html","normalizedLabel","classMap","ifDefined","nothing","forcedColorsInteractive","__decorateClass","property","customElement"],"mappings":"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"}
|
|
@@ -43,10 +43,7 @@ const y = x`
|
|
|
43
43
|
|
|
44
44
|
.link:focus-visible {
|
|
45
45
|
outline: var(--hx-focus-ring-width, 2px) solid
|
|
46
|
-
var(
|
|
47
|
-
--hx-link-focus-ring-color,
|
|
48
|
-
var(--hx-focus-ring-color, var(--hx-color-primary-400, #6ab1b1))
|
|
49
|
-
);
|
|
46
|
+
var(--hx-link-focus-ring-color, var(--hx-focus-ring-color, #0f7078));
|
|
50
47
|
outline-offset: var(--hx-focus-ring-offset, 2px);
|
|
51
48
|
border-radius: var(--hx-border-radius-sm, 0.25rem);
|
|
52
49
|
}
|
|
@@ -128,10 +125,10 @@ const y = x`
|
|
|
128
125
|
border: 0;
|
|
129
126
|
}
|
|
130
127
|
`;
|
|
131
|
-
var
|
|
132
|
-
for (var o = l > 1 ? void 0 : l ?
|
|
128
|
+
var g = Object.defineProperty, b = Object.getOwnPropertyDescriptor, t = (e, n, s, l) => {
|
|
129
|
+
for (var o = l > 1 ? void 0 : l ? b(n, s) : n, d = e.length - 1, c; d >= 0; d--)
|
|
133
130
|
(c = e[d]) && (o = (l ? c(n, s, o) : c(o)) || o);
|
|
134
|
-
return l && o &&
|
|
131
|
+
return l && o && g(n, s, o), o;
|
|
135
132
|
};
|
|
136
133
|
let r = class extends m {
|
|
137
134
|
constructor() {
|
|
@@ -241,4 +238,4 @@ r = t([
|
|
|
241
238
|
export {
|
|
242
239
|
r as H
|
|
243
240
|
};
|
|
244
|
-
//# sourceMappingURL=hx-link-
|
|
241
|
+
//# sourceMappingURL=hx-link-C-O6vq0Q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hx-link-C-O6vq0Q.js","sources":["../../src/components/hx-link/hx-link.styles.ts","../../src/components/hx-link/hx-link.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const helixLinkStyles = css`\n :host {\n display: inline;\n }\n\n :host([disabled]) {\n cursor: not-allowed;\n }\n\n /* --- Base Link --- */\n\n .link {\n display: inline-flex;\n align-items: center;\n gap: var(--hx-space-1, 0.25rem);\n color: var(--hx-link-color, var(--hx-color-primary-600, #0f7078));\n font-family: var(--hx-link-font-family, var(--hx-font-family-sans, inherit));\n font-size: inherit;\n line-height: inherit;\n text-decoration: var(--hx-link-text-decoration, underline);\n text-underline-offset: var(--hx-link-underline-offset, 2px);\n cursor: pointer;\n outline: 0;\n transition:\n color var(--hx-transition-fast, 150ms ease),\n text-decoration-color var(--hx-transition-fast, 150ms ease);\n }\n\n .link:hover {\n color: var(--hx-link-color-hover, var(--hx-color-primary-700, #0f6363));\n text-decoration: var(--hx-link-text-decoration-hover, underline);\n }\n\n .link:active {\n color: var(--hx-link-color-active, var(--hx-color-primary-800, #07494a));\n }\n\n .link:focus-visible {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-link-focus-ring-color, var(--hx-focus-ring-color, #0f7078));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n border-radius: var(--hx-border-radius-sm, 0.25rem);\n }\n\n /* --- Variant: subtle --- */\n\n .link--subtle {\n color: var(--hx-link-color-subtle, var(--hx-color-text-secondary, #313e4b));\n text-decoration: none;\n }\n\n .link--subtle:hover {\n color: var(--hx-link-color-hover, var(--hx-color-primary-700, #0f6363));\n text-decoration: underline;\n }\n\n /* --- Variant: danger --- */\n\n .link--danger {\n color: var(--hx-link-color-danger, var(--hx-color-error-text, #c92a2a));\n }\n\n .link--danger:hover {\n color: var(--hx-link-color-danger-hover, var(--hx-color-error-700, #a21312));\n }\n\n /* --- Disabled --- */\n\n .link--disabled {\n color: var(--hx-link-color-disabled, var(--hx-color-text-disabled, #8e9c98));\n text-decoration: none;\n cursor: not-allowed;\n }\n\n /* --- External link icon --- */\n\n .link__external-icon {\n display: inline-flex;\n width: 0.75em;\n height: 0.75em;\n flex-shrink: 0;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .link {\n transition: none;\n }\n }\n\n /* ─── High Contrast Mode (forced-colors) ─── */\n\n @media (forced-colors: active) {\n .link {\n forced-color-adjust: none;\n color: LinkText;\n }\n\n .link:focus-visible {\n outline: 3px solid Highlight;\n outline-offset: 2px;\n }\n\n .link--disabled {\n color: GrayText;\n }\n }\n\n /* --- Visually hidden (sr-only) --- */\n\n .sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n }\n`;\n","import { html, nothing, svg } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property } from 'lit/decorators.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { HelixElement } from '../../base/index.js';\nimport { helixLinkStyles } from './hx-link.styles.js';\nimport { forcedColorsLink } from '../../styles/forced-colors.js';\n\n/**\n * Variant options for the link component.\n */\nexport type LinkVariant = 'default' | 'subtle' | 'danger';\n\n/**\n * A semantic hyperlink component with accessibility-first design.\n * Renders a native `<a>` element for enabled state and a `<span>` for\n * disabled state with full keyboard and screen reader support.\n *\n * @summary Accessible hyperlink with external-link detection, disabled state,\n * and download support.\n *\n * @tag hx-link\n *\n * @slot - Default slot for link label text or content.\n *\n * @fires {CustomEvent<{originalEvent: MouseEvent}>} hx-click - Dispatched when\n * the link is clicked and is not disabled.\n *\n * @csspart link - The inner anchor or span element.\n * @csspart external-icon - The external link icon SVG (when target=\"_blank\").\n *\n * @cssprop [--hx-link-color=var(--hx-color-primary-500)] - Default link color.\n * @cssprop [--hx-link-color-hover=var(--hx-color-primary-700)] - Hover color.\n * @cssprop [--hx-link-color-active=var(--hx-color-primary-800)] - Active color.\n * @cssprop [--hx-link-color-disabled=var(--hx-color-neutral-400)] - Disabled color.\n * @cssprop [--hx-link-color-subtle=var(--hx-color-neutral-600)] - Subtle variant color.\n * @cssprop [--hx-link-color-danger=var(--hx-color-error-text)] - Danger variant color.\n * @cssprop [--hx-link-color-danger-hover=var(--hx-color-error-700)] - Danger variant hover color.\n * @cssprop [--hx-link-font-family=var(--hx-font-family-sans)] - Link font family.\n * @cssprop [--hx-link-text-decoration=underline] - Link text decoration.\n * @cssprop [--hx-link-text-decoration-hover=underline] - Hover text decoration.\n * @cssprop [--hx-link-underline-offset=2px] - Text underline offset.\n * @cssprop [--hx-link-focus-ring-color=var(--hx-focus-ring-color)] - Focus ring color.\n *\n * @note The `:visited` pseudo-class does not work inside Shadow DOM due to\n * browser privacy restrictions. This is a known platform limitation.\n * @cssprop [--hx-space-1] - Spacing token.\n * @cssprop [--hx-color-primary-500] - Color.\n * @cssprop [--hx-font-family-sans] - Font family.\n * @cssprop [--hx-transition-fast] - Transition timing.\n * @cssprop [--hx-color-primary-700] - Color.\n * @cssprop [--hx-color-primary-800] - Color.\n * @cssprop [--hx-focus-ring-width] - Width.\n * @cssprop [--hx-focus-ring-color] - Color.\n * @cssprop [--hx-color-primary-400] - Color.\n * @cssprop [--hx-focus-ring-offset] - CSS custom property.\n * @cssprop [--hx-border-radius-sm] - CSS custom property.\n * @cssprop [--hx-color-neutral-600] - Color.\n * @cssprop [--hx-color-error-text] - Color.\n * @cssprop [--hx-color-error-700] - Color.\n * @cssprop [--hx-color-neutral-400] - Color.\n */\n@customElement('hx-link')\nexport class HelixLink extends HelixElement {\n static override styles = [helixLinkStyles, forcedColorsLink];\n\n /**\n * The URL the link points to.\n * @attr href\n */\n @property({ type: String })\n href: string | undefined = undefined;\n\n /**\n * Where to display the linked URL (_self, _blank, etc.).\n * When set to \"_blank\", automatically adds rel=\"noopener noreferrer\"\n * and shows an external-link indicator.\n * @attr target\n */\n @property({ type: String })\n target: string | undefined = undefined;\n\n /**\n * Visual style variant of the link.\n * @attr variant\n */\n @property({ type: String, reflect: true })\n variant: 'default' | 'subtle' | 'danger' = 'default';\n\n /**\n * Whether the link is disabled. Renders a span instead of an anchor.\n * The disabled span is keyboard-focusable (tabindex=\"0\") and announces\n * as a disabled link to screen readers.\n * @attr disabled\n */\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n /**\n * Prompts the user to download the linked URL. When set to a string,\n * the value is used as the suggested filename.\n * @attr download\n */\n @property({ type: String })\n download: string | undefined = undefined;\n\n /**\n * Relationship between the current document and the linked URL.\n * Automatically set to \"noopener noreferrer\" when target=\"_blank\".\n * @attr rel\n */\n @property({ type: String })\n rel: string | undefined = undefined;\n\n // --- Event Handling ---\n\n /** @internal Blocks Enter and Space activation on disabled span. */\n private _handleDisabledKeydown(e: KeyboardEvent): void {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n }\n }\n\n /** @internal */\n private _handleClick(e: MouseEvent): void {\n if (this.disabled) {\n e.preventDefault();\n e.stopPropagation();\n return;\n }\n\n this.dispatchEvent(\n new CustomEvent<{ originalEvent: MouseEvent }>('hx-click', {\n bubbles: true,\n composed: true,\n detail: { originalEvent: e },\n }),\n );\n }\n\n // --- Render Helpers ---\n\n /** @internal */\n private _computeRel(): string | undefined {\n if (this.rel) return this.rel;\n if (this.target === '_blank') return 'noopener noreferrer';\n return undefined;\n }\n\n /** @internal */\n private _renderExternalIcon() {\n if (this.target !== '_blank') return nothing;\n\n return html`\n <svg\n class=\"link__external-icon\"\n part=\"external-icon\"\n aria-hidden=\"true\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n ${svg`<path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\" /><polyline points=\"15 3 21 3 21 9\" /><line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\" />`}\n </svg>\n <span class=\"sr-only\">(opens in new tab)</span>\n `;\n }\n\n // --- Render ---\n\n override render() {\n const classes = {\n link: true,\n [`link--${this.variant}`]: this.variant !== 'default',\n 'link--disabled': this.disabled,\n };\n\n if (this.disabled) {\n return html`\n <span\n part=\"link\"\n class=${classMap(classes)}\n role=\"link\"\n aria-disabled=\"true\"\n tabindex=\"0\"\n @click=${this._handleClick}\n @keydown=${this._handleDisabledKeydown}\n >\n <slot></slot>\n </span>\n `;\n }\n\n return html`\n <a\n part=\"link\"\n class=${classMap(classes)}\n href=${ifDefined(this.href)}\n target=${ifDefined(this.target)}\n rel=${ifDefined(this._computeRel())}\n download=${ifDefined(this.download)}\n @click=${this._handleClick}\n >\n <slot></slot>\n ${this._renderExternalIcon()}\n </a>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-link': HelixLink;\n }\n}\n"],"names":["helixLinkStyles","css","HelixLink","HelixElement","nothing","html","svg","classes","classMap","ifDefined","forcedColorsLink","__decorateClass","property","customElement"],"mappings":";;;;;;AAEO,MAAMA,IAAkBC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;;AC8DxB,IAAMC,IAAN,cAAwBC,EAAa;AAAA,EAArC,cAAA;AAAA,UAAA,GAAA,SAAA,GAQL,KAAA,OAA2B,QAS3B,KAAA,SAA6B,QAO7B,KAAA,UAA2C,WAS3C,KAAA,WAAW,IAQX,KAAA,WAA+B,QAQ/B,KAAA,MAA0B;AAAA,EAAA;AAAA;AAAA;AAAA,EAKlB,uBAAuB,GAAwB;AACrD,KAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,QACjC,EAAE,eAAA;AAAA,EAEN;AAAA;AAAA,EAGQ,aAAa,GAAqB;AACxC,QAAI,KAAK,UAAU;AACjB,QAAE,eAAA,GACF,EAAE,gBAAA;AACF;AAAA,IACF;AAEA,SAAK;AAAA,MACH,IAAI,YAA2C,YAAY;AAAA,QACzD,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ,EAAE,eAAe,EAAA;AAAA,MAAE,CAC5B;AAAA,IAAA;AAAA,EAEL;AAAA;AAAA;AAAA,EAKQ,cAAkC;AACxC,QAAI,KAAK,IAAK,QAAO,KAAK;AAC1B,QAAI,KAAK,WAAW,SAAU,QAAO;AAAA,EAEvC;AAAA;AAAA,EAGQ,sBAAsB;AAC5B,WAAI,KAAK,WAAW,WAAiBC,IAE9BC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAYDC,mJAAqJ;AAAA;AAAA;AAAA;AAAA,EAI7J;AAAA;AAAA,EAIS,SAAS;AAChB,UAAMC,IAAU;AAAA,MACd,MAAM;AAAA,MACN,CAAC,SAAS,KAAK,OAAO,EAAE,GAAG,KAAK,YAAY;AAAA,MAC5C,kBAAkB,KAAK;AAAA,IAAA;AAGzB,WAAI,KAAK,WACAF;AAAA;AAAA;AAAA,kBAGKG,EAASD,CAAO,CAAC;AAAA;AAAA;AAAA;AAAA,mBAIhB,KAAK,YAAY;AAAA,qBACf,KAAK,sBAAsB;AAAA;AAAA;AAAA;AAAA,UAOrCF;AAAA;AAAA;AAAA,gBAGKG,EAASD,CAAO,CAAC;AAAA,eAClBE,EAAU,KAAK,IAAI,CAAC;AAAA,iBAClBA,EAAU,KAAK,MAAM,CAAC;AAAA,cACzBA,EAAU,KAAK,aAAa,CAAC;AAAA,mBACxBA,EAAU,KAAK,QAAQ,CAAC;AAAA,iBAC1B,KAAK,YAAY;AAAA;AAAA;AAAA,UAGxB,KAAK,qBAAqB;AAAA;AAAA;AAAA,EAGlC;AACF;AApJaP,EACK,SAAS,CAACF,GAAiBU,CAAgB;AAO3DC,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GAPfV,EAQX,WAAA,QAAA,CAAA;AASAS,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GAhBfV,EAiBX,WAAA,UAAA,CAAA;AAOAS,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,QAAQ,SAAS,IAAM;AAAA,GAvB9BV,EAwBX,WAAA,WAAA,CAAA;AASAS,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,SAAS,SAAS,IAAM;AAAA,GAhC/BV,EAiCX,WAAA,YAAA,CAAA;AAQAS,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GAxCfV,EAyCX,WAAA,YAAA,CAAA;AAQAS,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GAhDfV,EAiDX,WAAA,OAAA,CAAA;AAjDWA,IAANS,EAAA;AAAA,EADNE,EAAc,SAAS;AAAA,GACXX,CAAA;"}
|
|
@@ -101,8 +101,7 @@ const _ = v`
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
:host([interactive]):focus-visible .list-item {
|
|
104
|
-
outline: var(--hx-focus-ring-width, 2px) solid
|
|
105
|
-
var(--hx-focus-ring-color, var(--hx-color-primary-600, #0f7078));
|
|
104
|
+
outline: var(--hx-focus-ring-width, 2px) solid var(--hx-focus-ring-color, #0f7078);
|
|
106
105
|
outline-offset: var(--hx-focus-ring-offset, 2px);
|
|
107
106
|
}
|
|
108
107
|
|
|
@@ -132,8 +131,7 @@ const _ = v`
|
|
|
132
131
|
}
|
|
133
132
|
|
|
134
133
|
.list-item__link:focus-visible {
|
|
135
|
-
outline: var(--hx-focus-ring-width, 2px) solid
|
|
136
|
-
var(--hx-focus-ring-color, var(--hx-color-primary-600, #0f7078));
|
|
134
|
+
outline: var(--hx-focus-ring-width, 2px) solid var(--hx-focus-ring-color, #0f7078);
|
|
137
135
|
outline-offset: var(--hx-focus-ring-offset, 2px);
|
|
138
136
|
border-radius: var(--hx-border-radius-sm, 0.25rem);
|
|
139
137
|
}
|
|
@@ -466,4 +464,4 @@ export {
|
|
|
466
464
|
p as H,
|
|
467
465
|
o as a
|
|
468
466
|
};
|
|
469
|
-
//# sourceMappingURL=hx-list-
|
|
467
|
+
//# sourceMappingURL=hx-list-MyEhh8c7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hx-list-MyEhh8c7.js","sources":["../../src/components/hx-list/hx-list.styles.ts","../../src/components/hx-list/hx-list-item.styles.ts","../../src/components/hx-list/hx-list-item.ts","../../src/components/hx-list/hx-list.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const helixListStyles = css`\n :host {\n display: block;\n }\n\n .list {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: var(--hx-list-gap, 0);\n }\n\n /* ─── Variant: bulleted ─── */\n\n .list--bulleted {\n padding-inline-start: var(--hx-space-6, 1.5rem);\n list-style: disc;\n }\n\n /* ─── Variant: numbered ─── */\n\n .list--numbered {\n padding-inline-start: var(--hx-space-6, 1.5rem);\n list-style: decimal;\n }\n\n /* ─── Variant: interactive ─── */\n\n .list--interactive {\n list-style: none;\n padding: 0;\n }\n\n /* ─── Variant: description ─── */\n\n .list--description {\n padding: 0;\n list-style: none;\n }\n\n /* ─── Dividers ─── */\n\n :host([divided]) .list > ::slotted(hx-list-item:not(:last-child)) {\n border-bottom: var(--hx-border-width-thin, 1px) solid\n var(--hx-list-divider-color, var(--hx-color-neutral-200, #d6dbd5));\n }\n\n /* ─── High Contrast Mode (forced-colors) ─── */\n\n @media (forced-colors: active) {\n /* Divider borders are already using a border property, which forced-colors respects.\n Ensure the system color is used for divider borders. */\n :host([divided]) .list > ::slotted(hx-list-item:not(:last-child)) {\n border-bottom-color: CanvasText;\n }\n }\n`;\n","import { css } from 'lit';\n\nexport const helixListItemStyles = css`\n :host {\n display: block;\n }\n\n :host([disabled]) {\n pointer-events: none;\n opacity: var(--hx-opacity-disabled, 0.5);\n }\n\n /* ─── Base list item ─── */\n\n .list-item {\n display: flex;\n align-items: center;\n gap: var(--hx-space-3, 0.75rem);\n padding: var(--hx-list-item-padding, var(--hx-space-3, 0.75rem));\n color: var(--hx-list-item-color, var(--hx-color-neutral-900, #0d1825));\n font-family: var(--hx-list-item-font-family, var(--hx-font-family-sans, sans-serif));\n font-size: var(--hx-font-size-md, 1rem);\n line-height: var(--hx-line-height-normal, 1.5);\n cursor: default;\n box-sizing: border-box;\n width: 100%;\n }\n\n /* ─── Interactive items — use :host([interactive]) instead of deprecated :host-context() ─── */\n\n :host([interactive]) .list-item {\n cursor: pointer;\n border-radius: var(--hx-border-radius-md, 0.375rem);\n transition: background-color var(--hx-transition-fast, 150ms ease);\n }\n\n :host([interactive]) .list-item:hover:not(.list-item--disabled) {\n background-color: var(--hx-list-item-bg-hover, var(--hx-color-neutral-50, #f5f8f3));\n }\n\n :host([interactive]):focus-visible .list-item {\n outline: var(--hx-focus-ring-width, 2px) solid var(--hx-focus-ring-color, #0f7078);\n outline-offset: var(--hx-focus-ring-offset, 2px);\n }\n\n /* ─── Selected state ─── */\n\n .list-item--selected {\n background-color: var(--hx-list-item-bg-selected, var(--hx-color-primary-50, #ebf8f8));\n color: var(--hx-list-item-color-selected, var(--hx-color-primary-700, #0f6363));\n }\n\n /* ─── Disabled state ─── */\n\n .list-item--disabled {\n cursor: not-allowed;\n opacity: var(--hx-opacity-disabled, 0.5);\n }\n\n /* ─── Link mode ─── */\n\n .list-item__link {\n display: flex;\n align-items: center;\n gap: var(--hx-space-3, 0.75rem);\n width: 100%;\n text-decoration: none;\n color: inherit;\n }\n\n .list-item__link:focus-visible {\n outline: var(--hx-focus-ring-width, 2px) solid var(--hx-focus-ring-color, #0f7078);\n outline-offset: var(--hx-focus-ring-offset, 2px);\n border-radius: var(--hx-border-radius-sm, 0.25rem);\n }\n\n /* ─── Slots ─── */\n\n .list-item__prefix,\n .list-item__suffix {\n display: inline-flex;\n align-items: center;\n flex-shrink: 0;\n }\n\n .list-item__body {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n min-width: 0;\n }\n\n .list-item__label {\n display: block;\n }\n\n .list-item__description {\n display: block;\n font-size: var(--hx-font-size-sm, 0.875rem);\n color: var(--hx-list-item-description-color, var(--hx-color-neutral-500, #66787b));\n margin-top: var(--hx-space-1, 0.25rem);\n }\n\n @media (prefers-reduced-motion: reduce) {\n :host([interactive]) .list-item {\n transition: none;\n }\n }\n\n /* ─── Forced colors (Windows High Contrast / forced-colors mode) ─── */\n\n @media (forced-colors: active) {\n /* Selected state: replace background-color with a visible border using system Highlight color */\n :host([interactive]) .list-item--selected {\n background-color: transparent;\n border: 2px solid Highlight;\n color: HighlightText;\n forced-color-adjust: none;\n }\n\n /* Hover state: use an outline instead of background-color */\n :host([interactive]) .list-item:hover:not(.list-item--disabled) {\n background-color: transparent;\n outline: 1px solid Highlight;\n outline-offset: -1px;\n }\n\n /* Disabled state: use system GrayText color */\n :host([interactive][disabled]) .list-item,\n :host([interactive]) .list-item--disabled {\n color: GrayText;\n }\n\n /* Focus ring: ensure visibility in forced-colors mode */\n :host([interactive]):focus-visible .list-item {\n outline: 2px solid Highlight;\n outline-offset: 2px;\n forced-color-adjust: none;\n }\n }\n`;\n","import { html, nothing, 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 { ifDefined } from 'lit/directives/if-defined.js';\nimport { HelixElement } from '../../base/index.js';\nimport { helixListItemStyles } from './hx-list-item.styles.js';\nimport { forcedColorsSurface } from '../../styles/forced-colors.js';\n\n/**\n * A rich list item for use inside `hx-list`.\n *\n * @summary Individual list item with optional prefix, suffix, description, link, and disabled/selected states.\n *\n * @tag hx-list-item\n *\n * @slot - Default slot for the item label text.\n * @slot prefix - Icon, avatar, or content rendered before the label.\n * @slot suffix - Icon, badge, or text rendered after the label.\n * @slot description - Secondary descriptive text rendered below the label.\n *\n * @fires {CustomEvent<{item: HelixListItem, value: string | undefined}>} hx-list-item-click -\n * Dispatched when the item is clicked and not disabled.\n *\n * @csspart base - The root item element (li, dt, dd, or wrapper).\n * @csspart prefix - The prefix slot container.\n * @csspart label - The label slot container.\n * @csspart description - The description slot container.\n * @csspart suffix - The suffix slot container.\n *\n * @cssprop [--hx-list-item-padding=var(--hx-space-3)] - Item padding.\n * @cssprop [--hx-list-item-color=var(--hx-color-neutral-900)] - Item text color.\n * @cssprop [--hx-list-item-bg-hover=var(--hx-color-neutral-50)] - Item hover background.\n * @cssprop [--hx-list-item-bg-selected=var(--hx-color-primary-50)] - Selected item background.\n * @cssprop [--hx-list-item-color-selected=var(--hx-color-primary-700)] - Selected item text color.\n * @cssprop [--hx-list-item-description-color=var(--hx-color-neutral-500)] - Description text color.\n */\n@customElement('hx-list-item')\nexport class HelixListItem extends HelixElement {\n static override styles = [helixListItemStyles, forcedColorsSurface];\n\n /**\n * Whether the item is disabled. Prevents interaction.\n * @attr disabled\n */\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n /**\n * Whether the item is selected (used in interactive mode).\n * @attr selected\n */\n @property({ type: Boolean, reflect: true })\n selected = false;\n\n /**\n * When set, renders the item as a link (only in non-interactive variants).\n * @attr href\n */\n @property({ type: String })\n href: string | undefined = undefined;\n\n /**\n * The value associated with this item (used with hx-select).\n * @attr value\n */\n @property({ type: String })\n value: string | undefined = undefined;\n\n /**\n * Set by the parent hx-list to indicate this item is part of an interactive listbox.\n * Controls CSS styling and ARIA role via host attributes.\n * @attr interactive\n */\n @property({ type: Boolean, reflect: true })\n interactive = false;\n\n /**\n * Item type for description list variant. Use 'term' for <dt> and 'definition' for <dd>.\n * @attr type\n */\n @property({ type: String, reflect: true })\n type: 'default' | 'term' | 'definition' = 'default';\n\n override updated(changedProps: PropertyValues<this>): void {\n super.updated(changedProps);\n if (\n changedProps.has('interactive') ||\n changedProps.has('selected') ||\n changedProps.has('disabled')\n ) {\n this._syncHostAria();\n }\n }\n\n /**\n * Syncs ARIA attributes to the host element when in interactive (listbox option) mode.\n * This ensures correct ARIA ownership: ul[role=listbox] > hx-list-item[role=option].\n */\n /** @internal */\n private _syncHostAria(): void {\n if (this.interactive) {\n this.setAttribute('role', 'option');\n this.setAttribute('aria-selected', String(this.selected));\n if (this.disabled) {\n this.setAttribute('aria-disabled', 'true');\n } else {\n this.removeAttribute('aria-disabled');\n }\n if (!this.disabled) {\n this.setAttribute('tabindex', '0');\n } else {\n this.removeAttribute('tabindex');\n }\n } else {\n this.removeAttribute('role');\n this.removeAttribute('aria-selected');\n this.removeAttribute('aria-disabled');\n this.removeAttribute('tabindex');\n }\n }\n\n /** @internal */\n private _dispatchListItemClick(): void {\n this.dispatchEvent(\n new CustomEvent<{ item: HelixListItem; value: string | undefined }>('hx-list-item-click', {\n bubbles: true,\n composed: true,\n detail: { item: this, value: this.value },\n }),\n );\n }\n\n /** @internal */\n private _handleClick(e: MouseEvent): void {\n if (this.disabled) {\n e.preventDefault();\n e.stopPropagation();\n return;\n }\n\n this._dispatchListItemClick();\n }\n\n /** @internal */\n private _renderContent() {\n return html`\n <span part=\"prefix\" class=\"list-item__prefix\">\n <slot name=\"prefix\"></slot>\n </span>\n <span class=\"list-item__body\">\n <span part=\"label\" class=\"list-item__label\">\n <slot></slot>\n </span>\n <span part=\"description\" class=\"list-item__description\">\n <slot name=\"description\"></slot>\n </span>\n </span>\n <span part=\"suffix\" class=\"list-item__suffix\">\n <slot name=\"suffix\"></slot>\n </span>\n `;\n }\n\n override render() {\n // Description list: render as <dt> (term) or <dd> (definition)\n if (this.type === 'term') {\n return html`\n <dt\n part=\"base\"\n class=${classMap({ 'list-item': true, 'list-item--disabled': this.disabled })}\n aria-disabled=${this.disabled ? 'true' : nothing}\n >\n ${this._renderContent()}\n </dt>\n `;\n }\n\n if (this.type === 'definition') {\n return html`\n <dd\n part=\"base\"\n class=${classMap({ 'list-item': true, 'list-item--disabled': this.disabled })}\n aria-disabled=${this.disabled ? 'true' : nothing}\n >\n ${this._renderContent()}\n </dd>\n `;\n }\n\n // Interactive mode: role is on the host element (ARIA ownership across shadow DOM).\n // Internal <li> uses role=\"presentation\" so AT sees: listbox > option (host).\n if (this.interactive) {\n // In interactive mode, links are not rendered as <a> to avoid\n // invalid ARIA: focusable content inside role=\"option\" is prohibited.\n return html`\n <li\n part=\"base\"\n role=\"presentation\"\n class=${classMap({\n 'list-item': true,\n 'list-item--selected': this.selected,\n 'list-item--disabled': this.disabled,\n })}\n @click=${this._handleClick}\n @keydown=${this._handleKeydown}\n >\n ${this._renderContent()}\n </li>\n `;\n }\n\n // Non-interactive with href: render as link\n if (this.href !== undefined && !this.disabled) {\n return html`\n <li\n part=\"base\"\n class=\"list-item\"\n role=\"listitem\"\n aria-disabled=${this.disabled ? 'true' : nothing}\n >\n <a class=\"list-item__link\" href=${ifDefined(this.href)} @click=${this._handleClick}>\n ${this._renderContent()}\n </a>\n </li>\n `;\n }\n\n // Default non-interactive\n return html`\n <li\n part=\"base\"\n class=${classMap({\n 'list-item': true,\n 'list-item--selected': this.selected,\n 'list-item--disabled': this.disabled,\n })}\n role=\"listitem\"\n aria-disabled=${this.disabled ? 'true' : nothing}\n @click=${this._handleClick}\n @keydown=${this._handleKeydown}\n >\n ${this._renderContent()}\n </li>\n `;\n }\n\n /** @internal */\n private _handleKeydown(e: KeyboardEvent): void {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n if (!this.disabled) {\n this._dispatchListItemClick();\n }\n }\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-list-item': HelixListItem;\n }\n}\n","import { html, nothing, type PropertyValues } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property } from 'lit/decorators.js';\nimport { ifDefined } from 'lit/directives/if-defined.js';\nimport { HelixElement } from '../../base/index.js';\nimport { helixListStyles } from './hx-list.styles.js';\nimport { forcedColorsSurface } from '../../styles/forced-colors.js';\nimport { HelixListItem } from './hx-list-item.js'; // real import for instanceof check and property access\nimport { devWarn } from '../../utils/dev-warn.js';\n\n/**\n * A styled list container supporting plain, bulleted, numbered, description, and interactive variants.\n *\n * @summary Container for list items with optional dividers and interactive selection.\n *\n * @tag hx-list\n *\n * @slot - Default slot for `hx-list-item` elements.\n *\n * @fires {CustomEvent<{item: HelixListItem, value: string | undefined}>} hx-select - Dispatched\n * when an item is clicked in interactive mode.\n *\n * @csspart base - The root list element.\n *\n * @cssprop [--hx-list-gap=0] - Gap between list items.\n * @cssprop [--hx-list-divider-color=var(--hx-color-neutral-200)] - Divider line color.\n * @cssprop [--hx-space-6] - Spacing token.\n * @cssprop [--hx-border-width-thin] - Width.\n * @cssprop [--hx-color-neutral-200] - Color.\n */\n@customElement('hx-list')\nexport class HelixList extends HelixElement {\n static override styles = [helixListStyles, forcedColorsSurface];\n\n /**\n * Visual variant of the list.\n * @attr variant\n */\n @property({ type: String, reflect: true })\n variant: 'plain' | 'bulleted' | 'numbered' | 'description' | 'interactive' = 'plain';\n\n /**\n * Whether to show dividers between list items.\n * @attr divided\n */\n @property({ type: Boolean, reflect: true })\n divided = false;\n\n /**\n * Accessible label for the list. Required when variant is \"interactive\" (listbox role).\n * @attr label\n */\n @property({ type: String })\n label: string | undefined = undefined;\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.addEventListener('keydown', this._handleKeydown);\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener('keydown', this._handleKeydown);\n }\n\n override updated(changedProps: PropertyValues<this>): void {\n super.updated(changedProps);\n if (changedProps.has('variant')) {\n this._updateItemStates();\n }\n if (this.variant === 'interactive' && !this.label) {\n devWarn(\n 'hx-list',\n 'The \"label\" attribute is required when variant is \"interactive\". ' +\n 'Add a label to provide an accessible name for the listbox (WCAG 2.1 SC 4.1.2).',\n );\n }\n }\n\n /**\n * Sets the `interactive` property on all child hx-list-item elements\n * so they can style and behave correctly without `:host-context()`.\n */\n /** @internal */\n private _updateItemStates(): void {\n const isInteractive = this.variant === 'interactive';\n const items = this.querySelectorAll('hx-list-item');\n for (const item of items) {\n (item as HelixListItem).interactive = isInteractive;\n }\n }\n\n /** @internal */\n private _handleSlotChange(): void {\n this._updateItemStates();\n }\n\n /** @internal */\n private readonly _handleKeydown = (e: KeyboardEvent): void => {\n if (this.variant !== 'interactive') return;\n\n const items = Array.from(this.querySelectorAll<HelixListItem>('hx-list-item:not([disabled])'));\n if (items.length === 0) return;\n\n const focused = this.querySelector<HelixListItem>('hx-list-item:focus');\n const currentIndex = focused ? items.indexOf(focused) : -1;\n\n let nextIndex = currentIndex;\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;\n break;\n case 'ArrowUp':\n e.preventDefault();\n nextIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;\n break;\n case 'Home':\n e.preventDefault();\n nextIndex = 0;\n break;\n case 'End':\n e.preventDefault();\n nextIndex = items.length - 1;\n break;\n default:\n return;\n }\n\n items[nextIndex]?.focus();\n };\n\n /** @internal */\n private _handleItemClick(e: Event): void {\n if (this.variant !== 'interactive') return;\n\n if (!(e.target instanceof HelixListItem)) return;\n const item = e.target;\n if (item.disabled) return;\n\n this.dispatchEvent(\n new CustomEvent<{ item: HelixListItem; value: string | undefined }>('hx-select', {\n bubbles: true,\n composed: true,\n detail: { item, value: item.value },\n }),\n );\n }\n\n override render() {\n const isInteractive = this.variant === 'interactive';\n const isNumbered = this.variant === 'numbered';\n const isDescription = this.variant === 'description';\n\n const slot = html`<slot @slotchange=${this._handleSlotChange}></slot>`;\n\n if (isDescription) {\n return html`\n <dl\n part=\"base\"\n class=\"list list--${this.variant}\"\n aria-label=${ifDefined(this.label)}\n @hx-list-item-click=${this._handleItemClick}\n >\n ${slot}\n </dl>\n `;\n }\n\n if (isNumbered) {\n return html`\n <div\n part=\"base\"\n role=\"list\"\n class=\"list list--${this.variant}\"\n aria-label=${ifDefined(this.label)}\n @hx-list-item-click=${this._handleItemClick}\n >\n ${slot}\n </div>\n `;\n }\n\n return html`\n <div\n part=\"base\"\n class=\"list list--${this.variant}\"\n role=${isInteractive ? 'listbox' : 'list'}\n aria-label=${isInteractive ? this.label || nothing : ifDefined(this.label)}\n aria-multiselectable=${isInteractive ? 'false' : nothing}\n @hx-list-item-click=${this._handleItemClick}\n >\n ${slot}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-list': HelixList;\n }\n}\n"],"names":["helixListStyles","css","helixListItemStyles","HelixListItem","HelixElement","changedProps","e","html","classMap","nothing","ifDefined","forcedColorsSurface","__decorateClass","property","customElement","HelixList","items","focused","currentIndex","nextIndex","_a","isInteractive","item","isNumbered","isDescription","slot"],"mappings":";;;;;;AAEO,MAAMA,IAAkBC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GCAlBC,IAAsBD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;;ACoC5B,IAAME,IAAN,cAA4BC,EAAa;AAAA,EAAzC,cAAA;AAAA,UAAA,GAAA,SAAA,GAQL,KAAA,WAAW,IAOX,KAAA,WAAW,IAOX,KAAA,OAA2B,QAO3B,KAAA,QAA4B,QAQ5B,KAAA,cAAc,IAOd,KAAA,OAA0C;AAAA,EAAA;AAAA,EAEjC,QAAQC,GAA0C;AACzD,UAAM,QAAQA,CAAY,IAExBA,EAAa,IAAI,aAAa,KAC9BA,EAAa,IAAI,UAAU,KAC3BA,EAAa,IAAI,UAAU,MAE3B,KAAK,cAAA;AAAA,EAET;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,gBAAsB;AAC5B,IAAI,KAAK,eACP,KAAK,aAAa,QAAQ,QAAQ,GAClC,KAAK,aAAa,iBAAiB,OAAO,KAAK,QAAQ,CAAC,GACpD,KAAK,WACP,KAAK,aAAa,iBAAiB,MAAM,IAEzC,KAAK,gBAAgB,eAAe,GAEjC,KAAK,WAGR,KAAK,gBAAgB,UAAU,IAF/B,KAAK,aAAa,YAAY,GAAG,MAKnC,KAAK,gBAAgB,MAAM,GAC3B,KAAK,gBAAgB,eAAe,GACpC,KAAK,gBAAgB,eAAe,GACpC,KAAK,gBAAgB,UAAU;AAAA,EAEnC;AAAA;AAAA,EAGQ,yBAA+B;AACrC,SAAK;AAAA,MACH,IAAI,YAAgE,sBAAsB;AAAA,QACxF,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ,EAAE,MAAM,MAAM,OAAO,KAAK,MAAA;AAAA,MAAM,CACzC;AAAA,IAAA;AAAA,EAEL;AAAA;AAAA,EAGQ,aAAaC,GAAqB;AACxC,QAAI,KAAK,UAAU;AACjB,MAAAA,EAAE,eAAA,GACFA,EAAE,gBAAA;AACF;AAAA,IACF;AAEA,SAAK,uBAAA;AAAA,EACP;AAAA;AAAA,EAGQ,iBAAiB;AACvB,WAAOC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBT;AAAA,EAES,SAAS;AAEhB,WAAI,KAAK,SAAS,SACTA;AAAA;AAAA;AAAA,kBAGKC,EAAS,EAAE,aAAa,IAAM,uBAAuB,KAAK,UAAU,CAAC;AAAA,0BAC7D,KAAK,WAAW,SAASC,CAAO;AAAA;AAAA,YAE9C,KAAK,gBAAgB;AAAA;AAAA,UAKzB,KAAK,SAAS,eACTF;AAAA;AAAA;AAAA,kBAGKC,EAAS,EAAE,aAAa,IAAM,uBAAuB,KAAK,UAAU,CAAC;AAAA,0BAC7D,KAAK,WAAW,SAASC,CAAO;AAAA;AAAA,YAE9C,KAAK,gBAAgB;AAAA;AAAA,UAOzB,KAAK,cAGAF;AAAA;AAAA;AAAA;AAAA,kBAIKC,EAAS;AAAA,MACf,aAAa;AAAA,MACb,uBAAuB,KAAK;AAAA,MAC5B,uBAAuB,KAAK;AAAA,IAAA,CAC7B,CAAC;AAAA,mBACO,KAAK,YAAY;AAAA,qBACf,KAAK,cAAc;AAAA;AAAA,YAE5B,KAAK,gBAAgB;AAAA;AAAA,UAMzB,KAAK,SAAS,UAAa,CAAC,KAAK,WAC5BD;AAAA;AAAA;AAAA;AAAA;AAAA,0BAKa,KAAK,WAAW,SAASE,CAAO;AAAA;AAAA,4CAEdC,EAAU,KAAK,IAAI,CAAC,WAAW,KAAK,YAAY;AAAA,cAC9E,KAAK,gBAAgB;AAAA;AAAA;AAAA,UAOxBH;AAAA;AAAA;AAAA,gBAGKC,EAAS;AAAA,MACf,aAAa;AAAA,MACb,uBAAuB,KAAK;AAAA,MAC5B,uBAAuB,KAAK;AAAA,IAAA,CAC7B,CAAC;AAAA;AAAA,wBAEc,KAAK,WAAW,SAASC,CAAO;AAAA,iBACvC,KAAK,YAAY;AAAA,mBACf,KAAK,cAAc;AAAA;AAAA,UAE5B,KAAK,gBAAgB;AAAA;AAAA;AAAA,EAG7B;AAAA;AAAA,EAGQ,eAAeH,GAAwB;AAC7C,KAAIA,EAAE,QAAQ,WAAWA,EAAE,QAAQ,SACjCA,EAAE,eAAA,GACG,KAAK,YACR,KAAK,uBAAA;AAAA,EAGX;AACF;AA1NaH,EACK,SAAS,CAACD,GAAqBS,CAAmB;AAOlEC,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,SAAS,SAAS,IAAM;AAAA,GAP/BV,EAQX,WAAA,YAAA,CAAA;AAOAS,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,SAAS,SAAS,IAAM;AAAA,GAd/BV,EAeX,WAAA,YAAA,CAAA;AAOAS,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GArBfV,EAsBX,WAAA,QAAA,CAAA;AAOAS,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GA5BfV,EA6BX,WAAA,SAAA,CAAA;AAQAS,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,SAAS,SAAS,IAAM;AAAA,GApC/BV,EAqCX,WAAA,eAAA,CAAA;AAOAS,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,QAAQ,SAAS,IAAM;AAAA,GA3C9BV,EA4CX,WAAA,QAAA,CAAA;AA5CWA,IAANS,EAAA;AAAA,EADNE,EAAc,cAAc;AAAA,GAChBX,CAAA;;;;;;ACPN,IAAMY,IAAN,cAAwBX,EAAa;AAAA,EAArC,cAAA;AAAA,UAAA,GAAA,SAAA,GAQL,KAAA,UAA6E,SAO7E,KAAA,UAAU,IAOV,KAAA,QAA4B,QA6C5B,KAAiB,iBAAiB,CAACE,MAA2B;;AAC5D,UAAI,KAAK,YAAY,cAAe;AAEpC,YAAMU,IAAQ,MAAM,KAAK,KAAK,iBAAgC,8BAA8B,CAAC;AAC7F,UAAIA,EAAM,WAAW,EAAG;AAExB,YAAMC,IAAU,KAAK,cAA6B,oBAAoB,GAChEC,IAAeD,IAAUD,EAAM,QAAQC,CAAO,IAAI;AAExD,UAAIE,IAAYD;AAEhB,cAAQZ,EAAE,KAAA;AAAA,QACR,KAAK;AACH,UAAAA,EAAE,eAAA,GACFa,IAAYD,IAAeF,EAAM,SAAS,IAAIE,IAAe,IAAI;AACjE;AAAA,QACF,KAAK;AACH,UAAAZ,EAAE,eAAA,GACFa,IAAYD,IAAe,IAAIA,IAAe,IAAIF,EAAM,SAAS;AACjE;AAAA,QACF,KAAK;AACH,UAAAV,EAAE,eAAA,GACFa,IAAY;AACZ;AAAA,QACF,KAAK;AACH,UAAAb,EAAE,eAAA,GACFa,IAAYH,EAAM,SAAS;AAC3B;AAAA,QACF;AACE;AAAA,MAAA;AAGJ,OAAAI,IAAAJ,EAAMG,CAAS,MAAf,QAAAC,EAAkB;AAAA,IACpB;AAAA,EAAA;AAAA,EA5ES,oBAA0B;AACjC,UAAM,kBAAA,GACN,KAAK,iBAAiB,WAAW,KAAK,cAAc;AAAA,EACtD;AAAA,EAES,uBAA6B;AACpC,UAAM,qBAAA,GACN,KAAK,oBAAoB,WAAW,KAAK,cAAc;AAAA,EACzD;AAAA,EAES,QAAQf,GAA0C;AACzD,UAAM,QAAQA,CAAY,GACtBA,EAAa,IAAI,SAAS,KAC5B,KAAK,kBAAA,GAEH,KAAK,YAAY,iBAAkB,KAAK;AAAA,EAO9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,oBAA0B;AAChC,UAAMgB,IAAgB,KAAK,YAAY,eACjCL,IAAQ,KAAK,iBAAiB,cAAc;AAClD,eAAWM,KAAQN;AAChB,MAAAM,EAAuB,cAAcD;AAAA,EAE1C;AAAA;AAAA,EAGQ,oBAA0B;AAChC,SAAK,kBAAA;AAAA,EACP;AAAA;AAAA,EAuCQ,iBAAiBf,GAAgB;AAGvC,QAFI,KAAK,YAAY,iBAEjB,EAAEA,EAAE,kBAAkBH,GAAgB;AAC1C,UAAMmB,IAAOhB,EAAE;AACf,IAAIgB,EAAK,YAET,KAAK;AAAA,MACH,IAAI,YAAgE,aAAa;AAAA,QAC/E,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ,EAAE,MAAAA,GAAM,OAAOA,EAAK,MAAA;AAAA,MAAM,CACnC;AAAA,IAAA;AAAA,EAEL;AAAA,EAES,SAAS;AAChB,UAAMD,IAAgB,KAAK,YAAY,eACjCE,IAAa,KAAK,YAAY,YAC9BC,IAAgB,KAAK,YAAY,eAEjCC,IAAOlB,sBAAyB,KAAK,iBAAiB;AAE5D,WAAIiB,IACKjB;AAAA;AAAA;AAAA,8BAGiB,KAAK,OAAO;AAAA,uBACnBG,EAAU,KAAK,KAAK,CAAC;AAAA,gCACZ,KAAK,gBAAgB;AAAA;AAAA,YAEzCe,CAAI;AAAA;AAAA,UAKRF,IACKhB;AAAA;AAAA;AAAA;AAAA,8BAIiB,KAAK,OAAO;AAAA,uBACnBG,EAAU,KAAK,KAAK,CAAC;AAAA,gCACZ,KAAK,gBAAgB;AAAA;AAAA,YAEzCe,CAAI;AAAA;AAAA,UAKLlB;AAAA;AAAA;AAAA,4BAGiB,KAAK,OAAO;AAAA,eACzBc,IAAgB,YAAY,MAAM;AAAA,qBAC5BA,IAAgB,KAAK,SAASZ,IAAUC,EAAU,KAAK,KAAK,CAAC;AAAA,+BACnDW,IAAgB,UAAUZ,CAAO;AAAA,8BAClC,KAAK,gBAAgB;AAAA;AAAA,UAEzCgB,CAAI;AAAA;AAAA;AAAA,EAGZ;AACF;AAtKaV,EACK,SAAS,CAACf,GAAiBW,CAAmB;AAO9DC,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,QAAQ,SAAS,IAAM;AAAA,GAP9BE,EAQX,WAAA,WAAA,CAAA;AAOAH,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,SAAS,SAAS,IAAM;AAAA,GAd/BE,EAeX,WAAA,WAAA,CAAA;AAOAH,EAAA;AAAA,EADCC,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GArBfE,EAsBX,WAAA,SAAA,CAAA;AAtBWA,IAANH,EAAA;AAAA,EADNE,EAAc,SAAS;AAAA,GACXC,CAAA;"}
|
|
@@ -211,10 +211,7 @@ const S = p`
|
|
|
211
211
|
|
|
212
212
|
.menu-item:focus-visible {
|
|
213
213
|
outline: var(--hx-focus-ring-width, 2px) solid
|
|
214
|
-
var(
|
|
215
|
-
--hx-menu-item-focus-ring-color,
|
|
216
|
-
var(--hx-focus-ring-color, var(--hx-color-primary-500, #429797))
|
|
217
|
-
);
|
|
214
|
+
var(--hx-menu-item-focus-ring-color, var(--hx-focus-ring-color, #0f7078));
|
|
218
215
|
outline-offset: var(--hx-menu-item-focus-ring-offset, 0px);
|
|
219
216
|
}
|
|
220
217
|
|
|
@@ -558,4 +555,4 @@ export {
|
|
|
558
555
|
m as a,
|
|
559
556
|
o as b
|
|
560
557
|
};
|
|
561
|
-
//# sourceMappingURL=hx-menu-divider-
|
|
558
|
+
//# sourceMappingURL=hx-menu-divider-C2omnPtj.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hx-menu-divider-C2omnPtj.js","sources":["../../src/components/hx-menu/hx-menu.styles.ts","../../src/components/hx-menu/hx-menu.ts","../../src/components/hx-menu/hx-menu-item.styles.ts","../../src/components/hx-menu/hx-menu-item.ts","../../src/components/hx-menu/hx-menu-divider.styles.ts","../../src/components/hx-menu/hx-menu-divider.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const helixMenuStyles = css`\n :host {\n display: block;\n }\n\n .menu {\n display: flex;\n flex-direction: column;\n padding: var(--hx-space-1, 0.25rem);\n background: var(--hx-menu-bg, var(--hx-color-surface-default, #ffffff));\n border: var(--hx-border-width-thin, 1px) solid\n var(--hx-menu-border-color, var(--hx-color-border-default, #d6dbd5));\n border-radius: var(--hx-menu-border-radius, var(--hx-border-radius-md, 0.375rem));\n box-shadow: var(\n --hx-menu-shadow,\n var(--hx-shadow-md, 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1))\n );\n min-width: var(--hx-menu-min-width, 10rem);\n max-height: var(--hx-menu-max-height, 20rem);\n overflow-y: auto;\n outline: none;\n }\n\n /* ─── High Contrast Mode (forced-colors) ─── */\n\n @media (forced-colors: active) {\n .menu {\n background-color: Canvas;\n border: 2px solid CanvasText;\n }\n }\n`;\n","import { html } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property } from 'lit/decorators.js';\nimport { HelixElement } from '../../base/index.js';\nimport { forcedColorsInteractive } from '../../styles/forced-colors.js';\nimport { helixMenuStyles } from './hx-menu.styles.js';\nimport type { HelixMenuItem } from './hx-menu-item.js';\nimport { devWarn } from '../../utils/dev-warn.js';\n\n/**\n * A menu container that manages keyboard navigation over a list of menu items.\n * Use with `hx-menu-item` and `hx-menu-divider`.\n *\n * @summary Context/action menu with keyboard-navigable items.\n *\n * @tag hx-menu\n *\n * @slot - Default slot for hx-menu-item and hx-menu-divider elements.\n *\n * @fires {CustomEvent<{item: HelixMenuItem, value: string}>} hx-select - Dispatched when an item is selected.\n * @fires {CustomEvent<void>} hx-close - Dispatched when Escape is pressed.\n *\n * @csspart base - The root menu element.\n *\n * @cssprop [--hx-menu-bg=var(--hx-color-neutral-0)] - Menu background color.\n * @cssprop [--hx-menu-border-color=var(--hx-color-neutral-200)] - Menu border color.\n * @cssprop [--hx-menu-border-radius=var(--hx-border-radius-md)] - Menu border radius.\n * @cssprop [--hx-menu-shadow] - Menu box shadow.\n * @cssprop [--hx-menu-min-width=10rem] - Minimum menu width.\n * @cssprop [--hx-menu-max-height=20rem] - Maximum menu height before vertical scroll is activated.\n * @cssprop [--hx-space-1] - Spacing token.\n * @cssprop [--hx-color-neutral-0] - Color.\n * @cssprop [--hx-border-width-thin] - Width.\n * @cssprop [--hx-color-neutral-200] - Color.\n * @cssprop [--hx-border-radius-md] - CSS custom property.\n * @cssprop [--hx-shadow-md] - Box shadow.\n */\n@customElement('hx-menu')\nexport class HelixMenu extends HelixElement {\n static override styles = [helixMenuStyles, forcedColorsInteractive];\n\n /**\n * Accessible label for the menu. Rendered as `aria-label` on the inner\n * `role=\"menu\"` element when set.\n * @attr label\n */\n @property({ type: String, reflect: true })\n label = '';\n\n /**\n * Index of the currently focused menu item within the list of enabled items.\n * @internal\n */\n private _focusedIndex = -1;\n\n /**\n * Accumulated character buffer for typeahead search within menu items.\n * @internal\n */\n private _typeaheadBuffer = '';\n\n /**\n * Timer handle that clears the typeahead buffer after a period of inactivity.\n * @internal\n */\n private _typeaheadTimer: ReturnType<typeof setTimeout> | null = null;\n\n /** @internal */\n private _getItems(): HelixMenuItem[] {\n return Array.from(this.querySelectorAll<HelixMenuItem>('hx-menu-item')).filter(\n (item) => !item.disabled && !item.loading,\n );\n }\n\n /**\n * Synchronize roving tabindex across all enabled items.\n * Only the active item (or first item if none active) gets tabindex=0.\n */\n /** @internal */\n private _syncRovingTabIndex(): void {\n const items = this._getItems();\n const activeIndex = this._focusedIndex >= 0 ? this._focusedIndex : 0;\n items.forEach((item, i) => {\n item.setRovingTabIndex(i === activeIndex ? 0 : -1);\n });\n }\n\n /** Focus the first menu item. */\n focusFirst(): void {\n const items = this._getItems();\n const first = items[0];\n if (first !== undefined) {\n this._focusedIndex = 0;\n this._syncRovingTabIndex();\n first.focus();\n }\n }\n\n /** Focus the last menu item. */\n focusLast(): void {\n const items = this._getItems();\n const last = items[items.length - 1];\n if (last !== undefined) {\n this._focusedIndex = items.length - 1;\n this._syncRovingTabIndex();\n last.focus();\n }\n }\n\n /** @internal */\n private _focusItem(index: number): void {\n const items = this._getItems();\n if (items.length === 0) return;\n this._focusedIndex = Math.max(0, Math.min(index, items.length - 1));\n this._syncRovingTabIndex();\n const target = items[this._focusedIndex];\n if (target !== undefined) target.focus();\n }\n\n /** @internal */\n private _updateFocusedIndex(): void {\n const items = this._getItems();\n const active = this.shadowRoot?.activeElement ?? document.activeElement;\n // Find the active item by checking if any item's shadow root contains the active element\n const idx = items.findIndex((item) => item.matches(':focus-within') || item === active);\n if (idx !== -1) this._focusedIndex = idx;\n }\n\n /** @internal */\n private _handleKeyDown(e: KeyboardEvent): void {\n this._updateFocusedIndex();\n const items = this._getItems();\n if (items.length === 0) return;\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n this._focusItem(this._focusedIndex + 1 < items.length ? this._focusedIndex + 1 : 0);\n break;\n case 'ArrowUp':\n e.preventDefault();\n this._focusItem(this._focusedIndex > 0 ? this._focusedIndex - 1 : items.length - 1);\n break;\n case 'Home':\n e.preventDefault();\n this._focusItem(0);\n break;\n case 'End':\n e.preventDefault();\n this._focusItem(items.length - 1);\n break;\n case 'Escape':\n e.preventDefault();\n this.dispatchEvent(new CustomEvent<void>('hx-close', { bubbles: true, composed: true }));\n break;\n default:\n if (e.key.length === 1 && e.key !== ' ' && !e.ctrlKey && !e.metaKey && !e.altKey) {\n this._handleTypeahead(e.key, items);\n }\n break;\n }\n }\n\n /** @internal */\n private _handleTypeahead(char: string, items: HelixMenuItem[]): void {\n if (this._typeaheadTimer !== null) {\n clearTimeout(this._typeaheadTimer);\n }\n this._typeaheadBuffer += char.toLowerCase();\n this._typeaheadTimer = setTimeout(() => {\n this._typeaheadBuffer = '';\n this._typeaheadTimer = null;\n }, 500);\n\n const match = items.findIndex((item) => {\n if (item.disabled || item.hasAttribute('disabled')) return false;\n const text = item.textContent?.trim().toLowerCase() ?? '';\n return text.startsWith(this._typeaheadBuffer);\n });\n\n if (match !== -1) {\n this._focusItem(match);\n }\n }\n\n /** @internal */\n private _handleSlotChange(e: Event): void {\n const slot = e.target;\n if (!(slot instanceof HTMLSlotElement)) return;\n const validTags = new Set(['hx-menu-item', 'hx-menu-divider']);\n const invalid = slot\n .assignedElements()\n .filter((el) => !validTags.has(el.tagName.toLowerCase()));\n if (invalid.length > 0) {\n devWarn(\n 'hx-menu',\n `Default slot expects <hx-menu-item> or <hx-menu-divider> elements. Found unexpected: ${invalid.map((el) => `<${el.tagName.toLowerCase()}>`).join(', ')}`,\n );\n }\n // Initialize roving tabindex when items are slotted\n this._syncRovingTabIndex();\n }\n\n /** @internal */\n private _handleItemSelect(e: Event): void {\n if (!(e instanceof CustomEvent)) return;\n const detail = (e as CustomEvent<{ item: HelixMenuItem; value: string }>).detail;\n const items = this._getItems();\n this._focusedIndex = items.indexOf(detail.item);\n\n this.dispatchEvent(\n new CustomEvent<{ item: HelixMenuItem; value: string }>('hx-select', {\n bubbles: true,\n composed: true,\n detail: { item: detail.item, value: detail.value },\n }),\n );\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n if (this._typeaheadTimer !== null) {\n clearTimeout(this._typeaheadTimer);\n this._typeaheadTimer = null;\n }\n }\n\n override firstUpdated(): void {\n if (!this.label) {\n devWarn(\n 'hx-menu',\n 'No accessible label provided. Set the `label` attribute on hx-menu so screen readers can identify this menu (WCAG 4.1.2).',\n );\n }\n }\n\n override render() {\n return html`\n <div\n part=\"base\"\n class=\"menu\"\n role=\"menu\"\n aria-label=${this.label || 'Menu'}\n @keydown=${this._handleKeyDown}\n @hx-item-select=${this._handleItemSelect}\n >\n <slot @slotchange=${this._handleSlotChange}></slot>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-menu': HelixMenu;\n }\n}\n","import { css } from 'lit';\n\nexport const helixMenuItemStyles = css`\n :host {\n display: block;\n }\n\n :host([disabled]) {\n pointer-events: none;\n opacity: var(--hx-opacity-disabled, 0.5);\n }\n\n .menu-item {\n display: flex;\n align-items: center;\n gap: var(--hx-space-2, 0.5rem);\n min-height: var(--hx-touch-target-min, 44px);\n padding: var(--hx-space-2, 0.5rem) var(--hx-space-3, 0.75rem);\n border-radius: var(--hx-border-radius-sm, 0.25rem);\n cursor: pointer;\n color: var(--hx-menu-item-color, var(--hx-color-text-primary, #0d1825));\n font-size: var(--hx-font-size-sm, 0.875rem);\n font-family: var(--hx-menu-item-font-family, var(--hx-font-family-sans, sans-serif));\n line-height: var(--hx-line-height-tight, 1.25);\n user-select: none;\n -webkit-user-select: none;\n outline: none;\n background: none;\n width: 100%;\n box-sizing: border-box;\n transition: background-color var(--hx-transition-fast, 150ms ease);\n }\n\n .menu-item:hover,\n .menu-item:focus-visible {\n background-color: var(--hx-menu-item-hover-bg, var(--hx-color-surface-sunken, #ebeee9));\n }\n\n .menu-item:focus-visible {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-menu-item-focus-ring-color, var(--hx-focus-ring-color, #0f7078));\n outline-offset: var(--hx-menu-item-focus-ring-offset, 0px);\n }\n\n .menu-item__prefix,\n .menu-item__suffix {\n display: inline-flex;\n align-items: center;\n flex-shrink: 0;\n }\n\n .menu-item__label {\n flex: 1 1 auto;\n }\n\n .menu-item__checked-icon {\n display: inline-flex;\n align-items: center;\n flex-shrink: 0;\n width: 1em;\n opacity: 0;\n transition: opacity var(--hx-transition-fast, 150ms ease);\n }\n\n .menu-item--checked .menu-item__checked-icon {\n opacity: 1;\n }\n\n .menu-item__submenu-icon {\n display: inline-flex;\n align-items: center;\n flex-shrink: 0;\n margin-inline-start: auto;\n }\n\n .menu-item__spinner {\n width: 1em;\n height: 1em;\n flex-shrink: 0;\n animation: hx-menu-spin var(--hx-duration-spinner, 750ms) linear infinite;\n }\n\n @keyframes hx-menu-spin {\n to {\n transform: rotate(360deg);\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .menu-item {\n transition: none;\n }\n\n .menu-item__checked-icon {\n transition: none;\n }\n\n .menu-item__spinner {\n animation: none;\n opacity: var(--hx-opacity-muted, 0.6);\n }\n }\n\n /* ─── High Contrast Mode (forced-colors) ─── */\n\n @media (forced-colors: active) {\n .menu-item {\n forced-color-adjust: none;\n color: CanvasText;\n background-color: Canvas;\n }\n\n .menu-item:hover,\n .menu-item:focus-visible {\n background-color: Highlight;\n color: HighlightText;\n }\n\n .menu-item:focus-visible {\n outline: 2px solid Highlight;\n outline-offset: -2px;\n }\n\n :host([disabled]) .menu-item {\n color: GrayText;\n opacity: 1;\n }\n }\n`;\n","import { html, nothing } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property, query, state } from 'lit/decorators.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { HelixElement } from '../../base/index.js';\nimport { forcedColorsInteractive } from '../../styles/forced-colors.js';\nimport { helixMenuItemStyles } from './hx-menu-item.styles.js';\nimport { devWarn } from '../../utils/dev-warn.js';\n\n/**\n * A single interactive item for use inside `hx-menu`. Supports normal, checkbox,\n * and radio types, loading state, prefix/suffix slots, and submenu nesting.\n * Use `aria-label` on the parent `hx-menu` to provide an accessible name.\n *\n * @summary Interactive item within an hx-menu.\n *\n * @tag hx-menu-item\n *\n * @slot - Default slot for the item label.\n * @slot prefix - Icon or content rendered before the label.\n * @slot suffix - Shortcut text or icon rendered after the label.\n * @slot submenu - A nested hx-menu for submenu content.\n *\n * @fires {CustomEvent<{item: HelixMenuItem, value: string}>} hx-item-select - Dispatched when the item is activated via click, Enter, or Space.\n * @fires {CustomEvent<{item: HelixMenuItem}>} hx-item-submenu-open - Dispatched when ArrowRight is pressed on an item with a submenu.\n * @fires {CustomEvent<{item: HelixMenuItem}>} hx-item-submenu-close - Dispatched when ArrowLeft is pressed on an item, signaling the parent to close the submenu and return focus.\n *\n * @csspart base - The root item element.\n * @csspart prefix - Prefix slot wrapper.\n * @csspart label - Label slot wrapper.\n * @csspart suffix - Suffix slot wrapper.\n * @csspart submenu-icon - The chevron icon indicating a submenu.\n * @csspart checked-icon - The checkmark icon for checkbox-type items.\n *\n * @cssprop [--hx-menu-item-color=var(--hx-color-neutral-900)] - Item text color.\n * @cssprop [--hx-menu-item-hover-bg=var(--hx-color-neutral-100)] - Item hover/focus background.\n */\n@customElement('hx-menu-item')\nexport class HelixMenuItem extends HelixElement {\n static override styles = [helixMenuItemStyles, forcedColorsInteractive];\n\n /**\n * @internal Managed by parent hx-menu for roving tabindex.\n * Only the active item in the menu has tabindex=0; all others have -1.\n */\n @state()\n private _rovingTabIndex = -1;\n\n /** @internal Set the roving tabindex value. Called by parent hx-menu. */\n setRovingTabIndex(value: number): void {\n this._rovingTabIndex = value;\n }\n\n /** Set whether the nested submenu is open. Called by the component managing submenu visibility. */\n setSubmenuOpen(open: boolean): void {\n this._submenuOpen = open;\n }\n\n /**\n * The value associated with this item, emitted in the hx-select event.\n * @attr value\n */\n @property({ type: String })\n value = '';\n\n /**\n * Whether the item is disabled. Prevents interaction and event dispatch.\n * @attr disabled\n */\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n /**\n * Whether the item is checked. Only meaningful when type=\"checkbox\".\n * @attr checked\n */\n @property({ type: Boolean, reflect: true })\n checked = false;\n\n /**\n * The type of menu item. \"checkbox\" renders a checkmark and toggles checked state.\n * \"radio\" renders a checkmark and emits selection for radio-group behavior.\n * @attr type\n */\n @property({ type: String, reflect: true })\n type: 'normal' | 'checkbox' | 'radio' = 'normal';\n\n /**\n * Whether the item is in a loading state. Shows a spinner and prevents interaction.\n * @attr loading\n */\n @property({ type: Boolean, reflect: true })\n loading = false;\n\n /** @internal */\n @state()\n private _hasSubmenu = false;\n\n /** @internal Tracks whether the nested submenu is currently open. */\n @state()\n private _submenuOpen = false;\n\n /** @internal */\n @query('.menu-item') private _menuItemEl!: HTMLElement | null;\n\n /** Focus the inner interactive element. */\n override focus(options?: FocusOptions): void {\n this._menuItemEl?.focus(options);\n }\n\n override connectedCallback(): void {\n super.connectedCallback();\n // WCAG 4.1.2: menuitem role is only valid inside a role=\"menu\" or role=\"menubar\" container.\n // Check the closest ancestor with a menu role.\n const menuHost = this.closest('hx-menu, hx-split-button, [role=\"menu\"], [role=\"menubar\"]');\n if (!menuHost) {\n devWarn(\n 'hx-menu-item',\n 'hx-menu-item must be used inside an hx-menu or an element with role=\"menu\". ' +\n 'An orphaned menuitem violates WCAG 1.3.1 (Info and Relationships).',\n );\n }\n }\n\n /** @internal */\n private _handleSubmenuSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n this._hasSubmenu = slot.assignedElements().length > 0;\n }\n\n /** @internal */\n private _activate(): void {\n if (this.disabled || this.loading) return;\n\n if (this.type === 'checkbox') {\n this.checked = !this.checked;\n } else if (this.type === 'radio') {\n const menu = this.closest('hx-menu');\n if (menu) {\n menu\n .querySelectorAll<HelixMenuItem>(':scope > hx-menu-item[type=\"radio\"]')\n .forEach((sibling) => {\n sibling.checked = sibling === this;\n });\n } else {\n this.checked = true;\n }\n }\n\n this.dispatchEvent(\n new CustomEvent<{ item: HelixMenuItem; value: string }>('hx-item-select', {\n bubbles: true,\n composed: true,\n detail: { item: this, value: this.value },\n }),\n );\n }\n\n /** @internal */\n private _handleClick(e: MouseEvent): void {\n if (this.disabled || this.loading) {\n e.preventDefault();\n e.stopPropagation();\n return;\n }\n this._activate();\n }\n\n /** @internal */\n private _handleKeyDown(e: KeyboardEvent): void {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n this._activate();\n return;\n }\n\n if (e.key === 'ArrowRight' && this._hasSubmenu) {\n e.preventDefault();\n this.dispatchEvent(\n new CustomEvent<{ item: HelixMenuItem }>('hx-item-submenu-open', {\n bubbles: true,\n composed: true,\n detail: { item: this },\n }),\n );\n return;\n }\n\n if (e.key === 'ArrowLeft') {\n e.preventDefault();\n this.dispatchEvent(\n new CustomEvent<{ item: HelixMenuItem }>('hx-item-submenu-close', {\n bubbles: true,\n composed: true,\n detail: { item: this },\n }),\n );\n }\n }\n\n /** @internal */\n private _renderCheckedIcon() {\n return html`\n <span part=\"checked-icon\" class=\"menu-item__checked-icon\" aria-hidden=\"true\">\n <svg\n width=\"1em\"\n height=\"1em\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n </span>\n `;\n }\n\n /** @internal */\n private _renderSubmenuIcon() {\n return html`\n <span part=\"submenu-icon\" class=\"menu-item__submenu-icon\" aria-hidden=\"true\">\n <svg\n width=\"1em\"\n height=\"1em\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <polyline points=\"9 18 15 12 9 6\" />\n </svg>\n </span>\n `;\n }\n\n /** @internal */\n private _renderSpinner() {\n return html`\n <svg class=\"menu-item__spinner\" aria-hidden=\"true\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"3\" opacity=\"0.3\" />\n <path\n d=\"M12 2a10 10 0 0 1 10 10\"\n stroke=\"currentColor\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n </svg>\n `;\n }\n\n /** @internal */\n private _getRole(): string {\n switch (this.type) {\n case 'checkbox':\n return 'menuitemcheckbox';\n case 'radio':\n return 'menuitemradio';\n default:\n return 'menuitem';\n }\n }\n\n override render() {\n const role = this._getRole();\n const hasCheckableRole = this.type === 'checkbox' || this.type === 'radio';\n const classes = {\n 'menu-item': true,\n 'menu-item--checked': this.checked,\n };\n\n return html`\n <div\n part=\"base\"\n class=${classMap(classes)}\n role=${role}\n tabindex=${this.disabled ? '-1' : String(this._rovingTabIndex)}\n aria-disabled=${this.disabled ? 'true' : nothing}\n aria-checked=${hasCheckableRole ? (this.checked ? 'true' : 'false') : nothing}\n aria-haspopup=${this._hasSubmenu ? 'menu' : nothing}\n aria-expanded=${this._hasSubmenu ? (this._submenuOpen ? 'true' : 'false') : nothing}\n aria-busy=${this.loading ? 'true' : nothing}\n @click=${this._handleClick}\n @keydown=${this._handleKeyDown}\n >\n ${this.loading ? this._renderSpinner() : nothing}\n ${hasCheckableRole ? this._renderCheckedIcon() : nothing}\n <span part=\"prefix\" class=\"menu-item__prefix\">\n <slot name=\"prefix\"></slot>\n </span>\n <span part=\"label\" class=\"menu-item__label\">\n <slot></slot>\n </span>\n <span part=\"suffix\" class=\"menu-item__suffix\">\n <slot name=\"suffix\"></slot>\n </span>\n ${this._hasSubmenu ? this._renderSubmenuIcon() : nothing}\n <slot name=\"submenu\" @slotchange=${this._handleSubmenuSlotChange}></slot>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-menu-item': HelixMenuItem;\n }\n}\n","import { css } from 'lit';\n\nexport const helixMenuDividerStyles = css`\n :host {\n display: block;\n }\n\n .menu-divider {\n height: var(--hx-border-width-thin, 1px);\n background-color: var(--hx-menu-divider-color, var(--hx-color-border-default, #d6dbd5));\n margin: var(--hx-space-1, 0.25rem) calc(-1 * var(--hx-space-1, 0.25rem));\n }\n\n /* ─── High Contrast Mode (forced-colors) ─── */\n\n @media (forced-colors: active) {\n .menu-divider {\n background-color: GrayText;\n }\n }\n`;\n","import { html } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement } from 'lit/decorators.js';\nimport { HelixElement } from '../../base/index.js';\nimport { forcedColorsInteractive } from '../../styles/forced-colors.js';\nimport { helixMenuDividerStyles } from './hx-menu-divider.styles.js';\n\n/**\n * A visual separator for grouping items within an `hx-menu`.\n *\n * @summary Horizontal divider between menu sections.\n *\n * @tag hx-menu-divider\n *\n * @csspart base - The root separator element.\n *\n * @cssprop [--hx-menu-divider-color=var(--hx-color-neutral-200)] - Divider line color.\n */\n@customElement('hx-menu-divider')\nexport class HelixMenuDivider extends HelixElement {\n static override styles = [helixMenuDividerStyles, forcedColorsInteractive];\n\n override render() {\n return html`<div\n part=\"base\"\n class=\"menu-divider\"\n role=\"separator\"\n aria-orientation=\"horizontal\"\n ></div>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-menu-divider': HelixMenuDivider;\n }\n}\n"],"names":["helixMenuStyles","css","HelixMenu","HelixElement","item","items","activeIndex","i","first","last","index","target","active","_a","idx","char","match","slot","validTags","invalid","el","devWarn","detail","html","forcedColorsInteractive","__decorateClass","property","customElement","helixMenuItemStyles","HelixMenuItem","value","open","options","menu","sibling","role","hasCheckableRole","classes","classMap","nothing","state","query","helixMenuDividerStyles","HelixMenuDivider"],"mappings":"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"}
|
|
@@ -20,8 +20,7 @@ const S = x`
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
.meter:focus-visible {
|
|
23
|
-
outline: var(--hx-focus-ring-width, 2px) solid
|
|
24
|
-
var(--hx-focus-ring-color, var(--hx-color-primary-500, #429797));
|
|
23
|
+
outline: var(--hx-focus-ring-width, 2px) solid var(--hx-focus-ring-color, #0f7078);
|
|
25
24
|
outline-offset: var(--hx-focus-ring-offset, 2px);
|
|
26
25
|
}
|
|
27
26
|
|
|
@@ -252,4 +251,4 @@ a = n([
|
|
|
252
251
|
export {
|
|
253
252
|
a as H
|
|
254
253
|
};
|
|
255
|
-
//# sourceMappingURL=hx-meter-
|
|
254
|
+
//# sourceMappingURL=hx-meter-BPscsw5t.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hx-meter-BPscsw5t.js","sources":["../../src/components/hx-meter/hx-meter.styles.ts","../../src/components/hx-meter/hx-meter.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const helixMeterStyles = css`\n :host {\n display: block;\n width: 100%;\n }\n\n .meter {\n display: flex;\n flex-direction: column;\n gap: var(--hx-space-2, 0.5rem);\n width: 100%;\n outline: none;\n border-radius: var(--hx-border-radius-md, 0.375rem);\n }\n\n .meter:focus-visible {\n outline: var(--hx-focus-ring-width, 2px) solid var(--hx-focus-ring-color, #0f7078);\n outline-offset: var(--hx-focus-ring-offset, 2px);\n }\n\n .meter__label {\n font-size: var(--hx-font-size-sm, 0.875rem);\n font-weight: var(--hx-font-weight-medium, 500);\n color: var(--hx-meter-label-color, var(--hx-color-neutral-700, #313e4b));\n line-height: var(--hx-line-height-normal, 1.5);\n }\n\n .meter__track {\n position: relative;\n width: 100%;\n height: var(--hx-meter-track-height, var(--hx-space-2, 0.5rem));\n background-color: var(--hx-meter-track-color, var(--hx-color-neutral-200, #d6dbd5));\n border-radius: var(--hx-meter-track-radius, var(--hx-border-radius-full, 9999px));\n overflow: hidden;\n }\n\n .meter__indicator {\n position: absolute;\n inset-block: 0;\n inset-inline-start: 0;\n height: 100%;\n width: 100%;\n border-radius: inherit;\n background-color: var(--_indicator-color);\n transform-origin: left center;\n transform: scaleX(var(--_value-ratio, 0));\n transition:\n transform var(--hx-transition-fast, 150ms ease),\n background-color var(--hx-transition-fast, 150ms ease);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .meter__indicator {\n transition: none;\n }\n }\n\n /* ─── Default (no thresholds configured) ─── */\n\n :host {\n --_indicator-color: var(--hx-meter-indicator-color, var(--hx-color-primary-500, #429797));\n }\n\n /* ─── Semantic state colors ─── */\n\n :host([data-state='optimum']) {\n --_indicator-color: var(--hx-meter-color-optimum, var(--hx-color-success-500, #3b9e58));\n }\n\n :host([data-state='warning']) {\n --_indicator-color: var(--hx-meter-color-warning, var(--hx-color-warning-500, #c2711c));\n }\n\n :host([data-state='danger']) {\n --_indicator-color: var(--hx-meter-color-danger, var(--hx-color-error-500, #e5493e));\n }\n\n /* ─── State Label (WCAG 1.4.1) ─── */\n /* Visible text label rendered below the track when a semantic state is active. */\n /* Ensures the meter state is not conveyed by fill color alone. */\n /* aria-hidden=\"true\" because aria-valuetext already includes the state for AT. */\n\n .meter__state-label {\n font-size: var(--hx-font-size-xs, 0.75rem);\n font-weight: var(--hx-font-weight-medium, 500);\n line-height: var(--hx-line-height-tight, 1.25);\n font-family: var(--hx-meter-font-family, var(--hx-font-family-sans, sans-serif));\n }\n\n .meter__state-label[data-state='optimum'] {\n color: var(--hx-meter-color-optimum, var(--hx-color-success-700, #146831));\n }\n\n .meter__state-label[data-state='warning'] {\n color: var(--hx-meter-color-warning, var(--hx-color-warning-700, #804605));\n }\n\n .meter__state-label[data-state='danger'] {\n color: var(--hx-meter-color-danger, var(--hx-color-error-700, #a21312));\n }\n\n /* ─── Native meter hidden (we use custom rendering) ─── */\n\n .meter__native {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n }\n\n /* ─── Forced Colors (Windows High Contrast) ─── */\n\n @media (forced-colors: active) {\n .meter__track {\n border: 1px solid CanvasText;\n }\n\n .meter__indicator {\n forced-color-adjust: none;\n background-color: Highlight;\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 { ifDefined } from 'lit/directives/if-defined.js';\nimport { HelixElement, createIdCounter } from '../../base/index.js';\nimport { helixMeterStyles } from './hx-meter.styles.js';\nimport { forcedColorsSurface } from '../../styles/forced-colors.js';\n\ntype MeterState = 'optimum' | 'warning' | 'danger' | 'default';\n\nconst _nextMeterId = createIdCounter('hx-meter');\n\n/**\n * A scalar measurement within a known range — e.g., disk usage, health score,\n * or any numeric value with defined min/max bounds. Supports low/high/optimum\n * threshold markers for semantic color feedback.\n *\n * @summary Scalar measurement gauge within a defined range.\n *\n * @tag hx-meter\n *\n * @slot label - Visible label rendered above the meter track. When using this\n * slot without the `label` attribute, the accessible name is derived from the\n * slot content via `aria-labelledby`. The `label` attribute is NOT required\n * when slot content is provided — the component detects slot content and\n * switches to `aria-labelledby` automatically.\n *\n * @csspart base - The outer wrapper element.\n * @csspart track - The unfilled track bar element.\n * @csspart indicator - The filled bar indicating the current value.\n * @csspart label - The label wrapper element.\n *\n * @cssprop [--hx-meter-track-height] - Height of the track bar.\n * @cssprop [--hx-meter-track-color] - Background color of the unfilled track.\n * @cssprop [--hx-meter-track-radius] - Border radius of the track.\n * @cssprop [--hx-meter-indicator-color] - Default filled bar color (no thresholds).\n * @cssprop [--hx-meter-color-optimum] - Color when value is in the optimum zone.\n * @cssprop [--hx-meter-color-warning] - Color when value is in a warning zone.\n * @cssprop [--hx-meter-color-danger] - Color when value is in the danger zone.\n * @cssprop [--hx-meter-label-color] - Label text color.\n * @cssprop [--hx-space-2] - Spacing token.\n * @cssprop [--hx-border-radius-md] - CSS custom property.\n * @cssprop [--hx-focus-ring-width] - Width.\n * @cssprop [--hx-focus-ring-color] - Color.\n * @cssprop [--hx-focus-ring-offset] - CSS custom property.\n * @cssprop [--hx-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-neutral-200] - Color.\n * @cssprop [--hx-border-radius-full] - CSS custom property.\n * @cssprop [--hx-transition-fast] - Transition timing.\n * @cssprop [--hx-color-primary-500] - Color.\n * @cssprop [--hx-color-success-500] - Color.\n * @cssprop [--hx-color-warning-500] - Color.\n * @cssprop [--hx-color-error-500] - Color.\n * @cssprop [--hx-font-size-xs] - Font size.\n * @cssprop [--hx-line-height-tight] - Line height.\n * @cssprop [--hx-meter-font-family=var(--hx-font-family-sans)] - CSS custom property.\n * @cssprop [--hx-font-family-sans] - Font family.\n * @cssprop [--hx-color-success-700] - Color.\n * @cssprop [--hx-color-warning-700] - Color.\n * @cssprop [--hx-color-error-700] - Color.\n */\n@customElement('hx-meter')\nexport class HelixMeter extends HelixElement {\n static override styles = [helixMeterStyles, forcedColorsSurface];\n\n /** @internal */\n private _uid = _nextMeterId();\n\n /**\n * Current value of the meter.\n * @attr value\n */\n @property({ type: Number, reflect: true })\n value = 0;\n\n /**\n * Minimum value of the range.\n * @attr min\n */\n @property({ type: Number, reflect: true })\n min = 0;\n\n /**\n * Maximum value of the range.\n * @attr max\n */\n @property({ type: Number, reflect: true })\n max = 100;\n\n /**\n * Threshold below which the value is considered suboptimal (lower range warning).\n * @attr low\n */\n @property({ type: Number, reflect: true })\n low?: number;\n\n /**\n * Threshold above which the value is considered suboptimal (upper range warning).\n * @attr high\n */\n @property({ type: Number, reflect: true })\n high?: number;\n\n /**\n * The optimal value within the range. Used to determine which zone is \"good\".\n * @attr optimum\n */\n @property({ type: Number, reflect: true })\n optimum?: number;\n\n /**\n * Accessible label for the meter. Used as the visible label text and as\n * the source for `aria-labelledby`. When only slot content is provided\n * (no `label` attribute), the slot content is used for the accessible name.\n * @attr label\n */\n @property({ type: String })\n label?: string;\n\n /** @internal */\n @state()\n private _hasSlotContent = false;\n\n /** @internal */\n private _clampedValue(): number {\n return Math.min(Math.max(this.value, this.min), this.max);\n }\n\n /** @internal */\n private _percentage(): number {\n const range = this.max - this.min;\n if (range === 0) return 0;\n return ((this._clampedValue() - this.min) / range) * 100;\n }\n\n /** @internal */\n private _resolveState(): MeterState {\n const v = this._clampedValue();\n const hasLow = this.low !== undefined;\n const hasHigh = this.high !== undefined;\n const hasOptimum = this.optimum !== undefined;\n\n if (!hasLow && !hasHigh && !hasOptimum) return 'default';\n\n // When hasLow/hasHigh/hasOptimum are true, the corresponding property is defined.\n // Use nullish coalescing to satisfy the type checker while preserving the runtime logic.\n const lowVal = this.low ?? 0;\n const highVal = this.high ?? this.max;\n const inLowZone = hasLow && v < lowVal;\n const inHighZone = hasHigh && v > highVal;\n const inMiddleZone = !inLowZone && !inHighZone;\n\n if (!hasOptimum) {\n if (inLowZone || inHighZone) return 'warning';\n return 'optimum';\n }\n\n const opt = this.optimum ?? this.min;\n const optimumInLow = hasLow && opt < lowVal;\n const optimumInHigh = hasHigh && opt > highVal;\n const optimumInMiddle = !optimumInLow && !optimumInHigh;\n\n if (optimumInMiddle) {\n if (inMiddleZone) return 'optimum';\n return 'warning';\n } else if (optimumInLow) {\n if (inLowZone) return 'optimum';\n if (inMiddleZone) return 'warning';\n return 'danger';\n } else {\n // optimumInHigh\n if (inHighZone) return 'optimum';\n if (inMiddleZone) return 'warning';\n return 'danger';\n }\n }\n\n /** @internal */\n private _onLabelSlotChange(e: Event) {\n const slot = e.target as HTMLSlotElement;\n this._hasSlotContent = slot.assignedNodes({ flatten: true }).length > 0;\n }\n\n override updated(changedProperties: PropertyValues<this>): void {\n super.updated(changedProperties);\n // Set data-state on host so :host([data-state]) CSS selectors work\n this.dataset['state'] = this._resolveState();\n // Set --_value-ratio for GPU-compositable scaleX() transform on indicator\n const ratio = this._percentage() / 100;\n this.style.setProperty('--_value-ratio', String(Math.max(0, Math.min(1, ratio))));\n }\n\n // ─── WCAG 1.4.1: State label map ───\n // The indicator bar color alone is insufficient for color-blind users.\n // A visible state label is rendered below the track when a semantic state\n // (optimum/warning/danger) is active, providing a non-color visual cue.\n // aria-valuetext already embeds the state for AT users.\n\n /** @internal */\n private static readonly _STATE_LABELS: Partial<Record<MeterState, string>> = {\n optimum: 'Optimum',\n warning: 'Warning',\n danger: 'Danger',\n };\n\n override render() {\n const state = this._resolveState();\n const clampedValue = this._clampedValue();\n const stateLabel = state !== 'default' ? ` — ${state}` : '';\n const ariaValuetext = `${clampedValue} of ${this.max}${stateLabel}`;\n const hasVisibleLabel = this.label !== undefined || this._hasSlotContent;\n const visibleStateLabel = HelixMeter._STATE_LABELS[state];\n\n return html`\n <div\n part=\"base\"\n class=\"meter\"\n role=\"meter\"\n tabindex=\"0\"\n aria-valuenow=${clampedValue}\n aria-valuemin=${this.min}\n aria-valuemax=${this.max}\n aria-valuetext=${ariaValuetext}\n aria-label=${ifDefined(!hasVisibleLabel ? `${clampedValue} of ${this.max}` : undefined)}\n aria-labelledby=${ifDefined(hasVisibleLabel ? `${this._uid}-label` : undefined)}\n >\n <span\n id=${`${this._uid}-label`}\n part=\"label\"\n class=\"meter__label\"\n ?hidden=${!hasVisibleLabel}\n >\n <slot name=\"label\" @slotchange=${this._onLabelSlotChange}>${this.label ?? ''}</slot>\n </span>\n <div class=\"meter__track\" part=\"track\">\n <div part=\"indicator\" class=\"meter__indicator\"></div>\n </div>\n ${visibleStateLabel\n ? html`<span class=\"meter__state-label\" data-state=${state} aria-hidden=\"true\"\n >${visibleStateLabel}</span\n >`\n : nothing}\n <meter\n class=\"meter__native\"\n value=${clampedValue}\n min=${this.min}\n max=${this.max}\n low=${ifDefined(this.low)}\n high=${ifDefined(this.high)}\n optimum=${ifDefined(this.optimum)}\n aria-hidden=\"true\"\n tabindex=\"-1\"\n ></meter>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-meter': HelixMeter;\n }\n}\n"],"names":["helixMeterStyles","css","_nextMeterId","createIdCounter","HelixMeter","HelixElement","range","v","hasLow","hasHigh","hasOptimum","lowVal","highVal","inLowZone","inHighZone","inMiddleZone","opt","optimumInLow","optimumInHigh","slot","changedProperties","ratio","state","clampedValue","stateLabel","ariaValuetext","hasVisibleLabel","visibleStateLabel","html","ifDefined","nothing","forcedColorsSurface","__decorateClass","property","customElement"],"mappings":"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"}
|