@helixui/library 3.4.1 → 3.5.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toast-factory-Dht3pVsw.js","sources":["../../src/components/hx-toast/hx-toast.styles.ts","../../src/components/hx-toast/hx-toast.ts","../../src/components/hx-toast/hx-toast-stack.ts","../../src/components/hx-toast/toast-factory.ts"],"sourcesContent":["import { css } from 'lit';\n\n/**\n * hx-toast styles.\n *\n * Component-tier tokens with two-level var() fallback:\n * var(--hx-toast-{prop}, var(--hx-color-{semantic}, #hex))\n * Inner hex fallbacks track the \"precision cool\" palette (3.2.0):\n * neutral-0 = #FFFFFF, neutral-900 = #0D1825 (surface-inverse anchor),\n * primary-600 = #0F7078, success-600 = #0E8A4A, warning-500 = #C2711C,\n * error-600 = #C92A2A.\n */\nexport const helixToastStyles = css`\n /* ─── hx-toast host ─── */\n\n :host {\n display: block;\n pointer-events: none;\n }\n\n :host([open]) {\n pointer-events: auto;\n }\n\n /* ─── Toast base ─── */\n\n .toast {\n display: flex;\n align-items: flex-start;\n gap: var(--hx-space-3, 0.75rem);\n padding: var(--hx-space-3, 0.75rem) var(--hx-space-4, 1rem);\n border-radius: var(--hx-toast-border-radius, var(--hx-border-radius-md, 0.375rem));\n background-color: var(--hx-toast-bg, var(--hx-color-surface-inverse, #0d1825));\n color: var(--hx-toast-color, var(--hx-color-text-inverse, #ffffff));\n font-family: var(--hx-toast-font-family, var(--hx-font-family-sans, sans-serif));\n font-size: var(--hx-font-size-sm, 0.875rem);\n line-height: var(--hx-line-height-normal, 1.5);\n box-shadow: var(\n --hx-toast-shadow,\n var(--hx-shadow-md, 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1))\n );\n opacity: 0;\n transform: translateY(var(--hx-toast-enter-translate, var(--hx-space-2, 0.5rem)));\n transition:\n opacity var(--hx-transition-normal, 250ms ease),\n transform var(--hx-transition-normal, 250ms ease);\n width: var(--hx-toast-width, 20rem);\n max-width: 100%;\n pointer-events: auto;\n }\n\n :host([open]) .toast {\n opacity: 1;\n transform: translateY(0);\n }\n\n /* ─── Variant overrides ─── */\n\n /*\n * Toast variants paint on darker brand fills (primary-600/success-700/\n * error-600) because the lighter -500 fills can't pass AA against white\n * text in the precision-cool palette. The neutral-900 on-{role} tokens\n * are tuned for the lighter -500 surfaces and would fail here (e.g.\n * neutral-900 on primary-600 = 3.07:1), so the on-{role}-strong tokens\n * (neutral-0, no dark-mode flip) keep fg legible on the darker fills.\n * - text.on-primary-strong on info.bg-strong (primary-600, #0F7078) = 5.82:1\n * - text.on-success-strong on success.bg-strong (success-700, #146831) = 6.88:1\n * (success-600 #0E8A4A on white = 4.42:1 — drifts under AA at 14px)\n * - text.on-error-strong on danger.bg-strong (error-600, #C92A2A) = 5.46:1\n * - text.on-warning on warning.bg-strong (warning-500, #C2711C) = 4.83:1\n * (warning stays on the lighter -500 surface so on-warning works)\n *\n * 3.2.1 token-cascade: bg variants now route through surface.{role}-strong\n * semantics; fg variants route through text.on-{role}-strong (or on-warning\n * for the warning variant). Component-tier tokens are NOT bypassed — the\n * --hx-toast-bg / --hx-toast-color slots remain the single override point.\n */\n .toast--success {\n --hx-toast-bg: var(--hx-color-surface-success-strong, #146831);\n --hx-toast-color: var(--hx-color-text-on-success-strong, #ffffff);\n }\n\n .toast--warning {\n --hx-toast-bg: var(--hx-color-surface-warning-strong, #c2711c);\n --hx-toast-color: var(--hx-color-text-on-warning, #0d1825);\n }\n\n .toast--danger {\n --hx-toast-bg: var(--hx-color-surface-danger-strong, #c92a2a);\n --hx-toast-color: var(--hx-color-text-on-error-strong, #ffffff);\n }\n\n .toast--info {\n --hx-toast-bg: var(--hx-color-surface-info-strong, #0f7078);\n --hx-toast-color: var(--hx-color-text-on-primary-strong, #ffffff);\n }\n\n /* ─── Severity Label (WCAG 1.4.1) ─── */\n /* Visually hidden — non-color cue for severity variants (success/warning/danger/info). */\n /* Ensures variant is not conveyed by color alone for color-blind users. */\n\n .toast__severity-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 /* ─── Icon ─── */\n\n .toast__icon {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n line-height: 1;\n }\n\n .toast__icon:empty {\n display: none;\n }\n\n /* ─── Message ─── */\n\n .toast__message {\n flex: 1 1 auto;\n min-width: 0;\n }\n\n /* ─── Action slot ─── */\n\n .toast__action {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n }\n\n .toast__action:empty {\n display: none;\n }\n\n /* ─── Close button ─── */\n\n .toast__close {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\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 padding: var(--hx-space-1, 0.25rem);\n background: transparent;\n border: none;\n border-radius: var(--hx-border-radius-sm, 0.25rem);\n color: inherit;\n cursor: pointer;\n opacity: var(--hx-opacity-75, 0.75);\n transition: opacity var(--hx-transition-fast, 150ms ease);\n }\n\n .toast__close:hover {\n opacity: 1;\n }\n\n .toast__close:focus-visible {\n outline: var(--hx-focus-ring-width, 2px) solid currentColor;\n outline-offset: var(--hx-focus-ring-offset, 2px);\n }\n\n /* ─── Reduced motion ─── */\n\n @media (prefers-reduced-motion: reduce) {\n .toast {\n transition: none;\n }\n\n .toast__close {\n transition: none;\n }\n }\n\n /* ─── Forced Colors (Windows High Contrast) ─── */\n /* Sole owner: bespoke per-class HC overrides on .toast and .toast__close.\n The shared forcedColorsSurface mixin was dropped in 3.2.1 (XOR rule);\n this block now carries the surface contract the mixin used to provide\n (background:Canvas + color:CanvasText + border:1px solid CanvasText)\n plus the per-class affordances the mixin never touched. */\n\n @media (forced-colors: active) {\n .toast {\n background: Canvas;\n color: CanvasText;\n border: 1px solid CanvasText;\n }\n\n .toast__close {\n color: ButtonText;\n border: 1px solid ButtonText;\n }\n }\n`;\n\nexport const helixToastStackStyles = css`\n :host {\n display: block;\n position: fixed;\n z-index: var(--hx-z-index-toast, 1700);\n pointer-events: none;\n }\n\n .toast-stack {\n display: flex;\n flex-direction: column;\n gap: var(--hx-space-3, 0.75rem);\n padding: var(--hx-space-4, 1rem);\n pointer-events: none;\n }\n\n /* ─── Placements ─── */\n\n :host([placement='top-start']) {\n top: 0;\n inset-inline-start: 0;\n inset-inline-end: auto;\n bottom: auto;\n }\n\n :host([placement='top-center']) {\n top: 0;\n inset-inline-start: 50%;\n transform: translateX(-50%);\n inset-inline-end: auto;\n bottom: auto;\n }\n\n :host([placement='top-end']) {\n top: 0;\n inset-inline-end: 0;\n inset-inline-start: auto;\n bottom: auto;\n }\n\n :host([placement='bottom-start']) {\n bottom: 0;\n inset-inline-start: 0;\n inset-inline-end: auto;\n top: auto;\n }\n\n :host([placement='bottom-center']) {\n bottom: 0;\n inset-inline-start: 50%;\n transform: translateX(-50%);\n inset-inline-end: auto;\n top: auto;\n }\n\n :host([placement='bottom-end']) {\n bottom: 0;\n inset-inline-end: 0;\n inset-inline-start: auto;\n top: auto;\n }\n\n /* ─── Bottom placements: reverse order so newest is on top ─── */\n\n :host([placement^='bottom']) .toast-stack {\n flex-direction: column-reverse;\n }\n\n /* ─── Slide direction by placement ─── */\n\n :host([placement^='top']) ::slotted(hx-toast) {\n --hx-toast-enter-translate: calc(var(--hx-space-2, 0.5rem) * -1);\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 { HelixElement } from '../../base/index.js';\nimport { devWarn } from '../../utils/dev-warn.js';\nimport { helixToastStyles } from './hx-toast.styles.js';\n\nexport type ToastVariant = 'default' | 'success' | 'warning' | 'danger' | 'info';\n\n// (group-6) WCAG 2.2.3 minimum display times by variant. When `duration > 0`\n// and shorter than the role-implied minimum, a devWarn fires recommending the\n// caller either lengthen `duration` or set `duration=0` for persistent\n// toasts. AT cannot reliably finish reading a danger announcement in less\n// than ~6 seconds; success/info polite announcements need at least ~3s.\nconst MIN_DISPLAY_MS_BY_VARIANT: Record<ToastVariant, number> = {\n default: 3000,\n info: 3000,\n success: 3000,\n warning: 4000,\n danger: 6000,\n};\n\n/**\n * A transient notification message that auto-dismisses after a configurable duration.\n * Supports multiple visual variants, a closable button, icon/action slots, and full\n * ARIA live region semantics for screen readers.\n *\n * @summary Transient notification toast component.\n *\n * @tag hx-toast\n *\n * @slot - Default slot for the notification message.\n * @slot icon - Optional icon rendered before the message.\n * @slot action - Optional action button rendered after the message.\n *\n * @fires {CustomEvent} hx-show - Dispatched when the toast becomes visible.\n * @fires {CustomEvent} hx-hide - Dispatched when the toast begins hiding.\n * @fires {CustomEvent} hx-after-hide - Dispatched after the hide animation completes.\n *\n * @csspart base - The inner toast container div.\n * @csspart icon - The icon slot wrapper.\n * @csspart message - The message slot wrapper.\n * @csspart close-button - The dismiss button (only when closable).\n * @csspart action - The action slot wrapper.\n *\n * @cssprop [--hx-toast-bg=var(--hx-color-surface-inverse)] - Toast background color.\n * @cssprop [--hx-toast-color=var(--hx-color-text-inverse)] - Toast text color.\n * @cssprop [--hx-toast-border-radius=var(--hx-border-radius-md)] - Toast border radius.\n * @cssprop [--hx-toast-shadow] - Toast box shadow.\n * @cssprop [--hx-toast-width=20rem] - Toast width.\n * @cssprop [--hx-space-3] - Spacing token.\n * @cssprop [--hx-space-4] - Spacing token.\n * @cssprop [--hx-border-radius-md] - CSS custom property.\n * @cssprop [--hx-color-surface-inverse] - Default-variant background semantic (neutral-900 anchor; flipped per mode).\n * @cssprop [--hx-color-text-inverse] - Default-variant foreground semantic (neutral-0 anchor; flipped per mode).\n * @cssprop [--hx-toast-font-family=var(--hx-font-family-sans)] - CSS custom property.\n * @cssprop [--hx-font-family-sans] - Font family.\n * @cssprop [--hx-font-size-sm] - Font size.\n * @cssprop [--hx-line-height-normal] - Line height.\n * @cssprop [--hx-shadow-md] - Box shadow.\n * @cssprop [--hx-toast-enter-translate=var(--hx-space-2)] - CSS custom property.\n * @cssprop [--hx-space-2] - Spacing token.\n * @cssprop [--hx-transition-normal] - Transition timing.\n * @cssprop [--hx-color-surface-success-strong] - Success-variant background semantic (3.2.1 cascade).\n * @cssprop [--hx-color-surface-warning-strong] - Warning-variant background semantic (3.2.1 cascade).\n * @cssprop [--hx-color-surface-danger-strong] - Danger-variant background semantic (3.2.1 cascade).\n * @cssprop [--hx-color-surface-info-strong] - Info-variant background semantic (3.2.1 cascade).\n * @cssprop [--hx-color-text-on-success-strong] - Success-variant foreground semantic (neutral-0 across modes).\n * @cssprop [--hx-color-text-on-warning] - Warning-variant foreground semantic (neutral-900; warning sits on lighter -500 fill).\n * @cssprop [--hx-color-text-on-error-strong] - Danger-variant foreground semantic (neutral-0 across modes).\n * @cssprop [--hx-color-text-on-primary-strong] - Info-variant foreground semantic (neutral-0 across modes).\n * @cssprop [--hx-touch-target-min] - Minimum touch target size.\n * @cssprop [--hx-space-1] - Spacing token.\n * @cssprop [--hx-border-radius-sm] - CSS custom property.\n * @cssprop [--hx-opacity-75] - Opacity.\n * @cssprop [--hx-transition-fast] - Transition timing.\n * @cssprop [--hx-focus-ring-width] - Width.\n * @cssprop [--hx-focus-ring-offset] - CSS custom property.\n */\n@customElement('hx-toast')\nexport class HelixToast extends HelixElement {\n // 3.2.1: forced-colors deference is owned by the bespoke @media block in\n // hx-toast.styles.ts (per-class overrides on .toast and .toast__close —\n // strictly more than the shared mixin's :host/[part] surface contract).\n // XOR rule per styles/forced-colors.ts: do NOT also compose\n // forcedColorsSurface — overlapping selectors inside the same media query\n // create a fragile specificity war.\n static override styles = [helixToastStyles];\n\n // ─── Public Properties ───\n\n /**\n * Visual style variant.\n * @attr variant\n */\n @property({ type: String, reflect: true })\n variant: 'default' | 'success' | 'warning' | 'danger' | 'info' = 'default';\n\n /**\n * Auto-dismiss duration in milliseconds. Set to 0 for persistent toasts.\n * @attr duration\n */\n @property({ type: Number })\n duration = 5000;\n\n /**\n * Whether to show a close button.\n * @attr closable\n */\n @property({ type: Boolean, reflect: true })\n closable = false;\n\n /**\n * Whether the toast is currently visible.\n * @attr open\n */\n @property({ type: Boolean, reflect: true })\n open = false;\n\n /**\n * Accessible label for the close button. Override for localization.\n * @attr label-close\n */\n @property({ attribute: 'label-close' })\n labelClose = 'Dismiss notification';\n\n // ─── Private State ───\n\n /** @internal */\n @query('.toast') private _toastEl!: HTMLElement | null;\n\n /** @internal Tracks whether the action slot has slotted content. */\n @state() private _hasActionContent = false;\n\n /** @internal */\n private _timer: ReturnType<typeof setTimeout> | null = null;\n\n // ─── Reduced Motion ───\n\n /** @internal Returns true when the user has opted into reduced motion. */\n private get _reducedMotion(): boolean {\n // Guard for SSR — window.matchMedia is unavailable server-side\n if (typeof window === 'undefined') return false;\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n }\n\n /** @internal */\n private _timerStartedAt: number | null = null;\n\n /** @internal */\n private _timerRemaining: number | null = null;\n\n // ─── Lifecycle ───\n\n override connectedCallback(): void {\n super.connectedCallback();\n // (group-6) Host-canonical role + aria-atomic via ElementInternals.\n // role implies aria-live per ARIA spec (alert→assertive, status→polite),\n // so we do NOT also set explicit aria-live (avoids §5.1 double-announce\n // on older NVDA/JAWS).\n //\n // (group-6 codex round-1) Dual-write: keep an attribute fallback for\n // `role` on the host, harmonized with hx-alert/hx-banner. Some AT +\n // browser combos still ignore internals-backed ARIA reflection on\n // custom elements (notably older JAWS + Chrome and certain\n // VoiceOver+Safari builds); without an attribute they would stop\n // announcing toasts entirely. role implies aria-live, so we do NOT\n // also write an explicit aria-live attribute (the implicit live region\n // is what `role` already provides — adding aria-live duplicates the\n // signal and re-introduces the §5.1 double-announce on those same\n // older AT+browser combos).\n this._internals.role = this._role;\n this._internals.ariaAtomic = 'true';\n this.setAttribute('role', this._role);\n }\n\n override updated(changedProperties: PropertyValues<this>): void {\n super.updated(changedProperties);\n if (changedProperties.has('variant')) {\n // Keep host role in sync with variant (alert vs status).\n // Dual-write: internals (modern) + attribute (legacy fallback).\n this._internals.role = this._role;\n this.setAttribute('role', this._role);\n }\n if (changedProperties.has('open') || changedProperties.has('duration')) {\n this._auditWcag223();\n }\n if (changedProperties.has('open')) {\n if (this.open) {\n this.removeAttribute('aria-hidden');\n this._emitShow();\n if (this.duration > 0 && !this._reducedMotion && !this._hasActionContent) {\n this._startTimer();\n }\n } else {\n this.setAttribute('aria-hidden', 'true');\n this._clearTimer();\n this._emitHide();\n }\n }\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this._clearTimer();\n }\n\n /**\n * (group-6 §5.3) WCAG 2.2.3 (Level AA — No Time Limits) audit. When\n * `duration` is shorter than the role-implied minimum-display-time for the\n * current variant, surface a developer warning recommending a longer\n * duration or `duration=0` for persistent toasts. Danger toasts in\n * particular are safety-critical and routinely need more than 5 seconds\n * for screen readers to finish reading.\n *\n * Fires only in DEV builds (Vite tree-shakes the call in production).\n *\n * @internal\n */\n private _auditWcag223(): void {\n if (this.duration <= 0) return;\n const min = MIN_DISPLAY_MS_BY_VARIANT[this.variant] ?? 0;\n if (this.duration < min) {\n devWarn(\n 'hx-toast',\n `duration=${this.duration}ms is shorter than the WCAG 2.2.3 minimum (${min}ms) for variant=\"${this.variant}\". Increase duration or set duration=0 for persistent toasts (recommended for variant=\"danger\").`,\n );\n }\n }\n\n // ─── Public API ───\n\n /** Show the toast. */\n show(): void {\n if (!this.open) {\n this.open = true;\n }\n }\n\n /** Hide the toast. */\n hide(): void {\n if (this.open) {\n this.open = false;\n }\n }\n\n // ─── Private Helpers ───\n\n /** @internal */\n private _startTimer(remaining?: number): void {\n this._clearTimerHandle();\n const delay = remaining ?? this.duration;\n this._timerStartedAt = Date.now();\n this._timerRemaining = delay;\n this._timer = setTimeout(() => {\n this.open = false;\n }, delay);\n }\n\n /** @internal */\n private _pauseTimer(): void {\n if (this._timer === null || this._timerStartedAt === null || this._timerRemaining === null) {\n return;\n }\n const elapsed = Date.now() - this._timerStartedAt;\n this._timerRemaining = Math.max(0, this._timerRemaining - elapsed);\n this._clearTimerHandle();\n }\n\n /** @internal */\n private _clearTimerHandle(): void {\n if (this._timer !== null) {\n clearTimeout(this._timer);\n this._timer = null;\n }\n }\n\n /** @internal */\n private _clearTimer(): void {\n this._clearTimerHandle();\n this._timerStartedAt = null;\n this._timerRemaining = null;\n }\n\n /** @internal */\n private _emitShow(): void {\n this.dispatchEvent(new CustomEvent<void>('hx-show', { bubbles: true, composed: true }));\n }\n\n /** @internal */\n private _emitHide(): void {\n this.dispatchEvent(new CustomEvent<void>('hx-hide', { bubbles: true, composed: true }));\n\n let fired = false;\n const fireAfterHide = () => {\n if (fired) return;\n fired = true;\n this.dispatchEvent(new CustomEvent<void>('hx-after-hide', { bubbles: true, composed: true }));\n };\n\n // Fire on transitionend if available; fallback ensures it fires in test environments\n // and when transitions are disabled (prefers-reduced-motion, no CSS transitions).\n const base = this._toastEl;\n if (base) {\n base.addEventListener('transitionend', fireAfterHide, { once: true });\n }\n // Fallback fires after the CSS transition duration (250ms) plus a small buffer.\n setTimeout(fireAfterHide, 300);\n }\n\n // ─── Event Handlers ───\n\n /** @internal */\n private _handleMouseEnter(): void {\n this._pauseTimer();\n }\n\n /** @internal */\n private _handleMouseLeave(): void {\n if (this.open && this.duration > 0) {\n this._startTimer(this._timerRemaining ?? undefined);\n }\n }\n\n /** @internal */\n private _handleFocusIn(): void {\n this._pauseTimer();\n }\n\n /** @internal */\n private _handleFocusOut(): void {\n if (this.open && this.duration > 0) {\n this._startTimer(this._timerRemaining ?? undefined);\n }\n }\n\n /** @internal */\n private _handleClose(): void {\n this.hide();\n }\n\n /** @internal */\n private _handleActionSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n this._hasActionContent = slot.assignedNodes({ flatten: true }).length > 0;\n if (this._hasActionContent && this.open) {\n this._pauseTimer();\n }\n }\n\n // ─── ARIA Helpers ───\n\n /**\n * @internal\n * Variant→role mapping (harmonized with hx-alert/hx-banner per group-6):\n * only `danger` is assertive (role=alert); all other variants are polite\n * (role=status). role implies aria-live, so we do NOT also expose an\n * `_ariaLive` getter — that path was removed when the inner div stopped\n * carrying explicit aria-live (§5.1 double-announce mitigation).\n */\n private get _role(): 'alert' | 'status' {\n return this.variant === 'danger' ? 'alert' : 'status';\n }\n\n // ─── WCAG 1.4.1: Default Icons ───\n // Each semantic variant renders a default icon when no icon is slotted,\n // ensuring the severity is not conveyed by color alone.\n\n /** @internal */\n private _renderSuccessIcon() {\n return html`<svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" width=\"16\" height=\"16\">\n <path\n fill=\"currentColor\"\n d=\"M10 2a8 8 0 100 16 8 8 0 000-16zm3.03 6.28a.75.75 0 00-1.06-1.06L9 10.19 7.78 8.97a.75.75 0 00-1.06 1.06l1.75 1.75a.75.75 0 001.06 0l3.5-3.5z\"\n />\n </svg>`;\n }\n\n /** @internal */\n private _renderWarningIcon() {\n return html`<svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" width=\"16\" height=\"16\">\n <path\n fill=\"currentColor\"\n d=\"M8.49 2.92a1.75 1.75 0 013.02 0l6.25 10.83A1.75 1.75 0 0116.25 16H3.75a1.75 1.75 0 01-1.51-2.25L8.49 2.92zM10 7a.75.75 0 01.75.75v3a.75.75 0 01-1.5 0v-3A.75.75 0 0110 7zm0 7.5a.75.75 0 100-1.5.75.75 0 000 1.5z\"\n />\n </svg>`;\n }\n\n /** @internal */\n private _renderDangerIcon() {\n return html`<svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" width=\"16\" height=\"16\">\n <path\n fill=\"currentColor\"\n d=\"M10 2a8 8 0 100 16 8 8 0 000-16zm-1.72 5.22a.75.75 0 011.06 0L10 7.94l.66-.72a.75.75 0 111.06 1.06L11.06 9l.66.72a.75.75 0 11-1.06 1.06L10 10.06l-.66.72a.75.75 0 01-1.06-1.06L8.94 9l-.66-.72a.75.75 0 010-1.06z\"\n />\n </svg>`;\n }\n\n /** @internal */\n private _renderInfoIcon() {\n return html`<svg viewBox=\"0 0 20 20\" aria-hidden=\"true\" width=\"16\" height=\"16\">\n <path\n fill=\"currentColor\"\n d=\"M10 2a8 8 0 100 16 8 8 0 000-16zm.75 4.75a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM9.25 9a.75.75 0 011.5 0v4a.75.75 0 01-1.5 0V9z\"\n />\n </svg>`;\n }\n\n /** @internal Returns the default icon for the current variant, or nothing for 'default'. */\n private get _defaultIcon() {\n switch (this.variant) {\n case 'success':\n return this._renderSuccessIcon();\n case 'warning':\n return this._renderWarningIcon();\n case 'danger':\n return this._renderDangerIcon();\n case 'info':\n return this._renderInfoIcon();\n default:\n return nothing;\n }\n }\n\n // ─── WCAG 1.4.1: Severity label map ───\n\n /** @internal */\n private static readonly _SEVERITY_LABELS: Partial<Record<ToastVariant, string>> = {\n success: 'Success',\n warning: 'Warning',\n danger: 'Error',\n info: 'Info',\n };\n\n /** @internal */\n private get _severityLabel(): string {\n return HelixToast._SEVERITY_LABELS[this.variant] ?? '';\n }\n\n // ─── Render ───\n //\n // (group-6) Host-canonical live region: role + aria-atomic live on the\n // host via ElementInternals (set in connectedCallback). The inner base\n // div is a presentation-only wrapper — it does NOT carry role, aria-live,\n // or aria-atomic. role implies aria-live per ARIA spec, so the host has\n // NO explicit aria-live attribute either (avoids §5.1 double-announce on\n // older NVDA/JAWS that read both role and aria-live as separate live\n // regions).\n\n override render() {\n const severityLabel = this._severityLabel;\n\n return html`\n <div\n part=\"base\"\n class=${classMap({\n toast: true,\n [`toast--${this.variant}`]: true,\n })}\n @mouseenter=${this._handleMouseEnter}\n @mouseleave=${this._handleMouseLeave}\n @focusin=${this._handleFocusIn}\n @focusout=${this._handleFocusOut}\n >\n ${this.open\n ? html`\n ${severityLabel\n ? html`<span class=\"toast__severity-label\">${severityLabel}: </span>`\n : nothing}\n <span part=\"icon\" class=\"toast__icon\">\n <slot name=\"icon\">${this._defaultIcon}</slot>\n </span>\n <span part=\"message\" class=\"toast__message\">\n <slot></slot>\n </span>\n <span part=\"action\" class=\"toast__action\">\n <slot name=\"action\" @slotchange=${this._handleActionSlotChange}></slot>\n </span>\n ${this.closable\n ? html`\n <button\n part=\"close-button\"\n class=\"toast__close\"\n aria-label=${this.labelClose}\n @click=${this._handleClose}\n >\n <svg\n aria-hidden=\"true\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"M18 6L6 18M6 6l12 12\" />\n </svg>\n </button>\n `\n : nothing}\n `\n : nothing}\n </div>\n `;\n }\n}\n\n// ─── Declarative Global Types ───\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-toast': HelixToast;\n }\n}\n","import { html } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property } from 'lit/decorators.js';\nimport { classMap } from 'lit/directives/class-map.js';\nimport { HelixElement } from '../../base/index.js';\nimport { helixToastStackStyles } from './hx-toast.styles.js';\n\nexport type ToastStackPlacement =\n | 'top-start'\n | 'top-center'\n | 'top-end'\n | 'bottom-start'\n | 'bottom-center'\n | 'bottom-end';\n\n/**\n * A fixed-position container that stacks `hx-toast` elements at the specified\n * corner of the viewport. Enforces a maximum visible toast count via `stack-limit`.\n *\n * @summary Toast stack container managing position and count limits.\n *\n * @tag hx-toast-stack\n *\n * @slot - Accepts `hx-toast` elements.\n *\n * @csspart base - The inner stack container div.\n *\n * @cssprop [--hx-z-index-toast=9000] - Z-index for the fixed stack.\n *\n * ─── ARIA scope (group-6 §3.2 / §5.9) ─────────────────────────────────────\n *\n * `hx-toast-stack` deliberately has NO `role`, `aria-live`, `aria-atomic`,\n * or `aria-relevant` on its host or inner container. Each child `hx-toast`\n * is its own live region (role=alert/status via ElementInternals). Wrapping\n * those toasts in a second live region (e.g. `role=\"log\"` on the stack)\n * would create nested live regions, which causes older NVDA/JAWS to\n * announce every toast TWICE — once for the toast's own role, once for the\n * surrounding log region.\n *\n * The stack is purely a positional/z-index container; it is invisible to\n * the AT tree. Do NOT add a container role unless this entire architecture\n * is rewritten so individual toasts no longer carry their own roles.\n */\n@customElement('hx-toast-stack')\nexport class HelixToastStack extends HelixElement {\n static override styles = [helixToastStackStyles];\n\n /**\n * Corner of the viewport where toasts appear.\n * @attr placement\n */\n @property({ type: String, reflect: true })\n placement:\n | 'top-start'\n | 'top-center'\n | 'top-end'\n | 'bottom-start'\n | 'bottom-center'\n | 'bottom-end' = 'bottom-end';\n\n /**\n * Maximum number of simultaneously visible toasts. 0 = unlimited.\n * @attr stack-limit\n */\n @property({ type: Number, attribute: 'stack-limit' })\n stackLimit = 3;\n\n override render() {\n return html`\n <div\n part=\"base\"\n class=${classMap({\n 'toast-stack': true,\n [`toast-stack--${this.placement}`]: true,\n })}\n >\n <slot></slot>\n </div>\n `;\n }\n}\n\n// ─── Declarative Global Types ───\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-toast-stack': HelixToastStack;\n }\n}\n","import { HelixToast } from './hx-toast.js';\nimport { HelixToastStack } from './hx-toast-stack.js';\nimport type { ToastVariant } from './hx-toast.js';\nimport type { ToastStackPlacement } from './hx-toast-stack.js';\n\nexport interface ToastOptions {\n /** The notification message text. */\n message: string;\n /** Visual variant. Defaults to 'default'. */\n variant?: ToastVariant;\n /** Auto-dismiss duration in ms. 0 = persistent. Defaults to 3000. */\n duration?: number;\n /** Placement of the shared stack. Defaults to 'bottom-end'. */\n placement?: ToastStackPlacement;\n}\n\n/**\n * (group-6 §5.5) Minimum on-screen lifetime for any toast created via the\n * factory, regardless of `stackLimit`-driven displacement. Prevents\n * AT-clipping when rapid-fire `toast()` calls (e.g. during a Drupal\n * BigPipe re-attach burst) would otherwise hide a toast before NVDA/JAWS\n * finished reading it.\n */\nconst MIN_DISPLAY_MS = 1500;\n\n/**\n * @internal\n *\n * Single coherent state machine per deferred toast (codex p1 round-2 holistic).\n *\n * ```\n * queued ── timer fires ──► showing ──► hiding ──► done\n * │\n * └── consumer hide() / DOM detach ──► cancelled ──► removed (terminal)\n * ```\n *\n * Every state transition flows through ONE of two helpers:\n *\n * - `promoteDeferredToShowing` — natural progression when the queued timer\n * fires. Pops the append-deadline, decrements `_pendingAppends`, clears\n * the inline `display: none` artifact, calls `show()`.\n * - `cancelDeferredToast` — terminal cancellation, whether triggered by a\n * consumer `.hide()` call inside the defer window OR by detecting a DOM\n * detach inside the timer callback. Pops the append-deadline, decrements\n * `_pendingAppends`, cancels the queued `setTimeout`, detaches the toast\n * element from the DOM, clears the inline `display: none` artifact, and\n * marks the deferred record as `cancelled` so the timer callback (if it\n * still fires) is a no-op.\n *\n * Concentrating cleanup in these two helpers prevents the per-call leaks\n * that produced rounds 4–8 of codex findings (`_pendingAppends` drift,\n * `_pendingAppendDeadlines` zombie entries, DOM-resident `display: none`\n * elements, double-show on cancelled toasts).\n */\ntype DeferredStatus = 'queued' | 'showing' | 'cancelled';\n\n/**\n * @internal\n * Captures a single displacement that THIS queued toast owns — the hide\n * timer it scheduled to free its slot. Recorded only for the survivor\n * branch (where we created the timer); the longer-burst fallback path\n * shares a previously-scheduled hide and stores `null` here.\n *\n * On cancellation we unwind the displacement: clearTimeout the hide\n * timer, drop the target from `_pendingDisplacements`, and pop its\n * `_pendingHideDeadlines` entry. Without this, decrementing the\n * pending-append count alone leaves the survivor pool under-counted —\n * the next `toast()` call sees fewer survivors than really exist and\n * skips the deferred-show path, briefly exceeding `stackLimit`.\n */\ninterface DisplacementClaim {\n target: HelixToast;\n fireAt: number;\n timerId: ReturnType<typeof setTimeout>;\n}\n\n/**\n * @internal\n * Captures a synth displacement scheduled by the saturation-fallback path.\n * Unlike `DisplacementClaim`, the synth has no schedule-time `target` — its\n * victim is picked dynamically when the timer fires (the oldest still-open,\n * not-already-displacing toast). On cancellation we clear the timer and\n * pop the synth hide-deadline; we deliberately do NOT touch\n * `_pendingDisplacements` (no target was ever inserted).\n */\ninterface SynthDisplacementClaim {\n fireAt: number;\n timerId: ReturnType<typeof setTimeout>;\n}\n\ninterface DeferredRecord {\n /** Current state in the lifecycle. */\n status: DeferredStatus;\n /** Owning stack — needed for queue accounting. */\n stack: HelixToastStack;\n /** Date.now()+deferAppendMs — used to pop the append-deadline timeline. */\n fireAt: number;\n /** Handle to the queued setTimeout so cancellation can clear it. */\n timerId: ReturnType<typeof setTimeout> | null;\n /**\n * Displacement THIS queued toast owns (survivor branch only). `null` for\n * the longer-burst fallback branch where this toast claimed an existing\n * hide-deadline scheduled by an earlier call.\n */\n displacement: DisplacementClaim | null;\n /**\n * (codex p2) Synth displacements THIS queued toast scheduled via the\n * saturation-fallback path. Tracked separately from `displacement`\n * because synths have no schedule-time target — the victim is chosen at\n * fire time. On cancellation each synth's timer is cleared and its\n * hide-deadline popped from `_pendingHideDeadlines` so a later poll of\n * the timeline does not see a phantom slot AND no stale `setTimeout`\n * fires later to hide an unrelated victim that this cancelled toast\n * never needed displaced. Empty array (not null) when there are none —\n * keeps the cleanup loop branchless.\n */\n synthDisplacements: SynthDisplacementClaim[];\n /**\n * (codex p2 round-10) MutationObserver scoped to the parent stack that\n * watches for THIS queued toast appearing in `removedNodes`. Without this,\n * a consumer running `toastEl.remove()` (or detaching the parent) inside\n * the defer window leaves the queued reservation in `_pendingAppends` and\n * the displacement claim live until the timer eventually fires —\n * displacement claim drift, follow-on `toast()` overshoots, the orphaned\n * displacement timer eventually hides a still-needed survivor.\n *\n * The observer fires synchronously after the removal microtask completes,\n * so cleanup runs before any follow-on `toast()` call observes a stale\n * `_pendingAppends` count. Disconnected by both `promoteDeferredToShowing`\n * (natural promotion path) and `cancelDeferredToast` (cancellation path)\n * so the observer never lingers past the queued window.\n */\n removalObserver: MutationObserver | null;\n}\n\nconst _deferredRecords = new WeakMap<HelixToast, DeferredRecord>();\n\n/** @internal Tracks `show()` timestamps so the factory can defer displacement. */\nconst _shownAt = new WeakMap<HelixToast, number>();\n\n/**\n * @internal\n * Tracks toasts that already have a deferred-hide scheduled so successive\n * `toast()` calls within the same MIN_DISPLAY_MS window do NOT pile multiple\n * timers on the same target while leaving newer overflow toasts untouched.\n */\nconst _pendingDisplacements = new WeakSet<HelixToast>();\n\n/**\n * @internal\n * Per-stack count of toasts that have been created via `toast()` but whose\n * append-and-show is queued behind a deferred-hide. These toasts already\n * \"own\" a future visible slot, so a subsequent burst call must count them\n * when deciding whether further displacement is needed — otherwise the\n * stack briefly exceeds `stackLimit`.\n *\n * Mutated only by `promoteDeferredToShowing` and `cancelDeferredToast` so\n * the count stays in lockstep with the deferred-record lifecycle.\n */\nconst _pendingAppends = new WeakMap<HelixToastStack, number>();\n\n/**\n * @internal\n * Per-stack ordered list of `Date.now()`-style timestamps when previously\n * scheduled deferred-hide timers will fire, plus when previously queued\n * appends will become visible. Used by longer-burst calls that exhaust the\n * live survivor pool: when every existing survivor has already been picked\n * for displacement by an earlier burst call, the next call has no fresh\n * target to hide. The new toast must still be queued so the stack never\n * exceeds `stackLimit`; we queue it for the NEXT free slot.\n */\nconst _pendingHideDeadlines = new WeakMap<HelixToastStack, number[]>();\nconst _pendingAppendDeadlines = new WeakMap<HelixToastStack, number[]>();\n\n/** @internal */\nfunction pushDeadline(map: WeakMap<HelixToastStack, number[]>, stack: HelixToastStack, ts: number) {\n const list = map.get(stack);\n if (list) {\n list.push(ts);\n list.sort((a, b) => a - b);\n } else {\n map.set(stack, [ts]);\n }\n}\n\n/** @internal */\nfunction popDeadline(\n map: WeakMap<HelixToastStack, number[]>,\n stack: HelixToastStack,\n ts: number,\n): void {\n const list = map.get(stack);\n if (!list) return;\n const idx = list.indexOf(ts);\n if (idx >= 0) list.splice(idx, 1);\n if (list.length === 0) map.delete(stack);\n}\n\n/** @internal Decrement the per-stack pending-append count (or clear at zero). */\nfunction decrementPendingAppends(stack: HelixToastStack): void {\n const remaining = (_pendingAppends.get(stack) ?? 1) - 1;\n if (remaining > 0) {\n _pendingAppends.set(stack, remaining);\n } else {\n _pendingAppends.delete(stack);\n }\n}\n\n/**\n * @internal\n * Cancels a deferred toast in any pre-`show()` state. Safe to call multiple\n * times on the same element — the deferred-record check makes subsequent\n * calls no-ops. Used by:\n *\n * 1. The consumer-facing `hide()` override installed during the defer window\n * (covers `t.hide()` mid-window).\n * 2. The deferred `setTimeout` callback when it discovers a non-connected\n * element (covers plain `t.remove()` / parent removal).\n */\nfunction cancelDeferredToast(toastEl: HelixToast): void {\n const record = _deferredRecords.get(toastEl);\n if (!record || record.status !== 'queued') return;\n\n // Mark cancelled FIRST so re-entry (e.g. from inside the timer callback,\n // or the removal observer firing on the same tick we're already in) sees\n // the terminal state and bails.\n record.status = 'cancelled';\n\n // Disconnect the parent-removal observer FIRST so its callback cannot\n // re-enter `cancelDeferredToast` for the `toastEl.remove()` we're about\n // to perform below. The status flip above already guards re-entry, but\n // disconnecting also frees the observer's reference to the stack subtree\n // immediately — no point keeping it armed for an already-cancelled record.\n if (record.removalObserver !== null) {\n record.removalObserver.disconnect();\n record.removalObserver = null;\n }\n\n // Clear the queued setTimeout so it cannot fire. (When cancellation is\n // triggered FROM that timer callback, timerId is set but the engine has\n // already dequeued it — clearTimeout on a fired id is a documented no-op.)\n if (record.timerId !== null) {\n clearTimeout(record.timerId);\n record.timerId = null;\n }\n\n // Decrement the per-stack queue count so subsequent burst calls do not\n // think this slot is still reserved.\n decrementPendingAppends(record.stack);\n popDeadline(_pendingAppendDeadlines, record.stack, record.fireAt);\n\n // Unwind the displacement we owned, if any. Without this, the\n // survivor-pool filter (`!_pendingDisplacements.has(t)`) keeps treating\n // the displaced target as already-leaving — a follow-on `toast()` call\n // observes too few survivors, skips the deferred-show path, and shows\n // immediately. Result: stack briefly exceeds `stackLimit` until the\n // orphaned displacement timer eventually fires. The fallback branch\n // (longer-burst path) does not own a displacement (`displacement ===\n // null`); it claimed an existing hide-deadline scheduled by an earlier\n // call, so cancellation is just our own queue-bookkeeping.\n if (record.displacement) {\n const { target, fireAt, timerId } = record.displacement;\n clearTimeout(timerId);\n _pendingDisplacements.delete(target);\n popDeadline(_pendingHideDeadlines, record.stack, fireAt);\n }\n\n // (codex p2) Unwind any synth displacements THIS toast scheduled via\n // the saturation-fallback path. Distinct from `record.displacement`\n // because synths have no schedule-time `target` — the victim is chosen\n // dynamically when the synth timer fires. Without this cleanup, a\n // cancelled synth-owning toast leaves its synth `setTimeout` armed; at\n // fire time it picks an UNRELATED still-open victim and hides it,\n // even though the call that scheduled the synth no longer exists in\n // the queue. The fix here clears each synth timer and pops its\n // synthesized hide-deadline so the timeline stays consistent for any\n // follow-on saturation-burst poll.\n for (const synth of record.synthDisplacements) {\n clearTimeout(synth.timerId);\n popDeadline(_pendingHideDeadlines, record.stack, synth.fireAt);\n }\n record.synthDisplacements = [];\n\n // Clear the inline `display: none` we set during the defer window so a\n // still-attached toast does not leave a zero-box artifact behind.\n toastEl.style.removeProperty('display');\n\n // Detach from DOM. A cancelled toast must not linger as a zombie element\n // (codex p2 round-2 finding) — the public API contract is that the\n // element is gone after `hide()` resolves, just like for shown toasts\n // post-`hx-after-hide`.\n toastEl.remove();\n\n // Drop the record entirely; the toast has reached its terminal state.\n _deferredRecords.delete(toastEl);\n}\n\n/**\n * @internal\n * Natural progression: the queued `setTimeout` fired and the slot is\n * available. Pop the append-deadline, decrement the queue count, clear the\n * inline `display: none`, and call `show()`.\n */\nfunction promoteDeferredToShowing(toastEl: HelixToast, wireAutoRemove: () => void): void {\n const record = _deferredRecords.get(toastEl);\n if (!record || record.status !== 'queued') return;\n\n record.status = 'showing';\n record.timerId = null;\n // (codex p2 round-10) Disconnect the parent-removal observer — once the\n // toast promotes to `showing`, its lifecycle is governed by the standard\n // auto-dismiss / `hx-after-hide` path which already handles cleanup.\n // Leaving the observer armed would leak a long-lived MutationObserver\n // reference per shown toast.\n if (record.removalObserver !== null) {\n record.removalObserver.disconnect();\n record.removalObserver = null;\n }\n decrementPendingAppends(record.stack);\n popDeadline(_pendingAppendDeadlines, record.stack, record.fireAt);\n\n // Clearing display BEFORE show() ensures the toast is in layout flow on\n // the same frame visibility activates — avoids a one-frame\n // invisible-but-allocated layout window.\n toastEl.style.removeProperty('display');\n wireAutoRemove();\n toastEl.show();\n _shownAt.set(toastEl, Date.now());\n\n // Showing toasts no longer need the deferred record — their lifecycle is\n // now governed by the standard auto-dismiss / hx-after-hide path.\n _deferredRecords.delete(toastEl);\n}\n\n/**\n * Imperatively create and display a toast notification.\n *\n * Creates a shared `hx-toast-stack` on `document.body` if one does not exist,\n * then appends a new `hx-toast` with the given options. Respects the stack's\n * `stackLimit` by hiding the oldest visible toast when the limit is exceeded.\n *\n * @example\n * import { toast } from '@helixui/library/components/hx-toast/index.js';\n * toast({ message: 'Patient record saved.', variant: 'success' });\n */\nexport function toast(options: ToastOptions): HelixToast {\n if (typeof document === 'undefined') throw new Error('toast() requires a browser environment');\n const placement = options.placement ?? 'bottom-end';\n\n // Find or create a dedicated stack for this placement\n const stackSelector = `hx-toast-stack[placement=\"${placement}\"]`;\n let stack = document.querySelector<HelixToastStack>(stackSelector);\n if (!stack) {\n stack = document.createElement('hx-toast-stack');\n stack.placement = placement;\n // Intentional design decision: the toast stack is appended to document.body rather\n // than inserted near the caller. This is required so that the fixed-position overlay\n // is not clipped by an ancestor with `overflow: hidden`, `transform`, or `filter`\n // (all of which create a new stacking context and break fixed positioning).\n // Drupal compatibility note: Drupal's BigPipe / AJAX behaviors can re-attach the\n // document body without removing these stacks. The selector check above\n // (`document.querySelector`) ensures only one stack per placement is ever created,\n // preventing duplicates on re-attach cycles.\n document.body.appendChild(stack);\n }\n\n // Enforce stack limit: hide oldest open toasts if at capacity.\n // (group-6 §5.5) Minimum display time prevents AT-clipping under rapid-fire\n // bursts. If a toast has not yet been on screen for MIN_DISPLAY_MS, defer\n // its hide until the remainder elapses; otherwise hide immediately.\n let deferAppendMs = 0;\n // Displacements scheduled by THIS call's overflow loop (survivor branch\n // only). Captured per call so the deferred record below can take\n // ownership of them and unwind them on cancellation. Practically this\n // list has at most one entry — the round-1 fix keeps overflow at 1 per\n // call — but the array form is honest about the loop shape.\n const scheduledDisplacements: DisplacementClaim[] = [];\n // (codex p2 saturation) Synth displacement scheduled by the\n // saturation fallback path. The synth timer picks its victim\n // dynamically at fire time (since there is no eligible target at\n // schedule time); cancellation only needs to clear the timer and pop\n // the synth hide-deadline. Stored separately because the displacement\n // record requires a real `target: HelixToast` reference, which we use\n // `toastEl` for (the new toast itself acts as the placeholder — it is\n // never in `_pendingDisplacements`, so the `delete()` in\n // `cancelDeferredToast` is a no-op).\n const synthDisplacements: SynthDisplacementClaim[] = [];\n if (stack.stackLimit > 0) {\n const openToasts = [...stack.querySelectorAll<HelixToast>('hx-toast')].filter((t) => t.open);\n // Treat anything already queued for displacement as already-hiding so we\n // pick the next surviving oldest. Since the new toast is appended after\n // this block, we must overshoot by 1 to cover it. We also count toasts\n // that are already queued behind a deferred append on this same stack —\n // they \"own\" a visible slot when their timer fires, so the displacement\n // math must reserve room for them too.\n const survivors = openToasts.filter((t) => !_pendingDisplacements.has(t));\n const queuedAppends = _pendingAppends.get(stack) ?? 0;\n const overflow = survivors.length + queuedAppends + 1 - stack.stackLimit;\n\n // Track which slots we've already accounted for in this call. Each\n // displacement we schedule below frees one slot; each pending hide we\n // claim from the existing timeline also frees one slot. The new toast\n // claims the soonest free slot — its deferAppendMs is set to that\n // deadline's remaining time (0 if a slot frees immediately).\n const claimedHideTimestamps: number[] = [];\n\n for (let i = 0; i < overflow; i++) {\n const target = survivors[i];\n if (target) {\n const shownAt = _shownAt.get(target);\n const elapsed = shownAt === undefined ? Number.POSITIVE_INFINITY : Date.now() - shownAt;\n if (elapsed >= MIN_DISPLAY_MS) {\n target.hide();\n // Slot frees synchronously; deferAppendMs stays at 0 unless a\n // later iteration of this loop forces it higher.\n continue;\n }\n const remaining = MIN_DISPLAY_MS - elapsed;\n const fireAt = Date.now() + remaining;\n _pendingDisplacements.add(target);\n pushDeadline(_pendingHideDeadlines, stack, fireAt);\n const stackRef = stack;\n const timerId = setTimeout(() => {\n _pendingDisplacements.delete(target);\n popDeadline(_pendingHideDeadlines, stackRef, fireAt);\n // Guard: only hide if still open (consumer may have hidden it manually)\n if (target.open) target.hide();\n }, remaining);\n claimedHideTimestamps.push(fireAt);\n // Record this displacement on a per-call list so the deferred\n // record (created later, after the toast element exists) can take\n // ownership of it for cancellation cleanup. Pre-existing deferred\n // records on OTHER toasts are unaffected; this list resets per\n // call.\n scheduledDisplacements.push({ target, fireAt, timerId });\n if (remaining > deferAppendMs) deferAppendMs = remaining;\n continue;\n }\n\n // No live survivor available for this overflow slot: every visible\n // toast is already queued for displacement by an earlier burst call.\n // (codex p1 round-2: longer-burst gap.) Find the next free slot from\n // the existing pending-hide timeline that we have not already\n // claimed in this call or by an earlier queued append.\n const allHideDeadlines = _pendingHideDeadlines.get(stack) ?? [];\n const allAppendDeadlines = _pendingAppendDeadlines.get(stack) ?? [];\n // A pending append CONSUMES a hide-slot when it fires, so any append\n // deadline ≤ a hide deadline cancels that hide-slot from the pool of\n // free slots available to *this* new toast. We pair them in\n // chronological order: each append claims the next hide slot.\n const availableHideDeadlines = [...allHideDeadlines].sort((a, b) => a - b);\n const queuedAppendsPending = [...allAppendDeadlines].sort((a, b) => a - b);\n // Pair each queued append with the soonest hide it depends on.\n for (const appendAt of queuedAppendsPending) {\n for (let j = 0; j < availableHideDeadlines.length; j++) {\n if ((availableHideDeadlines[j] as number) <= appendAt) {\n availableHideDeadlines.splice(j, 1);\n break;\n }\n }\n }\n // Subtract slots claimed by THIS call's earlier loop iterations.\n for (const claimed of claimedHideTimestamps) {\n const idx = availableHideDeadlines.indexOf(claimed);\n if (idx >= 0) availableHideDeadlines.splice(idx, 1);\n }\n let nextFreeAt = availableHideDeadlines[0];\n if (nextFreeAt === undefined) {\n // (codex p2) Max-burst saturation: every visible survivor is\n // already in `_pendingDisplacements` AND every pending hide slot\n // is paired with a queued append. The stack is fully reserved\n // for the next MIN_DISPLAY_MS window — there is no existing\n // hide-deadline this toast can ride.\n //\n // Without action, the previous code path bailed (`break`),\n // leaving the new toast on the immediate-show path and briefly\n // exceeding `stackLimit`. Fix: synthesize a future displacement\n // by deferring against the LATEST queued-append deadline +\n // MIN_DISPLAY_MS — that is the soonest moment at which the\n // queued chain frees up and a fresh displacement is legal.\n //\n // We also schedule a real hide at that moment that picks the\n // oldest open toast dynamically. By the time it fires, every\n // toast in the burst chain ahead of us has promoted, lived its\n // MIN_DISPLAY_MS, and is eligible to be displaced. Pushing the\n // synthesized deadline into `_pendingHideDeadlines` keeps the\n // queue accounting consistent with the survivor-branch shape so\n // any later saturation-burst call observes a coherent timeline.\n const allDeadlines: number[] = [];\n for (const ts of allHideDeadlines) allDeadlines.push(ts);\n for (const ts of allAppendDeadlines) allDeadlines.push(ts);\n for (const ts of claimedHideTimestamps) allDeadlines.push(ts);\n if (allDeadlines.length === 0) {\n // Truly pathological: overflow > 0 with no deadlines anywhere.\n // Bail rather than guess.\n break;\n }\n nextFreeAt = Math.max(...allDeadlines) + MIN_DISPLAY_MS;\n const synthFireAt = nextFreeAt;\n const synthRemaining = synthFireAt - Date.now();\n const stackRef = stack;\n pushDeadline(_pendingHideDeadlines, stackRef, synthFireAt);\n const synthTimerId = setTimeout(() => {\n popDeadline(_pendingHideDeadlines, stackRef, synthFireAt);\n // Pick the oldest-still-open toast at fire time. By\n // construction every queued chain ahead has had time to\n // promote and live MIN_DISPLAY_MS, so the oldest open is\n // always displaceable.\n const liveOpen = [...stackRef.querySelectorAll<HelixToast>('hx-toast')].filter(\n (t) => t.open && !_pendingDisplacements.has(t),\n );\n const victim = liveOpen[0];\n if (victim) victim.hide();\n }, synthRemaining);\n // Defer attachment to the deferred record until after `toastEl`\n // is created. Cancellation will clearTimeout this id and pop\n // the synth hide-deadline.\n synthDisplacements.push({ fireAt: synthFireAt, timerId: synthTimerId });\n }\n claimedHideTimestamps.push(nextFreeAt);\n const remaining = nextFreeAt - Date.now();\n if (remaining > deferAppendMs) deferAppendMs = Math.max(0, remaining);\n }\n }\n\n // Create toast element. We attach the auto-remove `hx-after-hide` listener\n // ONLY after the first `show()` has fired, otherwise the toast's initial\n // mount with the default `open=false` causes its `updated()` to dispatch a\n // spurious `hx-hide` → `hx-after-hide` chain that would self-remove the\n // queued toast before its slot ever opens.\n const toastEl = document.createElement('hx-toast');\n toastEl.variant = options.variant ?? 'default';\n toastEl.duration = options.duration ?? 3000;\n toastEl.closable = true;\n toastEl.textContent = options.message;\n\n /** Wires the lifecycle hide listener — called once, the first time `show()` fires. */\n const wireAutoRemove = (): void => {\n toastEl.addEventListener('hx-after-hide', () => {\n _shownAt.delete(toastEl);\n _pendingDisplacements.delete(toastEl);\n toastEl.remove();\n });\n };\n\n if (deferAppendMs > 0) {\n // Suppress the spurious initial `hx-hide` / `hx-after-hide` events that\n // hx-toast's `updated()` dispatches when an element mounts with the\n // default `open=false`. Consumers should only see hide events for\n // toasts that actually showed; for a QUEUED toast the first real hide\n // is its own auto-dismiss or the displaced-hide later. The listeners\n // are `{ once: true }` so they fire exactly once on the spurious mount\n // events and never interfere with subsequent legitimate hides.\n const swallowInitial = (e: Event): void => {\n e.stopImmediatePropagation();\n e.stopPropagation();\n };\n toastEl.addEventListener('hx-hide', swallowInitial, { once: true });\n toastEl.addEventListener('hx-after-hide', swallowInitial, { once: true });\n\n // (codex p2) Hide the queued toast from layout flow during the defer\n // window. Without this, the toast's `:host { display: block }` keeps\n // it in the document layout even with `open=false` — its outer wrapper\n // pushes earlier toasts out of position and may be announced by some\n // ATs on insertion. `display: none` removes it from layout AND the\n // accessibility tree until `show()` flips it visible. The inline style\n // is cleared inside the deferred `setTimeout` (or the cancellation\n // helper) so the natural `:host` display rule reasserts.\n toastEl.style.display = 'none';\n\n // Override `hide()` so a consumer call inside the defer window flows\n // through the canonical cancellation helper. After cancellation the\n // toast is no longer in the DOM (and its deferred record is gone), so\n // any subsequent `hide()` calls fall through to the original\n // implementation — which itself is a safe no-op on a detached toast.\n const originalHide = toastEl.hide.bind(toastEl);\n toastEl.hide = function deferredHide(this: HelixToast): void {\n const record = _deferredRecords.get(toastEl);\n if (record?.status === 'queued') {\n cancelDeferredToast(toastEl);\n return;\n }\n originalHide();\n };\n }\n\n // Append the toast to the stack synchronously so the host element is\n // connected and consumers awaiting `t.updateComplete` after `toast()`\n // resolve immediately. For the immediate-show path the very next line\n // calls `show()` so the toast renders open on its first Lit update; for\n // the deferred path the toast renders with `open=false` (queued) and\n // flips open when the slot frees up.\n stack.appendChild(toastEl);\n if (deferAppendMs > 0) {\n // Defer `show()` (which flips `open=true` and starts the auto-dismiss\n // timer) by exactly the longest deferred-hide window so the new toast\n // becomes visible as the displaced one disappears — preserving both\n // `stackLimit` (max visible) and MIN_DISPLAY_MS (min lifetime). Until\n // then the toast is in the DOM but `open=false`, so the visible count\n // never exceeds `stackLimit`.\n const targetStack = stack;\n const appendFireAt = Date.now() + deferAppendMs;\n _pendingAppends.set(targetStack, (_pendingAppends.get(targetStack) ?? 0) + 1);\n pushDeadline(_pendingAppendDeadlines, targetStack, appendFireAt);\n\n // Register the deferred record so `cancelDeferredToast` and\n // `promoteDeferredToShowing` can drive a single coherent state machine.\n // The timerId is filled in immediately below; the record is created\n // first so cancellation reaching the lookup before assignment still\n // sees a 'queued' status and works correctly.\n //\n // Take ownership of the displacement THIS call scheduled (survivor\n // branch). Practically `scheduledDisplacements` has at most one entry\n // per call — round-1 burst-fix keeps overflow at 1 — and the\n // longer-burst fallback path schedules nothing here, instead reusing\n // an existing hide-deadline. We pick the soonest entry (in case the\n // shape ever changes) and leave any others orphaned (they'd still\n // fire as plain hides; they aren't owned by THIS toast). The\n // fallback path stores `displacement: null` so cancellation only\n // unwinds our own queue bookkeeping.\n const ownedDisplacement = scheduledDisplacements.length > 0 ? scheduledDisplacements[0]! : null;\n // (codex p2) Take ownership of any synth displacements THIS call\n // scheduled via the saturation-fallback path. Stored on the deferred\n // record (not promoted into `scheduledDisplacements` with a phantom\n // `target: toastEl`) so cancellation can unwind them via the synth\n // cleanup loop in `cancelDeferredToast` without the misleading\n // `_pendingDisplacements.delete(toastEl)` no-op the older shape\n // implied. Each synth still claims a slot in `_pendingHideDeadlines`\n // — cancellation pops them so the timeline stays consistent.\n const record: DeferredRecord = {\n status: 'queued',\n stack: targetStack,\n fireAt: appendFireAt,\n timerId: null,\n displacement: ownedDisplacement,\n synthDisplacements: [...synthDisplacements],\n removalObserver: null,\n };\n _deferredRecords.set(toastEl, record);\n\n // (codex p2 round-10) Watch for direct DOM removal during the defer\n // window. Without this, a consumer running `toastEl.remove()` (or\n // detaching the parent stack) skips the `hide()` override AND the\n // `setTimeout` does not run cleanup until the full `deferAppendMs`\n // window elapses. Result: `_pendingAppends` stays incremented, the\n // displacement claim hangs (the displaced survivor still hides on\n // schedule even though no replacement is coming), follow-on\n // `toast()` overshoots the stack limit, and a MutationObserver on\n // the stack subtree leaks until the orphaned timer eventually fires.\n //\n // The observer fires synchronously after the removal microtask, so\n // any follow-on `toast()` call observes the post-cleanup state.\n // Cancellation routes through the canonical helper, which is\n // idempotent (status check at the top) and disconnects this\n // observer first.\n const removalObserver = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n for (const removed of mutation.removedNodes) {\n if (removed === toastEl) {\n cancelDeferredToast(toastEl);\n return;\n }\n }\n }\n });\n removalObserver.observe(targetStack, { childList: true });\n record.removalObserver = removalObserver;\n\n record.timerId = setTimeout(() => {\n // The setTimeout callback covers two paths:\n //\n // 1. Plain DOM removal. The consumer ran `toastEl.remove()` (or a\n // parent was detached) without going through `hide()`, so the\n // `hide()` override above never ran. Treat the missing\n // connection as cancellation — the canonical helper already\n // handles all bookkeeping idempotently.\n //\n // 2. Natural promotion. The defer window elapsed and the slot is\n // free. Promote 'queued' → 'showing'.\n const currentRecord = _deferredRecords.get(toastEl);\n if (!currentRecord || currentRecord.status === 'cancelled') {\n // Already cancelled (consumer hide() during defer window). All\n // bookkeeping was performed in `cancelDeferredToast`; nothing to\n // do here.\n return;\n }\n if (!toastEl.isConnected) {\n cancelDeferredToast(toastEl);\n return;\n }\n promoteDeferredToShowing(toastEl, wireAutoRemove);\n }, deferAppendMs);\n } else {\n wireAutoRemove();\n toastEl.show();\n _shownAt.set(toastEl, Date.now());\n }\n\n return toastEl;\n}\n"],"names":["helixToastStyles","css","helixToastStackStyles","MIN_DISPLAY_MS_BY_VARIANT","HelixToast","HelixElement","changedProperties","min","devWarn","remaining","delay","elapsed","fired","fireAfterHide","base","slot","html","nothing","severityLabel","classMap","__decorateClass","property","query","state","customElement","HelixToastStack","MIN_DISPLAY_MS","_deferredRecords","_shownAt","_pendingDisplacements","_pendingAppends","_pendingHideDeadlines","_pendingAppendDeadlines","pushDeadline","map","stack","ts","list","a","b","popDeadline","idx","decrementPendingAppends","cancelDeferredToast","toastEl","record","target","fireAt","timerId","synth","promoteDeferredToShowing","wireAutoRemove","toast","options","placement","stackSelector","deferAppendMs","scheduledDisplacements","synthDisplacements","survivors","t","queuedAppends","overflow","claimedHideTimestamps","i","shownAt","stackRef","allHideDeadlines","allAppendDeadlines","availableHideDeadlines","queuedAppendsPending","appendAt","j","claimed","nextFreeAt","allDeadlines","synthFireAt","synthRemaining","synthTimerId","victim","swallowInitial","e","originalHide","targetStack","appendFireAt","ownedDisplacement","removalObserver","mutations","mutation","removed","currentRecord"],"mappings":";;;;;AAYO,MAAMA,KAAmBC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAmMnBC,KAAwBD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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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"}
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-05-06T14:32:03.211Z",
3
+ "generatedAt": "2026-05-07T16:39:03.418Z",
4
4
  "sourceCem": "custom-elements.json",
