Skip to main content
cngx-src documentation

CngxMenuFocusStack

Interface

projects/common/interactive/menu/menu-focus-stack.ts

Import#

import { CngxMenuFocusStack } from '@cngx/common/interactive'

Description#

Submenu focus-stack model shared by CngxMenuTrigger and the context-menu trigger core. Owns the active submenu chain, the saved-focus slot restored after close, and the W3C APG ArrowRight / ArrowLeft / Escape / activation routing.

Index#

Instance Properties#

stack#Signal
Readonly

Active submenu chain - empty when only the root menu is open.

Methods#

The submenu companion of the effective menu's active item, or undefined when the active item is a leaf (or nothing is active). The single lookup shared by keyboard, click, and hover activation - callers guard on the result and route it to openSubmenuFor / noteSubmenuOpened.

captureFocus#void

Capture the currently focused element (once) for post-close restore.

closeAll#void

Close every open submenu innermost-first, then hide the popover.

closeSubmenuFor#void
closeSubmenuFor(submenu: CngxMenuSubmenuLike)

Close a specific submenu through the stack - the close counterpart to openSubmenuFor shared by hover leave and programmatic close. Pops the stack innermost-first until the submenu's inner menu is off it (closing any open descendants on the way), so keyboard routing falls back to the parent level. When the submenu is not stack-tracked (already popped by a sibling's chain-correcting open) it only ensures the popover is hidden. Idempotent on a closed submenu; a no-op on an inert brain.

@paramsubmenuCngxMenuSubmenuLike
discardFocus#void
Optional

Drop the captured element without focusing it. For ownership handoffs where another opener now owns focus and a restore would yank it away. Optional so custom factory implementations stay assignable.

effectiveMenu#CngxMenuHost

Top-of-stack menu host, or the root when the stack is empty.

handleActivation#void
handleActivation(menu: CngxMenuHost, event: KeyboardEvent)

Enter/Space: open a submenu parent, else activate the leaf and close.

@parammenuCngxMenuHost
@parameventKeyboardEvent
handleArrowLeft#void
handleArrowLeft(menu: CngxMenuHost, event: KeyboardEvent)

ArrowLeft: pop the current submenu level when one is open.

@parammenuCngxMenuHost
@parameventKeyboardEvent
handleArrowRight#void
handleArrowRight(menu: CngxMenuHost, event: KeyboardEvent)

ArrowRight: open the active item's submenu when the policy allows.

@parammenuCngxMenuHost
@parameventKeyboardEvent
handleEscape#void
handleEscape(event: KeyboardEvent)

Escape: pop the innermost submenu, or hide the popover at the root.

@parameventKeyboardEvent
noteSubmenuOpened#void
noteSubmenuOpened(submenu: CngxMenuSubmenuLike)

Record that a submenu is already open, pushing its inner menu onto the stack and highlighting the first item WITHOUT calling submenu.open(). Safety net for opens the stack did not perform itself - e.g. the organism's programmatic openAsSubmenu seam - so an externally opened submenu is stack-tracked (ArrowLeft / Escape pop it) exactly like a keyboard-opened one, with no risk of re-entering the open path. Hover does not need it: it routes through openSubmenuFor via connectSubmenuHoverToFocusStack, and this call is then an idempotent no-op because the submenu is already on the stack.

@paramsubmenuCngxMenuSubmenuLike
openSubmenuFor#void
openSubmenuFor(submenu: CngxMenuSubmenuLike)

Open a specific submenu through the stack - the single open-only primitive shared by keyboard activation, pointer click, and hover. Delegates to the same private open the ArrowRight path uses: opens the submenu popover (flipping its aria-expanded), pushes the inner menu onto the stack, and highlights the inner menu's first item. Idempotent - a no-op when the submenu's inner menu is already on the stack, so repeated activation or hover never double-pushes. Chain-correcting: hover can target a submenu whose parent menu is not the top of the stack (a sibling parent while another branch is open), so any levels above the submenu's parent menu are popped first and the stack stays a strict chain. A no-op on an inert brain (inner never resolved - an organism leaf item): there is nothing to open and the open branch must survive a sweep across a leaf row. Also a no-op when the submenu's parent menu is not part of the open chain (its branch was popped since the caller resolved the submenu) - opening there would show an orphaned popover and fork the stack.

@paramsubmenuCngxMenuSubmenuLike
reset#void

Close every open submenu innermost-first and clear the stack WITHOUT hiding the root popover. Called on the trigger's close path so a stale submenu chain never survives a non-Escape dismissal (outside-click, blur, scroll): the root popover is already closing, and a reopened menu must start from the root, not a submenu that no longer exists.

restoreFocus#void

Restore focus to the captured element after the close DOM settles.