{"version":3,"file":"focus-trap.mjs","sources":["../../../../../../packages/components/focus-trap/src/focus-trap.vue"],"sourcesContent":["<template>\n  <slot :handle-keydown=\"onKeydown\"></slot>\n</template>\n<script lang=\"ts\">\nimport {\n  defineComponent,\n  nextTick,\n  onBeforeUnmount,\n  onMounted,\n  provide,\n  ref,\n  unref,\n  watch,\n} from 'vue'\nimport { isNil } from 'lodash-unified'\nimport { EVENT_CODE } from '@hongluan-ui/constants'\nimport { useEscapeKeydown } from '@hongluan-ui/hooks'\nimport { isString } from '@hongluan-ui/utils'\nimport {\n  createFocusOutPreventedEvent,\n  focusFirstDescendant,\n  focusableStack,\n  getEdges,\n  isFocusCausedByUserEvent,\n  obtainAllFocusableElements,\n  tryFocus,\n  useFocusReason,\n} from './utils'\nimport {\n  FOCUS_AFTER_RELEASED,\n  FOCUS_AFTER_TRAPPED,\n  FOCUS_AFTER_TRAPPED_OPTS,\n  FOCUS_TRAP_INJECTION_KEY,\n  ON_RELEASE_FOCUS_EVT,\n  ON_TRAP_FOCUS_EVT,\n} from './tokens'\n\nimport type { PropType } from 'vue'\nimport type { FocusLayer } from './utils'\n\nexport default defineComponent({\n  name: 'FocusTrap',\n  inheritAttrs: false,\n  props: {\n    loop: Boolean,\n    trapped: Boolean,\n    focusTrapEl: Object as PropType<HTMLElement>,\n    focusStartEl: {\n      type: [Object, String] as PropType<'container' | 'first' | HTMLElement>,\n      default: 'first',\n    },\n  },\n  emits: [\n    ON_TRAP_FOCUS_EVT,\n    ON_RELEASE_FOCUS_EVT,\n    'focusin',\n    'focusout',\n    'focusout-prevented',\n    'release-requested',\n  ],\n  setup(props, { emit }) {\n    const forwardRef = ref<HTMLElement | undefined>()\n    let lastFocusBeforeTrapped: HTMLElement | null\n    let lastFocusAfterTrapped: HTMLElement | null\n\n    const { focusReason } = useFocusReason()\n\n    useEscapeKeydown(event => {\n      if (props.trapped && !focusLayer.paused) {\n        emit('release-requested', event)\n      }\n    })\n\n    const focusLayer: FocusLayer = {\n      paused: false,\n      pause() {\n        this.paused = true\n      },\n      resume() {\n        this.paused = false\n      },\n    }\n\n    const onKeydown = (e: KeyboardEvent) => {\n      if (!props.loop && !props.trapped) return\n      if (focusLayer.paused) return\n\n      const { key, altKey, ctrlKey, metaKey, currentTarget, shiftKey } = e\n      const { loop } = props\n      const isTabbing =\n        key === EVENT_CODE.tab && !altKey && !ctrlKey && !metaKey\n\n      const currentFocusingEl = document.activeElement\n      if (isTabbing && currentFocusingEl) {\n        const container = currentTarget as HTMLElement\n        const [first, last] = getEdges(container)\n        const isTabbable = first && last\n\n        if (!isTabbable) {\n          if (currentFocusingEl === container) {\n            const focusoutPreventedEvent = createFocusOutPreventedEvent({\n              focusReason: focusReason.value,\n            })\n            emit('focusout-prevented', focusoutPreventedEvent)\n            if (!focusoutPreventedEvent.defaultPrevented) {\n              e.preventDefault()\n            }\n          }\n        } else {\n          if (!shiftKey && currentFocusingEl === last) {\n            const focusoutPreventedEvent = createFocusOutPreventedEvent({\n              focusReason: focusReason.value,\n            })\n            emit('focusout-prevented', focusoutPreventedEvent)\n            if (!focusoutPreventedEvent.defaultPrevented) {\n              e.preventDefault()\n              if (loop) tryFocus(first, true)\n            }\n          } else if (\n            shiftKey &&\n            [first, container].includes(currentFocusingEl as HTMLElement)\n          ) {\n            const focusoutPreventedEvent = createFocusOutPreventedEvent({\n              focusReason: focusReason.value,\n            })\n            emit('focusout-prevented', focusoutPreventedEvent)\n            if (!focusoutPreventedEvent.defaultPrevented) {\n              e.preventDefault()\n              if (loop) tryFocus(last, true)\n            }\n          }\n        }\n      }\n    }\n\n    provide(FOCUS_TRAP_INJECTION_KEY, {\n      focusTrapRef: forwardRef,\n      onKeydown,\n    })\n\n    watch(\n      () => props.focusTrapEl,\n      focusTrapEl => {\n        if (focusTrapEl) {\n          forwardRef.value = focusTrapEl\n        }\n      },\n      { immediate: true },\n    )\n\n    watch([forwardRef], ([forwardRef], [oldForwardRef]) => {\n      if (forwardRef) {\n        forwardRef.addEventListener('keydown', onKeydown)\n        forwardRef.addEventListener('focusin', onFocusIn)\n        forwardRef.addEventListener('focusout', onFocusOut)\n      }\n      if (oldForwardRef) {\n        oldForwardRef.removeEventListener('keydown', onKeydown)\n        oldForwardRef.removeEventListener('focusin', onFocusIn)\n        oldForwardRef.removeEventListener('focusout', onFocusOut)\n      }\n    })\n\n    const trapOnFocus = (e: Event) => {\n      emit(ON_TRAP_FOCUS_EVT, e)\n    }\n    const releaseOnFocus = (e: Event) => emit(ON_RELEASE_FOCUS_EVT, e)\n\n    const onFocusIn = (e: FocusEvent) => {\n      const trapContainer = unref(forwardRef)\n      if (!trapContainer) return\n\n      const target = e.target as HTMLElement | null\n      const relatedTarget = e.relatedTarget as HTMLElement | null\n      const isFocusedInTrap = target && trapContainer.contains(target)\n\n      if (!props.trapped) {\n        const isPrevFocusedInTrap =\n          relatedTarget && trapContainer.contains(relatedTarget)\n        if (!isPrevFocusedInTrap) {\n          lastFocusBeforeTrapped = relatedTarget\n        }\n      }\n\n      if (isFocusedInTrap) emit('focusin', e)\n\n      if (focusLayer.paused) return\n\n      if (props.trapped) {\n        if (isFocusedInTrap) {\n          lastFocusAfterTrapped = target\n        } else {\n          tryFocus(lastFocusAfterTrapped, true)\n        }\n      }\n    }\n\n    const onFocusOut = (e: Event) => {\n      const trapContainer = unref(forwardRef)\n      if (focusLayer.paused || !trapContainer) return\n\n      if (props.trapped) {\n        const relatedTarget = (e as FocusEvent)\n          .relatedTarget as HTMLElement | null\n        if (!isNil(relatedTarget) && !trapContainer.contains(relatedTarget)) {\n          // Give embedded focus layer time to pause this layer before reclaiming focus\n          // And only reclaim focus if it should currently be trapping\n          setTimeout(() => {\n            if (!focusLayer.paused && props.trapped) {\n              const focusoutPreventedEvent = createFocusOutPreventedEvent({\n                focusReason: focusReason.value,\n              })\n              emit('focusout-prevented', focusoutPreventedEvent)\n              if (!focusoutPreventedEvent.defaultPrevented) {\n                tryFocus(lastFocusAfterTrapped, true)\n              }\n            }\n          }, 0)\n        }\n      } else {\n        const target = e.target as HTMLElement | null\n        const isFocusedInTrap = target && trapContainer.contains(target)\n        if (!isFocusedInTrap) emit('focusout', e)\n      }\n    }\n\n    async function startTrap() {\n      // Wait for forwardRef to resolve\n      await nextTick()\n      const trapContainer = unref(forwardRef)\n      if (trapContainer) {\n        focusableStack.push(focusLayer)\n        const prevFocusedElement = trapContainer.contains(\n          document.activeElement,\n        )\n          ? lastFocusBeforeTrapped\n          : document.activeElement\n        lastFocusBeforeTrapped = prevFocusedElement as HTMLElement | null\n        const isPrevFocusContained = trapContainer.contains(prevFocusedElement)\n        if (!isPrevFocusContained) {\n          const focusEvent = new Event(\n            FOCUS_AFTER_TRAPPED,\n            FOCUS_AFTER_TRAPPED_OPTS,\n          )\n          trapContainer.addEventListener(FOCUS_AFTER_TRAPPED, trapOnFocus)\n          trapContainer.dispatchEvent(focusEvent)\n          if (!focusEvent.defaultPrevented) {\n            nextTick(() => {\n              let focusStartEl = props.focusStartEl\n              if (!isString(focusStartEl)) {\n                tryFocus(focusStartEl)\n                if (document.activeElement !== focusStartEl) {\n                  focusStartEl = 'first'\n                }\n              }\n              if (focusStartEl === 'first') {\n                focusFirstDescendant(\n                  obtainAllFocusableElements(trapContainer),\n                  true,\n                )\n              }\n              if (\n                document.activeElement === prevFocusedElement ||\n                focusStartEl === 'container'\n              ) {\n                tryFocus(trapContainer)\n              }\n            })\n          }\n        }\n      }\n    }\n\n    function stopTrap() {\n      const trapContainer = unref(forwardRef)\n\n      if (trapContainer) {\n        trapContainer.removeEventListener(FOCUS_AFTER_TRAPPED, trapOnFocus)\n\n        const releasedEvent = new CustomEvent(FOCUS_AFTER_RELEASED, {\n          ...FOCUS_AFTER_TRAPPED_OPTS,\n          detail: {\n            focusReason: focusReason.value,\n          },\n        })\n        trapContainer.addEventListener(FOCUS_AFTER_RELEASED, releaseOnFocus)\n        trapContainer.dispatchEvent(releasedEvent)\n\n        if (\n          !releasedEvent.defaultPrevented &&\n          (focusReason.value == 'keyboard' ||\n            !isFocusCausedByUserEvent() ||\n            trapContainer.contains(document.activeElement))\n        ) {\n          tryFocus(lastFocusBeforeTrapped ?? document.body)\n        }\n\n        trapContainer.removeEventListener(FOCUS_AFTER_RELEASED, releaseOnFocus)\n        focusableStack.remove(focusLayer)\n      }\n    }\n\n    onMounted(() => {\n      if (props.trapped) {\n        startTrap()\n      }\n\n      watch(\n        () => props.trapped,\n        trapped => {\n          if (trapped) {\n            startTrap()\n          } else {\n            stopTrap()\n          }\n        },\n      )\n    })\n\n    onBeforeUnmount(() => {\n      if (props.trapped) {\n        stopTrap()\n      }\n\n      if (forwardRef.value) {\n        forwardRef.value.removeEventListener('keydown', onKeydown)\n        forwardRef.value.removeEventListener('focusin', onFocusIn)\n        forwardRef.value.removeEventListener('focusout', onFocusOut)\n        forwardRef.value = undefined\n      }\n    })\n\n    return {\n      onKeydown,\n    }\n  },\n})\n</script>\n"],"names":["_renderSlot"],"mappings":"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"}