@hydroperx/inputaction
Advanced tools
| import Input from "./Input"; | ||
| import { InputActionAtom, InputActionKeyName } from "./InputAction"; | ||
| export { Input }; | ||
| export type { InputActionAtom, InputActionKey, InputActionKeyName, } from "./InputAction"; | ||
| /** | ||
| * Returns the display text of a shortcut, such as `"Ctrl+A"`. | ||
| * | ||
| * @param param Either an action name or a series of action atoms. | ||
| */ | ||
| export declare function shortcutDisplayText(param: string | InputActionAtom[]): string; | ||
| export declare function inputActionKeyNameDisplayText(name: InputActionKeyName): string; |
| import Input from "./Input"; | ||
| export { Input }; | ||
| /** | ||
| * Returns the display text of a shortcut, such as `"Ctrl+A"`. | ||
| * | ||
| * @param param Either an action name or a series of action atoms. | ||
| */ | ||
| export function shortcutDisplayText(param) { | ||
| if (typeof param == "string") { | ||
| return shortcutDisplayText(Input.input.getActions()[param]); | ||
| } | ||
| if (!param) | ||
| return ""; | ||
| for (const atom of param) { | ||
| if (atom.hasOwnProperty("key")) { | ||
| const key = atom; | ||
| const parts = []; | ||
| if (key.control) { | ||
| parts.push("Ctrl"); | ||
| } | ||
| if (key.alt) { | ||
| parts.push("Alt"); | ||
| } | ||
| if (key.shift) { | ||
| parts.push("Shift"); | ||
| } | ||
| parts.push(inputActionKeyNameDisplayText(key.key)); | ||
| return parts.join("+"); | ||
| } | ||
| } | ||
| return ""; | ||
| } | ||
| export function inputActionKeyNameDisplayText(name) { | ||
| switch (name) { | ||
| case "leftArrow": return "Left"; | ||
| case "rightArrow": return "Right"; | ||
| case "upArrow": return "Up"; | ||
| case "downArrow": return "Down"; | ||
| case "spacebar": return "Space"; | ||
| case "enter": return "Enter"; | ||
| case "backspace": return "Backspace"; | ||
| case "minus": return "Minus"; | ||
| case "plus": return "Plus"; | ||
| case "tab": return "Tab"; | ||
| case "f1": return "F1"; | ||
| case "f2": return "F2"; | ||
| case "f3": return "F3"; | ||
| case "f4": return "F4"; | ||
| case "f5": return "F5"; | ||
| case "f6": return "F6"; | ||
| case "f7": return "F7"; | ||
| case "f8": return "F8"; | ||
| case "f9": return "F9"; | ||
| case "f10": return "F10"; | ||
| case "f11": return "F11"; | ||
| case "f12": return "F12"; | ||
| case "0": return "0"; | ||
| case "1": return "1"; | ||
| case "2": return "2"; | ||
| case "3": return "3"; | ||
| case "4": return "4"; | ||
| case "5": return "5"; | ||
| case "6": return "6"; | ||
| case "7": return "7"; | ||
| case "8": return "8"; | ||
| case "9": return "9"; | ||
| default: return name.toUpperCase(); | ||
| } | ||
| } |
| import { InputActionAtom } from "./InputAction"; | ||
| import { TypedEventTarget } from "@hydroperx/event"; | ||
| declare const Input_base: TypedEventTarget<{ | ||
| inputPressed: Event; | ||
| inputReleased: Event; | ||
| actionsUpdated: Event; | ||
| }>; | ||
| /** | ||
| * The `Input` class handles action mapping and user input event listening. | ||
| * | ||
| * # Getting started | ||
| * | ||
| * The following code demonstrates using arrows and WASD keys | ||
| * for entity movement: | ||
| * | ||
| * ```ts | ||
| * import { Input } from "@hydroperx/inputaction"; | ||
| * | ||
| * Input.input.setActions({ | ||
| * "moveLeft": [ | ||
| * { key: "a" }, | ||
| * { key: "leftArrow" }, | ||
| * ], | ||
| * "moveRight": [ | ||
| * { key: "d" }, | ||
| * { key: "rightArrow" }, | ||
| * ], | ||
| * "moveUp": [ | ||
| * { key: "w" }, | ||
| * { key: "upArrow" }, | ||
| * ], | ||
| * "moveDown": [ | ||
| * { key: "s" }, | ||
| * { key: "downArrow" }, | ||
| * ], | ||
| * }); | ||
| * | ||
| * Input.input.addEventListener("inputPressed", () => { | ||
| * const shouldMoveRight = Input.input.isPressed("moveRight"); | ||
| * }); | ||
| * ``` | ||
| * | ||
| * # Built-in actions | ||
| * | ||
| * The following actions are pre-defined in every action map | ||
| * and can be overriden: | ||
| * | ||
| * * `escape` - Used for escaping out in the user interface. | ||
| * * `navigateLeft` — Used for focusing the left neighbor of an user interface control. | ||
| * * `navigateRight` — Used for focusing the right neighbor of an user interface control. | ||
| * * `navigateUp` — Used for focusing the top neighbor of an user interface control. | ||
| * * `navigateDown` — Used for focusing the bottom neighbor of an user interface control. | ||
| * | ||
| * # Events | ||
| * | ||
| * This class extends `EventTarget` and may dispatch the following events: | ||
| * | ||
| * ```ts | ||
| * // Dispatched when user input starts being pressed or | ||
| * // is continuously pressed. | ||
| * inputPressed: Event; | ||
| * // Dispatched when any user input is released. | ||
| * inputReleased: Event; | ||
| * // Dispatched when the actions map is updated. | ||
| * actionsUpdated: Event; | ||
| * ``` | ||
| */ | ||
| export default class Input extends Input_base { | ||
| /** | ||
| * The singleton instance of the `Input` class. | ||
| */ | ||
| static readonly input: Input; | ||
| private mMap; | ||
| /** | ||
| * Returns the current action map in read-only mode. | ||
| */ | ||
| getActions(): Record<string, InputActionAtom[]>; | ||
| /** | ||
| * Updates the action map. | ||
| * @fires Input#actionsUpdated | ||
| */ | ||
| setActions(map: Record<string, InputActionAtom[]>): void; | ||
| private static builtin; | ||
| private static readonly mPressedStatePoolKeys; | ||
| /** | ||
| * Determines whether an action is pressed. | ||
| * @throws Error Thrown if the action does not exist. | ||
| */ | ||
| isPressed(name: string): boolean; | ||
| /** | ||
| * Determines whether an action has been just pressed right now. | ||
| * @throws Error Thrown if the action does not exist. | ||
| */ | ||
| justPressed(name: string): boolean; | ||
| } | ||
| export {}; |
+208
| import clonePlainObject from "./util/clonePlainObject"; | ||
| import { navigatorKeyToThis } from "./InputAction"; | ||
| import assert from "assert"; | ||
| /** | ||
| * The `Input` class handles action mapping and user input event listening. | ||
| * | ||
| * # Getting started | ||
| * | ||
| * The following code demonstrates using arrows and WASD keys | ||
| * for entity movement: | ||
| * | ||
| * ```ts | ||
| * import { Input } from "@hydroperx/inputaction"; | ||
| * | ||
| * Input.input.setActions({ | ||
| * "moveLeft": [ | ||
| * { key: "a" }, | ||
| * { key: "leftArrow" }, | ||
| * ], | ||
| * "moveRight": [ | ||
| * { key: "d" }, | ||
| * { key: "rightArrow" }, | ||
| * ], | ||
| * "moveUp": [ | ||
| * { key: "w" }, | ||
| * { key: "upArrow" }, | ||
| * ], | ||
| * "moveDown": [ | ||
| * { key: "s" }, | ||
| * { key: "downArrow" }, | ||
| * ], | ||
| * }); | ||
| * | ||
| * Input.input.addEventListener("inputPressed", () => { | ||
| * const shouldMoveRight = Input.input.isPressed("moveRight"); | ||
| * }); | ||
| * ``` | ||
| * | ||
| * # Built-in actions | ||
| * | ||
| * The following actions are pre-defined in every action map | ||
| * and can be overriden: | ||
| * | ||
| * * `escape` - Used for escaping out in the user interface. | ||
| * * `navigateLeft` — Used for focusing the left neighbor of an user interface control. | ||
| * * `navigateRight` — Used for focusing the right neighbor of an user interface control. | ||
| * * `navigateUp` — Used for focusing the top neighbor of an user interface control. | ||
| * * `navigateDown` — Used for focusing the bottom neighbor of an user interface control. | ||
| * | ||
| * # Events | ||
| * | ||
| * This class extends `EventTarget` and may dispatch the following events: | ||
| * | ||
| * ```ts | ||
| * // Dispatched when user input starts being pressed or | ||
| * // is continuously pressed. | ||
| * inputPressed: Event; | ||
| * // Dispatched when any user input is released. | ||
| * inputReleased: Event; | ||
| * // Dispatched when the actions map is updated. | ||
| * actionsUpdated: Event; | ||
| * ``` | ||
| */ | ||
| export default class Input extends EventTarget { | ||
| /** | ||
| * The singleton instance of the `Input` class. | ||
| */ | ||
| static input = new Input; | ||
| // Actions map | ||
| mMap = { | ||
| ...Input.builtin(), | ||
| }; | ||
| /** | ||
| * Returns the current action map in read-only mode. | ||
| */ | ||
| getActions() { | ||
| return clonePlainObject(this.mMap, true); | ||
| } | ||
| /** | ||
| * Updates the action map. | ||
| * @fires Input#actionsUpdated | ||
| */ | ||
| setActions(map) { | ||
| // Update static map | ||
| this.mMap = { | ||
| ...Input.builtin(), | ||
| ...clonePlainObject(map, true), | ||
| }; | ||
| // Dispatch update event | ||
| this.dispatchEvent(new Event("actionsUpdated")); | ||
| } | ||
| static builtin() { | ||
| return { | ||
| "escape": [{ key: "escape" }], | ||
| "navigateLeft": [{ key: "leftArrow" }], | ||
| "navigateRight": [{ key: "rightArrow" }], | ||
| "navigateUp": [{ key: "upArrow" }], | ||
| "navigateDown": [{ key: "downArrow" }], | ||
| }; | ||
| } | ||
| // Static pressed state pool | ||
| static mPressedStatePoolKeys = new Map(); | ||
| static { | ||
| if (typeof window !== "undefined") { | ||
| window.addEventListener("keydown", evt => { | ||
| const keyName = navigatorKeyToThis(evt.key); | ||
| if (keyName !== undefined) { | ||
| // Mutate pressed state | ||
| let state = Input.mPressedStatePoolKeys.get(keyName); | ||
| if (state === undefined) { | ||
| state = { | ||
| pressed: false, | ||
| pressedTimestamp: 0, | ||
| control: false, | ||
| shift: false, | ||
| alt: false, | ||
| }; | ||
| Input.mPressedStatePoolKeys.set(keyName, state); | ||
| } | ||
| state.pressed = true; | ||
| state.pressedTimestamp = Date.now(); | ||
| state.control = evt.ctrlKey; | ||
| state.shift = evt.shiftKey; | ||
| state.alt = evt.altKey; | ||
| // Dispatch pressed event | ||
| const evt1 = new Event("inputPressed", { cancelable: true }); | ||
| const r = Input.input.dispatchEvent(evt1); | ||
| if (evt1.defaultPrevented) { | ||
| evt.preventDefault(); | ||
| } | ||
| return r; | ||
| } | ||
| }); | ||
| window.addEventListener("keyup", evt => { | ||
| const keyName = navigatorKeyToThis(evt.key); | ||
| if (keyName !== undefined) { | ||
| // Mutate pressed state | ||
| let state = Input.mPressedStatePoolKeys.get(keyName); | ||
| if (state === undefined) { | ||
| state = { | ||
| pressed: false, | ||
| pressedTimestamp: 0, | ||
| control: false, | ||
| shift: false, | ||
| alt: false, | ||
| }; | ||
| Input.mPressedStatePoolKeys.set(keyName, state); | ||
| } | ||
| state.pressed = false; | ||
| state.control = false; | ||
| state.shift = false; | ||
| state.alt = false; | ||
| // Dispatch released event | ||
| const evt1 = new Event("inputReleased", { cancelable: true }); | ||
| const r = Input.input.dispatchEvent(evt1); | ||
| if (evt1.defaultPrevented) { | ||
| evt.preventDefault(); | ||
| } | ||
| return r; | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| /** | ||
| * Determines whether an action is pressed. | ||
| * @throws Error Thrown if the action does not exist. | ||
| */ | ||
| isPressed(name) { | ||
| const action = this.mMap[name]; | ||
| assert(action !== undefined, "The specified action for Input.isPressed(name) does not exist."); | ||
| for (const item of action) { | ||
| if (item.hasOwnProperty("key")) { | ||
| const inputActionKey = item; | ||
| const pressedState = Input.mPressedStatePoolKeys.get(inputActionKey.key); | ||
| const pressed = pressedState !== undefined && pressedState.pressed | ||
| && (inputActionKey.control ? pressedState.control : !pressedState.control) | ||
| && (inputActionKey.shift ? pressedState.shift : !pressedState.shift) | ||
| && (inputActionKey.alt ? pressedState.alt : !pressedState.alt); | ||
| if (pressed) { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| /** | ||
| * Determines whether an action has been just pressed right now. | ||
| * @throws Error Thrown if the action does not exist. | ||
| */ | ||
| justPressed(name) { | ||
| const action = this.mMap[name]; | ||
| assert(action !== undefined, "The specified action for Input.isPressed(name) does not exist."); | ||
| for (const item of action) { | ||
| if (item.hasOwnProperty("key")) { | ||
| const inputActionKey = item; | ||
| const pressedState = Input.mPressedStatePoolKeys.get(inputActionKey.key); | ||
| const pressed = pressedState !== undefined && pressedState.pressed | ||
| && (inputActionKey.control ? pressedState.control : !pressedState.control) | ||
| && (inputActionKey.shift ? pressedState.shift : !pressedState.shift) | ||
| && (inputActionKey.alt ? pressedState.alt : !pressedState.alt); | ||
| if (pressed && pressedState.pressedTimestamp > Date.now() - 15) { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| } |
| /** | ||
| * Part of the `Input` class. It is used to describe a specific user input key or gamepad | ||
| * button. | ||
| */ | ||
| export type InputActionAtom = InputActionKey; | ||
| /** | ||
| * Part of the Input API. It is used to describe a specific user input key. | ||
| */ | ||
| export type InputActionKey = { | ||
| /** | ||
| * Identifies the input key. | ||
| */ | ||
| key: InputActionKeyName; | ||
| /** | ||
| * Whether the `Ctrl` key is used. | ||
| */ | ||
| control?: boolean; | ||
| /** | ||
| * Whether the `Alt` key is used. | ||
| */ | ||
| alt?: boolean; | ||
| /** | ||
| * Whether the `Shift` key is used. | ||
| */ | ||
| shift?: boolean; | ||
| }; | ||
| /** | ||
| * Part of the Input API. It is used to identify the name of a specific user input key. | ||
| */ | ||
| export type InputActionKeyName = "leftArrow" | "rightArrow" | "upArrow" | "downArrow" | "spacebar" | "enter" | "backspace" | "minus" | "plus" | "tab" | "escape" | "assign" | "comma" | "dot" | "semicolon" | "f1" | "f2" | "f3" | "f4" | "f5" | "f6" | "f7" | "f8" | "f9" | "f10" | "f11" | "f12" | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"; | ||
| export declare function navigatorKeyToThis(name: string): InputActionKeyName | undefined; |
| const mapNavigatorKeyToThis = new Map([ | ||
| ["arrowleft", "leftArrow"], | ||
| ["arrowright", "rightArrow"], | ||
| ["arrowup", "upArrow"], | ||
| ["arrowdown", "downArrow"], | ||
| ["escape", "escape"], | ||
| [" ", "spacebar"], | ||
| ["enter", "enter"], | ||
| ["backspace", "backspace"], | ||
| ["subtract", "minus"], | ||
| ["add", "plus"], | ||
| ["tab", "tab"], | ||
| ["+", "plus"], | ||
| ["-", "minus"], | ||
| ["=", "assign"], | ||
| [",", "comma"], | ||
| [".", "dot"], | ||
| [";", "semicolon"], | ||
| ]); | ||
| export function navigatorKeyToThis(name) { | ||
| name = name.toLowerCase(); | ||
| if (/^([a-z0-9]|f\d\d?)$/.test(name)) { | ||
| return name; | ||
| } | ||
| return mapNavigatorKeyToThis.get(name); | ||
| } |
| /** | ||
| * Clones a plain object. `deep` is `false` by default. | ||
| */ | ||
| export default function clonePlainObject(object: any, deep?: boolean): any; |
| /** | ||
| * Clones a plain object. `deep` is `false` by default. | ||
| */ | ||
| export default function clonePlainObject(object, deep = false) { | ||
| if (deep) { | ||
| return clonePlainObjectDeep(object); | ||
| } | ||
| const r = {}; | ||
| for (const k in object) { | ||
| r[k] = object[k]; | ||
| } | ||
| return r; | ||
| } | ||
| function clonePlainObjectDeep(object) { | ||
| const r = {}; | ||
| for (const k in object) { | ||
| const objectK = object[k]; | ||
| if (objectK instanceof Array) { | ||
| const newArray = []; | ||
| for (const el of objectK) { | ||
| newArray.push(clonePlainObjectDeep(el)); | ||
| } | ||
| r[k] = newArray; | ||
| } | ||
| else if (typeof objectK === "object") { | ||
| r[k] = clonePlainObjectDeep(objectK); | ||
| } | ||
| else { | ||
| r[k] = objectK; | ||
| } | ||
| } | ||
| return r; | ||
| } |
+12
-3
| { | ||
| "name": "@hydroperx/inputaction", | ||
| "version": "1.0.0", | ||
| "version": "1.0.1", | ||
| "description": "Input actions library.", | ||
| "main": "src/index.ts", | ||
| "repository": "https://github.com/hydroperx/inputaction", | ||
@@ -10,3 +9,13 @@ "license": "Apache-2.0", | ||
| "keywords": ["input", "shortcut", "keyboard", "key combination"], | ||
| "scripts": {}, | ||
| "files": ["/dist"], | ||
| "scripts": { | ||
| "build": "tsc", | ||
| "prepublishOnly": "npm run build" | ||
| }, | ||
| "exports": { | ||
| ".": { | ||
| "default": "./dist/index.js", | ||
| "types": "./dist/index.d.ts" | ||
| } | ||
| }, | ||
| "dependencies": { | ||
@@ -13,0 +22,0 @@ "@hydroperx/event": "^1.0.0", |
-76
| import Input from "./Input"; | ||
| import { InputActionAtom, InputActionKey, InputActionKeyName } from "./InputAction"; | ||
| export { Input }; | ||
| export type { | ||
| InputActionAtom, | ||
| InputActionKey, | ||
| InputActionKeyName, | ||
| } from "./InputAction"; | ||
| /** | ||
| * Returns the display text of a shortcut, such as `"Ctrl+A"`. | ||
| * | ||
| * @param param Either an action name or a series of action atoms. | ||
| */ | ||
| export function shortcutDisplayText(param: string | InputActionAtom[]): string { | ||
| if (typeof param == "string") { | ||
| return shortcutDisplayText(Input.input.getActions()[param]); | ||
| } | ||
| if (!param) return ""; | ||
| for (const atom of param as InputActionAtom[]) { | ||
| if (atom.hasOwnProperty("key")) { | ||
| const key = atom as InputActionKey; | ||
| const parts = []; | ||
| if (key.control) { | ||
| parts.push("Ctrl"); | ||
| } | ||
| if (key.alt) { | ||
| parts.push("Alt"); | ||
| } | ||
| if (key.shift) { | ||
| parts.push("Shift"); | ||
| } | ||
| parts.push(inputActionKeyNameDisplayText(key.key)); | ||
| return parts.join("+"); | ||
| } | ||
| } | ||
| return ""; | ||
| } | ||
| export function inputActionKeyNameDisplayText(name: InputActionKeyName): string { | ||
| switch (name) { | ||
| case "leftArrow": return "Left"; | ||
| case "rightArrow": return "Right"; | ||
| case "upArrow": return "Up"; | ||
| case "downArrow": return "Down"; | ||
| case "spacebar": return "Space"; | ||
| case "enter": return "Enter"; | ||
| case "backspace": return "Backspace"; | ||
| case "minus": return "Minus"; | ||
| case "plus": return "Plus"; | ||
| case "tab": return "Tab"; | ||
| case "f1": return "F1"; | ||
| case "f2": return "F2"; | ||
| case "f3": return "F3"; | ||
| case "f4": return "F4"; | ||
| case "f5": return "F5"; | ||
| case "f6": return "F6"; | ||
| case "f7": return "F7"; | ||
| case "f8": return "F8"; | ||
| case "f9": return "F9"; | ||
| case "f10": return "F10"; | ||
| case "f11": return "F11"; | ||
| case "f12": return "F12"; | ||
| case "0": return "0"; | ||
| case "1": return "1"; | ||
| case "2": return "2"; | ||
| case "3": return "3"; | ||
| case "4": return "4"; | ||
| case "5": return "5"; | ||
| case "6": return "6"; | ||
| case "7": return "7"; | ||
| case "8": return "8"; | ||
| case "9": return "9"; | ||
| default: return name.toUpperCase(); | ||
| } | ||
| } |
-237
| import clonePlainObject from "./util/clonePlainObject"; | ||
| import { InputActionAtom, InputActionKey, InputActionKeyName, navigatorKeyToThis } from "./InputAction"; | ||
| import assert from "assert"; | ||
| import { TypedEventTarget } from "@hydroperx/event"; | ||
| /** | ||
| * The `Input` class handles action mapping and user input event listening. | ||
| * | ||
| * # Getting started | ||
| * | ||
| * The following code demonstrates using arrows and WASD keys | ||
| * for entity movement: | ||
| * | ||
| * ```ts | ||
| * import { Input } from "@hydroperx/inputaction"; | ||
| * | ||
| * Input.input.setActions({ | ||
| * "moveLeft": [ | ||
| * { key: "a" }, | ||
| * { key: "leftArrow" }, | ||
| * ], | ||
| * "moveRight": [ | ||
| * { key: "d" }, | ||
| * { key: "rightArrow" }, | ||
| * ], | ||
| * "moveUp": [ | ||
| * { key: "w" }, | ||
| * { key: "upArrow" }, | ||
| * ], | ||
| * "moveDown": [ | ||
| * { key: "s" }, | ||
| * { key: "downArrow" }, | ||
| * ], | ||
| * }); | ||
| * | ||
| * Input.input.addEventListener("inputPressed", () => { | ||
| * const shouldMoveRight = Input.input.isPressed("moveRight"); | ||
| * }); | ||
| * ``` | ||
| * | ||
| * # Built-in actions | ||
| * | ||
| * The following actions are pre-defined in every action map | ||
| * and can be overriden: | ||
| * | ||
| * * `escape` - Used for escaping out in the user interface. | ||
| * * `navigateLeft` — Used for focusing the left neighbor of an user interface control. | ||
| * * `navigateRight` — Used for focusing the right neighbor of an user interface control. | ||
| * * `navigateUp` — Used for focusing the top neighbor of an user interface control. | ||
| * * `navigateDown` — Used for focusing the bottom neighbor of an user interface control. | ||
| * | ||
| * # Events | ||
| * | ||
| * This class extends `EventTarget` and may dispatch the following events: | ||
| * | ||
| * ```ts | ||
| * // Dispatched when user input starts being pressed or | ||
| * // is continuously pressed. | ||
| * inputPressed: Event; | ||
| * // Dispatched when any user input is released. | ||
| * inputReleased: Event; | ||
| * // Dispatched when the actions map is updated. | ||
| * actionsUpdated: Event; | ||
| * ``` | ||
| */ | ||
| export default class Input extends (EventTarget as TypedEventTarget<{ | ||
| inputPressed: Event; | ||
| inputReleased: Event; | ||
| actionsUpdated: Event; | ||
| }>) { | ||
| /** | ||
| * The singleton instance of the `Input` class. | ||
| */ | ||
| public static readonly input = new Input; | ||
| // Actions map | ||
| private mMap: Record<string, InputActionAtom[]> = { | ||
| ...Input.builtin(), | ||
| }; | ||
| /** | ||
| * Returns the current action map in read-only mode. | ||
| */ | ||
| public getActions(): Record<string, InputActionAtom[]> { | ||
| return clonePlainObject(this.mMap, true); | ||
| } | ||
| /** | ||
| * Updates the action map. | ||
| * @fires Input#actionsUpdated | ||
| */ | ||
| public setActions(map: Record<string, InputActionAtom[]>) { | ||
| // Update static map | ||
| this.mMap = { | ||
| ...Input.builtin(), | ||
| ...clonePlainObject(map, true), | ||
| }; | ||
| // Dispatch update event | ||
| this.dispatchEvent(new Event("actionsUpdated")); | ||
| } | ||
| private static builtin(): Record<string, InputActionAtom[]> { | ||
| return { | ||
| "escape": [ { key: "escape" } ], | ||
| "navigateLeft": [ { key: "leftArrow" } ], | ||
| "navigateRight": [ { key: "rightArrow" } ], | ||
| "navigateUp": [ { key: "upArrow" } ], | ||
| "navigateDown": [ { key: "downArrow" } ], | ||
| }; | ||
| } | ||
| // Static pressed state pool | ||
| private static readonly mPressedStatePoolKeys: Map<InputActionKeyName, PressedState> = new Map(); | ||
| static { | ||
| if (typeof window !== "undefined") | ||
| { | ||
| window.addEventListener("keydown", evt => { | ||
| const keyName = navigatorKeyToThis(evt.key); | ||
| if (keyName !== undefined) { | ||
| // Mutate pressed state | ||
| let state = Input.mPressedStatePoolKeys.get(keyName); | ||
| if (state === undefined) { | ||
| state = { | ||
| pressed: false, | ||
| pressedTimestamp: 0, | ||
| control: false, | ||
| shift: false, | ||
| alt: false, | ||
| }; | ||
| Input.mPressedStatePoolKeys.set(keyName, state); | ||
| } | ||
| state.pressed = true; | ||
| state.pressedTimestamp = Date.now(); | ||
| state.control = evt.ctrlKey; | ||
| state.shift = evt.shiftKey; | ||
| state.alt = evt.altKey; | ||
| // Dispatch pressed event | ||
| const evt1 = new Event("inputPressed", { cancelable: true }); | ||
| const r = Input.input.dispatchEvent(evt1); | ||
| if (evt1.defaultPrevented) | ||
| { | ||
| evt.preventDefault(); | ||
| } | ||
| return r; | ||
| } | ||
| }); | ||
| window.addEventListener("keyup", evt => { | ||
| const keyName = navigatorKeyToThis(evt.key); | ||
| if (keyName !== undefined) { | ||
| // Mutate pressed state | ||
| let state = Input.mPressedStatePoolKeys.get(keyName); | ||
| if (state === undefined) { | ||
| state = { | ||
| pressed: false, | ||
| pressedTimestamp: 0, | ||
| control: false, | ||
| shift: false, | ||
| alt: false, | ||
| }; | ||
| Input.mPressedStatePoolKeys.set(keyName, state); | ||
| } | ||
| state.pressed = false; | ||
| state.control = false; | ||
| state.shift = false; | ||
| state.alt = false; | ||
| // Dispatch released event | ||
| const evt1 = new Event("inputReleased", { cancelable: true }); | ||
| const r = Input.input.dispatchEvent(evt1); | ||
| if (evt1.defaultPrevented) | ||
| { | ||
| evt.preventDefault(); | ||
| } | ||
| return r; | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| /** | ||
| * Determines whether an action is pressed. | ||
| * @throws Error Thrown if the action does not exist. | ||
| */ | ||
| public isPressed(name: string): boolean { | ||
| const action = this.mMap[name]; | ||
| assert(action !== undefined, "The specified action for Input.isPressed(name) does not exist."); | ||
| for (const item of action!) { | ||
| if (item.hasOwnProperty("key")) { | ||
| const inputActionKey = item as InputActionKey; | ||
| const pressedState = Input.mPressedStatePoolKeys.get(inputActionKey.key); | ||
| const pressed = pressedState !== undefined && pressedState.pressed | ||
| && (inputActionKey.control ? pressedState.control : !pressedState.control) | ||
| && (inputActionKey.shift ? pressedState.shift : !pressedState.shift) | ||
| && (inputActionKey.alt ? pressedState.alt : !pressedState.alt); | ||
| if (pressed) { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| /** | ||
| * Determines whether an action has been just pressed right now. | ||
| * @throws Error Thrown if the action does not exist. | ||
| */ | ||
| public justPressed(name: string): boolean { | ||
| const action = this.mMap[name]; | ||
| assert(action !== undefined, "The specified action for Input.isPressed(name) does not exist."); | ||
| for (const item of action!) { | ||
| if (item.hasOwnProperty("key")) { | ||
| const inputActionKey = item as InputActionKey; | ||
| const pressedState = Input.mPressedStatePoolKeys.get(inputActionKey.key); | ||
| const pressed = pressedState !== undefined && pressedState.pressed | ||
| && (inputActionKey.control ? pressedState.control : !pressedState.control) | ||
| && (inputActionKey.shift ? pressedState.shift : !pressedState.shift) | ||
| && (inputActionKey.alt ? pressedState.alt : !pressedState.alt); | ||
| if (pressed && pressedState.pressedTimestamp > Date.now() - 15) { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| } | ||
| type PressedState = { | ||
| pressed: boolean, | ||
| pressedTimestamp: number, | ||
| control: boolean, | ||
| shift: boolean, | ||
| alt: boolean, | ||
| }; |
| /** | ||
| * Part of the `Input` class. It is used to describe a specific user input key or gamepad | ||
| * button. | ||
| */ | ||
| export type InputActionAtom = | ||
| | InputActionKey; | ||
| /** | ||
| * Part of the Input API. It is used to describe a specific user input key. | ||
| */ | ||
| export type InputActionKey = { | ||
| /** | ||
| * Identifies the input key. | ||
| */ | ||
| key: InputActionKeyName, | ||
| /** | ||
| * Whether the `Ctrl` key is used. | ||
| */ | ||
| control?: boolean, | ||
| /** | ||
| * Whether the `Alt` key is used. | ||
| */ | ||
| alt?: boolean, | ||
| /** | ||
| * Whether the `Shift` key is used. | ||
| */ | ||
| shift?: boolean, | ||
| }; | ||
| /** | ||
| * Part of the Input API. It is used to identify the name of a specific user input key. | ||
| */ | ||
| export type InputActionKeyName = | ||
| | "leftArrow" | "rightArrow" | "upArrow" | "downArrow" | ||
| | "spacebar" | "enter" | "backspace" | ||
| | "minus" | "plus" | "tab" | "escape" | ||
| | "assign" | "comma" | "dot" | "semicolon" | ||
| | "f1" | "f2" | "f3" | "f4" | "f5" | "f6" | "f7" | "f8" | "f9" | "f10" | "f11" | "f12" | ||
| | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | ||
| | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | ||
| | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"; | ||
| const mapNavigatorKeyToThis = new Map<string, InputActionKeyName>([ | ||
| ["arrowleft", "leftArrow"], | ||
| ["arrowright", "rightArrow"], | ||
| ["arrowup", "upArrow"], | ||
| ["arrowdown", "downArrow"], | ||
| ["escape", "escape"], | ||
| [" ", "spacebar"], | ||
| ["enter", "enter"], | ||
| ["backspace", "backspace"], | ||
| ["subtract", "minus"], | ||
| ["add", "plus"], | ||
| ["tab", "tab"], | ||
| ["+", "plus"], | ||
| ["-", "minus"], | ||
| ["=", "assign"], | ||
| [",", "comma"], | ||
| [".", "dot"], | ||
| [";", "semicolon"], | ||
| ]); | ||
| export function navigatorKeyToThis(name: string): InputActionKeyName | undefined { | ||
| name = name.toLowerCase(); | ||
| if (/^([a-z0-9]|f\d\d?)$/.test(name)) { | ||
| return name as any; | ||
| } | ||
| return mapNavigatorKeyToThis.get(name); | ||
| } |
| /** | ||
| * Clones a plain object. `deep` is `false` by default. | ||
| */ | ||
| export default function clonePlainObject(object: any, deep: boolean = false) { | ||
| if (deep) { | ||
| return clonePlainObjectDeep(object); | ||
| } | ||
| const r: any = {}; | ||
| for (const k in object) { | ||
| r[k] = object[k]; | ||
| } | ||
| return r; | ||
| } | ||
| function clonePlainObjectDeep(object: any) { | ||
| const r: any = {}; | ||
| for (const k in object) { | ||
| const objectK = object[k]; | ||
| if (objectK instanceof Array) { | ||
| const newArray = []; | ||
| for (const el of objectK) { | ||
| newArray.push(clonePlainObjectDeep(el)); | ||
| } | ||
| r[k] = newArray; | ||
| } else if (typeof objectK === "object") { | ||
| r[k] = clonePlainObjectDeep(objectK); | ||
| } else { | ||
| r[k] = objectK; | ||
| } | ||
| } | ||
| return r; | ||
| } |
| { | ||
| "compilerOptions": { | ||
| "target": "ES2024", | ||
| "moduleResolution": "bundler", | ||
| "lib": ["es2017", "DOM", "ES2020.Intl"], | ||
| "strict": true, | ||
| "noEmit": true | ||
| } | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
28773
7.25%11
37.5%479
20.65%1
Infinity%