├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── main.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── biome.json ├── package-lock.json ├── package.json ├── packages ├── documentation │ ├── .gitignore │ ├── README.md │ ├── babel.config.js │ ├── blog │ │ └── 2021-09-24-new-documentation.md │ ├── docs │ │ ├── api │ │ │ ├── _category_.json │ │ │ ├── is-hotkey-pressed.mdx │ │ │ ├── use-hotkeys.mdx │ │ │ └── use-record-hotkeys.mdx │ │ ├── documentation │ │ │ ├── _category_.json │ │ │ ├── advanced-usage.mdx │ │ │ ├── hotkeys-provider.mdx │ │ │ ├── installation.mdx │ │ │ ├── is-hotkey-pressed.mdx │ │ │ ├── typescript.mdx │ │ │ ├── use-record-hotkeys.mdx │ │ │ └── useHotkeys │ │ │ │ ├── _category_.json │ │ │ │ ├── basic-usage.mdx │ │ │ │ ├── disable-hotkeys.mdx │ │ │ │ ├── ignore-layouts.mdx │ │ │ │ ├── scoping-hotkeys.mdx │ │ │ │ └── setting-callback-dependencies.mdx │ │ ├── intro.mdx │ │ └── migrate-to-5.mdx │ ├── docusaurus.config.js │ ├── package.json │ ├── sidebars.js │ ├── src │ │ ├── components │ │ │ ├── HomepageFeatures │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ └── UnreferencedHotkeysNotice.tsx │ │ ├── css │ │ │ └── custom.css │ │ ├── pages │ │ │ ├── index.module.css │ │ │ ├── index.tsx │ │ │ └── markdown-page.md │ │ └── theme │ │ │ └── ReactLiveScope │ │ │ └── index.ts │ ├── static │ │ ├── .nojekyll │ │ └── img │ │ │ ├── battery-full-solid.svg │ │ │ ├── favicon.ico │ │ │ ├── logo.svg │ │ │ ├── react.svg │ │ │ └── typescript.svg │ ├── tsconfig.json │ ├── versioned_docs │ │ ├── version-3.x │ │ │ ├── api │ │ │ │ ├── _category_.json │ │ │ │ ├── is-hotkey-pressed.mdx │ │ │ │ ├── use-hotkeys.mdx │ │ │ │ └── use-is-hotkey-pressed.mdx │ │ │ ├── documentation │ │ │ │ ├── _category_.json │ │ │ │ ├── advanced-usage.mdx │ │ │ │ ├── installation.mdx │ │ │ │ ├── is-hotkey-pressed.mdx │ │ │ │ ├── typescript.mdx │ │ │ │ └── useHotkeys │ │ │ │ │ ├── _category_.json │ │ │ │ │ ├── basic-usage.mdx │ │ │ │ │ ├── disable-hotkeys.mdx │ │ │ │ │ ├── scoping-hotkeys.mdx │ │ │ │ │ └── setting-callback-dependencies.mdx │ │ │ └── intro.mdx │ │ └── version-4.x │ │ │ ├── api │ │ │ ├── _category_.json │ │ │ ├── is-hotkey-pressed.mdx │ │ │ ├── use-hotkeys.mdx │ │ │ └── use-record-hotkeys.mdx │ │ │ ├── documentation │ │ │ ├── _category_.json │ │ │ ├── advanced-usage.mdx │ │ │ ├── hotkeys-provider.mdx │ │ │ ├── installation.mdx │ │ │ ├── is-hotkey-pressed.mdx │ │ │ ├── typescript.mdx │ │ │ ├── use-record-hotkeys.mdx │ │ │ └── useHotkeys │ │ │ │ ├── _category_.json │ │ │ │ ├── basic-usage.mdx │ │ │ │ ├── disable-hotkeys.mdx │ │ │ │ ├── scoping-hotkeys.mdx │ │ │ │ └── setting-callback-dependencies.mdx │ │ │ ├── intro.mdx │ │ │ └── migrate-to-4.mdx │ ├── versioned_sidebars │ │ ├── version-3.x-sidebars.json │ │ └── version-4.x-sidebars.json │ ├── versions.json │ └── yarn.lock └── react-hotkeys-hook │ ├── .gitignore │ ├── README.md │ ├── eslint.config.js │ ├── package.json │ ├── src │ ├── lib │ │ ├── BoundHotkeysProxyProvider.tsx │ │ ├── HotkeysProvider.tsx │ │ ├── deepEqual.ts │ │ ├── index.ts │ │ ├── isHotkeyPressed.ts │ │ ├── parseHotkeys.ts │ │ ├── types.ts │ │ ├── useDeepEqualMemo.ts │ │ ├── useHotkeys.ts │ │ ├── useRecordHotkeys.ts │ │ └── validators.ts │ ├── test │ │ ├── HotkeysProvider.test.tsx │ │ ├── isHotkeyPressed.test.tsx │ │ ├── useHotkeys.test.tsx │ │ └── useRecordHotkeys.test.tsx │ └── vite-env.d.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── vitest.setup.ts └── renovate.json /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | Please try and add a codesandbox or stackblitz to reproduce the bug: 21 | <-- Link to reproducable --> 22 | 23 | Here is a codesandbox template that you can use to recreate a minimal example 24 | https://codesandbox.io/p/sandbox/adoring-shape-bzvk53 25 | 26 | **Expected behavior** 27 | A clear and concise description of what you expected to happen. 28 | 29 | **Screenshots** 30 | If applicable, add screenshots to help explain your problem. 31 | 32 | **Desktop (please complete the following information):** 33 | - OS: [e.g. iOS] 34 | - Browser [e.g. chrome, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | node-version: [22.x] 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | 26 | - name: Install dependencies 27 | run: npm install --frozen-lockfile 28 | 29 | - name: Lint 30 | run: npm run lint 31 | env: 32 | CI: true 33 | 34 | - name: Test 35 | run: npm run test 36 | env: 37 | CI: true 38 | 39 | - name: Build 40 | run: npm run build 41 | env: 42 | CI: true 43 | 44 | - name: Build Documentation 45 | run: npm run build:documentation 46 | env: 47 | CI: true 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .npmrc 3 | .DS_Store 4 | node_modules 5 | .cache 6 | dist 7 | public 8 | lab 9 | 10 | # Logs 11 | logs 12 | *.log 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # TypeScript v1 declaration files 49 | typings/ 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional REPL history 58 | .node_repl_history 59 | 60 | # Output of 'npm pack' 61 | *.tgz 62 | 63 | # Yarn Integrity file 64 | .yarn-integrity 65 | 66 | # dotenv environment variables file 67 | .env 68 | 69 | # next.js build output 70 | .next 71 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [4.6.0] - 01-Nov-2024 2 | * Added 'hotkey' field, which shows which combination triggered the handler 3 | 4 | ## [2.2.1] - 23-Jul-2020 5 | * Remove deprecated findDOMNode 6 | * Added useIsHotkeyPressed hook. 7 | 8 | ## [2.2.0] - 23-Jul-2020 9 | * Return ref to scope hotkey 10 | * Fixed test for scopable ref feature 11 | 12 | ## [2.1.4] - 30-Jun-2020 13 | * added updated readme to docz 14 | * Update README to reflect lack of filter scoping 15 | 16 | ## [2.1.3] - 16-Apr-2020 17 | * Code cleanup 18 | * Overloaded function type, so that options does not have to be set when deps is used only. 19 | 20 | ## [2.1.2] - 13-Apr-2020 21 | * Added tests 22 | 23 | ## [2.1.1] - 05-Apr-2020 24 | * Fixed bug in 2.1.0 that every filter returns false that has no available tags given. 25 | 26 | ## [2.1.0] - 04-Apr-2020 27 | * Added filter implementation 28 | 29 | ## [2.0.1] - 04-Apr-2020 30 | * Update readme 31 | * Fixed Option Type 32 | 33 | ## [2.0.0] - 04-Apr-2020 34 | * BC: Added more options Swapped deps and options param. Updated docz. 35 | 36 | ## [1.6.1] - 27-Mar-2020 37 | * Directly use KeyHandler type from hotkeys-js 38 | 39 | ## [1.6.0] - 09-Mar-2020 40 | * Add options parameter to useHotkeys hook 41 | 42 | ## [1.5.4] - 09-Dec-2019 43 | * Updated docz. 44 | 45 | ## [1.5.3] - 09-Sep-2019 46 | * make sure only memoisedCallback is unbound 47 | 48 | ## [1.5.2] - 24-Aug-2019 49 | * Update readme. 50 | * Bump hotkeys version to 3.7.1 51 | 52 | ## [1.5.1] - 21-Jul-2019 53 | * Update readme. 54 | 55 | ## [1.5.0] - 21-Jul-2019 56 | * Callback gets memoised inside hook by default 57 | * Add memo deps array as third argument to hook 58 | 59 | ## [1.4.0] - 03-Jun-2019 60 | * Add callback to useEffect deps to allow update of hotkeys when callback changes 61 | * This also fixes the stale state bug 62 | * Bump hotkeys version to 3.6.11 63 | 64 | ## [1.3.4] - 11-May-2019 65 | * Bump hotkeys version to 3.6.8 66 | 67 | ## [1.3.3] - 02-May-2019 68 | * Removed console.logs 69 | * tightened source 70 | * Updated docz 71 | 72 | ## [1.3.0] - 06-Apr-2019 73 | * Fixed bind and unbind on every render 74 | * Updated docz to make it work with the new deps array 75 | 76 | ## [1.2.0] - 30-Mar-2019 77 | * Updated hotkeys 78 | * Support for hotkeys 3.6 79 | * Fixed typos in docz 80 | 81 | ## [1.1.1] - 20-Feb-2019 82 | * Updated examples in readme.md 83 | 84 | ## [1.1.0] - 20-Feb-2019 85 | * BC: Renamed `useHotKeys` to `useHotkeys` to keep it identical with the hotkeys package 86 | * Switched to docz for documentation 87 | * Switched to pika for packaging and publishing 88 | 89 | ## [1.0.3] - 13-Feb-2019 90 | * Bumped up hotkeys-js version to 3.4.4 91 | 92 | ## [1.0.2] - 07-Feb-2019 93 | * Bumped up hotkeys-js version 94 | 95 | ## [1.0.1] - 07-Feb-2019 96 | * Bumped peerDependencies for react and react-dom 97 | * Cleaned up repository 98 | 99 | ## [0.1.2] - 15-Jan-2019 100 | * Initial release 101 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Johannes Klauss 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "vcs": { 4 | "enabled": false, 5 | "clientKind": "git", 6 | "useIgnoreFile": false 7 | }, 8 | "files": { 9 | "include": ["**/src/*.ts"], 10 | "ignoreUnknown": false 11 | }, 12 | "formatter": { 13 | "enabled": true, 14 | "indentStyle": "space" 15 | }, 16 | "organizeImports": { 17 | "enabled": true 18 | }, 19 | "linter": { 20 | "enabled": true, 21 | "rules": { 22 | "recommended": true, 23 | "complexity": { 24 | "noForEach": "off" 25 | } 26 | } 27 | }, 28 | "javascript": { 29 | "formatter": { 30 | "lineWidth": 120, 31 | "quoteStyle": "single", 32 | "semicolons": "asNeeded" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-hotkeys-hook", 3 | "description": "React hook for handling keyboard shortcuts", 4 | "version": "5.1.0", 5 | "private": true, 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/JohannesKlauss/react-keymap-hook.git" 9 | }, 10 | "homepage": "https://johannesklauss.github.io/react-hotkeys-hook/", 11 | "author": "Johannes Klauss", 12 | "type": "module", 13 | "main": "packages/react-hotkeys-hook/dist/index.js", 14 | "types": "packages/react-hotkeys-hook/dist/index.d.ts", 15 | "files": [ 16 | "packages/react-hotkeys-hook/dist" 17 | ], 18 | "keywords": [ 19 | "react", 20 | "hook", 21 | "hooks", 22 | "component", 23 | "hotkey", 24 | "shortcut", 25 | "keyboard", 26 | "shortcuts", 27 | "keypress", 28 | "hotkeys" 29 | ], 30 | "license": "MIT", 31 | "scripts": { 32 | "build": "npm run -w packages/react-hotkeys-hook build", 33 | "build:documentation": "npm run -w packages/documentation build", 34 | "test": "npm run -w packages/react-hotkeys-hook test", 35 | "prepublishOnly": "npm run test && npm run lint && npm run build", 36 | "format": "npx @biomejs/biome format --write packages", 37 | "lint": "npx @biomejs/biome lint --write packages" 38 | }, 39 | "workspaces": [ 40 | "packages/*" 41 | ], 42 | "peerDependencies": { 43 | "react": ">=16.8.0", 44 | "react-dom": ">=16.8.0" 45 | }, 46 | "devDependencies": { 47 | "@biomejs/biome": "1.9.4" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/documentation/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /packages/documentation/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /packages/documentation/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/documentation/blog/2021-09-24-new-documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: new-documentation 3 | title: New Documentation and Support Channel 4 | author: Johannes Klauss 5 | author_title: Software Engineer @ Spaceteams 6 | author_url: https://github.com/johannesklauss 7 | author_image_url: https://avatars0.githubusercontent.com/u/6214415?s=400&v=4 8 | tags: [documentation, support, v4] 9 | --- 10 | 11 | ## New Docs 12 | 13 | The last couple of weeks I overhauled the documentation to better explain how the hook works and what you can do with it. 14 | I hope this will reduce questions that are frequently popping up in the issues panel of the 15 | [Github repo](https://github.com/JohannesKlauss/react-hotkeys-hook). 16 | The new site is powered by [Docusaurus](https://docusaurus.io/), an awesome tool to quickly set up a complete documentation ecosystem. 17 | Be sure to check that out if you yourself have to write a documentation. 18 | 19 | This includes a [Quick Start section](/docs/intro), extensive documentation about [every parameter the hook accepts](/docs/api/use-hotkeys) 20 | with a lot of examples to cover different scenarios, 21 | different approaches, pitfalls and shortcomings. 22 | 23 | The recipes section will get content later on. I might also add a comparison page to other hotkey packages for react, 24 | probably the most interesting would be a comparison to [react-hotkeys](https://github.com/greena13/react-hotkeys). 25 | 26 | If you find any typos, confusing sentences, have questions or think that some points need to be elaborated on, please let 27 | me know by [opening an issue](https://github.com/JohannesKlauss/react-hotkeys-hook/issues/new). 28 | 29 | There is no search functionality yet. I hope to add that soon. 30 | 31 | I will now start the work on version 4, which everyone is welcome to contribute to. There is an 32 | [open issue](https://github.com/JohannesKlauss/react-hotkeys-hook/issues/574) titled "Roadmap to Version 4" where you can 33 | join our discussion about what version 4 should look like and add feature wise. 34 | 35 | ## New Support Channel 36 | 37 | Since there are popping up question on all different kind of channels (Github issues, email, private messages, etc.) 38 | on how to use react-hotkeys-hook I added a [discussions](https://github.com/JohannesKlauss/react-hotkeys-hook/discussions) 39 | panel on the Github Repo. If you have questions please use this section to ask them, and I am happy to help you out. 40 | Make sure to use the Q&A category. 41 | Of course you can also ask your questions on [Stackoverflow](https://stackoverflow.com/search?page=1&tab=Relevance&q=react-hotkeys-hook). 42 | 43 | ### [Ask a question on Github Discussions](https://github.com/JohannesKlauss/react-hotkeys-hook/discussions/new) 44 | ### [Ask a question on Stackoverflow](https://stackoverflow.com/search?page=1&tab=Relevance&q=react-hotkeys-hook) 45 | 46 | Last but not least: thank you all for the ongoing interest in this package. 47 | 48 | ## In need for a logo/icon 49 | 50 | I'd love to have an icon for this package, but I have zero talent for graphic design. If anybody would like to contribute 51 | a small and simple logo for this package, I'd more than welcome it. -------------------------------------------------------------------------------- /packages/documentation/docs/api/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "API", 3 | "position": 3 4 | } 5 | -------------------------------------------------------------------------------- /packages/documentation/docs/api/is-hotkey-pressed.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | title: isHotkeyPressed 4 | --- 5 | 6 | # isHotkeyPressed API 7 | 8 | Function signature: 9 | 10 | ```ts 11 | function isHotkeyPressed(key: string | string[], splitKey: string = ','): boolean 12 | ``` 13 | 14 | This function allows us to check if a specific key is pressed by the user. 15 | 16 | ```jsx 17 | const isHotkeyPressed = useIsHotkeyPressed(); 18 | 19 | const onClick = () => isHotkeyPressed('shift') ? setCount(count - 1) : setCount(count + 1); 20 | ``` 21 | 22 | *** 23 | 24 | ## Arguments 25 | 26 | ### key 27 | 28 | ```ts 29 | key: string | string[] 30 | ``` 31 | 32 | The key we want to listen to. This can be a string (like `'a'`, `'shift+a'`) or an array of strings (like `['a', 'shift+a']`). 33 | 34 | ### splitKey 35 | 36 | ```ts 37 | key: string = ',' 38 | ``` 39 | 40 | We can combine to check for multiple keys in one string by separating them with a comma (`,`). We can change 41 | this by passing a different key. 42 | 43 | ## Return value 44 | 45 | ### boolean 46 | 47 | The function returns `true` if the key or keystroke is currently pressed down, otherwise it will return `false`. 48 | -------------------------------------------------------------------------------- /packages/documentation/docs/api/use-record-hotkeys.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | title: useRecordHotkeys 4 | --- 5 | 6 | import { useRecordHotkeys } from "react-hotkeys-hook"; 7 | 8 | # useRecordHotkeys API 9 | 10 | Function signature: 11 | 12 | ```ts 13 | function useRecordHotkeys(useKey?: boolean): [Set, { start: () => void, stop: () => void, isRecording: boolean }] 14 | ``` 15 | 16 | ## `useKey` 17 | 18 | By default recording hotkeys will record the keys code, not the produced character. If you want to record the produced 19 | character, you can pass `true` as the first argument to the hook. 20 | 21 | *** 22 | 23 | ## Return value 24 | 25 | ```ts 26 | const [keys, { start, stop, isRecording }] = useRecordHotkeys() 27 | ``` 28 | 29 | ### `keys` 30 | 31 | A `Set` of the keys that have been pressed. 32 | 33 | ### `start` 34 | 35 | A function that starts recording the keys that are pressed. Only works in the browser. 36 | 37 | ### `stop` 38 | 39 | A function that stops recording the keys that are pressed. Only works in the browser. 40 | 41 | ### `isRecording` 42 | 43 | A boolean that indicates whether the recording is in progress or not. 44 | -------------------------------------------------------------------------------- /packages/documentation/docs/documentation/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Documentation", 3 | "position": 2 4 | } 5 | -------------------------------------------------------------------------------- /packages/documentation/docs/documentation/advanced-usage.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | title: "Advanced Usage" 4 | --- 5 | 6 | # Advanced Usage 7 | 8 | This section described advanced functionalities of the hook. 9 | 10 | **🚧 This section is under construction 🚧** 11 | 12 | ## Using typescript types to reference key names 13 | Sometimes we might get confused if we need to listen to `ArrowLeft` or `LeftArrow` to listen to the left arrow key. 14 | To avoid this confusion, we can use the typescript types provided by the `ts-key-enum` library. 15 | 16 | You add the library to your project by running: 17 | 18 | ```bash 19 | npm install ts-key-enum 20 | ``` 21 | 22 | Then you can import the types and use them in your code: 23 | 24 | ```ts 25 | import { Key } from 'ts-key-enum' 26 | 27 | useHotkeys(Key.Backspace, () => { 28 | console.log(`delete`) 29 | }) 30 | ``` 31 | 32 | See https://gitlab.com/nfriend/ts-key-enum/-/blob/master/Key.enum.d.ts for the full list of available keys. 33 | -------------------------------------------------------------------------------- /packages/documentation/docs/documentation/hotkeys-provider.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | title: "Grouping Hotkeys together" 4 | --- 5 | 6 | # `HotkeysProvider` 7 | 8 | The `HotkeysProvider` component is a wrapper component that allows you to group hotkeys together. 9 | This is useful if you want to disable a group of hotkeys in a certain part of your application. 10 | 11 | In order to use the grouping feature, you need to wrap your application with the `HotkeysProvider` component. 12 | 13 | ```jsx 14 | import { HotkeysProvider } from 'react-hotkeys-hook'; 15 | 16 | function App() { 17 | return ( 18 | 19 |
20 |

