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