diff --git a/src/ngAnimate/animateCss.js b/src/ngAnimate/animateCss.js index b61b8aa28ef6..b64cda7e0afe 100644 --- a/src/ngAnimate/animateCss.js +++ b/src/ngAnimate/animateCss.js @@ -805,6 +805,12 @@ var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animatePro event.stopPropagation(); var ev = event.originalEvent || event; + if (ev.target !== node) { + // Since TransitionEvent / AnimationEvent bubble up, + // we have to ignore events by finished child animations + return; + } + // we now always use `Date.now()` due to the recent changes with // event.timeStamp in Firefox, Webkit and Chrome (see #13494 for more info) var timeStamp = ev.$manualTimeStamp || Date.now(); diff --git a/src/ngMock/browserTrigger.js b/src/ngMock/browserTrigger.js index e2483204d467..13b2592fea03 100644 --- a/src/ngMock/browserTrigger.js +++ b/src/ngMock/browserTrigger.js @@ -55,25 +55,25 @@ if (/transitionend/.test(eventType)) { if (window.WebKitTransitionEvent) { evnt = new window.WebKitTransitionEvent(eventType, eventData); - evnt.initEvent(eventType, false, true); + evnt.initEvent(eventType, eventData.bubbles, true); } else { try { evnt = new window.TransitionEvent(eventType, eventData); } catch (e) { evnt = window.document.createEvent('TransitionEvent'); - evnt.initTransitionEvent(eventType, null, null, null, eventData.elapsedTime || 0); + evnt.initTransitionEvent(eventType, eventData.bubbles, null, null, eventData.elapsedTime || 0); } } } else if (/animationend/.test(eventType)) { if (window.WebKitAnimationEvent) { evnt = new window.WebKitAnimationEvent(eventType, eventData); - evnt.initEvent(eventType, false, true); + evnt.initEvent(eventType, eventData.bubbles, true); } else { try { evnt = new window.AnimationEvent(eventType, eventData); } catch (e) { evnt = window.document.createEvent('AnimationEvent'); - evnt.initAnimationEvent(eventType, null, null, null, eventData.elapsedTime || 0); + evnt.initAnimationEvent(eventType, eventData.bubbles, null, null, eventData.elapsedTime || 0); } } } else if (/touch/.test(eventType) && supportsTouchEvents()) { diff --git a/test/ngAnimate/animateCssSpec.js b/test/ngAnimate/animateCssSpec.js index b274de05f18a..d22100f7cf83 100644 --- a/test/ngAnimate/animateCssSpec.js +++ b/test/ngAnimate/animateCssSpec.js @@ -532,6 +532,60 @@ describe('ngAnimate $animateCss', function() { assertAnimationComplete(true); })); + it('should not close a transition when a child element fires the transitionend event', + inject(function($animateCss) { + + ss.addPossiblyPrefixedRule('.ng-enter', 'transition:4s linear all;'); + ss.addPossiblyPrefixedRule('.non-angular-animation', 'transition:5s linear all;'); + + var child = angular.element('
'); + element.append(child); + + var animator = $animateCss(element, options); + animator.start(); + triggerAnimationStartFrame(); + + browserTrigger(child, 'transitionend', { + timeStamp: Date.now(), + elapsedTime: 5, + bubbles: true + }); + + transitionProgress(element, 1); + + assertAnimationComplete(false); + + transitionProgress(element, 4); + assertAnimationComplete(true); + })); + + it('should not close a keyframe animation when a child element fires the animationend event', + inject(function($animateCss) { + + ss.addPossiblyPrefixedRule('.ng-enter', 'animation:animation 4s;'); + ss.addPossiblyPrefixedRule('.non-angular-animation', 'animation:animation 5s;'); + + var child = angular.element('
'); + element.append(child); + + var animator = $animateCss(element, options); + animator.start(); + triggerAnimationStartFrame(); + + browserTrigger(child, 'animationend', { + timeStamp: Date.now(), + elapsedTime: 5, + bubbles: true + }); + + keyframeProgress(element, 1); + + assertAnimationComplete(false); + + keyframeProgress(element, 4); + assertAnimationComplete(true); + })); + it('should use the highest keyframe duration value detected in the CSS class', inject(function($animateCss) { ss.addPossiblyPrefixedRule('.ng-enter', 'animation:animation 1s, animation 2s, animation 3s;');