luxen-ui 0.18.1 → 0.18.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"tabs.js","names":[],"sources":["../../../src/html/elements/tabs/tabs.ts"],"sourcesContent":["import { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { uniqueId } from '../../registry.js';\n\nexport type TabsVariant = 'enclosed' | 'line';\nexport type TabsOrientation = 'horizontal' | 'vertical';\n\n/** Fired when the active tab changes. Bubbles; not composed. */\nexport class TabsChangeEvent extends Event {\n readonly index: number;\n readonly name: string | null;\n constructor(index: number, name: string | null) {\n super('change', { bubbles: true, composed: false, cancelable: false });\n this.index = index;\n this.name = name;\n }\n}\n\ninterface TabsEventMap {\n change: TabsChangeEvent;\n}\n\n/**\n * @summary A tabs component that progressively enhances light DOM markup\n * with ARIA roles, keyboard navigation, and animated indicators.\n *\n * @example\n * Mark the initially-active tab with `aria-selected=\"true\"` so it is styled as\n * selected before the script upgrades the element (progressive enhancement).\n * ```html\n * <l-tabs variant=\"enclosed\">\n * <div>\n * <button aria-selected=\"true\">Tab 1</button>\n * <button>Tab 2</button>\n * </div>\n * <div>Content 1</div>\n * <div>Content 2</div>\n * </l-tabs>\n * ```\n *\n * @event change - Fired when the active tab changes. Bubbles. Properties: `index: number`, `name: string | null`.\n *\n * @cssproperty [--indicator-color=var(--l-color-text-primary)] - `line` variant: color of the active underline that slides under the selected tab.\n * @cssproperty [--indicator-thickness=2px] - `line` variant: thickness of the active underline.\n * @cssproperty [--track-color=var(--l-color-border)] - `line` variant: color of the static bottom border the tabs sit on.\n * @cssproperty [--track-thickness=1px] - `line` variant: thickness of the static bottom border.\n *\n * @customElement l-tabs\n */\n// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- typed addEventListener overloads merged below; no uninitialized properties.\nexport class Tabs extends LuxenElement {\n override createRenderRoot() {\n return this;\n }\n\n private _initialized = false;\n private _setupTimer = 0;\n private _instanceId = uniqueId('tabs');\n private _tablistEl: HTMLElement | null = null;\n private _tabs: HTMLButtonElement[] = [];\n private _panels: HTMLElement[] = [];\n private _resizeObserver: ResizeObserver | null = null;\n\n /** Visual variant. */\n @property({ reflect: true })\n variant: TabsVariant = 'line';\n\n /** Index of the active tab (0-based). */\n @property({ reflect: true })\n value = '0';\n\n /** Stretch tabs to fill container width. */\n @property({ type: Boolean, reflect: true, attribute: 'full-width' })\n fullWidth = false;\n\n /** Tab orientation. */\n @property({ reflect: true })\n orientation: TabsOrientation = 'horizontal';\n\n // --- Lifecycle ---\n\n override connectedCallback() {\n super.connectedCallback();\n // Children may not be parsed yet when the element upgrades mid-parse: try\n // synchronously, then retry once on a macrotask. setTimeout, not rAF — rAF\n // is suspended in hidden documents, so the element would stay inert there.\n if (!this._trySetup()) {\n this._setupTimer = window.setTimeout(() => this._trySetup(), 0);\n }\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n clearTimeout(this._setupTimer);\n this._teardown();\n }\n\n /** @returns true when setup ran or was already done; false to schedule a retry. */\n private _trySetup(): boolean {\n if (this._initialized || !this.isConnected) return true;\n if (this.children.length < 2) return false;\n this._setup();\n return true;\n }\n\n override updated(changed: Map<string, unknown>) {\n if (changed.has('value') && this._tabs.length) {\n this._selectTab(Number(this.value), false);\n }\n if (changed.has('orientation') && this._tablistEl) {\n this._tablistEl.setAttribute('aria-orientation', this.orientation);\n }\n }\n\n // --- Setup / Teardown ---\n\n private _setup() {\n const children = Array.from(this.children) as HTMLElement[];\n if (children.length < 2) return;\n this._initialized = true;\n\n // First child is the tablist container\n this._tablistEl = children[0];\n this._tablistEl.setAttribute('role', 'tablist');\n this._tablistEl.setAttribute('aria-orientation', this.orientation);\n\n // Buttons inside tablist become tabs\n this._tabs = Array.from(this._tablistEl.querySelectorAll('button'));\n\n // Remaining children become panels\n this._panels = children.slice(1);\n\n const activeIndex = Number(this.value) || 0;\n\n // Enhance tabs\n for (let i = 0; i < this._tabs.length; i++) {\n const tab = this._tabs[i];\n const panel = this._panels[i];\n const name = tab.getAttribute('name') ?? String(i);\n const tabId = `${this._instanceId}-tab-${name}`;\n const panelId = `${this._instanceId}-panel-${name}`;\n\n tab.setAttribute('role', 'tab');\n tab.setAttribute('id', tabId);\n tab.setAttribute('aria-selected', String(i === activeIndex));\n tab.setAttribute('tabindex', i === activeIndex ? '0' : '-1');\n\n if (panel) {\n tab.setAttribute('aria-controls', panelId);\n panel.setAttribute('role', 'tabpanel');\n panel.setAttribute('id', panelId);\n panel.setAttribute('aria-labelledby', tabId);\n panel.setAttribute('tabindex', '0');\n if (i !== activeIndex) {\n panel.hidden = true;\n } else {\n panel.hidden = false;\n }\n }\n }\n\n // Attach listeners\n this._tablistEl.addEventListener('click', this._onClick);\n this._tablistEl.addEventListener('keydown', this._onKeyDown);\n\n // Initial indicator position. Synchronous first (offset reads force\n // layout), then a ResizeObserver keeps it correct across hidden→visible\n // transitions and size changes — rAF is suspended in hidden documents,\n // so it must not be the only scheduling mechanism here.\n this._updateIndicator();\n this._resizeObserver = new ResizeObserver(() => this._updateIndicator());\n this._resizeObserver.observe(this._tablistEl);\n }\n\n private _teardown() {\n this._tablistEl?.removeEventListener('click', this._onClick);\n this._tablistEl?.removeEventListener('keydown', this._onKeyDown);\n this._resizeObserver?.disconnect();\n this._resizeObserver = null;\n this._initialized = false;\n }\n\n // --- Tab selection ---\n\n private _selectTab(index: number, emitEvent = true) {\n if (index < 0 || index >= this._tabs.length) return;\n\n for (let i = 0; i < this._tabs.length; i++) {\n const tab = this._tabs[i];\n const panel = this._panels[i];\n const isActive = i === index;\n\n tab.setAttribute('aria-selected', String(isActive));\n tab.setAttribute('tabindex', isActive ? '0' : '-1');\n\n if (panel) {\n panel.hidden = !isActive;\n }\n }\n\n this.value = String(index);\n this._updateIndicator();\n\n if (emitEvent) {\n const name = this._tabs[index]?.getAttribute('name') ?? null;\n this.dispatchEvent(new TabsChangeEvent(index, name));\n }\n }\n\n // --- Indicator ---\n\n private _updateIndicator() {\n if (!this._tablistEl) return;\n const activeTab = this._tabs[Number(this.value)];\n if (!activeTab) return;\n\n const isVertical = this.orientation === 'vertical';\n\n if (isVertical) {\n this._tablistEl.style.setProperty('--_indicator-top', `${activeTab.offsetTop}px`);\n this._tablistEl.style.setProperty('--_indicator-height', `${activeTab.offsetHeight}px`);\n } else {\n this._tablistEl.style.setProperty('--_indicator-left', `${activeTab.offsetLeft}px`);\n this._tablistEl.style.setProperty('--_indicator-width', `${activeTab.offsetWidth}px`);\n }\n }\n\n // --- Event handlers ---\n\n private _onClick = (e: Event) => {\n const tab = (e.target as HTMLElement).closest<HTMLButtonElement>('[role=\"tab\"]');\n if (!tab) return;\n const index = this._tabs.indexOf(tab);\n if (index >= 0) {\n this._selectTab(index);\n tab.focus();\n }\n };\n\n private _onKeyDown = (e: KeyboardEvent) => {\n const target = e.target as HTMLElement;\n if (target.getAttribute('role') !== 'tab') return;\n\n const isHorizontal = this.orientation === 'horizontal';\n const prevKey = isHorizontal ? 'ArrowLeft' : 'ArrowUp';\n const nextKey = isHorizontal ? 'ArrowRight' : 'ArrowDown';\n\n let index = this._tabs.indexOf(target as HTMLButtonElement);\n\n switch (e.key) {\n case nextKey:\n e.preventDefault();\n index = (index + 1) % this._tabs.length;\n this._selectTab(index);\n this._tabs[index].focus();\n break;\n case prevKey:\n e.preventDefault();\n index = (index - 1 + this._tabs.length) % this._tabs.length;\n this._selectTab(index);\n this._tabs[index].focus();\n break;\n case 'Home':\n e.preventDefault();\n this._selectTab(0);\n this._tabs[0].focus();\n break;\n case 'End':\n e.preventDefault();\n this._selectTab(this._tabs.length - 1);\n this._tabs[this._tabs.length - 1].focus();\n break;\n }\n };\n}\n\n// Type `addEventListener('change', …)` on this element so the listener receives\n// a `TabsChangeEvent` (with `.index`/`.name`) instead of a bare `Event`, with no\n// cast. This is the canonical reference for the pattern used across the library.\n//\n// Why a same-named interface and not a global augmentation: `change` already\n// exists in lib.dom's `GlobalEventHandlersEventMap` (typed `Event`), and TS\n// forbids re-declaring an existing key with a different type — so colliding\n// names (`change`, `select`, `input`, `toggle`) can't be typed globally. Merging\n// a same-named `interface Tabs` into the `class Tabs` adds these overloads to\n// the element's own type only. (Non-colliding names like `show`/`story-change`\n// ARE augmented globally in their event-class module — no per-element block.)\n//\n// In `(type: K, listener: (ev: TabsEventMap[K]) => void)`, K is pinned to the\n// string literal you pass, and `TabsEventMap[K]` indexes the event type from it\n// (`'change'` → `TabsChangeEvent`). `this: Tabs` is a fake parameter that types\n// `this` inside a non-arrow listener; it emits no runtime code.\n//\n// Three load-bearing details:\n// 1. The `(type: string, …)` fallback overloads are REQUIRED: declaring our\n// own `addEventListener` hides ALL inherited `HTMLElement` overloads, so\n// without them `addEventListener('click', …)` would stop compiling. The\n// typed overload comes first so `'change'` resolves before the fallback.\n// 2. This interface sits AFTER the class: the CEM analyzer dedups on the first\n// declaration of the name, so an interface before the class would shadow it\n// and the manifest would extract nothing for this element.\n// 3. The merge is flagged by `no-unsafe-declaration-merging` (disabled on the\n// class above) — safe here because we merge only method overloads, never\n// uninitialized properties.\nexport interface Tabs {\n addEventListener<K extends keyof TabsEventMap>(\n type: K,\n listener: (this: Tabs, ev: TabsEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): void;\n removeEventListener<K extends keyof TabsEventMap>(\n type: K,\n listener: (this: Tabs, ev: TabsEventMap[K]) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): void;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tabs.js","names":[],"sources":["../../../src/html/elements/tabs/tabs.ts"],"sourcesContent":["import { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { uniqueId } from '../../registry.js';\n\nexport type TabsVariant = 'enclosed' | 'line';\nexport type TabsOrientation = 'horizontal' | 'vertical';\n\n/** Fired when the active tab changes. Bubbles; not composed. */\nexport class TabsChangeEvent extends Event {\n readonly index: number;\n readonly name: string | null;\n constructor(index: number, name: string | null) {\n super('change', { bubbles: true, composed: false, cancelable: false });\n this.index = index;\n this.name = name;\n }\n}\n\ninterface TabsEventMap {\n change: TabsChangeEvent;\n}\n\n/**\n * @summary A tabs component that progressively enhances light DOM markup\n * with ARIA roles, keyboard navigation, and animated indicators.\n *\n * @example\n * Mark the initially-active tab with `aria-selected=\"true\"` so it is styled as\n * selected before the script upgrades the element (progressive enhancement).\n * ```html\n * <l-tabs variant=\"enclosed\">\n * <div>\n * <button aria-selected=\"true\">Tab 1</button>\n * <button>Tab 2</button>\n * </div>\n * <div>Content 1</div>\n * <div>Content 2</div>\n * </l-tabs>\n * ```\n *\n * @event change - Fired when the active tab changes. Bubbles. Properties: `index: number`, `name: string | null`.\n *\n * @cssproperty [--indicator-color=var(--l-color-text-primary)] - `line` variant: color of the active underline that slides under the selected tab.\n * @cssproperty [--indicator-thickness=2px] - `line` variant: thickness of the active underline.\n * @cssproperty [--track-color=var(--l-color-border)] - `line` variant: color of the static bottom border the tabs sit on.\n * @cssproperty [--track-thickness=1px] - `line` variant: thickness of the static bottom border.\n * @cssproperty [--hover-color=var(--l-color-bg-state-hover)] - `line` variant: fill of the pill that appears behind a hovered tab label.\n * @cssproperty [--hover-inset=4px] - `line` variant: block-axis gap between the hover pill and the tab edges, keeping it detached from the bottom border.\n *\n * @customElement l-tabs\n */\n// oxlint-disable-next-line typescript/no-unsafe-declaration-merging -- typed addEventListener overloads merged below; no uninitialized properties.\nexport class Tabs extends LuxenElement {\n override createRenderRoot() {\n return this;\n }\n\n private _initialized = false;\n private _setupTimer = 0;\n private _instanceId = uniqueId('tabs');\n private _tablistEl: HTMLElement | null = null;\n private _tabs: HTMLButtonElement[] = [];\n private _panels: HTMLElement[] = [];\n private _resizeObserver: ResizeObserver | null = null;\n\n /** Visual variant. */\n @property({ reflect: true })\n variant: TabsVariant = 'line';\n\n /** Index of the active tab (0-based). */\n @property({ reflect: true })\n value = '0';\n\n /** Stretch tabs to fill container width. */\n @property({ type: Boolean, reflect: true, attribute: 'full-width' })\n fullWidth = false;\n\n /** Tab orientation. */\n @property({ reflect: true })\n orientation: TabsOrientation = 'horizontal';\n\n // --- Lifecycle ---\n\n override connectedCallback() {\n super.connectedCallback();\n // Children may not be parsed yet when the element upgrades mid-parse: try\n // synchronously, then retry once on a macrotask. setTimeout, not rAF — rAF\n // is suspended in hidden documents, so the element would stay inert there.\n if (!this._trySetup()) {\n this._setupTimer = window.setTimeout(() => this._trySetup(), 0);\n }\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n clearTimeout(this._setupTimer);\n this._teardown();\n }\n\n /** @returns true when setup ran or was already done; false to schedule a retry. */\n private _trySetup(): boolean {\n if (this._initialized || !this.isConnected) return true;\n if (this.children.length < 2) return false;\n this._setup();\n return true;\n }\n\n override updated(changed: Map<string, unknown>) {\n if (changed.has('value') && this._tabs.length) {\n this._selectTab(Number(this.value), false);\n }\n if (changed.has('orientation') && this._tablistEl) {\n this._tablistEl.setAttribute('aria-orientation', this.orientation);\n }\n }\n\n // --- Setup / Teardown ---\n\n private _setup() {\n const children = Array.from(this.children) as HTMLElement[];\n if (children.length < 2) return;\n this._initialized = true;\n\n // First child is the tablist container\n this._tablistEl = children[0];\n this._tablistEl.setAttribute('role', 'tablist');\n this._tablistEl.setAttribute('aria-orientation', this.orientation);\n\n // Buttons inside tablist become tabs\n this._tabs = Array.from(this._tablistEl.querySelectorAll('button'));\n\n // Remaining children become panels\n this._panels = children.slice(1);\n\n const activeIndex = Number(this.value) || 0;\n\n // Enhance tabs\n for (let i = 0; i < this._tabs.length; i++) {\n const tab = this._tabs[i];\n const panel = this._panels[i];\n const name = tab.getAttribute('name') ?? String(i);\n const tabId = `${this._instanceId}-tab-${name}`;\n const panelId = `${this._instanceId}-panel-${name}`;\n\n tab.setAttribute('role', 'tab');\n tab.setAttribute('id', tabId);\n tab.setAttribute('aria-selected', String(i === activeIndex));\n tab.setAttribute('tabindex', i === activeIndex ? '0' : '-1');\n\n if (panel) {\n tab.setAttribute('aria-controls', panelId);\n panel.setAttribute('role', 'tabpanel');\n panel.setAttribute('id', panelId);\n panel.setAttribute('aria-labelledby', tabId);\n panel.setAttribute('tabindex', '0');\n if (i !== activeIndex) {\n panel.hidden = true;\n } else {\n panel.hidden = false;\n }\n }\n }\n\n // Attach listeners\n this._tablistEl.addEventListener('click', this._onClick);\n this._tablistEl.addEventListener('keydown', this._onKeyDown);\n\n // Initial indicator position. Synchronous first (offset reads force\n // layout), then a ResizeObserver keeps it correct across hidden→visible\n // transitions and size changes — rAF is suspended in hidden documents,\n // so it must not be the only scheduling mechanism here.\n this._updateIndicator();\n this._resizeObserver = new ResizeObserver(() => this._updateIndicator());\n this._resizeObserver.observe(this._tablistEl);\n }\n\n private _teardown() {\n this._tablistEl?.removeEventListener('click', this._onClick);\n this._tablistEl?.removeEventListener('keydown', this._onKeyDown);\n this._resizeObserver?.disconnect();\n this._resizeObserver = null;\n this._initialized = false;\n }\n\n // --- Tab selection ---\n\n private _selectTab(index: number, emitEvent = true) {\n if (index < 0 || index >= this._tabs.length) return;\n\n for (let i = 0; i < this._tabs.length; i++) {\n const tab = this._tabs[i];\n const panel = this._panels[i];\n const isActive = i === index;\n\n tab.setAttribute('aria-selected', String(isActive));\n tab.setAttribute('tabindex', isActive ? '0' : '-1');\n\n if (panel) {\n panel.hidden = !isActive;\n }\n }\n\n this.value = String(index);\n this._updateIndicator();\n\n if (emitEvent) {\n const name = this._tabs[index]?.getAttribute('name') ?? null;\n this.dispatchEvent(new TabsChangeEvent(index, name));\n }\n }\n\n // --- Indicator ---\n\n private _updateIndicator() {\n if (!this._tablistEl) return;\n const activeTab = this._tabs[Number(this.value)];\n if (!activeTab) return;\n\n const isVertical = this.orientation === 'vertical';\n\n if (isVertical) {\n this._tablistEl.style.setProperty('--_indicator-top', `${activeTab.offsetTop}px`);\n this._tablistEl.style.setProperty('--_indicator-height', `${activeTab.offsetHeight}px`);\n } else {\n this._tablistEl.style.setProperty('--_indicator-left', `${activeTab.offsetLeft}px`);\n this._tablistEl.style.setProperty('--_indicator-width', `${activeTab.offsetWidth}px`);\n }\n }\n\n // --- Event handlers ---\n\n private _onClick = (e: Event) => {\n const tab = (e.target as HTMLElement).closest<HTMLButtonElement>('[role=\"tab\"]');\n if (!tab) return;\n const index = this._tabs.indexOf(tab);\n if (index >= 0) {\n this._selectTab(index);\n tab.focus();\n }\n };\n\n private _onKeyDown = (e: KeyboardEvent) => {\n const target = e.target as HTMLElement;\n if (target.getAttribute('role') !== 'tab') return;\n\n const isHorizontal = this.orientation === 'horizontal';\n const prevKey = isHorizontal ? 'ArrowLeft' : 'ArrowUp';\n const nextKey = isHorizontal ? 'ArrowRight' : 'ArrowDown';\n\n let index = this._tabs.indexOf(target as HTMLButtonElement);\n\n switch (e.key) {\n case nextKey:\n e.preventDefault();\n index = (index + 1) % this._tabs.length;\n this._selectTab(index);\n this._tabs[index].focus();\n break;\n case prevKey:\n e.preventDefault();\n index = (index - 1 + this._tabs.length) % this._tabs.length;\n this._selectTab(index);\n this._tabs[index].focus();\n break;\n case 'Home':\n e.preventDefault();\n this._selectTab(0);\n this._tabs[0].focus();\n break;\n case 'End':\n e.preventDefault();\n this._selectTab(this._tabs.length - 1);\n this._tabs[this._tabs.length - 1].focus();\n break;\n }\n };\n}\n\n// Type `addEventListener('change', …)` on this element so the listener receives\n// a `TabsChangeEvent` (with `.index`/`.name`) instead of a bare `Event`, with no\n// cast. This is the canonical reference for the pattern used across the library.\n//\n// Why a same-named interface and not a global augmentation: `change` already\n// exists in lib.dom's `GlobalEventHandlersEventMap` (typed `Event`), and TS\n// forbids re-declaring an existing key with a different type — so colliding\n// names (`change`, `select`, `input`, `toggle`) can't be typed globally. Merging\n// a same-named `interface Tabs` into the `class Tabs` adds these overloads to\n// the element's own type only. (Non-colliding names like `show`/`story-change`\n// ARE augmented globally in their event-class module — no per-element block.)\n//\n// In `(type: K, listener: (ev: TabsEventMap[K]) => void)`, K is pinned to the\n// string literal you pass, and `TabsEventMap[K]` indexes the event type from it\n// (`'change'` → `TabsChangeEvent`). `this: Tabs` is a fake parameter that types\n// `this` inside a non-arrow listener; it emits no runtime code.\n//\n// Three load-bearing details:\n// 1. The `(type: string, …)` fallback overloads are REQUIRED: declaring our\n// own `addEventListener` hides ALL inherited `HTMLElement` overloads, so\n// without them `addEventListener('click', …)` would stop compiling. The\n// typed overload comes first so `'change'` resolves before the fallback.\n// 2. This interface sits AFTER the class: the CEM analyzer dedups on the first\n// declaration of the name, so an interface before the class would shadow it\n// and the manifest would extract nothing for this element.\n// 3. The merge is flagged by `no-unsafe-declaration-merging` (disabled on the\n// class above) — safe here because we merge only method overloads, never\n// uninitialized properties.\nexport interface Tabs {\n addEventListener<K extends keyof TabsEventMap>(\n type: K,\n listener: (this: Tabs, ev: TabsEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): void;\n removeEventListener<K extends keyof TabsEventMap>(\n type: K,\n listener: (this: Tabs, ev: TabsEventMap[K]) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): void;\n}\n"],"mappings":"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"}
@@ -5626,6 +5626,41 @@ In dark mode, mixes the base color with black (default 15% black).
5626
5626
 
