diff --git a/src/aria/menu/menu.spec.ts b/src/aria/menu/menu.spec.ts index e6d422b29a5c..316feba0141b 100644 --- a/src/aria/menu/menu.spec.ts +++ b/src/aria/menu/menu.spec.ts @@ -212,6 +212,17 @@ describe('Standalone Menu Pattern', () => { expect(fixture.componentInstance.itemSelected).toHaveBeenCalledWith('Banana'); }); + it('should select the focused item on enter when focus was moved externally', async () => { + const cherry = getItem('Cherry')!; + spyOn(fixture.componentInstance, 'itemSelected'); + + cherry.focus(); + await fixture.whenStable(); + await keydown(cherry, 'Enter'); + + expect(fixture.componentInstance.itemSelected).toHaveBeenCalledWith('Cherry'); + }); + it('should select an item on space', async () => { const banana = getItem('Banana'); spyOn(fixture.componentInstance, 'itemSelected'); diff --git a/src/aria/menu/menu.ts b/src/aria/menu/menu.ts index cb7fafaaaf23..0cfd9682258c 100644 --- a/src/aria/menu/menu.ts +++ b/src/aria/menu/menu.ts @@ -67,7 +67,7 @@ import {MENU_COMPONENT} from './menu-tokens'; '(mouseover)': '_pattern.onMouseOver($event)', '(mouseout)': '_pattern.onMouseOut($event)', '(focusout)': '_pattern.onFocusOut($event)', - '(focusin)': '_pattern.onFocusIn()', + '(focusin)': '_pattern.onFocusIn($event)', '(click)': '_pattern.onClick($event)', }, hostDirectives: [ diff --git a/src/aria/private/menu/menu.ts b/src/aria/private/menu/menu.ts index c7ff24ec72d6..8c0443542458 100644 --- a/src/aria/private/menu/menu.ts +++ b/src/aria/private/menu/menu.ts @@ -333,9 +333,17 @@ export class MenuPattern { } /** Handles focusin events for the menu. */ - onFocusIn() { + onFocusIn(event?: FocusEvent) { this.isFocused.set(true); this.hasBeenInteracted.set(true); + + const target = event && _getEventTarget(event); + const focusedItem = + target && this.inputs.items().find(item => item.element()?.contains(target)); + + if (focusedItem) { + this.listBehavior.goto(focusedItem, {focusElement: false}); + } } /** Handles the focusout event for the menu. */ diff --git a/src/aria/private/tabs/tabs.ts b/src/aria/private/tabs/tabs.ts index 85a82d8496a1..458aafb9ffb9 100644 --- a/src/aria/private/tabs/tabs.ts +++ b/src/aria/private/tabs/tabs.ts @@ -268,8 +268,13 @@ export class TabListPattern { } /** Handles focusin events for the tablist. */ - onFocusIn() { + onFocusIn(event?: FocusEvent) { this.hasBeenInteracted.set(true); + + const item = event && this._getItem(event); + if (item) { + this.navigationBehavior.goto(item, {focusElement: false}); + } } /** Opens the given tab or the current active tab. */ @@ -295,8 +300,8 @@ export class TabListPattern { } } - /** Returns the tab item associated with the given pointer event. */ - private _getItem(e: PointerEvent) { + /** Returns the tab item associated with the given event. */ + private _getItem(e: Event) { const target = _getEventTarget(e); if (!target) { return; diff --git a/src/aria/tabs/tab-list.ts b/src/aria/tabs/tab-list.ts index 6cccfb77ab72..949148eab81b 100644 --- a/src/aria/tabs/tab-list.ts +++ b/src/aria/tabs/tab-list.ts @@ -54,7 +54,7 @@ import type {Tab} from './tab'; '[attr.aria-activedescendant]': '_pattern.activeDescendant()', '(keydown)': '_pattern.onKeydown($event)', '(click)': '_pattern.onClick($event)', - '(focusin)': '_pattern.onFocusIn()', + '(focusin)': '_pattern.onFocusIn($event)', }, providers: [{provide: TAB_LIST, useExisting: TabList}], }) diff --git a/src/aria/tabs/tabs.spec.ts b/src/aria/tabs/tabs.spec.ts index 065677e040d1..be3bfdce94a6 100644 --- a/src/aria/tabs/tabs.spec.ts +++ b/src/aria/tabs/tabs.spec.ts @@ -616,6 +616,21 @@ describe('Tabs', () => { expect(tabElements[1].getAttribute('aria-selected')).toBe('true'); }); + it('should act on the tab that receives external focus', async () => { + await updateTabs({selectedTab: 'tab1'}); + + tabElements[2].focus(); + await fixture.whenStable(); + expect(isTabFocused(2)).toBe(true); + + await enter(); + expect(testComponent.selectedTab()).toBe('tab3'); + + await left(); + expect(isTabFocused(1)).toBe(true); + expect(testComponent.selectedTab()).toBe('tab3'); + }); + it('should select focused tab on Enter', async () => { await updateTabs({selectedTab: 'tab1'}); expect(testComponent.selectedTab()).toBe('tab1');