Repository: jaywcjlove/hotkeys-js Branch: master Commit: b274b620e006 Files: 35 Total size: 180.1 KB Directory structure: gitextract_070mo1hj/ ├── .editorconfig ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ ├── ci.yml │ └── pr.yml ├── .gitignore ├── .husky/ │ ├── .gitignore │ └── pre-commit ├── .npmignore ├── LICENSE ├── README-zh.md ├── README.md ├── dist/ │ ├── compatibility-test.html │ ├── hotkeys-js.js │ ├── hotkeys-js.umd.cjs │ └── index.d.ts ├── eslint.config.js ├── package.json ├── src/ │ ├── index.ts │ ├── types.ts │ ├── utils.ts │ └── var.ts ├── test/ │ ├── index.html │ └── run.test.js ├── tsconfig.json ├── vite.config.ts └── website/ ├── App.tsx ├── components/ │ ├── Footer.module.css │ └── Footer.tsx ├── index.html ├── index.tsx ├── styles/ │ ├── index.module.css │ └── reset.css ├── tsconfig.json ├── vite-env.d.ts └── vite.config.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # http://editorconfig.org root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false [Makefile] indent_style = tab ================================================ FILE: .github/FUNDING.yml ================================================ github: [jaywcjlove] #ko_fi: jaywcjlove #buy_me_a_coffee: jaywcjlove # custom: ["https://wangchujiang.com/#/sponsor"] ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: branches: - master # tags: # - v* env: SKIP_PREFLIGHT_CHECK: true jobs: build-deploy: runs-on: ubuntu-latest permissions: contents: write id-token: write steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: 24 registry-url: 'https://registry.npmjs.org' - name: Look Changelog uses: jaywcjlove/changelog-generator@main with: token: ${{ secrets.GITHUB_TOKEN }} filter-author: (jaywcjlove|小弟调调™|dependabot\[bot\]|Renovate Bot) filter: (^[\s]+?[R|r]elease)|(^[R|r]elease) - run: npm install - run: npm run build - run: npm run test - run: cp -rp dist doc - run: cp -rp coverage/lcov-report doc - name: Extract coverage percentage id: coverage run: | COVERAGE=$(cat coverage/js-coverage.txt) echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT echo "Coverage: $COVERAGE%" - name: Generate Badges uses: jaywcjlove/generated-badges@main with: label: coverage status: ${{ steps.coverage.outputs.coverage }}% output: doc/coverage.svg - name: Generate Contributors Images uses: jaywcjlove/github-action-contributors@main with: filter-author: (renovate\[bot\]|renovate-bot|dependabot\[bot\]) output: doc/CONTRIBUTORS.svg avatarSize: 42 - name: Create Tag id: create_tag uses: jaywcjlove/create-tag-action@main with: package-path: ./package.json - name: get tag version id: tag_version uses: jaywcjlove/changelog-generator@main - name: Build and Deploy uses: peaceiris/actions-gh-pages@v4 with: commit_message: ${{steps.tag_version.outputs.tag}} ${{ github.event.head_commit.message }} github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./doc - name: Generate Changelog id: changelog uses: jaywcjlove/changelog-generator@main with: token: ${{ secrets.GITHUB_TOKEN }} filter-author: (jaywcjlove|小弟调调™|dependabot\[bot\]|Renovate Bot) filter: (^[\s]+?[R|r]elease)|(^[R|r]elease) - run: | echo "tag: ${{ steps.changelog.outputs.tag }}" echo "version: ${{ steps.changelog.outputs.version }}" echo "ref: ${{ github.ref }}" - name: Create Release uses: ncipollo/release-action@v1 if: steps.create_tag.outputs.successful with: allowUpdates: true token: ${{ secrets.GITHUB_TOKEN }} name: ${{ steps.create_tag.outputs.version }} tag: ${{ steps.create_tag.outputs.version }} body: | [![Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-048754?logo=buymeacoffee)](https://jaywcjlove.github.io/#/sponsor) [![](https://img.shields.io/badge/Open%20in-unpkg-blue)](https://uiwjs.github.io/npm-unpkg/#/pkg/hotkeys-js@${{steps.changelog.outputs.version}}/file/README.md) [![npm bundle size](https://img.shields.io/bundlephobia/minzip/hotkeys-js)](https://bundlephobia.com/result?p=hotkeys-js@${{steps.changelog.outputs.version}}) [![npm version](https://img.shields.io/npm/v/hotkeys-js.svg)](https://www.npmjs.com/package/hotkeys-js) Documentation ${{ steps.changelog.outputs.tag }}: https://raw.githack.com/jaywcjlove/hotkeys/${{ steps.changelog.outputs.gh-pages-short-hash }}/index.html Comparing Changes: ${{ steps.changelog.outputs.compareurl }} ```bash npm i hotkeys-js@${{steps.changelog.outputs.version}} ``` ${{ steps.changelog.outputs.changelog }} - name: package.json info uses: jaywcjlove/github-action-package@main with: unset: browserslist,lint-staged,devDependencies,jest,scripts # node@v24.0.0+ # https://gist.github.com/jaywcjlove/a178278521a6f72c74525d3f1d9c4bf9 - run: NODE_AUTH_TOKEN="" npm publish --access public --provenance name: 📦 hotkeys-js to NPM continue-on-error: true ================================================ FILE: .github/workflows/pr.yml ================================================ name: CI-PR on: pull_request: env: SKIP_PREFLIGHT_CHECK: true jobs: build-deploy: runs-on: ubuntu-latest permissions: contents: write id-token: write steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: 24 registry-url: 'https://registry.npmjs.org' - name: Look Changelog uses: jaywcjlove/changelog-generator@main with: token: ${{ secrets.GITHUB_TOKEN }} filter-author: (jaywcjlove|小弟调调™|dependabot\[bot\]|Renovate Bot) filter: (^[\s]+?[R|r]elease)|(^[R|r]elease) - run: npm install - run: npm run build - run: npm run test ================================================ FILE: .gitignore ================================================ npm-debug.log yarn.lock node_modules build doc coverage .DS_Store .cache .vscode .idea *.bak *.tem *.temp #.swp *.*~ ~*.* ================================================ FILE: .husky/.gitignore ================================================ _ ================================================ FILE: .husky/pre-commit ================================================ #!/bin/sh . "$(dirname "$0")/_/husky.sh" npx lint-staged ================================================ FILE: .npmignore ================================================ # Exclude test files dist/test-formats.html # Exclude development files src/ test/ website/ coverage/ # Exclude configuration files *.config.ts *.config.js eslint.config.js tsconfig.json # Exclude other unnecessary files README-zh.md DISTRIBUTION-GUIDE.md DISTRIBUTION-GUIDE-zh.md .github/ .gitignore .husky/ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2015-present, Kenny Wong. Copyright (c) 2011-2013 Thomas Fuchs (https://github.com/madrobby/keymaster) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README-zh.md ================================================
使用我的应用也是一种支持我的方式:
Scap: Screenshot & Markup Edit Screen Test Deskmark Keyzer Vidwall Hub VidCrop Vidwall Mousio Hint Mousio Musicer Audioer FileSentinel FocusCursor Videoer KeyClicker DayBar Iconed Menuist Quick RSS Quick RSS Web Serve Copybook Generator DevTutor for SwiftUI RegexMate Time Passage Iconize Folder Textsound Saver Create Custom Symbols DevHub Resume Revise Palette Genius Symbol Scribe