5627
5627
  & > :first-child button {
5628
5628
  padding: calc(var(--spacing) * 2) calc(var(--spacing) * 3);
5629
+
5630
+ /* `isolation` keeps the pill's negative z-index inside the button: it
5631
+ paints above the button background but under the label, and can never
5632
+ slip behind an ancestor's background (a card wrapping the tabs). */
5633
+ position: relative;
5634
+ isolation: isolate;
5635
+
5636
+ /* Hover pill, detached from the track: inset on the block axis so it
5637
+ never touches the bottom border or the sliding indicator. A 4px inset
5638
+ on a 44px tab leaves a 36px pill — same height as a medium control. */
5639
+ &::after {
5640
+ content: '';
5641
+ position: absolute;
5642
+ z-index: -1;
5643
+ inset-block: var(--hover-inset, calc(var(--spacing) * 1));
5644
+ inset-inline: 0;
5645
+ border-radius: var(--l-radius-md);
5646
+ background: transparent;
5647
+ transition: background-color var(--_duration) var(--_easing);
5648
+ }
5649
+
5650
+ /* Applies to the selected tab too — the fill is click feedback, not a
5651
+ selection cue (the underline carries that), so it would read as dead
5652
+ if the active tab were the one place it stopped responding. */
5653
+ @media (hover: hover) {
5654
+ &:hover::after {
5655
+ background: var(--hover-color, var(--l-color-bg-state-hover));
5656
+ }
5657
+ }
5658
+ }
5659
+ }
5660
+
5661
+ @media (prefers-reduced-motion: reduce) {
5662
+ l-tabs[variant='line'] > :first-child button::after {
5663
+ transition-duration: 0ms;
5629
5664
  }
5630
5665
  }