5
- "helixVersion": "3.4.1",
6
- "tokensVersion": "3.3.1",
5
+ "helixVersion": "3.5.0",
6
+ "tokensVersion": "3.5.0",
7
7
  "components": [
8
8
  {
9
9
  "tag": "hx-accordion",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helixui/library",
3
- "version": "3.4.1",
3
+ "version": "3.5.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.3.1"
62
+ "@helixui/tokens": "3.5.0"
63
63
  },
64
64
  "peerDependencies": {
65
65
  "@floating-ui/dom": "^1.7.6"
@@ -1 +0,0 @@
1
- {"version":3,"file":"hx-drawer-D81tb4BD.js","sources":["../../src/components/hx-drawer/hx-drawer.styles.ts","../../src/components/hx-drawer/hx-drawer.ts"],"sourcesContent":["import { css } from 'lit';\n\n/**\n * hx-drawer styles.\n *\n * Component-tier tokens with two-level var() fallback:\n * var(--hx-drawer-{prop}, var(--hx-color-{semantic}, #hex))\n * Inner hex fallbacks track the \"precision cool\" palette (3.2.0):\n * neutral-0 = #FFFFFF, neutral-100 = #EBEEE9, neutral-200 = #D6DBD5,\n * neutral-500 = #66787B, neutral-800 = #202B39, neutral-900 = #0D1825,\n * primary-500 = #429797.\n */\nexport const helixDrawerStyles = css`\n /* P2-03: Explicit [hidden] rule to survive CSS resets that may override the UA stylesheet. */\n [hidden] {\n display: none !important;\n }\n\n :host {\n display: contents;\n }\n\n :host([contained]) {\n display: block;\n position: relative;\n overflow: hidden;\n }\n\n /* ─── Overlay ─── */\n\n .drawer-overlay {\n position: fixed;\n inset: 0;\n z-index: var(--hx-z-index-modal, 1400);\n display: flex;\n pointer-events: none;\n visibility: hidden;\n }\n\n :host([contained]) .drawer-overlay {\n position: absolute;\n }\n\n .drawer-overlay.is-open {\n pointer-events: auto;\n visibility: visible;\n }\n\n /* ─── Backdrop ─── */\n\n .drawer-backdrop {\n position: absolute;\n inset: 0;\n background-color: var(\n --hx-drawer-backdrop-color,\n var(--hx-color-surface-overlay, rgba(0, 0, 0, 0.75))\n );\n opacity: 0;\n transition: opacity var(--hx-duration-slow, 300ms) var(--hx-easing-out, ease-out);\n }\n\n .drawer-overlay.is-open .drawer-backdrop {\n opacity: var(--hx-drawer-backdrop-opacity, 0.5);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .drawer-backdrop {\n transition: none;\n }\n }\n\n /* ─── Panel ─── */\n\n .drawer-panel {\n position: absolute;\n display: flex;\n flex-direction: column;\n background-color: var(--hx-drawer-bg, var(--hx-color-surface-default, #ffffff));\n color: var(--hx-drawer-color, var(--hx-color-text-primary, #0d1825));\n box-shadow: var(--hx-drawer-shadow, var(--hx-shadow-xl, 0 20px 25px -5px rgb(0 0 0 / 0.1)));\n overflow: hidden;\n outline: none;\n z-index: 1; /* local stacking context: panel above backdrop within overlay container */\n transition:\n transform var(--hx-duration-slow, 300ms) var(--hx-easing-out, ease-out),\n opacity var(--hx-duration-slow, 300ms) var(--hx-easing-out, ease-out);\n opacity: 0;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .drawer-panel {\n transition: none;\n }\n\n .drawer-close-button {\n transition: none;\n }\n }\n\n /* ─── Placement: end (default — right) ─── */\n\n :host([placement='end']) .drawer-panel,\n :host(:not([placement])) .drawer-panel {\n top: 0;\n inset-inline-end: 0;\n bottom: 0;\n width: var(--_drawer-size, var(--hx-drawer-size-md, 30rem));\n max-width: 100%;\n transform: translateX(100%);\n }\n\n :host([placement='end']) .drawer-overlay.is-open .drawer-panel,\n :host(:not([placement])) .drawer-overlay.is-open .drawer-panel {\n transform: translateX(0);\n opacity: 1;\n }\n\n /* ─── Placement: start (left) ─── */\n\n :host([placement='start']) .drawer-panel {\n top: 0;\n inset-inline-start: 0;\n bottom: 0;\n width: var(--_drawer-size, var(--hx-drawer-size-md, 30rem));\n max-width: 100%;\n transform: translateX(-100%);\n }\n\n :host([placement='start']) .drawer-overlay.is-open .drawer-panel {\n transform: translateX(0);\n opacity: 1;\n }\n\n /* ─── Placement: top ─── */\n\n :host([placement='top']) .drawer-panel {\n top: 0;\n inset-inline-start: 0;\n inset-inline-end: 0;\n height: var(--_drawer-size, var(--hx-drawer-size-md, 30rem));\n max-height: 100%;\n width: 100%;\n transform: translateY(-100%);\n }\n\n :host([placement='top']) .drawer-overlay.is-open .drawer-panel {\n transform: translateY(0);\n opacity: 1;\n }\n\n /* ─── Placement: bottom ─── */\n\n :host([placement='bottom']) .drawer-panel {\n bottom: 0;\n inset-inline-start: 0;\n inset-inline-end: 0;\n height: var(--_drawer-size, var(--hx-drawer-size-md, 30rem));\n max-height: 100%;\n width: 100%;\n transform: translateY(100%);\n }\n\n :host([placement='bottom']) .drawer-overlay.is-open .drawer-panel {\n transform: translateY(0);\n opacity: 1;\n }\n\n /* ─── Header ─── */\n\n .drawer-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--hx-space-4, 1rem);\n padding: var(--hx-drawer-header-padding, var(--hx-space-5, 1.25rem) var(--hx-space-6, 1.5rem));\n border-bottom: var(--hx-border-width-thin, 1px) solid\n var(--hx-drawer-header-border-color, var(--hx-color-border-default, #d6dbd5));\n flex-shrink: 0;\n }\n\n .drawer-title {\n margin: 0;\n flex: 1 1 auto;\n font-family: var(--hx-drawer-font-family, var(--hx-font-family-sans, sans-serif));\n font-size: var(--hx-font-size-lg, 1.125rem);\n font-weight: var(--hx-font-weight-semibold, 600);\n line-height: var(--hx-line-height-tight, 1.25);\n color: var(--hx-drawer-title-color, var(--hx-color-text-primary, #0d1825));\n }\n\n .drawer-header-actions {\n display: flex;\n align-items: center;\n gap: var(--hx-space-2, 0.5rem);\n flex-shrink: 0;\n }\n\n .drawer-close-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n /* WCAG 2.5.5 (healthcare mandate): minimum 44x44px touch target */\n min-width: var(--hx-touch-target-min, 2.75rem);\n min-height: var(--hx-touch-target-min, 2.75rem);\n width: var(--hx-touch-target-min, 2.75rem);\n height: var(--hx-touch-target-min, 2.75rem);\n padding: 0;\n border: none;\n border-radius: var(--hx-border-radius-md, 0.375rem);\n background: transparent;\n color: var(--hx-drawer-close-btn-color, var(--hx-color-text-muted, #4a5362));\n cursor: pointer;\n flex-shrink: 0;\n transition: background-color var(--hx-duration-fast, 100ms) var(--hx-easing-default, ease);\n }\n\n .drawer-close-button:hover {\n background-color: var(--hx-drawer-close-btn-hover-bg, var(--hx-color-surface-sunken, #ebeee9));\n color: var(--hx-drawer-close-btn-hover-color, var(--hx-color-text-primary, #0d1825));\n }\n\n .drawer-close-button:focus-visible {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-drawer-close-btn-focus-ring-color, var(--hx-focus-ring-color, #0f7078));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n }\n\n /* ─── Visually-hidden close button (no-header mode, WCAG 4.1.2) ─── */\n /* Keeps the button reachable by keyboard/AT but invisible to sighted users. */\n\n .drawer-close-button--sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n }\n\n /* When focused via keyboard, restore visibility so users know where focus is. */\n .drawer-close-button--sr-only:focus-visible {\n position: static;\n width: auto;\n height: auto;\n clip: auto;\n white-space: normal;\n overflow: visible;\n margin: 0;\n }\n\n /* ─── Synthesized consumer-description span ─── */\n /* Visually hidden mirror of consumer-resolved aria-describedby text used by */\n /* the host-canonical ARIA pipeline. Always present in the shadow tree so AT */\n /* can resolve the same-root id even when the description is currently empty. */\n\n .drawer-sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n }\n\n /* ─── Body ─── */\n\n .drawer-body {\n flex: 1 1 auto;\n padding: var(--hx-drawer-body-padding, var(--hx-space-6, 1.5rem));\n overflow-y: auto;\n overscroll-behavior: contain;\n }\n\n /* ─── Footer ─── */\n\n .drawer-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: var(--hx-space-3, 0.75rem);\n padding: var(--hx-drawer-footer-padding, var(--hx-space-4, 1rem) var(--hx-space-6, 1.5rem));\n border-top: var(--hx-border-width-thin, 1px) solid\n var(--hx-drawer-footer-border-color, var(--hx-color-border-default, #d6dbd5));\n flex-shrink: 0;\n }\n\n /* ─── Forced Colors (Windows High Contrast) ─── */\n\n @media (forced-colors: active) {\n .drawer-panel {\n border: 1px solid CanvasText;\n }\n\n .drawer-header {\n border-bottom-color: CanvasText;\n }\n\n .drawer-footer {\n border-top-color: CanvasText;\n }\n\n .drawer-close-button {\n color: ButtonText;\n border: 1px solid ButtonText;\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 { lockBodyScroll, unlockBodyScroll } from '../../utils/body-scroll-lock.js';\nimport { devWarn } from '../../utils/dev-warn.js';\nimport { HelixElement, createIdCounter } from '../../base/index.js';\nimport { helixDrawerStyles } from './hx-drawer.styles.js';\nimport { forcedColorsSurface } from '../../styles/forced-colors.js';\nimport { flattenAccName } from '../../utils/aria-flatten.js';\nimport {\n installAriaIdrefMirror,\n resolveIdrefTokens,\n supportsIdrefElementReferences,\n type AriaIdrefMirrorHandle,\n} from '../../utils/aria-idref.js';\n\nconst _nextDrawerId = createIdCounter('hx-drawer');\n\ntype DrawerSizePreset = 'sm' | 'md' | 'lg' | 'full';\ntype DrawerSize = DrawerSizePreset | (string & Record<never, never>);\n\nconst DRAWER_SIZE_MAP: Record<DrawerSizePreset, string> = {\n sm: '20rem',\n md: '30rem',\n lg: '40rem',\n full: '100%',\n};\n\nconst FOCUSABLE_SELECTORS = [\n 'a[href]',\n 'area[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n 'details > summary',\n].join(',');\n\n/**\n * A slide-in drawer panel that can appear from any edge of the viewport.\n * Supports focus trapping, overlay backdrop, keyboard navigation, and full\n * ARIA labelling for enterprise healthcare accessibility requirements.\n *\n * ## Architecture Note: Host-Canonical ARIA (group-4 round-1, Path A)\n *\n * The host carries the announced dialog semantics via `ElementInternals`:\n *\n * - `internals.role = 'dialog'` (the host IS the dialog surface)\n * - `internals.ariaModal = 'true'` (modality declared on host)\n * - `internals.ariaLabelledByElements` / `internals.ariaDescribedByElements`\n * project consumer light-DOM IDREFs across the shadow boundary.\n * - `internals.ariaLabel` carries the resolved fallback name when no\n * IDREF chain or slotted title exists.\n *\n * The inner `<div part=\"overlay\">` no longer carries `role`, `aria-modal`,\n * `aria-labelledby`, or `aria-label` — those would create a nested-dialog\n * announcement above the host's canonical surface. Belt-and-suspenders\n * fallback: when the runtime does NOT expose IDL element references on\n * `ElementInternals` (older Firefox / Safari builds), the resolved label\n * text is text-flattened and written to the inner overlay's `aria-label`\n * so AT walking down from the host still finds an announceable name.\n *\n * Naming precedence (W3C AccName 1.2 §4.3.1):\n *\n * 1. Consumer `aria-labelledby` on the host — IDREFs resolved across the\n * shadow boundary via `resolveIdrefTokens` (same scope walk used by\n * every host-canonical hx-* control: own root → ancestor shadow hosts\n * → owner document → slot owners).\n * 2. Consumer `aria-label` on the host.\n * 3. `<slot name=\"label\">` text content (multi-node aggregation per\n * AccName 1.2 §4.3.10 — decorative `aria-hidden` / `[hidden]` subtrees\n * contribute zero to the name).\n * 4. `label` property — explicit author fallback string.\n * 5. Hard-coded literal `\"Drawer\"` (last-resort accessible name).\n *\n * Description channel: a synthesized `<span id=\"${id}-consumer-desc\">` is\n * rendered inside the shadow root and updated to mirror consumer-resolved\n * description text. The host's `internals.ariaDescribedByElements` carries\n * the live element references on the modern path; the in-shadow span is the\n * fallback target for AT that does not walk IDL refs. `aria-description` is\n * intentionally NEVER written — the W3C AccName algorithm ignores it\n * whenever `aria-describedby` is also present.\n *\n * Slot mutation observers track:\n * 1. The label slot's text content (in-place i18n re-renders).\n * 2. Consumer-resolved external IDREF targets (so a consumer mutating\n * `<label id=\"x\">Patient</label>` in place re-flows the name).\n * 3. Host attribute mutations (delegated to `installAriaIdrefMirror`,\n * which also catches late-inserted IDREF targets and id renames in\n * every relevant root).\n *\n * Focus trap, ESC dismiss, focus-restore, and the inert-outside-content\n * sibling-walk are unchanged from the pre-host-canonical implementation —\n * they operate against the shadow-internal panel which is still the focus\n * target for keyboard users.\n *\n * ## Architecture Note: Native `<dialog>` Migration\n *\n * This component currently uses `role=\"dialog\"` + `aria-modal=\"true\"` on a\n * `<div>` rather than the native `<dialog>` element. This is intentional for\n * the current release because:\n *\n * 1. **SSR compatibility**: Native `<dialog>` requires `showModal()` to activate\n * its modal behavior (focus trapping, backdrop, top-layer). This JavaScript\n * call is not available during server-side rendering, which is a primary\n * consumption pattern for Drupal/Twig templates.\n *\n * 2. **Contained mode**: The `contained` property constrains the drawer to a\n * positioned parent. Native `<dialog>` in modal mode renders in the top layer\n * and cannot be constrained to a parent element.\n *\n * 3. **Animation control**: The current CSS transition approach provides precise\n * control over slide-in/slide-out animations. Native `<dialog>` `::backdrop`\n * animations have inconsistent cross-browser support.\n *\n * Migration to native `<dialog>` is tracked as a future enhancement. When browser\n * support for `CloseWatcher`, `::backdrop` transitions, and declarative dialog\n * opening stabilizes, this component will be migrated to native semantics.\n *\n * @summary Slide-in panel overlay from any viewport edge.\n *\n * @tag hx-drawer\n *\n * @slot label - The drawer title text.\n * @slot header-actions - Action buttons displayed in the header near the close button.\n * @slot - Default slot for the drawer body content.\n * @slot footer - Action buttons or footer content.\n *\n * @fires {CustomEvent<void>} hx-show - Fired when the drawer begins to open.\n * @fires {CustomEvent<void>} hx-after-show - Fired after the drawer open animation completes.\n * @fires {CustomEvent<void>} hx-hide - Fired when the drawer begins to close.\n * @fires {CustomEvent<void>} hx-after-hide - Fired after the drawer close animation completes.\n * @fires {CustomEvent<void>} hx-initial-focus - Fired when initial focus is set inside the drawer. Cancelable to override focus behavior.\n *\n * **Event naming rationale:** hx-drawer uses the `hx-show`/`hx-hide`/`hx-after-show`/`hx-after-hide`\n * pattern shared by all overlay components (hx-popover, hx-tooltip, hx-dropdown). This differs from\n * hx-dialog's `hx-open`/`hx-close`/`hx-cancel` events, which align with native `<dialog>` semantics.\n * The distinction is intentional: overlays are transient visibility toggles, while dialog is a stateful\n * container with cancel semantics.\n *\n * @csspart overlay - The full-screen overlay container (includes backdrop and panel).\n * @csspart panel - The drawer panel itself.\n * @csspart header - The header region containing the title and actions.\n * @csspart title - The drawer title element.\n * @csspart close-button - The built-in close button.\n * @csspart close-btn - The visually-hidden close button rendered when noHeader is true.\n * @csspart body - The scrollable body region.\n * @csspart footer - The footer region.\n *\n * @attr [label] - Accessible label for the dialog when no visible label slot is provided.\n *\n * @cssprop [--hx-drawer-bg=var(--hx-color-neutral-0)] - Drawer panel background color.\n * @cssprop [--hx-drawer-color=var(--hx-color-neutral-900)] - Drawer panel text color.\n * @cssprop [--hx-drawer-shadow=var(--hx-shadow-xl)] - Drawer panel box shadow.\n * @cssprop [--hx-drawer-backdrop-color=var(--hx-color-neutral-900)] - Backdrop color.\n * @cssprop [--hx-drawer-backdrop-opacity=0.5] - Backdrop opacity.\n * @cssprop [--hx-drawer-header-padding] - Padding inside the header.\n * @cssprop [--hx-drawer-header-border-color=var(--hx-color-neutral-200)] - Header border color.\n * @cssprop [--hx-drawer-title-color=var(--hx-color-neutral-900)] - Title text color.\n * @cssprop [--hx-drawer-body-padding] - Padding inside the body.\n * @cssprop [--hx-drawer-footer-padding] - Padding inside the footer.\n * @cssprop [--hx-drawer-footer-border-color=var(--hx-color-neutral-200)] - Footer border color.\n * @cssprop [--hx-z-index-modal] - Z-index layer.\n * @cssprop [--hx-color-neutral-900] - Color.\n * @cssprop [--hx-duration-slow] - Animation duration.\n * @cssprop [--hx-easing-out] - CSS custom property.\n * @cssprop [--hx-color-neutral-0] - Color.\n * @cssprop [--hx-shadow-xl] - Box shadow.\n * @cssprop [--hx-drawer-size-md=30rem] - CSS custom property.\n * @cssprop [--hx-space-4] - Spacing token.\n * @cssprop [--hx-space-5] - Spacing token.\n * @cssprop [--hx-space-6] - Spacing token.\n * @cssprop [--hx-border-width-thin] - Width.\n * @cssprop [--hx-color-neutral-200] - Color.\n * @cssprop [--hx-drawer-font-family=var(--hx-font-family-sans)] - CSS custom property.\n * @cssprop [--hx-font-family-sans] - Font family.\n * @cssprop [--hx-font-size-lg] - Font size.\n * @cssprop [--hx-font-weight-semibold] - Font weight.\n * @cssprop [--hx-line-height-tight] - Line height.\n * @cssprop [--hx-space-2] - Spacing token.\n * @cssprop [--hx-touch-target-min] - Minimum touch target size.\n * @cssprop [--hx-border-radius-md] - CSS custom property.\n * @cssprop [--hx-color-neutral-500] - Color.\n * @cssprop [--hx-duration-fast] - Animation duration.\n * @cssprop [--hx-easing-default] - CSS custom property.\n * @cssprop [--hx-color-neutral-100] - Color.\n * @cssprop [--hx-focus-ring-width] - Width.\n * @cssprop [--hx-drawer-close-btn-focus-ring-color] - Color.\n * @cssprop [--hx-focus-ring-color] - Color.\n * @cssprop [--hx-color-primary-500] - Color.\n * @cssprop [--hx-focus-ring-offset] - CSS custom property.\n * @cssprop [--hx-space-3] - Spacing token.\n */\n@customElement('hx-drawer')\nexport class HelixDrawer extends HelixElement {\n static override styles = [helixDrawerStyles, forcedColorsSurface];\n\n // ─── Queries ───\n\n /**\n * Reference to the overlay element that wraps the backdrop and panel.\n * @internal\n */\n @query('.drawer-overlay')\n private _overlayEl: HTMLElement | null | undefined;\n\n /**\n * Reference to the drawer panel element used for focus management.\n * @internal\n */\n @query('.drawer-panel')\n private _panelEl: HTMLElement | null | undefined;\n\n // ─── Internal state ───\n\n /**\n * Whether the drawer is in the open state and visible to the user.\n * @internal\n */\n @state()\n private _isOpen = false;\n\n /**\n * Whether the header-actions slot has any assigned content.\n * @internal\n */\n @state()\n private _hasHeaderActionsSlot = false;\n\n /**\n * Whether the footer slot has any assigned content.\n * @internal\n */\n @state()\n private _hasFooterSlot = false;\n\n /**\n * Whether the label slot has any assigned content.\n * @internal\n */\n @state()\n private _hasLabelSlot = false;\n\n /**\n * Cached list of focusable elements within the drawer, used for focus trapping.\n * @internal\n */\n private _cachedFocusableElements: HTMLElement[] = [];\n /**\n * The element that triggered the drawer to open, restored focus when the drawer closes.\n * @internal\n */\n private _triggerElement: HTMLElement | null = null;\n /**\n * Handle for the pending animation end timeout, cleared when the drawer opens or closes again.\n * @internal\n */\n private _animationTimeout: ReturnType<typeof setTimeout> | null = null;\n /** Whether this drawer instance currently holds a body-scroll lock. */\n /** @internal */\n private _hasScrollLock = false;\n /**\n * Elements outside the drawer that were given aria-hidden during open, restored on close.\n * @internal\n */\n private _siblingAriaHiddenElements: Element[] = [];\n\n /**\n * Unique ID for the title element, used by aria-labelledby to link the dialog to its label.\n * @internal\n */\n private readonly _id = _nextDrawerId();\n /** @internal */\n private readonly _titleId = `${this._id}-title`;\n /**\n * Id of the synthesized in-shadow span that mirrors consumer-resolved\n * description text. Belt-and-suspenders: the host's\n * `internals.ariaDescribedByElements` carries the live element references\n * on the modern path; this in-shadow span is the fallback referenced via\n * `aria-describedby` on the inner overlay so AT that does not walk IDL refs\n * still finds an announceable description.\n * @internal\n */\n private readonly _consumerDescId = `${this._id}-consumer-desc`;\n\n // ─── Host-canonical ARIA state ───\n\n /**\n * Test seam: when set to `true` or `false`, overrides the platform\n * `supportsIdrefElementReferences` probe before `connectedCallback`\n * seeds `_supportsIdrefRefs`. Mirrors hx-combobox round-3 finding 4 —\n * tests must select the path BEFORE the host connects so the synthetic\n * environment matches a legacy engine. Production code MUST NOT touch\n * this field. It is `static` so the cleanup is global and obvious.\n * @internal\n */\n static __testSupportsIdrefRefsOverride: boolean | null = null;\n\n /**\n * Whether the runtime exposes IDL element references on ElementInternals.\n * Drives the modern-vs-fallback ARIA projection in `_syncHostAriaSemantics`.\n * @internal\n */\n @state() private _supportsIdrefRefs = true;\n\n /**\n * Direct references to ALL labellable elements projected into\n * `<slot name=\"label\">`. Aggregates every assigned element so composed\n * labels (e.g. `<svg slot=\"label\" aria-hidden=\"true\">…</svg><span slot=\"label\">Patient</span>`)\n * project the FULL visible label via `internals.ariaLabelledByElements`\n * while `flattenAccName` strips the decorative subtree per AccName 1.2.\n * @internal\n */\n private _slottedLabelEls: Element[] = [];\n\n /**\n * Flattened text content of the slotted label nodes, used for the no-IDL-ref\n * fallback `internals.ariaLabel` and the legacy inner-overlay `aria-label`.\n * @internal\n */\n @state() private _labelSlotText = '';\n\n /**\n * Most recently observed consumer-supplied `aria-labelledby` token list on\n * the host. Refreshed every sync via `getAttribute()` — the host attribute\n * IS the live source of truth, so `removeAttribute` is observable on the\n * next sync (it returns `null`).\n * @internal\n */\n private _consumerLabelledBy: string | null = null;\n /** @internal — see `_consumerLabelledBy`. */\n private _consumerDescribedBy: string | null = null;\n\n /**\n * Handle for the shared IDREF mirror. See `installAriaIdrefMirror()`.\n * @internal\n */\n private _ariaMirror: AriaIdrefMirrorHandle | null = null;\n\n /**\n * Watches in-place text mutations on the assigned slotted label nodes\n * (e.g. consumer i18n re-renders that mutate `textContent` instead of\n * replacing the node). `slotchange` does NOT fire on descendant text\n * mutations, so this observer is the only signal that keeps the host's\n * accessible name synchronized with the visible label.\n * @internal\n */\n private _labelSlotTextObserver: MutationObserver | null = null;\n\n /**\n * Watches in-place text / visibility mutations on consumer light-DOM\n * elements resolved from host `aria-labelledby` / `aria-describedby`.\n * Reinstalled on every sync against the deduped union of resolved\n * elements; disconnects automatically when the consumer retracts both\n * IDREF chains.\n * @internal\n */\n private _externalRefsObserver: MutationObserver | null = null;\n\n /**\n * Dedicated host observer scoped to `aria-describedby` with\n * `attributeOldValue: true`. Catches authentic consumer retraction\n * (oldValue !== null && newValue === null) so the cached baseline\n * follows the live attribute.\n * @internal\n */\n private _hostDescribedByObserver: MutationObserver | null = null;\n\n // ─── Public Properties ───\n\n /**\n * Controls whether the drawer is open.\n * @attr open\n */\n @property({ type: Boolean, reflect: true })\n open = false;\n\n /**\n * Which edge of the viewport the drawer slides in from.\n * @attr placement\n */\n @property({ type: String, reflect: true })\n placement: 'start' | 'end' | 'top' | 'bottom' = 'end';\n\n /**\n * The size of the drawer panel. Use 'sm', 'md', 'lg', 'full', or any valid CSS length.\n * @attr hx-size\n */\n @property({ type: String, reflect: true, attribute: 'hx-size' })\n size: 'sm' | 'md' | 'lg' | 'full' | (string & Record<never, never>) = 'md';\n\n /**\n * When true, the drawer is constrained to its positioned parent instead of the viewport.\n * The host element must have `position: relative` (or the library handles it via :host).\n * @attr contained\n */\n @property({ type: Boolean, reflect: true })\n contained = false;\n\n /**\n * When true, the header (title, header-actions, close button) is hidden.\n * @attr no-header\n */\n @property({ type: Boolean, reflect: true, attribute: 'no-header' })\n noHeader = false;\n\n /**\n * When true, the footer slot is hidden.\n * @attr no-footer\n */\n @property({ type: Boolean, reflect: true, attribute: 'no-footer' })\n noFooter = false;\n\n /**\n * Accessible label for the dialog when the `label` slot is not populated.\n * When the `label` slot is used, `aria-labelledby` takes precedence.\n * @attr label\n */\n @property({ type: String })\n label = '';\n\n /** Accessible label for the built-in close button. Override for localized text. */\n @property({ type: String, attribute: 'label-close' })\n labelClose = 'Close drawer';\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-drawer', 'The \"size\" attribute is deprecated. Use \"hx-size\" instead.');\n this.size = legacySize as DrawerSize;\n }\n\n // Honour the static test override so synthetic environments choose the\n // path BEFORE connect runs.\n const ctor = this.constructor as typeof HelixDrawer;\n this._supportsIdrefRefs =\n ctor.__testSupportsIdrefRefsOverride !== null\n ? ctor.__testSupportsIdrefRefsOverride\n : supportsIdrefElementReferences(this._internals);\n\n // Establish host-canonical ARIA semantics BEFORE first paint. The host\n // carries `role=\"dialog\"` and (when modal) `aria-modal=\"true\"` via\n // ElementInternals so consumers and AT see the dialog surface even before\n // any IDREF resolution lands.\n this._internals.role = 'dialog';\n this._internals.ariaModal = 'true';\n\n // Install the dedicated `aria-describedby` retraction observer BEFORE the\n // seeded `_syncHostAriaSemantics()` call below — mirrors hx-combobox\n // round-10 finding 1 — so authentic consumer clears propagate immediately.\n this._hostDescribedByObserver = new MutationObserver((records) => {\n let consumerCleared = false;\n for (const record of records) {\n if (record.attributeName !== 'aria-describedby') continue;\n const oldValue = record.oldValue;\n const newValue = this.getAttribute('aria-describedby');\n if (oldValue !== null && newValue === null) {\n this._consumerDescribedBy = null;\n consumerCleared = true;\n }\n }\n if (consumerCleared) {\n this._syncHostAriaSemantics();\n }\n });\n this._hostDescribedByObserver.observe(this, {\n attributes: true,\n attributeFilter: ['aria-describedby'],\n attributeOldValue: true,\n });\n\n // Seed root-independent semantics from connect so the host announces its\n // dialog role + accessible name before first paint. The mirror's initial\n // sync also fires synchronously inside `installAriaIdrefMirror`.\n this._syncHostAriaSemantics();\n this._ariaMirror = installAriaIdrefMirror(this, () => {\n this._syncHostAriaSemantics();\n });\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this._removeListeners();\n if (this._animationTimeout !== null) {\n clearTimeout(this._animationTimeout);\n }\n this._restoreBodyScroll();\n this._ariaMirror?.disconnect();\n this._ariaMirror = null;\n this._labelSlotTextObserver?.disconnect();\n this._labelSlotTextObserver = null;\n this._externalRefsObserver?.disconnect();\n this._externalRefsObserver = null;\n this._hostDescribedByObserver?.disconnect();\n this._hostDescribedByObserver = null;\n }\n\n override updated(changedProperties: PropertyValues<this>): void {\n super.updated(changedProperties);\n\n if (changedProperties.has('open')) {\n if (this.open) {\n this._openDrawer();\n } else {\n this._closeDrawer();\n }\n }\n\n if (changedProperties.has('size')) {\n this._applySizeVar();\n }\n\n // Re-sync host ARIA on every update — `label` / `_hasLabelSlot` /\n // `_slottedLabelEls` / `_labelSlotText` / consumer attributes can all\n // change between renders and the projection is the SSOT for AT.\n this._syncHostAriaSemantics();\n }\n\n override firstUpdated(changedProperties: PropertyValues<this>): void {\n super.firstUpdated(changedProperties);\n // The native `slotchange` event fires as a microtask after the initial\n // synchronous render. We deliberately do NOT proactively seed\n // `_hasLabelSlot` / `_labelSlotText` from `firstUpdated` because doing so\n // schedules an additional Lit re-render that subtly reorders the\n // promise-chain inside `_openDrawer` (`updateComplete.then(...) →\n // _isOpen = true → updateComplete.then(...) → _setInitialFocus()`). On\n // Chromium that reordering interleaves the inner `is-open` visibility\n // flip with `_setInitialFocus()` and breaks slotted-children focus for\n // consumer test code that calls `.focus()` immediately after the first\n // `updateComplete` (regression: `Focus Trap > traps forward Tab at the\n // last focusable element`). The slotchange handler runs one microtask\n // later and the `_syncHostAriaSemantics()` call from `updated()` picks\n // up the resolved state on the very next paint — close enough that AT\n // never observes the unnamed window. If a future round needs the seed\n // for a stricter timing requirement, re-introduce it here AND update the\n // open-drawer chain to await `_isOpen` activation before the focus\n // restoration test runs (likely needs an extra `updateComplete`).\n }\n\n // ─── Public Methods ───\n\n /** Opens the drawer. */\n show(): void {\n this.open = true;\n }\n\n /** Closes the drawer. */\n hide(): void {\n this.open = false;\n }\n\n // ─── Private: Size CSS variable ───\n\n /** @internal */\n private _applySizeVar(): void {\n const resolvedSize = DRAWER_SIZE_MAP[this.size as DrawerSizePreset] ?? this.size;\n this.style.setProperty('--_drawer-size', resolvedSize);\n }\n\n // ─── Private: Open / Close ───\n\n /** @internal */\n private _lockBodyScroll(): void {\n if (this.contained || this._hasScrollLock) return;\n // Uses a shared reference-counted lock so that simultaneous hx-dialog / hx-drawer\n // instances don't clobber each other when one closes before the other\n // (see utils/body-scroll-lock.ts).\n lockBodyScroll();\n this._hasScrollLock = true;\n }\n\n /** @internal */\n private _restoreBodyScroll(): void {\n if (!this._hasScrollLock) return;\n unlockBodyScroll();\n this._hasScrollLock = false;\n }\n\n /** @internal */\n private _openDrawer(): void {\n // Capture trigger for focus restoration (P2-04: use instanceof guard)\n const active = document.activeElement;\n this._triggerElement = active instanceof HTMLElement ? active : null;\n\n // P1-05: clear any pending animation timeout before scheduling a new one\n if (this._animationTimeout !== null) {\n clearTimeout(this._animationTimeout);\n this._animationTimeout = null;\n }\n\n this._applySizeVar();\n this._lockBodyScroll();\n this._hideBackgroundFromScreenReaders();\n\n // Dispatch hx-show before visual update\n this.dispatchEvent(new CustomEvent<void>('hx-show', { bubbles: true, composed: true }));\n\n // Transition to open state\n void this.updateComplete\n .then(() => {\n this._isOpen = true;\n this._addListeners();\n\n // Set initial focus after next render\n return this.updateComplete;\n })\n .then(() => {\n this._cachedFocusableElements = this._getFocusableElements();\n this._setInitialFocus();\n\n // Dispatch hx-after-show when the panel's CSS transition completes.\n // If prefers-reduced-motion is active (duration === 0) or the element\n // is missing, fire immediately — transitionend will never fire.\n const duration = this._getAnimationDuration();\n const panel = this._panelEl;\n if (duration === 0 || !panel) {\n this.dispatchEvent(\n new CustomEvent<void>('hx-after-show', { bubbles: true, composed: true }),\n );\n } else {\n const emitAfterShow = () => {\n if (this._animationTimeout !== null) {\n clearTimeout(this._animationTimeout);\n this._animationTimeout = null;\n }\n this.dispatchEvent(\n new CustomEvent<void>('hx-after-show', { bubbles: true, composed: true }),\n );\n };\n panel.addEventListener('transitionend', emitAfterShow, { once: true });\n // Safety fallback: if transitionend never fires (e.g. transition\n // cancelled, element removed), ensure the event is still dispatched.\n this._animationTimeout = setTimeout(emitAfterShow, duration + 50);\n }\n })\n .catch(console.error);\n }\n\n /** @internal */\n private _closeDrawer(): void {\n // P1-05: clear any pending animation timeout before scheduling a new one\n if (this._animationTimeout !== null) {\n clearTimeout(this._animationTimeout);\n this._animationTimeout = null;\n }\n\n this._isOpen = false;\n this._removeListeners();\n this._cachedFocusableElements = [];\n this._restoreBodyScroll();\n this._restoreBackgroundForScreenReaders();\n\n this.dispatchEvent(new CustomEvent<void>('hx-hide', { bubbles: true, composed: true }));\n\n // Restore focus to the trigger immediately — before any animation timeout.\n // WCAG 2.4.3: focus must never remain on invisible or inert content.\n if (this._triggerElement && typeof this._triggerElement.focus === 'function') {\n this._triggerElement.focus();\n }\n this._triggerElement = null;\n\n // Dispatch hx-after-hide when the panel's CSS transition completes.\n // If prefers-reduced-motion is active (duration === 0) or the element\n // is missing, fire immediately — transitionend will never fire.\n const duration = this._getAnimationDuration();\n const panel = this._panelEl;\n if (duration === 0 || !panel) {\n this.dispatchEvent(new CustomEvent<void>('hx-after-hide', { bubbles: true, composed: true }));\n } else {\n const emitAfterHide = () => {\n if (this._animationTimeout !== null) {\n clearTimeout(this._animationTimeout);\n this._animationTimeout = null;\n }\n this.dispatchEvent(\n new CustomEvent<void>('hx-after-hide', { bubbles: true, composed: true }),\n );\n };\n panel.addEventListener('transitionend', emitAfterHide, { once: true });\n // Safety fallback: if transitionend never fires (e.g. transition\n // cancelled, element removed), ensure the event is still dispatched.\n this._animationTimeout = setTimeout(emitAfterHide, duration + 50);\n }\n }\n\n /** @internal */\n private _getAnimationDuration(): number {\n if (typeof window === 'undefined') return 0;\n if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return 0;\n return 300;\n }\n\n // ─── Background aria-hidden management (P1-03) ───\n\n /** @internal */\n private _hideBackgroundFromScreenReaders(): void {\n if (this.contained) return;\n this._siblingAriaHiddenElements = [];\n // Walk the parent chain once to find which body child is an ancestor of this component.\n // This avoids calling child.contains(this) in a loop (which is O(n * depth)).\n // Starting from parentElement avoids aliasing `this` to a local variable.\n let ancestorBodyChild: Element | null = null;\n let el: Element | null = this.parentElement;\n while (el && el.parentElement !== document.body) {\n el = el.parentElement;\n }\n if (el && el.parentElement === document.body) {\n ancestorBodyChild = el;\n }\n Array.from(document.body.children).forEach((child) => {\n if (child === this || child === ancestorBodyChild) return;\n if (!child.hasAttribute('aria-hidden')) {\n child.setAttribute('aria-hidden', 'true');\n this._siblingAriaHiddenElements.push(child);\n }\n });\n }\n\n /** @internal */\n private _restoreBackgroundForScreenReaders(): void {\n this._siblingAriaHiddenElements.forEach((el) => {\n el.removeAttribute('aria-hidden');\n });\n this._siblingAriaHiddenElements = [];\n }\n\n // ─── Event Listeners (P1-01: use only document listener, not overlay) ───\n\n /** @internal */\n private _addListeners(): void {\n document.addEventListener('keydown', this._handleKeyDown);\n }\n\n /** @internal */\n private _removeListeners(): void {\n document.removeEventListener('keydown', this._handleKeyDown);\n }\n\n // ─── Keyboard Handler ───\n\n /**\n * Handles keyboard events on the document to trap focus and close the drawer on Escape.\n * @internal\n */\n private _handleKeyDown = (e: KeyboardEvent): void => {\n if (!this._isOpen) return;\n\n if (e.key === 'Escape') {\n e.preventDefault();\n this.open = false;\n return;\n }\n\n if (e.key === 'Tab') {\n this._trapFocus(e);\n }\n };\n\n // ─── Focus ───\n\n /** @internal */\n private _setInitialFocus(): void {\n const event = new CustomEvent<void>('hx-initial-focus', {\n bubbles: true,\n composed: true,\n cancelable: true,\n });\n this.dispatchEvent(event);\n\n if (!event.defaultPrevented) {\n const focusable = this._cachedFocusableElements;\n if (focusable.length > 0 && focusable[0]) {\n focusable[0].focus();\n } else {\n this._panelEl?.focus();\n }\n }\n }\n\n /** @internal */\n private _getFocusableElements(): HTMLElement[] {\n const shadowFocusable = Array.from(\n this.shadowRoot?.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS) ?? [],\n );\n\n const slots = this.shadowRoot?.querySelectorAll<HTMLSlotElement>('slot') ?? [];\n const lightFocusable: HTMLElement[] = [];\n\n slots.forEach((slot) => {\n slot.assignedElements({ flatten: true }).forEach((el) => {\n if (el instanceof HTMLElement) {\n if (el.matches(FOCUSABLE_SELECTORS)) {\n lightFocusable.push(el);\n }\n el.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS).forEach((child) => {\n lightFocusable.push(child);\n });\n }\n });\n });\n\n return [...shadowFocusable, ...lightFocusable].filter(\n (el) => !el.hasAttribute('disabled') && el.getAttribute('tabindex') !== '-1',\n );\n }\n\n /** @internal */\n private _trapFocus(e: KeyboardEvent): void {\n const focusable =\n this._cachedFocusableElements.length > 0\n ? this._cachedFocusableElements\n : this._getFocusableElements();\n\n if (focusable.length === 0) {\n e.preventDefault();\n return;\n }\n\n const [first, ...rest] = focusable;\n const last = rest.length > 0 ? rest[rest.length - 1] : first;\n\n if (!first || !last) return;\n\n // P1-02: Use document.activeElement for reliable detection of slotted (light DOM) elements.\n // shadowRoot.activeElement returns the <slot> host for slotted content, not the actual element.\n const active = document.activeElement as HTMLElement | null;\n\n if (e.shiftKey) {\n if (active === first) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (active === last) {\n e.preventDefault();\n first.focus();\n }\n }\n }\n\n // ─── Overlay Click ───\n\n /**\n * Handles clicks on the overlay backdrop to close the drawer when the user clicks outside the panel.\n * @internal\n */\n private _handleOverlayClick = (e: MouseEvent): void => {\n // Only close when clicking the overlay itself (backdrop), not the panel\n const target = e.target as HTMLElement;\n if (target === this._overlayEl || target.classList.contains('drawer-backdrop')) {\n this.open = false;\n }\n };\n\n // ─── Slot change handlers ───\n\n /** @internal */\n private _handleHeaderActionsSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n this._hasHeaderActionsSlot = slot.assignedNodes({ flatten: true }).length > 0;\n }\n\n /** @internal */\n private _handleFooterSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n this._hasFooterSlot = slot.assignedNodes({ flatten: true }).length > 0;\n }\n\n /** @internal */\n private _handleLabelSlotChange(e: Event): void {\n if (!(e.target instanceof HTMLSlotElement)) return;\n const state = this._readLabelSlotState(e.target);\n this._hasLabelSlot = state.hasUsefulName;\n this._slottedLabelEls = state.elements;\n this._labelSlotText = state.text;\n this._installLabelSlotTextObserver(state.elements);\n this._syncHostAriaSemantics();\n }\n\n // ─── Host-canonical ARIA helpers ───\n\n /**\n * Reads the label slot's assigned nodes and computes the discriminated\n * naming state. Aggregates ALL assigned elements (not just the first) so\n * composed labels project the FULL visible label via\n * `internals.ariaLabelledByElements`. Per AccName 1.2 §4.3.10,\n * `aria-hidden=\"true\"` / `[hidden]` elements contribute zero to the\n * accessible name but stay in `elements` so AT walking IDL refs sees the\n * full visible group. `hasUsefulName` is gated on the flattened text\n * length: a slot containing only decorative wrappers does NOT name the\n * dialog, and the host falls through to the next naming source.\n * @internal\n */\n private _readLabelSlotState(slot: HTMLSlotElement): {\n hasUsefulName: boolean;\n elements: Element[];\n text: string;\n } {\n const nodes = slot.assignedNodes({ flatten: true });\n const elements: Element[] = [];\n const fragments: string[] = [];\n for (const node of nodes) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const el = node as Element;\n elements.push(el);\n if (el.getAttribute('aria-hidden') === 'true') continue;\n const elText = flattenAccName(el);\n if (elText) fragments.push(elText);\n } else if (node.nodeType === Node.TEXT_NODE) {\n const txt = (node.textContent ?? '').trim();\n if (txt) fragments.push(txt);\n }\n }\n const trimmedText = fragments.join(' ').replace(/\\s+/g, ' ').trim();\n return {\n hasUsefulName: trimmedText.length > 0,\n elements,\n text: trimmedText,\n };\n }\n\n /**\n * (Re-)installs the mutation observer over the current set of slotted label\n * elements. On any descendant text/visibility mutation we re-flatten and\n * re-sync so the host's accessible name tracks the visible label.\n * @internal\n */\n private _installLabelSlotTextObserver(elements: Element[]): void {\n this._labelSlotTextObserver?.disconnect();\n if (elements.length === 0) {\n this._labelSlotTextObserver = null;\n return;\n }\n const observer = new MutationObserver(() => {\n const fragments: string[] = [];\n for (const el of elements) {\n if (el.getAttribute('aria-hidden') === 'true') continue;\n const t = flattenAccName(el);\n if (t) fragments.push(t);\n }\n const trimmed = fragments.join(' ').replace(/\\s+/g, ' ').trim();\n this._labelSlotText = trimmed;\n this._hasLabelSlot = trimmed.length > 0;\n this._syncHostAriaSemantics();\n });\n for (const el of elements) {\n observer.observe(el, {\n characterData: true,\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['aria-hidden', 'hidden'],\n });\n }\n this._labelSlotTextObserver = observer;\n }\n\n /**\n * (Re-)installs a `MutationObserver` against the deduped union of\n * consumer-resolved label/description elements. Watches `characterData`,\n * `childList`, `subtree`, and `aria-hidden` / `hidden` attributes so any\n * in-place mutation on the referenced light-DOM nodes triggers a fresh\n * sync — keeping the modern-path IDL refs and the fallback-path text\n * flatten aligned with the live consumer text.\n * @internal\n */\n private _installExternalRefsObserver(elements: Element[]): void {\n if (this._externalRefsObserver) {\n this._externalRefsObserver.disconnect();\n this._externalRefsObserver = null;\n }\n if (elements.length === 0) return;\n const unique = new Set<Element>(elements);\n const observer = new MutationObserver(() => {\n this._syncHostAriaSemantics();\n });\n for (const el of unique) {\n observer.observe(el, {\n characterData: true,\n subtree: true,\n childList: true,\n attributes: true,\n attributeFilter: ['aria-hidden', 'hidden'],\n });\n }\n this._externalRefsObserver = observer;\n }\n\n /**\n * Resolves consumer-supplied label/description IDREFs on the host and\n * projects the canonical dialog ARIA onto the **host** via\n * `ElementInternals` (modern path) and onto the inner overlay via attribute\n * writes (fallback path).\n *\n * Cross-shadow naming is belt-and-suspenders:\n *\n * 1. **Modern path** (`_supportsIdrefRefs === true`): consumer-resolved\n * label/description elements are written onto\n * `internals.ariaLabelledByElements` / `internals.ariaDescribedByElements`\n * on the host. AT walking the host's accessibility tree finds them\n * across the shadow boundary. `internals.ariaLabel` carries the\n * flattened text fallback (only when no labelledby resolves) so AT\n * that does not walk IDL refs still announces a name.\n * 2. **Fallback path** (`_supportsIdrefRefs === false`): the resolved\n * element text is flattened and written to `internals.ariaLabel` AND\n * mirrored to the inner overlay's `aria-label`.\n *\n * The synthesized `<span id=\"${_consumerDescId}\">` mirrors the resolved\n * description text on every sync. The inner overlay's `aria-describedby`\n * chains the in-shadow span. `aria-description` is intentionally NEVER\n * written — W3C AccName ignores it whenever `aria-describedby` is set.\n *\n * Naming precedence (W3C AccName 1.2 §4.3.1):\n * 1. Consumer `aria-labelledby` (resolved IDREFs, text-flattened)\n * 2. Consumer `aria-label`\n * 3. Slotted `<slot name=\"label\">` text\n * 4. `label` property\n * 5. Hard-coded `\"Drawer\"`\n * @internal\n */\n private _syncHostAriaSemantics(): void {\n const internals = this._internals;\n // The host's canonical role + modal flag are seeded in `connectedCallback`.\n // We do NOT rewrite them on every sync — they are stable for the\n // component's lifetime and rewriting them on every render shifts focus\n // tracking on some Chromium / WebKit builds (idempotent writes can still\n // re-enter the AT layer's accessibility tree builder).\n\n // Refresh the consumer baseline. The host attribute IS the live source\n // of truth — `null` authentically represents consumer retraction.\n const liveLabelledBy = this.getAttribute('aria-labelledby');\n this._consumerLabelledBy = liveLabelledBy;\n const liveDescribedBy = this.getAttribute('aria-describedby');\n this._consumerDescribedBy = liveDescribedBy;\n\n const consumerLabelEls = resolveIdrefTokens(this, this._consumerLabelledBy);\n const hasEffectiveLabelledBy = consumerLabelEls.length > 0;\n const consumerDescEls = resolveIdrefTokens(this, this._consumerDescribedBy);\n\n // Observe in-place mutations on the resolved external IDREF targets.\n // Without this a consumer mutating `<h2 id=\"x\">Patient</h2>` → \"Member\"\n // in place leaves the host's flattened `aria-label` stuck on \"Patient\".\n this._installExternalRefsObserver([...consumerLabelEls, ...consumerDescEls]);\n\n // Per AccName 1.2 §4.3.10, top-level aria-hidden / hidden elements\n // contribute zero to the name. Filter them from the IDL-refs path so the\n // modern path matches the fallback path's text-flatten behavior.\n const isVisibleForAccName = (el: Element): boolean =>\n el.getAttribute('aria-hidden') !== 'true' && !el.hasAttribute('hidden');\n\n const liveAriaLabel = this.getAttribute('aria-label');\n const hostAriaLabel = liveAriaLabel !== null ? liveAriaLabel.trim() : '';\n\n // Build the augmented label-elements list used by the modern path.\n // Slotted-title elements feed in only when no consumer aria-labelledby\n // resolved (AccName 1.2 precedence: external > slot > property).\n const labelElsForInternals: Element[] = [];\n labelElsForInternals.push(...consumerLabelEls.filter(isVisibleForAccName));\n if (!hasEffectiveLabelledBy && !hostAriaLabel && this._hasLabelSlot) {\n // Aggregate every slotted label element so AT composes icon + text.\n labelElsForInternals.push(...this._slottedLabelEls.filter(isVisibleForAccName));\n }\n\n const descElsForInternals: Element[] = [...consumerDescEls.filter(isVisibleForAccName)];\n\n // ─── Compute the resolved accessible name (text-flatten path) ───\n const flattenText = (els: Element[]): string =>\n els\n .filter(isVisibleForAccName)\n .map((el) => flattenAccName(el))\n .filter((t) => t.length > 0)\n .join(' ');\n\n let resolvedName = '';\n if (hasEffectiveLabelledBy) {\n resolvedName = flattenText(consumerLabelEls);\n }\n if (!resolvedName && hostAriaLabel) {\n resolvedName = hostAriaLabel;\n }\n if (!resolvedName && this._hasLabelSlot && this._labelSlotText) {\n resolvedName = this._labelSlotText;\n }\n if (!resolvedName && this.label) {\n resolvedName = this.label;\n }\n if (!resolvedName) {\n // Last-resort literal — preserves the pre-host-canonical default so an\n // unlabeled drawer still has SOME announced name. Consumer responsibility\n // to provide a meaningful one in real usage.\n resolvedName = 'Drawer';\n }\n\n // ─── Modern-path: ElementInternals IDL element references ───\n type InternalsWithIdrefRefs = ElementInternals & {\n ariaLabelledByElements: Element[] | null;\n ariaDescribedByElements: Element[] | null;\n };\n if (this._supportsIdrefRefs) {\n const refsInternals = internals as InternalsWithIdrefRefs;\n refsInternals.ariaLabelledByElements =\n labelElsForInternals.length > 0 ? labelElsForInternals : null;\n refsInternals.ariaDescribedByElements =\n descElsForInternals.length > 0 ? descElsForInternals : null;\n // Forward `aria-label` to `internals.ariaLabel` ONLY when no labelledby\n // resolved — per AccName 1.2 a non-empty aria-label outranks\n // aria-labelledby, and we never want to silently erase the IDL-ref\n // resolution. When labelledby is present, `null` removes the override\n // so element references win.\n if (hasEffectiveLabelledBy) {\n internals.ariaLabel = null;\n } else {\n internals.ariaLabel = resolvedName;\n }\n } else {\n // Fallback path: write the flattened name directly to internals.\n // Older engines without IDL refs use this as the canonical name.\n internals.ariaLabel = resolvedName;\n }\n\n // ─── Synthesized in-shadow consumer-description span ───\n // Mirror consumer-resolved description text into a same-root span so the\n // inner overlay's `aria-describedby` resolves cross-shadow without\n // pointing at light-DOM ids (which do not resolve from inside a shadow\n // root). `aria-description` is intentionally NEVER written.\n const consumerDescSpan = this.shadowRoot?.getElementById(this._consumerDescId) ?? null;\n const consumerDescText = flattenText(consumerDescEls);\n if (consumerDescSpan && consumerDescSpan.textContent !== consumerDescText) {\n consumerDescSpan.textContent = consumerDescText;\n }\n\n // ─── Inner overlay attribute reconciliation ───\n // The overlay no longer carries `role` / `aria-modal` / `aria-labelledby`\n // / `aria-label` on the modern path — the host owns those via internals.\n // Strip stale attributes defensively in case an earlier sync wrote them\n // and reconcile the fallback `aria-label` only when IDL refs are not\n // supported (so screen readers walking the inner overlay still find a\n // name on legacy engines).\n const overlay = this._overlayEl ?? null;\n if (overlay) {\n // Belt-and-suspenders: never write `role` / `aria-modal` to the inner\n // overlay on either path — that would create nested-dialog semantics\n // above the host's canonical surface.\n if (overlay.hasAttribute('role')) overlay.removeAttribute('role');\n if (overlay.hasAttribute('aria-modal')) overlay.removeAttribute('aria-modal');\n // Internal slotted-title id on the SAME shadow root resolves cleanly,\n // so we project it onto the overlay's `aria-labelledby` only on the\n // fallback path. The modern path uses `internals.ariaLabelledByElements`.\n const wantOverlayLabelledBy =\n !this._supportsIdrefRefs && this._hasLabelSlot ? this._titleId : null;\n const wantOverlayLabel =\n !this._supportsIdrefRefs && !wantOverlayLabelledBy ? resolvedName : null;\n if (wantOverlayLabelledBy) {\n if (overlay.getAttribute('aria-labelledby') !== wantOverlayLabelledBy) {\n overlay.setAttribute('aria-labelledby', wantOverlayLabelledBy);\n }\n } else if (overlay.hasAttribute('aria-labelledby')) {\n overlay.removeAttribute('aria-labelledby');\n }\n if (wantOverlayLabel) {\n if (overlay.getAttribute('aria-label') !== wantOverlayLabel) {\n overlay.setAttribute('aria-label', wantOverlayLabel);\n }\n } else if (overlay.hasAttribute('aria-label')) {\n overlay.removeAttribute('aria-label');\n }\n\n // Inner overlay's `aria-describedby` chains the in-shadow consumer-desc\n // span. Same-root id resolves cleanly; cross-shadow consumer ids are\n // ignored at the AT level (light-DOM ids do not resolve from inside a\n // shadow root) so we never write them directly.\n if (consumerDescText && consumerDescSpan) {\n const value = this._consumerDescId;\n if (overlay.getAttribute('aria-describedby') !== value) {\n overlay.setAttribute('aria-describedby', value);\n }\n } else if (overlay.hasAttribute('aria-describedby')) {\n overlay.removeAttribute('aria-describedby');\n }\n\n // Forced-colors: the host has display: contents so :host(:focus-visible)\n // has no painting surface — focus is on the inner panel. The existing\n // `forcedColorsSurface` mixin paints the host CanvasText border which\n // the panel inherits via the parent box. No change owed here, but if a\n // future change makes the host focusable directly we'd add the rule.\n }\n\n // Strip `aria-description` defensively — never written on either path.\n if (overlay && overlay.hasAttribute('aria-description')) {\n overlay.removeAttribute('aria-description');\n }\n }\n\n // ─── Render Helpers ───\n\n /** @internal */\n private _renderHeader() {\n if (this.noHeader) {\n // WCAG 4.1.2: When the header is hidden there must still be a reachable close\n // mechanism for keyboard and mouse/touch users. Render a visually-hidden close\n // button that is focusable and announced by screen readers.\n return html`\n <button\n part=\"close-btn\"\n class=\"drawer-close-button drawer-close-button--sr-only\"\n aria-label=${this.labelClose}\n @click=${() => {\n this.open = false;\n }}\n ></button>\n `;\n }\n\n return html`\n <div part=\"header\" class=\"drawer-header\">\n <h2 part=\"title\" id=${this._titleId} class=\"drawer-title\">\n <slot name=\"label\" @slotchange=${this._handleLabelSlotChange}></slot>\n </h2>\n <div class=\"drawer-header-actions\">\n ${this._hasHeaderActionsSlot\n ? html`<slot\n name=\"header-actions\"\n @slotchange=${this._handleHeaderActionsSlotChange}\n ></slot>`\n : html`<slot\n name=\"header-actions\"\n @slotchange=${this._handleHeaderActionsSlotChange}\n style=\"display:none\"\n ></slot>`}\n <button\n part=\"close-button\"\n class=\"drawer-close-button\"\n aria-label=${this.labelClose}\n @click=${() => {\n this.open = false;\n }}\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n </div>\n </div>\n `;\n }\n\n /** @internal */\n private _renderFooter() {\n if (this.noFooter) return nothing;\n\n return html`\n <div part=\"footer\" class=\"drawer-footer\" ?hidden=${!this._hasFooterSlot}>\n <slot name=\"footer\" @slotchange=${this._handleFooterSlotChange}></slot>\n </div>\n `;\n }\n\n // ─── Render ───\n\n override render() {\n const overlayClasses = {\n 'drawer-overlay': true,\n 'is-open': this._isOpen,\n };\n\n // Host-canonical: the inner overlay carries NO `role` / `aria-modal` /\n // `aria-labelledby` / `aria-label` here. Those are projected onto the\n // host via `ElementInternals` in `_syncHostAriaSemantics()`. On the\n // legacy (no-IDL-ref) fallback path the sync method imperatively writes\n // `aria-label` / `aria-labelledby` onto the overlay so AT walking down\n // from the host still finds an announceable name.\n return html`\n <div\n part=\"overlay\"\n class=${classMap(overlayClasses)}\n tabindex=\"-1\"\n @click=${this._handleOverlayClick}\n >\n <div class=\"drawer-backdrop\" aria-hidden=\"true\"></div>\n <div part=\"panel\" class=\"drawer-panel\" tabindex=\"-1\">\n ${this._renderHeader()}\n <div part=\"body\" class=\"drawer-body\">\n <slot></slot>\n </div>\n ${this._renderFooter()}\n </div>\n <!--\n Synthesized in-shadow span carrying consumer-resolved description\n text. Updated imperatively on every sync. The inner overlay's\n \\`aria-describedby\\` references this span so cross-shadow consumer\n descriptions resolve through the standard described-by channel\n without writing light-DOM ids that cannot resolve from inside a\n shadow root. \\`aria-description\\` is intentionally NEVER written —\n AccName ignores it whenever \\`aria-describedby\\` is present.\n -->\n <span id=${this._consumerDescId} class=\"drawer-sr-only\" aria-hidden=\"false\"></span>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-drawer': HelixDrawer;\n }\n interface HTMLElementEventMap {\n 'hx-show': CustomEvent<void>;\n 'hx-after-show': CustomEvent<void>;\n 'hx-hide': CustomEvent<void>;\n 'hx-after-hide': CustomEvent<void>;\n 'hx-initial-focus': CustomEvent<void>;\n }\n}\n"],"names":["helixDrawerStyles","css","_nextDrawerId","createIdCounter","DRAWER_SIZE_MAP","FOCUSABLE_SELECTORS","HelixDrawer","HelixElement","target","legacySize","ctor","supportsIdrefElementReferences","records","consumerCleared","record","oldValue","newValue","installAriaIdrefMirror","_a","_b","_c","_d","changedProperties","resolvedSize","lockBodyScroll","unlockBodyScroll","active","duration","panel","emitAfterShow","emitAfterHide","ancestorBodyChild","el","child","event","focusable","shadowFocusable","slots","lightFocusable","slot","first","rest","last","state","nodes","elements","fragments","node","elText","flattenAccName","txt","trimmedText","observer","t","trimmed","unique","internals","liveLabelledBy","liveDescribedBy","consumerLabelEls","resolveIdrefTokens","hasEffectiveLabelledBy","consumerDescEls","isVisibleForAccName","liveAriaLabel","hostAriaLabel","labelElsForInternals","descElsForInternals","flattenText","els","resolvedName","refsInternals","consumerDescSpan","consumerDescText","overlay","wantOverlayLabelledBy","wantOverlayLabel","value","html","nothing","overlayClasses","classMap","forcedColorsSurface","__decorateClass","query","property","customElement"],"mappings":";;;;;;;;;AAYO,MAAMA,IAAoBC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"hx-tab-panel-Cu--8psg.js","sources":["../../src/components/hx-tabs/hx-tabs.styles.ts","../../src/components/hx-tabs/hx-tabs.ts","../../src/components/hx-tabs/hx-tab.styles.ts","../../src/components/hx-tabs/hx-tab.ts","../../src/components/hx-tabs/hx-tab-panel.styles.ts","../../src/components/hx-tabs/hx-tab-panel.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const helixTabsStyles = css`\n :host {\n display: block;\n font-family: var(--hx-tabs-font-family, var(--hx-font-family-sans, sans-serif));\n }\n\n * {\n box-sizing: border-box;\n }\n\n /* ─── Container ─── */\n\n .tabs {\n display: flex;\n flex-direction: column;\n gap: var(--hx-tabs-gap, 0);\n }\n\n :host([orientation='vertical']) .tabs {\n flex-direction: row;\n }\n\n /* ─── Tablist ─── */\n\n .tablist {\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n gap: 0;\n border-bottom: var(--hx-tabs-border-width, 1px) solid\n var(--hx-tabs-border-color, var(--hx-color-neutral-200, #d6dbd5));\n overflow-x: auto;\n scrollbar-width: none;\n }\n\n .tablist::-webkit-scrollbar {\n display: none;\n }\n\n /* ─── Vertical Orientation ─── */\n\n :host([orientation='vertical']) {\n --_tab-indicator-bottom: 0px;\n --_tab-indicator-end: var(--hx-tabs-indicator-size, 2px);\n --_tab-indicator-bottom-color: transparent;\n --_tab-indicator-end-color: var(\n --hx-tabs-indicator-color,\n var(--hx-color-primary-500, #429797)\n );\n }\n\n :host([orientation='vertical']) .tablist {\n flex-direction: column;\n border-bottom: none;\n border-inline-end: var(--hx-tabs-border-width, 1px) solid\n var(--hx-tabs-border-color, var(--hx-color-neutral-200, #d6dbd5));\n overflow-x: visible;\n overflow-y: auto;\n min-width: var(--hx-tabs-vertical-width, 12rem);\n flex-shrink: 0;\n }\n\n /* ─── Panels Container ─── */\n\n .panels {\n flex: 1 1 auto;\n min-width: 0;\n }\n\n /* ─── Reduced Motion ─── */\n\n @media (prefers-reduced-motion: reduce) {\n .tablist {\n scroll-behavior: auto;\n }\n }\n\n /* ─── High Contrast Mode (forced-colors) ─── */\n\n @media (forced-colors: active) {\n .tablist {\n border-bottom-color: CanvasText;\n }\n\n :host([orientation='vertical']) .tablist {\n border-inline-end-color: CanvasText;\n }\n }\n`;\n","import { html, type PropertyValues } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { HelixElement, createIdCounter } from '../../base/index.js';\nimport { forcedColorsInteractive } from '../../styles/forced-colors.js';\nimport { helixTabsStyles } from './hx-tabs.styles.js';\nimport type { HelixTab } from './hx-tab.js';\nimport type { HelixTabPanel } from './hx-tab-panel.js';\nimport { devWarn } from '../../utils/dev-warn.js';\nimport {\n installAriaIdrefMirror,\n resolveIdrefTokens,\n supportsIdrefElementReferences,\n type AriaIdrefMirrorHandle,\n} from '../../utils/aria-idref.js';\nimport { flattenAccName } from '../../utils/aria-flatten.js';\n\nconst _nextTabsId = createIdCounter('hx-tabs');\n\n/** Detail for the hx-tab-change event dispatched by hx-tabs. */\nexport interface HxTabChangeDetail {\n tabId: string;\n index: number;\n}\n\n/**\n * A tabbed content organizer that manages a set of `<hx-tab>` and `<hx-tab-panel>` children.\n * Supports horizontal and vertical orientations, automatic and manual activation modes,\n * and full keyboard navigation per the ARIA Authoring Practices Guide.\n *\n * Group 5a host-canonical: `role=\"tablist\"` lives on the host via\n * `_internals.role`. `aria-orientation`, `aria-label`, and consumer\n * `aria-labelledby` resolve through the host. Per-tab `role=\"tab\"` and\n * per-panel `role=\"tabpanel\"` likewise live on their respective hosts.\n *\n * Activation defaults to **manual** per healthcare patterns — keyboard arrow\n * keys move focus only; Enter/Space activates. APG explicitly allows both\n * automatic and manual activation; manual is safer when panels are heavy or\n * announce changes via live regions.\n *\n * @summary Tab container that organizes content into selectable panels.\n *\n * @tag hx-tabs\n *\n * @slot tab - Slot for `<hx-tab>` elements. Rendered inside the tablist.\n * @slot - Default slot for `<hx-tab-panel>` elements.\n *\n * @fires {CustomEvent<{tabId: string, index: number}>} hx-tab-change - Dispatched when the active tab changes.\n *\n * @csspart tablist - The tablist container element.\n * @csspart panels - The panel content container element.\n *\n * @cssprop [--hx-tabs-border-color=var(--hx-color-neutral-200, #D6DBD5)] - Tablist border color.\n * @cssprop [--hx-tabs-border-width=1px] - Tablist border width.\n * @cssprop [--hx-tabs-vertical-width=12rem] - Width of the tablist in vertical orientation.\n * @cssprop [--hx-tabs-gap=0] - Gap between the tablist and panels container.\n * @cssprop [--hx-tabs-tab-color=var(--hx-color-neutral-600, #4A5362)] - Inactive tab text color.\n * @cssprop [--hx-tabs-tab-active-color=var(--hx-color-primary-600, #0F7078)] - Active tab text color.\n * @cssprop [--hx-tabs-tab-hover-color=var(--hx-color-neutral-800, #202B39)] - Tab hover text color.\n * @cssprop [--hx-tabs-tab-hover-bg=var(--hx-color-neutral-50, #F5F8F3)] - Tab hover background.\n * @cssprop [--hx-tabs-tab-font-size=var(--hx-font-size-md, 1rem)] - Tab font size.\n * @cssprop [--hx-tabs-tab-font-weight=var(--hx-font-weight-medium, 500)] - Tab font weight.\n * @cssprop [--hx-tabs-tab-active-font-weight=var(--hx-font-weight-semibold, 600)] - Active tab font weight.\n * @cssprop [--hx-tabs-tab-padding-x=var(--hx-space-4, 1rem)] - Horizontal tab padding.\n * @cssprop [--hx-tabs-tab-padding-y=var(--hx-space-2, 0.5rem)] - Vertical tab padding.\n * @cssprop [--hx-tabs-indicator-color=var(--hx-color-primary-500, #429797)] - Active indicator color.\n * @cssprop [--hx-tabs-indicator-size=2px] - Active indicator thickness.\n * @cssprop [--hx-tabs-focus-ring-color=var(--hx-focus-ring-color, #6AB1B1)] - Focus ring color for tabs and panels.\n * @cssprop [--hx-tabs-panel-padding=var(--hx-space-4, 1rem)] - Panel inner padding.\n * @cssprop [--hx-tabs-panel-color=var(--hx-color-neutral-700, #313E4B)] - Panel text color.\n * @cssprop [--hx-tabs-font-family=var(--hx-font-family-sans)] - CSS custom property.\n * @cssprop [--hx-font-family-sans] - Font family.\n * @cssprop [--hx-color-neutral-200] - Color.\n * @cssprop [--hx-color-primary-500] - Color.\n */\n@customElement('hx-tabs')\nexport class HelixTabs extends HelixElement {\n static override styles = [helixTabsStyles, forcedColorsInteractive];\n\n // ─── Internal ID ───\n\n /** @internal */\n private _id = _nextTabsId();\n\n // ─── Properties ───\n\n /**\n * The layout orientation of the tabs.\n * @attr orientation\n */\n @property({ type: String, reflect: true })\n orientation: 'horizontal' | 'vertical' = 'horizontal';\n\n /**\n * Controls how keyboard navigation activates tabs.\n * In `automatic` mode, focus also activates the tab.\n * In `manual` mode, focus moves independently; Space or Enter activates.\n *\n * Group 5a default: `manual` — safer for healthcare patterns where panel\n * content may be heavy or announce updates via live regions. APG explicitly\n * allows both modes; manual avoids disorienting auto-activation when users\n * scan tabs with arrow keys.\n *\n * @attr activation\n */\n @property({ type: String, attribute: 'activation', reflect: true })\n activation: 'manual' | 'automatic' = 'manual';\n\n /**\n * Accessible label for the tablist. Drives the host `internals.ariaLabel`.\n * Provide a brief description of what the tabs represent (e.g., \"Patient\n * record sections\"). Consumer `aria-label` / `aria-labelledby` on the host\n * override this property when present.\n * @attr label\n */\n @property({ type: String, reflect: true })\n label = '';\n\n // ─── State ───\n\n /** @internal */\n @state() private _activePanel = '';\n\n /** @internal */\n @state() private _supportsIdrefRefs = true;\n\n // ─── Host-canonical ARIA bookkeeping ───\n\n /** @internal */\n private _ariaMirror: AriaIdrefMirrorHandle | null = null;\n\n // ─── Child Accessors ───\n\n /** @internal */\n private _cachedTabs: HelixTab[] | null = null;\n /** @internal */\n private _cachedPanels: HelixTabPanel[] | null = null;\n /** @internal */\n private _observer: MutationObserver | null = null;\n /**\n * Stores a requested tab index from the `selected-index` attribute before the component\n * has finished its first update (e.g. server-rendered Drupal pages).\n * @internal\n */\n private _pendingIndex: number | null = null;\n\n // ─── Attribute Observation ───\n\n static override get observedAttributes(): string[] {\n return [...(super.observedAttributes ?? []), 'selected-index'];\n }\n\n override attributeChangedCallback(name: string, old: string | null, value: string | null): void {\n super.attributeChangedCallback(name, old, value);\n if (name === 'selected-index' && value !== null && old !== value) {\n const index = parseInt(value, 10);\n if (!isNaN(index) && index >= 0) {\n if (this.hasUpdated) {\n // Already initialised — apply immediately\n const tab = this._getTabs()[index];\n if (tab && !tab.disabled) {\n this._activateTab(tab, false);\n }\n } else {\n // Store for application in firstUpdated\n this._pendingIndex = index;\n }\n }\n }\n }\n\n // ─── Public API ───\n\n /**\n * Gets or sets the zero-based index of the currently selected tab.\n * Setting this programmatically activates the tab at the given index.\n * Can also be set via the `selected-index` HTML attribute for server-side\n * pre-selection (e.g. Drupal Twig templates).\n */\n get selectedIndex(): number {\n return this._getTabs().findIndex((tab) => tab.panel === this._activePanel);\n }\n\n set selectedIndex(index: number) {\n const tab = this._getTabs()[index];\n if (tab && !tab.disabled) {\n this._activateTab(tab, true);\n }\n }\n\n /** @internal */\n private _getTabs(): HelixTab[] {\n if (!this._cachedTabs) {\n this._cachedTabs = Array.from(this.querySelectorAll(':scope > hx-tab')).filter(\n (el): el is HelixTab => el.tagName.toLowerCase() === 'hx-tab',\n );\n }\n return this._cachedTabs;\n }\n\n /** @internal */\n private _getPanels(): HelixTabPanel[] {\n if (!this._cachedPanels) {\n this._cachedPanels = Array.from(this.querySelectorAll(':scope > hx-tab-panel')).filter(\n (el): el is HelixTabPanel => el.tagName.toLowerCase() === 'hx-tab-panel',\n );\n }\n return this._cachedPanels;\n }\n\n /** @internal */\n private _getEnabledTabs(): HelixTab[] {\n return this._getTabs().filter((tab) => !tab.disabled);\n }\n\n // ─── Lifecycle ───\n\n override connectedCallback(): void {\n super.connectedCallback();\n this._supportsIdrefRefs = supportsIdrefElementReferences(this._internals);\n this.addEventListener('hx-tab-select', this._handleTabSelect);\n this.addEventListener('keydown', this._handleKeydown);\n // Watch for panel/name attribute changes on child tabs and panels\n if (typeof MutationObserver !== 'undefined') {\n this._observer = new MutationObserver(() => {\n this._cachedTabs = null;\n this._cachedPanels = null;\n this._syncTabsAndPanels();\n });\n this._observer.observe(this, {\n subtree: false,\n attributeFilter: ['panel', 'name'],\n });\n }\n // Seed host-canonical semantics so the role/label appear before first paint.\n this._syncHostAriaSemantics();\n this._ariaMirror = installAriaIdrefMirror(this, () => {\n this._syncHostAriaSemantics();\n });\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener('hx-tab-select', this._handleTabSelect);\n this.removeEventListener('keydown', this._handleKeydown);\n this._observer?.disconnect();\n this._observer = null;\n this._ariaMirror?.disconnect();\n this._ariaMirror = null;\n }\n\n override firstUpdated(): void {\n if (this.label === '') {\n devWarn(\n 'hx-tabs',\n 'No accessible label provided. Set the `label` attribute on hx-tabs to describe what the tabs represent (e.g., \"Patient record sections\"). An unlabeled tablist violates WCAG 4.1.2.',\n );\n }\n\n this._syncTabsAndPanels();\n\n // Apply a pending selected-index (set via HTML attribute before upgrade, e.g. Drupal Twig)\n if (this._pendingIndex !== null) {\n const pendingTab = this._getTabs()[this._pendingIndex];\n this._pendingIndex = null;\n if (pendingTab && !pendingTab.disabled) {\n this._activateTab(pendingTab, false);\n return;\n }\n }\n\n // Activate the first enabled tab if none is selected\n if (!this._activePanel) {\n const firstEnabled = this._getEnabledTabs()[0];\n if (firstEnabled) {\n this._activateTab(firstEnabled, false);\n }\n }\n }\n\n override updated(changedProperties: PropertyValues<this>): void {\n super.updated(changedProperties);\n if ((changedProperties as Map<PropertyKey, unknown>).has('_activePanel')) {\n this._updateTabsAndPanels();\n }\n if (\n (changedProperties as Map<PropertyKey, unknown>).has('orientation') ||\n (changedProperties as Map<PropertyKey, unknown>).has('label')\n ) {\n this._syncHostAriaSemantics();\n }\n }\n\n // ─── Host ARIA Sync ───\n\n /**\n * Mirror tablist semantics onto the host via ElementInternals so consumer-\n * supplied `aria-label`, `aria-labelledby`, and the `label` property all\n * reach the announced control. The host carries `role=\"tablist\"` and the\n * orientation reflects the `orientation` property reactively.\n * @internal\n */\n private _syncHostAriaSemantics(): void {\n const internals = this._internals;\n internals.role = 'tablist';\n internals.ariaOrientation = this.orientation;\n\n const hostAriaLabel = this.getAttribute('aria-label')?.trim() || '';\n const consumerLabelledBy = this.getAttribute('aria-labelledby');\n const labelEls = resolveIdrefTokens(this, consumerLabelledBy);\n const hasEffectiveLabelledBy = labelEls.length > 0;\n\n type InternalsWithRefs = ElementInternals & {\n ariaLabelledByElements: Element[] | null;\n };\n\n if (this._supportsIdrefRefs) {\n const refsInternals = internals as InternalsWithRefs;\n refsInternals.ariaLabelledByElements = hasEffectiveLabelledBy ? labelEls : null;\n }\n\n // Precedence: consumer aria-label > consumer aria-labelledby (resolved) >\n // `label` property. When labelledby resolves on the modern path, the IDL\n // refs above carry the live target; clear ariaLabel so the element refs\n // win. On the fallback path, flatten the labelledby targets to a string.\n if (hostAriaLabel) {\n internals.ariaLabel = hostAriaLabel;\n } else if (hasEffectiveLabelledBy) {\n if (this._supportsIdrefRefs) {\n internals.ariaLabel = null;\n } else {\n internals.ariaLabel =\n labelEls\n .map((el) => flattenAccName(el))\n .filter(Boolean)\n .join(' ') ||\n this.label ||\n null;\n }\n } else {\n internals.ariaLabel = this.label || null;\n }\n }\n\n // ─── Tab / Panel Sync ───\n\n /** @internal */\n private _syncTabsAndPanels(): void {\n const tabs = this._getTabs();\n const panels = this._getPanels();\n\n tabs.forEach((tab, i) => {\n const tabId = tab.id || `hx-tab-${this._id}-${i}`;\n tab.id = tabId;\n\n // Connect tab to its panel by aria-controls\n const panelName = tab.panel;\n const panel = panels.find((p) => p.name === panelName) ?? panels[i];\n if (panel) {\n const panelId = panel.id || `hx-panel-${this._id}-${i}`;\n panel.id = panelId;\n // String IDREF (legacy fallback path) — the inner button mirrors this\n // when the platform lacks IDL element references.\n tab.controls = panelId;\n // Modern path: project the panel host as an element reference so AT\n // walks across the shadow boundary by reference rather than IDREF.\n tab.setControlsPanel(panel);\n // Project the tab host as the panel's labelledby reference. Cross-\n // shadow naming via IDL element references resolves the tab's\n // accessible name (its slotted label content) without serialization.\n // Legacy fallback: the parent additionally writes a flattened\n // `aria-label` string on the panel host so AT without IDL refs still\n // names the panel.\n panel.setLabelledByTabs([tab]);\n\n if (!this._supportsIdrefRefs) {\n // Extract only default-slot children (no `slot` attribute) to exclude\n // prefix/suffix slot content (e.g. badge counts) from the panel\n // accessible name (WCAG 1.3.1).\n const tabLabel = Array.from(tab.childNodes)\n .filter(\n (node) =>\n node.nodeType === Node.TEXT_NODE ||\n (node.nodeType === Node.ELEMENT_NODE && !(node as Element).hasAttribute('slot')),\n )\n .map((node) => node.textContent ?? '')\n .join('')\n .trim();\n if (tabLabel) {\n panel.setAttribute('aria-label', tabLabel);\n panel.removeAttribute('aria-labelledby');\n } else {\n // Fall back to aria-labelledby string ID if no text content yet;\n // this gets corrected on the next slotchange.\n panel.setAttribute('aria-labelledby', tabId);\n }\n } else {\n // Modern path: scrub legacy fallback attributes so a previously-\n // mounted-on-legacy panel doesn't leak stale strings.\n panel.removeAttribute('aria-label');\n panel.removeAttribute('aria-labelledby');\n }\n }\n });\n\n this._updateTabsAndPanels();\n }\n\n /** @internal */\n private _updateTabsAndPanels(): void {\n const tabs = this._getTabs();\n const panels = this._getPanels();\n\n tabs.forEach((tab) => {\n const isSelected = tab.panel === this._activePanel;\n tab.selected = isSelected;\n // Single-host roving tabindex (Group 5a): the host is the only focusable\n // surface for the tab. The inner button is `tabindex=-1` and\n // presentational on the modern path. document.activeElement compares\n // directly against the host.\n tab.tabIndex = isSelected ? 0 : -1;\n });\n\n panels.forEach((panel) => {\n const isActive = panel.name === this._activePanel;\n if (isActive) {\n panel.removeAttribute('hidden');\n panel.setAttribute('tabindex', '0');\n } else {\n panel.setAttribute('hidden', '');\n panel.setAttribute('tabindex', '-1');\n }\n });\n }\n\n // ─── Tab Activation ───\n\n /** @internal */\n private _activateTab(tab: HelixTab, dispatchEvent = true): void {\n if (tab.disabled) {\n return;\n }\n\n const tabs = this._getTabs();\n const previousPanel = this._activePanel;\n this._activePanel = tab.panel;\n\n if (dispatchEvent && previousPanel !== this._activePanel) {\n const index = tabs.indexOf(tab);\n /**\n * Dispatched when the active tab changes.\n * @event hx-tab-change\n */\n this.dispatchEvent(\n new CustomEvent<{ tabId: string; index: number }>('hx-tab-change', {\n bubbles: true,\n composed: true,\n detail: { tabId: tab.id, index },\n }),\n );\n }\n }\n\n // ─── Event Handling ───\n\n /** @internal */\n private _handleTabSelect = (e: Event): void => {\n if (!(e instanceof CustomEvent)) return;\n e.stopPropagation();\n const tab = e\n .composedPath()\n .find((el): el is HelixTab => el instanceof Element && el.tagName.toLowerCase() === 'hx-tab');\n if (tab) {\n this._activateTab(tab);\n }\n };\n\n /** @internal */\n private _warnInvalidSlotContent(): void {\n const tabSlot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot[name=\"tab\"]');\n const panelSlot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot:not([name])');\n if (tabSlot) {\n const invalid = tabSlot\n .assignedElements()\n .filter((el) => el.tagName.toLowerCase() !== 'hx-tab');\n if (invalid.length > 0) {\n devWarn(\n 'hx-tabs',\n `Slot \"tab\" expects <hx-tab> elements. Found unexpected: ${invalid.map((el) => `<${el.tagName.toLowerCase()}>`).join(', ')}`,\n );\n }\n }\n if (panelSlot) {\n const invalid = panelSlot\n .assignedElements()\n .filter((el) => el.tagName.toLowerCase() !== 'hx-tab-panel');\n if (invalid.length > 0) {\n devWarn(\n 'hx-tabs',\n `Default slot expects <hx-tab-panel> elements. Found unexpected: ${invalid.map((el) => `<${el.tagName.toLowerCase()}>`).join(', ')}`,\n );\n }\n }\n }\n\n /** @internal */\n private _handleSlotChange = (): void => {\n this._warnInvalidSlotContent();\n this._cachedTabs = null;\n this._cachedPanels = null;\n this._syncTabsAndPanels();\n // If the active panel was removed, fall back to the first enabled tab\n const panels = this._getPanels();\n const activePanelExists = panels.some((p) => p.name === this._activePanel);\n if (!activePanelExists) {\n const firstEnabled = this._getEnabledTabs()[0];\n if (firstEnabled) {\n this._activateTab(firstEnabled, false);\n } else {\n this._activePanel = '';\n }\n }\n };\n\n /** @internal */\n private _handleKeydown = (e: KeyboardEvent): void => {\n // Use ALL tabs (including disabled) so keyboard users can discover disabled tabs\n // per ARIA APG tab pattern — disabled tabs receive focus but are not activated.\n const allTabs = this._getTabs();\n if (allTabs.length === 0) {\n return;\n }\n\n const isHorizontal = this.orientation === 'horizontal';\n const prevKey = isHorizontal ? 'ArrowLeft' : 'ArrowUp';\n const nextKey = isHorizontal ? 'ArrowRight' : 'ArrowDown';\n\n const isNavigationKey = [prevKey, nextKey, 'Home', 'End', ' ', 'Enter'].includes(e.key);\n if (!isNavigationKey) {\n return;\n }\n\n // Determine focused tab — host-canonical: the host IS the focusable\n // surface, so document.activeElement matches the hx-tab host directly\n // (no shadow-DOM activeElement traversal needed on the modern path).\n // On the legacy fallback path, focus may land on the inner button; the\n // tab host is still the focused element in document.activeElement\n // because the shadow root is closed at the host boundary for outer-tree\n // queries.\n const focusedTab = allTabs.find((tab) => tab === document.activeElement);\n\n if (e.key === ' ' || e.key === 'Enter') {\n // Only activate if the focused tab is not disabled\n if (focusedTab && !focusedTab.disabled) {\n e.preventDefault();\n this._activateTab(focusedTab);\n focusedTab.focus();\n }\n return;\n }\n\n e.preventDefault();\n\n let currentIndex = focusedTab ? allTabs.indexOf(focusedTab) : -1;\n // Fall back to the active tab's index if nothing is focused yet\n if (currentIndex === -1) {\n const activeTab = allTabs.find((tab) => tab.panel === this._activePanel);\n currentIndex = activeTab ? allTabs.indexOf(activeTab) : 0;\n }\n\n let nextIndex: number;\n\n if (e.key === 'Home') {\n nextIndex = 0;\n } else if (e.key === 'End') {\n nextIndex = allTabs.length - 1;\n } else if (e.key === nextKey) {\n nextIndex = (currentIndex + 1) % allTabs.length;\n } else {\n // prevKey\n nextIndex = currentIndex <= 0 ? allTabs.length - 1 : currentIndex - 1;\n }\n\n const targetTab = allTabs[nextIndex];\n if (!targetTab) {\n return;\n }\n\n // Focus the host directly — single-host roving tabindex (Group 5a).\n // The host owns the tab stop; the inner button is presentational.\n targetTab.focus();\n\n // Only activate in automatic mode if the target tab is not disabled\n if (this.activation === 'automatic' && !targetTab.disabled) {\n this._activateTab(targetTab);\n }\n };\n\n // ─── Render ───\n\n override render() {\n return html`\n <div class=\"tabs\">\n <div part=\"tablist\" class=\"tablist\">\n <slot name=\"tab\" @slotchange=${this._handleSlotChange}></slot>\n </div>\n <div part=\"panels\" class=\"panels\">\n <slot @slotchange=${this._handleSlotChange}></slot>\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-tabs': HelixTabs;\n }\n interface HTMLElementEventMap {\n 'hx-tab-change': CustomEvent<{ tabId: string; index: number }>;\n }\n}\n","import { css } from 'lit';\n\nexport const helixTabStyles = css`\n :host {\n display: inline-block;\n }\n\n :host([disabled]) {\n opacity: var(--hx-opacity-disabled, 0.5);\n }\n\n * {\n box-sizing: border-box;\n }\n\n .tab {\n display: inline-flex;\n align-items: center;\n gap: var(--hx-space-2, 0.5rem);\n min-height: var(--hx-touch-target-min, 44px);\n padding: var(--hx-tabs-tab-padding-y, var(--hx-space-2, 0.5rem))\n var(--hx-tabs-tab-padding-x, var(--hx-space-4, 1rem));\n border: none;\n border-bottom: var(--_tab-indicator-bottom, var(--hx-tabs-indicator-size, 2px)) solid\n transparent;\n border-inline-end: var(--_tab-indicator-end, 0px) solid transparent;\n background: none;\n font-family: var(--hx-tabs-tab-font-family, var(--hx-font-family-sans, sans-serif));\n font-size: var(--hx-tabs-tab-font-size, var(--hx-font-size-md, 1rem));\n font-weight: var(--hx-tabs-tab-font-weight, var(--hx-font-weight-medium, 500));\n color: var(--hx-tabs-tab-color, var(--hx-color-neutral-600, #4a5362));\n line-height: var(--hx-line-height-tight, 1.25);\n cursor: pointer;\n white-space: nowrap;\n user-select: none;\n -webkit-user-select: none;\n transition:\n color var(--hx-transition-fast, 150ms ease),\n border-color var(--hx-transition-fast, 150ms ease),\n border-inline-end-color var(--hx-transition-fast, 150ms ease),\n background-color var(--hx-transition-fast, 150ms ease);\n position: relative;\n }\n\n /* ─── Hover State ───\n Group 5a host-canonical: drive visual state from host attributes\n (selected / disabled are reflect: true), not from inner-element ARIA\n attributes which now live on the host's ElementInternals. */\n\n :host(:not([selected]):not([disabled])) .tab:hover {\n color: var(--hx-tabs-tab-hover-color, var(--hx-color-neutral-800, #202b39));\n background-color: var(--hx-tabs-tab-hover-bg, var(--hx-color-neutral-50, #f5f8f3));\n }\n\n /* ─── Selected State ─── */\n\n :host([selected]) .tab {\n color: var(--hx-tabs-tab-active-color, var(--hx-color-primary-600, #0f7078));\n border-bottom-color: var(\n --_tab-indicator-bottom-color,\n var(--hx-tabs-indicator-color, var(--hx-color-primary-500, #429797))\n );\n border-inline-end-color: var(--_tab-indicator-end-color, transparent);\n font-weight: var(--hx-tabs-tab-active-font-weight, var(--hx-font-weight-semibold, 600));\n }\n\n /* ─── Focus State ───\n Focus lands on the HOST in Group 5a host-canonical mode. The inner\n [part=\"tab\"] is presentational (tabindex=-1). Use :host(:focus-visible). */\n\n :host(:focus-visible) .tab {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-tabs-focus-ring-color, var(--hx-focus-ring-color, #0f7078));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n border-radius: var(--hx-border-radius-sm, 0.25rem);\n }\n\n /* Strip the default host focus ring — outline lands on the inner [part=\"tab\"]\n surface above for visual continuity with the existing component appearance. */\n :host(:focus) {\n outline: none;\n }\n\n /* ─── Disabled State ─── */\n\n :host([disabled]) {\n cursor: not-allowed;\n }\n\n :host([disabled]) .tab {\n pointer-events: none;\n color: var(--hx-tabs-tab-disabled-color, var(--hx-color-neutral-400, #8e9c98));\n }\n\n /* ─── Prefix / Suffix Slots ─── */\n\n .tab__prefix,\n .tab__suffix {\n display: inline-flex;\n align-items: center;\n flex-shrink: 0;\n }\n\n /* ─── Reduced Motion ─── */\n\n @media (prefers-reduced-motion: reduce) {\n .tab {\n transition: none;\n }\n }\n\n /* ─── High Contrast Mode (forced-colors) ─── */\n\n @media (forced-colors: active) {\n .tab {\n forced-color-adjust: none;\n color: ButtonText;\n border-bottom-color: transparent;\n background-color: ButtonFace;\n }\n\n :host([selected]) .tab {\n color: HighlightText;\n background-color: Highlight;\n border-bottom-color: Highlight;\n }\n\n :host(:focus-visible) .tab {\n outline: 3px solid Highlight;\n outline-offset: 2px;\n }\n\n :host([disabled]) .tab {\n color: GrayText;\n }\n }\n`;\n","import { html, nothing, type PropertyValues } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { HelixElement } from '../../base/index.js';\nimport { forcedColorsInteractive } from '../../styles/forced-colors.js';\nimport { helixTabStyles } from './hx-tab.styles.js';\nimport { devWarn } from '../../utils/dev-warn.js';\nimport {\n installAriaIdrefMirror,\n resolveIdrefTokens,\n supportsIdrefElementReferences,\n type AriaIdrefMirrorHandle,\n} from '../../utils/aria-idref.js';\nimport { flattenAccName } from '../../utils/aria-flatten.js';\n\n/**\n * An individual tab button, designed to be used inside an `<hx-tabs>` container.\n * Must be placed in the `tab` named slot of `<hx-tabs>`.\n *\n * Group 5a host-canonical: `role=\"tab\"` lives on the **host** via\n * `_internals.role`. The host is the focusable surface (carries the roving\n * tabindex); the inner `<button>` retains click activation semantics\n * (Enter/Space and pointer events) but is no longer the AT-announced surface\n * — its role and ARIA state are stripped on the modern path so the host's\n * canonical surface wins. `internals.ariaSelected`, `ariaDisabled`, and\n * `ariaControlsElements` mirror reactive state. The host `aria-label` /\n * `aria-labelledby` resolved via the shared IDREF mirror name the tab\n * cross-shadow.\n *\n * @summary Presentational tab button that activates a corresponding panel.\n *\n * @tag hx-tab\n *\n * @slot - Default slot for the tab 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 * @csspart tab - The underlying button element.\n * @csspart prefix - The container for prefix slot content (e.g. icons).\n * @csspart suffix - The container for suffix slot content (e.g. badges).\n *\n * @cssprop [--hx-tabs-tab-color=var(--hx-color-neutral-600, #4A5362)] - Inactive tab text color.\n * @cssprop [--hx-tabs-tab-active-color=var(--hx-color-primary-600, #0F7078)] - Active tab text color.\n * @cssprop [--hx-tabs-tab-hover-color=var(--hx-color-neutral-800, #202B39)] - Tab hover text color.\n * @cssprop [--hx-tabs-tab-hover-bg=var(--hx-color-neutral-50, #F5F8F3)] - Tab hover background.\n * @cssprop [--hx-tabs-tab-font-size=var(--hx-font-size-md, 1rem)] - Tab font size.\n * @cssprop [--hx-tabs-tab-font-weight=var(--hx-font-weight-medium, 500)] - Tab font weight.\n * @cssprop [--hx-tabs-tab-active-font-weight=var(--hx-font-weight-semibold, 600)] - Active tab font weight.\n * @cssprop [--hx-tabs-tab-padding-x=var(--hx-space-4, 1rem)] - Horizontal tab padding.\n * @cssprop [--hx-tabs-tab-padding-y=var(--hx-space-2, 0.5rem)] - Vertical tab padding.\n * @cssprop [--hx-tabs-indicator-color=var(--hx-color-primary-500, #429797)] - Active indicator color.\n * @cssprop [--hx-tabs-indicator-size=2px] - Active indicator thickness.\n * @cssprop [--hx-tabs-focus-ring-color=var(--hx-focus-ring-color, #6AB1B1)] - Focus ring color.\n */\n@customElement('hx-tab')\nexport class HelixTab extends HelixElement {\n static override styles = [helixTabStyles, forcedColorsInteractive];\n\n // ─── Properties ───\n\n /**\n * The name of the `<hx-tab-panel>` this tab controls. Must match the `name`\n * attribute on the corresponding `<hx-tab-panel>`.\n * @attr panel\n */\n @property({ type: String, reflect: true })\n panel = '';\n\n /**\n * Whether this tab is currently selected. Managed by the parent `<hx-tabs>`.\n * @attr selected\n */\n @property({ type: Boolean, reflect: true })\n selected = false;\n\n /**\n * Whether this tab is disabled. Prevents selection and keyboard navigation.\n * @attr disabled\n */\n @property({ type: Boolean, reflect: true })\n disabled = false;\n\n /**\n * The id of the panel this tab controls. Set by the parent `<hx-tabs>` to\n * establish the aria-controls relationship via element references on the\n * host (modern path) or as a string fallback (legacy path).\n * @internal\n */\n @property({ type: String, attribute: false })\n controls = '';\n\n // ─── Host-canonical ARIA bookkeeping ───\n\n /** @internal */\n @state() private _supportsIdrefRefs = true;\n\n /** @internal */\n private _ariaMirror: AriaIdrefMirrorHandle | null = null;\n\n /**\n * Element reference for the controlled `<hx-tab-panel>` host. Set by the\n * parent `<hx-tabs>` via `setControlsPanel()` and projected onto\n * `internals.ariaControlsElements` so cross-shadow controls resolution\n * works via IDL element references. Falls through to the `controls`\n * string property on the legacy path.\n * @internal\n */\n private _controlledPanel: Element | null = null;\n\n // ─── Lifecycle ───\n\n override connectedCallback(): void {\n super.connectedCallback();\n this._supportsIdrefRefs = supportsIdrefElementReferences(this._internals);\n if (!this.closest('hx-tabs')) {\n devWarn('hx-tab', 'hx-tab must be a direct child of hx-tabs to function correctly.');\n }\n this._syncHostAriaSemantics();\n this._ariaMirror = installAriaIdrefMirror(this, () => {\n this._syncHostAriaSemantics();\n });\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this._ariaMirror?.disconnect();\n this._ariaMirror = null;\n }\n\n override updated(changedProperties: PropertyValues<this>): void {\n super.updated(changedProperties);\n if (\n changedProperties.has('selected') ||\n changedProperties.has('disabled') ||\n changedProperties.has('controls') ||\n changedProperties.has('panel')\n ) {\n this._syncHostAriaSemantics();\n }\n }\n\n /**\n * Set by `<hx-tabs>` whenever the tab→panel relationship is recomputed.\n * Drives `internals.ariaControlsElements` (modern path) so AT walks across\n * the shadow boundary to the controlled panel by element reference.\n * @internal\n */\n setControlsPanel(panel: Element | null): void {\n this._controlledPanel = panel;\n this._syncHostAriaSemantics();\n }\n\n /**\n * Mirror reactive ARIA state onto ElementInternals on the **host**. The\n * inner `<button>` no longer carries role/aria-* on the modern path — the\n * host is the canonical AT-announced surface. The button stays as the\n * activation surface (Enter/Space/click) but is `tabindex=-1` and\n * presentational from AT's perspective.\n * @internal\n */\n private _syncHostAriaSemantics(): void {\n const internals = this._internals;\n internals.role = 'tab';\n internals.ariaSelected = this.selected ? 'true' : 'false';\n internals.ariaDisabled = this.disabled ? 'true' : null;\n\n // ariaControls — modern path uses element references; legacy path falls\n // back to a string IDREF (set on the host element directly via\n // setAttribute below so AT that does not implement element references\n // still has a token to walk).\n type InternalsWithRefs = ElementInternals & {\n ariaControlsElements: Element[] | null;\n ariaLabelledByElements: Element[] | null;\n };\n\n if (this._supportsIdrefRefs) {\n const refsInternals = internals as InternalsWithRefs;\n refsInternals.ariaControlsElements = this._controlledPanel ? [this._controlledPanel] : null;\n }\n\n // Resolve consumer host aria-labelledby / aria-label so the tab announces\n // with the consumer's chosen name when present. Default name comes from\n // the slotted text content (handled by AT walking the host's children\n // through the shadow slot — works for accessible name computation in\n // modern engines because the host carries the role).\n const consumerLabelledBy = this.getAttribute('aria-labelledby');\n const hostAriaLabel = this.getAttribute('aria-label');\n const labelEls = resolveIdrefTokens(this, consumerLabelledBy);\n\n if (this._supportsIdrefRefs) {\n const refsInternals = internals as InternalsWithRefs;\n refsInternals.ariaLabelledByElements = labelEls.length > 0 ? labelEls : null;\n }\n\n if (hostAriaLabel && hostAriaLabel.trim()) {\n internals.ariaLabel = hostAriaLabel;\n } else if (!this._supportsIdrefRefs && labelEls.length > 0) {\n // Legacy fallback: flatten consumer aria-labelledby targets into a\n // string so AT without IDL refs still names the tab.\n internals.ariaLabel =\n labelEls\n .map((el) => flattenAccName(el))\n .filter(Boolean)\n .join(' ') || null;\n } else {\n internals.ariaLabel = null;\n }\n }\n\n // ─── Slot Visibility ───\n\n /** @internal */\n @state() private _hasPrefixSlot = false;\n /** @internal */\n @state() private _hasSuffixSlot = false;\n\n // ─── Event Handling ───\n\n /** @internal */\n private _handleClick(): void {\n if (this.disabled) {\n return;\n }\n /**\n * Internal event dispatched to signal tab selection to the parent container.\n * Not part of the public API.\n * @internal\n */\n this.dispatchEvent(\n new CustomEvent<{ panel: string }>('hx-tab-select', {\n bubbles: true,\n composed: true,\n detail: { panel: this.panel },\n }),\n );\n }\n\n /** @internal */\n private _handlePrefixSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n this._hasPrefixSlot = slot.assignedNodes({ flatten: true }).length > 0;\n }\n\n /** @internal */\n private _handleSuffixSlotChange(e: Event): void {\n const slot = e.target as HTMLSlotElement;\n this._hasSuffixSlot = slot.assignedNodes({ flatten: true }).length > 0;\n }\n\n // ─── Render ───\n\n override render() {\n // Host-canonical Path A: `role=\"tab\"`, `aria-selected`, `aria-disabled`,\n // and `aria-controls` are projected onto the HOST via `_internals` —\n // NOT on the inner button. The inner button is the activation surface\n // (Enter/Space/click) but is presentational from AT's perspective:\n // tabindex=-1 keeps focus on the host (which carries the roving tabindex\n // managed by `<hx-tabs>`), and no role/aria-* attributes leak through.\n //\n // Legacy fallback: when the platform lacks IDL element references on\n // ElementInternals, mirror `aria-controls` as a string IDREF on the host\n // so AT that walks string tokens still resolves the panel relationship.\n if (!this._supportsIdrefRefs && this.controls) {\n this.setAttribute('aria-controls', this.controls);\n } else if (this._supportsIdrefRefs && this.hasAttribute('aria-controls')) {\n this.removeAttribute('aria-controls');\n }\n\n // Modern path: the inner element is a presentational <div> click\n // surface — no native button semantics to leak to AT alongside the\n // host's `role=\"tab\"`. ARIA 1.2 forbids `role=\"presentation\"` on\n // focusable elements, so the cleanest strip is to use a non-button\n // tag. Click handler still fires; keyboard activation is owned by the\n // parent `<hx-tabs>` keydown handler operating on the host (which\n // carries the canonical `role=\"tab\"` + roving tabindex).\n //\n // Legacy fallback (no IDL element references): keep the `<button>`\n // with `role=\"tab\"` and ARIA state on the button, since `_internals`\n // can't expose element-references on engines without that API. The\n // host attribute mirror (this.controls → aria-controls string) is\n // applied above for AT that walks string IDREFs.\n if (this._supportsIdrefRefs) {\n // `aria-disabled` is mirrored on the inner element so axe-core's\n // color-contrast rule recognizes the disabled state and excludes the\n // surface from contrast checks (axe excludes `aria-disabled=\"true\"`\n // elements). The HOST is the canonical AT surface — its\n // `internals.ariaDisabled` is what AT announces; this attribute on the\n // inner div is a presentational signal for static analyzers and CSS.\n return html`\n <div\n part=\"tab\"\n class=\"tab\"\n tabindex=\"-1\"\n aria-disabled=${this.disabled ? 'true' : nothing}\n @click=${this._handleClick}\n >\n <span part=\"prefix\" class=\"tab__prefix\" ?hidden=${!this._hasPrefixSlot}>\n <slot name=\"prefix\" @slotchange=${this._handlePrefixSlotChange}></slot>\n </span>\n <slot></slot>\n <span part=\"suffix\" class=\"tab__suffix\" ?hidden=${!this._hasSuffixSlot}>\n <slot name=\"suffix\" @slotchange=${this._handleSuffixSlotChange}></slot>\n </span>\n </div>\n `;\n }\n\n return html`\n <button\n part=\"tab\"\n class=\"tab\"\n role=\"tab\"\n tabindex=\"-1\"\n aria-selected=${this.selected ? 'true' : 'false'}\n aria-disabled=${this.disabled ? 'true' : 'false'}\n aria-controls=${this.controls || nothing}\n @click=${this._handleClick}\n >\n <span part=\"prefix\" class=\"tab__prefix\" ?hidden=${!this._hasPrefixSlot}>\n <slot name=\"prefix\" @slotchange=${this._handlePrefixSlotChange}></slot>\n </span>\n <slot></slot>\n <span part=\"suffix\" class=\"tab__suffix\" ?hidden=${!this._hasSuffixSlot}>\n <slot name=\"suffix\" @slotchange=${this._handleSuffixSlotChange}></slot>\n </span>\n </button>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-tab': HelixTab;\n }\n}\n","import { css } from 'lit';\n\nexport const helixTabPanelStyles = css`\n :host {\n display: block;\n }\n\n :host([hidden]) {\n display: none;\n }\n\n * {\n box-sizing: border-box;\n }\n\n :host(:focus-visible) {\n outline: var(--hx-focus-ring-width, 2px) solid\n var(--hx-tabs-focus-ring-color, var(--hx-focus-ring-color, #0f7078));\n outline-offset: var(--hx-focus-ring-offset, 2px);\n border-radius: var(--hx-border-radius-sm, 0.25rem);\n }\n\n .panel {\n padding: var(--hx-tabs-panel-padding, var(--hx-space-4, 1rem));\n font-family: var(--hx-tab-panel-font-family, var(--hx-font-family-sans, sans-serif));\n font-size: var(--hx-font-size-md, 1rem);\n color: var(--hx-tabs-panel-color, var(--hx-color-neutral-700, #313e4b));\n line-height: var(--hx-line-height-normal, 1.5);\n outline: none;\n }\n\n /* ─── High Contrast Mode (forced-colors) ─── */\n\n @media (forced-colors: active) {\n :host(:focus-visible) {\n outline: 3px solid Highlight;\n outline-offset: 2px;\n }\n\n .panel {\n color: CanvasText;\n }\n }\n`;\n","import { html, type PropertyValues } from 'lit';\nimport '../../utilities/document-token-adoption.js';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { HelixElement } from '../../base/index.js';\nimport { forcedColorsField } from '../../styles/forced-colors.js';\nimport { helixTabPanelStyles } from './hx-tab-panel.styles.js';\nimport {\n installAriaIdrefMirror,\n resolveIdrefTokens,\n supportsIdrefElementReferences,\n type AriaIdrefMirrorHandle,\n} from '../../utils/aria-idref.js';\n\n/**\n * A content panel associated with an `<hx-tab>`, managed by a parent `<hx-tabs>`.\n * Group 5a host-canonical: `role=\"tabpanel\"` lives on the host via\n * `_internals.role`. The host carries the canonical AT surface — consumer\n * `aria-labelledby` / `aria-describedby` on the host resolve through the\n * shared IDREF mirror. The parent `hx-tabs` writes `internals.ariaLabelledByElements`\n * referencing the corresponding `<hx-tab>` host so cross-shadow naming works\n * via IDL element references (the modern path) without serializing tab text.\n *\n * @summary Tab content panel shown when its corresponding tab is selected.\n *\n * @tag hx-tab-panel\n *\n * @slot - Default slot for panel content.\n *\n * @csspart panel - The panel content wrapper.\n *\n * @cssprop [--hx-tabs-panel-padding=var(--hx-space-4, 1rem)] - Panel inner padding.\n * @cssprop [--hx-tabs-panel-color=var(--hx-color-neutral-700, #313E4B)] - Panel text color.\n * @cssprop [--hx-tabs-focus-ring-color=var(--hx-focus-ring-color, #6AB1B1)] - Focus ring color.\n */\n@customElement('hx-tab-panel')\nexport class HelixTabPanel extends HelixElement {\n static override styles = [helixTabPanelStyles, forcedColorsField];\n\n // ─── Properties ───\n\n /**\n * The name that corresponds to the `panel` attribute on the associated `<hx-tab>`.\n * @attr name\n */\n @property({ type: String, reflect: true })\n name = '';\n\n // ─── Host-canonical ARIA bookkeeping ───\n\n /**\n * Element references for the controlling `<hx-tab>` host(s). Set by the\n * parent `<hx-tabs>` via `setLabelledByTabs()` and projected onto\n * `internals.ariaLabelledByElements` so cross-shadow naming via IDL element\n * references resolves to the live tab host without flattening text.\n * @internal\n */\n private _labelledByTabs: Element[] = [];\n\n /**\n * Whether the platform supports IDL element references on `ElementInternals`.\n * @internal\n */\n @state() private _supportsIdrefRefs = true;\n\n /** @internal */\n private _ariaMirror: AriaIdrefMirrorHandle | null = null;\n\n // ─── Lifecycle ───\n\n override connectedCallback(): void {\n super.connectedCallback();\n this._supportsIdrefRefs = supportsIdrefElementReferences(this._internals);\n // Host-canonical role via ElementInternals — replaces the imperative\n // setAttribute('role', 'tabpanel') from pre-aria-group-5 code.\n this._internals.role = 'tabpanel';\n this._syncHostAriaSemantics();\n this._ariaMirror = installAriaIdrefMirror(this, () => {\n this._syncHostAriaSemantics();\n });\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this._ariaMirror?.disconnect();\n this._ariaMirror = null;\n }\n\n override updated(changedProperties: PropertyValues<this>): void {\n super.updated(changedProperties);\n this._syncHostAriaSemantics();\n }\n\n /**\n * Set by `<hx-tabs>` whenever the tab→panel relationship is recomputed.\n * Drives `internals.ariaLabelledByElements` so AT walks across the shadow\n * boundary to the announcing tab host(s) by element reference rather than\n * IDREF string. Pass an empty array to clear.\n * @internal\n */\n setLabelledByTabs(tabs: Element[]): void {\n this._labelledByTabs = tabs;\n this._syncHostAriaSemantics();\n }\n\n /** @internal */\n private _syncHostAriaSemantics(): void {\n const internals = this._internals;\n // Re-assert role on every sync so a stale connect path doesn't leave the\n // panel role-less if internals are accessed before connect (e.g. tests).\n internals.role = 'tabpanel';\n\n // Resolve consumer-supplied IDREFs — host-canonical: light-DOM aria-labelledby\n // / aria-describedby on `<hx-tab-panel>` should reach the announced surface.\n const consumerLabelledBy = this.getAttribute('aria-labelledby');\n const consumerDescribedBy = this.getAttribute('aria-describedby');\n const consumerLabelEls = resolveIdrefTokens(this, consumerLabelledBy);\n const consumerDescEls = resolveIdrefTokens(this, consumerDescribedBy);\n\n // Host aria-label, if explicitly provided by the consumer, wins.\n const hostAriaLabel = this.getAttribute('aria-label');\n\n // Build the labelledBy element list: consumer takes precedence; otherwise\n // fall back to the parent-projected `<hx-tab>` host references so the\n // panel announces with the tab's accessible name without text flattening.\n const labelEls: Element[] =\n consumerLabelEls.length > 0 ? consumerLabelEls : [...this._labelledByTabs];\n\n type InternalsWithRefs = ElementInternals & {\n ariaLabelledByElements: Element[] | null;\n ariaDescribedByElements: Element[] | null;\n };\n\n if (this._supportsIdrefRefs) {\n const refsInternals = internals as InternalsWithRefs;\n refsInternals.ariaLabelledByElements = labelEls.length > 0 ? labelEls : null;\n refsInternals.ariaDescribedByElements = consumerDescEls.length > 0 ? consumerDescEls : null;\n // Modern path: the consumer's `aria-label` (if any) wins via internals.\n // When absent, leave it null so the element-references path supplies the name.\n internals.ariaLabel = hostAriaLabel && hostAriaLabel.trim() ? hostAriaLabel : null;\n } else {\n // No-IDL-ref fallback: cross-shadow tab→panel naming via element refs is\n // unavailable. The parent `hx-tabs` mirrors a serialized `aria-label`\n // string onto the host attribute (legacy fallback) — leave that in\n // place. Consumer `aria-label` overrides via internals string.\n internals.ariaLabel = hostAriaLabel && hostAriaLabel.trim() ? hostAriaLabel : null;\n }\n }\n\n // ─── Render ───\n\n override render() {\n return html`\n <div part=\"panel\" class=\"panel\">\n <slot></slot>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'hx-tab-panel': HelixTabPanel;\n }\n}\n"],"names":["helixTabsStyles","css","_nextTabsId","createIdCounter","HelixTabs","HelixElement","e","tab","el","p","firstEnabled","allTabs","isHorizontal","prevKey","nextKey","focusedTab","currentIndex","activeTab","nextIndex","targetTab","name","old","value","index","supportsIdrefElementReferences","installAriaIdrefMirror","_a","_b","pendingTab","changedProperties","internals","hostAriaLabel","consumerLabelledBy","labelEls","resolveIdrefTokens","hasEffectiveLabelledBy","refsInternals","flattenAccName","tabs","panels","tabId","panelName","panel","panelId","tabLabel","node","isSelected","dispatchEvent","previousPanel","tabSlot","panelSlot","invalid","devWarn","html","forcedColorsInteractive","__decorateClass","property","state","customElement","helixTabStyles","HelixTab","slot","nothing","helixTabPanelStyles","HelixTabPanel","consumerDescribedBy","consumerLabelEls","consumerDescEls","forcedColorsField"],"mappings":"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"}