'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var vue = require('vue'); var lodashUnified = require('lodash-unified'); require('../../../constants/index.js'); require('../../../hooks/index.js'); require('../../../utils/index.js'); var utils = require('./utils.js'); var tokens = require('./tokens.js'); var pluginVue_exportHelper = require('../../../_virtual/plugin-vue_export-helper.js'); var index = require('../../../hooks/use-escape-keydown/index.js'); var aria = require('../../../constants/aria.js'); var shared = require('@vue/shared'); const _sfc_main = vue.defineComponent({ name: "ElFocusTrap", inheritAttrs: false, props: { loop: Boolean, trapped: Boolean, focusTrapEl: Object, focusStartEl: { type: [Object, String], default: "first" } }, emits: [ tokens.ON_TRAP_FOCUS_EVT, tokens.ON_RELEASE_FOCUS_EVT, "focusin", "focusout", "focusout-prevented", "release-requested" ], setup(props, { emit }) { const forwardRef = vue.ref(); let lastFocusBeforeTrapped; let lastFocusAfterTrapped; const { focusReason } = utils.useFocusReason(); index.useEscapeKeydown((event) => { if (props.trapped && !focusLayer.paused) { emit("release-requested", event); } }); const focusLayer = { paused: false, pause() { this.paused = true; }, resume() { this.paused = false; } }; const onKeydown = (e) => { if (!props.loop && !props.trapped) return; if (focusLayer.paused) return; const { key, altKey, ctrlKey, metaKey, currentTarget, shiftKey } = e; const { loop } = props; const isTabbing = key === aria.EVENT_CODE.tab && !altKey && !ctrlKey && !metaKey; const currentFocusingEl = document.activeElement; if (isTabbing && currentFocusingEl) { const container = currentTarget; const [first, last] = utils.getEdges(container); const isTabbable = first && last; if (!isTabbable) { if (currentFocusingEl === container) { const focusoutPreventedEvent = utils.createFocusOutPreventedEvent({ focusReason: focusReason.value }); emit("focusout-prevented", focusoutPreventedEvent); if (!focusoutPreventedEvent.defaultPrevented) { e.preventDefault(); } } } else { if (!shiftKey && currentFocusingEl === last) { const focusoutPreventedEvent = utils.createFocusOutPreventedEvent({ focusReason: focusReason.value }); emit("focusout-prevented", focusoutPreventedEvent); if (!focusoutPreventedEvent.defaultPrevented) { e.preventDefault(); if (loop) utils.tryFocus(first, true); } } else if (shiftKey && [first, container].includes(currentFocusingEl)) { const focusoutPreventedEvent = utils.createFocusOutPreventedEvent({ focusReason: focusReason.value }); emit("focusout-prevented", focusoutPreventedEvent); if (!focusoutPreventedEvent.defaultPrevented) { e.preventDefault(); if (loop) utils.tryFocus(last, true); } } } } }; vue.provide(tokens.FOCUS_TRAP_INJECTION_KEY, { focusTrapRef: forwardRef, onKeydown }); vue.watch(() => props.focusTrapEl, (focusTrapEl) => { if (focusTrapEl) { forwardRef.value = focusTrapEl; } }, { immediate: true }); vue.watch([forwardRef], ([forwardRef2], [oldForwardRef]) => { if (forwardRef2) { forwardRef2.addEventListener("keydown", onKeydown); forwardRef2.addEventListener("focusin", onFocusIn); forwardRef2.addEventListener("focusout", onFocusOut); } if (oldForwardRef) { oldForwardRef.removeEventListener("keydown", onKeydown); oldForwardRef.removeEventListener("focusin", onFocusIn); oldForwardRef.removeEventListener("focusout", onFocusOut); } }); const trapOnFocus = (e) => { emit(tokens.ON_TRAP_FOCUS_EVT, e); }; const releaseOnFocus = (e) => emit(tokens.ON_RELEASE_FOCUS_EVT, e); const onFocusIn = (e) => { const trapContainer = vue.unref(forwardRef); if (!trapContainer) return; const target = e.target; const relatedTarget = e.relatedTarget; const isFocusedInTrap = target && trapContainer.contains(target); if (!props.trapped) { const isPrevFocusedInTrap = relatedTarget && trapContainer.contains(relatedTarget); if (!isPrevFocusedInTrap) { lastFocusBeforeTrapped = relatedTarget; } } if (isFocusedInTrap) emit("focusin", e); if (focusLayer.paused) return; if (props.trapped) { if (isFocusedInTrap) { lastFocusAfterTrapped = target; } else { utils.tryFocus(lastFocusAfterTrapped, true); } } }; const onFocusOut = (e) => { const trapContainer = vue.unref(forwardRef); if (focusLayer.paused || !trapContainer) return; if (props.trapped) { const relatedTarget = e.relatedTarget; if (!lodashUnified.isNil(relatedTarget) && !trapContainer.contains(relatedTarget)) { setTimeout(() => { if (!focusLayer.paused && props.trapped) { const focusoutPreventedEvent = utils.createFocusOutPreventedEvent({ focusReason: focusReason.value }); emit("focusout-prevented", focusoutPreventedEvent); if (!focusoutPreventedEvent.defaultPrevented) { utils.tryFocus(lastFocusAfterTrapped, true); } } }, 0); } } else { const target = e.target; const isFocusedInTrap = target && trapContainer.contains(target); if (!isFocusedInTrap) emit("focusout", e); } }; async function startTrap() { await vue.nextTick(); const trapContainer = vue.unref(forwardRef); if (trapContainer) { utils.focusableStack.push(focusLayer); const prevFocusedElement = trapContainer.contains(document.activeElement) ? lastFocusBeforeTrapped : document.activeElement; lastFocusBeforeTrapped = prevFocusedElement; const isPrevFocusContained = trapContainer.contains(prevFocusedElement); if (!isPrevFocusContained) { const focusEvent = new Event(tokens.FOCUS_AFTER_TRAPPED, tokens.FOCUS_AFTER_TRAPPED_OPTS); trapContainer.addEventListener(tokens.FOCUS_AFTER_TRAPPED, trapOnFocus); trapContainer.dispatchEvent(focusEvent); if (!focusEvent.defaultPrevented) { vue.nextTick(() => { let focusStartEl = props.focusStartEl; if (!shared.isString(focusStartEl)) { utils.tryFocus(focusStartEl); if (document.activeElement !== focusStartEl) { focusStartEl = "first"; } } if (focusStartEl === "first") { utils.focusFirstDescendant(utils.obtainAllFocusableElements(trapContainer), true); } if (document.activeElement === prevFocusedElement || focusStartEl === "container") { utils.tryFocus(trapContainer); } }); } } } } function stopTrap() { const trapContainer = vue.unref(forwardRef); if (trapContainer) { trapContainer.removeEventListener(tokens.FOCUS_AFTER_TRAPPED, trapOnFocus); const releasedEvent = new CustomEvent(tokens.FOCUS_AFTER_RELEASED, { ...tokens.FOCUS_AFTER_TRAPPED_OPTS, detail: { focusReason: focusReason.value } }); trapContainer.addEventListener(tokens.FOCUS_AFTER_RELEASED, releaseOnFocus); trapContainer.dispatchEvent(releasedEvent); if (!releasedEvent.defaultPrevented && (focusReason.value == "keyboard" || !utils.isFocusCausedByUserEvent() || trapContainer.contains(document.activeElement))) { utils.tryFocus(lastFocusBeforeTrapped != null ? lastFocusBeforeTrapped : document.body); } trapContainer.removeEventListener(tokens.FOCUS_AFTER_RELEASED, releaseOnFocus); utils.focusableStack.remove(focusLayer); } } vue.onMounted(() => { if (props.trapped) { startTrap(); } vue.watch(() => props.trapped, (trapped) => { if (trapped) { startTrap(); } else { stopTrap(); } }); }); vue.onBeforeUnmount(() => { if (props.trapped) { stopTrap(); } if (forwardRef.value) { forwardRef.value.removeEventListener("keydown", onKeydown); forwardRef.value.removeEventListener("focusin", onFocusIn); forwardRef.value.removeEventListener("focusout", onFocusOut); forwardRef.value = void 0; } }); return { onKeydown }; } }); function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { return vue.renderSlot(_ctx.$slots, "default", { handleKeydown: _ctx.onKeydown }); } var ElFocusTrap = /* @__PURE__ */ pluginVue_exportHelper["default"](_sfc_main, [["render", _sfc_render], ["__file", "focus-trap.vue"]]); exports["default"] = ElFocusTrap; //# sourceMappingURL=focus-trap.js.map