@@ -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' ;
@@ -394,6 +395,10 @@ angular.module('ngAnimate', ['ng'])
394
395
return extractElementNode ( elm1 ) == extractElementNode ( elm2 ) ;
395
396
}
396
397
398
+ function isEmpty ( val ) {
399
+ return ! val && val . length === 0 ;
400
+ }
401
+
397
402
$provide . decorator ( '$animate' , [ '$delegate' , '$injector' , '$sniffer' , '$rootElement' , '$$asyncCallback' , '$rootScope' , '$document' ,
398
403
function ( $delegate , $injector , $sniffer , $rootElement , $$asyncCallback , $rootScope , $document ) {
399
404
@@ -419,10 +424,14 @@ angular.module('ngAnimate', ['ng'])
419
424
return classNameFilter . test ( className ) ;
420
425
} ;
421
426
422
- function blockElementAnimations ( element ) {
427
+ function classBasedAnimationsBlocked ( element , setter ) {
423
428
var data = element . data ( NG_ANIMATE_STATE ) || { } ;
424
- data . running = true ;
425
- element . data ( NG_ANIMATE_STATE , data ) ;
429
+ if ( setter ) {
430
+ data . running = true ;
431
+ data . structural = true ;
432
+ element . data ( NG_ANIMATE_STATE , data ) ;
433
+ }
434
+ return data . disabled || ( data . running && data . structural ) ;
426
435
}
427
436
428
437
function runAnimationPostDigest ( fn ) {
@@ -435,6 +444,51 @@ angular.module('ngAnimate', ['ng'])
435
444
} ;
436
445
}
437
446
447
+ function resolveElementClasses ( element , cache , runningAnimations ) {
448
+ runningAnimations = runningAnimations || { } ;
449
+ var map = { } ;
450
+
451
+ forEach ( cache . add , function ( className ) {
452
+ if ( className && className . length ) {
453
+ map [ className ] = map [ className ] || 0 ;
454
+ map [ className ] ++ ;
455
+ }
456
+ } ) ;
457
+
458
+ forEach ( cache . remove , function ( className ) {
459
+ if ( className && className . length ) {
460
+ map [ className ] = map [ className ] || 0 ;
461
+ map [ className ] -- ;
462
+ }
463
+ } ) ;
464
+
465
+ var lookup = [ ] ;
466
+ forEach ( runningAnimations , function ( data , selector ) {
467
+ forEach ( selector . split ( ' ' ) , function ( s ) {
468
+ lookup [ s ] = data ;
469
+ } ) ;
470
+ } ) ;
471
+
472
+ var toAdd = [ ] , toRemove = [ ] ;
473
+ forEach ( map , function ( status , className ) {
474
+ var hasClass = element . hasClass ( className ) ;
475
+ var matchingAnimation = lookup [ className ] || { } ;
476
+ if ( status < 0 ) {
477
+ //does it have the class or will it have the class
478
+ if ( hasClass || matchingAnimation . event == 'addClass' ) {
479
+ toRemove . push ( className ) ;
480
+ }
481
+ } else if ( status > 0 ) {
482
+ //is the class missing or will it be removed?
483
+ if ( ! hasClass || matchingAnimation . event == 'removeClass' ) {
484
+ toAdd . push ( className ) ;
485
+ }
486
+ }
487
+ } ) ;
488
+
489
+ return ( toAdd . length + toRemove . length ) > 0 && [ toAdd . join ( ' ' ) , toRemove . join ( ' ' ) ] ;
490
+ }
491
+
438
492
function lookup ( name ) {
439
493
if ( name ) {
440
494
var matches = [ ] ,
@@ -473,17 +527,26 @@ angular.module('ngAnimate', ['ng'])
473
527
return ;
474
528
}
475
529
530
+ var classNameAdd , classNameRemove ;
531
+ if ( isArray ( className ) ) {
532
+ classNameAdd = className [ 0 ] ;
533
+ classNameRemove = className [ 1 ] ;
534
+ if ( isEmpty ( classNameAdd ) ) {
535
+ className = classNameRemove ;
536
+ animationEvent = 'removeClass' ;
537
+ } else if ( isEmpty ( classNameRemove ) ) {
538
+ className = classNameAdd ;
539
+ animationEvent = 'addClass' ;
540
+ } else {
541
+ className = classNameAdd + ' ' + classNameRemove ;
542
+ }
543
+ }
544
+
476
545
var isSetClassOperation = animationEvent == 'setClass' ;
477
546
var isClassBased = isSetClassOperation ||
478
547
animationEvent == 'addClass' ||
479
548
animationEvent == 'removeClass' ;
480
549
481
- var classNameAdd , classNameRemove ;
482
- if ( angular . isArray ( className ) ) {
483
- classNameAdd = className [ 0 ] ;
484
- classNameRemove = className [ 1 ] ;
485
- className = classNameAdd + ' ' + classNameRemove ;
486
- }
487
550
488
551
var currentClassName = element . attr ( 'class' ) ;
489
552
var classes = currentClassName + ' ' + className ;
@@ -665,7 +728,7 @@ angular.module('ngAnimate', ['ng'])
665
728
parentElement = prepareElement ( parentElement ) ;
666
729
afterElement = prepareElement ( afterElement ) ;
667
730
668
- blockElementAnimations ( element ) ;
731
+ classBasedAnimationsBlocked ( element , true ) ;
669
732
$delegate . enter ( element , parentElement , afterElement ) ;
670
733
return runAnimationPostDigest ( function ( ) {
671
734
return performAnimation ( 'enter' , 'ng-enter' , stripCommentsFromElement ( element ) , parentElement , afterElement , noop , doneCallback ) ;
@@ -707,7 +770,7 @@ angular.module('ngAnimate', ['ng'])
707
770
element = angular . element ( element ) ;
708
771
709
772
cancelChildAnimations ( element ) ;
710
- blockElementAnimations ( element ) ;
773
+ classBasedAnimationsBlocked ( element , true ) ;
711
774
this . enabled ( false , element ) ;
712
775
return runAnimationPostDigest ( function ( ) {
713
776
return performAnimation ( 'leave' , 'ng-leave' , stripCommentsFromElement ( element ) , null , null , function ( ) {
@@ -756,7 +819,7 @@ angular.module('ngAnimate', ['ng'])
756
819
afterElement = prepareElement ( afterElement ) ;
757
820
758
821
cancelChildAnimations ( element ) ;
759
- blockElementAnimations ( element ) ;
822
+ classBasedAnimationsBlocked ( element , true ) ;
760
823
$delegate . move ( element , parentElement , afterElement ) ;
761
824
return runAnimationPostDigest ( function ( ) {
762
825
return performAnimation ( 'move' , 'ng-move' , stripCommentsFromElement ( element ) , parentElement , afterElement , noop , doneCallback ) ;
@@ -794,11 +857,7 @@ angular.module('ngAnimate', ['ng'])
794
857
* @return {function } the animation cancellation function
795
858
*/
796
859
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 ) ;
860
+ return this . setClass ( element , className , [ ] , doneCallback ) ;
802
861
} ,
803
862
804
863
/**
@@ -832,11 +891,7 @@ angular.module('ngAnimate', ['ng'])
832
891
* @return {function } the animation cancellation function
833
892
*/
834
893
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 ) ;
894
+ return this . setClass ( element , [ ] , className , doneCallback ) ;
840
895
} ,
841
896
842
897
/**
@@ -868,11 +923,54 @@ angular.module('ngAnimate', ['ng'])
868
923
* @return {function } the animation cancellation function
869
924
*/
870
925
setClass : function ( element , add , remove , doneCallback ) {
926
+ var STORAGE_KEY = '$$animateClasses' ;
871
927
element = angular . element ( element ) ;
872
928
element = stripCommentsFromElement ( element ) ;
873
- return performAnimation ( 'setClass' , [ add , remove ] , element , null , null , function ( ) {
874
- $delegate . setClass ( element , add , remove ) ;
875
- } , doneCallback ) ;
929
+
930
+ if ( classBasedAnimationsBlocked ( element ) ) {
931
+ return $delegate . setClass ( element , add , remove , doneCallback ) ;
932
+ }
933
+
934
+ add = isArray ( add ) ? add : add . split ( ' ' ) ;
935
+ remove = isArray ( remove ) ? remove : remove . split ( ' ' ) ;
936
+ doneCallback = doneCallback || noop ;
937
+
938
+ var cache = element . data ( STORAGE_KEY ) ;
939
+ if ( cache ) {
940
+ cache . callbacks . push ( doneCallback ) ;
941
+ cache . add = cache . add . concat ( add ) ;
942
+ cache . remove = cache . remove . concat ( remove ) ;
943
+
944
+ //the digest cycle will combine all the animations into one function
945
+ return ;
946
+ } else {
947
+ element . data ( STORAGE_KEY , cache = {
948
+ callbacks : [ doneCallback ] ,
949
+ add : add ,
950
+ remove : remove
951
+ } ) ;
952
+ }
953
+
954
+ return runAnimationPostDigest ( function ( ) {
955
+ var cache = element . data ( STORAGE_KEY ) ;
956
+ var callbacks = cache . callbacks ;
957
+
958
+ element . removeData ( STORAGE_KEY ) ;
959
+
960
+ var state = element . data ( NG_ANIMATE_STATE ) || { } ;
961
+ var classes = resolveElementClasses ( element , cache , state . active ) ;
962
+ return ! classes
963
+ ? $$asyncCallback ( onComplete )
964
+ : performAnimation ( 'setClass' , classes , element , null , null , function ( ) {
965
+ $delegate . setClass ( element , classes [ 0 ] , classes [ 1 ] ) ;
966
+ } , onComplete ) ;
967
+
968
+ function onComplete ( ) {
969
+ forEach ( callbacks , function ( fn ) {
970
+ fn ( ) ;
971
+ } ) ;
972
+ }
973
+ } ) ;
876
974
} ,
877
975
878
976
/**
@@ -931,6 +1029,7 @@ angular.module('ngAnimate', ['ng'])
931
1029
return noopCancel ;
932
1030
}
933
1031
1032
+ animationEvent = runner . event ;
934
1033
className = runner . className ;
935
1034
var elementEvents = angular . element . _data ( runner . node ) ;
936
1035
elementEvents = elementEvents && elementEvents . events ;
@@ -939,33 +1038,24 @@ angular.module('ngAnimate', ['ng'])
939
1038
parentElement = afterElement ? afterElement . parent ( ) : element . parent ( ) ;
940
1039
}
941
1040
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
1041
//skip the animation if animations are disabled, a parent is already being animated,
957
1042
//the element is not currently attached to the document body or then completely close
958
1043
//the animation if any matching animations are not found at all.
959
1044
//NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case an animation was found.
960
- if ( skipAnimations || animationsDisabled ( element , parentElement ) ) {
1045
+ if ( animationsDisabled ( element , parentElement ) ) {
961
1046
fireDOMOperation ( ) ;
962
1047
fireBeforeCallbackAsync ( ) ;
963
1048
fireAfterCallbackAsync ( ) ;
964
1049
closeAnimation ( ) ;
965
1050
return noopCancel ;
966
1051
}
967
1052
1053
+ var ngAnimateState = element . data ( NG_ANIMATE_STATE ) || { } ;
1054
+ var runningAnimations = ngAnimateState . active || { } ;
1055
+ var totalActiveAnimations = ngAnimateState . totalActive || 0 ;
1056
+ var lastAnimation = ngAnimateState . last ;
968
1057
var skipAnimation = false ;
1058
+
969
1059
if ( totalActiveAnimations > 0 ) {
970
1060
var animationsToCancel = [ ] ;
971
1061
if ( ! runner . isClassBased ) {
@@ -1000,9 +1090,6 @@ angular.module('ngAnimate', ['ng'])
1000
1090
}
1001
1091
}
1002
1092
1003
- runningAnimations = ngAnimateState . active || { } ;
1004
- totalActiveAnimations = ngAnimateState . totalActive || 0 ;
1005
-
1006
1093
if ( runner . isClassBased && ! runner . isSetClassOperation && ! skipAnimation ) {
1007
1094
skipAnimation = ( animationEvent == 'addClass' ) == element . hasClass ( className ) ; //opposite of XOR
1008
1095
}
@@ -1015,6 +1102,9 @@ angular.module('ngAnimate', ['ng'])
1015
1102
return noopCancel ;
1016
1103
}
1017
1104
1105
+ runningAnimations = ngAnimateState . active || { } ;
1106
+ totalActiveAnimations = ngAnimateState . totalActive || 0 ;
1107
+
1018
1108
if ( animationEvent == 'leave' ) {
1019
1109
//there's no need to ever remove the listener since the element
1020
1110
//will be removed (destroyed) after the leave animation ends or
@@ -1708,7 +1798,7 @@ angular.module('ngAnimate', ['ng'])
1708
1798
1709
1799
function suffixClasses ( classes , suffix ) {
1710
1800
var className = '' ;
1711
- classes = angular . isArray ( classes ) ? classes : classes . split ( / \s + / ) ;
1801
+ classes = isArray ( classes ) ? classes : classes . split ( / \s + / ) ;
1712
1802
forEach ( classes , function ( klass , i ) {
1713
1803
if ( klass && klass . length > 0 ) {
1714
1804
className += ( i > 0 ? ' ' : '' ) + klass + suffix ;
0 commit comments