import{type TemplateResult,type PropertyValues}from'lit';import{LyraElement}from'../../../internal/lyra-element.js'; /** Which edge the tab strip sits on. `start`/`end` are logical, so they mirror under RTL. */ export type LyraTabGroupPlacement='top'|'bottom'|'start'|'end'; /** * `auto` moves selection with focus (the APG's automatic activation). `manual` moves focus only, * and the user commits with Enter or Space — required by the APG whenever revealing a panel is * expensive, since automatic activation would load every panel the user arrows past. */ export type LyraTabGroupActivation='auto'|'manual';export interface LyraTabGroupEventMap{'lr-tab-show':CustomEvent<{name:string;}>;'lr-tab-hide':CustomEvent<{name:string;}>;'lr-activate':CustomEvent<{value:string;}>;} /** * `` — a tab strip composed from direct `` and * `` children. This is the one canonical child model shared with both * mapped upstreams; the group assigns private projection slots itself so consumer markup remains * a mechanical tag rename. * * Implements the WAI-ARIA APG tabs pattern. With the default `activation="auto"`, Left/Right * (swapped under RTL, or Up/Down when `placement` is `start`/`end`) move focus *and* selection * together; with `activation="manual"` they move focus only and Enter/Space commits, which the APG * requires whenever revealing a panel is expensive. Removing a focused unselected tab silently * rehomes focus to a survivor while retaining a valid selection and preserving outside focus. * Home/End jump to the first/last enabled tab, * and a roving `tabindex` follows the focused tab. An enabled closable `` adds * `aria-keyshortcuts="Delete"` to that same real tab button; Delete routes the close request through * the descriptor so `lr-close` still targets ``. The visual close affordance stays * non-focusable and accessibility-hidden, avoiding a nested interactive control. Rich `` * content is likewise inert while projected; only its accessibility-exposed, default-slot text * names the real tab button rather than leaving an interactive descendant inside it. Author * `aria-hidden`, hidden, inert, and CSS-hidden branches never leak into that name, and visibility * or text changes refresh it. * A labeled direct tab without `panel` receives a stable synthetic panel key that cannot collide * with an authored tab or panel name. An unpaneled descriptor with no accessibility-exposed label * is omitted instead of speaking that internal key; an explicitly paneled empty descriptor retains * its authored panel name as the accessible fallback. * * **`inert` on a source child excludes its tab exactly as `disabled` does** — it never takes * selection, never holds the roving `tabindex`, and arrow keys step past it — and the rendered tab * button is itself marked `inert`, so a pointer cannot reach it either. Only the child's *own* * `inert` counts: a whole group inerted by an open modal keeps its selection and panels intact. * * `defaultSlot` exposes the real unnamed shadow slot expected by mapped integrations. Lyra keeps * it hidden because every accepted child is assigned to a deterministic named projection slot. * * **Overflow.** A horizontal tab row that does not fit stays natively scrollable and gains two * scroll controls flanking the tablist inside `[part="nav"]`, mirroring both upstreams. They are * rendered only for a horizontal `placement` (a vertical strip scrolls in the block direction, * which these controls do not address — the same restriction both upstreams apply) and are laid out * only while the tablist genuinely overflows, gated on the measurement the edge fade already uses, * so a row that fits is never flanked by two dead buttons. `without-scroll-controls` (or Shoelace's * `no-scroll-controls`) opts out, leaving the pre-8.0.0 behavior: native scrolling plus the fade. * The fade is deliberately kept alongside the controls — it says "the row continues past this * edge", which the controls themselves cannot show, and both appear on exactly the same condition. * By default an inactive edge control is hidden once that direction has nothing left to scroll to. * Shoelace's `fixed-scroll-controls` keeps both controls present throughout an overflowing range; * in that mode an exhausted control stays visible but remains a no-op. Edge state is measured in * logical coordinates, so controls and the one-sided fade remain truthful under RTL too. * A vertical `start`/`end` nav stays shrinkable and is capped at * `--lr-tab-group-vertical-nav-max-inline-size` (default `var(--lr-size-12rem)`), so long labels * ellipsize rather than consuming the panel's allocation. * * **The scroll controls are `aria-hidden="true"` and `tabindex="-1"`** — a pointer affordance only, * matching upstream. The tablist is already fully keyboard-scrollable without them: the roving * `tabindex` puts every tab one arrow key away, and focusing a tab scrolls it into view. Adding two * tab stops in the middle of the strip would therefore buy no capability and cost every keyboard * user two extra stops between the tabs and the panel. They still carry a localized `aria-label`, * so the name is there for automation and for a consumer that chooses to expose them. * * @customElement lr-tab-group * @slot - Canonical ``/`` pairs. * @slot nav - Upstream-compatible slot used by `` descriptors. * @event lr-tab-show - `detail: { name }`, fired when a tab becomes active via click or keyboard. * @event lr-tab-hide - `detail: { name }`, fired for the outgoing tab immediately before `lr-tab-show`. * @event lr-activate - Fired on every user activation of a navigable tab -- a click, or an * Arrow/Home/End key under `activation="auto"`, or Enter/Space under `activation="manual"` -- * whether or not the active tab actually moved. `detail: { value }` carries the activated tab's * panel name, the same identity `lr-tab-show` reports. Bubbling and composed, so a host outside * the shadow tree receives it. Not cancelable: it is a notification that the user picked a tab, * not a veto point, and nothing in this component branches on it. Re-picking the active tab is * the case `lr-tab-show` deliberately stays silent for -- "reload that panel" is a real intent -- * and from the keyboard it is otherwise unobservable, because Home on an already-first active tab * (or End on an already-last one) activates a tab and produces no click at all. When an * activation does move the tab, `lr-tab-hide` and `lr-tab-show` are emitted first. The * programmatic `show()` method is not a user activation and never fires it. * @csspart base - Compatibility name for the root wrapper; use `tab-group`. * @csspart tab-group - The root wrapper around the tablist and panels. It is the same node as * `base`. * @csspart nav - The row wrapping the tablist together with the two overflow scroll controls; mirrors the upstream part of the same name. * @csspart tabs - Upstream name on the same scroll container as `tablist`. * @csspart body - Wrapper around all tab panels. * @csspart tablist - The `role="tablist"` row of tab buttons. * @csspart scroll-button - Shared part on both overflow scroll controls. * @csspart scroll-button__base - Export-compatible base name on both scroll controls. * @csspart scroll-button-start - The control that scrolls the tabs toward their inline start ("previous" — the right-hand control under RTL). * @csspart scroll-button-end - The control that scrolls the tabs toward their inline end ("next" — the left-hand control under RTL). * @csspart scroll-button--start - Upstream modifier alias on `scroll-button-start`. * @csspart scroll-button--end - Upstream modifier alias on `scroll-button-end`. * @csspart scroll-button-glyph - The chevron wrapper inside a scroll control. This is the element that mirrors under RTL; the icon itself never rotates. * @csspart tab - A single tab button. * @csspart panel - A single `role="tabpanel"` wrapper (one per tab, hidden unless active). * @csspart active-tab-indicator - Indicator inside the active tab. * @cssprop --indicator-color - Upstream alias for the active indicator color. * @cssprop --track-color - Upstream track color. * @cssprop --track-width - Upstream track width. * @cssprop [--lr-scroll-fade-size=2rem] - Width of the fade at each horizontal scroll edge. The * fade is applied only while the tablist actually overflows, so a row that fits is never dimmed. * @cssprop [--lr-tab-group-selected-color=var(--lr-color-brand)] - Text color of the selected tab. * Scoped to `[aria-selected='true']` only, so it never repaints a hovered unselected tab (which * is what hijacking `--lr-color-brand` library-wide used to do). * @cssprop [--lr-tab-group-indicator-color=var(--lr-color-brand)] - Color of the selected tab's * underline, themeable independently of its text color. * @cssprop [--lr-tab-group-hover-color=var(--lr-color-text)] - Text color of a hovered, non-disabled tab. * Independent of the selected-state props above. * Pressed-tab and scroll-control hooks use inline `var()` fallbacks rather than a `:host` * declaration, so they inherit from the group or any ancestor without retheming the other states. * @cssprop [--lr-tab-group-active-bg=color-mix(in oklab, transparent, var(--lr-color-mix-partner) var(--lr-color-mix-active))] - Background of a pressed, non-disabled tab. * @cssprop [--lr-tab-group-active-color=var(--lr-tab-group-hover-color, var(--lr-color-text))] - Text * color of a pressed, non-disabled tab. * @cssprop [--lr-tab-group-scroll-button-hover-color=var(--lr-color-text)] - Text color of a * hovered overflow scroll control. * @cssprop [--lr-tab-group-scroll-button-active-bg=color-mix(in oklab, transparent, var(--lr-color-mix-partner) var(--lr-color-mix-active))] - Background of a pressed overflow scroll control. * @cssprop [--lr-tab-group-scroll-button-active-color=var(--lr-color-text)] - Text color of a * pressed overflow scroll control. * @cssprop [--lr-tab-group-vertical-nav-max-inline-size=var(--lr-size-12rem)] - Maximum logical * inline size of a vertical `start`/`end` nav. Long labels ellipsize inside that bound so the * panel remains usable at narrow allocations. * @cssprop [--lr-tab-group-panel-hover-outline-width=var(--lr-border-width-thin)] - Outline width * of the mouse-hover preview on `[part="panel"]`. * @cssprop [--lr-tab-group-panel-hover-outline-style=solid] - Outline style of the mouse-hover * preview on `[part="panel"]`. * @cssprop [--lr-tab-group-panel-hover-outline-color=var(--lr-color-border)] - Outline color of * the mouse-hover preview on `[part="panel"]`. Set to `transparent` to opt out of the hover * treatment entirely. * @cssprop [--lr-tab-group-panel-hover-outline-offset=var(--lr-focus-ring-offset)] - Offset of * the mouse-hover preview on `[part="panel"]`. * @status stable * @since 8.0.0 */ export declare class LyraTabGroup extends LyraElement{static styles:import("lit").CSSResultGroup[]; /** The active tab's panel name. Falls back to the first enabled tab whenever the current value doesn't resolve to one. */ active:string; /** Accessible name for the `role="tablist"` strip. Attribute-reflects from a host-level * `aria-label` so a plain-markup consumer gets ARIA-name forwarding without setting a JS * property. `null` omits the attribute; an explicit empty string is forwarded (the role has no * localized default name). */ accessibleLabel:string|null; /** Which edge the tab strip sits on. `start`/`end` are logical and mirror under RTL; both make * the tablist vertical, which swaps the navigation keys to Up/Down per the APG. */ placement:LyraTabGroupPlacement; /** `auto` (the default) moves selection with focus. `manual` moves focus only and waits for * Enter or Space — the APG requirement for panels that are expensive to reveal. */ activation:LyraTabGroupActivation; /** Suppresses the overflow scroll controls, leaving an overflowing tab row natively scrollable * with the edge fade as its only affordance. Web Awesome's spelling of the flag. */ withoutScrollControls:boolean; /** Shoelace's spelling of `withoutScrollControls`, read alongside it so a consumer arriving from * either upstream finds their own attribute working. Neither is deprecated. */ noScrollControls:boolean; /** Keeps both overflow controls visible while the row overflows, even when one logical edge is * exhausted. Without it, each inactive edge control is hidden, matching Shoelace. */ fixedScrollControls:boolean; /** The group's real unnamed slot. It is kept hidden because Lyra assigns each tab and panel to * its own deterministic named slot, but remains exposed for mapped slot observation. Stays a * writable `@query` field rather than a readonly `get`: `wa-tab-group` declares it writable, and * check-pinned-upstream-manifests treats a readonly-vs-writable difference on a mirrored member * as an `unsupported` surface drift -- a release blocker. */ defaultSlot:HTMLSlotElement;private tabs; /** Where keyboard focus currently sits under `activation="manual"`, which is allowed to differ * from `active`. Under `auto` the two are always the same, so this simply follows selection. */ private focusedTab;private baseId;private nextOpaqueId;private readonly idsBySlot;private mutationObserver?;private mutationObserverDocument?;private mutationObserverGeneration;private readonly projectedSourceSnapshots;private readonly projectedSlotSnapshots;private projectedSourceObserver?;private projectedSourceObserverDocument?;private projectedSourceObserverGeneration;private rehomeTabFocus;private scrollStartAvailable;private scrollEndAvailable; /** Shares its single `ResizeObserver` instance with the tab-scroll-edge bookkeeping below via * `onResize`/`observeExtra` -- a separate second observer on the very same `[part~="tablist"]` * element would double the observer count anything stubbing `ResizeObserver` globally sees, and * need its own independent owner-realm rebind on adoption instead of reusing this one's. */ private scrollOverflow;connectedCallback():void;disconnectedCallback():void;adoptedCallback():void;private resetMutationObserver; /** Rebuilds the canonical ``/`` model. First panel name wins so every * selection, focus, key, slot, and event identity remains unambiguous. */ private syncTabs; /** * Reads ``/`` pairs and assigns each one the `slot` that lands it in the * right place: a tab projects into its own button, a panel into its own tabpanel wrapper. Writing * `slot` here rather than asking consumers to is what keeps the upstream markup a pure rename -- * and it is idempotent, so the mutation observer that sees the write does not re-enter. * * A labeled tab with no `panel` still gets a stable, collision-free synthetic name from its * position, so it renders a button with an empty panel. An unpaneled descriptor with no * accessibility-exposed label has neither a usable name nor a panel relationship and is omitted * rather than exposing that internal key as its accessible name. An empty tab with an explicit * panel keeps that authored name as its stable fallback. */ private readElementModel; /** Rich tab descriptors are visual-only projections. Their direct default-slot elements are * made inert by this group, but the outer real tab still needs the author-visible, accessible * label text. Read that text through the same flattened slot shape while preserving every * author-owned accessibility exclusion. */ private readElementLabel;private libraryOwnsSourceInert;private isTabLabelSubtreeExcluded;private snapshotProjectedSource;private adoptProjectedSourceInert;private makeProjectedSourceInert;private restoreProjectedSource;private restoreProjectedSources;private resetProjectedSourceObserver;private observeProjectedSources;private projectedSources;private syncProjectedSources;private adoptProjectedSourceInertStates;private snapshotProjectedSlot;private adoptProjectedSlot;private projectSlot;private restoreProjectedSlot;private syncProjectedSlots;private restoreProjectedSlots;private adoptProjectedSlotStates; /** Whether a tab can hold selection, the roving `tabindex`, and arrow-key focus. `inert` counts * alongside `disabled` — see `isInertChild()` for why, and for why only the child's own inert * state is consulted. */ private isNavigable; /** Keeps `active` resolved to a real, navigable tab -- covers the initial default, a tab disappearing/becoming disabled or inert underneath the current selection, and a consumer assigning `.active` directly. Silent (no `lr-tab-show`): this corrects *invalid* state rather than responding to a user picking a different tab. Reading the focused element *before* the render that marks the outgoing button `inert` is what lets `updated()` rehome real focus rather than leaving it stranded on ``. */ protected willUpdate(changed:PropertyValues):void;protected updated(changed:PropertyValues):void; /** Activates `tab` (no-op for a disabled tab or one that's already active), emitting * `lr-tab-hide` for the outgoing tab before `lr-tab-show` for the incoming one, so a listener * that tears down the old panel always runs before the one that builds the new one. */ private selectTab; /** The user-driven wrapper around `selectTab`: it additionally reports the activation itself, * including the re-pick of the already-active tab that `lr-tab-show` is defined to stay silent * for. See the class doc's `lr-activate` entry. Kept separate from `selectTab` so the public * `show()` method stays a programmatic move rather than a synthesized user gesture. */ private activateTab; /** Show the panel named by an ``. */ show(panel:string):void; /** Mirrors selection onto public `active` hints for SSR-compatible tab/panel children. */ private syncElementActiveState; /** Moves the roving focus without selecting -- the `activation="manual"` path. */ private focusOnly; /** Whichever tab currently owns `tabindex="0"`. Under `auto` that is always the selected tab; * under `manual` focus may sit elsewhere, and it must fall back to the selection whenever the * remembered tab has gone away or become disabled. */ private get rovingTab(); /** The event-target tab is authoritative even when a controlled `active` write or manual-mode * memory points elsewhere. Fall back to real shadow focus, then to the current roving state only * when keyboard input did not originate from an owned tab button. */ private keyboardOriginTab; /** Moves real DOM focus to tab `slotName`'s button. Safe to call immediately (no `updateComplete` wait): every tab button already exists in the DOM regardless of its current `tabindex`, and `tabindex="-1"` elements are still focusable via script. */ private focusTab; /** Whether the strip runs down the side rather than across the top -- which decides both the * navigation keys and `aria-orientation`. */ private get isVertical();private onTabListKeyDown;private tabId;private panelId;private idsFor; /** Derives the `slot` an `` is projected into -- its own button. */ private tabSlotName; /** Whether either upstream's opt-out attribute is set. Both are read; neither wins. */ private get scrollControlsSuppressed();private measureTabScrollEdges;private setTabScrollEdges;private onTabListScroll; /** * Scrolls the tab row one step toward `edge`, unconditionally -- native scrolling does the work; * the lightweight scroll listener only refreshes logical edge availability for controls and * fades. This method itself does not consult `scrollStartAvailable`/`scrollEndAvailable`; the one * rendered caller (`renderScrollControl()`'s `@click`) checks that first so a user clicking a * boundary-adjacent control never issues a no-op native `scrollBy()`, but a direct/programmatic * caller always gets a real attempt -- e.g. a track with no tabs yet still needs its reduced-motion * resolution exercised the moment a caller does invoke this. * * `edge` is logical, so it is `effectiveDirection` that turns it into a physical delta: per the * CSSOM View spec (what every browser this library targets implements) `scrollLeft` under RTL * runs 0 at the inline start down to -max at the inline end, so "toward the end" is a *negative* * left delta there -- the mirror image of the LTR case, and the reason this cannot be a constant. * * `instant` rather than `auto` for the reduced-motion branch: `auto` defers to the stylesheet, so * a consumer's own `scroll-behavior: smooth` on the tablist would animate the very scroll the * preference asks not to animate. */ private scrollTabs; /** One overflow scroll control, or nothing at all when this group cannot have them (see the class * doc: vertical placement, or either upstream's opt-out attribute). Whether a *rendered* control * is laid out is a separate, purely visual question the stylesheet answers from the tablist's own * overflow measurement. */ private renderScrollControl; /** Pressing a control must not pull focus off the tab the user was on: Chromium focuses a button * on mousedown, and focus landing on an `aria-hidden` element leaves assistive technology with * no focus context at all. Suppressing the default keeps the roving tabindex where it was; the * click still fires. */ private onScrollControlMouseDown; /** Firefox suppresses the native `:active` pseudo-class when the mouse-down default is * prevented to retain the tab's roving focus. Mirror that short-lived state on the control so * its pressed affordance remains visible in every supported engine. */ private onScrollControlPointerDown;private onScrollControlPointerEnd; /** The projected `` below carries the rich, author-visible content across the shadow * boundary for *rendering*, but a ``'s assigned nodes are never part of its own DOM * subtree, so `Node.textContent` read on this button (or any ancestor of the slot) cannot see * through it -- unlike `aria-label` above, which the accessibility tree resolves independently. * The hidden span mirrors the same already-computed `tab.label` as a genuine light/shadow-tree * text node purely so `textContent` has something real to read; `hidden` (a real UA-default * `display: none`) keeps it invisible and out of the accessibility tree, so it never doubles the * visible label or the `aria-label` name. */ private renderTab;private renderPanel;render():TemplateResult;}declare global{interface HTMLElementTagNameMap{'lr-tab-group':LyraTabGroup;}}