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); + }; + } +});