toukey
Version:
Toukey is a Javascript library for keyboard shortcuts
246 lines (241 loc) • 6.29 kB
JavaScript
function transModifierKey(key) {
switch (key) {
case "ctrl":
return "Control";
case "space":
return " ";
case "left":
return "ArrowLeft";
case "right":
return "ArrowRight";
case "up":
return "ArrowUp";
case "bottom":
return "ArrowBottom";
default:
return key;
}
}
function filterBlank(str) {
return str.replace(/\s+/g, "");
}
function lowerCase(value) {
return value.toLowerCase();
}
function isString(value) {
return typeof value === "string";
}
function isFunction(value) {
return typeof value === "function";
}
function isObject(value) {
return typeof value === "object";
}
function isUndefined(value) {
return typeof value === "undefined";
}
function remove(arr, value) {
const index = arr.indexOf(value);
if (index !== -1) {
arr.splice(index, 1);
}
}
const KEY_DOWN = "keydown";
const KEY_UP = "keyup";
let _curScope = "default";
let _shouldBindToDocument = true;
let _isEnabled = true;
const _pressedKeys = [];
const _handlerMap = /* @__PURE__ */ new Map();
_handlerMap.set("*", []);
function _splitKeys(keys) {
return filterBlank(keys).split(",");
}
function _composeKeys(keys, subStr) {
return filterBlank(keys).split(subStr);
}
function _isComposeKey(key, subStr) {
return key.split(subStr).length > 1;
}
function _isKeyMatch(key) {
if (Array.isArray(key)) {
return lowerCase(
key.map((value) => transModifierKey(value)).sort().join("")
) === lowerCase(_pressedKeys.join(""));
} else {
return _pressedKeys.length === 1 && lowerCase(_pressedKeys[0]) === lowerCase(transModifierKey(key));
}
}
function _isKeyEventMatch(event, keydown, keyup) {
const { type } = event;
return type === "keydown" && keydown || type === "keyup" && keyup;
}
function _clearPressedKeys() {
_pressedKeys.length = 0;
}
function _handleEvent(event) {
_updatePressedKeys(event);
if (!isEnabled()) {
return;
}
const listForAll = _handlerMap.get("*") || [];
const listForScope = _handlerMap.get(_curScope) || [];
[...listForAll, ...listForScope].forEach((item) => {
const { handler, keydown, keyup, key, splitValue, once } = item;
const triggerIfMatch = (matchKey) => {
if (_isKeyMatch(matchKey) && _isKeyEventMatch(event, keydown, keyup)) {
handler.call(this, event);
once && queueMicrotask(() => unsubscribe(item, _curScope));
}
};
const chunks = _splitKeys(key);
chunks.forEach((chunk) => {
if (_isComposeKey(chunk, splitValue)) {
const composes = _composeKeys(chunk, splitValue);
triggerIfMatch(composes);
} else {
triggerIfMatch(chunk);
}
});
});
}
function _updatePressedKeys(event) {
const { type, key, repeat } = event;
if (type === "keydown" && !repeat) {
_pressedKeys.push(key);
}
if (type === "keyup") {
queueMicrotask(() => {
remove(_pressedKeys, key);
});
}
}
function subscribe(key, handler, options) {
if (!isString(key)) {
throw new Error("key must be string");
}
if (!isFunction(handler)) {
throw new Error("handler must be function");
}
const _key = key;
let _scope = "default";
let _splitValue = "+";
let _shouldHandleInKeydown = true;
let _shouldHandleInKeyup = false;
let _once = false;
if (isString(options)) {
_scope = options;
} else if (isObject(options)) {
Object.keys(options).forEach((key2) => {
if (key2 === "scope") {
_scope = options[key2];
}
if (key2 === "splitValue") {
_splitValue = options[key2];
}
if (key2 === KEY_DOWN) {
_shouldHandleInKeydown = true;
}
if (key2 === KEY_UP) {
_shouldHandleInKeyup = true;
}
if (key2 === "once") {
_once = true;
}
});
}
if (!_shouldHandleInKeydown && !_shouldHandleInKeyup) {
_shouldHandleInKeydown = true;
}
if (_shouldBindToDocument) {
_shouldBindToDocument = false;
document.addEventListener("keydown", _handleEvent);
document.addEventListener("keyup", _handleEvent);
window.addEventListener("focus", _clearPressedKeys);
window.addEventListener("blur", _clearPressedKeys);
}
if (!_handlerMap.has(_scope)) {
_handlerMap.set(_scope, []);
}
const _list = _handlerMap.get(_scope);
const _item = {
handler,
key: _key,
splitValue: _splitValue,
keydown: _shouldHandleInKeydown,
keyup: _shouldHandleInKeyup,
once: _once
};
_list.push(_item);
return () => {
unsubscribe(_item, _scope);
};
}
function unsubscribe(item, scope) {
const _list = _handlerMap.get(scope);
_list && remove(_list, item);
}
function getScope() {
return _curScope;
}
function setScope(scope) {
if (!isString(scope)) {
throw new Error("scope must be string");
}
_curScope = scope;
}
function deleteScope(scope) {
if (scope !== "*") {
_handlerMap.delete(scope);
_curScope = "default";
}
}
function clearAll() {
_handlerMap.clear();
_handlerMap.set("*", []);
_curScope = "default";
_clearPressedKeys();
}
function enable() {
_isEnabled = true;
}
function disable() {
_isEnabled = false;
}
function isEnabled() {
return _isEnabled;
}
function on(key, handler, options) {
subscribe(key, handler, options);
}
function off(key, handler, options) {
if (isUndefined(handler) && isUndefined(options)) {
_handlerMap.forEach((list, mapKey) => {
_handlerMap.set(
mapKey,
list.filter((item) => item.key !== key)
);
});
return;
}
const { scope = "default", keydown = true, keyup = false, splitValue = "+" } = options || {};
const listForAll = _handlerMap.get("*") || [];
const listForScope = _handlerMap.get(scope) || [];
const removeList = [...listForAll, ...listForScope].filter(
(item) => item.handler === handler && item.key === key && item.keydown === keydown && item.keyup === keyup && item.splitValue === splitValue
);
removeList.forEach((item) => {
unsubscribe(item, scope);
});
}
exports.clearAll = clearAll;
exports.deleteScope = deleteScope;
exports.disable = disable;
exports.enable = enable;
exports.getScope = getScope;
exports.isEnabled = isEnabled;
exports.off = off;
exports.on = on;
exports.setScope = setScope;
exports.subscribe = subscribe;
;