My App

21 |
22 |
23 | ) 24 | } 25 | ``` 26 | 27 | Grouping works by passing setting the scope option of a hotkey. 28 | 29 | ```jsx 30 | import { useHotkeys } from 'react-hotkeys-hook'; 31 | 32 | function MyComponent() { 33 | useHotkeys('ctrl+a', () => console.log('ctrl+a'), { scopes: 'scopeA' }) 34 | useHotkeys('ctrl+b', () => console.log('ctrl+b'), { scopes: 'scopeB' }) 35 | useHotkeys('ctrl+c', () => console.log('ctrl+c'), { scopes: 'scopeA' }) 36 | 37 | return ( 38 |
39 |

My App

40 |
41 | ) 42 | } 43 | ``` 44 | 45 | By default all hotkeys are in the `*` (wildcard) scope. This means that they will be active regardless of where you are in your application. 46 | Wrapping your app with the `` component will enable the wildcard scope by default. 47 | 48 | If you set a scope on a hotkey, it will **not** be part of the wildcard scope anymore. 49 | 50 | ## Activating and deactivating scopes 51 | 52 | You can enable and disable scopes by using the `useHotkeysContext` hook. 53 | 54 | ```jsx 55 | import { useHotkeysContext } from 'react-hotkeys-hook'; 56 | 57 | function MyComponent() { 58 | const { enableScope, disableScope } = useHotkeysContext(); 59 | 60 | return ( 61 |
62 | 63 | 64 |
65 | ) 66 | } 67 | ``` 68 | 69 | You can also toggle scopes by using the `toggleScope` function. 70 | 71 | :::tip Get currently active scopes 72 | If your app is highly dynamic and you want to know which scopes are currently active, you can get an array of string via 73 | `activeScopes` from the context. 74 | ::: 75 | 76 | ## Set initially active scopes 77 | 78 | You can set the initially active scopes by passing an array of strings to the `initiallyActiveScopes` prop of the `HotkeysProvider`. 79 | 80 | ```jsx 81 | import { HotkeysProvider } from 'react-hotkeys-hook'; 82 | 83 | function App() { 84 | return ( 85 | 86 |
87 |

My App

88 |
89 |
90 | ) 91 | } 92 | ``` 93 | 94 | :::info Wildcard scope when using `initiallyActiveScopes` 95 | If you set the `initiallyActiveScopes` prop, the wildcard scope will not be active by default. You would have to set `['scopeA', '*']`. 96 | ::: 97 | -------------------------------------------------------------------------------- /packages/documentation/docs/documentation/installation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | import Tabs from '@theme/Tabs'; 6 | import TabItem from '@theme/TabItem'; 7 | import CodeBlock from '@theme/CodeBlock'; 8 | 9 | # Installation 10 | 11 | ## Via Package Manager 12 | 13 | To install the package we use our favorite package manager. 14 | 15 | 22 | 23 | yarn add react-hotkeys-hook 24 | 25 | 26 | npm i react-hotkeys-hook --save 27 | 28 | 29 | bun i react-hotkeys-hook 30 | 31 | 32 | 33 | ```jsx 34 | import { useHotkeys } from 'react-hotkeys-hook'; 35 | ``` 36 | 37 | ## Via CDN 38 | 39 | To use the ES Module we can reference it directly from [unpkg.com](https://unpkg.com): 40 | 41 | ```js 42 | import { useHotkeys } from 'https://unpkg.com/browse/react-hotkeys-hook@5.0.0/dist/react-hotkeys-hook.esm.js'; 43 | ``` 44 | 45 | *** 46 | 47 | ```jsx live 48 | function MyComponent() { 49 | const [count, setCount] = useState(0); 50 | 51 | useHotkeys('a', () => setCount(count => count + 1)); 52 | 53 | return ( 54 | Pressed 'a' key {count} times. 55 | ); 56 | } 57 | ``` -------------------------------------------------------------------------------- /packages/documentation/docs/documentation/is-hotkey-pressed.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # isHotkeyPressed 6 | 7 | This function allows us to check if the user is currently pressing down a key. 8 | 9 | ## Basic Usage 10 | 11 | Import the `isHotkeyPressed` function from the package: 12 | 13 | ```js 14 | import { isHotkeyPressed } from 'react-hotkeys-hook'; 15 | ``` 16 | 17 | or if you are using `require` style syntax: 18 | 19 | ```js 20 | const { isHotkeyPressed } = require('react-hotkeys-hook') 21 | // or 22 | const isHotkeyPressed = require('react-hotkeys-hook').isHotkeyPressed 23 | ``` 24 | 25 | *** 26 | 27 | ### Check for pressed keys on callbacks 28 | 29 | One common use case would be to check if the user holds down a modifier key. 30 | 31 | ```jsx live 32 | function ExampleComponent() { 33 | const [count, setCount] = useState(0); 34 | 35 | const onClick = () => isHotkeyPressed('shift') ? setCount(count - 1) : setCount(count + 1); 36 | 37 | return ( 38 |
39 |

