@helixui/library 3.6.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/custom-elements.json +1561 -1356
  2. package/dist/components/hx-alert/hx-alert.d.ts +18 -0
  3. package/dist/components/hx-alert/hx-alert.d.ts.map +1 -1
  4. package/dist/components/hx-badge/index.js +1 -1
  5. package/dist/components/hx-button/hx-button.d.ts +18 -0
  6. package/dist/components/hx-button/hx-button.d.ts.map +1 -1
  7. package/dist/components/hx-button/hx-button.styles.d.ts.map +1 -1
  8. package/dist/components/hx-button/index.js +1 -1
  9. package/dist/components/hx-checkbox/hx-checkbox.d.ts +18 -0
  10. package/dist/components/hx-checkbox/hx-checkbox.d.ts.map +1 -1
  11. package/dist/components/hx-checkbox/hx-checkbox.styles.d.ts.map +1 -1
  12. package/dist/components/hx-checkbox/index.js +1 -1
  13. package/dist/components/hx-date-picker/hx-date-picker.styles.d.ts.map +1 -1
  14. package/dist/components/hx-date-picker/index.js +1 -1
  15. package/dist/components/hx-dialog/hx-dialog.d.ts +18 -0
  16. package/dist/components/hx-dialog/hx-dialog.d.ts.map +1 -1
  17. package/dist/components/hx-icon-button/hx-icon-button.styles.d.ts.map +1 -1
  18. package/dist/components/hx-icon-button/index.js +1 -1
  19. package/dist/components/hx-pagination/hx-pagination.styles.d.ts.map +1 -1
  20. package/dist/components/hx-pagination/index.js +1 -1
  21. package/dist/components/hx-radio-group/index.js +1 -1
  22. package/dist/components/hx-steps/index.js +1 -1
  23. package/dist/components/hx-text-input/hx-text-input.d.ts +18 -0
  24. package/dist/components/hx-text-input/hx-text-input.d.ts.map +1 -1
  25. package/dist/components/hx-toggle-button/hx-toggle-button.styles.d.ts.map +1 -1
  26. package/dist/components/hx-toggle-button/index.js +1 -1
  27. package/dist/css/helix-all.css +59 -34
  28. package/dist/css/helix-core.css +20 -14
  29. package/dist/css/helix-forms.css +33 -17
  30. package/dist/css/helix-navigation.css +6 -3
  31. package/dist/css/helix-tokens.css +5 -4
  32. package/dist/css/hx-badge.css +5 -5
  33. package/dist/css/hx-button.css +11 -4
  34. package/dist/css/hx-checkbox.css +10 -4
  35. package/dist/css/hx-date-picker.css +5 -2
  36. package/dist/css/hx-icon-button.css +4 -5
  37. package/dist/css/hx-pagination.css +6 -3
  38. package/dist/css/hx-toggle-button.css +18 -11
  39. package/dist/css/index.css +1 -1
  40. package/dist/css/manifest.json +18 -9
  41. package/dist/index.js +9 -9
  42. package/dist/shared/hx-alert-Bto8-TIi.js.map +1 -1
  43. package/dist/shared/{hx-badge-JlFtAdxS.js → hx-badge-DFL35nzi.js} +16 -16
  44. package/dist/shared/hx-badge-DFL35nzi.js.map +1 -0
  45. package/dist/shared/{hx-button-BOwAEcF1.js → hx-button-rRNmD4fd.js} +12 -5
  46. package/dist/shared/hx-button-rRNmD4fd.js.map +1 -0
  47. package/dist/shared/{hx-checkbox-CYd0YV_u.js → hx-checkbox-hPlIw6Lb.js} +25 -19
  48. package/dist/shared/hx-checkbox-hPlIw6Lb.js.map +1 -0
  49. package/dist/shared/{hx-date-picker-B49yo4Vm.js → hx-date-picker-DSKDkCy1.js} +6 -3
  50. package/dist/shared/hx-date-picker-DSKDkCy1.js.map +1 -0
  51. package/dist/shared/hx-dialog-B4weoj_1.js.map +1 -1
  52. package/dist/shared/{hx-icon-button-a6OpeQz5.js → hx-icon-button-B2BdVdyK.js} +10 -11
  53. package/dist/shared/hx-icon-button-B2BdVdyK.js.map +1 -0
  54. package/dist/shared/{hx-pagination-C7y8GVyU.js → hx-pagination-D726PyTM.js} +7 -4
  55. package/dist/shared/hx-pagination-D726PyTM.js.map +1 -0
  56. package/dist/shared/{hx-radio-C7eTj5YI.js → hx-radio-CY4kQfZw.js} +44 -44
  57. package/dist/shared/{hx-radio-C7eTj5YI.js.map → hx-radio-CY4kQfZw.js.map} +1 -1
  58. package/dist/shared/{hx-step-R2rjp1fT.js → hx-step-CUzliIK_.js} +3 -3
  59. package/dist/shared/hx-step-CUzliIK_.js.map +1 -0
  60. package/dist/shared/hx-text-input-D6FlOZM-.js.map +1 -1
  61. package/dist/shared/{hx-toggle-button-DwBers3A.js → hx-toggle-button-DSJeFlb0.js} +52 -45
  62. package/dist/shared/{hx-toggle-button-DwBers3A.js.map → hx-toggle-button-DSJeFlb0.js.map} +1 -1
  63. package/figma-inventory.json +144 -48
  64. package/package.json +2 -2
  65. package/dist/shared/hx-badge-JlFtAdxS.js.map +0 -1
  66. package/dist/shared/hx-button-BOwAEcF1.js.map +0 -1
  67. package/dist/shared/hx-checkbox-CYd0YV_u.js.map +0 -1
  68. package/dist/shared/hx-date-picker-B49yo4Vm.js.map +0 -1
  69. package/dist/shared/hx-icon-button-a6OpeQz5.js.map +0 -1
  70. package/dist/shared/hx-pagination-C7y8GVyU.js.map +0 -1
  71. package/dist/shared/hx-step-R2rjp1fT.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"hx-toggle-button-DwBers3A.js","sources":["../../src/components/hx-toggle-button/hx-toggle-button.styles.ts","../../src/components/hx-toggle-button/hx-toggle-button.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const helixToggleButtonStyles = 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 /* ─── Base Button ─── */\n\n .button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--hx-space-2, 0.5rem);\n border: var(--hx-border-width-thin, 1px) solid var(--hx-toggle-button-border-color, transparent);\n border-radius: var(--hx-toggle-button-border-radius, var(--hx-border-radius-md, 0.375rem));\n background-color: var(--hx-toggle-button-bg, var(--hx-color-primary-500, #429797));\n color: var(--hx-toggle-button-color, var(--hx-color-text-on-primary, #ffffff));\n font-family: var(--hx-toggle-button-font-family, var(--hx-font-family-sans, sans-serif));\n font-weight: var(--hx-toggle-button-font-weight, var(--hx-font-weight-semibold, 600));\n line-height: var(--hx-line-height-tight, 1.25);\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 white-space: nowrap;\n user-select: none;\n -webkit-user-select: none;\n }\n\n /*\n * Host-focus path: on the modern (IDL element-references) render branch the\n * host is the tabbable surface (tabindex=0) and the inner <button> is\n * demoted to tabindex=-1. Drive the focus ring from ':host(:focus-visible)'\n * so keyboard users still see a visible affordance. Codex round-11 P1.\n */\n :host(:focus-visible) .button {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-toggle-button-focus-ring-color, var(--hx-focus-ring-color, #0f7078));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n }\n\n /*\n * Fallback (no-IDL-ref) path: the host carries tabindex=-1 and the inner\n * <button> is the tab target. Native :focus-visible drives the ring.\n */\n .button:focus-visible {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-toggle-button-focus-ring-color, var(--hx-focus-ring-color, #0f7078));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n }\n\n .button:hover {\n filter: brightness(var(--hx-filter-brightness-hover, 0.9));\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 44px touch target for sm variant.\n min-height uses --hx-touch-target-min to guarantee the interactive area\n meets the threshold even though the visual size token is smaller. */\n .button--sm {\n padding: var(--hx-space-1, 0.25rem) var(--hx-space-3, 0.75rem);\n font-size: var(--hx-font-size-sm, 0.875rem);\n min-height: var(--hx-touch-target-min, 2.75rem);\n }\n\n .button--md {\n padding: var(--hx-space-2, 0.5rem) var(--hx-space-4, 1rem);\n font-size: var(--hx-font-size-md, 1rem);\n min-height: var(--hx-size-10, 2.5rem);\n }\n\n .button--lg {\n padding: var(--hx-space-3, 0.75rem) var(--hx-space-6, 1.5rem);\n font-size: var(--hx-font-size-lg, 1.125rem);\n min-height: var(--hx-size-12, 3rem);\n }\n\n /* ─── Style Variants ─── */\n\n .button--primary {\n --hx-toggle-button-bg: var(--hx-color-primary-500, #429797);\n --hx-toggle-button-color: var(--hx-color-text-on-primary, #ffffff);\n --hx-toggle-button-border-color: transparent;\n }\n\n /*\n * secondary/ghost paint primary text on the surface (white). primary-500\n * (#429797) on white = 3.43:1 — fails AA at body sizes. shift to\n * primary-600 (#0F7078) = 6.06:1 — AA pass. border keeps primary-500\n * for the brand affordance (3:1 non-text contrast still met).\n */\n .button--secondary {\n --hx-toggle-button-bg: transparent;\n --hx-toggle-button-color: var(--hx-color-primary-600, #0f7078);\n --hx-toggle-button-border-color: var(--hx-color-primary-600, #0f7078);\n }\n\n .button--secondary:hover {\n --hx-toggle-button-bg: var(--hx-color-primary-50, #ebf8f8);\n }\n\n .button--tertiary {\n --hx-toggle-button-bg: var(--hx-color-surface-sunken, #ebeee9);\n --hx-toggle-button-color: var(--hx-color-text-primary, #0d1825);\n --hx-toggle-button-border-color: transparent;\n }\n\n .button--tertiary:hover {\n --hx-toggle-button-bg: var(--hx-color-surface-raised, #f5f8f3);\n }\n\n .button--ghost {\n --hx-toggle-button-bg: transparent;\n --hx-toggle-button-color: var(--hx-color-primary-600, #0f7078);\n --hx-toggle-button-border-color: transparent;\n }\n\n .button--ghost:hover {\n --hx-toggle-button-bg: var(--hx-color-surface-raised, #f5f8f3);\n }\n\n .button--outline {\n --hx-toggle-button-bg: transparent;\n --hx-toggle-button-color: var(--hx-color-text-primary, #0d1825);\n --hx-toggle-button-border-color: var(--hx-color-border-strong, #66787b);\n }\n\n .button--outline:hover {\n --hx-toggle-button-bg: var(--hx-color-surface-raised, #f5f8f3);\n }\n\n /* ─── Pressed State ─── */\n\n /*\n * Primary: already uses solid primary bg; pressed deepens to primary-700\n * to give clear visual feedback without introducing a new color.\n *\n * AA fix: text.on-primary (= neutral-900 #0D1825) on primary-700 (#0F6363)\n * = 2.54:1 — fails AA. Pin fg at neutral-0 for the darker pressed fill\n * (neutral-0 on primary-700 = 7.03:1, AAA pass). Mirrors hx-toast\n * precedent (commit 300e21ab0) and hx-button hover treatment.\n * Note: secondary.pressed below uses primary-500 and stays at 5.20:1 —\n * leave it alone.\n */\n .button--primary.button--pressed {\n --hx-toggle-button-bg: var(--hx-toggle-button-pressed-bg, var(--hx-color-primary-700, #0f6363));\n --hx-toggle-button-color: var(\n --hx-toggle-button-pressed-color,\n var(--hx-color-neutral-0, #ffffff)\n );\n --hx-toggle-button-border-color: transparent;\n }\n\n /*\n * Secondary: unpressed is outlined/transparent; pressed fills with primary bg\n * so the state change is immediately legible.\n */\n .button--secondary.button--pressed {\n --hx-toggle-button-bg: var(--hx-toggle-button-pressed-bg, var(--hx-color-primary-500, #429797));\n --hx-toggle-button-color: var(\n --hx-toggle-button-pressed-color,\n var(--hx-color-text-on-primary, #ffffff)\n );\n --hx-toggle-button-border-color: var(--hx-color-primary-500, #429797);\n }\n\n /* Tertiary pressed: use primary-100 bg + primary-700 text + border for WCAG 3:1 non-text contrast. */\n .button--tertiary.button--pressed {\n --hx-toggle-button-bg: var(--hx-toggle-button-pressed-bg, var(--hx-color-primary-100, #dbf0f0));\n --hx-toggle-button-color: var(\n --hx-toggle-button-pressed-color,\n var(--hx-color-primary-700, #0f6363)\n );\n --hx-toggle-button-border-color: var(--hx-color-primary-600, #0f7078);\n box-shadow: inset 0 0 0 1px var(--hx-color-primary-600, #0f7078);\n }\n\n /* Ghost pressed: subtle neutral fill, matching hover behavior as a baseline. */\n .button--ghost.button--pressed {\n --hx-toggle-button-bg: var(--hx-toggle-button-pressed-bg, var(--hx-color-primary-100, #dbf0f0));\n --hx-toggle-button-color: var(\n --hx-toggle-button-pressed-color,\n var(--hx-color-primary-700, #0f6363)\n );\n --hx-toggle-button-border-color: transparent;\n }\n\n /* Outline pressed: fills with a neutral tint, darkens the border, and adds an inset shadow for WCAG 3:1 non-text contrast. */\n .button--outline.button--pressed {\n --hx-toggle-button-bg: var(\n --hx-toggle-button-pressed-bg,\n var(--hx-color-surface-sunken, #ebeee9)\n );\n --hx-toggle-button-color: var(\n --hx-toggle-button-pressed-color,\n var(--hx-color-text-primary, #0d1825)\n );\n --hx-toggle-button-border-color: var(--hx-color-text-muted, #4a5362);\n box-shadow: inset 0 0 0 1px var(--hx-color-neutral-500, #66787b);\n }\n\n /* ─── Disabled ─── */\n\n .button[disabled] {\n cursor: not-allowed;\n }\n\n /* ─── Prefix / Suffix / Label ─── */\n\n .button__prefix,\n .button__suffix {\n display: inline-flex;\n align-items: center;\n flex-shrink: 0;\n }\n\n .button__label {\n flex: 1 1 auto;\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 :host(:focus-visible) .button,\n .button:focus-visible {\n outline: 3px solid Highlight;\n outline-offset: 2px;\n }\n\n .button--pressed {\n background-color: Highlight;\n color: HighlightText;\n border-color: Highlight;\n box-shadow: none;\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, type PropertyValues } 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 { ifDefined } from 'lit/directives/if-defined.js';\nimport { HelixElement } from '../../base/index.js';\nimport { helixToggleButtonStyles } from './hx-toggle-button.styles.js';\nimport { forcedColorsInteractive } from '../../styles/forced-colors.js';\nimport {\n installAriaIdrefMirror,\n resolveIdrefTokens,\n supportsIdrefElementReferences,\n type AriaIdrefMirrorHandle,\n} from '../../utils/aria-idref.js';\n\n/** Detail for the hx-toggle event dispatched by hx-toggle-button. */\nexport interface HxToggleDetail {\n pressed: boolean;\n}\n\n/**\n * A two-state toggle button that communicates a pressed/unpressed status to\n * assistive technology via `aria-pressed`. Supports multiple visual variants\n * and sizes, prefix/suffix slots, full ElementInternals form association, and\n * a distinct pressed visual state for every variant.\n *\n * @summary Two-state toggle button with pressed/unpressed ARIA semantics.\n *\n * @tag hx-toggle-button\n *\n * @slot - Default slot for the button label text or content.\n * @slot prefix - Icon or content rendered before the label.\n * @slot suffix - Icon or content rendered after the label.\n *\n * @fires {CustomEvent<{pressed: boolean}>} hx-toggle - Dispatched when the\n * toggle state changes. Not dispatched when the button is disabled.\n *\n * @csspart button - The native `<button>` element.\n * @csspart label - The label text wrapper span.\n * @csspart prefix - The prefix slot container span.\n * @csspart suffix - The suffix slot container span.\n *\n * @cssprop [--hx-toggle-button-bg=var(--hx-color-primary-500)] - Button background color.\n * @cssprop [--hx-toggle-button-color=var(--hx-color-neutral-0)] - Button text color.\n * @cssprop [--hx-toggle-button-border-color=transparent] - Button border color.\n * @cssprop [--hx-toggle-button-border-radius=var(--hx-border-radius-md)] - Button border radius.\n * @cssprop [--hx-toggle-button-font-family=var(--hx-font-family-sans)] - Button font family.\n * @cssprop [--hx-toggle-button-font-weight=var(--hx-font-weight-semibold)] - Button font weight.\n * @cssprop [--hx-toggle-button-focus-ring-color=var(--hx-focus-ring-color)] - Focus ring color.\n * @cssprop [--hx-toggle-button-pressed-bg=var(--hx-color-primary-500)] - Background when pressed (variant-specific fallback applies).\n * @cssprop [--hx-toggle-button-pressed-color=var(--hx-color-neutral-0)] - Text color when pressed (variant-specific fallback applies).\n * @cssprop [--hx-opacity-disabled] - Opacity.\n * @cssprop [--hx-space-2] - Spacing token.\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-color-neutral-0] - Color.\n * @cssprop [--hx-font-family-sans] - Font family.\n * @cssprop [--hx-font-weight-semibold] - Font weight.\n * @cssprop [--hx-line-height-tight] - Line height.\n * @cssprop [--hx-transition-fast] - Transition timing.\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-filter-brightness-hover] - CSS filter.\n * @cssprop [--hx-filter-brightness-active] - CSS filter.\n * @cssprop [--hx-touch-target-min] - Minimum touch target size.\n * @cssprop [--hx-space-1] - Spacing token.\n * @cssprop [--hx-space-3] - Spacing token.\n * @cssprop [--hx-font-size-sm] - Font size.\n * @cssprop [--hx-space-4] - Spacing token.\n * @cssprop [--hx-font-size-md] - Font size.\n * @cssprop [--hx-size-10] - Size token.\n * @cssprop [--hx-space-6] - Spacing token.\n * @cssprop [--hx-font-size-lg] - Font size.\n * @cssprop [--hx-size-12] - Size token.\n * @cssprop [--hx-color-primary-50] - Color.\n * @cssprop [--hx-color-neutral-100] - Color.\n * @cssprop [--hx-color-neutral-900] - Color.\n * @cssprop [--hx-color-neutral-200] - Color.\n * @cssprop [--hx-color-neutral-300] - Color.\n * @cssprop [--hx-color-primary-700] - Color.\n * @cssprop [--hx-color-primary-100] - Color.\n * @cssprop [--hx-color-neutral-50] - Color.\n * @cssprop [--hx-color-neutral-500] - Color.\n */\n@customElement('hx-toggle-button')\nexport class HelixToggleButton extends HelixElement {\n static override styles = [helixToggleButtonStyles, forcedColorsInteractive];\n\n // ─── Form Association ───\n\n /** @internal */\n static override formAssociated = true;\n\n /** @internal */\n @query('slot:not([name])') private _defaultSlot!: HTMLSlotElement | null;\n\n // ─── Public Properties ───\n\n /**\n * Whether the toggle button is in the pressed state.\n * Reflected as an attribute so CSS selectors like `:host([pressed])` work.\n * @attr pressed\n */\n @property({ type: Boolean, reflect: true })\n pressed = false;\n\n /**\n * Visual style variant of the button.\n * @attr variant\n */\n @property({ type: String, reflect: true })\n variant: 'primary' | 'secondary' | 'tertiary' | 'ghost' | 'outline' = 'secondary';\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 * Whether the button is disabled. Prevents all interaction and form actions.\n * @attr disabled\n */\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n /**\n * Form field name submitted via ElementInternals when the button is pressed.\n * @attr name\n */\n @property({ type: String })\n name: string | undefined = undefined;\n\n /**\n * Form field value submitted via ElementInternals when the button is pressed.\n * @attr value\n */\n @property({ type: String })\n value: string | undefined = undefined;\n\n /**\n * Accessible label forwarded to the inner `<button>` as `aria-label`.\n * Required for icon-only toggle buttons where no visible text is present.\n * @attr label\n */\n @property({ type: String })\n label: string | undefined = undefined;\n\n /**\n * When true, the button must be in the pressed state for the form to be submitted.\n * @attr required\n */\n @property({ type: Boolean, reflect: true })\n required = false;\n\n // ─── Form API ───\n\n /** Returns the ValidityState object. */\n override get validity(): ValidityState {\n return this._internals.validity;\n }\n\n /** Returns the current validation message. */\n override get validationMessage(): string {\n return this._internals.validationMessage;\n }\n\n /** Checks whether the button satisfies its constraints. */\n checkValidity(): boolean {\n return this._internals.checkValidity();\n }\n\n /** Reports validity and shows the browser's constraint validation UI. */\n reportValidity(): boolean {\n return this._internals.reportValidity();\n }\n\n /**\n * Handle for the shared IDREF observer. See `installAriaIdrefMirror()`.\n * @internal\n */\n private _ariaMirror: AriaIdrefMirrorHandle | null = null;\n\n /**\n * Default-slot text content captured for use as the host's accessible name\n * when no explicit `label`/`aria-label`/`aria-labelledby` is supplied.\n * Codex round-1 finding #9.\n * @internal\n */\n @state() private _slotLabelText: string = '';\n\n /** No-IDL-ref fallback tokens applied to the inner button. @internal */\n @state() private _fallbackAriaLabelledBy: string | null = null;\n /** @internal */\n @state() private _fallbackAriaDescribedBy: string | null = null;\n /** @internal */\n @state() private _fallbackAriaLabel: string | null = null;\n\n /**\n * Whether the platform supports IDL element references on `ElementInternals`.\n * Drives the render-time branch between the modern path (host is the\n * announced surface, inner button is `aria-hidden + tabindex=-1`) and the\n * fallback path (inner button is the announced surface, host is demoted).\n * Codex round-2 finding #2.\n * @internal\n */\n @state() private _supportsIdrefRefs = true;\n\n /**\n * Tracks whether the host's `tabindex` is managed by the component itself\n * (vs. set explicitly by a consumer). Codex round-14 P2: a consumer-supplied\n * `tabindex` (e.g. roving-tabindex toolbar pattern with `tabindex=\"-1\"`)\n * must survive disabled flips and re-renders. Only re-assert tabindex in\n * `updated()` when the component originally claimed it.\n * @internal\n */\n private _internalTabindexManaged = false;\n\n // ─── Lifecycle ───\n\n override connectedCallback(): void {\n super.connectedCallback();\n // Detect platform support for IDL element references. Codex round-2\n // finding #2: drives the render-time branch between modern (host\n // announced) and fallback (inner button announced) paths.\n this._supportsIdrefRefs = supportsIdrefElementReferences(this._internals);\n // Seed root-independent semantics from the moment of connection so AT\n // sees the toggle role + pressed state before the first paint.\n this._syncHostAriaSemantics();\n // Codex round-1 finding #1: host is the canonical announced surface on\n // modern browsers, so the inner `<button>` is demoted via\n // `aria-hidden + tabindex=-1`.\n // Codex round-2 finding #2: on no-IDL-ref browsers the inner button is\n // the announced surface (it carries native button + aria-pressed), so\n // the host is demoted to `tabindex=-1` and lets the inner button own\n // tab order + activation.\n // Codex round-14 P2: only claim ownership of `tabindex` when no consumer\n // value is present. Consumers using roving-tabindex toolbar patterns\n // must be able to set `tabindex=\"-1\"` on the host without it being\n // clobbered on every disabled flip. Note we still claim ownership when\n // disabled — the initial value is `-1` to keep the host out of tab order\n // and `updated()` re-asserts the appropriate value when disabled flips.\n if (!this.hasAttribute('tabindex')) {\n this._internalTabindexManaged = true;\n const enabledTabIndex = this._supportsIdrefRefs ? '0' : '-1';\n this.setAttribute('tabindex', this.disabled ? '-1' : enabledTabIndex);\n }\n this.addEventListener('keydown', this._handleHostKeyDown);\n this.addEventListener('click', this._handleHostClickRouted);\n this._ariaMirror = installAriaIdrefMirror(this, () => {\n this._syncHostAriaSemantics();\n });\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener('keydown', this._handleHostKeyDown);\n this.removeEventListener('click', this._handleHostClickRouted);\n // Codex round-13 P2: tear down the assigned-node text observer so a\n // detached host does not receive mutation callbacks from nodes that may\n // outlive it (e.g. lifted out of the slot but kept in the document).\n this._slotTextObserver?.disconnect();\n this._slotTextObserver = null;\n this._ariaMirror?.disconnect();\n this._ariaMirror = null;\n }\n\n /**\n * Host-level keydown handler. Active only on the modern path; on the\n * no-IDL-ref fallback the inner `<button>` owns native Space/Enter\n * activation. Codex round-2 finding #2.\n * @internal\n */\n private _handleHostKeyDown = (e: KeyboardEvent): void => {\n if (this.disabled) return;\n if (!this._supportsIdrefRefs) return;\n if (e.target !== this) return;\n if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n this._invokeToggle();\n }\n };\n\n /**\n * Host-level click router. The inner `<button>` is `aria-hidden + tabindex=-1`\n * on modern browsers but still receives clicks via mouse activation; toggling\n * is handled by the inner `_handleClick`. Host-level clicks (composed path\n * origin === host) only come from AT activation or programmatic triggers,\n * so we route those to the toggle pipeline.\n *\n * Codex round-2 finding #2: on no-IDL-ref browsers the inner button is the\n * announced surface so this host-level routing is a no-op — AT activation\n * lands on the inner button directly.\n * @internal\n */\n private _handleHostClickRouted = (e: MouseEvent): void => {\n if (this.disabled) return;\n if (!this._supportsIdrefRefs) return;\n // Only fire when the host itself originated the click — inner-button\n // clicks bubble through here too but are handled by `_handleClick`.\n const path = e.composedPath();\n if (path[0] !== this) return;\n this._invokeToggle();\n };\n\n /** @internal */\n private _invokeToggle(): void {\n this.pressed = !this.pressed;\n this._syncFormValue();\n this.dispatchEvent(\n new CustomEvent<{ pressed: boolean }>('hx-toggle', {\n bubbles: true,\n composed: true,\n detail: { pressed: this.pressed },\n }),\n );\n }\n\n /**\n * Moves focus to the announced toggle-button surface. Codex round-1 finding\n * `#1` made the host the canonical focus target on modern engines so AT\n * announces a single widget. Round-7 finding `#9` extends that contract to\n * the no-IDL-ref fallback: when the host is demoted (`tabindex=-1`,\n * role/state cleared on `internals`) the inner `<button>` owns the announced\n * semantics and tab order, so programmatic `focus()` must redirect there —\n * otherwise scripted focus and error recovery land on the demoted host on\n * unsupported engines.\n */\n override focus(options?: FocusOptions): void {\n if (this._supportsIdrefRefs) {\n super.focus(options);\n return;\n }\n this.shadowRoot?.querySelector<HTMLButtonElement>('[part=\"button\"]')?.focus(options);\n }\n\n override firstUpdated(changedProperties: PropertyValues<this>): void {\n super.firstUpdated(changedProperties);\n\n // Track default-slot text content so it can serve as the accessible\n // name when no explicit label is set. Codex round-1 finding #9.\n this._captureSlotLabelText();\n // Codex round-13 P2: also observe in-place text mutations on assigned\n // nodes so framework-driven `Mute` → `Unmute` rewrites refresh the\n // cached label without requiring the consumer to swap the node.\n this._installSlotTextObserver();\n\n if (!this.label) {\n const slot = this._defaultSlot;\n const hasSlotText = slot\n ? slot.assignedNodes({ flatten: true }).some((n) => n.textContent?.trim())\n : false;\n if (!hasSlotText) {\n console.warn(\n '[hx-toggle-button] No accessible label found. Set the `label` attribute or provide slot text content for WCAG 4.1.2 compliance.',\n );\n }\n }\n }\n\n override updated(changedProperties: PropertyValues<this>): void {\n super.updated(changedProperties);\n\n if (\n changedProperties.has('pressed') ||\n changedProperties.has('value') ||\n changedProperties.has('required')\n ) {\n this._syncFormValue();\n }\n\n if (\n changedProperties.has('disabled') ||\n (changedProperties as Map<PropertyKey, unknown>).has('_supportsIdrefRefs')\n ) {\n // Codex round-2 finding #2: align host tabindex with the chosen\n // announced surface. On no-IDL-ref browsers the inner button owns\n // tab order, so re-enabling the host should leave it `tabindex=-1`.\n // Codex round-14 P2: only re-assert when the component owns tabindex.\n // Consumer-managed values (e.g. roving-tabindex toolbar with `-1`) must\n // not be overwritten on disabled flips or supports-flag transitions.\n if (this._internalTabindexManaged) {\n const enabledTabIndex = this._supportsIdrefRefs ? '0' : '-1';\n this.setAttribute('tabindex', this.disabled ? '-1' : enabledTabIndex);\n }\n }\n\n // Host-elevated ARIA semantics — see _syncHostAriaSemantics.\n this._syncHostAriaSemantics();\n }\n\n /**\n * Reads the default slot's flattened text content into `_slotLabelText`.\n * Called from `firstUpdated()` and `slotchange` so host-canonical semantics\n * pick up slotted label text. Codex round-1 finding #9.\n * @internal\n */\n private _captureSlotLabelText(): void {\n const slot = this._defaultSlot;\n if (!slot) {\n this._slotLabelText = '';\n return;\n }\n const text = slot\n .assignedNodes({ flatten: true })\n .map((n) => n.textContent ?? '')\n .join(' ')\n .trim();\n this._slotLabelText = text;\n }\n\n /**\n * Watches assigned default-slot nodes for in-place text mutations so the\n * cached `_slotLabelText` (and thus `internals.ariaLabel`) stays in sync\n * when a framework rewrites textContent of an already-assigned node without\n * replacing it. `slotchange` does NOT fire for those mutations, so a\n * separate observer is required. Codex round-13 P2.\n * @internal\n */\n private _slotTextObserver: MutationObserver | null = null;\n\n /**\n * (Re-)installs the mutation observer over the current set of assigned\n * default-slot nodes. Disconnects any prior observer first so detached\n * nodes stop firing into a torn-down host.\n * @internal\n */\n private _installSlotTextObserver(): void {\n this._slotTextObserver?.disconnect();\n const slot = this._defaultSlot;\n if (!slot) {\n this._slotTextObserver = null;\n return;\n }\n const observer = new MutationObserver(() => {\n this._captureSlotLabelText();\n this._syncHostAriaSemantics();\n });\n slot.assignedNodes({ flatten: true }).forEach((node) => {\n observer.observe(node, {\n characterData: true,\n childList: true,\n subtree: true,\n });\n });\n this._slotTextObserver = observer;\n }\n\n /** @internal */\n private _handleDefaultSlotChange(): void {\n this._captureSlotLabelText();\n this._syncHostAriaSemantics();\n // Re-tune the in-place text observer over the new assigned-node set\n // (codex round-13 P2).\n this._installSlotTextObserver();\n }\n\n /**\n * Mirrors toggle-button semantics onto the host via ElementInternals so that\n * consumer-supplied `aria-label`, `aria-labelledby`, and `aria-describedby`\n * on `<hx-toggle-button>` reach the announced control. The codex aria-group-2\n * finding identified that the inner shadow `<button>` was the only carrier of\n * the toggle role + pressed state, leaving host-level IDREF tokens stranded\n * across the shadow boundary.\n *\n * The shadow `<button>` keeps `aria-pressed`/`aria-label` mirrored for\n * backwards-compat with existing tests and for browsers without IDL element\n * references; the host is the canonical announced surface.\n * @internal\n */\n private _syncHostAriaSemantics(): void {\n const internals = this._internals;\n\n const hostAriaLabel = this.getAttribute('aria-label')?.trim() || '';\n const externalLabelTokens = this.getAttribute('aria-labelledby');\n const externalDescTokens = this.getAttribute('aria-describedby');\n const labelEls = resolveIdrefTokens(this, externalLabelTokens);\n const descEls = resolveIdrefTokens(this, externalDescTokens);\n // Codex round-35 finding (CR major): `aria-labelledby` is only \"effective\"\n // when at least one IDREF resolves. A typo or transiently-missing target\n // must NOT erase the visible label — fall back to label/slot text so a\n // visibly-labeled toggle never becomes unnamed.\n const hasEffectiveLabelledBy = labelEls.length > 0;\n let resolvedLabel: string | null;\n if (hostAriaLabel) {\n resolvedLabel = hostAriaLabel;\n } else if (hasEffectiveLabelledBy) {\n resolvedLabel = null;\n } else if (this.label) {\n resolvedLabel = this.label;\n } else {\n // Codex round-1 finding #9: default-slot text becomes the accessible\n // name when no explicit label is provided. Without this, slotted text\n // never reaches the host's announced surface.\n resolvedLabel = this._slotLabelText || null;\n }\n\n // Codex round-2 finding #2: branch on platform support. Modern path —\n // host carries `role=button` + aria-pressed via ElementInternals.\n // Fallback path — inner native `<button>` is the announced surface; clear\n // host role/state on internals so AT does not double-announce.\n if (this._supportsIdrefRefs) {\n // ─── Modern path ───\n internals.role = 'button';\n internals.ariaPressed = this.pressed ? 'true' : 'false';\n internals.ariaDisabled = this.disabled ? 'true' : 'false';\n // Codex round-1 finding #6: drive aria-invalid from the live ValidityState\n // so a required-but-unpressed toggle is announced as invalid even before\n // any visible affordance renders. `_updateValidity()` calls this method\n // synchronously after every `setValidity()`.\n internals.ariaInvalid = !internals.validity.valid ? 'true' : 'false';\n internals.ariaLabel = resolvedLabel;\n\n type InternalsWithRefs = ElementInternals & {\n ariaLabelledByElements: Element[] | null;\n ariaDescribedByElements: Element[] | null;\n };\n const refsInternals = internals as InternalsWithRefs;\n\n refsInternals.ariaLabelledByElements = hasEffectiveLabelledBy ? labelEls : null;\n refsInternals.ariaDescribedByElements = descEls.length > 0 ? descEls : null;\n // Clear fallbacks when IDL refs are available.\n this._fallbackAriaLabelledBy = null;\n this._fallbackAriaDescribedBy = null;\n this._fallbackAriaLabel = null;\n } else {\n // ─── Fallback path: inner button is the announced surface ───\n // Round-2 finding #2: round-1 set host role/state via internals AND\n // mirrored aria-* onto the `aria-hidden` inner button — making the\n // mirrored attributes inert. The fix is to clear host role/state and\n // let the inner native `<button>` (rendered without aria-hidden, with\n // `tabindex=0`, with aria-pressed) carry semantics natively.\n internals.role = null;\n internals.ariaPressed = null;\n internals.ariaDisabled = null;\n internals.ariaInvalid = null;\n internals.ariaLabel = null;\n\n // Codex round-36 (medium): gate the fallback label tokens on the\n // *effective* labelledby contract. Mirroring broken consumer tokens to\n // the inner button on legacy engines erases the accessible name —\n // exactly the bug the modern path now defends against. When tokens\n // don't resolve, fall through to `_fallbackAriaLabel` (resolvedLabel)\n // so the slot/property still names the announced surface.\n this._fallbackAriaLabelledBy = hasEffectiveLabelledBy ? externalLabelTokens : null;\n this._fallbackAriaDescribedBy = externalDescTokens || null;\n this._fallbackAriaLabel = resolvedLabel;\n }\n }\n\n protected override _onFormReset(): void {\n this.pressed = false;\n }\n\n protected override _onFormStateRestore(\n state: string | File | FormData | null,\n _mode: 'restore' | 'autocomplete',\n ): void {\n this.pressed = typeof state === 'string' && state === 'pressed';\n }\n\n protected override _onFormDisabled(disabled: boolean): void {\n this.disabled = disabled;\n }\n\n // ─── Private Helpers ───\n\n /** @internal */\n private _syncFormValue(): void {\n if (this.pressed && this.value !== undefined) {\n // Pass explicit state 'pressed' so formStateRestoreCallback can reliably detect it.\n this._internals.setFormValue(this.value, 'pressed');\n } else {\n this._internals.setFormValue(null);\n }\n this._updateValidity();\n }\n\n /** @internal */\n private _updateValidity(): void {\n if (this.required && !this.pressed) {\n // Codex round-17 P2: anchor validity UI to the announced surface. On\n // the modern path the host is the canonical announced/focus surface\n // (the inner button is `aria-hidden + tabindex=-1`), so passing the\n // host avoids `reportValidity()` landing focus on a hidden node. On\n // the fallback path the inner button is the announced surface.\n const anchor: HTMLElement | undefined = this._supportsIdrefRefs\n ? this\n : (this.shadowRoot?.querySelector<HTMLElement>('[part=\"button\"]') ?? undefined);\n this._internals.setValidity(\n { valueMissing: true },\n 'Please activate this toggle button.',\n anchor,\n );\n } else {\n this._internals.setValidity({});\n }\n // Codex round-1 finding #6: keep `internals.ariaInvalid` aligned with the\n // current `ValidityState`.\n this._syncHostAriaSemantics();\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 this.pressed = !this.pressed;\n this._syncFormValue();\n\n /**\n * Dispatched when the toggle state changes.\n * @event hx-toggle\n */\n this.dispatchEvent(\n new CustomEvent<{ pressed: boolean }>('hx-toggle', {\n bubbles: true,\n composed: true,\n detail: { pressed: this.pressed },\n }),\n );\n\n // Codex round-12 P2: on the modern path the host owns role=\"button\" and\n // tabindex=0; the inner <button tabindex=\"-1\"> is aria-hidden. Native\n // click activation on a `<button>` still focuses it, which would leave\n // `document.activeElement` and AT focus on a hidden node. Move focus to\n // the host so the announced surface is also the focus target.\n if (this._supportsIdrefRefs) {\n this.focus();\n }\n }\n\n // ─── Render Helpers ───\n\n /** @internal */\n private _renderInner() {\n return html`\n <span part=\"prefix\" class=\"button__prefix\">\n <slot name=\"prefix\"></slot>\n </span>\n <span part=\"label\" class=\"button__label\">\n <slot @slotchange=${this._handleDefaultSlotChange}></slot>\n </span>\n <span part=\"suffix\" class=\"button__suffix\">\n <slot name=\"suffix\"></slot>\n </span>\n `;\n }\n\n // ─── Render ───\n\n override render() {\n const classes = {\n button: true,\n [`button--${this.variant}`]: true,\n [`button--${this.size}`]: true,\n 'button--pressed': this.pressed,\n };\n\n // Codex round-2 finding #2: branch the inner button on platform support.\n // Modern path — host announced, inner button is `aria-hidden + tabindex=-1`.\n // Fallback path — inner native `<button>` is announced (NO aria-hidden,\n // tabindex=0) so consumer-mirrored aria-* attributes resolve through a\n // visible accessibility-tree node and AT can name + activate it natively.\n const innerIsAnnounced = !this._supportsIdrefRefs;\n const innerTabIndex = innerIsAnnounced && !this.disabled ? '0' : '-1';\n // Round-1 finding #8: on no-IDL-ref browsers mirror host aria tokens onto\n // the inner button so it carries an accessible name. On modern browsers\n // we still mirror `label` onto the inner button for non-AT consumers\n // (testing, devtools) — does not affect announced semantics.\n const innerAriaLabel = this._fallbackAriaLabel ?? this.label ?? undefined;\n const innerAriaLabelledBy = this._fallbackAriaLabelledBy ?? undefined;\n const innerAriaDescribedBy = this._fallbackAriaDescribedBy ?? undefined;\n\n return html`\n <button\n part=\"button\"\n class=${classMap(classes)}\n ?disabled=${this.disabled}\n type=\"button\"\n tabindex=${innerTabIndex}\n aria-pressed=${this.pressed ? 'true' : 'false'}\n aria-label=${ifDefined(innerAriaLabel)}\n aria-labelledby=${ifDefined(innerAriaLabelledBy)}\n aria-describedby=${ifDefined(innerAriaDescribedBy)}\n aria-hidden=${innerIsAnnounced ? nothing : 'true'}\n @click=${this._handleClick}\n >\n ${this._renderInner()}\n </button>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-toggle-button': HelixToggleButton;\n }\n}\n"],"names":["helixToggleButtonStyles","css","HelixToggleButton","HelixElement","e","supportsIdrefElementReferences","enabledTabIndex","installAriaIdrefMirror","_a","_b","options","changedProperties","slot","n","text","observer","node","internals","hostAriaLabel","externalLabelTokens","externalDescTokens","labelEls","resolveIdrefTokens","descEls","hasEffectiveLabelledBy","resolvedLabel","refsInternals","state","_mode","disabled","anchor","html","classes","innerIsAnnounced","innerTabIndex","innerAriaLabel","innerAriaLabelledBy","innerAriaDescribedBy","classMap","ifDefined","nothing","forcedColorsInteractive","__decorateClass","query","property","customElement"],"mappings":";;;;;;;AAEO,MAAMA,IAA0BC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;;ACsFhC,IAAMC,IAAN,cAAgCC,EAAa;AAAA,EAA7C,cAAA;AAAA,UAAA,GAAA,SAAA,GAmBL,KAAA,UAAU,IAOV,KAAA,UAAsE,aAOtE,KAAA,OAA2B,MAO3B,KAAA,WAAW,IAOX,KAAA,OAA2B,QAO3B,KAAA,QAA4B,QAQ5B,KAAA,QAA4B,QAO5B,KAAA,WAAW,IA4BX,KAAQ,cAA4C,MAQ3C,KAAQ,iBAAyB,IAGjC,KAAQ,0BAAyC,MAEjD,KAAQ,2BAA0C,MAElD,KAAQ,qBAAoC,MAU5C,KAAQ,qBAAqB,IAUtC,KAAQ,2BAA2B,IAyDnC,KAAQ,qBAAqB,CAACC,MAA2B;AACvD,MAAI,KAAK,YACJ,KAAK,sBACNA,EAAE,WAAW,SACbA,EAAE,QAAQ,OAAOA,EAAE,QAAQ,aAC7BA,EAAE,eAAA,GACF,KAAK,cAAA;AAAA,IAET,GAcA,KAAQ,yBAAyB,CAACA,MAAwB;AAMxD,MALI,KAAK,YACL,CAAC,KAAK,sBAGGA,EAAE,aAAA,EACN,CAAC,MAAM,QAChB,KAAK,cAAA;AAAA,IACP,GAoHA,KAAQ,oBAA6C;AAAA,EAAA;AAAA;AAAA;AAAA,EArQrD,IAAa,WAA0B;AACrC,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA;AAAA,EAGA,IAAa,oBAA4B;AACvC,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA;AAAA,EAGA,gBAAyB;AACvB,WAAO,KAAK,WAAW,cAAA;AAAA,EACzB;AAAA;AAAA,EAGA,iBAA0B;AACxB,WAAO,KAAK,WAAW,eAAA;AAAA,EACzB;AAAA;AAAA,EA6CS,oBAA0B;AAsBjC,QArBA,MAAM,kBAAA,GAIN,KAAK,qBAAqBC,EAA+B,KAAK,UAAU,GAGxE,KAAK,uBAAA,GAcD,CAAC,KAAK,aAAa,UAAU,GAAG;AAClC,WAAK,2BAA2B;AAChC,YAAMC,IAAkB,KAAK,qBAAqB,MAAM;AACxD,WAAK,aAAa,YAAY,KAAK,WAAW,OAAOA,CAAe;AAAA,IACtE;AACA,SAAK,iBAAiB,WAAW,KAAK,kBAAkB,GACxD,KAAK,iBAAiB,SAAS,KAAK,sBAAsB,GAC1D,KAAK,cAAcC,EAAuB,MAAM,MAAM;AACpD,WAAK,uBAAA;AAAA,IACP,CAAC;AAAA,EACH;AAAA,EAES,uBAA6B;;AACpC,UAAM,qBAAA,GACN,KAAK,oBAAoB,WAAW,KAAK,kBAAkB,GAC3D,KAAK,oBAAoB,SAAS,KAAK,sBAAsB,IAI7DC,IAAA,KAAK,sBAAL,QAAAA,EAAwB,cACxB,KAAK,oBAAoB,OACzBC,IAAA,KAAK,gBAAL,QAAAA,EAAkB,cAClB,KAAK,cAAc;AAAA,EACrB;AAAA;AAAA,EAyCQ,gBAAsB;AAC5B,SAAK,UAAU,CAAC,KAAK,SACrB,KAAK,eAAA,GACL,KAAK;AAAA,MACH,IAAI,YAAkC,aAAa;AAAA,QACjD,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ,EAAE,SAAS,KAAK,QAAA;AAAA,MAAQ,CACjC;AAAA,IAAA;AAAA,EAEL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYS,MAAMC,GAA8B;;AAC3C,QAAI,KAAK,oBAAoB;AAC3B,YAAM,MAAMA,CAAO;AACnB;AAAA,IACF;AACA,KAAAD,KAAAD,IAAA,KAAK,eAAL,gBAAAA,EAAiB,cAAiC,uBAAlD,QAAAC,EAAsE,MAAMC;AAAA,EAC9E;AAAA,EAES,aAAaC,GAA+C;AAWnE,QAVA,MAAM,aAAaA,CAAiB,GAIpC,KAAK,sBAAA,GAIL,KAAK,yBAAA,GAED,CAAC,KAAK,OAAO;AACf,YAAMC,IAAO,KAAK;AAIlB,OAHoBA,IAChBA,EAAK,cAAc,EAAE,SAAS,GAAA,CAAM,EAAE,KAAK,CAACC,MAAA;;AAAM,gBAAAL,IAAAK,EAAE,gBAAF,gBAAAL,EAAe;AAAA,OAAM,IACvE,OAEF,QAAQ;AAAA,QACN;AAAA,MAAA;AAAA,IAGN;AAAA,EACF;AAAA,EAES,QAAQG,GAA+C;AAW9D,QAVA,MAAM,QAAQA,CAAiB,IAG7BA,EAAkB,IAAI,SAAS,KAC/BA,EAAkB,IAAI,OAAO,KAC7BA,EAAkB,IAAI,UAAU,MAEhC,KAAK,eAAA,IAILA,EAAkB,IAAI,UAAU,KAC/BA,EAAgD,IAAI,oBAAoB,MAQrE,KAAK,0BAA0B;AACjC,YAAML,IAAkB,KAAK,qBAAqB,MAAM;AACxD,WAAK,aAAa,YAAY,KAAK,WAAW,OAAOA,CAAe;AAAA,IACtE;AAIF,SAAK,uBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,wBAA8B;AACpC,UAAMM,IAAO,KAAK;AAClB,QAAI,CAACA,GAAM;AACT,WAAK,iBAAiB;AACtB;AAAA,IACF;AACA,UAAME,IAAOF,EACV,cAAc,EAAE,SAAS,GAAA,CAAM,EAC/B,IAAI,CAACC,MAAMA,EAAE,eAAe,EAAE,EAC9B,KAAK,GAAG,EACR,KAAA;AACH,SAAK,iBAAiBC;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBQ,2BAAiC;;AACvC,KAAAN,IAAA,KAAK,sBAAL,QAAAA,EAAwB;AACxB,UAAMI,IAAO,KAAK;AAClB,QAAI,CAACA,GAAM;AACT,WAAK,oBAAoB;AACzB;AAAA,IACF;AACA,UAAMG,IAAW,IAAI,iBAAiB,MAAM;AAC1C,WAAK,sBAAA,GACL,KAAK,uBAAA;AAAA,IACP,CAAC;AACD,IAAAH,EAAK,cAAc,EAAE,SAAS,GAAA,CAAM,EAAE,QAAQ,CAACI,MAAS;AACtD,MAAAD,EAAS,QAAQC,GAAM;AAAA,QACrB,eAAe;AAAA,QACf,WAAW;AAAA,QACX,SAAS;AAAA,MAAA,CACV;AAAA,IACH,CAAC,GACD,KAAK,oBAAoBD;AAAA,EAC3B;AAAA;AAAA,EAGQ,2BAAiC;AACvC,SAAK,sBAAA,GACL,KAAK,uBAAA,GAGL,KAAK,yBAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeQ,yBAA+B;;AACrC,UAAME,IAAY,KAAK,YAEjBC,MAAgBV,IAAA,KAAK,aAAa,YAAY,MAA9B,gBAAAA,EAAiC,WAAU,IAC3DW,IAAsB,KAAK,aAAa,iBAAiB,GACzDC,IAAqB,KAAK,aAAa,kBAAkB,GACzDC,IAAWC,EAAmB,MAAMH,CAAmB,GACvDI,IAAUD,EAAmB,MAAMF,CAAkB,GAKrDI,IAAyBH,EAAS,SAAS;AACjD,QAAII;AAkBJ,QAjBIP,IACFO,IAAgBP,IACPM,IACTC,IAAgB,OACP,KAAK,QACdA,IAAgB,KAAK,QAKrBA,IAAgB,KAAK,kBAAkB,MAOrC,KAAK,oBAAoB;AAE3B,MAAAR,EAAU,OAAO,UACjBA,EAAU,cAAc,KAAK,UAAU,SAAS,SAChDA,EAAU,eAAe,KAAK,WAAW,SAAS,SAKlDA,EAAU,cAAeA,EAAU,SAAS,QAAiB,UAAT,QACpDA,EAAU,YAAYQ;AAMtB,YAAMC,IAAgBT;AAEtB,MAAAS,EAAc,yBAAyBF,IAAyBH,IAAW,MAC3EK,EAAc,0BAA0BH,EAAQ,SAAS,IAAIA,IAAU,MAEvE,KAAK,0BAA0B,MAC/B,KAAK,2BAA2B,MAChC,KAAK,qBAAqB;AAAA,IAC5B;AAOE,MAAAN,EAAU,OAAO,MACjBA,EAAU,cAAc,MACxBA,EAAU,eAAe,MACzBA,EAAU,cAAc,MACxBA,EAAU,YAAY,MAQtB,KAAK,0BAA0BO,IAAyBL,IAAsB,MAC9E,KAAK,2BAA2BC,KAAsB,MACtD,KAAK,qBAAqBK;AAAA,EAE9B;AAAA,EAEmB,eAAqB;AACtC,SAAK,UAAU;AAAA,EACjB;AAAA,EAEmB,oBACjBE,GACAC,GACM;AACN,SAAK,UAAU,OAAOD,KAAU,YAAYA,MAAU;AAAA,EACxD;AAAA,EAEmB,gBAAgBE,GAAyB;AAC1D,SAAK,WAAWA;AAAA,EAClB;AAAA;AAAA;AAAA,EAKQ,iBAAuB;AAC7B,IAAI,KAAK,WAAW,KAAK,UAAU,SAEjC,KAAK,WAAW,aAAa,KAAK,OAAO,SAAS,IAElD,KAAK,WAAW,aAAa,IAAI,GAEnC,KAAK,gBAAA;AAAA,EACP;AAAA;AAAA,EAGQ,kBAAwB;;AAC9B,QAAI,KAAK,YAAY,CAAC,KAAK,SAAS;AAMlC,YAAMC,IAAkC,KAAK,qBACzC,SACCtB,IAAA,KAAK,eAAL,gBAAAA,EAAiB,cAA2B,uBAAsB;AACvE,WAAK,WAAW;AAAA,QACd,EAAE,cAAc,GAAA;AAAA,QAChB;AAAA,QACAsB;AAAA,MAAA;AAAA,IAEJ;AACE,WAAK,WAAW,YAAY,EAAE;AAIhC,SAAK,uBAAA;AAAA,EACP;AAAA;AAAA;AAAA,EAKQ,aAAa1B,GAAqB;AACxC,QAAI,KAAK,UAAU;AACjB,MAAAA,EAAE,eAAA,GACFA,EAAE,gBAAA;AACF;AAAA,IACF;AAEA,SAAK,UAAU,CAAC,KAAK,SACrB,KAAK,eAAA,GAML,KAAK;AAAA,MACH,IAAI,YAAkC,aAAa;AAAA,QACjD,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ,EAAE,SAAS,KAAK,QAAA;AAAA,MAAQ,CACjC;AAAA,IAAA,GAQC,KAAK,sBACP,KAAK,MAAA;AAAA,EAET;AAAA;AAAA;AAAA,EAKQ,eAAe;AACrB,WAAO2B;AAAA;AAAA;AAAA;AAAA;AAAA,4BAKiB,KAAK,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMvD;AAAA;AAAA,EAIS,SAAS;AAChB,UAAMC,IAAU;AAAA,MACd,QAAQ;AAAA,MACR,CAAC,WAAW,KAAK,OAAO,EAAE,GAAG;AAAA,MAC7B,CAAC,WAAW,KAAK,IAAI,EAAE,GAAG;AAAA,MAC1B,mBAAmB,KAAK;AAAA,IAAA,GAQpBC,IAAmB,CAAC,KAAK,oBACzBC,IAAgBD,KAAoB,CAAC,KAAK,WAAW,MAAM,MAK3DE,IAAiB,KAAK,sBAAsB,KAAK,SAAS,QAC1DC,IAAsB,KAAK,2BAA2B,QACtDC,IAAuB,KAAK,4BAA4B;AAE9D,WAAON;AAAA;AAAA;AAAA,gBAGKO,EAASN,CAAO,CAAC;AAAA,oBACb,KAAK,QAAQ;AAAA;AAAA,mBAEdE,CAAa;AAAA,uBACT,KAAK,UAAU,SAAS,OAAO;AAAA,qBACjCK,EAAUJ,CAAc,CAAC;AAAA,0BACpBI,EAAUH,CAAmB,CAAC;AAAA,2BAC7BG,EAAUF,CAAoB,CAAC;AAAA,sBACpCJ,IAAmBO,IAAU,MAAM;AAAA,iBACxC,KAAK,YAAY;AAAA;AAAA,UAExB,KAAK,cAAc;AAAA;AAAA;AAAA,EAG3B;AACF;AArmBatC,EACK,SAAS,CAACF,GAAyByC,CAAuB;AAD/DvC,EAMK,iBAAiB;AAGEwC,EAAA;AAAA,EAAlCC,EAAM,kBAAkB;AAAA,GATdzC,EASwB,WAAA,gBAAA,CAAA;AAUnCwC,EAAA;AAAA,EADCE,EAAS,EAAE,MAAM,SAAS,SAAS,IAAM;AAAA,GAlB/B1C,EAmBX,WAAA,WAAA,CAAA;AAOAwC,EAAA;AAAA,EADCE,EAAS,EAAE,MAAM,QAAQ,SAAS,IAAM;AAAA,GAzB9B1C,EA0BX,WAAA,WAAA,CAAA;AAOAwC,EAAA;AAAA,EADCE,EAAS,EAAE,MAAM,QAAQ,SAAS,IAAM,WAAW,WAAW;AAAA,GAhCpD1C,EAiCX,WAAA,QAAA,CAAA;AAOAwC,EAAA;AAAA,EADCE,EAAS,EAAE,MAAM,SAAS,SAAS,IAAM;AAAA,GAvC/B1C,EAwCX,WAAA,YAAA,CAAA;AAOAwC,EAAA;AAAA,EADCE,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GA9Cf1C,EA+CX,WAAA,QAAA,CAAA;AAOAwC,EAAA;AAAA,EADCE,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GArDf1C,EAsDX,WAAA,SAAA,CAAA;AAQAwC,EAAA;AAAA,EADCE,EAAS,EAAE,MAAM,OAAA,CAAQ;AAAA,GA7Df1C,EA8DX,WAAA,SAAA,CAAA;AAOAwC,EAAA;AAAA,EADCE,EAAS,EAAE,MAAM,SAAS,SAAS,IAAM;AAAA,GApE/B1C,EAqEX,WAAA,YAAA,CAAA;AAoCiBwC,EAAA;AAAA,EAAhBf,EAAA;AAAM,GAzGIzB,EAyGM,WAAA,kBAAA,CAAA;AAGAwC,EAAA;AAAA,EAAhBf,EAAA;AAAM,GA5GIzB,EA4GM,WAAA,2BAAA,CAAA;AAEAwC,EAAA;AAAA,EAAhBf,EAAA;AAAM,GA9GIzB,EA8GM,WAAA,4BAAA,CAAA;AAEAwC,EAAA;AAAA,EAAhBf,EAAA;AAAM,GAhHIzB,EAgHM,WAAA,sBAAA,CAAA;AAUAwC,EAAA;AAAA,EAAhBf,EAAA;AAAM,GA1HIzB,EA0HM,WAAA,sBAAA,CAAA;AA1HNA,IAANwC,EAAA;AAAA,EADNG,EAAc,kBAAkB;AAAA,GACpB3C,CAAA;"}
