Vben
2021-03-16 3732016062ca586c7d7ad7aa5e479c423be77871
提交 | 用户 | 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
34     excludes.push((binding.arg as unknown) as HTMLElement);
35   }
36   return function (mouseup, mousedown) {
37     const popperRef = (binding.instance as ComponentPublicInstance<{
38       popperRef: Nullable<HTMLElement>;
39     }>).popperRef;
40     const mouseUpTarget = mouseup.target as Node;
41     const mouseDownTarget = mousedown.target as Node;
42     const isBound = !binding || !binding.instance;
43     const isTargetExists = !mouseUpTarget || !mouseDownTarget;
44     const isContainedByEl = el.contains(mouseUpTarget) || el.contains(mouseDownTarget);
45     const isSelf = el === mouseUpTarget;
46
47     const isTargetExcluded =
48       (excludes.length && excludes.some((item) => item?.contains(mouseUpTarget))) ||
49       (excludes.length && excludes.includes(mouseDownTarget as HTMLElement));
50     const isContainedByPopper =
51       popperRef && (popperRef.contains(mouseUpTarget) || popperRef.contains(mouseDownTarget));
52     if (
53       isBound ||
54       isTargetExists ||
55       isContainedByEl ||
56       isSelf ||
57       isTargetExcluded ||
58       isContainedByPopper
59     ) {
60       return;
61     }
62     binding.value();
63   };
64 }
65
66 const ClickOutside: ObjectDirective = {
67   beforeMount(el, binding) {
68     nodeList.set(el, {
69       documentHandler: createDocumentHandler(el, binding),
70       bindingFn: binding.value,
71     });
72   },
73   updated(el, binding) {
74     nodeList.set(el, {
75       documentHandler: createDocumentHandler(el, binding),
76       bindingFn: binding.value,
77     });
78   },
79   unmounted(el) {
80     nodeList.delete(el);
81   },
82 };
83
84 export default ClickOutside;