mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(menu): improve a11y for screenreaders (#1715)
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<p>You clicked on: {{ selected }}</p>
|
||||
|
||||
<md-toolbar>
|
||||
<button md-icon-button [md-menu-trigger-for]="menu">
|
||||
<button md-icon-button [md-menu-trigger-for]="menu" aria-label="Open basic menu">
|
||||
<md-icon>more_vert</md-icon>
|
||||
</button>
|
||||
</md-toolbar>
|
||||
@@ -17,7 +17,7 @@
|
||||
<div class="menu-section">
|
||||
<p> Clicking these will navigate:</p>
|
||||
<md-toolbar>
|
||||
<button md-icon-button [md-menu-trigger-for]="anchorMenu">
|
||||
<button md-icon-button [md-menu-trigger-for]="anchorMenu" aria-label="Open anchor menu">
|
||||
<md-icon>more_vert</md-icon>
|
||||
</button>
|
||||
</md-toolbar>
|
||||
@@ -33,7 +33,7 @@
|
||||
Position x: before
|
||||
</p>
|
||||
<md-toolbar class="end-icon">
|
||||
<button md-icon-button [md-menu-trigger-for]="posXMenu">
|
||||
<button md-icon-button [md-menu-trigger-for]="posXMenu" aria-label="Open x-positioned menu">
|
||||
<md-icon>more_vert</md-icon>
|
||||
</button>
|
||||
</md-toolbar>
|
||||
@@ -50,7 +50,7 @@
|
||||
Position y: above
|
||||
</p>
|
||||
<md-toolbar>
|
||||
<button md-icon-button [md-menu-trigger-for]="posYMenu">
|
||||
<button md-icon-button [md-menu-trigger-for]="posYMenu" aria-label="Open y-positioned menu">
|
||||
<md-icon>more_vert</md-icon>
|
||||
</button>
|
||||
</md-toolbar>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
/**
|
||||
* Screenreaders will often fire fake mousedown events when a focusable element
|
||||
* is activated using the keyboard. We can typically distinguish between these faked
|
||||
* mousedown events and real mousedown events using the "buttons" property. While
|
||||
* real mousedowns will indicate the mouse button that was pressed (e.g. "1" for
|
||||
* the left mouse button), faked mousedowns will usually set the property value to 0.
|
||||
*/
|
||||
export function isFakeMousedownFromScreenReader(event: MouseEvent): boolean {
|
||||
return event.buttons === 0;
|
||||
}
|
||||
@@ -54,6 +54,7 @@ export {
|
||||
|
||||
export {FocusTrap} from './a11y/focus-trap';
|
||||
export {InteractivityChecker} from './a11y/interactivity-checker';
|
||||
export {isFakeMousedownFromScreenReader} from './a11y/fake-mousedown';
|
||||
|
||||
export {A11yModule} from './a11y/index';
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {MdFocusable} from '../core/a11y/list-key-manager';
|
||||
host: {
|
||||
'role': 'menuitem',
|
||||
'(click)': '_checkDisabled($event)',
|
||||
'tabindex': '-1'
|
||||
'[attr.tabindex]': '_tabindex'
|
||||
},
|
||||
templateUrl: 'menu-item.html',
|
||||
exportAs: 'mdMenuItem'
|
||||
@@ -41,6 +41,10 @@ export class MdMenuItem implements MdFocusable {
|
||||
return String(!!this.disabled);
|
||||
}
|
||||
|
||||
get _tabindex() {
|
||||
return this.disabled ? '-1' : '0';
|
||||
}
|
||||
|
||||
|
||||
_getHostElement(): HTMLElement {
|
||||
return this._elementRef.nativeElement;
|
||||
|
||||
@@ -12,8 +12,7 @@ import {
|
||||
import {MdMenuPanel} from './menu-panel';
|
||||
import {MdMenuMissingError} from './menu-errors';
|
||||
import {
|
||||
ENTER,
|
||||
SPACE,
|
||||
isFakeMousedownFromScreenReader,
|
||||
Overlay,
|
||||
OverlayState,
|
||||
OverlayRef,
|
||||
@@ -32,8 +31,8 @@ import { Subscription } from 'rxjs/Subscription';
|
||||
selector: '[md-menu-trigger-for]',
|
||||
host: {
|
||||
'aria-haspopup': 'true',
|
||||
'(keydown)': '_handleKeydown($event)',
|
||||
'(click)': 'toggleMenu()'
|
||||
'(mousedown)': '_handleMousedown($event)',
|
||||
'(click)': 'toggleMenu()',
|
||||
},
|
||||
exportAs: 'mdMenuTrigger'
|
||||
})
|
||||
@@ -45,7 +44,7 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy {
|
||||
|
||||
// tracking input type is necessary so it's possible to only auto-focus
|
||||
// the first item of the list when the menu is opened via the keyboard
|
||||
private _openedFromKeyboard: boolean = false;
|
||||
private _openedByMouse: boolean = false;
|
||||
|
||||
@Input('md-menu-trigger-for') menu: MdMenuPanel;
|
||||
@Output() onMenuOpen = new EventEmitter<void>();
|
||||
@@ -118,7 +117,10 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy {
|
||||
private _initMenu(): void {
|
||||
this._setIsMenuOpen(true);
|
||||
|
||||
if (this._openedFromKeyboard) {
|
||||
// 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) {
|
||||
this.menu.focusFirstItem();
|
||||
}
|
||||
};
|
||||
@@ -130,10 +132,12 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy {
|
||||
private _resetMenu(): void {
|
||||
this._setIsMenuOpen(false);
|
||||
|
||||
if (this._openedFromKeyboard) {
|
||||
// Focus only needs to be reset to the host element if the menu was opened
|
||||
// by the keyboard and manually shifted to the first menu item.
|
||||
if (!this._openedByMouse) {
|
||||
this.focus();
|
||||
this._openedFromKeyboard = false;
|
||||
}
|
||||
this._openedByMouse = false;
|
||||
}
|
||||
|
||||
// set state rather than toggle to support triggers sharing a menu
|
||||
@@ -191,10 +195,9 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: internal
|
||||
_handleKeydown(event: KeyboardEvent): void {
|
||||
if (event.keyCode === ENTER || event.keyCode === SPACE) {
|
||||
this._openedFromKeyboard = true;
|
||||
_handleMousedown(event: MouseEvent): void {
|
||||
if (!isFakeMousedownFromScreenReader(event)) {
|
||||
this._openedByMouse = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ class CustomMenuPanel implements MdMenuPanel {
|
||||
positionY: MenuPositionY = 'below';
|
||||
@ViewChild(TemplateRef) templateRef: TemplateRef<any>;
|
||||
@Output() close = new EventEmitter<void>();
|
||||
focusFirstItem: () => void;
|
||||
focusFirstItem = () => {};
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
||||
Reference in New Issue
Block a user