UNPKG

angular2-hotkeys

Version:

Angular 16 and Ivy Compatible. Older versions might work but isn't officially tested.

344 lines (334 loc) 18.3 kB
import * as i0 from '@angular/core'; import { InjectionToken, Injectable, Inject, Directive, Input, Component, NgModule } from '@angular/core'; import { Subject, BehaviorSubject } from 'rxjs'; import * as Mousetrap from 'mousetrap'; import * as i2 from '@angular/common'; import { CommonModule } from '@angular/common'; class Hotkey { static symbolize(combo) { const map = { command: '\u2318', shift: '\u21E7', left: '\u2190', right: '\u2192', up: '\u2191', down: '\u2193', // tslint:disable-next-line:object-literal-key-quotes 'return': '\u23CE', backspace: '\u232B' // ⌫ }; const comboSplit = combo.split('+'); for (let i = 0; i < comboSplit.length; i++) { // try to resolve command / ctrl based on OS: if (comboSplit[i] === 'mod') { if (window.navigator && window.navigator.platform.indexOf('Mac') >= 0) { comboSplit[i] = 'command'; } else { comboSplit[i] = 'ctrl'; } } comboSplit[i] = map[comboSplit[i]] || comboSplit[i]; } return comboSplit.join(' + '); } /** * Creates a new Hotkey for Mousetrap binding * * @param combo mousetrap key binding * @param callback method to call when key is pressed * @param allowIn an array of tag names to allow this combo in ('INPUT', 'SELECT', and/or 'TEXTAREA') * @param description description for the help menu * @param action the type of event to listen for (for mousetrap) * @param persistent if true, the binding is preserved upon route changes */ constructor(combo, callback, allowIn, description, action, persistent) { this.combo = combo; this.callback = callback; this.allowIn = allowIn; this.description = description; this.action = action; this.persistent = persistent; this.combo = (Array.isArray(combo) ? combo : [combo]); this.allowIn = allowIn || []; this.description = description || ''; } get formatted() { if (!this.formattedHotkey) { const sequence = [...this.combo]; for (let i = 0; i < sequence.length; i++) { sequence[i] = Hotkey.symbolize(sequence[i]); } this.formattedHotkey = sequence; } return this.formattedHotkey; } } const HotkeyOptions = new InjectionToken('HotkeyOptions'); class HotkeysService { constructor(options) { this.options = options; this.hotkeys = []; this.pausedHotkeys = []; this.cheatSheetToggle = new Subject(); this.preventIn = ['INPUT', 'SELECT', 'TEXTAREA']; // noinspection JSUnusedGlobalSymbols,JSUnusedLocalSymbols Mousetrap.prototype.stopCallback = (event, element, combo, callback) => { // if the element has the class "mousetrap" then no need to stop if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) { return false; } return (element.contentEditable && element.contentEditable === 'true'); }; this.mousetrap = new Mousetrap.default(); this.initCheatSheet(); } initCheatSheet() { if (!this.options.disableCheatSheet) { this.add(new Hotkey(this.options.cheatSheetHotkey || '?', function (_) { this.cheatSheetToggle.next(); }.bind(this), [], this.options.cheatSheetDescription || 'Show / hide this help menu')); } if (this.options.cheatSheetCloseEsc) { this.add(new Hotkey('esc', function (_) { this.cheatSheetToggle.next(false); }.bind(this), ['HOTKEYS-CHEATSHEET'], this.options.cheatSheetCloseEscDescription || 'Hide this help menu')); } } add(hotkey, specificEvent) { if (Array.isArray(hotkey)) { const temp = []; for (const key of hotkey) { temp.push(this.add(key, specificEvent)); } return temp; } this.remove(hotkey); this.hotkeys.push(hotkey); this.mousetrap.bind(hotkey.combo, (event, combo) => { let shouldExecute = true; // if the callback is executed directly `hotkey.get('w').callback()` // there will be no event, so just execute the callback. if (event) { const target = (event.target || event.srcElement); // srcElement is IE only const nodeName = target.nodeName.toUpperCase(); // check if the input has a mousetrap class, and skip checking preventIn if so if ((' ' + target.className + ' ').indexOf(' mousetrap ') > -1) { shouldExecute = true; } else if (this.preventIn.indexOf(nodeName) > -1 && hotkey.allowIn.map(allow => allow.toUpperCase()).indexOf(nodeName) === -1) { // don't execute callback if the event was fired from inside an element listed in preventIn but not in allowIn shouldExecute = false; } } if (shouldExecute) { return hotkey.callback.apply(this, [event, combo]); } }, specificEvent); return hotkey; } remove(hotkey, specificEvent) { const temp = []; if (!hotkey) { for (const key of this.hotkeys) { temp.push(this.remove(key, specificEvent)); } return temp; } if (Array.isArray(hotkey)) { for (const key of hotkey) { temp.push(this.remove(key)); } return temp; } const index = this.findHotkey(hotkey); if (index > -1) { this.hotkeys.splice(index, 1); this.mousetrap.unbind(hotkey.combo, specificEvent); return hotkey; } return null; } get(combo) { if (!combo) { return this.hotkeys; } if (Array.isArray(combo)) { const temp = []; for (const key of combo) { temp.push(this.get(key)); } return temp; } for (const hotkey of this.hotkeys) { if (hotkey.combo.indexOf(combo) > -1) { return hotkey; } } return null; } // noinspection JSUnusedGlobalSymbols pause(hotkey) { if (!hotkey) { return this.pause(this.hotkeys); } if (Array.isArray(hotkey)) { const temp = []; for (const key of hotkey.slice()) { temp.push(this.pause(key)); } return temp; } this.remove(hotkey); this.pausedHotkeys.push(hotkey); return hotkey; } // noinspection JSUnusedGlobalSymbols unpause(hotkey) { if (!hotkey) { return this.unpause(this.pausedHotkeys); } if (Array.isArray(hotkey)) { const temp = []; for (const key of hotkey.slice()) { temp.push(this.unpause(key)); } return temp; } const index = this.pausedHotkeys.indexOf(hotkey); if (index > -1) { this.add(hotkey); return this.pausedHotkeys.splice(index, 1); } return null; } // noinspection JSUnusedGlobalSymbols reset() { this.mousetrap.reset(); this.hotkeys = []; this.pausedHotkeys = []; this.initCheatSheet(); } findHotkey(hotkey) { return this.hotkeys.indexOf(hotkey); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: HotkeysService, deps: [{ token: HotkeyOptions }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: HotkeysService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: HotkeysService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: function () { return [{ type: undefined, decorators: [{ type: Inject, args: [HotkeyOptions] }] }]; } }); class HotkeysDirective { constructor(hotkeysService, elementRef) { this.hotkeysService = hotkeysService; this.elementRef = elementRef; this.hotkeysList = []; this.oldHotkeys = []; // Bind hotkeys to the current element (and any children) this.mousetrap = new Mousetrap.default(this.elementRef.nativeElement); } ngOnInit() { for (const hotkey of this.hotkeys) { const combo = Object.keys(hotkey)[0]; const hotkeyObj = new Hotkey(combo, hotkey[combo]); const oldHotkey = this.hotkeysService.get(combo); if (oldHotkey !== null) { // We let the user overwrite callbacks temporarily if you specify it in HTML this.oldHotkeys.push(oldHotkey); this.hotkeysService.remove(oldHotkey); } this.hotkeysList.push(hotkeyObj); this.mousetrap.bind(hotkeyObj.combo, hotkeyObj.callback); } } ngOnDestroy() { for (const hotkey of this.hotkeysList) { this.mousetrap.unbind(hotkey.combo); } this.hotkeysService.add(this.oldHotkeys); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: HotkeysDirective, deps: [{ token: HotkeysService }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.2", type: HotkeysDirective, selector: "[hotkeys]", inputs: { hotkeys: "hotkeys" }, providers: [HotkeysService], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: HotkeysDirective, decorators: [{ type: Directive, args: [{ selector: '[hotkeys]', providers: [HotkeysService] }] }], ctorParameters: function () { return [{ type: HotkeysService }, { type: i0.ElementRef }]; }, propDecorators: { hotkeys: [{ type: Input }] } }); class HotkeysCheatsheetComponent { constructor(hotkeysService) { this.hotkeysService = hotkeysService; this.helpVisible$ = new BehaviorSubject(false); this.title = 'Keyboard Shortcuts:'; } ngOnInit() { this.subscription = this.hotkeysService.cheatSheetToggle.subscribe((isOpen) => { if (isOpen !== false) { this.hotkeys = this.hotkeysService.hotkeys.filter(hotkey => hotkey.description); } if (isOpen === false) { this.helpVisible$.next(false); } else { this.toggleCheatSheet(); } }); } ngOnDestroy() { if (this.subscription) { this.subscription.unsubscribe(); } } toggleCheatSheet() { this.helpVisible$.next(!this.helpVisible$.value); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: HotkeysCheatsheetComponent, deps: [{ token: HotkeysService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.2", type: HotkeysCheatsheetComponent, selector: "hotkeys-cheatsheet", inputs: { title: "title" }, ngImport: i0, template: "<div class=\"cfp-hotkeys-container fade\" [ngClass]=\"{'in': helpVisible$|async}\" style=\"display:none\">\n <div class=\"cfp-hotkeys\">\n <h4 class=\"cfp-hotkeys-title\">{{ title }}</h4>\n <table>\n <tbody>\n <tr *ngFor=\"let hotkey of hotkeys\">\n <td class=\"cfp-hotkeys-keys\">\n <span *ngFor=\"let key of hotkey.formatted\" class=\"cfp-hotkeys-key\">{{ key }}</span>\n </td>\n <td class=\"cfp-hotkeys-text\">{{ hotkey.description }}</td>\n </tr>\n </tbody>\n </table>\n <div class=\"cfp-hotkeys-close\" (click)=\"toggleCheatSheet()\">&#215;</div>\n </div>\n</div>\n", styles: [".cfp-hotkeys-container{display:table!important;position:fixed;width:100%;height:100%;top:0;left:0;color:#333;font-size:1em;background-color:#ffffffe6}.cfp-hotkeys-container.fade{z-index:-1024;visibility:hidden;opacity:0;transition:opacity .15s linear}.cfp-hotkeys-container.fade.in{z-index:10002;visibility:visible;opacity:1}.cfp-hotkeys-title{font-weight:700;text-align:center;font-size:1.2em}.cfp-hotkeys{width:100%;height:100%;display:table-cell;vertical-align:middle}.cfp-hotkeys table{margin:auto;color:#333}.cfp-content{display:table-cell;vertical-align:middle}.cfp-hotkeys-keys{padding:5px;text-align:right}.cfp-hotkeys-key{display:inline-block;color:#fff;background-color:#333;border:1px solid #333;border-radius:5px;text-align:center;margin-right:5px;box-shadow:inset 0 1px #666,0 1px #bbb;padding:5px 9px;font-size:1em}.cfp-hotkeys-text{padding-left:10px;font-size:1em}.cfp-hotkeys-close{position:fixed;top:20px;right:20px;font-size:2em;font-weight:700;padding:5px 10px;border:1px solid #ddd;border-radius:5px;min-height:45px;min-width:45px;text-align:center}.cfp-hotkeys-close:hover{background-color:#fff;cursor:pointer}@media all and (max-width: 500px){.cfp-hotkeys{font-size:.8em}}@media all and (min-width: 750px){.cfp-hotkeys{font-size:1.2em}}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: HotkeysCheatsheetComponent, decorators: [{ type: Component, args: [{ selector: 'hotkeys-cheatsheet', template: "<div class=\"cfp-hotkeys-container fade\" [ngClass]=\"{'in': helpVisible$|async}\" style=\"display:none\">\n <div class=\"cfp-hotkeys\">\n <h4 class=\"cfp-hotkeys-title\">{{ title }}</h4>\n <table>\n <tbody>\n <tr *ngFor=\"let hotkey of hotkeys\">\n <td class=\"cfp-hotkeys-keys\">\n <span *ngFor=\"let key of hotkey.formatted\" class=\"cfp-hotkeys-key\">{{ key }}</span>\n </td>\n <td class=\"cfp-hotkeys-text\">{{ hotkey.description }}</td>\n </tr>\n </tbody>\n </table>\n <div class=\"cfp-hotkeys-close\" (click)=\"toggleCheatSheet()\">&#215;</div>\n </div>\n</div>\n", styles: [".cfp-hotkeys-container{display:table!important;position:fixed;width:100%;height:100%;top:0;left:0;color:#333;font-size:1em;background-color:#ffffffe6}.cfp-hotkeys-container.fade{z-index:-1024;visibility:hidden;opacity:0;transition:opacity .15s linear}.cfp-hotkeys-container.fade.in{z-index:10002;visibility:visible;opacity:1}.cfp-hotkeys-title{font-weight:700;text-align:center;font-size:1.2em}.cfp-hotkeys{width:100%;height:100%;display:table-cell;vertical-align:middle}.cfp-hotkeys table{margin:auto;color:#333}.cfp-content{display:table-cell;vertical-align:middle}.cfp-hotkeys-keys{padding:5px;text-align:right}.cfp-hotkeys-key{display:inline-block;color:#fff;background-color:#333;border:1px solid #333;border-radius:5px;text-align:center;margin-right:5px;box-shadow:inset 0 1px #666,0 1px #bbb;padding:5px 9px;font-size:1em}.cfp-hotkeys-text{padding-left:10px;font-size:1em}.cfp-hotkeys-close{position:fixed;top:20px;right:20px;font-size:2em;font-weight:700;padding:5px 10px;border:1px solid #ddd;border-radius:5px;min-height:45px;min-width:45px;text-align:center}.cfp-hotkeys-close:hover{background-color:#fff;cursor:pointer}@media all and (max-width: 500px){.cfp-hotkeys{font-size:.8em}}@media all and (min-width: 750px){.cfp-hotkeys{font-size:1.2em}}\n"] }] }], ctorParameters: function () { return [{ type: HotkeysService }]; }, propDecorators: { title: [{ type: Input }] } }); class HotkeyModule { // noinspection JSUnusedGlobalSymbols static forRoot(options = {}) { return { ngModule: HotkeyModule, providers: [ HotkeysService, { provide: HotkeyOptions, useValue: options } ] }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: HotkeyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.2.2", ngImport: i0, type: HotkeyModule, declarations: [HotkeysDirective, HotkeysCheatsheetComponent], imports: [CommonModule], exports: [HotkeysDirective, HotkeysCheatsheetComponent] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: HotkeyModule, imports: [CommonModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.2", ngImport: i0, type: HotkeyModule, decorators: [{ type: NgModule, args: [{ declarations: [HotkeysDirective, HotkeysCheatsheetComponent], imports: [CommonModule], exports: [HotkeysDirective, HotkeysCheatsheetComponent] }] }] }); /* * Public API Surface of angular2-hotkeys */ /** * Generated bundle index. Do not edit. */ export { Hotkey, HotkeyModule, HotkeyOptions, HotkeysCheatsheetComponent, HotkeysDirective, HotkeysService }; //# sourceMappingURL=angular2-hotkeys.mjs.map