{"version":3,"file":"hotkey-recorder.cjs","names":["Store","#options","#platform","detectPlatform","#keydownHandler","isInputElement","hotkeyChordFromKeydown","#removeListener","#addListener"],"sources":["../src/hotkey-recorder.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport { detectPlatform } from './constants'\nimport { isInputElement } from './manager.utils'\nimport { hotkeyChordFromKeydown } from './recorder-chord'\nimport type { Hotkey } from './hotkey'\n\n/**\n * State interface for the HotkeyRecorder.\n */\nexport interface HotkeyRecorderState {\n  /** Whether recording is currently active */\n  isRecording: boolean\n  /** The currently recorded hotkey (for live preview) */\n  recordedHotkey: Hotkey | null\n}\n\n/**\n * Options for configuring a HotkeyRecorder instance.\n */\nexport interface HotkeyRecorderOptions {\n  /** Callback when a hotkey is successfully recorded */\n  onRecord: (hotkey: Hotkey) => void\n  /** Optional callback when recording is cancelled (Escape pressed) */\n  onCancel?: () => void\n  /** Optional callback when shortcut is cleared (Backspace/Delete pressed) */\n  onClear?: () => void\n  /**\n   * Whether to ignore keyboard events from input-like elements (text inputs,\n   * textarea, select, contenteditable). When true, typing in inputs passes\n   * through normally instead of being captured as a hotkey recording.\n   * Escape always works regardless of this setting.\n   * @default true\n   */\n  ignoreInputs?: boolean\n}\n\n/**\n * Framework-agnostic class for recording keyboard shortcuts.\n *\n * This class handles all the complexity of capturing keyboard events,\n * converting them to hotkey strings, and handling edge cases like\n * Escape to cancel or Backspace/Delete to clear.\n *\n * State Management:\n * - Uses TanStack Store for reactive state management\n * - State can be accessed via `recorder.store.state` when using the class directly\n * - When using framework adapters (React), use `useSelector` hooks for reactive state\n *\n * @example\n * ```ts\n * const recorder = new HotkeyRecorder({\n *   onRecord: (hotkey) => {\n *     console.log('Recorded:', hotkey)\n *   },\n *   onCancel: () => {\n *     console.log('Recording cancelled')\n *   },\n * })\n *\n * // Start recording\n * recorder.start()\n *\n * // Access state directly\n * console.log(recorder.store.state.isRecording) // true\n *\n * // Subscribe to changes with TanStack Store\n * const unsubscribe = recorder.store.subscribe(() => {\n *   console.log('Recording:', recorder.store.state.isRecording)\n * })\n *\n * // Cleanup\n * recorder.destroy()\n * unsubscribe()\n * ```\n */\nexport class HotkeyRecorder {\n  /**\n   * The TanStack Store instance containing the recorder state.\n   * Use this to subscribe to state changes or access current state.\n   */\n  readonly store: Store<HotkeyRecorderState> = new Store<HotkeyRecorderState>({\n    isRecording: false,\n    recordedHotkey: null,\n  })\n\n  #keydownHandler: ((event: KeyboardEvent) => void) | null = null\n  #options: HotkeyRecorderOptions\n  #platform: 'mac' | 'windows' | 'linux'\n\n  constructor(options: HotkeyRecorderOptions) {\n    this.#options = options\n    this.#platform = detectPlatform()\n  }\n\n  /**\n   * Updates the recorder options, including callbacks.\n   * This allows framework adapters to sync callback changes without recreating the recorder.\n   */\n  setOptions(options: Partial<HotkeyRecorderOptions>): void {\n    this.#options = {\n      ...this.#options,\n      ...options,\n    }\n  }\n\n  /**\n   * Start recording a new hotkey.\n   *\n   * Sets up a keydown event listener that captures keyboard events\n   * and converts them to hotkey strings. Recording continues until\n   * a valid hotkey is recorded, Escape is pressed, or stop/cancel is called.\n   */\n  start(): void {\n    // Prevent starting recording if already recording\n    if (this.#keydownHandler) {\n      return\n    }\n\n    // Update store state\n    this.store.setState(() => ({\n      isRecording: true,\n      recordedHotkey: null,\n    }))\n\n    // Create keydown handler\n    const handler = (event: KeyboardEvent) => {\n      // Check if we're still recording (handler might be called after stop/cancel)\n      if (!this.#keydownHandler) {\n        return\n      }\n\n      // If ignoreInputs is enabled (default) and focus is in an input element,\n      // let the event pass through so the user can type normally.\n      // Escape is the exception — it should always cancel recording.\n      if (this.#options.ignoreInputs !== false) {\n        const activeEl =\n          typeof document !== 'undefined' ? document.activeElement : null\n        if (isInputElement(activeEl) && event.key !== 'Escape') {\n          return\n        }\n      }\n\n      event.preventDefault()\n      event.stopPropagation()\n\n      // Handle Escape to cancel\n      if (event.key === 'Escape') {\n        this.cancel()\n        return\n      }\n\n      // Handle Backspace/Delete to clear shortcut\n      if (event.key === 'Backspace' || event.key === 'Delete') {\n        if (\n          !event.ctrlKey &&\n          !event.shiftKey &&\n          !event.altKey &&\n          !event.metaKey\n        ) {\n          this.#options.onClear?.()\n          this.#options.onRecord('' as Hotkey)\n          this.stop()\n          return\n        }\n      }\n\n      const finalHotkey = hotkeyChordFromKeydown(event, this.#platform)\n      if (finalHotkey === null) {\n        return\n      }\n\n      // Remove listener FIRST to prevent any additional events\n      const handlerToRemove = this.#keydownHandler as\n        | ((event: KeyboardEvent) => void)\n        | null\n      if (handlerToRemove) {\n        this.#removeListener(handlerToRemove)\n        this.#keydownHandler = null\n      }\n\n      // Update store state immediately\n      this.store.setState(() => ({\n        isRecording: false,\n        recordedHotkey: finalHotkey,\n      }))\n\n      // Call callback AFTER listener is removed and state is set\n      this.#options.onRecord(finalHotkey)\n    }\n\n    this.#keydownHandler = handler\n    this.#addListener(handler)\n  }\n\n  /**\n   * Stop recording (same as cancel, but doesn't call onCancel).\n   *\n   * Removes the event listener and resets the recording state.\n   */\n  stop(): void {\n    // Remove event listener immediately\n    if (this.#keydownHandler) {\n      this.#removeListener(this.#keydownHandler)\n      this.#keydownHandler = null\n    }\n\n    // Update store state\n    this.store.setState(() => ({\n      isRecording: false,\n      recordedHotkey: null,\n    }))\n  }\n\n  /**\n   * Cancel recording without saving.\n   *\n   * Removes the event listener, resets the recording state, and calls\n   * the onCancel callback if provided.\n   */\n  cancel(): void {\n    // Remove event listener immediately\n    if (this.#keydownHandler) {\n      this.#removeListener(this.#keydownHandler)\n      this.#keydownHandler = null\n    }\n\n    // Update store state\n    this.store.setState(() => ({\n      isRecording: false,\n      recordedHotkey: null,\n    }))\n\n    // Call cancel callback\n    this.#options.onCancel?.()\n  }\n\n  /**\n   * Adds the keydown event listener to the document.\n   */\n  #addListener(handler: (event: KeyboardEvent) => void): void {\n    if (typeof document === 'undefined') {\n      return // SSR safety\n    }\n\n    document.addEventListener('keydown', handler, true)\n  }\n\n  /**\n   * Removes the keydown event listener from the document.\n   */\n  #removeListener(handler: (event: KeyboardEvent) => void): void {\n    if (typeof document === 'undefined') {\n      return\n    }\n\n    document.removeEventListener('keydown', handler, true)\n  }\n\n  /**\n   * Clean up event listeners and reset state.\n   *\n   * Call this when you're done with the recorder to ensure\n   * all event listeners are properly removed.\n   */\n  destroy(): void {\n    this.stop()\n    this.store.setState(() => ({\n      isRecording: false,\n      recordedHotkey: null,\n    }))\n  }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2EA,IAAa,iBAAb,MAA4B;CAU1B,kBAA2D;CAC3D;CACA;CAEA,YAAY,SAAgC;eATC,IAAIA,sBAA2B;GAC1E,aAAa;GACb,gBAAgB;GACjB,CAAC;AAOA,QAAKC,UAAW;AAChB,QAAKC,WAAYC,kCAAgB;;;;;;CAOnC,WAAW,SAA+C;AACxD,QAAKF,UAAW;GACd,GAAG,MAAKA;GACR,GAAG;GACJ;;;;;;;;;CAUH,QAAc;AAEZ,MAAI,MAAKG,eACP;AAIF,OAAK,MAAM,gBAAgB;GACzB,aAAa;GACb,gBAAgB;GACjB,EAAE;EAGH,MAAM,WAAW,UAAyB;AAExC,OAAI,CAAC,MAAKA,eACR;AAMF,OAAI,MAAKH,QAAS,iBAAiB,OAGjC;QAAII,qCADF,OAAO,aAAa,cAAc,SAAS,gBAAgB,KACjC,IAAI,MAAM,QAAQ,SAC5C;;AAIJ,SAAM,gBAAgB;AACtB,SAAM,iBAAiB;AAGvB,OAAI,MAAM,QAAQ,UAAU;AAC1B,SAAK,QAAQ;AACb;;AAIF,OAAI,MAAM,QAAQ,eAAe,MAAM,QAAQ,UAC7C;QACE,CAAC,MAAM,WACP,CAAC,MAAM,YACP,CAAC,MAAM,UACP,CAAC,MAAM,SACP;AACA,WAAKJ,QAAS,WAAW;AACzB,WAAKA,QAAS,SAAS,GAAa;AACpC,UAAK,MAAM;AACX;;;GAIJ,MAAM,cAAcK,8CAAuB,OAAO,MAAKJ,SAAU;AACjE,OAAI,gBAAgB,KAClB;GAIF,MAAM,kBAAkB,MAAKE;AAG7B,OAAI,iBAAiB;AACnB,UAAKG,eAAgB,gBAAgB;AACrC,UAAKH,iBAAkB;;AAIzB,QAAK,MAAM,gBAAgB;IACzB,aAAa;IACb,gBAAgB;IACjB,EAAE;AAGH,SAAKH,QAAS,SAAS,YAAY;;AAGrC,QAAKG,iBAAkB;AACvB,QAAKI,YAAa,QAAQ;;;;;;;CAQ5B,OAAa;AAEX,MAAI,MAAKJ,gBAAiB;AACxB,SAAKG,eAAgB,MAAKH,eAAgB;AAC1C,SAAKA,iBAAkB;;AAIzB,OAAK,MAAM,gBAAgB;GACzB,aAAa;GACb,gBAAgB;GACjB,EAAE;;;;;;;;CASL,SAAe;AAEb,MAAI,MAAKA,gBAAiB;AACxB,SAAKG,eAAgB,MAAKH,eAAgB;AAC1C,SAAKA,iBAAkB;;AAIzB,OAAK,MAAM,gBAAgB;GACzB,aAAa;GACb,gBAAgB;GACjB,EAAE;AAGH,QAAKH,QAAS,YAAY;;;;;CAM5B,aAAa,SAA+C;AAC1D,MAAI,OAAO,aAAa,YACtB;AAGF,WAAS,iBAAiB,WAAW,SAAS,KAAK;;;;;CAMrD,gBAAgB,SAA+C;AAC7D,MAAI,OAAO,aAAa,YACtB;AAGF,WAAS,oBAAoB,WAAW,SAAS,KAAK;;;;;;;;CASxD,UAAgB;AACd,OAAK,MAAM;AACX,OAAK,MAAM,gBAAgB;GACzB,aAAa;GACb,gBAAgB;GACjB,EAAE"}