/** * createMenu - the HEADLESS core behind / , in the same * spirit as `createListbox`: a runes-based state machine for ONE menu level - * roving focus over enabled items, submenu open/collapse, and the full WAI-ARIA * menu keyboard contract (arrows, Home/End, Enter/Space, Escape, Tab, and * rtl-aware ArrowLeft/Right) - with **prop-getters** you spread onto your own * markup. No styles, no DOM assumptions. * * A menu tree is recursive, so you create one core per nesting level (exactly * what the recursive does). The styled component is just one * renderer over this core. * * ```svelte * *
* {#each items as item, i} * * {/each} *
* ``` */ import type { MenuItem } from './menu-item'; import type { EditorDir } from './editor-contract'; /** Reactive inputs are getters so the core tracks live prop changes (the same * controlled pattern as `createListbox`). */ export type MenuConfig = { items: () => ReadonlyArray; /** Report the chosen leaf item (a non-parent menuitem). */ onSelect: (item: MenuItem) => void; /** Close the WHOLE menu tree - a leaf was chosen, or Escape / Tab / dismissal. */ onClose: () => void; /** This level is a submenu: enables ArrowLeft (or ArrowRight under rtl) to * collapse back into the opener. The root level has nowhere to collapse. */ submenu?: () => boolean; /** Collapse just THIS submenu level and refocus the item that opened it. */ onCollapse?: () => void; /** Text direction; under `rtl` the open/collapse arrow keys swap. */ dir?: () => EditorDir | undefined; /** DOM focus hook, invoked ONLY on keyboard navigation (never on hover) so the * core stays DOM-free. Receives the item index that should take focus. */ focusItem?: (index: number) => void; }; export type MenuItemProps = { role: 'menuitem'; 'data-mi': number; tabindex: 0 | -1; 'aria-haspopup': 'menu' | undefined; 'aria-expanded': boolean | undefined; 'aria-disabled': true | undefined; disabled: boolean | undefined; onkeydown: (event: KeyboardEvent) => void; onpointerenter: () => void; onclick: () => void; }; export declare function createMenu(config: MenuConfig): { readonly active: number; readonly openSub: number; /** Enabled (focusable) item indices. */ readonly focusables: number[]; isActive: (i: number) => boolean; isSubOpen: (i: number) => boolean; itemTabIndex: (i: number) => 0 | -1; move: (delta: number) => void; first: () => void; last: () => void; /** Highlight + DOM-focus item `i` (e.g. to refocus a submenu's opener). */ focus: (i: number) => void; hover: (i: number) => void; choose: (item: MenuItem) => void; onItemKeydown: (e: KeyboardEvent, i: number) => void; /** Spread onto the menu container element. */ listProps: () => { role: "menu"; }; /** Spread onto the menuitem element at `i`. */ itemProps: (i: number) => MenuItemProps; }; export type Menu = ReturnType; export type { MenuItem };