Skip to content

Commit 995ed98

Browse files
crisbetoannieyw
authored andcommitted
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. (cherry picked from commit 3484f71)
1 parent 4063dc4 commit 995ed98

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 {
@@ -726,6 +735,27 @@ describe('MDC-based MatMenu', () => {
726735
expect(items[2].classList).toContain('cdk-keyboard-focused');
727736
}));
728737

738+
it('should set the keyboard focus origin when opened using the keyboard', fakeAsync(() => {
739+
const fixture = createComponent(SimpleMenu, [], [FakeIcon]);
740+
fixture.detectChanges();
741+
const trigger = fixture.componentInstance.triggerEl.nativeElement;
742+
743+
// Note that we dispatch both a `click` and a `keydown` to imitate the browser behavior.
744+
dispatchKeyboardEvent(trigger, 'keydown', ENTER);
745+
trigger.click();
746+
fixture.detectChanges();
747+
748+
const items =
749+
Array.from<HTMLElement>(document.querySelectorAll('.mat-mdc-menu-panel [mat-menu-item]'));
750+
751+
items.forEach(item => patchElementFocus(item));
752+
tick(500);
753+
tick();
754+
fixture.detectChanges();
755+
756+
expect(items[0].classList).toContain('cdk-keyboard-focused');
757+
}));
758+
729759
it('should toggle the aria-expanded attribute on the trigger', () => {
730760
const fixture = createComponent(SimpleMenu, [], [FakeIcon]);
731761
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 {
@@ -724,6 +733,27 @@ describe('MatMenu', () => {
724733
expect(items[2].classList).toContain('cdk-keyboard-focused');
725734
}));
726735

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