PageSourceSearch

https://www.staffordshiretheatre.com/wp-content/plugins/live-searc…?ver=6af84233f57fba47317e505e13276611

js staffordshiretheatre.com collected 2026-10-03 18:36:57 UTC 106,563 bytes, 2,139 lines download raw bytes

1/**
2 * @license AngularJS v1.3.16
3 * (c) 2010-2014 Google, Inc. http://angularjs.org
4 * License: MIT
5 */
6(function(window, angular, undefined) {'use strict';
7
8/* jshint maxlen: false */
9
10/**
11 * @ngdoc module
12 * @name ngAnimate
13 * @description
14 *
15 * The `ngAnimate` module provides support for JavaScript, CSS3 transition and CSS3 keyframe animation hooks within existing core and custom directives.
16 *
17 * <div doc-module-components="ngAnimate"></div>
18 *
19 * # Usage
20 *
21 * To see animations in action, all that is required is to define the appropriate CSS classes
22 * or to register a JavaScript animation via the `myModule.animation()` function. The directives that support animation automatically are:
23 * `ngRepeat`, `ngInclude`, `ngIf`, `ngSwitch`, `ngShow`, `ngHide`, `ngView` and `ngClass`. Custom directives can take advantage of animation
24 * by using the `$animate` service.
25 *
26 * Below is a more detailed breakdown of the supported animation events provided by pre-existing ng directives:
27 *
28 * | Directive                                                                                                | Supported Animations                                                     |
29 * |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
30 * | {@link ng.directive:ngRepeat#animations ngRepeat}                                                        | enter, leave and move                                                    |
31 * | {@link ngRoute.directive:ngView#animations ngView}                                                       | enter and leave                                                          |
32 * | {@link ng.directive:ngInclude#animations ngInclude}                                                      | enter and leave                                                          |
33 * | {@link ng.directive:ngSwitch#animations ngSwitch}                                                        | enter and leave                                                          |
34 * | {@link ng.directive:ngIf#animations ngIf}                                                                | enter and leave                                                          |
35 * | {@link ng.directive:ngClass#animations ngClass}                                                          | add and remove (the CSS class(es) present)                               |
36 * | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide}            | add and remove (the ng-hide class value)                                 |
37 * | {@link ng.directive:form#animation-hooks form} & {@link ng.directive:ngModel#animation-hooks ngModel}    | add and remove (dirty, pristine, valid, invalid & all other validations) |
38 * | {@link module:ngMessages#animations ngMessages}                                                          | add and remove (ng-active & ng-inactive)                                 |
39 * | {@link module:ngMessages#animations ngMessage}                                                           | enter and leave                                                          |
40 *
41 * You can find out more information about animations upon visiting each directive page.
42 *
43 * Below is an example of how to apply animations to a directive that supports animation hooks:
44 *
45 * ```html
46 * <style type="text/css">
47 * .slide.ng-enter, .slide.ng-leave {
48 *   -webkit-transition:0.5s linear all;
49 *   transition:0.5s linear all;
50 * }
51 *
52 * .slide.ng-enter { }        /&#42; starting animations for enter &#42;/
53 * .slide.ng-enter.ng-enter-active { } /&#42; terminal animations for enter &#42;/
54 * .slide.ng-leave { }        /&#42; starting animations for leave &#42;/
55 * .slide.ng-leave.ng-leave-active { } /&#42; terminal animations for leave &#42;/
56 * </style>
57 *
58 * <!--
59 * the animate service will automatically add .ng-enter and .ng-leave to the element
60 * to trigger the CSS transition/animations
61 * -->
62 * <ANY class="slide" ng-include="..."></ANY>
63 * ```
64 *
65 * Keep in mind that, by default, if an animation is running, any child elements cannot be animated
66 * until the parent element's animation has completed. This blocking feature can be overridden by
67 * placing the `ng-animate-children` attribute on a parent container tag.
68 *
69 * ```html
70 * <div class="slide-animation" ng-if="on" ng-animate-children>
71 *   <div class="fade-animation" ng-if="on">
72 *     <div class="explode-animation" ng-if="on">
73 *        ...
74 *     </div>
75 *   </div>
76 * </div>
77 * ```
78 *
79 * When the `on` expression value changes and an animation is triggered then each of the elements within
80 * will all animate without the block being applied to child elements.
81 *
82 * ## Are animations run when the application starts?
83 * No they are not. When an application is bootstrapped Angular will disable animations from running to avoid
84 * a frenzy of animations from being triggered as soon as the browser has rendered the screen. For this to work,
85 * Angular will wait for two digest cycles until enabling animations. From there on, any animation-triggering
86 * layout changes in the application will trigger animations as normal.
87 *
88 * In addition, upon bootstrap, if the routing system or any directives or load remote data (via $http) then Angular
89 * will automatically extend the wait time to enable animations once **all** of the outbound HTTP requests
90 * are complete.
91 *
92 * ## CSS-defined Animations
93 * The animate service will automatically apply two CSS classes to the animated element and these two CSS classes
94 * are designed to contain the start and end CSS styling. Both CSS transitions and keyframe animations are supported
95 * and can be used to play along with this naming structure.
96 *
97 * The following code below demonstrates how to perform animations using **CSS transitions** with Angular:
98 *
99 * ```html
100 * <style type="text/css">
101 * /&#42;
102 *  The animate class is apart of the element and the ng-enter class
103 *  is attached to the element once the enter animation event is triggered
104 * &#42;/
105 * .reveal-animation.ng-enter {
106 *  -webkit-transition: 1s linear all; /&#42; Safari/Chrome &#42;/
107 *  transition: 1s linear all; /&#42; All other modern browsers and IE10+ &#42;/
108 *
109 *  /&#42; The animation preparation code &#42;/
110 *  opacity: 0;
111 * }
112 *
113 * /&#42;
114 *  Keep in mind that you want to combine both CSS
115 *  classes together to avoid any CSS-specificity
116 *  conflicts
117 * &#42;/
118 * .reveal-animation.ng-enter.ng-enter-active {
119 *  /&#42; The animation code itself &#42;/
120 *  opacity: 1;
121 * }
122 * </style>
123 *
124 * <div class="view-container">
125 *   <div ng-view class="reveal-animation"></div>
126 * </div>
127 * ```
128 *
129 * The following code below demonstrates how to perform animations using **CSS animations** with Angular:
130 *
131 * ```html
132 * <style type="text/css">
133 * .reveal-animation.ng-enter {
134 *   -webkit-animation: enter_sequence 1s linear; /&#42; Safari/Chrome &#42;/
135 *   animation: enter_sequence 1s linear; /&#42; IE10+ and Future Browsers &#42;/
136 * }
137 * @-webkit-keyframes enter_sequence {
138 *   from { opacity:0; }
139 *   to { opacity:1; }
140 * }
141 * @keyframes enter_sequence {
142 *   from { opacity:0; }
143 *   to { opacity:1; }
144 * }
145 * </style>
146 *
147 * <div class="view-container">
148 *   <div ng-view class="reveal-animation"></div>
149 * </div>
150 * ```
151 *
152 * Both CSS3 animations and transitions can be used together and the animate service will figure out the correct duration and delay timing.
153 *
154 * Upon DOM mutation, the event class is added first (something like `ng-enter`), then the browser prepares itself to add
155 * the active class (in this case `ng-enter-active`) which then triggers the animation. The animation module will automatically
156 * detect the CSS code to determine when the animation ends. Once the animation is over then both CSS classes will be
157 * removed from the DOM. If a browser does not support CSS transitions or CSS animations then the animation will start and end
158 * immediately resulting in a DOM element that is at its final state. This final state is when the DOM element
159 * has no CSS transition/animation classes applied to it.
160 *
161 * ### Structural transition animations
162 *
163 * Structural transitions (such as enter, leave and move) will always apply a `0s none` transition
164 * value to force the browser into rendering the styles defined in the setup (`.ng-enter`, `.ng-leave`
165 * or `.ng-move`) class. This means that any active transition animations operating on the element
166 * will be cut off to make way for the enter, leave or move animation.
167 *
168 * ### Class-based transition animations
169 *
170 * Class-based transitions refer to transition animations that are triggered when a CSS class is
171 * added to or removed from the element (via `$animate.addClass`, `$animate.removeClass`,
172 * `$animate.setClass`, or by directives such as `ngClass`, `ngModel` and `form`).
173 * They are different when compared to structural animations since they **do not cancel existing
174 * animations** nor do they **block successive transitions** from rendering on the same element.
175 * This distinction allows for **multiple class-based transitions** to be performed on the same element.
176 *
177 * In addition to ngAnimate supporting the default (natural) functionality of class-based transition
178 * animations, ngAnimate also decorates the element with starting and ending CSS classes to aid the
179 * developer in further styling the element throughout the transition animation. Earlier versions
180 * of ngAnimate may have caused natural CSS transitions to break and not render properly due to
181 * $animate temporarily blocking transitions using `0s none` in order to allow the setup CSS class
182 * (the `-add` or `-remove` class) to be applied without triggering an animation. However, as of
183 * **version 1.3**, this workaround has been removed with ngAnimate and all non-ngAnimate CSS
184 * class transitions are compatible with ngAnimate.
185 *
186 * There is, however, one special case when dealing with class-based transitions in ngAnimate.
187 * When rendering class-based transitions that make use of the setup and active CSS classes
188 * (e.g. `.fade-add` and `.fade-add-active` for when `.fade` is added) be sure to define
189 * the transition value **on the active CSS class** and not the setup class.
190 *
191 * ```css
192 * .fade-add {
193 *   /&#42; remember to place a 0s transition here
194 *      to ensure that the styles are applied instantly
195 *      even if the element already has a transition style &#42;/
196 *   transition:0s linear all;
197 *
198 *   /&#42; starting CSS styles &#42;/
199 *   opacity:1;
200 * }
201 * .fade-add.fade-add-active {
202 *   /&#42; this will be the length of the animation &#42;/
203 *   transition:1s linear all;
204 *   opacity:0;
205 * }
206 * ```
207 *
208 * The setup CSS class (in this case `.fade-add`) also has a transition style property, however, it
209 * has a duration of zero. This may not be required, however, incase the browser is unable to render
210 * the styling present in this CSS class instantly then it could be that the browser is attempting
211 * to perform an unnecessary transition.
212 *
213 * This workaround, however, does not apply to  standard class-based transitions that are rendered
214 * when a CSS class containing a transition is applied to an element:
215 *
216 * ```css
217 * /&#42; this works as expected &#42;/
218 * .fade {
219 *   transition:1s linear all;
220 *   opacity:0;
221 * }
222 * ```
223 *
224 * Please keep this in mind when coding the CSS markup that will be used within class-based transitions.
225 * Also, try not to mix the two class-based animation flavors together since the CSS code may become
226 * overly complex.
227 *
228 *
229 * ### Preventing Collisions With Third Party Libraries
230 *
231 * Some third-party frameworks place animation duration defaults across many element or className
232 * selectors in order to make their code small and reuseable. This can lead to issues with ngAnimate, which
233 * is expecting actual animations on these elements and has to wait for their completion.
234 *
235 * You can prevent this unwanted behavior by using a prefix on all your animation classes:
236 *
237 * ```css
238 * /&#42; prefixed with animate- &#42;/
239 * .animate-fade-add.animate-fade-add-active {
240 *   transition:1s linear all;
241 *   opacity:0;
242 * }
243 * ```
244 *
245 * You then configure `$animate` to enforce this prefix:
246 *
247 * ```js
248 * $animateProvider.classNameFilter(/animate-/);
249 * ```
250 * </div>
251 *
252 * ### CSS Staggering Animations
253 * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a
254 * curtain-like effect. The ngAnimate module (versions >=1.2) supports staggering animations and the stagger effect can be
255 * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for
256 * the animation. The style property expected within the stagger class can either be a **transition-delay** or an
257 * **animation-delay** property (or both if your animation contains both transitions and keyframe animations).
258 *
259 * ```css
260 * .my-animation.ng-enter {
261 *   /&#42; standard transition code &#42;/
262 *   -webkit-transition: 1s linear all;
263 *   transition: 1s linear all;
264 *   opacity:0;
265 * }
266 * .my-animation.ng-enter-stagger {
267 *   /&#42; this will have a 100ms delay between each successive leave animation &#42;/
268 *   -webkit-transition-delay: 0.1s;
269 *   transition-delay: 0.1s;
270 *
271 *   /&#42; in case the stagger doesn't work then these two values
272 *    must be set to 0 to avoid an accidental CSS inheritance &#42;/
273 *   -webkit-transition-duration: 0s;
274 *   transition-duration: 0s;
275 * }
276 * .my-animation.ng-enter.ng-enter-active {
277 *   /&#42; standard transition styles &#42;/
278 *   opacity:1;
279 * }
280 * ```
281 *
282 * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations
283 * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this
284 * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation
285 * will also be reset if more than 10ms has passed after the last animation has been fired.
286 *
287 * The following code will issue the **ng-leave-stagger** event on the element provided:
288 *
289 * ```js
290 * var kids = parent.children();
291 *
292 * $animate.leave(kids[0]); //stagger index=0
293 * $animate.leave(kids[1]); //stagger index=1
294 * $animate.leave(kids[2]); //stagger index=2
295 * $animate.leave(kids[3]); //stagger index=3
296 * $animate.leave(kids[4]); //stagger index=4
297 *
298 * $timeout(function() {
299 *   //stagger has reset itself
300 *   $animate.leave(kids[5]); //stagger index=0
301 *   $animate.leave(kids[6]); //stagger index=1
302 * }, 100, false);
303 * ```
304 *
305 * Stagger animations are currently only supported within CSS-defined animations.
306 *
307 * ## JavaScript-defined Animations
308 * 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
309 * yet support CSS transitions/animations, then you can make use of JavaScript animations defined inside of your AngularJS module.
310 *
311 * ```js
312 * //!annotate="YourApp" Your AngularJS Module|Replace this or ngModule with the module that you used to define your application.
313 * var ngModule = angular.module('YourApp', ['ngAnimate']);
314 * ngModule.animation('.my-crazy-animation', function() {
315 *   return {
316 *     enter: function(element, done) {
317 *       //run the animation here and call done when the animation is complete
318 *       return function(cancelled) {
319 *         //this (optional) function will be called when the animation
320 *         //completes or when the animation is cancelled (the cancelled
321 *         //flag will be set to true if cancelled).
322 *       };
323 *     },
324 *     leave: function(element, done) { },
325 *     move: function(element, done) { },
326 *
327 *     //animation that can be triggered before the class is added
328 *     beforeAddClass: function(element, className, done) { },
329 *
330 *     //animation that can be triggered after the class is added
331 *     addClass: function(element, className, done) { },
332 *
333 *     //animation that can be triggered before the class is removed
334 *     beforeRemoveClass: function(element, className, done) { },
335 *
336 *     //animation that can be triggered after the class is removed
337 *     removeClass: function(element, className, done) { }
338 *   };
339 * });
340 * ```
341 *
342 * JavaScript-defined animations are created with a CSS-like class selector and a collection of events which are set to run
343 * a javascript callback function. When an animation is triggered, $animate will look for a matching animation which fits
344 * the element's CSS class attribute value and then run the matching animation event function (if found).
345 * In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function will
346 * be executed. It should be also noted that only simple, single class selectors are allowed (compound class selectors are not supported).
347 *
348 * Within a JavaScript animation, an object containing various event callback animation functions is expected to be returned.
349 * As explained above, these callbacks are triggered based on the animation event. Therefore if an enter animation is run,
350 * and the JavaScript animation is found, then the enter callback will handle that animation (in addition to the CSS keyframe animation
351 * or transition code that is defined via a stylesheet).
352 *
353 *
354 * ### Applying Directive-specific Styles to an Animation
355 * In some cases a directive or service may want to provide `$animate` with extra details that the animation will
356 * include into its animation. Let's say for example we wanted to render an animation that animates an element
357 * towards the mouse coordinates as to where the user clicked last. By collecting the X/Y coordinates of the click
358 * (via the event parameter) we can set the `top` and `left` styles into an object and pass that into our function
359 * call to `$animate.addClass`.
360 *
361 * ```js
362 * canvas.on('click', function(e) {
363 *   $animate.addClass(element, 'on', {
364 *     to: {
365 *       left : e.client.x + 'px',
366 *       top : e.client.y + 'px'
367 *     }
368 *   }):
369 * });
370 * ```
371 *
372 * Now when the animation runs, and a transition or keyframe animation is picked up, then the animation itself will
373 * also include and transition the styling of the `left` and `top` properties into its running animation. If we want
374 * to provide some starting animation values then we can do so by placing the starting animations styles into an object
375 * called `from` in the same object as the `to` animations.
376 *
377 * ```js
378 * canvas.on('click', function(e) {
379 *   $animate.addClass(element, 'on', {
380 *     from: {
381 *        position: 'absolute',
382 *        left: '0px',
383 *        top: '0px'
384 *     },
385 *     to: {
386 *       left : e.client.x + 'px',
387 *       top : e.client.y + 'px'
388 *     }
389 *   }):
390 * });
391 * ```
392 *
393 * Once the animation is complete or cancelled then the union of both the before and after styles are applied to the
394 * element. If `ngAnimate` is not present then the styles will be applied immediately.
395 *
396 */
397
398angular.module('ngAnimate', ['ng'])
399
400  /**
401   * @ngdoc provider
402   * @name $animateProvider
403   * @description
404   *
405   * The `$animateProvider` allows developers to register JavaScript animation event handlers directly inside of a module.
406   * When an animation is triggered, the $animate service will query the $animate service to find any animations that match
407   * the provided name value.
408   *
409   * Requires the {@link ngAnimate `ngAnimate`} module to be installed.
410   *
411   * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
412   *
413   */
414  .directive('ngAnimateChildren', function() {
415    var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren';
416    return function(scope, element, attrs) {
417      var val = attrs.ngAnimateChildren;
418      if (angular.isString(val) && val.length === 0) { //empty attribute
419        element.data(NG_ANIMATE_CHILDREN, true);
420      } else {
421        scope.$watch(val, function(value) {
422          element.data(NG_ANIMATE_CHILDREN, !!value);
423        });
424      }
425    };
426  })
427
428  //this private service is only used within CSS-enabled animations
429  //IE8 + IE9 do not support rAF natively, but that is fine since they
430  //also don't support transitions and keyframes which means that the code
431  //below will never be used by the two browsers.
432  .factory('$$animateReflow', ['$$rAF', '$document', function($$rAF, $document) {
433    var bod = $document[0].body;
434    return function(fn) {
435      //the returned function acts as the cancellation function
436      return $$rAF(function() {
437        //the line below will force the browser to perform a repaint
438        //so that all the animated elements within the animation frame
439        //will be properly updated and drawn on screen. This is
440        //required to perform multi-class CSS based animations with
441        //Firefox. DO NOT REMOVE THIS LINE. DO NOT OPTIMIZE THIS LINE.
442        //THE MINIFIER WILL REMOVE IT OTHERWISE WHICH WILL RESULT IN AN
443        //UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND WILL
444        //TAKE YEARS AWAY FROM YOUR LIFE!
445        fn(bod.offsetWidth);
446      });
447    };
448  }])
449
450  .config(['$provide', '$animateProvider', function($provide, $animateProvider) {
451    var noop = angular.noop;
452    var forEach = angular.forEach;
453    var selectors = $animateProvider.$$selectors;
454    var isArray = angular.isArray;
455    var isString = angular.isString;
456    var isObject = angular.isObject;
457
458    var ELEMENT_NODE = 1;
459    var NG_ANIMATE_STATE = '$$ngAnimateState';
460    var NG_ANIMATE_CHILDREN = '$$ngAnimateChildren';
461    var NG_ANIMATE_CLASS_NAME = 'ng-animate';
462    var rootAnimateState = {running: true};
463
464    function extractElementNode(element) {
465      for (var i = 0; i < element.length; i++) {
466        var elm = element[i];
467        if (elm.nodeType == ELEMENT_NODE) {
468          return elm;
469        }
470      }
471    }
472
473    function prepareElement(element) {
474      return element && angular.element(element);
475    }
476
477    function stripCommentsFromElement(element) {
478      return angular.element(extractElementNode(element));
479    }
480
481    function isMatchingElement(elm1, elm2) {
482      return extractElementNode(elm1) == extractElementNode(elm2);
483    }
484    var $$jqLite;
485    $provide.decorator('$animate',
486        ['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', '$templateRequest', '$$jqLite',
487 function($delegate,   $$q,   $injector,   $sniffer,   $rootElement,   $$asyncCallback,   $rootScope,   $document,   $templateRequest,   $$$jqLite) {
488
489      $$jqLite = $$$jqLite;
490      $rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
491
492      // Wait until all directive and route-related templates are downloaded and
493      // compiled. The $templateRequest.totalPendingRequests variable keeps track of
494      // all of the remote templates being currently downloaded. If there are no
495      // templates currently downloading then the watcher will still fire anyway.
496      var deregisterWatch = $rootScope.$watch(
497        function() { return $templateRequest.totalPendingRequests; },
498        function(val, oldVal) {
499          if (val !== 0) return;
500          deregisterWatch();
501
502          // Now that all templates have been downloaded, $animate will wait until
503          // the post digest queue is empty before enabling animations. By having two
504          // calls to $postDigest calls we can ensure that the flag is enabled at the
505          // very end of the post digest queue. Since all of the animations in $animate
506          // use $postDigest, it's important that the code below executes at the end.
507          // This basically means that the page is fully downloaded and compiled before
508          // any animations are triggered.
509          $rootScope.$$postDigest(function() {
510            $rootScope.$$postDigest(function() {
511              rootAnimateState.running = false;
512            });
513          });
514        }
515      );
516
517      var globalAnimationCounter = 0;
518      var classNameFilter = $animateProvider.classNameFilter();
519      var isAnimatableClassName = !classNameFilter
520              ? function() { return true; }
521              : function(className) {
522                return classNameFilter.test(className);
523              };
524
525      function classBasedAnimationsBlocked(element, setter) {
526        var data = element.data(NG_ANIMATE_STATE) || {};
527        if (setter) {
528          data.running = true;
529          data.structural = true;
530          element.data(NG_ANIMATE_STATE, data);
531        }
532        return data.disabled || (data.running && data.structural);
533      }
534
535      function runAnimationPostDigest(fn) {
536        var cancelFn, defer = $$q.defer();
537        defer.promise.$$cancelFn = function() {
538          cancelFn && cancelFn();
539        };
540        $rootScope.$$postDigest(function() {
541          cancelFn = fn(function() {
542            defer.resolve();
543          });
544        });
545        return defer.promise;
546      }
547
548      function parseAnimateOptions(options) {
549        // some plugin code may still be passing in the callback
550        // function as the last param for the $animate methods so
551        // it's best to only allow string or array values for now
552        if (isObject(options)) {
553          if (options.tempClasses && isString(options.tempClasses)) {
554            options.tempClasses = options.tempClasses.split(/\s+/);
555          }
556          return options;
557        }
558      }
559
560      function resolveElementClasses(element, cache, runningAnimations) {
561        runningAnimations = runningAnimations || {};
562
563        var lookup = {};
564        forEach(runningAnimations, function(data, selector) {
565          forEach(selector.split(' '), function(s) {
566            lookup[s]=data;
567          });
568        });
569
570        var hasClasses = Object.create(null);
571        forEach((element.attr('class') || '').split(/\s+/), function(className) {
572          hasClasses[className] = true;
573        });
574
575        var toAdd = [], toRemove = [];
576        forEach((cache && cache.classes) || [], function(status, className) {
577          var hasClass = hasClasses[className];
578          var matchingAnimation = lookup[className] || {};
579
580          // When addClass and removeClass is called then $animate will check to
581          // see if addClass and removeClass cancel each other out. When there are
582          // more calls to removeClass than addClass then the count falls below 0
583          // and then the removeClass animation will be allowed. Otherwise if the
584          // count is above 0 then that means an addClass animation will commence.
585          // Once an animation is allowed then the code will also check to see if
586          // there exists any on-going animation that is already adding or remvoing
587          // the matching CSS class.
588          if (status === false) {
589            //does it have the class or will it have the class
590            if (hasClass || matchingAnimation.event == 'addClass') {
591              toRemove.push(className);
592            }
593          } else if (status === true) {
594            //is the class missing or will it be removed?
595            if (!hasClass || matchingAnimation.event == 'removeClass') {
596              toAdd.push(className);
597            }
598          }
599        });
600
601        return (toAdd.length + toRemove.length) > 0 && [toAdd.join(' '), toRemove.join(' ')];
602      }
603
604      function lookup(name) {
605        if (name) {
606          var matches = [],
607              flagMap = {},
608              classes = name.substr(1).split('.');
609
610          //the empty string value is the default animation
611          //operation which performs CSS transition and keyframe
612          //animations sniffing. This is always included for each
613          //element animation procedure if the browser supports
614          //transitions and/or keyframe animations. The default
615          //animation is added to the top of the list to prevent
616          //any previous animations from affecting the element styling
617          //prior to the element being animated.
618          if ($sniffer.transitions || $sniffer.animations) {
619            matches.push($injector.get(selectors['']));
620          }
621
622          for (var i=0; i < classes.length; i++) {
623            var klass = classes[i],
624                selectorFactoryName = selectors[klass];
625            if (selectorFactoryName && !flagMap[klass]) {
626              matches.push($injector.get(selectorFactoryName));
627              flagMap[klass] = true;
628            }
629          }
630          return matches;
631        }
632      }
633
634      function animationRunner(element, animationEvent, className, options) {
635        //transcluded directives may sometimes fire an animation using only comment nodes
636        //best to catch this early on to prevent any animation operations from occurring
637        var node = element[0];
638        if (!node) {
639          return;
640        }
641
642        if (options) {
643          options.to = options.to || {};
644          options.from = options.from || {};
645        }
646
647        var classNameAdd;
648        var classNameRemove;
649        if (isArray(className)) {
650          classNameAdd = className[0];
651          classNameRemove = className[1];
652          if (!classNameAdd) {
653            className = classNameRemove;
654            animationEvent = 'removeClass';
655          } else if (!classNameRemove) {
656            className = classNameAdd;
657            animationEvent = 'addClass';
658          } else {
659            className = classNameAdd + ' ' + classNameRemove;
660          }
661        }
662
663        var isSetClassOperation = animationEvent == 'setClass';
664        var isClassBased = isSetClassOperation
665                           || animationEvent == 'addClass'
666                           || animationEvent == 'removeClass'
667                           || animationEvent == 'animate';
668
669        var currentClassName = element.attr('class');
670        var classes = currentClassName + ' ' + className;
671        if (!isAnimatableClassName(classes)) {
672          return;
673        }
674
675        var beforeComplete = noop,
676            beforeCancel = [],
677            before = [],
678            afterComplete = noop,
679            afterCancel = [],
680            after = [];
681
682        var animationLookup = (' ' + classes).replace(/\s+/g,'.');
683        forEach(lookup(animationLookup), function(animationFactory) {
684          var created = registerAnimation(animationFactory, animationEvent);
685          if (!created && isSetClassOperation) {
686            registerAnimation(animationFactory, 'addClass');
687            registerAnimation(animationFactory, 'removeClass');
688          }
689        });
690
691        function registerAnimation(animationFactory, event) {
692          var afterFn = animationFactory[event];
693          var beforeFn = animationFactory['before' + event.charAt(0).toUpperCase() + event.substr(1)];
694          if (afterFn || beforeFn) {
695            if (event == 'leave') {
696              beforeFn = afterFn;
697              //when set as null then animation knows to skip this phase
698              afterFn = null;
699            }
700            after.push({
701              event: event, fn: afterFn
702            });
703            before.push({
704              event: event, fn: beforeFn
705            });
706            return true;
707          }
708        }
709
710        function run(fns, cancellations, allCompleteFn) {
711          var animations = [];
712          forEach(fns, function(animation) {
713            animation.fn && animations.push(animation);
714          });
715
716          var count = 0;
717          function afterAnimationComplete(index) {
718            if (cancellations) {
719              (cancellations[index] || noop)();
720              if (++count < animations.length) return;
721              cancellations = null;
722            }
723            allCompleteFn();
724          }
725
726          //The code below adds directly to the array in order to work with
727          //both sync and async animations. Sync animations are when the done()
728          //operation is called right away. DO NOT REFACTOR!
729          forEach(animations, function(animation, index) {
730            var progress = function() {
731              afterAnimationComplete(index);
732            };
733            switch (animation.event) {
734              case 'setClass':
735                cancellations.push(animation.fn(element, classNameAdd, classNameRemove, progress, options));
736                break;
737              case 'animate':
738                cancellations.push(animation.fn(element, className, options.from, options.to, progress));
739                break;
740              case 'addClass':
741                cancellations.push(animation.fn(element, classNameAdd || className,     progress, options));
742                break;
743              case 'removeClass':
744                cancellations.push(animation.fn(element, classNameRemove || className,  progress, options));
745                break;
746              default:
747                cancellations.push(animation.fn(element, progress, options));
748                break;
749            }
750          });
751
752          if (cancellations && cancellations.length === 0) {
753            allCompleteFn();
754          }
755        }
756
757        return {
758          node: node,
759          event: animationEvent,
760          className: className,
761          isClassBased: isClassBased,
762          isSetClassOperation: isSetClassOperation,
763          applyStyles: function() {
764            if (options) {
765              element.css(angular.extend(options.from || {}, options.to || {}));
766            }
767          },
768          before: function(allCompleteFn) {
769            beforeComplete = allCompleteFn;
770            run(before, beforeCancel, function() {
771              beforeComplete = noop;
772              allCompleteFn();
773            });
774          },
775          after: function(allCompleteFn) {
776            afterComplete = allCompleteFn;
777            run(after, afterCancel, function() {
778              afterComplete = noop;
779              allCompleteFn();
780            });
781          },
782          cancel: function() {
783            if (beforeCancel) {
784              forEach(beforeCancel, function(cancelFn) {
785                (cancelFn || noop)(true);
786              });
787              beforeComplete(true);
788            }
789            if (afterCancel) {
790              forEach(afterCancel, function(cancelFn) {
791                (cancelFn || noop)(true);
792              });
793              afterComplete(true);
794            }
795          }
796        };
797      }
798
799      /**
800       * @ngdoc service
801       * @name $animate
802       * @kind object
803       *
804       * @description
805       * The `$animate` service provides animation detection support while performing DOM operations (enter, leave and move) as well as during addClass and removeClass operations.
806       * When any of these operations are run, the $animate service
807       * will examine any JavaScript-defined animations (which are defined by using the $animateProvider provider object)
808       * as well as any CSS-defined animations against the CSS classes present on the element once the DOM operation is run.
809       *
810       * The `$animate` service is used behind the scenes with pre-existing directives and animation with these directives
811       * will work out of the box without any extra configuration.
812       *
813       * Requires the {@link ngAnimate `ngAnimate`} module to be installed.
814       *
815       * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
816       * ## Callback Promises
817       * With AngularJS 1.3, each of the animation methods, on the `$animate` service, return a promise when called. The
818       * promise itself is then resolved once the animation has completed itself, has been cancelled or has been
819       * skipped due to animations being disabled. (Note that even if the animation is cancelled it will still
820       * call the resolve function of the animation.)
821       *
822       * ```js
823       * $animate.enter(element, container).then(function() {
824       *   //...this is called once the animation is complete...
825       * });
826       * ```
827       *
828       * Also note that, due to the nature of the callback promise, if any Angular-specific code (like changing the scope,
829       * location of the page, etc...) is executed within the callback promise then be sure to wrap the code using
830       * `$scope.$apply(...)`;
831       *
832       * ```js
833       * $animate.leave(element).then(function() {
834       *   $scope.$apply(function() {
835       *     $location.path('/new-page');
836       *   });
837       * });
838       * ```
839       *
840       * An animation can also be cancelled by calling the `$animate.cancel(promise)` method with the provided
841       * promise that was returned when the animation was started.
842       *
843       * ```js
844       * var promise = $animate.addClass(element, 'super-long-animation');
845       * promise.then(function() {
846       *   //this will still be called even if cancelled
847       * });
848       *
849       * element.on('click', function() {
850       *   //tooo lazy to wait for the animation to end
851       *   $animate.cancel(promise);
852       * });
853       * ```
854       *
855       * (Keep in mind that the promise cancellation is unique to `$animate` since promises in
856       * general cannot be cancelled.)
857       *
858       */
859      return {
860        /**
861         * @ngdoc method
862         * @name $animate#animate
863         * @kind function
864         *
865         * @description
866         * Performs an inline animation on the element which applies the provided `to` and `from` CSS styles to the element.
867         * If any detected CSS transition, keyframe or JavaScript matches the provided `className` value then the animation
868         * will take on the provided styles. For example, if a transition animation is set for the given className then the
869         * provided `from` and `to` styles will be applied alongside the given transition. If a JavaScript animation is
870         * detected then the provided styles will be given in as function paramters.
871         *
872         * ```js
873         * ngModule.animation('.my-inline-animation', function() {
874         *   return {
875         *     animate : function(element, className, from, to, done) {
876         *       //styles
877         *     }
878         *   }
879         * });
880         * ```
881         *
882         * Below is a breakdown of each step that occurs during the `animate` animation:
883         *
884         * | Animation Step                                                                                                        | What the element class attribute looks like                  |
885         * |-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------|
886         * | 1. `$animate.animate(...)` is called                                                                                  | `class="my-animation"`                                       |
887         * | 2. `$animate` waits for the next digest to start the animation                                                        | `class="my-animation ng-animate"`                            |
888         * | 3. `$animate` runs the JavaScript-defined animations detected on the element                                          | `class="my-animation ng-animate"`                            |
889         * | 4. the `className` class value is added to the element                                                                | `class="my-animation ng-animate className"`                  |
890         * | 5. `$animate` scans the element styles to get the CSS transition/animation duration and delay                         | `class="my-animation ng-animate className"`                  |
891         * | 6. `$animate` blocks all CSS transitions on the element to ensure the `.className` class styling is applied right away| `class="my-animation ng-animate className"`                  |
892         * | 7. `$animate` applies the provided collection of `from` CSS styles to the element                                     | `class="my-animation ng-animate className"`                  |
893         * | 8. `$animate` waits for a single animation frame (this performs a reflow)                                             | `class="my-animation ng-animate className"`                  |
894         * | 9. `$animate` removes the CSS transition block placed on the element                                                  | `class="my-animation ng-animate className"`                  |
895         * | 10. the `className-active` class is added (this triggers the CSS transition/animation)                                | `class="my-animation ng-animate className className-active"` |
896         * | 11. `$animate` applies the collection of `to` CSS styles to the element which are then handled by the transition      | `class="my-animation ng-animate className className-active"` |
897         * | 12. `$animate` waits for the animation to complete (via events and timeout)                                           | `class="my-animation ng-animate className className-active"` |
898         * | 13. The animation ends and all generated CSS classes are removed from the element                                     | `class="my-animation"`                                       |
899         * | 14. The returned promise is resolved.                                                                                 | `class="my-animation"`                                       |
900         *
901         * @param {DOMElement} element the element that will be the focus of the enter animation
902         * @param {object} from a collection of CSS styles that will be applied to the element at the start of the animation
903         * @param {object} to a collection of CSS styles that the element will animate towards
904         * @param {string=} className an optional CSS class that will be added to the element for the duration of the animation (the default class is `ng-inline-animate`)
905         * @param {object=} options an optional collection of options that will be picked up by the CSS transition/animation
906         * @return {Promise} the animation callback promise
907        */
908        animate: function(element, from, to, className, options) {
909          className = className || 'ng-inline-animate';
910          options = parseAnimateOptions(options) || {};
911          options.from = to ? from : null;
912          options.to   = to ? to : from;
913
914          return runAnimationPostDigest(function(done) {
915            return performAnimation('animate', className, stripCommentsFromElement(element), null, null, noop, options, done);
916          });
917        },
918
919        /**
920         * @ngdoc method
921         * @name $animate#enter
922         * @kind function
923         *
924         * @description
925         * Appends the element to the parentElement element that resides in the document and then runs the enter animation. Once
926         * the animation is started, the following CSS classes will be present on the element for the duration of the animation:
927         *
928         * Below is a breakdown of each step that occurs during enter animation:
929         *
930         * | Animation Step                                                                                                        | What the element class attribute looks like                |
931         * |-----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|
932         * | 1. `$animate.enter(...)` is called                                                                                    | `class="my-animation"`                                     |
933         * | 2. element is inserted into the `parentElement` element or beside the `afterElement` element                          | `class="my-animation"`                                     |
934         * | 3. `$animate` waits for the next digest to start the animation                                                        | `class="my-animation ng-animate"`                          |
935         * | 4. `$animate` runs the JavaScript-defined animations detected on the element                                          | `class="my-animation ng-animate"`                          |
936         * | 5. the `.ng-enter` class is added to the element                                                                      | `class="my-animation ng-animate ng-enter"`                 |
937         * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay                         | `class="my-animation ng-animate ng-enter"`                 |
938         * | 7. `$animate` blocks all CSS transitions on the element to ensure the `.ng-enter` class styling is applied right away | `class="my-animation ng-animate ng-enter"`                 |
939         * | 8. `$animate` waits for a single animation frame (this performs a reflow)                                             | `class="my-animation ng-animate ng-enter"`                 |
940         * | 9. `$animate` removes the CSS transition block placed on the element                                                  | `class="my-animation ng-animate ng-enter"`                 |
941         * | 10. the `.ng-enter-active` class is added (this triggers the CSS transition/animation)                                | `class="my-animation ng-animate ng-enter ng-enter-active"` |
942         * | 11. `$animate` waits for the animation to complete (via events and timeout)                                           | `class="my-animation ng-animate ng-enter ng-enter-active"` |
943         * | 12. The animation ends and all generated CSS classes are removed from the element                                     | `class="my-animation"`                                     |
944         * | 13. The returned promise is resolved.                                                                                 | `class="my-animation"`                                     |
945         *
946         * @param {DOMElement} element the element that will be the focus of the enter animation
947         * @param {DOMElement} parentElement the parent element of the element that will be the focus of the enter animation
948         * @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation
949         * @param {object=} options an optional collection of options that will be picked up by the CSS transition/animation
950         * @return {Promise} the animation callback promise
951        */
952        enter: function(element, parentElement, afterElement, options) {
953          options = parseAnimateOptions(options);
954          element = angular.element(element);
955          parentElement = prepareElement(parentElement);
956          afterElement = prepareElement(afterElement);
957
958          classBasedAnimationsBlocked(element, true);
959          $delegate.enter(element, parentElement, afterElement);
960          return runAnimationPostDigest(function(done) {
961            return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, options, done);
962          });
963        },
964
965        /**
966         * @ngdoc method
967         * @name $animate#leave
968         * @kind function
969         *
970         * @description
971         * Runs the leave animation operation and, upon completion, removes the element from the DOM. Once
972         * the animation is started, the following CSS classes will be added for the duration of the animation:
973         *
974         * Below is a breakdown of each step that occurs during leave animation:
975         *
976         * | Animation Step                                                                                                        | What the element class attribute looks like                |
977         * |-----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|
978         * | 1. `$animate.leave(...)` is called                                                                                    | `class="my-animation"`                                     |
979         * | 2. `$animate` runs the JavaScript-defined animations detected on the element                                          | `class="my-animation ng-animate"`                          |
980         * | 3. `$animate` waits for the next digest to start the animation                                                        | `class="my-animation ng-animate"`                          |
981         * | 4. the `.ng-leave` class is added to the element                                                                      | `class="my-animation ng-animate ng-leave"`                 |
982         * | 5. `$animate` scans the element styles to get the CSS transition/animation duration and delay                         | `class="my-animation ng-animate ng-leave"`                 |
983         * | 6. `$animate` blocks all CSS transitions on the element to ensure the `.ng-leave` class styling is applied right away | `class="my-animation ng-animate ng-leave"`                 |
984         * | 7. `$animate` waits for a single animation frame (this performs a reflow)                                             | `class="my-animation ng-animate ng-leave"`                 |
985         * | 8. `$animate` removes the CSS transition block placed on the element                                                  | `class="my-animation ng-animate ng-leave"`                 |
986         * | 9. the `.ng-leave-active` class is added (this triggers the CSS transition/animation)                                 | `class="my-animation ng-animate ng-leave ng-leave-active"` |
987         * | 10. `$animate` waits for the animation to complete (via events and timeout)                                           | `class="my-animation ng-animate ng-leave ng-leave-active"` |
988         * | 11. The animation ends and all generated CSS classes are removed from the element                                     | `class="my-animation"`                                     |
989         * | 12. The element is removed from the DOM                                                                               | ...                                                        |
990         * | 13. The returned promise is resolved.                                                                                 | ...                                                        |
991         *
992         * @param {DOMElement} element the element that will be the focus of the leave animation
993         * @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation
994         * @return {Promise} the animation callback promise
995        */
996        leave: function(element, options) {
997          options = parseAnimateOptions(options);
998          element = angular.element(element);
999
1000          cancelChildAnimations(element);
1001          classBasedAnimationsBlocked(element, true);
1002          return runAnimationPostDigest(function(done) {
1003            return performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
1004              $delegate.leave(element);
1005            }, options, done);
1006          });
1007        },
1008
1009        /**
1010         * @ngdoc method
1011         * @name $animate#move
1012         * @kind function
1013         *
1014         * @description
1015         * Fires the move DOM operation. Just before the animation starts, the animate service will either append it into the parentElement container or
1016         * add the element directly after the afterElement element if present. Then the move animation will be run. Once
1017         * the animation is started, the following CSS classes will be added for the duration of the animation:
1018         *
1019         * Below is a breakdown of each step that occurs during move animation:
1020         *
1021         * | Animation Step                                                                                                       | What the element class attribute looks like              |
1022         * |----------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
1023         * | 1. `$animate.move(...)` is called                                                                                    | `class="my-animation"`                                   |
1024         * | 2. element is moved into the parentElement element or beside the afterElement element                                | `class="my-animation"`                                   |
1025         * | 3. `$animate` waits for the next digest to start the animation                                                       | `class="my-animation ng-animate"`                        |
1026         * | 4. `$animate` runs the JavaScript-defined animations detected on the element                                         | `class="my-animation ng-animate"`                        |
1027         * | 5. the `.ng-move` class is added to the element                                                                      | `class="my-animation ng-animate ng-move"`                |
1028         * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay                        | `class="my-animation ng-animate ng-move"`                |
1029         * | 7. `$animate` blocks all CSS transitions on the element to ensure the `.ng-move` class styling is applied right away | `class="my-animation ng-animate ng-move"`                |
1030         * | 8. `$animate` waits for a single animation frame (this performs a reflow)                                            | `class="my-animation ng-animate ng-move"`                |
1031         * | 9. `$animate` removes the CSS transition block placed on the element                                                 | `class="my-animation ng-animate ng-move"`                |
1032         * | 10. the `.ng-move-active` class is added (this triggers the CSS transition/animation)                                | `class="my-animation ng-animate ng-move ng-move-active"` |
1033         * | 11. `$animate` waits for the animation to complete (via events and timeout)                                          | `class="my-animation ng-animate ng-move ng-move-active"` |
1034         * | 12. The animation ends and all generated CSS classes are removed from the element                                    | `class="my-animation"`                                   |
1035         * | 13. The returned promise is resolved.                                                                                | `class="my-animation"`                                   |
1036         *
1037         * @param {DOMElement} element the element that will be the focus of the move animation
1038         * @param {DOMElement} parentElement the parentElement element of the element that will be the focus of the move animation
1039         * @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation
1040         * @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation
1041         * @return {Promise} the animation callback promise
1042        */
1043        move: function(element, parentElement, afterElement, options) {
1044          options = parseAnimateOptions(options);
1045          element = angular.element(element);
1046          parentElement = prepareElement(parentElement);
1047          afterElement = prepareElement(afterElement);
1048
1049          cancelChildAnimations(element);
1050          classBasedAnimationsBlocked(element, true);
1051          $delegate.move(element, parentElement, afterElement);
1052          return runAnimationPostDigest(function(done) {
1053            return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, options, done);
1054          });
1055        },
1056
1057        /**
1058         * @ngdoc method
1059         * @name $animate#addClass
1060         *
1061         * @description
1062         * Triggers a custom animation event based off the className variable and then attaches the className value to the element as a CSS class.
1063         * Unlike the other animation methods, the animate service will suffix the className value with {@type -add} in order to provide
1064         * the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if no CSS transitions
1065         * or keyframes are defined on the -add-active or base CSS class).
1066         *
1067         * Below is a breakdown of each step that occurs during addClass animation:
1068         *
1069         * | Animation Step                                                                                         | What the element class attribute looks like                        |
1070         * |--------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|
1071         * | 1. `$animate.addClass(element, 'super')` is called                                                     | `class="my-animation"`                                             |
1072         * | 2. `$animate` runs the JavaScript-defined animations detected on the element                           | `class="my-animation ng-animate"`                                  |
1073         * | 3. the `.super-add` class is added to the element                                                      | `class="my-animation ng-animate super-add"`                        |
1074         * | 4. `$animate` waits for a single animation frame (this performs a reflow)                              | `class="my-animation ng-animate super-add"`                        |
1075         * | 5. the `.super` and `.super-add-active` classes are added (this triggers the CSS transition/animation) | `class="my-animation ng-animate super super-add super-add-active"` |
1076         * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay          | `class="my-animation ng-animate super super-add super-add-active"` |
1077         * | 7. `$animate` waits for the animation to complete (via events and timeout)                             | `class="my-animation ng-animate super super-add super-add-active"` |
1078         * | 8. The animation ends and all generated CSS classes are removed from the element                       | `class="my-animation super"`                                       |
1079         * | 9. The super class is kept on the element                                                              | `class="my-animation super"`                                       |
1080         * | 10. The returned promise is resolved.                                                                  | `class="my-animation super"`                                       |
1081         *
1082         * @param {DOMElement} element the element that will be animated
1083         * @param {string} className the CSS class that will be added to the element and then animated
1084         * @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation
1085         * @return {Promise} the animation callback promise
1086        */
1087        addClass: function(element, className, options) {
1088          return this.setClass(element, className, [], options);
1089        },
1090
1091        /**
1092         * @ngdoc method
1093         * @name $animate#removeClass
1094         *
1095         * @description
1096         * Triggers a custom animation event based off the className variable and then removes the CSS class provided by the className value
1097         * from the element. Unlike the other animation methods, the animate service will suffix the className value with {@type -remove} in
1098         * order to provide the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if
1099         * no CSS transitions or keyframes are defined on the -remove or base CSS classes).
1100         *
1101         * Below is a breakdown of each step that occurs during removeClass animation:
1102         *
1103         * | Animation Step                                                                                                       | What the element class attribute looks like                        |
1104         * |----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|
1105         * | 1. `$animate.removeClass(element, 'super')` is called                                                                | `class="my-animation super"`                                       |
1106         * | 2. `$animate` runs the JavaScript-defined animations detected on the element                                         | `class="my-animation super ng-animate"`                            |
1107         * | 3. the `.super-remove` class is added to the element                                                                 | `class="my-animation super ng-animate super-remove"`               |
1108         * | 4. `$animate` waits for a single animation frame (this performs a reflow)                                            | `class="my-animation super ng-animate super-remove"`               |
1109         * | 5. the `.super-remove-active` classes are added and `.super` is removed (this triggers the CSS transition/animation) | `class="my-animation ng-animate super-remove super-remove-active"` |
1110         * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay                        | `class="my-animation ng-animate super-remove super-remove-active"` |
1111         * | 7. `$animate` waits for the animation to complete (via events and timeout)                                           | `class="my-animation ng-animate super-remove super-remove-active"` |
1112         * | 8. The animation ends and all generated CSS classes are removed from the element                                     | `class="my-animation"`                                             |
1113         * | 9. The returned promise is resolved.                                                                                 | `class="my-animation"`                                             |
1114         *
1115         *
1116         * @param {DOMElement} element the element that will be animated
1117         * @param {string} className the CSS class that will be animated and then removed from the element
1118         * @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation
1119         * @return {Promise} the animation callback promise
1120        */
1121        removeClass: function(element, className, options) {
1122          return this.setClass(element, [], className, options);
1123        },
1124
1125        /**
1126         *
1127         * @ngdoc method
1128         * @name $animate#setClass
1129         *
1130         * @description Adds and/or removes the given CSS classes to and from the element.
1131         * Once complete, the `done()` callback will be fired (if provided).
1132         *
1133         * | Animation Step                                                                                                                               | What the element class attribute looks like                                            |
1134         * |----------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|
1135         * | 1. `$animate.setClass(element, 'on', 'off')` is called                                                                                       | `class="my-animation off"`                                                             |
1136         * | 2. `$animate` runs the JavaScript-defined animations detected on the element                                                                 | `class="my-animation ng-animate off"`                                                  |
1137         * | 3. the `.on-add` and `.off-remove` classes are added to the element                                                                          | `class="my-animation ng-animate on-add off-remove off"`                                |
1138         * | 4. `$animate` waits for a single animation frame (this performs a reflow)                                                                    | `class="my-animation ng-animate on-add off-remove off"`                                |
1139         * | 5. the `.on`, `.on-add-active` and `.off-remove-active` classes are added and `.off` is removed (this triggers the CSS transition/animation) | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` |
1140         * | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay                                                | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` |
1141         * | 7. `$animate` waits for the animation to complete (via events and timeout)                                                                   | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` |
1142         * | 8. The animation ends and all generated CSS classes are removed from the element                                                             | `class="my-animation on"`                                                              |
1143         * | 9. The returned promise is resolved.                                                                                                         | `class="my-animation on"`                                                              |
1144         *
1145         * @param {DOMElement} element the element which will have its CSS classes changed
1146         *   removed from it
1147         * @param {string} add the CSS classes which will be added to the element
1148         * @param {string} remove the CSS class which will be removed from the element
1149         *   CSS classes have been set on the element
1150         * @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation
1151         * @return {Promise} the animation callback promise
1152         */
1153        setClass: function(element, add, remove, options) {
1154          options = parseAnimateOptions(options);
1155
1156          var STORAGE_KEY = '$$animateClasses';
1157          element = angular.element(element);
1158          element = stripCommentsFromElement(element);
1159
1160          if (classBasedAnimationsBlocked(element)) {
1161            return $delegate.$$setClassImmediately(element, add, remove, options);
1162          }
1163
1164          // we're using a combined array for both the add and remove
1165          // operations since the ORDER OF addClass and removeClass matters
1166          var classes, cache = element.data(STORAGE_KEY);
1167          var hasCache = !!cache;
1168          if (!cache) {
1169            cache = {};
1170            cache.classes = {};
1171          }
1172          classes = cache.classes;
1173
1174          add = isArray(add) ? add : add.split(' ');
1175          forEach(add, function(c) {
1176            if (c && c.length) {
1177              classes[c] = true;
1178            }
1179          });
1180
1181          remove = isArray(remove) ? remove : remove.split(' ');
1182          forEach(remove, function(c) {
1183            if (c && c.length) {
1184              classes[c] = false;
1185            }
1186          });
1187
1188          if (hasCache) {
1189            if (options && cache.options) {
1190              cache.options = angular.extend(cache.options || {}, options);
1191            }
1192
1193            //the digest cycle will combine all the animations into one function
1194            return cache.promise;
1195          } else {
1196            element.data(STORAGE_KEY, cache = {
1197              classes: classes,
1198              options: options
1199            });
1200          }
1201
1202          return cache.promise = runAnimationPostDigest(function(done) {
1203            var parentElement = element.parent();
1204            var elementNode = extractElementNode(element);
1205            var parentNode = elementNode.parentNode;
1206            // TODO(matsko): move this code into the animationsDisabled() function once #8092 is fixed
1207            if (!parentNode || parentNode['$$NG_REMOVED'] || elementNode['$$NG_REMOVED']) {
1208              done();
1209              return;
1210            }
1211
1212            var cache = element.data(STORAGE_KEY);
1213            element.removeData(STORAGE_KEY);
1214
1215            var state = element.data(NG_ANIMATE_STATE) || {};
1216            var classes = resolveElementClasses(element, cache, state.active);
1217            return !classes
1218              ? done()
1219              : performAnimation('setClass', classes, element, parentElement, null, function() {
1220                  if (classes[0]) $delegate.$$addClassImmediately(element, classes[0]);
1221                  if (classes[1]) $delegate.$$removeClassImmediately(element, classes[1]);
1222                }, cache.options, done);
1223          });
1224        },
1225
1226        /**
1227         * @ngdoc method
1228         * @name $animate#cancel
1229         * @kind function
1230         *
1231         * @param {Promise} animationPromise The animation promise that is returned when an animation is started.
1232         *
1233         * @description
1234         * Cancels the provided animation.
1235        */
1236        cancel: function(promise) {
1237          promise.$$cancelFn();
1238        },
1239
1240        /**
1241         * @ngdoc method
1242         * @name $animate#enabled
1243         * @kind function
1244         *
1245         * @param {boolean=} value If provided then set the animation on or off.
1246         * @param {DOMElement=} element If provided then the element will be used to represent the enable/disable operation
1247         * @return {boolean} Current animation state.
1248         *
1249         * @description
1250         * Globally enables/disables animations.
1251         *
1252        */
1253        enabled: function(value, element) {
1254          switch (arguments.length) {
1255            case 2:
1256              if (value) {
1257                cleanup(element);
1258              } else {
1259                var data = element.data(NG_ANIMATE_STATE) || {};
1260                data.disabled = true;
1261                element.data(NG_ANIMATE_STATE, data);
1262              }
1263            break;
1264
1265            case 1:
1266              rootAnimateState.disabled = !value;
1267            break;
1268
1269            default:
1270              value = !rootAnimateState.disabled;
1271            break;
1272          }
1273          return !!value;
1274         }
1275      };
1276
1277      /*
1278        all animations call this shared animation triggering function internally.
1279        The animationEvent variable refers to the JavaScript animation event that will be triggered
1280        and the className value is the name of the animation that will be applied within the
1281        CSS code. Element, `parentElement` and `afterElement` are provided DOM elements for the animation
1282        and the onComplete callback will be fired once the animation is fully complete.
1283      */
1284      function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, options, doneCallback) {
1285        var noopCancel = noop;
1286        var runner = animationRunner(element, animationEvent, className, options);
1287        if (!runner) {
1288          fireDOMOperation();
1289          fireBeforeCallbackAsync();
1290          fireAfterCallbackAsync();
1291          closeAnimation();
1292          return noopCancel;
1293        }
1294
1295        animationEvent = runner.event;
1296        className = runner.className;
1297        var elementEvents = angular.element._data(runner.node);
1298        elementEvents = elementEvents && elementEvents.events;
1299
1300        if (!parentElement) {
1301          parentElement = afterElement ? afterElement.parent() : element.parent();
1302        }
1303
1304        //skip the animation if animations are disabled, a parent is already being animated,
1305        //the element is not currently attached to the document body or then completely close
1306        //the animation if any matching animations are not found at all.
1307        //NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case an animation was found.
1308        if (animationsDisabled(element, parentElement)) {
1309          fireDOMOperation();
1310          fireBeforeCallbackAsync();
1311          fireAfterCallbackAsync();
1312          closeAnimation();
1313          return noopCancel;
1314        }
1315
1316        var ngAnimateState  = element.data(NG_ANIMATE_STATE) || {};
1317        var runningAnimations     = ngAnimateState.active || {};
1318        var totalActiveAnimations = ngAnimateState.totalActive || 0;
1319        var lastAnimation         = ngAnimateState.last;
1320        var skipAnimation = false;
1321
1322        if (totalActiveAnimations > 0) {
1323          var animationsToCancel = [];
1324          if (!runner.isClassBased) {
1325            if (animationEvent == 'leave' && runningAnimations['ng-leave']) {
1326              skipAnimation = true;
1327            } else {
1328              //cancel all animations when a structural animation takes place
1329              for (var klass in runningAnimations) {
1330                animationsToCancel.push(runningAnimations[klass]);
1331              }
1332              ngAnimateState = {};
1333              cleanup(element, true);
1334            }
1335          } else if (lastAnimation.event == 'setClass') {
1336            animationsToCancel.push(lastAnimation);
1337            cleanup(element, className);
1338          } else if (runningAnimations[className]) {
1339            var current = runningAnimations[className];
1340            if (current.event == animationEvent) {
1341              skipAnimation = true;
1342            } else {
1343              animationsToCancel.push(current);
1344              cleanup(element, className);
1345            }
1346          }
1347
1348          if (animationsToCancel.length > 0) {
1349            forEach(animationsToCancel, function(operation) {
1350              operation.cancel();
1351            });
1352          }
1353        }
1354
1355        if (runner.isClassBased
1356            && !runner.isSetClassOperation
1357            && animationEvent != 'animate'
1358            && !skipAnimation) {
1359          skipAnimation = (animationEvent == 'addClass') == element.hasClass(className); //opposite of XOR
1360        }
1361
1362        if (skipAnimation) {
1363          fireDOMOperation();
1364          fireBeforeCallbackAsync();
1365          fireAfterCallbackAsync();
1366          fireDoneCallbackAsync();
1367          return noopCancel;
1368        }
1369
1370        runningAnimations     = ngAnimateState.active || {};
1371        totalActiveAnimations = ngAnimateState.totalActive || 0;
1372
1373        if (animationEvent == 'leave') {
1374          //there's no need to ever remove the listener since the element
1375          //will be removed (destroyed) after the leave animation ends or
1376          //is cancelled midway
1377          element.one('$destroy', function(e) {
1378            var element = angular.element(this);
1379            var state = element.data(NG_ANIMATE_STATE);
1380            if (state) {
1381              var activeLeaveAnimation = state.active['ng-leave'];
1382              if (activeLeaveAnimation) {
1383                activeLeaveAnimation.cancel();
1384                cleanup(element, 'ng-leave');
1385              }
1386            }
1387          });
1388        }
1389
1390        //the ng-animate class does nothing, but it's here to allow for
1391        //parent animations to find and cancel child animations when needed
1392        $$jqLite.addClass(element, NG_ANIMATE_CLASS_NAME);
1393        if (options && options.tempClasses) {
1394          forEach(options.tempClasses, function(className) {
1395            $$jqLite.addClass(element, className);
1396          });
1397        }
1398
1399        var localAnimationCount = globalAnimationCounter++;
1400        totalActiveAnimations++;
1401        runningAnimations[className] = runner;
1402
1403        element.data(NG_ANIMATE_STATE, {
1404          last: runner,
1405          active: runningAnimations,
1406          index: localAnimationCount,
1407          totalActive: totalActiveAnimations
1408        });
1409
1410        //first we run the before animations and when all of those are complete
1411        //then we perform the DOM operation and run the next set of animations
1412        fireBeforeCallbackAsync();
1413        runner.before(function(cancelled) {
1414          var data = element.data(NG_ANIMATE_STATE);
1415          cancelled = cancelled ||
1416                        !data || !data.active[className] ||
1417                        (runner.isClassBased && data.active[className].event != animationEvent);
1418
1419          fireDOMOperation();
1420          if (cancelled === true) {
1421            closeAnimation();
1422          } else {
1423            fireAfterCallbackAsync();
1424            runner.after(closeAnimation);
1425          }
1426        });
1427
1428        return runner.cancel;
1429
1430        function fireDOMCallback(animationPhase) {
1431          var eventName = '$animate:' + animationPhase;
1432          if (elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) {
1433            $$asyncCallback(function() {
1434              element.triggerHandler(eventName, {
1435                event: animationEvent,
1436                className: className
1437              });
1438            });
1439          }
1440        }
1441
1442        function fireBeforeCallbackAsync() {
1443          fireDOMCallback('before');
1444        }
1445
1446        function fireAfterCallbackAsync() {
1447          fireDOMCallback('after');
1448        }
1449
1450        function fireDoneCallbackAsync() {
1451          fireDOMCallback('close');
1452          doneCallback();
1453        }
1454
1455        //it is less complicated to use a flag than managing and canceling
1456        //timeouts containing multiple callbacks.
1457        function fireDOMOperation() {
1458          if (!fireDOMOperation.hasBeenRun) {
1459            fireDOMOperation.hasBeenRun = true;
1460            domOperation();
1461          }
1462        }
1463
1464        function closeAnimation() {
1465          if (!closeAnimation.hasBeenRun) {
1466            if (runner) { //the runner doesn't exist if it fails to instantiate
1467              runner.applyStyles();
1468            }
1469
1470            closeAnimation.hasBeenRun = true;
1471            if (options && options.tempClasses) {
1472              forEach(options.tempClasses, function(className) {
1473                $$jqLite.removeClass(element, className);
1474              });
1475            }
1476
1477            var data = element.data(NG_ANIMATE_STATE);
1478            if (data) {
1479
1480              /* only structural animations wait for reflow before removing an
1481                 animation, but class-based animations don't. An example of this
1482                 failing would be when a parent HTML tag has a ng-class attribute
1483                 causing ALL directives below to skip animations during the digest */
1484              if (runner && runner.isClassBased) {
1485                cleanup(element, className);
1486              } else {
1487                $$asyncCallback(function() {
1488                  var data = element.data(NG_ANIMATE_STATE) || {};
1489                  if (localAnimationCount == data.index) {
1490                    cleanup(element, className, animationEvent);
1491                  }
1492                });
1493                element.data(NG_ANIMATE_STATE, data);
1494              }
1495            }
1496            fireDoneCallbackAsync();
1497          }
1498        }
1499      }
1500
1501      function cancelChildAnimations(element) {
1502        var node = extractElementNode(element);
1503        if (node) {
1504          var nodes = angular.isFunction(node.getElementsByClassName) ?
1505            node.getElementsByClassName(NG_ANIMATE_CLASS_NAME) :
1506            node.querySelectorAll('.' + NG_ANIMATE_CLASS_NAME);
1507          forEach(nodes, function(element) {
1508            element = angular.element(element);
1509            var data = element.data(NG_ANIMATE_STATE);
1510            if (data && data.active) {
1511              forEach(data.active, function(runner) {
1512                runner.cancel();
1513              });
1514            }
1515          });
1516        }
1517      }
1518
1519      function cleanup(element, className) {
1520        if (isMatchingElement(element, $rootElement)) {
1521          if (!rootAnimateState.disabled) {
1522            rootAnimateState.running = false;
1523            rootAnimateState.structural = false;
1524          }
1525        } else if (className) {
1526          var data = element.data(NG_ANIMATE_STATE) || {};
1527
1528          var removeAnimations = className === true;
1529          if (!removeAnimations && data.active && data.active[className]) {
1530            data.totalActive--;
1531            delete data.active[className];
1532          }
1533
1534          if (removeAnimations || !data.totalActive) {
1535            $$jqLite.removeClass(element, NG_ANIMATE_CLASS_NAME);
1536            element.removeData(NG_ANIMATE_STATE);
1537          }
1538        }
1539      }
1540
1541      function animationsDisabled(element, parentElement) {
1542        if (rootAnimateState.disabled) {
1543          return true;
1544        }
1545
1546        if (isMatchingElement(element, $rootElement)) {
1547          return rootAnimateState.running;
1548        }
1549
1550        var allowChildAnimations, parentRunningAnimation, hasParent;
1551        do {
1552          //the element did not reach the root element which means that it
1553          //is not apart of the DOM. Therefore there is no reason to do
1554          //any animations on it
1555          if (parentElement.length === 0) break;
1556
1557          var isRoot = isMatchingElement(parentElement, $rootElement);
1558          var state = isRoot ? rootAnimateState : (parentElement.data(NG_ANIMATE_STATE) || {});
1559          if (state.disabled) {
1560            return true;
1561          }
1562
1563          //no matter what, for an animation to work it must reach the root element
1564          //this implies that the element is attached to the DOM when the animation is run
1565          if (isRoot) {
1566            hasParent = true;
1567          }
1568
1569          //once a flag is found that is strictly false then everything before
1570          //it will be discarded and all child animations will be restricted
1571          if (allowChildAnimations !== false) {
1572            var animateChildrenFlag = parentElement.data(NG_ANIMATE_CHILDREN);
1573            if (angular.isDefined(animateChildrenFlag)) {
1574              allowChildAnimations = animateChildrenFlag;
1575            }
1576          }
1577
1578          parentRunningAnimation = parentRunningAnimation ||
1579                                   state.running ||
1580                                   (state.last && !state.last.isClassBased);
1581        }
1582        while (parentElement = parentElement.parent());
1583
1584        return !hasParent || (!allowChildAnimations && parentRunningAnimation);
1585      }
1586    }]);
1587
1588    $animateProvider.register('', ['$window', '$sniffer', '$timeout', '$$animateReflow',
1589                           function($window,   $sniffer,   $timeout,   $$animateReflow) {
1590      // Detect proper transitionend/animationend event names.
1591      var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;
1592
1593      // If unprefixed events are not supported but webkit-prefixed are, use the latter.
1594      // Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
1595      // Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend`
1596      // but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`.
1597      // Register both events in case `window.onanimationend` is not supported because of that,
1598      // do the same for `transitionend` as Safari is likely to exhibit similar behavior.
1599      // Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
1600      // therefore there is no reason to test anymore for other vendor prefixes: http://caniuse.com/#search=transition
1601      if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) {
1602        CSS_PREFIX = '-webkit-';
1603        TRANSITION_PROP = 'WebkitTransition';
1604        TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
1605      } else {
1606        TRANSITION_PROP = 'transition';
1607        TRANSITIONEND_EVENT = 'transitionend';
1608      }
1609
1610      if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) {
1611        CSS_PREFIX = '-webkit-';
1612        ANIMATION_PROP = 'WebkitAnimation';
1613        ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
1614      } else {
1615        ANIMATION_PROP = 'animation';
1616        ANIMATIONEND_EVENT = 'animationend';
1617      }
1618
1619      var DURATION_KEY = 'Duration';
1620      var PROPERTY_KEY = 'Property';
1621      var DELAY_KEY = 'Delay';
1622      var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
1623      var ANIMATION_PLAYSTATE_KEY = 'PlayState';
1624      var NG_ANIMATE_PARENT_KEY = '$$ngAnimateKey';
1625      var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data';
1626      var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
1627      var CLOSING_TIME_BUFFER = 1.5;
1628      var ONE_SECOND = 1000;
1629
1630      var lookupCache = {};
1631      var parentCounter = 0;
1632      var animationReflowQueue = [];
1633      var cancelAnimationReflow;
1634      function clearCacheAfterReflow() {
1635        if (!cancelAnimationReflow) {
1636          cancelAnimationReflow = $$animateReflow(function() {
1637            animationReflowQueue = [];
1638            cancelAnimationReflow = null;
1639            lookupCache = {};
1640          });
1641        }
1642      }
1643
1644      function afterReflow(element, callback) {
1645        if (cancelAnimationReflow) {
1646          cancelAnimationReflow();
1647        }
1648        animationReflowQueue.push(callback);
1649        cancelAnimationReflow = $$animateReflow(function() {
1650          forEach(animationReflowQueue, function(fn) {
1651            fn();
1652          });
1653
1654          animationReflowQueue = [];
1655          cancelAnimationReflow = null;
1656          lookupCache = {};
1657        });
1658      }
1659
1660      var closingTimer = null;
1661      var closingTimestamp = 0;
1662      var animationElementQueue = [];
1663      function animationCloseHandler(element, totalTime) {
1664        var node = extractElementNode(element);
1665        element = angular.element(node);
1666
1667        //this item will be garbage collected by the closing
1668        //animation timeout
1669        animationElementQueue.push(element);
1670
1671        //but it may not need to cancel out the existing timeout
1672        //if the timestamp is less than the previous one
1673        var futureTimestamp = Date.now() + totalTime;
1674        if (futureTimestamp <= closingTimestamp) {
1675          return;
1676        }
1677
1678        $timeout.cancel(closingTimer);
1679
1680        closingTimestamp = futureTimestamp;
1681        closingTimer = $timeout(function() {
1682          closeAllAnimations(animationElementQueue);
1683          animationElementQueue = [];
1684        }, totalTime, false);
1685      }
1686
1687      function closeAllAnimations(elements) {
1688        forEach(elements, function(element) {
1689          var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
1690          if (elementData) {
1691            forEach(elementData.closeAnimationFns, function(fn) {
1692              fn();
1693            });
1694          }
1695        });
1696      }
1697
1698      function getElementAnimationDetails(element, cacheKey) {
1699        var data = cacheKey ? lookupCache[cacheKey] : null;
1700        if (!data) {
1701          var transitionDuration = 0;
1702          var transitionDelay = 0;
1703          var animationDuration = 0;
1704          var animationDelay = 0;
1705
1706          //we want all the styles defined before and after
1707          forEach(element, function(element) {
1708            if (element.nodeType == ELEMENT_NODE) {
1709              var elementStyles = $window.getComputedStyle(element) || {};
1710
1711              var transitionDurationStyle = elementStyles[TRANSITION_PROP + DURATION_KEY];
1712              transitionDuration = Math.max(parseMaxTime(transitionDurationStyle), transitionDuration);
1713
1714              var transitionDelayStyle = elementStyles[TRANSITION_PROP + DELAY_KEY];
1715              transitionDelay  = Math.max(parseMaxTime(transitionDelayStyle), transitionDelay);
1716
1717              var animationDelayStyle = elementStyles[ANIMATION_PROP + DELAY_KEY];
1718              animationDelay   = Math.max(parseMaxTime(elementStyles[ANIMATION_PROP + DELAY_KEY]), animationDelay);
1719
1720              var aDuration  = parseMaxTime(elementStyles[ANIMATION_PROP + DURATION_KEY]);
1721
1722              if (aDuration > 0) {
1723                aDuration *= parseInt(elementStyles[ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY], 10) || 1;
1724              }
1725              animationDuration = Math.max(aDuration, animationDuration);
1726            }
1727          });
1728          data = {
1729            total: 0,
1730            transitionDelay: transitionDelay,
1731            transitionDuration: transitionDuration,
1732            animationDelay: animationDelay,
1733            animationDuration: animationDuration
1734          };
1735          if (cacheKey) {
1736            lookupCache[cacheKey] = data;
1737          }
1738        }
1739        return data;
1740      }
1741
1742      function parseMaxTime(str) {
1743        var maxValue = 0;
1744        var values = isString(str) ?
1745          str.split(/\s*,\s*/) :
1746          [];
1747        forEach(values, function(value) {
1748          maxValue = Math.max(parseFloat(value) || 0, maxValue);
1749        });
1750        return maxValue;
1751      }
1752
1753      function getCacheKey(element) {
1754        var parentElement = element.parent();
1755        var parentID = parentElement.data(NG_ANIMATE_PARENT_KEY);
1756        if (!parentID) {
1757          parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter);
1758          parentID = parentCounter;
1759        }
1760        return parentID + '-' + extractElementNode(element).getAttribute('class');
1761      }
1762
1763      function animateSetup(animationEvent, element, className, styles) {
1764        var structural = ['ng-enter','ng-leave','ng-move'].indexOf(className) >= 0;
1765
1766        var cacheKey = getCacheKey(element);
1767        var eventCacheKey = cacheKey + ' ' + className;
1768        var itemIndex = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0;
1769
1770        var stagger = {};
1771        if (itemIndex > 0) {
1772          var staggerClassName = className + '-stagger';
1773          var staggerCacheKey = cacheKey + ' ' + staggerClassName;
1774          var applyClasses = !lookupCache[staggerCacheKey];
1775
1776          applyClasses && $$jqLite.addClass(element, staggerClassName);
1777
1778          stagger = getElementAnimationDetails(element, staggerCacheKey);
1779
1780          applyClasses && $$jqLite.removeClass(element, staggerClassName);
1781        }
1782
1783        $$jqLite.addClass(element, className);
1784
1785        var formerData = element.data(NG_ANIMATE_CSS_DATA_KEY) || {};
1786        var timings = getElementAnimationDetails(element, eventCacheKey);
1787        var transitionDuration = timings.transitionDuration;
1788        var animationDuration = timings.animationDuration;
1789
1790        if (structural && transitionDuration === 0 && animationDuration === 0) {
1791          $$jqLite.removeClass(element, className);
1792          return false;
1793        }
1794
1795        var blockTransition = styles || (structural && transitionDuration > 0);
1796        var blockAnimation = animationDuration > 0 &&
1797                             stagger.animationDelay > 0 &&
1798                             stagger.animationDuration === 0;
1799
1800        var closeAnimationFns = formerData.closeAnimationFns || [];
1801        element.data(NG_ANIMATE_CSS_DATA_KEY, {
1802          stagger: stagger,
1803          cacheKey: eventCacheKey,
1804          running: formerData.running || 0,
1805          itemIndex: itemIndex,
1806          blockTransition: blockTransition,
1807          closeAnimationFns: closeAnimationFns
1808        });
1809
1810        var node = extractElementNode(element);
1811
1812        if (blockTransition) {
1813          blockTransitions(node, true);
1814          if (styles) {
1815            element.css(styles);
1816          }
1817        }
1818
1819        if (blockAnimation) {
1820          blockAnimations(node, true);
1821        }
1822
1823        return true;
1824      }
1825
1826      function animateRun(animationEvent, element, className, activeAnimationComplete, styles) {
1827        var node = extractElementNode(element);
1828        var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
1829        if (node.getAttribute('class').indexOf(className) == -1 || !elementData) {
1830          activeAnimationComplete();
1831          return;
1832        }
1833
1834        var activeClassName = '';
1835        var pendingClassName = '';
1836        forEach(className.split(' '), function(klass, i) {
1837          var prefix = (i > 0 ? ' ' : '') + klass;
1838          activeClassName += prefix + '-active';
1839          pendingClassName += prefix + '-pending';
1840        });
1841
1842        var style = '';
1843        var appliedStyles = [];
1844        var itemIndex = elementData.itemIndex;
1845        var stagger = elementData.stagger;
1846        var staggerTime = 0;
1847        if (itemIndex > 0) {
1848          var transitionStaggerDelay = 0;
1849          if (stagger.transitionDelay > 0 && stagger.transitionDuration === 0) {
1850            transitionStaggerDelay = stagger.transitionDelay * itemIndex;
1851          }
1852
1853          var animationStaggerDelay = 0;
1854          if (stagger.animationDelay > 0 && stagger.animationDuration === 0) {
1855            animationStaggerDelay = stagger.animationDelay * itemIndex;
1856            appliedStyles.push(CSS_PREFIX + 'animation-play-state');
1857          }
1858
1859          staggerTime = Math.round(Math.max(transitionStaggerDelay, animationStaggerDelay) * 100) / 100;
1860        }
1861
1862        if (!staggerTime) {
1863          $$jqLite.addClass(element, activeClassName);
1864          if (elementData.blockTransition) {
1865            blockTransitions(node, false);
1866          }
1867        }
1868
1869        var eventCacheKey = elementData.cacheKey + ' ' + activeClassName;
1870        var timings = getElementAnimationDetails(element, eventCacheKey);
1871        var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
1872        if (maxDuration === 0) {
1873          $$jqLite.removeClass(element, activeClassName);
1874          animateClose(element, className);
1875          activeAnimationComplete();
1876          return;
1877        }
1878
1879        if (!staggerTime && styles && Object.keys(styles).length > 0) {
1880          if (!timings.transitionDuration) {
1881            element.css('transition', timings.animationDuration + 's linear all');
1882            appliedStyles.push('transition');
1883          }
1884          element.css(styles);
1885        }
1886
1887        var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay);
1888        var maxDelayTime = maxDelay * ONE_SECOND;
1889
1890        if (appliedStyles.length > 0) {
1891          //the element being animated may sometimes contain comment nodes in
1892          //the jqLite object, so we're safe to use a single variable to house
1893          //the styles since there is always only one element being animated
1894          var oldStyle = node.getAttribute('style') || '';
1895          if (oldStyle.charAt(oldStyle.length - 1) !== ';') {
1896            oldStyle += ';';
1897          }
1898          node.setAttribute('style', oldStyle + ' ' + style);
1899        }
1900
1901        var startTime = Date.now();
1902        var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
1903        var animationTime     = (maxDelay + maxDuration) * CLOSING_TIME_BUFFER;
1904        var totalTime         = (staggerTime + animationTime) * ONE_SECOND;
1905
1906        var staggerTimeout;
1907        if (staggerTime > 0) {
1908          $$jqLite.addClass(element, pendingClassName);
1909          staggerTimeout = $timeout(function() {
1910            staggerTimeout = null;
1911
1912            if (timings.transitionDuration > 0) {
1913              blockTransitions(node, false);
1914            }
1915            if (timings.animationDuration > 0) {
1916              blockAnimations(node, false);
1917            }
1918
1919            $$jqLite.addClass(element, activeClassName);
1920            $$jqLite.removeClass(element, pendingClassName);
1921
1922            if (styles) {
1923              if (timings.transitionDuration === 0) {
1924                element.css('transition', timings.animationDuration + 's linear all');
1925              }
1926              element.css(styles);
1927              appliedStyles.push('transition');
1928            }
1929          }, staggerTime * ONE_SECOND, false);
1930        }
1931
1932        element.on(css3AnimationEvents, onAnimationProgress);
1933        elementData.closeAnimationFns.push(function() {
1934          onEnd();
1935          activeAnimationComplete();
1936        });
1937
1938        elementData.running++;
1939        animationCloseHandler(element, totalTime);
1940        return onEnd;
1941
1942        // This will automatically be called by $animate so
1943        // there is no need to attach this internally to the
1944        // timeout done method.
1945        function onEnd() {
1946          element.off(css3AnimationEvents, onAnimationProgress);
1947          $$jqLite.removeClass(element, activeClassName);
1948          $$jqLite.removeClass(element, pendingClassName);
1949          if (staggerTimeout) {
1950            $timeout.cancel(staggerTimeout);
1951          }
1952          animateClose(element, className);
1953          var node = extractElementNode(element);
1954          for (var i in appliedStyles) {
1955            node.style.removeProperty(appliedStyles[i]);
1956          }
1957        }
1958
1959        function onAnimationProgress(event) {
1960          event.stopPropagation();
1961          var ev = event.originalEvent || event;
1962          var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now();
1963
1964          /* Firefox (or possibly just Gecko) likes to not round values up
1965           * when a ms measurement is used for the animation */
1966          var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
1967
1968          /* $manualTimeStamp is a mocked timeStamp value which is set
1969           * within browserTrigger(). This is only here so that tests can
1970           * mock animations properly. Real events fallback to event.timeStamp,
1971           * or, if they don't, then a timeStamp is automatically created for them.
1972           * We're checking to see if the timeStamp surpasses the expected delay,
1973           * but we're using elapsedTime instead of the timeStamp on the 2nd
1974           * pre-condition since animations sometimes close off early */
1975          if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
1976            activeAnimationComplete();
1977          }
1978        }
1979      }
1980
1981      function blockTransitions(node, bool) {
1982        node.style[TRANSITION_PROP + PROPERTY_KEY] = bool ? 'none' : '';
1983      }
1984
1985      function blockAnimations(node, bool) {
1986        node.style[ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY] = bool ? 'paused' : '';
1987      }
1988
1989      function animateBefore(animationEvent, element, className, styles) {
1990        if (animateSetup(animationEvent, element, className, styles)) {
1991          return function(cancelled) {
1992            cancelled && animateClose(element, className);
1993          };
1994        }
1995      }
1996
1997      function animateAfter(animationEvent, element, className, afterAnimationComplete, styles) {
1998        if (element.data(NG_ANIMATE_CSS_DATA_KEY)) {
1999          return animateRun(animationEvent, element, className, afterAnimationComplete, styles);
2000        } else {
2001          animateClose(element, className);
2002          afterAnimationComplete();
2003        }
2004      }
2005
2006      function animate(animationEvent, element, className, animationComplete, options) {
2007        //If the animateSetup function doesn't bother returning a
2008        //cancellation function then it means that there is no animation
2009        //to perform at all
2010        var preReflowCancellation = animateBefore(animationEvent, element, className, options.from);
2011        if (!preReflowCancellation) {
2012          clearCacheAfterReflow();
2013          animationComplete();
2014          return;
2015        }
2016
2017        //There are two cancellation functions: one is before the first
2018        //reflow animation and the second is during the active state
2019        //animation. The first function will take care of removing the
2020        //data from the element which will not make the 2nd animation
2021        //happen in the first place
2022        var cancel = preReflowCancellation;
2023        afterReflow(element, function() {
2024          //once the reflow is complete then we point cancel to
2025          //the new cancellation function which will remove all of the
2026          //animation properties from the active animation
2027          cancel = animateAfter(animationEvent, element, className, animationComplete, options.to);
2028        });
2029
2030        return function(cancelled) {
2031          (cancel || noop)(cancelled);
2032        };
2033      }
2034
2035      function animateClose(element, className) {
2036        $$jqLite.removeClass(element, className);
2037        var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
2038        if (data) {
2039          if (data.running) {
2040            data.running--;
2041          }
2042          if (!data.running || data.running === 0) {
2043            element.removeData(NG_ANIMATE_CSS_DATA_KEY);
2044          }
2045        }
2046      }
2047
2048      return {
2049        animate: function(element, className, from, to, animationCompleted, options) {
2050          options = options || {};
2051          options.from = from;
2052          options.to = to;
2053          return animate('animate', element, className, animationCompleted, options);
2054        },
2055
2056        enter: function(element, animationCompleted, options) {
2057          options = options || {};
2058          return animate('enter', element, 'ng-enter', animationCompleted, options);
2059        },
2060
2061        leave: function(element, animationCompleted, options) {
2062          options = options || {};
2063          return animate('leave', element, 'ng-leave', animationCompleted, options);
2064        },
2065
2066        move: function(element, animationCompleted, options) {
2067          options = options || {};
2068          return animate('move', element, 'ng-move', animationCompleted, options);
2069        },
2070
2071        beforeSetClass: function(element, add, remove, animationCompleted, options) {
2072          options = options || {};
2073          var className = suffixClasses(remove, '-remove') + ' ' +
2074                          suffixClasses(add, '-add');
2075          var cancellationMethod = animateBefore('setClass', element, className, options.from);
2076          if (cancellationMethod) {
2077            afterReflow(element, animationCompleted);
2078            return cancellationMethod;
2079          }
2080          clearCacheAfterReflow();
2081          animationCompleted();
2082        },
2083
2084        beforeAddClass: function(element, className, animationCompleted, options) {
2085          options = options || {};
2086          var cancellationMethod = animateBefore('addClass', element, suffixClasses(className, '-add'), options.from);
2087          if (cancellationMethod) {
2088            afterReflow(element, animationCompleted);
2089            return cancellationMethod;
2090          }
2091          clearCacheAfterReflow();
2092          animationCompleted();
2093        },
2094
2095        beforeRemoveClass: function(element, className, animationCompleted, options) {
2096          options = options || {};
2097          var cancellationMethod = animateBefore('removeClass', element, suffixClasses(className, '-remove'), options.from);
2098          if (cancellationMethod) {
2099            afterReflow(element, animationCompleted);
2100            return cancellationMethod;
2101          }
2102          clearCacheAfterReflow();
2103          animationCompleted();
2104        },
2105
2106        setClass: function(element, add, remove, animationCompleted, options) {
2107          options = options || {};
2108          remove = suffixClasses(remove, '-remove');
2109          add = suffixClasses(add, '-add');
2110          var className = remove + ' ' + add;
2111          return animateAfter('setClass', element, className, animationCompleted, options.to);
2112        },
2113
2114        addClass: function(element, className, animationCompleted, options) {
2115          options = options || {};
2116          return animateAfter('addClass', element, suffixClasses(className, '-add'), animationCompleted, options.to);
2117        },
2118
2119        removeClass: function(element, className, animationCompleted, options) {
2120          options = options || {};
2121          return animateAfter('removeClass', element, suffixClasses(className, '-remove'), animationCompleted, options.to);
2122        }
2123      };
2124
2125      function suffixClasses(classes, suffix) {
2126        var className = '';
2127        classes = isArray(classes) ? classes : classes.split(/\s+/);
2128        forEach(classes, function(klass, i) {
2129          if (klass && klass.length > 0) {
2130            className += (i > 0 ? ' ' : '') + klass + suffix;
2131          }
2132        });
2133        return className;
2134      }
2135    }]);
2136  }]);
2137
2138
2139})(window, window.angular);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.