UNPKG

vuetify

Version:

Vue Material Component Framework

131 lines (129 loc) 4.05 kB
// Utilities import { onBeforeUnmount, toValue, watch } from 'vue'; import { IN_BROWSER } from "../util/index.js"; import { getCurrentInstance } from "../util/getCurrentInstance.js"; // Types export function useHotkey(keys, callback) { let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; if (!IN_BROWSER) return function () {}; const { event = 'keydown', inputs = false, preventDefault = true, sequenceTimeout = 1000 } = options; const isMac = navigator?.userAgent?.includes('Macintosh'); let timeout = 0; let keyGroups; let isSequence = false; let groupIndex = 0; function clearTimer() { if (!timeout) return; clearTimeout(timeout); timeout = 0; } function isInputFocused() { if (inputs) return false; const activeElement = document.activeElement; return activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.isContentEditable || activeElement.contentEditable === 'true'); } function resetSequence() { groupIndex = 0; clearTimer(); } function handler(e) { const group = keyGroups[groupIndex]; if (!group || isInputFocused()) return; if (!matchesKeyGroup(e, group)) { if (isSequence) resetSequence(); return; } if (preventDefault) e.preventDefault(); if (!isSequence) { callback(e); return; } clearTimer(); groupIndex++; if (groupIndex === keyGroups.length) { callback(e); resetSequence(); return; } timeout = window.setTimeout(resetSequence, sequenceTimeout); } function cleanup() { window.removeEventListener(event, handler); clearTimer(); } function splitKeySequence(str) { const groups = []; let current = ''; for (let i = 0; i < str.length; i++) { const char = str[i]; if (char === '-') { const next = str[i + 1]; // Treat '-' as a sequence delimiter only if the next character exists // and is NOT one of '-', '+', or '_' (these indicate the '-' belongs to the key itself) if (next && !['-', '+', '_'].includes(next)) { groups.push(current); current = ''; continue; } } current += char; } groups.push(current); return groups; } watch(() => toValue(keys), function (unrefKeys) { cleanup(); if (unrefKeys) { const groups = splitKeySequence(unrefKeys.toLowerCase()); isSequence = groups.length > 1; keyGroups = groups; resetSequence(); window.addEventListener(event, handler); } }, { immediate: true }); try { getCurrentInstance('useHotkey'); onBeforeUnmount(cleanup); } catch { // Not in Vue setup context } function parseKeyGroup(group) { const MODIFIERS = ['ctrl', 'shift', 'alt', 'meta', 'cmd']; // Split on +, -, or _ but keep empty strings which indicate consecutive separators (e.g. alt--) const parts = group.toLowerCase().split(/[+_-]/); const modifiers = Object.fromEntries(MODIFIERS.map(m => [m, false])); let actualKey; for (const part of parts) { if (!part) continue; // Skip empty tokens if (MODIFIERS.includes(part)) { modifiers[part] = true; } else { actualKey = part; } } // Fallback for cases where actualKey is a literal '+' or '-' (e.g. alt--, alt++ , alt+-, alt-+) if (!actualKey) { const lastChar = group.slice(-1); if (['+', '-', '_'].includes(lastChar)) actualKey = lastChar; } return { modifiers, actualKey }; } function matchesKeyGroup(e, group) { const { modifiers, actualKey } = parseKeyGroup(group); return e.ctrlKey === (isMac && modifiers.cmd ? false : modifiers.ctrl) && e.metaKey === (isMac && modifiers.cmd ? true : modifiers.meta) && e.shiftKey === modifiers.shift && e.altKey === modifiers.alt && e.key.toLowerCase() === actualKey?.toLowerCase(); } return cleanup; } //# sourceMappingURL=hotkey.js.map