angularjs-rails 1.2.22 → 1.2.25

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.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
  });