Skip to content
This repository was archived by the owner on May 28, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
example/node_modules
example/yarn.lock
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 3.0.0

Remove mixin in favor of HOC.

## 2.1.1

* make 'keybindingsOnInputs' opt-in, the default is again that inputs do not trigger keybindings
Expand Down
23 changes: 10 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![build status](https://secure.travis-ci.org/mapbox/react-keybinding.png)](http://travis-ci.org/mapbox/react-keybinding)

Declarative, lightweight, and robust keybindings mixin for React.
Declarative, lightweight, and robust keybindings HOC for React.

* Straightforward `'⌘S'` string syntax for declaring bindings
* Automatically binds & unbinds keybindings when components mount and unmount
Expand All @@ -27,10 +27,10 @@ $ npm install react-keybinding

```js
var React = require('react'),
ReactDOM = require('react-dom'),
Keybinding = require('../');
var HelloMessage = React.createClass({
mixins: [Keybinding],
keybindingsPlatformAgnostic: true,
ReactDOM.render(React.createElement(HelloMessage, {
name: "John",
keybindings: {
'⌘S': function(e) {
console.log('save!');
Expand All @@ -39,14 +39,11 @@ var HelloMessage = React.createClass({
'⌘C': 'COPY'
},
keybinding: function(event, action) {
// event is the browser event, action is 'COPY'
// event is the browser event
// action is 'COPY'
console.log(arguments);
},
render: function() {
return React.createElement("div", null, "Hello");
}
});
React.render(React.createElement(HelloMessage, {name: "John"}), document.body);
}), document.body);
```

There's a runnable example in the `./examples` directory: to run it,
Expand All @@ -63,9 +60,9 @@ react-keybinding in an application.

### API

This module exposes a single mixin called `Keybinding`.
This module exposes a single HOC called `withKeybindings`.

Where you use this mixin on Components, it expects a property called
Where you use this HOC on Components, it expects a prop called
`keybindings` of the format:

```js
Expand All @@ -91,7 +88,7 @@ keybindingsPlatformAgnostic: true,
keybindings: { ... }
```

The mixin provides a method for components called `.getAllKeybindings()`:
The HOC provides a method for components called `.getAllKeybindings()`:
this yields an array of all `keybindings` properties on all active components.

## [Syntax](SYNTAX.md)
Expand Down
21 changes: 12 additions & 9 deletions example/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
var React = require('react'),
Keybinding = require('../');
ReactDOM = require('react-dom'),
withKeybindings = require('../'),
createReactClass = require('create-react-class');

var HelloMessage = React.createClass({
mixins: [Keybinding],
var HelloMessage = withKeybindings(createReactClass({
render: function() {
return React.createElement("div", null, "Hello");
}
}));

ReactDOM.render(React.createElement(HelloMessage, {
name: "John",
keybindings: {
'⌘S': function(e) {
console.log('save!');
Expand All @@ -15,9 +23,4 @@ var HelloMessage = React.createClass({
// action is 'COPY'
console.log(arguments);
},
render: function() {
return React.createElement("div", null, "Hello");
}
});

React.render(React.createElement(HelloMessage, {name: "John"}), document.body);
}), document.body);
4 changes: 2 additions & 2 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
"author": "",
"license": "ISC",
"devDependencies": {
"budo": "^0.1.12",
"budo": "^11.2.0",
"watchify": "^2.3.0"
},
"dependencies": {
"react": "^0.12.2"
"react": "^16.x"
}
}
148 changes: 76 additions & 72 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,86 +1,90 @@
var React = require('react'),
PropTypes = require('prop-types'),
createReactClass = require('create-react-class'),
parseEvents = require('./src/parse_events.js'),
isInput = require('./src/is_input.js'),
match = require('./src/match.js');

/**
* A React mixin that provides keybinding support for components
*/
var Keybinding = {
// This function takes a component...
function withKeybindings(WrappedComponent) {
// ...and returns another component...
return createReactClass({
/**
* Housekeeping to pass around a single array of all
* currently-active keybinding objects.
*/
childContextTypes: { __keybindings: PropTypes.array },
contextTypes: { __keybindings: PropTypes.array },
getChildContext: function() {
return { __keybindings: this.__getKeybindings() };
},
__getKeybindings: function() {
this.__keybindings = this.__keybindings ||
(this.context && this.context.__keybindings) || [];
return this.__keybindings;
},

/**
* Housekeeping to pass around a single array of all
* currently-active keybinding objects.
*/
childContextTypes: {
__keybindings: React.PropTypes.array
},
contextTypes: {
__keybindings: React.PropTypes.array
},
getChildContext: function() {
return { __keybindings: this.__getKeybindings() };
},
__getKeybindings: function() {
this.__keybindings = this.__keybindings ||
(this.context && this.context.__keybindings) || [];
return this.__keybindings;
},
/**
* This is the only method meant to be exposed to the user: it
* returns the global keybinding index for the purposes of documentation
* generation.
*/
getAllKeybindings: function() {
return this.__getKeybindings();
},

/**
* This is the only method meant to be exposed to the user: it
* returns the global keybinding index for the purposes of documentation
* generation.
*/
getAllKeybindings: function() {
return this.__getKeybindings();
},

/**
* Internal method: avoids firing keybindings in textareas,
* figures out if they match any of the bindings from this component,
* and then either fires an inline method or the .keybinding() method.
*/
__keybinding: function(event) {
if (isInput(event) && !this.keybindingsOnInputs) return;
for (var i = 0; i < this.matchers.length; i++) {
if (match(this.matchers[i].expectation, event)) {
if (typeof this.matchers[i].action === 'function') {
this.matchers[i].action.apply(this, [event]);
} else {
if (typeof this.keybinding !== 'function') {
throw new Error('non-function keybinding action given but no .keybinding method found on component');
/**
* Internal method: avoids firing keybindings in textareas,
* figures out if they match any of the bindings from this component,
* and then either fires an inline method or the .keybinding() method.
*/
__keybinding: function(event) {
if (isInput(event) && !this.props.keybindingsOnInputs) return;
for (var i = 0; i < this.matchers.length; i++) {
if (match(this.matchers[i].expectation, event)) {
if (typeof this.matchers[i].action === 'function') {
this.matchers[i].action.apply(this, [event]);
} else {
if (typeof this.props.keybinding !== 'function') {
throw new Error('non-function keybinding action given but no .keybinding method found on component');
}
this.props.keybinding.call(this, event, this.matchers[i].action);
}
this.keybinding(event, this.matchers[i].action);
}
}
}
},
},

/**
* When the component mounts, bind our event listener and
* add our keybindings to the global index.
*/
componentDidMount: function() {
if (this.keybindings !== undefined) {
this.matchers = parseEvents(this.keybindings, !!this.keybindingsPlatformAgnostic);
this.__boundKeybinding = this.__keybinding.bind(this);
document.addEventListener('keydown', this.__boundKeybinding);
this.__getKeybindings().push(this.keybindings);
}
},
/**
* When the component mounts, bind our event listener and
* add our keybindings to the global index.
*/
componentDidMount: function() {
if (this.props.keybindings !== undefined) {
this.matchers = parseEvents(this.props.keybindings, !!this.props.keybindingsPlatformAgnostic);
document.addEventListener('keydown', this.__keybinding);
this.__getKeybindings().push(this.props.keybindings);
}
},

/**
* When the component unmounts, unbind our event listener and
* remove our keybindings from the global index.
*/
componentWillUnmount: function() {
if (this.keybindings !== undefined && this.__boundKeybinding !== undefined) {
document.removeEventListener('keydown', this.__boundKeybinding);
this.__getKeybindings()
.splice(this.__getKeybindings().indexOf(this.keybindings), 1);
/**
* When the component unmounts, unbind our event listener and
* remove our keybindings from the global index.
*/
componentWillUnmount: function() {
if (this.props.keybindings !== undefined && this.__keybinding !== undefined) {
document.removeEventListener('keydown', this.__keybinding);
this.__getKeybindings()
.splice(this.__getKeybindings().indexOf(this.props.keybindings), 1);
}
},
render: function() {
const props = Object.assign({}, this.props, {
getAllKeybindings: this.getAllKeybindings
});
return React.createElement(WrappedComponent, props);
}
}
};
});

}

module.exports = Keybinding;
module.exports = withKeybindings;
53 changes: 29 additions & 24 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
{
"peerDependencies": {
"react": ">=0.13.0 <16.0.0"
},
"name": "@mapbox/react-keybinding",
"license": "MIT",
"author": "Tom MacWright",
"react": ">=0.15.0 <17.0.0"
},
"name": "@mapbox/react-keybinding",
"license": "MIT",
"author": "Tom MacWright",
"repository": {
"url": "git@github.com:mapbox/react-keybinding.git",
"url": "git@github.com:mapbox/react-keybinding.git",
"type": "git"
},
"version": "3.0.0",
},
"version": "3.0.0",
"scripts": {
"test": "browserify test.js | tap-closer | smokestack",
"test": "browserify test.js | tap-closer | smokestack",
"test-ff": "node test.js && browserify test.js | tap-closer | smokestack -b firefox"
},
},
"keywords": [
"keybinding",
"keybindings",
"react",
"keybinding",
"keybindings",
"react",
"react-component"
],
],
"devDependencies": {
"react": "^0.13.3",
"tap-closer": "^1.0.0",
"tape": "^3.5.0",
"happen": "^0.1.3",
"smokestack": "^3.2.0",
"browserify": "^9.0.3"
},
"main": "index.js",
"description": "declarative, concise keybindings for react"
}
"browserify": "^9.0.3",
"happen": "^0.1.3",
"react": "^16.3.2",
"react-dom": "^16.3.2",
"smokestack": "^3.2.0",
"tap-closer": "^1.0.0",
"tape": "^3.5.0"
},
"main": "index.js",
"description": "declarative, concise keybindings for react",
"dependencies": {
"create-react-class": "^15.6.3",
"prop-types": "^15.6.1"
}
}
2 changes: 1 addition & 1 deletion syntax.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ var codes = require('./src/codes');

/**
* This file generates the file SYNTAX.md, which lists
* all of the available key combinations supported by this mixin.
* all of the available key combinations supported by this HOC.
*/
function pairs(o) {
return Object.keys(o).map(function(k) { return [k, o[k]]; });
Expand Down
Loading