@@ -493,35 +493,47 @@ angular.module('ngAnimate', ['ng'])
493493 */
494494 function performAnimation ( event , className , element , parent , after , onComplete ) {
495495 var classes = ( element . attr ( 'class' ) || '' ) + ' ' + className ;
496- var animationLookup = ( ' ' + classes ) . replace ( / \s + / g, '.' ) ,
497- animations = [ ] ;
498- forEach ( lookup ( animationLookup ) , function ( animation , index ) {
499- animations . push ( {
500- start : animation [ event ]
501- } ) ;
502- } ) ;
503-
496+ var animationLookup = ( ' ' + classes ) . replace ( / \s + / g, '.' ) ;
504497 if ( ! parent ) {
505498 parent = after ? after . parent ( ) : element . parent ( ) ;
506499 }
500+
507501 var disabledAnimation = { running : true } ;
502+ var matches = lookup ( animationLookup ) ;
503+ var isClassBased = event == 'addClass' || event == 'removeClass' ;
504+ var ngAnimateState = element . data ( NG_ANIMATE_STATE ) || { } ;
508505
509- //skip the animation if animations are disabled, a parent is already being animated
510- //or the element is not currently attached to the document body.
511- if ( ( parent . inheritedData ( NG_ANIMATE_STATE ) || disabledAnimation ) . running || animations . length === 0 ) {
506+ //skip the animation if animations are disabled, a parent is already being animated,
507+ //the element is not currently attached to the document body or then completely close
508+ //the animation if any matching animations are not found at all.
509+ //NOTE: IE8 + IE9 should close properly (run done()) in case a NO animation is not found.
510+ if ( ( parent . inheritedData ( NG_ANIMATE_STATE ) || disabledAnimation ) . running || matches . length == 0 ) {
512511 done ( ) ;
513512 return ;
514513 }
515514
516- var ngAnimateState = element . data ( NG_ANIMATE_STATE ) || { } ;
515+ var animations = [ ] ;
516+ //only add animations if the currently running animation is not structural
517+ //or if there is no animation running at all
518+ if ( ! ngAnimateState . running || ! ( isClassBased && ngAnimateState . structural ) ) {
519+ forEach ( matches , function ( animation ) {
520+ //add the animation to the queue to if it is allowed to be cancelled
521+ if ( ! animation . allowCancel || animation . allowCancel ( element , event , className ) ) {
522+ animations . push ( {
523+ start : animation [ event ]
524+ } ) ;
525+ }
526+ } ) ;
527+ }
517528
518- var isClassBased = event == 'addClass' || event == 'removeClass' ;
519- if ( ngAnimateState . running ) {
520- if ( isClassBased && ngAnimateState . structural ) {
521- onComplete && onComplete ( ) ;
522- return ;
523- }
529+ //this would mean that an animation was not allowed so let the existing
530+ //animation do it's thing and close this one early
531+ if ( animations . length == 0 ) {
532+ onComplete && onComplete ( ) ;
533+ return ;
534+ }
524535
536+ if ( ngAnimateState . running ) {
525537 //if an animation is currently running on the element then lets take the steps
526538 //to cancel that animation and fire any required callbacks
527539 $timeout . cancel ( ngAnimateState . flagTimer ) ;
@@ -651,6 +663,7 @@ angular.module('ngAnimate', ['ng'])
651663 animationIterationCountKey = 'IterationCount' ;
652664
653665 var NG_ANIMATE_PARENT_KEY = '$ngAnimateKey' ;
666+ var NG_ANIMATE_CLASS_KEY = '$$ngAnimateClasses' ;
654667 var lookupCache = { } ;
655668 var parentCounter = 0 ;
656669
@@ -669,7 +682,7 @@ angular.module('ngAnimate', ['ng'])
669682 }
670683
671684 function getElementAnimationDetails ( element , cacheKey , onlyCheckTransition ) {
672- var data = lookupCache [ cacheKey ] ;
685+ var data = cacheKey ? lookupCache [ cacheKey ] : null ;
673686 if ( ! data ) {
674687 var transitionDuration = 0 , transitionDelay = 0 ,
675688 animationDuration = 0 , animationDelay = 0 ;
@@ -702,7 +715,9 @@ angular.module('ngAnimate', ['ng'])
702715 transitionDuration : transitionDuration ,
703716 animationDuration : animationDuration
704717 } ;
705- lookupCache [ cacheKey ] = data ;
718+ if ( cacheKey ) {
719+ lookupCache [ cacheKey ] = data ;
720+ }
706721 }
707722 return data ;
708723 }
@@ -769,6 +784,7 @@ angular.module('ngAnimate', ['ng'])
769784 element . addClass ( activeClassName ) ;
770785 } ) ;
771786
787+ element . data ( NG_ANIMATE_CLASS_KEY , className + ' ' + activeClassName ) ;
772788 element . on ( css3AnimationEvents , onAnimationProgress ) ;
773789
774790 // This will automatically be called by $animate so
@@ -778,6 +794,7 @@ angular.module('ngAnimate', ['ng'])
778794 element . off ( css3AnimationEvents , onAnimationProgress ) ;
779795 element . removeClass ( className ) ;
780796 element . removeClass ( activeClassName ) ;
797+ element . removeData ( NG_ANIMATE_CLASS_KEY ) ;
781798
782799 // Only when the animation is cancelled is the done()
783800 // function not called for this animation therefore
@@ -811,6 +828,35 @@ angular.module('ngAnimate', ['ng'])
811828 }
812829
813830 return {
831+ allowCancel : function ( element , event , className ) {
832+ //always cancel the current animation if it is a
833+ //structural animation
834+ var oldClasses = element . data ( NG_ANIMATE_CLASS_KEY ) ;
835+ if ( ! oldClasses || [ 'enter' , 'leave' , 'move' ] . indexOf ( event ) >= 0 ) {
836+ return true ;
837+ }
838+
839+ var parent = element . parent ( ) ;
840+ var clone = angular . element ( element [ 0 ] . cloneNode ( ) ) ;
841+
842+ //make the element super hidden and override any CSS style values
843+ clone . attr ( 'style' , 'position:absolute; top:-9999px; left:-9999px' ) ;
844+ clone . removeAttr ( 'id' ) ;
845+ clone . html ( '' ) ;
846+
847+ angular . forEach ( oldClasses . split ( ' ' ) , function ( klass ) {
848+ clone . removeClass ( klass ) ;
849+ } ) ;
850+
851+ var suffix = event == 'addClass' ? '-add' : '-remove' ;
852+ clone . addClass ( suffixClasses ( className , suffix ) ) ;
853+ parent . append ( clone ) ;
854+
855+ var timings = getElementAnimationDetails ( clone ) ;
856+ clone . remove ( ) ;
857+
858+ return Math . max ( timings . transitionDuration , timings . animationDuration ) > 0 ;
859+ } ,
814860 enter : function ( element , done ) {
815861 return animate ( element , 'ng-enter' , done ) ;
816862 } ,
0 commit comments