fix(menu): improve a11y for screenreaders (#1715)

This commit is contained in:
Kara
2016-11-08 13:41:27 -08:00
committed by Jeremy Elbourn
parent 15cd28bca3
commit 267e3238b8
6 changed files with 37 additions and 18 deletions
+4 -4
View File
@@ -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>
+11
View File
@@ -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;
}
+1
View File
@@ -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';
+5 -1
View File
@@ -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;
+15 -12
View File
@@ -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;
}
}
+1 -1
View File
@@ -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({