Skip to content

Commit 48b616f

Browse files
committed
fix(jqLite): change implementation of mouseenter/mouseleave event
Implement mouseenter/mouseleave event refering to http://www.quirksmode.org/js/events_mouse.html#link8 and jQuery source code(not dependent on jQuery). The old implementation is wrong. When moving mouse from a parent element into a child element, it would trigger mouseleave event, which should not. And the old test about mouseenter/mouseleave is wrong too. It just triggers mouseover and mouseout events, cannot describe the process of mouse moving from one element to another element, which is important for mouseenter/mouseleave. Closes angular#2131, angular#1811
1 parent a491ea3 commit 48b616f

File tree

2 files changed

+63
-22
lines changed

2 files changed

+63
-22
lines changed

src/jqLite.js

+34-14
Original file line numberDiff line numberDiff line change
@@ -607,23 +607,43 @@ forEach({
607607

608608
if (!eventFns) {
609609
if (type == 'mouseenter' || type == 'mouseleave') {
610-
var counter = 0;
610+
var contains = document.body.contains || document.body.compareDocumentPosition ?
611+
function( a, b ) {
612+
var adown = a.nodeType === 9 ? a.documentElement : a,
613+
bup = b && b.parentNode;
614+
return a === bup || !!( bup && bup.nodeType === 1 && (
615+
adown.contains ?
616+
adown.contains( bup ) :
617+
a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
618+
));
619+
} :
620+
function( a, b ) {
621+
if ( b ) {
622+
while ( (b = b.parentNode) ) {
623+
if ( b === a ) {
624+
return true;
625+
}
626+
}
627+
}
628+
return false;
629+
};
611630

612-
events.mouseenter = [];
613-
events.mouseleave = [];
631+
events[type] = [];
632+
633+
// Refer to jQuery's implementation of mouseenter & mouseleave
634+
// Read about mouseenter and mouseleave:
635+
// http://www.quirksmode.org/js/events_mouse.html#link8
636+
var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}
637+
bindFn(element, eventmap[type], function(event) {
638+
var ret, target = this, related = event.relatedTarget;
639+
// For mousenter/leave call the handler if related is outside the target.
640+
// NB: No relatedTarget if the mouse left/entered the browser window
641+
if ( !related || (related !== target && !contains(target, related)) ){
642+
handle(event, type);
643+
}
614644

615-
bindFn(element, 'mouseover', function(event) {
616-
counter++;
617-
if (counter == 1) {
618-
handle(event, 'mouseenter');
619-
}
620-
});
621-
bindFn(element, 'mouseout', function(event) {
622-
counter --;
623-
if (counter == 0) {
624-
handle(event, 'mouseleave');
625-
}
626645
});
646+
627647
} else {
628648
addEventListenerFn(element, type, handle);
629649
events[type] = [];

test/jqLiteSpec.js

+29-8
Original file line numberDiff line numberDiff line change
@@ -790,20 +790,41 @@ describe('jqLite', function() {
790790

791791
it('should fire mouseenter when coming from outside the browser window', function() {
792792
if (window.jQuery) return;
793-
parent.mouseover();
793+
var browserMoveTrigger = function(from, to){
794+
var fireEvent = function(type, element, relatedTarget){
795+
var evnt = document.createEvent('MouseEvents'),
796+
originalPreventDefault = evnt.preventDefault,
797+
appWindow = window,
798+
fakeProcessDefault = true,
799+
finalProcessDefault;
800+
801+
evnt.preventDefault = function() {
802+
fakeProcessDefault = false;
803+
return originalPreventDefault.apply(evnt, arguments);
804+
};
805+
806+
var x = 0, y = 0;
807+
evnt.initMouseEvent(type, true, true, window, 0, x, y, x, y, false, false,
808+
false, false, 0, relatedTarget);
809+
810+
element.dispatchEvent(evnt);
811+
};
812+
fireEvent('mouseout', from[0], to[0]);
813+
fireEvent('mouseover', to[0], from[0]);
814+
815+
};
816+
browserMoveTrigger(root, parent);
794817
expect(log).toEqual('parentEnter;');
795818

796-
child.mouseover();
797-
expect(log).toEqual('parentEnter;childEnter;');
798-
child.mouseover();
819+
browserMoveTrigger(parent, child);
799820
expect(log).toEqual('parentEnter;childEnter;');
800821

801-
child.mouseout();
802-
expect(log).toEqual('parentEnter;childEnter;');
803-
child.mouseout();
822+
browserMoveTrigger(child, parent);
804823
expect(log).toEqual('parentEnter;childEnter;childLeave;');
805-
parent.mouseout();
824+
825+
browserMoveTrigger(parent, root);
806826
expect(log).toEqual('parentEnter;childEnter;childLeave;parentLeave;');
827+
807828
});
808829
});
809830
});

0 commit comments

Comments
 (0)