import{type TemplateResult,type PropertyValues}from'lit';import{LyraElement,type LyraEventDetailSnapshot}from'../../../internal/lyra-element.js';import type{LyraMenuItem}from'./menu-item.class.js';import'./menu-item.class.js';export type{MenuFocusTarget}from'./menu-shared.js'; /** WA-compatible selection detail. The complete item keeps `value`, checkbox state, and any * consumer metadata available without translating the activation into a lossy string. */ export interface MenuItemSelectDetail{readonly item:LyraMenuItem;}export interface LyraMenuEventMap{'lr-select':CustomEvent>;} /** * `` — the inline semantic controller mapped from ``. It owns the * `role="menu"` list, real roving focus, wrapping Arrow/Home/End navigation, typeahead, and the * single canonical `lr-select` event. Wrap it in `` when a trigger, positioned popup, * open state, lifecycle events, or imperative overlay methods are required; those concerns belong * to the dropdown shell rather than being duplicated on the menu. * Background item removal or disablement repairs the roving stop without taking focus from * outside controls or the header/footer. Displacing the focused item rehomes actual focus. * * An `` with `` uses a private, symbol-keyed presentation * controller. ArrowRight steps into the submenu and ArrowLeft returns (mirrored under RTL), * Enter/Space open the branch, Escape and outside pointer dismissal close only the innermost * branch, hover uses an intent delay, and at most one submenu per level stays open. This internal * shell does not add root-menu attributes, methods, parts, or lifecycle events. * * Selection is always one cancelable `lr-select` carrying the complete item. A nested selection * bubbles through every ancestor without translation or re-emission; preventing it anywhere keeps * the current branch open. Item-to-menu activation uses private owner plumbing, so neither * `lr-menu-item-select` nor `lr-menu-select` is part of the public contract. * * @customElement lr-menu * @slot - `` elements, plus optionally plain `
` dividers * between groups (native `
` already carries an implicit `separator` * role, matching what `role="menu"` expects between item groups). Arbitrary * non-item content still renders here for backward compatibility, but the * `header`/`footer` slots below are the supported place for it. * @slot header - Composed content rendered above the items and *outside* the * `role="menu"` list — a filter/search field, a section title, a summary row. * Keeps its own full default keyboard behavior, is reachable with Tab from * the items, and is ARIA-valid (arbitrary content inside `role="menu"` is * not). Collapses to no box at all while unfilled. * @slot footer - Same as `header`, rendered below the items — an * "Apply"/"Done" button, a link to a fuller settings page, a count. * @event lr-select - WA-compatible selection event carrying `detail: { item }`. Cancelable; * preventing it keeps the current menu/submenu chain open. Emitted once by the menu that owns * the activated item and allowed to bubble through ancestors without translation/re-emission. * @csspart header - The wrapper around the `header` slot, above the list and * outside `role="menu"`. `display: none` while the slot is unfilled. * @csspart list - The `role="menu"` container wrapping the default slot. * @csspart footer - The wrapper around the `footer` slot, below the list and * outside `role="menu"`. `display: none` while the slot is unfilled. * @cssprop [--lr-menu-max-inline-size=var(--lr-size-20rem)] - Width ceiling of the standalone menu * surface and of a submenu's own surface. Takes a length or a percentage; `100%` and `none` both * uncap it to the container, and any other value outside `` is treated as * `none` rather than silently dropping the cap's safety terms. The viewport clamp and the * container allocation are applied outside this name, so no value can make a menu overflow * either. A contained menu (inside `lr-dropdown`) sizes from its dropdown and is unaffected. * @cssprop [--lr-menu-min-inline-size=var(--lr-size-10rem)] - Width floor of the same two * surfaces. Lower it alongside `--lr-menu-max-inline-size` to make a menu narrower than 10rem; * the floor wins over the ceiling, so capping alone cannot go below it. * @cssprop [--lr-overlay-surface=var(--lr-color-surface-overlay)] - Shared floating-surface fill, * on the standalone menu surface and on a submenu's own surface. A contained menu (inside * `lr-dropdown`) paints no surface of its own, so it is unaffected. * @cssprop [--lr-overlay-border=var(--lr-color-border)] - Shared floating-surface edge colour, on * the same two surfaces. The header/footer dividing rules and a slotted `hr` are deliberately * excluded: they separate content, they do not draw the surface's own edge. * @cssprop [--lr-overlay-radius=var(--lr-radius)] - Shared floating-surface corner radius, on the * same two surfaces. * @cssprop [--lr-overlay-shadow-anchored=var(--lr-shadow-m)] - Elevation of the anchored submenu * surface. * @cssprop --lr-positioning-strategy - Cascading `absolute`/`fixed` override for the private * submenu surface's `fixed` default, read from computed style when a submenu is (re)positioned. * Set it once on `:root`, a theme, or one clipping ancestor to change every unset submenu * beneath it; an unrecognized value falls back to `fixed`. * @status stable * @since 4.0.0 */ export declare class LyraMenu extends LyraElement{protected static readonly immutableEventDetails:readonly string[];protected static readonly identityEventDetailProperties:Readonly<{'lr-select':readonly string[];}>;static styles:import("lit").CSSResultGroup[];private presentationOpen;private submenuAnchor;private submenuStateChange; /** Accessible name for the `role="menu"` list — override with something * specific (e.g. "Row actions") when a page has more than one menu. * Localized (`menuLabel`) when omitted. Any supplied string, including * `"Menu"` or an empty string, remains literal. A host-level * `aria-label` attribute takes precedence over both this prop and the * localized default — including an explicit empty `aria-label=""` — * matching `lr-select`/`lr-model-select`'s established * `this.getAttribute('aria-label') ?? ` precedence * (see `effectiveLabel`). */ label?:string;get dropdownOpen():boolean;set dropdownOpen(next:boolean);private items;private activeIndex;private itemsSlot?;private cleanup?;private presentationPositioned;private itemStateObserver?;private pointerDocument?;private pendingFocus;private submenuOpenTimer?;private submenuCloseTimer?;private readonly listId;private readonly owningMenu;private typeAheadBuffer; /** The buffer's reset debounce. Every printable keystroke restarts it; the buffer clears only * once the quiet window passes, which is what makes "d" then "e" narrow to "de". Scheduled on * -- and cancelled through -- the realm this menu lives in at the time, the same ownership the * submenu hover-intent timers keep through `scheduleOwnedTimeout()`. Supersession is the * controller's own generation guard, so a callback already queued when a newer keystroke * restarted the timer arrives inert. */ private readonly typeAheadReset;private createSubmenuController;connectedCallback():void;protected firstUpdated(changed:PropertyValues):void; /** A root mapped menu is inline; only an owning menu item installs the private submenu shell. */ private get hasStandalonePresentation();private get interactionOpen(); /** Keeps the inline mapped shape visible and gives it one keyboard entry point. */ private syncPresentationState;private onRegionSlotChange; /** * Reflects "is this slot filled?" onto the host so the stylesheet can collapse an unfilled * header/footer wrapper to no box at all, and can skip the divider border next to an empty * list. `:empty` cannot do this job: Chromium's `:empty` does not ignore the whitespace-only * text nodes Lit leaves inside a part, so a `[part='header']:empty` rule never matches. * * The polarity is deliberate -- `data-has-header`/`data-has-footer` are *absent* for a menu * with neither slot filled, and `data-list-empty` is absent for a menu that has items, so the * overwhelmingly common shape gains no host attribute of any kind. */ private syncRegionState;protected updated(changed:PropertyValues):void;private reposition;private focusAfterPlacement;disconnectedCallback():void;private bindDocumentPointer;private unbindDocumentPointer;private scheduleOwnedTimeout;private clearOwnedTimeout;private openPresentation;private closePresentation;private onDocPointer;private onItemsSlotChange;private syncItemsFromSlot;private acquireItemOwner;private releaseItemOwner; /** Enforces `type="radio"` exclusive choice among the items this menu owns directly (`this.items` * never includes a nested submenu's items, which belong to that submenu's own `` — so * the scope this walks is already exactly "the owning menu", matching the group's default). */ private uncheckRadioGroupSiblings; /** `inert` counts alongside disabled/hidden because an inert element *refuses* focus: stepping * onto one leaves `focus()` a silent no-op, so roving focus is stranded on whatever held it * (or on ``) and every later key press dies. `closest` covers an inert ancestor, which * inerts the item just as completely as the attribute on the item itself. */ private isNavigable;private applyDropdownSize; /** Repairs the roving stop when an active item becomes unavailable; move actual focus only * when that item held it, preserving outside and slotted region focus during background edits. */ private onItemStateChange; /** Never leave real DOM focus on a removed/inert final row. Trigger-owned menus close and return * focus through their public owner contract; a standalone menu falls back to its role owner. */ private rehomeZeroSurvivorFocus;private onOwnedItemSelect;private dismissAfterSelection; /** Flips exactly one non-disabled item's `tabIndex` to `0` (the roving * target) and every other item's to `-1` -- see ``'s doc * for why this is the sole authority over that property. */ private applyRovingTabIndex; /** Moves the roving focus (and real DOM focus) to the first/last * non-disabled item. A no-op when there are none. */ private focusRoving;private setActiveItem; /** Closes every open submenu at this level except `keep`. */ private closeSubmenus;private openSubmenuItem;private clearSubmenuTimers; /** * The row of *this* menu the event passed through, or `undefined` for one that touched none — * the *last* `LyraMenuItem` before this host on the path, deliberately not the first. Pointing * at a row inside an open submenu passes through that submenu's own row first and only then * through the row that opened it here, and the latter is the answer the hover bookkeeping * wants: the pointer is still inside the branch that row owns. */ private ownItemFromEvent; /** * Whether a nested `` owns this event. Events from a submenu's rows reach this menu's * own delegated listeners (a submenu is light-DOM content inside one of this menu's items), and * without this guard an Escape or Arrow key inside a submenu would drive both menus at once. * * This is also the whole stacking story for a nested dismissible: the innermost open menu is * the one containing focus, it is the first `` on the event's path, so it handles the * keypress and every ancestor declines -- no document-level listener and no shared overlay * stack are involved on either side. */ private isForeignEvent; /** Resyncs `activeIndex` (and the roving `tabindex`) to wherever real DOM * focus actually lands, for any path that doesn't go through * `setActiveItem()` -- e.g. a real mousedown on an item, which focuses it * even while `disabled` (`tabIndex="-1"` remains mouse-focusable per * spec). Without this, `activeIndex` goes stale the moment focus moves any * other way, and subsequent Arrow-key navigation computes its next item * from that stale position instead of from where focus actually is. A * no-op for `setActiveItem()`'s own `.focus()` call, since `activeIndex` * there is already set to match before focus moves. */ private onListFocusIn;private onListKeyDown; /** * Pointer intent for submenus, bound to the menu surface rather than to each row: an open * submenu is DOM-nested inside the row that opened it, so a `pointerover` anywhere within that * submenu still resolves to the parent row here (see `ownItemFromEvent`) and reads as "still * inside this branch". That, plus the close delay being longer than the open delay, is what * lets the cursor cut diagonally across the rows in between without the submenu vanishing. * * Focus is deliberately never moved: the pointer opens a submenu, it does not claim the * keyboard. */ private onPopupPointerOver; /** Leaving the popup entirely is the same intent as hovering a different row -- with one * exception: crossing the gap between a popup and its own submenu fires this too, which is * why it schedules rather than closes. */ private onPopupPointerLeave;private scheduleSubmenuClose; /** A submenu's single `lr-select` keeps bubbling through every ancestor. Closure waits one * microtask so a consumer later on that same dispatch path can veto it. */ private onNestedSelect;private popupPart; /** * Whether a Tab/Shift+Tab keypress would move focus out of the private submenu surface * altogether, rather than to another focusable inside it. Its Tab sequence is * `[header focusables, the list, footer focusables]`, and the list contributes exactly one stop * -- the roving `tabindex="0"` item. * * Non-item content slotted into the *default* slot is deliberately not part of that sequence * from an item's point of view: Tab from an `` closes the menu exactly as it * always has unless there is a real region to move into. Tab *from* such content is still * measured against its default-slot neighbors, so a legacy two-control filter row keeps * working -- only tabbing past the last of them now closes the menu, which is the dismissal * hole this seals (previously the item-target gate swallowed the keypress entirely and focus * walked out of the popup while the menu stayed open). */ private tabWouldLeavePopup; /** * Bound to the private submenu surface rather than `[part='list']` so it also sees keydowns from the * `header`/`footer` regions, which sit outside the `role="menu"` list. * * Escape from those regions closes the submenu. Escape bubbling up from inside the list stays * with `onListKeyDown`, which can distinguish this submenu from an inline root menu. */ private onPopupKeyDown; /** Standard WAI-ARIA APG menu-button type-ahead: moves the roving focus to * the next non-disabled item whose text content starts with the * accumulated buffer, cycling from just after the currently active item * -- mirrors ``'s identical listbox type-ahead. */ private typeAhead; /** Discards the buffer and any armed reset. `cancel()`, never `dispose()`: this also runs on * disconnect, which here may be a re-parent, and a disposed controller would refuse every * later keystroke's reset for good. */ private resetTypeAhead; /** What type-ahead matches against: the row's accessible name where it has one, its text * otherwise. The distinction is load-bearing for a submenu parent, whose `textContent` also * contains every label inside the submenu. */ private itemText; /** Resolves `label`'s effective text: a host-level `aria-label` attribute wins first * (unset by default, so this is a no-op for every existing consumer), then an explicit menu * `label`, then a containing dropdown's label. With none of those supplied, it routes through * `this.localize()` so a locale/`.strings` override applies without requiring `label` itself * to be set. */ private get effectiveLabel();private renderContents;render():TemplateResult;}declare global{interface HTMLElementTagNameMap{'lr-menu':LyraMenu;}}