Sanakey
2024-08-10 28c4845d6fee084d30767a3fec831588d13c88c1
提交 | 用户 | age
4d2fb0 1 import { on } from '@/utils/domUtils';
X 2 import { isServer } from '@/utils/is';
e6db0d 3 import type { ComponentPublicInstance, DirectiveBinding, ObjectDirective } from 'vue';
V 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) {
1e9570 20   on(document, 'mousedown', (e: Event) => (startClick = e as MouseEvent));
IW 21   on(document, 'mouseup', (e: Event) => {
e6db0d 22     for (const { documentHandler } of nodeList.values()) {
1e9570 23       documentHandler(e as MouseEvent, startClick);
e6db0d 24     }
V 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;