-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve keyboard interaction of the DropdownMenu #1975
Conversation
Fixes #1880 |
components/dropdown-menu/index.js
Outdated
} | ||
return -1; | ||
} | ||
} | ||
|
||
focusIndex( index ) { | ||
if ( this.menuRef ) { | ||
const menu = findDOMNode( this.menuRef ); | ||
if ( this.menuNode ) { | ||
if ( index < 0 ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With changes to focusPrevious
, is it ever the case that this condition would pass? Also generally concerned about previousElementSibling
potentially not being a focusable element. Might be worth dropping this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
components/dropdown-menu/index.js
Outdated
|
||
this.maybeCloseMenuOnBlur = setTimeout( () => { | ||
this.closeMenu(); | ||
}, 100 ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How did you settle on 100
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Completely arbitrary (I know...). There's a brief interval between blur on an element and focus on the following element. '0' didn't work. On the W3C examples, they use '300', see:
https://www.w3.org/TR/wai-aria-practices/examples/menu-button/js/Menubutton.js
https://www.w3.org/TR/wai-aria-practices/examples/menu-button/js/MenuItemAction.js
https://www.w3.org/TR/wai-aria-practices/examples/menu-button/js/PopupMenuAction.js
Open to suggestions...
components/dropdown-menu/index.js
Outdated
handleKeyDown( keydown ) { | ||
if ( this.state.open ) { | ||
switch ( keydown.keyCode ) { | ||
case ESCAPE: | ||
keydown.preventDefault(); | ||
keydown.stopPropagation(); | ||
this.closeMenu(); | ||
const node = findDOMNode( this ); | ||
const toggle = node.querySelector( '.components-dropdown-menu__toggle' ); | ||
toggle.focus(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm finding that occasionally pressing escape when navigating buttons will clear focus altogether (document.activeElement === document.body
). Not sure if that's specific to your changes here though.
macOS Chrome 59.0.3071.115
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same chrome here and can't reproduce. Updated to 60: same.
Instead, I think handleKeyUp()
doesn't work as intended: when I keep Escape pressed for a while, the menu closes immediately so when I release Escape the condition to stop propagation isn't met and the toolbar disappears.
components/dropdown-menu/index.js
Outdated
ref={ this.bindMenuReferenceNode } | ||
onFocus={ this.handleFocus } | ||
onBlur={ this.handleBlur } | ||
tabIndex="-1" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is tabindex
necessary on div
?
Using keyup to close the menu when pressing Escape allows to greatly simplify. No timers, no focus/blur. To recap:
|
components/dropdown-menu/index.js
Outdated
if ( event.keyCode === ESCAPE && this.state.open ) { | ||
event.preventDefault(); | ||
event.stopPropagation(); | ||
const node = findDOMNode( this ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could probably create a separate ref
for the root rendered element to avoid the findDOMNode
here.
components/dropdown-menu/index.js
Outdated
event.preventDefault(); | ||
event.stopPropagation(); | ||
const node = findDOMNode( this ); | ||
const toggle = node.querySelector( '.components-dropdown-menu__toggle' ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, for that matter, we could bind the ref
to the toggle to avoid the querySelector
as well.
Would be good to have some unit tests for this component, but that can be addressed separately I think. |
ec8272a
to
ed9cd44
Compare
Codecov Report
@@ Coverage Diff @@
## master #1975 +/- ##
==========================================
+ Coverage 20.25% 20.49% +0.24%
==========================================
Files 131 135 +4
Lines 4207 4240 +33
Branches 716 728 +12
==========================================
+ Hits 852 869 +17
- Misses 2827 2839 +12
- Partials 528 532 +4
Continue to review full report at Codecov.
|
In e91f3a2, I revised |
When used on a component class, the value of a ref is the component instance, not the DOM node. https://facebook.github.io/react/docs/refs-and-the-dom.html#adding-a-ref-to-a-class-component
This PR tries to improve the accessibility and keyboard interaction of the DropdownMenu following the model described in the ARIA Authoring Practices.
The DropdownMenu is, in ARIA terms, a composite widget made of a Menu button and a Menu. To get how the expected keyboard interaction should work I'd recommend to jump directly to the W3C examples for the standalone Menu:
https://www.w3.org/TR/wai-aria-practices/examples/menu-button/menu-button-actions.html
and the same example in the context of a Toolbar:
https://www.w3.org/TR/wai-aria-practices/examples/toolbar/toolbar.html
Navigation through the menu items should work with arrow keys only, while tabbing should close the menu (same for Escape). Optionally, items navigation should "loop" continuously.
Worth reminding ARIA widgets replicate common, established patterns already used in our operating systems and keyboard users are used to them. For example, the macOS Finder menu works this way: it uses arrow keys to navigate the drop-down items:
The mixed use of the Tab key and arrow keys is a great way to reduce the amount of Tab key presses necessary to navigate through content, as it allows to open a menu just if that's really needed.
As mentioned in the issue, it would be great to consider to extend this behavior to other components too.
Fixes #1880