kbind
Version:
Library for working with keybinds
679 lines (678 loc) • 18.6 kB
JavaScript
var D = Object.defineProperty;
var L = (e, i, a) => i in e ? D(e, i, { enumerable: !0, configurable: !0, writable: !0, value: a }) : e[i] = a;
var p = (e, i, a) => L(e, typeof i != "symbol" ? i + "" : i, a);
import { isUndefined as m, isArray as K, sortedIndex as A, isNil as S } from "lodash";
var t = /* @__PURE__ */ ((e) => (e.Alt = "Alt", e.AltLeft = "AltLeft", e.AltRight = "AltRight", e.ArrowDown = "ArrowDown", e.ArrowLeft = "ArrowLeft", e.ArrowRight = "ArrowRight", e.ArrowUp = "ArrowUp", e.Backquote = "Backquote", e.Backslash = "Backslash", e.Backspace = "Backspace", e.BracketLeft = "BracketLeft", e.BracketRight = "BracketRight", e.CapsLock = "CapsLock", e.Comma = "Comma", e.Control = "Control", e.ControlLeft = "ControlLeft", e.ControlRight = "ControlRight", e.Delete = "Delete", e.Digit0 = "Digit0", e.Digit1 = "Digit1", e.Digit2 = "Digit2", e.Digit3 = "Digit3", e.Digit4 = "Digit4", e.Digit5 = "Digit5", e.Digit6 = "Digit6", e.Digit7 = "Digit7", e.Digit8 = "Digit8", e.Digit9 = "Digit9", e.Minus = "Minus", e.Equal = "Equal", e.End = "End", e.Enter = "Enter", e.Escape = "Escape", e.Home = "Home", e.Insert = "Insert", e.IntlBackslash = "IntlBackslash", e.IntlRo = "IntlRo", e.IntlYen = "IntlYen", e.F1 = "F1", e.F2 = "F2", e.F3 = "F3", e.F4 = "F4", e.F5 = "F5", e.F6 = "F6", e.F7 = "F7", e.F8 = "F8", e.F9 = "F9", e.F10 = "F10", e.F11 = "F11", e.F12 = "F12", e.F13 = "F13", e.F14 = "F14", e.F15 = "F15", e.F16 = "F16", e.F17 = "F17", e.F18 = "F18", e.F19 = "F19", e.F20 = "F20", e.F21 = "F21", e.F22 = "F22", e.F23 = "F23", e.F24 = "F24", e.KeyA = "KeyA", e.KeyB = "KeyB", e.KeyC = "KeyC", e.KeyD = "KeyD", e.KeyE = "KeyE", e.KeyF = "KeyF", e.KeyG = "KeyG", e.KeyH = "KeyH", e.KeyI = "KeyI", e.KeyJ = "KeyJ", e.KeyK = "KeyK", e.KeyL = "KeyL", e.KeyM = "KeyM", e.KeyN = "KeyN", e.KeyO = "KeyO", e.KeyP = "KeyP", e.KeyQ = "KeyQ", e.KeyR = "KeyR", e.KeyS = "KeyS", e.KeyT = "KeyT", e.KeyU = "KeyU", e.KeyV = "KeyV", e.KeyW = "KeyW", e.KeyX = "KeyX", e.KeyY = "KeyY", e.KeyZ = "KeyZ", e.Numpad0 = "Numpad0", e.Numpad1 = "Numpad1", e.Numpad2 = "Numpad2", e.Numpad3 = "Numpad3", e.Numpad4 = "Numpad4", e.Numpad5 = "Numpad5", e.Numpad6 = "Numpad6", e.Numpad7 = "Numpad7", e.Numpad8 = "Numpad8", e.Numpad9 = "Numpad9", e.NumpadAdd = "NumpadAdd", e.NumpadSubtract = "NumpadSubtract", e.NumpadMultiply = "NumpadMultiply", e.NumpadDivide = "NumpadDivide", e.NumpadEqual = "NumpadEqual", e.NumpadDecimal = "NumpadDecimal", e.NumpadComma = "NumpadComma", e.NumpadEnter = "NumpadEnter", e.PageUp = "PageUp", e.PageDown = "PageDown", e.Pause = "Pause", e.Period = "Period", e.Quote = "Quote", e.ScrollLock = "ScrollLock", e.Semicolon = "Semicolon", e.Shift = "Shift", e.ShiftLeft = "ShiftLeft", e.ShiftRight = "ShiftRight", e.Slash = "Slash", e.Space = "Space", e.Tab = "Tab", e))(t || {});
const v = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
default: t
}, Symbol.toStringTag, { value: "Module" })), R = [
t.AltLeft,
t.AltRight,
t.ControlLeft,
t.ControlRight,
t.ShiftLeft,
t.ShiftRight
];
t.AltLeft, t.AltRight, t.ShiftLeft, t.ShiftRight, t.ControlLeft, t.ControlRight;
const C = [
t.ArrowDown,
t.ArrowLeft,
t.ArrowRight,
t.ArrowUp,
t.Backquote,
t.Backslash,
t.Backspace,
t.BracketLeft,
t.BracketRight,
t.CapsLock,
t.Comma,
t.Delete,
t.Digit0,
t.Digit1,
t.Digit2,
t.Digit3,
t.Digit4,
t.Digit5,
t.Digit6,
t.Digit7,
t.Digit8,
t.Digit9,
t.Minus,
t.Equal,
t.End,
t.Enter,
t.Escape,
t.Home,
t.Insert,
t.IntlBackslash,
t.IntlRo,
t.IntlYen,
t.F1,
t.F2,
t.F3,
t.F4,
t.F5,
t.F6,
t.F7,
t.F8,
t.F9,
t.F10,
t.F11,
t.F12,
t.F13,
t.F14,
t.F15,
t.F16,
t.F17,
t.F18,
t.F19,
t.F20,
t.F21,
t.F22,
t.F23,
t.F24,
t.KeyA,
t.KeyB,
t.KeyC,
t.KeyD,
t.KeyE,
t.KeyF,
t.KeyG,
t.KeyH,
t.KeyI,
t.KeyJ,
t.KeyK,
t.KeyL,
t.KeyM,
t.KeyN,
t.KeyO,
t.KeyP,
t.KeyQ,
t.KeyR,
t.KeyS,
t.KeyT,
t.KeyU,
t.KeyV,
t.KeyW,
t.KeyX,
t.KeyY,
t.KeyZ,
t.Numpad0,
t.Numpad1,
t.Numpad2,
t.Numpad3,
t.Numpad4,
t.Numpad5,
t.Numpad6,
t.Numpad7,
t.Numpad8,
t.Numpad9,
t.NumpadAdd,
t.NumpadSubtract,
t.NumpadMultiply,
t.NumpadDivide,
t.NumpadEqual,
t.NumpadDecimal,
t.NumpadComma,
t.NumpadEnter,
t.PageUp,
t.PageDown,
t.Pause,
t.Period,
t.Quote,
t.ScrollLock,
t.Semicolon,
t.Slash,
t.Space,
t.Tab
], k = {
[]: "Alt",
[]: "AltLeft",
[]: "AltRight",
[]: "ArrowDown",
[]: "ArrowLeft",
[]: "ArrowRight",
[]: "ArrowUp",
[]: "`",
[]: "\\",
[]: "Backspace",
[]: "[",
[]: "]",
[]: "CapsLock",
[]: ",",
[]: "Ctrl",
[]: "ControlLeft",
[]: "ControlRight",
[]: "Delete",
[]: "0",
[]: "1",
[]: "2",
[]: "3",
[]: "4",
[]: "5",
[]: "6",
[]: "7",
[]: "8",
[]: "9",
[]: "-",
[]: "=",
[]: "End",
[]: "Enter",
[]: "Escape",
[]: "Home",
[]: "Insert",
[]: "IntlBackslash",
[]: "IntlRo",
[]: "IntlYen",
[]: "F1",
[]: "F2",
[]: "F3",
[]: "F4",
[]: "F5",
[]: "F6",
[]: "F7",
[]: "F8",
[]: "F9",
[]: "F10",
[]: "F11",
[]: "F12",
[]: "F13",
[]: "F14",
[]: "F15",
[]: "F16",
[]: "F17",
[]: "F18",
[]: "F19",
[]: "F20",
[]: "F21",
[]: "F22",
[]: "F23",
[]: "F24",
[]: "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",
[]: "Numpad 0",
[]: "Numpad 1",
[]: "Numpad 2",
[]: "Numpad 3",
[]: "Numpad 4",
[]: "Numpad 5",
[]: "Numpad 6",
[]: "Numpad 7",
[]: "Numpad 8",
[]: "Numpad 9",
[]: "+",
[]: "-",
[]: "*",
[]: "/",
[]: "=",
[]: ".",
[]: ",",
[]: "Enter",
[]: "PageUp",
[]: "PageDown",
[]: "Pause",
[]: ".",
[]: '"',
[]: "ScrollLock",
[]: ";",
[]: "Shift",
[]: "ShiftLeft",
[]: "ShiftRight",
[]: "/",
[]: "Space",
[]: "Tab"
};
function w(e, i) {
const { target: a } = e;
if (!(a instanceof HTMLInputElement || a instanceof HTMLTextAreaElement))
return !1;
const s = a.getAttribute("type") ?? "text-area";
return m(i) ? !0 : K(i) ? (i == null ? void 0 : i.includes(s)) ?? !0 : s === i;
}
const y = [
t.Backquote,
t.Backslash,
t.BracketLeft,
t.BracketRight,
t.Comma,
t.Digit0,
t.Digit1,
t.Digit2,
t.Digit3,
t.Digit4,
t.Digit5,
t.Digit6,
t.Digit7,
t.Digit8,
t.Digit9,
t.Minus,
t.Equal,
t.IntlBackslash,
t.IntlRo,
t.IntlYen,
t.KeyA,
t.KeyB,
t.KeyC,
t.KeyD,
t.KeyE,
t.KeyF,
t.KeyG,
t.KeyH,
t.KeyI,
t.KeyJ,
t.KeyK,
t.KeyL,
t.KeyM,
t.KeyN,
t.KeyO,
t.KeyP,
t.KeyQ,
t.KeyR,
t.KeyS,
t.KeyT,
t.KeyU,
t.KeyV,
t.KeyW,
t.KeyX,
t.KeyY,
t.KeyZ,
t.Quote,
t.Semicolon,
t.Slash
], E = [
...y,
t.Backspace,
t.Delete,
t.Numpad0,
t.Numpad1,
t.Numpad2,
t.Numpad3,
t.Numpad4,
t.Numpad5,
t.Numpad6,
t.Numpad7,
t.Numpad8,
t.Numpad9,
t.NumpadAdd,
t.NumpadSubtract,
t.NumpadMultiply,
t.NumpadDivide,
t.NumpadEqual,
t.NumpadDecimal,
t.NumpadComma,
t.Space
], I = [t.Home, t.End, t.PageUp, t.PageDown], P = [
t.ArrowLeft,
t.ArrowRight,
t.Backspace,
t.Delete,
t.Home,
t.End,
t.KeyA,
t.KeyC,
t.Insert,
t.KeyX,
t.KeyV,
t.KeyZ,
t.KeyY
], B = [
t.Numpad0,
t.Numpad1,
t.Numpad2,
t.Numpad3,
t.Numpad4,
t.Numpad5,
t.Numpad6,
t.Numpad7,
t.Numpad8,
t.Numpad9
], b = [
t.ArrowLeft,
t.ArrowRight,
t.Home,
t.End,
t.Delete,
t.Insert
], M = [t.ArrowLeft, t.ArrowRight, t.Home, t.End];
function N(e) {
if (!w(e, "text"))
return !0;
const { ctrlKey: i, shiftKey: a, altKey: s, code: n } = e;
return !i && !a && !s ? !I.includes(n) && !E.includes(n) : i && !a && !s ? !P.includes(n) : !i && a && !s ? !b.includes(n) && !y.includes(n) : !i && !a && s ? !B.includes(n) : i && a && !s ? !M.includes(n) : !0;
}
const r = class r {
/**
* Checks if either the left or right Control key is currently pressed.
*
* @returns {boolean} `true` if either ControlLeft or ControlRight key is pressed,
* otherwise `false`.
*/
static get isControlPressed() {
return r.state.modifierKeys.includes(t.ControlLeft) || r.state.modifierKeys.includes(t.ControlRight);
}
/**
* Checks if either the left or right Shift key is currently pressed.
*
* @returns {boolean} `true` if either ShiftLeft or ShiftRight key is pressed,
* otherwise `false`
*/
static get isShiftPressed() {
return r.state.modifierKeys.includes(t.ShiftLeft) || r.state.modifierKeys.includes(t.ShiftRight);
}
/**
* Checks if either the left or right Alt key is currently pressed.
*
* @returns {boolean} `true` if either AltLeft or AltRight key is pressed,
* otherwise `false`.
*/
static get isAltPressed() {
return r.state.modifierKeys.includes(t.AltLeft) || r.state.modifierKeys.includes(t.AltRight);
}
/**
* Gets the sorted list of layer ids based on their priority.
*
* NOTE: heavy operation, use it wisely.
*
* @returns {string[]} An array of layer IDs sorted in descending order of their priority.
*/
static get layerIds() {
return Object.keys(r.state.layers).sort(
(i, a) => {
const s = r.state.layers[i];
return r.state.layers[a].priority - s.priority;
}
);
}
/**
* Gets the sorted list of listener ids based on their priority.
*
* NOTE: heavy operation, use it wisely.
*
* @returns {string[]} An array of listener IDs sorted in descending order of their priority.
*/
static get listenerIds() {
return Object.keys(r.state.listeners).sort(
(i, a) => {
const s = r.state.listeners[i];
return r.state.listeners[a].priority - s.priority;
}
);
}
/**
* Gets the highest priority of the layer in the stack.
*
* NOTE: heavy operation, use it wisely.
*
* @returns {number | undefined} The highest priority of the layer in the stack.
*/
static get highestLayerPriority() {
const i = r.layerIds[0];
if (!m(i))
return r.state.layers[i].priority;
}
/**
* Mounts the KeyBinder event listeners to the window object.
* This method attaches the following event listeners:
* - 'blur': Calls the `blurHandler` method when the window loses focus.
* - 'keydown': Calls the `keyDownHandler` method when a key is pressed down.
* - 'keyup': Calls the `keyUpHandler` method when a key is released.
*/
static mount() {
window.addEventListener("blur", r.blurHandler), window.addEventListener("keydown", r.keyDownHandler), window.addEventListener("keyup", r.keyUpHandler);
}
/**
* Unmounts the KeyBinder by removing event listeners for 'blur', 'keydown', and 'keyup' events.
* This method should be called to clean up event listeners when the KeyBinder is no longer
* needed.
*/
static unmount() {
window.removeEventListener("blur", r.blurHandler), window.removeEventListener("keydown", r.keyDownHandler), window.removeEventListener("keyup", r.keyUpHandler);
}
/**
* Handles the blur event by resetting the state of modifier keys.
* This method is called when the window loses focus.
*/
static blurHandler() {
r.state.modifierKeys = [];
}
/**
* Handles the key down event by processing the key code and updating the state of modifier
* keys.
*
* @param event - The keyboard event triggered by a key press.
*/
static keyDownHandler(i) {
if (i.code) {
if (!r.isModifierKeyCode(i.code)) {
r.emit(i);
return;
}
r.state.modifierKeys.includes(i.code) || r.state.modifierKeys.splice(
A(r.state.modifierKeys, i.code),
0,
i.code
);
}
}
/**
* Handles the key up event for modifier keys.
*
* @param event - The keyboard event triggered on key up.
*/
static keyUpHandler(i) {
if (!i.code || !r.isModifierKeyCode(i.code)) return;
const a = r.state.modifierKeys.indexOf(i.code);
a !== -1 && r.state.modifierKeys.splice(a, 1);
}
/**
* Checks if the given key code is a modifier key.
*
* @param key - The key code to check.
* @returns `true` if the key is a modifier key, otherwise `false`.
*/
static isModifierKeyCode(i) {
return R.includes(i);
}
/**
* Checks if the given key code is a primary key.
*
* @param key - The key code to check.
* @returns `true` if the key is a primary key, otherwise `false`.
*/
static isPrimaryKeyCode(i) {
return C.includes(i);
}
/**
* Converts a given key code to its corresponding key name.
*
* @param key - The key code to convert.
* @returns The key name corresponding to the key code,
* or the key code itself if no key name is found.
*/
static normalizeKey(i) {
return k[i] || i;
}
/**
* Converts a key binding to an array of key names.
*
* @param keyBind - The key binding to convert.
* @returns An array of key names corresponding to the key binding.
*/
static normalizeKeyBind(i) {
return Array.isArray(i) ? i.map(r.normalizeKey).sort((a, s) => a.localeCompare(s)) : i ? [r.normalizeKey(i)] : [];
}
/**
* Gets the highest priority of the listener in the stack.
*
* NOTE: heavy operation, use it wisely.
*
* @param layerId - The id of the layer to get the highest listener priority from.
* @returns {number | undefined} The highest priority of the listener in the given layer stack.
*/
static getHighestListenerPriority(i) {
const { listenerIds: a } = r, s = a.find((n) => r.state.listeners[n].layerId === i);
if (!m(s))
return r.state.listeners[s].priority;
}
/**
* Registers a listener with a specified id.
*
* @param id - The unique identifier for the listener.
* @param listener - The listener metadata object containing the callback and other properties.
*/
static registerListener(i, a) {
r.state.listeners[i] = {
layerId: a.layerId ?? "default",
...a
};
}
/**
* Removes a listener from the state by its unique id.
*
* @param id - The unique identifier for the listener.
*/
static unregisterListener(i) {
delete r.state.listeners[i];
}
/**
* Registers a layer with a specified id.
*
* @param id - The unique identifier for the layer.
* @param layer - The layer metadata object containing the priority and propagation properties
*/
static registerLayer(i, a) {
r.state.layers[i] = a;
}
/**
* Removes a layer from the state by its unique id.
*
* @param id - The unique identifier for the layer.
*/
static unregisterLayer(i) {
delete r.state.layers[i];
}
static isValidEvent(i) {
return r.eventValidators.every(
(a) => a(i)
);
}
static checkIfBindModifierKeysPressed(i) {
const a = i, s = {
Control: {
Left: r.state.modifierKeys.includes(t.ControlLeft),
Right: r.state.modifierKeys.includes(t.ControlRight)
},
Shift: {
Left: r.state.modifierKeys.includes(t.ShiftLeft),
Right: r.state.modifierKeys.includes(t.ShiftRight)
},
Alt: {
Left: r.state.modifierKeys.includes(t.AltLeft),
Right: r.state.modifierKeys.includes(t.AltRight)
}
}, n = {
Control: {
Left: a.includes(t.Control) || a.includes(t.ControlLeft),
Right: a.includes(t.Control) || a.includes(t.ControlRight)
},
Shift: {
Left: a.includes(t.Shift) || a.includes(t.ShiftLeft),
Right: a.includes(t.Shift) || a.includes(t.ShiftRight)
},
Alt: {
Left: a.includes(t.Alt) || a.includes(t.AltLeft),
Right: a.includes(t.Alt) || a.includes(t.AltRight)
}
};
return Object.keys(s).every((l) => {
const o = l, u = s[o], d = n[o];
return u.Left && d.Left || u.Right && d.Right || !u.Left && !u.Right && !d.Left && !d.Right;
});
}
static findListenerIdByPressedKey(i) {
const { layerIds: a, listenerIds: s } = r;
let n;
if (a.some((o) => {
const u = r.state.layers[o], d = s.find((h) => {
const g = r.state.listeners[h];
if (g.layerId !== o) return !1;
const { bind: c } = g;
if (S(c)) return !1;
if (r.checkIfBindModifierKeysPressed(c)) {
const F = c[c.length - 1];
return F === i || F === "Enter" && i === "NumpadEnter";
}
return !1;
});
return n = d, !(!d && u.propagate);
})) return n;
}
static emit(i) {
var n, l, o;
if (!r.isValidEvent(i)) return;
const a = r.findListenerIdByPressedKey(
i.code
);
if (m(a)) return;
const s = r.state.listeners[a];
(n = s.options) != null && n.preventDefault && i.preventDefault(), (l = s.options) != null && l.stopPropagation && i.stopPropagation(), (o = s.handler) == null || o.call(s, i);
}
};
p(r, "state", {
modifierKeys: [],
listeners: {},
layers: {
default: {
priority: 0,
propagate: !0
}
}
}), /**
* An array of event validator functions used to determine the validity of keyboard events.
*
* It's necessary in case you don't want to trigger some listeners under certain conditions,
* e.g. `Ctrl+A` in a text field.
*
* @type {KeyBinder.EventValidator[]}
*/
p(r, "eventValidators", [
N
]);
let f = r;
const V = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
default: f
}, Symbol.toStringTag, { value: "Module" })), q = {
isTextInputEventValid: N
};
export {
v as Key,
V as KeyBinder,
f as default,
q as defaultEventValidators
};