[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\"es2015\", \"stage-0\", \"react\"],\n  \"plugins\": [\"add-module-exports\"],\n  \"env\": {\n    \"production\": {\n      \"presets\": [\"react-optimize\"]\n    }\n  }\n}\n"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"parser\": \"babel-eslint\",\n  \"plugins\": [\n    \"import\",\n    \"react\"\n  ],\n  \"extends\": [ \"airbnb\" ],\n  \"env\": {\n    \"browser\": true,\n    \"mocha\": true,\n    \"node\": true\n  },\n  \"rules\": {\n    \"array-bracket-spacing\": 0,\n    \"arrow-body-style\": 0,\n    \"comma-dangle\": [ 2, \"always-multiline\" ],\n    \"consistent-return\": 0,\n    \"default-case\": 0,\n    \"dot-notation\": 0,\n    \"func-names\": 0,\n    \"global-require\": 0,\n    \"import/default\": 2,\n    \"import/export\": 2,\n    \"import/imports-first\": 0,\n    \"import/named\": 2,\n    \"import/namespace\": 2,\n    \"import/no-extraneous-dependencies\": [\n      \"warn\", {\n        \"devDependencies\": true\n      }\n    ],\n    \"import/no-unresolved\": [\n      \"error\", {\n        \"commonjs\": true,\n        \"amd\": true\n      }\n    ],\n    \"import/prefer-default-export\": 1,\n    \"jsx-quotes\": 0,\n    \"new-cap\": 0,\n    \"max-len\": 0,\n    \"no-console\": 0,\n    \"no-fallthrough\": 1,\n    \"no-global-assign\": 0,\n    \"no-irregular-whitespace\": [\n      \"error\", {\n        \"skipStrings\": true,\n        \"skipTemplates\": true,\n        \"skipRegExps\": true\n      }\n    ],\n    \"no-lonely-if\": 0,\n    \"no-param-reassign\": 0,\n    \"no-shadow\": 1,\n    \"no-underscore-dangle\": 0,\n    \"no-unsafe-negation\": 0,\n    \"no-unused-expressions\": 0,\n    \"no-unused-vars\": [\n      \"warn\", {\n        \"vars\": \"all\",\n        \"args\": \"none\"\n      }\n    ],\n    \"no-use-before-define\": [\n      \"warn\", {\n        \"functions\": false,\n        \"classes\": true\n      }\n    ],\n    \"quote-props\": 0,\n    \"react/no-find-dom-node\": 1,\n    \"react/prop-types\": 1,\n    \"react/no-did-mount-set-state\": 1,\n    \"react/no-did-update-set-state\": 1,\n    \"react/prefer-stateless-function\": 0,\n    \"react/jsx-curly-spacing\": 0,\n    \"react/jsx-no-bind\": 0,\n    \"react/jsx-filename-extension\": 0,\n    \"semi\": [ 2, \"never\" ],\n  },\n  \"globals\": {\n    \"require\": false,\n    \"ga\": false\n  },\n  \"settings\": {\n    \"import/ignore\": [\n      \"node_modules\",\n      \"\\\\.json$\"\n    ],\n    \"import/resolver\": {\n      \"webpack\": {\n        \"config\": \"webpack-js.config.js\"\n      }\n    },\n    \"import/parser\": \"babel-eslint\",\n  }\n}"
  },
  {
    "path": ".gitignore",
    "content": "# OS garbage\n.DS_Store\nThumbs.db\n\n# built sources\ndist/\nlib/\n\n# npm stuff\nnode_modules/\nnpm-debug.log\ncoverage/\n"
  },
  {
    "path": ".npmignore",
    "content": ".babelrc\nexample\ntest\n*.coffee\n*.sh\n*.md\n*.yml\nwebpack.config.js\ncoverage\ndist\nsrc\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n  - \"12\"\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Petr Brzek\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "React Shortcuts\n=========\n\n**Manage keyboard shortcuts from one place.**\n\n[![Build Status](https://travis-ci.org/avocode/react-shortcuts.svg)][travis]\n\n\nIntro\n------\n\n\nManaging keyboard shortcuts can sometimes get messy. Or always, if not implemented the right way.\n\nReal problems:\n\n- You can't easily tell which shortcut is bound to which component\n- You have to write a lot of boilerplate code (`addEventListeners`, `removeEventListeners`, ...)\n- Memory leaks are a real problem if components don’t remove their listeners properly\n- Platform specific shortcuts is another headache\n- It's more difficult to implement feature like user-defined shortcuts\n- You can't easily get allthe application shortcuts and display it (e.g. in settings)\n\n\n**React shortcuts to the rescue!**\n-----------\n\nWith `react-shortcuts` you can declaratively manage shortcuts for each one of your React components.\n\n**Important parts of React Shortcuts:**\n\n- Your `keymap` definition\n- `ShortcutManager` which handles `keymap`\n- `<Shortcut>` component for handling shortcuts\n\n\nTry online demo\n-------\n\n[![Edit l40jjo48nl](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/l40jjo48nl)\n\n\nQuick tour\n----------\n\n\n#### 1. `npm install react-shortcuts`\n\n\n#### 2. **Define application shortcuts**\n\nCreate a new JS, Coffee, JSON or CSON file wherever you want (which probably is your project root). And define the shortcuts for your React component.\n\n**Keymap definition**\n\n```json\n{\n \"Namespace\": {\n   \"Action\": \"Shortcut\",\n   \"Action_2\": [\"Shortcut\", \"Shortcut\"],\n   \"Action_3\": {\n     \"osx\": \"Shortcut\",\n     \"windows\": [\"Shortcut\", \"Shortcut\"],\n     \"linux\": \"Shortcut\",\n     \"other\": \"Shortcut\"\n   }\n }\n}\n```\n\n- `Namespace` should ideally be the component’s `displayName`.\n- `Action` describes what will be happening. For example `MODAL_CLOSE`.\n- `Keyboard shortcut` can be a string, array of strings or an object which\n  specifies platform differences (Windows, OSX, Linux, other). The\n  shortcut may be composed of single keys (`a`, `6`,…), combinations\n  (`command+shift+k`) or sequences (`up up down down left right left right B A`).\n\n> **Combokeys** is used under the\n  hood for handling the shortcuts. [Read more][mousetrap] about how you can\n  specify keys.\n\n\n##### Example `keymap` definition:\n\n\n```javascript\nexport default {\n  TODO_ITEM: {\n    MOVE_LEFT: 'left',\n    MOVE_RIGHT: 'right',\n    MOVE_UP: ['up', 'w'],\n    DELETE: {\n      osx: ['command+backspace', 'k'],\n      windows: 'delete',\n      linux: 'delete',\n    },\n  },\n}\n\n```\n\nSave this file as `keymap.[js|coffee|json|cson]` and require it into your main\nfile.\n\n```javascript\nimport keymap from './keymap'\n```\n\n#### 3. Rise of the ShortcutsManager\n\nDefine your keymap in whichever supported format but in the end it must be an\nobject. `ShortcutsManager` can’t parse JSON and will certainly not be happy\nabout the situation.\n\n```javascript\nimport keymap from './keymap'\nimport { ShortcutManager } from 'react-shortcuts'\n\nconst shortcutManager = new ShortcutManager(keymap)\n\n// Or like this\n\nconst shortcutManager = new ShortcutManager()\nshortcutManager.setKeymap(keymap)\n```\n\n#### 4. Include `shortcutManager` into getChildContext of some parent component. So that `<shortcuts>` can receive it.\n\n```javascript\nclass App extends React.Component {\n  getChildContext() {\n    return { shortcuts: shortcutManager }\n  }\n}\n\nApp.childContextTypes = {\n  shortcuts: PropTypes.object.isRequired\n}\n```\n\n#### 5. Require the <shortcuts> component\n\nYou need to require the component in the file you want to use shortcuts in.\nFor example `<TodoItem>`.\n\n```javascript\nimport { Shortcuts } from `react-shortcuts`\n\nclass TodoItem extends React.Component {\n  _handleShortcuts = (action, event) => {\n    switch (action) {\n      case 'MOVE_LEFT':\n        console.log('moving left')\n        break\n      case 'MOVE_RIGHT':\n        console.log('moving right')\n        break\n      case 'MOVE_UP':\n        console.log('moving up')\n        break\n      case 'COPY':\n        console.log('copying stuff')\n        break\n    }\n  }\n\n  render() {\n    return (\n      <Shortcuts\n        name='TODO_ITEM'\n        handler={this._handleShortcuts}\n      >\n        <div>Make something amazing today</div>\n      </Shortcuts>\n    )\n  }\n}\n```\n\n> The `<Shortcuts>` component creates a `<shortcuts>` element in HTML, binds\n  listeners and adds tabIndex to the element so that it’s focusable.\n  `_handleShortcuts` is invoked when some of the defined shortcuts fire.\n\n## Custom props for `<Shortcuts>` component\n\n- `handler`: func\n  - callback function that will fire when a shortcut occurs\n- `name`: string\n  - The name of the namespace specified in keymap file\n- `tabIndex`: number\n  - Default is `-1`\n- `className`: string\n- `eventType`: string\n  - Just for gourmets (keyup, keydown, keypress)\n- `stopPropagation`: bool\n- `preventDefault`: bool\n- `targetNodeSelector`: DOM Node Selector like `body` or `.my-class`\n  - Use this one with caution. It binds listeners to the provided string instead\n  of the component.\n- `global`: bool\n  - Use this when you have some global app wide shortcuts like `CMD+Q`.\n- `isolate`: bool\n  - Use this when a child component has React's key handler (onKeyUp, onKeyPress, onKeyDown). Otherwise, React Shortcuts stops propagation of that event due to nature of event delegation that React uses internally.\n- `alwaysFireHandler`: bool\n  - Use this when you want events keep firing on the focused input elements. \n\n\n## Thanks, Atom\n\n\nThis library is inspired by [Atom Keymap].\n\n\n[Atom Keymap]: https://github.com/atom/atom-keymap/\n[travis]: https://travis-ci.org/avocode/react-shortcuts\n[mousetrap]: https://craig.is/killing/mice\n[keymaps]: https://github.com/atom/atom-keymap/\n"
  },
  {
    "path": "example/app.js",
    "content": "import React from 'react'\nimport PropTypes from 'prop-types'\nimport createClass from 'create-react-class'\nimport ReactDOMFactories from 'react-dom-factories'\n\nlet { Shortcuts } = require('../src')\n\nShortcuts = React.createFactory(Shortcuts)\nconst { button, div, h1, p } = ReactDOMFactories\n\nexport default createClass({\n  displayName: 'App',\n\n  childContextTypes: {\n    shortcuts: PropTypes.object.isRequired,\n  },\n\n  getInitialState() {\n    return { show: true, who: 'Nobody' }\n  },\n\n  getChildContext() {\n    return { shortcuts: this.props.shortcuts }\n  },\n\n  _handleShortcuts(command) {\n    switch (command) {\n      case 'MOVE_LEFT': return this.setState({ who: 'Hemingway - left' })\n      case 'DELETE': return this.setState({ who: 'Hemingway - delete' })\n      case 'MOVE_RIGHT': return this.setState({ who: 'Hemingway - right' })\n      case 'MOVE_UP': return this.setState({ who: 'Hemingway - top' })\n    }\n  },\n\n  _handleShortcuts2(command) {\n    switch (command) {\n      case 'MOVE_LEFT': return this.setState({ who: 'Franz Kafka - left' })\n      case 'DELETE': return this.setState({ who: 'Franz Kafka - delete' })\n      case 'MOVE_RIGHT': return this.setState({ who: 'Franz Kafka - right' })\n      case 'MOVE_UP': return this.setState({ who: 'Franz Kafka - top' })\n    }\n  },\n\n  _handleRoot(command) {\n    this.setState({ who: 'Root shortcuts component' })\n  },\n\n  _rebind() {\n    this.setState({ show: false })\n\n    setTimeout(() => {\n      this.setState({ show: true })\n    }, 100)\n  },\n\n  render() {\n    if (!this.state.show) {\n      return null\n    }\n\n    return (\n\n      div({ className: 'root' },\n\n        h1({ className: 'who' }, this.state.who),\n        button({ className: 'rebind', onClick: this._rebind }, 'Rebind listeners'),\n\n        Shortcuts({\n          name: this.constructor.displayName,\n          handler: this._handleShortcuts,\n          targetNodeSelector: '#app',\n          className: 'content',\n        },\n          div(null,\n            h1(null, 'Hemingway'),\n            p(null, 'Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean. A small river named Duden flows by their place and supplies it with the necessary regelialia.')\n          )\n        ),\n\n        Shortcuts({\n          name: this.constructor.displayName,\n          handler: this._handleShortcuts2,\n          stopPropagation: true,\n          className: 'content',\n        },\n\n          div(null,\n            h1(null, 'Franz Kafka'),\n            p(null, 'One morning, when Gregor Samsa woke from troubled dreams, he found himself transformed in his bed into a horrible vermin. He lay on his armour-like back, and if he lifted his head a little he could see his brown belly, slightly domed and divided by arches into stiff sections.')\n          )\n        )\n      )\n\n    )\n  },\n})\n"
  },
  {
    "path": "example/index.html",
    "content": "<!doctype html>\n<meta charset=\"utf-8\">\n\n<div id=\"app\"></div>\n\n<script src=\"/index.js\"></script>\n"
  },
  {
    "path": "example/keymap.js",
    "content": "export default {\n  App: {\n    MOVE_LEFT: 'left',\n    MOVE_RIGHT: 'right',\n    MOVE_UP: ['up', 'w'],\n    DELETE: {\n      osx: ['command+backspace', 'k'],\n      windows: 'delete',\n      linux: 'delete',\n    },\n  },\n}\n"
  },
  {
    "path": "example/main.js",
    "content": "import React from 'react'\nimport ReactDOM from 'react-dom'\nimport './main.less'\nimport keymap from './keymap'\nimport App from './app'\nimport { ShortcutManager } from '../src'\n\nconst shortcutManager = new ShortcutManager(keymap)\n\n// Just for testing\nwindow.shortcutManager = shortcutManager\n\nconst element = React.createElement(App, { shortcuts: shortcutManager })\nReactDOM.render(element, document.getElementById('app'))\n"
  },
  {
    "path": "example/main.less",
    "content": "html {\n  color: #fff;\n  background: #222;\n  line-height: 1.5;\n}\n\n.root {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  flex-direction: column;\n}\n\n.who {\n  text-align: center;\n}\n\n.content {\n  width: 400px;\n  margin: 15px auto;\n  background: #535394;\n  padding: 20px;\n  display: flex;\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"react-shortcuts\",\n  \"description\": \"React shortcuts\",\n  \"version\": \"2.1.0\",\n  \"license\": \"MIT\",\n  \"main\": \"./lib/\",\n  \"maintainers\": [\n    {\n      \"name\": \"Petr Brzek\",\n      \"email\": \"petr@avocode.com\"\n    }\n  ],\n  \"keywords\": [\n    \"react\",\n    \"react-component\",\n    \"keyboard\",\n    \"shortcuts\",\n    \"mousetrap\"\n  ],\n  \"scripts\": {\n    \"prepublish\": \"babel src/ -d lib/\",\n    \"start\": \"webpack-dev-server --hot --progress --colors\",\n    \"test\": \"mocha\"\n  },\n  \"dependencies\": {\n    \"combokeys\": \"^3.0.1\",\n    \"events\": \"^1.0.2\",\n    \"invariant\": \"^2.1.0\",\n    \"just-reduce-object\": \"^1.0.3\",\n    \"platform\": \"^1.3.0\",\n    \"prop-types\": \"^15.5.8\"\n  },\n  \"peerDependencies\": {\n    \"react\": \"^0.14.8 || ^15 || ^16\",\n    \"react-dom\": \"^0.14.8 || ^15 || ^16\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/avocode/react-shortcuts.git\",\n    \"web\": \"http://github.com/avocode/react-shortcuts\"\n  },\n  \"bugs\": {\n    \"url\": \"http://github.com/avocode/react-shortcuts/issues\"\n  },\n  \"devDependencies\": {\n    \"babel-cli\": \"^6.14.0\",\n    \"babel-core\": \"^6.14.0\",\n    \"babel-eslint\": \"^7.1.1\",\n    \"babel-loader\": \"^6.2.5\",\n    \"babel-plugin-add-module-exports\": \"^0.2.1\",\n    \"babel-polyfill\": \"^6.13.0\",\n    \"babel-preset-es2015\": \"^6.14.0\",\n    \"babel-preset-react\": \"^6.11.1\",\n    \"babel-preset-react-optimize\": \"^1.0.1\",\n    \"babel-preset-stage-0\": \"^6.5.0\",\n    \"chai\": \"^3.5.0\",\n    \"chai-enzyme\": \"^1.0.0-beta.1\",\n    \"cheerio\": \"^0.20.0\",\n    \"create-react-class\": \"^15.6.3\",\n    \"css-loader\": \"^0.15.6\",\n    \"enzyme\": \"^3.0.0\",\n    \"enzyme-adapter-react-16\": \"^1.15.1\",\n    \"eslint\": \"^3.10.2\",\n    \"eslint-config-airbnb\": \"^13.0.0\",\n    \"eslint-import-resolver-webpack\": \"^0.7.0\",\n    \"eslint-plugin-import\": \"^2.2.0\",\n    \"eslint-plugin-jsx-a11y\": \"^2.2.3\",\n    \"eslint-plugin-react\": \"^6.7.1\",\n    \"eslint-plugin-standard\": \"^2.0.1\",\n    \"istanbul\": \"^0.3.18\",\n    \"jsdom\": \"^8.0.4\",\n    \"less\": \"^2.5.1\",\n    \"less-loader\": \"^2.2.0\",\n    \"lodash\": \"^4.15.0\",\n    \"mocha\": \"^2.2.5\",\n    \"react\": \"^16\",\n    \"react-dom\": \"^16\",\n    \"react-dom-factories\": \"^1.0.2\",\n    \"simulant\": \"^0.2.2\",\n    \"sinon\": \"^1.17.5\",\n    \"sinon-chai\": \"^2.8.0\",\n    \"style-loader\": \"^0.12.3\",\n    \"webpack\": \"^1.11.0\",\n    \"webpack-dev-server\": \"^1.10.1\"\n  }\n}\n"
  },
  {
    "path": "src/component/index.js",
    "content": "module.exports = require('./shortcuts')\n"
  },
  {
    "path": "src/component/shortcuts.js",
    "content": "import React from 'react'\nimport invariant from 'invariant'\nimport Combokeys from 'combokeys'\nimport PropTypes from 'prop-types'\n\nimport helpers from '../helpers'\n\nexport default class extends React.Component {\n  static displayName = 'Shortcuts';\n\n  static contextTypes = {\n    shortcuts: PropTypes.object.isRequired,\n  };\n\n  static propTypes = {\n    children: PropTypes.node,\n    handler: PropTypes.func,\n    name: PropTypes.string,\n    tabIndex: PropTypes.number,\n    className: PropTypes.string,\n    eventType: PropTypes.string,\n    stopPropagation: PropTypes.bool,\n    preventDefault: PropTypes.bool,\n    targetNodeSelector: PropTypes.string,\n    global: PropTypes.bool,\n    isolate: PropTypes.bool,\n    alwaysFireHandler: PropTypes.bool,\n  };\n\n  static defaultProps = {\n    tabIndex: -1,\n    className: null,\n    eventType: null,\n    stopPropagation: true,\n    preventDefault: false,\n    targetNodeSelector: null,\n    global: false,\n    isolate: false,\n    alwaysFireHandler: false,\n  };\n\n  componentDidMount() {\n    this._onUpdate()\n\n    if (this.props.name) {\n      this.context.shortcuts.addUpdateListener(this._onUpdate)\n    }\n  }\n\n  componentWillUnmount() {\n    this._unbindShortcuts()\n\n    if (this.props.name) {\n      this.context.shortcuts.removeUpdateListener(this._onUpdate)\n    }\n\n    if (this.props.global) {\n      const element = this._getElementToBind()\n      element.removeEventListener(\n        'shortcuts:global',\n        this._customGlobalHandler\n      )\n    }\n  }\n\n  // NOTE: combokeys must be instance per component\n  _combokeys = null;\n\n  _lastEvent = null;\n\n  _bindShortcuts = (shortcutsArr) => {\n    const element = this._getElementToBind()\n    element.setAttribute('tabindex', this.props.tabIndex)\n    this._combokeys = new Combokeys(element, { storeInstancesGlobally: false })\n    this._decorateCombokeys()\n    this._combokeys.bind(\n      shortcutsArr,\n      this._handleShortcuts,\n      this.props.eventType\n    )\n\n    if (this.props.global) {\n      element.addEventListener('shortcuts:global', this._customGlobalHandler)\n    }\n  };\n\n  _customGlobalHandler = (e) => {\n    const { character, modifiers, event } = e.detail\n\n    let targetNode = null\n    if (this.props.targetNodeSelector) {\n      targetNode = document.querySelector(this.props.targetNodeSelector)\n    }\n\n    if (e.target !== this._domNode && e.target !== targetNode) {\n      this._combokeys.handleKey(character, modifiers, event, true)\n    }\n  };\n\n  _decorateCombokeys = () => {\n    const element = this._getElementToBind()\n    const originalHandleKey = this._combokeys.handleKey.bind(this._combokeys)\n\n    // NOTE: stopCallback is a method that is called to see\n    // if the keyboard event should fire\n    this._combokeys.stopCallback = (event, domElement, combo) => {\n      const isInputLikeElement = domElement.tagName === 'INPUT' ||\n        domElement.tagName === 'SELECT' ||\n        domElement.tagName === 'TEXTAREA' ||\n        (domElement.contentEditable && domElement.contentEditable === 'true')\n\n      let isReturnString\n      if (event.key) {\n        isReturnString = event.key.length === 1\n      } else {\n        isReturnString = Boolean(helpers.getCharacter(event))\n      }\n\n      if (\n        isInputLikeElement && isReturnString && !this.props.alwaysFireHandler\n      ) {\n        return true\n      }\n\n      return false\n    }\n\n    this._combokeys.handleKey = (\n      character,\n      modifiers,\n      event,\n      isGlobalHandler\n    ) => {\n      if (\n        this._lastEvent &&\n        event.timeStamp === this._lastEvent.timeStamp &&\n        event.type === this._lastEvent.type\n      ) {\n        return\n      }\n      this._lastEvent = event\n\n      let isolateOwner = false\n      if (this.props.isolate && !event.__isolateShortcuts) {\n        event.__isolateShortcuts = true\n        isolateOwner = true\n      }\n\n      if (!isGlobalHandler) {\n        element.dispatchEvent(\n          new CustomEvent('shortcuts:global', {\n            detail: { character, modifiers, event },\n            bubbles: true,\n            cancelable: true,\n          })\n        )\n      }\n\n      // NOTE: works normally if it's not an isolated event\n      if (!event.__isolateShortcuts) {\n        if (this.props.preventDefault) {\n          event.preventDefault()\n        }\n        if (this.props.stopPropagation && !isGlobalHandler) {\n          event.stopPropagation()\n        }\n        originalHandleKey(character, modifiers, event)\n        return\n      }\n\n      // NOTE: global shortcuts should work even for an isolated event\n      if (this.props.global || isolateOwner) {\n        originalHandleKey(character, modifiers, event)\n      }\n    }\n  };\n\n  _getElementToBind = () => {\n    let element = null\n    if (this.props.targetNodeSelector) {\n      element = document.querySelector(this.props.targetNodeSelector)\n      invariant(\n        element,\n        `Node selector '${this.props.targetNodeSelector}'  was not found.`\n      )\n    } else {\n      element = this._domNode\n    }\n\n    return element\n  };\n\n  _unbindShortcuts = () => {\n    if (this._combokeys) {\n      this._combokeys.detach()\n      this._combokeys.reset()\n    }\n  };\n\n  _onUpdate = () => {\n    const shortcutsArr = this.props.name &&\n      this.context.shortcuts.getShortcuts(this.props.name)\n    this._unbindShortcuts()\n    this._bindShortcuts(shortcutsArr || [])\n  };\n\n  _handleShortcuts = (event, keyName) => {\n    if (this.props.name) {\n      const shortcutName = this.context.shortcuts.findShortcutName(\n        keyName,\n        this.props.name\n      )\n\n      if (this.props.handler) {\n        this.props.handler(shortcutName, event)\n      }\n    }\n  };\n\n  render() {\n    return (\n      <div\n        ref={(node) => {\n          this._domNode = node\n        }}\n        tabIndex={this.props.tabIndex}\n        className={this.props.className}\n      >\n        {this.props.children}\n      </div>\n    )\n  }\n}\n"
  },
  {
    "path": "src/helpers.js",
    "content": "import platform from 'platform'\n\nconst getPlatformName = () => {\n  let os = platform.os.family || ''\n  os = os.toLowerCase().replace(/ /g, '')\n  if (/\\bwin/.test(os)) {\n    os = 'windows'\n  } else if (/darwin|osx/.test(os)) {\n    os = 'osx'\n  } else if (/linux|freebsd|sunos|ubuntu|debian|fedora|redhat|suse/.test(os)) {\n    os = 'linux'\n  } else {\n    os = 'other'\n  }\n  return os\n}\n\nconst getCharacter = (event) => {\n  if (event.which == null) {\n    // NOTE: IE\n    return String.fromCharCode(event.keyCode)\n  } else if (event.which !== 0 && event.charCode !== 0) {\n    // NOTE: the rest\n    return String.fromCharCode(event.which)\n  }\n  return null\n}\n\nexport default { getPlatformName, getCharacter }\n"
  },
  {
    "path": "src/index.js",
    "content": "module.exports = {\n  ShortcutManager: require('./shortcut-manager'),\n  Shortcuts: require('./component/'),\n}\n"
  },
  {
    "path": "src/shortcut-manager.js",
    "content": "import reduce from 'just-reduce-object'\nimport invariant from 'invariant'\nimport { EventEmitter } from 'events'\nimport helpers from './helpers'\nimport { isPlainObject, findKey, isArray, map, compact, flatten } from './utils'\n\n\nconst warning = (text) => {\n  if (process && process.env.NODE_ENV !== 'production') {\n    console.warn(text)\n  }\n}\n\nclass ShortcutManager extends EventEmitter {\n  static CHANGE_EVENT = 'shortcuts:update'\n\n  constructor(keymap = {}) {\n    super()\n    this._keymap = keymap\n  }\n\n  addUpdateListener(callback) {\n    invariant(callback,\n      'addUpdateListener: callback argument is not defined or falsy')\n    this.on(ShortcutManager.CHANGE_EVENT, callback)\n  }\n\n  removeUpdateListener(callback) {\n    this.removeListener(ShortcutManager.CHANGE_EVENT, callback)\n  }\n\n  _platformName = helpers.getPlatformName()\n\n  _parseShortcutDescriptor = (item) => {\n    if (isPlainObject(item)) {\n      return item[this._platformName]\n    }\n    return item\n  }\n\n  setKeymap(keymap) {\n    invariant(keymap,\n      'setKeymap: keymap argument is not defined or falsy.')\n    this._keymap = keymap\n    this.emit(ShortcutManager.CHANGE_EVENT)\n  }\n\n  extendKeymap(keymap) {\n    invariant(keymap,\n      'extendKeymap: keymap argument is not defined or falsy.')\n    this._keymap = Object.assign({}, this._keymap, keymap)\n    this.emit(ShortcutManager.CHANGE_EVENT)\n  }\n\n  getAllShortcuts() {\n    return this._keymap\n  }\n\n  getAllShortcutsForPlatform(platformName) {\n    const _transformShortcuts = (shortcuts) => {\n      return reduce(shortcuts, (result, keyName, keyValue) => {\n        if (isPlainObject(keyValue)) {\n          if (keyValue[platformName]) {\n            keyValue = keyValue[platformName]\n          } else {\n            result[keyName] = _transformShortcuts(keyValue)\n            return result\n          }\n        }\n\n        result[keyName] = keyValue\n        return result\n      }, {})\n    }\n\n    return _transformShortcuts(this._keymap)\n  }\n\n  getAllShortcutsForCurrentPlatform() {\n    return this.getAllShortcutsForPlatform(this._platformName)\n  }\n\n  getShortcuts(componentName) {\n    invariant(componentName,\n      'getShortcuts: name argument is not defined or falsy.')\n\n    const cursor = this._keymap[componentName]\n    if (!cursor) {\n      warning(`getShortcuts: There are no shortcuts with name ${componentName}.`)\n      return\n    }\n\n    const shortcuts = compact(flatten(map(cursor, this._parseShortcutDescriptor)))\n\n    return shortcuts\n  }\n\n  _parseShortcutKeyName(obj, keyName) {\n    const result = findKey(obj, (item) => {\n      if (isPlainObject(item)) {\n        item = item[this._platformName]\n      }\n      if (isArray(item)) {\n        const index = item.indexOf(keyName)\n        if (index >= 0) { item = item[index] }\n      }\n      return item === keyName\n    })\n\n    return result\n  }\n\n  findShortcutName(keyName, componentName) {\n    invariant(keyName,\n      'findShortcutName: keyName argument is not defined or falsy.')\n    invariant(componentName,\n      'findShortcutName: componentName argument is not defined or falsy.')\n\n    const cursor = this._keymap[componentName]\n    const result = this._parseShortcutKeyName(cursor, keyName)\n\n    return result\n  }\n}\n\n\nexport default ShortcutManager\n"
  },
  {
    "path": "src/utils.js",
    "content": "export const isArray = arr => Array.isArray(arr)\n\nexport const isPlainObject = (obj) => {\n  const isObject = typeof obj === 'object' && obj !== null && !isArray(obj)\n  if (!isObject || (obj.toString && obj.toString() !== '[object Object]')) return false\n  const proto = Object.getPrototypeOf(obj)\n  if (proto === null) {\n    return true\n  }\n  const Ctor = Object.prototype.hasOwnProperty.call(proto, 'constructor') && proto.constructor\n  return typeof Ctor === 'function' && Ctor instanceof Ctor &&\n    Function.prototype.toString.call(Ctor) === Function.prototype.toString.call(Object)\n}\n\nexport const findKey = (obj, fn) => {\n  if (!isPlainObject(obj) && !isArray(obj)) return\n\n  const keys = Object.keys(obj)\n  return keys.find(key => fn(obj[key]))\n}\n\nexport const compact = arr => arr.filter(Boolean)\n\nconst flattenOnce = (arr, recurse = true) => {\n  return arr.reduce((acc, val) => {\n    if (isArray(val) && recurse) return acc.concat(flattenOnce(val, false))\n    acc.push(val)\n    return acc\n  }, [])\n}\n\nexport const flatten = (arr) => {\n  if (!isArray(arr)) throw new Error('flatten expects an array')\n  return flattenOnce(arr)\n}\n\nexport const map = (itr, fn) => {\n  if (isArray(itr)) return itr.map(fn)\n\n  const results = []\n  const keys = Object.keys(itr)\n  const len = keys.length\n  for (let i = 0; i < len; i += 1) {\n    const key = keys[i]\n    results.push(fn(itr[key], key))\n  }\n\n  return results\n}\n"
  },
  {
    "path": "test/keymap.js",
    "content": "export default {\n  'Test': {\n    MOVE_LEFT: 'left',\n    MOVE_RIGHT: 'right',\n    MOVE_UP: ['up', 'w'],\n    DELETE: {\n      osx: 'alt+backspace',\n      windows: 'delete',\n      linux: 'alt+backspace',\n      other: 'alt+backspace',\n    },\n  },\n  'Next': {\n    OPEN: 'alt+o',\n    ABORT: ['d', 'c'],\n    CLOSE: {\n      osx: ['esc', 'enter'],\n      windows: ['esc', 'enter'],\n      linux: ['esc', 'enter'],\n      other: ['esc', 'enter'],\n    },\n  },\n  'TESTING': {\n    'OPEN': 'enter',\n    'CLOSE': 'esc',\n  },\n  'NON-EXISTING': {},\n}\n"
  },
  {
    "path": "test/mocha.opts",
    "content": "--compilers js:babel-core/register\n--recursive\n"
  },
  {
    "path": "test/shortcut-manager.spec.js",
    "content": "import jsdom from 'jsdom'\nimport chai from 'chai'\nimport _ from 'lodash'\nimport sinonChai from 'sinon-chai'\nimport sinon from 'sinon'\n\nimport keymap from './keymap'\n\nchai.use(sinonChai)\n\nconst { expect } = chai\n\ndescribe('Shortcut manager', () => {\n  let ShortcutManager = null\n\n  before(() => {\n    global.document = jsdom.jsdom('<html><body></body></html>')\n    global.window = document.defaultView\n    global.Image = window.Image\n    global.navigator = window.navigator\n    global.CustomEvent = window.CustomEvent\n\n    ShortcutManager = require('../src').ShortcutManager\n  })\n\n  it('should return empty object when calling empty constructor', () => {\n    const manager = new ShortcutManager()\n    expect(manager.getAllShortcuts()).to.be.empty\n  })\n\n  it('should return all shortcuts', () => {\n    const manager = new ShortcutManager(keymap)\n    expect(manager.getAllShortcuts()).to.not.be.empty\n    expect(manager.getAllShortcuts()).to.be.equal(keymap)\n\n    manager.setKeymap({})\n    expect(manager.getAllShortcuts()).to.be.empty\n\n    manager.setKeymap(keymap)\n    expect(manager.getAllShortcuts()).to.be.equal(keymap)\n  })\n\n  it('should return all shortcuts for the Windows platform', () => {\n    const manager = new ShortcutManager(keymap)\n    const keyMapResult = {\n      'Test': {\n        MOVE_LEFT: 'left',\n        MOVE_RIGHT: 'right',\n        MOVE_UP: ['up', 'w'],\n        DELETE: 'delete',\n      },\n      'Next': {\n        OPEN: 'alt+o',\n        ABORT: ['d', 'c'],\n        CLOSE: ['esc', 'enter'],\n      },\n      'TESTING': {\n        'OPEN': 'enter',\n        'CLOSE': 'esc',\n      },\n      'NON-EXISTING': {},\n    }\n\n    expect(manager.getAllShortcutsForPlatform('windows')).to.eql(keyMapResult)\n  })\n\n  it('should return all shortcuts for the macOs platform', () => {\n    const manager = new ShortcutManager(keymap)\n    const keyMapResult = {\n      'Test': {\n        MOVE_LEFT: 'left',\n        MOVE_RIGHT: 'right',\n        MOVE_UP: ['up', 'w'],\n        DELETE: 'alt+backspace',\n      },\n      'Next': {\n        OPEN: 'alt+o',\n        ABORT: ['d', 'c'],\n        CLOSE: ['esc', 'enter'],\n      },\n      'TESTING': {\n        'OPEN': 'enter',\n        'CLOSE': 'esc',\n      },\n      'NON-EXISTING': {},\n    }\n\n    expect(manager.getAllShortcutsForPlatform('osx')).to.eql(keyMapResult)\n  })\n\n  it('should expose the change event type as a static constant', () =>\n    expect(ShortcutManager.CHANGE_EVENT).to.exist\n  )\n\n  it('should have static CHANGE_EVENT', () =>\n    expect(ShortcutManager.CHANGE_EVENT).to.be.equal('shortcuts:update')\n  )\n\n  it('should call onUpdate', () => {\n    const manager = new ShortcutManager()\n    const spy = sinon.spy()\n    manager.addUpdateListener(spy)\n    manager.setKeymap({})\n    expect(spy).to.have.beenCalled\n  })\n\n  it('should throw an error when setKeymap is called without arg', () => {\n    const manager = new ShortcutManager(keymap)\n    const error = /setKeymap: keymap argument is not defined or falsy./\n    expect(manager.setKeymap).to.throw(error)\n  })\n\n  it('should extend the keymap', () => {\n    const manager = new ShortcutManager()\n    const newKeymap = { 'TESTING-NAMESPACE': {} }\n    const extendedKeymap = Object.assign({}, keymap, newKeymap)\n    manager.setKeymap(keymap)\n    manager.extendKeymap(newKeymap)\n\n    expect(manager.getAllShortcuts()).to.eql(extendedKeymap)\n  })\n\n  it('should return array of shortcuts', () => {\n    const manager = new ShortcutManager(keymap)\n    let shortcuts = manager.getShortcuts('Test')\n    expect(shortcuts).to.be.an.array\n\n    let shouldContainStrings = _.every(shortcuts, _.isString)\n    expect(shouldContainStrings).to.be.equal(true)\n    expect(shortcuts.length).to.be.equal(5)\n\n    shortcuts = manager.getShortcuts('Next')\n    expect(shortcuts).to.be.an.array\n    shouldContainStrings = _.every(shortcuts, _.isString)\n    expect(shouldContainStrings).to.be.equal(true)\n    expect(shortcuts.length).to.be.equal(5)\n  })\n\n  it('should not throw an error when getting not existing key from keymap', () => {\n    const manager = new ShortcutManager(keymap)\n    const notExist = () => manager.getShortcuts('NotExist')\n    expect(notExist).to.not.throw()\n  })\n\n  it('should return correct key label', () => {\n    const manager = new ShortcutManager()\n    manager.setKeymap(keymap)\n\n    // Test\n    expect(manager.findShortcutName('alt+backspace', 'Test')).to.be.equal('DELETE')\n    expect(manager.findShortcutName('w', 'Test')).to.be.equal('MOVE_UP')\n    expect(manager.findShortcutName('up', 'Test')).to.be.equal('MOVE_UP')\n    expect(manager.findShortcutName('left', 'Test')).to.be.equal('MOVE_LEFT')\n    expect(manager.findShortcutName('right', 'Test')).to.be.equal('MOVE_RIGHT')\n\n    // Next\n    expect(manager.findShortcutName('alt+o', 'Next')).to.be.equal('OPEN')\n    expect(manager.findShortcutName('d', 'Next')).to.be.equal('ABORT')\n    expect(manager.findShortcutName('c', 'Next')).to.be.equal('ABORT')\n    expect(manager.findShortcutName('esc', 'Next')).to.be.equal('CLOSE')\n    expect(manager.findShortcutName('enter', 'Next')).to.be.equal('CLOSE')\n  })\n\n  it('should throw an error', () => {\n    const manager = new ShortcutManager()\n    const fn = () => manager.findShortcutName('left')\n    expect(manager.findShortcutName).to.throw(/findShortcutName: keyName argument is not defined or falsy./)\n    expect(fn).to.throw(/findShortcutName: componentName argument is not defined or falsy./)\n  })\n})\n"
  },
  {
    "path": "test/shortcuts.spec.js",
    "content": "import ReactDOMFactories from 'react-dom-factories'\nimport jsdom from 'jsdom'\nimport chai from 'chai'\nimport sinonChai from 'sinon-chai'\nimport sinon from 'sinon'\nimport _ from 'lodash'\nimport enzyme from 'enzyme'\nimport Adapter from 'enzyme-adapter-react-16'\nimport keymap from './keymap'\n\nenzyme.configure({ adapter: new Adapter() })\n\ndescribe('Shortcuts component', () => {\n  let baseProps = null\n  let baseContext = null\n\n  let simulant = null\n  let ShortcutManager = null\n  let Shortcuts = null\n  let ReactDOM = null\n  let React = null\n\n  chai.use(sinonChai)\n  const { expect } = chai\n\n  beforeEach(() => {\n    global.document = jsdom.jsdom('<html><body></body></html>')\n    global.window = document.defaultView\n    global.Image = window.Image\n    global.navigator = window.navigator\n    global.CustomEvent = window.CustomEvent\n    simulant = require('simulant')\n    ReactDOM = require('react-dom')\n    React = require('react')\n    const chaiEnzyme = require('chai-enzyme')\n\n    chai.use(chaiEnzyme())\n\n    ShortcutManager = require('../src').ShortcutManager\n    const shortcutsManager = new ShortcutManager(keymap)\n\n    Shortcuts = require('../src/').Shortcuts\n\n    baseProps = {\n      handler: sinon.spy(),\n      name: 'TESTING',\n      className: null,\n    }\n    baseContext = { shortcuts: shortcutsManager }\n  })\n\n  it('should render component', () => {\n    const shortcutComponent = React.createElement(Shortcuts, baseProps)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.find('div')).to.have.length(1)\n  })\n\n  it('should have a tabIndex of -1 by default', () => {\n    let shortcutComponent = React.createElement(Shortcuts, baseProps)\n    let wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().tabIndex).to.be.equal(-1)\n\n    const props = _.assign({}, baseProps, { tabIndex: 42 })\n    shortcutComponent = React.createElement(Shortcuts, props)\n    wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().tabIndex).to.be.equal(props.tabIndex)\n    let realTabIndex = ReactDOM.findDOMNode(wrapper.instance()).getAttribute('tabindex')\n    expect(realTabIndex).to.have.equal(String(props.tabIndex))\n\n    props.tabIndex = 0\n    shortcutComponent = React.createElement(Shortcuts, props)\n    wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().tabIndex).to.be.equal(props.tabIndex)\n    realTabIndex = ReactDOM.findDOMNode(wrapper.instance()).getAttribute('tabindex')\n    expect(realTabIndex).to.have.equal(String(props.tabIndex))\n  })\n\n  it('should not have className by default', () => {\n    const shortcutComponent = React.createElement(Shortcuts, baseProps)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().className).to.be.equal(null)\n  })\n\n  it('should have className', () => {\n    const props = _.assign({}, baseProps, { className: 'testing' })\n    const shortcutComponent = React.createElement(Shortcuts, props)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().className).to.be.equal('testing')\n    expect(wrapper).to.have.className('testing')\n  })\n\n  it('should have isolate prop set to false by default', () => {\n    const shortcutComponent = React.createElement(Shortcuts, baseProps)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().isolate).to.be.equal(false)\n  })\n\n  it('should NOT store combokeys instances on Combokeys constructor', () => {\n    const shortcutComponent = React.createElement(Shortcuts, baseProps)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.find('Shortcuts').instance()._combokeys.constructor.instances).to.be.empty\n  })\n\n  it('should have isolate prop', () => {\n    const props = _.assign({}, baseProps, { isolate: true })\n    const shortcutComponent = React.createElement(Shortcuts, props)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().isolate).to.be.equal(true)\n  })\n\n  it('should not have children by default', () => {\n    const shortcutComponent = React.createElement(Shortcuts, baseProps)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().children).to.be.equal(undefined)\n  })\n\n  it('should have children', () => {\n    const props = _.assign({}, baseProps, { children: ReactDOMFactories.div() })\n    const shortcutComponent = React.createElement(Shortcuts, props)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper).to.contain(ReactDOMFactories.div())\n  })\n\n  it('should have handler prop', () => {\n    const shortcutComponent = React.createElement(Shortcuts, baseProps)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().handler).to.be.function\n  })\n\n  it('should have name prop', () => {\n    const props = _.assign({}, baseProps,\n      { name: 'TESTING' })\n    const shortcutComponent = React.createElement(Shortcuts, props)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().name).to.be.equal('TESTING')\n  })\n\n  it('should not have eventType prop by default', () => {\n    const shortcutComponent = React.createElement(Shortcuts, baseProps)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().eventType).to.be.equal(null)\n  })\n\n  it('should have eventType prop', () => {\n    const props = _.assign({}, baseProps, { eventType: 'keyUp' })\n    const shortcutComponent = React.createElement(Shortcuts, props)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().eventType).to.be.equal('keyUp')\n  })\n\n  it('should have stopPropagation prop by default', () => {\n    const shortcutComponent = React.createElement(Shortcuts, baseProps)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().stopPropagation).to.be.equal(true)\n  })\n\n  it('should have stopPropagation prop set to false', () => {\n    const props = _.assign({}, baseProps, { stopPropagation: false })\n    const shortcutComponent = React.createElement(Shortcuts, props)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().stopPropagation).to.be.equal(false)\n  })\n\n  it('should have preventDefault prop set to false by default', () => {\n    const shortcutComponent = React.createElement(Shortcuts, baseProps)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().preventDefault).to.be.equal(false)\n  })\n\n  it('should have preventDefault prop set to true', () => {\n    const props = _.assign({}, baseProps, { preventDefault: true })\n    const shortcutComponent = React.createElement(Shortcuts, props)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().preventDefault).to.be.equal(true)\n  })\n\n  it('should not have targetNodeSelector prop by default', () => {\n    const shortcutComponent = React.createElement(Shortcuts, baseProps)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().targetNodeSelector).to.be.equal(null)\n  })\n\n  it('should have targetNode prop', () => {\n    const props = _.assign({}, baseProps, { targetNodeSelector: 'body' })\n    const shortcutComponent = React.createElement(Shortcuts, props)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().targetNodeSelector).to.be.equal('body')\n  })\n\n  it('should have global prop set to false by default', () => {\n    const shortcutComponent = React.createElement(Shortcuts, baseProps)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().global).to.be.equal(false)\n  })\n\n  it('should have global prop set to true', () => {\n    const props = _.assign({}, baseProps, { global: true })\n    const shortcutComponent = React.createElement(Shortcuts, props)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    expect(wrapper.props().global).to.be.equal(true)\n  })\n\n  it('should fire the handler prop with the correct argument', () => {\n    const shortcutComponent = React.createElement(Shortcuts, baseProps)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    const node = ReactDOM.findDOMNode(wrapper.instance())\n    node.focus()\n\n    const enter = 13\n    simulant.fire(node, 'keydown', { keyCode: enter })\n\n    expect(wrapper.props().handler).to.have.been.calledWith('OPEN')\n\n    const esc = 27\n    simulant.fire(node, 'keydown', { keyCode: esc })\n\n    expect(wrapper.props().handler).to.have.been.calledWith('CLOSE')\n  })\n\n  it('should not fire the handler', () => {\n    const props = _.assign({}, baseProps, { name: 'NON-EXISTING' })\n    const shortcutComponent = React.createElement(Shortcuts, props)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    const node = ReactDOM.findDOMNode(wrapper.instance())\n    node.focus()\n\n    const enter = 13\n    simulant.fire(node, 'keydown', { keyCode: enter })\n\n    expect(wrapper.props().handler).to.not.have.been.called\n  })\n\n  it('should not fire twice when global prop is truthy', () => {\n    const props = _.assign({}, baseProps, { global: true })\n    const shortcutComponent = React.createElement(Shortcuts, props)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    const node = ReactDOM.findDOMNode(wrapper.instance())\n    node.focus()\n\n    const enter = 13\n    simulant.fire(node, 'keydown', { keyCode: enter })\n\n    expect(wrapper.props().handler).to.have.been.calledOnce\n  })\n\n  it('should not fire when the component has been unmounted', () => {\n    const handler = sinon.spy()\n    const shortcutComponent = React.createElement(Shortcuts, { ...baseProps, handler })\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    const node = ReactDOM.findDOMNode(wrapper.instance())\n    node.focus()\n\n    wrapper.unmount()\n\n    const enter = 13\n    simulant.fire(node, 'keydown', { keyCode: enter })\n\n    expect(handler).to.not.have.been.called\n  })\n\n  it.skip('should update the shortcuts and fire the handler', () => {\n    const shortcutComponent = React.createElement(Shortcuts, baseProps)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    const node = ReactDOM.findDOMNode(wrapper.instance())\n    node.focus()\n\n    const space = 32\n    simulant.fire(node, 'keydown', { keyCode: space })\n\n    expect(wrapper.props().handler).to.not.have.been.called\n\n    const editedKeymap = _.assign({}, keymap, {\n      'TESTING': {\n        'SPACE': 'space',\n      },\n    }\n    )\n    baseContext.shortcuts.setKeymap(editedKeymap)\n\n    simulant.fire(node, 'keydown', { keyCode: space })\n\n    expect(baseProps.handler).to.have.been.called\n\n    // NOTE: rollback the previous keymap\n    baseContext.shortcuts.setKeymap(keymap)\n  })\n\n  it('should fire the handler from a child input', () => {\n    const props = _.assign({}, baseProps, {\n      children: ReactDOMFactories.input({ type: 'text', className: 'input' }),\n    })\n    const shortcutComponent = React.createElement(Shortcuts, props)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    const parentNode = ReactDOM.findDOMNode(wrapper.instance())\n    const node = parentNode.querySelector('.input')\n    node.focus()\n\n    const enter = 13\n    simulant.fire(node, 'keydown', { keyCode: enter, key: 'Enter' })\n\n    expect(wrapper.props().handler).to.have.been.called\n  })\n\n  it('should fire the handler when using targetNodeSelector', () => {\n    const props = _.assign({}, baseProps, { targetNodeSelector: 'body' })\n    const shortcutComponent = React.createElement(Shortcuts, props)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    const enter = 13\n    simulant.fire(document.body, 'keydown', { keyCode: enter, key: 'Enter' })\n\n    expect(wrapper.props().handler).to.have.been.called\n  })\n\n  it('should throw and error if targetNodeSelector is not found', () => {\n    const props = _.assign({}, baseProps, { targetNodeSelector: 'non-existing' })\n    const shortcutComponent = React.createElement(Shortcuts, props)\n\n    try {\n      enzyme.mount(shortcutComponent, { context: baseContext })\n    } catch (err) {\n      expect(err).to.match(/Node selector 'non-existing' {2}was not found/)\n    }\n  })\n\n  it('should fire the handler from focused input', () => {\n    const props = _.assign({}, baseProps, {\n      alwaysFireHandler: true,\n      children: ReactDOMFactories.input({ type: 'text', className: 'input' }),\n    })\n    const shortcutComponent = React.createElement(Shortcuts, props)\n    const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n    const parentNode = ReactDOM.findDOMNode(wrapper.instance())\n    const node = parentNode.querySelector('.input')\n    node.focus()\n\n    const enter = 13\n    simulant.fire(node, 'keydown', { keyCode: enter })\n\n    expect(wrapper.props().handler).to.have.been.called\n  })\n\n\n  describe('Shortcuts component inside Shortcuts component:', () => {\n    it('should not fire parent handler when child handler is fired', () => {\n      const props = _.assign({}, baseProps, {\n        children: React.createElement(Shortcuts, _.assign({}, baseProps, { className: 'test' })),\n      })\n      const shortcutComponent = React.createElement(Shortcuts, props)\n      const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n      const parentNode = ReactDOM.findDOMNode(wrapper.instance())\n      const node = parentNode.querySelector('.test')\n\n      node.focus()\n\n      const enter = 13\n      simulant.fire(node, 'keydown', { keyCode: enter })\n\n      expect(baseProps.handler).to.have.been.calledOnce\n    })\n\n    it('should fire parent handler when child handler is fired', () => {\n      const props = _.assign({}, baseProps, {\n        children: React.createElement(Shortcuts, _.assign({}, baseProps, { className: 'test', stopPropagation: false })),\n      })\n      const shortcutComponent = React.createElement(Shortcuts, props)\n      const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n      const parentNode = ReactDOM.findDOMNode(wrapper.instance())\n      const node = parentNode.querySelector('.test')\n\n      node.focus()\n\n      const enter = 13\n      simulant.fire(node, 'keydown', { keyCode: enter })\n\n      expect(baseProps.handler).to.have.been.calledTwice\n    })\n\n    it('should fire parent handler when parent handler has global prop', () => {\n      const props = _.assign({}, baseProps, {\n        children: React.createElement(Shortcuts, _.assign({}, baseProps, { className: 'test' })),\n        global: true,\n      })\n\n      const shortcutComponent = React.createElement(Shortcuts, props)\n      const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n      const parentNode = ReactDOM.findDOMNode(wrapper.instance())\n      const node = parentNode.querySelector('.test')\n\n      node.focus()\n\n      const enter = 13\n      simulant.fire(node, 'keydown', { keyCode: enter })\n\n      expect(baseProps.handler).to.have.been.calledTwice\n    })\n\n    it('should fire parent handler but not the child handler', () => {\n      const props = _.assign({}, baseProps, {\n        children: React.createElement(Shortcuts, _.assign({}, baseProps, { name: 'NON-EXISTING', className: 'test' })),\n        global: true,\n      })\n\n      const shortcutComponent = React.createElement(Shortcuts, props)\n      const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n      const parentNode = ReactDOM.findDOMNode(wrapper.instance())\n      const node = parentNode.querySelector('.test')\n\n      node.focus()\n\n      const enter = 13\n      simulant.fire(node, 'keydown', { keyCode: enter })\n\n      expect(baseProps.handler).to.have.been.calledOnce\n    })\n\n    it('should fire for all global components', () => {\n      const props = _.assign({}, baseProps, {\n        children: React.createElement(Shortcuts, _.assign({}, baseProps, {\n          global: true,\n          children: React.createElement(Shortcuts, _.assign({}, baseProps, { name: 'NON-EXISTING', className: 'test' })),\n        })),\n        global: true,\n      })\n\n      const shortcutComponent = React.createElement(Shortcuts, props)\n      const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n      const parentNode = ReactDOM.findDOMNode(wrapper.instance())\n      const node = parentNode.querySelector('.test')\n\n      node.focus()\n\n      const enter = 13\n      simulant.fire(node, 'keydown', { keyCode: enter })\n\n      expect(baseProps.handler).to.have.been.calledTwice\n    })\n\n    it('should not fire parent handler when a child has isolate prop set to true', () => {\n      const childHandlerSpy = sinon.spy()\n      const props = _.assign({}, baseProps, {\n        children: React.createElement(Shortcuts, _.assign({}, baseProps, {\n          className: 'test',\n          isolate: true,\n          handler: childHandlerSpy,\n        })),\n      })\n\n      const shortcutComponent = React.createElement(Shortcuts, props)\n      const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n      const parentNode = ReactDOM.findDOMNode(wrapper.instance())\n      const node = parentNode.querySelector('.test')\n\n      node.focus()\n\n      const enter = 13\n      simulant.fire(node, 'keydown', { keyCode: enter })\n\n      expect(childHandlerSpy).to.have.been.called\n      expect(baseProps.handler).to.not.have.been.called\n    })\n\n    it('should fire parent handler when is global and a child has isolate prop set to true', () => {\n      const props = _.assign({}, baseProps, {\n        global: true,\n        children: React.createElement(Shortcuts, _.assign({}, baseProps, { className: 'test', isolate: true })),\n      })\n\n      const shortcutComponent = React.createElement(Shortcuts, props)\n      const wrapper = enzyme.mount(shortcutComponent, { context: baseContext })\n\n      const parentNode = ReactDOM.findDOMNode(wrapper.instance())\n      const node = parentNode.querySelector('.test')\n\n      node.focus()\n\n      const enter = 13\n      simulant.fire(node, 'keydown', { keyCode: enter })\n\n      expect(baseProps.handler).to.have.been.called\n    })\n  })\n})\n"
  },
  {
    "path": "test/utils.js",
    "content": "import chai from 'chai'\nimport _ from 'lodash'\nimport { isArray, isPlainObject, findKey, compact, flatten, map } from '../src/utils'\n\ndescribe('utils', () => {\n  const { expect } = chai\n  let primitives\n\n  beforeEach(() => {\n    function fn() { this.a = 1 }\n\n    primitives = [\n      ['array'],\n      { object: true },\n      Object.create(null),\n      'string',\n      null,\n      undefined,\n      NaN,\n      new Map([[ 1, 'one' ], [ 2, 'two' ]]),\n      new fn(),\n      true,\n      42,\n    ]\n  })\n\n  describe('isArray', () => {\n    it('should be true for arrays', () => {\n      primitives.forEach((val, idx) => {\n        if (idx === 0) {\n          expect(isArray(val)).to.be.true\n          expect(_.isArray(val)).to.be.true\n        } else {\n          expect(isArray(val)).to.be.false\n          expect(_.isArray(val)).to.be.false\n        }\n      })\n    })\n  })\n\n  describe('isPlainObject', () => {\n    it('should be true for plain objects', () => {\n      primitives.forEach((val, idx) => {\n        if (idx === 1 || idx === 2) {\n          expect(isPlainObject(val)).to.be.true\n          expect(_.isPlainObject(val)).to.be.true\n        } else {\n          expect(isPlainObject(val)).to.be.false\n          expect(_.isPlainObject(val)).to.be.false\n        }\n      })\n    })\n  })\n\n  describe('findKey', () => {\n    it('should return the matching key', () => {\n      const obj = {\n        simple: 1,\n        obj: {\n          val: 4,\n        },\n      }\n\n      const checkOne = val => val === 1\n      const checkTwo = val => typeof val === 'object'\n\n      expect(findKey(obj, checkOne)).to.deep.equal(_.findKey(obj, checkOne))\n      expect(findKey(obj, checkTwo)).to.deep.equal(_.findKey(obj, checkTwo))\n    })\n  })\n\n  describe('compact', () => {\n    it('removes falsy values', () => {\n      const values = [\n        true,\n        false,\n        10,\n        0,\n        null,\n        undefined,\n        NaN,\n        '',\n        'false, null, 0, \"\", undefined, and NaN are falsy',\n      ]\n\n      expect(compact(values)).to.deep.equal(_.compact(values))\n    })\n  })\n\n  describe('flatten', () => {\n    it('flattens an array 1 level', () => {\n      const value = [1, [2, [3, [4]], 5, [[[6], 7], 8], 9]]\n      expect(flatten(value)).to.deep.equal(_.flatten(value))\n    })\n  })\n\n  describe('map', () => {\n    it('should map an array', () => {\n      const values = [1, 2, 3, 4]\n      const mapFn = val => val * 10\n\n      expect(map(values, mapFn)).to.deep.equal(_.map(values, mapFn))\n      expect(map(values, mapFn)).to.deep.equal([10, 20, 30, 40])\n\n      // ensure that values array is not mutated\n      expect(values).to.deep.equal([1, 2, 3, 4])\n    })\n\n    it('should map an object', () => {\n      const obj = {\n        one: 1,\n        two: 2,\n        three: 3,\n      }\n      const mapFn = (val, key) => `${key} - ${val * 10}`\n\n      expect(map(obj, mapFn)).to.deep.equal(_.map(obj, mapFn))\n      expect(map(obj, mapFn)).to.deep.equal([\n        'one - 10',\n        'two - 20',\n        'three - 30',\n      ])\n\n      // ensure the object was not mutated\n      expect(obj).to.deep.equal({\n        one: 1,\n        two: 2,\n        three: 3,\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "webpack.config.js",
    "content": "const webpack = require('webpack')\n\nmodule.exports = {\n  entry: [\n    'webpack-dev-server/client?http://localhost:8080',\n    'webpack/hot/dev-server',\n    `${__dirname}/example/main.js`,\n  ],\n  devtool: 'inline-source-map',\n  debug: true,\n  output: {\n    path: `${__dirname}/dist`,\n    filename: 'index.js',\n  },\n  resolve: {\n    extensions: ['', '.js'],\n  },\n  resolveLoader: {\n    modulesDirectories: ['node_modules'],\n  },\n  plugins: [\n    new webpack.HotModuleReplacementPlugin(),\n  ],\n  module: {\n    loaders: [\n      {\n        test: /\\.less$/,\n        loader: 'style-loader!css-loader!less-loader',\n      },\n      {\n        test: /\\.js$/,\n        exclude: /(node_modules|bower_components)/,\n        loader: 'babel',\n      },\n    ],\n    noParse: /\\.min\\.js/,\n  },\n}\n"
  }
]