angular-rails-engine 1.2.5.0 → 1.2.13.0

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.
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