vben
2021-08-24 56a966cfbf8db5b29a42185f0f25a0e800c30dbb
提交 | 用户 | age
e6db0d 1 import { on } from '/@/utils/domUtils';
V 2 import { isServer } from '/@/utils/is';
3 import type { ComponentPublicInstance, DirectiveBinding, ObjectDirective } from 'vue';
4
5 type DocumentHandler = <T extends MouseEvent>(mouseup: T, mousedown: T) => void;
6
7 type FlushList = Map<
8   HTMLElement,
9   {
10     documentHandler: DocumentHandler;
11     bindingFn: (...args: unknown[]) => unknown;
12   }
13 >;
14
15 const nodeList: FlushList = new Map();
16
17 let startClick: MouseEvent;
18
19 if (!isServer) {
20   on(document, 'mousedown', (e: MouseEvent) => (startClick = e));
21   on(document, 'mouseup', (e: MouseEvent) => {
22     for (const { documentHandler } of nodeList.values()) {
23       documentHandler(e, startClick);
24     }
25   });
26 }
27
28 function createDocumentHandler(el: HTMLElement, binding: DirectiveBinding): DocumentHandler {
29   let excludes: HTMLElement[] = [];
30   if (Array.isArray(binding.arg)) {
31     excludes = binding.arg;
32   } else {
33     // due to current implementation on binding type is wrong the type casting is necessary here
00fca0 34     excludes.push(binding.arg as unknown as HTMLElement);
e6db0d 35   }
V 36   return function (mouseup, mousedown) {
00fca0 37     const popperRef = (
V 38       binding.instance as ComponentPublicInstance<{
39         popperRef: Nullable<HTMLElement>;
40       }>
41     ).popperRef;
e6db0d 42     const mouseUpTarget = mouseup.target as Node;
V 43     const mouseDownTarget = mousedown.target as Node;
44     const isBound = !binding || !binding.instance;
45     const isTargetExists = !mouseUpTarget || !mouseDownTarget;
46     const isContainedByEl = el.contains(mouseUpTarget) || el.contains(mouseDownTarget);
47     const isSelf = el === mouseUpTarget;
48
49     const isTargetExcluded =
50       (excludes.length && excludes.some((item) => item?.contains(mouseUpTarget))) ||
51       (excludes.length && excludes.includes(mouseDownTarget as HTMLElement));
52     const isContainedByPopper =
53       popperRef && (popperRef.contains(mouseUpTarget) || popperRef.contains(mouseDownTarget));
54     if (
55       isBound ||
56       isTargetExists ||
57       isContainedByEl ||
58       isSelf ||
59       isTargetExcluded ||
60       isContainedByPopper
61     ) {
62       return;
63     }
64     binding.value();
65   };
66 }
67
68 const ClickOutside: ObjectDirective = {
69   beforeMount(el, binding) {
70     nodeList.set(el, {
71       documentHandler: createDocumentHandler(el, binding),
72       bindingFn: binding.value,
73     });
74   },
75   updated(el, binding) {
76     nodeList.set(el, {
77       documentHandler: createDocumentHandler(el, binding),
78       bindingFn: binding.value,
79     });
80   },
81   unmounted(el) {
82     nodeList.delete(el);
83   },
84 };
85
86 export default ClickOutside;