Files
qbz/src/lib/utils/keyboard.ts
T
vicrodh fa6538df2c feat(keybindings): implement centralized keybindings system
Phase 1: Core infrastructure
- Add keyboard.ts with event-to-shortcut conversion, display formatting
- Add keybindingsStore.ts with action registry, conflict detection, persistence

Phase 2: Migration
- Migrate +page.svelte shortcuts to use keybinding manager
- Register global actions: playback, navigation, UI controls
- Integrate FocusMode with contextual seek actions
- Simplify ExpandedPlayer to use keybinding handler
- Add i18n translations for keybinding labels (en/es)
2026-02-02 23:02:33 -06:00

110 lines
2.7 KiB
TypeScript

/**
* Keyboard utilities for keybindings system
*/
/** Display mappings for special keys */
const KEY_DISPLAY_MAP: Record<string, string> = {
ArrowLeft: '←',
ArrowRight: '→',
ArrowUp: '↑',
ArrowDown: '↓',
Space: 'Space',
Escape: 'Esc',
Enter: '↵',
Backspace: '⌫',
Delete: 'Del',
Tab: 'Tab',
};
/**
* Converts a KeyboardEvent to a normalized shortcut string
* @example eventToShortcut({ key: 'ArrowRight', ctrlKey: true }) // 'Ctrl+ArrowRight'
*/
export function eventToShortcut(event: KeyboardEvent): string {
const parts: string[] = [];
if (event.ctrlKey || event.metaKey) parts.push('Ctrl');
if (event.altKey) parts.push('Alt');
if (event.shiftKey) parts.push('Shift');
// Normalize key
let key = event.key;
// Ignore if it's only a modifier key
if (['Control', 'Alt', 'Shift', 'Meta'].includes(key)) {
return '';
}
// Normalize space
if (key === ' ') key = 'Space';
parts.push(key);
return parts.join('+');
}
/**
* Parses a shortcut string into its components
* @example parseShortcut('Ctrl+Shift+F') // { ctrl: true, alt: false, shift: true, key: 'F' }
*/
export function parseShortcut(shortcut: string): {
ctrl: boolean;
alt: boolean;
shift: boolean;
key: string;
} {
const parts = shortcut.split('+');
const key = parts.pop() || '';
return {
ctrl: parts.includes('Ctrl'),
alt: parts.includes('Alt'),
shift: parts.includes('Shift'),
key,
};
}
/**
* Formats a shortcut for display in UI (with platform-specific symbols)
* @example formatShortcutDisplay('Ctrl+ArrowRight') // '⌘ →' on macOS, 'Ctrl + →' on others
*/
export function formatShortcutDisplay(shortcut: string): string {
const isMac =
typeof navigator !== 'undefined' &&
navigator.platform.toLowerCase().includes('mac');
const { ctrl, alt, shift, key } = parseShortcut(shortcut);
const parts: string[] = [];
if (ctrl) parts.push(isMac ? '⌘' : 'Ctrl');
if (alt) parts.push(isMac ? '⌥' : 'Alt');
if (shift) parts.push(isMac ? '⇧' : 'Shift');
const displayKey = KEY_DISPLAY_MAP[key] || key.toUpperCase();
parts.push(displayKey);
return parts.join(isMac ? ' ' : ' + ');
}
/**
* Checks if an event matches a shortcut string
*/
export function eventMatchesShortcut(
event: KeyboardEvent,
shortcut: string
): boolean {
const eventShortcut = eventToShortcut(event);
return eventShortcut === shortcut;
}
/**
* Checks if the event target is an input element (to ignore shortcuts)
*/
export function isInputTarget(event: KeyboardEvent): boolean {
const target = event.target;
return (
target instanceof HTMLInputElement ||
target instanceof HTMLTextAreaElement ||
(target instanceof HTMLElement && target.isContentEditable)
);
}