Skip to content

Commit 3484f71

Browse files
authored
fix(material/menu): set correct focus origin on item when opened via keyboard (#21252)
Fixes that we were setting the `program` focus origin on the first menu item, even though the user opened it using the keyboard. Fixes #21241.
1 parent fc68d5c commit 3484f71

File tree

4 files changed

+71
-5
lines changed

4 files changed

+71
-5
lines changed

src/material-experimental/mdc-menu/menu.spec.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,16 @@ import {
1818
} from '@angular/core';
1919
import {Direction, Directionality} from '@angular/cdk/bidi';
2020
import {OverlayContainer, Overlay} from '@angular/cdk/overlay';
21-
import {ESCAPE, LEFT_ARROW, RIGHT_ARROW, DOWN_ARROW, TAB, HOME, END} from '@angular/cdk/keycodes';
21+
import {
22+
ESCAPE,
23+
LEFT_ARROW,
24+
RIGHT_ARROW,
25+
DOWN_ARROW,
26+
TAB,
27+
HOME,
28+
END,
29+
ENTER,
30+
} from '@angular/cdk/keycodes';
2231
import {MatMenu, MatMenuModule, MatMenuItem} from './index';
2332
import {MatRipple} from '@angular/material-experimental/mdc-core';
2433
import {
@@ -723,6 +732,27 @@ describe('MDC-based MatMenu', () => {
723732
expect(items[2].classList).toContain('cdk-keyboard-focused');
724733
}));
725734

735+
it('should set the keyboard focus origin when opened using the keyboard', fakeAsync(() => {
736+
const fixture = createComponent(SimpleMenu, [], [FakeIcon]);
737+
fixture.detectChanges();
738+
const trigger = fixture.componentInstance.triggerEl.nativeElement;
739+
740+
// Note that we dispatch both a `click` and a `keydown` to imitate the browser behavior.
741+
dispatchKeyboardEvent(trigger, 'keydown', ENTER);
742+
trigger.click();
743+
fixture.detectChanges();
744+
745+
const items =
746+
Array.from<HTMLElement>(document.querySelectorAll('.mat-mdc-menu-panel [mat-menu-item]'));
747+
748+
items.forEach(item => patchElementFocus(item));
749+
tick(500);
750+
tick();
751+
fixture.detectChanges();
752+
753+
expect(items[0].classList).toContain('cdk-keyboard-focused');
754+
}));
755+
726756
it('should toggle the aria-expanded attribute on the trigger', () => {
727757
const fixture = createComponent(SimpleMenu, [], [FakeIcon]);
728758
fixture.detectChanges();

src/material/menu/menu-trigger.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {FocusMonitor, FocusOrigin, isFakeMousedownFromScreenReader} from '@angular/cdk/a11y';
1010
import {Direction, Directionality} from '@angular/cdk/bidi';
11-
import {LEFT_ARROW, RIGHT_ARROW} from '@angular/cdk/keycodes';
11+
import {ENTER, LEFT_ARROW, RIGHT_ARROW, SPACE} from '@angular/cdk/keycodes';
1212
import {
1313
FlexibleConnectedPositionStrategy,
1414
HorizontalConnectionPos,
@@ -103,7 +103,7 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
103103

104104
// Tracking input type is necessary so it's possible to only auto-focus
105105
// the first item of the list when the menu is opened via the keyboard
106-
_openedBy: 'mouse' | 'touch' | null = null;
106+
_openedBy: Exclude<FocusOrigin, 'program'> = null;
107107

108108
/**
109109
* @deprecated
@@ -520,9 +520,15 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
520520
_handleKeydown(event: KeyboardEvent): void {
521521
const keyCode = event.keyCode;
522522

523+
// Pressing enter on the trigger will trigger the click handler later.
524+
if (keyCode === ENTER || keyCode === SPACE) {
525+
this._openedBy = 'keyboard';
526+
}
527+
523528
if (this.triggersSubmenu() && (
524529
(keyCode === RIGHT_ARROW && this.dir === 'ltr') ||
525530
(keyCode === LEFT_ARROW && this.dir === 'rtl'))) {
531+
this._openedBy = 'keyboard';
526532
this.openMenu();
527533
}
528534
}

src/material/menu/menu.spec.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import {FocusMonitor} from '@angular/cdk/a11y';
22
import {Direction, Directionality} from '@angular/cdk/bidi';
3-
import {DOWN_ARROW, END, ESCAPE, HOME, LEFT_ARROW, RIGHT_ARROW, TAB} from '@angular/cdk/keycodes';
3+
import {
4+
DOWN_ARROW,
5+
END,
6+
ENTER,
7+
ESCAPE,
8+
HOME,
9+
LEFT_ARROW,
10+
RIGHT_ARROW,
11+
TAB,
12+
} from '@angular/cdk/keycodes';
413
import {Overlay, OverlayContainer} from '@angular/cdk/overlay';
514
import {ScrollDispatcher} from '@angular/cdk/scrolling';
615
import {
@@ -745,6 +754,27 @@ describe('MatMenu', () => {
745754
expect(items[2].classList).toContain('cdk-keyboard-focused');
746755
}));
747756

757+
it('should set the keyboard focus origin when opened using the keyboard', fakeAsync(() => {
758+
const fixture = createComponent(SimpleMenu, [], [FakeIcon]);
759+
fixture.detectChanges();
760+
const trigger = fixture.componentInstance.triggerEl.nativeElement;
761+
762+
// Note that we dispatch both a `click` and a `keydown` to imitate the browser behavior.
763+
dispatchKeyboardEvent(trigger, 'keydown', ENTER);
764+
trigger.click();
765+
fixture.detectChanges();
766+
767+
const items =
768+
Array.from<HTMLElement>(document.querySelectorAll('.mat-menu-panel [mat-menu-item]'));
769+
770+
items.forEach(item => patchElementFocus(item));
771+
tick(500);
772+
tick();
773+
fixture.detectChanges();
774+
775+
expect(items[0].classList).toContain('cdk-keyboard-focused');
776+
}));
777+
748778
it('should toggle the aria-expanded attribute on the trigger', () => {
749779
const fixture = createComponent(SimpleMenu, [], [FakeIcon]);
750780
fixture.detectChanges();

tools/public_api_guard/material/menu.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ export interface MatMenuPanel<T = any> {
149149
export declare class MatMenuTrigger implements AfterContentInit, OnDestroy {
150150
get _deprecatedMatMenuTriggerFor(): MatMenuPanel;
151151
set _deprecatedMatMenuTriggerFor(v: MatMenuPanel);
152-
_openedBy: 'mouse' | 'touch' | null;
152+
_openedBy: Exclude<FocusOrigin, 'program'>;
153153
get dir(): Direction;
154154
get menu(): MatMenuPanel;
155155
set menu(menu: MatMenuPanel);

0 commit comments

Comments
 (0)