The count is: {count}

40 | 41 |
42 | ) 43 | } 44 | ``` 45 | 46 | Just like with `useHotkeys`, you can pass an array as the first argument to check for multiple keys. 47 | -------------------------------------------------------------------------------- /packages/documentation/docs/documentation/typescript.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | --- 4 | 5 | # Using TypeScript 6 | 7 | :::tip Works out of the box! 8 | React Hotkeys Hook is written in TypeScript and therefore ready to go with our TypeScript project. 9 | 10 | ::: 11 | 12 | ## Examples 13 | 14 | Using type safety to prevent the passing of parameters with an incorrect type. 15 | 16 | ```tsx 17 | import { useHotkeys } from "react-hotkeys-hook"; 18 | import { useState } from "React"; 19 | 20 | interface Props { 21 | hotKey: number; 22 | } 23 | 24 | const MyComponent = ({hotKey}: Props) => { 25 | const [count, setCount] = useState(0); 26 | 27 | // This will produce an error, because hotKey has to be of type string. 28 | useHotkeys(hotKey, () => setCount(prevCount => prevCount + 1)); 29 | 30 | return ( 31 |
32 | The count is {count}. 33 |
34 | ) 35 | } 36 | ``` 37 | 38 | *** 39 | 40 | Using TypeScript with refs. 41 | 42 | ```tsx 43 | import { useHotkeys } from "react-hotkeys-hook"; 44 | import { useState } from "React"; 45 | 46 | interface Props { 47 | hotKey: string; 48 | } 49 | 50 | const MyComponent = ({hotKey}: Props) => { 51 | const [count, setCount] = useState(0); 52 | 53 | const ref = useHotkeys(hotKey, () => setCount(prevCount => prevCount + 1)); 54 | 55 | return ( 56 |
57 | The count is {count}. 58 |
59 | ) 60 | } 61 | ``` 62 | -------------------------------------------------------------------------------- /packages/documentation/docs/documentation/use-record-hotkeys.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | title: "useRecordHotkeys" 4 | --- 5 | 6 | # useRecordHotkeys 7 | 8 | This hook allows you to record a hotkey stroke by the user. This is useful if you want the user to define their custom 9 | keystroke for your application. 10 | 11 | ## Basic Usage 12 | 13 | Import the `useRecordHotkeys` hook from the package: 14 | 15 | ```js 16 | import { useRecordHotkeys } from 'react-hotkeys-hook'; 17 | ``` 18 | 19 | or if you are using `require` style syntax: 20 | 21 | ```js 22 | const { useRecordHotkeys } = require('react-hotkeys-hook') 23 | // or 24 | const useRecordHotkeys = require('react-hotkeys-hook').useRecordHotkeys 25 | ``` 26 | 27 | *** 28 | 29 | ### Record hotkeys 30 | 31 | In order to record a hotkey, you need to call the `useRecordHotkeys` hook. This hook returns an array similar to the `useState` 32 | hook, but the second argument has some extra properties. 33 | 34 | ```jsx live 35 | function ExampleComponent() { 36 | const [keys, { start, stop, isRecording }] = useRecordHotkeys() 37 | 38 | return ( 39 |
40 |

Is recording: {isRecording ? 'yes' : 'no'}

41 |

Recorded keys: {Array.from(keys).join(' + ')}

