{"version":3,"file":"match.cjs","names":["detectPlatform","parseHotkey","normalizeKeyName","isSingleLetterKey","PUNCTUATION_CODE_MAP"],"sources":["../src/match.ts"],"sourcesContent":["import {\n  PUNCTUATION_CODE_MAP,\n  detectPlatform,\n  isSingleLetterKey,\n  normalizeKeyName,\n} from './constants'\nimport { parseHotkey } from './parse'\nimport type {\n  Hotkey,\n  HotkeyCallback,\n  HotkeyCallbackContext,\n  ParsedHotkey,\n} from './hotkey'\n\n/**\n * Checks if a KeyboardEvent matches a hotkey.\n *\n * Uses the `key` property from KeyboardEvent for matching, with a fallback to `code`\n * for letter keys, digit keys (0-9), and punctuation keys when `key` produces special\n * characters (e.g., macOS Option+letter, Shift+number, or Option+punctuation).\n * Letter keys are matched case-insensitively.\n *\n * Also handles \"dead key\" events where `event.key` is `'Dead'` instead of the expected\n * character. This commonly occurs on macOS with Option+letter combinations (e.g., Option+E,\n * Option+I, Option+U, Option+N) and on Windows/Linux with international keyboard layouts.\n * In these cases, `event.code` is used to determine the physical key.\n *\n * @param event - The KeyboardEvent to check\n * @param hotkey - The hotkey string or ParsedHotkey to match against\n * @param platform - The target platform for resolving 'Mod' (defaults to auto-detection)\n * @returns True if the event matches the hotkey\n *\n * @example\n * ```ts\n * document.addEventListener('keydown', (event) => {\n *   if (matchesKeyboardEvent(event, 'Mod+S')) {\n *     event.preventDefault()\n *     handleSave()\n *   }\n * })\n * ```\n */\nexport function matchesKeyboardEvent(\n  event: KeyboardEvent,\n  hotkey: Hotkey | ParsedHotkey,\n  platform: 'mac' | 'windows' | 'linux' = detectPlatform(),\n): boolean {\n  const parsed =\n    typeof hotkey === 'string' ? parseHotkey(hotkey, platform) : hotkey\n\n  // Check modifiers\n  if (event.ctrlKey !== parsed.ctrl) {\n    return false\n  }\n  if (event.shiftKey !== parsed.shift) {\n    return false\n  }\n  if (event.altKey !== parsed.alt) {\n    return false\n  }\n  if (event.metaKey !== parsed.meta) {\n    return false\n  }\n\n  // Check key (case-insensitive for letters)\n  const eventKey = normalizeKeyName(event.key)\n  const hotkeyKey = parsed.key\n\n  // For single-character keys (not dead keys), try direct event.key match first\n  if (eventKey !== 'Dead' && eventKey.length === 1 && hotkeyKey.length === 1) {\n    if (eventKey.toUpperCase() === hotkeyKey.toUpperCase()) {\n      return true\n    }\n\n    // If event.key is a letter, we usually trust the keyboard layout.\n    // For ASCII letters: always trust layout (Dvorak, Colemak, AZERTY support).\n    // For non-ASCII letters without Alt: trust layout (e.g., Cyrillic keyboards).\n    //\n    // For non-ASCII letters WITH Alt held down: fall through to the event.code\n    // fallback. macOS Option+letter combinations produce non-ASCII letters as\n    // event.key (e.g., Option+A → 'å', Option+' → 'æ', Option+P → 'π'), but\n    // event.code still reflects the physical key. We want Alt+A to match even\n    // when Option+A fires event.key='å'.\n    if (\n      isSingleLetterKey(eventKey) &&\n      (/^[A-Za-z]$/.test(eventKey) || !event.altKey)\n    ) {\n      return false\n    }\n  }\n\n  // Fallback to event.code for dead keys or single-char mismatches where\n  // event.key is a non-letter special character.\n  // Dead keys: Option+letter on macOS, international layouts produce event.key === 'Dead'\n  // Single-char mismatches: Cmd+Option+T gives '†' instead of 'T', Shift+4 gives '$'\n  if (\n    event.code &&\n    (eventKey === 'Dead' || (eventKey.length === 1 && hotkeyKey.length === 1))\n  ) {\n    // fallback for letter keys (common with mac option + letter)\n    if (event.code.startsWith('Key')) {\n      const codeLetter = event.code.slice(3)\n      if (codeLetter.length === 1 && /^[A-Za-z]$/.test(codeLetter)) {\n        return codeLetter.toUpperCase() === hotkeyKey.toUpperCase()\n      }\n    }\n\n    // fallback for number keys (common with mac option + num)\n    if (event.code.startsWith('Digit')) {\n      const codeDigit = event.code.slice(5)\n      if (codeDigit.length === 1 && /^[0-9]$/.test(codeDigit)) {\n        return codeDigit === hotkeyKey\n      }\n    }\n    // Fallback for punctuation keys (e.g., Minus, Slash, BracketLeft).\n    // On macOS, Option+punctuation produces composed characters (e.g., Option+- → '–'),\n    // but event.code still reports the physical key.\n    if (event.code in PUNCTUATION_CODE_MAP) {\n      return PUNCTUATION_CODE_MAP[event.code] === hotkeyKey\n    }\n\n    return false\n  }\n\n  // For special keys, compare exactly (after normalization)\n  return eventKey === hotkeyKey\n}\n\n/**\n * Options for creating a hotkey handler.\n */\nexport interface CreateHotkeyHandlerOptions {\n  /** Prevent the default browser action when the hotkey matches. Defaults to true */\n  preventDefault?: boolean\n  /** Stop event propagation when the hotkey matches. Defaults to true */\n  stopPropagation?: boolean\n  /** The target platform for resolving 'Mod' */\n  platform?: 'mac' | 'windows' | 'linux'\n}\n\n/**\n * Creates a keyboard event handler that calls the callback when the hotkey matches.\n *\n * @param hotkey - The hotkey string or ParsedHotkey to match\n * @param callback - The function to call when the hotkey matches\n * @param options - Options for matching and handling\n * @returns A function that can be used as an event handler\n *\n * @example\n * ```ts\n * const handler = createHotkeyHandler('Mod+S', (event, { hotkey, parsedHotkey }) => {\n *   console.log(`${hotkey} was pressed`)\n *   handleSave()\n * })\n *\n * document.addEventListener('keydown', handler)\n * ```\n */\nexport function createHotkeyHandler(\n  hotkey: Hotkey | ParsedHotkey,\n  callback: HotkeyCallback,\n  options: CreateHotkeyHandlerOptions = {},\n): (event: KeyboardEvent) => void {\n  const { preventDefault = true, stopPropagation = true, platform } = options\n  const resolvedPlatform = platform ?? detectPlatform()\n\n  const hotkeyString: Hotkey =\n    typeof hotkey === 'string' ? hotkey : formatParsedHotkey(hotkey)\n  const parsed =\n    typeof hotkey === 'string' ? parseHotkey(hotkey, resolvedPlatform) : hotkey\n\n  const context: HotkeyCallbackContext = {\n    hotkey: hotkeyString,\n    parsedHotkey: parsed,\n  }\n\n  return (event: KeyboardEvent) => {\n    if (matchesKeyboardEvent(event, parsed, resolvedPlatform)) {\n      if (preventDefault) {\n        event.preventDefault()\n      }\n      if (stopPropagation) {\n        event.stopPropagation()\n      }\n      callback(event, context)\n    }\n  }\n}\n\ntype MultiHotkeyHandler = { [K in Hotkey]?: HotkeyCallback }\n\n/**\n * Creates a handler that matches multiple hotkeys.\n *\n * @param handlers - A map of hotkey strings to their handlers\n * @param options - Options for matching and handling\n * @returns A function that can be used as an event handler\n *\n * @example\n * ```ts\n * const handler = createMultiHotkeyHandler({\n *   'Mod+S': (event, { hotkey }) => handleSave(),\n *   'Mod+Z': (event, { hotkey }) => handleUndo(),\n *   'Mod+Shift+Z': (event, { hotkey }) => handleRedo(),\n * })\n *\n * document.addEventListener('keydown', handler)\n * ```\n */\nexport function createMultiHotkeyHandler(\n  handlers: MultiHotkeyHandler,\n  options: CreateHotkeyHandlerOptions = {},\n): (event: KeyboardEvent) => void {\n  const { preventDefault = true, stopPropagation = true, platform } = options\n  const resolvedPlatform = platform ?? detectPlatform()\n\n  // Pre-parse all hotkeys for efficiency\n  const parsedHandlers = Object.entries(handlers)\n    .filter((entry): entry is [string, HotkeyCallback] => Boolean(entry[1]))\n    .map(([hotkey, handler]) => {\n      const parsed = parseHotkey(hotkey as Hotkey, resolvedPlatform)\n      const context: HotkeyCallbackContext = {\n        hotkey: hotkey as Hotkey,\n        parsedHotkey: parsed,\n      }\n      return { parsed, handler, context }\n    })\n\n  return (event: KeyboardEvent) => {\n    for (const { parsed, handler, context } of parsedHandlers) {\n      if (matchesKeyboardEvent(event, parsed, resolvedPlatform)) {\n        if (preventDefault) {\n          event.preventDefault()\n        }\n        if (stopPropagation) {\n          event.stopPropagation()\n        }\n        handler(event, context)\n        return // Only handle the first match\n      }\n    }\n  }\n}\n\n/**\n * Formats a ParsedHotkey back to a hotkey string.\n * Used internally to provide the hotkey string in callback context.\n */\nfunction formatParsedHotkey(parsed: ParsedHotkey): Hotkey {\n  const parts: Array<string> = []\n\n  if (parsed.ctrl) parts.push('Control')\n  if (parsed.alt) parts.push('Alt')\n  if (parsed.shift) parts.push('Shift')\n  if (parsed.meta) parts.push('Meta')\n  parts.push(parsed.key)\n\n  return parts.join('+') as Hotkey\n}\n"],"mappings":"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"}