UNPKG

element-plus

Version:

A Component Library for Vue 3

1 lines 7.59 kB
{"version":3,"file":"focus-trap.mjs","sources":["../../../../../../packages/components/focus-trap/src/focus-trap.vue"],"sourcesContent":["<template>\n <slot />\n</template>\n<script lang=\"ts\">\nimport {\n defineComponent,\n ref,\n onMounted,\n onBeforeUnmount,\n provide,\n unref,\n watch,\n} from 'vue'\nimport { on, off } from '@element-plus/utils'\nimport { EVENT_CODE } from '@element-plus/constants'\nimport {\n focusableStack,\n focusFirstDescendant,\n getEdges,\n tryFocus,\n obtainAllFocusableElements,\n} from './utils'\nimport {\n FOCUS_TRAP_INJECTION_KEY,\n FOCUS_ON_MOUNT,\n FOCUS_ON_MOUNT_OPTS,\n ON_MOUNT_FOCUS_EVT,\n ON_UNMOUNT_FOCUS_EVT,\n FOCUS_ON_UNMOUNT,\n} from './tokens'\n\nimport type { FocusLayer } from './utils'\n\nexport default defineComponent({\n name: 'ElFocusTrap',\n inheritAttrs: false,\n props: {\n loop: Boolean,\n trapped: Boolean,\n },\n emits: [ON_MOUNT_FOCUS_EVT, ON_UNMOUNT_FOCUS_EVT],\n setup(props, { emit }) {\n const focusTrapRef = ref<HTMLElement | null>()\n const forwardRef = ref<HTMLElement | null>(null)\n let lastFocusBeforeMounted: HTMLElement | null\n let lastFocusAfterMounted: HTMLElement | null\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) e.preventDefault()\n } else {\n if (!shiftKey && currentFocusingEl === last) {\n e.preventDefault()\n if (loop) tryFocus(first, true)\n } else if (shiftKey && currentFocusingEl === first) {\n e.preventDefault()\n if (loop) tryFocus(last, true)\n }\n }\n }\n }\n\n provide(FOCUS_TRAP_INJECTION_KEY, {\n focusTrapRef: forwardRef,\n onKeydown,\n })\n\n const focusOnMount = (e: Event) => {\n emit(ON_MOUNT_FOCUS_EVT, e)\n }\n const focusOnUnmount = (e: Event) => emit(ON_UNMOUNT_FOCUS_EVT, e)\n const onFocusIn = (e: Event) => {\n const trapContainer = unref(forwardRef)\n if (focusLayer.paused || !trapContainer) return\n const target = e.target as HTMLElement | null\n if (target && trapContainer.contains(target)) {\n lastFocusAfterMounted = target\n } else {\n tryFocus(lastFocusAfterMounted, true)\n }\n }\n\n const onFocusOut = (e: Event) => {\n const trapContainer = unref(forwardRef)\n if (focusLayer.paused || !trapContainer) return\n\n if (\n !trapContainer.contains(\n (e as FocusEvent).relatedTarget as HTMLElement | null\n )\n ) {\n tryFocus(lastFocusAfterMounted, true)\n }\n }\n\n onMounted(() => {\n const trapContainer = unref(forwardRef)\n if (trapContainer) {\n focusableStack.push(focusLayer)\n const prevFocusedElement = document.activeElement\n lastFocusBeforeMounted = prevFocusedElement as HTMLElement | null\n const isPrevFocusContained = trapContainer.contains(prevFocusedElement)\n if (!isPrevFocusContained) {\n const mountEvent = new Event(FOCUS_ON_MOUNT, FOCUS_ON_MOUNT_OPTS)\n on(trapContainer, FOCUS_ON_MOUNT, focusOnMount)\n trapContainer.dispatchEvent(mountEvent)\n if (!mountEvent.defaultPrevented) {\n focusFirstDescendant(\n obtainAllFocusableElements(trapContainer),\n true\n )\n if (document.activeElement === prevFocusedElement) {\n tryFocus(trapContainer)\n }\n }\n }\n }\n\n watch(\n () => props.trapped,\n (trapped) => {\n if (trapped) {\n on(document, 'focusin', onFocusIn)\n on(document, 'focusout', onFocusOut)\n } else {\n off(document, 'focusin', onFocusIn)\n off(document, 'focusout', onFocusOut)\n }\n },\n { immediate: true }\n )\n })\n\n onBeforeUnmount(() => {\n const trapContainer = unref(forwardRef)\n\n if (trapContainer) {\n off(trapContainer, FOCUS_ON_MOUNT, focusOnMount)\n const unmountEvent = new Event(FOCUS_ON_UNMOUNT, FOCUS_ON_MOUNT_OPTS)\n\n on(trapContainer, FOCUS_ON_UNMOUNT, focusOnUnmount)\n trapContainer.dispatchEvent(unmountEvent)\n\n if (!unmountEvent.defaultPrevented) {\n tryFocus(lastFocusBeforeMounted ?? document.body, true)\n }\n\n off(trapContainer, FOCUS_ON_UNMOUNT, focusOnUnmount)\n\n focusableStack.remove(focusLayer)\n }\n })\n\n return {\n focusTrapRef,\n forwardRef,\n onKeydown,\n }\n },\n})\n</script>\n"],"names":[],"mappings":"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"}