-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Block Toolbar: Refactor using KeyboardShorcuts component #3031
Changes from 2 commits
7d036d0
a6340ac
9314146
2601680
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { mount } from 'enzyme'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import KeyboardShortcuts from '../'; | ||
|
||
describe( 'KeyboardShortcuts', () => { | ||
afterEach( () => { | ||
while ( document.body.firstChild ) { | ||
document.body.removeChild( document.body.firstChild ); | ||
} | ||
} ); | ||
|
||
function keyPress( which, target ) { | ||
[ 'keydown', 'keypress', 'keyup' ].forEach( ( eventName ) => { | ||
const event = new window.Event( eventName, { bubbles: true } ); | ||
event.keyCode = which; | ||
event.which = which; | ||
target.dispatchEvent( event ); | ||
} ); | ||
} | ||
|
||
it( 'should capture key events', () => { | ||
const spy = jest.fn(); | ||
mount( | ||
<KeyboardShortcuts | ||
shortcuts={ { | ||
d: spy, | ||
} } /> | ||
); | ||
|
||
keyPress( 68, document ); | ||
|
||
expect( spy ).toHaveBeenCalled(); | ||
} ); | ||
|
||
it( 'should capture key events globally', () => { | ||
const spy = jest.fn(); | ||
const attachNode = document.createElement( 'div' ); | ||
document.body.appendChild( attachNode ); | ||
|
||
const wrapper = mount( | ||
<div> | ||
<KeyboardShortcuts | ||
shortcuts={ { | ||
d: [ spy, true ], | ||
} } /> | ||
<textarea></textarea> | ||
</div>, | ||
{ attachTo: attachNode } | ||
); | ||
|
||
keyPress( 68, wrapper.find( 'textarea' ).getDOMNode() ); | ||
|
||
expect( spy ).toHaveBeenCalled(); | ||
} ); | ||
} ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,7 @@ import classnames from 'classnames'; | |
/** | ||
* WordPress Dependencies | ||
*/ | ||
import { IconButton, Toolbar, NavigableMenu, Slot } from '@wordpress/components'; | ||
import { IconButton, Toolbar, NavigableMenu, Slot, KeyboardShortcuts } from '@wordpress/components'; | ||
import { Component, Children, findDOMNode } from '@wordpress/element'; | ||
import { __ } from '@wordpress/i18n'; | ||
import { focus, keycodes } from '@wordpress/utils'; | ||
|
@@ -19,47 +19,29 @@ 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; | ||
const { ESCAPE } = 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.focusToolbar = this.focusToolbar.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 ) { | ||
|
@@ -72,21 +54,10 @@ 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(); | ||
} | ||
focusToolbar() { | ||
const tabbables = focus.tabbable.find( this.toolbar ); | ||
if ( tabbables.length ) { | ||
tabbables[ 0 ].focus(); | ||
} | ||
} | ||
|
||
|
@@ -128,6 +99,10 @@ class BlockToolbar extends Component { | |
role="toolbar" | ||
deep | ||
> | ||
<KeyboardShortcuts shortcuts={ { | ||
mod: [ this.focusToolbar, true ], | ||
'alt+f10': [ this.focusToolbar, true ], | ||
} } /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice refactoring 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we handle the "escape" key as well There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We can, but to recreate the handling of these keys only taking effect while within the toolbar, we'd want to introduce child scoping for KeyboardShortcuts. I mentioned this in #1944, and should probably be done as a separate refactoring step. |
||
<div className="editor-block-toolbar__group" onKeyDown={ this.onToolbarKeyDown }> | ||
{ ! showMobileControls && [ | ||
<BlockSwitcher key="switcher" uid={ uid } />, | ||
|
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.
Do you think the shortcuts prop could change over time? Should we implement
componentWillReceivePorps
or not?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.
Potentially, though the effect could be imitated by rendering separate
KeyboardShortcuts
components (or changing thekey
of an existing one). Doesn't seem like a need we currently have, but then again, could be a potential source of confusion for future usage.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 started looking at this, and realized that there may be some non-trivial checks to consider whether keyboard bindings need to be updated.
https://gist.github.com/aduth/52f5c4ff561723c2d71b7e9d7fc142e7
Instead, and since there's currently no need, I improved documentation to note that shortcuts changes won't take effect and a suggested workaround (2601680).