angular-rails-engine 1.2.5.0 → 1.2.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/README.md +1 -1
  5. data/app/assets/javascripts/angular/angular-animate.js +461 -195
  6. data/app/assets/javascripts/angular/angular-animate.min.js +23 -18
  7. data/app/assets/javascripts/angular/angular-cookies.js +1 -1
  8. data/app/assets/javascripts/angular/angular-cookies.min.js +2 -1
  9. data/app/assets/javascripts/angular/angular-loader.js +2 -2
  10. data/app/assets/javascripts/angular/angular-loader.min.js +3 -2
  11. data/app/assets/javascripts/angular/angular-mocks.js +54 -34
  12. data/app/assets/javascripts/angular/angular-resource.js +36 -5
  13. data/app/assets/javascripts/angular/angular-resource.min.js +9 -8
  14. data/app/assets/javascripts/angular/angular-route.js +25 -15
  15. data/app/assets/javascripts/angular/angular-route.min.js +10 -9
  16. data/app/assets/javascripts/angular/angular-sanitize.js +35 -32
  17. data/app/assets/javascripts/angular/angular-sanitize.min.js +3 -2
  18. data/app/assets/javascripts/angular/angular-scenario.js +1472 -966
  19. data/app/assets/javascripts/angular/angular-touch.js +1 -1
  20. data/app/assets/javascripts/angular/angular-touch.min.js +2 -1
  21. data/app/assets/javascripts/angular/angular.js +1470 -965
  22. data/app/assets/javascripts/angular/angular.min.js +200 -196
  23. data/app/assets/stylesheets/angular/angular-csp.css +18 -0
  24. data/gem-public_cert.pem +11 -10
  25. data/lib/angular-rails-engine.rb +1 -1
  26. data/lib/angular-rails-engine/version.rb +1 -1
  27. metadata +14 -13
  28. metadata.gz.sig +0 -0
  29. data/app/assets/stylesheets/angular-csp.css +0 -24
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2618f19ea1daee4c72b2b53cb4edafd16fce433e
4
- data.tar.gz: 2d607694d485ae185e8b1f08e44d762a95f50eac
3
+ metadata.gz: b76648c79a80360cd119a13af54e6de295a0d351
4
+ data.tar.gz: 8406b089e22bcfe90d4410e4285c644298e27790
5
5
  SHA512:
6
- metadata.gz: ed4878379237a9f40e304e2d2a9effa953e5a06da04c62c4b0d5ce4759531792d17b9c61ffe5450e3f43f81afd626215d075409763876bd9f7e7be95a5868961
7
- data.tar.gz: 23388b3a6fb6dba5365e5beaf70632389d49e3f12c933d10c32d14c99ecfb02bdf11087ec0fa3d1ced4a2cae3debc72f34edf3f2ac095e5bb37ff265f6d834ec
6
+ metadata.gz: 2fbee693bf3f3aee84cb130c69433dc8ada0df0bec51b4031328e8ab6f6dd4dea619d99f6529ca29fbd6bee041c1fbbbc91a9babb9a7aff56dc9fea182dad60d
7
+ data.tar.gz: 3df28d0ea4b0117408e36e86eac34f90661cfc822b3dbc8d86a78867ac168d85240ece6e01a1c478f67b04d78dec08c6c536434b8cb1e201cb825c02121b3092
Binary file
data.tar.gz.sig CHANGED
Binary file
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  Make [Angular.js](http://angularjs.org) into Rails Engine.
3
3
 
4
4
  ## Version
5
- Angular.js 1.2.0
5
+ Angular.js 1.2.13.
6
6
 
7
7
  ### Older Versions
8
8
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.2.5
2
+ * @license AngularJS v1.2.13
3
3
  * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -254,6 +254,45 @@ angular.module('ngAnimate', ['ng'])
254
254
  * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
255
255
  *
256
256
  */
257
+ .factory('$$animateReflow', ['$window', '$timeout', '$document',
258
+ function($window, $timeout, $document) {
259
+ var bod = $document[0].body;
260
+ var requestAnimationFrame = $window.requestAnimationFrame ||
261
+ $window.webkitRequestAnimationFrame ||
262
+ function(fn) {
263
+ return $timeout(fn, 10, false);
264
+ };
265
+
266
+ var cancelAnimationFrame = $window.cancelAnimationFrame ||
267
+ $window.webkitCancelAnimationFrame ||
268
+ function(timer) {
269
+ return $timeout.cancel(timer);
270
+ };
271
+ return function(fn) {
272
+ var id = requestAnimationFrame(function() {
273
+ var a = bod.offsetWidth + 1;
274
+ fn();
275
+ });
276
+ return function() {
277
+ cancelAnimationFrame(id);
278
+ };
279
+ };
280
+ }])
281
+
282
+ .factory('$$asyncQueueBuffer', ['$timeout', function($timeout) {
283
+ var timer, queue = [];
284
+ return function(fn) {
285
+ $timeout.cancel(timer);
286
+ queue.push(fn);
287
+ timer = $timeout(function() {
288
+ for(var i = 0; i < queue.length; i++) {
289
+ queue[i]();
290
+ }
291
+ queue = [];
292
+ }, 0, false);
293
+ };
294
+ }])
295
+
257
296
  .config(['$provide', '$animateProvider', function($provide, $animateProvider) {
258
297
  var noop = angular.noop;
259
298
  var forEach = angular.forEach;
@@ -273,13 +312,18 @@ angular.module('ngAnimate', ['ng'])
273
312
  }
274
313
  }
275
314
 
315
+ function stripCommentsFromElement(element) {
316
+ return angular.element(extractElementNode(element));
317
+ }
318
+
276
319
  function isMatchingElement(elm1, elm2) {
277
320
  return extractElementNode(elm1) == extractElementNode(elm2);
278
321
  }
279
322
 
280
- $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', '$rootScope', '$document',
281
- function($delegate, $injector, $sniffer, $rootElement, $timeout, $rootScope, $document) {
323
+ $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$$asyncQueueBuffer', '$rootScope', '$document',
324
+ function($delegate, $injector, $sniffer, $rootElement, $$asyncQueueBuffer, $rootScope, $document) {
282
325
 
326
+ var globalAnimationCounter = 0;
283
327
  $rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
284
328
 
285
329
  // disable animations during bootstrap, but once we bootstrapped, wait again
@@ -294,6 +338,13 @@ angular.module('ngAnimate', ['ng'])
294
338
  });
295
339
  });
