Files
local-cal/src/lib/keyboard-shortcuts.ts

91 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ---------------------------------------------------------------------------
// Keyboard shortcuts OS-aware key resolution
//
// Design:
// - SHORTCUT_DEFINITIONS: abstract schema using modifier tokens
// - resolveKeys(): pure function, safe to call anywhere (including tests)
// - useOs(): client-only React hook that detects Mac vs other after hydration
//
// Modifier tokens:
// "mod" → ⌘ (Mac) | Ctrl (other)
// "shift" → ⇧ (Mac) | Shift (other)
// "alt" → ⌥ (Mac) | Alt (other)
// "enter" → ↵ (Mac) | Enter (other)
// "esc" → Esc (both)
// anything else passes through as-is (plain letter keys like "A")
// ---------------------------------------------------------------------------
export type Os = "mac" | "other" | "unknown";
export type Modifier = string; // "mod" | "shift" | "alt" | "enter" | "esc" | plain key
export interface ShortcutDefinition {
modifiers: readonly Modifier[];
label: string;
}
// ─── Abstract shortcut definitions ───────────────────────────────────────────
export const SHORTCUT_DEFINITIONS: ShortcutDefinition[] = [
{ modifiers: ["mod", "enter"], label: "Generate event" },
{ modifiers: ["mod", "shift", "A"], label: "Attach image" },
{ modifiers: ["mod", "V"], label: "Paste image" },
{ modifiers: ["esc"], label: "Clear prompt" },
];
// ─── Key resolution ───────────────────────────────────────────────────────────
const MAC_MAP: Record<string, string> = {
mod: "⌘",
shift: "⇧",
alt: "⌥",
enter: "↵",
esc: "Esc",
};
const OTHER_MAP: Record<string, string> = {
mod: "Ctrl",
shift: "Shift",
alt: "Alt",
enter: "Enter",
esc: "Esc",
};
/**
* Pure function — maps abstract modifier tokens to display glyphs.
* "unknown" falls back to Mac (most common dev/user base).
*/
export function resolveKeys(modifiers: readonly Modifier[], os: Os): string[] {
const map = os === "other" ? OTHER_MAP : MAC_MAP;
return modifiers.map((m) => map[m] ?? m);
}
// ─── OS detection hook (client-only) ─────────────────────────────────────────
/**
* Detects the user's OS after hydration.
* Returns "unknown" on the server or before the effect runs.
*
* Detection order (most → least reliable):
* 1. navigator.userAgentData.platform (modern browsers, Chromium)
* 2. navigator.platform (legacy, still widely supported)
* 3. navigator.userAgent string match (last resort)
*/
export function detectOs(): Os {
if (typeof navigator === "undefined") return "unknown";
// Modern API — Chromium 90+
const uaData = (navigator as Navigator & { userAgentData?: { platform: string } })
.userAgentData;
if (uaData?.platform) {
return uaData.platform.toLowerCase().includes("mac") ? "mac" : "other";
}
// Legacy API — still reliable on Safari and Firefox
if (navigator.platform) {
return navigator.platform.toLowerCase().startsWith("mac") ? "mac" : "other";
}
// Last resort — UA string
return /mac/i.test(navigator.userAgent) ? "mac" : "other";
}