mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-17 07:40:22 +00:00
fix(jqLite): correct implementation of mouseenter/mouseleave event
Implement mouseenter/mouseleave event referring 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 #2131, #1811
This commit is contained in:
parent
c3235db9ee
commit
b0233a33a1
2 changed files with 71 additions and 26 deletions
|
|
@ -602,23 +602,43 @@ forEach({
|
|||
|
||||
if (!eventFns) {
|
||||
if (type == 'mouseenter' || type == 'mouseleave') {
|
||||
var counter = 0;
|
||||
var contains = document.body.contains || document.body.compareDocumentPosition ?
|
||||
function( a, b ) {
|
||||
var adown = a.nodeType === 9 ? a.documentElement : a,
|
||||
bup = b && b.parentNode;
|
||||
return a === bup || !!( bup && bup.nodeType === 1 && (
|
||||
adown.contains ?
|
||||
adown.contains( bup ) :
|
||||
a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
|
||||
));
|
||||
} :
|
||||
function( a, b ) {
|
||||
if ( b ) {
|
||||
while ( (b = b.parentNode) ) {
|
||||
if ( b === a ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
events.mouseenter = [];
|
||||
events.mouseleave = [];
|
||||
events[type] = [];
|
||||
|
||||
// Refer to jQuery's implementation of mouseenter & mouseleave
|
||||
// Read about mouseenter and mouseleave:
|
||||
// http://www.quirksmode.org/js/events_mouse.html#link8
|
||||
var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}
|
||||
bindFn(element, eventmap[type], function(event) {
|
||||
var ret, target = this, related = event.relatedTarget;
|
||||
// For mousenter/leave call the handler if related is outside the target.
|
||||
// NB: No relatedTarget if the mouse left/entered the browser window
|
||||
if ( !related || (related !== target && !contains(target, related)) ){
|
||||
handle(event, type);
|
||||
}
|
||||
|
||||
bindFn(element, 'mouseover', function(event) {
|
||||
counter++;
|
||||
if (counter == 1) {
|
||||
handle(event, 'mouseenter');
|
||||
}
|
||||
});
|
||||
bindFn(element, 'mouseout', function(event) {
|
||||
counter --;
|
||||
if (counter == 0) {
|
||||
handle(event, 'mouseleave');
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
addEventListenerFn(element, type, handle);
|
||||
events[type] = [];
|
||||
|
|
|
|||
|
|
@ -775,13 +775,9 @@ describe('jqLite', function() {
|
|||
|
||||
parent.bind('mouseenter', function() { log += 'parentEnter;'; });
|
||||
parent.bind('mouseleave', function() { log += 'parentLeave;'; });
|
||||
parent.mouseover = function() { browserTrigger(parent, 'mouseover'); };
|
||||
parent.mouseout = function() { browserTrigger(parent, 'mouseout'); };
|
||||
|
||||
child.bind('mouseenter', function() { log += 'childEnter;'; });
|
||||
child.bind('mouseleave', function() { log += 'childLeave;'; });
|
||||
child.mouseover = function() { browserTrigger(child, 'mouseover'); };
|
||||
child.mouseout = function() { browserTrigger(child, 'mouseout'); };
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
|
|
@ -790,20 +786,49 @@ describe('jqLite', function() {
|
|||
|
||||
it('should fire mouseenter when coming from outside the browser window', function() {
|
||||
if (window.jQuery) return;
|
||||
parent.mouseover();
|
||||
var browserMoveTrigger = function(from, to){
|
||||
var fireEvent = function(type, element, relatedTarget){
|
||||
var msie = parseInt((/msie (\d+)/.exec(navigator.userAgent.toLowerCase()) || [])[1]);
|
||||
if (msie < 9){
|
||||
var evnt = document.createEventObject();
|
||||
evnt.srcElement = element;
|
||||
evnt.relatedTarget = relatedTarget;
|
||||
element.fireEvent('on' + type, evnt);
|
||||
return;
|
||||
};
|
||||
var evnt = document.createEvent('MouseEvents'),
|
||||
originalPreventDefault = evnt.preventDefault,
|
||||
appWindow = window,
|
||||
fakeProcessDefault = true,
|
||||
finalProcessDefault;
|
||||
|
||||
evnt.preventDefault = function() {
|
||||
fakeProcessDefault = false;
|
||||
return originalPreventDefault.apply(evnt, arguments);
|
||||
};
|
||||
|
||||
var x = 0, y = 0;
|
||||
evnt.initMouseEvent(type, true, true, window, 0, x, y, x, y, false, false,
|
||||
false, false, 0, relatedTarget);
|
||||
|
||||
element.dispatchEvent(evnt);
|
||||
};
|
||||
fireEvent('mouseout', from[0], to[0]);
|
||||
fireEvent('mouseover', to[0], from[0]);
|
||||
};
|
||||
|
||||
browserMoveTrigger(root, parent);
|
||||
expect(log).toEqual('parentEnter;');
|
||||
|
||||
child.mouseover();
|
||||
expect(log).toEqual('parentEnter;childEnter;');
|
||||
child.mouseover();
|
||||
browserMoveTrigger(parent, child);
|
||||
expect(log).toEqual('parentEnter;childEnter;');
|
||||
|
||||
child.mouseout();
|
||||
expect(log).toEqual('parentEnter;childEnter;');
|
||||
child.mouseout();
|
||||
browserMoveTrigger(child, parent);
|
||||
expect(log).toEqual('parentEnter;childEnter;childLeave;');
|
||||
parent.mouseout();
|
||||
|
||||
browserMoveTrigger(parent, root);
|
||||
expect(log).toEqual('parentEnter;childEnter;childLeave;parentLeave;');
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue