Handy/src/lib/utils/keyboard.ts

184 lines
5 KiB
TypeScript

/**
* Keyboard utility functions for handling keyboard events
*/
export type OSType = "macos" | "windows" | "linux" | "unknown";
/**
* Extract a consistent key name from a KeyboardEvent
* This function provides cross-platform keyboard event handling
* and returns key names appropriate for the target operating system
*/
export const getKeyName = (
e: KeyboardEvent,
osType: OSType = "unknown",
): string => {
// Handle special cases first
if (e.code) {
const code = e.code;
// Handle function keys (F1-F24)
if (code.match(/^F\d+$/)) {
return code.toLowerCase(); // F1, F2, ..., F14, F15, etc.
}
// Handle regular letter keys (KeyA -> a)
if (code.match(/^Key[A-Z]$/)) {
return code.replace("Key", "").toLowerCase();
}
// Handle digit keys (Digit0 -> 0)
if (code.match(/^Digit\d$/)) {
return code.replace("Digit", "");
}
// Handle numpad digit keys (Numpad0 -> numpad 0)
if (code.match(/^Numpad\d$/)) {
return code.replace("Numpad", "numpad ").toLowerCase();
}
// Handle modifier keys - OS-specific naming
const getModifierName = (baseModifier: string): string => {
switch (baseModifier) {
case "shift":
return "shift";
case "ctrl":
return osType === "macos" ? "ctrl" : "ctrl";
case "alt":
return osType === "macos" ? "option" : "alt";
case "meta":
// Windows key on Windows/Linux, Command key on Mac
if (osType === "macos") return "command";
if (osType === "windows") return "win";
return "super"; // Linux convention
default:
return baseModifier;
}
};
const modifierMap: Record<string, string> = {
ShiftLeft: getModifierName("shift"),
ShiftRight: getModifierName("shift"),
ControlLeft: getModifierName("ctrl"),
ControlRight: getModifierName("ctrl"),
AltLeft: getModifierName("alt"),
AltRight: getModifierName("alt"),
MetaLeft: getModifierName("meta"),
MetaRight: getModifierName("meta"),
OSLeft: getModifierName("meta"),
OSRight: getModifierName("meta"),
CapsLock: "caps lock",
Tab: "tab",
Enter: "enter",
Space: "space",
Backspace: "backspace",
Delete: "delete",
Escape: "esc",
ArrowUp: "up",
ArrowDown: "down",
ArrowLeft: "left",
ArrowRight: "right",
Home: "home",
End: "end",
PageUp: "page up",
PageDown: "page down",
Insert: "insert",
PrintScreen: "print screen",
ScrollLock: "scroll lock",
Pause: "pause",
ContextMenu: "menu",
NumpadMultiply: "numpad *",
NumpadAdd: "numpad +",
NumpadSubtract: "numpad -",
NumpadDecimal: "numpad .",
NumpadDivide: "numpad /",
NumLock: "num lock",
};
if (modifierMap[code]) {
return modifierMap[code];
}
// Handle punctuation and special characters
const punctuationMap: Record<string, string> = {
Semicolon: ";",
Equal: "=",
Comma: ",",
Minus: "-",
Period: ".",
Slash: "/",
Backquote: "`",
BracketLeft: "[",
Backslash: "\\",
BracketRight: "]",
Quote: "'",
};
if (punctuationMap[code]) {
return punctuationMap[code];
}
// For any other codes, try to convert to a reasonable format
return code.toLowerCase().replace(/([a-z])([A-Z])/g, "$1 $2");
}
// Fallback to e.key if e.code is not available
if (e.key) {
const key = e.key;
// Handle special key names with OS-specific formatting
const keyMap: Record<string, string> = {
Control: osType === "macos" ? "ctrl" : "ctrl",
Alt: osType === "macos" ? "option" : "alt",
Shift: "shift",
Meta:
osType === "macos" ? "command" : osType === "windows" ? "win" : "super",
OS:
osType === "macos" ? "command" : osType === "windows" ? "win" : "super",
CapsLock: "caps lock",
ArrowUp: "up",
ArrowDown: "down",
ArrowLeft: "left",
ArrowRight: "right",
Escape: "esc",
" ": "space",
};
if (keyMap[key]) {
return keyMap[key];
}
return key.toLowerCase();
}
// Last resort fallback
return `unknown-${e.keyCode || e.which || 0}`;
};
/**
* Get display-friendly key combination string for the current OS
* Returns basic plus-separated format with correct platform key names
*/
export const formatKeyCombination = (
combination: string,
osType: OSType,
): string => {
// Simply return the combination as-is since getKeyName already provides
// the correct platform-specific key names
return combination;
};
/**
* Normalize modifier keys to handle left/right variants
*/
export const normalizeKey = (key: string): string => {
// Handle left/right variants of modifier keys
if (key.startsWith("left ") || key.startsWith("right ")) {
const parts = key.split(" ");
if (parts.length === 2) {
// Return just the modifier name without left/right prefix
return parts[1];
}
}
return key;
};