# Hotkeys [![Buy me a coffee](https://img.shields.io/badge/Buy_Me_a_Coffee-ffdd00?logo=buy-me-a-coffee&logoColor=black)](https://jaywcjlove.github.io/#/sponsor) [![Follow On X](https://img.shields.io/badge/Follow%20on%20X-333333?logo=x&logoColor=white)](https://x.com/jaywcjlove) [![](https://img.shields.io/npm/dm/hotkeys-js?logo=npm)](https://www.npmjs.com/package/hotkeys-js) [![](https://img.shields.io/github/stars/jaywcjlove/hotkeys-js.svg)](https://github.com/jaywcjlove/hotkeys/stargazers) [![GitHub Actions CI](https://github.com/jaywcjlove/hotkeys-js/actions/workflows/ci.yml/badge.svg)](https://github.com/jaywcjlove/hotkeys-js/actions/workflows/ci.yml) [![Coverage Status](https://jaywcjlove.github.io/hotkeys-js/coverage.svg)](https://jaywcjlove.github.io/hotkeys-js/lcov-report/index.html) [![English](https://jaywcjlove.github.io/sb/lang/english.svg)](https://jaywcjlove.github.io/hotkeys-js/) [![jaywcjlove/hotkeys-js](https://jaywcjlove.github.io/sb/ico/gitee.svg)](https://gitee.com/jaywcjlove/hotkeys) HotKeys.js 是一个具有一些非常特殊功能的输入捕获库,它易于上手和使用,占用空间合理([~8kB](https://bundlephobia.com/result?p=hotkeys-js))(压缩后:**`3.8kB`**),无依赖。它不应该干扰任何 JavaScript 库或框架。官方文档 [演示预览](https://jaywcjlove.github.io/hotkeys-js),[兼容性测试](https://jaywcjlove.github.io/hotkeys-js/dist/compatibility-test.html)。[更多示例](https://github.com/jaywcjlove/hotkeys-js/issues?q=label%3ADemo+)。 ```bash ╭┈┈╮ ╭┈┈╮ ╭┈┈╮ ┆ ├┈┈..┈┈┈┈┈.┆ └┈╮┆ ├┈┈..┈┈┈┈┈..┈┈.┈┈..┈┈┈┈┈. ┆ ┆┆ □ ┆┆ ┈┤┆ < ┆ -__┘┆ ┆ ┆┆__ ┈┈┤ ╰┈┈┴┈┈╯╰┈┈┈┈┈╯╰┈┈┈┈╯╰┈┈┴┈┈╯╰┈┈┈┈┈╯╰┈┈┈ ┆╰┈┈┈┈┈╯ ╰┈┈┈┈┈╯ ``` ## 使用 您的系统需要安装 `Node.js`。 ```bash npm install hotkeys-js --save ``` ```js import hotkeys from 'hotkeys-js'; hotkeys('f5', function(event, handler){ // 阻止 WINDOWS 系统下的默认刷新事件 event.preventDefault() alert('你按下了 F5!') }); ``` ### 浏览器使用 或者手动下载并在 HTML 中链接 **hotkeys.js**。该库提供了不同格式以满足不同的使用需求: **CDN 链接:** [UNPKG](https://unpkg.com/hotkeys-js/dist/) | [jsDelivr](https://cdn.jsdelivr.net/npm/hotkeys-js/) | [Githack](https://raw.githack.com/jaywcjlove/hotkeys/master/dist/) | [Statically](https://cdn.statically.io/gh/jaywcjlove/hotkeys/master/dist/) **可用格式:** **IIFE(立即调用函数表达式)- 推荐用于直接浏览器使用:** ```html ``` **UMD(通用模块定义)- 用于 CommonJS/AMD 环境:** ```html ``` **ES 模块 - 用于支持模块的现代浏览器:** ```html ``` ### 在 React 中使用 [react-hotkeys](https://github.com/jaywcjlove/react-hotkeys) 是监听 keydown 和 keyup 键盘事件的 React 组件,定义和调度键盘快捷键。详细的使用方法请查看其文档 [react-hotkeys](https://github.com/jaywcjlove/react-hotkeys)。 [react-hotkeys-hook](https://github.com/JohannesKlauss/react-hotkeys-hook) - 在组件中使用键盘快捷键的 React hook。请确保您至少安装了 react 和 react-dom 的 16.8 版本,否则 hooks 将不会为您工作。 ## 浏览器支持 Hotkeys.js 已经过测试,应该在以下浏览器中工作。 ```shell Internet Explorer 6+ Safari Firefox Chrome ``` ## 支持的按键 HotKeys 理解以下修饰符:`⇧`、`shift`、`option`、`⌥`、`alt`、`ctrl`、`control`、`command` 和 `⌘`。 以下特殊按键可用于快捷键:backspace、tab、clear、enter、return、esc、escape、space、up、down、left、right、home、end、pageup、pagedown、del、delete、f1 到 f19、num_0 到 num_9、num_multiply、num_add、num_enter、num_subtract、num_decimal、num_divide。 `⌘` Command() `⌃` Control `⌥` Option(alt) `⇧` Shift `⇪` Caps Lock(Capital) ~~`fn` 不支持 fn~~ `↩︎` return/Enter space ## 定义快捷键 暴露了一个全局方法,当直接调用时定义快捷键。 ```ts declare interface HotkeysInterface extends HotkeysAPI { (key: string, method: KeyHandler): void; (key: string, scope: string, method: KeyHandler): void; (key: string, option: HotkeysOptions, method: KeyHandler): void; shift?: boolean; ctrl?: boolean; alt?: boolean; option?: boolean; control?: boolean; cmd?: boolean; command?: boolean; } declare interface HotkeysAPI { setScope: SetScope; getScope: GetScope; deleteScope: DeleteScope; getPressedKeyCodes: GetPressedKeyCodes; getPressedKeyString: GetPressedKeyString; getAllKeyCodes: GetAllKeyCodes; isPressed: IsPressed; filter: Filter; trigger: Trigger; unbind: Unbind; noConflict: NoConflict; keyMap: Record; modifier: Record; modifierMap: Record; } ``` ```js hotkeys('f5', function(event, handler) { // 阻止 WINDOWS 系统下的默认刷新事件 event.preventDefault(); alert('你按下了 F5!'); }); // 返回 false 停止事件并阻止默认浏览器事件 // Mac OS 系统将 `command + r` 定义为刷新快捷键 hotkeys('ctrl+r, command+r', function() { alert('停止刷新!'); return false; }); // 单个按键 hotkeys('a', function(event,handler){ //event.srcElement: input //event.target: input if(event.target === "input"){ alert('你按下了 a!') } alert('你按下了 a!') }); // 组合键 hotkeys('ctrl+a,ctrl+b,r,f', function (event, handler){ switch (handler.key) { case 'ctrl+a': alert('你按下了 ctrl+a!'); break; case 'ctrl+b': alert('你按下了 ctrl+b!'); break; case 'r': alert('你按下了 r!'); break; case 'f': alert('你按下了 f!'); break; default: alert(event); } }); hotkeys('ctrl+a+s', function() { alert('你按下了 ctrl+a+s!'); }); // 使用作用域 hotkeys('*','wcj', function(event){ console.log('做一些事情', event); }); ``` #### option 选项 - `scope`:设置快捷键生效的作用域 - `element`:指定要绑定事件的 DOM 元素 - `keyup`:是否在按键释放时触发快捷键 - `keydown`:是否在按键按下时触发快捷键 - `splitKey`:组合键的分隔符(默认为 `+`) - `capture`:是否在捕获阶段触发监听器(在事件冒泡之前) - `single`:只允许一个回调函数(自动解绑之前的) ```js hotkeys('o, enter', { scope: 'wcj', element: document.getElementById('wrapper'), }, function() { console.log('做其他事情'); }); hotkeys('ctrl-+', { splitKey: '-' }, function(e) { console.log('你按下了 ctrl 和 +'); }); hotkeys('+', { splitKey: '-' }, function(e){ console.log('你按下了 +'); }) ``` **keyup** **按键按下** 和 **按键释放** 都执行回调事件。 ```js hotkeys('ctrl+a,alt+a+s', {keyup: true}, function(event, handler) { if (event.type === 'keydown') { console.log('keydown:', event.type, handler, handler.key); } if (event.type === 'keyup') { console.log('keyup:', event.type, handler, handler.key); } }); ``` ## API 参考 星号 "*" 修饰键判断 ```js hotkeys('*', function() { if (hotkeys.shift) { console.log('按下了 shift!'); } if (hotkeys.ctrl) { console.log('按下了 ctrl!'); } if (hotkeys.alt) { console.log('按下了 alt!'); } if (hotkeys.option) { console.log('按下了 option!'); } if (hotkeys.control) { console.log('按下了 control!'); } if (hotkeys.cmd) { console.log('按下了 cmd!'); } if (hotkeys.command) { console.log('按下了 command!'); } }); ``` ### setScope 使用 `hotkeys.setScope` 方法来设置作用域。除了 'all' 之外,只能有一个活动作用域。默认情况下 'all' 总是活动的。 ```js // 定义带有作用域的快捷键 hotkeys('ctrl+o, ctrl+alt+enter', 'issues', function() { console.log('做一些事情'); }); hotkeys('o, enter', 'files', function() { console.log('做其他事情'); }); // 设置作用域(只有 'all' 和 'issues' 快捷键会被处理) hotkeys.setScope('issues'); // 默认作用域是 'all' ``` ### getScope 使用 `hotkeys.getScope` 方法来获取作用域。 ```js hotkeys.getScope(); ``` ### deleteScope 使用 `hotkeys.deleteScope` 方法来删除作用域。这也会移除与之关联的所有热键。 ```js hotkeys.deleteScope('issues'); ``` 如果需要在删除后设置新的作用域,可以使用第二个参数。 ```js hotkeys.deleteScope('issues', 'newScopeName'); ``` ### unbind 与定义快捷键类似,它们可以使用 `hotkeys.unbind` 来解绑。 ```js // 解绑 'a' 处理器 hotkeys.unbind('a'); // 只为单个作用域解绑热键 // 如果没有指定作用域,默认为当前作用域 // (hotkeys.getScope()) hotkeys.unbind('o, enter', 'issues'); hotkeys.unbind('o, enter', 'files'); ``` 通过函数解绑事件。 ```js function example() { hotkeys('a', example); hotkeys.unbind('a', example); hotkeys('a', 'issues', example); hotkeys.unbind('a', 'issues', example); } ``` 解绑所有。 ```js hotkeys.unbind(); ``` ### isPressed 例如,如果当前按下了 `M` 键,`hotkeys.isPressed(77)` 返回 true。 ```js hotkeys('a', function() { console.log(hotkeys.isPressed('a')); //=> true console.log(hotkeys.isPressed('A')); //=> true console.log(hotkeys.isPressed(65)); //=> true }); ``` ### trigger 触发快捷键事件 ```js hotkeys.trigger('ctrl+o'); hotkeys.trigger('ctrl+o', 'scope2'); ``` ### getPressedKeyCodes 返回当前按下的键码数组。 ```js hotkeys('command+ctrl+shift+a,f', function() { console.log(hotkeys.getPressedKeyCodes()); //=> [17, 65] 或 [70] }) ``` ### getPressedKeyString 返回当前按下的键字符串数组。 ```js hotkeys('command+ctrl+shift+a,f', function() { console.log(hotkeys.getPressedKeyString()); //=> ['⌘', '⌃', '⇧', 'A', 'F'] }) ``` ### getAllKeyCodes 获取所有注册码的列表。 ```js hotkeys('command+ctrl+shift+a,f', function() { console.log(hotkeys.getAllKeyCodes()); // [ // { // scope: 'all', // shortcut: 'command+ctrl+shift+a', // mods: [91, 17, 16], // keys: [91, 17, 16, 65] // }, // { scope: 'all', shortcut: 'f', mods: [], keys: [42] } // ] }) ``` ### filter 默认情况下,`INPUT` `SELECT` `TEXTAREA` 元素不启用热键。`Hotkeys.filter` 返回 `true` 快捷键设置发挥作用,`false` 快捷键设置失败。 ```js hotkeys.filter = function(event){ return true; } // 如何为编辑标签添加过滤器。 //
// "contentEditable" 不支持的较旧浏览器会被丢弃 hotkeys.filter = function(event) { var target = event.target || event.srcElement; var tagName = target.tagName; return !( target.isContentEditable || tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA' ); } hotkeys.filter = function(event){ var tagName = (event.target || event.srcElement).tagName; hotkeys.setScope( /^(INPUT|TEXTAREA|SELECT)$/.test(tagName) ? 'input' : 'other' ); return true; } ``` ### noConflict 放弃 HotKeys 对 `hotkeys` 变量的控制。 ```js var k = hotkeys.noConflict(); k('a', function() { console.log("做一些事情") }); hotkeys() // -->Uncaught TypeError: hotkeys is not a function(anonymous function) // @ VM2170:2InjectedScript._evaluateOn // @ VM2165:883InjectedScript._evaluateAndWrap // @ VM2165:816InjectedScript.evaluate @ VM2165:682 ``` ## 开发 要开发,需要安装依赖,获取代码: ```shell $ git https://github.com/jaywcjlove/hotkeys.git $ cd hotkeys # 进入目录 $ npm install # 或者 yarn install ``` 要开发,运行自重载构建: ```shell $ npm run watch ``` 运行文档网站环境。 ```shell $ npm run doc # 生成文档网页 # 实时生成文档网页 $ npm run start ``` 要贡献,请 fork Hotkeys.js,添加您的补丁和测试(在 `test/` 文件夹中)并提交拉取请求。 ```shell $ npm run test $ npm run test:watch # 开发模式 ``` ## 贡献者 一如既往,感谢我们出色的贡献者! 使用 [action-contributors](https://github.com/jaywcjlove/github-action-contributors) 制作。 特别感谢 [@dimensi](https://github.com/dimensi) 对版本 [4.0](https://github.com/jaywcjlove/hotkeys-js/issues/313) 的重构。 ## 许可证 [MIT © Kenny Wong](./LICENSE) ================================================ FILE: README.md ================================================
Using my app is also a way to support me:
Scap: Screenshot & Markup Edit Screen Test Deskmark Keyzer Vidwall Hub VidCrop Vidwall Mousio Hint Mousio Musicer Audioer FileSentinel FocusCursor Videoer KeyClicker DayBar Iconed Menuist Quick RSS Quick RSS Web Serve Copybook Generator DevTutor for SwiftUI RegexMate Time Passage Iconize Folder Textsound Saver Create Custom Symbols DevHub Resume Revise Palette Genius Symbol Scribe

