diff --git a/package.json b/package.json index 93caf45..f7ab2a8 100644 --- a/package.json +++ b/package.json @@ -37,19 +37,19 @@ "tslib": "^2.3.1" }, "devDependencies": { - "@angular-devkit/build-angular": "^16.2.0", - "@angular-devkit/core": "^16.2.0", - "@angular/animations": "^16.2.0", - "@angular/cli": "^16.2.0", - "@angular/common": "^16.2.0", - "@angular/compiler": "^16.2.0", - "@angular/compiler-cli": "^16.2.0", - "@angular/core": "^16.2.0", - "@angular/forms": "^16.2.0", - "@angular/language-service": "^16.2.0", - "@angular/platform-browser": "^16.2.0", - "@angular/platform-browser-dynamic": "^16.2.0", - "@angular/router": "^16.2.0", + "@angular-devkit/build-angular": "^17.0.9", + "@angular-devkit/core": "^17.0.9", + "@angular/animations": "^17.0.9", + "@angular/cli": "^17.0.9", + "@angular/common": "^17.0.9", + "@angular/compiler": "^17.0.9", + "@angular/compiler-cli": "^17.0.9", + "@angular/core": "^17.0.9", + "@angular/forms": "^17.0.9", + "@angular/language-service": "^17.0.9", + "@angular/platform-browser": "^17.0.9", + "@angular/platform-browser-dynamic": "^17.0.9", + "@angular/router": "^17.0.9", "@types/jasmine": "~3.10.3", "@types/jasminewd2": "~2.0.10", "@types/node": "^18.11.9 ", @@ -61,12 +61,12 @@ "karma-coverage-istanbul-reporter": "~3.0.3", "karma-jasmine": "~4.0.1", "karma-jasmine-html-reporter": "^1.7.0", - "ng-packagr": "^16.2.0", + "ng-packagr": "^17.0.3", "protractor": "~7.0.0", "rxjs": "~7.5.2", "ts-node": "~10.4.0", "tslint": "~6.1.0", - "typescript": "~5.1.6", - "zone.js": "~0.13.1" + "typescript": "~5.2.2", + "zone.js": "~0.14.3" } -} +} \ No newline at end of file diff --git a/src/lib/hotkey.options.ts b/src/lib/hotkey.interfaces-types.ts similarity index 78% rename from src/lib/hotkey.options.ts rename to src/lib/hotkey.interfaces-types.ts index 3a08682..1aaa983 100644 --- a/src/lib/hotkey.options.ts +++ b/src/lib/hotkey.interfaces-types.ts @@ -1,5 +1,7 @@ import { InjectionToken } from '@angular/core'; +export const HotkeyOptions = new InjectionToken('HotkeyOptions'); + export interface IHotkeyOptions { /** * Disable the cheat sheet popover dialog? Default: false @@ -24,4 +26,8 @@ export interface IHotkeyOptions { cheatSheetDescription?: string; } -export const HotkeyOptions = new InjectionToken('HotkeyOptions'); +export interface ExtendedKeyboardEvent extends KeyboardEvent { + returnValue: boolean; // IE returnValue +} + +export type HotkeyMap = { [combo: string]: (event: KeyboardEvent, combo: string) => ExtendedKeyboardEvent }[]; diff --git a/src/lib/hotkey.model.ts b/src/lib/hotkey.model.ts index b58fd6b..175c1b9 100644 --- a/src/lib/hotkey.model.ts +++ b/src/lib/hotkey.model.ts @@ -1,9 +1,8 @@ -export interface ExtendedKeyboardEvent extends KeyboardEvent { - returnValue: boolean; // IE returnValue -} +import { ExtendedKeyboardEvent } from "./hotkey.interfaces-types"; + export class Hotkey { - private formattedHotkey: string[]; + private formattedHotkey: string[] = []; static symbolize(combo: string): string { const map: any = { @@ -46,8 +45,8 @@ export class Hotkey { * @param persistent if true, the binding is preserved upon route changes */ constructor(public combo: string | string[], public callback: (event: KeyboardEvent, combo: string) => ExtendedKeyboardEvent | boolean, - public allowIn?: string[], public description?: string | Function, public action?: string, - public persistent?: boolean) { + public allowIn?: string[], public description?: string | Function, public action?: string, + public persistent?: boolean) { this.combo = (Array.isArray(combo) ? combo : [combo as string]); this.allowIn = allowIn || []; this.description = description || ''; diff --git a/src/lib/hotkey.module.ts b/src/lib/hotkey.module.ts index 8b00585..f05966e 100644 --- a/src/lib/hotkey.module.ts +++ b/src/lib/hotkey.module.ts @@ -2,22 +2,21 @@ import { ModuleWithProviders, NgModule } from '@angular/core'; import { HotkeysDirective } from './hotkeys.directive'; import { HotkeysCheatsheetComponent } from './hotkeys-cheatsheet/hotkeys-cheatsheet.component'; import { CommonModule } from '@angular/common'; -import { HotkeyOptions, IHotkeyOptions } from './hotkey.options'; +import { HotkeyOptions, IHotkeyOptions } from './hotkey.interfaces-types'; import { HotkeysService } from './hotkeys.service'; @NgModule({ - declarations: [HotkeysDirective, HotkeysCheatsheetComponent], - imports: [CommonModule], + imports: [CommonModule, HotkeysDirective, HotkeysCheatsheetComponent], exports: [HotkeysDirective, HotkeysCheatsheetComponent] }) export class HotkeyModule { // noinspection JSUnusedGlobalSymbols static forRoot(options: IHotkeyOptions = {}): ModuleWithProviders { return { - ngModule : HotkeyModule, - providers : [ + ngModule: HotkeyModule, + providers: [ HotkeysService, - {provide : HotkeyOptions, useValue : options} + { provide: HotkeyOptions, useValue: options } ] }; } diff --git a/src/lib/hotkeys-cheatsheet/hotkeys-cheatsheet.component.spec.ts b/src/lib/hotkeys-cheatsheet/hotkeys-cheatsheet.component.spec.ts index 6f7ee97..c4debba 100644 --- a/src/lib/hotkeys-cheatsheet/hotkeys-cheatsheet.component.spec.ts +++ b/src/lib/hotkeys-cheatsheet/hotkeys-cheatsheet.component.spec.ts @@ -7,8 +7,8 @@ describe('HotkeysCheatsheetComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [HotkeysCheatsheetComponent] - }) + imports: [HotkeysCheatsheetComponent] +}) .compileComponents(); })); diff --git a/src/lib/hotkeys-cheatsheet/hotkeys-cheatsheet.component.ts b/src/lib/hotkeys-cheatsheet/hotkeys-cheatsheet.component.ts index 2c03f4f..798a11e 100644 --- a/src/lib/hotkeys-cheatsheet/hotkeys-cheatsheet.component.ts +++ b/src/lib/hotkeys-cheatsheet/hotkeys-cheatsheet.component.ts @@ -1,19 +1,22 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { Hotkey } from '../hotkey.model'; import { HotkeysService } from '../hotkeys.service'; -import {BehaviorSubject, Subscription} from 'rxjs'; +import { BehaviorSubject, Subscription } from 'rxjs'; +import { NgClass, NgFor, AsyncPipe } from '@angular/common'; @Component({ selector: 'hotkeys-cheatsheet', templateUrl: './hotkeys-cheatsheet.component.html', - styleUrls: ['./hotkeys-cheatsheet.component.css'] + styleUrls: ['./hotkeys-cheatsheet.component.css'], + standalone: true, + imports: [NgClass, NgFor, AsyncPipe] }) export class HotkeysCheatsheetComponent implements OnInit, OnDestroy { helpVisible$ = new BehaviorSubject(false); @Input() title = 'Keyboard Shortcuts:'; - subscription: Subscription; + subscription: Subscription = new Subscription(); - hotkeys: Hotkey[]; + hotkeys: Hotkey[] = []; constructor(private hotkeysService: HotkeysService) { } diff --git a/src/lib/hotkeys.directive.ts b/src/lib/hotkeys.directive.ts index abd4571..b2851b4 100644 --- a/src/lib/hotkeys.directive.ts +++ b/src/lib/hotkeys.directive.ts @@ -1,15 +1,20 @@ import { Directive, ElementRef, Input, OnDestroy, OnInit } from '@angular/core'; -import { ExtendedKeyboardEvent, Hotkey } from './hotkey.model'; +import { Hotkey } from './hotkey.model'; import { HotkeysService } from './hotkeys.service'; import { MousetrapInstance } from 'mousetrap'; import * as Mousetrap from 'mousetrap'; +import { ExtendedKeyboardEvent } from './hotkey.interfaces-types'; + +type HotkeyMap = { [combo: string]: (event: KeyboardEvent, combo: string) => ExtendedKeyboardEvent }[]; @Directive({ selector: '[hotkeys]', - providers: [HotkeysService] + providers: [HotkeysService], + standalone: true }) export class HotkeysDirective implements OnInit, OnDestroy { - @Input() hotkeys: { [combo: string]: (event: KeyboardEvent, combo: string) => ExtendedKeyboardEvent }[]; + @Input() + hotkeys: HotkeyMap = []; private mousetrap: MousetrapInstance; private hotkeysList: Hotkey[] = []; diff --git a/src/lib/hotkeys.service.ts b/src/lib/hotkeys.service.ts index bba97e5..8e8864f 100644 --- a/src/lib/hotkeys.service.ts +++ b/src/lib/hotkeys.service.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@angular/core'; import { Hotkey } from './hotkey.model'; import { Subject } from 'rxjs'; -import { HotkeyOptions, IHotkeyOptions } from './hotkey.options'; +import { HotkeyOptions, IHotkeyOptions } from './hotkey.interfaces-types'; import { MousetrapInstance } from 'mousetrap'; import * as Mousetrap from 'mousetrap'; @@ -30,37 +30,44 @@ export class HotkeysService { } private initCheatSheet() { + // Cheat sheet hotkey if (!this.options.disableCheatSheet) { - this.add(new Hotkey( - this.options.cheatSheetHotkey || '?', - function(_: KeyboardEvent) { - this.cheatSheetToggle.next(); - }.bind(this), - [], - this.options.cheatSheetDescription || 'Show / hide this help menu', - )); + let opt = { + combo: this.options.cheatSheetHotkey || '?', + callback: (_event: KeyboardEvent, _combo: string) => { + this.cheatSheetToggle.next(null); + return false; + }, + description: this.options.cheatSheetDescription || 'Show / hide this help menu', + allowIn: [] + }; + let add: Hotkey | Hotkey[] = new Hotkey(opt.combo, opt.callback.bind(this), opt.allowIn, opt.description); + this.add(add); } - + // Cheat sheet close esc if (this.options.cheatSheetCloseEsc) { - this.add(new Hotkey( - 'esc', - function(_: KeyboardEvent) { + let opt = { + combo: 'esc', + callback: (_event: KeyboardEvent, _combo: string) => { this.cheatSheetToggle.next(false); - }.bind(this), - ['HOTKEYS-CHEATSHEET'], - this.options.cheatSheetCloseEscDescription || 'Hide this help menu', - )); + return false; + }, + description: this.options.cheatSheetCloseEscDescription || 'Hide this help menu', + allowIn: ['HOTKEYS-CHEATSHEET'] + }; + let add: Hotkey | Hotkey[] = new Hotkey(opt.combo, opt.callback.bind(this), opt.allowIn, opt.description); + this.add(add); } } - add(hotkey: Hotkey | Hotkey[], specificEvent?: string): Hotkey | Hotkey[] { + add(hotkey: T, specificEvent?: string): T { if (Array.isArray(hotkey)) { const temp: Hotkey[] = []; for (const key of hotkey) { - temp.push(this.add(key, specificEvent) as Hotkey); + temp.push(this.add(key, specificEvent)); } - return temp; + return temp as T; } this.remove(hotkey); this.hotkeys.push(hotkey as Hotkey); @@ -73,11 +80,12 @@ export class HotkeysService { const target: HTMLElement = (event.target || event.srcElement) as HTMLElement; // srcElement is IE only const nodeName: string = target.nodeName.toUpperCase(); + const allowIn = (hotkey as Hotkey).allowIn || []; + // 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 as Hotkey).allowIn.map(allow => allow.toUpperCase()).indexOf(nodeName) === -1) { + } else if (this.preventIn.indexOf(nodeName) > -1 && 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; } @@ -90,19 +98,19 @@ export class HotkeysService { return hotkey; } - remove(hotkey?: Hotkey | Hotkey[], specificEvent?: string): Hotkey | Hotkey[] { + remove(hotkey?: T, specificEvent?: string): T | null { const temp: Hotkey[] = []; if (!hotkey) { for (const key of this.hotkeys) { temp.push(this.remove(key, specificEvent) as Hotkey); } - return temp; + return temp as T; } if (Array.isArray(hotkey)) { for (const key of hotkey) { temp.push(this.remove(key) as Hotkey); } - return temp; + return temp as T; } const index = this.findHotkey(hotkey as Hotkey); if (index > -1) { @@ -113,7 +121,7 @@ export class HotkeysService { return null; } - get(combo?: string | string[]): Hotkey | Hotkey[] { + get(combo?: T): Hotkey | Hotkey[] | null { if (!combo) { return this.hotkeys; } @@ -150,21 +158,21 @@ export class HotkeysService { } // noinspection JSUnusedGlobalSymbols - unpause(hotkey?: Hotkey | Hotkey[]): Hotkey | Hotkey[] { + unpause(hotkey?: T): T | null { if (!hotkey) { - return this.unpause(this.pausedHotkeys); + return this.unpause(this.pausedHotkeys) as T; } if (Array.isArray(hotkey)) { const temp: Hotkey[] = []; for (const key of hotkey.slice()) { temp.push(this.unpause(key) as Hotkey); } - return temp; + return temp as T; } const index: number = this.pausedHotkeys.indexOf(hotkey as Hotkey); if (index > -1) { this.add(hotkey); - return this.pausedHotkeys.splice(index, 1); + return this.pausedHotkeys.splice(index, 1) as T; } return null; } @@ -180,5 +188,4 @@ export class HotkeysService { private findHotkey(hotkey: Hotkey): number { return this.hotkeys.indexOf(hotkey); } -} - +} \ No newline at end of file diff --git a/src/public-api.ts b/src/public-api.ts index 7c40a9b..47f9b66 100644 --- a/src/public-api.ts +++ b/src/public-api.ts @@ -6,5 +6,5 @@ export * from './lib/hotkeys.service'; export * from './lib/hotkeys.directive'; export * from './lib/hotkeys-cheatsheet/hotkeys-cheatsheet.component'; export * from './lib/hotkey.model'; -export * from './lib/hotkey.options'; +export * from './lib/hotkey.interfaces-types'; export * from './lib/hotkey.module'; diff --git a/tsconfig.json b/tsconfig.json index ff1a5df..8e4f4f0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,32 +1,34 @@ { - "compileOnSave": false, - "compilerOptions": { - "baseUrl": "./", - "outDir": "./dist/out-tsc", - "sourceMap": true, - "declaration": false, - "downlevelIteration": true, - "experimentalDecorators": true, - "module": "es2020", - "moduleResolution": "node", - "importHelpers": true, - "target": "ES2022", - "typeRoots": [ - "node_modules/@types" - ], - "lib": [ - "es2018", - "dom" - ], - "paths": { - "angular2-hotkeys": [ - "dist/" - ] - }, - "useDefineForClassFields": false + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "downlevelIteration": true, + "experimentalDecorators": true, + "module": "es2020", + "moduleResolution": "node", + "importHelpers": true, + "target": "ES2022", + "strict": true, + "typeRoots": [ + "node_modules/@types" + ], + "lib": [ + "es2018", + "dom" + ], + "paths": { + "angular2-hotkeys": [ + "dist/" + ] }, - "angularCompilerOptions": { - "fullTemplateTypeCheck": true, - "strictInjectionParameters": true - } -} + "useDefineForClassFields": false + }, + "angularCompilerOptions": { + "strictTemplates": true, + "fullTemplateTypeCheck": true, + "strictInjectionParameters": true + } +} \ No newline at end of file