From 5676c4bc821aa0aa7ffaddd82c90807a96b8e1fa Mon Sep 17 00:00:00 2001 From: simonihmig Date: Sat, 24 Nov 2018 02:21:43 +0100 Subject: [PATCH] [BUGFIX beta] fix mouseEnter/Leave event delegation w/o jQuery for SVG elements in IE11 IE11 does not support `Node#contains()` on SVG elements, so in that case a custom fallback implementation is used Fixes #17225 --- .../integration/event-dispatcher-test.js | 56 +++++++++++++++++++ .../views/lib/system/event_dispatcher.js | 5 +- .../-internals/views/lib/system/utils.js | 9 +++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/packages/@ember/-internals/glimmer/tests/integration/event-dispatcher-test.js b/packages/@ember/-internals/glimmer/tests/integration/event-dispatcher-test.js index 999b4ed5c50..2bc15bebda5 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/event-dispatcher-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/event-dispatcher-test.js @@ -202,6 +202,62 @@ moduleFor( assert.strictEqual(receivedLeaveEvents[0].target, outer); } + ['@test delegated event listeners work for mouseEnter on SVG elements'](assert) { + let receivedEnterEvents = []; + let receivedLeaveEvents = []; + + this.registerComponent('x-foo', { + ComponentClass: Component.extend({ + tagName: 'svg', + mouseEnter(event) { + receivedEnterEvents.push(event); + }, + mouseLeave(event) { + receivedLeaveEvents.push(event); + }, + }), + template: ``, + }); + + this.render(`{{x-foo id="outer"}}`); + + let parent = this.element; + let outer = this.$('#outer')[0]; + let inner = this.$('#inner')[0]; + + // mouse moves over #outer + this.runTask(() => { + this.$(outer).trigger('mouseenter', { canBubble: false, relatedTarget: parent }); + this.$(outer).trigger('mouseover', { relatedTarget: parent }); + this.$(parent).trigger('mouseout', { relatedTarget: outer }); + }); + assert.equal(receivedEnterEvents.length, 1, 'mouseenter event was triggered'); + assert.strictEqual(receivedEnterEvents[0].target, outer); + + // mouse moves over #inner + this.runTask(() => { + this.$(inner).trigger('mouseover', { relatedTarget: outer }); + this.$(outer).trigger('mouseout', { relatedTarget: inner }); + }); + assert.equal(receivedEnterEvents.length, 1, 'mouseenter event was not triggered again'); + + // mouse moves out of #inner + this.runTask(() => { + this.$(inner).trigger('mouseout', { relatedTarget: outer }); + this.$(outer).trigger('mouseover', { relatedTarget: inner }); + }); + assert.equal(receivedLeaveEvents.length, 0, 'mouseleave event was not triggered'); + + // mouse moves out of #outer + this.runTask(() => { + this.$(outer).trigger('mouseleave', { canBubble: false, relatedTarget: parent }); + this.$(outer).trigger('mouseout', { relatedTarget: parent }); + this.$(parent).trigger('mouseover', { relatedTarget: outer }); + }); + assert.equal(receivedLeaveEvents.length, 1, 'mouseleave event was triggered'); + assert.strictEqual(receivedLeaveEvents[0].target, outer); + } + ['@test delegated event listeners work for mouseEnter/Leave with skipped events'](assert) { let receivedEnterEvents = []; let receivedLeaveEvents = []; diff --git a/packages/@ember/-internals/views/lib/system/event_dispatcher.js b/packages/@ember/-internals/views/lib/system/event_dispatcher.js index e2bcac3feef..52cd7bf1fc7 100644 --- a/packages/@ember/-internals/views/lib/system/event_dispatcher.js +++ b/packages/@ember/-internals/views/lib/system/event_dispatcher.js @@ -7,6 +7,7 @@ import jQuery, { jQueryDisabled } from './jquery'; import ActionManager from './action_manager'; import fallbackViewRegistry from '../compat/fallback-view-registry'; import addJQueryEventDeprecation from './jquery_event_deprecation'; +import { contains } from './utils'; /** @module ember @@ -335,7 +336,9 @@ export default EmberObject.extend({ while ( target && target.nodeType === 1 && - (!related || (related !== target && !target.contains(related))) + (!related || + (related !== target && + !(target.contains ? target.contains(related) : contains(target, related)))) ) { // mouseEnter/Leave don't bubble, so there is no logic to prevent it as with other events if (viewRegistry[target.id]) { diff --git a/packages/@ember/-internals/views/lib/system/utils.js b/packages/@ember/-internals/views/lib/system/utils.js index 55cedbd579b..9128da5a310 100644 --- a/packages/@ember/-internals/views/lib/system/utils.js +++ b/packages/@ember/-internals/views/lib/system/utils.js @@ -199,3 +199,12 @@ export const elMatches = export function matches(el, selector) { return elMatches.call(el, selector); } + +export function contains(a, b) { + while ((b = b.parentNode)) { + if (b === a) { + return true; + } + } + return false; +}