Skip to content
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

Merged
merged 8 commits into from
Jul 31, 2017

Conversation

afercia
Copy link
Contributor

@afercia afercia commented Jul 21, 2017

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:

screen shot 2017-07-21 at 19 17 36

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

@afercia afercia added the [Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). label Jul 21, 2017
@afercia afercia requested a review from aduth July 21, 2017 18:08
@afercia
Copy link
Contributor Author

afercia commented Jul 21, 2017

Fixes #1880

}
return -1;
}
}

focusIndex( index ) {
if ( this.menuRef ) {
const menu = findDOMNode( this.menuRef );
if ( this.menuNode ) {
if ( index < 0 ) {
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK


this.maybeCloseMenuOnBlur = setTimeout( () => {
this.closeMenu();
}, 100 );
Copy link
Member

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 ?

Copy link
Contributor Author

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...

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();
Copy link
Member

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

Copy link
Contributor Author

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.

ref={ this.bindMenuReferenceNode }
onFocus={ this.handleFocus }
onBlur={ this.handleBlur }
tabIndex="-1"
Copy link
Member

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?

@afercia
Copy link
Contributor Author

afercia commented Jul 27, 2017

Using keyup to close the menu when pressing Escape allows to greatly simplify. No timers, no focus/blur. To recap:

  • menu items can be navigated with arrows only
  • tabbing will close the menu and natively focus the next/previous focusable elements (note: there's a pre-existing and unrelated bug when using Firefox, will open a separate issue)
  • pressing Escape closes the menu and focuses the toggle
  • one more Escape press makes the whole toolbar disappear, as expected

if ( event.keyCode === ESCAPE && this.state.open ) {
event.preventDefault();
event.stopPropagation();
const node = findDOMNode( this );
Copy link
Member

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.

event.preventDefault();
event.stopPropagation();
const node = findDOMNode( this );
const toggle = node.querySelector( '.components-dropdown-menu__toggle' );
Copy link
Member

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.

@aduth
Copy link
Member

aduth commented Jul 28, 2017

Would be good to have some unit tests for this component, but that can be addressed separately I think.

@afercia afercia force-pushed the update/dropdown-menu-keyboard-interaction branch from ec8272a to ed9cd44 Compare July 28, 2017 14:36
@codecov
Copy link

codecov bot commented Jul 28, 2017

Codecov Report

Merging #1975 into master will increase coverage by 0.24%.
The diff coverage is 11.53%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
components/dropdown-menu/index.js 0% <0%> (ø) ⬆️
components/icon-button/index.js 100% <100%> (ø) ⬆️
utils/nodetypes.js 100% <0%> (ø)
blocks/api/paste/remove-spans.js 0% <0%> (ø)
blocks/api/paste/strip-attributes.js 0% <0%> (ø)
blocks/api/paste/convert-tables.js 0% <0%> (ø)
blocks/api/paste.js 62.71% <0%> (+2.18%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c9a619d...f74d28a. Read the comment docs.

@aduth
Copy link
Member

aduth commented Jul 28, 2017

In e91f3a2, I revised IconButton to be a stateful component class, so that we can apply a ref to target it directly shifting focus to the toggle when the menu closes. Let me know if this looks good to you, otherwise I think this is ready to merge.

@afercia afercia merged commit 581b585 into master Jul 31, 2017
@afercia afercia deleted the update/dropdown-menu-keyboard-interaction branch July 31, 2017 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes).
Projects
None yet
Development

Successfully merging this pull request may close these issues.

DropdownMenu a11y improvements and proposal to reuse it for other UI controls
2 participants