123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 |
- import { isClient } from '@vueuse/core'
- import { isElement } from '@element-plus/utils'
- import type {
- ComponentPublicInstance,
- DirectiveBinding,
- ObjectDirective,
- } from 'vue'
- type DocumentHandler = <T extends MouseEvent>(mouseup: T, mousedown: T) => void
- type FlushList = Map<
- HTMLElement,
- {
- documentHandler: DocumentHandler
- bindingFn: (...args: unknown[]) => unknown
- }[]
- >
- const nodeList: FlushList = new Map()
- let startClick: MouseEvent
- if (isClient) {
- document.addEventListener('mousedown', (e: MouseEvent) => (startClick = e))
- document.addEventListener('mouseup', (e: MouseEvent) => {
- for (const handlers of nodeList.values()) {
- for (const { documentHandler } of handlers) {
- documentHandler(e as MouseEvent, startClick)
- }
- }
- })
- }
- function createDocumentHandler(
- el: HTMLElement,
- binding: DirectiveBinding
- ): DocumentHandler {
- let excludes: HTMLElement[] = []
- if (Array.isArray(binding.arg)) {
- excludes = binding.arg
- } else if (isElement(binding.arg)) {
- // due to current implementation on binding type is wrong the type casting is necessary here
- excludes.push(binding.arg as unknown as HTMLElement)
- }
- return function (mouseup, mousedown) {
- const popperRef = (
- binding.instance as ComponentPublicInstance<{
- popperRef: HTMLElement
- }>
- ).popperRef
- const mouseUpTarget = mouseup.target as Node
- const mouseDownTarget = mousedown?.target as Node
- const isBound = !binding || !binding.instance
- const isTargetExists = !mouseUpTarget || !mouseDownTarget
- const isContainedByEl =
- el.contains(mouseUpTarget) || el.contains(mouseDownTarget)
- const isSelf = el === mouseUpTarget
- const isTargetExcluded =
- (excludes.length &&
- excludes.some((item) => item?.contains(mouseUpTarget))) ||
- (excludes.length && excludes.includes(mouseDownTarget as HTMLElement))
- const isContainedByPopper =
- popperRef &&
- (popperRef.contains(mouseUpTarget) || popperRef.contains(mouseDownTarget))
- if (
- isBound ||
- isTargetExists ||
- isContainedByEl ||
- isSelf ||
- isTargetExcluded ||
- isContainedByPopper
- ) {
- return
- }
- binding.value(mouseup, mousedown)
- }
- }
- const ClickOutside: ObjectDirective = {
- beforeMount(el: HTMLElement, binding: DirectiveBinding) {
- // there could be multiple handlers on the element
- if (!nodeList.has(el)) {
- nodeList.set(el, [])
- }
- nodeList.get(el)!.push({
- documentHandler: createDocumentHandler(el, binding),
- bindingFn: binding.value,
- })
- },
- updated(el: HTMLElement, binding: DirectiveBinding) {
- if (!nodeList.has(el)) {
- nodeList.set(el, [])
- }
- const handlers = nodeList.get(el)!
- const oldHandlerIndex = handlers.findIndex(
- (item) => item.bindingFn === binding.oldValue
- )
- const newHandler = {
- documentHandler: createDocumentHandler(el, binding),
- bindingFn: binding.value,
- }
- if (oldHandlerIndex >= 0) {
- // replace the old handler to the new handler
- handlers.splice(oldHandlerIndex, 1, newHandler)
- } else {
- handlers.push(newHandler)
- }
- },
- unmounted(el: HTMLElement) {
- // remove all listeners when a component unmounted
- nodeList.delete(el)
- },
- }
- export default ClickOutside
|