1
+ {"version":3,"file":"hx-toggle-button-DSJeFlb0.js","sources":["../../src/components/hx-toggle-button/hx-toggle-button.styles.ts","../../src/components/hx-toggle-button/hx-toggle-button.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const helixToggleButtonStyles = 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 /* ─── Base Button ─── */\n\n .button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--hx-space-2, 0.5rem);\n border: var(--hx-border-width-thin, 1px) solid var(--hx-toggle-button-border-color, transparent);\n border-radius: var(--hx-toggle-button-border-radius, var(--hx-border-radius-md, 0.375rem));\n background-color: var(--hx-toggle-button-bg, var(--hx-color-action-primary-bg, #0f7078));\n color: var(--hx-toggle-button-color, var(--hx-color-text-on-primary, #ffffff));\n font-family: var(--hx-toggle-button-font-family, var(--hx-font-family-sans, sans-serif));\n font-weight: var(--hx-toggle-button-font-weight, var(--hx-font-weight-semibold, 600));\n line-height: var(--hx-line-height-tight, 1.25);\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 white-space: nowrap;\n user-select: none;\n -webkit-user-select: none;\n }\n\n /*\n * Host-focus path: on the modern (IDL element-references) render branch the\n * host is the tabbable surface (tabindex=0) and the inner <button> is\n * demoted to tabindex=-1. Drive the focus ring from ':host(:focus-visible)'\n * so keyboard users still see a visible affordance. Codex round-11 P1.\n */\n :host(:focus-visible) .button {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-toggle-button-focus-ring-color, var(--hx-focus-ring-color, #0f7078));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n }\n\n /*\n * Fallback (no-IDL-ref) path: the host carries tabindex=-1 and the inner\n * <button> is the tab target. Native :focus-visible drives the ring.\n */\n .button:focus-visible {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-toggle-button-focus-ring-color, var(--hx-focus-ring-color, #0f7078));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n }\n\n .button:hover {\n filter: brightness(var(--hx-filter-brightness-hover, 0.9));\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 44px touch target for sm variant.\n min-height uses --hx-touch-target-min to guarantee the interactive area\n meets the threshold even though the visual size token is smaller. */\n .button--sm {\n padding: var(--hx-space-1, 0.25rem) var(--hx-space-3, 0.75rem);\n font-size: var(--hx-font-size-sm, 0.875rem);\n min-height: var(--hx-touch-target-min, 2.75rem);\n }\n\n .button--md {\n padding: var(--hx-space-2, 0.5rem) var(--hx-space-4, 1rem);\n font-size: var(--hx-font-size-md, 1rem);\n min-height: var(--hx-size-10, 2.5rem);\n }\n\n .button--lg {\n padding: var(--hx-space-3, 0.75rem) var(--hx-space-6, 1.5rem);\n font-size: var(--hx-font-size-lg, 1.125rem);\n min-height: var(--hx-size-12, 3rem);\n }\n\n /* ─── Style Variants ─── */\n\n .button--primary {\n --hx-toggle-button-bg: var(--hx-color-action-primary-bg, #0f7078);\n --hx-toggle-button-color: var(--hx-color-text-on-primary, #ffffff);\n --hx-toggle-button-border-color: transparent;\n }\n\n /*\n * secondary/ghost paint primary text on the surface (white). primary-500\n * (#429797) on white = 3.43:1 — fails AA at body sizes. shift to\n * primary-600 (#0F7078) = 6.06:1 — AA pass. border keeps primary-500\n * for the brand affordance (3:1 non-text contrast still met).\n */\n .button--secondary {\n --hx-toggle-button-bg: transparent;\n --hx-toggle-button-color: var(--hx-color-primary-600, #0f7078);\n --hx-toggle-button-border-color: var(--hx-color-primary-600, #0f7078);\n }\n\n .button--secondary:hover {\n --hx-toggle-button-bg: var(--hx-color-primary-50, #ebf8f8);\n }\n\n .button--tertiary {\n --hx-toggle-button-bg: var(--hx-color-surface-sunken, #ebeee9);\n --hx-toggle-button-color: var(--hx-color-text-primary, #0d1825);\n --hx-toggle-button-border-color: transparent;\n }\n\n .button--tertiary:hover {\n --hx-toggle-button-bg: var(--hx-color-surface-raised, #f5f8f3);\n }\n\n .button--ghost {\n --hx-toggle-button-bg: transparent;\n --hx-toggle-button-color: var(--hx-color-primary-600, #0f7078);\n --hx-toggle-button-border-color: transparent;\n }\n\n .button--ghost:hover {\n --hx-toggle-button-bg: var(--hx-color-surface-raised, #f5f8f3);\n }\n\n .button--outline {\n --hx-toggle-button-bg: transparent;\n --hx-toggle-button-color: var(--hx-color-text-primary, #0d1825);\n --hx-toggle-button-border-color: var(--hx-color-border-strong, #66787b);\n }\n\n .button--outline:hover {\n --hx-toggle-button-bg: var(--hx-color-surface-raised, #f5f8f3);\n }\n\n /* ─── Pressed State ─── */\n\n /*\n * Primary: already uses solid primary bg; pressed deepens to primary-700\n * to give clear visual feedback without introducing a new color.\n *\n * AA fix: text.on-primary (= neutral-900 #0D1825) on primary-700 (#0F6363)\n * = 2.54:1 — fails AA. Pin fg at neutral-0 for the darker pressed fill\n * (neutral-0 on primary-700 = 7.03:1, AAA pass). Mirrors hx-toast\n * precedent (commit 300e21ab0) and hx-button hover treatment.\n * Note: secondary.pressed below uses primary-500 and stays at 5.20:1 —\n * leave it alone.\n */\n .button--primary.button--pressed {\n --hx-toggle-button-bg: var(--hx-toggle-button-pressed-bg, var(--hx-color-primary-700, #0f6363));\n --hx-toggle-button-color: var(\n --hx-toggle-button-pressed-color,\n var(--hx-color-neutral-0, #ffffff)\n );\n --hx-toggle-button-border-color: transparent;\n }\n\n /*\n * Secondary: unpressed is outlined/transparent; pressed fills with primary bg\n * so the state change is immediately legible.\n */\n .button--secondary.button--pressed {\n --hx-toggle-button-bg: var(\n --hx-toggle-button-pressed-bg,\n var(--hx-color-action-primary-bg, #0f7078)\n );\n --hx-toggle-button-color: var(\n --hx-toggle-button-pressed-color,\n var(--hx-color-text-on-primary, #ffffff)\n );\n --hx-toggle-button-border-color: var(--hx-color-action-primary-bg, #0f7078);\n }\n\n /* Tertiary pressed: use primary-100 bg + primary-700 text + border for WCAG 3:1 non-text contrast. */\n .button--tertiary.button--pressed {\n --hx-toggle-button-bg: var(--hx-toggle-button-pressed-bg, var(--hx-color-primary-100, #dbf0f0));\n --hx-toggle-button-color: var(\n --hx-toggle-button-pressed-color,\n var(--hx-color-primary-700, #0f6363)\n );\n --hx-toggle-button-border-color: var(--hx-color-primary-600, #0f7078);\n box-shadow: inset 0 0 0 var(--hx-toggle-button-pressed-ring-width, 1px)\n var(--hx-color-primary-600, #0f7078);\n }\n\n /* Ghost pressed: subtle neutral fill, matching hover behavior as a baseline. */\n .button--ghost.button--pressed {\n --hx-toggle-button-bg: var(--hx-toggle-button-pressed-bg, var(--hx-color-primary-100, #dbf0f0));\n --hx-toggle-button-color: var(\n --hx-toggle-button-pressed-color,\n var(--hx-color-primary-700, #0f6363)\n );\n --hx-toggle-button-border-color: transparent;\n }\n\n /* Outline pressed: fills with a neutral tint, darkens the border, and adds an inset shadow for WCAG 3:1 non-text contrast. */\n .button--outline.button--pressed {\n --hx-toggle-button-bg: var(\n --hx-toggle-button-pressed-bg,\n var(--hx-color-surface-sunken, #ebeee9)\n );\n --hx-toggle-button-color: var(\n --hx-toggle-button-pressed-color,\n var(--hx-color-text-primary, #0d1825)\n );\n --hx-toggle-button-border-color: var(--hx-color-text-muted, #4a5362);\n box-shadow: inset 0 0 0 var(--hx-toggle-button-pressed-ring-width, 1px)\n var(--hx-color-neutral-500, #66787b);\n }\n\n /* ─── Disabled ─── */\n\n .button[disabled] {\n cursor: not-allowed;\n }\n\n /* ─── Prefix / Suffix / Label ─── */\n\n .button__prefix,\n .button__suffix {\n display: inline-flex;\n align-items: center;\n flex-shrink: 0;\n }\n\n .button__label {\n flex: 1 1 auto;\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: var(--hx-toggle-button-fc-bg, ButtonFace);\n color: var(--hx-toggle-button-fc-color, ButtonText);\n border: var(--hx-toggle-button-fc-border-width, 2px) solid\n var(--hx-toggle-button-fc-border-color, ButtonText);\n }\n\n :host(:focus-visible) .button,\n .button:focus-visible {\n outline: var(--hx-toggle-button-fc-focus-ring-width, 3px) solid\n var(--hx-toggle-button-fc-focus-ring-color, Highlight);\n outline-offset: var(--hx-toggle-button-fc-focus-ring-offset, 2px);\n }\n\n .button--pressed {\n background-color: Highlight;\n color: HighlightText;\n border-color: Highlight;\n box-shadow: none;\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, type PropertyValues } 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 { ifDefined } from 'lit/directives/if-defined.js';\nimport { HelixElement } from '../../base/index.js';\nimport { helixToggleButtonStyles } from './hx-toggle-button.styles.js';\nimport { forcedColorsInteractive } from '../../styles/forced-colors.js';\nimport {\n installAriaIdrefMirror,\n resolveIdrefTokens,\n supportsIdrefElementReferences,\n type AriaIdrefMirrorHandle,\n} from '../../utils/aria-idref.js';\n\n/** Detail for the hx-toggle event dispatched by hx-toggle-button. */\nexport interface HxToggleDetail {\n pressed: boolean;\n}\n\n/**\n * A two-state toggle button that communicates a pressed/unpressed status to\n * assistive technology via `aria-pressed`. Supports multiple visual variants\n * and sizes, prefix/suffix slots, full ElementInternals form association, and\n * a distinct pressed visual state for every variant.\n *\n * @summary Two-state toggle button with pressed/unpressed ARIA semantics.\n *\n * @tag hx-toggle-button\n *\n * @slot - Default slot for the button label text or content.\n * @slot prefix - Icon or content rendered before the label.\n * @slot suffix - Icon or content rendered after the label.\n *\n * @fires {CustomEvent<{pressed: boolean}>} hx-toggle - Dispatched when the\n * toggle state changes. Not dispatched when the button is disabled.\n *\n * @csspart button - The native `<button>` element.\n * @csspart label - The label text wrapper span.\n * @csspart prefix - The prefix slot container span.\n * @csspart suffix - The suffix slot container span.\n *\n * @cssprop [--hx-toggle-button-bg=var(--hx-color-primary-500)] - Button background color.\n * @cssprop [--hx-toggle-button-color=var(--hx-color-neutral-0)] - Button text color.\n * @cssprop [--hx-toggle-button-border-color=transparent] - Button border color.\n * @cssprop [--hx-toggle-button-border-radius=var(--hx-border-radius-md)] - Button border radius.\n * @cssprop [--hx-toggle-button-font-family=var(--hx-font-family-sans)] - Button font family.\n * @cssprop [--hx-toggle-button-font-weight=var(--hx-font-weight-semibold)] - Button font weight.\n * @cssprop [--hx-toggle-button-focus-ring-color=var(--hx-focus-ring-color)] - Focus ring color.\n * @cssprop [--hx-toggle-button-pressed-bg=var(--hx-color-primary-500)] - Background when pressed (variant-specific fallback applies).\n * @cssprop [--hx-toggle-button-pressed-color=var(--hx-color-neutral-0)] - Text color when pressed (variant-specific fallback applies).\n * @cssprop [--hx-opacity-disabled] - Opacity.\n * @cssprop [--hx-space-2] - Spacing token.\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-color-neutral-0] - Color.\n * @cssprop [--hx-font-family-sans] - Font family.\n * @cssprop [--hx-font-weight-semibold] - Font weight.\n * @cssprop [--hx-line-height-tight] - Line height.\n * @cssprop [--hx-transition-fast] - Transition timing.\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-filter-brightness-hover] - CSS filter.\n * @cssprop [--hx-filter-brightness-active] - CSS filter.\n * @cssprop [--hx-touch-target-min] - Minimum touch target size.\n * @cssprop [--hx-space-1] - Spacing token.\n * @cssprop [--hx-space-3] - Spacing token.\n * @cssprop [--hx-font-size-sm] - Font size.\n * @cssprop [--hx-space-4] - Spacing token.\n * @cssprop [--hx-font-size-md] - Font size.\n * @cssprop [--hx-size-10] - Size token.\n * @cssprop [--hx-space-6] - Spacing token.\n * @cssprop [--hx-font-size-lg] - Font size.\n * @cssprop [--hx-size-12] - Size token.\n * @cssprop [--hx-color-primary-50] - Color.\n * @cssprop [--hx-color-neutral-100] - Color.\n * @cssprop [--hx-color-neutral-900] - Color.\n * @cssprop [--hx-color-neutral-200] - Color.\n * @cssprop [--hx-color-neutral-300] - Color.\n * @cssprop [--hx-color-primary-700] - Color.\n * @cssprop [--hx-color-primary-100] - Color.\n * @cssprop [--hx-color-neutral-50] - Color.\n * @cssprop [--hx-color-neutral-500] - Color.\n */\n@customElement('hx-toggle-button')\nexport class HelixToggleButton extends HelixElement {\n static override styles = [helixToggleButtonStyles, forcedColorsInteractive];\n\n // ─── Form Association ───\n\n /** @internal */\n static override formAssociated = true;\n\n /** @internal */\n @query('slot:not([name])') private _defaultSlot!: HTMLSlotElement | null;\n\n // ─── Public Properties ───\n\n /**\n * Whether the toggle button is in the pressed state.\n * Reflected as an attribute so CSS selectors like `:host([pressed])` work.\n * @attr pressed\n */\n @property({ type: Boolean, reflect: true })\n pressed = false;\n\n /**\n * Visual style variant of the button.\n * @attr variant\n */\n @property({ type: String, reflect: true })\n variant: 'primary' | 'secondary' | 'tertiary' | 'ghost' | 'outline' = 'secondary';\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 * Whether the button is disabled. Prevents all interaction and form actions.\n * @attr disabled\n */\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n /**\n * Form field name submitted via ElementInternals when the button is pressed.\n * @attr name\n */\n @property({ type: String })\n name: string | undefined = undefined;\n\n /**\n * Form field value submitted via ElementInternals when the button is pressed.\n * @attr value\n */\n @property({ type: String })\n value: string | undefined = undefined;\n\n /**\n * Accessible label forwarded to the inner `<button>` as `aria-label`.\n * Required for icon-only toggle buttons where no visible text is present.\n * @attr label\n */\n @property({ type: String })\n label: string | undefined = undefined;\n\n /**\n * When true, the button must be in the pressed state for the form to be submitted.\n * @attr required\n */\n @property({ type: Boolean, reflect: true })\n required = false;\n\n // ─── Form API ───\n\n /** Returns the ValidityState object. */\n override get validity(): ValidityState {\n return this._internals.validity;\n }\n\n /** Returns the current validation message. */\n override get validationMessage(): string {\n return this._internals.validationMessage;\n }\n\n /** Checks whether the button satisfies its constraints. */\n checkValidity(): boolean {\n return this._internals.checkValidity();\n }\n\n /** Reports validity and shows the browser's constraint validation UI. */\n reportValidity(): boolean {\n return this._internals.reportValidity();\n }\n\n /**\n * Handle for the shared IDREF observer. See `installAriaIdrefMirror()`.\n * @internal\n */\n private _ariaMirror: AriaIdrefMirrorHandle | null = null;\n\n /**\n * Default-slot text content captured for use as the host's accessible name\n * when no explicit `label`/`aria-label`/`aria-labelledby` is supplied.\n * Codex round-1 finding #9.\n * @internal\n */\n @state() private _slotLabelText: string = '';\n\n /** No-IDL-ref fallback tokens applied to the inner button. @internal */\n @state() private _fallbackAriaLabelledBy: string | null = null;\n /** @internal */\n @state() private _fallbackAriaDescribedBy: string | null = null;\n /** @internal */\n @state() private _fallbackAriaLabel: string | null = null;\n\n /**\n * Whether the platform supports IDL element references on `ElementInternals`.\n * Drives the render-time branch between the modern path (host is the\n * announced surface, inner button is `aria-hidden + tabindex=-1`) and the\n * fallback path (inner button is the announced surface, host is demoted).\n * Codex round-2 finding #2.\n * @internal\n */\n @state() private _supportsIdrefRefs = true;\n\n /**\n * Tracks whether the host's `tabindex` is managed by the component itself\n * (vs. set explicitly by a consumer). Codex round-14 P2: a consumer-supplied\n * `tabindex` (e.g. roving-tabindex toolbar pattern with `tabindex=\"-1\"`)\n * must survive disabled flips and re-renders. Only re-assert tabindex in\n * `updated()` when the component originally claimed it.\n * @internal\n */\n private _internalTabindexManaged = false;\n\n // ─── Lifecycle ───\n\n override connectedCallback(): void {\n super.connectedCallback();\n // Detect platform support for IDL element references. Codex round-2\n // finding #2: drives the render-time branch between modern (host\n // announced) and fallback (inner button announced) paths.\n this._supportsIdrefRefs = supportsIdrefElementReferences(this._internals);\n // Seed root-independent semantics from the moment of connection so AT\n // sees the toggle role + pressed state before the first paint.\n this._syncHostAriaSemantics();\n // Codex round-1 finding #1: host is the canonical announced surface on\n // modern browsers, so the inner `<button>` is demoted via\n // `aria-hidden + tabindex=-1`.\n // Codex round-2 finding #2: on no-IDL-ref browsers the inner button is\n // the announced surface (it carries native button + aria-pressed), so\n // the host is demoted to `tabindex=-1` and lets the inner button own\n // tab order + activation.\n // Codex round-14 P2: only claim ownership of `tabindex` when no consumer\n // value is present. Consumers using roving-tabindex toolbar patterns\n // must be able to set `tabindex=\"-1\"` on the host without it being\n // clobbered on every disabled flip. Note we still claim ownership when\n // disabled — the initial value is `-1` to keep the host out of tab order\n // and `updated()` re-asserts the appropriate value when disabled flips.\n if (!this.hasAttribute('tabindex')) {\n this._internalTabindexManaged = true;\n const enabledTabIndex = this._supportsIdrefRefs ? '0' : '-1';\n this.setAttribute('tabindex', this.disabled ? '-1' : enabledTabIndex);\n }\n this.addEventListener('keydown', this._handleHostKeyDown);\n this.addEventListener('click', this._handleHostClickRouted);\n this._ariaMirror = installAriaIdrefMirror(this, () => {\n this._syncHostAriaSemantics();\n });\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener('keydown', this._handleHostKeyDown);\n this.removeEventListener('click', this._handleHostClickRouted);\n // Codex round-13 P2: tear down the assigned-node text observer so a\n // detached host does not receive mutation callbacks from nodes that may\n // outlive it (e.g. lifted out of the slot but kept in the document).\n this._slotTextObserver?.disconnect();\n this._slotTextObserver = null;\n this._ariaMirror?.disconnect();\n this._ariaMirror = null;\n }\n\n /**\n * Host-level keydown handler. Active only on the modern path; on the\n * no-IDL-ref fallback the inner `<button>` owns native Space/Enter\n * activation. Codex round-2 finding #2.\n * @internal\n */\n private _handleHostKeyDown = (e: KeyboardEvent): void => {\n if (this.disabled) return;\n if (!this._supportsIdrefRefs) return;\n if (e.target !== this) return;\n if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n this._invokeToggle();\n }\n };\n\n /**\n * Host-level click router. The inner `<button>` is `aria-hidden + tabindex=-1`\n * on modern browsers but still receives clicks via mouse activation; toggling\n * is handled by the inner `_handleClick`. Host-level clicks (composed path\n * origin === host) only come from AT activation or programmatic triggers,\n * so we route those to the toggle pipeline.\n *\n * Codex round-2 finding #2: on no-IDL-ref browsers the inner button is the\n * announced surface so this host-level routing is a no-op — AT activation\n * lands on the inner button directly.\n * @internal\n */\n private _handleHostClickRouted = (e: MouseEvent): void => {\n if (this.disabled) return;\n if (!this._supportsIdrefRefs) return;\n // Only fire when the host itself originated the click — inner-button\n // clicks bubble through here too but are handled by `_handleClick`.\n const path = e.composedPath();\n if (path[0] !== this) return;\n this._invokeToggle();\n };\n\n /** @internal */\n private _invokeToggle(): void {\n this.pressed = !this.pressed;\n this._syncFormValue();\n this.dispatchEvent(\n new CustomEvent<{ pressed: boolean }>('hx-toggle', {\n bubbles: true,\n composed: true,\n detail: { pressed: this.pressed },\n }),\n );\n }\n\n /**\n * Moves focus to the announced toggle-button surface. Codex round-1 finding\n * `#1` made the host the canonical focus target on modern engines so AT\n * announces a single widget. Round-7 finding `#9` extends that contract to\n * the no-IDL-ref fallback: when the host is demoted (`tabindex=-1`,\n * role/state cleared on `internals`) the inner `<button>` owns the announced\n * semantics and tab order, so programmatic `focus()` must redirect there —\n * otherwise scripted focus and error recovery land on the demoted host on\n * unsupported engines.\n */\n override focus(options?: FocusOptions): void {\n if (this._supportsIdrefRefs) {\n super.focus(options);\n return;\n }\n this.shadowRoot?.querySelector<HTMLButtonElement>('[part=\"button\"]')?.focus(options);\n }\n\n override firstUpdated(changedProperties: PropertyValues<this>): void {\n super.firstUpdated(changedProperties);\n\n // Track default-slot text content so it can serve as the accessible\n // name when no explicit label is set. Codex round-1 finding #9.\n this._captureSlotLabelText();\n // Codex round-13 P2: also observe in-place text mutations on assigned\n // nodes so framework-driven `Mute` → `Unmute` rewrites refresh the\n // cached label without requiring the consumer to swap the node.\n this._installSlotTextObserver();\n\n if (!this.label) {\n const slot = this._defaultSlot;\n const hasSlotText = slot\n ? slot.assignedNodes({ flatten: true }).some((n) => n.textContent?.trim())\n : false;\n if (!hasSlotText) {\n console.warn(\n '[hx-toggle-button] No accessible label found. Set the `label` attribute or provide slot text content for WCAG 4.1.2 compliance.',\n );\n }\n }\n }\n\n override updated(changedProperties: PropertyValues<this>): void {\n super.updated(changedProperties);\n\n if (\n changedProperties.has('pressed') ||\n changedProperties.has('value') ||\n changedProperties.has('required')\n ) {\n this._syncFormValue();\n }\n\n if (\n changedProperties.has('disabled') ||\n (changedProperties as Map<PropertyKey, unknown>).has('_supportsIdrefRefs')\n ) {\n // Codex round-2 finding #2: align host tabindex with the chosen\n // announced surface. On no-IDL-ref browsers the inner button owns\n // tab order, so re-enabling the host should leave it `tabindex=-1`.\n // Codex round-14 P2: only re-assert when the component owns tabindex.\n // Consumer-managed values (e.g. roving-tabindex toolbar with `-1`) must\n // not be overwritten on disabled flips or supports-flag transitions.\n if (this._internalTabindexManaged) {\n const enabledTabIndex = this._supportsIdrefRefs ? '0' : '-1';\n this.setAttribute('tabindex', this.disabled ? '-1' : enabledTabIndex);\n }\n }\n\n // Host-elevated ARIA semantics — see _syncHostAriaSemantics.\n this._syncHostAriaSemantics();\n }\n\n /**\n * Reads the default slot's flattened text content into `_slotLabelText`.\n * Called from `firstUpdated()` and `slotchange` so host-canonical semantics\n * pick up slotted label text. Codex round-1 finding #9.\n * @internal\n */\n private _captureSlotLabelText(): void {\n const slot = this._defaultSlot;\n if (!slot) {\n this._slotLabelText = '';\n return;\n }\n const text = slot\n .assignedNodes({ flatten: true })\n .map((n) => n.textContent ?? '')\n .join(' ')\n .trim();\n this._slotLabelText = text;\n }\n\n /**\n * Watches assigned default-slot nodes for in-place text mutations so the\n * cached `_slotLabelText` (and thus `internals.ariaLabel`) stays in sync\n * when a framework rewrites textContent of an already-assigned node without\n * replacing it. `slotchange` does NOT fire for those mutations, so a\n * separate observer is required. Codex round-13 P2.\n * @internal\n */\n private _slotTextObserver: MutationObserver | null = null;\n\n /**\n * (Re-)installs the mutation observer over the current set of assigned\n * default-slot nodes. Disconnects any prior observer first so detached\n * nodes stop firing into a torn-down host.\n * @internal\n */\n private _installSlotTextObserver(): void {\n this._slotTextObserver?.disconnect();\n const slot = this._defaultSlot;\n if (!slot) {\n this._slotTextObserver = null;\n return;\n }\n const observer = new MutationObserver(() => {\n this._captureSlotLabelText();\n this._syncHostAriaSemantics();\n });\n slot.assignedNodes({ flatten: true }).forEach((node) => {\n observer.observe(node, {\n characterData: true,\n childList: true,\n subtree: true,\n });\n });\n this._slotTextObserver = observer;\n }\n\n /** @internal */\n private _handleDefaultSlotChange(): void {\n this._captureSlotLabelText();\n this._syncHostAriaSemantics();\n // Re-tune the in-place text observer over the new assigned-node set\n // (codex round-13 P2).\n this._installSlotTextObserver();\n }\n\n /**\n * Mirrors toggle-button semantics onto the host via ElementInternals so that\n * consumer-supplied `aria-label`, `aria-labelledby`, and `aria-describedby`\n * on `<hx-toggle-button>` reach the announced control. The codex aria-group-2\n * finding identified that the inner shadow `<button>` was the only carrier of\n * the toggle role + pressed state, leaving host-level IDREF tokens stranded\n * across the shadow boundary.\n *\n * The shadow `<button>` keeps `aria-pressed`/`aria-label` mirrored for\n * backwards-compat with existing tests and for browsers without IDL element\n * references; the host is the canonical announced surface.\n * @internal\n */\n private _syncHostAriaSemantics(): void {\n const internals = this._internals;\n\n const hostAriaLabel = this.getAttribute('aria-label')?.trim() || '';\n const externalLabelTokens = this.getAttribute('aria-labelledby');\n const externalDescTokens = this.getAttribute('aria-describedby');\n const labelEls = resolveIdrefTokens(this, externalLabelTokens);\n const descEls = resolveIdrefTokens(this, externalDescTokens);\n // Codex round-35 finding (CR major): `aria-labelledby` is only \"effective\"\n // when at least one IDREF resolves. A typo or transiently-missing target\n // must NOT erase the visible label — fall back to label/slot text so a\n // visibly-labeled toggle never becomes unnamed.\n const hasEffectiveLabelledBy = labelEls.length > 0;\n let resolvedLabel: string | null;\n if (hostAriaLabel) {\n resolvedLabel = hostAriaLabel;\n } else if (hasEffectiveLabelledBy) {\n resolvedLabel = null;\n } else if (this.label) {\n resolvedLabel = this.label;\n } else {\n // Codex round-1 finding #9: default-slot text becomes the accessible\n // name when no explicit label is provided. Without this, slotted text\n // never reaches the host's announced surface.\n resolvedLabel = this._slotLabelText || null;\n }\n\n // Codex round-2 finding #2: branch on platform support. Modern path —\n // host carries `role=button` + aria-pressed via ElementInternals.\n // Fallback path — inner native `<button>` is the announced surface; clear\n // host role/state on internals so AT does not double-announce.\n if (this._supportsIdrefRefs) {\n // ─── Modern path ───\n internals.role = 'button';\n internals.ariaPressed = this.pressed ? 'true' : 'false';\n internals.ariaDisabled = this.disabled ? 'true' : 'false';\n // Codex round-1 finding #6: drive aria-invalid from the live ValidityState\n // so a required-but-unpressed toggle is announced as invalid even before\n // any visible affordance renders. `_updateValidity()` calls this method\n // synchronously after every `setValidity()`.\n internals.ariaInvalid = !internals.validity.valid ? 'true' : 'false';\n internals.ariaLabel = resolvedLabel;\n\n type InternalsWithRefs = ElementInternals & {\n ariaLabelledByElements: Element[] | null;\n ariaDescribedByElements: Element[] | null;\n };\n const refsInternals = internals as InternalsWithRefs;\n\n refsInternals.ariaLabelledByElements = hasEffectiveLabelledBy ? labelEls : null;\n refsInternals.ariaDescribedByElements = descEls.length > 0 ? descEls : null;\n // Clear fallbacks when IDL refs are available.\n this._fallbackAriaLabelledBy = null;\n this._fallbackAriaDescribedBy = null;\n this._fallbackAriaLabel = null;\n } else {\n // ─── Fallback path: inner button is the announced surface ───\n // Round-2 finding #2: round-1 set host role/state via internals AND\n // mirrored aria-* onto the `aria-hidden` inner button — making the\n // mirrored attributes inert. The fix is to clear host role/state and\n // let the inner native `<button>` (rendered without aria-hidden, with\n // `tabindex=0`, with aria-pressed) carry semantics natively.\n internals.role = null;\n internals.ariaPressed = null;\n internals.ariaDisabled = null;\n internals.ariaInvalid = null;\n internals.ariaLabel = null;\n\n // Codex round-36 (medium): gate the fallback label tokens on the\n // *effective* labelledby contract. Mirroring broken consumer tokens to\n // the inner button on legacy engines erases the accessible name —\n // exactly the bug the modern path now defends against. When tokens\n // don't resolve, fall through to `_fallbackAriaLabel` (resolvedLabel)\n // so the slot/property still names the announced surface.\n this._fallbackAriaLabelledBy = hasEffectiveLabelledBy ? externalLabelTokens : null;\n this._fallbackAriaDescribedBy = externalDescTokens || null;\n this._fallbackAriaLabel = resolvedLabel;\n }\n }\n\n protected override _onFormReset(): void {\n this.pressed = false;\n }\n\n protected override _onFormStateRestore(\n state: string | File | FormData | null,\n _mode: 'restore' | 'autocomplete',\n ): void {\n this.pressed = typeof state === 'string' && state === 'pressed';\n }\n\n protected override _onFormDisabled(disabled: boolean): void {\n this.disabled = disabled;\n }\n\n // ─── Private Helpers ───\n\n /** @internal */\n private _syncFormValue(): void {\n if (this.pressed && this.value !== undefined) {\n // Pass explicit state 'pressed' so formStateRestoreCallback can reliably detect it.\n this._internals.setFormValue(this.value, 'pressed');\n } else {\n this._internals.setFormValue(null);\n }\n this._updateValidity();\n }\n\n /** @internal */\n private _updateValidity(): void {\n if (this.required && !this.pressed) {\n // Codex round-17 P2: anchor validity UI to the announced surface. On\n // the modern path the host is the canonical announced/focus surface\n // (the inner button is `aria-hidden + tabindex=-1`), so passing the\n // host avoids `reportValidity()` landing focus on a hidden node. On\n // the fallback path the inner button is the announced surface.\n const anchor: HTMLElement | undefined = this._supportsIdrefRefs\n ? this\n : (this.shadowRoot?.querySelector<HTMLElement>('[part=\"button\"]') ?? undefined);\n this._internals.setValidity(\n { valueMissing: true },\n 'Please activate this toggle button.',\n anchor,\n );\n } else {\n this._internals.setValidity({});\n }\n // Codex round-1 finding #6: keep `internals.ariaInvalid` aligned with the\n // current `ValidityState`.\n this._syncHostAriaSemantics();\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 this.pressed = !this.pressed;\n this._syncFormValue();\n\n /**\n * Dispatched when the toggle state changes.\n * @event hx-toggle\n */\n this.dispatchEvent(\n new CustomEvent<{ pressed: boolean }>('hx-toggle', {\n bubbles: true,\n composed: true,\n detail: { pressed: this.pressed },\n }),\n );\n\n // Codex round-12 P2: on the modern path the host owns role=\"button\" and\n // tabindex=0; the inner <button tabindex=\"-1\"> is aria-hidden. Native\n // click activation on a `<button>` still focuses it, which would leave\n // `document.activeElement` and AT focus on a hidden node. Move focus to\n // the host so the announced surface is also the focus target.\n if (this._supportsIdrefRefs) {\n this.focus();\n }\n }\n\n // ─── Render Helpers ───\n\n /** @internal */\n private _renderInner() {\n return html`\n <span part=\"prefix\" class=\"button__prefix\">\n <slot name=\"prefix\"></slot>\n </span>\n <span part=\"label\" class=\"button__label\">\n <slot @slotchange=${this._handleDefaultSlotChange}></slot>\n </span>\n <span part=\"suffix\" class=\"button__suffix\">\n <slot name=\"suffix\"></slot>\n </span>\n `;\n }\n\n // ─── Render ───\n\n override render() {\n const classes = {\n button: true,\n [`button--${this.variant}`]: true,\n [`button--${this.size}`]: true,\n 'button--pressed': this.pressed,\n };\n\n // Codex round-2 finding #2: branch the inner button on platform support.\n // Modern path — host announced, inner button is `aria-hidden + tabindex=-1`.\n // Fallback path — inner native `<button>` is announced (NO aria-hidden,\n // tabindex=0) so consumer-mirrored aria-* attributes resolve through a\n // visible accessibility-tree node and AT can name + activate it natively.\n const innerIsAnnounced = !this._supportsIdrefRefs;\n const innerTabIndex = innerIsAnnounced && !this.disabled ? '0' : '-1';\n // Round-1 finding #8: on no-IDL-ref browsers mirror host aria tokens onto\n // the inner button so it carries an accessible name. On modern browsers\n // we still mirror `label` onto the inner button for non-AT consumers\n // (testing, devtools) — does not affect announced semantics.\n const innerAriaLabel = this._fallbackAriaLabel ?? this.label ?? undefined;\n const innerAriaLabelledBy = this._fallbackAriaLabelledBy ?? undefined;\n const innerAriaDescribedBy = this._fallbackAriaDescribedBy ?? undefined;\n\n return html`\n <button\n part=\"button\"\n class=${classMap(classes)}\n ?disabled=${this.disabled}\n type=\"button\"\n tabindex=${innerTabIndex}\n aria-pressed=${this.pressed ? 'true' : 'false'}\n aria-label=${ifDefined(innerAriaLabel)}\n aria-labelledby=${ifDefined(innerAriaLabelledBy)}\n aria-describedby=${ifDefined(innerAriaDescribedBy)}\n aria-hidden=${innerIsAnnounced ? nothing : 'true'}\n @click=${this._handleClick}\n >\n ${this._renderInner()}\n </button>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-toggle-button': HelixToggleButton;\n }\n}\n"],"names":["helixToggleButtonStyles","css","HelixToggleButton","HelixElement","e","supportsIdrefElementReferences","enabledTabIndex","installAriaIdrefMirror","_a","_b","options","changedProperties","slot","n","text","observer","node","internals","hostAriaLabel","externalLabelTokens","externalDescTokens","labelEls","resolveIdrefTokens","descEls","hasEffectiveLabelledBy","resolvedLabel","refsInternals","state","_mode","disabled","anchor","html","classes","innerIsAnnounced","innerTabIndex","innerAriaLabel","innerAriaLabelledBy","innerAriaDescribedBy","classMap","ifDefined","nothing","forcedColorsInteractive","__decorateClass","query","property","customElement"],"mappings":";;;;;;;AAEO,MAAMA,IAA0BC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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"}
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-05-08T00:21:19.078Z",
3
+ "generatedAt": "2026-05-08T04:04:17.319Z",
4
4
  "sourceCem": "custom-elements.json",
