{"version":3,"sources":["../../../../src/sensors/useOnClickOutside/core.ts"],"sourcesContent":["import { peek, type DeepMaybeObservable, type Fn } from \"@usels/core\";\nimport { noop } from \"@usels/core/shared/utils\";\nimport { isIOS } from \"@shared/utils\";\nimport { type ConfigurableWindow, defaultWindow } from \"@shared/configurable\";\nimport type { MaybeEventTarget } from \"../../types\";\nimport { createEventListener } from \"../../browser/useEventListener/core\";\n\nexport interface OnClickOutsideOptions<\n  Controls extends boolean = false,\n> extends ConfigurableWindow {\n  /**\n   * List of elements or CSS selectors that should not trigger the handler.\n   */\n  ignore?: (string | MaybeEventTarget)[];\n  /**\n   * Use capturing phase for internal event listener.\n   * @default true\n   */\n  capture?: boolean;\n  /**\n   * Run handler function if focus moves to an iframe.\n   * @default false\n   */\n  detectIframe?: boolean;\n  /**\n   * Return control functions (`stop`, `cancel`, `trigger`) instead of just `stop`.\n   * @default false\n   */\n  controls?: Controls;\n}\n\nexport type OnClickOutsideHandler<\n  T extends { detectIframe?: boolean; controls?: boolean } = {\n    detectIframe: false;\n    controls: false;\n  },\n> = (\n  event:\n    | (T[\"detectIframe\"] extends true ? FocusEvent : never)\n    | (T[\"controls\"] extends true ? Event : never)\n    | PointerEvent\n) => void;\n\nexport interface OnClickOutsideReturn {\n  /** Remove all event listeners */\n  stop: Fn;\n  /** Prevent the next outside click from triggering the handler */\n  cancel: Fn;\n  /** Manually trigger the handler with a given event */\n  trigger: (event: Event) => void;\n}\n\nlet _iOSWorkaround = false;\n\n// ---------------------------------------------------------------------------\n// Overloads\n// ---------------------------------------------------------------------------\n\nexport function createOnClickOutside<T extends OnClickOutsideOptions<false>>(\n  target: MaybeEventTarget,\n  handler: OnClickOutsideHandler<T>,\n  options?: T\n): Fn;\n\nexport function createOnClickOutside<T extends OnClickOutsideOptions<true>>(\n  target: MaybeEventTarget,\n  handler: OnClickOutsideHandler<T>,\n  options: T\n): OnClickOutsideReturn;\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\nexport function createOnClickOutside(\n  target: MaybeEventTarget,\n  handler: OnClickOutsideHandler<OnClickOutsideOptions<boolean>>,\n  options: DeepMaybeObservable<OnClickOutsideOptions<boolean>> = {}\n): Fn | OnClickOutsideReturn {\n  const raw = peek(options) as OnClickOutsideOptions<boolean> | undefined;\n  const capture = raw?.capture ?? true;\n  const controls = raw?.controls ?? false;\n  const detectIframe = raw?.detectIframe ?? false;\n  const ignore = raw?.ignore;\n\n  // Mutable flags — toggled synchronously within event handlers on a hot path.\n  let shouldListen = true;\n  let isProcessing = false;\n  // cancel() sets this — prevents the next click even if pointerdown resets shouldListen.\n  let cancelled = false;\n\n  // iOS workaround — passive click listeners on body children\n  // https://github.com/vueuse/vueuse/issues/1520\n  if (defaultWindow && isIOS() && !_iOSWorkaround) {\n    _iOSWorkaround = true;\n    const listenerOptions = { passive: true };\n    Array.from(defaultWindow.document.body.children).forEach((el) =>\n      el.addEventListener(\"click\", noop, listenerOptions)\n    );\n    defaultWindow.document.documentElement.addEventListener(\"click\", noop, listenerOptions);\n  }\n\n  const shouldIgnore = (event: Event): boolean => {\n    if (!ignore?.length) return false;\n    return ignore.some((item) => {\n      if (typeof item === \"string\") {\n        return Array.from(defaultWindow?.document?.querySelectorAll(item) ?? []).some(\n          (el) => el === event.target || event.composedPath().includes(el)\n        );\n      }\n      const el = peek(item);\n      return (\n        el && el instanceof Element && (event.target === el || event.composedPath().includes(el))\n      );\n    });\n  };\n\n  const listener = (event: Event) => {\n    const el = peek(target);\n\n    if (event.target == null) return;\n    if (!el || !(el instanceof Element)) return;\n    if (el === event.target || event.composedPath().includes(el)) return;\n\n    // detail === 0 means keyboard-triggered click — check ignore at this point\n    if (\"detail\" in event && (event as PointerEvent).detail === 0) {\n      shouldListen = !shouldIgnore(event);\n    }\n\n    if (!shouldListen || cancelled) {\n      shouldListen = true;\n      cancelled = false;\n      return;\n    }\n\n    handler(event as PointerEvent);\n  };\n\n  const win = (defaultWindow as MaybeEventTarget) ?? null;\n\n  // Main click listener\n  const clickCleanup = createEventListener(\n    win,\n    \"click\",\n    (event: PointerEvent) => {\n      if (!isProcessing) {\n        isProcessing = true;\n        setTimeout(() => {\n          isProcessing = false;\n        }, 0);\n        listener(event);\n      }\n    },\n    { passive: true, capture }\n  );\n\n  // Pointerdown — track whether the click started inside\n  const pointerCleanup = createEventListener(\n    win,\n    \"pointerdown\",\n    (e: PointerEvent) => {\n      const el = peek(target);\n      shouldListen =\n        !shouldIgnore(e) && !!(el && el instanceof Element && !e.composedPath().includes(el));\n    },\n    { passive: true }\n  );\n\n  // Iframe blur detection\n  const blurCleanup = createEventListener(\n    win,\n    \"blur\",\n    (event: FocusEvent) => {\n      if (!detectIframe) return;\n      setTimeout(() => {\n        if (!defaultWindow) return;\n        const el = peek(target);\n        if (\n          defaultWindow.document.activeElement?.tagName === \"IFRAME\" &&\n          el instanceof Element &&\n          !el.contains(defaultWindow.document.activeElement)\n        ) {\n          handler(event as unknown as PointerEvent);\n        }\n      }, 0);\n    },\n    { passive: true }\n  );\n\n  const stop: Fn = () => {\n    clickCleanup();\n    pointerCleanup();\n    blurCleanup();\n  };\n\n  const cancel: Fn = () => {\n    cancelled = true;\n  };\n\n  const trigger = (event: Event) => {\n    handler(event as PointerEvent);\n  };\n\n  return controls ? { stop, cancel, trigger } : stop;\n}\n"],"mappings":"AAAA,SAAS,YAA+C;AACxD,SAAS,YAAY;AACrB,SAAS,aAAa;AACtB,SAAkC,qBAAqB;AAEvD,SAAS,2BAA2B;AA+CpC,IAAI,iBAAiB;AAsBd,SAAS,qBACd,QACA,SACA,UAA+D,CAAC,GACrC;AAC3B,QAAM,MAAM,KAAK,OAAO;AACxB,QAAM,UAAU,KAAK,WAAW;AAChC,QAAM,WAAW,KAAK,YAAY;AAClC,QAAM,eAAe,KAAK,gBAAgB;AAC1C,QAAM,SAAS,KAAK;AAGpB,MAAI,eAAe;AACnB,MAAI,eAAe;AAEnB,MAAI,YAAY;AAIhB,MAAI,iBAAiB,MAAM,KAAK,CAAC,gBAAgB;AAC/C,qBAAiB;AACjB,UAAM,kBAAkB,EAAE,SAAS,KAAK;AACxC,UAAM,KAAK,cAAc,SAAS,KAAK,QAAQ,EAAE;AAAA,MAAQ,CAAC,OACxD,GAAG,iBAAiB,SAAS,MAAM,eAAe;AAAA,IACpD;AACA,kBAAc,SAAS,gBAAgB,iBAAiB,SAAS,MAAM,eAAe;AAAA,EACxF;AAEA,QAAM,eAAe,CAAC,UAA0B;AAC9C,QAAI,CAAC,QAAQ,OAAQ,QAAO;AAC5B,WAAO,OAAO,KAAK,CAAC,SAAS;AAC3B,UAAI,OAAO,SAAS,UAAU;AAC5B,eAAO,MAAM,KAAK,eAAe,UAAU,iBAAiB,IAAI,KAAK,CAAC,CAAC,EAAE;AAAA,UACvE,CAACA,QAAOA,QAAO,MAAM,UAAU,MAAM,aAAa,EAAE,SAASA,GAAE;AAAA,QACjE;AAAA,MACF;AACA,YAAM,KAAK,KAAK,IAAI;AACpB,aACE,MAAM,cAAc,YAAY,MAAM,WAAW,MAAM,MAAM,aAAa,EAAE,SAAS,EAAE;AAAA,IAE3F,CAAC;AAAA,EACH;AAEA,QAAM,WAAW,CAAC,UAAiB;AACjC,UAAM,KAAK,KAAK,MAAM;AAEtB,QAAI,MAAM,UAAU,KAAM;AAC1B,QAAI,CAAC,MAAM,EAAE,cAAc,SAAU;AACrC,QAAI,OAAO,MAAM,UAAU,MAAM,aAAa,EAAE,SAAS,EAAE,EAAG;AAG9D,QAAI,YAAY,SAAU,MAAuB,WAAW,GAAG;AAC7D,qBAAe,CAAC,aAAa,KAAK;AAAA,IACpC;AAEA,QAAI,CAAC,gBAAgB,WAAW;AAC9B,qBAAe;AACf,kBAAY;AACZ;AAAA,IACF;AAEA,YAAQ,KAAqB;AAAA,EAC/B;AAEA,QAAM,MAAO,iBAAsC;AAGnD,QAAM,eAAe;AAAA,IACnB;AAAA,IACA;AAAA,IACA,CAAC,UAAwB;AACvB,UAAI,CAAC,cAAc;AACjB,uBAAe;AACf,mBAAW,MAAM;AACf,yBAAe;AAAA,QACjB,GAAG,CAAC;AACJ,iBAAS,KAAK;AAAA,MAChB;AAAA,IACF;AAAA,IACA,EAAE,SAAS,MAAM,QAAQ;AAAA,EAC3B;AAGA,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA;AAAA,IACA,CAAC,MAAoB;AACnB,YAAM,KAAK,KAAK,MAAM;AACtB,qBACE,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,cAAc,WAAW,CAAC,EAAE,aAAa,EAAE,SAAS,EAAE;AAAA,IACvF;AAAA,IACA,EAAE,SAAS,KAAK;AAAA,EAClB;AAGA,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA,CAAC,UAAsB;AACrB,UAAI,CAAC,aAAc;AACnB,iBAAW,MAAM;AACf,YAAI,CAAC,cAAe;AACpB,cAAM,KAAK,KAAK,MAAM;AACtB,YACE,cAAc,SAAS,eAAe,YAAY,YAClD,cAAc,WACd,CAAC,GAAG,SAAS,cAAc,SAAS,aAAa,GACjD;AACA,kBAAQ,KAAgC;AAAA,QAC1C;AAAA,MACF,GAAG,CAAC;AAAA,IACN;AAAA,IACA,EAAE,SAAS,KAAK;AAAA,EAClB;AAEA,QAAM,OAAW,MAAM;AACrB,iBAAa;AACb,mBAAe;AACf,gBAAY;AAAA,EACd;AAEA,QAAM,SAAa,MAAM;AACvB,gBAAY;AAAA,EACd;AAEA,QAAM,UAAU,CAAC,UAAiB;AAChC,YAAQ,KAAqB;AAAA,EAC/B;AAEA,SAAO,WAAW,EAAE,MAAM,QAAQ,QAAQ,IAAI;AAChD;","names":["el"]}