296
340
 
341
+ var classNameFilter = $animateProvider.classNameFilter();
342
+ var isAnimatableClassName = !classNameFilter
343
+ ? function() { return true; }
344
+ : function(className) {
345
+ return classNameFilter.test(className);
346
+ };
347
+
297
348
  function lookup(name) {
298
349
  if (name) {
299
350
  var matches = [],
@@ -375,6 +426,7 @@ angular.module('ngAnimate', ['ng'])
375
426
  this.enabled(false, element);
376
427
  $delegate.enter(element, parentElement, afterElement);
377
428
  $rootScope.$$postDigest(function() {
429
+ element = stripCommentsFromElement(element);
378
430
  performAnimation('enter', 'ng-enter', element, parentElement, afterElement, noop, doneCallback);
379
431
  });
380
432
  },
@@ -411,6 +463,7 @@ angular.module('ngAnimate', ['ng'])
411
463
  cancelChildAnimations(element);
412
464
  this.enabled(false, element);
413
465
  $rootScope.$$postDigest(function() {
466
+ element = stripCommentsFromElement(element);
414
467
  performAnimation('leave', 'ng-leave', element, null, null, function() {
415
468
  $delegate.leave(element);
416
469
  }, doneCallback);
@@ -453,6 +506,7 @@ angular.module('ngAnimate', ['ng'])
453
506
  this.enabled(false, element);
454
507
  $delegate.move(element, parentElement, afterElement);
455
508
  $rootScope.$$postDigest(function() {
509
+ element = stripCommentsFromElement(element);
456
510
  performAnimation('move', 'ng-move', element, parentElement, afterElement, noop, doneCallback);
457
511
  });
458
512
  },
@@ -488,6 +542,7 @@ angular.module('ngAnimate', ['ng'])
488
542
  * @param {function()=} doneCallback the callback function that will be called once the animation is complete
489
543
  */
490
544
  addClass : function(element, className, doneCallback) {
545
+ element = stripCommentsFromElement(element);
491
546
  performAnimation('addClass', className, element, null, null, function() {
492
547
  $delegate.addClass(element, className);
493
548
  }, doneCallback);
@@ -524,11 +579,34 @@ angular.module('ngAnimate', ['ng'])
524
579
  * @param {function()=} doneCallback the callback function that will be called once the animation is complete
525
580
  */
526
581
  removeClass : function(element, className, doneCallback) {
582
+ element = stripCommentsFromElement(element);
527
583
  performAnimation('removeClass', className, element, null, null, function() {
528
584
  $delegate.removeClass(element, className);
529
585
  }, doneCallback);
530
586
  },
531
587
 
588
+ /**
589
+ *
590
+ * @ngdoc function
591
+ * @name ng.$animate#setClass
592
+ * @methodOf ng.$animate
593
+ * @function
594
+ * @description Adds and/or removes the given CSS classes to and from the element.
595
+ * Once complete, the done() callback will be fired (if provided).
596
+ * @param {jQuery/jqLite element} element the element which will it's CSS classes changed
597
+ * removed from it
598
+ * @param {string} add the CSS classes which will be added to the element
599
+ * @param {string} remove the CSS class which will be removed from the element
600
+ * @param {function=} done the callback function (if provided) that will be fired after the
601
+ * CSS classes have been set on the element
602
+ */
603
+ setClass : function(element, add, remove, doneCallback) {
604
+ element = stripCommentsFromElement(element);
605
+ performAnimation('setClass', [add, remove], element, null, null, function() {
606
+ $delegate.setClass(element, add, remove);
607
+ }, doneCallback);
608
+ },
609
+
532
610
  /**
533
611
  * @ngdoc function
534
612
  * @name ngAnimate.$animate#enabled
@@ -575,25 +653,47 @@ angular.module('ngAnimate', ['ng'])
575
653
  and the onComplete callback will be fired once the animation is fully complete.
576
654
  */
577
655
  function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
578
- var node = extractElementNode(element);
656
+
657
+ var classNameAdd, classNameRemove, setClassOperation = animationEvent == 'setClass';
658
+ if(setClassOperation) {
659
+ classNameAdd = className[0];
660
+ classNameRemove = className[1];
661
+ className = classNameAdd + ' ' + classNameRemove;
662
+ }
663
+
664
+ var currentClassName, classes, node = element[0];
665
+ if(node) {
666
+ currentClassName = node.className;
667
+ classes = currentClassName + ' ' + className;
668
+ }
669
+
579
670
  //transcluded directives may sometimes fire an animation using only comment nodes
580
671
  //best to catch this early on to prevent any animation operations from occurring
581
- if(!node) {
672
+ if(!node || !isAnimatableClassName(classes)) {
582
673
  fireDOMOperation();
583
- closeAnimation();
674
+ fireBeforeCallbackAsync();
675
+ fireAfterCallbackAsync();
676
+ fireDoneCallbackAsync();
584
677
  return;
585
678
  }
586
679
 
587
- var currentClassName = node.className;
588
- var classes = currentClassName + ' ' + className;
680
+ var elementEvents = angular.element._data(node);
681
+ elementEvents = elementEvents && elementEvents.events;
682
+
589
683
  var animationLookup = (' ' + classes).replace(/\s+/g,'.');
590
684
  if (!parentElement) {
591
685
  parentElement = afterElement ? afterElement.parent() : element.parent();
592
686
  }
593
687
 
594
- var matches = lookup(animationLookup);
595
- var isClassBased = animationEvent == 'addClass' || animationEvent == 'removeClass';
596
- var ngAnimateState = element.data(NG_ANIMATE_STATE) || {};
688
+ var matches = lookup(animationLookup);
689
+ var isClassBased = animationEvent == 'addClass' ||
690
+ animationEvent == 'removeClass' ||
691
+ setClassOperation;
692
+ var ngAnimateState = element.data(NG_ANIMATE_STATE) || {};
693
+
694
+ var runningAnimations = ngAnimateState.active || {};
695
+ var totalActiveAnimations = ngAnimateState.totalActive || 0;
696
+ var lastAnimation = ngAnimateState.last;
597
697
 
598
698
  //skip the animation if animations are disabled, a parent is already being animated,
599
699
  //the element is not currently attached to the document body or then completely close
@@ -601,14 +701,21 @@ angular.module('ngAnimate', ['ng'])
601
701
  //NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case a NO animation is not found.
602
702
  if (animationsDisabled(element, parentElement) || matches.length === 0) {
603
703
  fireDOMOperation();
704
+ fireBeforeCallbackAsync();
705
+ fireAfterCallbackAsync();
604
706
  closeAnimation();
605
707
  return;
606
708
  }
607
709
 
608
710
  var animations = [];
711
+
609
712
  //only add animations if the currently running animation is not structural
610
713
  //or if there is no animation running at all
611
- if(!ngAnimateState.running || !(isClassBased && ngAnimateState.structural)) {
714
+ var allowAnimations = isClassBased ?
715
+ !ngAnimateState.disabled && (!lastAnimation || lastAnimation.classBased) :
716
+ true;
717
+
718
+ if(allowAnimations) {
612
719
  forEach(matches, function(animation) {
613
720
  //add the animation to the queue to if it is allowed to be cancelled
614
721
  if(!animation.allowCancel || animation.allowCancel(element, animationEvent, className)) {
@@ -634,47 +741,56 @@ angular.module('ngAnimate', ['ng'])
634
741
  //animation do it's thing and close this one early
635
742
  if(animations.length === 0) {
636
743
  fireDOMOperation();
744
+ fireBeforeCallbackAsync();
745
+ fireAfterCallbackAsync();
637
746
  fireDoneCallbackAsync();
638
747
  return;
639
748
  }
640
749
 
641
- //this value will be searched for class-based CSS className lookup. Therefore,
642
- //we prefix and suffix the current className value with spaces to avoid substring
643
- //lookups of className tokens
644
- var futureClassName = ' ' + currentClassName + ' ';
645
- if(ngAnimateState.running) {
646
- //if an animation is currently running on the element then lets take the steps
647
- //to cancel that animation and fire any required callbacks
648
- $timeout.cancel(ngAnimateState.closeAnimationTimeout);
649
- cleanup(element);
650
- cancelAnimations(ngAnimateState.animations);
651
-
652
- //if the class is removed during the reflow then it will revert the styles temporarily
653
- //back to the base class CSS styling causing a jump-like effect to occur. This check
654
- //here ensures that the domOperation is only performed after the reflow has commenced
655
- if(ngAnimateState.beforeComplete) {
656
- (ngAnimateState.done || noop)(true);
657
- } else if(isClassBased && !ngAnimateState.structural) {
658
- //class-based animations will compare element className values after cancelling the
659
- //previous animation to see if the element properties already contain the final CSS
660
- //class and if so then the animation will be skipped. Since the domOperation will
661
- //be performed only after the reflow is complete then our element's className value
662
- //will be invalid. Therefore the same string manipulation that would occur within the
663
- //DOM operation will be performed below so that the class comparison is valid...
664
- futureClassName = ngAnimateState.event == 'removeClass' ?
665
- futureClassName.replace(ngAnimateState.className, '') :
666
- futureClassName + ngAnimateState.className + ' ';
750
+ var skipAnimation = false;
751
+ if(totalActiveAnimations > 0) {
752
+ var animationsToCancel = [];
753
+ if(!isClassBased) {
754
+ if(animationEvent == 'leave' && runningAnimations['ng-leave']) {
755
+ skipAnimation = true;
756
+ } else {
757
+ //cancel all animations when a structural animation takes place
758
+ for(var klass in runningAnimations) {
759
+ animationsToCancel.push(runningAnimations[klass]);
760
+ cleanup(element, klass);
761
+ }
762
+ runningAnimations = {};
763
+ totalActiveAnimations = 0;
764
+ }
765
+ } else if(lastAnimation.event == 'setClass') {
766
+ animationsToCancel.push(lastAnimation);
767
+ cleanup(element, className);
768
+ }
769
+ else if(runningAnimations[className]) {
770
+ var current = runningAnimations[className];
771
+ if(current.event == animationEvent) {
772
+ skipAnimation = true;
773
+ } else {
774
+ animationsToCancel.push(current);
775
+ cleanup(element, className);
776
+ }
777
+ }
778
+
779
+ if(animationsToCancel.length > 0) {
780
+ angular.forEach(animationsToCancel, function(operation) {
781
+ (operation.done || noop)(true);
782
+ cancelAnimations(operation.animations);
783
+ });
667
784
  }
668
785
  }
669
786
 
670
- //There is no point in perform a class-based animation if the element already contains
671
- //(on addClass) or doesn't contain (on removeClass) the className being animated.
672
- //The reason why this is being called after the previous animations are cancelled
673
- //is so that the CSS classes present on the element can be properly examined.
674
- var classNameToken = ' ' + className + ' ';
675
- if((animationEvent == 'addClass' && futureClassName.indexOf(classNameToken) >= 0) ||
676
- (animationEvent == 'removeClass' && futureClassName.indexOf(classNameToken) == -1)) {
677
- fireDOMOperation();
787
+ if(isClassBased && !setClassOperation && !skipAnimation) {
788
+ skipAnimation = (animationEvent == 'addClass') == element.hasClass(className); //opposite of XOR
789
+ }
790
+
791
+ if(skipAnimation) {
792
+ fireBeforeCallbackAsync();
793
+ fireAfterCallbackAsync();
678
794
  fireDoneCallbackAsync();
679
795
  return;
680
796
  }
@@ -683,13 +799,22 @@ angular.module('ngAnimate', ['ng'])
683
799
  //parent animations to find and cancel child animations when needed
684
800
  element.addClass(NG_ANIMATE_CLASS_NAME);
685
801
 
686
- element.data(NG_ANIMATE_STATE, {
687
- running:true,
688
- event:animationEvent,
689
- className:className,
690
- structural:!isClassBased,
691
- animations:animations,
802
+ var localAnimationCount = globalAnimationCounter++;
803
+ lastAnimation = {
804
+ classBased : isClassBased,
805
+ event : animationEvent,
806
+ animations : animations,
692
807
  done:onBeforeAnimationsComplete
808
+ };
809
+
810
+ totalActiveAnimations++;
811
+ runningAnimations[className] = lastAnimation;
812
+
813
+ element.data(NG_ANIMATE_STATE, {
814
+ last : lastAnimation,
815
+ active : runningAnimations,
816
+ index : localAnimationCount,
817
+ totalActive : totalActiveAnimations
693
818
  });
694
819
 
695
820
  //first we run the before animations and when all of those are complete
@@ -697,6 +822,11 @@ angular.module('ngAnimate', ['ng'])
697
822
  invokeRegisteredAnimationFns(animations, 'before', onBeforeAnimationsComplete);
698
823
 
699
824
  function onBeforeAnimationsComplete(cancelled) {
825
+ var data = element.data(NG_ANIMATE_STATE);
826
+ cancelled = cancelled ||
827
+ !data || !data.active[className] ||
828
+ (isClassBased && data.active[className].event != animationEvent);
829
+
700
830
  fireDOMOperation();
701
831
  if(cancelled === true) {
702
832
  closeAnimation();
@@ -706,15 +836,16 @@ angular.module('ngAnimate', ['ng'])
706
836
  //set the done function to the final done function
707
837
  //so that the DOM event won't be executed twice by accident
708
838
  //if the after animation is cancelled as well
709
- var data = element.data(NG_ANIMATE_STATE);
710
- if(data) {
711
- data.done = closeAnimation;
712
- element.data(NG_ANIMATE_STATE, data);
713
- }
839
+ var currentAnimation = data.active[className];
840
+ currentAnimation.done = closeAnimation;
714
841
  invokeRegisteredAnimationFns(animations, 'after', closeAnimation);
715
842
  }
716
843
 
717
844
  function invokeRegisteredAnimationFns(animations, phase, allAnimationFnsComplete) {
845
+ phase == 'after' ?
846
+ fireAfterCallbackAsync() :
847
+ fireBeforeCallbackAsync();
848
+
718
849
  var endFnName = phase + 'End';
719
850
  forEach(animations, function(animation, index) {
720
851
  var animationPhaseCompleted = function() {
@@ -729,9 +860,13 @@ angular.module('ngAnimate', ['ng'])
729
860
  }
730
861
 
731
862
  if(animation[phase]) {
732
- animation[endFnName] = isClassBased ?
733
- animation[phase](element, className, animationPhaseCompleted) :
734
- animation[phase](element, animationPhaseCompleted);
863
+ if(setClassOperation) {
864
+ animation[endFnName] = animation[phase](element, classNameAdd, classNameRemove, animationPhaseCompleted);
865
+ } else {
866
+ animation[endFnName] = isClassBased ?
867
+ animation[phase](element, className, animationPhaseCompleted) :
868
+ animation[phase](element, animationPhaseCompleted);
869
+ }
735
870
  } else {
736
871
  animationPhaseCompleted();
737
872
  }
@@ -751,8 +886,33 @@ angular.module('ngAnimate', ['ng'])
751
886
  }
752
887
  }
753
888
 
889
+ function fireDOMCallback(animationPhase) {
890
+ var eventName = '$animate:' + animationPhase;
891
+ if(elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) {
892
+ $$asyncQueueBuffer(function() {
893
+ element.triggerHandler(eventName, {
894
+ event : animationEvent,
895
+ className : className
896
+ });
897
+ });
898
+ }
899
+ }
900
+
901
+ function fireBeforeCallbackAsync() {
902
+ fireDOMCallback('before');
903
+ }
904
+
905
+ function fireAfterCallbackAsync() {
906
+ fireDOMCallback('after');
907
+ }
908
+
754
909
  function fireDoneCallbackAsync() {
755
- doneCallback && $timeout(doneCallback, 0, false);
910
+ fireDOMCallback('close');
911
+ if(doneCallback) {
912
+ $$asyncQueueBuffer(function() {
913
+ doneCallback();
914
+ });
915
+ }
756
916
  }
757
917
 
758
918
  //it is less complicated to use a flag than managing and cancelling
@@ -774,11 +934,14 @@ angular.module('ngAnimate', ['ng'])
774
934
  failing would be when a parent HTML tag has a ng-class attribute
775
935
  causing ALL directives below to skip animations during the digest */
776
936
  if(isClassBased) {
777
- cleanup(element);
937
+ cleanup(element, className);
778
938
  } else {
779
- data.closeAnimationTimeout = $timeout(function() {
780
- cleanup(element);
781
- }, 0, false);
939
+ $$asyncQueueBuffer(function() {
940
+ var data = element.data(NG_ANIMATE_STATE) || {};
941
+ if(localAnimationCount == data.index) {
942
+ cleanup(element, className, animationEvent);
943
+ }
944
+ });
782
945
  element.data(NG_ANIMATE_STATE, data);
783
946
  }
784
947
  }
@@ -792,9 +955,11 @@ angular.module('ngAnimate', ['ng'])
792
955
  forEach(node.querySelectorAll('.' + NG_ANIMATE_CLASS_NAME), function(element) {
793
956
  element = angular.element(element);
794
957
  var data = element.data(NG_ANIMATE_STATE);
795
- if(data) {
796
- cancelAnimations(data.animations);
797
- cleanup(element);
958
+ if(data && data.active) {
959
+ angular.forEach(data.active, function(operation) {
960
+ (operation.done || noop)(true);
961
+ cancelAnimations(operation.animations);
962
+ });
798
963
  }
799
964
  });
800
965
  }
@@ -802,24 +967,36 @@ angular.module('ngAnimate', ['ng'])
802
967
  function cancelAnimations(animations) {
803
968
  var isCancelledFlag = true;
804
969
  forEach(animations, function(animation) {
805
- if(!animations.beforeComplete) {
970
+ if(!animation.beforeComplete) {
806
971
  (animation.beforeEnd || noop)(isCancelledFlag);
807
972
  }
808
- if(!animations.afterComplete) {
973
+ if(!animation.afterComplete) {
809
974
  (animation.afterEnd || noop)(isCancelledFlag);
810
975
  }
811
976
  });
812
977
  }
813
978
 
814
- function cleanup(element) {
979
+ function cleanup(element, className) {
815
980
  if(isMatchingElement(element, $rootElement)) {
816
981
  if(!rootAnimateState.disabled) {
817
982
  rootAnimateState.running = false;
818
983
  rootAnimateState.structural = false;
819
984
  }
820
- } else {
821
- element.removeClass(NG_ANIMATE_CLASS_NAME);
822
- element.removeData(NG_ANIMATE_STATE);
985
+ } else if(className) {
986
+ var data = element.data(NG_ANIMATE_STATE) || {};
987
+
988
+ var removeAnimations = className === true;
989
+ if(!removeAnimations) {
990
+ if(data.active && data.active[className]) {
991
+ data.totalActive--;
992
+ delete data.active[className];
993
+ }
994
+ }
995
+
996
+ if(removeAnimations || !data.totalActive) {
997
+ element.removeClass(NG_ANIMATE_CLASS_NAME);
998
+ element.removeData(NG_ANIMATE_STATE);
999
+ }
823
1000
  }
824
1001
  }
825
1002
 
@@ -838,7 +1015,7 @@ angular.module('ngAnimate', ['ng'])
838
1015
 
839
1016
  var isRoot = isMatchingElement(parentElement, $rootElement);
840
1017
  var state = isRoot ? rootAnimateState : parentElement.data(NG_ANIMATE_STATE);
841
- var result = state && (!!state.disabled || !!state.running);
1018
+ var result = state && (!!state.disabled || state.running || state.totalActive > 0);
842
1019
  if(isRoot || result) {
843
1020
  return result;
844
1021
  }
@@ -851,7 +1028,8 @@ angular.module('ngAnimate', ['ng'])
851
1028
  }
852
1029
  }]);
853
1030
 
854
- $animateProvider.register('', ['$window', '$sniffer', '$timeout', function($window, $sniffer, $timeout) {
1031
+ $animateProvider.register('', ['$window', '$sniffer', '$timeout', '$$animateReflow',
1032
+ function($window, $sniffer, $timeout, $$animateReflow) {
855
1033
  // Detect proper transitionend/animationend event names.
856
1034
  var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;
857
1035
 
@@ -887,25 +1065,60 @@ angular.module('ngAnimate', ['ng'])
887
1065
  var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
888
1066
  var NG_ANIMATE_PARENT_KEY = '$$ngAnimateKey';
889
1067
  var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data';
890
- var NG_ANIMATE_FALLBACK_CLASS_NAME = 'ng-animate-start';
891
- var NG_ANIMATE_FALLBACK_ACTIVE_CLASS_NAME = 'ng-animate-active';
1068
+ var NG_ANIMATE_BLOCK_CLASS_NAME = 'ng-animate-block-transitions';
892
1069
  var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
1070
+ var CLOSING_TIME_BUFFER = 1.5;
1071
+ var ONE_SECOND = 1000;
893
1072
 
894
1073
  var lookupCache = {};
895
1074
  var parentCounter = 0;
896
-
897
- var animationReflowQueue = [], animationTimer, timeOut = false;
898
- function afterReflow(callback) {
1075
+ var animationReflowQueue = [];
1076
+ var cancelAnimationReflow;
1077
+ function afterReflow(element, callback) {
1078
+ if(cancelAnimationReflow) {
1079
+ cancelAnimationReflow();
1080
+ }
899
1081
  animationReflowQueue.push(callback);
900
- $timeout.cancel(animationTimer);
901
- animationTimer = $timeout(function() {
1082
+ cancelAnimationReflow = $$animateReflow(function() {
902
1083
  forEach(animationReflowQueue, function(fn) {
903
1084
  fn();
904
1085
  });
1086
+
905
1087
  animationReflowQueue = [];
906
- animationTimer = null;
1088
+ cancelAnimationReflow = null;
907
1089
  lookupCache = {};
908
- }, 10, false);
1090
+ });
1091
+ }
1092
+
1093
+ var closingTimer = null;
1094
+ var closingTimestamp = 0;
1095
+ var animationElementQueue = [];
1096
+ function animationCloseHandler(element, totalTime) {
1097
+ var futureTimestamp = Date.now() + (totalTime * 1000);
1098
+ if(futureTimestamp <= closingTimestamp) {
1099
+ return;
1100
+ }
1101
+
1102
+ $timeout.cancel(closingTimer);
1103
+
1104
+ var node = extractElementNode(element);
1105
+ element = angular.element(node);
1106
+ animationElementQueue.push(element);
1107
+
1108
+ closingTimestamp = futureTimestamp;
1109
+ closingTimer = $timeout(function() {
1110
+ closeAllAnimations(animationElementQueue);
1111
+ animationElementQueue = [];
1112
+ }, totalTime, false);
1113
+ }
1114
+
1115
+ function closeAllAnimations(elements) {
1116
+ forEach(elements, function(element) {
1117
+ var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
1118
+ if(elementData) {
1119
+ (elementData.closeAnimationFn || noop)();
1120
+ }
1121
+ });
909
1122
  }
910
1123
 
911
1124
  function getElementAnimationDetails(element, cacheKey) {
@@ -987,13 +1200,13 @@ angular.module('ngAnimate', ['ng'])
987
1200
  return parentID + '-' + extractElementNode(element).className;
988
1201
  }
989
1202
 
990
- function animateSetup(element, className) {
1203
+ function animateSetup(animationEvent, element, className, calculationDecorator) {
991
1204
  var cacheKey = getCacheKey(element);
992
1205
  var eventCacheKey = cacheKey + ' ' + className;
993
- var stagger = {};
994
- var ii = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0;
1206
+ var itemIndex = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0;
995
1207
 
996
- if(ii > 0) {
1208
+ var stagger = {};
1209
+ if(itemIndex > 0) {
997
1210
  var staggerClassName = className + '-stagger';
998
1211
  var staggerCacheKey = cacheKey + ' ' + staggerClassName;
999
1212
  var applyClasses = !lookupCache[staggerCacheKey];
@@ -1005,62 +1218,70 @@ angular.module('ngAnimate', ['ng'])
1005
1218
  applyClasses && element.removeClass(staggerClassName);
1006
1219
  }
1007
1220
 
1221
+ /* the animation itself may need to add/remove special CSS classes
1222
+ * before calculating the anmation styles */
1223
+ calculationDecorator = calculationDecorator ||
1224
+ function(fn) { return fn(); };
1225
+
1008
1226
  element.addClass(className);
1009
1227
 
1010
- var timings = getElementAnimationDetails(element, eventCacheKey);
1228
+ var formerData = element.data(NG_ANIMATE_CSS_DATA_KEY) || {};
1011
1229
 
1012
- /* there is no point in performing a reflow if the animation
1013
- timeout is empty (this would cause a flicker bug normally
1014
- in the page. There is also no point in performing an animation
1015
- that only has a delay and no duration */
1016
- var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
1017
- if(maxDuration === 0) {
1230
+ var timings = calculationDecorator(function() {
1231
+ return getElementAnimationDetails(element, eventCacheKey);
1232
+ });
1233
+
1234
+ var transitionDuration = timings.transitionDuration;
1235
+ var animationDuration = timings.animationDuration;
1236
+ if(transitionDuration === 0 && animationDuration === 0) {
1018
1237
  element.removeClass(className);
1019
1238
  return false;
1020
1239
  }
1021
1240
 
1241
+ element.data(NG_ANIMATE_CSS_DATA_KEY, {
1242
+ running : formerData.running || 0,
1243
+ itemIndex : itemIndex,
1244
+ stagger : stagger,
1245
+ timings : timings,
1246
+ closeAnimationFn : angular.noop
1247
+ });
1248
+
1022
1249
  //temporarily disable the transition so that the enter styles
1023
1250
  //don't animate twice (this is here to avoid a bug in Chrome/FF).
1024
- var activeClassName = '';
1025
- if(timings.transitionDuration > 0) {
1026
- element.addClass(NG_ANIMATE_FALLBACK_CLASS_NAME);
1027
- activeClassName += NG_ANIMATE_FALLBACK_ACTIVE_CLASS_NAME + ' ';
1028
- blockTransitions(element);
1029
- } else {
1251
+ var isCurrentlyAnimating = formerData.running > 0 || animationEvent == 'setClass';
1252
+ if(transitionDuration > 0) {
1253
+ blockTransitions(element, className, isCurrentlyAnimating);
1254
+ }
1255
+ if(animationDuration > 0) {
1030
1256
  blockKeyframeAnimations(element);
1031
1257
  }
1032
1258
 
1033
- forEach(className.split(' '), function(klass, i) {
1034
- activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
1035
- });
1036
-
1037
- element.data(NG_ANIMATE_CSS_DATA_KEY, {
1038
- className : className,
1039
- activeClassName : activeClassName,
1040
- maxDuration : maxDuration,
1041
- classes : className + ' ' + activeClassName,
1042
- timings : timings,
1043
- stagger : stagger,
1044
- ii : ii
1045
- });
1046
-
1047
1259
  return true;
1048
1260
  }
1049
1261
 
1050
- function blockTransitions(element) {
1051
- extractElementNode(element).style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
1262
+ function isStructuralAnimation(className) {
1263
+ return className == 'ng-enter' || className == 'ng-move' || className == 'ng-leave';
1264
+ }
1265
+
1266
+ function blockTransitions(element, className, isAnimating) {
1267
+ if(isStructuralAnimation(className) || !isAnimating) {
1268
+ extractElementNode(element).style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
1269
+ } else {
1270
+ element.addClass(NG_ANIMATE_BLOCK_CLASS_NAME);
1271
+ }
1052
1272
  }
1053
1273
 
1054
1274
  function blockKeyframeAnimations(element) {
1055
1275
  extractElementNode(element).style[ANIMATION_PROP] = 'none 0s';
1056
1276
  }
1057
1277
 
1058
- function unblockTransitions(element) {
1278
+ function unblockTransitions(element, className) {
1059
1279
  var prop = TRANSITION_PROP + PROPERTY_KEY;
1060
1280
  var node = extractElementNode(element);
1061
1281
  if(node.style[prop] && node.style[prop].length > 0) {
1062
1282
  node.style[prop] = '';
1063
1283
  }
1284
+ element.removeClass(NG_ANIMATE_BLOCK_CLASS_NAME);
1064
1285
  }
1065
1286
 
1066
1287
  function unblockKeyframeAnimations(element) {
@@ -1071,51 +1292,51 @@ angular.module('ngAnimate', ['ng'])
1071
1292
  }
1072
1293
  }
1073
1294
 
1074
- function animateRun(element, className, activeAnimationComplete) {
1075
- var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
1295
+ function animateRun(animationEvent, element, className, activeAnimationComplete) {
1076
1296
  var node = extractElementNode(element);
1077
- if(node.className.indexOf(className) == -1 || !data) {
1297
+ var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
1298
+ if(node.className.indexOf(className) == -1 || !elementData) {
1078
1299
  activeAnimationComplete();
1079
1300
  return;
1080
1301
  }
1081
1302
 
1082
- var timings = data.timings;
1083
- var stagger = data.stagger;
1084
- var maxDuration = data.maxDuration;
1085
- var activeClassName = data.activeClassName;
1086
- var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * 1000;
1303
+ var activeClassName = '';
1304
+ forEach(className.split(' '), function(klass, i) {
1305
+ activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
1306
+ });
1307
+
1308
+ var stagger = elementData.stagger;
1309
+ var timings = elementData.timings;
1310
+ var itemIndex = elementData.itemIndex;
1311
+ var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
1312
+ var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay);
1313
+ var maxDelayTime = maxDelay * ONE_SECOND;
1314
+
1087
1315
  var startTime = Date.now();
1088
1316
  var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
1089
- var ii = data.ii;
1090
1317
 
1091
- var applyFallbackStyle, style = '', appliedStyles = [];
1318
+ var style = '', appliedStyles = [];
1092
1319
  if(timings.transitionDuration > 0) {
1093
1320
  var propertyStyle = timings.transitionPropertyStyle;
1094
1321
  if(propertyStyle.indexOf('all') == -1) {
1095
- applyFallbackStyle = true;
1096
- var fallbackProperty = $sniffer.msie ? '-ms-zoom' : 'border-spacing';
1097
- style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ', ' + fallbackProperty + '; ';
1098
- style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + ', ' + timings.transitionDuration + 's; ';
1322
+ style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ';';
1323
+ style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + ';';
1099
1324
  appliedStyles.push(CSS_PREFIX + 'transition-property');
1100
1325
  appliedStyles.push(CSS_PREFIX + 'transition-duration');
1101
1326
  }
1102
1327
  }
1103
1328
 
1104
- if(ii > 0) {
1329
+ if(itemIndex > 0) {
1105
1330
  if(stagger.transitionDelay > 0 && stagger.transitionDuration === 0) {
1106
1331
  var delayStyle = timings.transitionDelayStyle;
1107
- if(applyFallbackStyle) {
1108
- delayStyle += ', ' + timings.transitionDelay + 's';
1109
- }
1110
-
1111
1332
  style += CSS_PREFIX + 'transition-delay: ' +
1112
- prepareStaggerDelay(delayStyle, stagger.transitionDelay, ii) + '; ';
1333
+ prepareStaggerDelay(delayStyle, stagger.transitionDelay, itemIndex) + '; ';
1113
1334
  appliedStyles.push(CSS_PREFIX + 'transition-delay');
1114
1335
  }
1115
1336
 
1116
1337
  if(stagger.animationDelay > 0 && stagger.animationDuration === 0) {
1117
1338
  style += CSS_PREFIX + 'animation-delay: ' +
1118
- prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, ii) + '; ';
1339
+ prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, itemIndex) + '; ';
1119
1340
  appliedStyles.push(CSS_PREFIX + 'animation-delay');
1120
1341
  }
1121
1342
  }
@@ -1130,11 +1351,23 @@ angular.module('ngAnimate', ['ng'])
1130
1351
 
1131
1352
  element.on(css3AnimationEvents, onAnimationProgress);
1132
1353
  element.addClass(activeClassName);
1354
+ elementData.closeAnimationFn = function() {
1355
+ onEnd();
1356
+ activeAnimationComplete();
1357
+ };
1358
+
1359
+ var staggerTime = itemIndex * (Math.max(stagger.animationDelay, stagger.transitionDelay) || 0);
1360
+ var animationTime = (maxDelay + maxDuration) * CLOSING_TIME_BUFFER;
1361
+ var totalTime = (staggerTime + animationTime) * ONE_SECOND;
1362
+
1363
+ elementData.running++;
1364
+ animationCloseHandler(element, totalTime);
1365
+ return onEnd;
1133
1366
 
1134
1367
  // This will automatically be called by $animate so
1135
1368
  // there is no need to attach this internally to the
1136
1369
  // timeout done method.
1137
- return function onEnd(cancelled) {
1370
+ function onEnd(cancelled) {
1138
1371
  element.off(css3AnimationEvents, onAnimationProgress);
1139
1372
  element.removeClass(activeClassName);
1140
1373
  animateClose(element, className);
@@ -1142,13 +1375,13 @@ angular.module('ngAnimate', ['ng'])
1142
1375
  for (var i in appliedStyles) {
1143
1376
  node.style.removeProperty(appliedStyles[i]);
1144
1377
  }
1145
- };
1378
+ }
1146
1379
 
1147
1380
  function onAnimationProgress(event) {
1148
1381
  event.stopPropagation();
1149
1382
  var ev = event.originalEvent || event;
1150
1383
  var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now();
1151
-
1384
+
1152
1385
  /* Firefox (or possibly just Gecko) likes to not round values up
1153
1386
  * when a ms measurement is used for the animation */
1154
1387
  var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
@@ -1175,28 +1408,28 @@ angular.module('ngAnimate', ['ng'])
1175
1408
  return style;
1176
1409
  }
1177
1410
 
1178
- function animateBefore(element, className) {
1179
- if(animateSetup(element, className)) {
1411
+ function animateBefore(animationEvent, element, className, calculationDecorator) {
1412
+ if(animateSetup(animationEvent, element, className, calculationDecorator)) {
1180
1413
  return function(cancelled) {
1181
1414
  cancelled && animateClose(element, className);
1182
1415
  };
1183
1416
  }
1184
1417
  }
1185
1418
 
1186
- function animateAfter(element, className, afterAnimationComplete) {
1419
+ function animateAfter(animationEvent, element, className, afterAnimationComplete) {
1187
1420
  if(element.data(NG_ANIMATE_CSS_DATA_KEY)) {
1188
- return animateRun(element, className, afterAnimationComplete);
1421
+ return animateRun(animationEvent, element, className, afterAnimationComplete);
1189
1422
  } else {
1190
1423
  animateClose(element, className);
1191
1424
  afterAnimationComplete();
1192
1425
  }
1193
1426
  }
1194
1427
 
1195
- function animate(element, className, animationComplete) {
1428
+ function animate(animationEvent, element, className, animationComplete) {
1196
1429
  //If the animateSetup function doesn't bother returning a
1197
1430
  //cancellation function then it means that there is no animation
1198
1431
  //to perform at all
1199
- var preReflowCancellation = animateBefore(element, className);
1432
+ var preReflowCancellation = animateBefore(animationEvent, element, className);
1200
1433
  if(!preReflowCancellation) {
1201
1434
  animationComplete();
1202
1435
  return;
@@ -1208,13 +1441,13 @@ angular.module('ngAnimate', ['ng'])
1208
1441
  //data from the element which will not make the 2nd animation
1209
1442
  //happen in the first place
1210
1443
  var cancel = preReflowCancellation;
1211
- afterReflow(function() {
1212
- unblockTransitions(element);
1444
+ afterReflow(element, function() {
1445
+ unblockTransitions(element, className);
1213
1446
  unblockKeyframeAnimations(element);
1214
1447
  //once the reflow is complete then we point cancel to
1215
1448
  //the new cancellation function which will remove all of the
1216
1449
  //animation properties from the active animation
1217
- cancel = animateAfter(element, className, animationComplete);
1450
+ cancel = animateAfter(animationEvent, element, className, animationComplete);
1218
1451
  });
1219
1452
 
1220
1453
  return function(cancelled) {
@@ -1224,58 +1457,73 @@ angular.module('ngAnimate', ['ng'])
1224
1457
 
1225
1458
  function animateClose(element, className) {
1226
1459
  element.removeClass(className);
1227
- element.removeClass(NG_ANIMATE_FALLBACK_CLASS_NAME);
1228
- element.removeData(NG_ANIMATE_CSS_DATA_KEY);
1460
+ var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
1461
+ if(data) {
1462
+ if(data.running) {
1463
+ data.running--;
1464
+ }
1465
+ if(!data.running || data.running === 0) {
1466
+ element.removeData(NG_ANIMATE_CSS_DATA_KEY);
1467
+ }
1468
+ }
1229
1469
  }
1230
1470
 
1231
1471
  return {
1232
- allowCancel : function(element, animationEvent, className) {
1233
- //always cancel the current animation if it is a
1234
- //structural animation
1235
- var oldClasses = (element.data(NG_ANIMATE_CSS_DATA_KEY) || {}).classes;
1236
- if(!oldClasses || ['enter','leave','move'].indexOf(animationEvent) >= 0) {
1237
- return true;
1238
- }
1239
-
1240
- var parentElement = element.parent();
1241
- var clone = angular.element(extractElementNode(element).cloneNode());
1242
-
1243
- //make the element super hidden and override any CSS style values
1244
- clone.attr('style','position:absolute; top:-9999px; left:-9999px');
1245
- clone.removeAttr('id');
1246
- clone.empty();
1247
-
1248
- forEach(oldClasses.split(' '), function(klass) {
1249
- clone.removeClass(klass);
1250
- });
1251
-
1252
- var suffix = animationEvent == 'addClass' ? '-add' : '-remove';
1253
- clone.addClass(suffixClasses(className, suffix));
1254
- parentElement.append(clone);
1255
-
1256
- var timings = getElementAnimationDetails(clone);
1257
- clone.remove();
1258
-
1259
- return Math.max(timings.transitionDuration, timings.animationDuration) > 0;
1260
- },
1261
-
1262
1472
  enter : function(element, animationCompleted) {
1263
- return animate(element, 'ng-enter', animationCompleted);
1473
+ return animate('enter', element, 'ng-enter', animationCompleted);
1264
1474
  },
1265
1475
 
1266
1476
  leave : function(element, animationCompleted) {
1267
- return animate(element, 'ng-leave', animationCompleted);
1477
+ return animate('leave', element, 'ng-leave', animationCompleted);
1268
1478
  },
1269
1479
 
1270
1480
  move : function(element, animationCompleted) {
1271
- return animate(element, 'ng-move', animationCompleted);
1481
+ return animate('move', element, 'ng-move', animationCompleted);
1482
+ },
1483
+
1484
+ beforeSetClass : function(element, add, remove, animationCompleted) {
1485
+ var className = suffixClasses(remove, '-remove') + ' ' +
1486
+ suffixClasses(add, '-add');
1487
+ var cancellationMethod = animateBefore('setClass', element, className, function(fn) {
1488
+ /* when classes are removed from an element then the transition style
1489
+ * that is applied is the transition defined on the element without the
1490
+ * CSS class being there. This is how CSS3 functions outside of ngAnimate.
1491
+ * http://plnkr.co/edit/j8OzgTNxHTb4n3zLyjGW?p=preview */
1492
+ var klass = element.attr('class');
1493
+ element.removeClass(remove);
1494
+ element.addClass(add);
1495
+ var timings = fn();
1496
+ element.attr('class', klass);
1497
+ return timings;
1498
+ });
1499
+
1500
+ if(cancellationMethod) {
1501
+ afterReflow(element, function() {
1502
+ unblockTransitions(element, className);
1503
+ unblockKeyframeAnimations(element);
1504
+ animationCompleted();
1505
+ });
1506
+ return cancellationMethod;
1507
+ }
1508
+ animationCompleted();
1272
1509
  },
1273
1510
 
1274
1511
  beforeAddClass : function(element, className, animationCompleted) {
1275
- var cancellationMethod = animateBefore(element, suffixClasses(className, '-add'));
1512
+ var cancellationMethod = animateBefore('addClass', element, suffixClasses(className, '-add'), function(fn) {
1513
+
1514
+ /* when a CSS class is added to an element then the transition style that
1515
+ * is applied is the transition defined on the element when the CSS class
1516
+ * is added at the time of the animation. This is how CSS3 functions
1517
+ * outside of ngAnimate. */
1518
+ element.addClass(className);
1519
+ var timings = fn();
1520
+ element.removeClass(className);
1521
+ return timings;
1522
+ });
1523
+
1276
1524
  if(cancellationMethod) {
1277
- afterReflow(function() {
1278
- unblockTransitions(element);
1525
+ afterReflow(element, function() {
1526
+ unblockTransitions(element, className);
1279
1527
  unblockKeyframeAnimations(element);
1280
1528
  animationCompleted();
1281
1529
  });
@@ -1284,15 +1532,33 @@ angular.module('ngAnimate', ['ng'])
1284
1532
  animationCompleted();
1285
1533
  },
1286
1534
 
1535
+ setClass : function(element, add, remove, animationCompleted) {
1536
+ remove = suffixClasses(remove, '-remove');
1537
+ add = suffixClasses(add, '-add');
1538
+ var className = remove + ' ' + add;
1539
+ return animateAfter('setClass', element, className, animationCompleted);
1540
+ },
1541
+
1287
1542
  addClass : function(element, className, animationCompleted) {
1288
- return animateAfter(element, suffixClasses(className, '-add'), animationCompleted);
1543
+ return animateAfter('addClass', element, suffixClasses(className, '-add'), animationCompleted);
1289
1544
  },
1290
1545
 
1291
1546
  beforeRemoveClass : function(element, className, animationCompleted) {
1292
- var cancellationMethod = animateBefore(element, suffixClasses(className, '-remove'));
1547
+ var cancellationMethod = animateBefore('removeClass', element, suffixClasses(className, '-remove'), function(fn) {
1548
+ /* when classes are removed from an element then the transition style
1549
+ * that is applied is the transition defined on the element without the
1550
+ * CSS class being there. This is how CSS3 functions outside of ngAnimate.
1551
+ * http://plnkr.co/edit/j8OzgTNxHTb4n3zLyjGW?p=preview */
1552
+ var klass = element.attr('class');
1553
+ element.removeClass(className);
1554
+ var timings = fn();
1555
+ element.attr('class', klass);
1556
+ return timings;
1557
+ });
1558
+
1293
1559
  if(cancellationMethod) {
1294
- afterReflow(function() {
1295
- unblockTransitions(element);
1560
+ afterReflow(element, function() {
1561
+ unblockTransitions(element, className);
1296
1562
  unblockKeyframeAnimations(element);
1297
1563
  animationCompleted();
1298
1564
  });
@@ -1302,7 +1568,7 @@ angular.module('ngAnimate', ['ng'])
1302
1568
  },
1303
1569
 
1304
1570
  removeClass : function(element, className, animationCompleted) {
1305
- return animateAfter(element, suffixClasses(className, '-remove'), animationCompleted);
1571
+ return animateAfter('removeClass', element, suffixClasses(className, '-remove'), animationCompleted);
1306
1572
  }
1307
1573
  };
1308
1574