pyc-website

main website for pyc inc.

git clone https://9o.is/git/pyc-website.git

ngAnimate.js

(74345B)


      1 'use strict';
      2 /* jshint maxlen: false */
      3 
      4 /**
      5  * @ngdoc module
      6  * @name ngAnimate
      7  * @description
      8  *
      9  * # ngAnimate
     10  *
     11  * The `ngAnimate` module provides support for JavaScript, CSS3 transition and CSS3 keyframe animation hooks within existing core and custom directives.
     12  *
     13  *
     14  * <div doc-module-components="ngAnimate"></div>
     15  *
     16  * # Usage
     17  *
     18  * To see animations in action, all that is required is to define the appropriate CSS classes
     19  * or to register a JavaScript animation via the myModule.animation() function. The directives that support animation automatically are:
     20  * `ngRepeat`, `ngInclude`, `ngIf`, `ngSwitch`, `ngShow`, `ngHide`, `ngView` and `ngClass`. Custom directives can take advantage of animation
     21  * by using the `$animate` service.
     22  *
     23  * Below is a more detailed breakdown of the supported animation events provided by pre-existing ng directives:
     24  *
     25  * | Directive                                                 | Supported Animations                               |
     26  * |---------------------------------------------------------- |----------------------------------------------------|
     27  * | {@link ng.directive:ngRepeat#usage_animations ngRepeat}         | enter, leave and move                              |
     28  * | {@link ngRoute.directive:ngView#usage_animations ngView}        | enter and leave                                    |
     29  * | {@link ng.directive:ngInclude#usage_animations ngInclude}       | enter and leave                                    |
     30  * | {@link ng.directive:ngSwitch#usage_animations ngSwitch}         | enter and leave                                    |
     31  * | {@link ng.directive:ngIf#usage_animations ngIf}                 | enter and leave                                    |
     32  * | {@link ng.directive:ngClass#usage_animations ngClass}           | add and remove                                     |
     33  * | {@link ng.directive:ngShow#usage_animations ngShow & ngHide}    | add and remove (the ng-hide class value)           |
     34  * | {@link ng.directive:form#usage_animations form}                 | add and remove (dirty, pristine, valid, invalid & all other validations)                |
     35  * | {@link ng.directive:ngModel#usage_animations ngModel}           | add and remove (dirty, pristine, valid, invalid & all other validations)                |
     36  *
     37  * You can find out more information about animations upon visiting each directive page.
     38  *
     39  * Below is an example of how to apply animations to a directive that supports animation hooks:
     40  *
     41  * ```html
     42  * <style type="text/css">
     43  * .slide.ng-enter, .slide.ng-leave {
     44  *   -webkit-transition:0.5s linear all;
     45  *   transition:0.5s linear all;
     46  * }
     47  *
     48  * .slide.ng-enter { }        /&#42; starting animations for enter &#42;/
     49  * .slide.ng-enter-active { } /&#42; terminal animations for enter &#42;/
     50  * .slide.ng-leave { }        /&#42; starting animations for leave &#42;/
     51  * .slide.ng-leave-active { } /&#42; terminal animations for leave &#42;/
     52  * </style>
     53  *
     54  * <!--
     55  * the animate service will automatically add .ng-enter and .ng-leave to the element
     56  * to trigger the CSS transition/animations
     57  * -->
     58  * <ANY class="slide" ng-include="..."></ANY>
     59  * ```
     60  *
     61  * Keep in mind that if an animation is running, any child elements cannot be animated until the parent element's
     62  * animation has completed.
     63  *
     64  * <h2>CSS-defined Animations</h2>
     65  * The animate service will automatically apply two CSS classes to the animated element and these two CSS classes
     66  * are designed to contain the start and end CSS styling. Both CSS transitions and keyframe animations are supported
     67  * and can be used to play along with this naming structure.
     68  *
     69  * The following code below demonstrates how to perform animations using **CSS transitions** with Angular:
     70  *
     71  * ```html
     72  * <style type="text/css">
     73  * /&#42;
     74  *  The animate class is apart of the element and the ng-enter class
     75  *  is attached to the element once the enter animation event is triggered
     76  * &#42;/
     77  * .reveal-animation.ng-enter {
     78  *  -webkit-transition: 1s linear all; /&#42; Safari/Chrome &#42;/
     79  *  transition: 1s linear all; /&#42; All other modern browsers and IE10+ &#42;/
     80  *
     81  *  /&#42; The animation preparation code &#42;/
     82  *  opacity: 0;
     83  * }
     84  *
     85  * /&#42;
     86  *  Keep in mind that you want to combine both CSS
     87  *  classes together to avoid any CSS-specificity
     88  *  conflicts
     89  * &#42;/
     90  * .reveal-animation.ng-enter.ng-enter-active {
     91  *  /&#42; The animation code itself &#42;/
     92  *  opacity: 1;
     93  * }
     94  * </style>
     95  *
     96  * <div class="view-container">
     97  *   <div ng-view class="reveal-animation"></div>
     98  * </div>
     99  * ```
    100  *
    101  * The following code below demonstrates how to perform animations using **CSS animations** with Angular:
    102  *
    103  * ```html
    104  * <style type="text/css">
    105  * .reveal-animation.ng-enter {
    106  *   -webkit-animation: enter_sequence 1s linear; /&#42; Safari/Chrome &#42;/
    107  *   animation: enter_sequence 1s linear; /&#42; IE10+ and Future Browsers &#42;/
    108  * }
    109  * @-webkit-keyframes enter_sequence {
    110  *   from { opacity:0; }
    111  *   to { opacity:1; }
    112  * }
    113  * @keyframes enter_sequence {
    114  *   from { opacity:0; }
    115  *   to { opacity:1; }
    116  * }
    117  * </style>
    118  *
    119  * <div class="view-container">
    120  *   <div ng-view class="reveal-animation"></div>
    121  * </div>
    122  * ```
    123  *
    124  * Both CSS3 animations and transitions can be used together and the animate service will figure out the correct duration and delay timing.
    125  *
    126  * Upon DOM mutation, the event class is added first (something like `ng-enter`), then the browser prepares itself to add
    127  * the active class (in this case `ng-enter-active`) which then triggers the animation. The animation module will automatically
    128  * detect the CSS code to determine when the animation ends. Once the animation is over then both CSS classes will be
    129  * removed from the DOM. If a browser does not support CSS transitions or CSS animations then the animation will start and end
    130  * immediately resulting in a DOM element that is at its final state. This final state is when the DOM element
    131  * has no CSS transition/animation classes applied to it.
    132  *
    133  * <h3>CSS Staggering Animations</h3>
    134  * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a
    135  * curtain-like effect. The ngAnimate module, as of 1.2.0, supports staggering animations and the stagger effect can be
    136  * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for
    137  * the animation. The style property expected within the stagger class can either be a **transition-delay** or an
    138  * **animation-delay** property (or both if your animation contains both transitions and keyframe animations).
    139  *
    140  * ```css
    141  * .my-animation.ng-enter {
    142  *   /&#42; standard transition code &#42;/
    143  *   -webkit-transition: 1s linear all;
    144  *   transition: 1s linear all;
    145  *   opacity:0;
    146  * }
    147  * .my-animation.ng-enter-stagger {
    148  *   /&#42; this will have a 100ms delay between each successive leave animation &#42;/
    149  *   -webkit-transition-delay: 0.1s;
    150  *   transition-delay: 0.1s;
    151  *
    152  *   /&#42; in case the stagger doesn't work then these two values
    153  *    must be set to 0 to avoid an accidental CSS inheritance &#42;/
    154  *   -webkit-transition-duration: 0s;
    155  *   transition-duration: 0s;
    156  * }
    157  * .my-animation.ng-enter.ng-enter-active {
    158  *   /&#42; standard transition styles &#42;/
    159  *   opacity:1;
    160  * }
    161  * ```
    162  *
    163  * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations
    164  * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this
    165  * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation
    166  * will also be reset if more than 10ms has passed after the last animation has been fired.
    167  *
    168  * The following code will issue the **ng-leave-stagger** event on the element provided:
    169  *
    170  * ```js
    171  * var kids = parent.children();
    172  *
    173  * $animate.leave(kids[0]); //stagger index=0
    174  * $animate.leave(kids[1]); //stagger index=1
    175  * $animate.leave(kids[2]); //stagger index=2
    176  * $animate.leave(kids[3]); //stagger index=3
    177  * $animate.leave(kids[4]); //stagger index=4
    178  *
    179  * $timeout(function() {
    180  *   //stagger has reset itself
    181  *   $animate.leave(kids[5]); //stagger index=0
    182  *   $animate.leave(kids[6]); //stagger index=1
    183  * }, 100, false);
    184  * ```
    185  *
    186  * Stagger animations are currently only supported within CSS-defined animations.
    187  *
    188  * <h2>JavaScript-defined Animations</h2>
    189  * In the event that you do not want to use CSS3 transitions or CSS3 animations or if you wish to offer animations on browsers that do not
    190  * yet support CSS transitions/animations, then you can make use of JavaScript animations defined inside of your AngularJS module.
    191  *
    192  * ```js
    193  * //!annotate="YourApp" Your AngularJS Module|Replace this or ngModule with the module that you used to define your application.
    194  * var ngModule = angular.module('YourApp', ['ngAnimate']);
    195  * ngModule.animation('.my-crazy-animation', function() {
    196  *   return {
    197  *     enter: function(element, done) {
    198  *       //run the animation here and call done when the animation is complete
    199  *       return function(cancelled) {
    200  *         //this (optional) function will be called when the animation
    201  *         //completes or when the animation is cancelled (the cancelled
    202  *         //flag will be set to true if cancelled).
    203  *       };
    204  *     },
    205  *     leave: function(element, done) { },
    206  *     move: function(element, done) { },
    207  *
    208  *     //animation that can be triggered before the class is added
    209  *     beforeAddClass: function(element, className, done) { },
    210  *
    211  *     //animation that can be triggered after the class is added
    212  *     addClass: function(element, className, done) { },
    213  *
    214  *     //animation that can be triggered before the class is removed
    215  *     beforeRemoveClass: function(element, className, done) { },
    216  *
    217  *     //animation that can be triggered after the class is removed
    218  *     removeClass: function(element, className, done) { }
    219  *   };
    220  * });
    221  * ```
    222  *
    223  * JavaScript-defined animations are created with a CSS-like class selector and a collection of events which are set to run
    224  * a javascript callback function. When an animation is triggered, $animate will look for a matching animation which fits
    225  * the element's CSS class attribute value and then run the matching animation event function (if found).
    226  * In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function will
    227  * be executed. It should be also noted that only simple, single class selectors are allowed (compound class selectors are not supported).
    228  *
    229  * Within a JavaScript animation, an object containing various event callback animation functions is expected to be returned.
    230  * As explained above, these callbacks are triggered based on the animation event. Therefore if an enter animation is run,
    231  * and the JavaScript animation is found, then the enter callback will handle that animation (in addition to the CSS keyframe animation
    232  * or transition code that is defined via a stylesheet).
    233  *
    234  */
    235 
    236 angular.module('ngAnimate', ['ng'])
    237 
    238   /**
    239    * @ngdoc provider
    240    * @name $animateProvider
    241    * @description
    242    *
    243    * The `$animateProvider` allows developers to register JavaScript animation event handlers directly inside of a module.
    244    * When an animation is triggered, the $animate service will query the $animate service to find any animations that match
    245    * the provided name value.
    246    *
    247    * Requires the {@link ngAnimate `ngAnimate`} module to be installed.
    248    *
    249    * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
    250    *
    251    */
    252 
    253   //this private service is only used within CSS-enabled animations
    254   //IE8 + IE9 do not support rAF natively, but that is fine since they
    255   //also don't support transitions and keyframes which means that the code
    256   //below will never be used by the two browsers.
    257   .factory('$$animateReflow', ['$$rAF', '$document', function($$rAF, $document) {
    258     var bod = $document[0].body;
    259     return function(fn) {
    260       //the returned function acts as the cancellation function
    261       return $$rAF(function() {
    262         //the line below will force the browser to perform a repaint
    263         //so that all the animated elements within the animation frame
    264         //will be properly updated and drawn on screen. This is
    265         //required to perform multi-class CSS based animations with
    266         //Firefox. DO NOT REMOVE THIS LINE.
    267         var a = bod.offsetWidth + 1;
    268         fn();
    269       });
    270     };
    271   }])
    272 
    273   .config(['$provide', '$animateProvider', function($provide, $animateProvider) {
    274     var noop = angular.noop;
    275     var forEach = angular.forEach;
    276     var selectors = $animateProvider.$$selectors;
    277 
    278     var ELEMENT_NODE = 1;
    279     var NG_ANIMATE_STATE = '$$ngAnimateState';
    280     var NG_ANIMATE_CLASS_NAME = 'ng-animate';
    281     var rootAnimateState = {running: true};
    282 
    283     function extractElementNode(element) {
    284       for(var i = 0; i < element.length; i++) {
    285         var elm = element[i];
    286         if(elm.nodeType == ELEMENT_NODE) {
    287           return elm;
    288         }
    289       }
    290     }
    291 
    292     function stripCommentsFromElement(element) {
    293       return angular.element(extractElementNode(element));
    294     }
    295 
    296     function isMatchingElement(elm1, elm2) {
    297       return extractElementNode(elm1) == extractElementNode(elm2);
    298     }
    299 
    300     $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document',
    301                             function($delegate,   $injector,   $sniffer,   $rootElement,   $$asyncCallback,    $rootScope,   $document) {
    302 
    303       var globalAnimationCounter = 0;
    304       $rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
    305 
    306       // disable animations during bootstrap, but once we bootstrapped, wait again
    307       // for another digest until enabling animations. The reason why we digest twice
    308       // is because all structural animations (enter, leave and move) all perform a
    309       // post digest operation before animating. If we only wait for a single digest
    310       // to pass then the structural animation would render its animation on page load.
    311       // (which is what we're trying to avoid when the application first boots up.)
    312       $rootScope.$$postDigest(function() {
    313         $rootScope.$$postDigest(function() {
    314           rootAnimateState.running = false;
    315         });
    316       });
    317 
    318       var classNameFilter = $animateProvider.classNameFilter();
    319       var isAnimatableClassName = !classNameFilter
    320               ? function() { return true; }
    321               : function(className) {
    322                 return classNameFilter.test(className);
    323               };
    324 
    325       function lookup(name) {
    326         if (name) {
    327           var matches = [],
    328               flagMap = {},
    329               classes = name.substr(1).split('.');
    330 
    331           //the empty string value is the default animation
    332           //operation which performs CSS transition and keyframe
    333           //animations sniffing. This is always included for each
    334           //element animation procedure if the browser supports
    335           //transitions and/or keyframe animations. The default
    336           //animation is added to the top of the list to prevent
    337           //any previous animations from affecting the element styling
    338           //prior to the element being animated.
    339           if ($sniffer.transitions || $sniffer.animations) {
    340             matches.push($injector.get(selectors['']));
    341           }
    342 
    343           for(var i=0; i < classes.length; i++) {
    344             var klass = classes[i],
    345                 selectorFactoryName = selectors[klass];
    346             if(selectorFactoryName && !flagMap[klass]) {
    347               matches.push($injector.get(selectorFactoryName));
    348               flagMap[klass] = true;
    349             }
    350           }
    351           return matches;
    352         }
    353       }
    354 
    355       function animationRunner(element, animationEvent, className) {
    356         //transcluded directives may sometimes fire an animation using only comment nodes
    357         //best to catch this early on to prevent any animation operations from occurring
    358         var node = element[0];
    359         if(!node) {
    360           return;
    361         }
    362 
    363         var isSetClassOperation = animationEvent == 'setClass';
    364         var isClassBased = isSetClassOperation ||
    365                            animationEvent == 'addClass' ||
    366                            animationEvent == 'removeClass';
    367 
    368         var classNameAdd, classNameRemove;
    369         if(angular.isArray(className)) {
    370           classNameAdd = className[0];
    371           classNameRemove = className[1];
    372           className = classNameAdd + ' ' + classNameRemove;
    373         }
    374 
    375         var currentClassName = element.attr('class');
    376         var classes = currentClassName + ' ' + className;
    377         if(!isAnimatableClassName(classes)) {
    378           return;
    379         }
    380 
    381         var beforeComplete = noop,
    382             beforeCancel = [],
    383             before = [],
    384             afterComplete = noop,
    385             afterCancel = [],
    386             after = [];
    387 
    388         var animationLookup = (' ' + classes).replace(/\s+/g,'.');
    389         forEach(lookup(animationLookup), function(animationFactory) {
    390           var created = registerAnimation(animationFactory, animationEvent);
    391           if(!created && isSetClassOperation) {
    392             registerAnimation(animationFactory, 'addClass');
    393             registerAnimation(animationFactory, 'removeClass');
    394           }
    395         });
    396 
    397         function registerAnimation(animationFactory, event) {
    398           var afterFn = animationFactory[event];
    399           var beforeFn = animationFactory['before' + event.charAt(0).toUpperCase() + event.substr(1)];
    400           if(afterFn || beforeFn) {
    401             if(event == 'leave') {
    402               beforeFn = afterFn;
    403               //when set as null then animation knows to skip this phase
    404               afterFn = null;
    405             }
    406             after.push({
    407               event : event, fn : afterFn
    408             });
    409             before.push({
    410               event : event, fn : beforeFn
    411             });
    412             return true;
    413           }
    414         }
    415 
    416         function run(fns, cancellations, allCompleteFn) {
    417           var animations = [];
    418           forEach(fns, function(animation) {
    419             animation.fn && animations.push(animation);
    420           });
    421 
    422           var count = 0;
    423           function afterAnimationComplete(index) {
    424             if(cancellations) {
    425               (cancellations[index] || noop)();
    426               if(++count < animations.length) return;
    427               cancellations = null;
    428             }
    429             allCompleteFn();
    430           }
    431 
    432           //The code below adds directly to the array in order to work with
    433           //both sync and async animations. Sync animations are when the done()
    434           //operation is called right away. DO NOT REFACTOR!
    435           forEach(animations, function(animation, index) {
    436             var progress = function() {
    437               afterAnimationComplete(index);
    438             };
    439             switch(animation.event) {
    440               case 'setClass':
    441                 cancellations.push(animation.fn(element, classNameAdd, classNameRemove, progress));
    442                 break;
    443               case 'addClass':
    444                 cancellations.push(animation.fn(element, classNameAdd || className,     progress));
    445                 break;
    446               case 'removeClass':
    447                 cancellations.push(animation.fn(element, classNameRemove || className,  progress));
    448                 break;
    449               default:
    450                 cancellations.push(animation.fn(element, progress));
    451                 break;
    452             }
    453           });
    454 
    455           if(cancellations && cancellations.length === 0) {
    456             allCompleteFn();
    457           }
    458         }
    459 
    460         return {
    461           node : node,
    462           event : animationEvent,
    463           className : className,
    464           isClassBased : isClassBased,
    465           isSetClassOperation : isSetClassOperation,
    466           before : function(allCompleteFn) {
    467             beforeComplete = allCompleteFn;
    468             run(before, beforeCancel, function() {
    469               beforeComplete = noop;
    470               allCompleteFn();
    471             });
    472           },
    473           after : function(allCompleteFn) {
    474             afterComplete = allCompleteFn;
    475             run(after, afterCancel, function() {
    476               afterComplete = noop;
    477               allCompleteFn();
    478             });
    479           },
    480           cancel : function() {
    481             if(beforeCancel) {
    482               forEach(beforeCancel, function(cancelFn) {
    483                 (cancelFn || noop)(true);
    484               });
    485               beforeComplete(true);
    486             }
    487             if(afterCancel) {
    488               forEach(afterCancel, function(cancelFn) {
    489                 (cancelFn || noop)(true);
    490               });
    491               afterComplete(true);
    492             }
    493           }
    494         };
    495       }
    496 
    497       /**
    498        * @ngdoc service
    499        * @name $animate
    500        * @function
    501        *
    502        * @description
    503        * The `$animate` service provides animation detection support while performing DOM operations (enter, leave and move) as well as during addClass and removeClass operations.
    504        * When any of these operations are run, the $animate service
    505        * will examine any JavaScript-defined animations (which are defined by using the $animateProvider provider object)
    506        * as well as any CSS-defined animations against the CSS classes present on the element once the DOM operation is run.
    507        *
    508        * The `$animate` service is used behind the scenes with pre-existing directives and animation with these directives
    509        * will work out of the box without any extra configuration.
    510        *
    511        * Requires the {@link ngAnimate `ngAnimate`} module to be installed.
    512        *
    513        * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
    514        *
    515        */
    516       return {
    517         /**
    518          * @ngdoc method
    519          * @name $animate#enter
    520          * @function
    521          *
    522          * @description
    523          * Appends the element to the parentElement element that resides in the document and then runs the enter animation. Once
    524          * the animation is started, the following CSS classes will be present on the element for the duration of the animation:
    525          *
    526          * Below is a breakdown of each step that occurs during enter animation:
    527          *
    528          * | Animation Step                                                                               | What the element class attribute looks like |
    529          * |----------------------------------------------------------------------------------------------|---------------------------------------------|
    530          * | 1. $animate.enter(...) is called                                                             | class="my-animation"                        |
    531          * | 2. element is inserted into the parentElement element or beside the afterElement element     | class="my-animation"                        |
    532          * | 3. $animate runs any JavaScript-defined animations on the element                            | class="my-animation ng-animate"             |
    533          * | 4. the .ng-enter class is added to the element                                               | class="my-animation ng-animate ng-enter"    |
    534          * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay  | class="my-animation ng-animate ng-enter"    |
    535          * | 6. $animate waits for 10ms (this performs a reflow)                                          | class="my-animation ng-animate ng-enter"    |
    536          * | 7. the .ng-enter-active and .ng-animate-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-enter ng-enter-active" |
    537          * | 8. $animate waits for X milliseconds for the animation to complete                           | class="my-animation ng-animate ng-animate-active ng-enter ng-enter-active" |
    538          * | 9. The animation ends and all generated CSS classes are removed from the element             | class="my-animation"                        |
    539          * | 10. The doneCallback() callback is fired (if provided)                                       | class="my-animation"                        |
    540          *
    541          * @param {DOMElement} element the element that will be the focus of the enter animation
    542          * @param {DOMElement} parentElement the parent element of the element that will be the focus of the enter animation
    543          * @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation
    544          * @param {function()=} doneCallback the callback function that will be called once the animation is complete
    545         */
    546         enter : function(element, parentElement, afterElement, doneCallback) {
    547           this.enabled(false, element);
    548           $delegate.enter(element, parentElement, afterElement);
    549           $rootScope.$$postDigest(function() {
    550             element = stripCommentsFromElement(element);
    551             performAnimation('enter', 'ng-enter', element, parentElement, afterElement, noop, doneCallback);
    552           });
    553         },
    554 
    555         /**
    556          * @ngdoc method
    557          * @name $animate#leave
    558          * @function
    559          *
    560          * @description
    561          * Runs the leave animation operation and, upon completion, removes the element from the DOM. Once
    562          * the animation is started, the following CSS classes will be added for the duration of the animation:
    563          *
    564          * Below is a breakdown of each step that occurs during leave animation:
    565          *
    566          * | Animation Step                                                                               | What the element class attribute looks like |
    567          * |----------------------------------------------------------------------------------------------|---------------------------------------------|
    568          * | 1. $animate.leave(...) is called                                                             | class="my-animation"                        |
    569          * | 2. $animate runs any JavaScript-defined animations on the element                            | class="my-animation ng-animate"             |
    570          * | 3. the .ng-leave class is added to the element                                               | class="my-animation ng-animate ng-leave"    |
    571          * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay  | class="my-animation ng-animate ng-leave"    |
    572          * | 5. $animate waits for 10ms (this performs a reflow)                                          | class="my-animation ng-animate ng-leave"    |
    573          * | 6. the .ng-leave-active and .ng-animate-active classes is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-leave ng-leave-active" |
    574          * | 7. $animate waits for X milliseconds for the animation to complete                           | class="my-animation ng-animate ng-animate-active ng-leave ng-leave-active" |
    575          * | 8. The animation ends and all generated CSS classes are removed from the element             | class="my-animation"                        |
    576          * | 9. The element is removed from the DOM                                                       | ...                                         |
    577          * | 10. The doneCallback() callback is fired (if provided)                                       | ...                                         |
    578          *
    579          * @param {DOMElement} element the element that will be the focus of the leave animation
    580          * @param {function()=} doneCallback the callback function that will be called once the animation is complete
    581         */
    582         leave : function(element, doneCallback) {
    583           cancelChildAnimations(element);
    584           this.enabled(false, element);
    585           $rootScope.$$postDigest(function() {
    586             performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
    587               $delegate.leave(element);
    588             }, doneCallback);
    589           });
    590         },
    591 
    592         /**
    593          * @ngdoc method
    594          * @name $animate#move
    595          * @function
    596          *
    597          * @description
    598          * Fires the move DOM operation. Just before the animation starts, the animate service will either append it into the parentElement container or
    599          * add the element directly after the afterElement element if present. Then the move animation will be run. Once
    600          * the animation is started, the following CSS classes will be added for the duration of the animation:
    601          *
    602          * Below is a breakdown of each step that occurs during move animation:
    603          *
    604          * | Animation Step                                                                               | What the element class attribute looks like |
    605          * |----------------------------------------------------------------------------------------------|---------------------------------------------|
    606          * | 1. $animate.move(...) is called                                                              | class="my-animation"                        |
    607          * | 2. element is moved into the parentElement element or beside the afterElement element        | class="my-animation"                        |
    608          * | 3. $animate runs any JavaScript-defined animations on the element                            | class="my-animation ng-animate"             |
    609          * | 4. the .ng-move class is added to the element                                                | class="my-animation ng-animate ng-move"     |
    610          * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay  | class="my-animation ng-animate ng-move"     |
    611          * | 6. $animate waits for 10ms (this performs a reflow)                                          | class="my-animation ng-animate ng-move"     |
    612          * | 7. the .ng-move-active and .ng-animate-active classes is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-move ng-move-active" |
    613          * | 8. $animate waits for X milliseconds for the animation to complete                           | class="my-animation ng-animate ng-animate-active ng-move ng-move-active" |
    614          * | 9. The animation ends and all generated CSS classes are removed from the element             | class="my-animation"                        |
    615          * | 10. The doneCallback() callback is fired (if provided)                                       | class="my-animation"                        |
    616          *
    617          * @param {DOMElement} element the element that will be the focus of the move animation
    618          * @param {DOMElement} parentElement the parentElement element of the element that will be the focus of the move animation
    619          * @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation
    620          * @param {function()=} doneCallback the callback function that will be called once the animation is complete
    621         */
    622         move : function(element, parentElement, afterElement, doneCallback) {
    623           cancelChildAnimations(element);
    624           this.enabled(false, element);
    625           $delegate.move(element, parentElement, afterElement);
    626           $rootScope.$$postDigest(function() {
    627             element = stripCommentsFromElement(element);
    628             performAnimation('move', 'ng-move', element, parentElement, afterElement, noop, doneCallback);
    629           });
    630         },
    631 
    632         /**
    633          * @ngdoc method
    634          * @name $animate#addClass
    635          *
    636          * @description
    637          * Triggers a custom animation event based off the className variable and then attaches the className value to the element as a CSS class.
    638          * Unlike the other animation methods, the animate service will suffix the className value with {@type -add} in order to provide
    639          * the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if no CSS transitions
    640          * or keyframes are defined on the -add or base CSS class).
    641          *
    642          * Below is a breakdown of each step that occurs during addClass animation:
    643          *
    644          * | Animation Step                                                                                 | What the element class attribute looks like |
    645          * |------------------------------------------------------------------------------------------------|---------------------------------------------|
    646          * | 1. $animate.addClass(element, 'super') is called                                               | class="my-animation"                        |
    647          * | 2. $animate runs any JavaScript-defined animations on the element                              | class="my-animation ng-animate"             |
    648          * | 3. the .super-add class are added to the element                                               | class="my-animation ng-animate super-add"   |
    649          * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay    | class="my-animation ng-animate super-add"   |
    650          * | 5. $animate waits for 10ms (this performs a reflow)                                            | class="my-animation ng-animate super-add"   |
    651          * | 6. the .super, .super-add-active and .ng-animate-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active super super-add super-add-active"          |
    652          * | 7. $animate waits for X milliseconds for the animation to complete                             | class="my-animation super super-add super-add-active"  |
    653          * | 8. The animation ends and all generated CSS classes are removed from the element               | class="my-animation super"                  |
    654          * | 9. The super class is kept on the element                                                      | class="my-animation super"                  |
    655          * | 10. The doneCallback() callback is fired (if provided)                                         | class="my-animation super"                  |
    656          *
    657          * @param {DOMElement} element the element that will be animated
    658          * @param {string} className the CSS class that will be added to the element and then animated
    659          * @param {function()=} doneCallback the callback function that will be called once the animation is complete
    660         */
    661         addClass : function(element, className, doneCallback) {
    662           element = stripCommentsFromElement(element);
    663           performAnimation('addClass', className, element, null, null, function() {
    664             $delegate.addClass(element, className);
    665           }, doneCallback);
    666         },
    667 
    668         /**
    669          * @ngdoc method
    670          * @name $animate#removeClass
    671          *
    672          * @description
    673          * Triggers a custom animation event based off the className variable and then removes the CSS class provided by the className value
    674          * from the element. Unlike the other animation methods, the animate service will suffix the className value with {@type -remove} in
    675          * order to provide the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if
    676          * no CSS transitions or keyframes are defined on the -remove or base CSS classes).
    677          *
    678          * Below is a breakdown of each step that occurs during removeClass animation:
    679          *
    680          * | Animation Step                                                                                | What the element class attribute looks like     |
    681          * |-----------------------------------------------------------------------------------------------|---------------------------------------------|
    682          * | 1. $animate.removeClass(element, 'super') is called                                           | class="my-animation super"                  |
    683          * | 2. $animate runs any JavaScript-defined animations on the element                             | class="my-animation super ng-animate"       |
    684          * | 3. the .super-remove class are added to the element                                           | class="my-animation super ng-animate super-remove"|
    685          * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay   | class="my-animation super ng-animate super-remove"   |
    686          * | 5. $animate waits for 10ms (this performs a reflow)                                           | class="my-animation super ng-animate super-remove"   |
    687          * | 6. the .super-remove-active and .ng-animate-active classes are added and .super is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active super-remove super-remove-active"          |
    688          * | 7. $animate waits for X milliseconds for the animation to complete                            | class="my-animation ng-animate ng-animate-active super-remove super-remove-active"   |
    689          * | 8. The animation ends and all generated CSS classes are removed from the element              | class="my-animation"                        |
    690          * | 9. The doneCallback() callback is fired (if provided)                                         | class="my-animation"                        |
    691          *
    692          *
    693          * @param {DOMElement} element the element that will be animated
    694          * @param {string} className the CSS class that will be animated and then removed from the element
    695          * @param {function()=} doneCallback the callback function that will be called once the animation is complete
    696         */
    697         removeClass : function(element, className, doneCallback) {
    698           element = stripCommentsFromElement(element);
    699           performAnimation('removeClass', className, element, null, null, function() {
    700             $delegate.removeClass(element, className);
    701           }, doneCallback);
    702         },
    703 
    704           /**
    705            *
    706            * @ngdoc function
    707            * @name $animate#setClass
    708            * @function
    709            * @description Adds and/or removes the given CSS classes to and from the element.
    710            * Once complete, the done() callback will be fired (if provided).
    711            * @param {DOMElement} element the element which will it's CSS classes changed
    712            *   removed from it
    713            * @param {string} add the CSS classes which will be added to the element
    714            * @param {string} remove the CSS class which will be removed from the element
    715            * @param {Function=} done the callback function (if provided) that will be fired after the
    716            *   CSS classes have been set on the element
    717            */
    718         setClass : function(element, add, remove, doneCallback) {
    719           element = stripCommentsFromElement(element);
    720           performAnimation('setClass', [add, remove], element, null, null, function() {
    721             $delegate.setClass(element, add, remove);
    722           }, doneCallback);
    723         },
    724 
    725         /**
    726          * @ngdoc method
    727          * @name $animate#enabled
    728          * @function
    729          *
    730          * @param {boolean=} value If provided then set the animation on or off.
    731          * @param {DOMElement=} element If provided then the element will be used to represent the enable/disable operation
    732          * @return {boolean} Current animation state.
    733          *
    734          * @description
    735          * Globally enables/disables animations.
    736          *
    737         */
    738         enabled : function(value, element) {
    739           switch(arguments.length) {
    740             case 2:
    741               if(value) {
    742                 cleanup(element);
    743               } else {
    744                 var data = element.data(NG_ANIMATE_STATE) || {};
    745                 data.disabled = true;
    746                 element.data(NG_ANIMATE_STATE, data);
    747               }
    748             break;
    749 
    750             case 1:
    751               rootAnimateState.disabled = !value;
    752             break;
    753 
    754             default:
    755               value = !rootAnimateState.disabled;
    756             break;
    757           }
    758           return !!value;
    759          }
    760       };
    761 
    762       /*
    763         all animations call this shared animation triggering function internally.
    764         The animationEvent variable refers to the JavaScript animation event that will be triggered
    765         and the className value is the name of the animation that will be applied within the
    766         CSS code. Element, parentElement and afterElement are provided DOM elements for the animation
    767         and the onComplete callback will be fired once the animation is fully complete.
    768       */
    769       function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
    770 
    771         var runner = animationRunner(element, animationEvent, className);
    772         if(!runner) {
    773           fireDOMOperation();
    774           fireBeforeCallbackAsync();
    775           fireAfterCallbackAsync();
    776           closeAnimation();
    777           return;
    778         }
    779 
    780         className = runner.className;
    781         var elementEvents = angular.element._data(runner.node);
    782         elementEvents = elementEvents && elementEvents.events;
    783 
    784         if (!parentElement) {
    785           parentElement = afterElement ? afterElement.parent() : element.parent();
    786         }
    787 
    788         var ngAnimateState  = element.data(NG_ANIMATE_STATE) || {};
    789         var runningAnimations     = ngAnimateState.active || {};
    790         var totalActiveAnimations = ngAnimateState.totalActive || 0;
    791         var lastAnimation         = ngAnimateState.last;
    792 
    793         //only allow animations if the currently running animation is not structural
    794         //or if there is no animation running at all
    795         var skipAnimations = runner.isClassBased ?
    796           ngAnimateState.disabled || (lastAnimation && !lastAnimation.isClassBased) :
    797           false;
    798 
    799         //skip the animation if animations are disabled, a parent is already being animated,
    800         //the element is not currently attached to the document body or then completely close
    801         //the animation if any matching animations are not found at all.
    802         //NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case an animation was found.
    803         if (skipAnimations || animationsDisabled(element, parentElement)) {
    804           fireDOMOperation();
    805           fireBeforeCallbackAsync();
    806           fireAfterCallbackAsync();
    807           closeAnimation();
    808           return;
    809         }
    810 
    811         var skipAnimation = false;
    812         if(totalActiveAnimations > 0) {
    813           var animationsToCancel = [];
    814           if(!runner.isClassBased) {
    815             if(animationEvent == 'leave' && runningAnimations['ng-leave']) {
    816               skipAnimation = true;
    817             } else {
    818               //cancel all animations when a structural animation takes place
    819               for(var klass in runningAnimations) {
    820                 animationsToCancel.push(runningAnimations[klass]);
    821                 cleanup(element, klass);
    822               }
    823               runningAnimations = {};
    824               totalActiveAnimations = 0;
    825             }
    826           } else if(lastAnimation.event == 'setClass') {
    827             animationsToCancel.push(lastAnimation);
    828             cleanup(element, className);
    829           }
    830           else if(runningAnimations[className]) {
    831             var current = runningAnimations[className];
    832             if(current.event == animationEvent) {
    833               skipAnimation = true;
    834             } else {
    835               animationsToCancel.push(current);
    836               cleanup(element, className);
    837             }
    838           }
    839 
    840           if(animationsToCancel.length > 0) {
    841             forEach(animationsToCancel, function(operation) {
    842               operation.cancel();
    843             });
    844           }
    845         }
    846 
    847         if(runner.isClassBased && !runner.isSetClassOperation && !skipAnimation) {
    848           skipAnimation = (animationEvent == 'addClass') == element.hasClass(className); //opposite of XOR
    849         }
    850 
    851         if(skipAnimation) {
    852           fireBeforeCallbackAsync();
    853           fireAfterCallbackAsync();
    854           fireDoneCallbackAsync();
    855           return;
    856         }
    857 
    858         if(animationEvent == 'leave') {
    859           //there's no need to ever remove the listener since the element
    860           //will be removed (destroyed) after the leave animation ends or
    861           //is cancelled midway
    862           element.one('$destroy', function(e) {
    863             var element = angular.element(this);
    864             var state = element.data(NG_ANIMATE_STATE);
    865             if(state) {
    866               var activeLeaveAnimation = state.active['ng-leave'];
    867               if(activeLeaveAnimation) {
    868                 activeLeaveAnimation.cancel();
    869                 cleanup(element, 'ng-leave');
    870               }
    871             }
    872           });
    873         }
    874 
    875         //the ng-animate class does nothing, but it's here to allow for
    876         //parent animations to find and cancel child animations when needed
    877         element.addClass(NG_ANIMATE_CLASS_NAME);
    878 
    879         var localAnimationCount = globalAnimationCounter++;
    880         totalActiveAnimations++;
    881         runningAnimations[className] = runner;
    882 
    883         element.data(NG_ANIMATE_STATE, {
    884           last : runner,
    885           active : runningAnimations,
    886           index : localAnimationCount,
    887           totalActive : totalActiveAnimations
    888         });
    889 
    890         //first we run the before animations and when all of those are complete
    891         //then we perform the DOM operation and run the next set of animations
    892         fireBeforeCallbackAsync();
    893         runner.before(function(cancelled) {
    894           var data = element.data(NG_ANIMATE_STATE);
    895           cancelled = cancelled ||
    896                         !data || !data.active[className] ||
    897                         (runner.isClassBased && data.active[className].event != animationEvent);
    898 
    899           fireDOMOperation();
    900           if(cancelled === true) {
    901             closeAnimation();
    902           } else {
    903             fireAfterCallbackAsync();
    904             runner.after(closeAnimation);
    905           }
    906         });
    907 
    908         function fireDOMCallback(animationPhase) {
    909           var eventName = '$animate:' + animationPhase;
    910           if(elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) {
    911             $$asyncCallback(function() {
    912               element.triggerHandler(eventName, {
    913                 event : animationEvent,
    914                 className : className
    915               });
    916             });
    917           }
    918         }
    919 
    920         function fireBeforeCallbackAsync() {
    921           fireDOMCallback('before');
    922         }
    923 
    924         function fireAfterCallbackAsync() {
    925           fireDOMCallback('after');
    926         }
    927 
    928         function fireDoneCallbackAsync() {
    929           fireDOMCallback('close');
    930           if(doneCallback) {
    931             $$asyncCallback(function() {
    932               doneCallback();
    933             });
    934           }
    935         }
    936 
    937         //it is less complicated to use a flag than managing and canceling
    938         //timeouts containing multiple callbacks.
    939         function fireDOMOperation() {
    940           if(!fireDOMOperation.hasBeenRun) {
    941             fireDOMOperation.hasBeenRun = true;
    942             domOperation();
    943           }
    944         }
    945 
    946         function closeAnimation() {
    947           if(!closeAnimation.hasBeenRun) {
    948             closeAnimation.hasBeenRun = true;
    949             var data = element.data(NG_ANIMATE_STATE);
    950             if(data) {
    951               /* only structural animations wait for reflow before removing an
    952                  animation, but class-based animations don't. An example of this
    953                  failing would be when a parent HTML tag has a ng-class attribute
    954                  causing ALL directives below to skip animations during the digest */
    955               if(runner && runner.isClassBased) {
    956                 cleanup(element, className);
    957               } else {
    958                 $$asyncCallback(function() {
    959                   var data = element.data(NG_ANIMATE_STATE) || {};
    960                   if(localAnimationCount == data.index) {
    961                     cleanup(element, className, animationEvent);
    962                   }
    963                 });
    964                 element.data(NG_ANIMATE_STATE, data);
    965               }
    966             }
    967             fireDoneCallbackAsync();
    968           }
    969         }
    970       }
    971 
    972       function cancelChildAnimations(element) {
    973         var node = extractElementNode(element);
    974         if (node) {
    975           var nodes = angular.isFunction(node.getElementsByClassName) ?
    976             node.getElementsByClassName(NG_ANIMATE_CLASS_NAME) :
    977             node.querySelectorAll('.' + NG_ANIMATE_CLASS_NAME);
    978           forEach(nodes, function(element) {
    979             element = angular.element(element);
    980             var data = element.data(NG_ANIMATE_STATE);
    981             if(data && data.active) {
    982               forEach(data.active, function(runner) {
    983                 runner.cancel();
    984               });
    985             }
    986           });
    987         }
    988       }
    989 
    990       function cleanup(element, className) {
    991         if(isMatchingElement(element, $rootElement)) {
    992           if(!rootAnimateState.disabled) {
    993             rootAnimateState.running = false;
    994             rootAnimateState.structural = false;
    995           }
    996         } else if(className) {
    997           var data = element.data(NG_ANIMATE_STATE) || {};
    998 
    999           var removeAnimations = className === true;
   1000           if(!removeAnimations && data.active && data.active[className]) {
   1001             data.totalActive--;
   1002             delete data.active[className];
   1003           }
   1004 
   1005           if(removeAnimations || !data.totalActive) {
   1006             element.removeClass(NG_ANIMATE_CLASS_NAME);
   1007             element.removeData(NG_ANIMATE_STATE);
   1008           }
   1009         }
   1010       }
   1011 
   1012       function animationsDisabled(element, parentElement) {
   1013         if (rootAnimateState.disabled) return true;
   1014 
   1015         if(isMatchingElement(element, $rootElement)) {
   1016           return rootAnimateState.disabled || rootAnimateState.running;
   1017         }
   1018 
   1019         do {
   1020           //the element did not reach the root element which means that it
   1021           //is not apart of the DOM. Therefore there is no reason to do
   1022           //any animations on it
   1023           if(parentElement.length === 0) break;
   1024 
   1025           var isRoot = isMatchingElement(parentElement, $rootElement);
   1026           var state = isRoot ? rootAnimateState : parentElement.data(NG_ANIMATE_STATE);
   1027           var result = state && (!!state.disabled || state.running || state.totalActive > 0);
   1028           if(isRoot || result) {
   1029             return result;
   1030           }
   1031 
   1032           if(isRoot) return true;
   1033         }
   1034         while(parentElement = parentElement.parent());
   1035 
   1036         return true;
   1037       }
   1038     }]);
   1039 
   1040     $animateProvider.register('', ['$window', '$sniffer', '$timeout', '$$animateReflow',
   1041                            function($window,   $sniffer,   $timeout,   $$animateReflow) {
   1042       // Detect proper transitionend/animationend event names.
   1043       var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;
   1044 
   1045       // If unprefixed events are not supported but webkit-prefixed are, use the latter.
   1046       // Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
   1047       // Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend`
   1048       // but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`.
   1049       // Register both events in case `window.onanimationend` is not supported because of that,
   1050       // do the same for `transitionend` as Safari is likely to exhibit similar behavior.
   1051       // Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
   1052       // therefore there is no reason to test anymore for other vendor prefixes: http://caniuse.com/#search=transition
   1053       if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) {
   1054         CSS_PREFIX = '-webkit-';
   1055         TRANSITION_PROP = 'WebkitTransition';
   1056         TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
   1057       } else {
   1058         TRANSITION_PROP = 'transition';
   1059         TRANSITIONEND_EVENT = 'transitionend';
   1060       }
   1061 
   1062       if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) {
   1063         CSS_PREFIX = '-webkit-';
   1064         ANIMATION_PROP = 'WebkitAnimation';
   1065         ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
   1066       } else {
   1067         ANIMATION_PROP = 'animation';
   1068         ANIMATIONEND_EVENT = 'animationend';
   1069       }
   1070 
   1071       var DURATION_KEY = 'Duration';
   1072       var PROPERTY_KEY = 'Property';
   1073       var DELAY_KEY = 'Delay';
   1074       var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
   1075       var NG_ANIMATE_PARENT_KEY = '$$ngAnimateKey';
   1076       var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data';
   1077       var NG_ANIMATE_BLOCK_CLASS_NAME = 'ng-animate-block-transitions';
   1078       var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
   1079       var CLOSING_TIME_BUFFER = 1.5;
   1080       var ONE_SECOND = 1000;
   1081 
   1082       var lookupCache = {};
   1083       var parentCounter = 0;
   1084       var animationReflowQueue = [];
   1085       var cancelAnimationReflow;
   1086       function afterReflow(element, callback) {
   1087         if(cancelAnimationReflow) {
   1088           cancelAnimationReflow();
   1089         }
   1090         animationReflowQueue.push(callback);
   1091         cancelAnimationReflow = $$animateReflow(function() {
   1092           forEach(animationReflowQueue, function(fn) {
   1093             fn();
   1094           });
   1095 
   1096           animationReflowQueue = [];
   1097           cancelAnimationReflow = null;
   1098           lookupCache = {};
   1099         });
   1100       }
   1101 
   1102       var closingTimer = null;
   1103       var closingTimestamp = 0;
   1104       var animationElementQueue = [];
   1105       function animationCloseHandler(element, totalTime) {
   1106         var node = extractElementNode(element);
   1107         element = angular.element(node);
   1108 
   1109         //this item will be garbage collected by the closing
   1110         //animation timeout
   1111         animationElementQueue.push(element);
   1112 
   1113         //but it may not need to cancel out the existing timeout
   1114         //if the timestamp is less than the previous one
   1115         var futureTimestamp = Date.now() + totalTime;
   1116         if(futureTimestamp <= closingTimestamp) {
   1117           return;
   1118         }
   1119 
   1120         $timeout.cancel(closingTimer);
   1121 
   1122         closingTimestamp = futureTimestamp;
   1123         closingTimer = $timeout(function() {
   1124           closeAllAnimations(animationElementQueue);
   1125           animationElementQueue = [];
   1126         }, totalTime, false);
   1127       }
   1128 
   1129       function closeAllAnimations(elements) {
   1130         forEach(elements, function(element) {
   1131           var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
   1132           if(elementData) {
   1133             (elementData.closeAnimationFn || noop)();
   1134           }
   1135         });
   1136       }
   1137 
   1138       function getElementAnimationDetails(element, cacheKey) {
   1139         var data = cacheKey ? lookupCache[cacheKey] : null;
   1140         if(!data) {
   1141           var transitionDuration = 0;
   1142           var transitionDelay = 0;
   1143           var animationDuration = 0;
   1144           var animationDelay = 0;
   1145           var transitionDelayStyle;
   1146           var animationDelayStyle;
   1147           var transitionDurationStyle;
   1148           var transitionPropertyStyle;
   1149 
   1150           //we want all the styles defined before and after
   1151           forEach(element, function(element) {
   1152             if (element.nodeType == ELEMENT_NODE) {
   1153               var elementStyles = $window.getComputedStyle(element) || {};
   1154 
   1155               transitionDurationStyle = elementStyles[TRANSITION_PROP + DURATION_KEY];
   1156 
   1157               transitionDuration = Math.max(parseMaxTime(transitionDurationStyle), transitionDuration);
   1158 
   1159               transitionPropertyStyle = elementStyles[TRANSITION_PROP + PROPERTY_KEY];
   1160 
   1161               transitionDelayStyle = elementStyles[TRANSITION_PROP + DELAY_KEY];
   1162 
   1163               transitionDelay  = Math.max(parseMaxTime(transitionDelayStyle), transitionDelay);
   1164 
   1165               animationDelayStyle = elementStyles[ANIMATION_PROP + DELAY_KEY];
   1166 
   1167               animationDelay   = Math.max(parseMaxTime(animationDelayStyle), animationDelay);
   1168 
   1169               var aDuration  = parseMaxTime(elementStyles[ANIMATION_PROP + DURATION_KEY]);
   1170 
   1171               if(aDuration > 0) {
   1172                 aDuration *= parseInt(elementStyles[ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY], 10) || 1;
   1173               }
   1174 
   1175               animationDuration = Math.max(aDuration, animationDuration);
   1176             }
   1177           });
   1178           data = {
   1179             total : 0,
   1180             transitionPropertyStyle: transitionPropertyStyle,
   1181             transitionDurationStyle: transitionDurationStyle,
   1182             transitionDelayStyle: transitionDelayStyle,
   1183             transitionDelay: transitionDelay,
   1184             transitionDuration: transitionDuration,
   1185             animationDelayStyle: animationDelayStyle,
   1186             animationDelay: animationDelay,
   1187             animationDuration: animationDuration
   1188           };
   1189           if(cacheKey) {
   1190             lookupCache[cacheKey] = data;
   1191           }
   1192         }
   1193         return data;
   1194       }
   1195 
   1196       function parseMaxTime(str) {
   1197         var maxValue = 0;
   1198         var values = angular.isString(str) ?
   1199           str.split(/\s*,\s*/) :
   1200           [];
   1201         forEach(values, function(value) {
   1202           maxValue = Math.max(parseFloat(value) || 0, maxValue);
   1203         });
   1204         return maxValue;
   1205       }
   1206 
   1207       function getCacheKey(element) {
   1208         var parentElement = element.parent();
   1209         var parentID = parentElement.data(NG_ANIMATE_PARENT_KEY);
   1210         if(!parentID) {
   1211           parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter);
   1212           parentID = parentCounter;
   1213         }
   1214         return parentID + '-' + extractElementNode(element).getAttribute('class');
   1215       }
   1216 
   1217       function animateSetup(animationEvent, element, className, calculationDecorator) {
   1218         var cacheKey = getCacheKey(element);
   1219         var eventCacheKey = cacheKey + ' ' + className;
   1220         var itemIndex = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0;
   1221 
   1222         var stagger = {};
   1223         if(itemIndex > 0) {
   1224           var staggerClassName = className + '-stagger';
   1225           var staggerCacheKey = cacheKey + ' ' + staggerClassName;
   1226           var applyClasses = !lookupCache[staggerCacheKey];
   1227 
   1228           applyClasses && element.addClass(staggerClassName);
   1229 
   1230           stagger = getElementAnimationDetails(element, staggerCacheKey);
   1231 
   1232           applyClasses && element.removeClass(staggerClassName);
   1233         }
   1234 
   1235         /* the animation itself may need to add/remove special CSS classes
   1236          * before calculating the anmation styles */
   1237         calculationDecorator = calculationDecorator ||
   1238                                function(fn) { return fn(); };
   1239 
   1240         element.addClass(className);
   1241 
   1242         var formerData = element.data(NG_ANIMATE_CSS_DATA_KEY) || {};
   1243 
   1244         var timings = calculationDecorator(function() {
   1245           return getElementAnimationDetails(element, eventCacheKey);
   1246         });
   1247 
   1248         var transitionDuration = timings.transitionDuration;
   1249         var animationDuration = timings.animationDuration;
   1250         if(transitionDuration === 0 && animationDuration === 0) {
   1251           element.removeClass(className);
   1252           return false;
   1253         }
   1254 
   1255         element.data(NG_ANIMATE_CSS_DATA_KEY, {
   1256           running : formerData.running || 0,
   1257           itemIndex : itemIndex,
   1258           stagger : stagger,
   1259           timings : timings,
   1260           closeAnimationFn : noop
   1261         });
   1262 
   1263         //temporarily disable the transition so that the enter styles
   1264         //don't animate twice (this is here to avoid a bug in Chrome/FF).
   1265         var isCurrentlyAnimating = formerData.running > 0 || animationEvent == 'setClass';
   1266         if(transitionDuration > 0) {
   1267           blockTransitions(element, className, isCurrentlyAnimating);
   1268         }
   1269 
   1270         //staggering keyframe animations work by adjusting the `animation-delay` CSS property
   1271         //on the given element, however, the delay value can only calculated after the reflow
   1272         //since by that time $animate knows how many elements are being animated. Therefore,
   1273         //until the reflow occurs the element needs to be blocked (where the keyframe animation
   1274         //is set to `none 0s`). This blocking mechanism should only be set for when a stagger
   1275         //animation is detected and when the element item index is greater than 0.
   1276         if(animationDuration > 0 && stagger.animationDelay > 0 && stagger.animationDuration === 0) {
   1277           blockKeyframeAnimations(element);
   1278         }
   1279 
   1280         return true;
   1281       }
   1282 
   1283       function isStructuralAnimation(className) {
   1284         return className == 'ng-enter' || className == 'ng-move' || className == 'ng-leave';
   1285       }
   1286 
   1287       function blockTransitions(element, className, isAnimating) {
   1288         if(isStructuralAnimation(className) || !isAnimating) {
   1289           extractElementNode(element).style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
   1290         } else {
   1291           element.addClass(NG_ANIMATE_BLOCK_CLASS_NAME);
   1292         }
   1293       }
   1294 
   1295       function blockKeyframeAnimations(element) {
   1296         extractElementNode(element).style[ANIMATION_PROP] = 'none 0s';
   1297       }
   1298 
   1299       function unblockTransitions(element, className) {
   1300         var prop = TRANSITION_PROP + PROPERTY_KEY;
   1301         var node = extractElementNode(element);
   1302         if(node.style[prop] && node.style[prop].length > 0) {
   1303           node.style[prop] = '';
   1304         }
   1305         element.removeClass(NG_ANIMATE_BLOCK_CLASS_NAME);
   1306       }
   1307 
   1308       function unblockKeyframeAnimations(element) {
   1309         var prop = ANIMATION_PROP;
   1310         var node = extractElementNode(element);
   1311         if(node.style[prop] && node.style[prop].length > 0) {
   1312           node.style[prop] = '';
   1313         }
   1314       }
   1315 
   1316       function animateRun(animationEvent, element, className, activeAnimationComplete) {
   1317         var node = extractElementNode(element);
   1318         var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
   1319         if(node.getAttribute('class').indexOf(className) == -1 || !elementData) {
   1320           activeAnimationComplete();
   1321           return;
   1322         }
   1323 
   1324         var activeClassName = '';
   1325         forEach(className.split(' '), function(klass, i) {
   1326           activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
   1327         });
   1328 
   1329         var stagger = elementData.stagger;
   1330         var timings = elementData.timings;
   1331         var itemIndex = elementData.itemIndex;
   1332         var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
   1333         var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay);
   1334         var maxDelayTime = maxDelay * ONE_SECOND;
   1335 
   1336         var startTime = Date.now();
   1337         var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
   1338 
   1339         var style = '', appliedStyles = [];
   1340         if(timings.transitionDuration > 0) {
   1341           var propertyStyle = timings.transitionPropertyStyle;
   1342           if(propertyStyle.indexOf('all') == -1) {
   1343             style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ';';
   1344             style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + ';';
   1345             appliedStyles.push(CSS_PREFIX + 'transition-property');
   1346             appliedStyles.push(CSS_PREFIX + 'transition-duration');
   1347           }
   1348         }
   1349 
   1350         if(itemIndex > 0) {
   1351           if(stagger.transitionDelay > 0 && stagger.transitionDuration === 0) {
   1352             var delayStyle = timings.transitionDelayStyle;
   1353             style += CSS_PREFIX + 'transition-delay: ' +
   1354                      prepareStaggerDelay(delayStyle, stagger.transitionDelay, itemIndex) + '; ';
   1355             appliedStyles.push(CSS_PREFIX + 'transition-delay');
   1356           }
   1357 
   1358           if(stagger.animationDelay > 0 && stagger.animationDuration === 0) {
   1359             style += CSS_PREFIX + 'animation-delay: ' +
   1360                      prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, itemIndex) + '; ';
   1361             appliedStyles.push(CSS_PREFIX + 'animation-delay');
   1362           }
   1363         }
   1364 
   1365         if(appliedStyles.length > 0) {
   1366           //the element being animated may sometimes contain comment nodes in
   1367           //the jqLite object, so we're safe to use a single variable to house
   1368           //the styles since there is always only one element being animated
   1369           var oldStyle = node.getAttribute('style') || '';
   1370           node.setAttribute('style', oldStyle + ' ' + style);
   1371         }
   1372 
   1373         element.on(css3AnimationEvents, onAnimationProgress);
   1374         element.addClass(activeClassName);
   1375         elementData.closeAnimationFn = function() {
   1376           onEnd();
   1377           activeAnimationComplete();
   1378         };
   1379 
   1380         var staggerTime       = itemIndex * (Math.max(stagger.animationDelay, stagger.transitionDelay) || 0);
   1381         var animationTime     = (maxDelay + maxDuration) * CLOSING_TIME_BUFFER;
   1382         var totalTime         = (staggerTime + animationTime) * ONE_SECOND;
   1383 
   1384         elementData.running++;
   1385         animationCloseHandler(element, totalTime);
   1386         return onEnd;
   1387 
   1388         // This will automatically be called by $animate so
   1389         // there is no need to attach this internally to the
   1390         // timeout done method.
   1391         function onEnd(cancelled) {
   1392           element.off(css3AnimationEvents, onAnimationProgress);
   1393           element.removeClass(activeClassName);
   1394           animateClose(element, className);
   1395           var node = extractElementNode(element);
   1396           for (var i in appliedStyles) {
   1397             node.style.removeProperty(appliedStyles[i]);
   1398           }
   1399         }
   1400 
   1401         function onAnimationProgress(event) {
   1402           event.stopPropagation();
   1403           var ev = event.originalEvent || event;
   1404           var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now();
   1405 
   1406           /* Firefox (or possibly just Gecko) likes to not round values up
   1407            * when a ms measurement is used for the animation */
   1408           var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
   1409 
   1410           /* $manualTimeStamp is a mocked timeStamp value which is set
   1411            * within browserTrigger(). This is only here so that tests can
   1412            * mock animations properly. Real events fallback to event.timeStamp,
   1413            * or, if they don't, then a timeStamp is automatically created for them.
   1414            * We're checking to see if the timeStamp surpasses the expected delay,
   1415            * but we're using elapsedTime instead of the timeStamp on the 2nd
   1416            * pre-condition since animations sometimes close off early */
   1417           if(Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
   1418             activeAnimationComplete();
   1419           }
   1420         }
   1421       }
   1422 
   1423       function prepareStaggerDelay(delayStyle, staggerDelay, index) {
   1424         var style = '';
   1425         forEach(delayStyle.split(','), function(val, i) {
   1426           style += (i > 0 ? ',' : '') +
   1427                    (index * staggerDelay + parseInt(val, 10)) + 's';
   1428         });
   1429         return style;
   1430       }
   1431 
   1432       function animateBefore(animationEvent, element, className, calculationDecorator) {
   1433         if(animateSetup(animationEvent, element, className, calculationDecorator)) {
   1434           return function(cancelled) {
   1435             cancelled && animateClose(element, className);
   1436           };
   1437         }
   1438       }
   1439 
   1440       function animateAfter(animationEvent, element, className, afterAnimationComplete) {
   1441         if(element.data(NG_ANIMATE_CSS_DATA_KEY)) {
   1442           return animateRun(animationEvent, element, className, afterAnimationComplete);
   1443         } else {
   1444           animateClose(element, className);
   1445           afterAnimationComplete();
   1446         }
   1447       }
   1448 
   1449       function animate(animationEvent, element, className, animationComplete) {
   1450         //If the animateSetup function doesn't bother returning a
   1451         //cancellation function then it means that there is no animation
   1452         //to perform at all
   1453         var preReflowCancellation = animateBefore(animationEvent, element, className);
   1454         if(!preReflowCancellation) {
   1455           animationComplete();
   1456           return;
   1457         }
   1458 
   1459         //There are two cancellation functions: one is before the first
   1460         //reflow animation and the second is during the active state
   1461         //animation. The first function will take care of removing the
   1462         //data from the element which will not make the 2nd animation
   1463         //happen in the first place
   1464         var cancel = preReflowCancellation;
   1465         afterReflow(element, function() {
   1466           unblockTransitions(element, className);
   1467           unblockKeyframeAnimations(element);
   1468           //once the reflow is complete then we point cancel to
   1469           //the new cancellation function which will remove all of the
   1470           //animation properties from the active animation
   1471           cancel = animateAfter(animationEvent, element, className, animationComplete);
   1472         });
   1473 
   1474         return function(cancelled) {
   1475           (cancel || noop)(cancelled);
   1476         };
   1477       }
   1478 
   1479       function animateClose(element, className) {
   1480         element.removeClass(className);
   1481         var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
   1482         if(data) {
   1483           if(data.running) {
   1484             data.running--;
   1485           }
   1486           if(!data.running || data.running === 0) {
   1487             element.removeData(NG_ANIMATE_CSS_DATA_KEY);
   1488           }
   1489         }
   1490       }
   1491 
   1492       return {
   1493         enter : function(element, animationCompleted) {
   1494           return animate('enter', element, 'ng-enter', animationCompleted);
   1495         },
   1496 
   1497         leave : function(element, animationCompleted) {
   1498           return animate('leave', element, 'ng-leave', animationCompleted);
   1499         },
   1500 
   1501         move : function(element, animationCompleted) {
   1502           return animate('move', element, 'ng-move', animationCompleted);
   1503         },
   1504 
   1505         beforeSetClass : function(element, add, remove, animationCompleted) {
   1506           var className = suffixClasses(remove, '-remove') + ' ' +
   1507                           suffixClasses(add, '-add');
   1508           var cancellationMethod = animateBefore('setClass', element, className, function(fn) {
   1509             /* when classes are removed from an element then the transition style
   1510              * that is applied is the transition defined on the element without the
   1511              * CSS class being there. This is how CSS3 functions outside of ngAnimate.
   1512              * http://plnkr.co/edit/j8OzgTNxHTb4n3zLyjGW?p=preview */
   1513             var klass = element.attr('class');
   1514             element.removeClass(remove);
   1515             element.addClass(add);
   1516             var timings = fn();
   1517             element.attr('class', klass);
   1518             return timings;
   1519           });
   1520 
   1521           if(cancellationMethod) {
   1522             afterReflow(element, function() {
   1523               unblockTransitions(element, className);
   1524               unblockKeyframeAnimations(element);
   1525               animationCompleted();
   1526             });
   1527             return cancellationMethod;
   1528           }
   1529           animationCompleted();
   1530         },
   1531 
   1532         beforeAddClass : function(element, className, animationCompleted) {
   1533           var cancellationMethod = animateBefore('addClass', element, suffixClasses(className, '-add'), function(fn) {
   1534 
   1535             /* when a CSS class is added to an element then the transition style that
   1536              * is applied is the transition defined on the element when the CSS class
   1537              * is added at the time of the animation. This is how CSS3 functions
   1538              * outside of ngAnimate. */
   1539             element.addClass(className);
   1540             var timings = fn();
   1541             element.removeClass(className);
   1542             return timings;
   1543           });
   1544 
   1545           if(cancellationMethod) {
   1546             afterReflow(element, function() {
   1547               unblockTransitions(element, className);
   1548               unblockKeyframeAnimations(element);
   1549               animationCompleted();
   1550             });
   1551             return cancellationMethod;
   1552           }
   1553           animationCompleted();
   1554         },
   1555 
   1556         setClass : function(element, add, remove, animationCompleted) {
   1557           remove = suffixClasses(remove, '-remove');
   1558           add = suffixClasses(add, '-add');
   1559           var className = remove + ' ' + add;
   1560           return animateAfter('setClass', element, className, animationCompleted);
   1561         },
   1562 
   1563         addClass : function(element, className, animationCompleted) {
   1564           return animateAfter('addClass', element, suffixClasses(className, '-add'), animationCompleted);
   1565         },
   1566 
   1567         beforeRemoveClass : function(element, className, animationCompleted) {
   1568           var cancellationMethod = animateBefore('removeClass', element, suffixClasses(className, '-remove'), function(fn) {
   1569             /* when classes are removed from an element then the transition style
   1570              * that is applied is the transition defined on the element without the
   1571              * CSS class being there. This is how CSS3 functions outside of ngAnimate.
   1572              * http://plnkr.co/edit/j8OzgTNxHTb4n3zLyjGW?p=preview */
   1573             var klass = element.attr('class');
   1574             element.removeClass(className);
   1575             var timings = fn();
   1576             element.attr('class', klass);
   1577             return timings;
   1578           });
   1579 
   1580           if(cancellationMethod) {
   1581             afterReflow(element, function() {
   1582               unblockTransitions(element, className);
   1583               unblockKeyframeAnimations(element);
   1584               animationCompleted();
   1585             });
   1586             return cancellationMethod;
   1587           }
   1588           animationCompleted();
   1589         },
   1590 
   1591         removeClass : function(element, className, animationCompleted) {
   1592           return animateAfter('removeClass', element, suffixClasses(className, '-remove'), animationCompleted);
   1593         }
   1594       };
   1595 
   1596       function suffixClasses(classes, suffix) {
   1597         var className = '';
   1598         classes = angular.isArray(classes) ? classes : classes.split(/\s+/);
   1599         forEach(classes, function(klass, i) {
   1600           if(klass && klass.length > 0) {
   1601             className += (i > 0 ? ' ' : '') + klass + suffix;
   1602           }
   1603         });
   1604         return className;
   1605       }
   1606     }]);
   1607   }]);