Simple way to bind keyboard to react with redux.
The need a fast way to bind keys to a react-powered UI with awesome redux for state management.
react-keys
requires React 15 or later
npm install react-keys
or yarn add react-keys
While not having direct dependencies, react-keys have to be used with react
, react-dom
and redux
:
yarn add react
yarn add react-dom
yarn add redux
import { keysInit, keysReducer, getCurrentBinderId } from 'react-keys';
import { createStore, combineReducers } from 'redux';
import { connect } from 'react-redux';
import PureMosaic from '../PureMosaic'; // Pure React Component
//ONE
const store = createStore(
combineReducers({
'@@keys': keysReducer, // you need to link the react-keys reducer with the @@keys id
})
);
//TWO
keysInit({ store: store }); // add the store there
//THREE
const Mosaic = connect(() => getCurrentBinderId()())(PureMosaic); // listen every changes of your mosaic like that
This is the entry point you need to define your redux store here. You can also define global behaviors
store
(redux store / mandatory) Link the redux store toreact-keys
eventCb
(function / optional) Define a callback triggered when short/long press are donedebounce
(number / optional) define a global debounce in ms, it can be overrided by components (default :10
)config
(object / optional) define keys to work with, this is the core concept for Keys components and it works as belowlongPressTouch
(array / optional) define list of keycode active for long press handling ( default :[37, 38, 39, 40]
)
The default config of react-keys
is
{
left: 37,
up: 38,
right: 39,
down: 40,
enter: 13,
}
You can get your computed config by calling like that
import { config } from 'react-keys';
const getConfig = () => config();
You can use them by adding a prop to Keys
component with the keyword on
before the key with CamelCase (onBack
, onUp
, onEnter
...)
You can extend this config by adding your own object config that will extend the default config.
react-keys
has two main Component
to deal with keyboard : Keys
and Binder
. Keys
is basically an handler for keydown
that execute a callback from a specific key
. Binder
helps you to navigate throw lists with spacial context.
You can mount has much as Keys
or Binder
you want at same time but there is just one handler triggered by key action.
By default the priority is given to the lastest Component
mounted. You can configure that by setting a priority
property on Keys
and Binder
.
That means a Binder
with priority 1
mounted before a Keys
with priority 0
(default) will still trigger the action.
When a Keys
or Binder
is unmonted
, react-keys
will figure out which Component
will take the lead.
Very basic way to handle keyboard events. Yet is pretty powerfull :-)
import { Keys } from 'react-keys';
function onBack() {
alert('alert bro');
}
const Component = ({ isActive }) => {
return (
<>
<Keys
id="rk-basic"
onBack={() => console.log('Did I just push A ?')}
active={isActive}
/>
<h1>Check that out !</h1>
</>
);
};
id
(string / mandatory) component idon${keyCode}
(function / optional) keyCode callbackactive
(boolean / optional) determine isKeys
component is active (defaulttrue
)debounce
(number / optional) define a debounce for keys press in ms (default global debounce)priority
(number / optional) define the priority from others (default0
)
A basic component that can execute a callback when observing a sequence. This component has two props:
sequence
(string / mandatory) the sequence to observe.cb
(function / mandatory) the callback to execute.
import { Catcher } from 'react-keys';
const ComponentWithCatcher = () => {
return (
<div>
<Catcher
sequence="424242"
cb={() => console.log('Yeah ! Cheat code activated')}
/>
</div>
);
};
// When 42 is enter 3 times
// It will print on console 'Yeah ! Cheat code activated'
Fancy React component to deal with space navigation. It handles communication with multiple Binder
compoments to create something great !
import { Binder, keysInit } from 'react-keys';
keysInit(); // must call it once at the app starting to enable the triggering
function renderWithId(id) {
ReactDOM.render(
<Mosaic binderId="mosaic-1" selectedId={id} />,
document.getElementById('body')
);
}
function onKey(element) {
renderWithId(element.id);
}
const Card = ({ id, active }) => {
const style = active ? 'selected' : '';
return (
<li id={id} className={style}>
#{id}
</li>
);
};
const Mosaic = ({ binderId, selectedId }) => {
return (
<Binder
id={binderId}
active={true}
onLeft={onKey}
onUp={onKey}
onDown={onKey}
onRight={onKey}
>
<ul>
<Card id={binderId + '-1'} active={selectedId === binderId + '-1'} /> //
element need a unique id
<Card id={binderId + '-2'} active={selectedId === binderId + '-2'} />
<Card id={binderId + '-3'} active={selectedId === binderId + '-3'} />
</ul>
</Binder>
);
};
renderWithId('mosaic-1-1');
id
(string / mandatory) Define the binder idactive
(boolean / optional) determine if binder is active (defaultfalse
)selector
(string / optional) DOM selector which define each element (defaultli
)priority
(number / optional) define priority among others binders when mouting (default0
)wrapper
(string / optional) DOM selector which define parent element (defaultdocument
)filter
(string / optional) class name which exclude elementdebounce
(number / optional) define a debounce for keys press in ms (default global debounce)memory
(boolean / optional) define if binder state has to be saved on unmounting (defaultfalse
)strategy
(string / optional) define strape strategy on enter:start
/mirror
/none
(defaultnone
)refreshStrategy
(string / optional) define how the focus behave on binder elements update (which element will be focused):first
/previous
(defaultfirst
)position
(string / optional) to better handle for enterStrategy (vertial/horizontal) (defaulthorizontal
)visibilityOffset
(number / optional) set an offset un pixel to determine if an element is visible or hidden (default0
)direction
(string / optional) give a hint toreact-keys
to know if the direction is mainlyhorizontal
orvertical
(defaultnone
)gap
(number / optional) reduce or increase elements margin (default0
)`boundedGap
(number / optional) reduce or increase bounded margin (default0
)triggerClick
(boolean / optional) elements will trigger a click event on enter event (defaulttrue
)`topGap
(number / optional) reduce or increase last top margin (default0
)longPress
(boolean / optional) active long press handler (defaulttrue
)rightGap
(number / optional) reduce or increase last right margin (default0
)leftGap
(number / optional) reduce or increase last left margin (default0
)downGap
(number / optional) reduce or increase last down margin (default0
)onRight
(function / optional) callback for right eventsfunction(nextElement)
onLeft
(function / optional) callback for left eventsfunction(nextElement)
onUp
(function / optional) callback for up eventsfunction(nextElement)
onDown
(function / optional) callback for down eventsfunction(nextElement)
onEnter
(function / optional) callback for enter eventsfunction(nextElement)
onRightExit
(function/string / optional) triggered when right event would go outside the elements block, it can be a function or the binder id we want to reachonLeftExit
(function/string / optional) triggered when left event would go outside the elements block, it can be a function or the binder id we want to reachonUpExit
(function/string / optional) triggered when up event would go outside the elements block, it can be a function or the binder id we want to reachonDownExit
(function/string / optional) triggered when down event would go outside the elements block, it can be a function or the binder id we want to reach
import { Binder, keysSelector } from 'react-keys';
const PureMosaic = ({ selectedId, marginTop, marginLeft }) => {
return (
<Binder
id="rk-binder"
wrapper="#myWrapper"
gap={20}
onEnter={element => console.log(`ENTER with ${element.id}`)}
>
<div id="myWrapper">
<ul style={{ marginTop: marginTop, marginLeft: marginLeft }}>
<li id="1" className={selectedId === '1' ? 'selected' : ''}>
#1
</li>
<li id="2" className={selectedId === '2' ? 'selected' : ''}>
#2
</li>
<li id="3" className={selectedId === '3' ? 'selected' : ''}>
#3
</li>
<li id="4" className={selectedId === '4' ? 'selected' : ''}>
#4
</li>
<li id="5" className={selectedId === '5' ? 'selected' : ''}>
#5
</li>
<li id="6" className={selectedId === '6' ? 'selected' : ''}>
#6
</li>
</ul>
</div>
</Binder>
);
};
const Mosaic = connect(() => ({
selectedId: getCurrentSelectedId()(),
marginTop: getBinderMarginTop('rk-binder')(),
marginLeft: getBinderMarginLeft('rk-binder')(),
}))(PureMosaic); // listen every changes of your mosaic like that
the keys store will manage the state of each binders (no matter how many they are).
Selectors give you easy control on your data binder, here few selectors for common use. they return a function
isCurrentBinder(binderId)
determine if your binder is the current binderisBinderActive(binderId)
determine if your binder is activegetBinderMarginLeft(binderId)
determine marginLeft of a bindergetBinderMarginTop(binderId)
determine marginTop of a bindergetBinderSelectedId(binderId)
determine selectedId of a bindergetCurrentSelectedId()
get selected id of current active bindergetCurrentBinder()
get state of current active bindergetCurrentBinderId()
get id of current active bindergetBinders()
get an array of all bindersgetKeyCode()
get key code currently pressedisLongPress()
determine if it is a long pressisVisibleInBinder(binderId, elementId)
determine if an element is visible inside its binder
activeBinder(binderId, selectedId(optional))
activate a new binder by giving its id (first id by default)resetBinder(binderId, selectedId(optional))
reset binder by giving its id (first id by default)updateBinder(binderState)
when you want to update the state manually (you must know what you do !)removeBinder(binderId)
remove a binder from the app state, useful when you want re-init the binder
For some reason you want sometime block a specific binder or a specific key, you can perform that with these functions. Don't forget to unblock when you block :-)
bock(values or array of values(optional))
it can be keyCode or binderId. when no argument are passed, it blocks everything.unblock(values or array of values(optional))
it can be keyCode or binderId. when no argument are passed, it unblocks everything.blockExcept(values or array of values)
it can be keyCode or binderId. Note when you want toi except a binder, you have to refers its keyCode associated
blockExcept('binderId', [
config().down,
config().up,
config().left,
config().right,
config().enter,
]);
unblockExcept(values or array of values)
it can be keyCode or binderId.
catcher(sequence, callback)
it trigger the callback when astring
sequence is catched
npm run test