Skip to content

Commit 267e323

Browse files
karajelbourn
authored andcommitted
fix(menu): improve a11y for screenreaders (#1715)
1 parent 15cd28b commit 267e323

File tree

6 files changed

+37
-18
lines changed

6 files changed

+37
-18
lines changed

src/demo-app/menu/menu-demo.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<p>You clicked on: {{ selected }}</p>
44

55
<md-toolbar>
6-
<button md-icon-button [md-menu-trigger-for]="menu">
6+
<button md-icon-button [md-menu-trigger-for]="menu" aria-label="Open basic menu">
77
<md-icon>more_vert</md-icon>
88
</button>
99
</md-toolbar>
@@ -17,7 +17,7 @@
1717
<div class="menu-section">
1818
<p> Clicking these will navigate:</p>
1919
<md-toolbar>
20-
<button md-icon-button [md-menu-trigger-for]="anchorMenu">
20+
<button md-icon-button [md-menu-trigger-for]="anchorMenu" aria-label="Open anchor menu">
2121
<md-icon>more_vert</md-icon>
2222
</button>
2323
</md-toolbar>
@@ -33,7 +33,7 @@
3333
Position x: before
3434
</p>
3535
<md-toolbar class="end-icon">
36-
<button md-icon-button [md-menu-trigger-for]="posXMenu">
36+
<button md-icon-button [md-menu-trigger-for]="posXMenu" aria-label="Open x-positioned menu">
3737
<md-icon>more_vert</md-icon>
3838
</button>
3939
</md-toolbar>
@@ -50,7 +50,7 @@
5050
Position y: above
5151
</p>
5252
<md-toolbar>
53-
<button md-icon-button [md-menu-trigger-for]="posYMenu">
53+
<button md-icon-button [md-menu-trigger-for]="posYMenu" aria-label="Open y-positioned menu">
5454
<md-icon>more_vert</md-icon>
5555
</button>
5656
</md-toolbar>

src/lib/core/a11y/fake-mousedown.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
/**
3+
* Screenreaders will often fire fake mousedown events when a focusable element
4+
* is activated using the keyboard. We can typically distinguish between these faked
5+
* mousedown events and real mousedown events using the "buttons" property. While
6+
* real mousedowns will indicate the mouse button that was pressed (e.g. "1" for
7+
* the left mouse button), faked mousedowns will usually set the property value to 0.
8+
*/
9+
export function isFakeMousedownFromScreenReader(event: MouseEvent): boolean {
10+
return event.buttons === 0;
11+
}

src/lib/core/core.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export {
5454

5555
export {FocusTrap} from './a11y/focus-trap';
5656
export {InteractivityChecker} from './a11y/interactivity-checker';
57+
export {isFakeMousedownFromScreenReader} from './a11y/fake-mousedown';
5758

5859
export {A11yModule} from './a11y/index';
5960

src/lib/menu/menu-item.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {MdFocusable} from '../core/a11y/list-key-manager';
1111
host: {
1212
'role': 'menuitem',
1313
'(click)': '_checkDisabled($event)',
14-
'tabindex': '-1'
14+
'[attr.tabindex]': '_tabindex'
1515
},
1616
templateUrl: 'menu-item.html',
1717
exportAs: 'mdMenuItem'
@@ -41,6 +41,10 @@ export class MdMenuItem implements MdFocusable {
4141
return String(!!this.disabled);
4242
}
4343

44+
get _tabindex() {
45+
return this.disabled ? '-1' : '0';
46+
}
47+
4448

4549
_getHostElement(): HTMLElement {
4650
return this._elementRef.nativeElement;

src/lib/menu/menu-trigger.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ import {
1212
import {MdMenuPanel} from './menu-panel';
1313
import {MdMenuMissingError} from './menu-errors';
1414
import {
15-
ENTER,
16-
SPACE,
15+
isFakeMousedownFromScreenReader,
1716
Overlay,
1817
OverlayState,
1918
OverlayRef,
@@ -32,8 +31,8 @@ import { Subscription } from 'rxjs/Subscription';
3231
selector: '[md-menu-trigger-for]',
3332
host: {
3433
'aria-haspopup': 'true',
35-
'(keydown)': '_handleKeydown($event)',
36-
'(click)': 'toggleMenu()'
34+
'(mousedown)': '_handleMousedown($event)',
35+
'(click)': 'toggleMenu()',
3736
},
3837
exportAs: 'mdMenuTrigger'
3938
})
@@ -45,7 +44,7 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy {
4544

4645
// tracking input type is necessary so it's possible to only auto-focus
4746
// the first item of the list when the menu is opened via the keyboard
48-
private _openedFromKeyboard: boolean = false;
47+
private _openedByMouse: boolean = false;
4948

5049
@Input('md-menu-trigger-for') menu: MdMenuPanel;
5150
@Output() onMenuOpen = new EventEmitter<void>();
@@ -118,7 +117,10 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy {
118117
private _initMenu(): void {
119118
this._setIsMenuOpen(true);
120119

121-
if (this._openedFromKeyboard) {
120+
// Should only set focus if opened via the keyboard, so keyboard users can
121+
// can easily navigate menu items. According to spec, mouse users should not
122+
// see the focus style.
123+
if (!this._openedByMouse) {
122124
this.menu.focusFirstItem();
123125
}
124126
};
@@ -130,10 +132,12 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy {
130132
private _resetMenu(): void {
131133
this._setIsMenuOpen(false);
132134

133-
if (this._openedFromKeyboard) {
135+
// Focus only needs to be reset to the host element if the menu was opened
136+
// by the keyboard and manually shifted to the first menu item.
137+
if (!this._openedByMouse) {
134138
this.focus();
135-
this._openedFromKeyboard = false;
136139
}
140+
this._openedByMouse = false;
137141
}
138142

139143
// set state rather than toggle to support triggers sharing a menu
@@ -191,10 +195,9 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy {
191195
);
192196
}
193197

194-
// TODO: internal
195-
_handleKeydown(event: KeyboardEvent): void {
196-
if (event.keyCode === ENTER || event.keyCode === SPACE) {
197-
this._openedFromKeyboard = true;
198+
_handleMousedown(event: MouseEvent): void {
199+
if (!isFakeMousedownFromScreenReader(event)) {
200+
this._openedByMouse = true;
198201
}
199202
}
200203

src/lib/menu/menu.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ class CustomMenuPanel implements MdMenuPanel {
181181
positionY: MenuPositionY = 'below';
182182
@ViewChild(TemplateRef) templateRef: TemplateRef<any>;
183183
@Output() close = new EventEmitter<void>();
184-
focusFirstItem: () => void;
184+
focusFirstItem = () => {};
185185
}
186186

187187
@Component({

0 commit comments

Comments
 (0)