import { Key, keyboard } from "wrdu-keyboard"; import { platform } from "@tauri-apps/plugin-os"; type KeyboardHandler = (event: KeyboardEvent) => void; const activeContexts = new Set(); const handlersByContext: Record< string, Array<{ keys: Key[]; callback: KeyboardHandler; prevent: boolean; priority?: number; }> > = {}; const PRIORITY = { HIGH: 100, MEDIUM: 50, LOW: 0, }; let currentOS = "windows"; const useKeyboard = { PRIORITY, registerContext: (contextName: string) => { if (!handlersByContext[contextName]) { handlersByContext[contextName] = []; } }, enableContext: (contextName: string) => { if (!handlersByContext[contextName]) { useKeyboard.registerContext(contextName); } activeContexts.add(contextName); initKeyboardHandlers(); }, disableContext: (contextName: string) => { activeContexts.delete(contextName); initKeyboardHandlers(); }, on: ( contextName: string, keys: Key[], callback: KeyboardHandler, options: { prevent?: boolean; priority?: number } = {} ) => { if (!handlersByContext[contextName]) { useKeyboard.registerContext(contextName); } const existingHandlerIndex = handlersByContext[contextName].findIndex( (handler) => handler.keys.length === keys.length && handler.keys.every((key, i) => key === keys[i]) && handler.callback.toString() === callback.toString() ); if (existingHandlerIndex !== -1) { handlersByContext[contextName][existingHandlerIndex] = { keys, callback, prevent: options.prevent ?? true, priority: options.priority ?? PRIORITY.LOW, }; } else { handlersByContext[contextName].push({ keys, callback, prevent: options.prevent ?? true, priority: options.priority ?? PRIORITY.LOW, }); } if (activeContexts.has(contextName)) { initKeyboardHandlers(); } }, clearAll: () => { keyboard.clear(); }, setupAppShortcuts: (options: { onNavigateUp?: () => void; onNavigateDown?: () => void; onSelect?: () => void; onEscape?: () => void; onToggleActions?: () => void; contextName?: string; priority?: number; }) => { const { onNavigateUp, onNavigateDown, onSelect, onEscape, onToggleActions, contextName = "app", priority = PRIORITY.LOW, } = options; if (!handlersByContext[contextName]) { useKeyboard.registerContext(contextName); } if (onNavigateUp) { useKeyboard.on(contextName, [Key.UpArrow], () => onNavigateUp(), { priority, }); } if (onNavigateDown) { useKeyboard.on(contextName, [Key.DownArrow], () => onNavigateDown(), { priority, }); } if (onSelect) { useKeyboard.on(contextName, [Key.Enter], () => onSelect(), { priority }); } if (onEscape) { useKeyboard.on(contextName, [Key.Escape], () => onEscape(), { priority }); } if (onToggleActions) { const togglePriority = Math.max(priority, PRIORITY.HIGH); if (currentOS === "macos") { useKeyboard.on( contextName, [Key.LeftMeta, Key.K], () => onToggleActions(), { priority: togglePriority } ); useKeyboard.on( contextName, [Key.RightMeta, Key.K], () => onToggleActions(), { priority: togglePriority } ); } else { useKeyboard.on( contextName, [Key.LeftControl, Key.K], () => onToggleActions(), { priority: togglePriority } ); useKeyboard.on( contextName, [Key.RightControl, Key.K], () => onToggleActions(), { priority: togglePriority } ); } } }, setupKeybindCapture: (options: { onCapture: (key: string) => void; onComplete: () => void; }) => { const { onCapture, onComplete } = options; keyboard.prevent.down([Key.All], (event: KeyboardEvent) => { if (event.code === "Escape") { onComplete(); return; } onCapture(event.code); }); }, }; const initKeyboardHandlers = () => { keyboard.clear(); let allHandlers: Array<{ keys: Key[]; callback: KeyboardHandler; prevent: boolean; priority: number; contextName: string; }> = []; for (const contextName of activeContexts) { const handlers = handlersByContext[contextName] || []; allHandlers = [ ...allHandlers, ...handlers.map((handler) => ({ ...handler, priority: handler.priority ?? PRIORITY.LOW, contextName, })), ]; } allHandlers.sort((a, b) => b.priority - a.priority); const handlersByKeyCombination: Record< string, Array<(typeof allHandlers)[0]> > = {}; allHandlers.forEach((handler) => { const keyCombo = handler.keys.sort().join("+"); if (!handlersByKeyCombination[keyCombo]) { handlersByKeyCombination[keyCombo] = []; } handlersByKeyCombination[keyCombo].push(handler); }); Object.entries(handlersByKeyCombination).forEach(([_keyCombo, handlers]) => { handlers.sort((a, b) => b.priority - a.priority); const handler = handlers[0]; const wrappedCallback: KeyboardHandler = (event) => { const isMetaCombo = handler.keys.length > 1 && (handler.keys.includes(Key.LeftMeta) || handler.keys.includes(Key.RightMeta) || handler.keys.includes(Key.LeftControl) || handler.keys.includes(Key.RightControl)); const isNavigationKey = event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Enter" || event.key === "Escape"; const isInInput = event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement; if ( (isMetaCombo || isNavigationKey || !isInInput) && activeContexts.has(handler.contextName) ) { handler.callback(event); } }; if (handler.prevent) { keyboard.prevent.down(handler.keys, wrappedCallback); } else { keyboard.down(handler.keys, wrappedCallback); } }); }; export default defineNuxtPlugin(async (nuxtApp) => { try { const osName = platform(); currentOS = osName.toLowerCase().includes("mac") ? "macos" : "windows"; } catch (error) { console.error("Error detecting platform:", error); } initKeyboardHandlers(); nuxtApp.hook("page:finish", () => { initKeyboardHandlers(); }); return { provide: { keyboard: { ...useKeyboard, Key, }, }, }; });