diff --git a/packages/directives/__tests__/click-outside.spec.ts b/packages/directives/__tests__/click-outside.spec.ts
index 7b9c80982d..c7247d47f3 100644
--- a/packages/directives/__tests__/click-outside.spec.ts
+++ b/packages/directives/__tests__/click-outside.spec.ts
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils'
+import { withDirectives, h } from 'vue'
import ClickOutside from '../click-outside'
const AXIOM = 'Rem is the best girl'
@@ -35,6 +36,16 @@ const _mount = () => mount(Component, {
},
})
+const triggerDocumentClickEvent = () => {
+ const mousedown = document.createEvent('MouseEvents')
+ mousedown.initMouseEvent('mousedown', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
+ document.dispatchEvent(mousedown)
+
+ const mouseup = document.createEvent('MouseEvents')
+ mouseup.initMouseEvent('mouseup', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
+ document.dispatchEvent(mouseup)
+}
+
describe('Directives.vue', () => {
beforeEach(() => {
// clear the previously assigned event object
@@ -57,13 +68,7 @@ describe('Directives.vue', () => {
await wrapper.find(`.${TRIGGER}`).trigger('mouseup')
expect(handler).toHaveBeenCalledTimes(0)
- const mousedown = document.createEvent('MouseEvents')
- mousedown.initMouseEvent('mousedown', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
- document.dispatchEvent(mousedown)
-
- const mouseup = document.createEvent('MouseEvents')
- mouseup.initMouseEvent('mouseup', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
- document.dispatchEvent(mouseup)
+ triggerDocumentClickEvent()
// here is the different part
// we test the existence of the local variable.
@@ -71,3 +76,51 @@ describe('Directives.vue', () => {
expect(mouseupObject).toBeDefined()
})
})
+
+describe('Multiple click-outside directives', () => {
+ const firstHandler = jest.fn()
+ const InnerComponent = {
+ template: `
${AXIOM}
`,
+ setup() {
+ return { handler: firstHandler }
+ },
+ }
+
+ const secondHandler = jest.fn()
+ const OuterComponent = {
+ setup() {
+ return () => {
+ const triggerNode = withDirectives(h(InnerComponent), [[ClickOutside, secondHandler]])
+ return h('div', { class: OTHER_CLASS }, triggerNode)
+ }
+ },
+ }
+
+
+ it('should support for multiple directives', async () => {
+ const wrapper = mount(OuterComponent, {
+ global: {
+ directives: {
+ ClickOutside,
+ },
+ },
+ })
+
+ // click inside trigger element
+ await wrapper.find(`.${TRIGGER}`).trigger('mousedown')
+ await wrapper.find(`.${TRIGGER}`).trigger('mouseup')
+ expect(firstHandler).toHaveBeenCalledTimes(0)
+ expect(secondHandler).toHaveBeenCalledTimes(0)
+
+ // click outside trigger element
+ triggerDocumentClickEvent()
+ expect(firstHandler).toHaveBeenCalledTimes(1)
+ expect(secondHandler).toHaveBeenCalledTimes(1)
+
+ // update the component instance
+ wrapper.vm.$forceUpdate()
+ triggerDocumentClickEvent()
+ expect(firstHandler).toHaveBeenCalledTimes(2)
+ expect(secondHandler).toHaveBeenCalledTimes(2)
+ })
+})
diff --git a/packages/directives/click-outside/index.ts b/packages/directives/click-outside/index.ts
index 9c3ec13d11..1bd293f68b 100644
--- a/packages/directives/click-outside/index.ts
+++ b/packages/directives/click-outside/index.ts
@@ -10,7 +10,7 @@ type FlushList = Map<
{
documentHandler: DocumentHandler
bindingFn: (...args: unknown[]) => unknown
- }
+ }[]
>;
const nodeList: FlushList = new Map()
@@ -20,8 +20,10 @@ let startClick: MouseEvent
if (!isServer) {
on(document, 'mousedown', (e: MouseEvent) => (startClick = e))
on(document, 'mouseup', (e: MouseEvent) => {
- for (const { documentHandler } of nodeList.values()) {
- documentHandler(e, startClick)
+ for (const handlers of nodeList.values()) {
+ for (const { documentHandler } of handlers) {
+ documentHandler(e, startClick)
+ }
}
})
}
@@ -33,7 +35,7 @@ function createDocumentHandler(
let excludes: HTMLElement[] = []
if (Array.isArray(binding.arg)) {
excludes = binding.arg
- } else {
+ } else if (binding.arg as unknown instanceof HTMLElement) {
// due to current implementation on binding type is wrong the type casting is necessary here
excludes.push(binding.arg as unknown as HTMLElement)
}
@@ -77,18 +79,37 @@ function createDocumentHandler(
const ClickOutside: ObjectDirective = {
beforeMount(el, binding) {
- nodeList.set(el, {
+ // 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, binding) {
- nodeList.set(el, {
+ 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) {
+ // remove all listeners when a component unmounted
nodeList.delete(el)
},
}