From 17a8573e26b32bb9335b6d8582150fc81b945d19 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Fri, 20 Oct 2017 12:41:10 +0100 Subject: [PATCH 1/8] Accessibility: Adding Keyboard Shorcuts to navigate the editor regions --- editor/header/index.js | 1 + editor/layout/index.js | 93 ++++++++++++++++++++++------- editor/layout/style.scss | 11 ++++ editor/modes/text-editor/index.js | 1 + editor/modes/visual-editor/index.js | 1 + editor/sidebar/index.js | 7 ++- 6 files changed, 91 insertions(+), 23 deletions(-) diff --git a/editor/header/index.js b/editor/header/index.js index 5d48cab2311e1f..ae9c17332a985b 100644 --- a/editor/header/index.js +++ b/editor/header/index.js @@ -35,6 +35,7 @@ function Header( { role="region" aria-label={ __( 'Editor toolbar' ) } className="editor-header" + tabIndex="-1" >
diff --git a/editor/layout/index.js b/editor/layout/index.js index 3f6eb13ded9881..d13287f0d962e4 100644 --- a/editor/layout/index.js +++ b/editor/layout/index.js @@ -7,7 +7,8 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { NoticeList, Popover } from '@wordpress/components'; +import { Component } from '@wordpress/element'; +import { NoticeList, Popover, KeyboardShortcuts } from '@wordpress/components'; /** * Internal dependencies @@ -28,29 +29,77 @@ import { getNotices, } from '../selectors'; -function Layout( { mode, isSidebarOpened, notices, ...props } ) { - const className = classnames( 'editor-layout', { - 'is-sidebar-opened': isSidebarOpened, - } ); - - return ( -
- - - - -
-
-
- { mode === 'text' && } - { mode === 'visual' && } +class Layout extends Component { + constructor() { + super( ...arguments ); + this.bindContainer = this.bindContainer.bind( this ); + this.focusNextRegion = this.focusRegion.bind( this, 1 ); + this.focusPreviousRegion = this.focusRegion.bind( this, -1 ); + this.onClick = this.onClick.bind( this ); + this.state = { + isFocusingRegions: false, + }; + } + + bindContainer( ref ) { + this.container = ref; + } + + focusRegion( offset ) { + const regions = [ ...this.container.querySelectorAll( '[role="region"]' ) ]; + if ( ! regions.length ) { + return; + } + let nextRegion = regions[ 0 ]; + const selectedIndex = regions.indexOf( document.activeElement ); + if ( selectedIndex !== -1 ) { + let nextIndex = selectedIndex + offset; + nextIndex = nextIndex === -1 ? regions.length - 1 : nextIndex; + nextIndex = nextIndex === regions.length ? 0 : nextIndex; + nextRegion = regions[ nextIndex ]; + } + + nextRegion.focus(); + this.setState( { isFocusingRegions: true } ); + } + + onClick() { + this.setState( { isFocusingRegions: false } ); + } + + render() { + const { mode, isSidebarOpened, notices, ...props } = this.props; + const className = classnames( 'editor-layout', { + 'is-sidebar-opened': isSidebarOpened, + 'is-focusing-regions': this.state.isFocusingRegions, + } ); + + // Disable reason: Clicking the editor should dismiss the regions focus style + /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + return ( +
+ + + + +
+
+
+ { mode === 'text' && } + { mode === 'visual' && } +
+
- + { isSidebarOpened && } + +
- { isSidebarOpened && } - -
- ); + /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + ); + } } export default connect( diff --git a/editor/layout/style.scss b/editor/layout/style.scss index c2eaba24fdceae..4b0e90f65bf11e 100644 --- a/editor/layout/style.scss +++ b/editor/layout/style.scss @@ -5,6 +5,17 @@ .editor-layout { position: relative; + + &.is-focusing-regions [role="region"]:focus:after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + pointer-events: none; + border: 2px solid $blue-medium-400; + } } .editor-layout__content { diff --git a/editor/modes/text-editor/index.js b/editor/modes/text-editor/index.js index 846efa57c71f90..8ce0888b11e7e2 100644 --- a/editor/modes/text-editor/index.js +++ b/editor/modes/text-editor/index.js @@ -54,6 +54,7 @@ class TextEditor extends Component { role="region" aria-label={ __( 'Editor content' ) } className="editor-text-editor" + tabIndex="-1" >
diff --git a/editor/modes/visual-editor/index.js b/editor/modes/visual-editor/index.js index 2f3744c607ab2b..50a1aa03cc98b3 100644 --- a/editor/modes/visual-editor/index.js +++ b/editor/modes/visual-editor/index.js @@ -88,6 +88,7 @@ class VisualEditor extends Component { onMouseDown={ this.onClick } onTouchStart={ this.onClick } ref={ this.bindContainer } + tabIndex="-1" > { return ( -
+
{ panel === 'document' && } { panel === 'block' && } From d3ca867acd5dfe549dc388d4d09f9a7871e97a02 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 23 Oct 2017 10:24:03 +0100 Subject: [PATCH 2/8] Components: Extract the region navigation into a reusable Higher-Order Component --- .../higher-order/navigate-regions/README.md | 19 ++++ .../higher-order/navigate-regions/index.js | 77 +++++++++++++++ .../higher-order/navigate-regions/style.scss | 12 +++ components/index.js | 1 + editor/layout/index.js | 95 +++++-------------- editor/layout/style.scss | 11 --- 6 files changed, 132 insertions(+), 83 deletions(-) create mode 100644 components/higher-order/navigate-regions/README.md create mode 100644 components/higher-order/navigate-regions/index.js create mode 100644 components/higher-order/navigate-regions/style.scss diff --git a/components/higher-order/navigate-regions/README.md b/components/higher-order/navigate-regions/README.md new file mode 100644 index 00000000000000..f93568558f48a5 --- /dev/null +++ b/components/higher-order/navigate-regions/README.md @@ -0,0 +1,19 @@ +# navigateRegions + +`navigateRegions` is a React [higher-order component](https://facebook.github.io/react/docs/higher-order-components.html) adding keyboard navigation to switch between the different DOM elements marked as "regions" (role="region"). These regions should be focusable (By adding a tabIndex attribute for example) + +## Example: + +```jsx +function MyLayout() { + return ( +
+
Header
+
Content
+
Sidebar
+
+ ); +} + +export default navigateRegions( MyLayout ); +``` \ No newline at end of file diff --git a/components/higher-order/navigate-regions/index.js b/components/higher-order/navigate-regions/index.js new file mode 100644 index 00000000000000..9274da4920e155 --- /dev/null +++ b/components/higher-order/navigate-regions/index.js @@ -0,0 +1,77 @@ +/** + * External Dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress Dependencies + */ +import { Component } from '@wordpress/element'; + +/** + * Internal Dependencies + */ +import './style.scss'; +import KeyboardShortcuts from '../../keyboard-shortcuts'; + +function navigateRegions( WrappedComponent ) { + return class extends Component { + constructor() { + super( ...arguments ); + this.bindContainer = this.bindContainer.bind( this ); + this.focusNextRegion = this.focusRegion.bind( this, 1 ); + this.focusPreviousRegion = this.focusRegion.bind( this, -1 ); + this.onClick = this.onClick.bind( this ); + this.state = { + isFocusingRegions: false, + }; + } + + bindContainer( ref ) { + this.container = ref; + } + + focusRegion( offset ) { + const regions = [ ...this.container.querySelectorAll( '[role="region"]' ) ]; + if ( ! regions.length ) { + return; + } + let nextRegion = regions[ 0 ]; + const selectedIndex = regions.indexOf( document.activeElement ); + if ( selectedIndex !== -1 ) { + let nextIndex = selectedIndex + offset; + nextIndex = nextIndex === -1 ? regions.length - 1 : nextIndex; + nextIndex = nextIndex === regions.length ? 0 : nextIndex; + nextRegion = regions[ nextIndex ]; + } + + nextRegion.focus(); + this.setState( { isFocusingRegions: true } ); + } + + onClick() { + this.setState( { isFocusingRegions: false } ); + } + + render() { + const className = classnames( 'components-navigate-regions', { + 'is-focusing-regions': this.state.isFocusingRegions, + } ); + + // Disable reason: Clicking the editor should dismiss the regions focus style + /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + return ( +
+ + +
+ ); + /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + } + }; +} + +export default navigateRegions; diff --git a/components/higher-order/navigate-regions/style.scss b/components/higher-order/navigate-regions/style.scss new file mode 100644 index 00000000000000..c3d7f7633f0a2d --- /dev/null +++ b/components/higher-order/navigate-regions/style.scss @@ -0,0 +1,12 @@ +.components-navigate-regions.is-focusing-regions [role="region"] { + &:focus:after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + pointer-events: none; + border: 2px solid $blue-medium-400; + } +} diff --git a/components/index.js b/components/index.js index f9b22dc1dbc9ea..f05e64cd5ac578 100644 --- a/components/index.js +++ b/components/index.js @@ -31,6 +31,7 @@ export { default as Tooltip } from './tooltip'; export { Slot, Fill, Provider as SlotFillProvider } from './slot-fill'; // Higher-Order Components +export { default as navigateRegions } from './higher-order/navigate-regions'; export { default as withAPIData } from './higher-order/with-api-data'; export { default as withFocusReturn } from './higher-order/with-focus-return'; export { default as withInstanceId } from './higher-order/with-instance-id'; diff --git a/editor/layout/index.js b/editor/layout/index.js index d13287f0d962e4..8e40bd6dc4fe98 100644 --- a/editor/layout/index.js +++ b/editor/layout/index.js @@ -7,8 +7,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; -import { NoticeList, Popover, KeyboardShortcuts } from '@wordpress/components'; +import { NoticeList, Popover, navigateRegions } from '@wordpress/components'; /** * Internal dependencies @@ -29,77 +28,29 @@ import { getNotices, } from '../selectors'; -class Layout extends Component { - constructor() { - super( ...arguments ); - this.bindContainer = this.bindContainer.bind( this ); - this.focusNextRegion = this.focusRegion.bind( this, 1 ); - this.focusPreviousRegion = this.focusRegion.bind( this, -1 ); - this.onClick = this.onClick.bind( this ); - this.state = { - isFocusingRegions: false, - }; - } - - bindContainer( ref ) { - this.container = ref; - } - - focusRegion( offset ) { - const regions = [ ...this.container.querySelectorAll( '[role="region"]' ) ]; - if ( ! regions.length ) { - return; - } - let nextRegion = regions[ 0 ]; - const selectedIndex = regions.indexOf( document.activeElement ); - if ( selectedIndex !== -1 ) { - let nextIndex = selectedIndex + offset; - nextIndex = nextIndex === -1 ? regions.length - 1 : nextIndex; - nextIndex = nextIndex === regions.length ? 0 : nextIndex; - nextRegion = regions[ nextIndex ]; - } - - nextRegion.focus(); - this.setState( { isFocusingRegions: true } ); - } - - onClick() { - this.setState( { isFocusingRegions: false } ); - } - - render() { - const { mode, isSidebarOpened, notices, ...props } = this.props; - const className = classnames( 'editor-layout', { - 'is-sidebar-opened': isSidebarOpened, - 'is-focusing-regions': this.state.isFocusingRegions, - } ); - - // Disable reason: Clicking the editor should dismiss the regions focus style - /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ - return ( -
- - - - -
-
-
- { mode === 'text' && } - { mode === 'visual' && } -
- +function Layout( { mode, isSidebarOpened, notices, ...props } ) { + const className = classnames( 'editor-layout', { + 'is-sidebar-opened': isSidebarOpened, + } ); + + return ( +
+ + + + +
+
+
+ { mode === 'text' && } + { mode === 'visual' && }
- { isSidebarOpened && } - - +
- /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ - ); - } + { isSidebarOpened && } + +
+ ); } export default connect( @@ -109,4 +60,4 @@ export default connect( notices: getNotices( state ), } ), { removeNotice } -)( Layout ); +)( navigateRegions( Layout ) ); diff --git a/editor/layout/style.scss b/editor/layout/style.scss index 4b0e90f65bf11e..c2eaba24fdceae 100644 --- a/editor/layout/style.scss +++ b/editor/layout/style.scss @@ -5,17 +5,6 @@ .editor-layout { position: relative; - - &.is-focusing-regions [role="region"]:focus:after { - content: ''; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - pointer-events: none; - border: 2px solid $blue-medium-400; - } } .editor-layout__content { From ca0cb25c4422ff84883ae6080968290cb335c138 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 26 Oct 2017 08:37:20 +0100 Subject: [PATCH 3/8] Accessibility: Use command + ` shortcut for region navigations --- components/higher-order/navigate-regions/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/higher-order/navigate-regions/index.js b/components/higher-order/navigate-regions/index.js index 9274da4920e155..c0929de0c34153 100644 --- a/components/higher-order/navigate-regions/index.js +++ b/components/higher-order/navigate-regions/index.js @@ -63,8 +63,8 @@ function navigateRegions( WrappedComponent ) { return (
From 90955c5be50f02d1343a149edfe85283caf197a5 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 26 Oct 2017 08:51:17 +0100 Subject: [PATCH 4/8] Accessibility: Using a flashing focus when navigating regions --- .../higher-order/navigate-regions/style.scss | 2 +- editor/assets/stylesheets/_animations.scss | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/components/higher-order/navigate-regions/style.scss b/components/higher-order/navigate-regions/style.scss index c3d7f7633f0a2d..d671ff1129caf9 100644 --- a/components/higher-order/navigate-regions/style.scss +++ b/components/higher-order/navigate-regions/style.scss @@ -7,6 +7,6 @@ left: 0; right: 0; pointer-events: none; - border: 2px solid $blue-medium-400; + @include focus_flash; } } diff --git a/editor/assets/stylesheets/_animations.scss b/editor/assets/stylesheets/_animations.scss index 0aa7f058ae47fb..f890300b81286c 100644 --- a/editor/assets/stylesheets/_animations.scss +++ b/editor/assets/stylesheets/_animations.scss @@ -38,3 +38,23 @@ 50% { opacity: 1; } 100% { opacity: .5; } } + + +@mixin focus_flash { + animation: focus_flash .4s; +} + +@keyframes focus_flash { + 0% { + border: 2px solid $blue-medium-400; + } + 10% { + border: 4px solid $blue-medium-400; + } + 90% { + border: 4px solid $blue-medium-400; + } + 100% { + border: 2px solid $blue-medium-400; + } +} From 13727635e1f8f3022eebfbf9d1933224f7cd0343 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 26 Oct 2017 10:14:44 +0100 Subject: [PATCH 5/8] Accessibility: Ctrl instead of command to navigate regions --- components/higher-order/navigate-regions/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/higher-order/navigate-regions/index.js b/components/higher-order/navigate-regions/index.js index c0929de0c34153..f060b5494af954 100644 --- a/components/higher-order/navigate-regions/index.js +++ b/components/higher-order/navigate-regions/index.js @@ -63,8 +63,8 @@ function navigateRegions( WrappedComponent ) { return (
From b5fc689b73f1cf84d9015434882c4c0e2bacbfcd Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 26 Oct 2017 12:36:48 +0100 Subject: [PATCH 6/8] iCode Style: Fix mixed indentation style --- editor/assets/stylesheets/_animations.scss | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/editor/assets/stylesheets/_animations.scss b/editor/assets/stylesheets/_animations.scss index f890300b81286c..0dd152dba8ce78 100644 --- a/editor/assets/stylesheets/_animations.scss +++ b/editor/assets/stylesheets/_animations.scss @@ -41,20 +41,20 @@ @mixin focus_flash { - animation: focus_flash .4s; + animation: focus_flash .4s; } @keyframes focus_flash { - 0% { + 0% { border: 2px solid $blue-medium-400; - } - 10% { + } + 10% { border: 4px solid $blue-medium-400; - } - 90% { + } + 90% { border: 4px solid $blue-medium-400; - } - 100% { + } + 100% { border: 2px solid $blue-medium-400; - } + } } From 21070a4e66ce6ddd400c654b52153285b2efc97b Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Fri, 27 Oct 2017 13:55:35 +0100 Subject: [PATCH 7/8] Drop focus animation when navigating regions --- .../higher-order/navigate-regions/style.scss | 2 +- editor/assets/stylesheets/_animations.scss | 20 ------------------- editor/assets/stylesheets/main.scss | 4 ++++ 3 files changed, 5 insertions(+), 21 deletions(-) diff --git a/components/higher-order/navigate-regions/style.scss b/components/higher-order/navigate-regions/style.scss index d671ff1129caf9..edf682bf0729c2 100644 --- a/components/higher-order/navigate-regions/style.scss +++ b/components/higher-order/navigate-regions/style.scss @@ -7,6 +7,6 @@ left: 0; right: 0; pointer-events: none; - @include focus_flash; + border: 4px solid $blue-medium-400; } } diff --git a/editor/assets/stylesheets/_animations.scss b/editor/assets/stylesheets/_animations.scss index 0dd152dba8ce78..0aa7f058ae47fb 100644 --- a/editor/assets/stylesheets/_animations.scss +++ b/editor/assets/stylesheets/_animations.scss @@ -38,23 +38,3 @@ 50% { opacity: 1; } 100% { opacity: .5; } } - - -@mixin focus_flash { - animation: focus_flash .4s; -} - -@keyframes focus_flash { - 0% { - border: 2px solid $blue-medium-400; - } - 10% { - border: 4px solid $blue-medium-400; - } - 90% { - border: 4px solid $blue-medium-400; - } - 100% { - border: 2px solid $blue-medium-400; - } -} diff --git a/editor/assets/stylesheets/main.scss b/editor/assets/stylesheets/main.scss index bf980881ddcb1a..2ed0d41352c1df 100644 --- a/editor/assets/stylesheets/main.scss +++ b/editor/assets/stylesheets/main.scss @@ -74,6 +74,10 @@ body.gutenberg-editor-page { iframe { width: 100%; } + + .components-navigate-regions { + height: 100%; + } } .editor-post-title, From e893cb0dae6df30159626269a4cec153766e554c Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Fri, 27 Oct 2017 15:04:56 +0100 Subject: [PATCH 8/8] Fix the editor content region focus style when scrolling --- editor/layout/index.js | 3 ++- editor/layout/style.scss | 1 + editor/modes/text-editor/index.js | 8 +------- editor/modes/visual-editor/index.js | 8 ++------ 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/editor/layout/index.js b/editor/layout/index.js index 8e40bd6dc4fe98..a3ffbfde124a48 100644 --- a/editor/layout/index.js +++ b/editor/layout/index.js @@ -8,6 +8,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { NoticeList, Popover, navigateRegions } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -40,7 +41,7 @@ function Layout( { mode, isSidebarOpened, notices, ...props } ) {
-
+
{ mode === 'text' && } { mode === 'visual' && } diff --git a/editor/layout/style.scss b/editor/layout/style.scss index c2eaba24fdceae..a62db2ad9b682d 100644 --- a/editor/layout/style.scss +++ b/editor/layout/style.scss @@ -8,6 +8,7 @@ } .editor-layout__content { + position: relative; display: flex; flex-direction: column; } diff --git a/editor/modes/text-editor/index.js b/editor/modes/text-editor/index.js index 8ce0888b11e7e2..6b9327d0f6c593 100644 --- a/editor/modes/text-editor/index.js +++ b/editor/modes/text-editor/index.js @@ -7,7 +7,6 @@ import Textarea from 'react-autosize-textarea'; /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { parse } from '@wordpress/blocks'; @@ -50,12 +49,7 @@ class TextEditor extends Component { const { value } = this.props; return ( -
+
diff --git a/editor/modes/visual-editor/index.js b/editor/modes/visual-editor/index.js index 50a1aa03cc98b3..d1edd5a1becb08 100644 --- a/editor/modes/visual-editor/index.js +++ b/editor/modes/visual-editor/index.js @@ -7,7 +7,6 @@ import { first, last } from 'lodash'; /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; import { Component, findDOMNode } from '@wordpress/element'; import { KeyboardShortcuts } from '@wordpress/components'; @@ -79,16 +78,13 @@ class VisualEditor extends Component { render() { // Disable reason: Clicking the canvas should clear the selection - /* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + /* eslint-disable jsx-a11y/no-static-element-interactions */ return (
); - /* eslint-enable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */ + /* eslint-enable jsx-a11y/no-static-element-interactions */ } }