5631
5666
  }
@@ -138,6 +138,41 @@
138
138
 
139
139
  & > :first-child button {
140
140
  padding: calc(var(--spacing) * 2) calc(var(--spacing) * 3);
141
+
142
+ /* `isolation` keeps the pill's negative z-index inside the button: it
143
+ paints above the button background but under the label, and can never
144
+ slip behind an ancestor's background (a card wrapping the tabs). */
145
+ position: relative;
146
+ isolation: isolate;
147
+
148
+ /* Hover pill, detached from the track: inset on the block axis so it
149
+ never touches the bottom border or the sliding indicator. A 4px inset
150
+ on a 44px tab leaves a 36px pill — same height as a medium control. */
151
+ &::after {
152
+ content: '';
153
+ position: absolute;
154
+ z-index: -1;
155
+ inset-block: var(--hover-inset, calc(var(--spacing) * 1));
156
+ inset-inline: 0;
157
+ border-radius: var(--l-radius-md);
158
+ background: transparent;
159
+ transition: background-color var(--_duration) var(--_easing);
160
+ }
161
+
162
+ /* Applies to the selected tab too — the fill is click feedback, not a
163
+ selection cue (the underline carries that), so it would read as dead
164
+ if the active tab were the one place it stopped responding. */
165
+ @media (hover: hover) {
166
+ &:hover::after {
167
+ background: var(--hover-color, var(--l-color-bg-state-hover));
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ @media (prefers-reduced-motion: reduce) {
174
+ l-tabs[variant='line'] > :first-child button::after {
175
+ transition-duration: 0ms;
141
176
  }
142
177
  }
143
178
  }
@@ -138,6 +138,41 @@
138
138
 
139
139
  & > :first-child button {
140
140
  padding: calc(var(--spacing) * 2) calc(var(--spacing) * 3);
141
+
142
+ /* `isolation` keeps the pill's negative z-index inside the button: it
143
+ paints above the button background but under the label, and can never
144
+ slip behind an ancestor's background (a card wrapping the tabs). */
145
+ position: relative;
146
+ isolation: isolate;
147
+
148
+ /* Hover pill, detached from the track: inset on the block axis so it
149
+ never touches the bottom border or the sliding indicator. A 4px inset
150
+ on a 44px tab leaves a 36px pill — same height as a medium control. */
151
+ &::after {
152
+ content: '';
153
+ position: absolute;
154
+ z-index: -1;
155
+ inset-block: var(--hover-inset, calc(var(--spacing) * 1));
156
+ inset-inline: 0;
157
+ border-radius: var(--l-radius-md);
158
+ background: transparent;
159
+ transition: background-color var(--_duration) var(--_easing);
160
+ }
161
+
162
+ /* Applies to the selected tab too — the fill is click feedback, not a
163
+ selection cue (the underline carries that), so it would read as dead
164
+ if the active tab were the one place it stopped responding. */
165
+ @media (hover: hover) {
166
+ &:hover::after {
167
+ background: var(--hover-color, var(--l-color-bg-state-hover));
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ @media (prefers-reduced-motion: reduce) {
174
+ l-tabs[variant='line'] > :first-child button::after {
175
+ transition-duration: 0ms;
141
176
  }
142
177
  }
143
178
  }
@@ -172,181 +172,6 @@
172
172
  }
173
173
  ]
174
174
  },
175
- {
176
- "kind": "javascript-module",
177
- "path": "src/html/elements/avatar/avatar.ts",
178
- "declarations": [
179
- {
180
- "kind": "class",
181
- "description": "",
182
- "name": "Avatar",
183
- "cssProperties": [
184
- {
185
- "description": "Box size (any length). Set it inline (e.g. `style=\"--size: 20px\"`) for an arbitrary size beyond the `size` token scale; the font then follows proportionally. The `size` attribute sets it to the matching token.",
186
- "name": "--size",
187
- "default": "40px"
188
- },
189
- {
190
- "description": "Background fill color for initials and the default icon.",
191
- "name": "--color"
192
- },
193
- {
194
- "description": "Initials/icon color over `--color`. Defaults to an auto-derived readable color; set it to enforce a specific brand color (overrides the automatic choice).",
195
- "name": "--text-color"
196
- },
197
- {
198
- "description": "Set to `circle` for a fully circular avatar (default is a rounded square).",
199
- "name": "--appearance"
200
- }
201
- ],
202
- "cssParts": [
203
- {
204
- "description": "The avatar container that paints `--color`; style it (e.g. `color`) to override the auto-derived text color.",
205
- "name": "base"
206
- }
207
- ],
208
- "members": [
209
- {
210
- "kind": "field",
211
- "name": "src",
212
- "type": {
213
- "text": "string"
214
- },
215
- "default": "''",
216
- "description": "Image URL. Falls back to initials (then the default icon) if it fails to load.",
217
- "attribute": "src"
218
- },
219
- {
220
- "kind": "field",
221
- "name": "name",
222
- "type": {
223
- "text": "string"
224
- },
225
- "default": "''",
226
- "description": "Name used as the accessible label and to derive the initials fallback.",
227
- "attribute": "name"
228
- },
229
- {
230
- "kind": "field",
231
- "name": "size",
232
- "type": {
233
- "text": "string"
234
- },
235
- "default": "'md'",
236
- "description": "Avatar size: `xs`, `sm`, `md` (default), `lg`, or `xl`.",
237
- "attribute": "size",
238
- "reflects": true
239
- },
240
- {
241
- "kind": "field",
242
- "name": "badge",
243
- "type": {
244
- "text": "number"
245
- },
246
- "default": "0",
247
- "description": "Count shown in the corner badge. `0` hides the badge.",
248
- "attribute": "badge"
249
- },
250
- {
251
- "kind": "field",
252
- "name": "interactive",
253
- "type": {
254
- "text": "boolean"
255
- },
256
- "default": "false",
257
- "description": "Render as a `<button>` with focus ring and hover states.",
258
- "attribute": "interactive",
259
- "reflects": true
260
- }
261
- ],
262
- "attributes": [
263
- {
264
- "name": "src",
265
- "type": {
266
- "text": "string"
267
- },
268
- "default": "''",
269
- "description": "Image URL. Falls back to initials (then the default icon) if it fails to load.",
270
- "fieldName": "src"
271
- },
272
- {
273
- "name": "name",
274
- "type": {
275
- "text": "string"
276
- },
277
- "default": "''",
278
- "description": "Name used as the accessible label and to derive the initials fallback.",
279
- "fieldName": "name"
280
- },
281
- {
282
- "name": "size",
283
- "type": {
284
- "text": "string"
285
- },
286
- "default": "'md'",
287
- "description": "Avatar size: `xs`, `sm`, `md` (default), `lg`, or `xl`.",
288
- "fieldName": "size"
289
- },
290
- {
291
- "name": "badge",
292
- "type": {
293
- "text": "number"
294
- },
295
- "default": "0",
296
- "description": "Count shown in the corner badge. `0` hides the badge.",
297
- "fieldName": "badge"
298
- },
299
- {
300
- "name": "interactive",
301
- "type": {
302
- "text": "boolean"
303
- },
304
- "default": "false",
305
- "description": "Render as a `<button>` with focus ring and hover states.",
306
- "fieldName": "interactive"
307
- }
308
- ],
309
- "superclass": {
310
- "name": "LuxenElement",
311
- "module": "/src/html/shared/luxen-element.js"
312
- },
313
- "tagName": "l-avatar",
314
- "customElement": true,
315
- "summary": "An avatar component for displaying user images, initials, or a default icon.",
316
- "_cssClasses": [
317
- {
318
- "name": ".l-avatar-group",
319
- "description": "Flex wrapper that overlaps a row of stacked avatars."
320
- }
321
- ]
322
- }
323
- ],
324
- "exports": [
325
- {
326
- "kind": "js",
327
- "name": "Avatar",
328
- "declaration": {
329
- "name": "Avatar",
330
- "module": "src/html/elements/avatar/avatar.ts"
331
- }
332
- }
333
- ]
334
- },
335
- {
336
- "kind": "javascript-module",
337
- "path": "src/html/elements/avatar/index.ts",
338
- "declarations": [],
339
- "exports": [
340
- {
341
- "kind": "js",
342
- "name": "*",
343
- "declaration": {
344
- "name": "*",
345
- "module": "src/html/elements/avatar/avatar.js"
346
- }
347
- }
348
- ]
349
- },
350
175
  {
351
176
  "kind": "javascript-module",
352
177
  "path": "src/html/elements/alert-dialog/alert-dialog.ts",
@@ -827,6 +652,181 @@
827
652
  }
828
653
  ]
