diff --git a/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.js b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.js
index 3cf72c823..e6e08d22f 100644
--- a/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.js
+++ b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.js
@@ -24,11 +24,12 @@ if(typeof modules === 'object' && modules.isDefined('jquery')) {
var doc = document,
USE_NATIVE_MAP = window.Map && window.Map.prototype.forEach,
HAS_BITMAP_TYPE = window.MSPointerEvent && typeof window.MSPointerEvent.MSPOINTER_TYPE_MOUSE === 'number',
- POINTERS_FN = function() { return this.size };
+ POINTERS_FN = function() { return this.size },
+ jqEvent = $.event;
// NOTE: Remove jQuery special fixes for pointerevents – we fix them ourself
-delete $.event.special.pointerenter;
-delete $.event.special.pointerleave;
+delete jqEvent.special.pointerenter;
+delete jqEvent.special.pointerleave;
/*!
* Returns a snapshot of inEvent, with writable properties.
@@ -47,6 +48,15 @@ function cloneEvent(event) {
return eventCopy;
}
+/*!
+ * Dispatches the event to the target, taking event's bubbling into account.
+ */
+function dispatchEvent(event, target) {
+ return event.bubbles?
+ jqEvent.trigger(event, null, target) :
+ jqEvent.dispatch.call(target, event);
+}
+
var MOUSE_PROPS = {
bubbles : false,
cancelable : false,
@@ -258,15 +268,25 @@ var dispatcher = {
leaveOut : function(event) {
this.out(event);
- if(!this.contains(event.target, event.relatedTarget)) {
- this.leave(event);
- }
+ this.enterLeave(event, this.leave);
},
enterOver : function(event) {
this.over(event);
- if(!this.contains(event.target, event.relatedTarget)) {
- this.enter(event);
+ this.enterLeave(event, this.enter);
+ },
+
+ enterLeave : function(event, fn) {
+ var target = event.target,
+ relatedTarget = event.relatedTarget;
+
+ if(!this.contains(target, relatedTarget)) {
+ while(target && target !== relatedTarget) {
+ event.target = target;
+ fn.call(this, event);
+
+ target = target.parentNode;
+ }
}
},
@@ -339,7 +359,11 @@ var dispatcher = {
dispatchEvent : function(event) {
var target = this.getTarget(event);
if(target) {
- return $(target).trigger(event);
+ if(!event.target) {
+ event.target = target;
+ }
+
+ return dispatchEvent(event, target);
}
},
diff --git a/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.spec.js b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.spec.js
index 5b583248d..8889935ce 100644
--- a/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.spec.js
+++ b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.spec.js
@@ -137,6 +137,56 @@ describe('jquery__event_type_pointer', function() {
spy.should.have.been.calledOnce;
});
});
+
+ it('should not bubble pointerenter / pointerleave events from inner elements (#801)', function() {
+ var innerElem = $('
').appendTo(elem),
+ enterSpy = sinon.spy(),
+ leaveSpy = sinon.spy();
+
+ elem
+ .on('pointerenter', enterSpy)
+ .on('pointerleave', leaveSpy)
+ .on('mouseenter', function() {
+ innerElem
+ .trigger($.Event('mouseenter', { relatedTarget : this })) // simulate mouseenter from elem to innerElem
+ .trigger($.Event('mouseleave', { relatedTarget : this })); // simulate mouseleave from innerElem to elem
+ })
+ .mouseenter();
+
+ enterSpy.should.have.been.calledOnce; // pointerenter shouldn't bubble from innerElem to elem
+ leaveSpy.should.not.have.been.called; // pointerleave shouldn't bubble from innerElem to elem
+ });
+
+ it('should trigger pointerenter / pointerleave on the subtree from event\'s relatedTarget to target', function() {
+ var elemSubtree = $('').appendTo(elem),
+ innerElem1 = elemSubtree.eq(0),
+ innerElem2 = innerElem1.find('div:eq(0)'),
+ enterEvent = $.Event('mouseenter', {
+ relatedTarget : elem
+ }),
+ leaveEvent = $.Event('mouseleave', {
+ relatedTarget : elem
+ }),
+ enterSpy = sinon.spy(),
+ leaveSpy = sinon.spy(),
+ enterArgs, leaveArgs;
+
+ innerElem1
+ .on('pointerenter', enterSpy)
+ .on('pointerleave', leaveSpy);
+
+ innerElem2
+ .trigger(enterEvent)
+ .trigger(leaveEvent);
+
+ enterSpy.should.have.been.calledOnce;
+ enterArgs = enterSpy.args[0][0];
+ enterArgs.target.should.be.equal(innerElem1[0], 'pointerenter triggered with wrong target');
+
+ leaveSpy.should.have.been.calledOnce;
+ leaveArgs = leaveSpy.args[0][0];
+ leaveArgs.target.should.be.equal(innerElem1[0], 'pointerleave triggered with wrong target');
+ });
});
provide();
diff --git a/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/.bem/level.js b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/.bem/level.js
new file mode 100644
index 000000000..cc6051064
--- /dev/null
+++ b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/.bem/level.js
@@ -0,0 +1 @@
+exports.baseLevelPath = require.resolve('../../../../../../.bem/levels/tests.js');
diff --git a/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.bemjson.js b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.bemjson.js
new file mode 100644
index 000000000..1e00952aa
--- /dev/null
+++ b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.bemjson.js
@@ -0,0 +1,9 @@
+({
+ block : 'page',
+ title : 'jquery pointer event',
+ head : [
+ { elem : 'css', url : '_simple.css' },
+ { elem : 'js', url : '_simple.js' }
+ ],
+ content : { block : 'example' }
+})
diff --git a/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/.bem/level.js b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/.bem/level.js
new file mode 100644
index 000000000..efd9269e6
--- /dev/null
+++ b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/.bem/level.js
@@ -0,0 +1 @@
+exports.baseLevelPath = require.resolve('../../../../../../../.bem/levels/blocks.js');
diff --git a/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/example/example.bemhtml b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/example/example.bemhtml
new file mode 100644
index 000000000..9d9d98f1e
--- /dev/null
+++ b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/example/example.bemhtml
@@ -0,0 +1,12 @@
+block('example').content()({
+ block : 'test-pointer',
+ content : {
+ elem : 'inner1',
+ content : {
+ elem : 'inner2',
+ content: {
+ elem: 'inner3'
+ }
+ }
+ }
+});
diff --git a/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/example/example.deps.js b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/example/example.deps.js
new file mode 100644
index 000000000..6e94321d6
--- /dev/null
+++ b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/example/example.deps.js
@@ -0,0 +1,6 @@
+({
+ shouldDeps : [
+ { block : 'jquery', elem : 'event', mods : { type : 'pointernative' } },
+ 'test-pointer'
+ ]
+})
diff --git a/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/page/page.css b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/page/page.css
new file mode 100644
index 000000000..2d1b4444f
--- /dev/null
+++ b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/page/page.css
@@ -0,0 +1,5 @@
+.page
+{
+ margin: 0;
+ padding: 0;
+}
diff --git a/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/test-pointer/__label/test-pointer__label.css b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/test-pointer/__label/test-pointer__label.css
new file mode 100644
index 000000000..a4ba3fa7d
--- /dev/null
+++ b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/test-pointer/__label/test-pointer__label.css
@@ -0,0 +1,9 @@
+.test-pointer__label
+{
+ color: #fff;
+ font-size: 11px;
+
+ position: absolute;
+
+ margin: 2px 0 0 2px;
+}
diff --git a/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/test-pointer/test-pointer.bemhtml b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/test-pointer/test-pointer.bemhtml
new file mode 100644
index 000000000..e03d6f443
--- /dev/null
+++ b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/test-pointer/test-pointer.bemhtml
@@ -0,0 +1,14 @@
+block('test-pointer')(
+ content()(function() {
+ return [
+ { elem : 'label', content : 'root' },
+ this.ctx.content
+ ];
+ }),
+ elemMatch(function() { return this.elem.indexOf('inner') === 0; }).content()(function() {
+ return [
+ { elem : 'label', content : this.elem.slice(-1) },
+ this.ctx.content
+ ];
+ })
+);
diff --git a/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/test-pointer/test-pointer.css b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/test-pointer/test-pointer.css
new file mode 100644
index 000000000..29236e1ef
--- /dev/null
+++ b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/test-pointer/test-pointer.css
@@ -0,0 +1,43 @@
+.test-pointer
+{
+ background: rgba(255, 0, 0, .7);
+
+ width: 400px;
+ height: 200px;
+}
+
+.test-pointer__inner1
+{
+ background: rgba(0, 255, 0, .7);
+
+ position: relative;
+ top: 50px;
+ left: 200px;
+
+ width: 250px;
+ height: 150px;
+}
+
+.test-pointer__inner2
+{
+ background: rgba(0, 0, 255, .7);
+
+ position: relative;
+ top: 35px;
+ left: -45px;
+
+ width: 150px;
+ height: 150px;
+}
+
+.test-pointer__inner3
+{
+ background: rgba(0, 255, 255, .7);
+
+ position: relative;
+ top: 45px;
+ left: -45px;
+
+ width: 250px;
+ height: 70px;
+}
diff --git a/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/test-pointer/test-pointer.deps.js b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/test-pointer/test-pointer.deps.js
new file mode 100644
index 000000000..11c9159bc
--- /dev/null
+++ b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/test-pointer/test-pointer.deps.js
@@ -0,0 +1,6 @@
+({
+ mustDeps : { block : 'i-bem', elems : ['dom'] },
+ shouldDeps : {
+ elem : 'label'
+ }
+})
diff --git a/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/test-pointer/test-pointer.js b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/test-pointer/test-pointer.js
new file mode 100644
index 000000000..b1851d282
--- /dev/null
+++ b/common.blocks/jquery/__event/_type/jquery__event_type_pointernative.tests/simple.blocks/test-pointer/test-pointer.js
@@ -0,0 +1,17 @@
+modules.require(['jquery'], function($) {
+ /* jshint devel:true */
+ var domElem = $('.test-pointer')
+ .on('pointerleave', makeHandler('P:'))
+ .on('mouseleave', makeHandler('$:'))
+ .on('pointerenter', makeHandler('P:'))
+ .on('mouseenter', makeHandler('$:'));
+
+ domElem.get(0).addEventListener('mouseenter', makeHandler('native:'));
+ domElem.get(0).addEventListener('mouseleave', makeHandler('native:'));
+
+ function makeHandler(name) {
+ return function handler(e) {
+ console.log(name, e.type, e.target);
+ };
+ }
+});