mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(menu): not handling keyboard events when opened by mouse (#4843)
Fixes the menu keyboard interactions not working when it is opened by a click. Fixes #4991.
This commit is contained in:
committed by
Andrew Seguin
parent
a9600e7ce9
commit
d822a3948e
@@ -97,9 +97,9 @@ describe('menu', () => {
|
||||
expectFocusOn(page.items(0));
|
||||
});
|
||||
|
||||
it('should not focus the first item when opened with mouse', () => {
|
||||
it('should focus the panel when opened by mouse', () => {
|
||||
page.trigger().click();
|
||||
expectFocusOn(page.trigger());
|
||||
expectFocusOn(page.menu());
|
||||
});
|
||||
|
||||
it('should focus subsequent items when down arrow is pressed', () => {
|
||||
|
||||
@@ -205,6 +205,14 @@ export class MatMenu implements AfterContentInit, MatMenuPanel, OnDestroy {
|
||||
this._keyManager.setFirstItemActive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the active item in the menu. This is used when the menu is opened by mouse,
|
||||
* allowing the user to start from the first option when pressing the down arrow.
|
||||
*/
|
||||
resetActiveItem() {
|
||||
this._keyManager.setActiveItem(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* It's necessary to set position-based classes to ensure the menu panel animation
|
||||
* folds out from the correct direction.
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface MatMenuPanel {
|
||||
parentMenu?: MatMenuPanel | undefined;
|
||||
direction?: Direction;
|
||||
focusFirstItem: () => void;
|
||||
resetActiveItem: () => void;
|
||||
setPositionClasses: (x: MenuPositionX, y: MenuPositionY) => void;
|
||||
setElevation?(depth: number): void;
|
||||
}
|
||||
|
||||
@@ -225,10 +225,16 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy {
|
||||
this._setMenuElevation();
|
||||
this._setIsMenuOpen(true);
|
||||
|
||||
// Should only set focus if opened via the keyboard, so keyboard users can
|
||||
// can easily navigate menu items. According to spec, mouse users should not
|
||||
// see the focus style.
|
||||
if (!this._openedByMouse) {
|
||||
// If the menu was opened by mouse, we focus the root node, which allows for the keyboard
|
||||
// interactions to work. Otherwise, if the menu was opened by keyboard, we focus the first item.
|
||||
if (this._openedByMouse) {
|
||||
let rootNode = this._overlayRef!.overlayElement.firstElementChild as HTMLElement;
|
||||
|
||||
if (rootNode) {
|
||||
this.menu.resetActiveItem();
|
||||
rootNode.focus();
|
||||
}
|
||||
} else {
|
||||
this.menu.focusFirstItem();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
(click)="close.emit('click')"
|
||||
[@transformMenu]="_panelAnimationState"
|
||||
(@transformMenu.done)="_onAnimationDone($event)"
|
||||
tabindex="-1"
|
||||
role="menu">
|
||||
<div class="mat-menu-content" [@fadeInItems]="'showing'">
|
||||
<ng-content></ng-content>
|
||||
|
||||
@@ -14,6 +14,7 @@ $mat-menu-submenu-indicator-size: 10px !default;
|
||||
@include mat-menu-positions();
|
||||
max-height: calc(100vh - #{$mat-menu-item-height});
|
||||
border-radius: $mat-menu-border-radius;
|
||||
outline: 0;
|
||||
|
||||
// Prevent the user from interacting while the panel is still animating.
|
||||
// This avoids issues where the user could accidentally open a sub-menu,
|
||||
|
||||
+19
-14
@@ -33,6 +33,7 @@ import {
|
||||
dispatchEvent,
|
||||
createKeyboardEvent,
|
||||
createMouseEvent,
|
||||
dispatchFakeEvent,
|
||||
} from '@angular/cdk/testing';
|
||||
|
||||
|
||||
@@ -191,6 +192,23 @@ describe('MatMenu', () => {
|
||||
expect(fixture.componentInstance.items.last.getLabel()).toBe('Item with an icon');
|
||||
});
|
||||
|
||||
it('should focus the menu panel root node when it was opened by mouse', () => {
|
||||
const fixture = TestBed.createComponent(SimpleMenu);
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
const triggerEl = fixture.componentInstance.triggerEl.nativeElement;
|
||||
|
||||
dispatchFakeEvent(triggerEl, 'mousedown');
|
||||
triggerEl.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
const panel = overlayContainerElement.querySelector('.mat-menu-panel');
|
||||
|
||||
expect(panel).toBeTruthy('Expected the panel to be rendered.');
|
||||
expect(document.activeElement).toBe(panel, 'Expected the panel to be focused.');
|
||||
});
|
||||
|
||||
describe('positions', () => {
|
||||
let fixture: ComponentFixture<PositionedMenu>;
|
||||
let panel: HTMLElement;
|
||||
@@ -811,20 +829,6 @@ describe('MatMenu', () => {
|
||||
.toBe(true, 'Expected focus to be back inside the root menu');
|
||||
});
|
||||
|
||||
it('should not shift focus to the sub-menu when it was opened by hover', () => {
|
||||
compileTestComponent();
|
||||
instance.rootTriggerEl.nativeElement.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
const levelOneTrigger = overlay.querySelector('#level-one-trigger')! as HTMLElement;
|
||||
|
||||
dispatchMouseEvent(levelOneTrigger, 'mouseenter');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(overlay.querySelectorAll('.mat-menu-panel')[1].contains(document.activeElement))
|
||||
.toBe(false, 'Expected focus to not be inside the nested menu');
|
||||
});
|
||||
|
||||
it('should position the sub-menu to the right edge of the trigger in ltr', () => {
|
||||
compileTestComponent();
|
||||
instance.rootTriggerEl.nativeElement.style.position = 'fixed';
|
||||
@@ -1179,6 +1183,7 @@ class CustomMenuPanel implements MatMenuPanel {
|
||||
@ViewChild(TemplateRef) templateRef: TemplateRef<any>;
|
||||
@Output() close = new EventEmitter<void | 'click' | 'keydown'>();
|
||||
focusFirstItem = () => {};
|
||||
resetActiveItem = () => {};
|
||||
setPositionClasses = () => {};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user