Skip to content

Commit

Permalink
Components: Enhance KeyboardShortcuts to optionally bind globally
Browse files Browse the repository at this point in the history
To capture events within inputs
  • Loading branch information
aduth committed Oct 3, 2017
1 parent 2753f63 commit 334351c
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 3 deletions.
10 changes: 8 additions & 2 deletions components/keyboard-shortcuts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class SelectAllDetection extends Component {
super( ...arguments );

this.setAllSelected = this.setAllSelected.bind( this );
this.save = this.save.bind( this );

this.state = { isAllSelected: false };
}
Expand All @@ -23,11 +24,16 @@ class SelectAllDetection extends Component {
this.setState( { isAllSelected: true } );
}

save() {
this.props.onSave();
}

render() {
return (
<div>
<KeyboardShorcuts shortcuts={ {
'mod+a': this.setAllSelected,
'mod+s': [ this.save, true ],
} } />
Combination pressed? { isAllSelected ? 'Yes' : 'No' }
</div>
Expand All @@ -38,15 +44,15 @@ class SelectAllDetection extends Component {
__Note:__ The value of each shortcut should be a consistent function reference, not an anonymous function. Otherwise, the callback will not be correctly unbound when the component unmounts.
__Note:__ The callback will not be invoked if the key combination occurs in an editable field.
__Note:__ The callback will not be invoked if the key combination occurs in an editable field, unless you pass the callback as an array with the callback and `true` as entries of the array respectively. Refer to the example above, and see the `shortcuts` prop documentation for more information.
## Props
The component accepts the following props:
### shortcuts
An object of shortcut bindings, where each key is a keyboard combination, the value of which is the callback to be invoked when the key combination is pressed.
An object of shortcut bindings, where each key is a keyboard combination, the value of which is the callback to be invoked when the key combination is pressed. To capture a key event globally (including within input fields), assign as the value an array with the function callback as the first argument and `true` as the second argument.
- Type: `Object`
- Required: No
Expand Down
15 changes: 14 additions & 1 deletion components/keyboard-shortcuts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* External dependencies
*/
import Mousetrap from 'mousetrap';
import 'mousetrap/plugins/global-bind/mousetrap-global-bind';
import { forEach } from 'lodash';

/**
Expand All @@ -13,7 +14,19 @@ class KeyboardShortcuts extends Component {
componentWillMount() {
this.mousetrap = new Mousetrap;
forEach( this.props.shortcuts, ( callback, key ) => {
this.mousetrap.bind( key, callback );
// Normalize callback, which can be passed as either a function or
// an array of [ callback, isGlobal ]
let isGlobal = false;
if ( Array.isArray( callback ) ) {
isGlobal = callback[ 1 ];
callback = callback[ 0 ];
}

if ( isGlobal ) {
this.mousetrap.bindGlobal( key, callback );
} else {
this.mousetrap.bind( key, callback );
}
} );
}

Expand Down
61 changes: 61 additions & 0 deletions components/keyboard-shortcuts/test/index.js
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();
} );
} );

0 comments on commit 334351c

Please sign in to comment.