angularjs-rails 1.2.12.1 → 1.2.13

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