# Hotkeys [![Buy me a coffee](https://img.shields.io/badge/Buy_Me_a_Coffee-ffdd00?logo=buy-me-a-coffee&logoColor=black)](https://jaywcjlove.github.io/#/sponsor) [![Follow On X](https://img.shields.io/badge/Follow%20on%20X-333333?logo=x&logoColor=white)](https://x.com/jaywcjlove) [![](https://img.shields.io/npm/dm/hotkeys-js?logo=npm&label=)](https://www.npmjs.com/package/hotkeys-js) [![](https://img.shields.io/github/stars/jaywcjlove/hotkeys-js.svg)](https://github.com/jaywcjlove/hotkeys/stargazers) [![GitHub Actions CI](https://github.com/jaywcjlove/hotkeys-js/actions/workflows/ci.yml/badge.svg)](https://github.com/jaywcjlove/hotkeys-js/actions/workflows/ci.yml) [![Coverage Status](https://jaywcjlove.github.io/hotkeys-js/coverage.svg)](https://jaywcjlove.github.io/hotkeys-js/lcov-report/index.html) [![Chinese](https://jaywcjlove.github.io/sb/lang/chinese.svg)](https://wangchujiang.com/hotkeys-js/?lang=zh) [![jaywcjlove/hotkeys-js](https://jaywcjlove.github.io/sb/ico/gitee.svg)](https://gitee.com/jaywcjlove/hotkeys) HotKeys.js is an input capture library with some very special features, it is easy to pick up and use, has a reasonable footprint ([~8kB](https://bundlephobia.com/result?p=hotkeys-js)) (gzipped: **`3.8kB`**), and has no dependencies. It should not interfere with any JavaScript libraries or frameworks. Official document [demo preview](https://jaywcjlove.github.io/hotkeys-js), [compatibility test](https://jaywcjlove.github.io/hotkeys-js/dist/compatibility-test.html). [More examples](https://github.com/jaywcjlove/hotkeys-js/issues?q=label%3ADemo+). ```bash ╭┈┈╮ ╭┈┈╮ ╭┈┈╮ ┆ ├┈┈..┈┈┈┈┈.┆ └┈╮┆ ├┈┈..┈┈┈┈┈..┈┈.┈┈..┈┈┈┈┈. ┆ ┆┆ □ ┆┆ ┈┤┆ < ┆ -__┘┆ ┆ ┆┆__ ┈┈┤ ╰┈┈┴┈┈╯╰┈┈┈┈┈╯╰┈┈┈┈╯╰┈┈┴┈┈╯╰┈┈┈┈┈╯╰┈┈┈ ┆╰┈┈┈┈┈╯ ╰┈┈┈┈┈╯ ``` ## Usage You will need `Node.js` installed on your system. ```bash npm install hotkeys-js --save ``` ```js import hotkeys from 'hotkeys-js'; hotkeys('f5', function(event, handler){ // Prevent the default refresh event under WINDOWS system event.preventDefault() alert('you pressed F5!') }); ``` ### Browser Usage Or manually download and link **hotkeys.js** in your HTML. The library provides different formats for different use cases: **CDN Links:** [UNPKG](https://unpkg.com/hotkeys-js/dist/) | [jsDelivr](https://cdn.jsdelivr.net/npm/hotkeys-js/) | [Githack](https://raw.githack.com/jaywcjlove/hotkeys/master/dist/) | [Statically](https://cdn.statically.io/gh/jaywcjlove/hotkeys/master/dist/) **Available Formats:** **IIFE (Immediately Invoked Function Expression) - Recommended for direct browser usage:** ```html ``` **UMD (Universal Module Definition) - For CommonJS/AMD environments:** ```html ``` **ES Module - For modern browsers with module support:** ```html ``` ### Used in React [react-hotkeys](https://github.com/jaywcjlove/react-hotkeys) is the React component that listen to keydown and keyup keyboard events, defining and dispatching keyboard shortcuts. Detailed use method please see its documentation [react-hotkeys](https://github.com/jaywcjlove/react-hotkeys). [react-hotkeys-hook](https://github.com/JohannesKlauss/react-hotkeys-hook) - React hook for using keyboard shortcuts in components. Make sure that you have at least version 16.8 of react and react-dom installed, or otherwise hooks won't work for you. ## Browser Support Hotkeys.js has been tested and should work in. ```shell Internet Explorer 6+ Safari Firefox Chrome ``` ## Supported Keys HotKeys understands the following modifiers: `⇧`, `shift`, `option`, `⌥`, `alt`, `ctrl`, `control`, `command`, and `⌘`. The following special keys can be used for shortcuts: backspace, tab, clear, enter, return, esc, escape, space, up, down, left, right, home, end, pageup, pagedown, del, delete, f1 through f19, num_0 through num_9, num_multiply, num_add, num_enter, num_subtract, num_decimal, num_divide. `⌘` Command() `⌃` Control `⌥` Option(alt) `⇧` Shift `⇪` Caps Lock(Capital) ~~`fn` Does not support fn~~ `↩︎` return/Enter space ## Defining Shortcuts One global method is exposed, key which defines shortcuts when called directly. ```ts declare interface HotkeysInterface extends HotkeysAPI { (key: string, method: KeyHandler): void; (key: string, scope: string, method: KeyHandler): void; (key: string, option: HotkeysOptions, method: KeyHandler): void; shift?: boolean; ctrl?: boolean; alt?: boolean; option?: boolean; control?: boolean; cmd?: boolean; command?: boolean; } declare interface HotkeysAPI { setScope: SetScope; getScope: GetScope; deleteScope: DeleteScope; getPressedKeyCodes: GetPressedKeyCodes; getPressedKeyString: GetPressedKeyString; getAllKeyCodes: GetAllKeyCodes; isPressed: IsPressed; filter: Filter; trigger: Trigger; unbind: Unbind; noConflict: NoConflict; keyMap: Record; modifier: Record; modifierMap: Record; } ``` ```js hotkeys('f5', function(event, handler) { // Prevent the default refresh event under WINDOWS system event.preventDefault(); alert('you pressed F5!'); }); // Returning false stops the event and prevents default browser events // Mac OS system defines `command + r` as a refresh shortcut hotkeys('ctrl+r, command+r', function() { alert('stopped reload!'); return false; }); // Single key hotkeys('a', function(event,handler){ //event.srcElement: input //event.target: input if(event.target === "input"){ alert('you pressed a!') } alert('you pressed a!') }); // Key Combination hotkeys('ctrl+a,ctrl+b,r,f', function (event, handler){ switch (handler.key) { case 'ctrl+a': alert('you pressed ctrl+a!'); break; case 'ctrl+b': alert('you pressed ctrl+b!'); break; case 'r': alert('you pressed r!'); break; case 'f': alert('you pressed f!'); break; default: alert(event); } }); hotkeys('ctrl+a+s', function() { alert('you pressed ctrl+a+s!'); }); // Using a scope hotkeys('*','wcj', function(event){ console.log('do something', event); }); ``` #### option - `scope`: Sets the scope in which the shortcut key is active - `element`: Specifies the DOM element to bind the event to - `keyup`: Whether to trigger the shortcut on key release - `keydown`: Whether to trigger the shortcut on key press - `splitKey`: Delimiter for key combinations (default is `+`) - `capture`: Whether to trigger the listener during the capture phase (before the event bubbles down) - `single`: Allows only one callback function (automatically unbinds previous one) ```js hotkeys('o, enter', { scope: 'wcj', element: document.getElementById('wrapper'), }, function() { console.log('do something else'); }); hotkeys('ctrl-+', { splitKey: '-' }, function(e) { console.log('you pressed ctrl and +'); }); hotkeys('+', { splitKey: '-' }, function(e){ console.log('you pressed +'); }) ``` **keyup** **key down** and **key up** both perform callback events. ```js hotkeys('ctrl+a,alt+a+s', {keyup: true}, function(event, handler) { if (event.type === 'keydown') { console.log('keydown:', event.type, handler, handler.key); } if (event.type === 'keyup') { console.log('keyup:', event.type, handler, handler.key); } }); ``` ## API REFERENCE Asterisk "*" Modifier key judgments ```js hotkeys('*', function() { if (hotkeys.shift) { console.log('shift is pressed!'); } if (hotkeys.ctrl) { console.log('ctrl is pressed!'); } if (hotkeys.alt) { console.log('alt is pressed!'); } if (hotkeys.option) { console.log('option is pressed!'); } if (hotkeys.control) { console.log('control is pressed!'); } if (hotkeys.cmd) { console.log('cmd is pressed!'); } if (hotkeys.command) { console.log('command is pressed!'); } }); ``` ### setScope Use the `hotkeys.setScope` method to set scope. There can only be one active scope besides 'all'. By default 'all' is always active. ```js // Define shortcuts with a scope hotkeys('ctrl+o, ctrl+alt+enter', 'issues', function() { console.log('do something'); }); hotkeys('o, enter', 'files', function() { console.log('do something else'); }); // Set the scope (only 'all' and 'issues' shortcuts will be honored) hotkeys.setScope('issues'); // default scope is 'all' ``` ### getScope Use the `hotkeys.getScope` method to get scope. ```js hotkeys.getScope(); ``` ### deleteScope Use the `hotkeys.deleteScope` method to delete a scope. This will also remove all associated hotkeys with it. ```js hotkeys.deleteScope('issues'); ``` You can use second argument, if need set new scope after deleting. ```js hotkeys.deleteScope('issues', 'newScopeName'); ``` ### unbind Similar to defining shortcuts, they can be unbound using `hotkeys.unbind`. ```js // unbind 'a' handler hotkeys.unbind('a'); // Unbind a hotkeys only for a single scope // If no scope is specified it defaults to the current // scope (hotkeys.getScope()) hotkeys.unbind('o, enter', 'issues'); hotkeys.unbind('o, enter', 'files'); ``` Unbind events through functions. ```js function example() { hotkeys('a', example); hotkeys.unbind('a', example); hotkeys('a', 'issues', example); hotkeys.unbind('a', 'issues', example); } ``` To unbind everything. ```js hotkeys.unbind(); ``` ### isPressed For example, `hotkeys.isPressed(77)` is true if the `M` key is currently pressed. ```js hotkeys('a', function() { console.log(hotkeys.isPressed('a')); //=> true console.log(hotkeys.isPressed('A')); //=> true console.log(hotkeys.isPressed(65)); //=> true }); ``` ### trigger trigger shortcut key event ```js hotkeys.trigger('ctrl+o'); hotkeys.trigger('ctrl+o', 'scope2'); ``` ### getPressedKeyCodes Returns an array of key codes currently pressed. ```js hotkeys('command+ctrl+shift+a,f', function() { console.log(hotkeys.getPressedKeyCodes()); //=> [17, 65] or [70] }) ``` ### getPressedKeyString Returns an array of key codes currently pressed. ```js hotkeys('command+ctrl+shift+a,f', function() { console.log(hotkeys.getPressedKeyString()); //=> ['⌘', '⌃', '⇧', 'A', 'F'] }) ``` ### getAllKeyCodes Get a list of all registration codes. ```js hotkeys('command+ctrl+shift+a,f', function() { console.log(hotkeys.getAllKeyCodes()); // [ // { // scope: 'all', // shortcut: 'command+ctrl+shift+a', // mods: [91, 17, 16], // keys: [91, 17, 16, 65] // }, // { scope: 'all', shortcut: 'f', mods: [], keys: [42] } // ] }) ``` ### filter By default hotkeys are not enabled for `INPUT` `SELECT` `TEXTAREA` elements. `Hotkeys.filter` to return to the `true` shortcut keys set to play a role, `false` shortcut keys set up failure. ```js hotkeys.filter = function(event){ return true; } // How to add the filter to edit labels. //
// "contentEditable" Older browsers that do not support drops hotkeys.filter = function(event) { var target = event.target || event.srcElement; var tagName = target.tagName; return !( target.isContentEditable || tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA' ); } hotkeys.filter = function(event){ var tagName = (event.target || event.srcElement).tagName; hotkeys.setScope( /^(INPUT|TEXTAREA|SELECT)$/.test(tagName) ? 'input' : 'other' ); return true; } ``` ### noConflict Relinquish HotKeys’s control of the `hotkeys` variable. ```js var k = hotkeys.noConflict(); k('a', function() { console.log("do something") }); hotkeys() // -->Uncaught TypeError: hotkeys is not a function(anonymous function) // @ VM2170:2InjectedScript._evaluateOn // @ VM2165:883InjectedScript._evaluateAndWrap // @ VM2165:816InjectedScript.evaluate @ VM2165:682 ``` ## Development To develop, Install dependencies, Get the code: ```shell $ git https://github.com/jaywcjlove/hotkeys.git $ cd hotkeys # Into the directory $ npm install # or yarn install ``` To develop, run the self-reloading build: ```shell $ npm run watch ``` Run Document Website Environment. ```shell # Generate documentation website $ npm run doc # Live-generate documentation website $ npm run start ``` To contribute, please fork Hotkeys.js, add your patch and tests for it (in the `test/` folder) and submit a pull request. ```shell $ npm run test $ npm run test:watch # Development model ``` ## Contributors As always, thanks to our amazing contributors! Made with [action-contributors](https://github.com/jaywcjlove/github-action-contributors). Special thanks to [@dimensi](https://github.com/dimensi) for the refactoring of version [4.0](https://github.com/jaywcjlove/hotkeys-js/issues/313). ## License [MIT © Kenny Wong](./LICENSE) ================================================ FILE: dist/compatibility-test.html ================================================ Hotkeys-js Comprehensive Compatibility Test

