From 65f10ac21e44b4f74e576255550d65fed82bdf6d Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Fri, 28 Aug 2026 12:24:07 +0200 Subject: [PATCH] fix(aria/menu): focus not moved into menu if items are delayed Fixes that if the items aren't available immediately on open (e.g. when they're in an overlay), the Aria menu fails to move focus into the first item. Fixes #33742. --- goldens/aria/private/index.api.md | 3 +-- src/aria/private/menu/menu.ts | 15 +++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/goldens/aria/private/index.api.md b/goldens/aria/private/index.api.md index a605f4808bd1..d8377b1340c3 100644 --- a/goldens/aria/private/index.api.md +++ b/goldens/aria/private/index.api.md @@ -439,7 +439,6 @@ export class MenuPattern { _clearTimeouts(): void; close(): void; closeAll(): void; - _closeTimeout: any; collapse(): void; readonly disabled: () => boolean; readonly dynamicSpaceKey: SignalLike<"" | " ">; @@ -451,6 +450,7 @@ export class MenuPattern { // (undocumented) readonly inputs: MenuInputs; readonly isFocused: WritableSignalLike; + readonly items: () => MenuItemPattern[]; readonly keydownManager: SignalLike>; last(): void; readonly listBehavior: List, V | undefined>; @@ -461,7 +461,6 @@ export class MenuPattern { onKeydown(event: KeyboardEvent): void; onMouseOut(event: MouseEvent): void; onMouseOver(event: MouseEvent): void; - _openTimeout: any; prev(): void; readonly role: () => string; readonly root: SignalLike | MenuBarPattern | MenuPattern | undefined>; diff --git a/src/aria/private/menu/menu.ts b/src/aria/private/menu/menu.ts index d739d3e22136..c7ff24ec72d6 100644 --- a/src/aria/private/menu/menu.ts +++ b/src/aria/private/menu/menu.ts @@ -105,11 +105,14 @@ export class MenuPattern { /** Whether the menu trigger has been hovered. */ readonly hasBeenHovered = signal(false); + /** Items currently inside the menu. */ + readonly items = () => this.inputs.items(); + /** Timeout used to open sub-menus on hover. */ - _openTimeout: any; + private _openTimeout: ReturnType | undefined; /** Timeout used to close sub-menus on hover out. */ - _closeTimeout: any; + private _closeTimeout: ReturnType | undefined; /** The tab index of the menu. */ readonly tabIndex = () => this.listBehavior.tabIndex(); @@ -246,7 +249,7 @@ export class MenuPattern { } const parent = this.inputs.parent(); - const activeItem = this?.inputs.activeItem(); + const activeItem = this.inputs.activeItem(); if (parent instanceof MenuItemPattern) { const grandparent = parent.inputs.parent(); @@ -689,7 +692,11 @@ export class MenuTriggerPattern { pendingFocusEffect(): void { const menu = this.inputs.menu(); const intent = this.pendingFocus(); - if (menu && intent) { + const items = menu?.items(); + + // We check the items so that we don't try calling into + // `first/last` until the items are actually available. + if (menu && intent && items?.length) { if (intent === 'first') { menu.first(); } else if (intent === 'last') {