Skip to content

Commit 6c727fe

Browse files
committed
fix(menu): not handling keyboard events when opened by mouse
Fixes the menu keyboard interactions not working when it is opened by a click. Fixes #4991.
1 parent f73cc97 commit 6c727fe

File tree

7 files changed

+42
-8
lines changed

7 files changed

+42
-8
lines changed

e2e/components/menu-e2e.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,9 @@ describe('menu', () => {
9090
expectFocusOn(page.items(0));
9191
});
9292

93-
it('should not focus the first item when opened with mouse', () => {
93+
it('should focus the panel when opened by mouse', () => {
9494
page.trigger().click();
95-
expectFocusOn(page.trigger());
95+
expectFocusOn(page.menu());
9696
});
9797

9898
it('should focus subsequent items when down arrow is pressed', () => {

src/lib/menu/menu-directive.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,14 @@ export class MdMenu implements AfterContentInit, MdMenuPanel, OnDestroy {
137137
this._keyManager.setFirstItemActive();
138138
}
139139

140+
/**
141+
* Resets the active item in the menu. This is used when the menu is opened by mouse,
142+
* allowing the user to start from the first option when pressing the down arrow.
143+
*/
144+
resetActiveItem() {
145+
this._keyManager.setActiveItem(-1);
146+
}
147+
140148
/**
141149
* This emits a close event to which the trigger is subscribed. When emitted, the
142150
* trigger will close the menu.

src/lib/menu/menu-panel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface MdMenuPanel {
1616
templateRef: TemplateRef<any>;
1717
close: EventEmitter<void>;
1818
focusFirstItem: () => void;
19+
resetActiveItem: () => void;
1920
setPositionClasses: (x: MenuPositionX, y: MenuPositionY) => void;
2021
_emitCloseEvent: () => void;
2122
}

src/lib/menu/menu-trigger.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,16 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy {
163163
private _initMenu(): void {
164164
this._setIsMenuOpen(true);
165165

166-
// Should only set focus if opened via the keyboard, so keyboard users can
167-
// can easily navigate menu items. According to spec, mouse users should not
168-
// see the focus style.
169-
if (!this._openedByMouse) {
166+
// If the menu was opened by mouse, we focus the root node, which allows for the keyboard
167+
// interactions to work. Otherwise, if the menu was opened by keyboard, we focus the first item.
168+
if (this._openedByMouse) {
169+
let rootNode = this._overlayRef!.overlayElement.firstElementChild as HTMLElement;
170+
171+
if (rootNode) {
172+
this.menu.resetActiveItem();
173+
rootNode.focus();
174+
}
175+
} else {
170176
this.menu.focusFirstItem();
171177
}
172178
}

src/lib/menu/menu.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<ng-template>
22
<div class="mat-menu-panel" [ngClass]="_classList" (keydown)="_handleKeydown($event)"
3-
(click)="_emitCloseEvent()" [@transformMenu]="'showing'" role="menu">
3+
(click)="_emitCloseEvent()" [@transformMenu]="'showing'" role="menu" tabindex="-1" #panel>
44
<div class="mat-menu-content" [@fadeInItems]="'showing'">
55
<ng-content></ng-content>
66
</div>

src/lib/menu/menu.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ $mat-menu-vertical-padding: 8px !default;
1212
@include mat-menu-base();
1313
@include mat-menu-positions();
1414
max-height: calc(100vh - #{$mat-menu-item-height});
15+
outline: 0;
1516

1617
@include cdk-high-contrast {
1718
outline: solid 1px;

src/lib/menu/menu.spec.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {OverlayContainer} from '../core/overlay/overlay-container';
2121
import {Directionality, Direction} from '../core/bidi/index';
2222
import {extendObject} from '../core/util/object-extend';
2323
import {ESCAPE} from '../core/keyboard/keycodes';
24-
import {dispatchKeyboardEvent} from '../core/testing/dispatch-events';
24+
import {dispatchKeyboardEvent, dispatchFakeEvent} from '../core/testing/dispatch-events';
2525

2626

2727
describe('MdMenu', () => {
@@ -146,6 +146,23 @@ describe('MdMenu', () => {
146146
expect(role).toBe('menu', 'Expected panel to have the "menu" role.');
147147
});
148148

149+
it('should focus the menu panel root node when it was opened by mouse', () => {
150+
const fixture = TestBed.createComponent(SimpleMenu);
151+
152+
fixture.detectChanges();
153+
154+
const triggerEl = fixture.componentInstance.triggerEl.nativeElement;
155+
156+
dispatchFakeEvent(triggerEl, 'mousedown');
157+
triggerEl.click();
158+
fixture.detectChanges();
159+
160+
const panel = overlayContainerElement.querySelector('.mat-menu-panel');
161+
162+
expect(panel).toBeTruthy('Expected the panel to be rendered.');
163+
expect(document.activeElement).toBe(panel, 'Expected the panel to be focused.');
164+
});
165+
149166
describe('positions', () => {
150167
let fixture: ComponentFixture<PositionedMenu>;
151168
let panel: HTMLElement;
@@ -553,6 +570,7 @@ class CustomMenuPanel implements MdMenuPanel {
553570
@ViewChild(TemplateRef) templateRef: TemplateRef<any>;
554571
@Output() close = new EventEmitter<void>();
555572
focusFirstItem = () => {};
573+
resetActiveItem = () => {};
556574
setPositionClasses = () => {};
557575
_emitCloseEvent() {
558576
this.close.emit();

0 commit comments

Comments
 (0)