🔥 Hotkeys-js Comprehensive Compatibility Test Suite

Test the compatibility of hotkeys-js library across different module formats and environments

0
Total Tests
0
Passed Tests
0
Failed Tests

📦 Module Format Compatibility Tests

Test loading and basic functionality of hotkeys-js library in different build formats

IIFE Format (hotkeys-js.min.js)

Immediately Invoked Function Expression, suitable for direct browser use

Press F1 to test

Loading...

UMD Format (hotkeys-js.umd.cjs)

Universal Module Definition, compatible with CommonJS, AMD and global variables

Press F2 to test

Loading...

ES Module Format (hotkeys-js.js)

Modern ES6 module format, requires server environment

Press F3 to test

Loading...

🎯 Hotkey Functionality Tests

Test various hotkey combinations and functions

Ctrl + K
Basic hotkey test
Alt + S
Modifier key combination test
Ctrl + Shift + T
Multiple modifier keys test
Esc
Single key test
Space
Space key test
Enter
Enter key test

🔧 API Method Tests

Test various API methods provided by hotkeys-js

📋 Module Loading Details

📝 Usage Examples

ESM Environment (Recommended)

import hotkeys from 'hotkeys-js';

hotkeys('ctrl+k', function(event, handler) {
  event.preventDefault();
  console.log('Hotkey pressed!');
});

CommonJS Environment

const hotkeys = require('hotkeys-js');
// or
const hotkeys = require('hotkeys-js').default;

hotkeys('ctrl+k', function(event, handler) {
  event.preventDefault();
  console.log('Hotkey pressed!');
});

Browser Global Environment

<script src="hotkeys-js/dist/hotkeys-js.min.js"></script>
<script>
  hotkeys('ctrl+k', function(event, handler) {
    event.preventDefault();
    console.log('Hotkey pressed!');
  });
