angularjs-rails 1.2.22 → 1.2.25

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.3.0-beta.18
2
+ * @license AngularJS v1.3.0-rc.3
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -79,6 +79,16 @@
79
79
  * When the `on` expression value changes and an animation is triggered then each of the elements within
80
80
  * will all animate without the block being applied to child elements.
81
81
  *
82
+ * ## Are animations run when the application starts?
83
+ * No they are not. When an application is bootstrapped Angular will disable animations from running to avoid
84
+ * a frenzy of animations from being triggered as soon as the browser has rendered the screen. For this to work,
85
+ * Angular will wait for two digest cycles until enabling animations. From there on, any animation-triggering
86
+ * layout changes in the application will trigger animations as normal.
87
+ *
88
+ * In addition, upon bootstrap, if the routing system or any directives or load remote data (via $http) then Angular
89
+ * will automatically extend the wait time to enable animations once **all** of the outbound HTTP requests
90
+ * are complete.
91
+ *
82
92
  * <h2>CSS-defined Animations</h2>
83
93
  * The animate service will automatically apply two CSS classes to the animated element and these two CSS classes
84
94
  * are designed to contain the start and end CSS styling. Both CSS transitions and keyframe animations are supported
@@ -270,7 +280,7 @@
270
280
  *
271
281
  * Stagger animations are currently only supported within CSS-defined animations.
272
282
  *
273
- * <h2>JavaScript-defined Animations</h2>
283
+ * ## JavaScript-defined Animations
274
284
  * In the event that you do not want to use CSS3 transitions or CSS3 animations or if you wish to offer animations on browsers that do not
275
285
  * yet support CSS transitions/animations, then you can make use of JavaScript animations defined inside of your AngularJS module.
276
286
  *
@@ -338,7 +348,7 @@ angular.module('ngAnimate', ['ng'])
338
348
  var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren';
