{"version":3,"file":"utils.cjs","sources":["../../src/DismissableLayer/utils.ts"],"sourcesContent":["import { isClient } from '@vueuse/shared'\nimport { handleAndDispatchCustomEvent } from '@/shared'\nimport { type Ref, nextTick, ref, watchEffect } from 'vue'\n\nexport type PointerDownOutsideEvent = CustomEvent<{\n  originalEvent: PointerEvent\n}>\nexport type FocusOutsideEvent = CustomEvent<{ originalEvent: FocusEvent }>\n\nexport const DISMISSABLE_LAYER_NAME = 'DismissableLayer'\nexport const CONTEXT_UPDATE = 'dismissableLayer.update'\nexport const POINTER_DOWN_OUTSIDE = 'dismissableLayer.pointerDownOutside'\nexport const FOCUS_OUTSIDE = 'dismissableLayer.focusOutside'\n\nfunction isLayerExist(layerElement: HTMLElement, targetElement: HTMLElement) {\n  const targetLayer = targetElement.closest(\n    '[data-dismissable-layer]',\n  ) as HTMLElement\n\n  const mainLayer = layerElement.dataset.dismissableLayer === ''\n    ? layerElement\n    : layerElement.querySelector(\n      '[data-dismissable-layer]',\n    ) as HTMLElement\n\n  const nodeList = Array.from(\n    layerElement.ownerDocument.querySelectorAll('[data-dismissable-layer]'),\n  )\n  if (\n    (targetLayer\n      && mainLayer === targetLayer)\n      || nodeList.indexOf(mainLayer) < nodeList.indexOf(targetLayer)\n  ) {\n    return true\n  }\n  else {\n    return false\n  }\n}\n\n/**\n * Listens for `pointerdown` outside a DOM subtree. We use `pointerdown` rather than `pointerup`\n * to mimic layer dismissing behaviour present in OS.\n * Returns props to pass to the node we want to check for outside events.\n */\nexport function usePointerDownOutside(\n  onPointerDownOutside?: (event: PointerDownOutsideEvent) => void,\n  element?: Ref<HTMLElement | undefined>,\n) {\n  const ownerDocument: Document\n    = element?.value?.ownerDocument ?? globalThis?.document\n\n  const isPointerInsideDOMTree = ref(false)\n  const handleClickRef = ref(() => {})\n\n  watchEffect((cleanupFn) => {\n    if (!isClient)\n      return\n    const handlePointerDown = async (event: PointerEvent) => {\n      const target = event.target as HTMLElement\n\n      if (!element?.value)\n        return\n\n      if (isLayerExist(element.value, target)) {\n        isPointerInsideDOMTree.value = false\n        return\n      }\n\n      if (event.target && !isPointerInsideDOMTree.value) {\n        const eventDetail = { originalEvent: event }\n\n        function handleAndDispatchPointerDownOutsideEvent() {\n          handleAndDispatchCustomEvent(\n            POINTER_DOWN_OUTSIDE,\n            onPointerDownOutside,\n            eventDetail,\n          )\n        }\n\n        /**\n         * On touch devices, we need to wait for a click event because browsers implement\n         * a ~350ms delay between the time the user stops touching the display and when the\n         * browser executes events. We need to ensure we don't reactivate pointer-events within\n         * this timeframe otherwise the browser may execute events that should have been prevented.\n         *\n         * Additionally, this also lets us deal automatically with cancellations when a click event\n         * isn't raised because the page was considered scrolled/drag-scrolled, long-pressed, etc.\n         *\n         * This is why we also continuously remove the previous listener, because we cannot be\n         * certain that it was raised, and therefore cleaned-up.\n         */\n        if (event.pointerType === 'touch') {\n          ownerDocument.removeEventListener('click', handleClickRef.value)\n          handleClickRef.value = handleAndDispatchPointerDownOutsideEvent\n          ownerDocument.addEventListener('click', handleClickRef.value, {\n            once: true,\n          })\n        }\n        else {\n          handleAndDispatchPointerDownOutsideEvent()\n        }\n      }\n      else {\n        // We need to remove the event listener in case the outside click has been canceled.\n        // See: https://github.com/radix-ui/primitives/issues/2171\n        ownerDocument.removeEventListener('click', handleClickRef.value)\n      }\n      isPointerInsideDOMTree.value = false\n    }\n    /**\n     * if this hook executes in a component that mounts via a `pointerdown` event, the event\n     * would bubble up to the document and trigger a `pointerDownOutside` event. We avoid\n     * this by delaying the event listener registration on the document.\n     * This is how the DOM works, ie:\n     * ```\n     * button.addEventListener('pointerdown', () => {\n     *   console.log('I will log');\n     *   document.addEventListener('pointerdown', () => {\n     *     console.log('I will also log');\n     *   })\n     * });\n     */\n    const timerId = window.setTimeout(() => {\n      ownerDocument.addEventListener('pointerdown', handlePointerDown)\n    }, 0)\n\n    cleanupFn(() => {\n      window.clearTimeout(timerId)\n      ownerDocument.removeEventListener('pointerdown', handlePointerDown)\n      ownerDocument.removeEventListener('click', handleClickRef.value)\n    })\n  })\n\n  return {\n    onPointerDownCapture: () => (isPointerInsideDOMTree.value = true),\n  }\n}\n\n/**\n * Listens for when focus happens outside a DOM subtree.\n * Returns props to pass to the root (node) of the subtree we want to check.\n */\nexport function useFocusOutside(\n  onFocusOutside?: (event: FocusOutsideEvent) => void,\n  element?: Ref<HTMLElement | undefined>,\n) {\n  const ownerDocument: Document\n    = element?.value?.ownerDocument ?? globalThis?.document\n\n  const isFocusInsideDOMTree = ref(false)\n  watchEffect((cleanupFn) => {\n    if (!isClient)\n      return\n    const handleFocus = async (event: FocusEvent) => {\n      if (!element?.value)\n        return\n\n      await nextTick()\n      await nextTick()\n      if (!element.value || isLayerExist(element.value, event.target as HTMLElement))\n        return\n\n      if (event.target && !isFocusInsideDOMTree.value) {\n        const eventDetail = { originalEvent: event }\n        handleAndDispatchCustomEvent(\n          FOCUS_OUTSIDE,\n          onFocusOutside,\n          eventDetail,\n        )\n      }\n    }\n\n    ownerDocument.addEventListener('focusin', handleFocus)\n\n    cleanupFn(() => ownerDocument.removeEventListener('focusin', handleFocus))\n  })\n\n  return {\n    onFocusCapture: () => (isFocusInsideDOMTree.value = true),\n    onBlurCapture: () => (isFocusInsideDOMTree.value = false),\n  }\n}\n\nexport function dispatchUpdate() {\n  const event = new CustomEvent(CONTEXT_UPDATE)\n  document.dispatchEvent(event)\n}\n"],"names":["ref","watchEffect","isClient","handleAndDispatchCustomEvent","nextTick"],"mappings":"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"}