5
- "helixVersion": "3.6.0",
6
- "tokensVersion": "3.5.0",
5
+ "helixVersion": "3.7.0",
6
+ "tokensVersion": "3.7.0",
7
7
  "components": [
8
8
  {
9
9
  "tag": "hx-accordion",
@@ -1044,16 +1044,35 @@
1044
1044
  "dependsOn": [],
1045
1045
  "excludedAttributes": [],
1046
1046
  "aaa": {
1047
- "certified": false,
1048
- "certifiedDate": null,
1049
- "criteria": [],
1050
- "auditUrl": null
1047
+ "certified": true,
1048
+ "certifiedDate": "2026-05-08",
1049
+ "criteria": [
1050
+ "1.4.6",
1051
+ "1.4.9",
1052
+ "2.1.3",
1053
+ "2.3.3",
1054
+ "2.4.12",
1055
+ "2.4.13",
1056
+ "2.5.5",
1057
+ "3.2.5",
1058
+ "3.3.6",
1059
+ "forced-colors",
1060
+ "apg-keyboard"
1061
+ ],
1062
+ "auditUrl": "src/components/hx-alert/AAA-AUDIT.md"
1051
1063
  },
1052
- "keyboardContract": null,
1053
- "ariaPattern": null,
1054
- "themeAware": null,
1055
- "brandAware": null,
1056
- "formAssociated": null,
1064
+ "keyboardContract": {
1065
+ "activate": [],
1066
+ "navigate": [],
1067
+ "dismiss": [
1068
+ "Escape"
1069
+ ],
1070
+ "disabledSuppresses": null
1071
+ },
1072
+ "ariaPattern": "alert",
1073
+ "themeAware": true,
1074
+ "brandAware": true,
1075
+ "formAssociated": false,
1057
1076
  "priorityTier": "P0"
1058
1077
  },
1059
1078
  {
@@ -3080,16 +3099,36 @@
3080
3099
  "dependsOn": [],
3081
3100
  "excludedAttributes": [],
3082
3101
  "aaa": {
3083
- "certified": false,
3084
- "certifiedDate": null,
3085
- "criteria": [],
3086
- "auditUrl": null
3102
+ "certified": true,
3103
+ "certifiedDate": "2026-05-08",
3104
+ "criteria": [
3105
+ "1.4.6",
3106
+ "1.4.9",
3107
+ "2.1.3",
3108
+ "2.3.3",
3109
+ "2.4.12",
3110
+ "2.4.13",
3111
+ "2.5.5",
3112
+ "3.2.5",
3113
+ "3.3.6",
3114
+ "forced-colors",
3115
+ "apg-keyboard"
3116
+ ],
3117
+ "auditUrl": "src/components/hx-button/AAA-AUDIT.md"
3087
3118
  },
3088
- "keyboardContract": null,
3089
- "ariaPattern": null,
3090
- "themeAware": null,
3091
- "brandAware": null,
3092
- "formAssociated": null,
3119
+ "keyboardContract": {
3120
+ "activate": [
3121
+ "Enter",
3122
+ "Space"
3123
+ ],
3124
+ "navigate": [],
3125
+ "dismiss": [],
3126
+ "disabledSuppresses": true
3127
+ },
3128
+ "ariaPattern": "button",
3129
+ "themeAware": true,
3130
+ "brandAware": true,
3131
+ "formAssociated": true,
3093
3132
  "priorityTier": "P0"
3094
3133
  },
3095
3134
  {
@@ -4475,16 +4514,35 @@
4475
4514
  "dependsOn": [],
4476
4515
  "excludedAttributes": [],
4477
4516
  "aaa": {
4478
- "certified": false,
4479
- "certifiedDate": null,
4480
- "criteria": [],
4481
- "auditUrl": null
4517
+ "certified": true,
4518
+ "certifiedDate": "2026-05-08",
4519
+ "criteria": [
4520
+ "1.4.6",
4521
+ "1.4.9",
4522
+ "2.1.3",
4523
+ "2.3.3",
4524
+ "2.4.12",
4525
+ "2.4.13",
4526
+ "2.5.5",
4527
+ "3.2.5",
4528
+ "3.3.6",
4529
+ "forced-colors",
4530
+ "apg-keyboard"
4531
+ ],
4532
+ "auditUrl": "src/components/hx-checkbox/AAA-AUDIT.md"
4482
4533
  },
4483
- "keyboardContract": null,
4484
- "ariaPattern": null,
4485
- "themeAware": null,
4486
- "brandAware": null,
4487
- "formAssociated": null,
4534
+ "keyboardContract": {
4535
+ "activate": [
4536
+ "Space"
4537
+ ],
4538
+ "navigate": [],
4539
+ "dismiss": [],
4540
+ "disabledSuppresses": true
4541
+ },
4542
+ "ariaPattern": "checkbox",
4543
+ "themeAware": true,
4544
+ "brandAware": true,
4545
+ "formAssociated": true,
4488
4546
  "priorityTier": "P0"
4489
4547
  },
4490
4548
  {
@@ -9136,16 +9194,35 @@
9136
9194
  "aria-label"
9137
9195
  ],
9138
9196
  "aaa": {
9139
- "certified": false,
9140
- "certifiedDate": null,
9141
- "criteria": [],
9142
- "auditUrl": null
9197
+ "certified": true,
9198
+ "certifiedDate": "2026-05-08",
9199
+ "criteria": [
9200
+ "1.4.6",
9201
+ "1.4.9",
9202
+ "2.1.3",
9203
+ "2.3.3",
9204
+ "2.4.12",
9205
+ "2.4.13",
9206
+ "2.5.5",
9207
+ "3.2.5",
9208
+ "3.3.6",
9209
+ "forced-colors",
9210
+ "apg-keyboard"
9211
+ ],
9212
+ "auditUrl": "src/components/hx-dialog/AAA-AUDIT.md"
9143
9213
  },
9144
- "keyboardContract": null,
9145
- "ariaPattern": null,
9146
- "themeAware": null,
9147
- "brandAware": null,
9148
- "formAssociated": null,
9214
+ "keyboardContract": {
9215
+ "activate": [],
9216
+ "navigate": [],
9217
+ "dismiss": [
9218
+ "Escape"
9219
+ ],
9220
+ "disabledSuppresses": null
9221
+ },
9222
+ "ariaPattern": "dialog",
9223
+ "themeAware": true,
9224
+ "brandAware": true,
9225
+ "formAssociated": false,
9149
9226
  "priorityTier": "P0"
9150
9227
  },