829
654
  },
655
+ {
656
+ "kind": "javascript-module",
657
+ "path": "src/html/elements/avatar/avatar.ts",
658
+ "declarations": [
659
+ {
660
+ "kind": "class",
661
+ "description": "",
662
+ "name": "Avatar",
663
+ "cssProperties": [
664
+ {
665
+ "description": "Box size (any length). Set it inline (e.g. `style=\"--size: 20px\"`) for an arbitrary size beyond the `size` token scale; the font then follows proportionally. The `size` attribute sets it to the matching token.",
666
+ "name": "--size",
667
+ "default": "40px"
668
+ },
669
+ {
670
+ "description": "Background fill color for initials and the default icon.",
671
+ "name": "--color"
672
+ },
673
+ {
674
+ "description": "Initials/icon color over `--color`. Defaults to an auto-derived readable color; set it to enforce a specific brand color (overrides the automatic choice).",
675
+ "name": "--text-color"
676
+ },
677
+ {
678
+ "description": "Set to `circle` for a fully circular avatar (default is a rounded square).",
679
+ "name": "--appearance"
680
+ }
681
+ ],
682
+ "cssParts": [
683
+ {
684
+ "description": "The avatar container that paints `--color`; style it (e.g. `color`) to override the auto-derived text color.",
685
+ "name": "base"
686
+ }
687
+ ],
688
+ "members": [
689
+ {
690
+ "kind": "field",
691
+ "name": "src",
692
+ "type": {
693
+ "text": "string"
694
+ },
695
+ "default": "''",
696
+ "description": "Image URL. Falls back to initials (then the default icon) if it fails to load.",
697
+ "attribute": "src"
698
+ },
699
+ {
700
+ "kind": "field",
701
+ "name": "name",
702
+ "type": {
703
+ "text": "string"
704
+ },
705
+ "default": "''",
706
+ "description": "Name used as the accessible label and to derive the initials fallback.",
707
+ "attribute": "name"
708
+ },
709
+ {
710
+ "kind": "field",
711
+ "name": "size",
712
+ "type": {
713
+ "text": "string"
714
+ },
715
+ "default": "'md'",
716
+ "description": "Avatar size: `xs`, `sm`, `md` (default), `lg`, or `xl`.",
717
+ "attribute": "size",
718
+ "reflects": true
719
+ },
720
+ {
721
+ "kind": "field",
722
+ "name": "badge",
723
+ "type": {
724
+ "text": "number"
725
+ },
726
+ "default": "0",
727
+ "description": "Count shown in the corner badge. `0` hides the badge.",
728
+ "attribute": "badge"
729
+ },
730
+ {
731
+ "kind": "field",
732
+ "name": "interactive",
733
+ "type": {
734
+ "text": "boolean"
735
+ },
736
+ "default": "false",
737
+ "description": "Render as a `<button>` with focus ring and hover states.",
738
+ "attribute": "interactive",
739
+ "reflects": true
740
+ }
741
+ ],
742
+ "attributes": [
743
+ {
744
+ "name": "src",
745
+ "type": {
746
+ "text": "string"
747
+ },
748
+ "default": "''",
749
+ "description": "Image URL. Falls back to initials (then the default icon) if it fails to load.",
750
+ "fieldName": "src"
751
+ },
752
+ {
753
+ "name": "name",
754
+ "type": {
755
+ "text": "string"
756
+ },
757
+ "default": "''",
758
+ "description": "Name used as the accessible label and to derive the initials fallback.",
759
+ "fieldName": "name"
760
+ },
761
+ {
762
+ "name": "size",
763
+ "type": {
764
+ "text": "string"
765
+ },
766
+ "default": "'md'",
767
+ "description": "Avatar size: `xs`, `sm`, `md` (default), `lg`, or `xl`.",
768
+ "fieldName": "size"
769
+ },
770
+ {
771
+ "name": "badge",
772
+ "type": {
773
+ "text": "number"
774
+ },
775
+ "default": "0",
776
+ "description": "Count shown in the corner badge. `0` hides the badge.",
777
+ "fieldName": "badge"
778
+ },
779
+ {
780
+ "name": "interactive",
781
+ "type": {
782
+ "text": "boolean"
783
+ },
784
+ "default": "false",
785
+ "description": "Render as a `<button>` with focus ring and hover states.",
786
+ "fieldName": "interactive"
787
+ }
788
+ ],
789
+ "superclass": {
790
+ "name": "LuxenElement",
791
+ "module": "/src/html/shared/luxen-element.js"
792
+ },
793
+ "tagName": "l-avatar",
794
+ "customElement": true,
795
+ "summary": "An avatar component for displaying user images, initials, or a default icon.",
796
+ "_cssClasses": [
797
+ {
798
+ "name": ".l-avatar-group",
799
+ "description": "Flex wrapper that overlaps a row of stacked avatars."
800
+ }
801
+ ]
802
+ }
803
+ ],
804
+ "exports": [
805
+ {
806
+ "kind": "js",
807
+ "name": "Avatar",
808
+ "declaration": {
809
+ "name": "Avatar",
810
+ "module": "src/html/elements/avatar/avatar.ts"
811
+ }
812
+ }
813
+ ]
814
+ },
815
+ {
816
+ "kind": "javascript-module",
817
+ "path": "src/html/elements/avatar/index.ts",
818
+ "declarations": [],
819
+ "exports": [
820
+ {
821
+ "kind": "js",
822
+ "name": "*",
823
+ "declaration": {
824
+ "name": "*",
825
+ "module": "src/html/elements/avatar/avatar.js"
826
+ }
827
+ }
828
+ ]
829
+ },
830
830
  {
831
831
  "kind": "javascript-module",
832
832
  "path": "src/html/elements/badge/badge.ts",
@@ -8573,6 +8573,16 @@
8573
8573
  "description": "`line` variant: thickness of the static bottom border.",
8574
8574
  "name": "--track-thickness",
8575
8575
  "default": "1px"
8576
+ },
8577
+ {
8578
+ "description": "`line` variant: fill of the pill that appears behind a hovered tab label.",
8579
+ "name": "--hover-color",
8580
+ "default": "var(--l-color-bg-state-hover)"
8581
+ },
8582
+ {
8583
+ "description": "`line` variant: block-axis gap between the hover pill and the tab edges, keeping it detached from the bottom border.",
8584
+ "name": "--hover-inset",
8585
+ "default": "4px"
8576
8586
  }
