@@ -366,6 +366,7 @@ angular.module('ngAnimate', ['ng'])
366
366
var noop = angular . noop ;
367
367
var forEach = angular . forEach ;
368
368
var selectors = $animateProvider . $$selectors ;
369
+ var isArray = angular . isArray ;
369
370
370
371
var ELEMENT_NODE = 1 ;
371
372
var NG_ANIMATE_STATE = '$$ngAnimateState' ;
@@ -419,10 +420,14 @@ angular.module('ngAnimate', ['ng'])
419
420
return classNameFilter . test ( className ) ;
420
421
} ;
421
422
422
- function blockElementAnimations ( element ) {
423
+ function classBasedAnimationsBlocked ( element , setter ) {
423
424
var data = element . data ( NG_ANIMATE_STATE ) || { } ;
424
- data . running = true ;
425
- element . data ( NG_ANIMATE_STATE , data ) ;
425
+ if ( setter ) {
426
+ data . running = true ;
427
+ data . structural = true ;
428
+ element . data ( NG_ANIMATE_STATE , data ) ;
429
+ }
430
+ return data . disabled || ( data . running && data . structural ) ;
426
431
}
427
432
428
433
function runAnimationPostDigest ( fn ) {
@@ -435,6 +440,60 @@ angular.module('ngAnimate', ['ng'])
435
440
} ;
436
441
}
437
442
443
+ function resolveElementClasses ( element , cache , runningAnimations ) {
444
+ runningAnimations = runningAnimations || { } ;
445
+ var map = { } ;
446
+
447
+ forEach ( cache . add , function ( className ) {
448
+ if ( className && className . length ) {
449
+ map [ className ] = map [ className ] || 0 ;
450
+ map [ className ] ++ ;
451
+ }
452
+ } ) ;
453
+
454
+ forEach ( cache . remove , function ( className ) {
455
+ if ( className && className . length ) {
456
+ map [ className ] = map [ className ] || 0 ;
457
+ map [ className ] -- ;
458
+ }
459
+ } ) ;
460
+
461
+ var lookup = [ ] ;
462
+ forEach ( runningAnimations , function ( data , selector ) {
463
+ forEach ( selector . split ( ' ' ) , function ( s ) {
464
+ lookup [ s ] = data ;
465
+ } ) ;
466
+ } ) ;
467
+
468
+ var toAdd = [ ] , toRemove = [ ] ;
469
+ forEach ( map , function ( status , className ) {
470
+ var hasClass = element . hasClass ( className ) ;
471
+ var matchingAnimation = lookup [ className ] || { } ;
472
+
473
+ // When addClass and removeClass is called then $animate will check to
474
+ // see if addClass and removeClass cancel each other out. When there are
475
+ // more calls to removeClass than addClass then the count falls below 0
476
+ // and then the removeClass animation will be allowed. Otherwise if the
477
+ // count is above 0 then that means an addClass animation will commence.
478
+ // Once an animation is allowed then the code will also check to see if
479
+ // there exists any on-going animation that is already adding or remvoing
480
+ // the matching CSS class.
481
+ if ( status < 0 ) {
482
+ //does it have the class or will it have the class
483
+ if ( hasClass || matchingAnimation . event == 'addClass' ) {
484
+ toRemove . push ( className ) ;
485
+ }
486
+ } else if ( status > 0 ) {
487
+ //is the class missing or will it be removed?
488
+ if ( ! hasClass || matchingAnimation . event == 'removeClass' ) {
489
+ toAdd . push ( className ) ;
490
+ }
491
+ }
492
+ } ) ;
493
+
494
+ return ( toAdd . length + toRemove . length ) > 0 && [ toAdd . join ( ' ' ) , toRemove . join ( ' ' ) ] ;
495
+ }
496
+
438
497
function lookup ( name ) {
439
498
if ( name ) {
440
499
var matches = [ ] ,
@@ -473,18 +532,27 @@ angular.module('ngAnimate', ['ng'])
473
532
return ;
474
533
}
475
534
535
+ var classNameAdd ;
536
+ var classNameRemove ;
537
+ if ( isArray ( className ) ) {
538
+ classNameAdd = className [ 0 ] ;
539
+ classNameRemove = className [ 1 ] ;
540
+ if ( ! classNameAdd ) {
541
+ className = classNameRemove ;
542
+ animationEvent = 'removeClass' ;
543
+ } else if ( ! classNameRemove ) {
544
+ className = classNameAdd ;
545
+ animationEvent = 'addClass' ;
546
+ } else {
547
+ className = classNameAdd + ' ' + classNameRemove ;
548
+ }
549
+ }
550
+
476
551
var isSetClassOperation = animationEvent == 'setClass' ;
477
552
var isClassBased = isSetClassOperation ||
478
553
animationEvent == 'addClass' ||
479
554
animationEvent == 'removeClass' ;
480
555
481
- var classNameAdd , classNameRemove ;
482
- if ( angular . isArray ( className ) ) {
483
- classNameAdd = className [ 0 ] ;
484
- classNameRemove = className [ 1 ] ;
485
- className = classNameAdd + ' ' + classNameRemove ;
486
- }
487
-
488
556
var currentClassName = element . attr ( 'class' ) ;
489
557
var classes = currentClassName + ' ' + className ;
490
558
if ( ! isAnimatableClassName ( classes ) ) {
@@ -665,7 +733,7 @@ angular.module('ngAnimate', ['ng'])
665
733
parentElement = prepareElement ( parentElement ) ;
666
734
afterElement = prepareElement ( afterElement ) ;
667
735
668
- blockElementAnimations ( element ) ;
736
+ classBasedAnimationsBlocked ( element , true ) ;
669
737
$delegate . enter ( element , parentElement , afterElement ) ;
670
738
return runAnimationPostDigest ( function ( ) {
671
739
return performAnimation ( 'enter' , 'ng-enter' , stripCommentsFromElement ( element ) , parentElement , afterElement , noop , doneCallback ) ;
@@ -707,7 +775,7 @@ angular.module('ngAnimate', ['ng'])
707
775
element = angular . element ( element ) ;
708
776
709
777
cancelChildAnimations ( element ) ;
710
- blockElementAnimations ( element ) ;
778
+ classBasedAnimationsBlocked ( element , true ) ;
711
779
this . enabled ( false , element ) ;
712
780
return runAnimationPostDigest ( function ( ) {
713
781
return performAnimation ( 'leave' , 'ng-leave' , stripCommentsFromElement ( element ) , null , null , function ( ) {
@@ -756,7 +824,7 @@ angular.module('ngAnimate', ['ng'])
756
824
afterElement = prepareElement ( afterElement ) ;
757
825
758
826
cancelChildAnimations ( element ) ;
759
- blockElementAnimations ( element ) ;
827
+ classBasedAnimationsBlocked ( element , true ) ;
760
828
$delegate . move ( element , parentElement , afterElement ) ;
761
829
return runAnimationPostDigest ( function ( ) {
762
830
return performAnimation ( 'move' , 'ng-move' , stripCommentsFromElement ( element ) , parentElement , afterElement , noop , doneCallback ) ;
@@ -794,11 +862,7 @@ angular.module('ngAnimate', ['ng'])
794
862
* @return {function } the animation cancellation function
795
863
*/
796
864
addClass : function ( element , className , doneCallback ) {
797
- element = angular . element ( element ) ;
798
- element = stripCommentsFromElement ( element ) ;
799
- return performAnimation ( 'addClass' , className , element , null , null , function ( ) {
800
- $delegate . addClass ( element , className ) ;
801
- } , doneCallback ) ;
865
+ return this . setClass ( element , className , [ ] , doneCallback ) ;
802
866
} ,
803
867
804
868
/**
@@ -832,11 +896,7 @@ angular.module('ngAnimate', ['ng'])
832
896
* @return {function } the animation cancellation function
833
897
*/
834
898
removeClass : function ( element , className , doneCallback ) {
835
- element = angular . element ( element ) ;
836
- element = stripCommentsFromElement ( element ) ;
837
- return performAnimation ( 'removeClass' , className , element , null , null , function ( ) {
838
- $delegate . removeClass ( element , className ) ;
839
- } , doneCallback ) ;
899
+ return this . setClass ( element , [ ] , className , doneCallback ) ;
840
900
} ,
841
901
842
902
/**
@@ -868,11 +928,54 @@ angular.module('ngAnimate', ['ng'])
868
928
* @return {function } the animation cancellation function
869
929
*/
870
930
setClass : function ( element , add , remove , doneCallback ) {
931
+ var STORAGE_KEY = '$$animateClasses' ;
871
932
element = angular . element ( element ) ;
872
933
element = stripCommentsFromElement ( element ) ;
873
- return performAnimation ( 'setClass' , [ add , remove ] , element , null , null , function ( ) {
874
- $delegate . setClass ( element , add , remove ) ;
875
- } , doneCallback ) ;
934
+
935
+ if ( classBasedAnimationsBlocked ( element ) ) {
936
+ return $delegate . setClass ( element , add , remove , doneCallback ) ;
937
+ }
938
+
939
+ add = isArray ( add ) ? add : add . split ( ' ' ) ;
940
+ remove = isArray ( remove ) ? remove : remove . split ( ' ' ) ;
941
+ doneCallback = doneCallback || noop ;
942
+
943
+ var cache = element . data ( STORAGE_KEY ) ;
944
+ if ( cache ) {
945
+ cache . callbacks . push ( doneCallback ) ;
946
+ cache . add = cache . add . concat ( add ) ;
947
+ cache . remove = cache . remove . concat ( remove ) ;
948
+
949
+ //the digest cycle will combine all the animations into one function
950
+ return ;
951
+ } else {
952
+ element . data ( STORAGE_KEY , cache = {
953
+ callbacks : [ doneCallback ] ,
954
+ add : add ,
955
+ remove : remove
956
+ } ) ;
957
+ }
958
+
959
+ return runAnimationPostDigest ( function ( ) {
960
+ var cache = element . data ( STORAGE_KEY ) ;
961
+ var callbacks = cache . callbacks ;
962
+
963
+ element . removeData ( STORAGE_KEY ) ;
964
+
965
+ var state = element . data ( NG_ANIMATE_STATE ) || { } ;
966
+ var classes = resolveElementClasses ( element , cache , state . active ) ;
967
+ return ! classes
968
+ ? $$asyncCallback ( onComplete )
969
+ : performAnimation ( 'setClass' , classes , element , null , null , function ( ) {
970
+ $delegate . setClass ( element , classes [ 0 ] , classes [ 1 ] ) ;
971
+ } , onComplete ) ;
972
+
973
+ function onComplete ( ) {
974
+ forEach ( callbacks , function ( fn ) {
975
+ fn ( ) ;
976
+ } ) ;
977
+ }
978
+ } ) ;
876
979
} ,
877
980
878
981
/**
@@ -931,6 +1034,7 @@ angular.module('ngAnimate', ['ng'])
931
1034
return noopCancel ;
932
1035
}
933
1036
1037
+ animationEvent = runner . event ;
934
1038
className = runner . className ;
935
1039
var elementEvents = angular . element . _data ( runner . node ) ;
936
1040
elementEvents = elementEvents && elementEvents . events ;
@@ -939,32 +1043,22 @@ angular.module('ngAnimate', ['ng'])
939
1043
parentElement = afterElement ? afterElement . parent ( ) : element . parent ( ) ;
940
1044
}
941
1045
942
- var ngAnimateState = element . data ( NG_ANIMATE_STATE ) || { } ;
943
- var runningAnimations = ngAnimateState . active || { } ;
944
- var totalActiveAnimations = ngAnimateState . totalActive || 0 ;
945
- var lastAnimation = ngAnimateState . last ;
946
-
947
- //only allow animations if the currently running animation is not structural
948
- //or if there is no animation running at all
949
- var skipAnimations ;
950
- if ( runner . isClassBased ) {
951
- skipAnimations = ngAnimateState . running ||
952
- ngAnimateState . disabled ||
953
- ( lastAnimation && ! lastAnimation . isClassBased ) ;
954
- }
955
-
956
1046
//skip the animation if animations are disabled, a parent is already being animated,
957
1047
//the element is not currently attached to the document body or then completely close
958
1048
//the animation if any matching animations are not found at all.
959
1049
//NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case an animation was found.
960
- if ( skipAnimations || animationsDisabled ( element , parentElement ) ) {
1050
+ if ( animationsDisabled ( element , parentElement ) ) {
961
1051
fireDOMOperation ( ) ;
962
1052
fireBeforeCallbackAsync ( ) ;
963
1053
fireAfterCallbackAsync ( ) ;
964
1054
closeAnimation ( ) ;
965
1055
return noopCancel ;
966
1056
}
967
1057
1058
+ var ngAnimateState = element . data ( NG_ANIMATE_STATE ) || { } ;
1059
+ var runningAnimations = ngAnimateState . active || { } ;
1060
+ var totalActiveAnimations = ngAnimateState . totalActive || 0 ;
1061
+ var lastAnimation = ngAnimateState . last ;
968
1062
var skipAnimation = false ;
969
1063
if ( totalActiveAnimations > 0 ) {
970
1064
var animationsToCancel = [ ] ;
@@ -1000,9 +1094,6 @@ angular.module('ngAnimate', ['ng'])
1000
1094
}
1001
1095
}
1002
1096
1003
- runningAnimations = ngAnimateState . active || { } ;
1004
- totalActiveAnimations = ngAnimateState . totalActive || 0 ;
1005
-
1006
1097
if ( runner . isClassBased && ! runner . isSetClassOperation && ! skipAnimation ) {
1007
1098
skipAnimation = ( animationEvent == 'addClass' ) == element . hasClass ( className ) ; //opposite of XOR
1008
1099
}
@@ -1015,6 +1106,9 @@ angular.module('ngAnimate', ['ng'])
1015
1106
return noopCancel ;
1016
1107
}
1017
1108
1109
+ runningAnimations = ngAnimateState . active || { } ;
1110
+ totalActiveAnimations = ngAnimateState . totalActive || 0 ;
1111
+
1018
1112
if ( animationEvent == 'leave' ) {
1019
1113
//there's no need to ever remove the listener since the element
1020
1114
//will be removed (destroyed) after the leave animation ends or
@@ -1708,7 +1802,7 @@ angular.module('ngAnimate', ['ng'])
1708
1802
1709
1803
function suffixClasses ( classes , suffix ) {
1710
1804
var className = '' ;
1711
- classes = angular . isArray ( classes ) ? classes : classes . split ( / \s + / ) ;
1805
+ classes = isArray ( classes ) ? classes : classes . split ( / \s + / ) ;
1712
1806
forEach ( classes , function ( klass , i ) {
1713
1807
if ( klass && klass . length > 0 ) {
1714
1808
className += ( i > 0 ? ' ' : '' ) + klass + suffix ;
0 commit comments