42 |
43 | 44 | 45 |
46 | ) 47 | } 48 | ``` 49 | 50 | :::info 51 | Note that the returned value of the recorded keys is a `Set` object. This is because every key gets recorded only once. 52 | Implementation wise it is easier to use a `Set` than an array, but you can convert it to an array if you want or need to, 53 | like we are in the example above. 54 | ::: 55 | -------------------------------------------------------------------------------- /packages/documentation/docs/documentation/useHotkeys/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "useHotkeys", 3 | "position": 2 4 | } -------------------------------------------------------------------------------- /packages/documentation/docs/documentation/useHotkeys/basic-usage.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | title: "Basic Usage" 4 | --- 5 | 6 | # useHotkeys 7 | 8 | This hook allows us to listen to hotkeys in a declarative way and execute a callback function once the user pressed down 9 | the given hotkey. 10 | 11 | :::tip tip 12 | We can edit the code of every example and immediately see the results of our changes. 13 | ::: 14 | 15 | # Basic Usage 16 | 17 | Import the `useHotkeys` hook from the package: 18 | 19 | ```js 20 | import { useHotkeys } from 'react-hotkeys-hook'; 21 | ``` 22 | 23 | or when using `require` style syntax: 24 | 25 | ```js 26 | const { useHotkeys } = require('react-hotkeys-hook') 27 | // or 28 | const useHotkeys = require('react-hotkeys-hook').useHotkeys 29 | ``` 30 | 31 | ## Simple Hotkey 32 | 33 | The most basic usage for the hook is to assign a hotkey we want to listen to and a callback to get executed once the user 34 | hits that key. 35 | 36 | ```jsx live 37 | function ExampleComponent() { 38 | useHotkeys('a', () => alert('Key a was pressed')) 39 | 40 | return ( 41 | Press the a key to see it work. 42 | ) 43 | } 44 | ``` 45 | 46 | > To make the hotkeys work, you have to focus the rendered output of the Live Editor. 47 | 48 | *** 49 | 50 | Inside the callback we can do whatever we like. One common use case is to alter some component state: 51 | 52 | ```jsx live 53 | function ExampleComponent() { 54 | const [count, setCount] = useState(0) 55 | useHotkeys('b', () => setCount(prevCount => prevCount + 1)) 56 | 57 | return ( 58 | Pressed the 'b' key {count} times. 59 | ) 60 | } 61 | ``` 62 | 63 | `useHotkeys` will create the listener on component mount and destroy the listener once the component unmounts. 64 | 65 | *** 66 | 67 | ## Multiple hotkeys per component 68 | 69 | Since `useHotkeys` is a hook, we can use it in one component as much as we like: 70 | 71 | ```jsx live 72 | function ExampleComponent() { 73 | const [count, setCount] = useState(0) 74 | useHotkeys('q', () => setCount(prevCount => prevCount + 1)) 75 | useHotkeys('w', () => setCount(prevCount => prevCount - 1)) 76 | 77 | return ( 78 | The count is {count}. 79 | ) 80 | } 81 | ``` 82 | 83 | *** 84 | 85 | ## Keystrokes 86 | 87 | We can also listen to keystrokes. That is a combination of keys that the user has to hit in order to execute the callback. 88 | One example would be a Jira like UI that listens to the `Shift+C` keystroke to create a new ticket: 89 | 90 | ```jsx live 91 | function CreateIssue() { 92 | const [showIssueCreatorModal, setShowIssueCreatorModal] = useState(false) 93 | 94 | useHotkeys('shift+c', () => setShowIssueCreatorModal(true)) 95 | 96 | return ( 97 | <> 98 | {showIssueCreatorModal &&
MODAL CONTENT
} 99 | {!showIssueCreatorModal &&
issue list
} 100 | 101 | ) 102 | } 103 | ``` 104 | 105 | This is not restricted to a combination of two keys. We can also do things like `Ctrl+Shift+A+C` (maybe not the most 106 | intuitive keystroke for a user to memorize, though). 107 | 108 | ```jsx live 109 | function ExampleComponent() { 110 | const [count, setCount] = useState(0) 111 | useHotkeys('ctrl+shift+a+c', () => setCount(prevCount => prevCount + 1)) 112 | 113 | return ( 114 | Pressed the 'ctrl+shift+a+c' key {count} times. 115 | ) 116 | } 117 | ``` 118 | 119 | :::note the keys argument is case-insensitive 120 | It doesn't matter if we listen for `CTRL+S`, `Ctrl+s`, `ctrl+S` or any other possible way of writing the keys in different 121 | cases. They all will listen to the user pressing the `ctrl` and the `s` key. This also means that upper case letters 122 | are treated as lower case letters. If we want to listen to the user pressing the upper case letter `S`, we'd have to listen 123 | to `shift+s`. 124 | ::: 125 | 126 | *** 127 | 128 | ## Multiple hotkeys 129 | 130 | We can also listen to multiple keystrokes and/or hotkeys and trigger the same callback. Combinations are separated by 131 | a comma sign: 132 | 133 | ```jsx live 134 | function ExampleComponent() { 135 | const [count, setCount] = useState(0) 136 | useHotkeys('ctrl+shift+a+c, c, shift+c', () => setCount(prevCount => prevCount + 1)) 137 | 138 | return ( 139 | Received the combination {count} times. 140 | ) 141 | } 142 | ``` 143 | 144 | You can also use an array as first arguments: `useHotkeys(['ctrl+shift+a+c', 'c', 'shift+c'], ....` 145 | 146 | :::info Noticed something? 147 | We used the `ctrl+shift+a+c` and `shift+c` combination two times for two components on this page. 148 | Since we pressed down one hotkey already the example above started at the count of one. Hotkeys are attached 149 | globally if we don't use its return value. 150 | ::: 151 | 152 | *** 153 | 154 | ## Sequential Hotkeys 155 | 156 | We can also listen to keystrokes sequentially, meaning that the user has to press keys in a specific order before invoking the callback. To specify a sequential hotkey, we use the `>` character. 157 | 158 | ```jsx live 159 | function ExampleComponent() { 160 | const [count, setCount] = useState(0) 161 | useHotkeys('g>h>i', () => setCount(prevCount => prevCount + 1)) 162 | 163 | return ( 164 | Received the combination {count} times. 165 | ) 166 | } 167 | ``` 168 | 169 | In the example above, the user has to press the `g` key, then the `h` key and finally the `i` key in order to trigger the callback. 170 | 171 | #### Timeout 172 | `useHotkeys` sets a default time window for the sequential hotkey of `1000ms` to prevent listening for incomplete sequences indefinitely. If the user doesn't press the next key in that given time, the sequence is aborted. 173 | 174 | We can override the timeout by passing the `sequenceTimeoutMs` option: 175 | 176 | ```jsx live 177 | function ExampleComponent() { 178 | const [count, setCount] = useState(0) 179 | useHotkeys('g>h>i>j', () => setCount(prevCount => prevCount + 1), { 180 | sequenceTimeoutMs: 10000, 181 | }) 182 | 183 | return ( 184 | Received the combination {count} times. 185 | ) 186 | } 187 | ``` 188 | 189 | In the example above, the user has to press the `g` key, then the `h` key and then the `i` key, and finally the `j` key in order to trigger the callback. 190 | The timeout is 10 seconds, so the user has a lot of time to press the keys. 191 | 192 | 193 | *** 194 | 195 | ## Modifiers & Special keys 196 | 197 | Of course, we also want to leverage more complex keystrokes. `useHotkeys` supports the following modifiers: 198 | 199 | * `shift` 200 | * `alt` 201 | * `ctrl` 202 | * `meta` 203 | * `mod` (which listens for `ctrl` on Windows/Linux) 204 | 205 | ## MacOS and Windows/Linux compatibility 206 | Since version 4 `alt` and `option` are identical. The `meta` key is the same as `cmd` on macOS and `os` key on Windows. 207 | 208 | ```jsx 209 | function ExampleComponent() { 210 | const [count, setCount] = useState(0) 211 | useHotkeys( 212 | 'ctrl+shift+a+c, c, shift+c, alt+n, ctrl+d, meta+d', 213 | () => setCount(prevCount => prevCount + 1) 214 | ) 215 | 216 | return ( 217 | Received the combination {count} times. 218 | ) 219 | } 220 | ``` 221 | 222 | *** 223 | 224 | There are also special keys like arrows, return, space bar, etc. that have their own name that can be used: 225 | 226 | * `backspace` 227 | * `tab` 228 | * `clear` 229 | * `enter` or `return` 230 | * `esc` or `escape` 231 | * `space` 232 | * `up`, `down`, `left`, `right` 233 | * `pageup`, `pagedown` 234 | * `del` or `delete` 235 | * `f1`, `f2` ... `f19` 236 | 237 | :::caution Warning 238 | Please be aware that there are some hotkeys that we cannot override, because they would interfere with a safe browsing 239 | experience for the user. These depend on the browser. 240 | For example in Chrome, most notably those are `meta + w` which closes a tab, `meta + n` for opening a new window and `meta + t` to open a new tab. 241 | Additionally `meta + shift + w` (closing all tabs of the current window), `meta + shift + n` (opening incognito window) and 242 | `meta + shift + t` (reopen the last closed tab) cannot be overridden. 243 | `meta + up + 1..9` on the other hand focuses the corresponding tab of the active window and also cannot be overridden. 244 | ::: 245 | -------------------------------------------------------------------------------- /packages/documentation/docs/documentation/useHotkeys/disable-hotkeys.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | title: "Disable/Enable Hotkeys" 4 | --- 5 | 6 | # Disable/Enable Hotkeys 7 | 8 | We are now able to register our hotkeys, scope them to the component (or part of a component) and prevent stale state 9 | inside our callback. But our hotkeys are always active. If the component has received focus, it will always trigger. However, 10 | sometimes we want to dynamically decide if the hotkey should even be enabled. 11 | 12 | There are multiple ways to disable a hotkey. Let's go through all of them. 13 | 14 | ## Dynamically disable or enable 15 | 16 | ### Toggle hotkeys with the `enabled` flag 17 | 18 | Using the `enabled` flag in the options object, we can dynamically tell `useHotkeys` if the hotkey should be active or not. 19 | 20 | ```jsx live 21 | function ExampleComponent() { 22 | const [enabled, setEnabled] = useState(false) 23 | const [count, setCount] = useState(0) 24 | useHotkeys('b', () => setCount(prevCount => prevCount + 1), { 25 | enabled, 26 | }) 27 | 28 | return ( 29 |
30 | 31 |

Hotkey is {!enabled && 'not'} enabled.

32 |

Pressed the 'b' key {count} times.

33 |
34 | ) 35 | } 36 | ``` 37 | 38 | This is useful to programmatically decide (depending on some internal state of our app or user input) if the hotkey 39 | should be active or not. `enabled` takes a `Trigger` type, so it can either be a boolean or a function that returns a boolean. 40 | See the documentation for the [enabled](/docs/api/use-hotkeys#enabled) option for more information. 41 | 42 | :::info Tip 43 | If we use the returned `ref` of the callback to scope our hotkey to a specific sub-tree of our component, note that 44 | this will automatically activate or deactivate our hotkey depending on which part of the page has active focus. 45 | ::: 46 | 47 | *** 48 | 49 | ## Prevent default browser behavior 50 | 51 | Some hotkeys like `meta + s` are already assigned to a browser function, in this case saving a website to 52 | the users local disk. Sometimes we want to override these hotkeys to implement our own functionality. Maybe we want to 53 | build a text editor or some other user based creation tool that needs a save shortcut to save the current progress. It 54 | would be silly to introduce a new shortcut for saving the users progress, since every user knows that `meta + s` is the 55 | universal shortcut for saving. 56 | 57 | To prevent the default browser behavior, we can just set `preventDefault` to true in our options: 58 | 59 | ```jsx live 60 | function ExampleComponent() { 61 | useHotkeys('meta+s', () => { 62 | alert('We saved your progress!') 63 | // ... set up our own saving dialog. 64 | }, { preventDefault: true }) 65 | 66 | return ( 67 |
68 |

Press cmd+s (or ctrl+s for non mac) to open custom save dialog.

69 |
70 | ) 71 | } 72 | ``` 73 | 74 | :::caution Warning 75 | Please be aware that there are some hotkeys that we cannot override, because they would interfere with a safe browsing 76 | experience for the user. These depend on the browser. 77 | For example in Chrome, most notably those are `meta + w` which closes a tab, `meta + n` for opening a new window and `meta + t` to open a new tab. 78 | Additionally `meta + shift + w` (closing all tabs of the current window), `meta + shift + n` (opening incognito window) and 79 | `meta + shift + t` (reopen the last closed tab) cannot be overridden. 80 | `meta + up + 1..9` on the other hand focuses the corresponding tab of the active window and also cannot be overridden. 81 | ::: 82 | 83 | *** 84 | 85 | ### Disable callback execution 86 | 87 | In the first section we set `enabled` to `false` to disable our hotkey. Internally this will also unbind our event listeners 88 | from the DOM. To keep the event listeners but not execute the callback, we can give `enabled` a function that returns `false`. 89 | 90 | ```jsx live 91 | function ExampleComponent() { 92 | useHotkeys('option+b', () => alert('Callback got executed'), { 93 | enabled: (event, hotkeysEvent) => { 94 | return true; 95 | } 96 | }) 97 | 98 | return ( 99 |
100 |

Pressing 'b' executes the callback, while 'alt+b' won't.

101 |
102 | ) 103 | } 104 | ``` 105 | 106 | We can combine both the `enabled` and `preventDefault` options. 107 | 108 | ```jsx 109 | function ExampleComponent() { 110 | useHotkeys('meta+s', () => alert('We saved your progress!'), { 111 | enabled: () => false, 112 | preventDefault: true, 113 | }) 114 | 115 | return ( 116 |
117 |

Press cmd+s (or ctrl+s for non mac) to open custom save dialog.

118 |
119 | ) 120 | } 121 | ``` 122 | 123 | This might be preferred if we want to build a custom hook on top of `useHotkeys` that always prevents the default behavior. 124 | 125 | :::info Notice the different behavior for the enabled option 126 | If `enabled` is set to `false` the internal browser event will not trigger at all, therefore no browser behavior will happen. 127 | If we set `enabled` to `() => false` our callback still won't get executed, but the event will trigger and therefore potential 128 | browser behavior will happen. 129 | ::: 130 | 131 | *** 132 | 133 | ### Enable hotkeys on form fields 134 | 135 | By default, hotkeys won't trigger when the user is focusing a form field. This is the expected case most of the time. 136 | For example, if we'd listen for the hotkey `'p'` on a review page for apps and a user would try to type `This app is simply 137 | the best` into the review input field, the resulting text would be `This a is simly 138 | the best`. Same with keystrokes that start with `shift + ...` since this normally capitalizes a letter. 139 | So it is good, that `useHotkeys` is smart enough to back off during focused form fields. However, sometimes you still 140 | want hotkeys to be enabled even when the user is focusing on an input field. There might be again the need so save 141 | the users progress. Our previous example would not trigger if the user is focusing a form field. Instead, we have to 142 | enable the hotkey on certain form fields. We can use the `enableOnFormTags` option for that which takes an array of form tags: 143 | 144 | ```jsx 145 | function ExampleComponent() { 146 | useHotkeys('meta+s', (e) => { 147 | e.preventDefault() 148 | 149 | alert('We saved your progress!') 150 | // ... set up our own saving dialog. 151 | }, { 152 | enableOnFormTags: ['input', 'select', 'textarea'] 153 | }) 154 | 155 | return ( 156 |
157 |

Press cmd+s (or ctrl+s for non mac) to open custom save dialog.

158 |
159 | ) 160 | } 161 | ``` 162 | 163 | We tell `useHotkeys` that the given keystrokes should be listened to and the callback executed even when the user is currently 164 | focusing an input, select or textarea field. To enable the hotkey on any input tag, set the `enableOnFormTags` 165 | option to `true`. 166 | 167 | *** 168 | 169 | ### Enable hotkeys on tags leveraging `contentEditable` 170 | 171 | The [`contentEditable` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable) 172 | allows users to edit text even though it is not part of an input field. For example a `
` tag could have the 173 | `contentEditable` attribute. Sometimes we want hotkeys to still trigger, even though a user is focusing an editing a 174 | tags content. A good example for this might be the editor on [medium.com](https://medium.com). `useHotkeys` provides 175 | an option for that: 176 | 177 | ```jsx live 178 | function ExampleComponent() { 179 | useHotkeys('meta+b', () => alert('Hotkeys are still working'), { 180 | enableOnContentEditable: true, 181 | }) 182 | 183 | return ( 184 |
185 | You can edit this text and still use all the active shortcuts. 186 |
187 | ) 188 | } 189 | ``` 190 | -------------------------------------------------------------------------------- /packages/documentation/docs/documentation/useHotkeys/ignore-layouts.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | title: "Keyboard Layouts" 4 | --- 5 | 6 | # Working with Keyboard Layouts 7 | 8 | When using hotkeys, we have to be aware of the keyboard layout that the user is using. 9 | `react-hotkeys-hook` will listen to the key code instead of the produced key by default. 10 | 11 | If we want to listen to a special key (`!`, `:` or `%`, etc.) there are two ways to go about it.Keyboard 12 | 13 | ## Option 1 - Listen to the key code (Layout independent) 14 | 15 | This is the default behavior of the hook. It will listen to the key code instead of the produced key. Let's listen to the `!` key. We can do it like this: 16 | 17 | ```jsx live 18 | function ExampleComponent() { 19 | const [count, setCount] = useState(0) 20 | useHotkeys('shift+1', () => setCount(prevCount => prevCount + 1)) 21 | 22 | return ( 23 | Pressed the '!' key {count} times. 24 | ) 25 | } 26 | ``` 27 | 28 | This is layout independent. A different keyboard layout may produce a different key than `!`, but the actually key stroke is 29 | the same for all users. The key here is to communicate to the user that they have to press `shift+1` and not `!`, since we 30 | cannot guarantee `!` to be produced by the `shift+1` stroke. 31 | 32 | But there might be situations where we don't care how the user produces the key, we really want to act upon `!`. This brings us 33 | to the next option. 34 | 35 | ## Option 2 - Listen to the produced key (Layout dependent) 36 | 37 | If we want to listen to the produced key, we can use the `useKey` option. This will listen to the produced key instead of the key code. 38 | 39 | ```jsx live 40 | function ExampleComponent() { 41 | const [count, setCount] = useState(0) 42 | useHotkeys('!', () => setCount(prevCount => prevCount + 1), { useKey: true }) 43 | 44 | return ( 45 | Pressed the '!' key {count} times. 46 | ) 47 | } 48 | ``` 49 | 50 | Now, the hotkey will only be triggered when the user presses `!` and not `shift+1`. For a standard american keyboard layout 51 | these two are the same, but there are dozens of different layouts. If we don't care how the user produces the key, this is the 52 | preferred option. -------------------------------------------------------------------------------- /packages/documentation/docs/documentation/useHotkeys/scoping-hotkeys.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | title: "Scoping Hotkeys" 4 | --- 5 | 6 | # Scoping hotkeys to components 7 | 8 | ## Global hotkeys and scoped components 9 | 10 | In the previous section we used two keystrokes in two different examples (`ctrl+shift+a+c` and `shift+c`). 11 | If we press down one of the keystroke it will trigger both components. Why is that? 12 | Hotkeys are attached globally, so there is no default scoping mechanism for them to only trigger if the component is 13 | focused. To emphasize the issue, check out these two components: 14 | 15 | ```jsx live noInline 16 | function UnscopedHotkey() { 17 | const [count, setCount] = useState(0) 18 | useHotkeys('c', () => setCount(prevCount => prevCount + 1)) 19 | 20 | return ( 21 |

The count is {count}.

22 | ) 23 | } 24 | 25 | function SecondUnscopedHotkey() { 26 | const [count, setCount] = useState(0) 27 | useHotkeys('c', () => setCount(prevCount => prevCount + 1)) 28 | 29 | return ( 30 |

The count is {count}.

31 | ) 32 | } 33 | 34 | render( 35 |
36 | 37 | 38 |
39 | ) 40 | ``` 41 | 42 | Everytime we press down the `c` key, both component trigger the callback. But how can we separate those two components 43 | and their assigned hotkeys? The answer is [`Refs`](https://react.dev/learn/manipulating-the-dom-with-refs). `useHotkeys` 44 | returns a [React ref callback function](https://react.dev/reference/react-dom/components/common#ref-callback) that we 45 | can attach to any component that takes a ref. This way we can tell the hook which element should receive the users focus 46 | before it triggers its callback. 47 | 48 | ```jsx live noInline 49 | function ScopedHotkey() { 50 | const [count, setCount] = useState(0) 51 | const ref = useHotkeys('shift+a', () => setCount(prevCount => prevCount + 1)) 52 | 53 | return ( 54 | <> 55 |

The count is {count}. Click anywhere except for the button to disable the hotkey.

56 | 59 | 60 | ) 61 | } 62 | 63 | function SecondScopedHotkey() { 64 | const [count, setCount] = useState(0) 65 | const ref = useHotkeys('shift+a', () => setCount(prevCount => prevCount + 1)) 66 | 67 | return ( 68 | <> 69 |

The count is {count}. Click anywhere except for the button to disable the hotkey.

70 | 73 | 74 | ) 75 | } 76 | 77 | render( 78 |
79 | 80 |
81 | 82 |
83 | ) 84 | ``` 85 | 86 | As we can see only if the button receives focus the hotkey gets enabled. We also added a second component that 87 | listens to the same hotkey but only triggers if an assigned component receives focus. 88 | 89 | We successfully scoped our duplicate global hotkey to their own components. This practice isn't restricted to duplicate 90 | hotkeys, we can use this technique to scope any hotkey we like. 91 | 92 | *** 93 | 94 | ## Scoping with non-focusable components 95 | 96 | Receiving focus on a button to enable a hotkey in a real world application is not very useful. Instead, we generally would 97 | like to set the focus to a modal or let the user click on an area which then receives the focus and enables 98 | its attached hotkeys. However, tags like `
`, `
`, ``, etc. cannot receive focus by default. To let them 99 | receive focus we have to use the `tabIndex` attribute: 100 | 101 | ```jsx live 102 | function SecondScopedHotkey() { 103 | const [count, setCount] = useState(0) 104 | const ref = useHotkeys('shift+a', () => setCount(prevCount => prevCount + 1)) 105 | 106 | return ( 107 |
108 |

The count is {count}. Click inside this area to enable the hotkey.

109 |
110 | ) 111 | } 112 | ``` 113 | 114 | :::info Important 115 | To ensure that we don't accidentally break sequential keyboard navigation of your page elements, we recommend to always 116 | set the `tabIndex` prop to `-1`. This way the element is not reachable via keyboard navigation. For more information on 117 | this topic, check out the [MDN page](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex). 118 | ::: 119 | 120 | *** 121 | 122 | ## Nesting scoped hotkeys 123 | 124 | This of course also works with nesting components: 125 | 126 | ```jsx live noInline 127 | function NestedScopedHotkey() { 128 | const [count, setCount] = useState(0) 129 | const ref = useHotkeys('shift+a', () => setCount(prevCount => prevCount + 1)) 130 | 131 | return ( 132 |
133 |

The count is {count}. Click inside this area to enable the hotkey.

134 |
135 | ) 136 | } 137 | 138 | function ScopedHotkey({children}) { 139 | const [count, setCount] = useState(0) 140 | const ref = useHotkeys('shift+a', () => setCount(prevCount => prevCount + 1)) 141 | 142 | return ( 143 |
144 |

The count is {count}. Click inside this area to enable the hotkey.

145 | {children} 146 |
147 | ) 148 | } 149 | 150 | render( 151 | 152 | 153 | 154 | ) 155 | ``` 156 | -------------------------------------------------------------------------------- /packages/documentation/docs/documentation/useHotkeys/setting-callback-dependencies.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | title: "Callback Dependencies" 4 | --- 5 | 6 | # Setting callback dependencies 7 | 8 | The callback we pass to `useHotkeys` gets memoised automatically inside the useHotkeys hook to prevent unnecessary re-renders. 9 | This can lead to 10 | problems with [stale state](https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function). 11 | To prevent those problems, the hook accepts a dependencies array just like Reacts internal 12 | [`useEffect`](https://reactjs.org/docs/hooks-effect.html), [`useMemo`](https://reactjs.org/docs/hooks-reference.html#usememo) and 13 | [`useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback) hooks. 14 | 15 | > The general rule is to put every unstable reference used inside the callback into the dependency array. 16 | 17 | Let's change the second example up a bit to illustrate the problem of stale state. Just as a reminder, we build a simple counter 18 | component: 19 | 20 | ```jsx live 21 | function ExampleComponent() { 22 | const [count, setCount] = useState(0) 23 | const ref = useHotkeys('b', () => setCount(prevCount => prevCount + 1)) 24 | 25 | return ( 26 | Pressed the 'b' key {count} times. 27 | ) 28 | } 29 | ``` 30 | 31 | :::note Notice 32 | Remember, that we are using the `ref` to scope our hotkey to this specific component. To let the component receive focus 33 | we add the `tabIndex={-1}` prop, since `` tags cannot receive focus by default. 34 | ::: 35 | 36 | *** 37 | 38 | Now, what if we change up the callback execution just a bit? 39 | 40 | ```jsx live 41 | function ExampleComponent() { 42 | const [count, setCount] = useState(0) 43 | const ref = useHotkeys('b', () => setCount(count + 1)) 44 | 45 | return ( 46 | Pressed the 'b' key {count} times. 47 | ) 48 | } 49 | ``` 50 | 51 | While the first component works as expected, the second does not and stops increasing `count` once we pressed the `'b'` 52 | key. This is called stale state. We are referencing a variable that changes over time. Since our callback gets memoised 53 | inside `useHotkeys` hook, the variable `count` inside the callback will always hold its initial value of `0`. 54 | 55 | To solve this problem, we could go back to the previous example. But that solution is specific to the `useState` hook. 56 | What if we want reference 57 | something other than a state variable, like a `useMemo` result? We can use the dependency array for that! 58 | 59 | ```jsx live 60 | function ExampleComponent() { 61 | const [count, setCount] = useState(0) 62 | const squaredCount = useMemo(() => count * 2, [count]) 63 | 64 | const ref = useHotkeys('b', () => setCount(squaredCount + 1), [squaredCount]) 65 | 66 | return ( 67 | Pressed the 'b' key {squaredCount} times. 68 | ) 69 | } 70 | ``` 71 | 72 | If we remove the dependency array from `useHotkeys` you will notice that again the callback execution _seems_ to stop 73 | after the first key press. But as we learned this is all due to stale state. The callback gets executed properly, just 74 | the referenced value is an old one. -------------------------------------------------------------------------------- /packages/documentation/docs/intro.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | import Tabs from '@theme/Tabs'; 6 | import TabItem from '@theme/TabItem'; 7 | import CodeBlock from '@theme/CodeBlock'; 8 | 9 | # Quick Start 10 | 11 | The fastest way to get you going. 12 | 13 | ## Install 14 | 15 | 22 | 23 | yarn add react-hotkeys-hook 24 | 25 | 26 | npm i react-hotkeys-hook --save 27 | 28 | 29 | bun i react-hotkeys-hook 30 | 31 | 32 | 33 | ## Usage 34 | 35 | ```jsx 36 | import { useHotkeys } from 'react-hotkeys-hook'; 37 | ``` 38 | 39 | ```jsx live 40 | function MyComponent() { 41 | const [count, setCount] = useState(0); 42 | 43 | useHotkeys('a', () => setCount(count => count + 1)); 44 | 45 | return ( 46 | Pressed 'a' key {count} times. 47 | ); 48 | } 49 | ``` 50 | 51 | :::note Out of the box support for TypeScript 52 | `react-hotkeys-hook` is written in TypeScript, so we don't have to install any additional typings. 53 | ::: 54 | -------------------------------------------------------------------------------- /packages/documentation/docs/migrate-to-5.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | title: "Migrate from 4.x to 5.x" 4 | --- 5 | 6 | # Migrate from 4.x to 5.x 7 | 8 | There are a few breaking changes in 5.x. This guide will help you migrate your code. 9 | 10 | ## Behavior changes 11 | 12 | ### `useHotkeys` listens to the code instead of the key by default 13 | 14 | From version 5 on `useHotkeys` will listen to the key code instead of the key. This means that if you were using `useHotkeys` 15 | to listen to a key (`:` or `%`, etc.), you'll have to update your code. 16 | 17 | The hook now accepts an option called `useKey` that will only listen to the produced key. This is useful if you don't care 18 | about the layout of the keyboard and want to listen to the symbol that the user produces (i.e. listening to `/` to open a search bar). 19 | 20 | ### `HotkeysProvider` 21 | 22 | - If all scopes are disabled in the HotkeysProvider, no hotkeys will be active. 23 | 24 | ## API changes 25 | 26 | ### `useHotkeys` 27 | 28 | - The `splitKey` option has been renamed to `delimiter` 29 | - The `combinationKey` option has been renamed to `splitKey` 30 | 31 | ### `HotkeysProvider` Context 32 | 33 | #### `enabledScopes` is now `activeScopes` 34 | `enabledScopes` has been renamed to `activeScopes`, to avoid confusion with the `enableScopes` method. 35 | 36 | ## Other changes 37 | 38 | - We dropped the CommonJS Module. The hook is now only available as an ES Module. If you need to use it in a CommonJS environment, you can use a bundler like Webpack or Rollup to convert it to CommonJS. -------------------------------------------------------------------------------- /packages/documentation/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | const lightCodeTheme = require('prism-react-renderer').themes.github; 2 | const darkCodeTheme = require('prism-react-renderer').themes.dracula; 3 | 4 | /** @type {import('@docusaurus/types').DocusaurusConfig} */ 5 | module.exports = { 6 | title: 'React Hotkeys Hook', 7 | tagline: 'Use hotkeys in a declarative way', 8 | url: 'https://johannesklauss.github.io', 9 | baseUrl: '/', 10 | onBrokenLinks: 'throw', 11 | onBrokenMarkdownLinks: 'warn', 12 | favicon: 'img/favicon.ico', 13 | organizationName: 'Johannes Klauss', // Usually your GitHub org/user name. 14 | projectName: 'react-hotkeys-hook', // Usually your repo name. 15 | themes: ['@docusaurus/theme-live-codeblock'], 16 | themeConfig: { 17 | navbar: { 18 | title: 'React Hotkeys Hook', 19 | items: [ 20 | { 21 | type: 'docsVersion', 22 | docId: 'intro', 23 | position: 'left', 24 | label: 'Documentation', 25 | }, 26 | { 27 | to: 'blog', 28 | label: 'Blog', 29 | position: 'left' 30 | }, 31 | { 32 | type: 'docsVersionDropdown', 33 | position: 'right', 34 | dropdownActiveClassDisabled: true, 35 | }, 36 | { 37 | href: 'https://github.com/JohannesKlauss/react-hotkeys-hook', 38 | label: 'GitHub', 39 | position: 'right', 40 | }, 41 | ], 42 | }, 43 | footer: { 44 | style: 'dark', 45 | links: [ 46 | { 47 | title: 'Docs', 48 | items: [ 49 | { 50 | label: 'Quick Start', 51 | to: '/docs/intro', 52 | }, 53 | { 54 | label: 'useHotkeys', 55 | to: '/docs/documentation/useHotkeys/basic-usage', 56 | }, 57 | { 58 | label: 'isHotkeyPressed', 59 | to: '/docs/documentation/is-hotkey-pressed', 60 | }, 61 | ], 62 | }, 63 | { 64 | title: 'API', 65 | items: [ 66 | { 67 | label: 'useHotkeys', 68 | to: '/docs/api/use-hotkeys', 69 | }, 70 | { 71 | label: 'isHotkeyPressed', 72 | to: '/docs/api/is-hotkey-pressed', 73 | }, 74 | ], 75 | }, 76 | { 77 | title: 'More', 78 | items: [ 79 | { 80 | label: 'Support', 81 | href: 'https://github.com/JohannesKlauss/react-hotkeys-hook/discussions', 82 | }, 83 | { 84 | label: 'Recipes', 85 | href: 'https://github.com/JohannesKlauss/react-hotkeys-hook', 86 | }, 87 | { 88 | label: 'FAQ', 89 | href: 'https://github.com/JohannesKlauss/react-hotkeys-hook', 90 | }, 91 | { 92 | label: 'GitHub', 93 | href: 'https://github.com/JohannesKlauss/react-hotkeys-hook', 94 | }, 95 | { 96 | label: 'StackOverflow', 97 | href: 'https://stackoverflow.com/search?page=1&tab=Relevance&q=react-hotkeys-hook', 98 | }, 99 | ], 100 | }, 101 | ], 102 | copyright: `Documentation built with Docusaurus.`, 103 | }, 104 | prism: { 105 | theme: lightCodeTheme, 106 | darkTheme: darkCodeTheme, 107 | }, 108 | algolia: { 109 | // The application ID provided by Algolia 110 | appId: 'MVSFN13FOZ', 111 | 112 | // Public API key: it is safe to commit it 113 | apiKey: 'ea10bff3a5e2fb0d82492d680e40959e', 114 | 115 | indexName: 'react-hotkeys-hook', 116 | contextualSearch: true, 117 | searchParameters: {}, 118 | 119 | // Optional: path for search page that enabled by default (`false` to disable it) 120 | searchPagePath: 'search', 121 | }, 122 | }, 123 | presets: [ 124 | [ 125 | '@docusaurus/preset-classic', 126 | { 127 | docs: { 128 | sidebarPath: require.resolve('./sidebars.js'), 129 | // Please change this to your repo. 130 | editUrl: 131 | 'https://github.com/JohannesKlauss/react-hotkeys-hook/edit/main/documentation/', 132 | lastVersion: 'current', 133 | versions: { 134 | current: { 135 | label: '5.0', 136 | }, 137 | }, 138 | }, 139 | theme: { 140 | customCss: require.resolve('./src/css/custom.css'), 141 | }, 142 | }, 143 | ], 144 | ], 145 | }; 146 | -------------------------------------------------------------------------------- /packages/documentation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "3.3.5", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "3.7.0", 18 | "@docusaurus/preset-classic": "3.7.0", 19 | "@docusaurus/theme-live-codeblock": "3.7.0", 20 | "@mdx-js/react": "3.1.0", 21 | "@svgr/webpack": "8.1.0", 22 | "clsx": "2.1.1", 23 | "file-loader": "6.2.0", 24 | "prism-react-renderer": "2.4.1", 25 | "react": "19.1.0", 26 | "react-dom": "19.1.0", 27 | "react-hotkeys-hook": "5.0.1", 28 | "url-loader": "4.1.1" 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.5%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/documentation/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], 18 | 19 | // But you can create a sidebar manually 20 | /* 21 | tutorialSidebar: [ 22 | { 23 | type: 'category', 24 | label: 'Tutorial', 25 | items: ['hello'], 26 | }, 27 | ], 28 | */ 29 | }; 30 | 31 | module.exports = sidebars; 32 | -------------------------------------------------------------------------------- /packages/documentation/src/components/HomepageFeatures/index.tsx: -------------------------------------------------------------------------------- 1 | import type React from 'react'; 2 | import clsx from 'clsx'; 3 | import styles from './styles.module.css'; 4 | 5 | type FeatureItem = { 6 | title: string; 7 | Svg: React.ComponentType>; 8 | description: JSX.Element; 9 | }; 10 | 11 | const FeatureList: FeatureItem[] = [ 12 | { 13 | title: 'Declarative', 14 | Svg: require('@site/static/img/react.svg').default, 15 | description: ( 16 | <> 17 | Define your hotkeys declaratively once, and let React Hotkeys take care of all the nitty gritty details of 18 | binding and unbinding them. 19 | 20 | ), 21 | }, 22 | { 23 | title: 'Batteries included', 24 | Svg: require('@site/static/img/battery-full-solid.svg').default, 25 | description: ( 26 | <> 27 | Memoisation, enable on form fields, check for currently pressed keys. React Hotkeys has you covered. 28 | 29 | ), 30 | }, 31 | { 32 | title: 'Powered by TypeScript', 33 | Svg: require('@site/static/img/typescript.svg').default, 34 | description: ( 35 | <> 36 | React Hotkeys Hook is written with TypeScript, so all typings come with it. 37 | 38 | ), 39 | }, 40 | ]; 41 | 42 | function Feature({title, Svg, description}: FeatureItem) { 43 | return ( 44 |
45 |
46 | 47 |
48 |
49 |

{title}

50 |

{description}

51 |
52 |
53 | ); 54 | } 55 | 56 | export default function HomepageFeatures(): JSX.Element { 57 | return ( 58 |
59 |
60 |
61 | {FeatureList.map((props, idx) => ( 62 | 63 | ))} 64 |
65 |
66 |
67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /packages/documentation/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 200px; 10 | width: 200px; 11 | } 12 | -------------------------------------------------------------------------------- /packages/documentation/src/components/UnreferencedHotkeysNotice.tsx: -------------------------------------------------------------------------------- 1 | import { useHotkeys } from 'react-hotkeys-hook'; 2 | import { useState } from 'react'; 3 | import React from 'react'; 4 | 5 | export interface UnreferencedHotkeysNoticeProps { 6 | hotkeyCombination: string; 7 | } 8 | 9 | const UnreferencedHotkeysNotice = ({ hotkeyCombination }: UnreferencedHotkeysNoticeProps) => { 10 | const [didPressCombination, setDidPressCombination] = useState(false); 11 | useHotkeys(hotkeyCombination, () => setDidPressCombination(true)); 12 | 13 | if (!didPressCombination) { 14 | return null; 15 | } 16 | 17 | return ( 18 |
19 |
20 |
21 | 22 | 23 | 24 | 25 | Noticed something? 26 |
27 |
28 |
29 |

30 | We used the ctrl+shift+a+c and shift+c combination two times for two components on this page. 31 | Since we pressed down one hotkey already the example above started at the count of one. Hotkeys are attached 32 | globally if we don't use its return value. 33 |

34 |
35 |
36 | ); 37 | }; 38 | 39 | export default UnreferencedHotkeysNotice; 40 | -------------------------------------------------------------------------------- /packages/documentation/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | /** 3 | * Any CSS included here will be global. The classic template 4 | * bundles Infima by default. Infima is a CSS framework designed to 5 | * work well for content-centric websites. 6 | */ 7 | 8 | /* You can override the default Infima variables here. */ 9 | html[data-theme='light'] { 10 | --ifm-font-color-base: #313845; 11 | --ifm-font-color-contrast: #cdcecf; 12 | 13 | --ifm-color-primary: #0c7d88; 14 | --ifm-color-primary-dark: #0b707a; 15 | --ifm-color-primary-darker: #0a6a74; 16 | --ifm-color-primary-darkest: #08575f; 17 | --ifm-color-primary-light: #0d8996; 18 | --ifm-color-primary-lighter: #0e909c; 19 | --ifm-color-primary-lightest: #10a2b1; 20 | 21 | --ifm-alert-background-color-highlight: rgba(235,237,240,0.15); 22 | 23 | --ifm-color-secondary-contrast-background: #fdfdfe; 24 | --ifm-color-success-contrast-background: #e6f6e6; 25 | --ifm-color-info-contrast-background: #eef9fd; 26 | --ifm-color-warning-contrast-background: #fff8e6; 27 | --ifm-color-danger-contrast-background: #ffebec; 28 | } 29 | 30 | html[data-theme='dark'] .docusaurus-highlight-code-line { 31 | background-color: rgba(0, 0, 0, 0.3); 32 | } 33 | 34 | html[data-theme='dark'] { 35 | --ifm-font-color-base: #cdcecf; 36 | --ifm-font-color-contrast: #313845; 37 | 38 | --ifm-color-primary: #11b6c7; 39 | --ifm-color-primary-dark: #0fa4b3; 40 | --ifm-color-primary-darker: #0e9ba9; 41 | --ifm-color-primary-darkest: #0c7f8b; 42 | --ifm-color-primary-light: #13c8db; 43 | --ifm-color-primary-lighter: #14d1e5; 44 | --ifm-color-primary-lightest: #2cdbed; 45 | } 46 | 47 | :root { 48 | --ifm-hr-border-color: #474748; 49 | 50 | --ifm-code-font-size: 95%; 51 | 52 | --ifm-color-secondary-contrast-background: var(--ifm-font-color-base); 53 | --ifm-color-secondary-contrast-foreground: #474748; 54 | --ifm-color-success-dark: #009400; 55 | --ifm-color-success-darker: #008b00; 56 | --ifm-color-success-darkest: #007300; 57 | --ifm-color-success-light: #26b226; 58 | --ifm-color-success-lighter: #4dbf4d; 59 | --ifm-color-success-lightest: #80d280; 60 | --ifm-color-success-contrast-background: var(--ifm-font-color-base); 61 | --ifm-color-success-contrast-foreground: #003100; 62 | --ifm-color-info-dark: #4cb3d4; 63 | --ifm-color-info-darker: #47a9c9; 64 | --ifm-color-info-darkest: #3b8ba5; 65 | --ifm-color-info-light: #6ecfef; 66 | --ifm-color-info-lighter: #87d8f2; 67 | --ifm-color-info-lightest: #aae3f6; 68 | --ifm-color-info-contrast-background: var(--ifm-font-color-base); 69 | --ifm-color-info-contrast-foreground: #193c47; 70 | --ifm-color-warning-dark: #e6a700; 71 | --ifm-color-warning-darker: #d99e00; 72 | --ifm-color-warning-darkest: #b38200; 73 | --ifm-color-warning-light: #ffc426; 74 | --ifm-color-warning-lighter: #ffcf4d; 75 | --ifm-color-warning-lightest: #ffdd80; 76 | --ifm-color-warning-contrast-background: var(--ifm-font-color-base); 77 | --ifm-color-warning-contrast-foreground: #4d3800; 78 | --ifm-color-danger-dark: #e13238; 79 | --ifm-color-danger-darker: #d53035; 80 | --ifm-color-danger-darkest: #af272b; 81 | --ifm-color-danger-light: #fb565b; 82 | --ifm-color-danger-lighter: #fb7478; 83 | --ifm-color-danger-lightest: #fd9c9f; 84 | --ifm-color-danger-contrast-background: var(--ifm-font-color-base); 85 | --ifm-color-danger-contrast-foreground: #4b1113; 86 | --ifm-alert-border-left-width: 5px; 87 | --ifm-alert-border-width: 0; 88 | } 89 | 90 | html[data-theme=dark] { 91 | --ifm-color-secondary-contrast-background: #474748; 92 | --ifm-color-secondary-contrast-foreground: var(--ifm-font-color-base); 93 | --ifm-color-success-contrast-background: #003100; 94 | --ifm-color-success-contrast-foreground: var(--ifm-font-color-base); 95 | --ifm-color-info-contrast-background: #193c47; 96 | --ifm-color-info-contrast-foreground: var(--ifm-font-color-base); 97 | --ifm-color-warning-contrast-background: #4d3800; 98 | --ifm-color-warning-contrast-foreground: var(--ifm-font-color-base); 99 | --ifm-color-danger-contrast-background: #4b1113; 100 | --ifm-color-danger-contrast-foreground: var(--ifm-font-color-base); 101 | } 102 | 103 | blockquote { 104 | border-left: 6px solid var(--ifm-color-primary-lighter); 105 | color: var(--ifm-color-primary-lighter); 106 | } 107 | 108 | .alert { 109 | --ifm-code-background: var(--ifm-alert-background-color-highlight); 110 | --ifm-link-color: var(--ifm-alert-foreground-color); 111 | --ifm-link-hover-color: var(--ifm-alert-foreground-color); 112 | --ifm-tabs-color: var(--ifm-alert-foreground-color); 113 | --ifm-tabs-color-active: var(--ifm-alert-foreground-color); 114 | --ifm-tabs-color-active-border: var(--ifm-alert-border-color); 115 | --ifm-link-decoration: underline; 116 | background-color: var(--ifm-alert-background-color); 117 | border-left-width: var(--ifm-alert-border-width); 118 | border: var(--ifm-alert-border-width) solid var(--ifm-alert-border-color); 119 | border-left: var(--ifm-alert-border-left-width) solid var(--ifm-alert-border-color); 120 | border-radius: var(--ifm-alert-border-radius); 121 | box-shadow: var(--ifm-alert-shadow); 122 | padding: var(--ifm-alert-padding-vertical) var(--ifm-alert-padding-horizontal); 123 | color: var(--ifm-alert-foreground-color); 124 | } 125 | 126 | .alert--info { 127 | --ifm-alert-background-color: var( --ifm-color-info-contrast-background ); 128 | --ifm-alert-background-color-highlight: rgba(84,199,236,0.15); 129 | --ifm-alert-foreground-color: var( --ifm-color-info-contrast-foreground ); 130 | --ifm-alert-border-color: var(--ifm-color-info-dark); 131 | } 132 | 133 | .alert--secondary { 134 | --ifm-alert-background-color: var( --ifm-color-secondary-contrast-background ); 135 | --ifm-alert-background-color-highlight: rgba(235,237,240,0.15); 136 | --ifm-alert-foreground-color: var( --ifm-color-secondary-contrast-foreground ); 137 | --ifm-alert-border-color: var(--ifm-color-secondary-dark); 138 | } 139 | 140 | .alert--success { 141 | --ifm-alert-background-color: var( --ifm-color-success-contrast-background ); 142 | --ifm-alert-background-color-highlight: rgba(0,164,0,0.15); 143 | --ifm-alert-foreground-color: var( --ifm-color-success-contrast-foreground ); 144 | --ifm-alert-border-color: var(--ifm-color-success-dark); 145 | } 146 | 147 | .alert--warning { 148 | --ifm-alert-background-color: var( --ifm-color-warning-contrast-background ); 149 | --ifm-alert-background-color-highlight: rgba(255,186,0,0.15); 150 | --ifm-alert-foreground-color: var( --ifm-color-warning-contrast-foreground ); 151 | --ifm-alert-border-color: var(--ifm-color-warning-dark); 152 | } 153 | 154 | .alert--danger { 155 | --ifm-alert-background-color: var( --ifm-color-danger-contrast-background ); 156 | --ifm-alert-background-color-highlight: rgba(250,56,62,0.15); 157 | --ifm-alert-foreground-color: var( --ifm-color-danger-contrast-foreground ); 158 | --ifm-alert-border-color: var(--ifm-color-danger-dark); 159 | } 160 | 161 | .alert a { 162 | color: var(--ifm-alert-foreground-color); 163 | text-decoration-color: var(--ifm-alert-border-color); 164 | } 165 | 166 | .alert .admonition-icon svg { 167 | fill: var(--ifm-alert-border-color); 168 | stroke: var(--ifm-alert-border-color); 169 | } 170 | 171 | .docusaurus-highlight-code-line { 172 | background-color: rgba(0, 0, 0, 0.1); 173 | display: block; 174 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 175 | padding: 0 var(--ifm-pre-padding); 176 | } 177 | 178 | iframe { 179 | border: none; 180 | } 181 | -------------------------------------------------------------------------------- /packages/documentation/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | 3 | /** 4 | * CSS files with the .module.css suffix will be treated as CSS modules 5 | * and scoped locally. 6 | */ 7 | 8 | .heroBanner { 9 | padding: 4rem 0; 10 | text-align: center; 11 | position: relative; 12 | overflow: hidden; 13 | } 14 | 15 | @media screen and (max-width: 966px) { 16 | .heroBanner { 17 | padding: 2rem; 18 | } 19 | } 20 | 21 | .buttons { 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | gap: 20px; 26 | } 27 | 28 | .heroProjectTagline { 29 | font-size: 60px; 30 | margin: 0; 31 | } 32 | 33 | .fullWidth { 34 | width: 100%; 35 | } 36 | 37 | .rightAlign { 38 | text-align: right; 39 | } 40 | -------------------------------------------------------------------------------- /packages/documentation/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import Layout from '@theme/Layout'; 4 | import Link from '@docusaurus/Link'; 5 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 6 | import styles from './index.module.css'; 7 | import HomepageFeatures from '@site/src/components/HomepageFeatures'; 8 | import CodeBlock from '@theme/CodeBlock'; 9 | 10 | function HomepageHeader() { 11 | const {siteConfig} = useDocusaurusContext(); 12 | return ( 13 |
14 |
15 |

{siteConfig.title}

16 |

{siteConfig.tagline}

17 |
18 | 21 | Quick Start 22 | 23 | 24 | 27 | Documentation 28 | 29 | 30 | 33 |
34 |
35 |
36 | ); 37 | } 38 | 39 | export default function Home(): JSX.Element { 40 | const {siteConfig} = useDocusaurusContext(); 41 | return ( 42 | 45 | 46 |
47 | 48 | 49 |
50 |
51 |
52 |

Easy to use

53 |

Use just one hook to bind your hotkeys to a component.

54 |
55 |
56 | 57 | {`function MyAwesomeComponent() { 58 | const [count, setCount] = useState(0) 59 | useHotkeys('a', () => setCount(prevCount => 60 | prevCount + 1 61 | )) 62 | 63 | return ( 64 | {count} 65 | ) 66 | }`} 67 | 68 |
69 |
70 | 71 |
72 |
73 | 74 | {`function MyAwesomeComponent() { 75 | const [count, setCount] = useState(0) 76 | const ref = useHotkeys('b', () => setCount(prevCount => 77 | prevCount + 1 78 | )) 79 | 80 | return ( 81 |
82 | 83 | Focusing this element will disable the hotkey {count}. 84 | 85 |
86 | 87 | Focusing this element will enable the hotkey {count}. 88 | 89 |
90 | ) 91 | }`} 92 |
93 | 94 |
95 |
96 |

Dead simple component scoping

97 |

With the usage of a returned ref you can easily scope your callback to your component, sub component or subtree.

98 |
99 |
100 | 101 |
102 |
103 |

Modifier support and combinations

104 |

Use all major modifiers. You can also combine multiple hotkey combinations to trigger the same callback. Supports ctrl, cmd, option, alt, pagedown, etc.

105 |
106 |
107 | 108 | {`function MyAwesomeComponent() { 109 | const [count, setCount] = useState(0); 110 | useHotkeys('ctrl+a, shift+ctrl+x', () => setCount(prevCount => 111 | prevCount + 1 112 | )) 113 | 114 | return ( 115 | {count} 116 | ) 117 | }`} 118 | 119 |
120 |
121 | 122 |
123 |
124 | 125 | {`function MyAwesomeComponent() { 126 | const [enabled, setEnabled] = useState(false) 127 | const [count, setCount] = useState(0) 128 | useHotkeys('shift+c', () => setCount(prevCount => prevCount + 1), { 129 | enabled, 130 | }) 131 | 132 | const onClick = () => setEnabled(prevEnabled => !prevEnabled) 133 | 134 | return ( 135 |
136 | 137 |

Hotkey is {!enabled && 'not'} enabled.

138 |

Pressed the 'shift+c' keystroke {count} times.

139 |
140 | ) 141 | }`} 142 |
143 | 144 |
145 |
146 |

Dynamically enable or disable hotkeys

147 |

You can enable and disable hotkeys during runtime as well as prevent the defaults browser behavior.

148 |
149 |
150 |
151 |
152 |
153 | ); 154 | } 155 | -------------------------------------------------------------------------------- /packages/documentation/src/pages/markdown-page.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohannesKlauss/react-hotkeys-hook/c5a7817c332b2fb96df28716495301adc9cf334f/packages/documentation/src/pages/markdown-page.md -------------------------------------------------------------------------------- /packages/documentation/src/theme/ReactLiveScope/index.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { isHotkeyPressed, useHotkeys, useRecordHotkeys } from 'react-hotkeys-hook/src/lib' 3 | // Add react-live imports you need here 4 | const ReactLiveScope = { 5 | React, 6 | ...React, 7 | isHotkeyPressed, 8 | useHotkeys, 9 | useRecordHotkeys, 10 | } 11 | export default ReactLiveScope 12 | -------------------------------------------------------------------------------- /packages/documentation/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohannesKlauss/react-hotkeys-hook/c5a7817c332b2fb96df28716495301adc9cf334f/packages/documentation/static/.nojekyll -------------------------------------------------------------------------------- /packages/documentation/static/img/battery-full-solid.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/documentation/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohannesKlauss/react-hotkeys-hook/c5a7817c332b2fb96df28716495301adc9cf334f/packages/documentation/static/img/favicon.ico -------------------------------------------------------------------------------- /packages/documentation/static/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/documentation/static/img/react.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/documentation/static/img/typescript.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | TypeScript logo 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/documentation/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@tsconfig/docusaurus/tsconfig.json", 4 | "compilerOptions": { 5 | "baseUrl": "./" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/documentation/versioned_docs/version-3.x/api/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "API", 3 | "position": 3 4 | } 5 | -------------------------------------------------------------------------------- /packages/documentation/versioned_docs/version-3.x/api/is-hotkey-pressed.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | title: isHotkeyPressed 4 | --- 5 | 6 | # isHotkeyPressed API 7 | 8 | Function signature: 9 | 10 | ```ts 11 | function isHotkeyPressed(key: string | number): boolean 12 | ``` 13 | 14 | This function allows us to check if a specific key is pressed by the user. 15 | 16 | ```jsx 17 | const isHotkeyPressed = useIsHotkeyPressed(); 18 | 19 | const onClick = () => isHotkeyPressed('shift') ? setCount(count - 1) : setCount(count + 1); 20 | ``` 21 | 22 | *** 23 | 24 | ## Arguments 25 | 26 | ### key 27 | 28 | ```ts 29 | key: string | number 30 | ``` 31 | 32 | The key we want to listen to. This can be a string (like `'a'`, `'shift'`) or a keycode number like `13` for the `return/enter` key 33 | or `65` for the `a` key. 34 | 35 | :::info Multiple keys are not supported 36 | Currently this function can only check for one key at a time. So something like `isHotkeyPressed('shift, a, c')` won't work. 37 | This might change in version 4. 38 | ::: 39 | 40 | ## Return value 41 | 42 | ### boolean 43 | 44 | The function returns `true` if the key is currently pressed down, otherwise it will return `false`. -------------------------------------------------------------------------------- /packages/documentation/versioned_docs/version-3.x/api/use-is-hotkey-pressed.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | title: useIsHotkeyPressed 4 | --- 5 | 6 | # useIsHotkeyPressed API 7 | 8 | :::danger This hook is deprecated 9 | Please do not use this hook anymore since it will be removed in version 4. Instead please use the more direct approach by 10 | importing the [`isHotkeyPressed`](is-hotkey-pressed.mdx) function. 11 | ::: 12 | 13 | Function signature: 14 | 15 | ```ts 16 | function useIsHotkeyPressed(): (key: string | number) => boolean 17 | ``` 18 | 19 | This hook allows us to check on function execution if a specific key is pressed by the user. 20 | If a user presses a button we might want to check if a key is pressed down to execute an alternative action for that button. 21 | 22 | ```jsx 23 | const isHotkeyPressed = useIsHotkeyPressed(); 24 | 25 | const onClick = () => isHotkeyPressed('shift') ? setCount(count - 1) : setCount(count + 1); 26 | ``` -------------------------------------------------------------------------------- /packages/documentation/versioned_docs/version-3.x/documentation/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Documentation", 3 | "position": 2 4 | } 5 | -------------------------------------------------------------------------------- /packages/documentation/versioned_docs/version-3.x/documentation/advanced-usage.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | title: "Advanced Usage" 4 | --- 5 | 6 | # Advanced Usage 7 | 8 | This section described advanced functionalities of the hook. 9 | 10 | **🚧 This section is under construction 🚧** 11 | 12 | ## Listening on `keyUp` 13 | 14 | ## Retrieve pressed hotkey 15 | 16 | ## Enable hotkeys on input fields 17 | 18 | ## The `contentEditable` prop 19 | 20 | ## Using the `filter` option 21 | 22 | ## `filter` vs `enabled` -------------------------------------------------------------------------------- /packages/documentation/versioned_docs/version-3.x/documentation/installation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | import Tabs from '@theme/Tabs'; 6 | import TabItem from '@theme/TabItem'; 7 | import CodeBlock from '@theme/CodeBlock'; 8 | 9 | # Installation 10 | 11 | > Note: React 16.8+ is required to use Hooks. 12 | 13 | ## Yarn & NPM 14 | 15 | To install the package we use our favorite package manager. 16 | 17 | 23 | 24 | yarn add react-hotkeys-hook 25 | 26 | 27 | npm i react-hotkeys-hook --save 28 | 29 | 30 | 31 | ## ES Module 32 | 33 | To use the ES Module we can reference it directly from [unpkg.com](https://unpkg.com): 34 | 35 | ```js 36 | import { useHotkeys } from 'https://unpkg.com/browse/react-hotkeys-hook@3.4.0/dist/react-hotkeys-hook.esm.js'; 37 | ``` 38 | 39 | ## Direct CommonJS Module 40 | 41 | We also have access to a development as well as a minified production bundle for CommonJS: 42 | 43 | 49 | 50 | 51 | {'