8577
8587
  ],
8578
8588
  "members": [
@@ -107,7 +107,22 @@ export declare class Dropdown extends LuxenElement {
107
107
  private _onPanelPointerOut;
108
108
  private _selectCurrentItem;
109
109
  private _selectItem;
110
- /** Sync `open` when popover="auto" light-dismiss fires. */
110
+ /**
111
+ * Route `popover="auto"` light-dismiss through the same close path as every
112
+ * other user-driven close (trigger click, Escape, item selection), so the
113
+ * cancelable `hide` event fires for it too. Assigning `open` here directly
114
+ * would skip it and desync anything a consumer syncs on `hide`.
115
+ *
116
+ * Driving `open` as a property or attribute still closes the menu with only
117
+ * `after-hide` — `l-dialog` mediates that path from `updated()`, this element
118
+ * does not. A gap worth closing on its own.
119
+ *
120
+ * The platform makes a popover's close non-cancelable (`beforetoggle` is only
121
+ * cancelable when opening), so a vetoed `hide` is honored by putting the panel
122
+ * back. `toggle` is the right hook for that: it is queued after the hide has
123
+ * actually been applied, whereas during `beforetoggle` the panel is still
124
+ * `:popover-open` and `showPopover()` would be a no-op the pending hide undoes.
125
+ */
111
126
  private _onToggle;
112
127
  render(): import("lit").TemplateResult<1>;
113
128
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dropdown.d.ts","sourceRoot":"","sources":["../../../src/html/elements/dropdown/dropdown.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dropdown.d.ts","sourceRoot":"","sources":["../../../src/html/elements/dropdown/dropdown.ts"],"names":[],"mappings":"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"}