9151
9228
  {
@@ -24581,16 +24658,35 @@
24581
24658
  "dependsOn": [],
24582
24659
  "excludedAttributes": [],
24583
24660
  "aaa": {
24584
- "certified": false,
24585
- "certifiedDate": null,
24586
- "criteria": [],
24587
- "auditUrl": null
24661
+ "certified": true,
24662
+ "certifiedDate": "2026-05-08",
24663
+ "criteria": [
24664
+ "1.4.6",
24665
+ "1.4.9",
24666
+ "2.1.3",
24667
+ "2.3.3",
24668
+ "2.4.12",
24669
+ "2.4.13",
24670
+ "2.5.5",
24671
+ "3.2.5",
24672
+ "3.3.6",
24673
+ "forced-colors",
24674
+ "apg-keyboard"
24675
+ ],
24676
+ "auditUrl": "src/components/hx-text-input/AAA-AUDIT.md"
24588
24677
  },
24589
- "keyboardContract": null,
24590
- "ariaPattern": null,
24591
- "themeAware": null,
24592
- "brandAware": null,
24593
- "formAssociated": null,
24678
+ "keyboardContract": {
24679
+ "activate": [
24680
+ "character-input"
24681
+ ],
24682
+ "navigate": [],
24683
+ "dismiss": [],
24684
+ "disabledSuppresses": true
24685
+ },
24686
+ "ariaPattern": "textbox",
24687
+ "themeAware": true,
24688
+ "brandAware": true,
24689
+ "formAssociated": true,
24594
24690
  "priorityTier": "P0"
24595
24691
  },
