85 lines
2.8 KiB
TypeScript
85 lines
2.8 KiB
TypeScript
import { MODIFIER_KEYS, type KeyboardShortcut, type Platform } from "@/lib/keyboard/constants";
|
|
import { detectPlatform, matchesShortcut } from "@/lib/keyboard/utils";
|
|
|
|
type HoldModifier = (typeof MODIFIER_KEYS)[keyof typeof MODIFIER_KEYS];
|
|
|
|
function resolvePlatform(platform?: Platform): Platform {
|
|
return platform ?? detectPlatform();
|
|
}
|
|
|
|
function getCtrlOrCmdModifier(platform?: Platform): HoldModifier {
|
|
return resolvePlatform(platform) === "mac" ? MODIFIER_KEYS.CMD : MODIFIER_KEYS.CTRL;
|
|
}
|
|
|
|
export function getHoldModifier(
|
|
shortcut: KeyboardShortcut,
|
|
platform?: Platform,
|
|
): HoldModifier | null {
|
|
const modifiers = shortcut.modifiers;
|
|
if (!modifiers) return null;
|
|
|
|
if (modifiers.ctrlOrCmd) return getCtrlOrCmdModifier(platform);
|
|
if (modifiers.cmd) return MODIFIER_KEYS.CMD;
|
|
if (modifiers.ctrl) return MODIFIER_KEYS.CTRL;
|
|
if (modifiers.alt) return MODIFIER_KEYS.ALT;
|
|
if (modifiers.shift) return MODIFIER_KEYS.SHIFT;
|
|
return null;
|
|
}
|
|
|
|
export function hasHoldModifier(shortcut: KeyboardShortcut): boolean {
|
|
return getHoldModifier(shortcut) !== null;
|
|
}
|
|
|
|
export function isCycleShortcutEvent(event: KeyboardEvent, shortcut: KeyboardShortcut): boolean {
|
|
if (event.repeat) return false;
|
|
return matchesShortcut(event, shortcut);
|
|
}
|
|
|
|
/**
|
|
* Make ctrlOrCmd's hold modifier match the key that opened this interaction.
|
|
* Browsers accept either Control or Meta for ctrlOrCmd, including off-platform
|
|
* variants such as Control on macOS.
|
|
*/
|
|
export function snapshotHeldShortcut(
|
|
event: KeyboardEvent,
|
|
shortcut: KeyboardShortcut,
|
|
): KeyboardShortcut {
|
|
if (!shortcut.modifiers?.ctrlOrCmd || event.ctrlKey === event.metaKey) return shortcut;
|
|
const { ctrlOrCmd: _ctrlOrCmd, ...modifiers } = shortcut.modifiers;
|
|
return {
|
|
...shortcut,
|
|
modifiers: { ...modifiers, ...(event.ctrlKey ? { ctrl: true } : { cmd: true }) },
|
|
};
|
|
}
|
|
|
|
function isHoldModifierPressed(event: KeyboardEvent, modifier: HoldModifier): boolean {
|
|
if (modifier === MODIFIER_KEYS.CMD) return event.metaKey;
|
|
if (modifier === MODIFIER_KEYS.CTRL) return event.ctrlKey;
|
|
if (modifier === MODIFIER_KEYS.ALT) return event.altKey;
|
|
return event.shiftKey;
|
|
}
|
|
|
|
/**
|
|
* Match a deliberate switcher continuation key press. Once a picker is open,
|
|
* its primary hold modifier is sufficient; secondary modifiers may be released.
|
|
*/
|
|
export function isHeldCycleKeyEvent(
|
|
event: KeyboardEvent,
|
|
shortcut: KeyboardShortcut,
|
|
platform?: Platform,
|
|
): boolean {
|
|
if (event.repeat || event.key.toLowerCase() !== shortcut.key.toLowerCase()) return false;
|
|
const holdModifier = getHoldModifier(shortcut, platform);
|
|
return holdModifier
|
|
? isHoldModifierPressed(event, holdModifier)
|
|
: matchesShortcut(event, shortcut);
|
|
}
|
|
|
|
export function isCommitReleaseEvent(
|
|
event: KeyboardEvent,
|
|
shortcut: KeyboardShortcut,
|
|
platform?: Platform,
|
|
): boolean {
|
|
return event.key === getHoldModifier(shortcut, platform);
|
|
}
|