On this pageFunctions
Ui/Menu
/**
* Returns the bare DOM id of the menu trigger button, derived from the
* menu's base id. Use this to associate an external label with the trigger
* via a native `<label for={Menu.buttonId(id)}>` or an `aria-labelledby`
* reference.
*/
(id: string): string/**
* Pairs the menu's `view` and `update` (and programmatic helpers)
* behind a single Item-typed entry point. Declaring the menu once at
* module scope ensures the view's `Item` type and the OutMessage's
* `item` type can't drift:
*
* ```ts
* const ActionMenu = Menu.create<Action>()
*
* // In view:
* h.submodel({ view: ActionMenu.view, ... })
*
* // In the parent update, pass ActionMenu.update to Update.foldChild and
* // handle Menu.OutMessage<Action> in foldOutMessage.
* ```
*/
<Item extends string = string>(): Bundle<Item>/** Creates an initial menu model from a config. Defaults to closed with no active item. */
(config: InitConfig): Menu.Model/** Schema for the activation trigger: whether the user interacted via mouse or keyboard. */
type ActivationTrigger = Literals<readonly ["Pointer", "Keyboard"]>type Bundle = Readonly<{
close: (model: Model) => BundleUpdateReturn<Item>
open: (model: Model) => BundleUpdateReturn<Item>
selectItem: (model: Model, item: Item, index: number) => BundleUpdateReturn<Item>
update: (model: Model, message: Message) => BundleUpdateReturn<Item>
view: SubmodelView<Model, Message, ViewInputs<Item>>
}>/** Configuration for a group heading rendered above a group of items. */
type GroupHeading = Readonly<{
className: string
content: Html
}>/** Configuration for creating a menu model with `init`. `isAnimated` enables animation coordination (default `false`). `isModal` locks page scroll and inerts other elements when open (default `false`). */
type InitConfig = Readonly<{
id: string
isAnimated: boolean
isModal: boolean
}>/** Configuration for an individual menu item's appearance. */
type ItemConfig = Readonly<{
className: string
content: Html
}>/**
* Generic over `Value extends string` so consumers using the typed
* `Menu.create<MyUnion>()` factory receive `value: MyUnion` in the
* `Selected` OutMessage. Defaults to `string`.
*/
type OutMessage = Selected<Value>/**
* Per-render view inputs passed to `view` via `h.submodel`'s `viewInputs` field.
*
* The Menu emits an `OutMessage.Selected({ value, index })` OutMessage on commit.
* The menu has already closed by the time this fires. Handle it in the
* `foldOutMessage` of the Menu's `Update.foldChild` config.
*/
type ViewInputs = Readonly<{
anchor: AnchorConfig
ariaLabel: string
ariaLabelledBy: string
attributes: ReadonlyArray<ChildAttribute>
backdropAttributes: ReadonlyArray<ChildAttribute>
backdropClassName: string
buttonAttributes: ReadonlyArray<ChildAttribute>
buttonClassName: string
buttonContent: Html
className: string
groupAttributes: ReadonlyArray<ChildAttribute>
groupClassName: string
groupToHeading: (groupKey: string) => GroupHeading | undefined
isButtonDisabled: boolean
isItemDisabled: (item: Item, index: number) => boolean
itemGroupKey: (item: Item, index: number) => string
items: ReadonlyArray<Item>
itemsAttributes: ReadonlyArray<ChildAttribute>
itemsClassName: string
itemsScrollAttributes: ReadonlyArray<ChildAttribute>
itemsScrollClassName: string
itemToConfig: (item: Item, context: Readonly<{
isActive: boolean
isDisabled: boolean
}>) => ItemConfig
itemToSearchText: (item: Item, index: number) => string
separatorAttributes: ReadonlyArray<ChildAttribute>
separatorClassName: string
}>/**
* The anchor-positioning Mount this Menu renders on its panel. The panel is
* always anchored to the button via Floating UI and portaled to the document
* body (opt out of portaling with `anchor.portal: false`), so it escapes
* ancestor stacking contexts and overflow clipping.
*
* It also carries the open-focus for the anchored panel. An anchored panel
* renders `visibility: hidden` until Floating UI resolves its first position,
* and `.focus()` does not land on a hidden element, so `FocusItems` alone
* cannot focus it. `focusAfterPosition` focuses the panel as part of that
* first reveal. `FocusItems` still focuses the panel when no anchor is
* configured, where the panel is visible as soon as the render commits.
*
* Exposed so Scene tests can call
* `Scene.Mount.resolve(AnchorMenu, Message.CompletedAnchorMenu())`.
*/
const AnchorMenu: MountDefinitionWithArgs<"AnchorMenu", {
anchor: Struct<{
gap: optional<Number>
isPlacementLocked: optional<Boolean>
offset: optional<Number>
padding: optional<Union<readonly [
Number,
Struct<{
bottom: optionalKey<Number>
left: optionalKey<Number>
right: optionalKey<Number>
top: optionalKey<Number>
}>
]>>
placement: optional<Literals<readonly ["top", "right", "bottom", "left", "top-start", "top-end", "right-start", "right-end", "bottom-start", "bottom-end", "left-start", "left-end"]>>
portal: optional<Boolean>
}>
buttonId: String
}, {
_tag: "CompletedAnchorMenu"
}>/** Programmatically clicks the active menu item's DOM element. */
const ClickItem: CommandDefinitionWithArgs<"ClickItem", {
id: String
index: Number
}, Effect<{
_tag: "CompletedClickItem"
}, never, never>>/** Waits for the typeahead search debounce period before clearing the query. */
const DelayClearSearch: CommandDefinitionWithArgs<"DelayClearSearch", {
version: Number
}, Effect<{
_tag: "CompletedDelayClearSearch"
version: number
}, never, never>>/** Detects whether the menu button moved or the leave animation ended. Whichever comes first; both outcomes signal the Animation submodel that leave is complete. */
const DetectMovementOrAnimationEnd: CommandDefinitionWithArgs<"DetectMovementOrAnimationEnd", {
id: String
}, Effect<{
_tag: "GotAnimationMessage"
message: {
_tag: "Showed"
} | {
_tag: "Hid"
} | {
_tag: "CompletedWaitForPaint"
} | {
_tag: "EndedAnimation"
}
}, never, never>>/** Moves focus back to the menu button after closing. */
const FocusButton: CommandDefinitionWithArgs<"FocusButton", {
id: String
}, Effect<{
_tag: "CompletedFocusButton"
}, never, never>>/** Moves focus to the menu items container after opening. */
const FocusItems: CommandDefinitionWithArgs<"FocusItems", {
id: String
}, Effect<{
_tag: "CompletedFocusItems"
}, never, never>>/** Marks all elements outside the menu as inert for modal behavior. */
const InertOthers: CommandDefinitionWithArgs<"InertOthers", {
id: String
}, Effect<{
_tag: "CompletedInertOthers"
}, never, never>>/** Prevents page scrolling while the menu is open. */
const LockScroll: CommandDefinitionNoArgs<"LockScroll", Effect<{
_tag: "CompletedLockScroll"
}, never, never>>/** Union of all messages the menu component can produce. */
const Message: MessageUnion<{
ActivatedItem: {
activationTrigger: Literals<readonly ["Pointer", "Keyboard"]>
index: Number
}
BlurredItems: {}
Closed: {}
CompletedAnchorMenu: {}
CompletedClickItem: {}
CompletedDelayClearSearch: {
version: Number
}
CompletedFocusButton: {}
CompletedFocusItems: {}
CompletedInertOthers: {}
CompletedLockScroll: {}
CompletedPortalMenuBackdrop: {}
CompletedRestoreInert: {}
CompletedScrollIntoView: {}
CompletedUnlockScroll: {}
DeactivatedItem: {}
GotAnimationMessage: {
message: MessageUnion<{
CompletedWaitForPaint: {}
EndedAnimation: {}
Hid: {}
Showed: {}
}>
}
IgnoredMouseClick: {}
MovedPointerOverItem: {
index: Number
screenX: Number
screenY: Number
}
Opened: {
maybeActiveItemIndex: Option<Number>
}
PressedPointerOnButton: {
button: Number
pointerType: String
screenX: Number
screenY: Number
timeStamp: Number
}
ReleasedPointerOnItems: {
screenX: Number
screenY: Number
timeStamp: Number
}
RequestedItemClick: {
index: Number
}
Searched: {
key: String
maybeTargetIndex: Option<Number>
}
SelectedItem: {
index: Number
item: String
}
SuppressedSpaceScroll: {}
}>/** Schema for the menu component's state, tracking open/closed status, active item, activation trigger, and typeahead search. */
const Model: Struct<{
activationTrigger: Literals<readonly ["Pointer", "Keyboard"]>
animation: Struct<{
id: String
isShowing: Boolean
transitionState: Literals<readonly ["Idle", "EnterStart", "EnterAnimating", "LeaveStart", "LeaveAnimating"]>
}>
id: String
isAnimated: Boolean
isModal: Boolean
isOpen: Boolean
maybeActiveItemIndex: Option<Number>
maybeLastButtonPointerType: Option<String>
maybeLastPointerPosition: Option<Struct<{
screenX: Number
screenY: Number
}>>
maybePointerOrigin: Option<Struct<{
screenX: Number
screenY: Number
timeStamp: Number
}>>
searchQuery: String
searchVersion: Number
}>/**
* Union of OutMessages the menu component can produce. The parent's
* `Update.foldChild` config handles them through `foldOutMessage`.
*/
const OutMessage: MessageUnion<{
Selected: {
index: Number
value: String
}
}>/**
* The backdrop-portaling Mount this Menu renders. Exposed so Scene tests can
* call `Scene.Mount.resolve(PortalMenuBackdrop, Message.CompletedPortalMenuBackdrop())` to
* acknowledge the mount produced by the rendered backdrop.
*/
const PortalMenuBackdrop: MountDefinitionNoArgs<"PortalMenuBackdrop", {
_tag: "CompletedPortalMenuBackdrop"
}>/** Removes the inert attribute from elements outside the menu. */
const RestoreInert: CommandDefinitionWithArgs<"RestoreInert", {
id: String
}, Effect<{
_tag: "CompletedRestoreInert"
}, never, never>>/** Scrolls the active menu item into view after keyboard navigation. */
const ScrollIntoView: CommandDefinitionWithArgs<"ScrollIntoView", {
id: String
index: Number
}, Effect<{
_tag: "CompletedScrollIntoView"
}, never, never>>