24596
24692
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helixui/library",
3
- "version": "3.6.0",
3
+ "version": "3.7.0",
4
4
  "description": "Enterprise Web Component Library built with Lit 3.x",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -59,7 +59,7 @@
59
59
  "customElements": "custom-elements.json",
60
60
  "dependencies": {
61
61
  "lit": "^3.3.2",
62
- "@helixui/tokens": "3.5.0"
62
+ "@helixui/tokens": "3.7.0"
63
63
  },
64
64
  "peerDependencies": {
65
65
  "@floating-ui/dom": "^1.7.6"
@@ -1 +0,0 @@
1
- {"version":3,"file":"hx-badge-JlFtAdxS.js","sources":["../../src/components/hx-badge/hx-badge.styles.ts","../../src/components/hx-badge/hx-badge.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const helixBadgeStyles = css`\n :host {\n display: inline-block;\n }\n\n .badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--hx-space-1, 0.25rem);\n border-radius: var(--hx-badge-border-radius, var(--hx-border-radius-md, 0.375rem));\n background-color: var(--hx-badge-bg, var(--hx-color-primary-500, #429797));\n color: var(--hx-badge-color, var(--hx-color-text-on-primary, #0d1825));\n font-family: var(--hx-badge-font-family, var(--hx-font-family-sans, sans-serif));\n font-weight: var(--hx-badge-font-weight, var(--hx-font-weight-semibold, 600));\n line-height: var(--hx-line-height-tight, 1.25);\n white-space: nowrap;\n vertical-align: middle;\n position: relative;\n }\n\n /* ─── Size Variants ─── */\n\n .badge--sm {\n font-size: var(--hx-badge-font-size, var(--hx-font-size-2xs, 0.625rem));\n padding: var(--hx-badge-padding-y, var(--hx-space-0-5, 0.125rem))\n var(--hx-badge-padding-x, var(--hx-space-1-5, 0.375rem));\n }\n\n .badge--md {\n font-size: var(--hx-badge-font-size, var(--hx-font-size-xs, 0.75rem));\n padding: var(--hx-badge-padding-y, var(--hx-space-1, 0.25rem))\n var(--hx-badge-padding-x, var(--hx-space-2, 0.5rem));\n }\n\n .badge--lg {\n font-size: var(--hx-badge-font-size, var(--hx-font-size-sm, 0.875rem));\n padding: var(--hx-badge-padding-y, var(--hx-space-1, 0.25rem))\n var(--hx-badge-padding-x, var(--hx-space-3, 0.75rem));\n }\n\n /* ─── Style Variants ─── */\n\n .badge--primary {\n --hx-badge-bg: var(--hx-badge-primary-bg, var(--hx-color-primary-500, #429797));\n --hx-badge-color: var(--hx-badge-primary-color, var(--hx-color-text-on-primary, #0d1825));\n --hx-badge-pulse-color-internal: var(\n --hx-badge-pulse-color,\n var(--hx-badge-primary-bg, var(--hx-color-primary-500, #429797))\n );\n }\n\n .badge--secondary {\n --hx-badge-bg: var(--hx-badge-secondary-bg, var(--hx-color-neutral-100, #ebeee9));\n --hx-badge-color: var(--hx-badge-secondary-color, var(--hx-color-neutral-700, #313e4b));\n --hx-badge-pulse-color-internal: var(\n --hx-badge-pulse-color,\n var(--hx-badge-secondary-bg, var(--hx-color-neutral-100, #ebeee9))\n );\n }\n\n .badge--success {\n --hx-badge-bg: var(--hx-badge-success-bg, var(--hx-color-success-700, #146831));\n --hx-badge-color: var(--hx-badge-success-color, var(--hx-color-neutral-0, #ffffff));\n --hx-badge-pulse-color-internal: var(\n --hx-badge-pulse-color,\n var(--hx-badge-success-bg, var(--hx-color-success-700, #146831))\n );\n }\n\n .badge--warning {\n --hx-badge-bg: var(--hx-badge-warning-bg, var(--hx-color-warning-500, #c2711c));\n --hx-badge-color: var(--hx-badge-warning-color, var(--hx-color-neutral-900, #0d1825));\n --hx-badge-pulse-color-internal: var(\n --hx-badge-pulse-color,\n var(--hx-badge-warning-bg, var(--hx-color-warning-500, #c2711c))\n );\n }\n\n .badge--error {\n --hx-badge-bg: var(--hx-badge-error-bg, var(--hx-color-error-500, #e5493e));\n --hx-badge-color: var(--hx-badge-error-color, var(--hx-color-text-on-error, #0d1825));\n --hx-badge-pulse-color-internal: var(\n --hx-badge-pulse-color,\n var(--hx-badge-error-bg, var(--hx-color-error-500, #e5493e))\n );\n }\n\n .badge--neutral {\n --hx-badge-bg: var(--hx-badge-neutral-bg, var(--hx-color-neutral-200, #d6dbd5));\n --hx-badge-color: var(--hx-badge-neutral-color, var(--hx-color-neutral-700, #313e4b));\n --hx-badge-pulse-color-internal: var(\n --hx-badge-pulse-color,\n var(--hx-badge-neutral-bg, var(--hx-color-neutral-200, #d6dbd5))\n );\n }\n\n .badge--info {\n --hx-badge-bg: var(--hx-badge-info-bg, var(--hx-color-info-700, #0e5997));\n --hx-badge-color: var(--hx-badge-info-color, var(--hx-color-neutral-0, #ffffff));\n --hx-badge-pulse-color-internal: var(\n --hx-badge-pulse-color,\n var(--hx-badge-info-bg, var(--hx-color-info-700, #0e5997))\n );\n }\n\n /* ─── Semantic Variant Label (WCAG 1.4.1) ─── */\n /* Visually hidden text prefix for semantic variants (success/warning/error/info). */\n /* Ensures the variant is not conveyed by color alone. */\n\n .badge__variant-label {\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 /* ─── Pill Mode ─── */\n\n .badge--pill {\n border-radius: var(--hx-badge-border-radius, var(--hx-border-radius-full, 9999px));\n }\n\n /* ─── Dot Indicator (empty + pulse) ─── */\n\n .badge--dot {\n width: var(--hx-badge-dot-size, var(--hx-space-2, 0.5rem));\n height: var(--hx-badge-dot-size, var(--hx-space-2, 0.5rem));\n padding: 0;\n border-radius: var(--hx-border-radius-full, 9999px);\n }\n\n /* Guard: hide all inner content in dot mode — the dot is purely visual */\n .badge--dot ::slotted(*),\n .badge--dot slot,\n .badge--dot slot[name='prefix'] {\n display: none !important;\n }\n\n /* ─── Pulse Animation ─── */\n\n @keyframes hx-badge-pulse {\n 0%,\n 100% {\n opacity: 1;\n box-shadow: 0 0 0 2px var(--hx-badge-pulse-color-internal, currentColor);\n }\n 50% {\n opacity: var(--hx-opacity-75, 0.75);\n box-shadow: 0 0 0 6px transparent;\n }\n }\n\n .badge--pulse {\n animation: hx-badge-pulse var(--hx-badge-pulse-duration, var(--hx-duration-slow, 2s))\n var(--hx-badge-pulse-easing, var(--hx-easing-in-out, ease-in-out)) infinite;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .badge--pulse {\n animation: none;\n }\n }\n\n /* ─── Remove Button ─── */\n\n .badge__remove-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: none;\n border: none;\n padding: 0;\n margin-inline-start: var(--hx-space-1, 0.25rem);\n cursor: pointer;\n color: inherit;\n opacity: var(--hx-opacity-75, 0.75);\n border-radius: var(--hx-border-radius-sm, 0.25rem);\n line-height: 0;\n /* WCAG 2.5.5: minimum 44×44px touch target */\n min-width: var(--hx-touch-target-min, 2.75rem);\n min-height: var(--hx-touch-target-min, 2.75rem);\n }\n\n .badge__remove-button:hover {\n opacity: var(--hx-opacity-100, 1);\n }\n\n .badge__remove-button:focus-visible {\n outline: var(--hx-focus-ring-width, 2px) solid var(--hx-focus-ring-color, currentColor);\n outline-offset: var(--hx-focus-ring-offset, 2px);\n }\n\n /* ─── Forced Colors (Windows High Contrast) ─── */\n\n @media (forced-colors: active) {\n .badge {\n border: 1px solid CanvasText;\n forced-color-adjust: none;\n background-color: Canvas;\n color: CanvasText;\n }\n\n .badge--pulse {\n animation: none;\n }\n\n .badge__remove-button {\n color: ButtonText;\n }\n\n /* Per-semantic-variant forced-colors fallbacks. The visually-hidden\n semantic variant label (.badge__variant-label) keeps AT users\n informed; these blocks restore visual semantic distinction for\n sighted users in HCM where bg/color collapse to system defaults.\n Pattern: distinct border-style per variant. */\n .badge--success {\n border-style: solid;\n border-width: 2px;\n }\n\n .badge--warning {\n border-style: dashed;\n border-width: 2px;\n }\n\n .badge--error {\n border-style: double;\n border-width: 3px;\n }\n\n .badge--info {\n border-style: dotted;\n border-width: 2px;\n }\n }\n`;\n","import { html, nothing } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { HelixElement } from '../../base/index.js';\nimport { helixBadgeStyles } from './hx-badge.styles.js';\nimport { forcedColorsSurface } from '../../styles/forced-colors.js';\nimport { devWarn } from '../../utils/dev-warn.js';\n\n/**\n * A small status indicator for notifications, counts, and labels.\n *\n * @summary Presentational badge for status indicators, notification counts, and labels.\n *\n * @tag hx-badge\n *\n * @slot - Default slot for badge content (text, number). When empty with pulse enabled, renders as a dot indicator.\n * @slot prefix - Icon or content rendered before the badge text.\n *\n * @fires {CustomEvent<void>} hx-remove - Dispatched when the user clicks the remove button.\n *\n * @csspart badge - The badge element.\n * @csspart remove-button - The remove/dismiss button.\n *\n * @cssprop [--hx-badge-bg=var(--hx-color-primary-500)] - Badge background color. The primary override point.\n * @cssprop [--hx-badge-color=var(--hx-color-neutral-0)] - Badge text color. The primary override point.\n * @cssprop [--hx-badge-font-size] - Badge font size (set per size variant).\n * @cssprop [--hx-badge-font-weight=var(--hx-font-weight-semibold)] - Badge font weight.\n * @cssprop [--hx-badge-font-family=var(--hx-font-family-sans)] - Badge font family.\n * @cssprop [--hx-badge-border-radius=var(--hx-border-radius-md)] - Badge border radius.\n * @cssprop [--hx-badge-padding-x] - Badge horizontal padding (set per size variant).\n * @cssprop [--hx-badge-padding-y] - Badge vertical padding (set per size variant).\n * @cssprop [--hx-badge-pulse-color] - Pulse color matching variant background with reduced opacity.\n * @cssprop [--hx-badge-dot-size=var(--hx-size-2)] - Dot indicator size when rendered without content.\n * @cssprop [--hx-badge-secondary-bg=var(--hx-color-neutral-100)] - Background for the secondary variant.\n * @cssprop [--hx-badge-secondary-color=var(--hx-color-neutral-700)] - Text color for the secondary variant.\n * @cssprop [--hx-badge-info-bg=var(--hx-color-info-700)] - Background for the info variant.\n * @cssprop [--hx-badge-info-color=var(--hx-color-neutral-0)] - Text color for the info variant.\n * @cssprop [--hx-space-1] - Spacing token.\n * @cssprop [--hx-border-radius-md] - CSS custom property.\n * @cssprop [--hx-color-primary-500] - Color.\n * @cssprop [--hx-color-neutral-0] - Color.\n * @cssprop [--hx-font-family-sans] - Font family.\n * @cssprop [--hx-font-weight-semibold] - Font weight.\n * @cssprop [--hx-line-height-tight] - Line height.\n * @cssprop [--hx-font-size-2xs] - Font size.\n * @cssprop [--hx-space-0-5] - Spacing token.\n * @cssprop [--hx-space-1-5] - Spacing token.\n * @cssprop [--hx-font-size-xs] - Font size.\n * @cssprop [--hx-space-2] - Spacing token.\n * @cssprop [--hx-font-size-sm] - Font size.\n * @cssprop [--hx-space-3] - Spacing token.\n * @cssprop [--hx-badge-pulse-color-internal] - Color.\n * @cssprop [--hx-color-neutral-100] - Color.\n * @cssprop [--hx-color-neutral-700] - Color.\n * @cssprop [--hx-color-success-700] - Color.\n * @cssprop [--hx-color-warning-500] - Color.\n * @cssprop [--hx-color-neutral-900] - Color.\n * @cssprop [--hx-color-error-500] - Color.\n * @cssprop [--hx-color-neutral-200] - Color.\n * @cssprop [--hx-color-info-700] - Color.\n * @cssprop [--hx-border-radius-full] - CSS custom property.\n * @cssprop [--hx-opacity-75] - Opacity.\n * @cssprop [--hx-badge-pulse-duration=var(--hx-duration-slow)] - CSS custom property.\n * @cssprop [--hx-duration-slow] - Animation duration.\n * @cssprop [--hx-badge-pulse-easing=var(--hx-easing-in-out)] - CSS custom property.\n * @cssprop [--hx-easing-in-out] - CSS custom property.\n * @cssprop [--hx-border-radius-sm] - CSS custom property.\n * @cssprop [--hx-touch-target-min] - Minimum touch target size.\n * @cssprop [--hx-opacity-100] - Opacity.\n * @cssprop [--hx-focus-ring-color] - Color.\n * @cssprop [--hx-focus-ring-width] - Width.\n * @cssprop [--hx-focus-ring-offset] - CSS custom property.\n */\n@customElement('hx-badge')\nexport class HelixBadge extends HelixElement {\n static override styles = [helixBadgeStyles, forcedColorsSurface];\n\n /**\n * Visual style variant of the badge.\n * @attr variant\n */\n @property({ type: String, reflect: true })\n variant: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'neutral' | 'info' =\n 'primary';\n\n /**\n * Size of the badge.\n * @attr hx-size\n */\n @property({ type: String, reflect: true, attribute: 'hx-size' })\n size: 'sm' | 'md' | 'lg' = 'md';\n\n /**\n * Whether the badge uses fully rounded (pill) styling.\n * @attr pill\n */\n @property({ type: Boolean, reflect: true })\n pill = false;\n\n /**\n * Whether the badge displays an animated pulse for attention.\n * @attr pulse\n */\n @property({ type: Boolean, reflect: true })\n pulse = false;\n\n /**\n * Whether the badge renders a dismiss button.\n * @attr removable\n */\n @property({ type: Boolean, reflect: true })\n removable = false;\n\n /**\n * Numeric count to display. When set, renders the count as badge content.\n * When count exceeds `max`, displays `${max}+` (e.g. `99+`).\n * @attr count\n */\n @property({ type: Number, reflect: true })\n count: number | undefined = undefined;\n\n /**\n * Maximum count value before truncation to `${max}+`. Defaults to 99.\n * @attr max\n */\n @property({ type: Number, reflect: true })\n max = 99;\n\n /**\n * Accessible label for the dot indicator mode (pulse + empty slot).\n * Required for WCAG 4.1.2 compliance when using the dot indicator pattern.\n * Example: `dot-label=\"3 new messages\"`.\n * @attr dot-label\n */\n @property({ type: String, attribute: 'dot-label' })\n dotLabel = '';\n\n /**\n * Accessible label for the remove button. Should describe what is being removed.\n * Defaults to \"Remove\". For better accessibility, include context: e.g. \"Remove Critical badge\".\n * @attr remove-label\n */\n @property({ type: String, attribute: 'remove-label' })\n removeLabel = 'Remove';\n\n /**\n * Tracks whether the default slot has assigned content.\n * @internal\n */\n @state()\n private _hasSlotContent = false;\n\n // ─── Lifecycle ───\n\n override connectedCallback(): void {\n super.connectedCallback();\n // Backward compat: accept legacy `size` attribute. When present and `hx-size`\n // is not set, map the value and emit a deprecation warning.\n const legacySize = this.getAttribute('size');\n if (legacySize !== null && !this.hasAttribute('hx-size')) {\n devWarn('hx-badge', 'The \"size\" attribute is deprecated. Use \"hx-size\" instead.');\n this.size = legacySize as 'sm' | 'md' | 'lg';\n }\n }\n\n // ─── Slot Change Handling ───\n\n /** @internal */\n private _handleSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n const nodes = slot.assignedNodes({ flatten: true });\n // Check if any assigned node has non-whitespace content\n this._hasSlotContent = nodes.some((node) => {\n if (node.nodeType === Node.ELEMENT_NODE) return true;\n if (node.nodeType === Node.TEXT_NODE) {\n return (node.textContent ?? '').trim().length > 0;\n }\n return false;\n });\n }\n\n // ─── Event Handling ───\n\n /** @internal */\n private _handleRemove(): void {\n this.dispatchEvent(\n new CustomEvent<void>('hx-remove', {\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n // ─── Count Display ───\n\n /** @internal */\n private get _countDisplay(): string {\n if (this.count === undefined) return '';\n return this.count > this.max ? `${this.max}+` : String(this.count);\n }\n\n // ─── WCAG 1.4.1: Semantic variant label map ───\n // Variants that carry semantic meaning beyond decoration need a non-color cue.\n // Visually-hidden text is prepended so screen reader users and color-blind\n // users get the variant context even when no icon is slotted.\n\n /** @internal */\n private static readonly _SEMANTIC_VARIANT_LABELS: Partial<Record<HelixBadge['variant'], string>> =\n {\n success: 'Success',\n warning: 'Warning',\n error: 'Error',\n info: 'Info',\n };\n\n /** @internal */\n private get _semanticVariantLabel(): string {\n return HelixBadge._SEMANTIC_VARIANT_LABELS[this.variant] ?? '';\n }\n\n // ─── Render ───\n\n override render() {\n const hasCount = this.count !== undefined;\n const isDot = !this._hasSlotContent && !hasCount && this.pulse;\n\n if (isDot && !this.dotLabel) {\n devWarn(\n 'hx-badge',\n 'Dot badge is missing an accessible label. Set `dotLabel` so screen readers can announce the badge purpose (WCAG 4.1.2).',\n );\n }\n\n const classes = {\n badge: true,\n [`badge--${this.variant}`]: true,\n [`badge--${this.size}`]: true,\n 'badge--pill': this.pill,\n 'badge--pulse': this.pulse,\n 'badge--dot': isDot,\n };\n\n const variantLabel = this._semanticVariantLabel;\n\n return html`\n <span\n part=\"badge\"\n class=${classMap(classes)}\n role=${isDot && this.dotLabel ? 'img' : nothing}\n aria-label=${isDot && this.dotLabel ? this.dotLabel : nothing}\n aria-live=${hasCount ? 'polite' : nothing}\n >\n ${variantLabel\n ? html`<span class=\"badge__variant-label\">${variantLabel}: </span>`\n : nothing}\n ${isDot ? nothing : html`<slot name=\"prefix\"></slot>`}\n ${hasCount ? this._countDisplay : html`<slot @slotchange=${this._handleSlotChange}></slot>`}\n ${this.removable\n ? html`<button\n part=\"remove-button\"\n class=\"badge__remove-button\"\n aria-label=${this.removeLabel}\n @click=${this._handleRemove}\n >\n <svg viewBox=\"0 0 12 12\" aria-hidden=\"true\" width=\"10\" height=\"10\">\n <path\n d=\"M2.22 2.22a.75.75 0 011.06 0L6 4.94l2.72-2.72a.75.75 0 011.06 1.06L7.06 6l2.72 2.72a.75.75 0 01-1.06 1.06L6 7.06 3.28 9.78a.75.75 0 01-1.06-1.06L4.94 6 2.22 3.28a.75.75 0 010-1.06z\"\n fill=\"currentColor\"\n />\n </svg>\n </button>`\n : nothing}\n </span>\n `;\n }\n}\n\n/** Canonical type alias for the hx-badge component. */\nexport type HxBadge = HelixBadge;\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-badge': HelixBadge;\n }\n}\n"],"names":["helixBadgeStyles","css","HelixBadge","HelixElement","legacySize","e","nodes","node","hasCount","isDot","classes","variantLabel","html","classMap","nothing","forcedColorsSurface","__decorateClass","property","state","customElement"],"mappings":";;;;;AAEO,MAAMA,IAAmBC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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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"}