angularjs-rails 1.2.12.1 → 1.2.13

Sign up to get free protection for your applications and to get access to all the features.
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