UNPKG

plotboilerplate

Version:

A simple javascript plotting boilerplate for 2d stuff.

463 lines (444 loc) 13.4 kB
/** * @author Ikaros Kappler * @date 2018-11-11 (Alaaf) * @modified 2020-03-28 Ported this class from vanilla-JS to Typescript. * @modified 2020-07-28 Changed the `delete` key code from 8 to 46. * @modified 2020-10-04 Changed `window` to `globalThis`. * @modified 2020-10-04 Added extended JSDoc. * @modified 2022-02-02 Added the `destroy` method. * @version 1.1.0 * * @file KeyHandler * @public **/ /** * @classdesc A generic key handler. * * Example * ======= * @example * // Javascript * new KeyHandler( { trackAll : true } ) * .down('enter',function() { console.log('ENTER was hit.'); } ) * .press('enter',function() { console.log('ENTER was pressed.'); } ) * .up('enter',function() { console.log('ENTER was released.'); } ) * * .down('e',function() { console.log('e was hit. shift is pressed?',keyHandler.isDown('shift')); } ) * * .up('windows',function() { console.log('windows was released.'); } ) * ; */ export class KeyHandler { private element: HTMLElement | Window | typeof globalThis; private downListeners: Array<any> = []; private pressListeners: Array<any> = []; private upListeners: Array<any> = []; private keyStates: Record<number, string | undefined> = {}; private trackAllKeys: boolean; // For later retrieval private _keyDownListener: (e: KeyboardEvent) => void; private _keyPressListener: (e: KeyboardEvent) => void; private _keyUpListener: (e: KeyboardEvent) => void; /** * The constructor. * * @constructor * @instance * @memberof KeyHandler * @param {HTMLElement} options.element (optional) The HTML element to listen on; if null then 'window' will be used. * @param {boolean} options.trackAll (optional) Set to true if you want to keep track of _all_ keys (keyStatus). **/ constructor(options: { element?: HTMLElement | Window | typeof globalThis; trackAll?: boolean }) { options = options || {}; this.element = options.element ? options.element : globalThis; this.downListeners = []; this.pressListeners = []; this.upListeners = []; this.keyStates = []; // This could be made configurable in a later version. It allows to // keep track of the key status no matter if there are any listeners // on the key or not. this.trackAllKeys = options.trackAll || false; // Install the listeners this.installListeners(); } /** * A helper function to fire key events from this KeyHandler. * * @param {KeyboardEvent} event - The key event to fire. * @param {Array<XKeyListener>} listener - The listeners to fire to. */ private fireEvent(event: KeyboardEvent, listeners: Array<XKeyListener>): boolean { let hasListener: boolean = false; for (var i in listeners) { var lis: XKeyListener = listeners[i]; if (lis.keyCode != event.keyCode) continue; lis.listener(event); hasListener = true; } return hasListener; } /** * Internal function to fire a new keydown event to all listeners. * You should not call this function on your own unless you know what you do. * * @name fireDownEvent * @memberof KeyHandler * @instance * @private * @param {KeyboardEvent} e * @param {KeyHandler} handler * @return {void} */ private fireDownEvent(e: KeyboardEvent, handler: KeyHandler): void { if (handler.fireEvent(e, handler.downListeners) || handler.trackAllKeys) { // Down event has listeners. Update key state. handler.keyStates[e.keyCode] = "down"; } } /** * Internal function to fire a new keypress event to all listeners. * You should not call this function on your own unless you know what you do. * * @name firePressEvent * @memberof KeyHandler * @instance * @private * @param {KeyboardEvent} e * @param {KeyHandler} handler * @return void */ private firePressEvent(e: KeyboardEvent, handler: KeyHandler): void { handler.fireEvent(e, handler.pressListeners); } /** * Internal function to fire a new keyup event to all listeners. * You should not call this function on your own unless you know what you do. * * @name fireUpEvent * @memberof KeyHandler * @instance * @private * @param {KeyboardEvent} e * @param {KeyHandler} handler * @return {void} */ private fireUpEvent(e: KeyboardEvent, handler: KeyHandler): void { if (handler.fireEvent(e, handler.upListeners) || handler.trackAllKeys) { // Up event has listeners. Clear key state. delete handler.keyStates[e.keyCode]; } } /** * Resolve the key/name code. */ static key2code(key: number | string): number { if (typeof key == "number") return key; if (typeof key != "string") throw "Unknown key name or key type (should be a string or integer): " + key; if (KeyHandler.KEY_CODES[key]) return KeyHandler.KEY_CODES[key]; throw "Unknown key (cannot resolve key code): " + key; } /** * Source: * https://keycode.info/ */ // prettier-ignore private static KEY_CODES: Record<string,number> = { 'break' : 3, // alternate: 19 'backspace' : 8, // 'delete' : 8, // alternate: 46 'tab' : 9, 'clear' : 12, 'enter' : 13, 'shift' : 16, 'ctrl' : 17, 'alt' : 18, 'pause' : 19, // 'break' : 19, 'capslock' : 20, 'hangul' : 21, 'hanja' : 25, 'escape' : 27, 'conversion' : 28, 'non-conversion' : 29, // alternate: 235? 'spacebar' : 32, 'pageup' : 33, 'pagedown' : 34, 'end' : 35, 'home' : 36, // alternate: 172? 'leftarrow' : 37, 'uparrow' : 38, 'rightarrow' : 39, 'downarrow' : 40, 'select' : 41, 'print' : 42, 'execute' : 43, 'printscreen' : 44, 'insert' : 45, 'delete' : 46, // alternate: 8 'help' : 47, '0' : 48, '1' : 49, '2' : 50, '3' : 51, '4' : 52, '5' : 53, '6' : 54, '7' : 55, '8' : 56, '9' : 57, ':' : 58, 'semicolon (firefox)' : 59, 'equals' : 59, '<' : 60, 'equals (firefox)' : 61, 'ß' : 63, '@ (firefox)' : 64, 'a' : 65, 'b' : 66, 'c' : 67, 'd' : 68, 'e' : 69, 'f' : 70, 'g' : 71, 'h' : 72, 'i' : 73, 'j' : 74, 'k' : 75, 'l' : 76, 'm' : 77, 'n' : 78, 'o' : 79, 'p' : 80, 'q' : 81, 'r' : 82, 's' : 83, 't' : 84, 'u' : 85, 'v' : 86, 'w' : 87, 'x' : 88, 'y' : 89, 'z' : 90, 'windows' : 91, 'leftcommand' : 91, // left ⌘ 'chromebooksearch' : 91, 'rightwindowkey' : 92, 'windowsmenu' : 93, 'rightcommant' : 93, // right ⌘ 'sleep' : 95, 'numpad0' : 96, 'numpad1' : 97, 'numpad2' : 98, 'numpad3' : 99, 'numpad4' : 100, 'numpad5' : 101, 'numpad6' : 102, 'numpad7' : 103, 'numpad8' : 104, 'numpad9' : 105, 'multiply' : 106, 'add' : 107, 'numpadperiod' : 108, // firefox, 194 on chrome 'subtract' : 109, 'decimalpoint' : 110, 'divide' : 111, 'f1' : 112, 'f2' : 113, 'f3' : 114, 'f4' : 115, 'f5' : 116, 'f6' : 117, 'f7' : 118, 'f8' : 119, 'f9' : 120, 'f10' : 121, 'f11' : 122, 'f12' : 123, 'f13' : 124, 'f14' : 125, 'f15' : 126, 'f16' : 127, 'f17' : 128, 'f18' : 129, 'f19' : 130, 'f20' : 131, 'f21' : 132, 'f22' : 133, 'f23' : 134, 'f24' : 135, 'numlock' : 144, 'scrolllock' : 145, '^' : 160, '!' : 161, // '؛' : 162 // (arabic semicolon) '#' : 163, '$' : 164, 'ù' : 165, 'pagebackward' : 166, 'pageforward' : 167, 'refresh' : 168, 'closingparen' : 169, // (AZERTY) '*' : 170, '~+*' : 171, // 'home' : 172, 'minus' : 173, // firefox // 'mute' : 173, // 'unmute' : 173, 'decreasevolumelevel' : 174, 'increasevolumelevel' : 175, 'next' : 176, 'previous' : 177, 'stop' : 178, 'play/pause' : 179, 'email' : 180, 'mute' : 181, // firefox, alternate: 173 'unmute' : 181, // alternate: 173? //'decreasevolumelevel' 182 // firefox //'increasevolumelevel' 183 // firefox 'semicolon' : 186, 'ñ' : 186, 'equal' : 187, 'comma' : 188, 'dash' : 189, 'period' : 190, 'forwardslash' : 191, 'ç' : 191, // 231 alternate? 'grave accent' : 192, //'ñ' 192, 'æ' : 192, 'ö' : 192, '?' : 193, '/' : 193, '°' : 193, // 'numpadperiod' : 194, // chrome 'openbracket' : 219, 'backslash' : 220, 'closebracket' : 221, 'å' : 221, 'singlequote' : 222, 'ø' : 222, 'ä' : 222, '`' : 223, // 'left or right ⌘ key (firefox)' 224 'altgr' : 225, // '< /git >, left back slash' 226 'GNOME Compose Key' : 230, 'XF86Forward' : 233, 'XF86Back' : 234, 'alphanumeric' : 240, 'hiragana' : 242, 'katakana' : 242, 'half-width' : 243, 'full-width' : 243, 'kanji' : 244, 'unlocktrackpad' : 251, // Chrome/Edge 'toggletouchpad' : 255 }; /** * Install the required listeners into the initially passed element. * * By default the listeners are installed into the root element specified on * construction (or 'window'). */ installListeners() { var _self = this; this.element.addEventListener( "keydown", (this._keyDownListener = (e: KeyboardEvent) => { _self.fireDownEvent(e, _self); }) as EventListener ); this.element.addEventListener( "keypress", (this._keyPressListener = (e: KeyboardEvent) => { _self.firePressEvent(e, _self); }) as EventListener ); this.element.addEventListener( "keyup", (this._keyUpListener = (e: KeyboardEvent) => { _self.fireUpEvent(e, _self); }) as EventListener ); } /** * Remove all installed event listeners from the underlying element. */ releaseListeners() { this.element.removeEventListener("keydown", this._keyDownListener as EventListener); this.element.removeEventListener("keypress", this._keyPressListener as EventListener); this.element.removeEventListener("keyup", this._keyUpListener as EventListener); } /** * Listen for key down. This function allows chaining. * * Example: new KeyHandler().down('enter',function() {console.log('Enter hit.')}); * * @name down * @memberof KeyHandler * @instance * @param {string|number} key - Any key identifier, key code or one from the KEY_CODES list. * @param {(e:KeyboardEvent)=>void} e - The callback to be triggered. * @return {KeyHandler} this */ down(key: string | number, listener: XKeyCallback): KeyHandler { this.downListeners.push({ key: key, keyCode: KeyHandler.key2code(key), listener: listener } as XKeyListener); return this; } /** * Listen for key press. * * Example: new KeyHandler().press('enter',function() {console.log('Enter pressed.')}); * * @name press * @memberof KeyHandler * @instance * @param {string|number} key - Any key identifier, key code or one from the KEY_CODES list. * @param {(e:KeyboardEvent)=>void} listener - The callback to be triggered. * @return {KeyHandler} this */ press(key: string | number, listener: XKeyCallback): KeyHandler { this.pressListeners.push({ key: key, keyCode: KeyHandler.key2code(key), listener: listener } as XKeyListener); return this; } /** * Listen for key up. * * Example: new KeyHandler().up('enter',function() {console.log('Enter released.')}); * * @name up * @memberof KeyHandler * @instance * @param {string} key - Any key identifier, key code or one from the KEY_CODES list. * @param {(e:KeyboardEvent)=>void)} e - The callback to be triggered. * @return {KeyHandler} this */ up(key: string, listener: XKeyCallback): KeyHandler { this.upListeners.push({ key: key, keyCode: KeyHandler.key2code(key), listener: listener } as XKeyListener); return this; } /** * Check if a specific key is currently held pressed. * * @param {string|number} key - Any key identifier, key code or one from the KEY_CODES list. */ isDown(key: string | number): boolean { if (typeof key == "number") return this.keyStates[key] ? true : false; else return this.keyStates[KeyHandler.key2code(key)] ? true : false; } /** * This function should invalidate any installed listeners and invalidate this object. * After calling this function the object might not hold valid data any more and * should not be used any more. */ destroy(): void { this.releaseListeners(); } } export interface XKeyListener { key: string; keyCode: number; listener: (event: KeyboardEvent) => void; } export type XKeyCallback = (e: KeyboardEvent) => void;