Skip to content

Commit

Permalink
Accessibility: Keyboard shortcuts to navigate to/from/in the block's …
Browse files Browse the repository at this point in the history
…toolbar (#2960)
  • Loading branch information
youknowriad authored Oct 13, 2017
1 parent ced2161 commit d8b04b3
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 8 deletions.
8 changes: 8 additions & 0 deletions components/navigable-menu/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,11 @@ A callback invoked when the menu navigates to one of its children passing the in

- Type: `Function`
- Required: No

## deep

A boolean to look for navigable children in the direct children or any descendant.

- Type: `Boolean`
- Required: No
- default: false
6 changes: 3 additions & 3 deletions components/navigable-menu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,18 @@ class NavigableMenu extends Component {
}

onKeyDown( event ) {
const { orientation = 'vertical', onNavigate = noop } = this.props;
const { orientation = 'vertical', onNavigate = noop, deep = false } = this.props;
if (
( orientation === 'vertical' && [ UP, DOWN, TAB ].indexOf( event.keyCode ) === -1 ) ||
( orientation === 'horizontal' && [ RIGHT, LEFT, TAB ].indexOf( event.keyCode ) === -1 )
) {
return;
}

const tabbables = focus.tabbable
.find( this.container )
.filter( ( node ) => node.parentElement === this.container );
.filter( ( node ) => deep || node.parentElement === this.container );
const indexOfTabbable = tabbables.indexOf( document.activeElement );

if ( indexOfTabbable === -1 ) {
return;
}
Expand Down
81 changes: 76 additions & 5 deletions editor/block-toolbar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import classnames from 'classnames';
/**
* WordPress Dependencies
*/
import { IconButton, Toolbar } from '@wordpress/components';
import { Component, Children } from '@wordpress/element';
import { IconButton, Toolbar, NavigableMenu } from '@wordpress/components';
import { Component, Children, findDOMNode } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { focus, keycodes } from '@wordpress/utils';

/**
* Internal Dependencies
Expand All @@ -19,19 +20,51 @@ import './style.scss';
import BlockSwitcher from '../block-switcher';
import BlockMover from '../block-mover';
import BlockRightMenu from '../block-settings-menu';
import { isMac } from '../utils/dom';

/**
* Module Constants
*/
const { ESCAPE, F10 } = keycodes;

function FirstChild( { children } ) {
const childrenArray = Children.toArray( children );
return childrenArray[ 0 ] || null;
}

function metaKeyPressed( event ) {
return isMac() ? event.metaKey : ( event.ctrlKey && ! event.altKey );
}

class BlockToolbar extends Component {
constructor() {
super( ...arguments );
this.toggleMobileControls = this.toggleMobileControls.bind( this );
this.bindNode = this.bindNode.bind( this );
this.onKeyUp = this.onKeyUp.bind( this );
this.onKeyDown = this.onKeyDown.bind( this );
this.onToolbarKeyDown = this.onToolbarKeyDown.bind( this );
this.state = {
showMobileControls: false,
};

// it's not easy to know if the user only clicked on a "meta" key without simultaneously clicking on another key
// We keep track of the key counts to ensure it's reliable
this.metaCount = 0;
}

componentDidMount() {
document.addEventListener( 'keyup', this.onKeyUp );
document.addEventListener( 'keydown', this.onKeyDown );
}

componentWillUnmount() {
document.removeEventListener( 'keyup', this.onKeyUp );
document.removeEventListener( 'keydown', this.onKeyDown );
}

bindNode( ref ) {
this.toolbar = findDOMNode( ref );
}

toggleMobileControls() {
Expand All @@ -40,6 +73,38 @@ class BlockToolbar extends Component {
} ) );
}

onKeyDown( event ) {
if ( metaKeyPressed( event ) ) {
this.metaCount++;
}
}

onKeyUp( event ) {
const shouldFocusToolbar = this.metaCount === 1 || ( event.keyCode === F10 && event.altKey );
this.metaCount = 0;

if ( shouldFocusToolbar ) {
const tabbables = focus.tabbable.find( this.toolbar );
if ( tabbables.length ) {
tabbables[ 0 ].focus();
}
}
}

onToolbarKeyDown( event ) {
if ( event.keyCode !== ESCAPE ) {
return;
}

// Is there a better way to focus the selected block
// TODO: separate focused/selected block state and use Redux actions instead
const selectedBlock = document.querySelector( '.editor-visual-editor__block.is-selected .editor-visual-editor__block-edit' );
if ( !! selectedBlock ) {
event.stopPropagation();
selectedBlock.focus();
}
}

render() {
const { showMobileControls } = this.state;
const { uid } = this.props;
Expand All @@ -57,8 +122,14 @@ class BlockToolbar extends Component {
transitionLeave={ false }
component={ FirstChild }
>
<div className={ toolbarClassname }>
<div className="editor-block-toolbar__group">
<NavigableMenu
className={ toolbarClassname }
ref={ this.bindNode }
orientation="horizontal"
role="toolbar"
deep
>
<div className="editor-block-toolbar__group" onKeyDown={ this.onToolbarKeyDown }>
{ ! showMobileControls && [
<BlockSwitcher key="switcher" uid={ uid } />,
<Slot key="slot" name="Formatting.Toolbar" />,
Expand All @@ -80,7 +151,7 @@ class BlockToolbar extends Component {
}
</Toolbar>
</div>
</div>
</NavigableMenu>
</CSSTransitionGroup>
);
}
Expand Down
9 changes: 9 additions & 0 deletions editor/utils/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,12 @@ export function placeCaretAtEdge( container, start = false ) {
sel.addRange( range );
container.focus();
}

/**
* Checks whether the user is on MacOS or not
*
* @return {Boolean} Is Mac or Not
*/
export function isMac() {
return window.navigator.platform.toLowerCase().indexOf( 'mac' ) !== -1;
}
2 changes: 2 additions & 0 deletions utils/keycodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ export const UP = 38;
export const RIGHT = 39;
export const DOWN = 40;
export const DELETE = 46;

export const F10 = 121;

0 comments on commit d8b04b3

Please sign in to comment.