{"version":3,"sources":["../../../../src/sensors/useMagicKeys/core.ts"],"sourcesContent":["import { observable, type Observable } from \"@legendapp/state\";\nimport { onUnmount, type DeepMaybeObservable, type ReadonlyObservable } from \"@usels/core\";\nimport { resolveWindowSource, type ConfigurableWindow } from \"@shared/configurable\";\nimport { createEventListener } from \"../../browser/useEventListener/core\";\n\nexport interface UseMagicKeysOptions extends ConfigurableWindow {\n  /** Custom alias map for key names */\n  aliasMap?: Record<string, string>;\n  /** Event handler called on every keydown/keyup. Return false to skip tracking. */\n  onEventFired?: (e: KeyboardEvent) => boolean | void;\n  /** Options passed to the underlying addEventListener calls. @default { passive: true } */\n  eventListenerOptions?: AddEventListenerOptions;\n}\n\nconst DEFAULT_ALIAS_MAP: Record<string, string> = {\n  ctrl: \"control\",\n  cmd: \"meta\",\n  command: \"meta\",\n  option: \"alt\",\n  esc: \"escape\",\n  del: \"delete\",\n  space: \" \",\n  up: \"arrowup\",\n  down: \"arrowdown\",\n  left: \"arrowleft\",\n  right: \"arrowright\",\n};\n\nexport type UseMagicKeysReturn = {\n  /** Set of currently pressed key names (lowercase) */\n  current$: ReadonlyObservable<Set<string>>;\n} & {\n  /** Access any key as a reactive boolean. e.g., keys[\"a\"], keys[\"shift\"], keys[\"ctrl+a\"] */\n  [key: string]: ReadonlyObservable<boolean>;\n};\n\n/**\n * Framework-agnostic magic keys tracker.\n *\n * Tracks keyboard state via `keydown`/`keyup`/`blur` events on window.\n * Returns a Proxy that exposes any key as a reactive boolean observable,\n * and supports combo keys (e.g. `ctrl+a`).\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function createMagicKeys(\n  options?: DeepMaybeObservable<UseMagicKeysOptions>\n): UseMagicKeysReturn {\n  const opts$ = observable(options);\n  const win$ = resolveWindowSource(opts$.window as unknown as Observable<unknown>);\n\n  // Mount-time reads\n  const aliasMap = opts$.peek()?.aliasMap ?? {};\n  const eventListenerOptions = (opts$.peek()?.eventListenerOptions ?? {\n    passive: true,\n  }) as AddEventListenerOptions;\n\n  const mergedAliases: Record<string, string> = { ...DEFAULT_ALIAS_MAP, ...aliasMap };\n  const keyMap = new Map<string, Observable<boolean>>();\n  const comboKeys = new Set<string>();\n  const pressedKeys = new Set<string>();\n  const current$ = observable<Set<string>>(new Set());\n\n  const resolveKey = (key: string): string => {\n    const lower = key.toLowerCase();\n    return mergedAliases[lower] ?? lower;\n  };\n\n  const getOrCreateKey = (key: string): Observable<boolean> => {\n    const resolved = resolveKey(key);\n    let obs = keyMap.get(resolved);\n    if (!obs) {\n      obs = observable(false);\n      keyMap.set(resolved, obs);\n    }\n    return obs;\n  };\n\n  const isComboPressed = (combo: string): boolean => {\n    return combo\n      .split(\"+\")\n      .map((p) => resolveKey(p.trim()))\n      .every((k) => pressedKeys.has(k));\n  };\n\n  const updateCombos = () => {\n    for (const combo of comboKeys) {\n      const key = `__combo__${combo}`;\n      const obs = keyMap.get(key);\n      if (obs) obs.set(isComboPressed(combo));\n    }\n  };\n\n  const proxy = new Proxy({} as UseMagicKeysReturn, {\n    get(_target, prop: string) {\n      if (prop === \"current$\") return current$;\n\n      // Strip trailing $ and convert _ to + for destructuring support\n      let key = prop;\n      if (key.endsWith(\"$\")) key = key.slice(0, -1);\n      key = key.replace(/_/g, \"+\");\n\n      if (key.includes(\"+\")) {\n        comboKeys.add(key);\n        const comboKey = `__combo__${key}`;\n        let obs = keyMap.get(comboKey);\n        if (!obs) {\n          obs = observable(false);\n          keyMap.set(comboKey, obs);\n        }\n        return obs;\n      }\n      return getOrCreateKey(key);\n    },\n  });\n\n  const onKeyDown = (e: KeyboardEvent) => {\n    const onEventFired = opts$.peek()?.onEventFired as\n      | ((e: KeyboardEvent) => boolean | void)\n      | undefined;\n    if (onEventFired?.(e) === false) return;\n    const key = resolveKey(e.key);\n    pressedKeys.add(key);\n    getOrCreateKey(e.key).set(true);\n    current$.set(new Set(pressedKeys));\n    updateCombos();\n  };\n\n  const onKeyUp = (e: KeyboardEvent) => {\n    const onEventFired = opts$.peek()?.onEventFired as\n      | ((e: KeyboardEvent) => boolean | void)\n      | undefined;\n    if (onEventFired?.(e) === false) return;\n    const key = resolveKey(e.key);\n\n    // macOS: when Meta is released, other keys don't receive keyup events.\n    // Clear everything to avoid stuck keys.\n    if (key === \"meta\") {\n      pressedKeys.clear();\n      for (const [, obs] of keyMap) {\n        obs.set(false);\n      }\n      current$.set(new Set<string>());\n      updateCombos();\n      return;\n    }\n\n    pressedKeys.delete(key);\n    getOrCreateKey(e.key).set(false);\n    current$.set(new Set(pressedKeys));\n    updateCombos();\n  };\n\n  const onBlur = () => {\n    pressedKeys.clear();\n    for (const [, obs] of keyMap) {\n      obs.set(false);\n    }\n    current$.set(new Set<string>());\n  };\n\n  createEventListener(win$, \"keydown\", onKeyDown, eventListenerOptions);\n  createEventListener(win$, \"keyup\", onKeyUp, eventListenerOptions);\n  createEventListener(win$, \"blur\", onBlur, eventListenerOptions);\n\n  onUnmount(() => {\n    pressedKeys.clear();\n    keyMap.clear();\n    comboKeys.clear();\n  });\n\n  return proxy;\n}\n"],"mappings":"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","names":[]}