angular-gem 1.2.12 → 1.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.2.9
2
+ * @license AngularJS v1.2.13
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -254,7 +254,9 @@ angular.module('ngAnimate', ['ng'])
254
254
  * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
255
255
  *
256
256
  */
257
- .factory('$$animateReflow', ['$window', '$timeout', function($window, $timeout) {
257
+ .factory('$$animateReflow', ['$window', '$timeout', '$document',
258
+ function($window, $timeout, $document) {
259
+ var bod = $document[0].body;
258
260
  var requestAnimationFrame = $window.requestAnimationFrame ||
259
261
  $window.webkitRequestAnimationFrame ||
260
262
  function(fn) {
@@ -267,13 +269,30 @@ angular.module('ngAnimate', ['ng'])
267
269
  return $timeout.cancel(timer);
268
270
  };
269
271
  return function(fn) {
270
- var id = requestAnimationFrame(fn);
272
+ var id = requestAnimationFrame(function() {
273
+ var a = bod.offsetWidth + 1;
274
+ fn();
275
+ });
271
276
  return function() {
272
277
  cancelAnimationFrame(id);
273
278
  };
274
279
  };
275
280
  }])
276
281
 
282
+ .factory('$$asyncQueueBuffer', ['$timeout', function($timeout) {
283
+ var timer, queue = [];
284
+ return function(fn) {
285
+ $timeout.cancel(timer);
286
+ queue.push(fn);
287
+ timer = $timeout(function() {
288
+ for(var i = 0; i < queue.length; i++) {
289
+ queue[i]();
290
+ }
291
+ queue = [];
292
+ }, 0, false);
293
+ };
294
+ }])
295
+
277
296
  .config(['$provide', '$animateProvider', function($provide, $animateProvider) {
278
297
  var noop = angular.noop;
279
298
  var forEach = angular.forEach;
@@ -293,13 +312,18 @@ angular.module('ngAnimate', ['ng'])
293
312
  }
294
313
  }
295
314
 
315
+ function stripCommentsFromElement(element) {
316
+ return angular.element(extractElementNode(element));
317
+ }
318
+
296
319
  function isMatchingElement(elm1, elm2) {
297
320
  return extractElementNode(elm1) == extractElementNode(elm2);
298
321
  }
299
322
 
300
- $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', '$rootScope', '$document',
301
- function($delegate, $injector, $sniffer, $rootElement, $timeout, $rootScope, $document) {
323
+ $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$$asyncQueueBuffer', '$rootScope', '$document',
324
+ function($delegate, $injector, $sniffer, $rootElement, $$asyncQueueBuffer, $rootScope, $document) {
302
325
 
326
+ var globalAnimationCounter = 0;
303
327
  $rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
304
328
 
305
329
  // disable animations during bootstrap, but once we bootstrapped, wait again
@@ -321,10 +345,6 @@ angular.module('ngAnimate', ['ng'])
321
345
  return classNameFilter.test(className);
322
346
  };
323
347
 
324
- function async(fn) {
325
- return $timeout(fn, 0, false);
326
- }
327
-
328
348
  function lookup(name) {
329
349
  if (name) {
330
350
  var matches = [],
@@ -406,6 +426,7 @@ angular.module('ngAnimate', ['ng'])
406
426
  this.enabled(false, element);
407
427
  $delegate.enter(element, parentElement, afterElement);
408
428
  $rootScope.$$postDigest(function() {
429
+ element = stripCommentsFromElement(element);
409
430
  performAnimation('enter', 'ng-enter', element, parentElement, afterElement, noop, doneCallback);
410
431
  });
411
432
  },
@@ -442,6 +463,7 @@ angular.module('ngAnimate', ['ng'])
442
463
  cancelChildAnimations(element);
443
464
  this.enabled(false, element);
444
465
  $rootScope.$$postDigest(function() {
466
+ element = stripCommentsFromElement(element);
445
467
  performAnimation('leave', 'ng-leave', element, null, null, function() {
446
468
  $delegate.leave(element);
447
469
  }, doneCallback);
@@ -484,6 +506,7 @@ angular.module('ngAnimate', ['ng'])
484
506
  this.enabled(false, element);
485
507
  $delegate.move(element, parentElement, afterElement);
486
508
  $rootScope.$$postDigest(function() {
509
+ element = stripCommentsFromElement(element);
487
510
  performAnimation('move', 'ng-move', element, parentElement, afterElement, noop, doneCallback);
488
511
  });
489
512
  },
@@ -519,6 +542,7 @@ angular.module('ngAnimate', ['ng'])
519
542
  * @param {function()=} doneCallback the callback function that will be called once the animation is complete
520
543
  */
521
544
  addClass : function(element, className, doneCallback) {
545
+ element = stripCommentsFromElement(element);
522
546
  performAnimation('addClass', className, element, null, null, function() {
523
547
  $delegate.addClass(element, className);
524
548
  }, doneCallback);
@@ -555,11 +579,34 @@ angular.module('ngAnimate', ['ng'])
555
579
  * @param {function()=} doneCallback the callback function that will be called once the animation is complete
556
580
  */
557
581
  removeClass : function(element, className, doneCallback) {
582
+ element = stripCommentsFromElement(element);
558
583
  performAnimation('removeClass', className, element, null, null, function() {
559
584
  $delegate.removeClass(element, className);
560
585
  }, doneCallback);
561
586
  },
562
587
 
588
+ /**
589
+ *
590
+ * @ngdoc function
591
+ * @name ng.$animate#setClass
592
+ * @methodOf ng.$animate
593
+ * @function
594
+ * @description Adds and/or removes the given CSS classes to and from the element.
595
+ * Once complete, the done() callback will be fired (if provided).
596
+ * @param {jQuery/jqLite element} element the element which will it's CSS classes changed
597
+ * removed from it
598
+ * @param {string} add the CSS classes which will be added to the element
599
+ * @param {string} remove the CSS class which will be removed from the element
600
+ * @param {function=} done the callback function (if provided) that will be fired after the
601
+ * CSS classes have been set on the element
602
+ */
603
+ setClass : function(element, add, remove, doneCallback) {
604
+ element = stripCommentsFromElement(element);
605
+ performAnimation('setClass', [add, remove], element, null, null, function() {
606
+ $delegate.setClass(element, add, remove);
607
+ }, doneCallback);
608
+ },
609
+
563
610
  /**
564
611
  * @ngdoc function
565
612
  * @name ngAnimate.$animate#enabled
@@ -606,7 +653,15 @@ angular.module('ngAnimate', ['ng'])
606
653
  and the onComplete callback will be fired once the animation is fully complete.
607
654
  */
608
655
  function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
609
- var currentClassName, classes, node = extractElementNode(element);
656
+
657
+ var classNameAdd, classNameRemove, setClassOperation = animationEvent == 'setClass';
658
+ if(setClassOperation) {
659
+ classNameAdd = className[0];
660
+ classNameRemove = className[1];
661
+ className = classNameAdd + ' ' + classNameRemove;
662
+ }
663
+
664
+ var currentClassName, classes, node = element[0];
610
665
  if(node) {
611
666
  currentClassName = node.className;
612
667
  classes = currentClassName + ' ' + className;
@@ -618,18 +673,27 @@ angular.module('ngAnimate', ['ng'])
618
673
  fireDOMOperation();
619
674
  fireBeforeCallbackAsync();
620
675
  fireAfterCallbackAsync();
621
- closeAnimation();
676
+ fireDoneCallbackAsync();
622
677
  return;
623
678
  }
624
679
 
680
+ var elementEvents = angular.element._data(node);
681
+ elementEvents = elementEvents && elementEvents.events;
682
+
625
683
  var animationLookup = (' ' + classes).replace(/\s+/g,'.');
626
684
  if (!parentElement) {
627
685
  parentElement = afterElement ? afterElement.parent() : element.parent();
628
686
  }
629
687
 
630
- var matches = lookup(animationLookup);
631
- var isClassBased = animationEvent == 'addClass' || animationEvent == 'removeClass';
632
- var ngAnimateState = element.data(NG_ANIMATE_STATE) || {};
688
+ var matches = lookup(animationLookup);
689
+ var isClassBased = animationEvent == 'addClass' ||
690
+ animationEvent == 'removeClass' ||
691
+ setClassOperation;
692
+ var ngAnimateState = element.data(NG_ANIMATE_STATE) || {};
693
+
694
+ var runningAnimations = ngAnimateState.active || {};
695
+ var totalActiveAnimations = ngAnimateState.totalActive || 0;
696
+ var lastAnimation = ngAnimateState.last;
633
697
 
634
698
  //skip the animation if animations are disabled, a parent is already being animated,
635
699
  //the element is not currently attached to the document body or then completely close
@@ -648,7 +712,7 @@ angular.module('ngAnimate', ['ng'])
648
712
  //only add animations if the currently running animation is not structural
649
713
  //or if there is no animation running at all
650
714
  var allowAnimations = isClassBased ?
651
- !ngAnimateState.disabled && (!ngAnimateState.running || !ngAnimateState.structural) :
715
+ !ngAnimateState.disabled && (!lastAnimation || lastAnimation.classBased) :
652
716
  true;
653
717
 
654
718
  if(allowAnimations) {
@@ -683,55 +747,48 @@ angular.module('ngAnimate', ['ng'])
683
747
  return;
684
748
  }
685
749
 
686
- var ONE_SPACE = ' ';
687
- //this value will be searched for class-based CSS className lookup. Therefore,
688
- //we prefix and suffix the current className value with spaces to avoid substring
689
- //lookups of className tokens
690
- var futureClassName = ONE_SPACE + currentClassName + ONE_SPACE;
691
- if(ngAnimateState.running) {
692
- //if an animation is currently running on the element then lets take the steps
693
- //to cancel that animation and fire any required callbacks
694
- $timeout.cancel(ngAnimateState.closeAnimationTimeout);
695
- cleanup(element);
696
- cancelAnimations(ngAnimateState.animations);
697
-
698
- //in the event that the CSS is class is quickly added and removed back
699
- //then we don't want to wait until after the reflow to add/remove the CSS
700
- //class since both class animations may run into a race condition.
701
- //The code below will check to see if that is occurring and will
702
- //immediately remove the former class before the reflow so that the
703
- //animation can snap back to the original animation smoothly
704
- var isFullyClassBasedAnimation = isClassBased && !ngAnimateState.structural;
705
- var isRevertingClassAnimation = isFullyClassBasedAnimation &&
706
- ngAnimateState.className == className &&
707
- animationEvent != ngAnimateState.event;
708
-
709
- //if the class is removed during the reflow then it will revert the styles temporarily
710
- //back to the base class CSS styling causing a jump-like effect to occur. This check
711
- //here ensures that the domOperation is only performed after the reflow has commenced
712
- if(ngAnimateState.beforeComplete || isRevertingClassAnimation) {
713
- (ngAnimateState.done || noop)(true);
714
- } else if(isFullyClassBasedAnimation) {
715
- //class-based animations will compare element className values after cancelling the
716
- //previous animation to see if the element properties already contain the final CSS
717
- //class and if so then the animation will be skipped. Since the domOperation will
718
- //be performed only after the reflow is complete then our element's className value
719
- //will be invalid. Therefore the same string manipulation that would occur within the
720
- //DOM operation will be performed below so that the class comparison is valid...
721
- futureClassName = ngAnimateState.event == 'removeClass' ?
722
- futureClassName.replace(ONE_SPACE + ngAnimateState.className + ONE_SPACE, ONE_SPACE) :
723
- futureClassName + ngAnimateState.className + ONE_SPACE;
750
+ var skipAnimation = false;
751
+ if(totalActiveAnimations > 0) {
752
+ var animationsToCancel = [];
753
+ if(!isClassBased) {
754
+ if(animationEvent == 'leave' && runningAnimations['ng-leave']) {
755
+ skipAnimation = true;
756
+ } else {
757
+ //cancel all animations when a structural animation takes place
758
+ for(var klass in runningAnimations) {
759
+ animationsToCancel.push(runningAnimations[klass]);
760
+ cleanup(element, klass);
761
+ }
762
+ runningAnimations = {};
763
+ totalActiveAnimations = 0;
764
+ }
765
+ } else if(lastAnimation.event == 'setClass') {
766
+ animationsToCancel.push(lastAnimation);
767
+ cleanup(element, className);
768
+ }
769
+ else if(runningAnimations[className]) {
770
+ var current = runningAnimations[className];
771
+ if(current.event == animationEvent) {
772
+ skipAnimation = true;
773
+ } else {
774
+ animationsToCancel.push(current);
775
+ cleanup(element, className);
776
+ }
777
+ }
778
+
779
+ if(animationsToCancel.length > 0) {
780
+ angular.forEach(animationsToCancel, function(operation) {
781
+ (operation.done || noop)(true);
782
+ cancelAnimations(operation.animations);
783
+ });
724
784
  }
725
785
  }
726
786
 
727
- //There is no point in perform a class-based animation if the element already contains
728
- //(on addClass) or doesn't contain (on removeClass) the className being animated.
729
- //The reason why this is being called after the previous animations are cancelled
730
- //is so that the CSS classes present on the element can be properly examined.
731
- var classNameToken = ONE_SPACE + className + ONE_SPACE;
732
- if((animationEvent == 'addClass' && futureClassName.indexOf(classNameToken) >= 0) ||
733
- (animationEvent == 'removeClass' && futureClassName.indexOf(classNameToken) == -1)) {
734
- fireDOMOperation();
787
+ if(isClassBased && !setClassOperation && !skipAnimation) {
788
+ skipAnimation = (animationEvent == 'addClass') == element.hasClass(className); //opposite of XOR
789
+ }
790
+
791
+ if(skipAnimation) {
735
792
  fireBeforeCallbackAsync();
736
793
  fireAfterCallbackAsync();
737
794
  fireDoneCallbackAsync();
@@ -742,13 +799,22 @@ angular.module('ngAnimate', ['ng'])
742
799
  //parent animations to find and cancel child animations when needed
743
800
  element.addClass(NG_ANIMATE_CLASS_NAME);
744
801
 
745
- element.data(NG_ANIMATE_STATE, {
746
- running:true,
747
- event:animationEvent,
748
- className:className,
749
- structural:!isClassBased,
750
- animations:animations,
802
+ var localAnimationCount = globalAnimationCounter++;
803
+ lastAnimation = {
804
+ classBased : isClassBased,
805
+ event : animationEvent,
806
+ animations : animations,
751
807
  done:onBeforeAnimationsComplete
808
+ };
809
+
810
+ totalActiveAnimations++;
811
+ runningAnimations[className] = lastAnimation;
812
+
813
+ element.data(NG_ANIMATE_STATE, {
814
+ last : lastAnimation,
815
+ active : runningAnimations,
816
+ index : localAnimationCount,
817
+ totalActive : totalActiveAnimations
752
818
  });
753
819
 
754
820
  //first we run the before animations and when all of those are complete
@@ -756,6 +822,11 @@ angular.module('ngAnimate', ['ng'])
756
822
  invokeRegisteredAnimationFns(animations, 'before', onBeforeAnimationsComplete);
757
823
 
758
824
  function onBeforeAnimationsComplete(cancelled) {
825
+ var data = element.data(NG_ANIMATE_STATE);
826
+ cancelled = cancelled ||
827
+ !data || !data.active[className] ||
828
+ (isClassBased && data.active[className].event != animationEvent);
829
+
759
830
  fireDOMOperation();
760
831
  if(cancelled === true) {
761
832
  closeAnimation();
@@ -765,11 +836,8 @@ angular.module('ngAnimate', ['ng'])
765
836
  //set the done function to the final done function
766
837
  //so that the DOM event won't be executed twice by accident
767
838
  //if the after animation is cancelled as well
768
- var data = element.data(NG_ANIMATE_STATE);
769
- if(data) {
770
- data.done = closeAnimation;
771
- element.data(NG_ANIMATE_STATE, data);
772
- }
839
+ var currentAnimation = data.active[className];
840
+ currentAnimation.done = closeAnimation;
773
841
  invokeRegisteredAnimationFns(animations, 'after', closeAnimation);
774
842
  }
775
843
 
@@ -792,9 +860,13 @@ angular.module('ngAnimate', ['ng'])
792
860
  }
793
861
 
794
862
  if(animation[phase]) {
795
- animation[endFnName] = isClassBased ?
796
- animation[phase](element, className, animationPhaseCompleted) :
797
- animation[phase](element, animationPhaseCompleted);
863
+ if(setClassOperation) {
864
+ animation[endFnName] = animation[phase](element, classNameAdd, classNameRemove, animationPhaseCompleted);
865
+ } else {
866
+ animation[endFnName] = isClassBased ?
867
+ animation[phase](element, className, animationPhaseCompleted) :
868
+ animation[phase](element, animationPhaseCompleted);
869
+ }
798
870
  } else {
799
871
  animationPhaseCompleted();
800
872
  }
@@ -815,26 +887,32 @@ angular.module('ngAnimate', ['ng'])
815
887
  }
816
888
 
817
889
  function fireDOMCallback(animationPhase) {
818
- element.triggerHandler('$animate:' + animationPhase, {
819
- event : animationEvent,
820
- className : className
821
- });
890
+ var eventName = '$animate:' + animationPhase;
891
+ if(elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) {
892
+ $$asyncQueueBuffer(function() {
893
+ element.triggerHandler(eventName, {
894
+ event : animationEvent,
895
+ className : className
896
+ });
897
+ });
898
+ }
822
899
  }
823
900
 
824
901
  function fireBeforeCallbackAsync() {
825
- async(function() {
826
- fireDOMCallback('before');
827
- });
902
+ fireDOMCallback('before');
828
903
  }
829
904
 
830
905
  function fireAfterCallbackAsync() {
831
- async(function() {
832
- fireDOMCallback('after');
833
- });
906
+ fireDOMCallback('after');
834
907
  }
835
908
 
836
909
  function fireDoneCallbackAsync() {
837
- doneCallback && async(doneCallback);
910
+ fireDOMCallback('close');
911
+ if(doneCallback) {
912
+ $$asyncQueueBuffer(function() {
913
+ doneCallback();
914
+ });
915
+ }
838
916
  }
839
917
 
840
918
  //it is less complicated to use a flag than managing and cancelling
@@ -856,10 +934,13 @@ angular.module('ngAnimate', ['ng'])
856
934
  failing would be when a parent HTML tag has a ng-class attribute
857
935
  causing ALL directives below to skip animations during the digest */
858
936
  if(isClassBased) {
859
- cleanup(element);
937
+ cleanup(element, className);
860
938
  } else {
861
- data.closeAnimationTimeout = async(function() {
862
- cleanup(element);
939
+ $$asyncQueueBuffer(function() {
940
+ var data = element.data(NG_ANIMATE_STATE) || {};
941
+ if(localAnimationCount == data.index) {
942
+ cleanup(element, className, animationEvent);
943
+ }
863
944
  });
864
945
  element.data(NG_ANIMATE_STATE, data);
865
946
  }
@@ -874,9 +955,11 @@ angular.module('ngAnimate', ['ng'])
874
955
  forEach(node.querySelectorAll('.' + NG_ANIMATE_CLASS_NAME), function(element) {
875
956
  element = angular.element(element);
876
957
  var data = element.data(NG_ANIMATE_STATE);
877
- if(data) {
878
- cancelAnimations(data.animations);
879
- cleanup(element);
958
+ if(data && data.active) {
959
+ angular.forEach(data.active, function(operation) {
960
+ (operation.done || noop)(true);
961
+ cancelAnimations(operation.animations);
962
+ });
880
963
  }
881
964
  });
882
965
  }
@@ -893,15 +976,27 @@ angular.module('ngAnimate', ['ng'])
893
976
  });
894
977
  }
895
978
 
896
- function cleanup(element) {
979
+ function cleanup(element, className) {
897
980
  if(isMatchingElement(element, $rootElement)) {
898
981
  if(!rootAnimateState.disabled) {
899
982
  rootAnimateState.running = false;
900
983
  rootAnimateState.structural = false;
901
984
  }
902
- } else {
903
- element.removeClass(NG_ANIMATE_CLASS_NAME);
904
- element.removeData(NG_ANIMATE_STATE);
985
+ } else if(className) {
986
+ var data = element.data(NG_ANIMATE_STATE) || {};
987
+
988
+ var removeAnimations = className === true;
989
+ if(!removeAnimations) {
990
+ if(data.active && data.active[className]) {
991
+ data.totalActive--;
992
+ delete data.active[className];
993
+ }
994
+ }
995
+
996
+ if(removeAnimations || !data.totalActive) {
997
+ element.removeClass(NG_ANIMATE_CLASS_NAME);
998
+ element.removeData(NG_ANIMATE_STATE);
999
+ }
905
1000
  }
906
1001
  }
907
1002
 
@@ -920,7 +1015,7 @@ angular.module('ngAnimate', ['ng'])
920
1015
 
921
1016
  var isRoot = isMatchingElement(parentElement, $rootElement);
922
1017
  var state = isRoot ? rootAnimateState : parentElement.data(NG_ANIMATE_STATE);
923
- var result = state && (!!state.disabled || !!state.running);
1018
+ var result = state && (!!state.disabled || state.running || state.totalActive > 0);
924
1019
  if(isRoot || result) {
925
1020
  return result;
926
1021
  }
@@ -970,74 +1065,57 @@ angular.module('ngAnimate', ['ng'])
970
1065
  var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
971
1066
  var NG_ANIMATE_PARENT_KEY = '$$ngAnimateKey';
972
1067
  var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data';
1068
+ var NG_ANIMATE_BLOCK_CLASS_NAME = 'ng-animate-block-transitions';
973
1069
  var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
974
1070
  var CLOSING_TIME_BUFFER = 1.5;
975
1071
  var ONE_SECOND = 1000;
976
1072
 
977
- var animationCounter = 0;
978
1073
  var lookupCache = {};
979
1074
  var parentCounter = 0;
980
1075
  var animationReflowQueue = [];
981
- var animationElementQueue = [];
982
1076
  var cancelAnimationReflow;
983
- var closingAnimationTime = 0;
984
- var timeOut = false;
985
1077
  function afterReflow(element, callback) {
986
1078
  if(cancelAnimationReflow) {
987
1079
  cancelAnimationReflow();
988
1080
  }
989
-
990
1081
  animationReflowQueue.push(callback);
991
-
992
- var node = extractElementNode(element);
993
- element = angular.element(node);
994
- animationElementQueue.push(element);
995
-
996
- var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
997
-
998
- var stagger = elementData.stagger;
999
- var staggerTime = elementData.itemIndex * (Math.max(stagger.animationDelay, stagger.transitionDelay) || 0);
1000
-
1001
- var animationTime = (elementData.maxDelay + elementData.maxDuration) * CLOSING_TIME_BUFFER;
1002
- closingAnimationTime = Math.max(closingAnimationTime, (staggerTime + animationTime) * ONE_SECOND);
1003
-
1004
- //by placing a counter we can avoid an accidental
1005
- //race condition which may close an animation when
1006
- //a follow-up animation is midway in its animation
1007
- elementData.animationCount = animationCounter;
1008
-
1009
1082
  cancelAnimationReflow = $$animateReflow(function() {
1010
1083
  forEach(animationReflowQueue, function(fn) {
1011
1084
  fn();
1012
1085
  });
1013
1086
 
1014
- //copy the list of elements so that successive
1015
- //animations won't conflict if they're added before
1016
- //the closing animation timeout has run
1017
- var elementQueueSnapshot = [];
1018
- var animationCounterSnapshot = animationCounter;
1019
- forEach(animationElementQueue, function(elm) {
1020
- elementQueueSnapshot.push(elm);
1021
- });
1022
-
1023
- $timeout(function() {
1024
- closeAllAnimations(elementQueueSnapshot, animationCounterSnapshot);
1025
- elementQueueSnapshot = null;
1026
- }, closingAnimationTime, false);
1027
-
1028
1087
  animationReflowQueue = [];
1029
- animationElementQueue = [];
1030
1088
  cancelAnimationReflow = null;
1031
1089
  lookupCache = {};
1032
- closingAnimationTime = 0;
1033
- animationCounter++;
1034
1090
  });
1035
1091
  }
1036
1092
 
1037
- function closeAllAnimations(elements, count) {
1093
+ var closingTimer = null;
1094
+ var closingTimestamp = 0;
1095
+ var animationElementQueue = [];
1096
+ function animationCloseHandler(element, totalTime) {
1097
+ var futureTimestamp = Date.now() + (totalTime * 1000);
1098
+ if(futureTimestamp <= closingTimestamp) {
1099
+ return;
1100
+ }
1101
+
1102
+ $timeout.cancel(closingTimer);
1103
+
1104
+ var node = extractElementNode(element);
1105
+ element = angular.element(node);
1106
+ animationElementQueue.push(element);
1107
+
1108
+ closingTimestamp = futureTimestamp;
1109
+ closingTimer = $timeout(function() {
1110
+ closeAllAnimations(animationElementQueue);
1111
+ animationElementQueue = [];
1112
+ }, totalTime, false);
1113
+ }
1114
+
1115
+ function closeAllAnimations(elements) {
1038
1116
  forEach(elements, function(element) {
1039
1117
  var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
1040
- if(elementData && elementData.animationCount == count) {
1118
+ if(elementData) {
1041
1119
  (elementData.closeAnimationFn || noop)();
1042
1120
  }
1043
1121
  });
@@ -1122,12 +1200,12 @@ angular.module('ngAnimate', ['ng'])
1122
1200
  return parentID + '-' + extractElementNode(element).className;
1123
1201
  }
1124
1202
 
1125
- function animateSetup(element, className, calculationDecorator) {
1203
+ function animateSetup(animationEvent, element, className, calculationDecorator) {
1126
1204
  var cacheKey = getCacheKey(element);
1127
1205
  var eventCacheKey = cacheKey + ' ' + className;
1128
- var stagger = {};
1129
1206
  var itemIndex = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0;
1130
1207
 
1208
+ var stagger = {};
1131
1209
  if(itemIndex > 0) {
1132
1210
  var staggerClassName = className + '-stagger';
1133
1211
  var staggerCacheKey = cacheKey + ' ' + staggerClassName;
@@ -1147,60 +1225,63 @@ angular.module('ngAnimate', ['ng'])
1147
1225
 
1148
1226
  element.addClass(className);
1149
1227
 
1228
+ var formerData = element.data(NG_ANIMATE_CSS_DATA_KEY) || {};
1229
+
1150
1230
  var timings = calculationDecorator(function() {
1151
1231
  return getElementAnimationDetails(element, eventCacheKey);
1152
1232
  });
1153
1233
 
1154
- /* there is no point in performing a reflow if the animation
1155
- timeout is empty (this would cause a flicker bug normally
1156
- in the page. There is also no point in performing an animation
1157
- that only has a delay and no duration */
1158
- var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay);
1159
- var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
1160
- if(maxDuration === 0) {
1234
+ var transitionDuration = timings.transitionDuration;
1235
+ var animationDuration = timings.animationDuration;
1236
+ if(transitionDuration === 0 && animationDuration === 0) {
1161
1237
  element.removeClass(className);
1162
1238
  return false;
1163
1239
  }
1164
1240
 
1165
- //temporarily disable the transition so that the enter styles
1166
- //don't animate twice (this is here to avoid a bug in Chrome/FF).
1167
- var activeClassName = '';
1168
- timings.transitionDuration > 0 ?
1169
- blockTransitions(element) :
1170
- blockKeyframeAnimations(element);
1171
-
1172
- forEach(className.split(' '), function(klass, i) {
1173
- activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
1174
- });
1175
-
1176
1241
  element.data(NG_ANIMATE_CSS_DATA_KEY, {
1177
- className : className,
1178
- activeClassName : activeClassName,
1179
- maxDuration : maxDuration,
1180
- maxDelay : maxDelay,
1181
- classes : className + ' ' + activeClassName,
1182
- timings : timings,
1242
+ running : formerData.running || 0,
1243
+ itemIndex : itemIndex,
1183
1244
  stagger : stagger,
1184
- itemIndex : itemIndex
1245
+ timings : timings,
1246
+ closeAnimationFn : angular.noop
1185
1247
  });
1186
1248
 
1249
+ //temporarily disable the transition so that the enter styles
1250
+ //don't animate twice (this is here to avoid a bug in Chrome/FF).
1251
+ var isCurrentlyAnimating = formerData.running > 0 || animationEvent == 'setClass';
1252
+ if(transitionDuration > 0) {
1253
+ blockTransitions(element, className, isCurrentlyAnimating);
1254
+ }
1255
+ if(animationDuration > 0) {
1256
+ blockKeyframeAnimations(element);
1257
+ }
1258
+
1187
1259
  return true;
1188
1260
  }
1189
1261
 
1190
- function blockTransitions(element) {
1191
- extractElementNode(element).style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
1262
+ function isStructuralAnimation(className) {
1263
+ return className == 'ng-enter' || className == 'ng-move' || className == 'ng-leave';
1264
+ }
1265
+
1266
+ function blockTransitions(element, className, isAnimating) {
1267
+ if(isStructuralAnimation(className) || !isAnimating) {
1268
+ extractElementNode(element).style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
1269
+ } else {
1270
+ element.addClass(NG_ANIMATE_BLOCK_CLASS_NAME);
1271
+ }
1192
1272
  }
1193
1273
 
1194
1274
  function blockKeyframeAnimations(element) {
1195
1275
  extractElementNode(element).style[ANIMATION_PROP] = 'none 0s';
1196
1276
  }
1197
1277
 
1198
- function unblockTransitions(element) {
1278
+ function unblockTransitions(element, className) {
1199
1279
  var prop = TRANSITION_PROP + PROPERTY_KEY;
1200
1280
  var node = extractElementNode(element);
1201
1281
  if(node.style[prop] && node.style[prop].length > 0) {
1202
1282
  node.style[prop] = '';
1203
1283
  }
1284
+ element.removeClass(NG_ANIMATE_BLOCK_CLASS_NAME);
1204
1285
  }
1205
1286
 
1206
1287
  function unblockKeyframeAnimations(element) {
@@ -1211,22 +1292,28 @@ angular.module('ngAnimate', ['ng'])
1211
1292
  }
1212
1293
  }
1213
1294
 
1214
- function animateRun(element, className, activeAnimationComplete) {
1215
- var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
1295
+ function animateRun(animationEvent, element, className, activeAnimationComplete) {
1216
1296
  var node = extractElementNode(element);
1297
+ var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
1217
1298
  if(node.className.indexOf(className) == -1 || !elementData) {
1218
1299
  activeAnimationComplete();
1219
1300
  return;
1220
1301
  }
1221
1302
 
1222
- var timings = elementData.timings;
1303
+ var activeClassName = '';
1304
+ forEach(className.split(' '), function(klass, i) {
1305
+ activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
1306
+ });
1307
+
1223
1308
  var stagger = elementData.stagger;
1224
- var maxDuration = elementData.maxDuration;
1225
- var activeClassName = elementData.activeClassName;
1226
- var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * ONE_SECOND;
1309
+ var timings = elementData.timings;
1310
+ var itemIndex = elementData.itemIndex;
1311
+ var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
1312
+ var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay);
1313
+ var maxDelayTime = maxDelay * ONE_SECOND;
1314
+
1227
1315
  var startTime = Date.now();
1228
1316
  var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
1229
- var itemIndex = elementData.itemIndex;
1230
1317
 
1231
1318
  var style = '', appliedStyles = [];
1232
1319
  if(timings.transitionDuration > 0) {
@@ -1268,6 +1355,13 @@ angular.module('ngAnimate', ['ng'])
1268
1355
  onEnd();
1269
1356
  activeAnimationComplete();
1270
1357
  };
1358
+
1359
+ var staggerTime = itemIndex * (Math.max(stagger.animationDelay, stagger.transitionDelay) || 0);
1360
+ var animationTime = (maxDelay + maxDuration) * CLOSING_TIME_BUFFER;
1361
+ var totalTime = (staggerTime + animationTime) * ONE_SECOND;
1362
+
1363
+ elementData.running++;
1364
+ animationCloseHandler(element, totalTime);
1271
1365
  return onEnd;
1272
1366
 
1273
1367
  // This will automatically be called by $animate so
@@ -1287,7 +1381,7 @@ angular.module('ngAnimate', ['ng'])
1287
1381
  event.stopPropagation();
1288
1382
  var ev = event.originalEvent || event;
1289
1383
  var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now();
1290
-
1384
+
1291
1385
  /* Firefox (or possibly just Gecko) likes to not round values up
1292
1386
  * when a ms measurement is used for the animation */
1293
1387
  var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
@@ -1314,28 +1408,28 @@ angular.module('ngAnimate', ['ng'])
1314
1408
  return style;
1315
1409
  }
1316
1410
 
1317
- function animateBefore(element, className, calculationDecorator) {
1318
- if(animateSetup(element, className, calculationDecorator)) {
1411
+ function animateBefore(animationEvent, element, className, calculationDecorator) {
1412
+ if(animateSetup(animationEvent, element, className, calculationDecorator)) {
1319
1413
  return function(cancelled) {
1320
1414
  cancelled && animateClose(element, className);
1321
1415
  };
1322
1416
  }
1323
1417
  }
1324
1418
 
1325
- function animateAfter(element, className, afterAnimationComplete) {
1419
+ function animateAfter(animationEvent, element, className, afterAnimationComplete) {
1326
1420
  if(element.data(NG_ANIMATE_CSS_DATA_KEY)) {
1327
- return animateRun(element, className, afterAnimationComplete);
1421
+ return animateRun(animationEvent, element, className, afterAnimationComplete);
1328
1422
  } else {
1329
1423
  animateClose(element, className);
1330
1424
  afterAnimationComplete();
1331
1425
  }
1332
1426
  }
1333
1427
 
1334
- function animate(element, className, animationComplete) {
1428
+ function animate(animationEvent, element, className, animationComplete) {
1335
1429
  //If the animateSetup function doesn't bother returning a
1336
1430
  //cancellation function then it means that there is no animation
1337
1431
  //to perform at all
1338
- var preReflowCancellation = animateBefore(element, className);
1432
+ var preReflowCancellation = animateBefore(animationEvent, element, className);
1339
1433
  if(!preReflowCancellation) {
1340
1434
  animationComplete();
1341
1435
  return;
@@ -1348,12 +1442,12 @@ angular.module('ngAnimate', ['ng'])
1348
1442
  //happen in the first place
1349
1443
  var cancel = preReflowCancellation;
1350
1444
  afterReflow(element, function() {
1351
- unblockTransitions(element);
1445
+ unblockTransitions(element, className);
1352
1446
  unblockKeyframeAnimations(element);
1353
1447
  //once the reflow is complete then we point cancel to
1354
1448
  //the new cancellation function which will remove all of the
1355
1449
  //animation properties from the active animation
1356
- cancel = animateAfter(element, className, animationComplete);
1450
+ cancel = animateAfter(animationEvent, element, className, animationComplete);
1357
1451
  });
1358
1452
 
1359
1453
  return function(cancelled) {
@@ -1363,54 +1457,59 @@ angular.module('ngAnimate', ['ng'])
1363
1457
 
1364
1458
  function animateClose(element, className) {
1365
1459
  element.removeClass(className);
1366
- element.removeData(NG_ANIMATE_CSS_DATA_KEY);
1460
+ var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
1461
+ if(data) {
1462
+ if(data.running) {
1463
+ data.running--;
1464
+ }
1465
+ if(!data.running || data.running === 0) {
1466
+ element.removeData(NG_ANIMATE_CSS_DATA_KEY);
1467
+ }
1468
+ }
1367
1469
  }
1368
1470
 
1369
1471
  return {
1370
- allowCancel : function(element, animationEvent, className) {
1371
- //always cancel the current animation if it is a
1372
- //structural animation
1373
- var oldClasses = (element.data(NG_ANIMATE_CSS_DATA_KEY) || {}).classes;
1374
- if(!oldClasses || ['enter','leave','move'].indexOf(animationEvent) >= 0) {
1375
- return true;
1376
- }
1377
-
1378
- var parentElement = element.parent();
1379
- var clone = angular.element(extractElementNode(element).cloneNode());
1380
-
1381
- //make the element super hidden and override any CSS style values
1382
- clone.attr('style','position:absolute; top:-9999px; left:-9999px');
1383
- clone.removeAttr('id');
1384
- clone.empty();
1385
-
1386
- forEach(oldClasses.split(' '), function(klass) {
1387
- clone.removeClass(klass);
1388
- });
1389
-
1390
- var suffix = animationEvent == 'addClass' ? '-add' : '-remove';
1391
- clone.addClass(suffixClasses(className, suffix));
1392
- parentElement.append(clone);
1393
-
1394
- var timings = getElementAnimationDetails(clone);
1395
- clone.remove();
1396
-
1397
- return Math.max(timings.transitionDuration, timings.animationDuration) > 0;
1398
- },
1399
-
1400
1472
  enter : function(element, animationCompleted) {
1401
- return animate(element, 'ng-enter', animationCompleted);
1473
+ return animate('enter', element, 'ng-enter', animationCompleted);
1402
1474
  },
1403
1475
 
1404
1476
  leave : function(element, animationCompleted) {
1405
- return animate(element, 'ng-leave', animationCompleted);
1477
+ return animate('leave', element, 'ng-leave', animationCompleted);
1406
1478
  },
1407
1479
 
1408
1480
  move : function(element, animationCompleted) {
1409
- return animate(element, 'ng-move', animationCompleted);
1481
+ return animate('move', element, 'ng-move', animationCompleted);
1482
+ },
1483
+
1484
+ beforeSetClass : function(element, add, remove, animationCompleted) {
1485
+ var className = suffixClasses(remove, '-remove') + ' ' +
1486
+ suffixClasses(add, '-add');
1487
+ var cancellationMethod = animateBefore('setClass', element, className, function(fn) {
1488
+ /* when classes are removed from an element then the transition style
1489
+ * that is applied is the transition defined on the element without the
1490
+ * CSS class being there. This is how CSS3 functions outside of ngAnimate.
1491
+ * http://plnkr.co/edit/j8OzgTNxHTb4n3zLyjGW?p=preview */
1492
+ var klass = element.attr('class');
1493
+ element.removeClass(remove);
1494
+ element.addClass(add);
1495
+ var timings = fn();
1496
+ element.attr('class', klass);
1497
+ return timings;
1498
+ });
1499
+
1500
+ if(cancellationMethod) {
1501
+ afterReflow(element, function() {
1502
+ unblockTransitions(element, className);
1503
+ unblockKeyframeAnimations(element);
1504
+ animationCompleted();
1505
+ });
1506
+ return cancellationMethod;
1507
+ }
1508
+ animationCompleted();
1410
1509
  },
1411
1510
 
1412
1511
  beforeAddClass : function(element, className, animationCompleted) {
1413
- var cancellationMethod = animateBefore(element, suffixClasses(className, '-add'), function(fn) {
1512
+ var cancellationMethod = animateBefore('addClass', element, suffixClasses(className, '-add'), function(fn) {
1414
1513
 
1415
1514
  /* when a CSS class is added to an element then the transition style that
1416
1515
  * is applied is the transition defined on the element when the CSS class
@@ -1424,7 +1523,7 @@ angular.module('ngAnimate', ['ng'])
1424
1523
 
1425
1524
  if(cancellationMethod) {
1426
1525
  afterReflow(element, function() {
1427
- unblockTransitions(element);
1526
+ unblockTransitions(element, className);
1428
1527
  unblockKeyframeAnimations(element);
1429
1528
  animationCompleted();
1430
1529
  });
@@ -1433,12 +1532,19 @@ angular.module('ngAnimate', ['ng'])
1433
1532
  animationCompleted();
1434
1533
  },
1435
1534
 
1535
+ setClass : function(element, add, remove, animationCompleted) {
1536
+ remove = suffixClasses(remove, '-remove');
1537
+ add = suffixClasses(add, '-add');
1538
+ var className = remove + ' ' + add;
1539
+ return animateAfter('setClass', element, className, animationCompleted);
1540
+ },
1541
+
1436
1542
  addClass : function(element, className, animationCompleted) {
1437
- return animateAfter(element, suffixClasses(className, '-add'), animationCompleted);
1543
+ return animateAfter('addClass', element, suffixClasses(className, '-add'), animationCompleted);
1438
1544
  },
1439
1545
 
1440
1546
  beforeRemoveClass : function(element, className, animationCompleted) {
1441
- var cancellationMethod = animateBefore(element, suffixClasses(className, '-remove'), function(fn) {
1547
+ var cancellationMethod = animateBefore('removeClass', element, suffixClasses(className, '-remove'), function(fn) {
1442
1548
  /* when classes are removed from an element then the transition style
1443
1549
  * that is applied is the transition defined on the element without the
1444
1550
  * CSS class being there. This is how CSS3 functions outside of ngAnimate.
@@ -1452,7 +1558,7 @@ angular.module('ngAnimate', ['ng'])
1452
1558
 
1453
1559
  if(cancellationMethod) {
1454
1560
  afterReflow(element, function() {
1455
- unblockTransitions(element);
1561
+ unblockTransitions(element, className);
1456
1562
  unblockKeyframeAnimations(element);
1457
1563
  animationCompleted();
1458
1564
  });
@@ -1462,7 +1568,7 @@ angular.module('ngAnimate', ['ng'])
1462
1568
  },
1463
1569
 
1464
1570
  removeClass : function(element, className, animationCompleted) {
1465
- return animateAfter(element, suffixClasses(className, '-remove'), animationCompleted);
1571
+ return animateAfter('removeClass', element, suffixClasses(className, '-remove'), animationCompleted);
1466
1572
  }
1467
1573
  };
1468
1574