[
  {
    "path": ".editorconfig",
    "content": "# http://editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false\n\n[Makefile]\nindent_style = tab\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [jaywcjlove]\n#ko_fi: jaywcjlove\n#buy_me_a_coffee: jaywcjlove\n# custom: [\"https://wangchujiang.com/#/sponsor\"]\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\non:\n  push:\n    branches:\n      - master\n    # tags:\n    #   - v*\n\nenv:\n  SKIP_PREFLIGHT_CHECK: true\n\njobs:\n  build-deploy:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      id-token: write\n    steps:\n    - uses: actions/checkout@v6\n    - uses: actions/setup-node@v6\n      with:\n        node-version: 24\n        registry-url: 'https://registry.npmjs.org'\n\n    - name: Look Changelog\n      uses: jaywcjlove/changelog-generator@main\n      with:\n        token: ${{ secrets.GITHUB_TOKEN }}\n        filter-author: (jaywcjlove|小弟调调™|dependabot\\[bot\\]|Renovate Bot)\n        filter: (^[\\s]+?[R|r]elease)|(^[R|r]elease)\n\n    - run: npm install\n    - run: npm run build\n    - run: npm run test\n    - run: cp -rp dist doc\n    - run: cp -rp coverage/lcov-report doc\n\n    - name: Extract coverage percentage\n      id: coverage\n      run: |\n        COVERAGE=$(cat coverage/js-coverage.txt)\n        echo \"coverage=$COVERAGE\" >> $GITHUB_OUTPUT\n        echo \"Coverage: $COVERAGE%\"\n\n    - name: Generate Badges\n      uses: jaywcjlove/generated-badges@main\n      with:\n        label: coverage\n        status: ${{ steps.coverage.outputs.coverage }}%\n        output: doc/coverage.svg\n\n    - name: Generate Contributors Images\n      uses: jaywcjlove/github-action-contributors@main\n      with:\n        filter-author: (renovate\\[bot\\]|renovate-bot|dependabot\\[bot\\])\n        output: doc/CONTRIBUTORS.svg\n        avatarSize: 42\n\n    - name: Create Tag\n      id: create_tag\n      uses: jaywcjlove/create-tag-action@main\n      with:\n        package-path: ./package.json\n\n    - name: get tag version\n      id: tag_version\n      uses: jaywcjlove/changelog-generator@main\n\n    - name: Build and Deploy\n      uses: peaceiris/actions-gh-pages@v4\n      with:\n        commit_message: ${{steps.tag_version.outputs.tag}} ${{ github.event.head_commit.message }}\n        github_token: ${{ secrets.GITHUB_TOKEN }}\n        publish_dir: ./doc\n\n    - name: Generate Changelog\n      id: changelog\n      uses: jaywcjlove/changelog-generator@main\n      with:\n        token: ${{ secrets.GITHUB_TOKEN }}\n        filter-author: (jaywcjlove|小弟调调™|dependabot\\[bot\\]|Renovate Bot)\n        filter: (^[\\s]+?[R|r]elease)|(^[R|r]elease)\n\n    - run: |\n        echo \"tag: ${{ steps.changelog.outputs.tag }}\"\n        echo \"version: ${{ steps.changelog.outputs.version }}\"\n        echo \"ref: ${{ github.ref }}\"\n\n    - name: Create Release\n      uses: ncipollo/release-action@v1\n      if: steps.create_tag.outputs.successful\n      with:\n        allowUpdates: true\n        token: ${{ secrets.GITHUB_TOKEN }}\n        name: ${{ steps.create_tag.outputs.version }}\n        tag: ${{ steps.create_tag.outputs.version }}\n        body: |\n          [![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)\n          \n          Documentation ${{ steps.changelog.outputs.tag }}: https://raw.githack.com/jaywcjlove/hotkeys/${{ steps.changelog.outputs.gh-pages-short-hash }}/index.html  \n          Comparing Changes: ${{ steps.changelog.outputs.compareurl }}  \n\n          ```bash\n          npm i hotkeys-js@${{steps.changelog.outputs.version}}\n          ```\n\n          ${{ steps.changelog.outputs.changelog }}\n\n    - name: package.json info\n      uses: jaywcjlove/github-action-package@main\n      with:\n        unset: browserslist,lint-staged,devDependencies,jest,scripts\n    \n    # node@v24.0.0+ \n    # https://gist.github.com/jaywcjlove/a178278521a6f72c74525d3f1d9c4bf9\n    - run: NODE_AUTH_TOKEN=\"\" npm publish --access public --provenance\n      name: 📦 hotkeys-js to NPM\n      continue-on-error: true"
  },
  {
    "path": ".github/workflows/pr.yml",
    "content": "name: CI-PR\non:\n  pull_request:\n\nenv:\n  SKIP_PREFLIGHT_CHECK: true\n\njobs:\n  build-deploy:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      id-token: write\n    steps:\n    - uses: actions/checkout@v6\n    - uses: actions/setup-node@v6\n      with:\n        node-version: 24\n        registry-url: 'https://registry.npmjs.org'\n\n    - name: Look Changelog\n      uses: jaywcjlove/changelog-generator@main\n      with:\n        token: ${{ secrets.GITHUB_TOKEN }}\n        filter-author: (jaywcjlove|小弟调调™|dependabot\\[bot\\]|Renovate Bot)\n        filter: (^[\\s]+?[R|r]elease)|(^[R|r]elease)\n\n    - run: npm install\n    - run: npm run build\n    - run: npm run test\n    "
  },
  {
    "path": ".gitignore",
    "content": "npm-debug.log\nyarn.lock\nnode_modules\nbuild\ndoc\ncoverage\n\n.DS_Store\n.cache\n.vscode\n.idea\n\n*.bak\n*.tem\n*.temp\n#.swp\n*.*~\n~*.*\n"
  },
  {
    "path": ".husky/.gitignore",
    "content": "_\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx lint-staged\n"
  },
  {
    "path": ".npmignore",
    "content": "# Exclude test files\ndist/test-formats.html\n\n# Exclude development files\nsrc/\ntest/\nwebsite/\ncoverage/\n\n# Exclude configuration files\n*.config.ts\n*.config.js\neslint.config.js\ntsconfig.json\n\n# Exclude other unnecessary files\nREADME-zh.md\nDISTRIBUTION-GUIDE.md\nDISTRIBUTION-GUIDE-zh.md\n.github/\n.gitignore\n.husky/"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2015-present, Kenny Wong.\n\nCopyright (c) 2011-2013 Thomas Fuchs (https://github.com/madrobby/keymaster)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README-zh.md",
    "content": "<div markdown=\"1\">\n  <sup>使用<a href=\"https://wangchujiang.com/#/app\" target=\"_blank\">我的应用</a>也是一种<a href=\"https://wangchujiang.com/#/sponsor\" target=\"_blank\">支持</a>我的方式：</sup>\n  <br>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6758053530\" title=\"Scap: Screenshot & Markup Edit for macOS\"><img alt=\"Scap: Screenshot & Markup Edit\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/scap.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6757317079\" title=\"Screen Test for macOS\"><img alt=\"Screen Test\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/screen-test.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/Deskmark/6755948110\" title=\"Deskmark for macOS\"><img alt=\"Deskmark\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/deskmark.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/Keyzer/6500434773\" title=\"Keyzer for macOS\"><img alt=\"Keyzer\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/keyzer.png\"></a>\n  <a target=\"_blank\" href=\"https://github.com/jaywcjlove/vidwall-hub\" title=\"Vidwall Hub for macOS\"><img alt=\"Vidwall Hub\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/vidwall-hub.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/VidCrop/6752624705\" title=\"VidCrop for macOS\"><img alt=\"VidCrop\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/vidcrop.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/Vidwall/6747587746\" title=\"Vidwall for macOS\"><img alt=\"Vidwall\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/vidwall.png\"></a>\n  <a target=\"_blank\" href=\"https://wangchujiang.com/mousio-hint/\" title=\"Mousio Hint for macOS\"><img alt=\"Mousio Hint\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/mousio-hint.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6746747327\" title=\"Mousio for macOS\"><img alt=\"Mousio\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/mousio.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6745227444\" title=\"Musicer for macOS\"><img alt=\"Musicer\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/musicer.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6743841447\" title=\"Audioer for macOS\"><img alt=\"Audioer\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/audioer.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6744690194\" title=\"FileSentinel for macOS\"><img alt=\"FileSentinel\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/file-sentinel.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6743495172\" title=\"FocusCursor for macOS\"><img alt=\"FocusCursor\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/focus-cursor.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6742680573\" title=\"Videoer for macOS\"><img alt=\"Videoer\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/videoer.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6740425504\" title=\"KeyClicker for macOS\"><img alt=\"KeyClicker\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/key-clicker.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6739052447\" title=\"DayBar for macOS\"><img alt=\"DayBar\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/daybar.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6739444407\" title=\"Iconed for macOS\"><img alt=\"Iconed\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/iconed.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6737160756\" title=\"Menuist for macOS\"><img alt=\"Menuist\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/rightmenu-master.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6723903021\" title=\"Paste Quick for macOS\"><img alt=\"Quick RSS\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/paste-quick.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6670696072\" title=\"Quick RSS for macOS/iOS\"><img alt=\"Quick RSS\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/quick-rss.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6670167443\" title=\"Web Serve for macOS\"><img alt=\"Web Serve\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/web-serve.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6503953628\" title=\"Copybook Generator for macOS/iOS\"><img alt=\"Copybook Generator\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/copybook-generator.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6471227008\" title=\"DevTutor for macOS/iOS\"><img alt=\"DevTutor for SwiftUI\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/devtutor.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6479819388\" title=\"RegexMate for macOS/iOS\"><img alt=\"RegexMate\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/regex-mate.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6479194014\" title=\"Time Passage for macOS/iOS\"><img alt=\"Time Passage\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/time-passage.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6478772538\" title=\"IconizeFolder for macOS\"><img alt=\"Iconize Folder\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/iconize-folder.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6478511402\" title=\"Textsound Saver for macOS/iOS\"><img alt=\"Textsound Saver\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/textsound-saver.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6476924627\" title=\"Create Custom Symbols for macOS\"><img alt=\"Create Custom Symbols\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/create-custom-symbols.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6476452351\" title=\"DevHub for macOS\"><img alt=\"DevHub\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/devhub.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6476400184\" title=\"Resume Revise for macOS\"><img alt=\"Resume Revise\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/resume-revise.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6472593276\" title=\"Palette Genius for macOS\"><img alt=\"Palette Genius\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/palette-genius.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6470879005\" title=\"Symbol Scribe for macOS\"><img alt=\"Symbol Scribe\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/symbol-scribe.png\"></a>\n</div>\n<hr>\n\n# Hotkeys\n\n<!--dividing-->\n\n[![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)\n[![Follow On X](https://img.shields.io/badge/Follow%20on%20X-333333?logo=x&logoColor=white)](https://x.com/jaywcjlove)\n[![](https://img.shields.io/npm/dm/hotkeys-js?logo=npm)](https://www.npmjs.com/package/hotkeys-js)\n[![](https://img.shields.io/github/stars/jaywcjlove/hotkeys-js.svg)](https://github.com/jaywcjlove/hotkeys/stargazers)\n[![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)\n[![Coverage Status](https://jaywcjlove.github.io/hotkeys-js/coverage.svg)](https://jaywcjlove.github.io/hotkeys-js/lcov-report/index.html)\n[![English](https://jaywcjlove.github.io/sb/lang/english.svg)](https://jaywcjlove.github.io/hotkeys-js/)\n[![jaywcjlove/hotkeys-js](https://jaywcjlove.github.io/sb/ico/gitee.svg)](https://gitee.com/jaywcjlove/hotkeys)\n\nHotKeys.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+)。\n\n```bash\n╭┈┈╮          ╭┈┈╮  ╭┈┈╮\n┆  ├┈┈..┈┈┈┈┈.┆  └┈╮┆  ├┈┈..┈┈┈┈┈..┈┈.┈┈..┈┈┈┈┈.\n┆     ┆┆  □  ┆┆   ┈┤┆    < ┆  -__┘┆  ┆  ┆┆__ ┈┈┤\n╰┈┈┴┈┈╯╰┈┈┈┈┈╯╰┈┈┈┈╯╰┈┈┴┈┈╯╰┈┈┈┈┈╯╰┈┈┈  ┆╰┈┈┈┈┈╯\n                                  ╰┈┈┈┈┈╯\n```\n\n## 使用\n\n您的系统需要安装 `Node.js`。\n\n```bash\nnpm install hotkeys-js --save\n```\n\n```js\nimport hotkeys from 'hotkeys-js';\n\nhotkeys('f5', function(event, handler){\n  // 阻止 WINDOWS 系统下的默认刷新事件\n  event.preventDefault()\n  alert('你按下了 F5!')\n});\n```\n\n### 浏览器使用\n\n或者手动下载并在 HTML 中链接 **hotkeys.js**。该库提供了不同格式以满足不同的使用需求：\n\n**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/)\n\n**可用格式：**\n\n**IIFE（立即调用函数表达式）- 推荐用于直接浏览器使用：**\n\n```html\n<script src=\"https://unpkg.com/hotkeys-js/dist/hotkeys-js.min.js\">\n</script>\n<script type=\"text/javascript\">\nhotkeys('ctrl+a,ctrl+b,r,f', function (event, handler){\n  switch (handler.key) {\n    case 'ctrl+a': alert('你按下了 ctrl+a!');\n      break;\n    case 'ctrl+b': alert('你按下了 ctrl+b!');\n      break;\n    case 'r': alert('你按下了 r!');\n      break;\n    case 'f': alert('你按下了 f!');\n      break;\n    default: alert(event);\n  }\n});\n</script>\n```\n\n**UMD（通用模块定义）- 用于 CommonJS/AMD 环境：**\n\n```html\n<script src=\"https://unpkg.com/hotkeys-js/dist/hotkeys-js.umd.cjs\">\n</script>\n```\n\n**ES 模块 - 用于支持模块的现代浏览器：**\n\n```html\n<script type=\"module\">\nimport hotkeys from 'https://unpkg.com/hotkeys-js/dist/hotkeys-js.js';\nhotkeys('ctrl+a', function(event, handler){\n  alert('你按下了 ctrl+a!');\n});\n</script>\n```\n\n### 在 React 中使用\n\n[react-hotkeys](https://github.com/jaywcjlove/react-hotkeys) 是监听 keydown 和 keyup 键盘事件的 React 组件，定义和调度键盘快捷键。详细的使用方法请查看其文档 [react-hotkeys](https://github.com/jaywcjlove/react-hotkeys)。  \n[react-hotkeys-hook](https://github.com/JohannesKlauss/react-hotkeys-hook) - 在组件中使用键盘快捷键的 React hook。请确保您至少安装了 react 和 react-dom 的 16.8 版本，否则 hooks 将不会为您工作。\n\n## 浏览器支持\n\nHotkeys.js 已经过测试，应该在以下浏览器中工作。\n\n```shell\nInternet Explorer 6+\nSafari\nFirefox\nChrome\n```\n\n## 支持的按键\n\nHotKeys 理解以下修饰符：`⇧`、`shift`、`option`、`⌥`、`alt`、`ctrl`、`control`、`command` 和 `⌘`。\n\n以下特殊按键可用于快捷键：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。\n\n`⌘` Command()  \n`⌃` Control  \n`⌥` Option(alt)  \n`⇧` Shift  \n`⇪` Caps Lock(Capital)  \n~~`fn` 不支持 fn~~  \n`↩︎` return/Enter space  \n\n## 定义快捷键\n\n暴露了一个全局方法，当直接调用时定义快捷键。\n\n```ts\ndeclare interface HotkeysInterface extends HotkeysAPI {\n  (key: string, method: KeyHandler): void;\n  (key: string, scope: string, method: KeyHandler): void;\n  (key: string, option: HotkeysOptions, method: KeyHandler): void;\n  shift?: boolean;\n  ctrl?: boolean;\n  alt?: boolean;\n  option?: boolean;\n  control?: boolean;\n  cmd?: boolean;\n  command?: boolean;\n}\ndeclare interface HotkeysAPI {\n  setScope: SetScope;\n  getScope: GetScope;\n  deleteScope: DeleteScope;\n  getPressedKeyCodes: GetPressedKeyCodes;\n  getPressedKeyString: GetPressedKeyString;\n  getAllKeyCodes: GetAllKeyCodes;\n  isPressed: IsPressed;\n  filter: Filter;\n  trigger: Trigger;\n  unbind: Unbind;\n  noConflict: NoConflict;\n  keyMap: Record<string, number>;\n  modifier: Record<string, number>;\n  modifierMap: Record<string | number, number | string>;\n}\n```\n\n```js\nhotkeys('f5', function(event, handler) {\n  // 阻止 WINDOWS 系统下的默认刷新事件\n  event.preventDefault();\n  alert('你按下了 F5!');\n});\n\n// 返回 false 停止事件并阻止默认浏览器事件\n// Mac OS 系统将 `command + r` 定义为刷新快捷键\nhotkeys('ctrl+r, command+r', function() {\n  alert('停止刷新!');\n  return false;\n});\n\n// 单个按键\nhotkeys('a', function(event,handler){\n  //event.srcElement: input\n  //event.target: input\n  if(event.target === \"input\"){\n      alert('你按下了 a!')\n  }\n  alert('你按下了 a!')\n});\n\n// 组合键\nhotkeys('ctrl+a,ctrl+b,r,f', function (event, handler){\n  switch (handler.key) {\n    case 'ctrl+a': alert('你按下了 ctrl+a!');\n      break;\n    case 'ctrl+b': alert('你按下了 ctrl+b!');\n      break;\n    case 'r': alert('你按下了 r!');\n      break;\n    case 'f': alert('你按下了 f!');\n      break;\n    default: alert(event);\n  }\n});\n\nhotkeys('ctrl+a+s', function() {\n    alert('你按下了 ctrl+a+s!');\n});\n\n// 使用作用域\nhotkeys('*','wcj', function(event){\n  console.log('做一些事情', event);\n});\n```\n\n#### option 选项\n\n- `scope<String>`：设置快捷键生效的作用域\n- `element<HTMLElement>`：指定要绑定事件的 DOM 元素\n- `keyup<Boolean>`：是否在按键释放时触发快捷键\n- `keydown<Boolean>`：是否在按键按下时触发快捷键\n- `splitKey<String>`：组合键的分隔符（默认为 `+`）\n- `capture<Boolean>`：是否在捕获阶段触发监听器（在事件冒泡之前）\n- `single<Boolean>`：只允许一个回调函数（自动解绑之前的）\n\n```js\nhotkeys('o, enter', {\n  scope: 'wcj',\n  element: document.getElementById('wrapper'),\n}, function() {\n  console.log('做其他事情');\n});\n\nhotkeys('ctrl-+', { splitKey: '-' }, function(e) {\n  console.log('你按下了 ctrl 和 +');\n});\n\nhotkeys('+', { splitKey: '-' }, function(e){\n  console.log('你按下了 +');\n})\n```\n\n**keyup**\n\n**按键按下** 和 **按键释放** 都执行回调事件。\n\n```js\nhotkeys('ctrl+a,alt+a+s', {keyup: true}, function(event, handler) {\n  if (event.type === 'keydown') {\n    console.log('keydown:', event.type, handler, handler.key);\n  }\n\n  if (event.type === 'keyup') {\n    console.log('keyup:', event.type, handler, handler.key);\n  }\n});\n```\n\n## API 参考\n\n星号 \"*\"\n\n修饰键判断\n\n```js\nhotkeys('*', function() {\n  if (hotkeys.shift) {\n    console.log('按下了 shift!');\n  }\n\n  if (hotkeys.ctrl) {\n    console.log('按下了 ctrl!');\n  }\n\n  if (hotkeys.alt) {\n    console.log('按下了 alt!');\n  }\n\n  if (hotkeys.option) {\n    console.log('按下了 option!');\n  }\n\n  if (hotkeys.control) {\n    console.log('按下了 control!');\n  }\n\n  if (hotkeys.cmd) {\n    console.log('按下了 cmd!');\n  }\n\n  if (hotkeys.command) {\n    console.log('按下了 command!');\n  }\n});\n```\n\n### setScope\n\n使用 `hotkeys.setScope` 方法来设置作用域。除了 'all' 之外，只能有一个活动作用域。默认情况下 'all' 总是活动的。\n\n```js\n// 定义带有作用域的快捷键\nhotkeys('ctrl+o, ctrl+alt+enter', 'issues', function() {\n  console.log('做一些事情');\n});\nhotkeys('o, enter', 'files', function() {\n  console.log('做其他事情');\n});\n\n// 设置作用域（只有 'all' 和 'issues' 快捷键会被处理）\nhotkeys.setScope('issues'); // 默认作用域是 'all'\n```\n\n### getScope\n\n使用 `hotkeys.getScope` 方法来获取作用域。\n\n```js\nhotkeys.getScope();\n```\n\n### deleteScope\n\n使用 `hotkeys.deleteScope` 方法来删除作用域。这也会移除与之关联的所有热键。\n\n```js\nhotkeys.deleteScope('issues');\n```\n如果需要在删除后设置新的作用域，可以使用第二个参数。\n\n```js\nhotkeys.deleteScope('issues', 'newScopeName');\n```\n\n### unbind\n\n与定义快捷键类似，它们可以使用 `hotkeys.unbind` 来解绑。\n\n```js\n// 解绑 'a' 处理器\nhotkeys.unbind('a');\n\n// 只为单个作用域解绑热键\n// 如果没有指定作用域，默认为当前作用域\n// （hotkeys.getScope()）\nhotkeys.unbind('o, enter', 'issues');\nhotkeys.unbind('o, enter', 'files');\n```\n\n通过函数解绑事件。\n\n```js\nfunction example() {\n  hotkeys('a', example);\n  hotkeys.unbind('a', example);\n\n  hotkeys('a', 'issues', example);\n  hotkeys.unbind('a', 'issues', example);\n}\n```\n\n解绑所有。\n\n```js\nhotkeys.unbind();\n```\n\n### isPressed\n\n例如，如果当前按下了 `M` 键，`hotkeys.isPressed(77)` 返回 true。\n\n```js\nhotkeys('a', function() {\n  console.log(hotkeys.isPressed('a')); //=> true\n  console.log(hotkeys.isPressed('A')); //=> true\n  console.log(hotkeys.isPressed(65)); //=> true\n});\n```\n\n### trigger\n\n触发快捷键事件\n\n```js\nhotkeys.trigger('ctrl+o');\nhotkeys.trigger('ctrl+o', 'scope2');\n```\n\n### getPressedKeyCodes\n\n返回当前按下的键码数组。\n\n```js\nhotkeys('command+ctrl+shift+a,f', function() {\n  console.log(hotkeys.getPressedKeyCodes()); //=> [17, 65] 或 [70]\n})\n```\n\n### getPressedKeyString\n\n返回当前按下的键字符串数组。\n\n```js\nhotkeys('command+ctrl+shift+a,f', function() {\n  console.log(hotkeys.getPressedKeyString()); \n  //=> ['⌘', '⌃', '⇧', 'A', 'F']\n})\n```\n\n### getAllKeyCodes\n\n获取所有注册码的列表。\n\n```js\nhotkeys('command+ctrl+shift+a,f', function() {\n  console.log(hotkeys.getAllKeyCodes());\n  // [\n  //   { \n  //      scope: 'all', \n  //      shortcut: 'command+ctrl+shift+a', \n  //      mods: [91, 17, 16], \n  //      keys: [91, 17, 16, 65] \n  //    },\n  //   { scope: 'all', shortcut: 'f', mods: [], keys: [42] }\n  // ]\n})\n```\n\n### filter\n\n默认情况下，`INPUT` `SELECT` `TEXTAREA` 元素不启用热键。`Hotkeys.filter` 返回 `true` 快捷键设置发挥作用，`false` 快捷键设置失败。\n\n```js\nhotkeys.filter = function(event){\n  return true;\n}\n// 如何为编辑标签添加过滤器。\n// <div contentEditable=\"true\"></div>\n// \"contentEditable\" 不支持的较旧浏览器会被丢弃\nhotkeys.filter = function(event) {\n  var target = event.target || event.srcElement;\n  var tagName = target.tagName;\n  return !(\n    target.isContentEditable ||\n    tagName == 'INPUT' ||\n    tagName == 'SELECT' ||\n    tagName == 'TEXTAREA'\n  );\n}\n\nhotkeys.filter = function(event){\n  var tagName = (event.target || event.srcElement).tagName;\n  hotkeys.setScope(\n    /^(INPUT|TEXTAREA|SELECT)$/.test(tagName) ? 'input' : 'other'\n  );\n  return true;\n}\n```\n\n### noConflict\n\n放弃 HotKeys 对 `hotkeys` 变量的控制。\n\n```js\nvar k = hotkeys.noConflict();\nk('a', function() {\n  console.log(\"做一些事情\")\n});\n\nhotkeys()\n// -->Uncaught TypeError: hotkeys is not a function(anonymous function)\n// @ VM2170:2InjectedScript._evaluateOn\n// @ VM2165:883InjectedScript._evaluateAndWrap\n// @ VM2165:816InjectedScript.evaluate @ VM2165:682\n```\n\n## 开发\n\n要开发，需要安装依赖，获取代码：\n\n```shell\n$ git https://github.com/jaywcjlove/hotkeys.git\n$ cd hotkeys     # 进入目录\n$ npm install    # 或者 yarn install\n```\n\n要开发，运行自重载构建：\n\n```shell\n$ npm run watch\n```\n\n运行文档网站环境。\n\n```shell\n$ npm run doc # 生成文档网页\n# 实时生成文档网页\n$ npm run start \n```\n\n要贡献，请 fork Hotkeys.js，添加您的补丁和测试（在 `test/` 文件夹中）并提交拉取请求。\n\n```shell\n$ npm run test\n$ npm run test:watch # 开发模式\n```\n\n## 贡献者\n\n一如既往，感谢我们出色的贡献者！\n\n<a href=\"https://github.com/jaywcjlove/hotkeys-js/graphs/contributors\">\n  <img src=\"https://jaywcjlove.github.io/hotkeys-js/CONTRIBUTORS.svg\" />\n</a>\n\n使用 [action-contributors](https://github.com/jaywcjlove/github-action-contributors) 制作。\n\n特别感谢 [@dimensi](https://github.com/dimensi) 对版本 [4.0](https://github.com/jaywcjlove/hotkeys-js/issues/313) 的重构。\n\n## 许可证\n\n[MIT © Kenny Wong](./LICENSE)\n"
  },
  {
    "path": "README.md",
    "content": "<div markdown=\"1\">\n  <sup>Using <a href=\"https://wangchujiang.com/#/app\" target=\"_blank\">my app</a> is also a way to <a href=\"https://wangchujiang.com/#/sponsor\" target=\"_blank\">support</a> me:</sup>\n  <br>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6758053530\" title=\"Scap: Screenshot & Markup Edit for macOS\"><img alt=\"Scap: Screenshot & Markup Edit\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/scap.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6757317079\" title=\"Screen Test for macOS\"><img alt=\"Screen Test\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/screen-test.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/Deskmark/6755948110\" title=\"Deskmark for macOS\"><img alt=\"Deskmark\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/deskmark.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/Keyzer/6500434773\" title=\"Keyzer for macOS\"><img alt=\"Keyzer\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/keyzer.png\"></a>\n  <a target=\"_blank\" href=\"https://github.com/jaywcjlove/vidwall-hub\" title=\"Vidwall Hub for macOS\"><img alt=\"Vidwall Hub\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/vidwall-hub.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/VidCrop/6752624705\" title=\"VidCrop for macOS\"><img alt=\"VidCrop\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/vidcrop.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/Vidwall/6747587746\" title=\"Vidwall for macOS\"><img alt=\"Vidwall\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/vidwall.png\"></a>\n  <a target=\"_blank\" href=\"https://wangchujiang.com/mousio-hint/\" title=\"Mousio Hint for macOS\"><img alt=\"Mousio Hint\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/mousio-hint.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6746747327\" title=\"Mousio for macOS\"><img alt=\"Mousio\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/mousio.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6745227444\" title=\"Musicer for macOS\"><img alt=\"Musicer\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/musicer.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6743841447\" title=\"Audioer for macOS\"><img alt=\"Audioer\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/audioer.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6744690194\" title=\"FileSentinel for macOS\"><img alt=\"FileSentinel\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/file-sentinel.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6743495172\" title=\"FocusCursor for macOS\"><img alt=\"FocusCursor\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/focus-cursor.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6742680573\" title=\"Videoer for macOS\"><img alt=\"Videoer\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/videoer.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6740425504\" title=\"KeyClicker for macOS\"><img alt=\"KeyClicker\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/key-clicker.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6739052447\" title=\"DayBar for macOS\"><img alt=\"DayBar\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/daybar.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6739444407\" title=\"Iconed for macOS\"><img alt=\"Iconed\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/iconed.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6737160756\" title=\"Menuist for macOS\"><img alt=\"Menuist\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/rightmenu-master.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6723903021\" title=\"Paste Quick for macOS\"><img alt=\"Quick RSS\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/paste-quick.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6670696072\" title=\"Quick RSS for macOS/iOS\"><img alt=\"Quick RSS\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/quick-rss.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6670167443\" title=\"Web Serve for macOS\"><img alt=\"Web Serve\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/web-serve.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6503953628\" title=\"Copybook Generator for macOS/iOS\"><img alt=\"Copybook Generator\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/copybook-generator.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6471227008\" title=\"DevTutor for macOS/iOS\"><img alt=\"DevTutor for SwiftUI\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/devtutor.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6479819388\" title=\"RegexMate for macOS/iOS\"><img alt=\"RegexMate\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/regex-mate.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6479194014\" title=\"Time Passage for macOS/iOS\"><img alt=\"Time Passage\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/time-passage.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6478772538\" title=\"IconizeFolder for macOS\"><img alt=\"Iconize Folder\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/iconize-folder.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6478511402\" title=\"Textsound Saver for macOS/iOS\"><img alt=\"Textsound Saver\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/textsound-saver.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6476924627\" title=\"Create Custom Symbols for macOS\"><img alt=\"Create Custom Symbols\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/create-custom-symbols.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6476452351\" title=\"DevHub for macOS\"><img alt=\"DevHub\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/devhub.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6476400184\" title=\"Resume Revise for macOS\"><img alt=\"Resume Revise\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/resume-revise.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6472593276\" title=\"Palette Genius for macOS\"><img alt=\"Palette Genius\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/palette-genius.png\"></a>\n  <a target=\"_blank\" href=\"https://apps.apple.com/app/6470879005\" title=\"Symbol Scribe for macOS\"><img alt=\"Symbol Scribe\" height=\"52\" width=\"52\" src=\"https://wangchujiang.com/appicon/symbol-scribe.png\"></a>\n</div>\n<hr>\n\n# Hotkeys\n\n[![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)\n[![Follow On X](https://img.shields.io/badge/Follow%20on%20X-333333?logo=x&logoColor=white)](https://x.com/jaywcjlove)\n[![](https://img.shields.io/npm/dm/hotkeys-js?logo=npm&label=)](https://www.npmjs.com/package/hotkeys-js)\n[![](https://img.shields.io/github/stars/jaywcjlove/hotkeys-js.svg)](https://github.com/jaywcjlove/hotkeys/stargazers)\n[![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)\n[![Coverage Status](https://jaywcjlove.github.io/hotkeys-js/coverage.svg)](https://jaywcjlove.github.io/hotkeys-js/lcov-report/index.html)\n[![Chinese](https://jaywcjlove.github.io/sb/lang/chinese.svg)](https://wangchujiang.com/hotkeys-js/?lang=zh)\n[![jaywcjlove/hotkeys-js](https://jaywcjlove.github.io/sb/ico/gitee.svg)](https://gitee.com/jaywcjlove/hotkeys)\n\nHotKeys.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+).\n\n```bash\n╭┈┈╮          ╭┈┈╮  ╭┈┈╮\n┆  ├┈┈..┈┈┈┈┈.┆  └┈╮┆  ├┈┈..┈┈┈┈┈..┈┈.┈┈..┈┈┈┈┈.\n┆     ┆┆  □  ┆┆   ┈┤┆    < ┆  -__┘┆  ┆  ┆┆__ ┈┈┤\n╰┈┈┴┈┈╯╰┈┈┈┈┈╯╰┈┈┈┈╯╰┈┈┴┈┈╯╰┈┈┈┈┈╯╰┈┈┈  ┆╰┈┈┈┈┈╯\n                                  ╰┈┈┈┈┈╯\n```\n\n## Usage\n\nYou will need `Node.js` installed on your system.\n\n```bash\nnpm install hotkeys-js --save\n```\n\n```js\nimport hotkeys from 'hotkeys-js';\n\nhotkeys('f5', function(event, handler){\n  // Prevent the default refresh event under WINDOWS system\n  event.preventDefault()\n  alert('you pressed F5!')\n});\n```\n\n### Browser Usage\n\nOr manually download and link **hotkeys.js** in your HTML. The library provides different formats for different use cases:\n\n**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/)\n\n**Available Formats:**\n\n**IIFE (Immediately Invoked Function Expression) - Recommended for direct browser usage:**\n\n```html\n<script src=\"https://unpkg.com/hotkeys-js/dist/hotkeys-js.min.js\">\n</script>\n<script type=\"text/javascript\">\nhotkeys('ctrl+a,ctrl+b,r,f', function (event, handler){\n  switch (handler.key) {\n    case 'ctrl+a': alert('you pressed ctrl+a!');\n      break;\n    case 'ctrl+b': alert('you pressed ctrl+b!');\n      break;\n    case 'r': alert('you pressed r!');\n      break;\n    case 'f': alert('you pressed f!');\n      break;\n    default: alert(event);\n  }\n});\n</script>\n```\n\n**UMD (Universal Module Definition) - For CommonJS/AMD environments:**\n\n```html\n<script src=\"https://unpkg.com/hotkeys-js/dist/hotkeys-js.umd.cjs\">\n</script>\n```\n\n**ES Module - For modern browsers with module support:**\n\n```html\n<script type=\"module\">\nimport hotkeys from 'https://unpkg.com/hotkeys-js/dist/hotkeys-js.js';\nhotkeys('ctrl+a', function(event, handler){\n  alert('you pressed ctrl+a!');\n});\n</script>\n```\n\n### Used in React\n\n[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).\n\n[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.\n\n## Browser Support\n\nHotkeys.js has been tested and should work in.\n\n```shell\nInternet Explorer 6+\nSafari\nFirefox\nChrome\n```\n\n## Supported Keys\n\nHotKeys understands the following modifiers: `⇧`, `shift`, `option`, `⌥`, `alt`, `ctrl`, `control`, `command`, and `⌘`.\n\nThe 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.\n\n`⌘` Command()  \n`⌃` Control  \n`⌥` Option(alt)  \n`⇧` Shift  \n`⇪` Caps Lock(Capital)  \n~~`fn` Does not support fn~~  \n`↩︎` return/Enter space  \n\n## Defining Shortcuts\n\nOne global method is exposed, key which defines shortcuts when called directly.\n\n```ts\ndeclare interface HotkeysInterface extends HotkeysAPI {\n  (key: string, method: KeyHandler): void;\n  (key: string, scope: string, method: KeyHandler): void;\n  (key: string, option: HotkeysOptions, method: KeyHandler): void;\n  shift?: boolean;\n  ctrl?: boolean;\n  alt?: boolean;\n  option?: boolean;\n  control?: boolean;\n  cmd?: boolean;\n  command?: boolean;\n}\ndeclare interface HotkeysAPI {\n  setScope: SetScope;\n  getScope: GetScope;\n  deleteScope: DeleteScope;\n  getPressedKeyCodes: GetPressedKeyCodes;\n  getPressedKeyString: GetPressedKeyString;\n  getAllKeyCodes: GetAllKeyCodes;\n  isPressed: IsPressed;\n  filter: Filter;\n  trigger: Trigger;\n  unbind: Unbind;\n  noConflict: NoConflict;\n  keyMap: Record<string, number>;\n  modifier: Record<string, number>;\n  modifierMap: Record<string | number, number | string>;\n}\n```\n\n\n```js\nhotkeys('f5', function(event, handler) {\n  // Prevent the default refresh event under WINDOWS system\n  event.preventDefault();\n  alert('you pressed F5!');\n});\n\n// Returning false stops the event and prevents default browser events\n// Mac OS system defines `command + r` as a refresh shortcut\nhotkeys('ctrl+r, command+r', function() {\n  alert('stopped reload!');\n  return false;\n});\n\n// Single key\nhotkeys('a', function(event,handler){\n  //event.srcElement: input\n  //event.target: input\n  if(event.target === \"input\"){\n      alert('you pressed a!')\n  }\n  alert('you pressed a!')\n});\n\n// Key Combination\nhotkeys('ctrl+a,ctrl+b,r,f', function (event, handler){\n  switch (handler.key) {\n    case 'ctrl+a': alert('you pressed ctrl+a!');\n      break;\n    case 'ctrl+b': alert('you pressed ctrl+b!');\n      break;\n    case 'r': alert('you pressed r!');\n      break;\n    case 'f': alert('you pressed f!');\n      break;\n    default: alert(event);\n  }\n});\n\nhotkeys('ctrl+a+s', function() {\n    alert('you pressed ctrl+a+s!');\n});\n\n// Using a scope\nhotkeys('*','wcj', function(event){\n  console.log('do something', event);\n});\n```\n\n#### option\n\n- `scope<String>`: Sets the scope in which the shortcut key is active\n- `element<HTMLElement>`: Specifies the DOM element to bind the event to\n- `keyup<Boolean>`: Whether to trigger the shortcut on key release\n- `keydown<Boolean>`: Whether to trigger the shortcut on key press\n- `splitKey<String>`: Delimiter for key combinations (default is `+`)\n- `capture<Boolean>`: Whether to trigger the listener during the capture phase (before the event bubbles down)\n- `single<Boolean>`: Allows only one callback function (automatically unbinds previous one)\n\n```js\nhotkeys('o, enter', {\n  scope: 'wcj',\n  element: document.getElementById('wrapper'),\n}, function() {\n  console.log('do something else');\n});\n\nhotkeys('ctrl-+', { splitKey: '-' }, function(e) {\n  console.log('you pressed ctrl and +');\n});\n\nhotkeys('+', { splitKey: '-' }, function(e){\n  console.log('you pressed +');\n})\n```\n\n**keyup**\n\n**key down** and **key up** both perform callback events.\n\n```js\nhotkeys('ctrl+a,alt+a+s', {keyup: true}, function(event, handler) {\n  if (event.type === 'keydown') {\n    console.log('keydown:', event.type, handler, handler.key);\n  }\n\n  if (event.type === 'keyup') {\n    console.log('keyup:', event.type, handler, handler.key);\n  }\n});\n```\n\n## API REFERENCE\n\nAsterisk \"*\"\n\nModifier key judgments\n\n```js\nhotkeys('*', function() {\n  if (hotkeys.shift) {\n    console.log('shift is pressed!');\n  }\n\n  if (hotkeys.ctrl) {\n    console.log('ctrl is pressed!');\n  }\n\n  if (hotkeys.alt) {\n    console.log('alt is pressed!');\n  }\n\n  if (hotkeys.option) {\n    console.log('option is pressed!');\n  }\n\n  if (hotkeys.control) {\n    console.log('control is pressed!');\n  }\n\n  if (hotkeys.cmd) {\n    console.log('cmd is pressed!');\n  }\n\n  if (hotkeys.command) {\n    console.log('command is pressed!');\n  }\n});\n```\n\n### setScope\n\nUse the `hotkeys.setScope` method to set scope. There can only be one active scope besides 'all'.  By default 'all' is always active.\n\n```js\n// Define shortcuts with a scope\nhotkeys('ctrl+o, ctrl+alt+enter', 'issues', function() {\n  console.log('do something');\n});\nhotkeys('o, enter', 'files', function() {\n  console.log('do something else');\n});\n\n// Set the scope (only 'all' and 'issues' shortcuts will be honored)\nhotkeys.setScope('issues'); // default scope is 'all'\n```\n\n### getScope\n\nUse the `hotkeys.getScope` method to get scope.\n\n```js\nhotkeys.getScope();\n```\n\n### deleteScope\n\nUse the `hotkeys.deleteScope` method to delete a scope. This will also remove all associated hotkeys with it.\n\n```js\nhotkeys.deleteScope('issues');\n```\nYou can use second argument, if need set new scope after deleting.\n\n```js\nhotkeys.deleteScope('issues', 'newScopeName');\n```\n\n### unbind\n\nSimilar to defining shortcuts, they can be unbound using `hotkeys.unbind`.\n\n```js\n// unbind 'a' handler\nhotkeys.unbind('a');\n\n// Unbind a hotkeys only for a single scope\n// If no scope is specified it defaults to the current \n// scope (hotkeys.getScope())\nhotkeys.unbind('o, enter', 'issues');\nhotkeys.unbind('o, enter', 'files');\n```\n\nUnbind events through functions.\n\n```js\nfunction example() {\n  hotkeys('a', example);\n  hotkeys.unbind('a', example);\n\n  hotkeys('a', 'issues', example);\n  hotkeys.unbind('a', 'issues', example);\n}\n```\n\nTo unbind everything.\n\n```js\nhotkeys.unbind();\n```\n\n### isPressed\n\nFor example, `hotkeys.isPressed(77)` is true if the `M` key is currently pressed.\n\n```js\nhotkeys('a', function() {\n  console.log(hotkeys.isPressed('a')); //=> true\n  console.log(hotkeys.isPressed('A')); //=> true\n  console.log(hotkeys.isPressed(65)); //=> true\n});\n```\n\n### trigger\n\ntrigger shortcut key event\n\n```js\nhotkeys.trigger('ctrl+o');\nhotkeys.trigger('ctrl+o', 'scope2');\n```\n\n### getPressedKeyCodes\n\nReturns an array of key codes currently pressed.\n\n```js\nhotkeys('command+ctrl+shift+a,f', function() {\n  console.log(hotkeys.getPressedKeyCodes()); //=> [17, 65] or [70]\n})\n```\n\n### getPressedKeyString\n\nReturns an array of key codes currently pressed.\n\n```js\nhotkeys('command+ctrl+shift+a,f', function() {\n  console.log(hotkeys.getPressedKeyString()); \n  //=> ['⌘', '⌃', '⇧', 'A', 'F']\n})\n```\n\n### getAllKeyCodes\n\nGet a list of all registration codes.\n\n```js\nhotkeys('command+ctrl+shift+a,f', function() {\n  console.log(hotkeys.getAllKeyCodes());\n  // [\n  //   { \n  //      scope: 'all', \n  //      shortcut: 'command+ctrl+shift+a', \n  //      mods: [91, 17, 16], \n  //      keys: [91, 17, 16, 65] \n  //    },\n  //   { scope: 'all', shortcut: 'f', mods: [], keys: [42] }\n  // ]\n})\n```\n\n### filter\n\nBy 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.\n\n```js\nhotkeys.filter = function(event){\n  return true;\n}\n// How to add the filter to edit labels. \n// <div contentEditable=\"true\"></div>\n// \"contentEditable\" Older browsers that do not support drops\nhotkeys.filter = function(event) {\n  var target = event.target || event.srcElement;\n  var tagName = target.tagName;\n  return !(\n    target.isContentEditable || \n    tagName == 'INPUT' || \n    tagName == 'SELECT' || \n    tagName == 'TEXTAREA'\n  );\n}\n\nhotkeys.filter = function(event){\n  var tagName = (event.target || event.srcElement).tagName;\n  hotkeys.setScope(\n    /^(INPUT|TEXTAREA|SELECT)$/.test(tagName) ? 'input' : 'other'\n  );\n  return true;\n}\n```\n\n### noConflict\n\nRelinquish HotKeys’s control of the `hotkeys` variable.\n\n```js\nvar k = hotkeys.noConflict();\nk('a', function() {\n  console.log(\"do something\")\n});\n\nhotkeys()\n// -->Uncaught TypeError: hotkeys is not a function(anonymous function)\n// @ VM2170:2InjectedScript._evaluateOn\n// @ VM2165:883InjectedScript._evaluateAndWrap\n// @ VM2165:816InjectedScript.evaluate @ VM2165:682\n```\n\n## Development\n\nTo develop, Install dependencies, Get the code:\n\n```shell\n$ git https://github.com/jaywcjlove/hotkeys.git\n$ cd hotkeys     # Into the directory\n$ npm install    # or  yarn install\n```\n\nTo develop, run the self-reloading build:\n\n```shell\n$ npm run watch\n```\n\nRun Document Website Environment.\n\n```shell\n# Generate documentation website\n$ npm run doc \n# Live-generate documentation website\n$ npm run start \n```\n\nTo contribute, please fork Hotkeys.js, add your patch and tests for it (in the `test/` folder) and submit a pull request.\n\n```shell\n$ npm run test\n$ npm run test:watch # Development model\n```\n\n## Contributors\n\nAs always, thanks to our amazing contributors!\n\n<a href=\"https://github.com/jaywcjlove/hotkeys-js/graphs/contributors\">\n  <img src=\"https://jaywcjlove.github.io/hotkeys-js/CONTRIBUTORS.svg\" />\n</a>\n\nMade with [action-contributors](https://github.com/jaywcjlove/github-action-contributors).\n\nSpecial thanks to [@dimensi](https://github.com/dimensi) for the refactoring of version [4.0](https://github.com/jaywcjlove/hotkeys-js/issues/313).\n\n## License\n\n[MIT © Kenny Wong](./LICENSE)\n"
  },
  {
    "path": "dist/compatibility-test.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Hotkeys-js Comprehensive Compatibility Test</title>\n    <style>\n        body {\n            font-family: Arial, sans-serif;\n            max-width: 1000px;\n            margin: 20px auto;\n            padding: 20px;\n            background: #f5f5f5;\n        }\n        .header {\n            text-align: center;\n            background: white;\n            padding: 30px;\n            border-radius: 10px;\n            box-shadow: 0 2px 10px rgba(0,0,0,0.1);\n            margin-bottom: 30px;\n        }\n        .test-section {\n            background: white;\n            margin: 20px 0;\n            padding: 25px;\n            border-radius: 8px;\n            box-shadow: 0 2px 8px rgba(0,0,0,0.1);\n        }\n        .test-section h2 {\n            margin-top: 0;\n            color: #333;\n            border-bottom: 2px solid #007bff;\n            padding-bottom: 10px;\n        }\n        .test-section h3 {\n            color: #555;\n            margin-top: 25px;\n        }\n        .success {\n            color: #28a745;\n            font-weight: bold;\n        }\n        .error {\n            color: #dc3545;\n            font-weight: bold;\n        }\n        .info {\n            color: #17a2b8;\n        }\n        .warning {\n            color: #ffc107;\n            font-weight: bold;\n        }\n        .log-entry {\n            padding: 8px 12px;\n            margin: 5px 0;\n            border-radius: 4px;\n            border-left: 4px solid #ddd;\n        }\n        .log-entry.success {\n            background: #d4edda;\n            border-left-color: #28a745;\n        }\n        .log-entry.error {\n            background: #f8d7da;\n            border-left-color: #dc3545;\n        }\n        .log-entry.info {\n            background: #d1ecf1;\n            border-left-color: #17a2b8;\n        }\n        .log-entry.warning {\n            background: #fff3cd;\n            border-left-color: #ffc107;\n        }\n        button {\n            background: #007bff;\n            color: white;\n            border: none;\n            padding: 10px 20px;\n            margin: 5px;\n            border-radius: 5px;\n            cursor: pointer;\n            font-size: 14px;\n        }\n        button:hover {\n            background: #0056b3;\n        }\n        kbd {\n            background: #f8f9fa;\n            border: 1px solid #ddd;\n            border-radius: 3px;\n            padding: 2px 6px;\n            font-family: monospace;\n            font-size: 0.9em;\n        }\n        .format-grid {\n            display: grid;\n            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n            gap: 20px;\n            margin-top: 20px;\n        }\n        .format-card {\n            background: #f8f9fa;\n            padding: 20px;\n            border-radius: 6px;\n            border: 1px solid #e9ecef;\n        }\n        .format-card h4 {\n            margin-top: 0;\n            color: #495057;\n        }\n        pre {\n            background: #f8f9fa;\n            padding: 15px;\n            border-radius: 5px;\n            overflow-x: auto;\n            font-size: 13px;\n        }\n        .stats-grid {\n            display: grid;\n            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n            gap: 15px;\n            margin: 20px 0;\n        }\n        .stat-card {\n            background: #f8f9fa;\n            padding: 15px;\n            border-radius: 6px;\n            text-align: center;\n        }\n        .stat-number {\n            font-size: 2em;\n            font-weight: bold;\n            color: #007bff;\n        }\n        .hotkey-list {\n            display: grid;\n            grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n            gap: 10px;\n            margin: 15px 0;\n        }\n        .hotkey-item {\n            background: #f8f9fa;\n            padding: 10px;\n            border-radius: 4px;\n            border-left: 3px solid #007bff;\n        }\n    </style>\n</head>\n<body>\n    <div class=\"header\">\n        <h1>🔥 Hotkeys-js Comprehensive Compatibility Test Suite</h1>\n        <p>Test the compatibility of hotkeys-js library across different module formats and environments</p>\n        <div class=\"stats-grid\">\n            <div class=\"stat-card\">\n                <div class=\"stat-number\" id=\"total-tests\">0</div>\n                <div>Total Tests</div>\n            </div>\n            <div class=\"stat-card\">\n                <div class=\"stat-number success\" id=\"passed-tests\">0</div>\n                <div>Passed Tests</div>\n            </div>\n            <div class=\"stat-card\">\n                <div class=\"stat-number error\" id=\"failed-tests\">0</div>\n                <div>Failed Tests</div>\n            </div>\n        </div>\n    </div>\n\n    <!-- Module Format Tests -->\n    <div class=\"test-section\">\n        <h2>📦 Module Format Compatibility Tests</h2>\n        <p>Test loading and basic functionality of hotkeys-js library in different build formats</p>\n        \n        <div class=\"format-grid\">\n            <div class=\"format-card\">\n                <h4>IIFE Format (hotkeys-js.min.js)</h4>\n                <p>Immediately Invoked Function Expression, suitable for direct browser use</p>\n                <p>Press <kbd>F1</kbd> to test</p>\n                <div id=\"iife-status\">Loading...</div>\n            </div>\n            \n            <div class=\"format-card\">\n                <h4>UMD Format (hotkeys-js.umd.cjs)</h4>\n                <p>Universal Module Definition, compatible with CommonJS, AMD and global variables</p>\n                <p>Press <kbd>F2</kbd> to test</p>\n                <div id=\"umd-status\">Loading...</div>\n            </div>\n            \n            <div class=\"format-card\">\n                <h4>ES Module Format (hotkeys-js.js)</h4>\n                <p>Modern ES6 module format, requires server environment</p>\n                <p>Press <kbd>F3</kbd> to test</p>\n                <div id=\"esm-status\">Loading...</div>\n            </div>\n        </div>\n    </div>\n\n    <!-- Hotkey Functionality Tests -->\n    <div class=\"test-section\">\n        <h2>🎯 Hotkey Functionality Tests</h2>\n        <p>Test various hotkey combinations and functions</p>\n        \n        <div class=\"hotkey-list\">\n            <div class=\"hotkey-item\">\n                <kbd>Ctrl + K</kbd><br>\n                <small>Basic hotkey test</small>\n            </div>\n            <div class=\"hotkey-item\">\n                <kbd>Alt + S</kbd><br>\n                <small>Modifier key combination test</small>\n            </div>\n            <div class=\"hotkey-item\">\n                <kbd>Ctrl + Shift + T</kbd><br>\n                <small>Multiple modifier keys test</small>\n            </div>\n            <div class=\"hotkey-item\">\n                <kbd>Esc</kbd><br>\n                <small>Single key test</small>\n            </div>\n            <div class=\"hotkey-item\">\n                <kbd>Space</kbd><br>\n                <small>Space key test</small>\n            </div>\n            <div class=\"hotkey-item\">\n                <kbd>Enter</kbd><br>\n                <small>Enter key test</small>\n            </div>\n        </div>\n        \n        <div id=\"hotkey-output\"></div>\n    </div>\n\n    <!-- API Tests -->\n    <div class=\"test-section\">\n        <h2>🔧 API Method Tests</h2>\n        <p>Test various API methods provided by hotkeys-js</p>\n        \n        <div>\n            <button onclick=\"testAPI()\">🧪 Run API Tests</button>\n            <button onclick=\"testScopes()\">🎯 Test Scope Functions</button>\n            <button onclick=\"testKeyMapping()\">🗝️ Test Key Mapping</button>\n            <button onclick=\"clearOutput('api-output')\">🧹 Clear Output</button>\n        </div>\n        \n        <div id=\"api-output\"></div>\n    </div>\n\n    <!-- Module Loading Details -->\n    <div class=\"test-section\">\n        <h2>📋 Module Loading Details</h2>\n        <div id=\"module-details\"></div>\n    </div>\n\n    <!-- Usage Examples -->\n    <div class=\"test-section\">\n        <h2>📝 Usage Examples</h2>\n        \n        <h3>ESM Environment (Recommended)</h3>\n        <pre><code>import hotkeys from 'hotkeys-js';\n\nhotkeys('ctrl+k', function(event, handler) {\n  event.preventDefault();\n  console.log('Hotkey pressed!');\n});</code></pre>\n        \n        <h3>CommonJS Environment</h3>\n        <pre><code>const hotkeys = require('hotkeys-js');\n// or\nconst hotkeys = require('hotkeys-js').default;\n\nhotkeys('ctrl+k', function(event, handler) {\n  event.preventDefault();\n  console.log('Hotkey pressed!');\n});</code></pre>\n        \n        <h3>Browser Global Environment</h3>\n        <pre><code>&lt;script src=\"hotkeys-js/dist/hotkeys-js.min.js\">&lt;/script>\n&lt;script>\n  hotkeys('ctrl+k', function(event, handler) {\n    event.preventDefault();\n    console.log('Hotkey pressed!');\n  });\n&lt;/script></code></pre>\n    </div>\n\n    <!-- Load all formats for testing -->\n    \n    <!-- 1. Load IIFE format -->\n    <script src=\"./hotkeys-js.min.js\"></script>\n    \n    <!-- 2. Load UMD format (load later to test independence) -->\n    \n    <!-- 3. ES Module will be loaded via import -->\n\n    <script>\n        // Global variables\n        let testStats = { total: 0, passed: 0, failed: 0 };\n        let hotkeysInstance = null;\n\n        // Utility functions\n        function updateStats() {\n            document.getElementById('total-tests').textContent = testStats.total;\n            document.getElementById('passed-tests').textContent = testStats.passed;\n            document.getElementById('failed-tests').textContent = testStats.failed;\n        }\n\n        function logTest(elementId, message, type = 'info', description = '') {\n            testStats.total++;\n            if (type === 'success') testStats.passed++;\n            if (type === 'error') testStats.failed++;\n            updateStats();\n\n            const element = document.getElementById(elementId);\n            if (!element) return;\n            \n            const div = document.createElement('div');\n            div.className = `log-entry ${type}`;\n            const timestamp = new Date().toLocaleTimeString();\n            const fullMessage = `[${timestamp}] ${message}`;\n            if (description) {\n                div.innerHTML = `${fullMessage}<br><small>${description}</small>`;\n            } else {\n                div.textContent = fullMessage;\n            }\n            element.appendChild(div);\n            \n            // Auto scroll to latest message\n            element.scrollTop = element.scrollHeight;\n        }\n\n        function clearOutput(elementId) {\n            const element = document.getElementById(elementId);\n            if (element) {\n                element.innerHTML = '';\n            }\n        }\n\n        // Test IIFE format\n        function testIIFE() {\n            try {\n                if (typeof window.hotkeys !== 'undefined') {\n                    hotkeysInstance = window.hotkeys;\n                    \n                    // Bind F1 hotkey\n                    hotkeys('f1', function(event, handler) {\n                        event.preventDefault();\n                        logTest('hotkey-output', '✅ IIFE Format: F1 hotkey triggered successfully!', 'success');\n                    });\n                    \n                    logTest('iife-status', '✅ IIFE format loaded successfully', 'success', 'Global window.hotkeys available');\n                    logTest('module-details', 'IIFE format: hotkeys type is ' + typeof hotkeys, 'info');\n                    \n                    return true;\n                } else {\n                    throw new Error('window.hotkeys is undefined');\n                }\n            } catch (e) {\n                logTest('iife-status', '❌ IIFE format failed: ' + e.message, 'error');\n                return false;\n            }\n        }\n\n        // Test UMD format\n        function testUMD() {\n            // Create new script tag to load UMD\n            const script = document.createElement('script');\n            script.src = './hotkeys-js.umd.cjs';\n            script.onload = function() {\n                try {\n                    if (typeof hotkeys !== 'undefined') {\n                        // Bind F2 hotkey\n                        hotkeys('f2', function(event, handler) {\n                            event.preventDefault();\n                            logTest('hotkey-output', '✅ UMD Format: F2 hotkey triggered successfully!', 'success');\n                        });\n                        \n                        logTest('umd-status', '✅ UMD format loaded successfully', 'success', 'Supports CommonJS/AMD/Global environments');\n                        logTest('module-details', 'UMD format: hotkeys type is ' + typeof hotkeys, 'info');\n                    } else {\n                        throw new Error('UMD hotkeys is undefined');\n                    }\n                } catch (e) {\n                    logTest('umd-status', '❌ UMD format failed: ' + e.message, 'error');\n                }\n            };\n            script.onerror = function() {\n                logTest('umd-status', '❌ UMD format loading failed', 'error', 'Cannot load hotkeys-js.umd.cjs');\n            };\n            document.head.appendChild(script);\n        }\n\n        // Test ES Module format\n        async function testESM() {\n            try {\n                const { default: hotkeysESM } = await import('./hotkeys-js.js');\n                \n                // Bind F3 hotkey\n                hotkeysESM('f3', function(event, handler) {\n                    event.preventDefault();\n                    logTest('hotkey-output', '✅ ES Module Format: F3 hotkey triggered successfully!', 'success');\n                });\n                \n                // Bind Ctrl+Shift+T (ESM specific test)\n                hotkeysESM('ctrl+shift+t', function(event, handler) {\n                    event.preventDefault();\n                    logTest('hotkey-output', '✅ ESM Format: Ctrl+Shift+T hotkey triggered successfully!', 'success');\n                });\n                \n                logTest('esm-status', '✅ ES Module format loaded successfully', 'success', 'Modern module format with tree-shaking support');\n                logTest('module-details', 'ESM format: hotkeys type is ' + typeof hotkeysESM, 'info');\n                \n            } catch (e) {\n                logTest('esm-status', '❌ ES Module format failed: ' + e.message, 'error', \n                       'ES Module requires server environment, does not support file:// protocol');\n            }\n        }\n\n        // Setup common hotkey tests\n        function setupCommonHotkeys() {\n            if (!hotkeysInstance) return;\n            \n            // Ctrl+K\n            hotkeysInstance('ctrl+k', function(event, handler) {\n                event.preventDefault();\n                logTest('hotkey-output', '✅ Ctrl+K hotkey triggered', 'success', 'Basic modifier key combination test');\n            });\n            \n            // Alt+S  \n            hotkeysInstance('alt+s', function(event, handler) {\n                event.preventDefault();\n                logTest('hotkey-output', '✅ Alt+S hotkey triggered', 'success', 'Alt modifier key test');\n            });\n            \n            // Esc\n            hotkeysInstance('esc', function(event, handler) {\n                event.preventDefault();\n                logTest('hotkey-output', '✅ Esc key triggered', 'success', 'Single key test');\n            });\n            \n            // Space\n            hotkeysInstance('space', function(event, handler) {\n                event.preventDefault();\n                logTest('hotkey-output', '✅ Space key triggered', 'success', 'Space key test');\n            });\n            \n            // Enter\n            hotkeysInstance('enter', function(event, handler) {\n                event.preventDefault();\n                logTest('hotkey-output', '✅ Enter key triggered', 'success', 'Enter key test');\n            });\n            \n            logTest('hotkey-output', '🎯 Common hotkeys have been set, please test by pressing keys', 'info');\n        }\n\n        // API tests\n        function testAPI() {\n            clearOutput('api-output');\n            \n            if (!hotkeysInstance) {\n                logTest('api-output', '❌ 没有可用的 hotkeys 实例', 'error');\n                return;\n            }\n\n            try {\n                // Test basic API\n                const methods = ['setScope', 'getScope', 'deleteScope', 'unbind', 'trigger', 'isPressed', \n                               'getPressedKeyCodes', 'getPressedKeyString', 'getAllKeyCodes', 'filter'];\n                \n                logTest('api-output', '🧪 Starting API method tests', 'info');\n                \n                methods.forEach(method => {\n                    if (hotkeysInstance[method]) {\n                        logTest('api-output', `✅ API method ${method} exists`, 'success');\n                    } else {\n                        logTest('api-output', `❌ API method ${method} missing`, 'error');\n                    }\n                });\n                \n                // Test scope functionality\n                const currentScope = hotkeysInstance.getScope();\n                logTest('api-output', `Current scope: ${currentScope}`, 'info');\n                \n                // Test key status\n                const pressedCodes = hotkeysInstance.getPressedKeyCodes();\n                logTest('api-output', `Currently pressed key codes: [${pressedCodes.join(', ')}]`, 'info');\n                \n                // Test key mapping\n                const enterCode = hotkeysInstance.keyMap.enter;\n                logTest('api-output', `Enter key mapping: ${enterCode}`, 'info');\n                \n                // 测试修饰键\n                const ctrlCode = hotkeysInstance.modifier.ctrl;\n                logTest('api-output', `Ctrl modifier key code: ${ctrlCode}`, 'info');\n                \n                logTest('api-output', '✅ API tests completed', 'success');\n                \n            } catch (e) {\n                logTest('api-output', `❌ API tests failed: ${e.message}`, 'error');\n            }\n        }\n\n        // Scope tests\n        function testScopes() {\n            clearOutput('api-output');\n            \n            if (!hotkeysInstance) {\n                logTest('api-output', '❌ 没有可用的 hotkeys 实例', 'error');\n                return;\n            }\n\n            try {\n                logTest('api-output', '🎯 Starting scope tests', 'info');\n                \n                // Get current scope\n                const originalScope = hotkeysInstance.getScope();\n                logTest('api-output', `Original scope: ${originalScope}`, 'info');\n                \n                // Set test scope\n                hotkeysInstance.setScope('test-scope');\n                const newScope = hotkeysInstance.getScope();\n                logTest('api-output', `Set new scope: ${newScope}`, newScope === 'test-scope' ? 'success' : 'error');\n                \n                // Restore original scope\n                hotkeysInstance.setScope(originalScope);\n                const restoredScope = hotkeysInstance.getScope();\n                logTest('api-output', `Restored scope: ${restoredScope}`, restoredScope === originalScope ? 'success' : 'error');\n                \n                logTest('api-output', '✅ Scope tests completed', 'success');\n                \n            } catch (e) {\n                logTest('api-output', `❌ Scope tests failed: ${e.message}`, 'error');\n            }\n        }\n\n        // Key mapping tests\n        function testKeyMapping() {\n            clearOutput('api-output');\n            \n            if (!hotkeysInstance) {\n                logTest('api-output', '❌ 没有可用的 hotkeys 实例', 'error');\n                return;\n            }\n\n            try {\n                logTest('api-output', '🗝️ Starting key mapping tests', 'info');\n                \n                // Test common key mappings\n                const commonKeys = ['enter', 'esc', 'space', 'tab', 'backspace', 'delete'];\n                commonKeys.forEach(key => {\n                    const code = hotkeysInstance.keyMap[key];\n                    if (code !== undefined) {\n                        logTest('api-output', `✅ ${key} -> ${code}`, 'success');\n                    } else {\n                        logTest('api-output', `❌ ${key} mapping missing`, 'error');\n                    }\n                });\n                \n                // Test modifier key mappings\n                const modifiers = ['ctrl', 'alt', 'shift', 'meta'];\n                modifiers.forEach(mod => {\n                    const code = hotkeysInstance.modifier[mod];\n                    if (code !== undefined) {\n                        logTest('api-output', `✅ ${mod} -> ${code}`, 'success');\n                    } else {\n                        logTest('api-output', `❌ ${mod} mapping missing`, 'error');\n                    }\n                });\n                \n                // Count key mappings\n                const keyCount = Object.keys(hotkeysInstance.keyMap).length;\n                const modCount = Object.keys(hotkeysInstance.modifier).length;\n                logTest('api-output', `Total key mappings: ${keyCount}, modifier keys: ${modCount}`, 'info');\n                \n                logTest('api-output', '✅ Key mapping tests completed', 'success');\n                \n            } catch (e) {\n                logTest('api-output', `❌ Key mapping tests failed: ${e.message}`, 'error');\n            }\n        }\n\n        // Run tests after page load\n        document.addEventListener('DOMContentLoaded', function() {\n            logTest('module-details', '🚀 Starting compatibility tests', 'info', 'Loading and testing various module formats');\n            \n            // Test IIFE\n            testIIFE();\n            \n            // Delay UMD test (avoid conflicts)\n            setTimeout(testUMD, 1000);\n            \n            // Delay ESM test\n            setTimeout(testESM, 1500);\n            \n            // Setup common hotkeys\n            setTimeout(() => {\n                setupCommonHotkeys();\n                logTest('module-details', '✅ Initialization completed', 'success', 'All tests are ready');\n            }, 2000);\n        });\n    </script>\n</body>\n</html>"
  },
  {
    "path": "dist/hotkeys-js.js",
    "content": "/*!\n * hotkeys-js v4.0.2\n * A simple micro-library for defining and dispatching keyboard shortcuts. It has no dependencies.\n * \n * @author kenny wong <wowohoo@qq.com>\n * @license MIT\n * @homepage https://jaywcjlove.github.io/hotkeys-js\n */\nconst isff = typeof navigator !== \"undefined\" ? navigator.userAgent.toLowerCase().indexOf(\"firefox\") > 0 : false;\nfunction addEvent(object, event, method, useCapture) {\n  if (object.addEventListener) {\n    object.addEventListener(event, method, useCapture);\n  } else if (object.attachEvent) {\n    object.attachEvent(`on${event}`, method);\n  }\n}\nfunction removeEvent(object, event, method, useCapture) {\n  if (!object) return;\n  if (object.removeEventListener) {\n    object.removeEventListener(event, method, useCapture);\n  } else if (object.detachEvent) {\n    object.detachEvent(`on${event}`, method);\n  }\n}\nfunction getMods(modifier, key) {\n  const modsKeys = key.slice(0, key.length - 1);\n  const modsCodes = [];\n  for (let i = 0; i < modsKeys.length; i++) {\n    modsCodes.push(modifier[modsKeys[i].toLowerCase()]);\n  }\n  return modsCodes;\n}\nfunction getKeys(key) {\n  if (typeof key !== \"string\") key = \"\";\n  key = key.replace(/\\s/g, \"\");\n  const keys = key.split(\",\");\n  let index = keys.lastIndexOf(\"\");\n  for (; index >= 0; ) {\n    keys[index - 1] += \",\";\n    keys.splice(index, 1);\n    index = keys.lastIndexOf(\"\");\n  }\n  return keys;\n}\nfunction compareArray(a1, a2) {\n  const arr1 = a1.length >= a2.length ? a1 : a2;\n  const arr2 = a1.length >= a2.length ? a2 : a1;\n  let isIndex = true;\n  for (let i = 0; i < arr1.length; i++) {\n    if (arr2.indexOf(arr1[i]) === -1) isIndex = false;\n  }\n  return isIndex;\n}\nfunction getLayoutIndependentKeyCode(event) {\n  let key = event.keyCode || event.which || event.charCode;\n  if (event.code && /^Key[A-Z]$/.test(event.code)) {\n    key = event.code.charCodeAt(3);\n  }\n  return key;\n}\nconst _keyMap = {\n  backspace: 8,\n  \"⌫\": 8,\n  tab: 9,\n  clear: 12,\n  enter: 13,\n  \"↩\": 13,\n  return: 13,\n  esc: 27,\n  escape: 27,\n  space: 32,\n  left: 37,\n  up: 38,\n  right: 39,\n  down: 40,\n  /// https://w3c.github.io/uievents/#events-keyboard-key-location\n  arrowup: 38,\n  arrowdown: 40,\n  arrowleft: 37,\n  arrowright: 39,\n  del: 46,\n  delete: 46,\n  ins: 45,\n  insert: 45,\n  home: 36,\n  end: 35,\n  pageup: 33,\n  pagedown: 34,\n  capslock: 20,\n  num_0: 96,\n  num_1: 97,\n  num_2: 98,\n  num_3: 99,\n  num_4: 100,\n  num_5: 101,\n  num_6: 102,\n  num_7: 103,\n  num_8: 104,\n  num_9: 105,\n  num_multiply: 106,\n  num_add: 107,\n  num_enter: 108,\n  num_subtract: 109,\n  num_decimal: 110,\n  num_divide: 111,\n  \"⇪\": 20,\n  \",\": 188,\n  \".\": 190,\n  \"/\": 191,\n  \"`\": 192,\n  \"-\": isff ? 173 : 189,\n  \"=\": isff ? 61 : 187,\n  \";\": isff ? 59 : 186,\n  \"'\": 222,\n  \"{\": 219,\n  \"}\": 221,\n  \"[\": 219,\n  \"]\": 221,\n  \"\\\\\": 220\n};\nconst _modifier = {\n  // shiftKey\n  \"⇧\": 16,\n  shift: 16,\n  // altKey\n  \"⌥\": 18,\n  alt: 18,\n  option: 18,\n  // ctrlKey\n  \"⌃\": 17,\n  ctrl: 17,\n  control: 17,\n  // metaKey\n  \"⌘\": 91,\n  cmd: 91,\n  meta: 91,\n  command: 91\n};\nconst modifierMap = {\n  16: \"shiftKey\",\n  18: \"altKey\",\n  17: \"ctrlKey\",\n  91: \"metaKey\",\n  shiftKey: 16,\n  ctrlKey: 17,\n  altKey: 18,\n  metaKey: 91\n};\nconst _mods = {\n  16: false,\n  18: false,\n  17: false,\n  91: false\n};\nconst _handlers = {};\nfor (let k = 1; k < 20; k++) {\n  _keyMap[`f${k}`] = 111 + k;\n}\nlet _downKeys = [];\nlet winListendFocus = null;\nlet winListendFullscreen = null;\nlet _scope = \"all\";\nconst elementEventMap = /* @__PURE__ */ new Map();\nconst code = (x) => _keyMap[x.toLowerCase()] || _modifier[x.toLowerCase()] || x.toUpperCase().charCodeAt(0);\nconst getKey = (x) => Object.keys(_keyMap).find((k) => _keyMap[k] === x);\nconst getModifier = (x) => Object.keys(_modifier).find((k) => _modifier[k] === x);\nconst setScope = (scope) => {\n  _scope = scope || \"all\";\n};\nconst getScope = () => {\n  return _scope || \"all\";\n};\nconst getPressedKeyCodes = () => {\n  return _downKeys.slice(0);\n};\nconst getPressedKeyString = () => {\n  return _downKeys.map(\n    (c) => getKey(c) || getModifier(c) || String.fromCharCode(c)\n  );\n};\nconst getAllKeyCodes = () => {\n  const result = [];\n  Object.keys(_handlers).forEach((k) => {\n    _handlers[k].forEach(({ key, scope, mods, shortcut }) => {\n      result.push({\n        scope,\n        shortcut,\n        mods,\n        keys: key.split(\"+\").map((v) => code(v))\n      });\n    });\n  });\n  return result;\n};\nconst filter = (event) => {\n  const target = event.target || event.srcElement;\n  const { tagName } = target;\n  let flag = true;\n  const isInput = tagName === \"INPUT\" && ![\n    \"checkbox\",\n    \"radio\",\n    \"range\",\n    \"button\",\n    \"file\",\n    \"reset\",\n    \"submit\",\n    \"color\"\n  ].includes(target.type);\n  if (target.isContentEditable || (isInput || tagName === \"TEXTAREA\" || tagName === \"SELECT\") && !target.readOnly) {\n    flag = false;\n  }\n  return flag;\n};\nconst isPressed = (keyCode) => {\n  if (typeof keyCode === \"string\") {\n    keyCode = code(keyCode);\n  }\n  return _downKeys.indexOf(keyCode) !== -1;\n};\nconst deleteScope = (scope, newScope) => {\n  let handlers;\n  let i;\n  if (!scope) scope = getScope();\n  for (const key in _handlers) {\n    if (Object.prototype.hasOwnProperty.call(_handlers, key)) {\n      handlers = _handlers[key];\n      for (i = 0; i < handlers.length; ) {\n        if (handlers[i].scope === scope) {\n          const deleteItems = handlers.splice(i, 1);\n          deleteItems.forEach(({ element }) => removeKeyEvent(element));\n        } else {\n          i++;\n        }\n      }\n    }\n  }\n  if (getScope() === scope) setScope(newScope || \"all\");\n};\nfunction clearModifier(event) {\n  let key = getLayoutIndependentKeyCode(event);\n  if (event.key && event.key.toLowerCase() === \"capslock\") {\n    key = code(event.key);\n  }\n  const i = _downKeys.indexOf(key);\n  if (i >= 0) {\n    _downKeys.splice(i, 1);\n  }\n  if (event.key && event.key.toLowerCase() === \"meta\") {\n    _downKeys.splice(0, _downKeys.length);\n  }\n  if (key === 93 || key === 224) key = 91;\n  if (key in _mods) {\n    _mods[key] = false;\n    for (const k in _modifier)\n      if (_modifier[k] === key) hotkeys[k] = false;\n  }\n}\nconst unbind = (keysInfo, ...args) => {\n  if (typeof keysInfo === \"undefined\") {\n    Object.keys(_handlers).forEach((key) => {\n      if (Array.isArray(_handlers[key])) {\n        _handlers[key].forEach((info) => eachUnbind(info));\n      }\n      delete _handlers[key];\n    });\n    removeKeyEvent(null);\n  } else if (Array.isArray(keysInfo)) {\n    keysInfo.forEach((info) => {\n      if (info.key) eachUnbind(info);\n    });\n  } else if (typeof keysInfo === \"object\") {\n    if (keysInfo.key) eachUnbind(keysInfo);\n  } else if (typeof keysInfo === \"string\") {\n    let [scope, method] = args;\n    if (typeof scope === \"function\") {\n      method = scope;\n      scope = \"\";\n    }\n    eachUnbind({\n      key: keysInfo,\n      scope,\n      method,\n      splitKey: \"+\"\n    });\n  }\n};\nconst eachUnbind = ({\n  key,\n  scope,\n  method,\n  splitKey = \"+\"\n}) => {\n  const multipleKeys = getKeys(key);\n  multipleKeys.forEach((originKey) => {\n    const unbindKeys = originKey.split(splitKey);\n    const len = unbindKeys.length;\n    const lastKey = unbindKeys[len - 1];\n    const keyCode = lastKey === \"*\" ? \"*\" : code(lastKey);\n    if (!_handlers[keyCode]) return;\n    if (!scope) scope = getScope();\n    const mods = len > 1 ? getMods(_modifier, unbindKeys) : [];\n    const unbindElements = [];\n    _handlers[keyCode] = _handlers[keyCode].filter((record) => {\n      const isMatchingMethod = method ? record.method === method : true;\n      const isUnbind = isMatchingMethod && record.scope === scope && compareArray(record.mods, mods);\n      if (isUnbind) unbindElements.push(record.element);\n      return !isUnbind;\n    });\n    unbindElements.forEach((element) => removeKeyEvent(element));\n  });\n};\nfunction eventHandler(event, handler, scope, element) {\n  if (handler.element !== element) {\n    return;\n  }\n  let modifiersMatch;\n  if (handler.scope === scope || handler.scope === \"all\") {\n    modifiersMatch = handler.mods.length > 0;\n    for (const y in _mods) {\n      if (Object.prototype.hasOwnProperty.call(_mods, y)) {\n        if (!_mods[y] && handler.mods.indexOf(+y) > -1 || _mods[y] && handler.mods.indexOf(+y) === -1) {\n          modifiersMatch = false;\n        }\n      }\n    }\n    if (handler.mods.length === 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91] || modifiersMatch || handler.shortcut === \"*\") {\n      handler.keys = [];\n      handler.keys = handler.keys.concat(_downKeys);\n      if (handler.method(event, handler) === false) {\n        if (event.preventDefault) event.preventDefault();\n        else event.returnValue = false;\n        if (event.stopPropagation) event.stopPropagation();\n        if (event.cancelBubble) event.cancelBubble = true;\n      }\n    }\n  }\n}\nfunction dispatch(event, element) {\n  const asterisk = _handlers[\"*\"];\n  let key = getLayoutIndependentKeyCode(event);\n  if (event.key && event.key.toLowerCase() === \"capslock\") {\n    return;\n  }\n  const filterFn = hotkeys.filter || filter;\n  if (!filterFn.call(this, event)) return;\n  if (key === 93 || key === 224) key = 91;\n  if (_downKeys.indexOf(key) === -1 && key !== 229) _downKeys.push(key);\n  [\"metaKey\", \"ctrlKey\", \"altKey\", \"shiftKey\"].forEach((keyName) => {\n    const keyNum = modifierMap[keyName];\n    if (event[keyName] && _downKeys.indexOf(keyNum) === -1) {\n      _downKeys.push(keyNum);\n    } else if (!event[keyName] && _downKeys.indexOf(keyNum) > -1) {\n      _downKeys.splice(_downKeys.indexOf(keyNum), 1);\n    } else if (keyName === \"metaKey\" && event[keyName]) {\n      _downKeys = _downKeys.filter((k) => k in modifierMap || k === key);\n    }\n  });\n  if (key in _mods) {\n    _mods[key] = true;\n    for (const k in _modifier) {\n      if (Object.prototype.hasOwnProperty.call(_modifier, k)) {\n        const eventKey = modifierMap[_modifier[k]];\n        hotkeys[k] = event[eventKey];\n      }\n    }\n    if (!asterisk) return;\n  }\n  for (const e in _mods) {\n    if (Object.prototype.hasOwnProperty.call(_mods, e)) {\n      _mods[e] = event[modifierMap[e]];\n    }\n  }\n  if (event.getModifierState && !(event.altKey && !event.ctrlKey) && event.getModifierState(\"AltGraph\")) {\n    if (_downKeys.indexOf(17) === -1) {\n      _downKeys.push(17);\n    }\n    if (_downKeys.indexOf(18) === -1) {\n      _downKeys.push(18);\n    }\n    _mods[17] = true;\n    _mods[18] = true;\n  }\n  const scope = getScope();\n  if (asterisk) {\n    for (let i = 0; i < asterisk.length; i++) {\n      if (asterisk[i].scope === scope && (event.type === \"keydown\" && asterisk[i].keydown || event.type === \"keyup\" && asterisk[i].keyup)) {\n        eventHandler(event, asterisk[i], scope, element);\n      }\n    }\n  }\n  if (!(key in _handlers)) return;\n  const handlerKey = _handlers[key];\n  const keyLen = handlerKey.length;\n  for (let i = 0; i < keyLen; i++) {\n    if (event.type === \"keydown\" && handlerKey[i].keydown || event.type === \"keyup\" && handlerKey[i].keyup) {\n      if (handlerKey[i].key) {\n        const record = handlerKey[i];\n        const { splitKey } = record;\n        const keyShortcut = record.key.split(splitKey);\n        const _downKeysCurrent = [];\n        for (let a = 0; a < keyShortcut.length; a++) {\n          _downKeysCurrent.push(code(keyShortcut[a]));\n        }\n        if (_downKeysCurrent.sort().join(\"\") === _downKeys.sort().join(\"\")) {\n          eventHandler(event, record, scope, element);\n        }\n      }\n    }\n  }\n}\nconst hotkeys = function hotkeys2(key, option, method) {\n  _downKeys = [];\n  const keys = getKeys(key);\n  let mods = [];\n  let scope = \"all\";\n  let element = document;\n  let i = 0;\n  let keyup = false;\n  let keydown = true;\n  let splitKey = \"+\";\n  let capture = false;\n  let single = false;\n  if (method === void 0 && typeof option === \"function\") {\n    method = option;\n  }\n  if (Object.prototype.toString.call(option) === \"[object Object]\") {\n    const opts = option;\n    if (opts.scope) scope = opts.scope;\n    if (opts.element) element = opts.element;\n    if (opts.keyup) keyup = opts.keyup;\n    if (opts.keydown !== void 0) keydown = opts.keydown;\n    if (opts.capture !== void 0) capture = opts.capture;\n    if (typeof opts.splitKey === \"string\") splitKey = opts.splitKey;\n    if (opts.single === true) single = true;\n  }\n  if (typeof option === \"string\") scope = option;\n  if (single) unbind(key, scope);\n  for (; i < keys.length; i++) {\n    const currentKey = keys[i].split(splitKey);\n    mods = [];\n    if (currentKey.length > 1) mods = getMods(_modifier, currentKey);\n    let finalKey = currentKey[currentKey.length - 1];\n    finalKey = finalKey === \"*\" ? \"*\" : code(finalKey);\n    if (!(finalKey in _handlers)) _handlers[finalKey] = [];\n    _handlers[finalKey].push({\n      keyup,\n      keydown,\n      scope,\n      mods,\n      shortcut: keys[i],\n      method,\n      key: keys[i],\n      splitKey,\n      element\n    });\n  }\n  if (typeof element !== \"undefined\" && typeof window !== \"undefined\") {\n    if (!elementEventMap.has(element)) {\n      const keydownListener = (event = window.event) => dispatch(event, element);\n      const keyupListenr = (event = window.event) => {\n        dispatch(event, element);\n        clearModifier(event);\n      };\n      elementEventMap.set(element, { keydownListener, keyupListenr, capture });\n      addEvent(element, \"keydown\", keydownListener, capture);\n      addEvent(element, \"keyup\", keyupListenr, capture);\n    }\n    if (!winListendFocus) {\n      const listener = () => {\n        _downKeys = [];\n      };\n      winListendFocus = { listener, capture };\n      addEvent(window, \"focus\", listener, capture);\n    }\n    if (!winListendFullscreen && typeof document !== \"undefined\") {\n      const onFullscreenChange = () => {\n        _downKeys = [];\n        for (const k in _mods) _mods[k] = false;\n        for (const k in _modifier) hotkeys2[k] = false;\n      };\n      const fullscreenListener = onFullscreenChange;\n      const webkitListener = onFullscreenChange;\n      document.addEventListener(\"fullscreenchange\", fullscreenListener);\n      document.addEventListener(\"webkitfullscreenchange\", webkitListener);\n      winListendFullscreen = { fullscreen: fullscreenListener, webkit: webkitListener };\n    }\n  }\n};\nfunction trigger(shortcut, scope = \"all\") {\n  Object.keys(_handlers).forEach((key) => {\n    const dataList = _handlers[key].filter(\n      (item) => item.scope === scope && item.shortcut === shortcut\n    );\n    dataList.forEach((data) => {\n      if (data && data.method) {\n        data.method({}, data);\n      }\n    });\n  });\n}\nfunction removeKeyEvent(element) {\n  const values = Object.values(_handlers).flat();\n  const findindex = values.findIndex(({ element: el }) => el === element);\n  if (findindex < 0 && element) {\n    const { keydownListener, keyupListenr, capture } = elementEventMap.get(element) || {};\n    if (keydownListener && keyupListenr) {\n      removeEvent(element, \"keyup\", keyupListenr, capture);\n      removeEvent(element, \"keydown\", keydownListener, capture);\n      elementEventMap.delete(element);\n    }\n  }\n  if (values.length <= 0 || elementEventMap.size <= 0) {\n    const eventKeys = Array.from(elementEventMap.keys());\n    eventKeys.forEach((el) => {\n      const { keydownListener, keyupListenr, capture } = elementEventMap.get(el) || {};\n      if (keydownListener && keyupListenr) {\n        removeEvent(el, \"keyup\", keyupListenr, capture);\n        removeEvent(el, \"keydown\", keydownListener, capture);\n        elementEventMap.delete(el);\n      }\n    });\n    elementEventMap.clear();\n    Object.keys(_handlers).forEach((key) => delete _handlers[key]);\n    if (winListendFocus) {\n      const { listener, capture } = winListendFocus;\n      removeEvent(window, \"focus\", listener, capture);\n      winListendFocus = null;\n    }\n    if (winListendFullscreen && typeof document !== \"undefined\") {\n      document.removeEventListener(\"fullscreenchange\", winListendFullscreen.fullscreen);\n      document.removeEventListener(\"webkitfullscreenchange\", winListendFullscreen.webkit);\n      winListendFullscreen = null;\n    }\n  }\n}\nconst _api = {\n  getPressedKeyString,\n  setScope,\n  getScope,\n  deleteScope,\n  getPressedKeyCodes,\n  getAllKeyCodes,\n  isPressed,\n  filter,\n  trigger,\n  unbind,\n  keyMap: _keyMap,\n  modifier: _modifier,\n  modifierMap\n};\nfor (const a in _api) {\n  const key = a;\n  if (Object.prototype.hasOwnProperty.call(_api, key)) {\n    hotkeys[key] = _api[key];\n  }\n}\nif (typeof window !== \"undefined\") {\n  const _hotkeys = window.hotkeys;\n  hotkeys.noConflict = (deep) => {\n    if (deep && window.hotkeys === hotkeys) {\n      window.hotkeys = _hotkeys;\n    }\n    return hotkeys;\n  };\n  window.hotkeys = hotkeys;\n}\nif (typeof module !== \"undefined\" && module.exports) {\n  module.exports = hotkeys;\n  module.exports.default = hotkeys;\n}\nexport {\n  hotkeys as default\n};\n//# sourceMappingURL=hotkeys-js.js.map\n"
  },
  {
    "path": "dist/hotkeys-js.umd.cjs",
    "content": "(function(global, factory) {\n  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());\n})(this, function() {\n  \"use strict\";/*!\n * hotkeys-js v4.0.2\n * A simple micro-library for defining and dispatching keyboard shortcuts. It has no dependencies.\n * \n * @author kenny wong <wowohoo@qq.com>\n * @license MIT\n * @homepage https://jaywcjlove.github.io/hotkeys-js\n */\n\n  const isff = typeof navigator !== \"undefined\" ? navigator.userAgent.toLowerCase().indexOf(\"firefox\") > 0 : false;\n  function addEvent(object, event, method, useCapture) {\n    if (object.addEventListener) {\n      object.addEventListener(event, method, useCapture);\n    } else if (object.attachEvent) {\n      object.attachEvent(`on${event}`, method);\n    }\n  }\n  function removeEvent(object, event, method, useCapture) {\n    if (!object) return;\n    if (object.removeEventListener) {\n      object.removeEventListener(event, method, useCapture);\n    } else if (object.detachEvent) {\n      object.detachEvent(`on${event}`, method);\n    }\n  }\n  function getMods(modifier, key) {\n    const modsKeys = key.slice(0, key.length - 1);\n    const modsCodes = [];\n    for (let i = 0; i < modsKeys.length; i++) {\n      modsCodes.push(modifier[modsKeys[i].toLowerCase()]);\n    }\n    return modsCodes;\n  }\n  function getKeys(key) {\n    if (typeof key !== \"string\") key = \"\";\n    key = key.replace(/\\s/g, \"\");\n    const keys = key.split(\",\");\n    let index = keys.lastIndexOf(\"\");\n    for (; index >= 0; ) {\n      keys[index - 1] += \",\";\n      keys.splice(index, 1);\n      index = keys.lastIndexOf(\"\");\n    }\n    return keys;\n  }\n  function compareArray(a1, a2) {\n    const arr1 = a1.length >= a2.length ? a1 : a2;\n    const arr2 = a1.length >= a2.length ? a2 : a1;\n    let isIndex = true;\n    for (let i = 0; i < arr1.length; i++) {\n      if (arr2.indexOf(arr1[i]) === -1) isIndex = false;\n    }\n    return isIndex;\n  }\n  function getLayoutIndependentKeyCode(event) {\n    let key = event.keyCode || event.which || event.charCode;\n    if (event.code && /^Key[A-Z]$/.test(event.code)) {\n      key = event.code.charCodeAt(3);\n    }\n    return key;\n  }\n  const _keyMap = {\n    backspace: 8,\n    \"⌫\": 8,\n    tab: 9,\n    clear: 12,\n    enter: 13,\n    \"↩\": 13,\n    return: 13,\n    esc: 27,\n    escape: 27,\n    space: 32,\n    left: 37,\n    up: 38,\n    right: 39,\n    down: 40,\n    /// https://w3c.github.io/uievents/#events-keyboard-key-location\n    arrowup: 38,\n    arrowdown: 40,\n    arrowleft: 37,\n    arrowright: 39,\n    del: 46,\n    delete: 46,\n    ins: 45,\n    insert: 45,\n    home: 36,\n    end: 35,\n    pageup: 33,\n    pagedown: 34,\n    capslock: 20,\n    num_0: 96,\n    num_1: 97,\n    num_2: 98,\n    num_3: 99,\n    num_4: 100,\n    num_5: 101,\n    num_6: 102,\n    num_7: 103,\n    num_8: 104,\n    num_9: 105,\n    num_multiply: 106,\n    num_add: 107,\n    num_enter: 108,\n    num_subtract: 109,\n    num_decimal: 110,\n    num_divide: 111,\n    \"⇪\": 20,\n    \",\": 188,\n    \".\": 190,\n    \"/\": 191,\n    \"`\": 192,\n    \"-\": isff ? 173 : 189,\n    \"=\": isff ? 61 : 187,\n    \";\": isff ? 59 : 186,\n    \"'\": 222,\n    \"{\": 219,\n    \"}\": 221,\n    \"[\": 219,\n    \"]\": 221,\n    \"\\\\\": 220\n  };\n  const _modifier = {\n    // shiftKey\n    \"⇧\": 16,\n    shift: 16,\n    // altKey\n    \"⌥\": 18,\n    alt: 18,\n    option: 18,\n    // ctrlKey\n    \"⌃\": 17,\n    ctrl: 17,\n    control: 17,\n    // metaKey\n    \"⌘\": 91,\n    cmd: 91,\n    meta: 91,\n    command: 91\n  };\n  const modifierMap = {\n    16: \"shiftKey\",\n    18: \"altKey\",\n    17: \"ctrlKey\",\n    91: \"metaKey\",\n    shiftKey: 16,\n    ctrlKey: 17,\n    altKey: 18,\n    metaKey: 91\n  };\n  const _mods = {\n    16: false,\n    18: false,\n    17: false,\n    91: false\n  };\n  const _handlers = {};\n  for (let k = 1; k < 20; k++) {\n    _keyMap[`f${k}`] = 111 + k;\n  }\n  let _downKeys = [];\n  let winListendFocus = null;\n  let winListendFullscreen = null;\n  let _scope = \"all\";\n  const elementEventMap = /* @__PURE__ */ new Map();\n  const code = (x) => _keyMap[x.toLowerCase()] || _modifier[x.toLowerCase()] || x.toUpperCase().charCodeAt(0);\n  const getKey = (x) => Object.keys(_keyMap).find((k) => _keyMap[k] === x);\n  const getModifier = (x) => Object.keys(_modifier).find((k) => _modifier[k] === x);\n  const setScope = (scope) => {\n    _scope = scope || \"all\";\n  };\n  const getScope = () => {\n    return _scope || \"all\";\n  };\n  const getPressedKeyCodes = () => {\n    return _downKeys.slice(0);\n  };\n  const getPressedKeyString = () => {\n    return _downKeys.map(\n      (c) => getKey(c) || getModifier(c) || String.fromCharCode(c)\n    );\n  };\n  const getAllKeyCodes = () => {\n    const result = [];\n    Object.keys(_handlers).forEach((k) => {\n      _handlers[k].forEach(({ key, scope, mods, shortcut }) => {\n        result.push({\n          scope,\n          shortcut,\n          mods,\n          keys: key.split(\"+\").map((v) => code(v))\n        });\n      });\n    });\n    return result;\n  };\n  const filter = (event) => {\n    const target = event.target || event.srcElement;\n    const { tagName } = target;\n    let flag = true;\n    const isInput = tagName === \"INPUT\" && ![\n      \"checkbox\",\n      \"radio\",\n      \"range\",\n      \"button\",\n      \"file\",\n      \"reset\",\n      \"submit\",\n      \"color\"\n    ].includes(target.type);\n    if (target.isContentEditable || (isInput || tagName === \"TEXTAREA\" || tagName === \"SELECT\") && !target.readOnly) {\n      flag = false;\n    }\n    return flag;\n  };\n  const isPressed = (keyCode) => {\n    if (typeof keyCode === \"string\") {\n      keyCode = code(keyCode);\n    }\n    return _downKeys.indexOf(keyCode) !== -1;\n  };\n  const deleteScope = (scope, newScope) => {\n    let handlers;\n    let i;\n    if (!scope) scope = getScope();\n    for (const key in _handlers) {\n      if (Object.prototype.hasOwnProperty.call(_handlers, key)) {\n        handlers = _handlers[key];\n        for (i = 0; i < handlers.length; ) {\n          if (handlers[i].scope === scope) {\n            const deleteItems = handlers.splice(i, 1);\n            deleteItems.forEach(({ element }) => removeKeyEvent(element));\n          } else {\n            i++;\n          }\n        }\n      }\n    }\n    if (getScope() === scope) setScope(newScope || \"all\");\n  };\n  function clearModifier(event) {\n    let key = getLayoutIndependentKeyCode(event);\n    if (event.key && event.key.toLowerCase() === \"capslock\") {\n      key = code(event.key);\n    }\n    const i = _downKeys.indexOf(key);\n    if (i >= 0) {\n      _downKeys.splice(i, 1);\n    }\n    if (event.key && event.key.toLowerCase() === \"meta\") {\n      _downKeys.splice(0, _downKeys.length);\n    }\n    if (key === 93 || key === 224) key = 91;\n    if (key in _mods) {\n      _mods[key] = false;\n      for (const k in _modifier)\n        if (_modifier[k] === key) hotkeys2[k] = false;\n    }\n  }\n  const unbind = (keysInfo, ...args) => {\n    if (typeof keysInfo === \"undefined\") {\n      Object.keys(_handlers).forEach((key) => {\n        if (Array.isArray(_handlers[key])) {\n          _handlers[key].forEach((info) => eachUnbind(info));\n        }\n        delete _handlers[key];\n      });\n      removeKeyEvent(null);\n    } else if (Array.isArray(keysInfo)) {\n      keysInfo.forEach((info) => {\n        if (info.key) eachUnbind(info);\n      });\n    } else if (typeof keysInfo === \"object\") {\n      if (keysInfo.key) eachUnbind(keysInfo);\n    } else if (typeof keysInfo === \"string\") {\n      let [scope, method] = args;\n      if (typeof scope === \"function\") {\n        method = scope;\n        scope = \"\";\n      }\n      eachUnbind({\n        key: keysInfo,\n        scope,\n        method,\n        splitKey: \"+\"\n      });\n    }\n  };\n  const eachUnbind = ({\n    key,\n    scope,\n    method,\n    splitKey = \"+\"\n  }) => {\n    const multipleKeys = getKeys(key);\n    multipleKeys.forEach((originKey) => {\n      const unbindKeys = originKey.split(splitKey);\n      const len = unbindKeys.length;\n      const lastKey = unbindKeys[len - 1];\n      const keyCode = lastKey === \"*\" ? \"*\" : code(lastKey);\n      if (!_handlers[keyCode]) return;\n      if (!scope) scope = getScope();\n      const mods = len > 1 ? getMods(_modifier, unbindKeys) : [];\n      const unbindElements = [];\n      _handlers[keyCode] = _handlers[keyCode].filter((record) => {\n        const isMatchingMethod = method ? record.method === method : true;\n        const isUnbind = isMatchingMethod && record.scope === scope && compareArray(record.mods, mods);\n        if (isUnbind) unbindElements.push(record.element);\n        return !isUnbind;\n      });\n      unbindElements.forEach((element) => removeKeyEvent(element));\n    });\n  };\n  function eventHandler(event, handler, scope, element) {\n    if (handler.element !== element) {\n      return;\n    }\n    let modifiersMatch;\n    if (handler.scope === scope || handler.scope === \"all\") {\n      modifiersMatch = handler.mods.length > 0;\n      for (const y in _mods) {\n        if (Object.prototype.hasOwnProperty.call(_mods, y)) {\n          if (!_mods[y] && handler.mods.indexOf(+y) > -1 || _mods[y] && handler.mods.indexOf(+y) === -1) {\n            modifiersMatch = false;\n          }\n        }\n      }\n      if (handler.mods.length === 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91] || modifiersMatch || handler.shortcut === \"*\") {\n        handler.keys = [];\n        handler.keys = handler.keys.concat(_downKeys);\n        if (handler.method(event, handler) === false) {\n          if (event.preventDefault) event.preventDefault();\n          else event.returnValue = false;\n          if (event.stopPropagation) event.stopPropagation();\n          if (event.cancelBubble) event.cancelBubble = true;\n        }\n      }\n    }\n  }\n  function dispatch(event, element) {\n    const asterisk = _handlers[\"*\"];\n    let key = getLayoutIndependentKeyCode(event);\n    if (event.key && event.key.toLowerCase() === \"capslock\") {\n      return;\n    }\n    const filterFn = hotkeys2.filter || filter;\n    if (!filterFn.call(this, event)) return;\n    if (key === 93 || key === 224) key = 91;\n    if (_downKeys.indexOf(key) === -1 && key !== 229) _downKeys.push(key);\n    [\"metaKey\", \"ctrlKey\", \"altKey\", \"shiftKey\"].forEach((keyName) => {\n      const keyNum = modifierMap[keyName];\n      if (event[keyName] && _downKeys.indexOf(keyNum) === -1) {\n        _downKeys.push(keyNum);\n      } else if (!event[keyName] && _downKeys.indexOf(keyNum) > -1) {\n        _downKeys.splice(_downKeys.indexOf(keyNum), 1);\n      } else if (keyName === \"metaKey\" && event[keyName]) {\n        _downKeys = _downKeys.filter((k) => k in modifierMap || k === key);\n      }\n    });\n    if (key in _mods) {\n      _mods[key] = true;\n      for (const k in _modifier) {\n        if (Object.prototype.hasOwnProperty.call(_modifier, k)) {\n          const eventKey = modifierMap[_modifier[k]];\n          hotkeys2[k] = event[eventKey];\n        }\n      }\n      if (!asterisk) return;\n    }\n    for (const e in _mods) {\n      if (Object.prototype.hasOwnProperty.call(_mods, e)) {\n        _mods[e] = event[modifierMap[e]];\n      }\n    }\n    if (event.getModifierState && !(event.altKey && !event.ctrlKey) && event.getModifierState(\"AltGraph\")) {\n      if (_downKeys.indexOf(17) === -1) {\n        _downKeys.push(17);\n      }\n      if (_downKeys.indexOf(18) === -1) {\n        _downKeys.push(18);\n      }\n      _mods[17] = true;\n      _mods[18] = true;\n    }\n    const scope = getScope();\n    if (asterisk) {\n      for (let i = 0; i < asterisk.length; i++) {\n        if (asterisk[i].scope === scope && (event.type === \"keydown\" && asterisk[i].keydown || event.type === \"keyup\" && asterisk[i].keyup)) {\n          eventHandler(event, asterisk[i], scope, element);\n        }\n      }\n    }\n    if (!(key in _handlers)) return;\n    const handlerKey = _handlers[key];\n    const keyLen = handlerKey.length;\n    for (let i = 0; i < keyLen; i++) {\n      if (event.type === \"keydown\" && handlerKey[i].keydown || event.type === \"keyup\" && handlerKey[i].keyup) {\n        if (handlerKey[i].key) {\n          const record = handlerKey[i];\n          const { splitKey } = record;\n          const keyShortcut = record.key.split(splitKey);\n          const _downKeysCurrent = [];\n          for (let a = 0; a < keyShortcut.length; a++) {\n            _downKeysCurrent.push(code(keyShortcut[a]));\n          }\n          if (_downKeysCurrent.sort().join(\"\") === _downKeys.sort().join(\"\")) {\n            eventHandler(event, record, scope, element);\n          }\n        }\n      }\n    }\n  }\n  const hotkeys2 = function hotkeys22(key, option, method) {\n    _downKeys = [];\n    const keys = getKeys(key);\n    let mods = [];\n    let scope = \"all\";\n    let element = document;\n    let i = 0;\n    let keyup = false;\n    let keydown = true;\n    let splitKey = \"+\";\n    let capture = false;\n    let single = false;\n    if (method === void 0 && typeof option === \"function\") {\n      method = option;\n    }\n    if (Object.prototype.toString.call(option) === \"[object Object]\") {\n      const opts = option;\n      if (opts.scope) scope = opts.scope;\n      if (opts.element) element = opts.element;\n      if (opts.keyup) keyup = opts.keyup;\n      if (opts.keydown !== void 0) keydown = opts.keydown;\n      if (opts.capture !== void 0) capture = opts.capture;\n      if (typeof opts.splitKey === \"string\") splitKey = opts.splitKey;\n      if (opts.single === true) single = true;\n    }\n    if (typeof option === \"string\") scope = option;\n    if (single) unbind(key, scope);\n    for (; i < keys.length; i++) {\n      const currentKey = keys[i].split(splitKey);\n      mods = [];\n      if (currentKey.length > 1) mods = getMods(_modifier, currentKey);\n      let finalKey = currentKey[currentKey.length - 1];\n      finalKey = finalKey === \"*\" ? \"*\" : code(finalKey);\n      if (!(finalKey in _handlers)) _handlers[finalKey] = [];\n      _handlers[finalKey].push({\n        keyup,\n        keydown,\n        scope,\n        mods,\n        shortcut: keys[i],\n        method,\n        key: keys[i],\n        splitKey,\n        element\n      });\n    }\n    if (typeof element !== \"undefined\" && typeof window !== \"undefined\") {\n      if (!elementEventMap.has(element)) {\n        const keydownListener = (event = window.event) => dispatch(event, element);\n        const keyupListenr = (event = window.event) => {\n          dispatch(event, element);\n          clearModifier(event);\n        };\n        elementEventMap.set(element, { keydownListener, keyupListenr, capture });\n        addEvent(element, \"keydown\", keydownListener, capture);\n        addEvent(element, \"keyup\", keyupListenr, capture);\n      }\n      if (!winListendFocus) {\n        const listener = () => {\n          _downKeys = [];\n        };\n        winListendFocus = { listener, capture };\n        addEvent(window, \"focus\", listener, capture);\n      }\n      if (!winListendFullscreen && typeof document !== \"undefined\") {\n        const onFullscreenChange = () => {\n          _downKeys = [];\n          for (const k in _mods) _mods[k] = false;\n          for (const k in _modifier) hotkeys22[k] = false;\n        };\n        const fullscreenListener = onFullscreenChange;\n        const webkitListener = onFullscreenChange;\n        document.addEventListener(\"fullscreenchange\", fullscreenListener);\n        document.addEventListener(\"webkitfullscreenchange\", webkitListener);\n        winListendFullscreen = { fullscreen: fullscreenListener, webkit: webkitListener };\n      }\n    }\n  };\n  function trigger(shortcut, scope = \"all\") {\n    Object.keys(_handlers).forEach((key) => {\n      const dataList = _handlers[key].filter(\n        (item) => item.scope === scope && item.shortcut === shortcut\n      );\n      dataList.forEach((data) => {\n        if (data && data.method) {\n          data.method({}, data);\n        }\n      });\n    });\n  }\n  function removeKeyEvent(element) {\n    const values = Object.values(_handlers).flat();\n    const findindex = values.findIndex(({ element: el }) => el === element);\n    if (findindex < 0 && element) {\n      const { keydownListener, keyupListenr, capture } = elementEventMap.get(element) || {};\n      if (keydownListener && keyupListenr) {\n        removeEvent(element, \"keyup\", keyupListenr, capture);\n        removeEvent(element, \"keydown\", keydownListener, capture);\n        elementEventMap.delete(element);\n      }\n    }\n    if (values.length <= 0 || elementEventMap.size <= 0) {\n      const eventKeys = Array.from(elementEventMap.keys());\n      eventKeys.forEach((el) => {\n        const { keydownListener, keyupListenr, capture } = elementEventMap.get(el) || {};\n        if (keydownListener && keyupListenr) {\n          removeEvent(el, \"keyup\", keyupListenr, capture);\n          removeEvent(el, \"keydown\", keydownListener, capture);\n          elementEventMap.delete(el);\n        }\n      });\n      elementEventMap.clear();\n      Object.keys(_handlers).forEach((key) => delete _handlers[key]);\n      if (winListendFocus) {\n        const { listener, capture } = winListendFocus;\n        removeEvent(window, \"focus\", listener, capture);\n        winListendFocus = null;\n      }\n      if (winListendFullscreen && typeof document !== \"undefined\") {\n        document.removeEventListener(\"fullscreenchange\", winListendFullscreen.fullscreen);\n        document.removeEventListener(\"webkitfullscreenchange\", winListendFullscreen.webkit);\n        winListendFullscreen = null;\n      }\n    }\n  }\n  const _api = {\n    getPressedKeyString,\n    setScope,\n    getScope,\n    deleteScope,\n    getPressedKeyCodes,\n    getAllKeyCodes,\n    isPressed,\n    filter,\n    trigger,\n    unbind,\n    keyMap: _keyMap,\n    modifier: _modifier,\n    modifierMap\n  };\n  for (const a in _api) {\n    const key = a;\n    if (Object.prototype.hasOwnProperty.call(_api, key)) {\n      hotkeys2[key] = _api[key];\n    }\n  }\n  if (typeof window !== \"undefined\") {\n    const _hotkeys = window.hotkeys;\n    hotkeys2.noConflict = (deep) => {\n      if (deep && window.hotkeys === hotkeys2) {\n        window.hotkeys = _hotkeys;\n      }\n      return hotkeys2;\n    };\n    window.hotkeys = hotkeys2;\n  }\n  if (typeof module !== \"undefined\" && module.exports) {\n    module.exports = hotkeys2;\n    module.exports.default = hotkeys2;\n  }\n  return hotkeys2;\n});\nif (typeof module === \"object\" && module.exports) {\n  module.exports.default = module.exports;\n}\nif (typeof define === \"function\" && define.amd) {\n  define([], function() {\n    return hotkeys;\n  });\n}\n//# sourceMappingURL=hotkeys-js.umd.cjs.map\n"
  },
  {
    "path": "dist/index.d.ts",
    "content": "declare const _default: HotkeysInterface;\r\nexport default _default;\r\n\r\ndeclare type DeleteScope = (scope?: string, newScope?: string) => void;\r\n\r\ndeclare type Filter = (event: KeyboardEvent) => boolean;\r\n\r\ndeclare type GetAllKeyCodes = () => KeyCodeInfo[];\r\n\r\ndeclare type GetPressedKeyCodes = () => number[];\r\n\r\ndeclare type GetPressedKeyString = () => string[];\r\n\r\ndeclare type GetScope = () => string;\r\n\r\ndeclare interface HotkeysAPI {\r\n    /**\r\n     * Use the `hotkeys.setScope` method to set scope. There can only be one active scope besides 'all'.  By default 'all' is always active.\r\n     *\r\n     * ```js\r\n     * // Define shortcuts with a scope\r\n     * hotkeys('ctrl+o, ctrl+alt+enter', 'issues', function() {\r\n     *   console.log('do something');\r\n     * });\r\n     * hotkeys('o, enter', 'files', function() {\r\n     *   console.log('do something else');\r\n     * });\r\n     *\r\n     * // Set the scope (only 'all' and 'issues' shortcuts will be honored)\r\n     * hotkeys.setScope('issues'); // default scope is 'all'\r\n     * ```\r\n     */\r\n    setScope: SetScope;\r\n    /**\r\n     * Use the `hotkeys.getScope` method to get scope.\r\n     *\r\n     * ```js\r\n     * hotkeys.getScope();\r\n     * ```\r\n     */\r\n    getScope: GetScope;\r\n    /**\r\n     * Use the `hotkeys.deleteScope` method to delete a scope. This will also remove all associated hotkeys with it.\r\n     *\r\n     * ```js\r\n     * hotkeys.deleteScope('issues');\r\n     * ```\r\n     * You can use second argument, if need set new scope after deleting.\r\n     *\r\n     * ```js\r\n     * hotkeys.deleteScope('issues', 'newScopeName');\r\n     * ```\r\n     */\r\n    deleteScope: DeleteScope;\r\n    /**\r\n     * Returns an array of key codes currently pressed.\r\n     *\r\n     * ```js\r\n     * hotkeys('command+ctrl+shift+a,f', function() {\r\n     *   console.log(hotkeys.getPressedKeyCodes()); //=> [17, 65] or [70]\r\n     * })\r\n     * ```\r\n     */\r\n    getPressedKeyCodes: GetPressedKeyCodes;\r\n    /**\r\n     * Returns an array of key codes currently pressed.\r\n     *\r\n     * ```js\r\n     * hotkeys('command+ctrl+shift+a,f', function() {\r\n     *   console.log(hotkeys.getPressedKeyString()); //=> ['⌘', '⌃', '⇧', 'A', 'F']\r\n     * })\r\n     * ```\r\n     */\r\n    getPressedKeyString: GetPressedKeyString;\r\n    /**\r\n     * Get a list of all registration codes.\r\n     *\r\n     * ```js\r\n     * hotkeys('command+ctrl+shift+a,f', function() {\r\n     *   console.log(hotkeys.getAllKeyCodes());\r\n     *   // [\r\n     *   //   { scope: 'all', shortcut: 'command+ctrl+shift+a', mods: [91, 17, 16], keys: [91, 17, 16, 65] },\r\n     *   //   { scope: 'all', shortcut: 'f', mods: [], keys: [42] }\r\n     *   // ]\r\n     * })\r\n     * ```\r\n     *\r\n     */\r\n    getAllKeyCodes: GetAllKeyCodes;\r\n    isPressed: IsPressed;\r\n    /**\r\n     * By default hotkeys are not enabled for `INPUT` `SELECT` `TEXTAREA` elements.\r\n     * `Hotkeys.filter` to return to the `true` shortcut keys set to play a role,\r\n     * `false` shortcut keys set up failure.\r\n     *\r\n     * ```js\r\n     * hotkeys.filter = function(event){\r\n     *   return true;\r\n     * }\r\n     * //How to add the filter to edit labels. <div contentEditable=\"true\"></div>\r\n     * //\"contentEditable\" Older browsers that do not support drops\r\n     * hotkeys.filter = function(event) {\r\n     *   var target = event.target || event.srcElement;\r\n     *   var tagName = target.tagName;\r\n     *   return !(target.isContentEditable || tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');\r\n     * }\r\n     *\r\n     * hotkeys.filter = function(event){\r\n     *   var tagName = (event.target || event.srcElement).tagName;\r\n     *   hotkeys.setScope(/^(INPUT|TEXTAREA|SELECT)$/.test(tagName) ? 'input' : 'other');\r\n     *   return true;\r\n     * }\r\n     * ```\r\n     */\r\n    filter: Filter;\r\n    /**\r\n     * trigger shortcut key event\r\n     *\r\n     * ```js\r\n     * hotkeys.trigger('ctrl+o');\r\n     * hotkeys.trigger('ctrl+o', 'scope2');\r\n     * ```\r\n     */\r\n    trigger: Trigger;\r\n    /**\r\n     * Unbinds a shortcut key event.\r\n     *\r\n     * ```js\r\n     * hotkeys.unbind('ctrl+o');\r\n     * hotkeys.unbind('ctrl+o', 'scope1');\r\n     * hotkeys.unbind('ctrl+o', 'scope1', method);\r\n     * hotkeys.unbind('ctrl+o', method);\r\n     * ```\r\n     */\r\n    unbind: Unbind;\r\n    /**\r\n     * Relinquish HotKeys’s control of the `hotkeys` variable.\r\n     *\r\n     * ```js\r\n     * var k = hotkeys.noConflict();\r\n     * k('a', function() {\r\n     *   console.log(\"do something\")\r\n     * });\r\n     *\r\n     * hotkeys()\r\n     * // -->Uncaught TypeError: hotkeys is not a function(anonymous function)\r\n     * // @ VM2170:2InjectedScript._evaluateOn\r\n     * // @ VM2165:883InjectedScript._evaluateAndWrap\r\n     * // @ VM2165:816InjectedScript.evaluate @ VM2165:682\r\n     * ```\r\n     */\r\n    noConflict: NoConflict;\r\n    keyMap: Record<string, number>;\r\n    modifier: Record<string, number>;\r\n    modifierMap: Record<string | number, number | string>;\r\n}\r\n\r\nexport declare interface HotkeysEvent {\r\n    keyup: boolean;\r\n    keydown: boolean;\r\n    scope: string;\r\n    mods: number[];\r\n    shortcut: string;\r\n    method: KeyHandler;\r\n    key: string;\r\n    splitKey: string;\r\n    element: HTMLElement | Document;\r\n    keys?: number[];\r\n}\r\n\r\ndeclare interface HotkeysInterface extends HotkeysAPI {\r\n    (key: string, method: KeyHandler): void;\r\n    (key: string, scope: string, method: KeyHandler): void;\r\n    (key: string, option: HotkeysOptions, method: KeyHandler): void;\r\n    shift?: boolean;\r\n    ctrl?: boolean;\r\n    alt?: boolean;\r\n    option?: boolean;\r\n    control?: boolean;\r\n    cmd?: boolean;\r\n    command?: boolean;\r\n}\r\n\r\ndeclare interface HotkeysOptions {\r\n    scope?: string;\r\n    element?: HTMLElement | Document;\r\n    keyup?: boolean;\r\n    keydown?: boolean;\r\n    capture?: boolean;\r\n    splitKey?: string;\r\n    single?: boolean;\r\n}\r\n\r\ndeclare interface IsPressed {\r\n    /** For example, `hotkeys.isPressed(77)` is true if the `M` key is currently pressed. */\r\n    (keyCode: number): boolean;\r\n    /** For example, `hotkeys.isPressed('m')` is true if the `M` key is currently pressed. */\r\n    (keyCode: string): boolean;\r\n}\r\n\r\ndeclare interface KeyCodeInfo {\r\n    scope: string;\r\n    shortcut: string;\r\n    mods: number[];\r\n    keys: number[];\r\n}\r\n\r\nexport declare interface KeyHandler {\r\n    (keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent): void | boolean;\r\n}\r\n\r\ndeclare type NoConflict = (deep?: boolean) => HotkeysInterface;\r\n\r\ndeclare type SetScope = (scope: string) => void;\r\n\r\ndeclare type Trigger = (shortcut: string, scope?: string) => void;\r\n\r\ndeclare interface Unbind {\r\n    (key?: string): void;\r\n    (keysInfo: UnbindInfo): void;\r\n    (keysInfo: UnbindInfo[]): void;\r\n    (key: string, scopeName: string): void;\r\n    (key: string, scopeName: string, method: KeyHandler): void;\r\n    (key: string, method: KeyHandler): void;\r\n}\r\n\r\ndeclare interface UnbindInfo {\r\n    key: string;\r\n    scope?: string;\r\n    method?: KeyHandler;\r\n    splitKey?: string;\r\n}\r\n\r\nexport { }\r\n"
  },
  {
    "path": "eslint.config.js",
    "content": "// @ts-check\nimport { defineConfig } from 'eslint/config';\nimport eslint from '@eslint/js';\nimport tseslint from 'typescript-eslint';\nimport reactPlugin from 'eslint-plugin-react';\nimport reactHooksPlugin from 'eslint-plugin-react-hooks';\nimport importPlugin from 'eslint-plugin-import';\nimport jsxA11yPlugin from 'eslint-plugin-jsx-a11y';\nimport globals from 'globals';\n\nexport default defineConfig([\n  eslint.configs.recommended,\n  ...tseslint.configs.recommended,\n  {\n    files: ['**/*.{js,jsx,ts,tsx}'],\n    languageOptions: {\n      ecmaVersion: 2020,\n      sourceType: 'module',\n      parserOptions: {\n        ecmaFeatures: {\n          jsx: true,\n        },\n      },\n      globals: {\n        ...globals.browser,\n        ...globals.node,\n        ...globals.es2020,\n        ...globals.jest,\n      },\n    },\n    plugins: {\n      react: reactPlugin,\n      'react-hooks': reactHooksPlugin,\n      import: importPlugin,\n      'jsx-a11y': jsxA11yPlugin,\n    },\n    settings: {\n      react: {\n        version: 'detect',\n      },\n    },\n    rules: {\n      // TypeScript rules\n      '@typescript-eslint/no-explicit-any': 'off',\n      '@typescript-eslint/explicit-module-boundary-types': 'off',\n      '@typescript-eslint/no-inferrable-types': 'off',\n      '@typescript-eslint/no-non-null-assertion': 'off',\n      '@typescript-eslint/no-unused-expressions': 'error',\n\n      // General rules\n      'no-console': ['error', { allow: ['log'] }],\n      'no-underscore-dangle': 'off',\n      'no-plusplus': 'off',\n      'no-param-reassign': 'off',\n      'no-restricted-syntax': 'off',\n      'no-use-before-define': 'off',\n      'max-len': 'off',\n      'comma-dangle': 'off',\n      'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }],\n      'quotes': ['error', 'single', { avoidEscape: true, allowTemplateLiterals: true }],\n      'indent': ['error', 2, { SwitchCase: 1 }],\n      'linebreak-style': ['error', 'unix'],\n      'no-trailing-spaces': 'error',\n      'eol-last': ['error', 'always'],\n      'object-curly-newline': 'off',\n      'arrow-body-style': 'off',\n      'consistent-return': 'off',\n      'generator-star-spacing': 'off',\n      'global-require': 'warn',\n      'no-bitwise': 'off',\n      'no-cond-assign': 'off',\n      'no-else-return': 'off',\n      'no-nested-ternary': 'off',\n      'require-yield': 'warn',\n      'class-methods-use-this': 'off',\n      'no-confusing-arrow': 'off',\n      'no-unused-expressions': 'off',\n\n      // React rules\n      'react/jsx-filename-extension': [\n        'warn',\n        {\n          extensions: ['.js', '.jsx', '.ts', '.tsx'],\n        },\n      ],\n      'react/jsx-no-bind': 'off',\n      'react/prop-types': 'off',\n      'react/no-array-index-key': 'off',\n      'react/forbid-prop-types': 'off',\n      'react/prefer-stateless-function': 'off',\n      'react/sort-comp': 'off',\n      'react/no-did-mount-set-state': 'off',\n      'react-hooks/rules-of-hooks': 'error',\n      'react-hooks/exhaustive-deps': 'warn',\n\n      // Import rules\n      'import/extensions': 'off',\n      'import/no-unresolved': 'off',\n      'import/no-extraneous-dependencies': 'off',\n      'import/prefer-default-export': 'off',\n\n      // JSX A11y rules\n      'jsx-a11y/no-noninteractive-element-interactions': 'off',\n      'jsx-a11y/no-static-element-interactions': 'off',\n    },\n  },\n  {\n    files: ['**/*.ts', '**/*.tsx'],\n    languageOptions: {\n      parserOptions: {\n        projectService: true,\n        tsconfigRootDir: import.meta.dirname,\n      },\n    },\n  },\n  {\n    ignores: ['dist/**', 'node_modules/**', 'coverage/**', 'build/**', 'doc/**'],\n  },\n]);\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"hotkeys-js\",\n  \"description\": \"A simple micro-library for defining and dispatching keyboard shortcuts. It has no dependencies.\",\n  \"version\": \"4.0.2\",\n  \"type\": \"module\",\n  \"main\": \"dist/hotkeys-js.umd.cjs\",\n  \"module\": \"dist/hotkeys-js.js\",\n  \"browser\": \"dist/hotkeys-js.min.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/hotkeys-js.js\",\n      \"require\": \"./dist/hotkeys-js.umd.cjs\",\n      \"browser\": \"./dist/hotkeys-js.min.js\",\n      \"default\": \"./dist/hotkeys-js.js\"\n    },\n    \"./package.json\": \"./package.json\"\n  },\n  \"scripts\": {\n    \"prepare\": \"npm run build:lib && husky install\",\n    \"lint\": \"eslint src website\",\n    \"type-check\": \"tsc --noEmit\",\n    \"watch\": \"vite build --watch\",\n    \"build:lib\": \"vite build\",\n    \"build\": \"npm run type-check && npm run build:lib && npm run doc && npm run lint\",\n    \"pretest\": \"npm run build\",\n    \"test\": \"jest --coverage --detectOpenHandles\",\n    \"test:watch\": \"jest --watch\",\n    \"doc\": \"vite build --config website/vite.config.ts\",\n    \"start\": \"vite --config website/vite.config.ts\"\n  },\n  \"files\": [\n    \"dist\",\n    \"doc\"\n  ],\n  \"keywords\": [\n    \"hotkey\",\n    \"hotkeys\",\n    \"hotkeys-js\",\n    \"hotkeysjs\",\n    \"key\",\n    \"keys\",\n    \"keyboard\",\n    \"shortcuts\",\n    \"keypress\"\n  ],\n  \"author\": \"kenny wong <wowohoo@qq.com>\",\n  \"license\": \"MIT\",\n  \"homepage\": \"https://jaywcjlove.github.io/hotkeys-js\",\n  \"funding\": \"https://jaywcjlove.github.io/#/sponsor\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/jaywcjlove/hotkeys-js.git\"\n  },\n  \"devDependencies\": {\n    \"@eslint/js\": \"^9.39.1\",\n    \"@rollup/plugin-terser\": \"^0.4.4\",\n    \"@types/node\": \"^24.10.1\",\n    \"@types/react\": \"^19.2.4\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"@uiw/react-github-corners\": \"^1.5.15\",\n    \"@uiw/react-mac-keyboard\": \"^1.1.5\",\n    \"@uiw/react-markdown-preview\": \"^5.0.3\",\n    \"@uiw/react-shields\": \"^2.0.1\",\n    \"@vitejs/plugin-react\": \"^5.1.1\",\n    \"@wcj/dark-mode\": \"~1.1.0\",\n    \"eslint\": \"^9.39.1\",\n    \"eslint-plugin-import\": \"^2.32.0\",\n    \"eslint-plugin-jsx-a11y\": \"^6.10.2\",\n    \"eslint-plugin-react\": \"^7.37.5\",\n    \"eslint-plugin-react-hooks\": \"^5.1.0\",\n    \"globals\": \"^16.5.0\",\n    \"husky\": \"^8.0.3\",\n    \"jest\": \"^29.7.0\",\n    \"jest-environment-jsdom\": \"^29.7.0\",\n    \"lint-staged\": \"^15.2.0\",\n    \"puppeteer\": \"~13.5.2\",\n    \"react\": \"^19.2.0\",\n    \"react-dom\": \"^19.2.0\",\n    \"typescript\": \"^5.8.2\",\n    \"typescript-eslint\": \"^8.46.4\",\n    \"vite\": \"^5.1.0\",\n    \"vite-plugin-dts\": \"^4.5.4\"\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.2%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  },\n  \"lint-staged\": {\n    \"src/**/*.{js,ts}\": \"eslint\",\n    \"website/**/*.{js,jsx,ts,tsx}\": \"eslint\"\n  },\n  \"overrides\": {\n    \"react\": \"^19.2.0\",\n    \"react-dom\": \"^19.2.0\"\n  }\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "import {\n  KeyCodeInfo,\n  HotkeysEvent,\n  UnbindInfo,\n  HotkeysOptions,\n  KeyHandler,\n  HotkeysInterface,\n  SetScope,\n  GetScope,\n  GetPressedKeyCodes,\n  GetPressedKeyString,\n  GetAllKeyCodes,\n  Filter,\n  IsPressed,\n  DeleteScope,\n  Unbind,\n  HotkeysAPI,\n} from './types';\nimport { addEvent, removeEvent, getMods, getKeys, compareArray, getLayoutIndependentKeyCode } from './utils';\nimport { _keyMap, _modifier, modifierMap, _mods, _handlers } from './var';\n\n/** Record the pressed keys */\nlet _downKeys: number[] = [];\n/** Whether the window has already listened to the focus event */\nlet winListendFocus: { listener: EventListener; capture: boolean } | null =\n  null;\n/** Whether we already listen to fullscreen change (to clear stuck _downKeys) */\nlet winListendFullscreen: { fullscreen: EventListener; webkit: EventListener } | null = null;\n/** Default hotkey scope */\nlet _scope: string = 'all';\n/** Map to record elements with bound events */\nconst elementEventMap = new Map<\n  HTMLElement | Document,\n  {\n    keydownListener: EventListener;\n    keyupListenr: EventListener;\n    capture: boolean;\n  }\n>();\n\n/** Return key code */\nconst code = (x: string): number =>\n  _keyMap[x.toLowerCase()] ||\n  _modifier[x.toLowerCase()] ||\n  x.toUpperCase().charCodeAt(0);\n\nconst getKey = (x: number): string | undefined =>\n  Object.keys(_keyMap).find((k) => _keyMap[k] === x);\nconst getModifier = (x: number): string | undefined =>\n  Object.keys(_modifier).find((k) => _modifier[k] === x);\n\n/** Set or get the current scope (defaults to 'all') */\nconst setScope: SetScope = (scope) => {\n  _scope = scope || 'all';\n};\n/** Get the current scope */\nconst getScope: GetScope = () => {\n  return _scope || 'all';\n};\n/** Get the key codes of the currently pressed keys */\nconst getPressedKeyCodes: GetPressedKeyCodes = () => {\n  return _downKeys.slice(0);\n};\n\nconst getPressedKeyString: GetPressedKeyString = () => {\n  return _downKeys.map(\n    (c) => getKey(c) || getModifier(c) || String.fromCharCode(c)\n  );\n};\n\nconst getAllKeyCodes: GetAllKeyCodes = () => {\n  const result: KeyCodeInfo[] = [];\n  Object.keys(_handlers).forEach((k) => {\n    _handlers[k].forEach(({ key, scope, mods, shortcut }) => {\n      result.push({\n        scope,\n        shortcut,\n        mods,\n        keys: key.split('+').map((v) => code(v)),\n      });\n    });\n  });\n  return result;\n};\n\n/** hotkey is effective only when filter return true */\nconst filter: Filter = (event) => {\n  const target = (event.target || event.srcElement) as HTMLElement;\n  const { tagName } = target;\n  let flag = true;\n  const isInput =\n    tagName === 'INPUT' &&\n    ![\n      'checkbox',\n      'radio',\n      'range',\n      'button',\n      'file',\n      'reset',\n      'submit',\n      'color',\n    ].includes((target as HTMLInputElement).type);\n  // ignore: isContentEditable === 'true', <input> and <textarea> when readOnly state is false, <select>\n  if (\n    target.isContentEditable ||\n    ((isInput || tagName === 'TEXTAREA' || tagName === 'SELECT') &&\n      !(target as HTMLInputElement | HTMLTextAreaElement).readOnly)\n  ) {\n    flag = false;\n  }\n  return flag;\n};\n\n/** Determine whether the pressed key matches a specific key, returns true or false */\nconst isPressed: IsPressed = (keyCode) => {\n  if (typeof keyCode === 'string') {\n    keyCode = code(keyCode); // Convert to key code\n  }\n  return _downKeys.indexOf(keyCode) !== -1;\n};\n\n/** Loop through and delete all handlers with the specified scope */\nconst deleteScope: DeleteScope = (scope, newScope) => {\n  let handlers: HotkeysEvent[];\n  let i: number;\n\n  // If no scope is specified, get the current scope\n  if (!scope) scope = getScope();\n\n  for (const key in _handlers) {\n    if (Object.prototype.hasOwnProperty.call(_handlers, key)) {\n      handlers = _handlers[key];\n      for (i = 0; i < handlers.length; ) {\n        if (handlers[i].scope === scope) {\n          const deleteItems = handlers.splice(i, 1);\n          deleteItems.forEach(({ element }) => removeKeyEvent(element));\n        } else {\n          i++;\n        }\n      }\n    }\n  }\n\n  // If the current scope has been deleted, reset the scope to 'all'\n  if (getScope() === scope) setScope(newScope || 'all');\n};\n\n/** Clear modifier keys */\nfunction clearModifier(event: KeyboardEvent): void {\n  let key = getLayoutIndependentKeyCode(event);\n\n  if (event.key && event.key.toLowerCase() === 'capslock') {\n    // Ensure that when capturing keystrokes in modern browsers,\n    // uppercase and lowercase letters (such as R and r) return the same key value.\n    // https://github.com/jaywcjlove/hotkeys-js/pull/514\n    // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key\n    key = code(event.key);\n  }\n  const i = _downKeys.indexOf(key);\n\n  // Remove the pressed key from the list\n  if (i >= 0) {\n    _downKeys.splice(i, 1);\n  }\n  // Special handling for the command key: fix the issue where keyup only triggers once for command combos\n  if (event.key && event.key.toLowerCase() === 'meta') {\n    _downKeys.splice(0, _downKeys.length);\n  }\n\n  // Clear modifier keys: shiftKey, altKey, ctrlKey, (command || metaKey)\n  if (key === 93 || key === 224) key = 91;\n  if (key in _mods) {\n    _mods[key] = false;\n\n    // Reset the modifier key status to false\n    for (const k in _modifier)\n      if (_modifier[k] === key) (hotkeys as any)[k] = false;\n  }\n}\n\nconst unbind: Unbind = (\n  keysInfo?: string | UnbindInfo | UnbindInfo[],\n  ...args: any[]\n): void => {\n  // unbind(), unbind all keys\n  if (typeof keysInfo === 'undefined') {\n    Object.keys(_handlers).forEach((key) => {\n      if (Array.isArray(_handlers[key])) {\n        _handlers[key].forEach((info) => eachUnbind(info));\n      }\n      delete _handlers[key];\n    });\n    removeKeyEvent(null);\n  } else if (Array.isArray(keysInfo)) {\n    // support like : unbind([{key: 'ctrl+a', scope: 's1'}, {key: 'ctrl-a', scope: 's2', splitKey: '-'}])\n    keysInfo.forEach((info) => {\n      if (info.key) eachUnbind(info);\n    });\n  } else if (typeof keysInfo === 'object') {\n    // support like unbind({key: 'ctrl+a, ctrl+b', scope:'abc'})\n    if (keysInfo.key) eachUnbind(keysInfo);\n  } else if (typeof keysInfo === 'string') {\n    // support old method\n    let [scope, method] = args;\n    if (typeof scope === 'function') {\n      method = scope;\n      scope = '';\n    }\n    eachUnbind({\n      key: keysInfo,\n      scope,\n      method,\n      splitKey: '+',\n    });\n  }\n};\n\n/** Unbind hotkeys for a specific scope */\nconst eachUnbind = ({\n  key,\n  scope,\n  method,\n  splitKey = '+',\n}: UnbindInfo): void => {\n  const multipleKeys = getKeys(key);\n  multipleKeys.forEach((originKey) => {\n    const unbindKeys = originKey.split(splitKey);\n    const len = unbindKeys.length;\n    const lastKey = unbindKeys[len - 1];\n    const keyCode = lastKey === '*' ? '*' : code(lastKey);\n    if (!_handlers[keyCode]) return;\n    // If scope is not provided, get the current scope\n    if (!scope) scope = getScope();\n    const mods = len > 1 ? getMods(_modifier, unbindKeys) : [];\n    const unbindElements: (HTMLElement | Document)[] = [];\n    _handlers[keyCode] = _handlers[keyCode].filter((record) => {\n      // Check if the method matches; if method is provided, must be equal to unbind\n      const isMatchingMethod = method ? record.method === method : true;\n      const isUnbind =\n        isMatchingMethod &&\n        record.scope === scope &&\n        compareArray(record.mods, mods);\n      if (isUnbind) unbindElements.push(record.element);\n      return !isUnbind;\n    });\n    unbindElements.forEach((element) => removeKeyEvent(element));\n  });\n};\n\n/** Handle the callback function for the corresponding hotkey */\nfunction eventHandler(\n  event: KeyboardEvent,\n  handler: HotkeysEvent,\n  scope: string,\n  element: HTMLElement | Document\n): void {\n  if (handler.element !== element) {\n    return;\n  }\n  let modifiersMatch: boolean;\n\n  // Check if it is within the current scope\n  if (handler.scope === scope || handler.scope === 'all') {\n    // Check whether modifier keys match (returns true if they do)\n    modifiersMatch = handler.mods.length > 0;\n\n    for (const y in _mods) {\n      if (Object.prototype.hasOwnProperty.call(_mods, y)) {\n        if (\n          (!_mods[y] && handler.mods.indexOf(+y) > -1) ||\n          (_mods[y] && handler.mods.indexOf(+y) === -1)\n        ) {\n          modifiersMatch = false;\n        }\n      }\n    }\n\n    // Call the handler function; ignore if it's only a modifier key\n    if (\n      (handler.mods.length === 0 &&\n        !_mods[16] &&\n        !_mods[18] &&\n        !_mods[17] &&\n        !_mods[91]) ||\n      modifiersMatch ||\n      handler.shortcut === '*'\n    ) {\n      handler.keys = [];\n      handler.keys = handler.keys.concat(_downKeys);\n      if (handler.method(event, handler) === false) {\n        if (event.preventDefault) event.preventDefault();\n        else event.returnValue = false;\n        if (event.stopPropagation) event.stopPropagation();\n        if (event.cancelBubble) event.cancelBubble = true;\n      }\n    }\n  }\n}\n\n/** Handle the keydown event */\nfunction dispatch(\n  this: any,\n  event: KeyboardEvent,\n  element: HTMLElement | Document\n): void {\n  const asterisk = _handlers['*'];\n  let key = getLayoutIndependentKeyCode(event);\n\n  // Ensure that when capturing keystrokes in modern browsers,\n  // uppercase and lowercase letters (such as R and r) return the same key value.\n  // https://github.com/jaywcjlove/hotkeys-js/pull/514\n  // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key\n  // CapsLock key\n  // There's an issue where `keydown` and `keyup` events are not triggered after CapsLock is enabled to activate uppercase.\n  if (event.key && event.key.toLowerCase() === 'capslock') {\n    return;\n  }\n  // Form control filter: by default, shortcut keys are not triggered in form elements\n  const filterFn = (hotkeys as any).filter || filter;\n  if (!filterFn.call(this, event)) return;\n\n  // In Gecko (Firefox), the command key code is 224; unify it with WebKit (Chrome)\n  // In WebKit, left and right command keys have different codes\n  if (key === 93 || key === 224) key = 91;\n\n  /**\n   * Collect bound keys\n   * If an Input Method Editor is processing key input and the event is keydown, return 229.\n   * https://stackoverflow.com/questions/25043934/is-it-ok-to-ignore-keydown-events-with-keycode-229\n   * http://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html\n   */\n  if (_downKeys.indexOf(key) === -1 && key !== 229) _downKeys.push(key);\n  /**\n   * Jest test cases are required.\n   * ===============================\n   */\n  ['metaKey', 'ctrlKey', 'altKey', 'shiftKey'].forEach((keyName) => {\n    const keyNum = (modifierMap as any)[keyName] as number;\n    if ((event as any)[keyName] && _downKeys.indexOf(keyNum) === -1) {\n      _downKeys.push(keyNum);\n    } else if (!(event as any)[keyName] && _downKeys.indexOf(keyNum) > -1) {\n      _downKeys.splice(_downKeys.indexOf(keyNum), 1);\n    } else if (keyName === 'metaKey' && (event as any)[keyName]) {\n      // If the command key is pressed, clear all non-modifier keys except the current event key.\n      // This is because keyup for non-modifier keys will NEVER be triggered when command is pressed.\n      // This is a known browser limitation.\n      _downKeys = _downKeys.filter((k) => k in modifierMap || k === key);\n    }\n  });\n  /**\n   * -------------------------------\n   */\n  if (key in _mods) {\n    _mods[key] = true;\n    // Register special modifier keys to the `hotkeys` object\n    for (const k in _modifier) {\n      if (Object.prototype.hasOwnProperty.call(_modifier, k)) {\n        const eventKey = (modifierMap as any)[_modifier[k]] as string;\n        (hotkeys as any)[k] = (event as any)[eventKey];\n      }\n    }\n\n    if (!asterisk) return;\n  }\n\n  // Bind the modifier keys in modifierMap to the event\n  for (const e in _mods) {\n    if (Object.prototype.hasOwnProperty.call(_mods, e)) {\n      _mods[e] = (event as any)[(modifierMap as any)[e]];\n    }\n  }\n  /**\n   * https://github.com/jaywcjlove/hotkeys/pull/129\n   * This solves the issue in Firefox on Windows where hotkeys corresponding to special characters would not trigger.\n   * An example of this is ctrl+alt+m on a Swedish keyboard which is used to type μ.\n   * Browser support: https://caniuse.com/#feat=keyboardevent-getmodifierstate\n   */\n  if (\n    event.getModifierState &&\n    !(event.altKey && !event.ctrlKey) &&\n    event.getModifierState('AltGraph')\n  ) {\n    if (_downKeys.indexOf(17) === -1) {\n      _downKeys.push(17);\n    }\n\n    if (_downKeys.indexOf(18) === -1) {\n      _downKeys.push(18);\n    }\n\n    _mods[17] = true;\n    _mods[18] = true;\n  }\n\n  // Get the current scope (defaults to 'all')\n  const scope = getScope();\n  // Handle any hotkeys registered as '*'\n  if (asterisk) {\n    for (let i = 0; i < asterisk.length; i++) {\n      if (\n        asterisk[i].scope === scope &&\n        ((event.type === 'keydown' && asterisk[i].keydown) ||\n          (event.type === 'keyup' && asterisk[i].keyup))\n      ) {\n        eventHandler(event, asterisk[i], scope, element);\n      }\n    }\n  }\n  // If the key is not registered, return\n  if (!(key in _handlers)) return;\n\n  const handlerKey = _handlers[key];\n  const keyLen = handlerKey.length;\n  for (let i = 0; i < keyLen; i++) {\n    if (\n      (event.type === 'keydown' && handlerKey[i].keydown) ||\n      (event.type === 'keyup' && handlerKey[i].keyup)\n    ) {\n      if (handlerKey[i].key) {\n        const record = handlerKey[i];\n        const { splitKey } = record;\n        const keyShortcut = record.key.split(splitKey);\n        const _downKeysCurrent: number[] = []; // Store the current key codes\n        for (let a = 0; a < keyShortcut.length; a++) {\n          _downKeysCurrent.push(code(keyShortcut[a]));\n        }\n        if (_downKeysCurrent.sort().join('') === _downKeys.sort().join('')) {\n          // Match found, call the handler\n          eventHandler(event, record, scope, element);\n        }\n      }\n    }\n  }\n}\n\nconst hotkeys = function hotkeys(\n  key: string,\n  option?: string | HotkeysOptions | KeyHandler,\n  method?: KeyHandler\n): void {\n  _downKeys = [];\n  /** List of hotkeys to handle */\n  const keys = getKeys(key);\n  let mods: number[] = [];\n  /** Default scope is 'all', meaning effective in all scopes */\n  let scope: string = 'all';\n  /** Element to which the hotkey events are bound */\n  let element: HTMLElement | Document = document;\n  let i = 0;\n  let keyup = false;\n  let keydown = true;\n  let splitKey = '+';\n  let capture = false;\n  /** Allow only a single callback */\n  let single = false;\n\n  // Determine if the second argument is a function (no options provided)\n  if (method === undefined && typeof option === 'function') {\n    method = option;\n  }\n\n  // Parse options object\n  if (Object.prototype.toString.call(option) === '[object Object]') {\n    const opts = option as HotkeysOptions;\n    if (opts.scope) scope = opts.scope; // Set scope\n    if (opts.element) element = opts.element; // Set binding element\n    if (opts.keyup) keyup = opts.keyup;\n    if (opts.keydown !== undefined) keydown = opts.keydown;\n    if (opts.capture !== undefined) capture = opts.capture;\n    if (typeof opts.splitKey === 'string') splitKey = opts.splitKey;\n    if (opts.single === true) single = true;\n  }\n\n  if (typeof option === 'string') scope = option;\n\n  // If only one callback is allowed, unbind the existing one first\n  if (single) unbind(key, scope);\n\n  // Handle each hotkey\n  for (; i < keys.length; i++) {\n    const currentKey = keys[i].split(splitKey); // Split into individual keys\n    mods = [];\n\n    // If it's a combination, extract modifier keys\n    if (currentKey.length > 1) mods = getMods(_modifier, currentKey);\n\n    // Convert non-modifier key to key code\n    let finalKey: string | number = currentKey[currentKey.length - 1];\n    finalKey = finalKey === '*' ? '*' : code(finalKey); // '*' means match all hotkeys\n\n    // Initialize handler array if this key has no handlers yet\n    if (!(finalKey in _handlers)) _handlers[finalKey] = [];\n\n    _handlers[finalKey].push({\n      keyup,\n      keydown,\n      scope,\n      mods,\n      shortcut: keys[i],\n      method: method!,\n      key: keys[i],\n      splitKey,\n      element,\n    });\n  }\n  // Register hotkey event listeners on the global document\n  if (typeof element !== 'undefined' && typeof window !== 'undefined') {\n    if (!elementEventMap.has(element)) {\n      // @ts-expect-error - window.event is deprecated IE property\n      const keydownListener = (event: Event = window.event) =>\n        dispatch(event as KeyboardEvent, element);\n      // @ts-expect-error - window.event is deprecated IE property\n      const keyupListenr = (event: Event = window.event) => {\n        dispatch(event as KeyboardEvent, element);\n        clearModifier(event as KeyboardEvent);\n      };\n      elementEventMap.set(element, { keydownListener, keyupListenr, capture });\n      addEvent(element, 'keydown', keydownListener, capture);\n      addEvent(element, 'keyup', keyupListenr, capture);\n    }\n    // Register focus event listener once to clear pressed keys on window focus\n    if (!winListendFocus) {\n      const listener = () => {\n        _downKeys = [];\n      };\n      winListendFocus = { listener, capture };\n      addEvent(window, 'focus', listener, capture);\n    }\n    // Register fullscreen change once: keyup can be lost when entering fullscreen (e.g. Alt+F), so clear stuck keys\n    if (!winListendFullscreen && typeof document !== 'undefined') {\n      const onFullscreenChange = () => {\n        _downKeys = [];\n        for (const k in _mods) _mods[k] = false;\n        for (const k in _modifier) (hotkeys as any)[k] = false;\n      };\n      const fullscreenListener = onFullscreenChange as EventListener;\n      const webkitListener = onFullscreenChange as EventListener;\n      document.addEventListener('fullscreenchange', fullscreenListener);\n      document.addEventListener('webkitfullscreenchange', webkitListener);\n      winListendFullscreen = { fullscreen: fullscreenListener, webkit: webkitListener };\n    }\n  }\n} as HotkeysInterface;\n\nfunction trigger(shortcut: string, scope: string = 'all'): void {\n  Object.keys(_handlers).forEach((key) => {\n    const dataList = _handlers[key].filter(\n      (item) => item.scope === scope && item.shortcut === shortcut\n    );\n    dataList.forEach((data) => {\n      if (data && data.method) {\n        data.method({} as KeyboardEvent, data);\n      }\n    });\n  });\n}\n\n/** Clean up event listeners. After unbinding, check whether the element still has any hotkeys bound. If not, remove its event listeners. */\nfunction removeKeyEvent(element: HTMLElement | Document | null): void {\n  const values = Object.values(_handlers).flat();\n  const findindex = values.findIndex(({ element: el }) => el === element);\n\n  if (findindex < 0 && element) {\n    const { keydownListener, keyupListenr, capture } =\n      elementEventMap.get(element) || {};\n    if (keydownListener && keyupListenr) {\n      removeEvent(element, 'keyup', keyupListenr, capture);\n      removeEvent(element, 'keydown', keydownListener, capture);\n      elementEventMap.delete(element);\n    }\n  }\n\n  if (values.length <= 0 || elementEventMap.size <= 0) {\n    // Remove all event listeners from all elements\n    const eventKeys = Array.from(elementEventMap.keys());\n    eventKeys.forEach((el) => {\n      const { keydownListener, keyupListenr, capture } =\n        elementEventMap.get(el) || {};\n      if (keydownListener && keyupListenr) {\n        removeEvent(el, 'keyup', keyupListenr, capture);\n        removeEvent(el, 'keydown', keydownListener, capture);\n        elementEventMap.delete(el);\n      }\n    });\n    // Clear the elementEventMap\n    elementEventMap.clear();\n    // Clear all handlers\n    Object.keys(_handlers).forEach((key) => delete _handlers[key]);\n    // Remove the global window focus event listener\n    if (winListendFocus) {\n      const { listener, capture } = winListendFocus;\n      removeEvent(window, 'focus', listener, capture);\n      winListendFocus = null;\n    }\n    // Remove fullscreen change listeners\n    if (winListendFullscreen && typeof document !== 'undefined') {\n      document.removeEventListener('fullscreenchange', winListendFullscreen.fullscreen);\n      document.removeEventListener('webkitfullscreenchange', winListendFullscreen.webkit);\n      winListendFullscreen = null;\n    }\n  }\n}\n\nconst _api: Omit<HotkeysAPI, 'noConflict'> = {\n  getPressedKeyString,\n  setScope,\n  getScope,\n  deleteScope,\n  getPressedKeyCodes,\n  getAllKeyCodes,\n  isPressed,\n  filter,\n  trigger,\n  unbind,\n  keyMap: _keyMap,\n  modifier: _modifier,\n  modifierMap,\n};\n\nfor (const a in _api) {\n  const key = a as keyof typeof _api;\n  if (Object.prototype.hasOwnProperty.call(_api, key)) {\n    (hotkeys as any)[key] = _api[key];\n  }\n}\n\nif (typeof window !== 'undefined') {\n  const _hotkeys = (window as any).hotkeys;\n  (hotkeys as HotkeysInterface).noConflict = (deep?: boolean) => {\n    if (deep && (window as any).hotkeys === hotkeys) {\n      (window as any).hotkeys = _hotkeys;\n    }\n    return hotkeys as HotkeysInterface;\n  };\n  (window as any).hotkeys = hotkeys;\n}\n\n// Export for both ESM and CommonJS\nexport default hotkeys as HotkeysInterface;\nexport type { KeyHandler, HotkeysEvent };\n\n// Additional CommonJS compatibility\n// This will be handled by the build process for UMD builds\nif (typeof module !== 'undefined' && module.exports) {\n  module.exports = hotkeys;\n  module.exports.default = hotkeys;\n}\n"
  },
  {
    "path": "src/types.ts",
    "content": "export interface KeyCodeInfo {\n  scope: string;\n  shortcut: string;\n  mods: number[];\n  keys: number[];\n}\n\nexport interface UnbindInfo {\n  key: string;\n  scope?: string;\n  method?: KeyHandler;\n  splitKey?: string;\n}\n\nexport interface HotkeysEvent {\n  keyup: boolean;\n  keydown: boolean;\n  scope: string;\n  mods: number[];\n  shortcut: string;\n  method: KeyHandler;\n  key: string;\n  splitKey: string;\n  element: HTMLElement | Document;\n  keys?: number[];\n}\n\nexport interface KeyHandler {\n  (keyboardEvent: KeyboardEvent, hotkeysEvent: HotkeysEvent): void | boolean;\n}\n\nexport type SetScope = (scope: string) => void;\n\nexport type GetScope = () => string;\n\nexport type DeleteScope = (scope?: string, newScope?: string) => void;\n\nexport type GetPressedKeyCodes = () => number[];\n\nexport type GetPressedKeyString = () => string[];\n\nexport type GetAllKeyCodes = () => KeyCodeInfo[];\n\nexport interface IsPressed {\n  /** For example, `hotkeys.isPressed(77)` is true if the `M` key is currently pressed. */\n  (keyCode: number): boolean;\n  /** For example, `hotkeys.isPressed('m')` is true if the `M` key is currently pressed. */\n  (keyCode: string): boolean;\n}\n\nexport type Filter = (event: KeyboardEvent) => boolean;\n\nexport type Trigger = (shortcut: string, scope?: string) => void;\n\nexport interface Unbind {\n  (key?: string): void;\n  (keysInfo: UnbindInfo): void;\n  (keysInfo: UnbindInfo[]): void;\n  (key: string, scopeName: string): void;\n  (key: string, scopeName: string, method: KeyHandler): void;\n  (key: string, method: KeyHandler): void;\n}\n\nexport type NoConflict = (deep?: boolean) => HotkeysInterface;\n\nexport interface HotkeysOptions {\n  scope?: string;\n  element?: HTMLElement | Document;\n  keyup?: boolean;\n  keydown?: boolean;\n  capture?: boolean;\n  splitKey?: string;\n  single?: boolean;\n}\n\nexport interface HotkeysAPI {\n  /**\n   * Use the `hotkeys.setScope` method to set scope. There can only be one active scope besides 'all'.  By default 'all' is always active.\n   *\n   * ```js\n   * // Define shortcuts with a scope\n   * hotkeys('ctrl+o, ctrl+alt+enter', 'issues', function() {\n   *   console.log('do something');\n   * });\n   * hotkeys('o, enter', 'files', function() {\n   *   console.log('do something else');\n   * });\n   *\n   * // Set the scope (only 'all' and 'issues' shortcuts will be honored)\n   * hotkeys.setScope('issues'); // default scope is 'all'\n   * ```\n   */\n  setScope: SetScope;\n  /**\n   * Use the `hotkeys.getScope` method to get scope.\n   *\n   * ```js\n   * hotkeys.getScope();\n   * ```\n   */\n  getScope: GetScope;\n  /**\n   * Use the `hotkeys.deleteScope` method to delete a scope. This will also remove all associated hotkeys with it.\n   *\n   * ```js\n   * hotkeys.deleteScope('issues');\n   * ```\n   * You can use second argument, if need set new scope after deleting.\n   *\n   * ```js\n   * hotkeys.deleteScope('issues', 'newScopeName');\n   * ```\n   */\n  deleteScope: DeleteScope;\n  /**\n   * Returns an array of key codes currently pressed.\n   *\n   * ```js\n   * hotkeys('command+ctrl+shift+a,f', function() {\n   *   console.log(hotkeys.getPressedKeyCodes()); //=> [17, 65] or [70]\n   * })\n   * ```\n   */\n  getPressedKeyCodes: GetPressedKeyCodes;\n  /**\n   * Returns an array of key codes currently pressed.\n   *\n   * ```js\n   * hotkeys('command+ctrl+shift+a,f', function() {\n   *   console.log(hotkeys.getPressedKeyString()); //=> ['⌘', '⌃', '⇧', 'A', 'F']\n   * })\n   * ```\n   */\n  getPressedKeyString: GetPressedKeyString;\n  /**\n   * Get a list of all registration codes.\n   *\n   * ```js\n   * hotkeys('command+ctrl+shift+a,f', function() {\n   *   console.log(hotkeys.getAllKeyCodes());\n   *   // [\n   *   //   { scope: 'all', shortcut: 'command+ctrl+shift+a', mods: [91, 17, 16], keys: [91, 17, 16, 65] },\n   *   //   { scope: 'all', shortcut: 'f', mods: [], keys: [42] }\n   *   // ]\n   * })\n   * ```\n   *\n   */\n  getAllKeyCodes: GetAllKeyCodes;\n  isPressed: IsPressed;\n  /**\n   * By default hotkeys are not enabled for `INPUT` `SELECT` `TEXTAREA` elements.\n   * `Hotkeys.filter` to return to the `true` shortcut keys set to play a role,\n   * `false` shortcut keys set up failure.\n   *\n   * ```js\n   * hotkeys.filter = function(event){\n   *   return true;\n   * }\n   * //How to add the filter to edit labels. <div contentEditable=\"true\"></div>\n   * //\"contentEditable\" Older browsers that do not support drops\n   * hotkeys.filter = function(event) {\n   *   var target = event.target || event.srcElement;\n   *   var tagName = target.tagName;\n   *   return !(target.isContentEditable || tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');\n   * }\n   *\n   * hotkeys.filter = function(event){\n   *   var tagName = (event.target || event.srcElement).tagName;\n   *   hotkeys.setScope(/^(INPUT|TEXTAREA|SELECT)$/.test(tagName) ? 'input' : 'other');\n   *   return true;\n   * }\n   * ```\n   */\n  filter: Filter;\n  /**\n   * trigger shortcut key event\n   *\n   * ```js\n   * hotkeys.trigger('ctrl+o');\n   * hotkeys.trigger('ctrl+o', 'scope2');\n   * ```\n   */\n  trigger: Trigger;\n  /**\n   * Unbinds a shortcut key event.\n   *\n   * ```js\n   * hotkeys.unbind('ctrl+o');\n   * hotkeys.unbind('ctrl+o', 'scope1');\n   * hotkeys.unbind('ctrl+o', 'scope1', method);\n   * hotkeys.unbind('ctrl+o', method);\n   * ```\n   */\n  unbind: Unbind;\n  /**\n   * Relinquish HotKeys’s control of the `hotkeys` variable.\n   *\n   * ```js\n   * var k = hotkeys.noConflict();\n   * k('a', function() {\n   *   console.log(\"do something\")\n   * });\n   *\n   * hotkeys()\n   * // -->Uncaught TypeError: hotkeys is not a function(anonymous function)\n   * // @ VM2170:2InjectedScript._evaluateOn\n   * // @ VM2165:883InjectedScript._evaluateAndWrap\n   * // @ VM2165:816InjectedScript.evaluate @ VM2165:682\n   * ```\n   */\n  noConflict: NoConflict;\n\n  keyMap: Record<string, number>;\n  modifier: Record<string, number>;\n  modifierMap: Record<string | number, number | string>;\n}\n\nexport interface HotkeysInterface extends HotkeysAPI {\n  (key: string, method: KeyHandler): void;\n  (key: string, scope: string, method: KeyHandler): void;\n  (key: string, option: HotkeysOptions, method: KeyHandler): void;\n\n  shift?: boolean;\n  ctrl?: boolean;\n  alt?: boolean;\n  option?: boolean;\n  control?: boolean;\n  cmd?: boolean;\n  command?: boolean;\n}\n"
  },
  {
    "path": "src/utils.ts",
    "content": "const isff: boolean =\n  typeof navigator !== 'undefined'\n    ? navigator.userAgent.toLowerCase().indexOf('firefox') > 0\n    : false;\n\n/** Bind event */\nfunction addEvent(\n  object: HTMLElement | Document | Window,\n  event: string,\n  method: EventListenerOrEventListenerObject,\n  useCapture?: boolean\n): void {\n  if (object.addEventListener) {\n    object.addEventListener(event, method, useCapture);\n    // @ts-expect-error - attachEvent is only available on IE\n  } else if (object.attachEvent) {\n    // @ts-expect-error - attachEvent is only available on IE\n    object.attachEvent(`on${event}`, method);\n  }\n}\n\nfunction removeEvent(\n  object: HTMLElement | Document | Window | null,\n  event: string,\n  method: EventListenerOrEventListenerObject,\n  useCapture?: boolean\n): void {\n  if (!object) return;\n  if (object.removeEventListener) {\n    object.removeEventListener(event, method, useCapture);\n    // @ts-expect-error - removeEvent is only available on IE\n  } else if (object.detachEvent) {\n    // @ts-expect-error - detachEvent is only available on IE\n    object.detachEvent(`on${event}`, method);\n  }\n}\n\n/** Convert modifier keys to their corresponding key codes */\nfunction getMods(modifier: Record<string, number>, key: string[]): number[] {\n  const modsKeys = key.slice(0, key.length - 1);\n  const modsCodes: number[] = [];\n  for (let i = 0; i < modsKeys.length; i++) {\n    modsCodes.push(modifier[modsKeys[i].toLowerCase()]);\n  }\n  return modsCodes;\n}\n\n/** Process the input key string and convert it to an array */\nfunction getKeys(key: string | undefined): string[] {\n  if (typeof key !== 'string') key = '';\n  key = key.replace(/\\s/g, ''); // Match any whitespace character, including spaces, tabs, form feeds, etc.\n  const keys = key.split(','); // Allow multiple shortcuts separated by ','\n  let index = keys.lastIndexOf('');\n\n  // Shortcut may include ',' — special handling needed\n  for (; index >= 0; ) {\n    keys[index - 1] += ',';\n    keys.splice(index, 1);\n    index = keys.lastIndexOf('');\n  }\n\n  return keys;\n}\n\n/** Compare arrays of modifier keys */\nfunction compareArray(a1: number[], a2: number[]): boolean {\n  const arr1 = a1.length >= a2.length ? a1 : a2;\n  const arr2 = a1.length >= a2.length ? a2 : a1;\n  let isIndex = true;\n\n  for (let i = 0; i < arr1.length; i++) {\n    if (arr2.indexOf(arr1[i]) === -1) isIndex = false;\n  }\n  return isIndex;\n}\n\n/**\n * Get layout-independent key code from keyboard event\n * This makes hotkeys work regardless of keyboard layout (Russian, German, etc.)\n * Uses event.code (physical key) instead of keyCode (character) for letter keys\n */\nfunction getLayoutIndependentKeyCode(event: KeyboardEvent): number {\n  let key = event.keyCode || event.which || event.charCode;\n\n  // Convert physical key code (KeyA-KeyZ) to corresponding ASCII code (65-90)\n  // This makes 'ctrl+a' work on any keyboard layout\n  if (event.code && /^Key[A-Z]$/.test(event.code)) {\n    key = event.code.charCodeAt(3); // \"KeyA\"[3] = \"A\" -> 65\n  }\n\n  return key;\n}\n\nexport {\n  isff,\n  getMods,\n  getKeys,\n  addEvent,\n  removeEvent,\n  compareArray,\n  getLayoutIndependentKeyCode,\n};\n"
  },
  {
    "path": "src/var.ts",
    "content": "import { HotkeysEvent } from './types';\nimport { isff } from './utils';\n\n// Special Keys\nconst _keyMap: Record<string, number> = {\n  backspace: 8,\n  '⌫': 8,\n  tab: 9,\n  clear: 12,\n  enter: 13,\n  '↩': 13,\n  return: 13,\n  esc: 27,\n  escape: 27,\n  space: 32,\n  left: 37,\n  up: 38,\n  right: 39,\n  down: 40,\n  /// https://w3c.github.io/uievents/#events-keyboard-key-location\n  arrowup: 38,\n  arrowdown: 40,\n  arrowleft: 37,\n  arrowright: 39,\n  del: 46,\n  delete: 46,\n  ins: 45,\n  insert: 45,\n  home: 36,\n  end: 35,\n  pageup: 33,\n  pagedown: 34,\n  capslock: 20,\n  num_0: 96,\n  num_1: 97,\n  num_2: 98,\n  num_3: 99,\n  num_4: 100,\n  num_5: 101,\n  num_6: 102,\n  num_7: 103,\n  num_8: 104,\n  num_9: 105,\n  num_multiply: 106,\n  num_add: 107,\n  num_enter: 108,\n  num_subtract: 109,\n  num_decimal: 110,\n  num_divide: 111,\n  '⇪': 20,\n  ',': 188,\n  '.': 190,\n  '/': 191,\n  '`': 192,\n  '-': isff ? 173 : 189,\n  '=': isff ? 61 : 187,\n  ';': isff ? 59 : 186,\n  '\\'': 222,\n  '{': 219,\n  '}': 221,\n  '[': 219,\n  ']': 221,\n  '\\\\': 220,\n};\n\n// Modifier Keys\nconst _modifier: Record<string, number> = {\n  // shiftKey\n  '⇧': 16,\n  shift: 16,\n  // altKey\n  '⌥': 18,\n  alt: 18,\n  option: 18,\n  // ctrlKey\n  '⌃': 17,\n  ctrl: 17,\n  control: 17,\n  // metaKey\n  '⌘': 91,\n  cmd: 91,\n  meta: 91,\n  command: 91,\n};\n\nconst modifierMap: Record<string | number, number | string> = {\n  16: 'shiftKey',\n  18: 'altKey',\n  17: 'ctrlKey',\n  91: 'metaKey',\n\n  shiftKey: 16,\n  ctrlKey: 17,\n  altKey: 18,\n  metaKey: 91,\n};\n\nconst _mods: Record<number, boolean> = {\n  16: false,\n  18: false,\n  17: false,\n  91: false,\n};\n\nconst _handlers: Record<string | number, HotkeysEvent[]> = {};\n\n// F1~F12 special key\nfor (let k = 1; k < 20; k++) {\n  _keyMap[`f${k}`] = 111 + k;\n}\n\nexport { _keyMap, _modifier, modifierMap, _mods, _handlers };\n"
  },
  {
    "path": "test/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"UTF-8\">\n  <title>hotkeys.js</title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\">\n  <meta name=\"description\" content=\"A robust Javascript library for capturing keyboard input and key combinations entered. It has no dependencies. Try to press your keyboard, The following button will highlight.\">\n  <script src=\"../dist/hotkeys-js.umd.cjs\"></script>\n</head>\n<body>\n  <div id=\"root\">hotkeys</div>\n</body>\n</html>\n"
  },
  {
    "path": "test/run.test.js",
    "content": "/* eslint-disable @typescript-eslint/no-require-imports */\nconst puppeteer = require('puppeteer');\nconst path = require('path');\n\nlet browser;\nlet page;\n\nbeforeAll(async () => {\n  browser = await puppeteer.launch({ args: ['--no-sandbox'] });\n  page = await browser.newPage();\n\n  // Enable coverage collection\n  await page.coverage.startJSCoverage();\n\n  // Load the test HTML file\n  const htmlPath = path.resolve(__dirname, './index.html');\n  await page.goto(`file://${htmlPath}`, { waitUntil: 'networkidle2' });\n}, 1000 * 120);\n\ndescribe('\\n   Hotkeys.js Test Case\\n', () => {\n  test('HTML loader', async () => {\n    const title = await page.title();\n    expect(title).toBe('hotkeys.js');\n  }, 10000);\n\n  test('Test HTML load', async () => {\n    const text = await page.$eval('#root', (el) => el.textContent);\n    expect(text).toBe('hotkeys');\n\n    const hasHotkeys = await page.evaluate(() => {\n      return typeof window.hotkeys !== 'undefined';\n    });\n    expect(hasHotkeys).toBeTruthy();\n  });\n\n  test('HotKeys getPressedKeyCodes Test Case', async () => {\n    const result = await page.evaluate(async () => {\n      return new Promise((resolve) => {\n        let isExecuteFunction = false;\n        window.hotkeys('command+ctrl+shift+a', (e) => {\n          isExecuteFunction = true;\n          const pressedKeys = window.hotkeys.getPressedKeyCodes();\n          resolve({\n            isExecuteFunction,\n            metaKey: e.metaKey,\n            ctrlKey: e.ctrlKey,\n            shiftKey: e.shiftKey,\n            pressedKeys,\n          });\n        });\n\n        // Trigger the event\n        const event = new KeyboardEvent('keydown', {\n          keyCode: 65,\n          which: 65,\n          metaKey: true,\n          ctrlKey: true,\n          shiftKey: true,\n          bubbles: true,\n          cancelable: true,\n        });\n        document.body.dispatchEvent(event);\n\n        setTimeout(() => {\n          window.hotkeys.unbind('command+ctrl+shift+a');\n          resolve({ isExecuteFunction, pressedKeys: [] });\n        }, 100);\n      });\n    });\n\n    expect(result.isExecuteFunction).toBeTruthy();\n    expect(result.metaKey).toBeTruthy();\n    expect(result.ctrlKey).toBeTruthy();\n    expect(result.shiftKey).toBeTruthy();\n    expect(result.pressedKeys).toEqual(\n      expect.arrayContaining([16, 17, 65, 91])\n    );\n  });\n\n  test('HotKeys getPressedKeyString Test Case', async () => {\n    const result = await page.evaluate(async () => {\n      return new Promise((resolve) => {\n        let isExecuteFunction = false;\n        window.hotkeys('command+ctrl+shift+a', (e) => {\n          isExecuteFunction = true;\n          const pressedKeys = window.hotkeys.getPressedKeyString();\n          resolve({\n            isExecuteFunction,\n            metaKey: e.metaKey,\n            ctrlKey: e.ctrlKey,\n            shiftKey: e.shiftKey,\n            pressedKeys,\n          });\n        });\n\n        const event = new KeyboardEvent('keydown', {\n          keyCode: 65,\n          which: 65,\n          metaKey: true,\n          ctrlKey: true,\n          shiftKey: true,\n          bubbles: true,\n          cancelable: true,\n        });\n        document.body.dispatchEvent(event);\n\n        setTimeout(() => {\n          window.hotkeys.unbind('command+ctrl+shift+a');\n          resolve({ isExecuteFunction: false, pressedKeys: [] });\n        }, 100);\n      });\n    });\n\n    expect(result.isExecuteFunction).toBeTruthy();\n    expect(result.pressedKeys).toEqual(\n      expect.arrayContaining(['⇧', '⌃', 'A', '⌘'])\n    );\n  });\n\n  test('HotKeys unbind Test Case', async () => {\n    const result = await page.evaluate(async () => {\n      return new Promise((resolve) => {\n        let isExecuteFunction = false;\n        window.hotkeys('enter', () => {\n          isExecuteFunction = true;\n        });\n\n        const event = new KeyboardEvent('keydown', {\n          keyCode: 13,\n          which: 13,\n          bubbles: true,\n          cancelable: true,\n        });\n        document.body.dispatchEvent(event);\n\n        setTimeout(() => {\n          const unbindResult = window.hotkeys.unbind('enter');\n          resolve({\n            isExecuteFunction,\n            unbindResult,\n            keyCode: 13,\n          });\n        }, 100);\n      });\n    });\n\n    expect(result.isExecuteFunction).toBeTruthy();\n    expect(result.unbindResult).toBeUndefined();\n  });\n\n  test('getAllKeyCodes Test Case', async () => {\n    const result = await page.evaluate(() => {\n      return window.hotkeys.getAllKeyCodes();\n    });\n\n    expect(Array.isArray(result)).toBeTruthy();\n  });\n\n  test('HotKeys Special keys Test Case', async () => {\n    const result = await page.evaluate(async () => {\n      const results = {};\n\n      // Test enter\n      await new Promise((resolve) => {\n        window.hotkeys('enter', (e) => {\n          results.enter = e.keyCode === 13;\n        });\n\n        const event = new KeyboardEvent('keydown', {\n          keyCode: 13,\n          which: 13,\n          bubbles: true,\n          cancelable: true,\n        });\n        document.body.dispatchEvent(event);\n\n        setTimeout(() => {\n          window.hotkeys.unbind('enter');\n          resolve();\n        }, 50);\n      });\n\n      // Test space\n      await new Promise((resolve) => {\n        window.hotkeys('space', (e) => {\n          results.space = e.keyCode === 32;\n        });\n\n        const event = new KeyboardEvent('keydown', {\n          keyCode: 32,\n          which: 32,\n          bubbles: true,\n          cancelable: true,\n        });\n        document.body.dispatchEvent(event);\n\n        setTimeout(() => {\n          window.hotkeys.unbind('space');\n          resolve();\n        }, 50);\n      });\n\n      return results;\n    });\n\n    expect(result.enter).toBeTruthy();\n    expect(result.space).toBeTruthy();\n  });\n\n  test('HotKeys Test Case', async () => {\n    const result = await page.evaluate(async () => {\n      const results = {};\n\n      // Test 'w'\n      await new Promise((resolve) => {\n        window.hotkeys('w', (e) => {\n          results.w = e.keyCode === 87;\n        });\n\n        const event = new KeyboardEvent('keydown', {\n          keyCode: 87,\n          which: 87,\n          bubbles: true,\n          cancelable: true,\n        });\n        document.body.dispatchEvent(event);\n\n        setTimeout(() => {\n          window.hotkeys.unbind('w');\n          resolve();\n        }, 50);\n      });\n\n      // Test 'a' with isPressed\n      await new Promise((resolve) => {\n        window.hotkeys('a', () => {\n          results.isPressedA = window.hotkeys.isPressed('a');\n          results.isPressedAUpper = window.hotkeys.isPressed('A');\n          results.isPressed65 = window.hotkeys.isPressed(65);\n        });\n\n        const event = new KeyboardEvent('keydown', {\n          keyCode: 65,\n          which: 65,\n          bubbles: true,\n          cancelable: true,\n        });\n        document.body.dispatchEvent(event);\n\n        setTimeout(() => {\n          window.hotkeys.unbind('a');\n          resolve();\n        }, 50);\n      });\n\n      return results;\n    });\n\n    expect(result.w).toBeTruthy();\n    expect(result.isPressedA).toBeTruthy();\n    expect(result.isPressedAUpper).toBeTruthy();\n    expect(result.isPressed65).toBeTruthy();\n  });\n\n  test('HotKeys Key combination Test Case', async () => {\n    const result = await page.evaluate(async () => {\n      const results = {};\n\n      // Test ⌘+d\n      await new Promise((resolve) => {\n        window.hotkeys('⌘+d', (e) => {\n          results.cmdD = e.keyCode === 68 && e.metaKey;\n        });\n\n        const event = new KeyboardEvent('keydown', {\n          keyCode: 68,\n          which: 68,\n          metaKey: true,\n          bubbles: true,\n          cancelable: true,\n        });\n        document.body.dispatchEvent(event);\n\n        setTimeout(() => {\n          window.hotkeys.unbind('⌘+d');\n          resolve();\n        }, 50);\n      });\n\n      // Test shift+a\n      await new Promise((resolve) => {\n        window.hotkeys('shift+a', (e) => {\n          results.shiftA = e.keyCode === 65 && e.shiftKey;\n        });\n\n        const event = new KeyboardEvent('keydown', {\n          keyCode: 65,\n          which: 65,\n          shiftKey: true,\n          bubbles: true,\n          cancelable: true,\n        });\n        document.body.dispatchEvent(event);\n\n        setTimeout(() => {\n          window.hotkeys.unbind('shift+a');\n          resolve();\n        }, 50);\n      });\n\n      return results;\n    });\n\n    expect(result.cmdD).toBeTruthy();\n    expect(result.shiftA).toBeTruthy();\n  });\n\n  test('Hotkey trigger with shortcut', async () => {\n    const result = await page.evaluate(() => {\n      let count = 0;\n      window.hotkeys('a', () => {\n        count++;\n      });\n\n      window.hotkeys.trigger('a');\n\n      window.hotkeys.unbind('a');\n\n      return count;\n    });\n\n    expect(result).toBe(1);\n  });\n\n  test('Hotkey trigger with multi shortcut', async () => {\n    const result = await page.evaluate(() => {\n      let count = 0;\n      for (let i = 0; i < 3; i++) {\n        window.hotkeys('a', () => {\n          count++;\n        });\n      }\n\n      window.hotkeys.trigger('a');\n\n      window.hotkeys.unbind('a');\n\n      return count;\n    });\n\n    expect(result).toBe(3);\n  });\n\n  test('Layout-independent hotkeys (Cyrillic/Russian keyboard)', async () => {\n    const result = await page.evaluate(async () => {\n      const results = {\n        altMTriggered: false,\n        altVTriggered: false,\n        downKeysAfterAltM: [],\n        downKeysAfterAltV: [],\n        downKeysAfterRelease: [],\n      };\n\n      // Register hotkeys for alt+m and alt+v\n      window.hotkeys('alt+m', () => {\n        results.altMTriggered = true;\n        results.downKeysAfterAltM = window.hotkeys.getPressedKeyCodes();\n      });\n\n      window.hotkeys('alt+v', () => {\n        results.altVTriggered = true;\n        results.downKeysAfterAltV = window.hotkeys.getPressedKeyCodes();\n      });\n\n      // Simulate Alt+M on Russian keyboard layout\n      // On Russian layout, M key produces \"Ь\" character (code 1068 or ~126)\n      // But the physical key is still KeyM\n      await new Promise((resolve) => {\n        // Press Alt\n        const altDown = new KeyboardEvent('keydown', {\n          keyCode: 18,\n          which: 18,\n          code: 'AltLeft',\n          altKey: true,\n          bubbles: true,\n          cancelable: true,\n        });\n        document.body.dispatchEvent(altDown);\n\n        setTimeout(() => {\n          // Press M (with wrong keyCode but correct code)\n          const mDown = new KeyboardEvent('keydown', {\n            keyCode: 126, // Wrong keyCode (~ character on Cyrillic layout)\n            which: 126,\n            code: 'KeyM', // Correct physical key code\n            altKey: true,\n            bubbles: true,\n            cancelable: true,\n          });\n          document.body.dispatchEvent(mDown);\n\n          setTimeout(() => {\n            // Release M\n            const mUp = new KeyboardEvent('keyup', {\n              keyCode: 126,\n              which: 126,\n              code: 'KeyM',\n              altKey: true,\n              bubbles: true,\n              cancelable: true,\n            });\n            document.body.dispatchEvent(mUp);\n\n            setTimeout(() => {\n              // Release Alt\n              const altUp = new KeyboardEvent('keyup', {\n                keyCode: 18,\n                which: 18,\n                code: 'AltLeft',\n                altKey: false,\n                bubbles: true,\n                cancelable: true,\n              });\n              document.body.dispatchEvent(altUp);\n\n              resolve();\n            }, 20);\n          }, 20);\n        }, 20);\n      });\n\n      // Small delay between combinations\n      await new Promise((resolve) => setTimeout(resolve, 50));\n\n      // Simulate Alt+V on Russian keyboard layout\n      // On Russian layout, V key produces \"М\" character\n      await new Promise((resolve) => {\n        // Press Alt\n        const altDown = new KeyboardEvent('keydown', {\n          keyCode: 18,\n          which: 18,\n          code: 'AltLeft',\n          altKey: true,\n          bubbles: true,\n          cancelable: true,\n        });\n        document.body.dispatchEvent(altDown);\n\n        setTimeout(() => {\n          // Press V (with wrong keyCode but correct code)\n          const vDown = new KeyboardEvent('keydown', {\n            keyCode: 1052, // Wrong keyCode (М character on Cyrillic layout)\n            which: 1052,\n            code: 'KeyV', // Correct physical key code\n            altKey: true,\n            bubbles: true,\n            cancelable: true,\n          });\n          document.body.dispatchEvent(vDown);\n\n          setTimeout(() => {\n            // Release V\n            const vUp = new KeyboardEvent('keyup', {\n              keyCode: 1052,\n              which: 1052,\n              code: 'KeyV',\n              altKey: true,\n              bubbles: true,\n              cancelable: true,\n            });\n            document.body.dispatchEvent(vUp);\n\n            setTimeout(() => {\n              // Release Alt\n              const altUp = new KeyboardEvent('keyup', {\n                keyCode: 18,\n                which: 18,\n                code: 'AltLeft',\n                altKey: false,\n                bubbles: true,\n                cancelable: true,\n              });\n              document.body.dispatchEvent(altUp);\n\n              // Check downKeys after all keys released\n              setTimeout(() => {\n                results.downKeysAfterRelease =\n                  window.hotkeys.getPressedKeyCodes();\n                resolve();\n              }, 20);\n            }, 20);\n          }, 20);\n        }, 20);\n      });\n\n      // Cleanup\n      window.hotkeys.unbind('alt+m');\n      window.hotkeys.unbind('alt+v');\n\n      return results;\n    });\n\n    // Both hotkeys should be triggered\n    expect(result.altMTriggered).toBeTruthy();\n    expect(result.altVTriggered).toBeTruthy();\n\n    // Alt (18) + M (77) should be in downKeys during Alt+M\n    expect(result.downKeysAfterAltM).toEqual(expect.arrayContaining([18, 77]));\n\n    // Alt (18) + V (86) should be in downKeys during Alt+V\n    expect(result.downKeysAfterAltV).toEqual(expect.arrayContaining([18, 86]));\n\n    // After all keys released, downKeys should be empty (this was the bug)\n    expect(result.downKeysAfterRelease).toEqual([]);\n  });\n\n  afterAll(async () => {\n    // Collect coverage data\n    const jsCoverage = await page.coverage.stopJSCoverage();\n    \n    // Convert to Istanbul/Jest compatible coverage format\n    global.__coverage__ = global.__coverage__ || {};\n    \n    for (const entry of jsCoverage) {\n      // Only process hotkeys related files\n      if (entry.url.includes('hotkeys') && !entry.url.includes('test')) {\n        // Handle file path correctly, remove file:// prefix\n        const filePath = entry.url.replace(/^file:\\/\\//, '');\n        const sourceMap = createCoverageMap(entry, filePath);\n        \n        // Add to global coverage object\n        global.__coverage__[filePath] = sourceMap;\n      }\n    }\n    \n    // Calculate total coverage for logging\n    let totalBytes = 0;\n    let usedBytes = 0;\n    for (const entry of jsCoverage) {\n      totalBytes += entry.text.length;\n      for (const range of entry.ranges) {\n        usedBytes += range.end - range.start;\n      }\n    }\n    const coverage = (usedBytes / totalBytes) * 100;\n    console.log(`JS Coverage: ${coverage.toFixed(2)}%`);\n    \n    // Save coverage metrics to file for CI/Actions usage\n    const fs = require('fs');\n    const coverageDir = path.join(process.cwd(), 'coverage');\n    if (!fs.existsSync(coverageDir)) {\n      fs.mkdirSync(coverageDir, { recursive: true });\n    }\n\n    // Save coverage as simple text for easy shell script access\n    fs.writeFileSync(\n      path.join(coverageDir, 'js-coverage.txt'), \n      `${coverage.toFixed(2)}`\n    );\n    \n    console.log(`Coverage metrics saved to: ${path.join(coverageDir, 'js-coverage.txt')}`);\n    \n    await browser.close();\n  });\n});\n\njest.setTimeout(30000);\n\n\n// Helper function to convert Puppeteer coverage data to Istanbul format\nfunction createCoverageMap(entry, filePath) {\n  const fs = require('fs');\n  \n  // Read source file content to create detailed line mapping\n  let sourceText = '';\n  try {\n    sourceText = fs.readFileSync(filePath, 'utf8');\n  } catch (error) {\n    sourceText = entry.text; // Fallback to entry.text\n  }\n  \n  const lines = sourceText.split('\\n');\n  const statements = {};\n  const functions = {};\n  const branches = {};\n  const statementMap = {};\n  const functionMap = {};\n  const branchMap = {};\n  \n  let statementId = 1;\n  \n  // Create statement mapping for each line of code\n  lines.forEach((line, lineIndex) => {\n    const lineNumber = lineIndex + 1;\n    const lineLength = line.length;\n    \n    if (line.trim().length > 0) { // Only process non-empty lines\n      const id = String(statementId++);\n      statementMap[id] = {\n        start: { line: lineNumber, column: 0 },\n        end: { line: lineNumber, column: lineLength }\n      };\n      \n      // Check if this line is covered\n      let covered = 0;\n      let lineStartOffset = lines.slice(0, lineIndex).reduce((acc, l) => acc + l.length + 1, 0);\n      let lineEndOffset = lineStartOffset + lineLength;\n      \n      for (const range of entry.ranges) {\n        if (range.start <= lineEndOffset && range.end >= lineStartOffset) {\n          covered = Math.max(covered, range.count || 1);\n        }\n      }\n      \n      statements[id] = covered;\n    }\n  });\n  \n  // If no statement mappings exist, create default ones\n  if (Object.keys(statements).length === 0) {\n    statementMap['1'] = {\n      start: { line: 1, column: 0 },\n      end: { line: 1, column: 100 }\n    };\n    statements['1'] = entry.ranges.length > 0 ? 1 : 0;\n  }\n  \n  // Add some function mappings (based on simple function detection)\n  lines.forEach((line, lineIndex) => {\n    if (line.includes('function') || line.includes('=>') || line.includes('class')) {\n      const lineNumber = lineIndex + 1;\n      const functionId = String(Object.keys(functionMap).length + 1);\n      \n      functionMap[functionId] = {\n        name: `function_${functionId}`,\n        decl: { \n          start: { line: lineNumber, column: 0 },\n          end: { line: lineNumber, column: line.length }\n        },\n        loc: { \n          start: { line: lineNumber, column: 0 },\n          end: { line: lineNumber, column: line.length }\n        },\n        line: lineNumber\n      };\n      \n      // Check if function is executed\n      let functionCovered = 0;\n      let lineStartOffset = lines.slice(0, lineIndex).reduce((acc, l) => acc + l.length + 1, 0);\n      let lineEndOffset = lineStartOffset + line.length;\n      \n      for (const range of entry.ranges) {\n        if (range.start <= lineEndOffset && range.end >= lineStartOffset && range.count > 0) {\n          functionCovered = range.count;\n          break;\n        }\n      }\n      \n      functions[functionId] = functionCovered;\n    }\n  });\n  \n  return {\n    path: filePath,\n    statementMap,\n    fnMap: functionMap,\n    branchMap,\n    s: statements,\n    f: functions,\n    b: branches,\n    _coverageSchema: \"1a1c01bbd47fc00a2c39e90264f33305004495a9\",\n    hash: \"coverage-\" + Date.now()\n  };\n}"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2015\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"ES2015\", \"DOM\"],\n    \"declaration\": false,\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./src\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n    \"noImplicitAny\": true,\n    \"strictNullChecks\": true,\n    \"strictFunctionTypes\": true,\n    \"strictBindCallApply\": true,\n    \"strictPropertyInitialization\": true,\n    \"noImplicitThis\": true,\n    \"alwaysStrict\": true\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"node_modules\", \"test\", \"website\"]\n}\n"
  },
  {
    "path": "vite.config.ts",
    "content": "import { defineConfig } from \"vite\";\nimport { resolve } from \"path\";\nimport dts from \"vite-plugin-dts\";\nimport { readFileSync } from \"fs\";\nimport terser from \"@rollup/plugin-terser\";\n\nconst pkg = JSON.parse(readFileSync(\"./package.json\", \"utf-8\"));\n\n// Generate banner\nconst banner = `/*!\n * ${pkg.name} v${pkg.version}\n * ${pkg.description}\n * \n * @author ${pkg.author}\n * @license ${pkg.license}\n * @homepage ${pkg.homepage}\n */`;\n\nexport default defineConfig({\n  publicDir: false,\n  plugins: [dts({ rollupTypes: true, tsconfigPath: \"./tsconfig.json\" })],\n  build: {\n    lib: {\n      entry: resolve(__dirname, \"src/index.ts\"),\n      name: \"hotkeys\",\n    },\n    rollupOptions: {\n      output: [\n        {\n          format: \"es\",\n          banner,\n          entryFileNames: \"hotkeys-js.js\",\n        },\n        {\n          format: \"umd\",\n          banner,\n          entryFileNames: \"hotkeys-js.umd.cjs\",\n          name: \"hotkeys\",\n          footer: `\n// CommonJS compatibility\nif (typeof module === \"object\" && module.exports) {\n  module.exports.default = module.exports;\n}\n// AMD compatibility  \nif (typeof define === \"function\" && define.amd) {\n  define([], function() { return hotkeys; });\n}`,\n        },\n        {\n          format: \"iife\",\n          banner,\n          entryFileNames: \"hotkeys-js.min.js\",\n          name: \"hotkeys\",\n          plugins: [terser()], // 仅对 IIFE 格式进行压缩\n        },\n      ],\n    },\n    minify: false,\n    sourcemap: true,\n    target: \"es2015\",\n    emptyOutDir: false, // Don't empty the output directory\n  },\n});\n"
  },
  {
    "path": "website/App.tsx",
    "content": "import React, { useEffect, useState } from 'react';\nimport GithubCorner from '@uiw/react-github-corners';\nimport { Github } from '@uiw/react-shields';\nimport MarkdownPreview from '@uiw/react-markdown-preview';\nimport KeyBoard from '@uiw/react-mac-keyboard';\nimport '@wcj/dark-mode';\nimport Footer from './components/Footer';\nimport styles from './styles/index.module.css';\nimport DocumentStr from '../README.md?raw';\nimport DocumentChinese from '../README-zh.md?raw';\nimport hotkeys from '../src';\nimport pkg from '../package.json';\n\nconst englishDescription = `A robust Javascript library for capturing keyboard input and key combinations entered. It has no dependencies. Try to press your keyboard, The following button will highlight.`;\nconst chineseDescription = `一个强大的Javascript库，用于捕获键盘输入和键组合。它没有依赖。尝试按下你的键盘，下面的按钮将会高亮显示。`;\n\nexport default function AppRoot() {\n  const [keyCode, setKeyCode] = useState<number[]>([]);\n  const [keyStr, setKeyStr] = useState<(string | number)[]>([]);\n  // 检查URL参数来决定初始语言\n  const getInitialLanguage = () => {\n    const urlParams = new URLSearchParams(window.location.search);\n    const lang = urlParams.get('lang');\n    return lang === 'zh' || lang === 'zh-CN';\n  };\n  const [isChineseDoc, setIsChineseDoc] = useState<boolean>(getInitialLanguage());\n  const [documentContent, setDocumentContent] = useState<string>(\n    getInitialLanguage() ? DocumentChinese : DocumentStr\n  );\n\n  useEffect(() => {\n    document.addEventListener('keyup', onKeyUpEvent);\n\n    function pkeys(keys: number[], key: number): number[] {\n      if (keys.indexOf(key) === -1) keys.push(key);\n      return keys;\n    }\n    function pkeysStr(\n      keysStr: (string | number)[],\n      key: string | number\n    ): (string | number)[] {\n      if (keysStr.indexOf(key) === -1) keysStr.push(key);\n      return keysStr;\n    }\n\n    hotkeys('*', (evn) => {\n      evn.preventDefault();\n      const keys: number[] = [];\n      const kStr: (string | number)[] = [];\n      if (hotkeys.shift) {\n        pkeys(keys, 16);\n        pkeysStr(kStr, 'shift');\n      }\n      if (hotkeys.ctrl) {\n        pkeys(keys, 17);\n        pkeysStr(kStr, 'ctrl');\n      }\n      if (hotkeys.alt) {\n        pkeys(keys, 18);\n        pkeysStr(kStr, 'alt');\n      }\n      if (hotkeys.control) {\n        pkeys(keys, 17);\n        pkeysStr(kStr, 'control');\n      }\n      if (hotkeys.command) {\n        pkeys(keys, 91);\n        pkeysStr(kStr, 'command');\n      }\n      kStr.push(evn.keyCode);\n      if (keys.indexOf(evn.keyCode) === -1) keys.push(evn.keyCode);\n\n      setKeyCode(keys);\n      setKeyStr(kStr);\n    });\n\n    return () => {\n      document.removeEventListener('keyup', onKeyUpEvent);\n    };\n  }, []);\n\n  const openVersionWebsite = (e: React.ChangeEvent<HTMLSelectElement>) => {\n    if (e.target && e.target.value) {\n      window.location.href = e.target.value;\n    }\n  };\n\n  const onKeyUpEvent = () => {\n    setKeyCode([]);\n    setKeyStr([]);\n  };\n\n  const toggleLanguage = () => {\n    const newIsChineseDoc = !isChineseDoc;\n    setIsChineseDoc(newIsChineseDoc);\n    setDocumentContent(newIsChineseDoc ? DocumentChinese : DocumentStr);\n\n    // 更新URL参数\n    const url = new URL(window.location.href);\n    if (newIsChineseDoc) {\n      url.searchParams.set('lang', 'zh');\n    } else {\n      url.searchParams.delete('lang');\n    }\n    window.history.pushState({}, '', url.toString());\n  };\n\n  const onKeyBoardMouseDown = (item: { keycode: number }) => {\n    if (item.keycode > -1) {\n      setKeyStr([item.keycode]);\n    }\n  };\n\n  const onKeyBoardMouseUp = () => setKeyStr([]);\n\n  return (\n    <div>\n      <div className={styles.tools}>\n        <select className={styles.version} onChange={openVersionWebsite}>\n          <option value=\"https://jaywcjlove.github.io/hotkeys-js\">\n            v{pkg.version}\n          </option>\n          <option value=\"https://raw.githack.com/jaywcjlove/hotkeys/dbdc32d/index.html\">\n            v3.13.15\n          </option>\n          <option value=\"https://unpkg.com/hotkeys-js@3.7.5/doc/index.html\">\n            v3.7.5\n          </option>\n          <option value=\"https://unpkg.com/hotkeys-js@3.4.2/doc/index.html\">\n            v3.4.2\n          </option>\n          <option value=\"https://unpkg.com/hotkeys-js@2.0.10/doc/index.html\">\n            v2.0.10\n          </option>\n        </select>\n        <dark-mode permanent />\n      </div>\n      {keyStr.length > -1 && (\n        <div className={styles.keyCodeInfo}>\n          {keyStr.map((item) => (\n            <span key={`${item}`}>{item}</span>\n          ))}\n        </div>\n      )}\n      <GithubCorner\n        href=\"https://github.com/jaywcjlove/hotkeys-js\"\n        target=\"__blank\"\n      />\n      <div className={styles.header}>\n        <a className={styles.title} href=\"http://jaywcjlove.github.io\">HotKeys.js</a>\n        <div className={styles.github}>\n          <a href=\"https://www.npmjs.com/package/hotkeys-js\">\n            <button type=\"button\">On NPM</button>\n          </a>&nbsp;\n          <a href=\"https://github.com/jaywcjlove/hotkeys-js/\">\n            <button type=\"button\">Fork on Github</button>\n          </a>&nbsp;\n          <a href=\"https://github.com/jaywcjlove/hotkeys-js/\">\n            <button type=\"button\">Doc on Github</button>\n          </a>&nbsp;\n          <a href=\"https://gitee.com/jaywcjlove/hotkeys/\">\n            <button type=\"button\">Doc on Gitee</button>\n          </a>&nbsp;\n          <button\n            onClick={toggleLanguage}\n            title={isChineseDoc ? 'Switch to English' : '切换到中文'}\n          >\n            {isChineseDoc ? 'EN' : '🇨🇳中文'}\n          </button>\n        </div>\n        <div className={styles.info}>\n          {isChineseDoc ? chineseDescription : englishDescription}\n        </div>\n      </div>\n      <KeyBoard\n        style={{ top: -40 }}\n        onMouseDown={(_, item) => onKeyBoardMouseDown(item)}\n        onMouseUp={onKeyBoardMouseUp}\n        keyCode={keyCode}\n      />\n      <MarkdownPreview\n        style={{ maxWidth: 995, margin: '0 auto' }}\n        source={documentContent}\n      />\n      <Footer\n        name=\"Kenny Wong\"\n        href=\"http://jaywcjlove.github.io\"\n        year=\"2015-present\"\n      >\n        <Github user=\"jaywcjlove\" repo=\"hotkeys-js\">\n          <Github.Social\n            href=\"https://github.com/jaywcjlove/hotkeys-js\"\n            type=\"forks\"\n          />\n          <Github.Social\n            href=\"https://github.com/jaywcjlove/hotkeys-js\"\n            type=\"stars\"\n          />\n          <Github.Social\n            href=\"https://github.com/jaywcjlove/hotkeys-js\"\n            type=\"watchers\"\n          />\n          <Github.Social\n            href=\"https://github.com/jaywcjlove/hotkeys-js\"\n            type=\"followers\"\n          />\n        </Github>\n      </Footer>\n    </div>\n  );\n}\n"
  },
  {
    "path": "website/components/Footer.module.css",
    "content": ".footer {\n    text-align: center;\n    padding: 15px 0 100px 0;\n    font-size: 12px;\n    line-height: 20px;\n}\n\n"
  },
  {
    "path": "website/components/Footer.tsx",
    "content": "import React from 'react';\nimport style from './Footer.module.css';\n\ninterface FooterProps {\n  name: string;\n  href: string;\n  year: string;\n  children?: React.ReactNode;\n}\n\nexport default function Footer({ name, href, year, children }: FooterProps) {\n  return (\n    <div className={style.footer}>\n      {children}\n      <div>\n        Licensed under MIT. (Yes it&acute;s free and\n        <a href=\"https://github.com/jaywcjlove/hotkeys\"> open-sourced</a>\n        )\n      </div>\n      <div>\n        ©\n        <a target=\"_blank\" rel=\"noopener noreferrer\" href={href}>{name}</a>\n        {year}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "website/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <title>\n      hotkeys.js - A robust Javascript library for capturing keyboard input.\n    </title>\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width, initial-scale=1, maximum-scale=1\"\n    />\n    <meta\n      name=\"keywords\"\n      content=\"hotkey,hotkeys,hotkeysjs,key,keys,keyboard,shortcuts,keypress\"\n    />\n    <meta\n      name=\"description\"\n      content=\"A robust Javascript library for capturing keyboard input and key combinations entered. It has no dependencies. Try to press your keyboard, The following button will highlight.\"\n    />\n  </head>\n\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/index.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "website/index.tsx",
    "content": "import { createRoot } from 'react-dom/client';\nimport App from './App';\nimport './styles/reset.css';\n\nconst container = document.getElementById('root');\nif (!container) {\n  throw new Error('Failed to find the root element');\n}\nconst root = createRoot(container);\nroot.render(<App />);\n"
  },
  {
    "path": "website/styles/index.module.css",
    "content": ".tools {\n  position: absolute;\n  margin: 15px 0 0 15px;\n}\n\n.version {\n  appearance: none;\n  cursor: pointer;\n  padding: 3px 6px;\n  margin-right: 10px;\n  vertical-align: middle;\n  box-sizing: border-box;\n  border: none;\n  border-radius: 3px;\n}\n\n.keyCodeInfo {\n  position: fixed;\n  bottom: 10px;\n  left: 10px;\n  z-index: 9999;\n}\n.keyCodeInfo span + span {\n  margin-left: 10px;\n}\n.keyCodeInfo span {\n  display: inline-block;\n  background: #eff0f2;\n  border-radius: 3px;\n  padding: 5px 10px;\n  border-top: 1px solid #f5f5f5;\n  box-shadow: inset 0 0 25px #e8e8e8, 0 1px 0 #c3c3c3, 0 2px 0 #c9c9c9, 0 2px 3px #333;\n  text-shadow: 0px 1px 0px #f5f5f5;\n}\n\n.header {\n  background-color: var(--color-header-bg);\n  transition: all  0.3s;\n  padding: 74px 0 60px 0;\n  text-align: center;\n}\n.header .title {\n  text-align: center;\n  font-size: 53px;\n  font-weight: bold;\n  color: #fff;\n  text-shadow: -3px -3px 0 #676767, -3px -3px 0 #676767, -3px -3px 0 #676767, -2px -2px 0 #676767, -2px -2px 0 #676767, -1px -1px 0 #676767;\n  transition: all 0.3s;\n}\n.header .title:hover {\n  text-shadow: -3px -3px 0 #363636, -3px -3px 0 #363636, -3px -3px 0 #363636, -2px -2px 0 #363636, -2px -2px 0 #363636, -1px -1px 0 #363636;\n}\n.header .lang {\n  text-align: center;\n  padding-top: 20px;\n}\n.header .lang a {\n  color: #fff;\n  margin: 0 5px;\n}\n.header .info {\n  padding: 25px 0 27px 0;\n  text-align: center;\n  font-size: 23px;\n  line-height: 29px;\n  color: #878787;\n  max-width: 702px;\n  margin: 0 auto;\n}\n.header .github {\n  text-align: center;\n  padding: 60px 0 22px 0;\n}\n.header .github button {\n  position: relative;\n  display: inline-block;\n  border: 1px solid #ddd;\n  border-bottom-color: #bbb;\n  padding: 0 15px;\n  font-family: Helvetica, arial, freesans, clean, sans-serif;\n  font-size: 12px;\n  font-weight: bold;\n  line-height: 23px;\n  color: #666;\n  text-shadow: 0 1px rgba(255, 255, 255, 0.9);\n  cursor: pointer;\n  border-radius: 3px;\n  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n  background: whitesmoke;\n  background-image: linear-gradient(-90deg, whitesmoke 0%, #e5e5e5 100%);\n}\n.header .github button:hover {\n  color: #337797;\n  border: 1px solid #CBE3EE;\n  border-bottom-color: #97C7DD;\n  background: #f0f7fa;\n  background-image: -webkit-linear-gradient(-90deg, #f0f7fa 0%, #d8eaf2 100%);\n}\n\n"
  },
  {
    "path": "website/styles/reset.css",
    "content": "[data-color-mode*='dark'], [data-color-mode*='dark'] body {\n  --color-header-bg: var(--color-theme-bg);\n}\n[data-color-mode*='light'], [data-color-mode*='light'] body {\n  --color-header-bg: #333;\n  background: #f8f8f8 url(../assets/bg.jpg) repeat top left;\n}\n\n*, *:before, *:after {\n  box-sizing: inherit !important;\n}\n\nbody {\n  font-family: \"PingHei\",\"Lucida Grande\", \"Lucida Sans Unicode\", \"STHeitiSC-Light\", \"Helvetica\",\"Arial\",\"Verdana\",\"sans-serif\";\n  transition: all  0.3s;\n  margin: 0;\n}\n\na {\n  text-decoration: none;\n}\n\n.wmde-markdown {\n  background-color: transparent !important;\n}\n.wmde-markdown img {\n  background-color: transparent !important;\n}\n\n"
  },
  {
    "path": "website/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"types\": [\"vite/client\"]\n  },\n  \"include\": [\"./**/*\", \"./css-modules.d.ts\", \"../src/**/*\"],\n  \"exclude\": [\"node_modules\", \"../dist\", \"../test\"]\n}\n\n"
  },
  {
    "path": "website/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n\ndeclare module '*.module.css' {\n  const classes: { readonly [key: string]: string };\n  export default classes;\n}\n\ndeclare module '*.css' {\n  const content: string;\n  export default content;\n}\n\nimport 'react';\n\ndeclare module 'react' {\n  namespace JSX {\n    interface IntrinsicElements {\n      'dark-mode': React.DetailedHTMLProps<\n        React.HTMLAttributes<HTMLElement> & {\n          permanent?: boolean;\n        },\n        HTMLElement\n      >;\n    }\n  }\n}\n"
  },
  {
    "path": "website/vite.config.ts",
    "content": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport { resolve } from 'path';\n\nexport default defineConfig({\n  base: './',\n  plugins: [\n    react({\n      jsxRuntime: 'automatic',\n    }),\n  ],\n  assetsInclude: ['**/*.md'],\n  root: resolve(__dirname),\n  publicDir: resolve(__dirname, '../public'),\n  build: {\n    outDir: resolve(__dirname, '../doc'),\n    emptyOutDir: true,\n    chunkSizeWarningLimit: 1000,\n    rollupOptions: {\n      output: {\n        manualChunks: {\n          // React 核心库单独分割\n          'react-vendor': ['react', 'react-dom'],\n          // UI 组件库分割\n          'markdown-vendor': ['@uiw/react-markdown-preview'],\n          'uiw-components': [\n            '@uiw/react-github-corners',\n            '@uiw/react-shields',\n            '@uiw/react-mac-keyboard'\n          ],\n          // 主题和样式相关\n          'theme-vendor': ['@wcj/dark-mode'],\n        },\n        // 自定义chunk命名和文件结构\n        chunkFileNames: () => {\n          return `assets/[name].js`;\n        },\n        assetFileNames: 'assets/[name]-[hash].[ext]'\n      }\n    },\n    // 启用更好的压缩\n    minify: 'terser',\n    terserOptions: {\n      compress: {\n        drop_console: true, // 移除console.log\n        drop_debugger: true,\n      },\n    },\n    // 启用source map用于调试\n    sourcemap: false, // 生产环境关闭source map减少文件大小\n  },\n  resolve: {\n    alias: {\n      '@': resolve(__dirname),\n    },\n  },\n  server: {\n    port: 3000,\n    open: true,\n  },\n});\n"
  }
]