@bitrix24/b24ui-nuxt
Version:
Bitrix24 UI-Kit for developing web applications REST API for NUXT & VUE
144 lines (143 loc) • 5.07 kB
JavaScript
import { ref, computed, toValue } from "vue";
import { useEventListener, useActiveElement, useDebounceFn } from "@vueuse/core";
import { useKbd } from "./useKbd.js";
const chainedShortcutRegex = /^[^-]+.*-.*[^-]+$/;
const combinedShortcutRegex = /^[^_]+.*_.*[^_]+$/;
const shiftableKeys = ["arrowleft", "arrowright", "arrowup", "arrowright", "tab", "escape", "enter", "backspace"];
export function extractShortcuts(items) {
const shortcuts = {};
function traverse(items2) {
items2.forEach((item) => {
if (item.kbds?.length && (item.onSelect || item.onClick)) {
const shortcutKey = item.kbds.join("_");
shortcuts[shortcutKey] = item.onSelect || item.onClick;
}
if (item.children) {
traverse(item.children.flat());
}
if (item.items) {
traverse(item.items.flat());
}
});
}
traverse(items.flat());
return shortcuts;
}
export function defineShortcuts(config, options = {}) {
const chainedInputs = ref([]);
const clearChainedInput = () => {
chainedInputs.value.splice(0, chainedInputs.value.length);
};
const debouncedClearChainedInput = useDebounceFn(clearChainedInput, options.chainDelay ?? 800);
const { macOS } = useKbd();
const activeElement = useActiveElement();
const onKeyDown = (e) => {
if (!e.key) {
return;
}
const alphabetKey = /^[a-z]{1}$/i.test(e.key);
const shiftableKey = shiftableKeys.includes(e.key.toLowerCase());
let chainedKey;
chainedInputs.value.push(e.key);
if (chainedInputs.value.length >= 2) {
chainedKey = chainedInputs.value.slice(-2).join("-");
for (const shortcut of shortcuts.value.filter((s) => s.chained)) {
if (shortcut.key !== chainedKey) {
continue;
}
if (shortcut.enabled) {
e.preventDefault();
shortcut.handler(e);
}
clearChainedInput();
return;
}
}
for (const shortcut of shortcuts.value.filter((s) => !s.chained)) {
if (e.key.toLowerCase() !== shortcut.key) {
continue;
}
if (e.metaKey !== shortcut.metaKey) {
continue;
}
if (e.ctrlKey !== shortcut.ctrlKey) {
continue;
}
if ((alphabetKey || shiftableKey) && e.shiftKey !== shortcut.shiftKey) {
continue;
}
if (shortcut.enabled) {
e.preventDefault();
shortcut.handler(e);
}
clearChainedInput();
return;
}
debouncedClearChainedInput();
};
const usingInput = computed(() => {
const tagName = activeElement.value?.tagName;
const contentEditable = activeElement.value?.contentEditable;
const usingInput2 = !!(tagName === "INPUT" || tagName === "TEXTAREA" || contentEditable === "true" || contentEditable === "plaintext-only");
if (usingInput2) {
return activeElement.value?.name || true;
}
return false;
});
const shortcuts = computed(() => {
return Object.entries(toValue(config)).map(([key, shortcutConfig]) => {
if (!shortcutConfig) {
return null;
}
let shortcut;
if (key.includes("-") && key !== "-" && !key.includes("_") && !key.match(chainedShortcutRegex)?.length) {
console.trace(`[Shortcut] Invalid key: "${key}"`);
}
if (key.includes("_") && key !== "_" && !key.match(combinedShortcutRegex)?.length) {
console.trace(`[Shortcut] Invalid key: "${key}"`);
}
const chained = key.includes("-") && key !== "-" && !key.includes("_");
if (chained) {
shortcut = {
key: key.toLowerCase(),
metaKey: false,
ctrlKey: false,
shiftKey: false,
altKey: false
};
} else {
const keySplit = key.toLowerCase().split("_").map((k) => k);
shortcut = {
key: keySplit.filter((k) => !["meta", "command", "ctrl", "shift", "alt", "option"].includes(k)).join("_"),
metaKey: keySplit.includes("meta") || keySplit.includes("command"),
ctrlKey: keySplit.includes("ctrl"),
shiftKey: keySplit.includes("shift"),
altKey: keySplit.includes("alt") || keySplit.includes("option")
};
}
shortcut.chained = chained;
if (!macOS.value && shortcut.metaKey && !shortcut.ctrlKey) {
shortcut.metaKey = false;
shortcut.ctrlKey = true;
}
if (typeof shortcutConfig === "function") {
shortcut.handler = shortcutConfig;
} else if (typeof shortcutConfig === "object") {
shortcut = { ...shortcut, handler: shortcutConfig.handler };
}
if (!shortcut.handler) {
console.trace("[Shortcut] Invalid value");
return null;
}
let enabled = true;
if (!shortcutConfig.usingInput) {
enabled = !usingInput.value;
} else if (typeof shortcutConfig.usingInput === "string") {
enabled = usingInput.value === shortcutConfig.usingInput;
}
shortcut.enabled = enabled;
return shortcut;
}).filter(Boolean);
});
return useEventListener("keydown", onKeyDown);
}