339
349
  return function(scope, element, attrs) {
340
350
  var val = attrs.ngAnimateChildren;
341
- if(angular.isString(val) && val.length === 0) { //empty attribute
351
+ if (angular.isString(val) && val.length === 0) { //empty attribute
342
352
  element.data(NG_ANIMATE_CHILDREN, true);
343
353
  } else {
344
354
  scope.$watch(val, function(value) {
@@ -372,6 +382,7 @@ angular.module('ngAnimate', ['ng'])
372
382
  var noop = angular.noop;
373
383
  var forEach = angular.forEach;
374
384
  var selectors = $animateProvider.$$selectors;
385
+ var isArray = angular.isArray;
375
386
 
376
387
  var ELEMENT_NODE = 1;
377
388
  var NG_ANIMATE_STATE = '$$ngAnimateState';
@@ -382,7 +393,7 @@ angular.module('ngAnimate', ['ng'])
382
393
  function extractElementNode(element) {
383
394
  for(var i = 0; i < element.length; i++) {
384
395
  var elm = element[i];
385
- if(elm.nodeType == ELEMENT_NODE) {
396
+ if (elm.nodeType == ELEMENT_NODE) {
386
397
  return elm;
387
398
  }
388
399
  }
@@ -400,24 +411,38 @@ angular.module('ngAnimate', ['ng'])
400
411
  return extractElementNode(elm1) == extractElementNode(elm2);
401
412
  }
402
413
 
403
- $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document',
404
- function($delegate, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document) {
414
+ $provide.decorator('$animate',
415
+ ['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', '$templateRequest',
416
+ function($delegate, $$q, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document, $templateRequest) {
405
417
 
406
- var globalAnimationCounter = 0;
407
418
  $rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
408
419
 
409
- // disable animations during bootstrap, but once we bootstrapped, wait again
410
- // for another digest until enabling animations. The reason why we digest twice
411
- // is because all structural animations (enter, leave and move) all perform a
412
- // post digest operation before animating. If we only wait for a single digest
413
- // to pass then the structural animation would render its animation on page load.
414
- // (which is what we're trying to avoid when the application first boots up.)
415
- $rootScope.$$postDigest(function() {
416
- $rootScope.$$postDigest(function() {
417
- rootAnimateState.running = false;
418
- });
419
- });
420
+ // Wait until all directive and route-related templates are downloaded and
421
+ // compiled. The $templateRequest.totalPendingRequests variable keeps track of
422
+ // all of the remote templates being currently downloaded. If there are no
423
+ // templates currently downloading then the watcher will still fire anyway.
424
+ var deregisterWatch = $rootScope.$watch(
425
+ function() { return $templateRequest.totalPendingRequests; },
426
+ function(val, oldVal) {
427
+ if (val !== 0) return;
428
+ deregisterWatch();
429
+
430
+ // Now that all templates have been downloaded, $animate will wait until
431
+ // the post digest queue is empty before enabling animations. By having two
432
+ // calls to $postDigest calls we can ensure that the flag is enabled at the
433
+ // very end of the post digest queue. Since all of the animations in $animate
434
+ // use $postDigest, it's important that the code below executes at the end.
435
+ // This basically means that the page is fully downloaded and compiled before
436
+ // any animations are triggered.
437
+ $rootScope.$$postDigest(function() {
438
+ $rootScope.$$postDigest(function() {
439
+ rootAnimateState.running = false;
440
+ });
441
+ });
442
+ }
443
+ );
420
444
 
445
+ var globalAnimationCounter = 0;
421
446
  var classNameFilter = $animateProvider.classNameFilter();
422
447
  var isAnimatableClassName = !classNameFilter
423
448
  ? function() { return true; }
@@ -425,20 +450,81 @@ angular.module('ngAnimate', ['ng'])
425
450
  return classNameFilter.test(className);
426
451
  };
427
452
 
428
- function blockElementAnimations(element) {
453
+ function classBasedAnimationsBlocked(element, setter) {
429
454
  var data = element.data(NG_ANIMATE_STATE) || {};
430
- data.running = true;
431
- element.data(NG_ANIMATE_STATE, data);
455
+ if (setter) {
456
+ data.running = true;
457
+ data.structural = true;
458
+ element.data(NG_ANIMATE_STATE, data);
459
+ }
460
+ return data.disabled || (data.running && data.structural);
432
461
  }
433
462
 
434
463
  function runAnimationPostDigest(fn) {
435
- var cancelFn;
436
- $rootScope.$$postDigest(function() {
437
- cancelFn = fn();
438
- });
439
- return function() {
464
+ var cancelFn, defer = $$q.defer();
465
+ defer.promise.$$cancelFn = function() {
440
466
  cancelFn && cancelFn();
441
467
  };
468
+ $rootScope.$$postDigest(function() {
469
+ cancelFn = fn(function() {
470
+ defer.resolve();
471
+ });
472
+ });
473
+ return defer.promise;
474
+ }
475
+
476
+ function resolveElementClasses(element, cache, runningAnimations) {
477
+ runningAnimations = runningAnimations || {};
478
+ var map = {};
479
+
480
+ forEach(cache.add, function(className) {
481
+ if (className && className.length) {
482
+ map[className] = map[className] || 0;
483
+ map[className]++;
484
+ }
485
+ });
486
+
487
+ forEach(cache.remove, function(className) {
488
+ if (className && className.length) {
489
+ map[className] = map[className] || 0;
490
+ map[className]--;
491
+ }
492
+ });
493
+
494
+ var lookup = [];
495
+ forEach(runningAnimations, function(data, selector) {
496
+ forEach(selector.split(' '), function(s) {
497
+ lookup[s]=data;
498
+ });
499
+ });
500
+
501
+ var toAdd = [], toRemove = [];
502
+ forEach(map, function(status, className) {
503
+ var hasClass = angular.$$hasClass(element[0], className);
504
+ var matchingAnimation = lookup[className] || {};
505
+
506
+ // When addClass and removeClass is called then $animate will check to
507
+ // see if addClass and removeClass cancel each other out. When there are
508
+ // more calls to removeClass than addClass then the count falls below 0
509
+ // and then the removeClass animation will be allowed. Otherwise if the
510
+ // count is above 0 then that means an addClass animation will commence.
511
+ // Once an animation is allowed then the code will also check to see if
512
+ // there exists any on-going animation that is already adding or remvoing
513
+ // the matching CSS class.
514
+ if (status < 0) {
515
+ //does it have the class or will it have the class
516
+ if (hasClass || matchingAnimation.event == 'addClass') {
517
+ toRemove.push(className);
518
+ }
519
+ } else if (status > 0) {
520
+ //is the class missing or will it be removed?
521
+ if (!hasClass || matchingAnimation.event == 'removeClass') {
522
+ toAdd.push(className);
523
+ }
524
+ }
525
+ });
526
+
527
+ return (toAdd.length + toRemove.length) > 0 && [toAdd.join(' '), toRemove.join(' ')];
442
528
  }
443
529
 
444
530
  function lookup(name) {
@@ -462,7 +548,7 @@ angular.module('ngAnimate', ['ng'])
462
548
  for(var i=0; i < classes.length; i++) {
463
549
  var klass = classes[i],
464
550
  selectorFactoryName = selectors[klass];
465
- if(selectorFactoryName && !flagMap[klass]) {
551
+ if (selectorFactoryName && !flagMap[klass]) {
466
552
  matches.push($injector.get(selectorFactoryName));
467
553
  flagMap[klass] = true;
468
554
  }
@@ -475,25 +561,34 @@ angular.module('ngAnimate', ['ng'])
475
561
  //transcluded directives may sometimes fire an animation using only comment nodes
476
562
  //best to catch this early on to prevent any animation operations from occurring
477
563
  var node = element[0];
478
- if(!node) {
564
+ if (!node) {
479
565
  return;
480
566
  }
481
567
 
568
+ var classNameAdd;
569
+ var classNameRemove;
570
+ if (isArray(className)) {
571
+ classNameAdd = className[0];
572
+ classNameRemove = className[1];
573
+ if (!classNameAdd) {
574
+ className = classNameRemove;
575
+ animationEvent = 'removeClass';
576
+ } else if (!classNameRemove) {
577
+ className = classNameAdd;
578
+ animationEvent = 'addClass';
579
+ } else {
580
+ className = classNameAdd + ' ' + classNameRemove;
581
+ }
582
+ }
583
+
482
584
  var isSetClassOperation = animationEvent == 'setClass';
483
585
  var isClassBased = isSetClassOperation ||
484
586
  animationEvent == 'addClass' ||
485
587
  animationEvent == 'removeClass';
486
588
 
487
- var classNameAdd, classNameRemove;
488
- if(angular.isArray(className)) {
489
- classNameAdd = className[0];
490
- classNameRemove = className[1];
491
- className = classNameAdd + ' ' + classNameRemove;
492
- }
493
-
494
589
  var currentClassName = element.attr('class');
495
590
  var classes = currentClassName + ' ' + className;
496
- if(!isAnimatableClassName(classes)) {
591
+ if (!isAnimatableClassName(classes)) {
497
592
  return;
498
593
  }
499
594
 
@@ -507,7 +602,7 @@ angular.module('ngAnimate', ['ng'])
507
602
  var animationLookup = (' ' + classes).replace(/\s+/g,'.');
508
603
  forEach(lookup(animationLookup), function(animationFactory) {
509
604
  var created = registerAnimation(animationFactory, animationEvent);
510
- if(!created && isSetClassOperation) {
605
+ if (!created && isSetClassOperation) {
511
606
  registerAnimation(animationFactory, 'addClass');
512
607
  registerAnimation(animationFactory, 'removeClass');
513
608
  }
@@ -516,8 +611,8 @@ angular.module('ngAnimate', ['ng'])
516
611
  function registerAnimation(animationFactory, event) {
517
612
  var afterFn = animationFactory[event];
518
613
  var beforeFn = animationFactory['before' + event.charAt(0).toUpperCase() + event.substr(1)];
519
- if(afterFn || beforeFn) {
520
- if(event == 'leave') {
614
+ if (afterFn || beforeFn) {
615
+ if (event == 'leave') {
521
616
  beforeFn = afterFn;
522
617
  //when set as null then animation knows to skip this phase
523
618
  afterFn = null;
@@ -540,9 +635,9 @@ angular.module('ngAnimate', ['ng'])
540
635
 
541
636
  var count = 0;
542
637
  function afterAnimationComplete(index) {
543
- if(cancellations) {
638
+ if (cancellations) {
544
639
  (cancellations[index] || noop)();
545
- if(++count < animations.length) return;
640
+ if (++count < animations.length) return;
546
641
  cancellations = null;
547
642
  }
548
643
  allCompleteFn();
@@ -571,7 +666,7 @@ angular.module('ngAnimate', ['ng'])
571
666
  }
572
667
  });
573
668
 
574
- if(cancellations && cancellations.length === 0) {
669
+ if (cancellations && cancellations.length === 0) {
575
670
  allCompleteFn();
576
671
  }
577
672
  }
@@ -597,13 +692,13 @@ angular.module('ngAnimate', ['ng'])
597
692
  });
598
693
  },
599
694
  cancel : function() {
600
- if(beforeCancel) {
695
+ if (beforeCancel) {
601
696
  forEach(beforeCancel, function(cancelFn) {
602
697
  (cancelFn || noop)(true);
603
698
  });
604
699
  beforeComplete(true);
605
700
  }
606
- if(afterCancel) {
701
+ if (afterCancel) {
607
702
  forEach(afterCancel, function(cancelFn) {
608
703
  (cancelFn || noop)(true);
609
704
  });
@@ -616,7 +711,7 @@ angular.module('ngAnimate', ['ng'])
616
711
  /**
617
712
  * @ngdoc service
618
713
  * @name $animate
619
- * @kind function
714
+ * @kind object
620
715
  *
621
716
  * @description
622
717
  * The `$animate` service provides animation detection support while performing DOM operations (enter, leave and move) as well as during addClass and removeClass operations.
@@ -630,6 +725,46 @@ angular.module('ngAnimate', ['ng'])
630
725
  * Requires the {@link ngAnimate `ngAnimate`} module to be installed.
631
726
  *
632
727
  * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
728
+ * ## Callback Promises
729
+ * With AngularJS 1.3, each of the animation methods, on the `$animate` service, return a promise when called. The
730
+ * promise itself is then resolved once the animation has completed itself, has been cancelled or has been
731
+ * skipped due to animations being disabled. (Note that even if the animation is cancelled it will still
732
+ * call the resolve function of the animation.)
733
+ *
734
+ * ```js
735
+ * $animate.enter(element, container).then(function() {
736
+ * //...this is called once the animation is complete...
737
+ * });
738
+ * ```
739
+ *
740
+ * Also note that, due to the nature of the callback promise, if any Angular-specific code (like changing the scope,
741
+ * location of the page, etc...) is executed within the callback promise then be sure to wrap the code using
742
+ * `$scope.$apply(...)`;
743
+ *
744
+ * ```js
745
+ * $animate.leave(element).then(function() {
746
+ * $scope.$apply(function() {
747
+ * $location.path('/new-page');
748
+ * });
749
+ * });
750
+ * ```
751
+ *
752
+ * An animation can also be cancelled by calling the `$animate.cancel(promise)` method with the provided
753
+ * promise that was returned when the animation was started.
754
+ *
755
+ * ```js
756
+ * var promise = $animate.addClass(element, 'super-long-animation').then(function() {
757
+ * //this will still be called even if cancelled
758
+ * });
759
+ *
760
+ * element.on('click', function() {
761
+ * //tooo lazy to wait for the animation to end
762
+ * $animate.cancel(promise);
763
+ * });
764
+ * ```
765
+ *
766
+ * (Keep in mind that the promise cancellation is unique to `$animate` since promises in
767
+ * general cannot be cancelled.)
633
768
  *
634
769
  */
635
770
  return {
@@ -658,23 +793,22 @@ angular.module('ngAnimate', ['ng'])
658
793
  * | 10. the .ng-enter-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-enter ng-enter-active" |
659
794
  * | 11. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-enter ng-enter-active" |
660
795
  * | 12. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
661
- * | 13. The doneCallback() callback is fired (if provided) | class="my-animation" |
796
+ * | 13. The returned promise is resolved. | class="my-animation" |
662
797
  *
663
798
  * @param {DOMElement} element the element that will be the focus of the enter animation
664
799
  * @param {DOMElement} parentElement the parent element of the element that will be the focus of the enter animation
665
800
  * @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation
666
- * @param {function()=} doneCallback the callback function that will be called once the animation is complete
667
- * @return {function} the animation cancellation function
801
+ * @return {Promise} the animation callback promise
668
802
  */
669
- enter : function(element, parentElement, afterElement, doneCallback) {
803
+ enter : function(element, parentElement, afterElement) {
670
804
  element = angular.element(element);
671
805
  parentElement = prepareElement(parentElement);
672
806
  afterElement = prepareElement(afterElement);
673
807
 
674
- blockElementAnimations(element);
808
+ classBasedAnimationsBlocked(element, true);
675
809
  $delegate.enter(element, parentElement, afterElement);
676
- return runAnimationPostDigest(function() {
677
- return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, doneCallback);
810
+ return runAnimationPostDigest(function(done) {
811
+ return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, done);
678
812
  });
679
813
  },
680
814
 
@@ -703,22 +837,21 @@ angular.module('ngAnimate', ['ng'])
703
837
  * | 10. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-leave ng-leave-active" |
704
838
  * | 11. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
705
839
  * | 12. The element is removed from the DOM | ... |
706
- * | 13. The doneCallback() callback is fired (if provided) | ... |
840
+ * | 13. The returned promise is resolved. | ... |
707
841
  *
708
842
  * @param {DOMElement} element the element that will be the focus of the leave animation
709
- * @param {function()=} doneCallback the callback function that will be called once the animation is complete
710
- * @return {function} the animation cancellation function
843
+ * @return {Promise} the animation callback promise
711
844
  */
712
- leave : function(element, doneCallback) {
845
+ leave : function(element) {
713
846
  element = angular.element(element);
714
847
 
715
848
  cancelChildAnimations(element);
716
- blockElementAnimations(element);
849
+ classBasedAnimationsBlocked(element, true);
717
850
  this.enabled(false, element);
718
- return runAnimationPostDigest(function() {
851
+ return runAnimationPostDigest(function(done) {
719
852
  return performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
720
853
  $delegate.leave(element);
721
- }, doneCallback);
854
+ }, done);
722
855
  });
723
856
  },
724
857
 
@@ -748,24 +881,23 @@ angular.module('ngAnimate', ['ng'])
748
881
  * | 10. the .ng-move-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-move ng-move-active" |
749
882
  * | 11. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-move ng-move-active" |
750
883
  * | 12. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
751
- * | 13. The doneCallback() callback is fired (if provided) | class="my-animation" |
884
+ * | 13. The returned promise is resolved. | class="my-animation" |
752
885
  *
753
886
  * @param {DOMElement} element the element that will be the focus of the move animation
754
887
  * @param {DOMElement} parentElement the parentElement element of the element that will be the focus of the move animation
755
888
  * @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation
756
- * @param {function()=} doneCallback the callback function that will be called once the animation is complete
757
- * @return {function} the animation cancellation function
889
+ * @return {Promise} the animation callback promise
758
890
  */
759
- move : function(element, parentElement, afterElement, doneCallback) {
891
+ move : function(element, parentElement, afterElement) {
760
892
  element = angular.element(element);
761
893
  parentElement = prepareElement(parentElement);
762
894
  afterElement = prepareElement(afterElement);
763
895
 
764
896
  cancelChildAnimations(element);
765
- blockElementAnimations(element);
897
+ classBasedAnimationsBlocked(element, true);
766
898
  $delegate.move(element, parentElement, afterElement);
767
- return runAnimationPostDigest(function() {
768
- return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, doneCallback);
899
+ return runAnimationPostDigest(function(done) {
900
+ return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, done);
769
901
  });
770
902
  },
771
903
 
@@ -792,19 +924,14 @@ angular.module('ngAnimate', ['ng'])
792
924
  * | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation super super-add super-add-active" |
793
925
  * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation super" |
794
926
  * | 9. The super class is kept on the element | class="my-animation super" |
795
- * | 10. The doneCallback() callback is fired (if provided) | class="my-animation super" |
927
+ * | 10. The returned promise is resolved. | class="my-animation super" |
796
928
  *
797
929
  * @param {DOMElement} element the element that will be animated
798
930
  * @param {string} className the CSS class that will be added to the element and then animated
799
- * @param {function()=} doneCallback the callback function that will be called once the animation is complete
800
- * @return {function} the animation cancellation function
931
+ * @return {Promise} the animation callback promise
801
932
  */
802
- addClass : function(element, className, doneCallback) {
803
- element = angular.element(element);
804
- element = stripCommentsFromElement(element);
805
- return performAnimation('addClass', className, element, null, null, function() {
806
- $delegate.addClass(element, className);
807
- }, doneCallback);
933
+ addClass : function(element, className) {
934
+ return this.setClass(element, className, []);
808
935
  },
809
936
 
810
937
  /**
@@ -829,20 +956,15 @@ angular.module('ngAnimate', ['ng'])
829
956
  * | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation super ng-animate super-remove" |
830
957
  * | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate super-remove super-remove-active" |
831
958
  * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
832
- * | 9. The doneCallback() callback is fired (if provided) | class="my-animation" |
959
+ * | 9. The returned promise is resolved. | class="my-animation" |
833
960
  *
834
961
  *
835
962
  * @param {DOMElement} element the element that will be animated
836
963
  * @param {string} className the CSS class that will be animated and then removed from the element
837
- * @param {function()=} doneCallback the callback function that will be called once the animation is complete
838
- * @return {function} the animation cancellation function
964
+ * @return {Promise} the animation callback promise
839
965
  */
840
- removeClass : function(element, className, doneCallback) {
841
- element = angular.element(element);
842
- element = stripCommentsFromElement(element);
843
- return performAnimation('removeClass', className, element, null, null, function() {
844
- $delegate.removeClass(element, className);
845
- }, doneCallback);
966
+ removeClass : function(element, className) {
967
+ return this.setClass(element, [], className);
846
968
  },
847
969
 
848
970
  /**
@@ -862,23 +984,68 @@ angular.module('ngAnimate', ['ng'])
862
984
  * | 5. the .on, .on-add-active and .off-remove-active classes are added and .off is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active” |
863
985
  * | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active" |
864
986
  * | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active" |
865
- * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
866
- * | 9. The doneCallback() callback is fired (if provided) | class="my-animation" |
987
+ * | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation on" |
988
+ * | 9. The returned promise is resolved. | class="my-animation on" |
867
989
  *
868
990
  * @param {DOMElement} element the element which will have its CSS classes changed
869
991
  * removed from it
870
992
  * @param {string} add the CSS classes which will be added to the element
871
993
  * @param {string} remove the CSS class which will be removed from the element
872
- * @param {function=} done the callback function (if provided) that will be fired after the
873
994
  * CSS classes have been set on the element
874
- * @return {function} the animation cancellation function
995
+ * @return {Promise} the animation callback promise
875
996
  */
876
- setClass : function(element, add, remove, doneCallback) {
997
+ setClass : function(element, add, remove) {
998
+ var STORAGE_KEY = '$$animateClasses';
877
999
  element = angular.element(element);
878
1000
  element = stripCommentsFromElement(element);
879
- return performAnimation('setClass', [add, remove], element, null, null, function() {
880
- $delegate.setClass(element, add, remove);
881
- }, doneCallback);
1001
+
1002
+ if (classBasedAnimationsBlocked(element)) {
1003
+ return $delegate.setClass(element, add, remove);
1004
+ }
1005
+
1006
+ add = isArray(add) ? add : add.split(' ');
1007
+ remove = isArray(remove) ? remove : remove.split(' ');
1008
+
1009
+ var cache = element.data(STORAGE_KEY);
1010
+ if (cache) {
1011
+ cache.add = cache.add.concat(add);
1012
+ cache.remove = cache.remove.concat(remove);
1013
+
1014
+ //the digest cycle will combine all the animations into one function
1015
+ return cache.promise;
1016
+ } else {
1017
+ element.data(STORAGE_KEY, cache = {
1018
+ add : add,
1019
+ remove : remove
1020
+ });
1021
+ }
1022
+
1023
+ return cache.promise = runAnimationPostDigest(function(done) {
1024
+ var cache = element.data(STORAGE_KEY);
1025
+ element.removeData(STORAGE_KEY);
1026
+
1027
+ var state = element.data(NG_ANIMATE_STATE) || {};
1028
+ var classes = resolveElementClasses(element, cache, state.active);
1029
+ return !classes
1030
+ ? done()
1031
+ : performAnimation('setClass', classes, element, null, null, function() {
1032
+ $delegate.setClass(element, classes[0], classes[1]);
1033
+ }, done);
1034
+ });
1035
+ },
1036
+
1037
+ /**
1038
+ * @ngdoc method
1039
+ * @name $animate#cancel
1040
+ * @kind function
1041
+ *
1042
+ * @param {Promise} animationPromise The animation promise that is returned when an animation is started.
1043
+ *
1044
+ * @description
1045
+ * Cancels the provided animation.
1046
+ */
1047
+ cancel : function(promise) {
1048
+ promise.$$cancelFn();
882
1049
  },
883
1050
 
884
1051
  /**
@@ -897,7 +1064,7 @@ angular.module('ngAnimate', ['ng'])
897
1064
  enabled : function(value, element) {
898
1065
  switch(arguments.length) {
899
1066
  case 2:
900
- if(value) {
1067
+ if (value) {
901
1068
  cleanup(element);
902
1069
  } else {
903
1070
  var data = element.data(NG_ANIMATE_STATE) || {};
@@ -929,7 +1096,7 @@ angular.module('ngAnimate', ['ng'])
929
1096
 
930
1097
  var noopCancel = noop;
931
1098
  var runner = animationRunner(element, animationEvent, className);
932
- if(!runner) {
1099
+ if (!runner) {
933
1100
  fireDOMOperation();
934
1101
  fireBeforeCallbackAsync();
935
1102
  fireAfterCallbackAsync();
@@ -937,6 +1104,7 @@ angular.module('ngAnimate', ['ng'])
937
1104
  return noopCancel;
938
1105
  }
939
1106
 
1107
+ animationEvent = runner.event;
940
1108
  className = runner.className;
941
1109
  var elementEvents = angular.element._data(runner.node);
942
1110
  elementEvents = elementEvents && elementEvents.events;
@@ -945,25 +1113,11 @@ angular.module('ngAnimate', ['ng'])
945
1113
  parentElement = afterElement ? afterElement.parent() : element.parent();
946
1114
  }
947
1115
 
948
- var ngAnimateState = element.data(NG_ANIMATE_STATE) || {};
949
- var runningAnimations = ngAnimateState.active || {};
950
- var totalActiveAnimations = ngAnimateState.totalActive || 0;
951
- var lastAnimation = ngAnimateState.last;
952
-
953
- //only allow animations if the currently running animation is not structural
954
- //or if there is no animation running at all
955
- var skipAnimations;
956
- if (runner.isClassBased) {
957
- skipAnimations = ngAnimateState.running ||
958
- ngAnimateState.disabled ||
959
- (lastAnimation && !lastAnimation.isClassBased);
960
- }
961
-
962
1116
  //skip the animation if animations are disabled, a parent is already being animated,
963
1117
  //the element is not currently attached to the document body or then completely close
964
1118
  //the animation if any matching animations are not found at all.
965
1119
  //NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case an animation was found.
966
- if (skipAnimations || animationsDisabled(element, parentElement)) {
1120
+ if (animationsDisabled(element, parentElement)) {
967
1121
  fireDOMOperation();
968
1122
  fireBeforeCallbackAsync();
969
1123
  fireAfterCallbackAsync();
@@ -971,11 +1125,16 @@ angular.module('ngAnimate', ['ng'])
971
1125
  return noopCancel;
972
1126
  }
973
1127
 
1128
+ var ngAnimateState = element.data(NG_ANIMATE_STATE) || {};
1129
+ var runningAnimations = ngAnimateState.active || {};
1130
+ var totalActiveAnimations = ngAnimateState.totalActive || 0;
1131
+ var lastAnimation = ngAnimateState.last;
974
1132
  var skipAnimation = false;
975
- if(totalActiveAnimations > 0) {
1133
+
1134
+ if (totalActiveAnimations > 0) {
976
1135
  var animationsToCancel = [];
977
- if(!runner.isClassBased) {
978
- if(animationEvent == 'leave' && runningAnimations['ng-leave']) {
1136
+ if (!runner.isClassBased) {
1137
+ if (animationEvent == 'leave' && runningAnimations['ng-leave']) {
979
1138
  skipAnimation = true;
980
1139
  } else {
981
1140
  //cancel all animations when a structural animation takes place
@@ -985,13 +1144,13 @@ angular.module('ngAnimate', ['ng'])
985
1144
  ngAnimateState = {};
986
1145
  cleanup(element, true);
987
1146
  }
988
- } else if(lastAnimation.event == 'setClass') {
1147
+ } else if (lastAnimation.event == 'setClass') {
989
1148
  animationsToCancel.push(lastAnimation);
990
1149
  cleanup(element, className);
991
1150
  }
992
- else if(runningAnimations[className]) {
1151
+ else if (runningAnimations[className]) {
993
1152
  var current = runningAnimations[className];
994
- if(current.event == animationEvent) {
1153
+ if (current.event == animationEvent) {
995
1154
  skipAnimation = true;
996
1155
  } else {
997
1156
  animationsToCancel.push(current);
@@ -999,21 +1158,18 @@ angular.module('ngAnimate', ['ng'])
999
1158
  }
1000
1159
  }
1001
1160
 
1002
- if(animationsToCancel.length > 0) {
1161
+ if (animationsToCancel.length > 0) {
1003
1162
  forEach(animationsToCancel, function(operation) {
1004
1163
  operation.cancel();
1005
1164
  });
1006
1165
  }
1007
1166
  }
1008
1167
 
1009
- runningAnimations = ngAnimateState.active || {};
1010
- totalActiveAnimations = ngAnimateState.totalActive || 0;
1011
-
1012
- if(runner.isClassBased && !runner.isSetClassOperation && !skipAnimation) {
1168
+ if (runner.isClassBased && !runner.isSetClassOperation && !skipAnimation) {
1013
1169
  skipAnimation = (animationEvent == 'addClass') == element.hasClass(className); //opposite of XOR
1014
1170
  }
1015
1171
 
1016
- if(skipAnimation) {
1172
+ if (skipAnimation) {
1017
1173
  fireDOMOperation();
1018
1174
  fireBeforeCallbackAsync();
1019
1175
  fireAfterCallbackAsync();
@@ -1021,16 +1177,19 @@ angular.module('ngAnimate', ['ng'])
1021
1177
  return noopCancel;
1022
1178
  }
1023
1179
 
1024
- if(animationEvent == 'leave') {
1180
+ runningAnimations = ngAnimateState.active || {};
1181
+ totalActiveAnimations = ngAnimateState.totalActive || 0;
1182
+
1183
+ if (animationEvent == 'leave') {
1025
1184
  //there's no need to ever remove the listener since the element
1026
1185
  //will be removed (destroyed) after the leave animation ends or
1027
1186
  //is cancelled midway
1028
1187
  element.one('$destroy', function(e) {
1029
1188
  var element = angular.element(this);
1030
1189
  var state = element.data(NG_ANIMATE_STATE);
1031
- if(state) {
1190
+ if (state) {
1032
1191
  var activeLeaveAnimation = state.active['ng-leave'];
1033
- if(activeLeaveAnimation) {
1192
+ if (activeLeaveAnimation) {
1034
1193
  activeLeaveAnimation.cancel();
1035
1194
  cleanup(element, 'ng-leave');
1036
1195
  }
@@ -1063,7 +1222,7 @@ angular.module('ngAnimate', ['ng'])
1063
1222
  (runner.isClassBased && data.active[className].event != animationEvent);
1064
1223
 
1065
1224
  fireDOMOperation();
1066
- if(cancelled === true) {
1225
+ if (cancelled === true) {
1067
1226
  closeAnimation();
1068
1227
  } else {
1069
1228
  fireAfterCallbackAsync();
@@ -1075,7 +1234,7 @@ angular.module('ngAnimate', ['ng'])
1075
1234
 
1076
1235
  function fireDOMCallback(animationPhase) {
1077
1236
  var eventName = '$animate:' + animationPhase;
1078
- if(elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) {
1237
+ if (elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) {
1079
1238
  $$asyncCallback(function() {
1080
1239
  element.triggerHandler(eventName, {
1081
1240
  event : animationEvent,
@@ -1095,37 +1254,33 @@ angular.module('ngAnimate', ['ng'])
1095
1254
 
1096
1255
  function fireDoneCallbackAsync() {
1097
1256
  fireDOMCallback('close');
1098
- if(doneCallback) {
1099
- $$asyncCallback(function() {
1100
- doneCallback();
1101
- });
1102
- }
1257
+ doneCallback();
1103
1258
  }
1104
1259
 
1105
1260
  //it is less complicated to use a flag than managing and canceling
1106
1261
  //timeouts containing multiple callbacks.
1107
1262
  function fireDOMOperation() {
1108
- if(!fireDOMOperation.hasBeenRun) {
1263
+ if (!fireDOMOperation.hasBeenRun) {
1109
1264
  fireDOMOperation.hasBeenRun = true;
1110
1265
  domOperation();
1111
1266
  }
1112
1267
  }
1113
1268
 
1114
1269
  function closeAnimation() {
1115
- if(!closeAnimation.hasBeenRun) {
1270
+ if (!closeAnimation.hasBeenRun) {
1116
1271
  closeAnimation.hasBeenRun = true;
1117
1272
  var data = element.data(NG_ANIMATE_STATE);
1118
- if(data) {
1273
+ if (data) {
1119
1274
  /* only structural animations wait for reflow before removing an
1120
1275
  animation, but class-based animations don't. An example of this
1121
1276
  failing would be when a parent HTML tag has a ng-class attribute
1122
1277
  causing ALL directives below to skip animations during the digest */
1123
- if(runner && runner.isClassBased) {
1278
+ if (runner && runner.isClassBased) {
1124
1279
  cleanup(element, className);
1125
1280
  } else {
1126
1281
  $$asyncCallback(function() {
1127
1282
  var data = element.data(NG_ANIMATE_STATE) || {};
1128
- if(localAnimationCount == data.index) {
1283
+ if (localAnimationCount == data.index) {
1129
1284
  cleanup(element, className, animationEvent);
1130
1285
  }
1131
1286
  });
@@ -1146,7 +1301,7 @@ angular.module('ngAnimate', ['ng'])
1146
1301
  forEach(nodes, function(element) {
1147
1302
  element = angular.element(element);
1148
1303
  var data = element.data(NG_ANIMATE_STATE);
1149
- if(data && data.active) {
1304
+ if (data && data.active) {
1150
1305
  forEach(data.active, function(runner) {
1151
1306
  runner.cancel();
1152
1307
  });
@@ -1156,21 +1311,21 @@ angular.module('ngAnimate', ['ng'])
1156
1311
  }
1157
1312
 
1158
1313
  function cleanup(element, className) {
1159
- if(isMatchingElement(element, $rootElement)) {
1160
- if(!rootAnimateState.disabled) {
1314
+ if (isMatchingElement(element, $rootElement)) {
1315
+ if (!rootAnimateState.disabled) {
1161
1316
  rootAnimateState.running = false;
1162
1317
  rootAnimateState.structural = false;
1163
1318
  }
1164
- } else if(className) {
1319
+ } else if (className) {
1165
1320
  var data = element.data(NG_ANIMATE_STATE) || {};
1166
1321
 
1167
1322
  var removeAnimations = className === true;
1168
- if(!removeAnimations && data.active && data.active[className]) {
1323
+ if (!removeAnimations && data.active && data.active[className]) {
1169
1324
  data.totalActive--;
1170
1325
  delete data.active[className];
1171
1326
  }
1172
1327
 
1173
- if(removeAnimations || !data.totalActive) {
1328
+ if (removeAnimations || !data.totalActive) {
1174
1329
  element.removeClass(NG_ANIMATE_CLASS_NAME);
1175
1330
  element.removeData(NG_ANIMATE_STATE);
1176
1331
  }
@@ -1209,7 +1364,7 @@ angular.module('ngAnimate', ['ng'])
1209
1364
  //it will be discarded and all child animations will be restricted
1210
1365
  if (allowChildAnimations !== false) {
1211
1366
  var animateChildrenFlag = parentElement.data(NG_ANIMATE_CHILDREN);
1212
- if(angular.isDefined(animateChildrenFlag)) {
1367
+ if (angular.isDefined(animateChildrenFlag)) {
1213
1368
  allowChildAnimations = animateChildrenFlag;
1214
1369
  }
1215
1370
  }
@@ -1259,6 +1414,7 @@ angular.module('ngAnimate', ['ng'])
1259
1414
  var PROPERTY_KEY = 'Property';
1260
1415
  var DELAY_KEY = 'Delay';
1261
1416
  var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
1417
+ var ANIMATION_PLAYSTATE_KEY = 'PlayState';
1262
1418
  var NG_ANIMATE_PARENT_KEY = '$$ngAnimateKey';
1263
1419
  var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data';
1264
1420
  var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
@@ -1270,7 +1426,7 @@ angular.module('ngAnimate', ['ng'])
1270
1426
  var animationReflowQueue = [];
1271
1427
  var cancelAnimationReflow;
1272
1428
  function afterReflow(element, callback) {
1273
- if(cancelAnimationReflow) {
1429
+ if (cancelAnimationReflow) {
1274
1430
  cancelAnimationReflow();
1275
1431
  }
1276
1432
  animationReflowQueue.push(callback);
@@ -1299,7 +1455,7 @@ angular.module('ngAnimate', ['ng'])
1299
1455
  //but it may not need to cancel out the existing timeout
1300
1456
  //if the timestamp is less than the previous one
1301
1457
  var futureTimestamp = Date.now() + totalTime;
1302
- if(futureTimestamp <= closingTimestamp) {
1458
+ if (futureTimestamp <= closingTimestamp) {
1303
1459
  return;
1304
1460
  }
1305
1461
 
@@ -1315,7 +1471,7 @@ angular.module('ngAnimate', ['ng'])
1315
1471
  function closeAllAnimations(elements) {
1316
1472
  forEach(elements, function(element) {
1317
1473
  var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
1318
- if(elementData) {
1474
+ if (elementData) {
1319
1475
  forEach(elementData.closeAnimationFns, function(fn) {
1320
1476
  fn();
1321
1477
  });
@@ -1325,56 +1481,42 @@ angular.module('ngAnimate', ['ng'])
1325
1481
 
1326
1482
  function getElementAnimationDetails(element, cacheKey) {
1327
1483
  var data = cacheKey ? lookupCache[cacheKey] : null;
1328
- if(!data) {
1484
+ if (!data) {
1329
1485
  var transitionDuration = 0;
1330
1486
  var transitionDelay = 0;
1331
1487
  var animationDuration = 0;
1332
1488
  var animationDelay = 0;
1333
- var transitionDelayStyle;
1334
- var animationDelayStyle;
1335
- var transitionDurationStyle;
1336
- var transitionPropertyStyle;
1337
1489
 
1338
1490
  //we want all the styles defined before and after
1339
1491
  forEach(element, function(element) {
1340
1492
  if (element.nodeType == ELEMENT_NODE) {
1341
1493
  var elementStyles = $window.getComputedStyle(element) || {};
1342
1494
 
1343
- transitionDurationStyle = elementStyles[TRANSITION_PROP + DURATION_KEY];
1344
-
1495
+ var transitionDurationStyle = elementStyles[TRANSITION_PROP + DURATION_KEY];
1345
1496
  transitionDuration = Math.max(parseMaxTime(transitionDurationStyle), transitionDuration);
1346
1497
 
1347
- transitionPropertyStyle = elementStyles[TRANSITION_PROP + PROPERTY_KEY];
1348
-
1349
- transitionDelayStyle = elementStyles[TRANSITION_PROP + DELAY_KEY];
1350
-
1498
+ var transitionDelayStyle = elementStyles[TRANSITION_PROP + DELAY_KEY];
1351
1499
  transitionDelay = Math.max(parseMaxTime(transitionDelayStyle), transitionDelay);
1352
1500
 
1353
- animationDelayStyle = elementStyles[ANIMATION_PROP + DELAY_KEY];
1354
-
1355
- animationDelay = Math.max(parseMaxTime(animationDelayStyle), animationDelay);
1501
+ var animationDelayStyle = elementStyles[ANIMATION_PROP + DELAY_KEY];
1502
+ animationDelay = Math.max(parseMaxTime(elementStyles[ANIMATION_PROP + DELAY_KEY]), animationDelay);
1356
1503
 
1357
1504
  var aDuration = parseMaxTime(elementStyles[ANIMATION_PROP + DURATION_KEY]);
1358
1505
 
1359
- if(aDuration > 0) {
1506
+ if (aDuration > 0) {
1360
1507
  aDuration *= parseInt(elementStyles[ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY], 10) || 1;
1361
1508
  }
1362
-
1363
1509
  animationDuration = Math.max(aDuration, animationDuration);
1364
1510
  }
1365
1511
  });
1366
1512
  data = {
1367
1513
  total : 0,
1368
- transitionPropertyStyle: transitionPropertyStyle,
1369
- transitionDurationStyle: transitionDurationStyle,
1370
- transitionDelayStyle: transitionDelayStyle,
1371
1514
  transitionDelay: transitionDelay,
1372
1515
  transitionDuration: transitionDuration,
1373
- animationDelayStyle: animationDelayStyle,
1374
1516
  animationDelay: animationDelay,
1375
1517
  animationDuration: animationDuration
1376
1518
  };
1377
- if(cacheKey) {
1519
+ if (cacheKey) {
1378
1520
  lookupCache[cacheKey] = data;
1379
1521
  }
1380
1522
  }
@@ -1395,7 +1537,7 @@ angular.module('ngAnimate', ['ng'])
1395
1537
  function getCacheKey(element) {
1396
1538
  var parentElement = element.parent();
1397
1539
  var parentID = parentElement.data(NG_ANIMATE_PARENT_KEY);
1398
- if(!parentID) {
1540
+ if (!parentID) {
1399
1541
  parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter);
1400
1542
  parentID = parentCounter;
1401
1543
  }
@@ -1410,7 +1552,7 @@ angular.module('ngAnimate', ['ng'])
1410
1552
  var itemIndex = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0;
1411
1553
 
1412
1554
  var stagger = {};
1413
- if(itemIndex > 0) {
1555
+ if (itemIndex > 0) {
1414
1556
  var staggerClassName = className + '-stagger';
1415
1557
  var staggerCacheKey = cacheKey + ' ' + staggerClassName;
1416
1558
  var applyClasses = !lookupCache[staggerCacheKey];
@@ -1429,7 +1571,7 @@ angular.module('ngAnimate', ['ng'])
1429
1571
  var transitionDuration = timings.transitionDuration;
1430
1572
  var animationDuration = timings.animationDuration;
1431
1573
 
1432
- if(structural && transitionDuration === 0 && animationDuration === 0) {
1574
+ if (structural && transitionDuration === 0 && animationDuration === 0) {
1433
1575
  element.removeClass(className);
1434
1576
  return false;
1435
1577
  }
@@ -1446,18 +1588,17 @@ angular.module('ngAnimate', ['ng'])
1446
1588
  running : formerData.running || 0,
1447
1589
  itemIndex : itemIndex,
1448
1590
  blockTransition : blockTransition,
1449
- blockAnimation : blockAnimation,
1450
1591
  closeAnimationFns : closeAnimationFns
1451
1592
  });
1452
1593
 
1453
1594
  var node = extractElementNode(element);
1454
1595
 
1455
- if(blockTransition) {
1456
- node.style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
1596
+ if (blockTransition) {
1597
+ blockTransitions(node, true);
1457
1598
  }
1458
1599
 
1459
- if(blockAnimation) {
1460
- node.style[ANIMATION_PROP] = 'none 0s';
1600
+ if (blockAnimation) {
1601
+ blockAnimations(node, true);
1461
1602
  }
1462
1603
 
1463
1604
  return true;
@@ -1466,30 +1607,51 @@ angular.module('ngAnimate', ['ng'])
1466
1607
  function animateRun(animationEvent, element, className, activeAnimationComplete) {
1467
1608
  var node = extractElementNode(element);
1468
1609
  var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
1469
- if(node.getAttribute('class').indexOf(className) == -1 || !elementData) {
1610
+ if (node.getAttribute('class').indexOf(className) == -1 || !elementData) {
1470
1611
  activeAnimationComplete();
1471
1612
  return;
1472
1613
  }
1473
1614
 
1474
- if(elementData.blockTransition) {
1475
- node.style[TRANSITION_PROP + PROPERTY_KEY] = '';
1476
- }
1477
-
1478
- if(elementData.blockAnimation) {
1479
- node.style[ANIMATION_PROP] = '';
1615
+ if (elementData.blockTransition) {
1616
+ blockTransitions(node, false);
1480
1617
  }
1481
1618
 
1482
1619
  var activeClassName = '';
1620
+ var pendingClassName = '';
1483
1621
  forEach(className.split(' '), function(klass, i) {
1484
- activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
1622
+ var prefix = (i > 0 ? ' ' : '') + klass;
1623
+ activeClassName += prefix + '-active';
1624
+ pendingClassName += prefix + '-pending';
1485
1625
  });
1486
1626
 
1487
- element.addClass(activeClassName);
1627
+ var style = '';
1628
+ var appliedStyles = [];
1629
+ var itemIndex = elementData.itemIndex;
1630
+ var stagger = elementData.stagger;
1631
+ var staggerTime = 0;
1632
+ if (itemIndex > 0) {
1633
+ var transitionStaggerDelay = 0;
1634
+ if (stagger.transitionDelay > 0 && stagger.transitionDuration === 0) {
1635
+ transitionStaggerDelay = stagger.transitionDelay * itemIndex;
1636
+ }
1637
+
1638
+ var animationStaggerDelay = 0;
1639
+ if (stagger.animationDelay > 0 && stagger.animationDuration === 0) {
1640
+ animationStaggerDelay = stagger.animationDelay * itemIndex;
1641
+ appliedStyles.push(CSS_PREFIX + 'animation-play-state');
1642
+ }
1643
+
1644
+ staggerTime = Math.round(Math.max(transitionStaggerDelay, animationStaggerDelay) * 100) / 100;
1645
+ }
1646
+
1647
+ if (!staggerTime) {
1648
+ element.addClass(activeClassName);
1649
+ }
1650
+
1488
1651
  var eventCacheKey = elementData.cacheKey + ' ' + activeClassName;
1489
1652
  var timings = getElementAnimationDetails(element, eventCacheKey);
1490
-
1491
1653
  var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
1492
- if(maxDuration === 0) {
1654
+ if (maxDuration === 0) {
1493
1655
  element.removeClass(activeClassName);
1494
1656
  animateClose(element, className);
1495
1657
  activeAnimationComplete();
@@ -1497,46 +1659,36 @@ angular.module('ngAnimate', ['ng'])
1497
1659
  }
1498
1660
 
1499
1661
  var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay);
1500
- var stagger = elementData.stagger;
1501
- var itemIndex = elementData.itemIndex;
1502
1662
  var maxDelayTime = maxDelay * ONE_SECOND;
1503
1663
 
1504
- var style = '', appliedStyles = [];
1505
- if(timings.transitionDuration > 0) {
1506
- var propertyStyle = timings.transitionPropertyStyle;
1507
- if(propertyStyle.indexOf('all') == -1) {
1508
- style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ';';
1509
- style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + ';';
1510
- appliedStyles.push(CSS_PREFIX + 'transition-property');
1511
- appliedStyles.push(CSS_PREFIX + 'transition-duration');
1512
- }
1513
- }
1514
-
1515
- if(itemIndex > 0) {
1516
- if(stagger.transitionDelay > 0 && stagger.transitionDuration === 0) {
1517
- var delayStyle = timings.transitionDelayStyle;
1518
- style += CSS_PREFIX + 'transition-delay: ' +
1519
- prepareStaggerDelay(delayStyle, stagger.transitionDelay, itemIndex) + '; ';
1520
- appliedStyles.push(CSS_PREFIX + 'transition-delay');
1521
- }
1522
-
1523
- if(stagger.animationDelay > 0 && stagger.animationDuration === 0) {
1524
- style += CSS_PREFIX + 'animation-delay: ' +
1525
- prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, itemIndex) + '; ';
1526
- appliedStyles.push(CSS_PREFIX + 'animation-delay');
1527
- }
1528
- }
1529
-
1530
- if(appliedStyles.length > 0) {
1664
+ if (appliedStyles.length > 0) {
1531
1665
  //the element being animated may sometimes contain comment nodes in
1532
1666
  //the jqLite object, so we're safe to use a single variable to house
1533
1667
  //the styles since there is always only one element being animated
1534
1668
  var oldStyle = node.getAttribute('style') || '';
1535
- node.setAttribute('style', oldStyle + '; ' + style);
1669
+ if (oldStyle.charAt(oldStyle.length-1) !== ';') {
1670
+ oldStyle += ';';
1671
+ }
1672
+ node.setAttribute('style', oldStyle + ' ' + style);
1536
1673
  }
1537
1674
 
1538
1675
  var startTime = Date.now();
1539
1676
  var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
1677
+ var animationTime = (maxDelay + maxDuration) * CLOSING_TIME_BUFFER;
1678
+ var totalTime = (staggerTime + animationTime) * ONE_SECOND;
1679
+
1680
+ var staggerTimeout;
1681
+ if (staggerTime > 0) {
1682
+ element.addClass(pendingClassName);
1683
+ staggerTimeout = $timeout(function() {
1684
+ staggerTimeout = null;
1685
+ element.addClass(activeClassName);
1686
+ element.removeClass(pendingClassName);
1687
+ if (timings.animationDuration > 0) {
1688
+ blockAnimations(node, false);
1689
+ }
1690
+ }, staggerTime * ONE_SECOND, false);
1691
+ }
1540
1692
 
1541
1693
  element.on(css3AnimationEvents, onAnimationProgress);
1542
1694
  elementData.closeAnimationFns.push(function() {
@@ -1544,10 +1696,6 @@ angular.module('ngAnimate', ['ng'])
1544
1696
  activeAnimationComplete();
1545
1697
  });
1546
1698
 
1547
- var staggerTime = itemIndex * (Math.max(stagger.animationDelay, stagger.transitionDelay) || 0);
1548
- var animationTime = (maxDelay + maxDuration) * CLOSING_TIME_BUFFER;
1549
- var totalTime = (staggerTime + animationTime) * ONE_SECOND;
1550
-
1551
1699
  elementData.running++;
1552
1700
  animationCloseHandler(element, totalTime);
1553
1701
  return onEnd;
@@ -1558,6 +1706,10 @@ angular.module('ngAnimate', ['ng'])
1558
1706
  function onEnd(cancelled) {
1559
1707
  element.off(css3AnimationEvents, onAnimationProgress);
1560
1708
  element.removeClass(activeClassName);
1709
+ element.removeClass(pendingClassName);
1710
+ if (staggerTimeout) {
1711
+ $timeout.cancel(staggerTimeout);
1712
+ }
1561
1713
  animateClose(element, className);
1562
1714
  var node = extractElementNode(element);
1563
1715
  for (var i in appliedStyles) {
@@ -1581,23 +1733,22 @@ angular.module('ngAnimate', ['ng'])
1581
1733
  * We're checking to see if the timeStamp surpasses the expected delay,
1582
1734
  * but we're using elapsedTime instead of the timeStamp on the 2nd
1583
1735
  * pre-condition since animations sometimes close off early */
1584
- if(Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
1736
+ if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
1585
1737
  activeAnimationComplete();
1586
1738
  }
1587
1739
  }
1588
1740
  }
1589
1741
 
1590
- function prepareStaggerDelay(delayStyle, staggerDelay, index) {
1591
- var style = '';
1592
- forEach(delayStyle.split(','), function(val, i) {
1593
- style += (i > 0 ? ',' : '') +
1594
- (index * staggerDelay + parseInt(val, 10)) + 's';
1595
- });
1596
- return style;
1742
+ function blockTransitions(node, bool) {
1743
+ node.style[TRANSITION_PROP + PROPERTY_KEY] = bool ? 'none' : '';
1744
+ }
1745
+
1746
+ function blockAnimations(node, bool) {
1747
+ node.style[ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY] = bool ? 'paused' : '';
1597
1748
  }
1598
1749
 
1599
1750
  function animateBefore(animationEvent, element, className, calculationDecorator) {
1600
- if(animateSetup(animationEvent, element, className, calculationDecorator)) {
1751
+ if (animateSetup(animationEvent, element, className, calculationDecorator)) {
1601
1752
  return function(cancelled) {
1602
1753
  cancelled && animateClose(element, className);
1603
1754
  };
@@ -1605,7 +1756,7 @@ angular.module('ngAnimate', ['ng'])
1605
1756
  }
1606
1757
 
1607
1758
  function animateAfter(animationEvent, element, className, afterAnimationComplete) {
1608
- if(element.data(NG_ANIMATE_CSS_DATA_KEY)) {
1759
+ if (element.data(NG_ANIMATE_CSS_DATA_KEY)) {
1609
1760
  return animateRun(animationEvent, element, className, afterAnimationComplete);
1610
1761
  } else {
1611
1762
  animateClose(element, className);
@@ -1618,7 +1769,7 @@ angular.module('ngAnimate', ['ng'])
1618
1769
  //cancellation function then it means that there is no animation
1619
1770
  //to perform at all
1620
1771
  var preReflowCancellation = animateBefore(animationEvent, element, className);
1621
- if(!preReflowCancellation) {
1772
+ if (!preReflowCancellation) {
1622
1773
  animationComplete();
1623
1774
  return;
1624
1775
  }
@@ -1644,11 +1795,11 @@ angular.module('ngAnimate', ['ng'])
1644
1795
  function animateClose(element, className) {
1645
1796
  element.removeClass(className);
1646
1797
  var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
1647
- if(data) {
1648
- if(data.running) {
1798
+ if (data) {
1799
+ if (data.running) {
1649
1800
  data.running--;
1650
1801
  }
1651
- if(!data.running || data.running === 0) {
1802
+ if (!data.running || data.running === 0) {
1652
1803
  element.removeData(NG_ANIMATE_CSS_DATA_KEY);
1653
1804
  }
1654
1805
  }
@@ -1671,7 +1822,7 @@ angular.module('ngAnimate', ['ng'])
1671
1822
  var className = suffixClasses(remove, '-remove') + ' ' +
1672
1823
  suffixClasses(add, '-add');
1673
1824
  var cancellationMethod = animateBefore('setClass', element, className);
1674
- if(cancellationMethod) {
1825
+ if (cancellationMethod) {
1675
1826
  afterReflow(element, animationCompleted);
1676
1827
  return cancellationMethod;
1677
1828
  }
@@ -1680,7 +1831,7 @@ angular.module('ngAnimate', ['ng'])
1680
1831
 
1681
1832
  beforeAddClass : function(element, className, animationCompleted) {
1682
1833
  var cancellationMethod = animateBefore('addClass', element, suffixClasses(className, '-add'));
1683
- if(cancellationMethod) {
1834
+ if (cancellationMethod) {
1684
1835
  afterReflow(element, animationCompleted);
1685
1836
  return cancellationMethod;
1686
1837
  }
@@ -1689,7 +1840,7 @@ angular.module('ngAnimate', ['ng'])
1689
1840
 
1690
1841
  beforeRemoveClass : function(element, className, animationCompleted) {
1691
1842
  var cancellationMethod = animateBefore('removeClass', element, suffixClasses(className, '-remove'));
1692
- if(cancellationMethod) {
1843
+ if (cancellationMethod) {
1693
1844
  afterReflow(element, animationCompleted);
1694
1845
  return cancellationMethod;
1695
1846
  }
@@ -1714,9 +1865,9 @@ angular.module('ngAnimate', ['ng'])
1714
1865
 
1715
1866
  function suffixClasses(classes, suffix) {
1716
1867
  var className = '';
1717
- classes = angular.isArray(classes) ? classes : classes.split(/\s+/);
1868
+ classes = isArray(classes) ? classes : classes.split(/\s+/);
1718
1869
  forEach(classes, function(klass, i) {
1719
- if(klass && klass.length > 0) {
1870
+ if (klass && klass.length > 0) {
1720
1871
  className += (i > 0 ? ' ' : '') + klass + suffix;
1721
1872
  }
1722
1873
  });