Newer
Older
KaiFengPC / scripts / new_node_modules / element-plus / es / components / focus-trap / src / focus-trap.mjs
@zhangdeliang zhangdeliang on 23 May 9 KB 初始化项目
  1. import {
  2. defineComponent,
  3. ref,
  4. provide,
  5. watch,
  6. unref,
  7. nextTick,
  8. onMounted,
  9. onBeforeUnmount,
  10. renderSlot,
  11. } from "vue";
  12. import { isNil } from "lodash-unified";
  13. import "../../../constants/index.mjs";
  14. import "../../../hooks/index.mjs";
  15. import "../../../utils/index.mjs";
  16. import {
  17. useFocusReason,
  18. getEdges,
  19. createFocusOutPreventedEvent,
  20. tryFocus,
  21. focusableStack,
  22. focusFirstDescendant,
  23. obtainAllFocusableElements,
  24. isFocusCausedByUserEvent,
  25. } from "./utils.mjs";
  26. import {
  27. ON_TRAP_FOCUS_EVT,
  28. ON_RELEASE_FOCUS_EVT,
  29. FOCUS_TRAP_INJECTION_KEY,
  30. FOCUS_AFTER_TRAPPED,
  31. FOCUS_AFTER_TRAPPED_OPTS,
  32. FOCUS_AFTER_RELEASED,
  33. } from "./tokens.mjs";
  34. import _export_sfc from "../../../_virtual/plugin-vue_export-helper.mjs";
  35. import { useEscapeKeydown } from "../../../hooks/use-escape-keydown/index.mjs";
  36. import { EVENT_CODE } from "../../../constants/aria.mjs";
  37. import { isString } from "@vue/shared";
  38.  
  39. const _sfc_main = defineComponent({
  40. name: "ElFocusTrap",
  41. inheritAttrs: false,
  42. props: {
  43. loop: Boolean,
  44. trapped: Boolean,
  45. focusTrapEl: Object,
  46. focusStartEl: {
  47. type: [Object, String],
  48. default: "first",
  49. },
  50. },
  51. emits: [
  52. ON_TRAP_FOCUS_EVT,
  53. ON_RELEASE_FOCUS_EVT,
  54. "focusin",
  55. "focusout",
  56. "focusout-prevented",
  57. "release-requested",
  58. ],
  59. setup(props, { emit }) {
  60. const forwardRef = ref();
  61. let lastFocusBeforeTrapped;
  62. let lastFocusAfterTrapped;
  63. const { focusReason } = useFocusReason();
  64. useEscapeKeydown((event) => {
  65. if (props.trapped && !focusLayer.paused) {
  66. emit("release-requested", event);
  67. }
  68. });
  69. const focusLayer = {
  70. paused: false,
  71. pause() {
  72. this.paused = true;
  73. },
  74. resume() {
  75. this.paused = false;
  76. },
  77. };
  78. const onKeydown = (e) => {
  79. if (!props.loop && !props.trapped) return;
  80. if (focusLayer.paused) return;
  81. const { key, altKey, ctrlKey, metaKey, currentTarget, shiftKey } = e;
  82. const { loop } = props;
  83. const isTabbing =
  84. key === EVENT_CODE.tab && !altKey && !ctrlKey && !metaKey;
  85. const currentFocusingEl = document.activeElement;
  86. if (isTabbing && currentFocusingEl) {
  87. const container = currentTarget;
  88. const [first, last] = getEdges(container);
  89. const isTabbable = first && last;
  90. if (!isTabbable) {
  91. if (currentFocusingEl === container) {
  92. const focusoutPreventedEvent = createFocusOutPreventedEvent({
  93. focusReason: focusReason.value,
  94. });
  95. emit("focusout-prevented", focusoutPreventedEvent);
  96. if (!focusoutPreventedEvent.defaultPrevented) {
  97. e.preventDefault();
  98. }
  99. }
  100. } else {
  101. if (!shiftKey && currentFocusingEl === last) {
  102. const focusoutPreventedEvent = createFocusOutPreventedEvent({
  103. focusReason: focusReason.value,
  104. });
  105. emit("focusout-prevented", focusoutPreventedEvent);
  106. if (!focusoutPreventedEvent.defaultPrevented) {
  107. e.preventDefault();
  108. if (loop) tryFocus(first, true);
  109. }
  110. } else if (
  111. shiftKey &&
  112. [first, container].includes(currentFocusingEl)
  113. ) {
  114. const focusoutPreventedEvent = createFocusOutPreventedEvent({
  115. focusReason: focusReason.value,
  116. });
  117. emit("focusout-prevented", focusoutPreventedEvent);
  118. if (!focusoutPreventedEvent.defaultPrevented) {
  119. e.preventDefault();
  120. if (loop) tryFocus(last, true);
  121. }
  122. }
  123. }
  124. }
  125. };
  126. provide(FOCUS_TRAP_INJECTION_KEY, {
  127. focusTrapRef: forwardRef,
  128. onKeydown,
  129. });
  130. watch(
  131. () => props.focusTrapEl,
  132. (focusTrapEl) => {
  133. if (focusTrapEl) {
  134. forwardRef.value = focusTrapEl;
  135. }
  136. },
  137. { immediate: true }
  138. );
  139. watch([forwardRef], ([forwardRef2], [oldForwardRef]) => {
  140. if (forwardRef2) {
  141. forwardRef2.addEventListener("keydown", onKeydown);
  142. forwardRef2.addEventListener("focusin", onFocusIn);
  143. forwardRef2.addEventListener("focusout", onFocusOut);
  144. }
  145. if (oldForwardRef) {
  146. oldForwardRef.removeEventListener("keydown", onKeydown);
  147. oldForwardRef.removeEventListener("focusin", onFocusIn);
  148. oldForwardRef.removeEventListener("focusout", onFocusOut);
  149. }
  150. });
  151. const trapOnFocus = (e) => {
  152. emit(ON_TRAP_FOCUS_EVT, e);
  153. };
  154. const releaseOnFocus = (e) => emit(ON_RELEASE_FOCUS_EVT, e);
  155. const onFocusIn = (e) => {
  156. const trapContainer = unref(forwardRef);
  157. if (!trapContainer) return;
  158. const target = e.target;
  159. const relatedTarget = e.relatedTarget;
  160. const isFocusedInTrap = target && trapContainer.contains(target);
  161. if (!props.trapped) {
  162. const isPrevFocusedInTrap =
  163. relatedTarget && trapContainer.contains(relatedTarget);
  164. if (!isPrevFocusedInTrap) {
  165. lastFocusBeforeTrapped = relatedTarget;
  166. }
  167. }
  168. if (isFocusedInTrap) emit("focusin", e);
  169. if (focusLayer.paused) return;
  170. if (props.trapped) {
  171. if (isFocusedInTrap) {
  172. lastFocusAfterTrapped = target;
  173. } else {
  174. tryFocus(lastFocusAfterTrapped, true);
  175. }
  176. }
  177. };
  178. const onFocusOut = (e) => {
  179. const trapContainer = unref(forwardRef);
  180. if (focusLayer.paused || !trapContainer) return;
  181. if (props.trapped) {
  182. const relatedTarget = e.relatedTarget;
  183. if (!isNil(relatedTarget) && !trapContainer.contains(relatedTarget)) {
  184. setTimeout(() => {
  185. if (!focusLayer.paused && props.trapped) {
  186. const focusoutPreventedEvent = createFocusOutPreventedEvent({
  187. focusReason: focusReason.value,
  188. });
  189. emit("focusout-prevented", focusoutPreventedEvent);
  190. if (!focusoutPreventedEvent.defaultPrevented) {
  191. tryFocus(lastFocusAfterTrapped, true);
  192. }
  193. }
  194. }, 0);
  195. }
  196. } else {
  197. const target = e.target;
  198. const isFocusedInTrap = target && trapContainer.contains(target);
  199. if (!isFocusedInTrap) emit("focusout", e);
  200. }
  201. };
  202. async function startTrap() {
  203. await nextTick();
  204. const trapContainer = unref(forwardRef);
  205. if (trapContainer) {
  206. focusableStack.push(focusLayer);
  207. const prevFocusedElement = trapContainer.contains(
  208. document.activeElement
  209. )
  210. ? lastFocusBeforeTrapped
  211. : document.activeElement;
  212. lastFocusBeforeTrapped = prevFocusedElement;
  213. const isPrevFocusContained = trapContainer.contains(prevFocusedElement);
  214. if (!isPrevFocusContained) {
  215. const focusEvent = new Event(
  216. FOCUS_AFTER_TRAPPED,
  217. FOCUS_AFTER_TRAPPED_OPTS
  218. );
  219. trapContainer.addEventListener(FOCUS_AFTER_TRAPPED, trapOnFocus);
  220. // trapContainer.dispatchEvent(focusEvent);
  221. if (!focusEvent.defaultPrevented) {
  222. nextTick(() => {
  223. let focusStartEl = props.focusStartEl;
  224. if (!isString(focusStartEl)) {
  225. tryFocus(focusStartEl);
  226. if (document.activeElement !== focusStartEl) {
  227. focusStartEl = "first";
  228. }
  229. }
  230. if (focusStartEl === "first") {
  231. focusFirstDescendant(
  232. obtainAllFocusableElements(trapContainer),
  233. true
  234. );
  235. }
  236. if (
  237. document.activeElement === prevFocusedElement ||
  238. focusStartEl === "container"
  239. ) {
  240. tryFocus(trapContainer);
  241. }
  242. });
  243. }
  244. }
  245. }
  246. }
  247. function stopTrap() {
  248. const trapContainer = unref(forwardRef);
  249. if (trapContainer) {
  250. trapContainer.removeEventListener(FOCUS_AFTER_TRAPPED, trapOnFocus);
  251. const releasedEvent = new CustomEvent(FOCUS_AFTER_RELEASED, {
  252. ...FOCUS_AFTER_TRAPPED_OPTS,
  253. detail: {
  254. focusReason: focusReason.value,
  255. },
  256. });
  257. trapContainer.addEventListener(FOCUS_AFTER_RELEASED, releaseOnFocus);
  258. trapContainer.dispatchEvent(releasedEvent);
  259. if (
  260. !releasedEvent.defaultPrevented &&
  261. (focusReason.value == "keyboard" || !isFocusCausedByUserEvent())
  262. ) {
  263. tryFocus(
  264. lastFocusBeforeTrapped != null
  265. ? lastFocusBeforeTrapped
  266. : document.body
  267. );
  268. }
  269. trapContainer.removeEventListener(FOCUS_AFTER_RELEASED, trapOnFocus);
  270. focusableStack.remove(focusLayer);
  271. }
  272. }
  273. onMounted(() => {
  274. if (props.trapped) {
  275. startTrap();
  276. }
  277. watch(
  278. () => props.trapped,
  279. (trapped) => {
  280. if (trapped) {
  281. startTrap();
  282. } else {
  283. stopTrap();
  284. }
  285. }
  286. );
  287. });
  288. onBeforeUnmount(() => {
  289. if (props.trapped) {
  290. stopTrap();
  291. }
  292. });
  293. return {
  294. onKeydown,
  295. };
  296. },
  297. });
  298. function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  299. return renderSlot(_ctx.$slots, "default", { handleKeydown: _ctx.onKeydown });
  300. }
  301. var ElFocusTrap = /* @__PURE__ */ _export_sfc(_sfc_main, [
  302. ["render", _sfc_render],
  303. [
  304. "__file",
  305. "/home/runner/work/element-plus/element-plus/packages/components/focus-trap/src/focus-trap.vue",
  306. ],
  307. ]);
  308.  
  309. export { ElFocusTrap as default };
  310. //# sourceMappingURL=focus-trap.mjs.map