{"id":13808662,"url":"https://github.com/ngneat/hotkeys","last_synced_at":"2025-05-15T01:08:54.184Z","repository":{"id":38083803,"uuid":"255298553","full_name":"ngneat/hotkeys","owner":"ngneat","description":"🤖 A declarative library for handling hotkeys in Angular applications","archived":false,"fork":false,"pushed_at":"2025-02-04T03:24:30.000Z","size":1447,"stargazers_count":339,"open_issues_count":30,"forks_count":19,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-05-07T18:15:40.931Z","etag":null,"topics":["angular","hotkeys","shortcuts"],"latest_commit_sha":null,"homepage":"https://netbasal.com/diy-keyboard-shortcuts-in-your-angular-application-4704734547a2","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ngneat.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"ngneat"}},"created_at":"2020-04-13T10:43:05.000Z","updated_at":"2025-04-11T07:47:16.000Z","dependencies_parsed_at":"2023-02-18T16:32:40.545Z","dependency_job_id":"283f8e69-8da8-4d9d-9f0e-9894041ace50","html_url":"https://github.com/ngneat/hotkeys","commit_stats":{"total_commits":105,"total_committers":14,"mean_commits":7.5,"dds":0.8,"last_synced_commit":"a9c011a484bbdcfa4b13db990a8bb935c65d230b"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngneat%2Fhotkeys","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngneat%2Fhotkeys/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngneat%2Fhotkeys/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngneat%2Fhotkeys/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ngneat","download_url":"https://codeload.github.com/ngneat/hotkeys/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253866108,"owners_count":21976039,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","hotkeys","shortcuts"],"created_at":"2024-08-04T01:01:48.415Z","updated_at":"2025-05-15T01:08:54.139Z","avatar_url":"https://github.com/ngneat.png","language":"TypeScript","funding_links":["https://github.com/sponsors/ngneat"],"categories":["Third Party Components","Projects by main language"],"sub_categories":["Keyboard Mouse","Old Projects"],"readme":"\u003cp align=\"center\"\u003e\n \u003cimg width=\"20%\" height=\"20%\" src=\"./logo.svg\"\u003e\n\u003c/p\u003e\n\n\u003cbr /\u003e\n\n![Test](https://github.com/ngneat/hotkeys/workflows/Test/badge.svg)\n[![MIT](https://img.shields.io/packagist/l/doctrine/orm.svg?style=flat-square)]()\n[![commitizen](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?style=flat-square)]()\n[![PRs](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)]()\n[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)\n[![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-)\n[![ngneat](https://img.shields.io/badge/@-ngneat-383636?style=flat-square\u0026labelColor=8f68d4)](https://github.com/ngneat/)\n[![spectator](https://img.shields.io/badge/tested%20with-spectator-2196F3.svg?style=flat-square)]()\n\n\u003e Shortcut like a pro!\n\nA declarative library for handling hotkeys in Angular applications.\n\nWeb apps are getting closer and closer to be desktop-class applications. With this in mind, it makes sense to add hotkeys for those power users that are looking to navigate their favorite websites using hotkeys just as they do on their regular native apps. To help you have a better experience we developed Hotkeys.\n\n## Features\n\n- ✅ Support Element Scope\n- ✅ Support Global Listeners\n- ✅ Platform Agnostic\n- ✅ Hotkeys Cheatsheet\n\n## Table of Contents\n\n- [Compatibility](#compatibility-with-angular-versions)\n- [Installation](#installation)\n- [Usage](#usage)\n- [FAQ](#faq)\n\n## Compatibility with Angular Versions\n\n| @ngneat/hotkeys | Angular |\n|-----------------|---------|\n| 3.x.x           | \u003e=17.2  |\n| 2.x.x           | \u003e=16    |\n| 1.3.x           | \u003e=14    |\n| 1.2.x           | \u003c=13    |\n\n## Installation\n\n`npm install @ngneat/hotkeys`\n\n## Usage\n\n### Module\nAdd `HotkeysModule` in your `AppModule`:\n\n```ts\nimport { HotkeysModule } from '@ngneat/hotkeys';\n\n@NgModule({\n  imports: [HotkeysModule]\n})\nexport class AppModule {}\n```\n\n### Standalone\nAdd `HotkeysService` in the standalone components :\n\n```ts\n@Component({\n  standalone: true,\n  imports: [HotkeysDirective],\n})\nexport class AppComponent {}\n```\n\nNow you have two ways to start adding shortcuts to your application:\n\n## Hotkeys Directive\n\n```html\n\u003cinput hotkeys=\"meta.a\" (hotkey)=\"handleHotkey($event)\" /\u003e\n```\n\n\u003e Hotkeys take care of transforming keys from macOS to Linux and Windows and vice-versa.\n\nAdditionally, the directive accepts five more `input`s:\n\n- `hotkeysGroup` - define the group name.\n- `hotkeysDescription` - add a description.\n- `hotkeysOptions` - See [Options](#options)\n- `isSequence` - indicate hotkey is a sequence of keys.\n- `isGlobal` - the hotkey event fires even if the element is not focused or active\n\nFor example:\n\n\u003c!-- prettier-ignore --\u003e\n```html\n\u003cinput hotkeys=\"meta.n\" \n      hotkeysGroup=\"File\" \n      hotkeysDescription=\"New Document\" \n      (hotkey)=\"handleHotkey($event)\"/\u003e\n\n\u003cbutton hotkeys=\"shift.f\" isGlobal (hotkey)=\"handleHotkey($event)\"\u003eClick me\u003c/button\u003e\n```\n\nExample sequence hotkey:\n\n```html\n\u003cinput hotkeys=\"g\u003ei\" hotkeysGroup=\"Navigate\" hotkeysDescription=\"Go to Inbox\" (hotkey)=\"handleHotkey($event)\"\n```\n\n## Hotkeys Service\n\nThis is a global service that can be injected anywhere:\n\n```ts\nimport { HotkeysService } from '@ngneat/hotkeys';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html',\n  styleUrls: ['./app.component.css']\n})\nexport class AppComponent {\n  constructor(private hotkeys: HotkeysService) {}\n\n  ngOnInit() {\n    this.hotkeys.addShortcut({ keys: 'meta.a' }).subscribe(e =\u003e console.log('Hotkey', e));\n    this.hotkeys.addSequenceShortcut({ keys: 'g\u003ei' }).subscribe(e =\u003e console.log('Hotkey', e));\n  }\n}\n```\n\n### Options\n\nThere are additional properties we can provide:\n\n```ts\ninterface Options {\n  // The group name\n  group: string;\n  // hotkey target element (defaults to `document`)\n  element: HTMLElement;\n  // The type of event (defaults to `keydown`)\n  trigger: 'keydown' | 'keyup';\n  // Allow input, textarea and select as key event sources (defaults to []).\n  // It can be 'INPUT', 'TEXTAREA', 'SELECT' or 'CONTENTEDITABLE'.\n  allowIn: AllowInElement[];\n  // hotkey description\n  description: string;\n  // Included in the shortcut list to be display in the help dialog (defaults to `true`)\n  showInHelpMenu: boolean;\n  // Whether to prevent the default behavior of the event. (defaults to `true`)\n  preventDefault: boolean;\n}\n```\n\n### `onShortcut`\n\nListen to any registered hotkey. For example:\n\n```ts\nconst unsubscribe = this.hotkeys.onShortcut((event, key, target) =\u003e console.log('callback', key));\n\n// When you're done listening, unsubscribe\nunsubscribe();\n```\n\n### `registerHelpModal`\n\nDisplay a help dialog listing all visible hotkeys:\n\n```ts\nimport { NgbModal } from '@ng-bootstrap/ng-bootstrap';\nimport { HotkeysHelpComponent, HotkeysService } from '@ngneat/hotkeys';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html',\n  styleUrls: ['./app.component.css']\n})\nexport class AppComponent implements AfterViewInit {\n  constructor(private hotkeys: HotkeysService, private dialog: NgbModal) {}\n\n  ngAfterViewInit() {\n    this.hotkeys.registerHelpModal(() =\u003e {\n      const ref = this.modalService.open(HotkeysHelpComponent, { size: 'lg' });\n      ref.componentInstance.title = 'Custom Shortcuts Title';\n      ref.componentInstance.dismiss.subscribe(() =\u003e ref.close());\n    });\n  }\n}\n```\n\nIt accepts a second input that allows defining the hotkey that should open the dialog. The default shortcut is `Shift + ?`. Here's how `HotkeysHelpComponent` looks like:\n```\n\u003cp align=\"center\"\u003e\n \u003cimg width=\"50%\" height=\"50%\" src=\"./help_screenshot.png\"\u003e\n\u003c/p\u003e\n```\n\nYou can also provide a custom component. To help you with that, the service exposes the `getShortcuts` method.\n\n### `removeShortcuts`\n\nRemove previously registered shortcuts.\n\n```ts\n// Remove a single shortcut\nthis.hotkeys.removeShortcuts('meta.a');\n// Remove several shortcuts\nthis.hotkeys.removeShortcuts(['meta.1', 'meta.2']);\n```\n\n### `pauseShortcuts and resumeShortcuts`\n\nPause the handling of all shortcuts. This is especially useful to prevent hotkeys from firing when a modal or sidebar is open.\n\n```ts\n// hotkey subscriptions won't emmit, and hotkeys callbacks won't be called\nthis.hotkeys.pauseShortcuts();\n// hotkeys will work again\nthis.hotkeys.resumeShortcuts();\n// get the current state of the hotkeys\nthis.hotkeys.isPaused();\n```\n\n### `setSequenceDebounce`\n\nSet the number of milliseconds to debounce a sequence of keys\n\n```ts\nthis.hotkeys.setSequenceDebounce(500);\n```\n\n## Hotkeys Shortcut Pipe\n\nThe `hotkeysShortcut` formats the shortcuts when presenting them in a custom help screen:\n\n```html\n\u003cdiv class=\"help-dialog-shortcut-key\"\u003e\n  \u003ckbd [innerHTML]=\"hotkey.keys | hotkeysShortcut\"\u003e\u003c/kbd\u003e\n\u003c/div\u003e\n```\n\nThe pipe accepts and additional parameter the way key combinations are separated. By default, the separator is `+`. In the following example, a `-` is used as separator.\n\n```html\n\u003cdiv class=\"help-dialog-shortcut-key\"\u003e\n  \u003ckbd [innerHTML]=\"hotkey.keys | hotkeysShortcut: '-'\"\u003e\u003c/kbd\u003e\n\u003c/div\u003e\n```\n\nIt is also possible to alias keys to custom strings. For example, the macos key for enter is `⌤`. To display it as `Enter`, you can use the following:\n\n```html\n\u003cdiv class=\"help-dialog-shortcut-key\"\u003e\n  \u003ckbd [innerHTML]=\"hotkey.keys | hotkeysShortcut: '-' : ' then ': {enter: 'Enter'}\"\u003e\u003c/kbd\u003e\n\u003c/div\u003e\n\n```html\n\n## Allowing hotkeys in form elements\n\nBy default, the library prevents hotkey callbacks from firing when their event originates from an `input`, `select`, or `textarea` element or any elements that are contenteditable. To enable hotkeys in these elements, specify them in the `allowIn` parameter:\n\n```ts\nimport { HotkeysService } from '@ngneat/hotkeys';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html',\n  styleUrls: ['./app.component.css']\n})\nexport class AppComponent {\n  constructor(private hotkeys: HotkeysService) {}\n\n  ngOnInit() {\n    this.hotkeys\n      .addShortcut({ keys: 'meta.a', allowIn: ['INPUT', 'SELECT', 'TEXTAREA', 'CONTENTEDITABLE'] })\n      .subscribe(e =\u003e console.log('Hotkey', e));\n  }\n}\n```\n\nIt's possible to enable them in the template as well:\n\n\u003c!-- prettier-ignore --\u003e\n```html\n\u003cinput hotkeys=\"meta.n\" \n      hotkeysGroup=\"File\" \n      hotkeysDescription=\"New Document\" \n      hotkeysOptions=\"{allowIn: ['INPUT','SELECT', 'TEXTAREA', 'CONTENTEDITABLE']}\" \n      (hotkey)=\"handleHotkey($event)\"\n```\n\nThat's all for now! Make sure to check out the `playground` inside the `src` [folder](https://github.com/ngneat/hotkeys/tree/master/src/app).\n\n## FAQ\n\n**Can I define duplicated hotkeys?**\n\nNo. It's not possible to define a hotkey multiple times. Each hotkey has a description and a group, so it doesn't make sense assigning a hotkey to different actions.\n\n**Why am I not receiving any event?**\n\nIf you've added a hotkey to a particular element of your DOM, make sure it's focusable. Otherwise, hotkeys cannot capture any keyboard event.\n\n## How to ...\n\n**Listening to the same shortcut in different places.**\n\nYou can always use `onShortcut`. This method allows listening to all registered hotkeys without affecting the original definition.\n\n## Contributors ✨\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --\u003e\n\u003c!-- prettier-ignore-start --\u003e\n\u003c!-- markdownlint-disable --\u003e\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"http://carlosvilacha.com\"\u003e\u003cimg src=\"https://avatars3.githubusercontent.com/u/1565222?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eCarlos Vilacha\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/@ngneat/hotkeys/commits?author=flatstadt\" title=\"Code\"\u003e💻\u003c/a\u003e \u003ca href=\"#content-flatstadt\" title=\"Content\"\u003e🖋\u003c/a\u003e \u003ca href=\"https://github.com/@ngneat/hotkeys/commits?author=flatstadt\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://www.netbasal.com\"\u003e\u003cimg src=\"https://avatars1.githubusercontent.com/u/6745730?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eNetanel Basal\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"#blog-NetanelBasal\" title=\"Blogposts\"\u003e📝\u003c/a\u003e \u003ca href=\"https://github.com/@ngneat/hotkeys/commits?author=NetanelBasal\" title=\"Code\"\u003e💻\u003c/a\u003e \u003ca href=\"https://github.com/@ngneat/hotkeys/commits?author=NetanelBasal\" title=\"Documentation\"\u003e📖\u003c/a\u003e \u003ca href=\"#ideas-NetanelBasal\" title=\"Ideas, Planning, \u0026 Feedback\"\u003e🤔\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/alvaromartmart\"\u003e\u003cimg src=\"https://avatars1.githubusercontent.com/u/18287793?s=120\u0026v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eÁlvaro Martínez\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/@ngneat/hotkeys/commits?author=alvaromartmart\" title=\"Code\"\u003e💻\u003c/a\u003e \u003ca href=\"https://github.com/@ngneat/hotkeys/commits?author=alvaromartmart\" title=\"Documentation\"\u003e📖\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n\u003c!-- markdownlint-enable --\u003e\n\u003c!-- prettier-ignore-end --\u003e\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:END --\u003e\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fngneat%2Fhotkeys","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fngneat%2Fhotkeys","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fngneat%2Fhotkeys/lists"}