From db32b2380c3040936b8e88f6d6dae5998750ddf6 Mon Sep 17 00:00:00 2001 From: alpadev <2838324+alpadev@users.noreply.github.com> Date: Tue, 13 Apr 2021 05:25:58 +0200 Subject: [PATCH] fix: make EventHandler better handle mouseenter/mouseleave events (#33310) * fix: make EventHandler better handle mouseenter/mouseleave events * refactor: simplify custom events regex and move it to a variable --- js/src/dom/event-handler.js | 38 ++++++++---- js/tests/unit/dom/event-handler.spec.js | 78 ++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 11 deletions(-) diff --git a/js/src/dom/event-handler.js b/js/src/dom/event-handler.js index 26f6a1e3f2..8ccb887fc3 100644 --- a/js/src/dom/event-handler.js +++ b/js/src/dom/event-handler.js @@ -22,6 +22,7 @@ const customEvents = { mouseenter: 'mouseover', mouseleave: 'mouseout' } +const customEventsRegex = /^(mouseenter|mouseleave)/i const nativeEvents = new Set([ 'click', 'dblclick', @@ -113,7 +114,7 @@ function bootstrapDelegationHandler(element, selector, fn) { if (handler.oneOff) { // eslint-disable-next-line unicorn/consistent-destructuring - EventHandler.off(element, event.type, fn) + EventHandler.off(element, event.type, selector, fn) } return fn.apply(target, [event]) @@ -144,14 +145,7 @@ function normalizeParams(originalTypeEvent, handler, delegationFn) { const delegation = typeof handler === 'string' const originalHandler = delegation ? delegationFn : handler - // allow to get the native events from namespaced events ('click.bs.button' --> 'click') - let typeEvent = originalTypeEvent.replace(stripNameRegex, '') - const custom = customEvents[typeEvent] - - if (custom) { - typeEvent = custom - } - + let typeEvent = getTypeEvent(originalTypeEvent) const isNative = nativeEvents.has(typeEvent) if (!isNative) { @@ -171,6 +165,24 @@ function addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) { delegationFn = null } + // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position + // this prevents the handler from being dispatched the same way as mouseover or mouseout does + if (customEventsRegex.test(originalTypeEvent)) { + const wrapFn = fn => { + return function (event) { + if (!event.relatedTarget || (event.relatedTarget !== event.delegateTarget && event.relatedTarget.contains(event.delegateTarget))) { + return fn.call(this, event) + } + } + } + + if (delegationFn) { + delegationFn = wrapFn(delegationFn) + } else { + handler = wrapFn(handler) + } + } + const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn) const events = getEvent(element) const handlers = events[typeEvent] || (events[typeEvent] = {}) @@ -219,6 +231,12 @@ function removeNamespacedHandlers(element, events, typeEvent, namespace) { }) } +function getTypeEvent(event) { + // allow to get the native events from namespaced events ('click.bs.button' --> 'click') + event = event.replace(stripNameRegex, '') + return customEvents[event] || event +} + const EventHandler = { on(element, event, handler, delegationFn) { addHandler(element, event, handler, delegationFn, false) @@ -272,7 +290,7 @@ const EventHandler = { } const $ = getjQuery() - const typeEvent = event.replace(stripNameRegex, '') + const typeEvent = getTypeEvent(event) const inNamespace = event !== typeEvent const isNative = nativeEvents.has(typeEvent) diff --git a/js/tests/unit/dom/event-handler.spec.js b/js/tests/unit/dom/event-handler.spec.js index e596a49b59..5fb1f01956 100644 --- a/js/tests/unit/dom/event-handler.spec.js +++ b/js/tests/unit/dom/event-handler.spec.js @@ -77,10 +77,64 @@ describe('EventHandler', () => { div.click() }) + + it('should handle mouseenter/mouseleave like the native counterpart', done => { + fixtureEl.innerHTML = [ + '