</script>
================================================ FILE: dist/hotkeys-js.js ================================================ /*! * hotkeys-js v4.0.2 * A simple micro-library for defining and dispatching keyboard shortcuts. It has no dependencies. * * @author kenny wong * @license MIT * @homepage https://jaywcjlove.github.io/hotkeys-js */ const isff = typeof navigator !== "undefined" ? navigator.userAgent.toLowerCase().indexOf("firefox") > 0 : false; function addEvent(object, event, method, useCapture) { if (object.addEventListener) { object.addEventListener(event, method, useCapture); } else if (object.attachEvent) { object.attachEvent(`on${event}`, method); } } function removeEvent(object, event, method, useCapture) { if (!object) return; if (object.removeEventListener) { object.removeEventListener(event, method, useCapture); } else if (object.detachEvent) { object.detachEvent(`on${event}`, method); } } function getMods(modifier, key) { const modsKeys = key.slice(0, key.length - 1); const modsCodes = []; for (let i = 0; i < modsKeys.length; i++) { modsCodes.push(modifier[modsKeys[i].toLowerCase()]); } return modsCodes; } function getKeys(key) { if (typeof key !== "string") key = ""; key = key.replace(/\s/g, ""); const keys = key.split(","); let index = keys.lastIndexOf(""); for (; index >= 0; ) { keys[index - 1] += ","; keys.splice(index, 1); index = keys.lastIndexOf(""); } return keys; } function compareArray(a1, a2) { const arr1 = a1.length >= a2.length ? a1 : a2; const arr2 = a1.length >= a2.length ? a2 : a1; let isIndex = true; for (let i = 0; i < arr1.length; i++) { if (arr2.indexOf(arr1[i]) === -1) isIndex = false; } return isIndex; } function getLayoutIndependentKeyCode(event) { let key = event.keyCode || event.which || event.charCode; if (event.code && /^Key[A-Z]$/.test(event.code)) { key = event.code.charCodeAt(3); } return key; } const _keyMap = { backspace: 8, "⌫": 8, tab: 9, clear: 12, enter: 13, "↩": 13, return: 13, esc: 27, escape: 27, space: 32, left: 37, up: 38, right: 39, down: 40, /// https://w3c.github.io/uievents/#events-keyboard-key-location arrowup: 38, arrowdown: 40, arrowleft: 37, arrowright: 39, del: 46, delete: 46, ins: 45, insert: 45, home: 36, end: 35, pageup: 33, pagedown: 34, capslock: 20, num_0: 96, num_1: 97, num_2: 98, num_3: 99, num_4: 100, num_5: 101, num_6: 102, num_7: 103, num_8: 104, num_9: 105, num_multiply: 106, num_add: 107, num_enter: 108, num_subtract: 109, num_decimal: 110, num_divide: 111, "⇪": 20, ",": 188, ".": 190, "/": 191, "`": 192, "-": isff ? 173 : 189, "=": isff ? 61 : 187, ";": isff ? 59 : 186, "'": 222, "{": 219, "}": 221, "[": 219, "]": 221, "\\": 220 }; const _modifier = { // shiftKey "⇧": 16, shift: 16, // altKey "⌥": 18, alt: 18, option: 18, // ctrlKey "⌃": 17, ctrl: 17, control: 17, // metaKey "⌘": 91, cmd: 91, meta: 91, command: 91 }; const modifierMap = { 16: "shiftKey", 18: "altKey", 17: "ctrlKey", 91: "metaKey", shiftKey: 16, ctrlKey: 17, altKey: 18, metaKey: 91 }; const _mods = { 16: false, 18: false, 17: false, 91: false }; const _handlers = {}; for (let k = 1; k < 20; k++) { _keyMap[`f${k}`] = 111 + k; } let _downKeys = []; let winListendFocus = null; let winListendFullscreen = null; let _scope = "all"; const elementEventMap = /* @__PURE__ */ new Map(); const code = (x) => _keyMap[x.toLowerCase()] || _modifier[x.toLowerCase()] || x.toUpperCase().charCodeAt(0); const getKey = (x) => Object.keys(_keyMap).find((k) => _keyMap[k] === x); const getModifier = (x) => Object.keys(_modifier).find((k) => _modifier[k] === x); const setScope = (scope) => { _scope = scope || "all"; }; const getScope = () => { return _scope || "all"; }; const getPressedKeyCodes = () => { return _downKeys.slice(0); }; const getPressedKeyString = () => { return _downKeys.map( (c) => getKey(c) || getModifier(c) || String.fromCharCode(c) ); }; const getAllKeyCodes = () => { const result = []; Object.keys(_handlers).forEach((k) => { _handlers[k].forEach(({ key, scope, mods, shortcut }) => { result.push({ scope, shortcut, mods, keys: key.split("+").map((v) => code(v)) }); }); }); return result; }; const filter = (event) => { const target = event.target || event.srcElement; const { tagName } = target; let flag = true; const isInput = tagName === "INPUT" && ![ "checkbox", "radio", "range", "button", "file", "reset", "submit", "color" ].includes(target.type); if (target.isContentEditable || (isInput || tagName === "TEXTAREA" || tagName === "SELECT") && !target.readOnly) { flag = false; } return flag; }; const isPressed = (keyCode) => { if (typeof keyCode === "string") { keyCode = code(keyCode); } return _downKeys.indexOf(keyCode) !== -1; }; const deleteScope = (scope, newScope) => { let handlers; let i; if (!scope) scope = getScope(); for (const key in _handlers) { if (Object.prototype.hasOwnProperty.call(_handlers, key)) { handlers = _handlers[key]; for (i = 0; i < handlers.length; ) { if (handlers[i].scope === scope) { const deleteItems = handlers.splice(i, 1); deleteItems.forEach(({ element }) => removeKeyEvent(element)); } else { i++; } } } } if (getScope() === scope) setScope(newScope || "all"); }; function clearModifier(event) { let key = getLayoutIndependentKeyCode(event); if (event.key && event.key.toLowerCase() === "capslock") { key = code(event.key); } const i = _downKeys.indexOf(key); if (i >= 0) { _downKeys.splice(i, 1); } if (event.key && event.key.toLowerCase() === "meta") { _downKeys.splice(0, _downKeys.length); } if (key === 93 || key === 224) key = 91; if (key in _mods) { _mods[key] = false; for (const k in _modifier) if (_modifier[k] === key) hotkeys[k] = false; } } const unbind = (keysInfo, ...args) => { if (typeof keysInfo === "undefined") { Object.keys(_handlers).forEach((key) => { if (Array.isArray(_handlers[key])) { _handlers[key].forEach((info) => eachUnbind(info)); } delete _handlers[key]; }); removeKeyEvent(null); } else if (Array.isArray(keysInfo)) { keysInfo.forEach((info) => { if (info.key) eachUnbind(info); }); } else if (typeof keysInfo === "object") { if (keysInfo.key) eachUnbind(keysInfo); } else if (typeof keysInfo === "string") { let [scope, method] = args; if (typeof scope === "function") { method = scope; scope = ""; } eachUnbind({ key: keysInfo, scope, method, splitKey: "+" }); } }; const eachUnbind = ({ key, scope, method, splitKey = "+" }) => { const multipleKeys = getKeys(key); multipleKeys.forEach((originKey) => { const unbindKeys = originKey.split(splitKey); const len = unbindKeys.length; const lastKey = unbindKeys[len - 1]; const keyCode = lastKey === "*" ? "*" : code(lastKey); if (!_handlers[keyCode]) return; if (!scope) scope = getScope(); const mods = len > 1 ? getMods(_modifier, unbindKeys) : []; const unbindElements = []; _handlers[keyCode] = _handlers[keyCode].filter((record) => { const isMatchingMethod = method ? record.method === method : true; const isUnbind = isMatchingMethod && record.scope === scope && compareArray(record.mods, mods); if (isUnbind) unbindElements.push(record.element); return !isUnbind; }); unbindElements.forEach((element) => removeKeyEvent(element)); }); }; function eventHandler(event, handler, scope, element) { if (handler.element !== element) { return; } let modifiersMatch; if (handler.scope === scope || handler.scope === "all") { modifiersMatch = handler.mods.length > 0; for (const y in _mods) { if (Object.prototype.hasOwnProperty.call(_mods, y)) { if (!_mods[y] && handler.mods.indexOf(+y) > -1 || _mods[y] && handler.mods.indexOf(+y) === -1) { modifiersMatch = false; } } } if (handler.mods.length === 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91] || modifiersMatch || handler.shortcut === "*") { handler.keys = []; handler.keys = handler.keys.concat(_downKeys); if (handler.method(event, handler) === false) { if (event.preventDefault) event.preventDefault(); else event.returnValue = false; if (event.stopPropagation) event.stopPropagation(); if (event.cancelBubble) event.cancelBubble = true; } } } } function dispatch(event, element) { const asterisk = _handlers["*"]; let key = getLayoutIndependentKeyCode(event); if (event.key && event.key.toLowerCase() === "capslock") { return; } const filterFn = hotkeys.filter || filter; if (!filterFn.call(this, event)) return; if (key === 93 || key === 224) key = 91; if (_downKeys.indexOf(key) === -1 && key !== 229) _downKeys.push(key); ["metaKey", "ctrlKey", "altKey", "shiftKey"].forEach((keyName) => { const keyNum = modifierMap[keyName]; if (event[keyName] && _downKeys.indexOf(keyNum) === -1) { _downKeys.push(keyNum); } else if (!event[keyName] && _downKeys.indexOf(keyNum) > -1) { _downKeys.splice(_downKeys.indexOf(keyNum), 1); } else if (keyName === "metaKey" && event[keyName]) { _downKeys = _downKeys.filter((k) => k in modifierMap || k === key); } }); if (key in _mods) { _mods[key] = true; for (const k in _modifier) { if (Object.prototype.hasOwnProperty.call(_modifier, k)) { const eventKey = modifierMap[_modifier[k]]; hotkeys[k] = event[eventKey]; } } if (!asterisk) return; } for (const e in _mods) { if (Object.prototype.hasOwnProperty.call(_mods, e)) { _mods[e] = event[modifierMap[e]]; } } if (event.getModifierState && !(event.altKey && !event.ctrlKey) && event.getModifierState("AltGraph")) { if (_downKeys.indexOf(17) === -1) { _downKeys.push(17); } if (_downKeys.indexOf(18) === -1) { _downKeys.push(18); } _mods[17] = true; _mods[18] = true; } const scope = getScope(); if (asterisk) { for (let i = 0; i < asterisk.length; i++) { if (asterisk[i].scope === scope && (event.type === "keydown" && asterisk[i].keydown || event.type === "keyup" && asterisk[i].keyup)) { eventHandler(event, asterisk[i], scope, element); } } } if (!(key in _handlers)) return; const handlerKey = _handlers[key]; const keyLen = handlerKey.length; for (let i = 0; i < keyLen; i++) { if (event.type === "keydown" && handlerKey[i].keydown || event.type === "keyup" && handlerKey[i].keyup) { if (handlerKey[i].key) { const record = handlerKey[i]; const { splitKey } = record; const keyShortcut = record.key.split(splitKey); const _downKeysCurrent = []; for (let a = 0; a < keyShortcut.length; a++) { _downKeysCurrent.push(code(keyShortcut[a])); } if (_downKeysCurrent.sort().join("") === _downKeys.sort().join("")) { eventHandler(event, record, scope, element); } } } } } const hotkeys = function hotkeys2(key, option, method) { _downKeys = []; const keys = getKeys(key); let mods = []; let scope = "all"; let element = document; let i = 0; let keyup = false; let keydown = true; let splitKey = "+"; let capture = false; let single = false; if (method === void 0 && typeof option === "function") { method = option; } if (Object.prototype.toString.call(option) === "[object Object]") { const opts = option; if (opts.scope) scope = opts.scope; if (opts.element) element = opts.element; if (opts.keyup) keyup = opts.keyup; if (opts.keydown !== void 0) keydown = opts.keydown; if (opts.capture !== void 0) capture = opts.capture; if (typeof opts.splitKey === "string") splitKey = opts.splitKey; if (opts.single === true) single = true; } if (typeof option === "string") scope = option; if (single) unbind(key, scope); for (; i < keys.length; i++) { const currentKey = keys[i].split(splitKey); mods = []; if (currentKey.length > 1) mods = getMods(_modifier, currentKey); let finalKey = currentKey[currentKey.length - 1]; finalKey = finalKey === "*" ? "*" : code(finalKey); if (!(finalKey in _handlers)) _handlers[finalKey] = []; _handlers[finalKey].push({ keyup, keydown, scope, mods, shortcut: keys[i], method, key: keys[i], splitKey, element }); } if (typeof element !== "undefined" && typeof window !== "undefined") { if (!elementEventMap.has(element)) { const keydownListener = (event = window.event) => dispatch(event, element); const keyupListenr = (event = window.event) => { dispatch(event, element); clearModifier(event); }; elementEventMap.set(element, { keydownListener, keyupListenr, capture }); addEvent(element, "keydown", keydownListener, capture); addEvent(element, "keyup", keyupListenr, capture); } if (!winListendFocus) { const listener = () => { _downKeys = []; }; winListendFocus = { listener, capture }; addEvent(window, "focus", listener, capture); } if (!winListendFullscreen && typeof document !== "undefined") { const onFullscreenChange = () => { _downKeys = []; for (const k in _mods) _mods[k] = false; for (const k in _modifier) hotkeys2[k] = false; }; const fullscreenListener = onFullscreenChange; const webkitListener = onFullscreenChange; document.addEventListener("fullscreenchange", fullscreenListener); document.addEventListener("webkitfullscreenchange", webkitListener); winListendFullscreen = { fullscreen: fullscreenListener, webkit: webkitListener }; } } }; function trigger(shortcut, scope = "all") { Object.keys(_handlers).forEach((key) => { const dataList = _handlers[key].filter( (item) => item.scope === scope && item.shortcut === shortcut ); dataList.forEach((data) => { if (data && data.method) { data.method({}, data); } }); }); } function removeKeyEvent(element) { const values = Object.values(_handlers).flat(); const findindex = values.findIndex(({ element: el }) => el === element); if (findindex < 0 && element) { const { keydownListener, keyupListenr, capture } = elementEventMap.get(element) || {}; if (keydownListener && keyupListenr) { removeEvent(element, "keyup", keyupListenr, capture); removeEvent(element, "keydown", keydownListener, capture); elementEventMap.delete(element); } } if (values.length <= 0 || elementEventMap.size <= 0) { const eventKeys = Array.from(elementEventMap.keys()); eventKeys.forEach((el) => { const { keydownListener, keyupListenr, capture } = elementEventMap.get(el) || {}; if (keydownListener && keyupListenr) { removeEvent(el, "keyup", keyupListenr, capture); removeEvent(el, "keydown", keydownListener, capture); elementEventMap.delete(el); } }); elementEventMap.clear(); Object.keys(_handlers).forEach((key) => delete _handlers[key]); if (winListendFocus) { const { listener, capture } = winListendFocus; removeEvent(window, "focus", listener, capture); winListendFocus = null; } if (winListendFullscreen && typeof document !== "undefined") { document.removeEventListener("fullscreenchange", winListendFullscreen.fullscreen); document.removeEventListener("webkitfullscreenchange", winListendFullscreen.webkit); winListendFullscreen = null; } } } const _api = { getPressedKeyString, setScope, getScope, deleteScope, getPressedKeyCodes, getAllKeyCodes, isPressed, filter, trigger, unbind, keyMap: _keyMap, modifier: _modifier, modifierMap }; for (const a in _api) { const key = a; if (Object.prototype.hasOwnProperty.call(_api, key)) { hotkeys[key] = _api[key]; } } if (typeof window !== "undefined") { const _hotkeys = window.hotkeys; hotkeys.noConflict = (deep) => { if (deep && window.hotkeys === hotkeys) { window.hotkeys = _hotkeys; } return hotkeys; }; window.hotkeys = hotkeys; } if (typeof module !== "undefined" && module.exports) { module.exports = hotkeys; module.exports.default = hotkeys; } export { hotkeys as default }; //# sourceMappingURL=hotkeys-js.js.map ================================================ FILE: dist/hotkeys-js.umd.cjs ================================================ (function(global, factory) { typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() : typeof define === "function" && define.amd ? define(factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, global.hotkeys = factory()); })(this, function() { "use strict";/*! * hotkeys-js v4.0.2 * A simple micro-library for defining and dispatching keyboard shortcuts. It has no dependencies. * * @author kenny wong * @license MIT * @homepage https://jaywcjlove.github.io/hotkeys-js */ const isff = typeof navigator !== "undefined" ? navigator.userAgent.toLowerCase().indexOf("firefox") > 0 : false; function addEvent(object, event, method, useCapture) { if (object.addEventListener) { object.addEventListener(event, method, useCapture); } else if (object.attachEvent) { object.attachEvent(`on${event}`, method); } } function removeEvent(object, event, method, useCapture) { if (!object) return; if (object.removeEventListener) { object.removeEventListener(event, method, useCapture); } else if (object.detachEvent) { object.detachEvent(`on${event}`, method); } } function getMods(modifier, key) { const modsKeys = key.slice(0, key.length - 1); const modsCodes = []; for (let i = 0; i < modsKeys.length; i++) { modsCodes.push(modifier[modsKeys[i].toLowerCase()]); } return modsCodes; } function getKeys(key) { if (typeof key !== "string") key = ""; key = key.replace(/\s/g, ""); const keys = key.split(","); let index = keys.lastIndexOf(""); for (; index >= 0; ) { keys[index - 1] += ","; keys.splice(index, 1); index = keys.lastIndexOf(""); } return keys; } function compareArray(a1, a2) { const arr1 = a1.length >= a2.length ? a1 : a2; const arr2 = a1.length >= a2.length ? a2 : a1; let isIndex = true; for (let i = 0; i < arr1.length; i++) { if (arr2.indexOf(arr1[i]) === -1) isIndex = false; } return isIndex; } function getLayoutIndependentKeyCode(event) { let key = event.keyCode || event.which || event.charCode; if (event.code && /^Key[A-Z]$/.test(event.code)) { key = event.code.charCodeAt(3); } return key; } const _keyMap = { backspace: 8, "⌫": 8, tab: 9, clear: 12, enter: 13, "↩": 13, return: 13, esc: 27, escape: 27, space: 32, left: 37, up: 38, right: 39, down: 40, /// https://w3c.github.io/uievents/#events-keyboard-key-location arrowup: 38, arrowdown: 40, arrowleft: 37, arrowright: 39, del: 46, delete: 46, ins: 45, insert: 45, home: 36, end: 35, pageup: 33, pagedown: 34, capslock: 20, num_0: 96, num_1: 97, num_2: 98, num_3: 99, num_4: 100, num_5: 101, num_6: 102, num_7: 103, num_8: 104, num_9: 105, num_multiply: 106, num_add: 107, num_enter: 108, num_subtract: 109, num_decimal: 110, num_divide: 111, "⇪": 20, ",": 188, ".": 190, "/": 191, "`": 192, "-": isff ? 173 : 189, "=": isff ? 61 : 187, ";": isff ? 59 : 186, "'": 222, "{": 219, "}": 221, "[": 219, "]": 221, "\\": 220 }; const _modifier = { // shiftKey "⇧": 16, shift: 16, // altKey "⌥": 18, alt: 18, option: 18, // ctrlKey "⌃": 17, ctrl: 17, control: 17, // metaKey "⌘": 91, cmd: 91, meta: 91, command: 91 }; const modifierMap = { 16: "shiftKey", 18: "altKey", 17: "ctrlKey", 91: "metaKey", shiftKey: 16, ctrlKey: 17, altKey: 18, metaKey: 91 }; const _mods = { 16: false, 18: false, 17: false, 91: false }; const _handlers = {}; for (let k = 1; k < 20; k++) { _keyMap[`f${k}`] = 111 + k; } let _downKeys = []; let winListendFocus = null; let winListendFullscreen = null; let _scope = "all"; const elementEventMap = /* @__PURE__ */ new Map(); const code = (x) => _keyMap[x.toLowerCase()] || _modifier[x.toLowerCase()] || x.toUpperCase().charCodeAt(0); const getKey = (x) => Object.keys(_keyMap).find((k) => _keyMap[k] === x); const getModifier = (x) => Object.keys(_modifier).find((k) => _modifier[k] === x); const setScope = (scope) => { _scope = scope || "all"; }; const getScope = () => { return _scope || "all"; }; const getPressedKeyCodes = () => { return _downKeys.slice(0); }; const getPressedKeyString = () => { return _downKeys.map( (c) => getKey(c) || getModifier(c) || String.fromCharCode(c) ); }; const getAllKeyCodes = () => { const result = []; Object.keys(_handlers).forEach((k) => { _handlers[k].forEach(({ key, scope, mods, shortcut }) => { result.push({ scope, shortcut, mods, keys: key.split("+").map((v) => code(v)) }); }); }); return result; }; const filter = (event) => { const target = event.target || event.srcElement; const { tagName } = target; let flag = true; const isInput = tagName === "INPUT" && ![ "checkbox", "radio", "range", "button", "file", "reset", "submit", "color" ].includes(target.type); if (target.isContentEditable || (isInput || tagName === "TEXTAREA" || tagName === "SELECT") && !target.readOnly) { flag = false; } return flag; }; const isPressed = (keyCode) => { if (typeof keyCode === "string") { keyCode = code(keyCode); } return _downKeys.indexOf(keyCode) !== -1; }; const deleteScope = (scope, newScope) => { let handlers; let i; if (!scope) scope = getScope(); for (const key in _handlers) { if (Object.prototype.hasOwnProperty.call(_handlers, key)) { handlers = _handlers[key]; for (i = 0; i < handlers.length; ) { if (handlers[i].scope === scope) { const deleteItems = handlers.splice(i, 1); deleteItems.forEach(({ element }) => removeKeyEvent(element)); } else { i++; } } } } if (getScope() === scope) setScope(newScope || "all"); }; function clearModifier(event) { let key = getLayoutIndependentKeyCode(event); if (event.key && event.key.toLowerCase() === "capslock") { key = code(event.key); } const i = _downKeys.indexOf(key); if (i >= 0) { _downKeys.splice(i, 1); } if (event.key && event.key.toLowerCase() === "meta") { _downKeys.splice(0, _downKeys.length); } if (key === 93 || key === 224) key = 91; if (key in _mods) { _mods[key] = false; for (const k in _modifier) if (_modifier[k] === key) hotkeys2[k] = false; } } const unbind = (keysInfo, ...args) => { if (typeof keysInfo === "undefined") { Object.keys(_handlers).forEach((key) => { if (Array.isArray(_handlers[key])) { _handlers[key].forEach((info) => eachUnbind(info)); } delete _handlers[key]; }); removeKeyEvent(null); } else if (Array.isArray(keysInfo)) { keysInfo.forEach((info) => { if (info.key) eachUnbind(info); }); } else if (typeof keysInfo === "object") { if (keysInfo.key) eachUnbind(keysInfo); } else if (typeof keysInfo === "string") { let [scope, method] = args; if (typeof scope === "function") { method = scope; scope = ""; } eachUnbind({ key: keysInfo, scope, method, splitKey: "+" }); } }; const eachUnbind = ({ key, scope, method, splitKey = "+" }) => { const multipleKeys = getKeys(key); multipleKeys.forEach((originKey) => { const unbindKeys = originKey.split(splitKey); const len = unbindKeys.length; const lastKey = unbindKeys[len - 1]; const keyCode = lastKey === "*" ? "*" : code(lastKey); if (!_handlers[keyCode]) return; if (!scope) scope = getScope(); const mods = len > 1 ? getMods(_modifier, unbindKeys) : []; const unbindElements = []; _handlers[keyCode] = _handlers[keyCode].filter((record) => { const isMatchingMethod = method ? record.method === method : true; const isUnbind = isMatchingMethod && record.scope === scope && compareArray(record.mods, mods); if (isUnbind) unbindElements.push(record.element); return !isUnbind; }); unbindElements.forEach((element) => removeKeyEvent(element)); }); }; function eventHandler(event, handler, scope, element) { if (handler.element !== element) { return; } let modifiersMatch; if (handler.scope === scope || handler.scope === "all") { modifiersMatch = handler.mods.length > 0; for (const y in _mods) { if (Object.prototype.hasOwnProperty.call(_mods, y)) { if (!_mods[y] && handler.mods.indexOf(+y) > -1 || _mods[y] && handler.mods.indexOf(+y) === -1) { modifiersMatch = false; } } } if (handler.mods.length === 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91] || modifiersMatch || handler.shortcut === "*") { handler.keys = []; handler.keys = handler.keys.concat(_downKeys); if (handler.method(event, handler) === false) { if (event.preventDefault) event.preventDefault(); else event.returnValue = false; if (event.stopPropagation) event.stopPropagation(); if (event.cancelBubble) event.cancelBubble = true; } } } } function dispatch(event, element) { const asterisk = _handlers["*"]; let key = getLayoutIndependentKeyCode(event); if (event.key && event.key.toLowerCase() === "capslock") { return; } const filterFn = hotkeys2.filter || filter; if (!filterFn.call(this, event)) return; if (key === 93 || key === 224) key = 91; if (_downKeys.indexOf(key) === -1 && key !== 229) _downKeys.push(key); ["metaKey", "ctrlKey", "altKey", "shiftKey"].forEach((keyName) => { const keyNum = modifierMap[keyName]; if (event[keyName] && _downKeys.indexOf(keyNum) === -1) { _downKeys.push(keyNum); } else if (!event[keyName] && _downKeys.indexOf(keyNum) > -1) { _downKeys.splice(_downKeys.indexOf(keyNum), 1); } else if (keyName === "metaKey" && event[keyName]) { _downKeys = _downKeys.filter((k) => k in modifierMap || k === key); } }); if (key in _mods) { _mods[key] = true; for (const k in _modifier) { if (Object.prototype.hasOwnProperty.call(_modifier, k)) { const eventKey = modifierMap[_modifier[k]]; hotkeys2[k] = event[eventKey]; } } if (!asterisk) return; } for (const e in _mods) { if (Object.prototype.hasOwnProperty.call(_mods, e)) { _mods[e] = event[modifierMap[e]]; } } if (event.getModifierState && !(event.altKey && !event.ctrlKey) && event.getModifierState("AltGraph")) { if (_downKeys.indexOf(17) === -1) { _downKeys.push(17); } if (_downKeys.indexOf(18) === -1) { _downKeys.push(18); } _mods[17] = true; _mods[18] = true; } const scope = getScope(); if (asterisk) { for (let i = 0; i < asterisk.length; i++) { if (asterisk[i].scope === scope && (event.type === "keydown" && asterisk[i].keydown || event.type === "keyup" && asterisk[i].keyup)) { eventHandler(event, asterisk[i], scope, element); } } } if (!(key in _handlers)) return; const handlerKey = _handlers[key]; const keyLen = handlerKey.length; for (let i = 0; i < keyLen; i++) { if (event.type === "keydown" && handlerKey[i].keydown || event.type === "keyup" && handlerKey[i].keyup) { if (handlerKey[i].key) { const record = handlerKey[i]; const { splitKey } = record; const keyShortcut = record.key.split(splitKey); const _downKeysCurrent = []; for (let a = 0; a < keyShortcut.length; a++) { _downKeysCurrent.push(code(keyShortcut[a])); } if (_downKeysCurrent.sort().join("") === _downKeys.sort().join("")) { eventHandler(event, record, scope, element); } } } } } const hotkeys2 = function hotkeys22(key, option, method) { _downKeys = []; const keys = getKeys(key); let mods = []; let scope = "all"; let element = document; let i = 0; let keyup = false; let keydown = true; let splitKey = "+"; let capture = false; let single = false; if (method === void 0 && typeof option === "function") { method = option; } if (Object.prototype.toString.call(option) === "[object Object]") { const opts = option; if (opts.scope) scope = opts.scope; if (opts.element) element = opts.element; if (opts.keyup) keyup = opts.keyup; if (opts.keydown !== void 0) keydown = opts.keydown; if (opts.capture !== void 0) capture = opts.capture; if (typeof opts.splitKey === "string") splitKey = opts.splitKey; if (opts.single === true) single = true; } if (typeof option === "string") scope = option; if (single) unbind(key, scope); for (; i < keys.length; i++) { const currentKey = keys[i].split(splitKey); mods = []; if (currentKey.length > 1) mods = getMods(_modifier, currentKey); let finalKey = currentKey[currentKey.length - 1]; finalKey = finalKey === "*" ? "*" : code(finalKey); if (!(finalKey in _handlers)) _handlers[finalKey] = []; _handlers[finalKey].push({ keyup, keydown, scope, mods, shortcut: keys[i], method, key: keys[i], splitKey, element }); } if (typeof element !== "undefined" && typeof window !== "undefined") { if (!elementEventMap.has(element)) { const keydownListener = (event = window.event) => dispatch(event, element); const keyupListenr = (event = window.event) => { dispatch(event, element); clearModifier(event); }; elementEventMap.set(element, { keydownListener, keyupListenr, capture }); addEvent(element, "keydown", keydownListener, capture); addEvent(element, "keyup", keyupListenr, capture); } if (!winListendFocus) { const listener = () => { _downKeys = []; }; winListendFocus = { listener, capture }; addEvent(window, "focus", listener, capture); } if (!winListendFullscreen && typeof document !== "undefined") { const onFullscreenChange = () => { _downKeys = []; for (const k in _mods) _mods[k] = false; for (const k in _modifier) hotkeys22[k] = false; }; const fullscreenListener = onFullscreenChange; const webkitListener = onFullscreenChange; document.addEventListener("fullscreenchange", fullscreenListener); document.addEventListener("webkitfullscreenchange", webkitListener); winListendFullscreen = { fullscreen: fullscreenListener, webkit: webkitListener }; } } }; function trigger(shortcut, scope = "all") { Object.keys(_handlers).forEach((key) => { const dataList = _handlers[key].filter( (item) => item.scope === scope && item.shortcut === shortcut ); dataList.forEach((data) => { if (data && data.method) { data.method({}, data); } }); }); } function removeKeyEvent(element) { const values = Object.values(_handlers).flat(); const findindex = values.findIndex(({ element: el }) => el === element); if (findindex < 0 && element) { const { keydownListener, keyupListenr, capture } = elementEventMap.get(element) || {}; if (keydownListener && keyupListenr) { removeEvent(element, "keyup", keyupListenr, capture); removeEvent(element, "keydown", keydownListener, capture); elementEventMap.delete(element); } } if (values.length <= 0 || elementEventMap.size <= 0) { const eventKeys = Array.from(elementEventMap.keys()); eventKeys.forEach((el) => { const { keydownListener, keyupListenr, capture } = elementEventMap.get(el) || {}; if (keydownListener && keyupListenr) { removeEvent(el, "keyup", keyupListenr, capture); removeEvent(el, "keydown", keydownListener, capture); elementEventMap.delete(el); } }); elementEventMap.clear(); Object.keys(_handlers).forEach((key) => delete _handlers[key]); if (winListendFocus) { const { listener, capture } = winListendFocus; removeEvent(window, "focus", listener, capture); winListendFocus = null; } if (winListendFullscreen && typeof document !== "undefined") { document.removeEventListener("fullscreenchange", winListendFullscreen.fullscreen); document.removeEventListener("webkitfullscreenchange", winListendFullscreen.webkit); winListendFullscreen = null; } } } const _api = { getPressedKeyString, setScope, getScope, deleteScope, getPressedKeyCodes, getAllKeyCodes, isPressed, filter, trigger, unbind, keyMap: _keyMap, modifier: _modifier, modifierMap }; for (const a in _api) { const key = a; if (Object.prototype.hasOwnProperty.call(_api, key)) { hotkeys2[key] = _api[key]; } } if (typeof window !== "undefined") { const _hotkeys = window.hotkeys; hotkeys2.noConflict = (deep) => { if (deep && window.hotkeys === hotkeys2) { window.hotkeys = _hotkeys; } return hotkeys2; }; window.hotkeys = hotkeys2; } if (typeof module !== "undefined" && module.exports) { module.exports = hotkeys2; module.exports.default = hotkeys2; } return hotkeys2; }); if (typeof module === "object" && module.exports) { module.exports.default = module.exports; } if (typeof define === "function" && define.amd) { define([], function() { return hotkeys; }); } //# sourceMappingURL=hotkeys-js.umd.cjs.map ================================================ FILE: dist/index.d.ts ================================================ declare const _default: HotkeysInterface; export default _default; declare type DeleteScope = (scope?: string, newScope?: string) => void; declare type Filter = (event: KeyboardEvent) => boolean; declare type GetAllKeyCodes = () => KeyCodeInfo[]; declare type GetPressedKeyCodes = () => number[]; declare type GetPressedKeyString = () => string[]; declare type GetScope = () => string; declare interface HotkeysAPI { /** * Use the `hotkeys.setScope` method to set scope. There can only be one active scope besides 'all'. By default 'all' is always active. * * ```js * // Define shortcuts with a scope * hotkeys('ctrl+o, ctrl+alt+enter', 'issues', function() { * console.log('do something'); * }); * hotkeys('o, enter', 'files', function() { * console.log('do something else'); * }); * * // Set the scope (only 'all' and 'issues' shortcuts will be honored) * hotkeys.setScope('issues'); // default scope is 'all' * ``` */ setScope: SetScope; /** * Use the `hotkeys.getScope` method to get scope. * * ```js * hotkeys.getScope(); * ``` */ getScope: GetScope; /** * Use the `hotkeys.deleteScope` method to delete a scope. This will also remove all associated hotkeys with it. * * ```js * hotkeys.deleteScope('issues'); * ``` * You can use second argument, if need set new scope after deleting. * * ```js * hotkeys.deleteScope('issues', 'newScopeName'); * ``` */ deleteScope: DeleteScope; /** * Returns an array of key codes currently pressed. * * ```js * hotkeys('command+ctrl+shift+a,f', function() { * console.log(hotkeys.getPressedKeyCodes()); //=> [17, 65] or [70] * }) * ``` */ getPressedKeyCodes: GetPressedKeyCodes; /** * Returns an array of key codes currently pressed. * * ```js * hotkeys('command+ctrl+shift+a,f', function() { * console.log(hotkeys.getPressedKeyString()); //=> ['⌘', '⌃', '⇧', 'A', 'F'] * }) * ``` */ getPressedKeyString: GetPressedKeyString; /** * Get a list of all registration codes. * * ```js * hotkeys('command+ctrl+shift+a,f', function() { * console.log(hotkeys.getAllKeyCodes()); * // [ * // { scope: 'all', shortcut: 'command+ctrl+shift+a', mods: [91, 17, 16], keys: [91, 17, 16, 65] }, * // { scope: 'all', shortcut: 'f', mods: [], keys: [42] } * // ] * }) * ``` * */ getAllKeyCodes: GetAllKeyCodes; isPressed: IsPressed; /** * By default hotkeys are not enabled for `INPUT` `SELECT` `TEXTAREA` elements. * `Hotkeys.filter` to return to the `true` shortcut keys set to play a role, * `false` shortcut keys set up failure. * * ```js * hotkeys.filter = function(event){ * return true; * } * //How to add the filter to edit labels.
* //"contentEditable" Older browsers that do not support drops * hotkeys.filter = function(event) { * var target = event.target || event.srcElement; * var tagName = target.tagName; * return !(target.isContentEditable || tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA'); * } * * hotkeys.filter = function(event){ * var tagName = (event.target || event.srcElement).tagName; * hotkeys.setScope(/^(INPUT|TEXTAREA|SELECT)$/.test(tagName) ? 'input' : 'other'); * return true; * } * ``` */ filter: Filter; /** * trigger shortcut key event * * ```js * hotkeys.trigger('ctrl+o'); * hotkeys.trigger('ctrl+o', 'scope2'); * ``` */ trigger: Trigger; /** * Unbinds a shortcut key event. * * ```js * hotkeys.unbind('ctrl+o'); * hotkeys.unbind('ctrl+o', 'scope1'); * hotkeys.unbind('ctrl+o', 'scope1', method); * hotkeys.unbind('ctrl+o', method); * ``` */ unbind: Unbind; /** * Relinquish HotKeys’s control of the `hotkeys` variable. * * ```js * var k = hotkeys.noConflict(); * k('a', function() { * console.log("do something") * }); * * hotkeys() * // -->Uncaught TypeError: hotkeys is not a function(anonymous function) * // @ VM2170:2InjectedScript._evaluateOn * // @ VM2165:883InjectedScript._evaluateAndWrap * // @ VM2165:816InjectedScript.evaluate @ VM2165:682 * ``` */ noConflict: NoConflict; keyMap: Record; modifier: Record; modifierMap: Record; } export declare interface HotkeysEvent { keyup: boolean; keydown: boolean; scope: string; mods: number[]; shortcut: string; method: KeyHandler; key: string; splitKey: string; element: HTMLElement | Document; keys?: number[]; } declare interface HotkeysInterface extends HotkeysAPI { (key: string, method: KeyHandler): void; (key: string, scope: string, method: KeyHandler): void; (key: string, option: HotkeysOptions, method: KeyHandler): void; shift?: boolean; ctrl?: boolean; alt?: boolean; option?: boolean; control?: boolean; cmd?: boolean; command?: boolean; } declare interface HotkeysOptions { scope?: string; element?: HTMLElement | Document; keyup?: boolean; keydown?: boolean; capture?: boolean; splitKey?: string; single?: boolean; } declare interface IsPressed { /** For example, `hotkeys.isPressed(77)` is true if the `M` key is currently pressed. */ (keyCode: number): boolean; /** For example, `hotkeys.isPressed('m')` is true if the `M` key is currently pressed. */ (keyCode: string): boolean; } declare interface KeyCodeInfo { scope: string; shortcut: string; mods: number[]; keys: number[]; } export declare interface KeyHandler { (keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent): void | boolean; } declare type NoConflict = (deep?: boolean) => HotkeysInterface; declare type SetScope = (scope: string) => void; declare type Trigger = (shortcut: string, scope?: string) => void; declare interface Unbind { (key?: string): void; (keysInfo: UnbindInfo): void; (keysInfo: UnbindInfo[]): void; (key: string, scopeName: string): void; (key: string, scopeName: string, method: KeyHandler): void; (key: string, method: KeyHandler): void; } declare interface UnbindInfo { key: string; scope?: string; method?: KeyHandler; splitKey?: string; } export { } ================================================ FILE: eslint.config.js ================================================ // @ts-check import { defineConfig } from 'eslint/config'; import eslint from '@eslint/js'; import tseslint from 'typescript-eslint'; import reactPlugin from 'eslint-plugin-react'; import reactHooksPlugin from 'eslint-plugin-react-hooks'; import importPlugin from 'eslint-plugin-import'; import jsxA11yPlugin from 'eslint-plugin-jsx-a11y'; import globals from 'globals'; export default defineConfig([ eslint.configs.recommended, ...tseslint.configs.recommended, { files: ['**/*.{js,jsx,ts,tsx}'], languageOptions: { ecmaVersion: 2020, sourceType: 'module', parserOptions: { ecmaFeatures: { jsx: true, }, }, globals: { ...globals.browser, ...globals.node, ...globals.es2020, ...globals.jest, }, }, plugins: { react: reactPlugin, 'react-hooks': reactHooksPlugin, import: importPlugin, 'jsx-a11y': jsxA11yPlugin, }, settings: { react: { version: 'detect', }, }, rules: { // TypeScript rules '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-inferrable-types': 'off', '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-unused-expressions': 'error', // General rules 'no-console': ['error', { allow: ['log'] }], 'no-underscore-dangle': 'off', 'no-plusplus': 'off', 'no-param-reassign': 'off', 'no-restricted-syntax': 'off', 'no-use-before-define': 'off', 'max-len': 'off', 'comma-dangle': 'off', 'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }], 'quotes': ['error', 'single', { avoidEscape: true, allowTemplateLiterals: true }], 'indent': ['error', 2, { SwitchCase: 1 }], 'linebreak-style': ['error', 'unix'], 'no-trailing-spaces': 'error', 'eol-last': ['error', 'always'], 'object-curly-newline': 'off', 'arrow-body-style': 'off', 'consistent-return': 'off', 'generator-star-spacing': 'off', 'global-require': 'warn', 'no-bitwise': 'off', 'no-cond-assign': 'off', 'no-else-return': 'off', 'no-nested-ternary': 'off', 'require-yield': 'warn', 'class-methods-use-this': 'off', 'no-confusing-arrow': 'off', 'no-unused-expressions': 'off', // React rules 'react/jsx-filename-extension': [ 'warn', { extensions: ['.js', '.jsx', '.ts', '.tsx'], }, ], 'react/jsx-no-bind': 'off', 'react/prop-types': 'off', 'react/no-array-index-key': 'off', 'react/forbid-prop-types': 'off', 'react/prefer-stateless-function': 'off', 'react/sort-comp': 'off', 'react/no-did-mount-set-state': 'off', 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'warn', // Import rules 'import/extensions': 'off', 'import/no-unresolved': 'off', 'import/no-extraneous-dependencies': 'off', 'import/prefer-default-export': 'off', // JSX A11y rules 'jsx-a11y/no-noninteractive-element-interactions': 'off', 'jsx-a11y/no-static-element-interactions': 'off', }, }, { files: ['**/*.ts', '**/*.tsx'], languageOptions: { parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname, }, }, }, { ignores: ['dist/**', 'node_modules/**', 'coverage/**', 'build/**', 'doc/**'], }, ]); ================================================ FILE: package.json ================================================ { "name": "hotkeys-js", "description": "A simple micro-library for defining and dispatching keyboard shortcuts. It has no dependencies.", "version": "4.0.2", "type": "module", "main": "dist/hotkeys-js.umd.cjs", "module": "dist/hotkeys-js.js", "browser": "dist/hotkeys-js.min.js", "types": "dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/hotkeys-js.js", "require": "./dist/hotkeys-js.umd.cjs", "browser": "./dist/hotkeys-js.min.js", "default": "./dist/hotkeys-js.js" }, "./package.json": "./package.json" }, "scripts": { "prepare": "npm run build:lib && husky install", "lint": "eslint src website", "type-check": "tsc --noEmit", "watch": "vite build --watch", "build:lib": "vite build", "build": "npm run type-check && npm run build:lib && npm run doc && npm run lint", "pretest": "npm run build", "test": "jest --coverage --detectOpenHandles", "test:watch": "jest --watch", "doc": "vite build --config website/vite.config.ts", "start": "vite --config website/vite.config.ts" }, "files": [ "dist", "doc" ], "keywords": [ "hotkey", "hotkeys", "hotkeys-js", "hotkeysjs", "key", "keys", "keyboard", "shortcuts", "keypress" ], "author": "kenny wong ", "license": "MIT", "homepage": "https://jaywcjlove.github.io/hotkeys-js", "funding": "https://jaywcjlove.github.io/#/sponsor", "repository": { "type": "git", "url": "git+https://github.com/jaywcjlove/hotkeys-js.git" }, "devDependencies": { "@eslint/js": "^9.39.1", "@rollup/plugin-terser": "^0.4.4", "@types/node": "^24.10.1", "@types/react": "^19.2.4", "@types/react-dom": "^19.2.3", "@uiw/react-github-corners": "^1.5.15", "@uiw/react-mac-keyboard": "^1.1.5", "@uiw/react-markdown-preview": "^5.0.3", "@uiw/react-shields": "^2.0.1", "@vitejs/plugin-react": "^5.1.1", "@wcj/dark-mode": "~1.1.0", "eslint": "^9.39.1", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.1.0", "globals": "^16.5.0", "husky": "^8.0.3", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "lint-staged": "^15.2.0", "puppeteer": "~13.5.2", "react": "^19.2.0", "react-dom": "^19.2.0", "typescript": "^5.8.2", "typescript-eslint": "^8.46.4", "vite": "^5.1.0", "vite-plugin-dts": "^4.5.4" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "lint-staged": { "src/**/*.{js,ts}": "eslint", "website/**/*.{js,jsx,ts,tsx}": "eslint" }, "overrides": { "react": "^19.2.0", "react-dom": "^19.2.0" } } ================================================ FILE: src/index.ts ================================================ import { KeyCodeInfo, HotkeysEvent, UnbindInfo, HotkeysOptions, KeyHandler, HotkeysInterface, SetScope, GetScope, GetPressedKeyCodes, GetPressedKeyString, GetAllKeyCodes, Filter, IsPressed, DeleteScope, Unbind, HotkeysAPI, } from './types'; import { addEvent, removeEvent, getMods, getKeys, compareArray, getLayoutIndependentKeyCode } from './utils'; import { _keyMap, _modifier, modifierMap, _mods, _handlers } from './var'; /** Record the pressed keys */ let _downKeys: number[] = []; /** Whether the window has already listened to the focus event */ let winListendFocus: { listener: EventListener; capture: boolean } | null = null; /** Whether we already listen to fullscreen change (to clear stuck _downKeys) */ let winListendFullscreen: { fullscreen: EventListener; webkit: EventListener } | null = null; /** Default hotkey scope */ let _scope: string = 'all'; /** Map to record elements with bound events */ const elementEventMap = new Map< HTMLElement | Document, { keydownListener: EventListener; keyupListenr: EventListener; capture: boolean; } >(); /** Return key code */ const code = (x: string): number => _keyMap[x.toLowerCase()] || _modifier[x.toLowerCase()] || x.toUpperCase().charCodeAt(0); const getKey = (x: number): string | undefined => Object.keys(_keyMap).find((k) => _keyMap[k] === x); const getModifier = (x: number): string | undefined => Object.keys(_modifier).find((k) => _modifier[k] === x); /** Set or get the current scope (defaults to 'all') */ const setScope: SetScope = (scope) => { _scope = scope || 'all'; }; /** Get the current scope */ const getScope: GetScope = () => { return _scope || 'all'; }; /** Get the key codes of the currently pressed keys */ const getPressedKeyCodes: GetPressedKeyCodes = () => { return _downKeys.slice(0); }; const getPressedKeyString: GetPressedKeyString = () => { return _downKeys.map( (c) => getKey(c) || getModifier(c) || String.fromCharCode(c) ); }; const getAllKeyCodes: GetAllKeyCodes = () => { const result: KeyCodeInfo[] = []; Object.keys(_handlers).forEach((k) => { _handlers[k].forEach(({ key, scope, mods, shortcut }) => { result.push({ scope, shortcut, mods, keys: key.split('+').map((v) => code(v)), }); }); }); return result; }; /** hotkey is effective only when filter return true */ const filter: Filter = (event) => { const target = (event.target || event.srcElement) as HTMLElement; const { tagName } = target; let flag = true; const isInput = tagName === 'INPUT' && ![ 'checkbox', 'radio', 'range', 'button', 'file', 'reset', 'submit', 'color', ].includes((target as HTMLInputElement).type); // ignore: isContentEditable === 'true', and