Skip to content

Commit

Permalink
Editor: Associate NavigableToolbar by keybind scope
Browse files Browse the repository at this point in the history
  • Loading branch information
aduth committed Oct 29, 2018
1 parent 9d3888f commit 6be549c
Show file tree
Hide file tree
Showing 11 changed files with 251 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ function HeaderToolbar( { hasFixedToolbar, isLargeViewport, mode } ) {
<NavigableToolbar
className="edit-post-header-toolbar"
aria-label={ toolbarAriaLabel }
scopeId="edit-post-header"
>
<FullscreenModeClose />
<div>
Expand Down
102 changes: 54 additions & 48 deletions packages/edit-post/src/components/layout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
EditorNotices,
PostPublishPanel,
PreserveScrollInReorder,
NavigableToolbar,
} from '@wordpress/editor';
import { withDispatch, withSelect } from '@wordpress/data';
import { Fragment } from '@wordpress/element';
Expand Down Expand Up @@ -70,56 +71,61 @@ function Layout( {
<UnsavedChangesWarning />
<AutosaveMonitor />
<Header />
<div
className="edit-post-layout__content"
role="region"
/* translators: accessibility text for the content landmark region. */
aria-label={ __( 'Editor content' ) }
tabIndex="-1"
<NavigableToolbar.KeybindScope
scopeId="edit-post-header"
className="edit-post-layout__navigable-toolbar-scope"
>
<EditorNotices />
<PreserveScrollInReorder />
<EditorModeKeyboardShortcuts />
<KeyboardShortcutHelpModal />
<OptionsModal />
{ mode === 'text' && <TextEditor /> }
{ mode === 'visual' && <VisualEditor /> }
<div className="edit-post-layout__metaboxes">
<MetaBoxes location="normal" />
</div>
<div className="edit-post-layout__metaboxes">
<MetaBoxes location="advanced" />
</div>
</div>
{ publishSidebarOpened ? (
<PostPublishPanel
{ ...publishLandmarkProps }
onClose={ closePublishSidebar }
forceIsDirty={ hasActiveMetaboxes }
forceIsSaving={ isSaving }
PrePublishExtension={ PluginPrePublishPanel.Slot }
PostPublishExtension={ PluginPostPublishPanel.Slot }
/>
) : (
<Fragment>
<div className="edit-post-toggle-publish-panel" { ...publishLandmarkProps }>
<Button
isDefault
type="button"
className="edit-post-toggle-publish-panel__button"
onClick={ togglePublishSidebar }
aria-expanded={ false }
>
{ __( 'Open publish panel' ) }
</Button>
<div
className="edit-post-layout__content"
role="region"
/* translators: accessibility text for the content landmark region. */
aria-label={ __( 'Editor content' ) }
tabIndex="-1"
>
<EditorNotices />
<PreserveScrollInReorder />
<EditorModeKeyboardShortcuts />
<KeyboardShortcutHelpModal />
<OptionsModal />
{ mode === 'text' && <TextEditor /> }
{ mode === 'visual' && <VisualEditor /> }
<div className="edit-post-layout__metaboxes">
<MetaBoxes location="normal" />
</div>
<SettingsSidebar />
<Sidebar.Slot />
{
isMobileViewport && sidebarIsOpened && <ScrollLock />
}
</Fragment>
) }
<div className="edit-post-layout__metaboxes">
<MetaBoxes location="advanced" />
</div>
</div>
{ publishSidebarOpened ? (
<PostPublishPanel
{ ...publishLandmarkProps }
onClose={ closePublishSidebar }
forceIsDirty={ hasActiveMetaboxes }
forceIsSaving={ isSaving }
PrePublishExtension={ PluginPrePublishPanel.Slot }
PostPublishExtension={ PluginPostPublishPanel.Slot }
/>
) : (
<Fragment>
<div className="edit-post-toggle-publish-panel" { ...publishLandmarkProps }>
<Button
isDefault
type="button"
className="edit-post-toggle-publish-panel__button"
onClick={ togglePublishSidebar }
aria-expanded={ false }
>
{ __( 'Open publish panel' ) }
</Button>
</div>
<SettingsSidebar />
<Sidebar.Slot />
{
isMobileViewport && sidebarIsOpened && <ScrollLock />
}
</Fragment>
) }
</NavigableToolbar.KeybindScope>
<Popover.Slot />
<PluginArea />
</div>
Expand Down
3 changes: 2 additions & 1 deletion packages/edit-post/src/components/layout/style.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.edit-post-layout,
.edit-post-layout__content {
.edit-post-layout__content,
.edit-post-layout__navigable-toolbar-scope {
height: 100%;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import NavigableToolbar from '../navigable-toolbar';
import { BlockToolbar } from '../';

function BlockContextualToolbar() {
return (
<NavigableToolbar
className="editor-block-contextual-toolbar"
aria-label={ __( 'Block Toolbar' ) }
>
<div className="editor-block-contextual-toolbar">
<BlockToolbar />
</NavigableToolbar>
</div>
);
}

Expand Down
43 changes: 23 additions & 20 deletions packages/editor/src/components/block-list/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import BlockMobileToolbar from './block-mobile-toolbar';
import BlockInsertionPoint from './insertion-point';
import IgnoreNestedEvents from '../ignore-nested-events';
import InserterWithShortcuts from '../inserter-with-shortcuts';
import NavigableToolbar from '../navigable-toolbar';
import Inserter from '../inserter';
import withHoverAreas from './with-hover-areas';
import { isInsideRootBlock } from '../../utils/dom';
Expand Down Expand Up @@ -528,27 +529,29 @@ export class BlockListBlock extends Component {
className="editor-block-list__block-edit"
data-block={ clientId }
>
<BlockCrashBoundary onError={ this.onBlockError }>
{ isValid && blockEdit }
{ isValid && mode === 'html' && (
<BlockHtml clientId={ clientId } />
<NavigableToolbar.KeybindScope scopeId={ 'block-' + clientId }>
<BlockCrashBoundary onError={ this.onBlockError }>
{ isValid && blockEdit }
{ isValid && mode === 'html' && (
<BlockHtml clientId={ clientId } />
) }
{ ! isValid && [
<BlockInvalidWarning
key="invalid-warning"
block={ block }
/>,
<div key="invalid-preview">
{ getSaveElement( blockType, block.attributes ) }
</div>,
] }
</BlockCrashBoundary>
{ shouldShowMobileToolbar && (
<BlockMobileToolbar
clientId={ clientId }
/>
) }
{ ! isValid && [
<BlockInvalidWarning
key="invalid-warning"
block={ block }
/>,
<div key="invalid-preview">
{ getSaveElement( blockType, block.attributes ) }
</div>,
] }
</BlockCrashBoundary>
{ shouldShowMobileToolbar && (
<BlockMobileToolbar
clientId={ clientId }
/>
) }
{ !! error && <BlockCrashWarning /> }
{ !! error && <BlockCrashWarning /> }
</NavigableToolbar.KeybindScope>
</IgnoreNestedEvents>
{ showEmptyBlockSideInserter && (
<Fragment>
Expand Down
10 changes: 8 additions & 2 deletions packages/editor/src/components/block-toolbar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { withSelect } from '@wordpress/data';
import { Component, createRef, Fragment } from '@wordpress/element';
import { focus } from '@wordpress/dom';
import { __ } from '@wordpress/i18n';

/**
* Internal Dependencies
Expand All @@ -13,6 +14,7 @@ import MultiBlocksSwitcher from '../block-switcher/multi-blocks-switcher';
import BlockControls from '../block-controls';
import BlockFormatControls from '../block-format-controls';
import BlockSettingsMenu from '../block-settings-menu';
import NavigableToolbar from '../navigable-toolbar';

class BlockToolbar extends Component {
constructor() {
Expand Down Expand Up @@ -59,7 +61,11 @@ class BlockToolbar extends Component {
}

return (
<div className="editor-block-toolbar">
<NavigableToolbar
className="editor-block-toolbar"
aria-label={ __( 'Block Toolbar' ) }
scopeId={ 'block-' + blockClientIds[ 0 ] }
>
{ mode === 'visual' && isValid && (
<Fragment>
<BlockSwitcher clientIds={ blockClientIds } />
Expand All @@ -68,7 +74,7 @@ class BlockToolbar extends Component {
</Fragment>
) }
<BlockSettingsMenu clientIds={ blockClientIds } />
</div>
</NavigableToolbar>
);
}
}
Expand Down
26 changes: 26 additions & 0 deletions packages/editor/src/components/navigable-toolbar/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
NavigableToolbar
================

NavigableToolbar is a set of components which renders a toolbar menu and coordinates focus to and from the toolbar by contextual keyboard shortcuts.

While focused within the scope of a `NavigableToolbar.KeybindScope` component, pressing <kbd>Alt + F10</kbd> will direct focus to the corresponding toolbar by scope identifier. Pressing <kbd>Escape</kbd> while in a toolbar will return focus to the element which had focus at the time of the <kbd>Alt + F10</kbd> key combination, if applicable.

## Usage

Render a `NavigableToolbar` with an assigned scopeId. Elsewhere, render one or more `NavigableToolbar.KeybindScope` wrapping the context(s) in which the toolbar's keyboard shortcut should be observed.

```jsx
import { NavigableToolbar } from '@wordpress/editor';
import { Toolbar } from '@wordpress/components';

function MyNavigableToolbar() {
return (
<NavigableToolbar scopeId="my-toolbar">
<Toolbar controls={ [ /* ... */ ] } />
</NavigableToolbar>
<NavigableToolbar.KeybindScope scopeId="my-toolbar">
<textarea />
</NavigableToolbar.KeybindScope>
);
}
```
65 changes: 55 additions & 10 deletions packages/editor/src/components/navigable-toolbar/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { cond, matchesProperty } from 'lodash';
import { cond, matchesProperty, omit } from 'lodash';

/**
* WordPress dependencies
Expand All @@ -24,6 +24,14 @@ class NavigableToolbar extends Component {
] );
}

componentDidMount() {
NavigableToolbar.registry.set( this.props.scopeId, this );
}

componentWillUnmount() {
NavigableToolbar.registry.delete( this.props.scopeId );
}

bindNode( ref ) {
// Disable reason: Need DOM node for finding first focusable element
// on keyboard interaction to shift to toolbar.
Expand Down Expand Up @@ -66,7 +74,17 @@ class NavigableToolbar extends Component {
}

render() {
const { children, ...props } = this.props;
const {
children,
// Disable reason: NavigableMenu will pass through props received
// to its rendered element. Avoid including NavigableToolbar's
// `scopeId` in destructured props.
/* eslint-disable no-unused-vars */
scopeId,
/* eslint-enable no-unused-vars */
...props
} = this.props;

return (
<NavigableMenu
orientation="horizontal"
Expand All @@ -75,18 +93,45 @@ class NavigableToolbar extends Component {
onKeyDown={ this.switchOnKeyDown }
{ ...props }
>
<KeyboardShortcuts
bindGlobal
// Use the same event that TinyMCE uses in the Classic block for its own `alt+f10` shortcut.
eventName="keydown"
shortcuts={ {
'alt+f10': this.focusToolbar,
} }
/>
{ children }
</NavigableMenu>
);
}
}

NavigableToolbar.registry = new Map;

NavigableToolbar.KeybindScope = class extends Component {
constructor() {
super( ...arguments );

this.focusToolbar = this.focusToolbar.bind( this );
}

/**
* Invokes `focusToolbar` on the `NavigableToolbar` instance corresponding
* to the scope's own `scopeId` prop, if one exists.
*/
focusToolbar() {
const toolbar = NavigableToolbar.registry.get( this.props.scopeId );
if ( toolbar ) {
toolbar.focusToolbar();
}
}

render() {
return (
<KeyboardShortcuts
bindGlobal
ignoreChildHandled
eventName="keydown"
shortcuts={ {
'alt+f10': this.focusToolbar,
} }
{ ...omit( this.props, [ 'scopeId' ] ) }
/>
);
}
};

export default NavigableToolbar;
Loading

0 comments on commit 6be549c

Please sign in to comment.