Initial OpenECOMP policy/engine commit
[policy/engine.git] / ecomp-sdk-app / src / main / webapp / app / fusion / external / angular-1.5 / angular-animate.js
1 /**
2  * @license AngularJS v1.5.0
3  * (c) 2010-2016 Google, Inc. http://angularjs.org
4  * License: MIT
5  */
6 (function(window, angular, undefined) {'use strict';
7
8 /* jshint ignore:start */
9 var noop        = angular.noop;
10 var copy        = angular.copy;
11 var extend      = angular.extend;
12 var jqLite      = angular.element;
13 var forEach     = angular.forEach;
14 var isArray     = angular.isArray;
15 var isString    = angular.isString;
16 var isObject    = angular.isObject;
17 var isUndefined = angular.isUndefined;
18 var isDefined   = angular.isDefined;
19 var isFunction  = angular.isFunction;
20 var isElement   = angular.isElement;
21
22 var ELEMENT_NODE = 1;
23 var COMMENT_NODE = 8;
24
25 var ADD_CLASS_SUFFIX = '-add';
26 var REMOVE_CLASS_SUFFIX = '-remove';
27 var EVENT_CLASS_PREFIX = 'ng-';
28 var ACTIVE_CLASS_SUFFIX = '-active';
29 var PREPARE_CLASS_SUFFIX = '-prepare';
30
31 var NG_ANIMATE_CLASSNAME = 'ng-animate';
32 var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren';
33
34 // Detect proper transitionend/animationend event names.
35 var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;
36
37 // If unprefixed events are not supported but webkit-prefixed are, use the latter.
38 // Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
39 // Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend`
40 // but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`.
41 // Register both events in case `window.onanimationend` is not supported because of that,
42 // do the same for `transitionend` as Safari is likely to exhibit similar behavior.
43 // Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
44 // therefore there is no reason to test anymore for other vendor prefixes:
45 // http://caniuse.com/#search=transition
46 if (isUndefined(window.ontransitionend) && isDefined(window.onwebkittransitionend)) {
47   CSS_PREFIX = '-webkit-';
48   TRANSITION_PROP = 'WebkitTransition';
49   TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
50 } else {
51   TRANSITION_PROP = 'transition';
52   TRANSITIONEND_EVENT = 'transitionend';
53 }
54
55 if (isUndefined(window.onanimationend) && isDefined(window.onwebkitanimationend)) {
56   CSS_PREFIX = '-webkit-';
57   ANIMATION_PROP = 'WebkitAnimation';
58   ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
59 } else {
60   ANIMATION_PROP = 'animation';
61   ANIMATIONEND_EVENT = 'animationend';
62 }
63
64 var DURATION_KEY = 'Duration';
65 var PROPERTY_KEY = 'Property';
66 var DELAY_KEY = 'Delay';
67 var TIMING_KEY = 'TimingFunction';
68 var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
69 var ANIMATION_PLAYSTATE_KEY = 'PlayState';
70 var SAFE_FAST_FORWARD_DURATION_VALUE = 9999;
71
72 var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY;
73 var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY;
74 var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY;
75 var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY;
76
77 var isPromiseLike = function(p) {
78   return p && p.then ? true : false;
79 };
80
81 var ngMinErr = angular.$$minErr('ng');
82 function assertArg(arg, name, reason) {
83   if (!arg) {
84     throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
85   }
86   return arg;
87 }
88
89 function mergeClasses(a,b) {
90   if (!a && !b) return '';
91   if (!a) return b;
92   if (!b) return a;
93   if (isArray(a)) a = a.join(' ');
94   if (isArray(b)) b = b.join(' ');
95   return a + ' ' + b;
96 }
97
98 function packageStyles(options) {
99   var styles = {};
100   if (options && (options.to || options.from)) {
101     styles.to = options.to;
102     styles.from = options.from;
103   }
104   return styles;
105 }
106
107 function pendClasses(classes, fix, isPrefix) {
108   var className = '';
109   classes = isArray(classes)
110       ? classes
111       : classes && isString(classes) && classes.length
112           ? classes.split(/\s+/)
113           : [];
114   forEach(classes, function(klass, i) {
115     if (klass && klass.length > 0) {
116       className += (i > 0) ? ' ' : '';
117       className += isPrefix ? fix + klass
118                             : klass + fix;
119     }
120   });
121   return className;
122 }
123
124 function removeFromArray(arr, val) {
125   var index = arr.indexOf(val);
126   if (val >= 0) {
127     arr.splice(index, 1);
128   }
129 }
130
131 function stripCommentsFromElement(element) {
132   if (element instanceof jqLite) {
133     switch (element.length) {
134       case 0:
135         return [];
136         break;
137
138       case 1:
139         // there is no point of stripping anything if the element
140         // is the only element within the jqLite wrapper.
141         // (it's important that we retain the element instance.)
142         if (element[0].nodeType === ELEMENT_NODE) {
143           return element;
144         }
145         break;
146
147       default:
148         return jqLite(extractElementNode(element));
149         break;
150     }
151   }
152
153   if (element.nodeType === ELEMENT_NODE) {
154     return jqLite(element);
155   }
156 }
157
158 function extractElementNode(element) {
159   if (!element[0]) return element;
160   for (var i = 0; i < element.length; i++) {
161     var elm = element[i];
162     if (elm.nodeType == ELEMENT_NODE) {
163       return elm;
164     }
165   }
166 }
167
168 function $$addClass($$jqLite, element, className) {
169   forEach(element, function(elm) {
170     $$jqLite.addClass(elm, className);
171   });
172 }
173
174 function $$removeClass($$jqLite, element, className) {
175   forEach(element, function(elm) {
176     $$jqLite.removeClass(elm, className);
177   });
178 }
179
180 function applyAnimationClassesFactory($$jqLite) {
181   return function(element, options) {
182     if (options.addClass) {
183       $$addClass($$jqLite, element, options.addClass);
184       options.addClass = null;
185     }
186     if (options.removeClass) {
187       $$removeClass($$jqLite, element, options.removeClass);
188       options.removeClass = null;
189     }
190   }
191 }
192
193 function prepareAnimationOptions(options) {
194   options = options || {};
195   if (!options.$$prepared) {
196     var domOperation = options.domOperation || noop;
197     options.domOperation = function() {
198       options.$$domOperationFired = true;
199       domOperation();
200       domOperation = noop;
201     };
202     options.$$prepared = true;
203   }
204   return options;
205 }
206
207 function applyAnimationStyles(element, options) {
208   applyAnimationFromStyles(element, options);
209   applyAnimationToStyles(element, options);
210 }
211
212 function applyAnimationFromStyles(element, options) {
213   if (options.from) {
214     element.css(options.from);
215     options.from = null;
216   }
217 }
218
219 function applyAnimationToStyles(element, options) {
220   if (options.to) {
221     element.css(options.to);
222     options.to = null;
223   }
224 }
225
226 function mergeAnimationDetails(element, oldAnimation, newAnimation) {
227   var target = oldAnimation.options || {};
228   var newOptions = newAnimation.options || {};
229
230   var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || '');
231   var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass || '');
232   var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove);
233
234   if (newOptions.preparationClasses) {
235     target.preparationClasses = concatWithSpace(newOptions.preparationClasses, target.preparationClasses);
236     delete newOptions.preparationClasses;
237   }
238
239   // noop is basically when there is no callback; otherwise something has been set
240   var realDomOperation = target.domOperation !== noop ? target.domOperation : null;
241
242   extend(target, newOptions);
243
244   // TODO(matsko or sreeramu): proper fix is to maintain all animation callback in array and call at last,but now only leave has the callback so no issue with this.
245   if (realDomOperation) {
246     target.domOperation = realDomOperation;
247   }
248
249   if (classes.addClass) {
250     target.addClass = classes.addClass;
251   } else {
252     target.addClass = null;
253   }
254
255   if (classes.removeClass) {
256     target.removeClass = classes.removeClass;
257   } else {
258     target.removeClass = null;
259   }
260
261   oldAnimation.addClass = target.addClass;
262   oldAnimation.removeClass = target.removeClass;
263
264   return target;
265 }
266
267 function resolveElementClasses(existing, toAdd, toRemove) {
268   var ADD_CLASS = 1;
269   var REMOVE_CLASS = -1;
270
271   var flags = {};
272   existing = splitClassesToLookup(existing);
273
274   toAdd = splitClassesToLookup(toAdd);
275   forEach(toAdd, function(value, key) {
276     flags[key] = ADD_CLASS;
277   });
278
279   toRemove = splitClassesToLookup(toRemove);
280   forEach(toRemove, function(value, key) {
281     flags[key] = flags[key] === ADD_CLASS ? null : REMOVE_CLASS;
282   });
283
284   var classes = {
285     addClass: '',
286     removeClass: ''
287   };
288
289   forEach(flags, function(val, klass) {
290     var prop, allow;
291     if (val === ADD_CLASS) {
292       prop = 'addClass';
293       allow = !existing[klass];
294     } else if (val === REMOVE_CLASS) {
295       prop = 'removeClass';
296       allow = existing[klass];
297     }
298     if (allow) {
299       if (classes[prop].length) {
300         classes[prop] += ' ';
301       }
302       classes[prop] += klass;
303     }
304   });
305
306   function splitClassesToLookup(classes) {
307     if (isString(classes)) {
308       classes = classes.split(' ');
309     }
310
311     var obj = {};
312     forEach(classes, function(klass) {
313       // sometimes the split leaves empty string values
314       // incase extra spaces were applied to the options
315       if (klass.length) {
316         obj[klass] = true;
317       }
318     });
319     return obj;
320   }
321
322   return classes;
323 }
324
325 function getDomNode(element) {
326   return (element instanceof angular.element) ? element[0] : element;
327 }
328
329 function applyGeneratedPreparationClasses(element, event, options) {
330   var classes = '';
331   if (event) {
332     classes = pendClasses(event, EVENT_CLASS_PREFIX, true);
333   }
334   if (options.addClass) {
335     classes = concatWithSpace(classes, pendClasses(options.addClass, ADD_CLASS_SUFFIX));
336   }
337   if (options.removeClass) {
338     classes = concatWithSpace(classes, pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX));
339   }
340   if (classes.length) {
341     options.preparationClasses = classes;
342     element.addClass(classes);
343   }
344 }
345
346 function clearGeneratedClasses(element, options) {
347   if (options.preparationClasses) {
348     element.removeClass(options.preparationClasses);
349     options.preparationClasses = null;
350   }
351   if (options.activeClasses) {
352     element.removeClass(options.activeClasses);
353     options.activeClasses = null;
354   }
355 }
356
357 function blockTransitions(node, duration) {
358   // we use a negative delay value since it performs blocking
359   // yet it doesn't kill any existing transitions running on the
360   // same element which makes this safe for class-based animations
361   var value = duration ? '-' + duration + 's' : '';
362   applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]);
363   return [TRANSITION_DELAY_PROP, value];
364 }
365
366 function blockKeyframeAnimations(node, applyBlock) {
367   var value = applyBlock ? 'paused' : '';
368   var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY;
369   applyInlineStyle(node, [key, value]);
370   return [key, value];
371 }
372
373 function applyInlineStyle(node, styleTuple) {
374   var prop = styleTuple[0];
375   var value = styleTuple[1];
376   node.style[prop] = value;
377 }
378
379 function concatWithSpace(a,b) {
380   if (!a) return b;
381   if (!b) return a;
382   return a + ' ' + b;
383 }
384
385 var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
386   var queue, cancelFn;
387
388   function scheduler(tasks) {
389     // we make a copy since RAFScheduler mutates the state
390     // of the passed in array variable and this would be difficult
391     // to track down on the outside code
392     queue = queue.concat(tasks);
393     nextTick();
394   }
395
396   queue = scheduler.queue = [];
397
398   /* waitUntilQuiet does two things:
399    * 1. It will run the FINAL `fn` value only when an uncanceled RAF has passed through
400    * 2. It will delay the next wave of tasks from running until the quiet `fn` has run.
401    *
402    * The motivation here is that animation code can request more time from the scheduler
403    * before the next wave runs. This allows for certain DOM properties such as classes to
404    * be resolved in time for the next animation to run.
405    */
406   scheduler.waitUntilQuiet = function(fn) {
407     if (cancelFn) cancelFn();
408
409     cancelFn = $$rAF(function() {
410       cancelFn = null;
411       fn();
412       nextTick();
413     });
414   };
415
416   return scheduler;
417
418   function nextTick() {
419     if (!queue.length) return;
420
421     var items = queue.shift();
422     for (var i = 0; i < items.length; i++) {
423       items[i]();
424     }
425
426     if (!cancelFn) {
427       $$rAF(function() {
428         if (!cancelFn) nextTick();
429       });
430     }
431   }
432 }];
433
434 /**
435  * @ngdoc directive
436  * @name ngAnimateChildren
437  * @restrict AE
438  * @element ANY
439  *
440  * @description
441  *
442  * ngAnimateChildren allows you to specify that children of this element should animate even if any
443  * of the children's parents are currently animating. By default, when an element has an active `enter`, `leave`, or `move`
444  * (structural) animation, child elements that also have an active structural animation are not animated.
445  *
446  * Note that even if `ngAnimteChildren` is set, no child animations will run when the parent element is removed from the DOM (`leave` animation).
447  *
448  *
449  * @param {string} ngAnimateChildren If the value is empty, `true` or `on`,
450  *     then child animations are allowed. If the value is `false`, child animations are not allowed.
451  *
452  * @example
453  * <example module="ngAnimateChildren" name="ngAnimateChildren" deps="angular-animate.js" animations="true">
454      <file name="index.html">
455        <div ng-controller="mainController as main">
456          <label>Show container? <input type="checkbox" ng-model="main.enterElement" /></label>
457          <label>Animate children? <input type="checkbox" ng-model="main.animateChildren" /></label>
458          <hr>
459          <div ng-animate-children="{{main.animateChildren}}">
460            <div ng-if="main.enterElement" class="container">
461              List of items:
462              <div ng-repeat="item in [0, 1, 2, 3]" class="item">Item {{item}}</div>
463            </div>
464          </div>
465        </div>
466      </file>
467      <file name="animations.css">
468
469       .container.ng-enter,
470       .container.ng-leave {
471         transition: all ease 1.5s;
472       }
473
474       .container.ng-enter,
475       .container.ng-leave-active {
476         opacity: 0;
477       }
478
479       .container.ng-leave,
480       .container.ng-enter-active {
481         opacity: 1;
482       }
483
484       .item {
485         background: firebrick;
486         color: #FFF;
487         margin-bottom: 10px;
488       }
489
490       .item.ng-enter,
491       .item.ng-leave {
492         transition: transform 1.5s ease;
493       }
494
495       .item.ng-enter {
496         transform: translateX(50px);
497       }
498
499       .item.ng-enter-active {
500         transform: translateX(0);
501       }
502     </file>
503     <file name="script.js">
504       angular.module('ngAnimateChildren', ['ngAnimate'])
505         .controller('mainController', function() {
506           this.animateChildren = false;
507           this.enterElement = false;
508         });
509     </file>
510   </example>
511  */
512 var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) {
513   return {
514     link: function(scope, element, attrs) {
515       var val = attrs.ngAnimateChildren;
516       if (angular.isString(val) && val.length === 0) { //empty attribute
517         element.data(NG_ANIMATE_CHILDREN_DATA, true);
518       } else {
519         // Interpolate and set the value, so that it is available to
520         // animations that run right after compilation
521         setData($interpolate(val)(scope));
522         attrs.$observe('ngAnimateChildren', setData);
523       }
524
525       function setData(value) {
526         value = value === 'on' || value === 'true';
527         element.data(NG_ANIMATE_CHILDREN_DATA, value);
528       }
529     }
530   };
531 }];
532
533 var ANIMATE_TIMER_KEY = '$$animateCss';
534
535 /**
536  * @ngdoc service
537  * @name $animateCss
538  * @kind object
539  *
540  * @description
541  * The `$animateCss` service is a useful utility to trigger customized CSS-based transitions/keyframes
542  * from a JavaScript-based animation or directly from a directive. The purpose of `$animateCss` is NOT
543  * to side-step how `$animate` and ngAnimate work, but the goal is to allow pre-existing animations or
544  * directives to create more complex animations that can be purely driven using CSS code.
545  *
546  * Note that only browsers that support CSS transitions and/or keyframe animations are capable of
547  * rendering animations triggered via `$animateCss` (bad news for IE9 and lower).
548  *
549  * ## Usage
550  * Once again, `$animateCss` is designed to be used inside of a registered JavaScript animation that
551  * is powered by ngAnimate. It is possible to use `$animateCss` directly inside of a directive, however,
552  * any automatic control over cancelling animations and/or preventing animations from being run on
553  * child elements will not be handled by Angular. For this to work as expected, please use `$animate` to
554  * trigger the animation and then setup a JavaScript animation that injects `$animateCss` to trigger
555  * the CSS animation.
556  *
557  * The example below shows how we can create a folding animation on an element using `ng-if`:
558  *
559  * ```html
560  * <!-- notice the `fold-animation` CSS class -->
561  * <div ng-if="onOff" class="fold-animation">
562  *   This element will go BOOM
563  * </div>
564  * <button ng-click="onOff=true">Fold In</button>
565  * ```
566  *
567  * Now we create the **JavaScript animation** that will trigger the CSS transition:
568  *
569  * ```js
570  * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {
571  *   return {
572  *     enter: function(element, doneFn) {
573  *       var height = element[0].offsetHeight;
574  *       return $animateCss(element, {
575  *         from: { height:'0px' },
576  *         to: { height:height + 'px' },
577  *         duration: 1 // one second
578  *       });
579  *     }
580  *   }
581  * }]);
582  * ```
583  *
584  * ## More Advanced Uses
585  *
586  * `$animateCss` is the underlying code that ngAnimate uses to power **CSS-based animations** behind the scenes. Therefore CSS hooks
587  * like `.ng-EVENT`, `.ng-EVENT-active`, `.ng-EVENT-stagger` are all features that can be triggered using `$animateCss` via JavaScript code.
588  *
589  * This also means that just about any combination of adding classes, removing classes, setting styles, dynamically setting a keyframe animation,
590  * applying a hardcoded duration or delay value, changing the animation easing or applying a stagger animation are all options that work with
591  * `$animateCss`. The service itself is smart enough to figure out the combination of options and examine the element styling properties in order
592  * to provide a working animation that will run in CSS.
593  *
594  * The example below showcases a more advanced version of the `.fold-animation` from the example above:
595  *
596  * ```js
597  * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {
598  *   return {
599  *     enter: function(element, doneFn) {
600  *       var height = element[0].offsetHeight;
601  *       return $animateCss(element, {
602  *         addClass: 'red large-text pulse-twice',
603  *         easing: 'ease-out',
604  *         from: { height:'0px' },
605  *         to: { height:height + 'px' },
606  *         duration: 1 // one second
607  *       });
608  *     }
609  *   }
610  * }]);
611  * ```
612  *
613  * Since we're adding/removing CSS classes then the CSS transition will also pick those up:
614  *
615  * ```css
616  * /&#42; since a hardcoded duration value of 1 was provided in the JavaScript animation code,
617  * the CSS classes below will be transitioned despite them being defined as regular CSS classes &#42;/
618  * .red { background:red; }
619  * .large-text { font-size:20px; }
620  *
621  * /&#42; we can also use a keyframe animation and $animateCss will make it work alongside the transition &#42;/
622  * .pulse-twice {
623  *   animation: 0.5s pulse linear 2;
624  *   -webkit-animation: 0.5s pulse linear 2;
625  * }
626  *
627  * @keyframes pulse {
628  *   from { transform: scale(0.5); }
629  *   to { transform: scale(1.5); }
630  * }
631  *
632  * @-webkit-keyframes pulse {
633  *   from { -webkit-transform: scale(0.5); }
634  *   to { -webkit-transform: scale(1.5); }
635  * }
636  * ```
637  *
638  * Given this complex combination of CSS classes, styles and options, `$animateCss` will figure everything out and make the animation happen.
639  *
640  * ## How the Options are handled
641  *
642  * `$animateCss` is very versatile and intelligent when it comes to figuring out what configurations to apply to the element to ensure the animation
643  * works with the options provided. Say for example we were adding a class that contained a keyframe value and we wanted to also animate some inline
644  * styles using the `from` and `to` properties.
645  *
646  * ```js
647  * var animator = $animateCss(element, {
648  *   from: { background:'red' },
649  *   to: { background:'blue' }
650  * });
651  * animator.start();
652  * ```
653  *
654  * ```css
655  * .rotating-animation {
656  *   animation:0.5s rotate linear;
657  *   -webkit-animation:0.5s rotate linear;
658  * }
659  *
660  * @keyframes rotate {
661  *   from { transform: rotate(0deg); }
662  *   to { transform: rotate(360deg); }
663  * }
664  *
665  * @-webkit-keyframes rotate {
666  *   from { -webkit-transform: rotate(0deg); }
667  *   to { -webkit-transform: rotate(360deg); }
668  * }
669  * ```
670  *
671  * The missing pieces here are that we do not have a transition set (within the CSS code nor within the `$animateCss` options) and the duration of the animation is
672  * going to be detected from what the keyframe styles on the CSS class are. In this event, `$animateCss` will automatically create an inline transition
673  * style matching the duration detected from the keyframe style (which is present in the CSS class that is being added) and then prepare both the transition
674  * and keyframe animations to run in parallel on the element. Then when the animation is underway the provided `from` and `to` CSS styles will be applied
675  * and spread across the transition and keyframe animation.
676  *
677  * ## What is returned
678  *
679  * `$animateCss` works in two stages: a preparation phase and an animation phase. Therefore when `$animateCss` is first called it will NOT actually
680  * start the animation. All that is going on here is that the element is being prepared for the animation (which means that the generated CSS classes are
681  * added and removed on the element). Once `$animateCss` is called it will return an object with the following properties:
682  *
683  * ```js
684  * var animator = $animateCss(element, { ... });
685  * ```
686  *
687  * Now what do the contents of our `animator` variable look like:
688  *
689  * ```js
690  * {
691  *   // starts the animation
692  *   start: Function,
693  *
694  *   // ends (aborts) the animation
695  *   end: Function
696  * }
697  * ```
698  *
699  * To actually start the animation we need to run `animation.start()` which will then return a promise that we can hook into to detect when the animation ends.
700  * If we choose not to run the animation then we MUST run `animation.end()` to perform a cleanup on the element (since some CSS classes and styles may have been
701  * applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties
702  * and that changing them will not reconfigure the parameters of the animation.
703  *
704  * ### runner.done() vs runner.then()
705  * It is documented that `animation.start()` will return a promise object and this is true, however, there is also an additional method available on the
706  * runner called `.done(callbackFn)`. The done method works the same as `.finally(callbackFn)`, however, it does **not trigger a digest to occur**.
707  * Therefore, for performance reasons, it's always best to use `runner.done(callback)` instead of `runner.then()`, `runner.catch()` or `runner.finally()`
708  * unless you really need a digest to kick off afterwards.
709  *
710  * Keep in mind that, to make this easier, ngAnimate has tweaked the JS animations API to recognize when a runner instance is returned from $animateCss
711  * (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code).
712  * Check the {@link ngAnimate.$animateCss#usage animation code above} to see how this works.
713  *
714  * @param {DOMElement} element the element that will be animated
715  * @param {object} options the animation-related options that will be applied during the animation
716  *
717  * * `event` - The DOM event (e.g. enter, leave, move). When used, a generated CSS class of `ng-EVENT` and `ng-EVENT-active` will be applied
718  * to the element during the animation. Multiple events can be provided when spaces are used as a separator. (Note that this will not perform any DOM operation.)
719  * * `structural` - Indicates that the `ng-` prefix will be added to the event class. Setting to `false` or omitting will turn `ng-EVENT` and
720  * `ng-EVENT-active` in `EVENT` and `EVENT-active`. Unused if `event` is omitted.
721  * * `easing` - The CSS easing value that will be applied to the transition or keyframe animation (or both).
722  * * `transitionStyle` - The raw CSS transition style that will be used (e.g. `1s linear all`).
723  * * `keyframeStyle` - The raw CSS keyframe animation style that will be used (e.g. `1s my_animation linear`).
724  * * `from` - The starting CSS styles (a key/value object) that will be applied at the start of the animation.
725  * * `to` - The ending CSS styles (a key/value object) that will be applied across the animation via a CSS transition.
726  * * `addClass` - A space separated list of CSS classes that will be added to the element and spread across the animation.
727  * * `removeClass` - A space separated list of CSS classes that will be removed from the element and spread across the animation.
728  * * `duration` - A number value representing the total duration of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `0`
729  * is provided then the animation will be skipped entirely.
730  * * `delay` - A number value representing the total delay of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `true` is
731  * used then whatever delay value is detected from the CSS classes will be mirrored on the elements styles (e.g. by setting delay true then the style value
732  * of the element will be `transition-delay: DETECTED_VALUE`). Using `true` is useful when you want the CSS classes and inline styles to all share the same
733  * CSS delay value.
734  * * `stagger` - A numeric time value representing the delay between successively animated elements
735  * ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.})
736  * * `staggerIndex` - The numeric index representing the stagger item (e.g. a value of 5 is equal to the sixth item in the stagger; therefore when a
737  *   `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
738  * * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occurring on the classes being added and removed.)
739  * * `cleanupStyles` - Whether or not the provided `from` and `to` styles will be removed once
740  *    the animation is closed. This is useful for when the styles are used purely for the sake of
741  *    the animation and do not have a lasting visual effect on the element (e.g. a collapse and open animation).
742  *    By default this value is set to `false`.
743  *
744  * @return {object} an object with start and end methods and details about the animation.
745  *
746  * * `start` - The method to start the animation. This will return a `Promise` when called.
747  * * `end` - This method will cancel the animation and remove all applied CSS classes and styles.
748  */
749 var ONE_SECOND = 1000;
750 var BASE_TEN = 10;
751
752 var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
753 var CLOSING_TIME_BUFFER = 1.5;
754
755 var DETECT_CSS_PROPERTIES = {
756   transitionDuration:      TRANSITION_DURATION_PROP,
757   transitionDelay:         TRANSITION_DELAY_PROP,
758   transitionProperty:      TRANSITION_PROP + PROPERTY_KEY,
759   animationDuration:       ANIMATION_DURATION_PROP,
760   animationDelay:          ANIMATION_DELAY_PROP,
761   animationIterationCount: ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY
762 };
763
764 var DETECT_STAGGER_CSS_PROPERTIES = {
765   transitionDuration:      TRANSITION_DURATION_PROP,
766   transitionDelay:         TRANSITION_DELAY_PROP,
767   animationDuration:       ANIMATION_DURATION_PROP,
768   animationDelay:          ANIMATION_DELAY_PROP
769 };
770
771 function getCssKeyframeDurationStyle(duration) {
772   return [ANIMATION_DURATION_PROP, duration + 's'];
773 }
774
775 function getCssDelayStyle(delay, isKeyframeAnimation) {
776   var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP;
777   return [prop, delay + 's'];
778 }
779
780 function computeCssStyles($window, element, properties) {
781   var styles = Object.create(null);
782   var detectedStyles = $window.getComputedStyle(element) || {};
783   forEach(properties, function(formalStyleName, actualStyleName) {
784     var val = detectedStyles[formalStyleName];
785     if (val) {
786       var c = val.charAt(0);
787
788       // only numerical-based values have a negative sign or digit as the first value
789       if (c === '-' || c === '+' || c >= 0) {
790         val = parseMaxTime(val);
791       }
792
793       // by setting this to null in the event that the delay is not set or is set directly as 0
794       // then we can still allow for negative values to be used later on and not mistake this
795       // value for being greater than any other negative value.
796       if (val === 0) {
797         val = null;
798       }
799       styles[actualStyleName] = val;
800     }
801   });
802
803   return styles;
804 }
805
806 function parseMaxTime(str) {
807   var maxValue = 0;
808   var values = str.split(/\s*,\s*/);
809   forEach(values, function(value) {
810     // it's always safe to consider only second values and omit `ms` values since
811     // getComputedStyle will always handle the conversion for us
812     if (value.charAt(value.length - 1) == 's') {
813       value = value.substring(0, value.length - 1);
814     }
815     value = parseFloat(value) || 0;
816     maxValue = maxValue ? Math.max(value, maxValue) : value;
817   });
818   return maxValue;
819 }
820
821 function truthyTimingValue(val) {
822   return val === 0 || val != null;
823 }
824
825 function getCssTransitionDurationStyle(duration, applyOnlyDuration) {
826   var style = TRANSITION_PROP;
827   var value = duration + 's';
828   if (applyOnlyDuration) {
829     style += DURATION_KEY;
830   } else {
831     value += ' linear all';
832   }
833   return [style, value];
834 }
835
836 function createLocalCacheLookup() {
837   var cache = Object.create(null);
838   return {
839     flush: function() {
840       cache = Object.create(null);
841     },
842
843     count: function(key) {
844       var entry = cache[key];
845       return entry ? entry.total : 0;
846     },
847
848     get: function(key) {
849       var entry = cache[key];
850       return entry && entry.value;
851     },
852
853     put: function(key, value) {
854       if (!cache[key]) {
855         cache[key] = { total: 1, value: value };
856       } else {
857         cache[key].total++;
858       }
859     }
860   };
861 }
862
863 // we do not reassign an already present style value since
864 // if we detect the style property value again we may be
865 // detecting styles that were added via the `from` styles.
866 // We make use of `isDefined` here since an empty string
867 // or null value (which is what getPropertyValue will return
868 // for a non-existing style) will still be marked as a valid
869 // value for the style (a falsy value implies that the style
870 // is to be removed at the end of the animation). If we had a simple
871 // "OR" statement then it would not be enough to catch that.
872 function registerRestorableStyles(backup, node, properties) {
873   forEach(properties, function(prop) {
874     backup[prop] = isDefined(backup[prop])
875         ? backup[prop]
876         : node.style.getPropertyValue(prop);
877   });
878 }
879
880 var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
881   var gcsLookup = createLocalCacheLookup();
882   var gcsStaggerLookup = createLocalCacheLookup();
883
884   this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
885                '$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue',
886        function($window,   $$jqLite,   $$AnimateRunner,   $timeout,
887                 $$forceReflow,   $sniffer,   $$rAFScheduler, $$animateQueue) {
888
889     var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
890
891     var parentCounter = 0;
892     function gcsHashFn(node, extraClasses) {
893       var KEY = "$$ngAnimateParentKey";
894       var parentNode = node.parentNode;
895       var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter);
896       return parentID + '-' + node.getAttribute('class') + '-' + extraClasses;
897     }
898
899     function computeCachedCssStyles(node, className, cacheKey, properties) {
900       var timings = gcsLookup.get(cacheKey);
901
902       if (!timings) {
903         timings = computeCssStyles($window, node, properties);
904         if (timings.animationIterationCount === 'infinite') {
905           timings.animationIterationCount = 1;
906         }
907       }
908
909       // we keep putting this in multiple times even though the value and the cacheKey are the same
910       // because we're keeping an internal tally of how many duplicate animations are detected.
911       gcsLookup.put(cacheKey, timings);
912       return timings;
913     }
914
915     function computeCachedCssStaggerStyles(node, className, cacheKey, properties) {
916       var stagger;
917
918       // if we have one or more existing matches of matching elements
919       // containing the same parent + CSS styles (which is how cacheKey works)
920       // then staggering is possible
921       if (gcsLookup.count(cacheKey) > 0) {
922         stagger = gcsStaggerLookup.get(cacheKey);
923
924         if (!stagger) {
925           var staggerClassName = pendClasses(className, '-stagger');
926
927           $$jqLite.addClass(node, staggerClassName);
928
929           stagger = computeCssStyles($window, node, properties);
930
931           // force the conversion of a null value to zero incase not set
932           stagger.animationDuration = Math.max(stagger.animationDuration, 0);
933           stagger.transitionDuration = Math.max(stagger.transitionDuration, 0);
934
935           $$jqLite.removeClass(node, staggerClassName);
936
937           gcsStaggerLookup.put(cacheKey, stagger);
938         }
939       }
940
941       return stagger || {};
942     }
943
944     var cancelLastRAFRequest;
945     var rafWaitQueue = [];
946     function waitUntilQuiet(callback) {
947       rafWaitQueue.push(callback);
948       $$rAFScheduler.waitUntilQuiet(function() {
949         gcsLookup.flush();
950         gcsStaggerLookup.flush();
951
952         // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable.
953         // PLEASE EXAMINE THE `$$forceReflow` service to understand why.
954         var pageWidth = $$forceReflow();
955
956         // we use a for loop to ensure that if the queue is changed
957         // during this looping then it will consider new requests
958         for (var i = 0; i < rafWaitQueue.length; i++) {
959           rafWaitQueue[i](pageWidth);
960         }
961         rafWaitQueue.length = 0;
962       });
963     }
964
965     function computeTimings(node, className, cacheKey) {
966       var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES);
967       var aD = timings.animationDelay;
968       var tD = timings.transitionDelay;
969       timings.maxDelay = aD && tD
970           ? Math.max(aD, tD)
971           : (aD || tD);
972       timings.maxDuration = Math.max(
973           timings.animationDuration * timings.animationIterationCount,
974           timings.transitionDuration);
975
976       return timings;
977     }
978
979     return function init(element, initialOptions) {
980       // all of the animation functions should create
981       // a copy of the options data, however, if a
982       // parent service has already created a copy then
983       // we should stick to using that
984       var options = initialOptions || {};
985       if (!options.$$prepared) {
986         options = prepareAnimationOptions(copy(options));
987       }
988
989       var restoreStyles = {};
990       var node = getDomNode(element);
991       if (!node
992           || !node.parentNode
993           || !$$animateQueue.enabled()) {
994         return closeAndReturnNoopAnimator();
995       }
996
997       var temporaryStyles = [];
998       var classes = element.attr('class');
999       var styles = packageStyles(options);
1000       var animationClosed;
1001       var animationPaused;
1002       var animationCompleted;
1003       var runner;
1004       var runnerHost;
1005       var maxDelay;
1006       var maxDelayTime;
1007       var maxDuration;
1008       var maxDurationTime;
1009       var startTime;
1010       var events = [];
1011
1012       if (options.duration === 0 || (!$sniffer.animations && !$sniffer.transitions)) {
1013         return closeAndReturnNoopAnimator();
1014       }
1015
1016       var method = options.event && isArray(options.event)
1017             ? options.event.join(' ')
1018             : options.event;
1019
1020       var isStructural = method && options.structural;
1021       var structuralClassName = '';
1022       var addRemoveClassName = '';
1023
1024       if (isStructural) {
1025         structuralClassName = pendClasses(method, EVENT_CLASS_PREFIX, true);
1026       } else if (method) {
1027         structuralClassName = method;
1028       }
1029
1030       if (options.addClass) {
1031         addRemoveClassName += pendClasses(options.addClass, ADD_CLASS_SUFFIX);
1032       }
1033
1034       if (options.removeClass) {
1035         if (addRemoveClassName.length) {
1036           addRemoveClassName += ' ';
1037         }
1038         addRemoveClassName += pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX);
1039       }
1040
1041       // there may be a situation where a structural animation is combined together
1042       // with CSS classes that need to resolve before the animation is computed.
1043       // However this means that there is no explicit CSS code to block the animation
1044       // from happening (by setting 0s none in the class name). If this is the case
1045       // we need to apply the classes before the first rAF so we know to continue if
1046       // there actually is a detected transition or keyframe animation
1047       if (options.applyClassesEarly && addRemoveClassName.length) {
1048         applyAnimationClasses(element, options);
1049       }
1050
1051       var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
1052       var fullClassName = classes + ' ' + preparationClasses;
1053       var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX);
1054       var hasToStyles = styles.to && Object.keys(styles.to).length > 0;
1055       var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0;
1056
1057       // there is no way we can trigger an animation if no styles and
1058       // no classes are being applied which would then trigger a transition,
1059       // unless there a is raw keyframe value that is applied to the element.
1060       if (!containsKeyframeAnimation
1061            && !hasToStyles
1062            && !preparationClasses) {
1063         return closeAndReturnNoopAnimator();
1064       }
1065
1066       var cacheKey, stagger;
1067       if (options.stagger > 0) {
1068         var staggerVal = parseFloat(options.stagger);
1069         stagger = {
1070           transitionDelay: staggerVal,
1071           animationDelay: staggerVal,
1072           transitionDuration: 0,
1073           animationDuration: 0
1074         };
1075       } else {
1076         cacheKey = gcsHashFn(node, fullClassName);
1077         stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES);
1078       }
1079
1080       if (!options.$$skipPreparationClasses) {
1081         $$jqLite.addClass(element, preparationClasses);
1082       }
1083
1084       var applyOnlyDuration;
1085
1086       if (options.transitionStyle) {
1087         var transitionStyle = [TRANSITION_PROP, options.transitionStyle];
1088         applyInlineStyle(node, transitionStyle);
1089         temporaryStyles.push(transitionStyle);
1090       }
1091
1092       if (options.duration >= 0) {
1093         applyOnlyDuration = node.style[TRANSITION_PROP].length > 0;
1094         var durationStyle = getCssTransitionDurationStyle(options.duration, applyOnlyDuration);
1095
1096         // we set the duration so that it will be picked up by getComputedStyle later
1097         applyInlineStyle(node, durationStyle);
1098         temporaryStyles.push(durationStyle);
1099       }
1100
1101       if (options.keyframeStyle) {
1102         var keyframeStyle = [ANIMATION_PROP, options.keyframeStyle];
1103         applyInlineStyle(node, keyframeStyle);
1104         temporaryStyles.push(keyframeStyle);
1105       }
1106
1107       var itemIndex = stagger
1108           ? options.staggerIndex >= 0
1109               ? options.staggerIndex
1110               : gcsLookup.count(cacheKey)
1111           : 0;
1112
1113       var isFirst = itemIndex === 0;
1114
1115       // this is a pre-emptive way of forcing the setup classes to be added and applied INSTANTLY
1116       // without causing any combination of transitions to kick in. By adding a negative delay value
1117       // it forces the setup class' transition to end immediately. We later then remove the negative
1118       // transition delay to allow for the transition to naturally do it's thing. The beauty here is
1119       // that if there is no transition defined then nothing will happen and this will also allow
1120       // other transitions to be stacked on top of each other without any chopping them out.
1121       if (isFirst && !options.skipBlocking) {
1122         blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
1123       }
1124
1125       var timings = computeTimings(node, fullClassName, cacheKey);
1126       var relativeDelay = timings.maxDelay;
1127       maxDelay = Math.max(relativeDelay, 0);
1128       maxDuration = timings.maxDuration;
1129
1130       var flags = {};
1131       flags.hasTransitions          = timings.transitionDuration > 0;
1132       flags.hasAnimations           = timings.animationDuration > 0;
1133       flags.hasTransitionAll        = flags.hasTransitions && timings.transitionProperty == 'all';
1134       flags.applyTransitionDuration = hasToStyles && (
1135                                         (flags.hasTransitions && !flags.hasTransitionAll)
1136                                          || (flags.hasAnimations && !flags.hasTransitions));
1137       flags.applyAnimationDuration  = options.duration && flags.hasAnimations;
1138       flags.applyTransitionDelay    = truthyTimingValue(options.delay) && (flags.applyTransitionDuration || flags.hasTransitions);
1139       flags.applyAnimationDelay     = truthyTimingValue(options.delay) && flags.hasAnimations;
1140       flags.recalculateTimingStyles = addRemoveClassName.length > 0;
1141
1142       if (flags.applyTransitionDuration || flags.applyAnimationDuration) {
1143         maxDuration = options.duration ? parseFloat(options.duration) : maxDuration;
1144
1145         if (flags.applyTransitionDuration) {
1146           flags.hasTransitions = true;
1147           timings.transitionDuration = maxDuration;
1148           applyOnlyDuration = node.style[TRANSITION_PROP + PROPERTY_KEY].length > 0;
1149           temporaryStyles.push(getCssTransitionDurationStyle(maxDuration, applyOnlyDuration));
1150         }
1151
1152         if (flags.applyAnimationDuration) {
1153           flags.hasAnimations = true;
1154           timings.animationDuration = maxDuration;
1155           temporaryStyles.push(getCssKeyframeDurationStyle(maxDuration));
1156         }
1157       }
1158
1159       if (maxDuration === 0 && !flags.recalculateTimingStyles) {
1160         return closeAndReturnNoopAnimator();
1161       }
1162
1163       if (options.delay != null) {
1164         var delayStyle;
1165         if (typeof options.delay !== "boolean") {
1166           delayStyle = parseFloat(options.delay);
1167           // number in options.delay means we have to recalculate the delay for the closing timeout
1168           maxDelay = Math.max(delayStyle, 0);
1169         }
1170
1171         if (flags.applyTransitionDelay) {
1172           temporaryStyles.push(getCssDelayStyle(delayStyle));
1173         }
1174
1175         if (flags.applyAnimationDelay) {
1176           temporaryStyles.push(getCssDelayStyle(delayStyle, true));
1177         }
1178       }
1179
1180       // we need to recalculate the delay value since we used a pre-emptive negative
1181       // delay value and the delay value is required for the final event checking. This
1182       // property will ensure that this will happen after the RAF phase has passed.
1183       if (options.duration == null && timings.transitionDuration > 0) {
1184         flags.recalculateTimingStyles = flags.recalculateTimingStyles || isFirst;
1185       }
1186
1187       maxDelayTime = maxDelay * ONE_SECOND;
1188       maxDurationTime = maxDuration * ONE_SECOND;
1189       if (!options.skipBlocking) {
1190         flags.blockTransition = timings.transitionDuration > 0;
1191         flags.blockKeyframeAnimation = timings.animationDuration > 0 &&
1192                                        stagger.animationDelay > 0 &&
1193                                        stagger.animationDuration === 0;
1194       }
1195
1196       if (options.from) {
1197         if (options.cleanupStyles) {
1198           registerRestorableStyles(restoreStyles, node, Object.keys(options.from));
1199         }
1200         applyAnimationFromStyles(element, options);
1201       }
1202
1203       if (flags.blockTransition || flags.blockKeyframeAnimation) {
1204         applyBlocking(maxDuration);
1205       } else if (!options.skipBlocking) {
1206         blockTransitions(node, false);
1207       }
1208
1209       // TODO(matsko): for 1.5 change this code to have an animator object for better debugging
1210       return {
1211         $$willAnimate: true,
1212         end: endFn,
1213         start: function() {
1214           if (animationClosed) return;
1215
1216           runnerHost = {
1217             end: endFn,
1218             cancel: cancelFn,
1219             resume: null, //this will be set during the start() phase
1220             pause: null
1221           };
1222
1223           runner = new $$AnimateRunner(runnerHost);
1224
1225           waitUntilQuiet(start);
1226
1227           // we don't have access to pause/resume the animation
1228           // since it hasn't run yet. AnimateRunner will therefore
1229           // set noop functions for resume and pause and they will
1230           // later be overridden once the animation is triggered
1231           return runner;
1232         }
1233       };
1234
1235       function endFn() {
1236         close();
1237       }
1238
1239       function cancelFn() {
1240         close(true);
1241       }
1242
1243       function close(rejected) { // jshint ignore:line
1244         // if the promise has been called already then we shouldn't close
1245         // the animation again
1246         if (animationClosed || (animationCompleted && animationPaused)) return;
1247         animationClosed = true;
1248         animationPaused = false;
1249
1250         if (!options.$$skipPreparationClasses) {
1251           $$jqLite.removeClass(element, preparationClasses);
1252         }
1253         $$jqLite.removeClass(element, activeClasses);
1254
1255         blockKeyframeAnimations(node, false);
1256         blockTransitions(node, false);
1257
1258         forEach(temporaryStyles, function(entry) {
1259           // There is only one way to remove inline style properties entirely from elements.
1260           // By using `removeProperty` this works, but we need to convert camel-cased CSS
1261           // styles down to hyphenated values.
1262           node.style[entry[0]] = '';
1263         });
1264
1265         applyAnimationClasses(element, options);
1266         applyAnimationStyles(element, options);
1267
1268         if (Object.keys(restoreStyles).length) {
1269           forEach(restoreStyles, function(value, prop) {
1270             value ? node.style.setProperty(prop, value)
1271                   : node.style.removeProperty(prop);
1272           });
1273         }
1274
1275         // the reason why we have this option is to allow a synchronous closing callback
1276         // that is fired as SOON as the animation ends (when the CSS is removed) or if
1277         // the animation never takes off at all. A good example is a leave animation since
1278         // the element must be removed just after the animation is over or else the element
1279         // will appear on screen for one animation frame causing an overbearing flicker.
1280         if (options.onDone) {
1281           options.onDone();
1282         }
1283
1284         if (events && events.length) {
1285           // Remove the transitionend / animationend listener(s)
1286           element.off(events.join(' '), onAnimationProgress);
1287         }
1288
1289         //Cancel the fallback closing timeout and remove the timer data
1290         var animationTimerData = element.data(ANIMATE_TIMER_KEY);
1291         if (animationTimerData) {
1292           $timeout.cancel(animationTimerData[0].timer);
1293           element.removeData(ANIMATE_TIMER_KEY);
1294         }
1295
1296         // if the preparation function fails then the promise is not setup
1297         if (runner) {
1298           runner.complete(!rejected);
1299         }
1300       }
1301
1302       function applyBlocking(duration) {
1303         if (flags.blockTransition) {
1304           blockTransitions(node, duration);
1305         }
1306
1307         if (flags.blockKeyframeAnimation) {
1308           blockKeyframeAnimations(node, !!duration);
1309         }
1310       }
1311
1312       function closeAndReturnNoopAnimator() {
1313         runner = new $$AnimateRunner({
1314           end: endFn,
1315           cancel: cancelFn
1316         });
1317
1318         // should flush the cache animation
1319         waitUntilQuiet(noop);
1320         close();
1321
1322         return {
1323           $$willAnimate: false,
1324           start: function() {
1325             return runner;
1326           },
1327           end: endFn
1328         };
1329       }
1330
1331       function onAnimationProgress(event) {
1332         event.stopPropagation();
1333         var ev = event.originalEvent || event;
1334
1335         // we now always use `Date.now()` due to the recent changes with
1336         // event.timeStamp in Firefox, Webkit and Chrome (see #13494 for more info)
1337         var timeStamp = ev.$manualTimeStamp || Date.now();
1338
1339         /* Firefox (or possibly just Gecko) likes to not round values up
1340          * when a ms measurement is used for the animation */
1341         var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
1342
1343         /* $manualTimeStamp is a mocked timeStamp value which is set
1344          * within browserTrigger(). This is only here so that tests can
1345          * mock animations properly. Real events fallback to event.timeStamp,
1346          * or, if they don't, then a timeStamp is automatically created for them.
1347          * We're checking to see if the timeStamp surpasses the expected delay,
1348          * but we're using elapsedTime instead of the timeStamp on the 2nd
1349          * pre-condition since animationPauseds sometimes close off early */
1350         if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
1351           // we set this flag to ensure that if the transition is paused then, when resumed,
1352           // the animation will automatically close itself since transitions cannot be paused.
1353           animationCompleted = true;
1354           close();
1355         }
1356       }
1357
1358       function start() {
1359         if (animationClosed) return;
1360         if (!node.parentNode) {
1361           close();
1362           return;
1363         }
1364
1365         // even though we only pause keyframe animations here the pause flag
1366         // will still happen when transitions are used. Only the transition will
1367         // not be paused since that is not possible. If the animation ends when
1368         // paused then it will not complete until unpaused or cancelled.
1369         var playPause = function(playAnimation) {
1370           if (!animationCompleted) {
1371             animationPaused = !playAnimation;
1372             if (timings.animationDuration) {
1373               var value = blockKeyframeAnimations(node, animationPaused);
1374               animationPaused
1375                   ? temporaryStyles.push(value)
1376                   : removeFromArray(temporaryStyles, value);
1377             }
1378           } else if (animationPaused && playAnimation) {
1379             animationPaused = false;
1380             close();
1381           }
1382         };
1383
1384         // checking the stagger duration prevents an accidentally cascade of the CSS delay style
1385         // being inherited from the parent. If the transition duration is zero then we can safely
1386         // rely that the delay value is an intentional stagger delay style.
1387         var maxStagger = itemIndex > 0
1388                          && ((timings.transitionDuration && stagger.transitionDuration === 0) ||
1389                             (timings.animationDuration && stagger.animationDuration === 0))
1390                          && Math.max(stagger.animationDelay, stagger.transitionDelay);
1391         if (maxStagger) {
1392           $timeout(triggerAnimationStart,
1393                    Math.floor(maxStagger * itemIndex * ONE_SECOND),
1394                    false);
1395         } else {
1396           triggerAnimationStart();
1397         }
1398
1399         // this will decorate the existing promise runner with pause/resume methods
1400         runnerHost.resume = function() {
1401           playPause(true);
1402         };
1403
1404         runnerHost.pause = function() {
1405           playPause(false);
1406         };
1407
1408         function triggerAnimationStart() {
1409           // just incase a stagger animation kicks in when the animation
1410           // itself was cancelled entirely
1411           if (animationClosed) return;
1412
1413           applyBlocking(false);
1414
1415           forEach(temporaryStyles, function(entry) {
1416             var key = entry[0];
1417             var value = entry[1];
1418             node.style[key] = value;
1419           });
1420
1421           applyAnimationClasses(element, options);
1422           $$jqLite.addClass(element, activeClasses);
1423
1424           if (flags.recalculateTimingStyles) {
1425             fullClassName = node.className + ' ' + preparationClasses;
1426             cacheKey = gcsHashFn(node, fullClassName);
1427
1428             timings = computeTimings(node, fullClassName, cacheKey);
1429             relativeDelay = timings.maxDelay;
1430             maxDelay = Math.max(relativeDelay, 0);
1431             maxDuration = timings.maxDuration;
1432
1433             if (maxDuration === 0) {
1434               close();
1435               return;
1436             }
1437
1438             flags.hasTransitions = timings.transitionDuration > 0;
1439             flags.hasAnimations = timings.animationDuration > 0;
1440           }
1441
1442           if (flags.applyAnimationDelay) {
1443             relativeDelay = typeof options.delay !== "boolean" && truthyTimingValue(options.delay)
1444                   ? parseFloat(options.delay)
1445                   : relativeDelay;
1446
1447             maxDelay = Math.max(relativeDelay, 0);
1448             timings.animationDelay = relativeDelay;
1449             delayStyle = getCssDelayStyle(relativeDelay, true);
1450             temporaryStyles.push(delayStyle);
1451             node.style[delayStyle[0]] = delayStyle[1];
1452           }
1453
1454           maxDelayTime = maxDelay * ONE_SECOND;
1455           maxDurationTime = maxDuration * ONE_SECOND;
1456
1457           if (options.easing) {
1458             var easeProp, easeVal = options.easing;
1459             if (flags.hasTransitions) {
1460               easeProp = TRANSITION_PROP + TIMING_KEY;
1461               temporaryStyles.push([easeProp, easeVal]);
1462               node.style[easeProp] = easeVal;
1463             }
1464             if (flags.hasAnimations) {
1465               easeProp = ANIMATION_PROP + TIMING_KEY;
1466               temporaryStyles.push([easeProp, easeVal]);
1467               node.style[easeProp] = easeVal;
1468             }
1469           }
1470
1471           if (timings.transitionDuration) {
1472             events.push(TRANSITIONEND_EVENT);
1473           }
1474
1475           if (timings.animationDuration) {
1476             events.push(ANIMATIONEND_EVENT);
1477           }
1478
1479           startTime = Date.now();
1480           var timerTime = maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime;
1481           var endTime = startTime + timerTime;
1482
1483           var animationsData = element.data(ANIMATE_TIMER_KEY) || [];
1484           var setupFallbackTimer = true;
1485           if (animationsData.length) {
1486             var currentTimerData = animationsData[0];
1487             setupFallbackTimer = endTime > currentTimerData.expectedEndTime;
1488             if (setupFallbackTimer) {
1489               $timeout.cancel(currentTimerData.timer);
1490             } else {
1491               animationsData.push(close);
1492             }
1493           }
1494
1495           if (setupFallbackTimer) {
1496             var timer = $timeout(onAnimationExpired, timerTime, false);
1497             animationsData[0] = {
1498               timer: timer,
1499               expectedEndTime: endTime
1500             };
1501             animationsData.push(close);
1502             element.data(ANIMATE_TIMER_KEY, animationsData);
1503           }
1504
1505           if (events.length) {
1506             element.on(events.join(' '), onAnimationProgress);
1507           }
1508
1509           if (options.to) {
1510             if (options.cleanupStyles) {
1511               registerRestorableStyles(restoreStyles, node, Object.keys(options.to));
1512             }
1513             applyAnimationToStyles(element, options);
1514           }
1515         }
1516
1517         function onAnimationExpired() {
1518           var animationsData = element.data(ANIMATE_TIMER_KEY);
1519
1520           // this will be false in the event that the element was
1521           // removed from the DOM (via a leave animation or something
1522           // similar)
1523           if (animationsData) {
1524             for (var i = 1; i < animationsData.length; i++) {
1525               animationsData[i]();
1526             }
1527             element.removeData(ANIMATE_TIMER_KEY);
1528           }
1529         }
1530       }
1531     };
1532   }];
1533 }];
1534
1535 var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationProvider) {
1536   $$animationProvider.drivers.push('$$animateCssDriver');
1537
1538   var NG_ANIMATE_SHIM_CLASS_NAME = 'ng-animate-shim';
1539   var NG_ANIMATE_ANCHOR_CLASS_NAME = 'ng-anchor';
1540
1541   var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out';
1542   var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in';
1543
1544   function isDocumentFragment(node) {
1545     return node.parentNode && node.parentNode.nodeType === 11;
1546   }
1547
1548   this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$sniffer', '$$jqLite', '$document',
1549        function($animateCss,   $rootScope,   $$AnimateRunner,   $rootElement,   $sniffer,   $$jqLite,   $document) {
1550
1551     // only browsers that support these properties can render animations
1552     if (!$sniffer.animations && !$sniffer.transitions) return noop;
1553
1554     var bodyNode = $document[0].body;
1555     var rootNode = getDomNode($rootElement);
1556
1557     var rootBodyElement = jqLite(
1558       // this is to avoid using something that exists outside of the body
1559       // we also special case the doc fragment case because our unit test code
1560       // appends the $rootElement to the body after the app has been bootstrapped
1561       isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode
1562     );
1563
1564     var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
1565
1566     return function initDriverFn(animationDetails) {
1567       return animationDetails.from && animationDetails.to
1568           ? prepareFromToAnchorAnimation(animationDetails.from,
1569                                          animationDetails.to,
1570                                          animationDetails.classes,
1571                                          animationDetails.anchors)
1572           : prepareRegularAnimation(animationDetails);
1573     };
1574
1575     function filterCssClasses(classes) {
1576       //remove all the `ng-` stuff
1577       return classes.replace(/\bng-\S+\b/g, '');
1578     }
1579
1580     function getUniqueValues(a, b) {
1581       if (isString(a)) a = a.split(' ');
1582       if (isString(b)) b = b.split(' ');
1583       return a.filter(function(val) {
1584         return b.indexOf(val) === -1;
1585       }).join(' ');
1586     }
1587
1588     function prepareAnchoredAnimation(classes, outAnchor, inAnchor) {
1589       var clone = jqLite(getDomNode(outAnchor).cloneNode(true));
1590       var startingClasses = filterCssClasses(getClassVal(clone));
1591
1592       outAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);
1593       inAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);
1594
1595       clone.addClass(NG_ANIMATE_ANCHOR_CLASS_NAME);
1596
1597       rootBodyElement.append(clone);
1598
1599       var animatorIn, animatorOut = prepareOutAnimation();
1600
1601       // the user may not end up using the `out` animation and
1602       // only making use of the `in` animation or vice-versa.
1603       // In either case we should allow this and not assume the
1604       // animation is over unless both animations are not used.
1605       if (!animatorOut) {
1606         animatorIn = prepareInAnimation();
1607         if (!animatorIn) {
1608           return end();
1609         }
1610       }
1611
1612       var startingAnimator = animatorOut || animatorIn;
1613
1614       return {
1615         start: function() {
1616           var runner;
1617
1618           var currentAnimation = startingAnimator.start();
1619           currentAnimation.done(function() {
1620             currentAnimation = null;
1621             if (!animatorIn) {
1622               animatorIn = prepareInAnimation();
1623               if (animatorIn) {
1624                 currentAnimation = animatorIn.start();
1625                 currentAnimation.done(function() {
1626                   currentAnimation = null;
1627                   end();
1628                   runner.complete();
1629                 });
1630                 return currentAnimation;
1631               }
1632             }
1633             // in the event that there is no `in` animation
1634             end();
1635             runner.complete();
1636           });
1637
1638           runner = new $$AnimateRunner({
1639             end: endFn,
1640             cancel: endFn
1641           });
1642
1643           return runner;
1644
1645           function endFn() {
1646             if (currentAnimation) {
1647               currentAnimation.end();
1648             }
1649           }
1650         }
1651       };
1652
1653       function calculateAnchorStyles(anchor) {
1654         var styles = {};
1655
1656         var coords = getDomNode(anchor).getBoundingClientRect();
1657
1658         // we iterate directly since safari messes up and doesn't return
1659         // all the keys for the coords object when iterated
1660         forEach(['width','height','top','left'], function(key) {
1661           var value = coords[key];
1662           switch (key) {
1663             case 'top':
1664               value += bodyNode.scrollTop;
1665               break;
1666             case 'left':
1667               value += bodyNode.scrollLeft;
1668               break;
1669           }
1670           styles[key] = Math.floor(value) + 'px';
1671         });
1672         return styles;
1673       }
1674
1675       function prepareOutAnimation() {
1676         var animator = $animateCss(clone, {
1677           addClass: NG_OUT_ANCHOR_CLASS_NAME,
1678           delay: true,
1679           from: calculateAnchorStyles(outAnchor)
1680         });
1681
1682         // read the comment within `prepareRegularAnimation` to understand
1683         // why this check is necessary
1684         return animator.$$willAnimate ? animator : null;
1685       }
1686
1687       function getClassVal(element) {
1688         return element.attr('class') || '';
1689       }
1690
1691       function prepareInAnimation() {
1692         var endingClasses = filterCssClasses(getClassVal(inAnchor));
1693         var toAdd = getUniqueValues(endingClasses, startingClasses);
1694         var toRemove = getUniqueValues(startingClasses, endingClasses);
1695
1696         var animator = $animateCss(clone, {
1697           to: calculateAnchorStyles(inAnchor),
1698           addClass: NG_IN_ANCHOR_CLASS_NAME + ' ' + toAdd,
1699           removeClass: NG_OUT_ANCHOR_CLASS_NAME + ' ' + toRemove,
1700           delay: true
1701         });
1702
1703         // read the comment within `prepareRegularAnimation` to understand
1704         // why this check is necessary
1705         return animator.$$willAnimate ? animator : null;
1706       }
1707
1708       function end() {
1709         clone.remove();
1710         outAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);
1711         inAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);
1712       }
1713     }
1714
1715     function prepareFromToAnchorAnimation(from, to, classes, anchors) {
1716       var fromAnimation = prepareRegularAnimation(from, noop);
1717       var toAnimation = prepareRegularAnimation(to, noop);
1718
1719       var anchorAnimations = [];
1720       forEach(anchors, function(anchor) {
1721         var outElement = anchor['out'];
1722         var inElement = anchor['in'];
1723         var animator = prepareAnchoredAnimation(classes, outElement, inElement);
1724         if (animator) {
1725           anchorAnimations.push(animator);
1726         }
1727       });
1728
1729       // no point in doing anything when there are no elements to animate
1730       if (!fromAnimation && !toAnimation && anchorAnimations.length === 0) return;
1731
1732       return {
1733         start: function() {
1734           var animationRunners = [];
1735
1736           if (fromAnimation) {
1737             animationRunners.push(fromAnimation.start());
1738           }
1739
1740           if (toAnimation) {
1741             animationRunners.push(toAnimation.start());
1742           }
1743
1744           forEach(anchorAnimations, function(animation) {
1745             animationRunners.push(animation.start());
1746           });
1747
1748           var runner = new $$AnimateRunner({
1749             end: endFn,
1750             cancel: endFn // CSS-driven animations cannot be cancelled, only ended
1751           });
1752
1753           $$AnimateRunner.all(animationRunners, function(status) {
1754             runner.complete(status);
1755           });
1756
1757           return runner;
1758
1759           function endFn() {
1760             forEach(animationRunners, function(runner) {
1761               runner.end();
1762             });
1763           }
1764         }
1765       };
1766     }
1767
1768     function prepareRegularAnimation(animationDetails) {
1769       var element = animationDetails.element;
1770       var options = animationDetails.options || {};
1771
1772       if (animationDetails.structural) {
1773         options.event = animationDetails.event;
1774         options.structural = true;
1775         options.applyClassesEarly = true;
1776
1777         // we special case the leave animation since we want to ensure that
1778         // the element is removed as soon as the animation is over. Otherwise
1779         // a flicker might appear or the element may not be removed at all
1780         if (animationDetails.event === 'leave') {
1781           options.onDone = options.domOperation;
1782         }
1783       }
1784
1785       // We assign the preparationClasses as the actual animation event since
1786       // the internals of $animateCss will just suffix the event token values
1787       // with `-active` to trigger the animation.
1788       if (options.preparationClasses) {
1789         options.event = concatWithSpace(options.event, options.preparationClasses);
1790       }
1791
1792       var animator = $animateCss(element, options);
1793
1794       // the driver lookup code inside of $$animation attempts to spawn a
1795       // driver one by one until a driver returns a.$$willAnimate animator object.
1796       // $animateCss will always return an object, however, it will pass in
1797       // a flag as a hint as to whether an animation was detected or not
1798       return animator.$$willAnimate ? animator : null;
1799     }
1800   }];
1801 }];
1802
1803 // TODO(matsko): use caching here to speed things up for detection
1804 // TODO(matsko): add documentation
1805 //  by the time...
1806
1807 var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
1808   this.$get = ['$injector', '$$AnimateRunner', '$$jqLite',
1809        function($injector,   $$AnimateRunner,   $$jqLite) {
1810
1811     var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
1812          // $animateJs(element, 'enter');
1813     return function(element, event, classes, options) {
1814       var animationClosed = false;
1815
1816       // the `classes` argument is optional and if it is not used
1817       // then the classes will be resolved from the element's className
1818       // property as well as options.addClass/options.removeClass.
1819       if (arguments.length === 3 && isObject(classes)) {
1820         options = classes;
1821         classes = null;
1822       }
1823
1824       options = prepareAnimationOptions(options);
1825       if (!classes) {
1826         classes = element.attr('class') || '';
1827         if (options.addClass) {
1828           classes += ' ' + options.addClass;
1829         }
1830         if (options.removeClass) {
1831           classes += ' ' + options.removeClass;
1832         }
1833       }
1834
1835       var classesToAdd = options.addClass;
1836       var classesToRemove = options.removeClass;
1837
1838       // the lookupAnimations function returns a series of animation objects that are
1839       // matched up with one or more of the CSS classes. These animation objects are
1840       // defined via the module.animation factory function. If nothing is detected then
1841       // we don't return anything which then makes $animation query the next driver.
1842       var animations = lookupAnimations(classes);
1843       var before, after;
1844       if (animations.length) {
1845         var afterFn, beforeFn;
1846         if (event == 'leave') {
1847           beforeFn = 'leave';
1848           afterFn = 'afterLeave'; // TODO(matsko): get rid of this
1849         } else {
1850           beforeFn = 'before' + event.charAt(0).toUpperCase() + event.substr(1);
1851           afterFn = event;
1852         }
1853
1854         if (event !== 'enter' && event !== 'move') {
1855           before = packageAnimations(element, event, options, animations, beforeFn);
1856         }
1857         after  = packageAnimations(element, event, options, animations, afterFn);
1858       }
1859
1860       // no matching animations
1861       if (!before && !after) return;
1862
1863       function applyOptions() {
1864         options.domOperation();
1865         applyAnimationClasses(element, options);
1866       }
1867
1868       function close() {
1869         animationClosed = true;
1870         applyOptions();
1871         applyAnimationStyles(element, options);
1872       }
1873
1874       var runner;
1875
1876       return {
1877         $$willAnimate: true,
1878         end: function() {
1879           if (runner) {
1880             runner.end();
1881           } else {
1882             close();
1883             runner = new $$AnimateRunner();
1884             runner.complete(true);
1885           }
1886           return runner;
1887         },
1888         start: function() {
1889           if (runner) {
1890             return runner;
1891           }
1892
1893           runner = new $$AnimateRunner();
1894           var closeActiveAnimations;
1895           var chain = [];
1896
1897           if (before) {
1898             chain.push(function(fn) {
1899               closeActiveAnimations = before(fn);
1900             });
1901           }
1902
1903           if (chain.length) {
1904             chain.push(function(fn) {
1905               applyOptions();
1906               fn(true);
1907             });
1908           } else {
1909             applyOptions();
1910           }
1911
1912           if (after) {
1913             chain.push(function(fn) {
1914               closeActiveAnimations = after(fn);
1915             });
1916           }
1917
1918           runner.setHost({
1919             end: function() {
1920               endAnimations();
1921             },
1922             cancel: function() {
1923               endAnimations(true);
1924             }
1925           });
1926
1927           $$AnimateRunner.chain(chain, onComplete);
1928           return runner;
1929
1930           function onComplete(success) {
1931             close(success);
1932             runner.complete(success);
1933           }
1934
1935           function endAnimations(cancelled) {
1936             if (!animationClosed) {
1937               (closeActiveAnimations || noop)(cancelled);
1938               onComplete(cancelled);
1939             }
1940           }
1941         }
1942       };
1943
1944       function executeAnimationFn(fn, element, event, options, onDone) {
1945         var args;
1946         switch (event) {
1947           case 'animate':
1948             args = [element, options.from, options.to, onDone];
1949             break;
1950
1951           case 'setClass':
1952             args = [element, classesToAdd, classesToRemove, onDone];
1953             break;
1954
1955           case 'addClass':
1956             args = [element, classesToAdd, onDone];
1957             break;
1958
1959           case 'removeClass':
1960             args = [element, classesToRemove, onDone];
1961             break;
1962
1963           default:
1964             args = [element, onDone];
1965             break;
1966         }
1967
1968         args.push(options);
1969
1970         var value = fn.apply(fn, args);
1971         if (value) {
1972           if (isFunction(value.start)) {
1973             value = value.start();
1974           }
1975
1976           if (value instanceof $$AnimateRunner) {
1977             value.done(onDone);
1978           } else if (isFunction(value)) {
1979             // optional onEnd / onCancel callback
1980             return value;
1981           }
1982         }
1983
1984         return noop;
1985       }
1986
1987       function groupEventedAnimations(element, event, options, animations, fnName) {
1988         var operations = [];
1989         forEach(animations, function(ani) {
1990           var animation = ani[fnName];
1991           if (!animation) return;
1992
1993           // note that all of these animations will run in parallel
1994           operations.push(function() {
1995             var runner;
1996             var endProgressCb;
1997
1998             var resolved = false;
1999             var onAnimationComplete = function(rejected) {
2000               if (!resolved) {
2001                 resolved = true;
2002                 (endProgressCb || noop)(rejected);
2003                 runner.complete(!rejected);
2004               }
2005             };
2006
2007             runner = new $$AnimateRunner({
2008               end: function() {
2009                 onAnimationComplete();
2010               },
2011               cancel: function() {
2012                 onAnimationComplete(true);
2013               }
2014             });
2015
2016             endProgressCb = executeAnimationFn(animation, element, event, options, function(result) {
2017               var cancelled = result === false;
2018               onAnimationComplete(cancelled);
2019             });
2020
2021             return runner;
2022           });
2023         });
2024
2025         return operations;
2026       }
2027
2028       function packageAnimations(element, event, options, animations, fnName) {
2029         var operations = groupEventedAnimations(element, event, options, animations, fnName);
2030         if (operations.length === 0) {
2031           var a,b;
2032           if (fnName === 'beforeSetClass') {
2033             a = groupEventedAnimations(element, 'removeClass', options, animations, 'beforeRemoveClass');
2034             b = groupEventedAnimations(element, 'addClass', options, animations, 'beforeAddClass');
2035           } else if (fnName === 'setClass') {
2036             a = groupEventedAnimations(element, 'removeClass', options, animations, 'removeClass');
2037             b = groupEventedAnimations(element, 'addClass', options, animations, 'addClass');
2038           }
2039
2040           if (a) {
2041             operations = operations.concat(a);
2042           }
2043           if (b) {
2044             operations = operations.concat(b);
2045           }
2046         }
2047
2048         if (operations.length === 0) return;
2049
2050         // TODO(matsko): add documentation
2051         return function startAnimation(callback) {
2052           var runners = [];
2053           if (operations.length) {
2054             forEach(operations, function(animateFn) {
2055               runners.push(animateFn());
2056             });
2057           }
2058
2059           runners.length ? $$AnimateRunner.all(runners, callback) : callback();
2060
2061           return function endFn(reject) {
2062             forEach(runners, function(runner) {
2063               reject ? runner.cancel() : runner.end();
2064             });
2065           };
2066         };
2067       }
2068     };
2069
2070     function lookupAnimations(classes) {
2071       classes = isArray(classes) ? classes : classes.split(' ');
2072       var matches = [], flagMap = {};
2073       for (var i=0; i < classes.length; i++) {
2074         var klass = classes[i],
2075             animationFactory = $animateProvider.$$registeredAnimations[klass];
2076         if (animationFactory && !flagMap[klass]) {
2077           matches.push($injector.get(animationFactory));
2078           flagMap[klass] = true;
2079         }
2080       }
2081       return matches;
2082     }
2083   }];
2084 }];
2085
2086 var $$AnimateJsDriverProvider = ['$$animationProvider', function($$animationProvider) {
2087   $$animationProvider.drivers.push('$$animateJsDriver');
2088   this.$get = ['$$animateJs', '$$AnimateRunner', function($$animateJs, $$AnimateRunner) {
2089     return function initDriverFn(animationDetails) {
2090       if (animationDetails.from && animationDetails.to) {
2091         var fromAnimation = prepareAnimation(animationDetails.from);
2092         var toAnimation = prepareAnimation(animationDetails.to);
2093         if (!fromAnimation && !toAnimation) return;
2094
2095         return {
2096           start: function() {
2097             var animationRunners = [];
2098
2099             if (fromAnimation) {
2100               animationRunners.push(fromAnimation.start());
2101             }
2102
2103             if (toAnimation) {
2104               animationRunners.push(toAnimation.start());
2105             }
2106
2107             $$AnimateRunner.all(animationRunners, done);
2108
2109             var runner = new $$AnimateRunner({
2110               end: endFnFactory(),
2111               cancel: endFnFactory()
2112             });
2113
2114             return runner;
2115
2116             function endFnFactory() {
2117               return function() {
2118                 forEach(animationRunners, function(runner) {
2119                   // at this point we cannot cancel animations for groups just yet. 1.5+
2120                   runner.end();
2121                 });
2122               };
2123             }
2124
2125             function done(status) {
2126               runner.complete(status);
2127             }
2128           }
2129         };
2130       } else {
2131         return prepareAnimation(animationDetails);
2132       }
2133     };
2134
2135     function prepareAnimation(animationDetails) {
2136       // TODO(matsko): make sure to check for grouped animations and delegate down to normal animations
2137       var element = animationDetails.element;
2138       var event = animationDetails.event;
2139       var options = animationDetails.options;
2140       var classes = animationDetails.classes;
2141       return $$animateJs(element, event, classes, options);
2142     }
2143   }];
2144 }];
2145
2146 var NG_ANIMATE_ATTR_NAME = 'data-ng-animate';
2147 var NG_ANIMATE_PIN_DATA = '$ngAnimatePin';
2148 var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
2149   var PRE_DIGEST_STATE = 1;
2150   var RUNNING_STATE = 2;
2151   var ONE_SPACE = ' ';
2152
2153   var rules = this.rules = {
2154     skip: [],
2155     cancel: [],
2156     join: []
2157   };
2158
2159   function makeTruthyCssClassMap(classString) {
2160     if (!classString) {
2161       return null;
2162     }
2163
2164     var keys = classString.split(ONE_SPACE);
2165     var map = Object.create(null);
2166
2167     forEach(keys, function(key) {
2168       map[key] = true;
2169     });
2170     return map;
2171   }
2172
2173   function hasMatchingClasses(newClassString, currentClassString) {
2174     if (newClassString && currentClassString) {
2175       var currentClassMap = makeTruthyCssClassMap(currentClassString);
2176       return newClassString.split(ONE_SPACE).some(function(className) {
2177         return currentClassMap[className];
2178       });
2179     }
2180   }
2181
2182   function isAllowed(ruleType, element, currentAnimation, previousAnimation) {
2183     return rules[ruleType].some(function(fn) {
2184       return fn(element, currentAnimation, previousAnimation);
2185     });
2186   }
2187
2188   function hasAnimationClasses(animation, and) {
2189     var a = (animation.addClass || '').length > 0;
2190     var b = (animation.removeClass || '').length > 0;
2191     return and ? a && b : a || b;
2192   }
2193
2194   rules.join.push(function(element, newAnimation, currentAnimation) {
2195     // if the new animation is class-based then we can just tack that on
2196     return !newAnimation.structural && hasAnimationClasses(newAnimation);
2197   });
2198
2199   rules.skip.push(function(element, newAnimation, currentAnimation) {
2200     // there is no need to animate anything if no classes are being added and
2201     // there is no structural animation that will be triggered
2202     return !newAnimation.structural && !hasAnimationClasses(newAnimation);
2203   });
2204
2205   rules.skip.push(function(element, newAnimation, currentAnimation) {
2206     // why should we trigger a new structural animation if the element will
2207     // be removed from the DOM anyway?
2208     return currentAnimation.event == 'leave' && newAnimation.structural;
2209   });
2210
2211   rules.skip.push(function(element, newAnimation, currentAnimation) {
2212     // if there is an ongoing current animation then don't even bother running the class-based animation
2213     return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural;
2214   });
2215
2216   rules.cancel.push(function(element, newAnimation, currentAnimation) {
2217     // there can never be two structural animations running at the same time
2218     return currentAnimation.structural && newAnimation.structural;
2219   });
2220
2221   rules.cancel.push(function(element, newAnimation, currentAnimation) {
2222     // if the previous animation is already running, but the new animation will
2223     // be triggered, but the new animation is structural
2224     return currentAnimation.state === RUNNING_STATE && newAnimation.structural;
2225   });
2226
2227   rules.cancel.push(function(element, newAnimation, currentAnimation) {
2228     var nA = newAnimation.addClass;
2229     var nR = newAnimation.removeClass;
2230     var cA = currentAnimation.addClass;
2231     var cR = currentAnimation.removeClass;
2232
2233     // early detection to save the global CPU shortage :)
2234     if ((isUndefined(nA) && isUndefined(nR)) || (isUndefined(cA) && isUndefined(cR))) {
2235       return false;
2236     }
2237
2238     return hasMatchingClasses(nA, cR) || hasMatchingClasses(nR, cA);
2239   });
2240
2241   this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap',
2242                '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow',
2243        function($$rAF,   $rootScope,   $rootElement,   $document,   $$HashMap,
2244                 $$animation,   $$AnimateRunner,   $templateRequest,   $$jqLite,   $$forceReflow) {
2245
2246     var activeAnimationsLookup = new $$HashMap();
2247     var disabledElementsLookup = new $$HashMap();
2248     var animationsEnabled = null;
2249
2250     function postDigestTaskFactory() {
2251       var postDigestCalled = false;
2252       return function(fn) {
2253         // we only issue a call to postDigest before
2254         // it has first passed. This prevents any callbacks
2255         // from not firing once the animation has completed
2256         // since it will be out of the digest cycle.
2257         if (postDigestCalled) {
2258           fn();
2259         } else {
2260           $rootScope.$$postDigest(function() {
2261             postDigestCalled = true;
2262             fn();
2263           });
2264         }
2265       };
2266     }
2267
2268     // Wait until all directive and route-related templates are downloaded and
2269     // compiled. The $templateRequest.totalPendingRequests variable keeps track of
2270     // all of the remote templates being currently downloaded. If there are no
2271     // templates currently downloading then the watcher will still fire anyway.
2272     var deregisterWatch = $rootScope.$watch(
2273       function() { return $templateRequest.totalPendingRequests === 0; },
2274       function(isEmpty) {
2275         if (!isEmpty) return;
2276         deregisterWatch();
2277
2278         // Now that all templates have been downloaded, $animate will wait until
2279         // the post digest queue is empty before enabling animations. By having two
2280         // calls to $postDigest calls we can ensure that the flag is enabled at the
2281         // very end of the post digest queue. Since all of the animations in $animate
2282         // use $postDigest, it's important that the code below executes at the end.
2283         // This basically means that the page is fully downloaded and compiled before
2284         // any animations are triggered.
2285         $rootScope.$$postDigest(function() {
2286           $rootScope.$$postDigest(function() {
2287             // we check for null directly in the event that the application already called
2288             // .enabled() with whatever arguments that it provided it with
2289             if (animationsEnabled === null) {
2290               animationsEnabled = true;
2291             }
2292           });
2293         });
2294       }
2295     );
2296
2297     var callbackRegistry = {};
2298
2299     // remember that the classNameFilter is set during the provider/config
2300     // stage therefore we can optimize here and setup a helper function
2301     var classNameFilter = $animateProvider.classNameFilter();
2302     var isAnimatableClassName = !classNameFilter
2303               ? function() { return true; }
2304               : function(className) {
2305                 return classNameFilter.test(className);
2306               };
2307
2308     var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
2309
2310     function normalizeAnimationDetails(element, animation) {
2311       return mergeAnimationDetails(element, animation, {});
2312     }
2313
2314     // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
2315     var contains = Node.prototype.contains || function(arg) {
2316       // jshint bitwise: false
2317       return this === arg || !!(this.compareDocumentPosition(arg) & 16);
2318       // jshint bitwise: true
2319     };
2320
2321     function findCallbacks(parent, element, event) {
2322       var targetNode = getDomNode(element);
2323       var targetParentNode = getDomNode(parent);
2324
2325       var matches = [];
2326       var entries = callbackRegistry[event];
2327       if (entries) {
2328         forEach(entries, function(entry) {
2329           if (contains.call(entry.node, targetNode)) {
2330             matches.push(entry.callback);
2331           } else if (event === 'leave' && contains.call(entry.node, targetParentNode)) {
2332             matches.push(entry.callback);
2333           }
2334         });
2335       }
2336
2337       return matches;
2338     }
2339
2340     return {
2341       on: function(event, container, callback) {
2342         var node = extractElementNode(container);
2343         callbackRegistry[event] = callbackRegistry[event] || [];
2344         callbackRegistry[event].push({
2345           node: node,
2346           callback: callback
2347         });
2348       },
2349
2350       off: function(event, container, callback) {
2351         var entries = callbackRegistry[event];
2352         if (!entries) return;
2353
2354         callbackRegistry[event] = arguments.length === 1
2355             ? null
2356             : filterFromRegistry(entries, container, callback);
2357
2358         function filterFromRegistry(list, matchContainer, matchCallback) {
2359           var containerNode = extractElementNode(matchContainer);
2360           return list.filter(function(entry) {
2361             var isMatch = entry.node === containerNode &&
2362                             (!matchCallback || entry.callback === matchCallback);
2363             return !isMatch;
2364           });
2365         }
2366       },
2367
2368       pin: function(element, parentElement) {
2369         assertArg(isElement(element), 'element', 'not an element');
2370         assertArg(isElement(parentElement), 'parentElement', 'not an element');
2371         element.data(NG_ANIMATE_PIN_DATA, parentElement);
2372       },
2373
2374       push: function(element, event, options, domOperation) {
2375         options = options || {};
2376         options.domOperation = domOperation;
2377         return queueAnimation(element, event, options);
2378       },
2379
2380       // this method has four signatures:
2381       //  () - global getter
2382       //  (bool) - global setter
2383       //  (element) - element getter
2384       //  (element, bool) - element setter<F37>
2385       enabled: function(element, bool) {
2386         var argCount = arguments.length;
2387
2388         if (argCount === 0) {
2389           // () - Global getter
2390           bool = !!animationsEnabled;
2391         } else {
2392           var hasElement = isElement(element);
2393
2394           if (!hasElement) {
2395             // (bool) - Global setter
2396             bool = animationsEnabled = !!element;
2397           } else {
2398             var node = getDomNode(element);
2399             var recordExists = disabledElementsLookup.get(node);
2400
2401             if (argCount === 1) {
2402               // (element) - Element getter
2403               bool = !recordExists;
2404             } else {
2405               // (element, bool) - Element setter
2406               disabledElementsLookup.put(node, !bool);
2407             }
2408           }
2409         }
2410
2411         return bool;
2412       }
2413     };
2414
2415     function queueAnimation(element, event, initialOptions) {
2416       // we always make a copy of the options since
2417       // there should never be any side effects on
2418       // the input data when running `$animateCss`.
2419       var options = copy(initialOptions);
2420
2421       var node, parent;
2422       element = stripCommentsFromElement(element);
2423       if (element) {
2424         node = getDomNode(element);
2425         parent = element.parent();
2426       }
2427
2428       options = prepareAnimationOptions(options);
2429
2430       // we create a fake runner with a working promise.
2431       // These methods will become available after the digest has passed
2432       var runner = new $$AnimateRunner();
2433
2434       // this is used to trigger callbacks in postDigest mode
2435       var runInNextPostDigestOrNow = postDigestTaskFactory();
2436
2437       if (isArray(options.addClass)) {
2438         options.addClass = options.addClass.join(' ');
2439       }
2440
2441       if (options.addClass && !isString(options.addClass)) {
2442         options.addClass = null;
2443       }
2444
2445       if (isArray(options.removeClass)) {
2446         options.removeClass = options.removeClass.join(' ');
2447       }
2448
2449       if (options.removeClass && !isString(options.removeClass)) {
2450         options.removeClass = null;
2451       }
2452
2453       if (options.from && !isObject(options.from)) {
2454         options.from = null;
2455       }
2456
2457       if (options.to && !isObject(options.to)) {
2458         options.to = null;
2459       }
2460
2461       // there are situations where a directive issues an animation for
2462       // a jqLite wrapper that contains only comment nodes... If this
2463       // happens then there is no way we can perform an animation
2464       if (!node) {
2465         close();
2466         return runner;
2467       }
2468
2469       var className = [node.className, options.addClass, options.removeClass].join(' ');
2470       if (!isAnimatableClassName(className)) {
2471         close();
2472         return runner;
2473       }
2474
2475       var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
2476
2477       // this is a hard disable of all animations for the application or on
2478       // the element itself, therefore  there is no need to continue further
2479       // past this point if not enabled
2480       // Animations are also disabled if the document is currently hidden (page is not visible
2481       // to the user), because browsers slow down or do not flush calls to requestAnimationFrame
2482       var skipAnimations = !animationsEnabled || $document[0].hidden || disabledElementsLookup.get(node);
2483       var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {};
2484       var hasExistingAnimation = !!existingAnimation.state;
2485
2486       // there is no point in traversing the same collection of parent ancestors if a followup
2487       // animation will be run on the same element that already did all that checking work
2488       if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state != PRE_DIGEST_STATE)) {
2489         skipAnimations = !areAnimationsAllowed(element, parent, event);
2490       }
2491
2492       if (skipAnimations) {
2493         close();
2494         return runner;
2495       }
2496
2497       if (isStructural) {
2498         closeChildAnimations(element);
2499       }
2500
2501       var newAnimation = {
2502         structural: isStructural,
2503         element: element,
2504         event: event,
2505         addClass: options.addClass,
2506         removeClass: options.removeClass,
2507         close: close,
2508         options: options,
2509         runner: runner
2510       };
2511
2512       if (hasExistingAnimation) {
2513         var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation);
2514         if (skipAnimationFlag) {
2515           if (existingAnimation.state === RUNNING_STATE) {
2516             close();
2517             return runner;
2518           } else {
2519             mergeAnimationDetails(element, existingAnimation, newAnimation);
2520             return existingAnimation.runner;
2521           }
2522         }
2523         var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation);
2524         if (cancelAnimationFlag) {
2525           if (existingAnimation.state === RUNNING_STATE) {
2526             // this will end the animation right away and it is safe
2527             // to do so since the animation is already running and the
2528             // runner callback code will run in async
2529             existingAnimation.runner.end();
2530           } else if (existingAnimation.structural) {
2531             // this means that the animation is queued into a digest, but
2532             // hasn't started yet. Therefore it is safe to run the close
2533             // method which will call the runner methods in async.
2534             existingAnimation.close();
2535           } else {
2536             // this will merge the new animation options into existing animation options
2537             mergeAnimationDetails(element, existingAnimation, newAnimation);
2538
2539             return existingAnimation.runner;
2540           }
2541         } else {
2542           // a joined animation means that this animation will take over the existing one
2543           // so an example would involve a leave animation taking over an enter. Then when
2544           // the postDigest kicks in the enter will be ignored.
2545           var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation);
2546           if (joinAnimationFlag) {
2547             if (existingAnimation.state === RUNNING_STATE) {
2548               normalizeAnimationDetails(element, newAnimation);
2549             } else {
2550               applyGeneratedPreparationClasses(element, isStructural ? event : null, options);
2551
2552               event = newAnimation.event = existingAnimation.event;
2553               options = mergeAnimationDetails(element, existingAnimation, newAnimation);
2554
2555               //we return the same runner since only the option values of this animation will
2556               //be fed into the `existingAnimation`.
2557               return existingAnimation.runner;
2558             }
2559           }
2560         }
2561       } else {
2562         // normalization in this case means that it removes redundant CSS classes that
2563         // already exist (addClass) or do not exist (removeClass) on the element
2564         normalizeAnimationDetails(element, newAnimation);
2565       }
2566
2567       // when the options are merged and cleaned up we may end up not having to do
2568       // an animation at all, therefore we should check this before issuing a post
2569       // digest callback. Structural animations will always run no matter what.
2570       var isValidAnimation = newAnimation.structural;
2571       if (!isValidAnimation) {
2572         // animate (from/to) can be quickly checked first, otherwise we check if any classes are present
2573         isValidAnimation = (newAnimation.event === 'animate' && Object.keys(newAnimation.options.to || {}).length > 0)
2574                             || hasAnimationClasses(newAnimation);
2575       }
2576
2577       if (!isValidAnimation) {
2578         close();
2579         clearElementAnimationState(element);
2580         return runner;
2581       }
2582
2583       // the counter keeps track of cancelled animations
2584       var counter = (existingAnimation.counter || 0) + 1;
2585       newAnimation.counter = counter;
2586
2587       markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation);
2588
2589       $rootScope.$$postDigest(function() {
2590         var animationDetails = activeAnimationsLookup.get(node);
2591         var animationCancelled = !animationDetails;
2592         animationDetails = animationDetails || {};
2593
2594         // if addClass/removeClass is called before something like enter then the
2595         // registered parent element may not be present. The code below will ensure
2596         // that a final value for parent element is obtained
2597         var parentElement = element.parent() || [];
2598
2599         // animate/structural/class-based animations all have requirements. Otherwise there
2600         // is no point in performing an animation. The parent node must also be set.
2601         var isValidAnimation = parentElement.length > 0
2602                                 && (animationDetails.event === 'animate'
2603                                     || animationDetails.structural
2604                                     || hasAnimationClasses(animationDetails));
2605
2606         // this means that the previous animation was cancelled
2607         // even if the follow-up animation is the same event
2608         if (animationCancelled || animationDetails.counter !== counter || !isValidAnimation) {
2609           // if another animation did not take over then we need
2610           // to make sure that the domOperation and options are
2611           // handled accordingly
2612           if (animationCancelled) {
2613             applyAnimationClasses(element, options);
2614             applyAnimationStyles(element, options);
2615           }
2616
2617           // if the event changed from something like enter to leave then we do
2618           // it, otherwise if it's the same then the end result will be the same too
2619           if (animationCancelled || (isStructural && animationDetails.event !== event)) {
2620             options.domOperation();
2621             runner.end();
2622           }
2623
2624           // in the event that the element animation was not cancelled or a follow-up animation
2625           // isn't allowed to animate from here then we need to clear the state of the element
2626           // so that any future animations won't read the expired animation data.
2627           if (!isValidAnimation) {
2628             clearElementAnimationState(element);
2629           }
2630
2631           return;
2632         }
2633
2634         // this combined multiple class to addClass / removeClass into a setClass event
2635         // so long as a structural event did not take over the animation
2636         event = !animationDetails.structural && hasAnimationClasses(animationDetails, true)
2637             ? 'setClass'
2638             : animationDetails.event;
2639
2640         markElementAnimationState(element, RUNNING_STATE);
2641         var realRunner = $$animation(element, event, animationDetails.options);
2642
2643         realRunner.done(function(status) {
2644           close(!status);
2645           var animationDetails = activeAnimationsLookup.get(node);
2646           if (animationDetails && animationDetails.counter === counter) {
2647             clearElementAnimationState(getDomNode(element));
2648           }
2649           notifyProgress(runner, event, 'close', {});
2650         });
2651
2652         // this will update the runner's flow-control events based on
2653         // the `realRunner` object.
2654         runner.setHost(realRunner);
2655         notifyProgress(runner, event, 'start', {});
2656       });
2657
2658       return runner;
2659
2660       function notifyProgress(runner, event, phase, data) {
2661         runInNextPostDigestOrNow(function() {
2662           var callbacks = findCallbacks(parent, element, event);
2663           if (callbacks.length) {
2664             // do not optimize this call here to RAF because
2665             // we don't know how heavy the callback code here will
2666             // be and if this code is buffered then this can
2667             // lead to a performance regression.
2668             $$rAF(function() {
2669               forEach(callbacks, function(callback) {
2670                 callback(element, phase, data);
2671               });
2672             });
2673           }
2674         });
2675         runner.progress(event, phase, data);
2676       }
2677
2678       function close(reject) { // jshint ignore:line
2679         clearGeneratedClasses(element, options);
2680         applyAnimationClasses(element, options);
2681         applyAnimationStyles(element, options);
2682         options.domOperation();
2683         runner.complete(!reject);
2684       }
2685     }
2686
2687     function closeChildAnimations(element) {
2688       var node = getDomNode(element);
2689       var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']');
2690       forEach(children, function(child) {
2691         var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME));
2692         var animationDetails = activeAnimationsLookup.get(child);
2693         if (animationDetails) {
2694           switch (state) {
2695             case RUNNING_STATE:
2696               animationDetails.runner.end();
2697               /* falls through */
2698             case PRE_DIGEST_STATE:
2699               activeAnimationsLookup.remove(child);
2700               break;
2701           }
2702         }
2703       });
2704     }
2705
2706     function clearElementAnimationState(element) {
2707       var node = getDomNode(element);
2708       node.removeAttribute(NG_ANIMATE_ATTR_NAME);
2709       activeAnimationsLookup.remove(node);
2710     }
2711
2712     function isMatchingElement(nodeOrElmA, nodeOrElmB) {
2713       return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);
2714     }
2715
2716     /**
2717      * This fn returns false if any of the following is true:
2718      * a) animations on any parent element are disabled, and animations on the element aren't explicitly allowed
2719      * b) a parent element has an ongoing structural animation, and animateChildren is false
2720      * c) the element is not a child of the body
2721      * d) the element is not a child of the $rootElement
2722      */
2723     function areAnimationsAllowed(element, parentElement, event) {
2724       var bodyElement = jqLite($document[0].body);
2725       var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML';
2726       var rootElementDetected = isMatchingElement(element, $rootElement);
2727       var parentAnimationDetected = false;
2728       var animateChildren;
2729       var elementDisabled = disabledElementsLookup.get(getDomNode(element));
2730
2731       var parentHost = element.data(NG_ANIMATE_PIN_DATA);
2732       if (parentHost) {
2733         parentElement = parentHost;
2734       }
2735
2736       while (parentElement && parentElement.length) {
2737         if (!rootElementDetected) {
2738           // angular doesn't want to attempt to animate elements outside of the application
2739           // therefore we need to ensure that the rootElement is an ancestor of the current element
2740           rootElementDetected = isMatchingElement(parentElement, $rootElement);
2741         }
2742
2743         var parentNode = parentElement[0];
2744         if (parentNode.nodeType !== ELEMENT_NODE) {
2745           // no point in inspecting the #document element
2746           break;
2747         }
2748
2749         var details = activeAnimationsLookup.get(parentNode) || {};
2750         // either an enter, leave or move animation will commence
2751         // therefore we can't allow any animations to take place
2752         // but if a parent animation is class-based then that's ok
2753         if (!parentAnimationDetected) {
2754           var parentElementDisabled = disabledElementsLookup.get(parentNode);
2755
2756           if (parentElementDisabled === true && elementDisabled !== false) {
2757             // disable animations if the user hasn't explicitly enabled animations on the
2758             // current element
2759             elementDisabled = true;
2760             // element is disabled via parent element, no need to check anything else
2761             break;
2762           } else if (parentElementDisabled === false) {
2763             elementDisabled = false;
2764           }
2765           parentAnimationDetected = details.structural;
2766         }
2767
2768         if (isUndefined(animateChildren) || animateChildren === true) {
2769           var value = parentElement.data(NG_ANIMATE_CHILDREN_DATA);
2770           if (isDefined(value)) {
2771             animateChildren = value;
2772           }
2773         }
2774
2775         // there is no need to continue traversing at this point
2776         if (parentAnimationDetected && animateChildren === false) break;
2777
2778         if (!bodyElementDetected) {
2779           // we also need to ensure that the element is or will be a part of the body element
2780           // otherwise it is pointless to even issue an animation to be rendered
2781           bodyElementDetected = isMatchingElement(parentElement, bodyElement);
2782         }
2783
2784         if (bodyElementDetected && rootElementDetected) {
2785           // If both body and root have been found, any other checks are pointless,
2786           // as no animation data should live outside the application
2787           break;
2788         }
2789
2790         if (!rootElementDetected) {
2791           // If no rootElement is detected, check if the parentElement is pinned to another element
2792           parentHost = parentElement.data(NG_ANIMATE_PIN_DATA);
2793           if (parentHost) {
2794             // The pin target element becomes the next parent element
2795             parentElement = parentHost;
2796             continue;
2797           }
2798         }
2799
2800         parentElement = parentElement.parent();
2801       }
2802
2803       var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true;
2804       return allowAnimation && rootElementDetected && bodyElementDetected;
2805     }
2806
2807     function markElementAnimationState(element, state, details) {
2808       details = details || {};
2809       details.state = state;
2810
2811       var node = getDomNode(element);
2812       node.setAttribute(NG_ANIMATE_ATTR_NAME, state);
2813
2814       var oldValue = activeAnimationsLookup.get(node);
2815       var newValue = oldValue
2816           ? extend(oldValue, details)
2817           : details;
2818       activeAnimationsLookup.put(node, newValue);
2819     }
2820   }];
2821 }];
2822
2823 var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
2824   var NG_ANIMATE_REF_ATTR = 'ng-animate-ref';
2825
2826   var drivers = this.drivers = [];
2827
2828   var RUNNER_STORAGE_KEY = '$$animationRunner';
2829
2830   function setRunner(element, runner) {
2831     element.data(RUNNER_STORAGE_KEY, runner);
2832   }
2833
2834   function removeRunner(element) {
2835     element.removeData(RUNNER_STORAGE_KEY);
2836   }
2837
2838   function getRunner(element) {
2839     return element.data(RUNNER_STORAGE_KEY);
2840   }
2841
2842   this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler',
2843        function($$jqLite,   $rootScope,   $injector,   $$AnimateRunner,   $$HashMap,   $$rAFScheduler) {
2844
2845     var animationQueue = [];
2846     var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
2847
2848     function sortAnimations(animations) {
2849       var tree = { children: [] };
2850       var i, lookup = new $$HashMap();
2851
2852       // this is done first beforehand so that the hashmap
2853       // is filled with a list of the elements that will be animated
2854       for (i = 0; i < animations.length; i++) {
2855         var animation = animations[i];
2856         lookup.put(animation.domNode, animations[i] = {
2857           domNode: animation.domNode,
2858           fn: animation.fn,
2859           children: []
2860         });
2861       }
2862
2863       for (i = 0; i < animations.length; i++) {
2864         processNode(animations[i]);
2865       }
2866
2867       return flatten(tree);
2868
2869       function processNode(entry) {
2870         if (entry.processed) return entry;
2871         entry.processed = true;
2872
2873         var elementNode = entry.domNode;
2874         var parentNode = elementNode.parentNode;
2875         lookup.put(elementNode, entry);
2876
2877         var parentEntry;
2878         while (parentNode) {
2879           parentEntry = lookup.get(parentNode);
2880           if (parentEntry) {
2881             if (!parentEntry.processed) {
2882               parentEntry = processNode(parentEntry);
2883             }
2884             break;
2885           }
2886           parentNode = parentNode.parentNode;
2887         }
2888
2889         (parentEntry || tree).children.push(entry);
2890         return entry;
2891       }
2892
2893       function flatten(tree) {
2894         var result = [];
2895         var queue = [];
2896         var i;
2897
2898         for (i = 0; i < tree.children.length; i++) {
2899           queue.push(tree.children[i]);
2900         }
2901
2902         var remainingLevelEntries = queue.length;
2903         var nextLevelEntries = 0;
2904         var row = [];
2905
2906         for (i = 0; i < queue.length; i++) {
2907           var entry = queue[i];
2908           if (remainingLevelEntries <= 0) {
2909             remainingLevelEntries = nextLevelEntries;
2910             nextLevelEntries = 0;
2911             result.push(row);
2912             row = [];
2913           }
2914           row.push(entry.fn);
2915           entry.children.forEach(function(childEntry) {
2916             nextLevelEntries++;
2917             queue.push(childEntry);
2918           });
2919           remainingLevelEntries--;
2920         }
2921
2922         if (row.length) {
2923           result.push(row);
2924         }
2925
2926         return result;
2927       }
2928     }
2929
2930     // TODO(matsko): document the signature in a better way
2931     return function(element, event, options) {
2932       options = prepareAnimationOptions(options);
2933       var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
2934
2935       // there is no animation at the current moment, however
2936       // these runner methods will get later updated with the
2937       // methods leading into the driver's end/cancel methods
2938       // for now they just stop the animation from starting
2939       var runner = new $$AnimateRunner({
2940         end: function() { close(); },
2941         cancel: function() { close(true); }
2942       });
2943
2944       if (!drivers.length) {
2945         close();
2946         return runner;
2947       }
2948
2949       setRunner(element, runner);
2950
2951       var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass));
2952       var tempClasses = options.tempClasses;
2953       if (tempClasses) {
2954         classes += ' ' + tempClasses;
2955         options.tempClasses = null;
2956       }
2957
2958       var prepareClassName;
2959       if (isStructural) {
2960         prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX;
2961         $$jqLite.addClass(element, prepareClassName);
2962       }
2963
2964       animationQueue.push({
2965         // this data is used by the postDigest code and passed into
2966         // the driver step function
2967         element: element,
2968         classes: classes,
2969         event: event,
2970         structural: isStructural,
2971         options: options,
2972         beforeStart: beforeStart,
2973         close: close
2974       });
2975
2976       element.on('$destroy', handleDestroyedElement);
2977
2978       // we only want there to be one function called within the post digest
2979       // block. This way we can group animations for all the animations that
2980       // were apart of the same postDigest flush call.
2981       if (animationQueue.length > 1) return runner;
2982
2983       $rootScope.$$postDigest(function() {
2984         var animations = [];
2985         forEach(animationQueue, function(entry) {
2986           // the element was destroyed early on which removed the runner
2987           // form its storage. This means we can't animate this element
2988           // at all and it already has been closed due to destruction.
2989           if (getRunner(entry.element)) {
2990             animations.push(entry);
2991           } else {
2992             entry.close();
2993           }
2994         });
2995
2996         // now any future animations will be in another postDigest
2997         animationQueue.length = 0;
2998
2999         var groupedAnimations = groupAnimations(animations);
3000         var toBeSortedAnimations = [];
3001
3002         forEach(groupedAnimations, function(animationEntry) {
3003           toBeSortedAnimations.push({
3004             domNode: getDomNode(animationEntry.from ? animationEntry.from.element : animationEntry.element),
3005             fn: function triggerAnimationStart() {
3006               // it's important that we apply the `ng-animate` CSS class and the
3007               // temporary classes before we do any driver invoking since these
3008               // CSS classes may be required for proper CSS detection.
3009               animationEntry.beforeStart();
3010
3011               var startAnimationFn, closeFn = animationEntry.close;
3012
3013               // in the event that the element was removed before the digest runs or
3014               // during the RAF sequencing then we should not trigger the animation.
3015               var targetElement = animationEntry.anchors
3016                   ? (animationEntry.from.element || animationEntry.to.element)
3017                   : animationEntry.element;
3018
3019               if (getRunner(targetElement)) {
3020                 var operation = invokeFirstDriver(animationEntry);
3021                 if (operation) {
3022                   startAnimationFn = operation.start;
3023                 }
3024               }
3025
3026               if (!startAnimationFn) {
3027                 closeFn();
3028               } else {
3029                 var animationRunner = startAnimationFn();
3030                 animationRunner.done(function(status) {
3031                   closeFn(!status);
3032                 });
3033                 updateAnimationRunners(animationEntry, animationRunner);
3034               }
3035             }
3036           });
3037         });
3038
3039         // we need to sort each of the animations in order of parent to child
3040         // relationships. This ensures that the child classes are applied at the
3041         // right time.
3042         $$rAFScheduler(sortAnimations(toBeSortedAnimations));
3043       });
3044
3045       return runner;
3046
3047       // TODO(matsko): change to reference nodes
3048       function getAnchorNodes(node) {
3049         var SELECTOR = '[' + NG_ANIMATE_REF_ATTR + ']';
3050         var items = node.hasAttribute(NG_ANIMATE_REF_ATTR)
3051               ? [node]
3052               : node.querySelectorAll(SELECTOR);
3053         var anchors = [];
3054         forEach(items, function(node) {
3055           var attr = node.getAttribute(NG_ANIMATE_REF_ATTR);
3056           if (attr && attr.length) {
3057             anchors.push(node);
3058           }
3059         });
3060         return anchors;
3061       }
3062
3063       function groupAnimations(animations) {
3064         var preparedAnimations = [];
3065         var refLookup = {};
3066         forEach(animations, function(animation, index) {
3067           var element = animation.element;
3068           var node = getDomNode(element);
3069           var event = animation.event;
3070           var enterOrMove = ['enter', 'move'].indexOf(event) >= 0;
3071           var anchorNodes = animation.structural ? getAnchorNodes(node) : [];
3072
3073           if (anchorNodes.length) {
3074             var direction = enterOrMove ? 'to' : 'from';
3075
3076             forEach(anchorNodes, function(anchor) {
3077               var key = anchor.getAttribute(NG_ANIMATE_REF_ATTR);
3078               refLookup[key] = refLookup[key] || {};
3079               refLookup[key][direction] = {
3080                 animationID: index,
3081                 element: jqLite(anchor)
3082               };
3083             });
3084           } else {
3085             preparedAnimations.push(animation);
3086           }
3087         });
3088
3089         var usedIndicesLookup = {};
3090         var anchorGroups = {};
3091         forEach(refLookup, function(operations, key) {
3092           var from = operations.from;
3093           var to = operations.to;
3094
3095           if (!from || !to) {
3096             // only one of these is set therefore we can't have an
3097             // anchor animation since all three pieces are required
3098             var index = from ? from.animationID : to.animationID;
3099             var indexKey = index.toString();
3100             if (!usedIndicesLookup[indexKey]) {
3101               usedIndicesLookup[indexKey] = true;
3102               preparedAnimations.push(animations[index]);
3103             }
3104             return;
3105           }
3106
3107           var fromAnimation = animations[from.animationID];
3108           var toAnimation = animations[to.animationID];
3109           var lookupKey = from.animationID.toString();
3110           if (!anchorGroups[lookupKey]) {
3111             var group = anchorGroups[lookupKey] = {
3112               structural: true,
3113               beforeStart: function() {
3114                 fromAnimation.beforeStart();
3115                 toAnimation.beforeStart();
3116               },
3117               close: function() {
3118                 fromAnimation.close();
3119                 toAnimation.close();
3120               },
3121               classes: cssClassesIntersection(fromAnimation.classes, toAnimation.classes),
3122               from: fromAnimation,
3123               to: toAnimation,
3124               anchors: [] // TODO(matsko): change to reference nodes
3125             };
3126
3127             // the anchor animations require that the from and to elements both have at least
3128             // one shared CSS class which effectively marries the two elements together to use
3129             // the same animation driver and to properly sequence the anchor animation.
3130             if (group.classes.length) {
3131               preparedAnimations.push(group);
3132             } else {
3133               preparedAnimations.push(fromAnimation);
3134               preparedAnimations.push(toAnimation);
3135             }
3136           }
3137
3138           anchorGroups[lookupKey].anchors.push({
3139             'out': from.element, 'in': to.element
3140           });
3141         });
3142
3143         return preparedAnimations;
3144       }
3145
3146       function cssClassesIntersection(a,b) {
3147         a = a.split(' ');
3148         b = b.split(' ');
3149         var matches = [];
3150
3151         for (var i = 0; i < a.length; i++) {
3152           var aa = a[i];
3153           if (aa.substring(0,3) === 'ng-') continue;
3154
3155           for (var j = 0; j < b.length; j++) {
3156             if (aa === b[j]) {
3157               matches.push(aa);
3158               break;
3159             }
3160           }
3161         }
3162
3163         return matches.join(' ');
3164       }
3165
3166       function invokeFirstDriver(animationDetails) {
3167         // we loop in reverse order since the more general drivers (like CSS and JS)
3168         // may attempt more elements, but custom drivers are more particular
3169         for (var i = drivers.length - 1; i >= 0; i--) {
3170           var driverName = drivers[i];
3171           if (!$injector.has(driverName)) continue; // TODO(matsko): remove this check
3172
3173           var factory = $injector.get(driverName);
3174           var driver = factory(animationDetails);
3175           if (driver) {
3176             return driver;
3177           }
3178         }
3179       }
3180
3181       function beforeStart() {
3182         element.addClass(NG_ANIMATE_CLASSNAME);
3183         if (tempClasses) {
3184           $$jqLite.addClass(element, tempClasses);
3185         }
3186         if (prepareClassName) {
3187           $$jqLite.removeClass(element, prepareClassName);
3188           prepareClassName = null;
3189         }
3190       }
3191
3192       function updateAnimationRunners(animation, newRunner) {
3193         if (animation.from && animation.to) {
3194           update(animation.from.element);
3195           update(animation.to.element);
3196         } else {
3197           update(animation.element);
3198         }
3199
3200         function update(element) {
3201           getRunner(element).setHost(newRunner);
3202         }
3203       }
3204
3205       function handleDestroyedElement() {
3206         var runner = getRunner(element);
3207         if (runner && (event !== 'leave' || !options.$$domOperationFired)) {
3208           runner.end();
3209         }
3210       }
3211
3212       function close(rejected) { // jshint ignore:line
3213         element.off('$destroy', handleDestroyedElement);
3214         removeRunner(element);
3215
3216         applyAnimationClasses(element, options);
3217         applyAnimationStyles(element, options);
3218         options.domOperation();
3219
3220         if (tempClasses) {
3221           $$jqLite.removeClass(element, tempClasses);
3222         }
3223
3224         element.removeClass(NG_ANIMATE_CLASSNAME);
3225         runner.complete(!rejected);
3226       }
3227     };
3228   }];
3229 }];
3230
3231 /**
3232  * @ngdoc directive
3233  * @name ngAnimateSwap
3234  * @restrict A
3235  * @scope
3236  *
3237  * @description
3238  *
3239  * ngAnimateSwap is a animation-oriented directive that allows for the container to
3240  * be removed and entered in whenever the associated expression changes. A
3241  * common usecase for this directive is a rotating banner component which
3242  * contains one image being present at a time. When the active image changes
3243  * then the old image will perform a `leave` animation and the new element
3244  * will be inserted via an `enter` animation.
3245  *
3246  * @example
3247  * <example name="ngAnimateSwap-directive" module="ngAnimateSwapExample"
3248  *          deps="angular-animate.js"
3249  *          animations="true" fixBase="true">
3250  *   <file name="index.html">
3251  *     <div class="container" ng-controller="AppCtrl">
3252  *       <div ng-animate-swap="number" class="cell swap-animation" ng-class="colorClass(number)">
3253  *         {{ number }}
3254  *       </div>
3255  *     </div>
3256  *   </file>
3257  *   <file name="script.js">
3258  *     angular.module('ngAnimateSwapExample', ['ngAnimate'])
3259  *       .controller('AppCtrl', ['$scope', '$interval', function($scope, $interval) {
3260  *         $scope.number = 0;
3261  *         $interval(function() {
3262  *           $scope.number++;
3263  *         }, 1000);
3264  *
3265  *         var colors = ['red','blue','green','yellow','orange'];
3266  *         $scope.colorClass = function(number) {
3267  *           return colors[number % colors.length];
3268  *         };
3269  *       }]);
3270  *   </file>
3271  *  <file name="animations.css">
3272  *  .container {
3273  *    height:250px;
3274  *    width:250px;
3275  *    position:relative;
3276  *    overflow:hidden;
3277  *    border:2px solid black;
3278  *  }
3279  *  .container .cell {
3280  *    font-size:150px;
3281  *    text-align:center;
3282  *    line-height:250px;
3283  *    position:absolute;
3284  *    top:0;
3285  *    left:0;
3286  *    right:0;
3287  *    border-bottom:2px solid black;
3288  *  }
3289  *  .swap-animation.ng-enter, .swap-animation.ng-leave {
3290  *    transition:0.5s linear all;
3291  *  }
3292  *  .swap-animation.ng-enter {
3293  *    top:-250px;
3294  *  }
3295  *  .swap-animation.ng-enter-active {
3296  *    top:0px;
3297  *  }
3298  *  .swap-animation.ng-leave {
3299  *    top:0px;
3300  *  }
3301  *  .swap-animation.ng-leave-active {
3302  *    top:250px;
3303  *  }
3304  *  .red { background:red; }
3305  *  .green { background:green; }
3306  *  .blue { background:blue; }
3307  *  .yellow { background:yellow; }
3308  *  .orange { background:orange; }
3309  *  </file>
3310  * </example>
3311  */
3312 var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $rootScope) {
3313   return {
3314     restrict: 'A',
3315     transclude: 'element',
3316     terminal: true,
3317     priority: 600, // we use 600 here to ensure that the directive is caught before others
3318     link: function(scope, $element, attrs, ctrl, $transclude) {
3319       var previousElement, previousScope;
3320       scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'], function(value) {
3321         if (previousElement) {
3322           $animate.leave(previousElement);
3323         }
3324         if (previousScope) {
3325           previousScope.$destroy();
3326           previousScope = null;
3327         }
3328         if (value || value === 0) {
3329           previousScope = scope.$new();
3330           $transclude(previousScope, function(element) {
3331             previousElement = element;
3332             $animate.enter(element, null, $element);
3333           });
3334         }
3335       });
3336     }
3337   };
3338 }];
3339
3340 /* global angularAnimateModule: true,
3341
3342    ngAnimateSwapDirective,
3343    $$AnimateAsyncRunFactory,
3344    $$rAFSchedulerFactory,
3345    $$AnimateChildrenDirective,
3346    $$AnimateQueueProvider,
3347    $$AnimationProvider,
3348    $AnimateCssProvider,
3349    $$AnimateCssDriverProvider,
3350    $$AnimateJsProvider,
3351    $$AnimateJsDriverProvider,
3352 */
3353
3354 /**
3355  * @ngdoc module
3356  * @name ngAnimate
3357  * @description
3358  *
3359  * The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via
3360  * callback hooks. Animations are not enabled by default, however, by including `ngAnimate` the animation hooks are enabled for an Angular app.
3361  *
3362  * <div doc-module-components="ngAnimate"></div>
3363  *
3364  * # Usage
3365  * Simply put, there are two ways to make use of animations when ngAnimate is used: by using **CSS** and **JavaScript**. The former works purely based
3366  * using CSS (by using matching CSS selectors/styles) and the latter triggers animations that are registered via `module.animation()`. For
3367  * both CSS and JS animations the sole requirement is to have a matching `CSS class` that exists both in the registered animation and within
3368  * the HTML element that the animation will be triggered on.
3369  *
3370  * ## Directive Support
3371  * The following directives are "animation aware":
3372  *
3373  * | Directive                                                                                                | Supported Animations                                                     |
3374  * |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
3375  * | {@link ng.directive:ngRepeat#animations ngRepeat}                                                        | enter, leave and move                                                    |
3376  * | {@link ngRoute.directive:ngView#animations ngView}                                                       | enter and leave                                                          |
3377  * | {@link ng.directive:ngInclude#animations ngInclude}                                                      | enter and leave                                                          |
3378  * | {@link ng.directive:ngSwitch#animations ngSwitch}                                                        | enter and leave                                                          |
3379  * | {@link ng.directive:ngIf#animations ngIf}                                                                | enter and leave                                                          |
3380  * | {@link ng.directive:ngClass#animations ngClass}                                                          | add and remove (the CSS class(es) present)                               |
3381  * | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide}            | add and remove (the ng-hide class value)                                 |
3382  * | {@link ng.directive:form#animation-hooks form} & {@link ng.directive:ngModel#animation-hooks ngModel}    | add and remove (dirty, pristine, valid, invalid & all other validations) |
3383  * | {@link module:ngMessages#animations ngMessages}                                                          | add and remove (ng-active & ng-inactive)                                 |
3384  * | {@link module:ngMessages#animations ngMessage}                                                           | enter and leave                                                          |
3385  *
3386  * (More information can be found by visiting each the documentation associated with each directive.)
3387  *
3388  * ## CSS-based Animations
3389  *
3390  * CSS-based animations with ngAnimate are unique since they require no JavaScript code at all. By using a CSS class that we reference between our HTML
3391  * and CSS code we can create an animation that will be picked up by Angular when an the underlying directive performs an operation.
3392  *
3393  * The example below shows how an `enter` animation can be made possible on an element using `ng-if`:
3394  *
3395  * ```html
3396  * <div ng-if="bool" class="fade">
3397  *    Fade me in out
3398  * </div>
3399  * <button ng-click="bool=true">Fade In!</button>
3400  * <button ng-click="bool=false">Fade Out!</button>
3401  * ```
3402  *
3403  * Notice the CSS class **fade**? We can now create the CSS transition code that references this class:
3404  *
3405  * ```css
3406  * /&#42; The starting CSS styles for the enter animation &#42;/
3407  * .fade.ng-enter {
3408  *   transition:0.5s linear all;
3409  *   opacity:0;
3410  * }
3411  *
3412  * /&#42; The finishing CSS styles for the enter animation &#42;/
3413  * .fade.ng-enter.ng-enter-active {
3414  *   opacity:1;
3415  * }
3416  * ```
3417  *
3418  * The key thing to remember here is that, depending on the animation event (which each of the directives above trigger depending on what's going on) two
3419  * generated CSS classes will be applied to the element; in the example above we have `.ng-enter` and `.ng-enter-active`. For CSS transitions, the transition
3420  * code **must** be defined within the starting CSS class (in this case `.ng-enter`). The destination class is what the transition will animate towards.
3421  *
3422  * If for example we wanted to create animations for `leave` and `move` (ngRepeat triggers move) then we can do so using the same CSS naming conventions:
3423  *
3424  * ```css
3425  * /&#42; now the element will fade out before it is removed from the DOM &#42;/
3426  * .fade.ng-leave {
3427  *   transition:0.5s linear all;
3428  *   opacity:1;
3429  * }
3430  * .fade.ng-leave.ng-leave-active {
3431  *   opacity:0;
3432  * }
3433  * ```
3434  *
3435  * We can also make use of **CSS Keyframes** by referencing the keyframe animation within the starting CSS class:
3436  *
3437  * ```css
3438  * /&#42; there is no need to define anything inside of the destination
3439  * CSS class since the keyframe will take charge of the animation &#42;/
3440  * .fade.ng-leave {
3441  *   animation: my_fade_animation 0.5s linear;
3442  *   -webkit-animation: my_fade_animation 0.5s linear;
3443  * }
3444  *
3445  * @keyframes my_fade_animation {
3446  *   from { opacity:1; }
3447  *   to { opacity:0; }
3448  * }
3449  *
3450  * @-webkit-keyframes my_fade_animation {
3451  *   from { opacity:1; }
3452  *   to { opacity:0; }
3453  * }
3454  * ```
3455  *
3456  * Feel free also mix transitions and keyframes together as well as any other CSS classes on the same element.
3457  *
3458  * ### CSS Class-based Animations
3459  *
3460  * Class-based animations (animations that are triggered via `ngClass`, `ngShow`, `ngHide` and some other directives) have a slightly different
3461  * naming convention. Class-based animations are basic enough that a standard transition or keyframe can be referenced on the class being added
3462  * and removed.
3463  *
3464  * For example if we wanted to do a CSS animation for `ngHide` then we place an animation on the `.ng-hide` CSS class:
3465  *
3466  * ```html
3467  * <div ng-show="bool" class="fade">
3468  *   Show and hide me
3469  * </div>
3470  * <button ng-click="bool=true">Toggle</button>
3471  *
3472  * <style>
3473  * .fade.ng-hide {
3474  *   transition:0.5s linear all;
3475  *   opacity:0;
3476  * }
3477  * </style>
3478  * ```
3479  *
3480  * All that is going on here with ngShow/ngHide behind the scenes is the `.ng-hide` class is added/removed (when the hidden state is valid). Since
3481  * ngShow and ngHide are animation aware then we can match up a transition and ngAnimate handles the rest.
3482  *
3483  * In addition the addition and removal of the CSS class, ngAnimate also provides two helper methods that we can use to further decorate the animation
3484  * with CSS styles.
3485  *
3486  * ```html
3487  * <div ng-class="{on:onOff}" class="highlight">
3488  *   Highlight this box
3489  * </div>
3490  * <button ng-click="onOff=!onOff">Toggle</button>
3491  *
3492  * <style>
3493  * .highlight {
3494  *   transition:0.5s linear all;
3495  * }
3496  * .highlight.on-add {
3497  *   background:white;
3498  * }
3499  * .highlight.on {
3500  *   background:yellow;
3501  * }
3502  * .highlight.on-remove {
3503  *   background:black;
3504  * }
3505  * </style>
3506  * ```
3507  *
3508  * We can also make use of CSS keyframes by placing them within the CSS classes.
3509  *
3510  *
3511  * ### CSS Staggering Animations
3512  * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a
3513  * curtain-like effect. The ngAnimate module (versions >=1.2) supports staggering animations and the stagger effect can be
3514  * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for
3515  * the animation. The style property expected within the stagger class can either be a **transition-delay** or an
3516  * **animation-delay** property (or both if your animation contains both transitions and keyframe animations).
3517  *
3518  * ```css
3519  * .my-animation.ng-enter {
3520  *   /&#42; standard transition code &#42;/
3521  *   transition: 1s linear all;
3522  *   opacity:0;
3523  * }
3524  * .my-animation.ng-enter-stagger {
3525  *   /&#42; this will have a 100ms delay between each successive leave animation &#42;/
3526  *   transition-delay: 0.1s;
3527  *
3528  *   /&#42; As of 1.4.4, this must always be set: it signals ngAnimate
3529  *     to not accidentally inherit a delay property from another CSS class &#42;/
3530  *   transition-duration: 0s;
3531  * }
3532  * .my-animation.ng-enter.ng-enter-active {
3533  *   /&#42; standard transition styles &#42;/
3534  *   opacity:1;
3535  * }
3536  * ```
3537  *
3538  * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations
3539  * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this
3540  * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation
3541  * will also be reset if one or more animation frames have passed since the multiple calls to `$animate` were fired.
3542  *
3543  * The following code will issue the **ng-leave-stagger** event on the element provided:
3544  *
3545  * ```js
3546  * var kids = parent.children();
3547  *
3548  * $animate.leave(kids[0]); //stagger index=0
3549  * $animate.leave(kids[1]); //stagger index=1
3550  * $animate.leave(kids[2]); //stagger index=2
3551  * $animate.leave(kids[3]); //stagger index=3
3552  * $animate.leave(kids[4]); //stagger index=4
3553  *
3554  * window.requestAnimationFrame(function() {
3555  *   //stagger has reset itself
3556  *   $animate.leave(kids[5]); //stagger index=0
3557  *   $animate.leave(kids[6]); //stagger index=1
3558  *
3559  *   $scope.$digest();
3560  * });
3561  * ```
3562  *
3563  * Stagger animations are currently only supported within CSS-defined animations.
3564  *
3565  * ### The `ng-animate` CSS class
3566  *
3567  * When ngAnimate is animating an element it will apply the `ng-animate` CSS class to the element for the duration of the animation.
3568  * This is a temporary CSS class and it will be removed once the animation is over (for both JavaScript and CSS-based animations).
3569  *
3570  * Therefore, animations can be applied to an element using this temporary class directly via CSS.
3571  *
3572  * ```css
3573  * .zipper.ng-animate {
3574  *   transition:0.5s linear all;
3575  * }
3576  * .zipper.ng-enter {
3577  *   opacity:0;
3578  * }
3579  * .zipper.ng-enter.ng-enter-active {
3580  *   opacity:1;
3581  * }
3582  * .zipper.ng-leave {
3583  *   opacity:1;
3584  * }
3585  * .zipper.ng-leave.ng-leave-active {
3586  *   opacity:0;
3587  * }
3588  * ```
3589  *
3590  * (Note that the `ng-animate` CSS class is reserved and it cannot be applied on an element directly since ngAnimate will always remove
3591  * the CSS class once an animation has completed.)
3592  *
3593  *
3594  * ### The `ng-[event]-prepare` class
3595  *
3596  * This is a special class that can be used to prevent unwanted flickering / flash of content before
3597  * the actual animation starts. The class is added as soon as an animation is initialized, but removed
3598  * before the actual animation starts (after waiting for a $digest).
3599  * It is also only added for *structural* animations (`enter`, `move`, and `leave`).
3600  *
3601  * In practice, flickering can appear when nesting elements with structural animations such as `ngIf`
3602  * into elements that have class-based animations such as `ngClass`.
3603  *
3604  * ```html
3605  * <div ng-class="{red: myProp}">
3606  *   <div ng-class="{blue: myProp}">
3607  *     <div class="message" ng-if="myProp"></div>
3608  *   </div>
3609  * </div>
3610  * ```
3611  *
3612  * It is possible that during the `enter` animation, the `.message` div will be briefly visible before it starts animating.
3613  * In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts:
3614  *
3615  * ```css
3616  * .message.ng-enter-prepare {
3617  *   opacity: 0;
3618  * }
3619  *
3620  * ```
3621  *
3622  * ## JavaScript-based Animations
3623  *
3624  * ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared
3625  * CSS class that is referenced in our HTML code) but in addition we need to register the JavaScript animation on the module. By making use of the
3626  * `module.animation()` module function we can register the animation.
3627  *
3628  * Let's see an example of a enter/leave animation using `ngRepeat`:
3629  *
3630  * ```html
3631  * <div ng-repeat="item in items" class="slide">
3632  *   {{ item }}
3633  * </div>
3634  * ```
3635  *
3636  * See the **slide** CSS class? Let's use that class to define an animation that we'll structure in our module code by using `module.animation`:
3637  *
3638  * ```js
3639  * myModule.animation('.slide', [function() {
3640  *   return {
3641  *     // make note that other events (like addClass/removeClass)
3642  *     // have different function input parameters
3643  *     enter: function(element, doneFn) {
3644  *       jQuery(element).fadeIn(1000, doneFn);
3645  *
3646  *       // remember to call doneFn so that angular
3647  *       // knows that the animation has concluded
3648  *     },
3649  *
3650  *     move: function(element, doneFn) {
3651  *       jQuery(element).fadeIn(1000, doneFn);
3652  *     },
3653  *
3654  *     leave: function(element, doneFn) {
3655  *       jQuery(element).fadeOut(1000, doneFn);
3656  *     }
3657  *   }
3658  * }]);
3659  * ```
3660  *
3661  * The nice thing about JS-based animations is that we can inject other services and make use of advanced animation libraries such as
3662  * greensock.js and velocity.js.
3663  *
3664  * If our animation code class-based (meaning that something like `ngClass`, `ngHide` and `ngShow` triggers it) then we can still define
3665  * our animations inside of the same registered animation, however, the function input arguments are a bit different:
3666  *
3667  * ```html
3668  * <div ng-class="color" class="colorful">
3669  *   this box is moody
3670  * </div>
3671  * <button ng-click="color='red'">Change to red</button>
3672  * <button ng-click="color='blue'">Change to blue</button>
3673  * <button ng-click="color='green'">Change to green</button>
3674  * ```
3675  *
3676  * ```js
3677  * myModule.animation('.colorful', [function() {
3678  *   return {
3679  *     addClass: function(element, className, doneFn) {
3680  *       // do some cool animation and call the doneFn
3681  *     },
3682  *     removeClass: function(element, className, doneFn) {
3683  *       // do some cool animation and call the doneFn
3684  *     },
3685  *     setClass: function(element, addedClass, removedClass, doneFn) {
3686  *       // do some cool animation and call the doneFn
3687  *     }
3688  *   }
3689  * }]);
3690  * ```
3691  *
3692  * ## CSS + JS Animations Together
3693  *
3694  * AngularJS 1.4 and higher has taken steps to make the amalgamation of CSS and JS animations more flexible. However, unlike earlier versions of Angular,
3695  * defining CSS and JS animations to work off of the same CSS class will not work anymore. Therefore the example below will only result in **JS animations taking
3696  * charge of the animation**:
3697  *
3698  * ```html
3699  * <div ng-if="bool" class="slide">
3700  *   Slide in and out
3701  * </div>
3702  * ```
3703  *
3704  * ```js
3705  * myModule.animation('.slide', [function() {
3706  *   return {
3707  *     enter: function(element, doneFn) {
3708  *       jQuery(element).slideIn(1000, doneFn);
3709  *     }
3710  *   }
3711  * }]);
3712  * ```
3713  *
3714  * ```css
3715  * .slide.ng-enter {
3716  *   transition:0.5s linear all;
3717  *   transform:translateY(-100px);
3718  * }
3719  * .slide.ng-enter.ng-enter-active {
3720  *   transform:translateY(0);
3721  * }
3722  * ```
3723  *
3724  * Does this mean that CSS and JS animations cannot be used together? Do JS-based animations always have higher priority? We can make up for the
3725  * lack of CSS animations by using the `$animateCss` service to trigger our own tweaked-out, CSS-based animations directly from
3726  * our own JS-based animation code:
3727  *
3728  * ```js
3729  * myModule.animation('.slide', ['$animateCss', function($animateCss) {
3730  *   return {
3731  *     enter: function(element) {
3732 *        // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`.
3733  *       return $animateCss(element, {
3734  *         event: 'enter',
3735  *         structural: true
3736  *       });
3737  *     }
3738  *   }
3739  * }]);
3740  * ```
3741  *
3742  * The nice thing here is that we can save bandwidth by sticking to our CSS-based animation code and we don't need to rely on a 3rd-party animation framework.
3743  *
3744  * The `$animateCss` service is very powerful since we can feed in all kinds of extra properties that will be evaluated and fed into a CSS transition or
3745  * keyframe animation. For example if we wanted to animate the height of an element while adding and removing classes then we can do so by providing that
3746  * data into `$animateCss` directly:
3747  *
3748  * ```js
3749  * myModule.animation('.slide', ['$animateCss', function($animateCss) {
3750  *   return {
3751  *     enter: function(element) {
3752  *       return $animateCss(element, {
3753  *         event: 'enter',
3754  *         structural: true,
3755  *         addClass: 'maroon-setting',
3756  *         from: { height:0 },
3757  *         to: { height: 200 }
3758  *       });
3759  *     }
3760  *   }
3761  * }]);
3762  * ```
3763  *
3764  * Now we can fill in the rest via our transition CSS code:
3765  *
3766  * ```css
3767  * /&#42; the transition tells ngAnimate to make the animation happen &#42;/
3768  * .slide.ng-enter { transition:0.5s linear all; }
3769  *
3770  * /&#42; this extra CSS class will be absorbed into the transition
3771  * since the $animateCss code is adding the class &#42;/
3772  * .maroon-setting { background:red; }
3773  * ```
3774  *
3775  * And `$animateCss` will figure out the rest. Just make sure to have the `done()` callback fire the `doneFn` function to signal when the animation is over.
3776  *
3777  * To learn more about what's possible be sure to visit the {@link ngAnimate.$animateCss $animateCss service}.
3778  *
3779  * ## Animation Anchoring (via `ng-animate-ref`)
3780  *
3781  * ngAnimate in AngularJS 1.4 comes packed with the ability to cross-animate elements between
3782  * structural areas of an application (like views) by pairing up elements using an attribute
3783  * called `ng-animate-ref`.
3784  *
3785  * Let's say for example we have two views that are managed by `ng-view` and we want to show
3786  * that there is a relationship between two components situated in within these views. By using the
3787  * `ng-animate-ref` attribute we can identify that the two components are paired together and we
3788  * can then attach an animation, which is triggered when the view changes.
3789  *
3790  * Say for example we have the following template code:
3791  *
3792  * ```html
3793  * <!-- index.html -->
3794  * <div ng-view class="view-animation">
3795  * </div>
3796  *
3797  * <!-- home.html -->
3798  * <a href="#/banner-page">
3799  *   <img src="./banner.jpg" class="banner" ng-animate-ref="banner">
3800  * </a>
3801  *
3802  * <!-- banner-page.html -->
3803  * <img src="./banner.jpg" class="banner" ng-animate-ref="banner">
3804  * ```
3805  *
3806  * Now, when the view changes (once the link is clicked), ngAnimate will examine the
3807  * HTML contents to see if there is a match reference between any components in the view
3808  * that is leaving and the view that is entering. It will scan both the view which is being
3809  * removed (leave) and inserted (enter) to see if there are any paired DOM elements that
3810  * contain a matching ref value.
3811  *
3812  * The two images match since they share the same ref value. ngAnimate will now create a
3813  * transport element (which is a clone of the first image element) and it will then attempt
3814  * to animate to the position of the second image element in the next view. For the animation to
3815  * work a special CSS class called `ng-anchor` will be added to the transported element.
3816  *
3817  * We can now attach a transition onto the `.banner.ng-anchor` CSS class and then
3818  * ngAnimate will handle the entire transition for us as well as the addition and removal of
3819  * any changes of CSS classes between the elements:
3820  *
3821  * ```css
3822  * .banner.ng-anchor {
3823  *   /&#42; this animation will last for 1 second since there are
3824  *          two phases to the animation (an `in` and an `out` phase) &#42;/
3825  *   transition:0.5s linear all;
3826  * }
3827  * ```
3828  *
3829  * We also **must** include animations for the views that are being entered and removed
3830  * (otherwise anchoring wouldn't be possible since the new view would be inserted right away).
3831  *
3832  * ```css
3833  * .view-animation.ng-enter, .view-animation.ng-leave {
3834  *   transition:0.5s linear all;
3835  *   position:fixed;
3836  *   left:0;
3837  *   top:0;
3838  *   width:100%;
3839  * }
3840  * .view-animation.ng-enter {
3841  *   transform:translateX(100%);
3842  * }
3843  * .view-animation.ng-leave,
3844  * .view-animation.ng-enter.ng-enter-active {
3845  *   transform:translateX(0%);
3846  * }
3847  * .view-animation.ng-leave.ng-leave-active {
3848  *   transform:translateX(-100%);
3849  * }
3850  * ```
3851  *
3852  * Now we can jump back to the anchor animation. When the animation happens, there are two stages that occur:
3853  * an `out` and an `in` stage. The `out` stage happens first and that is when the element is animated away
3854  * from its origin. Once that animation is over then the `in` stage occurs which animates the
3855  * element to its destination. The reason why there are two animations is to give enough time
3856  * for the enter animation on the new element to be ready.
3857  *
3858  * The example above sets up a transition for both the in and out phases, but we can also target the out or
3859  * in phases directly via `ng-anchor-out` and `ng-anchor-in`.
3860  *
3861  * ```css
3862  * .banner.ng-anchor-out {
3863  *   transition: 0.5s linear all;
3864  *
3865  *   /&#42; the scale will be applied during the out animation,
3866  *          but will be animated away when the in animation runs &#42;/
3867  *   transform: scale(1.2);
3868  * }
3869  *
3870  * .banner.ng-anchor-in {
3871  *   transition: 1s linear all;
3872  * }
3873  * ```
3874  *
3875  *
3876  *
3877  *
3878  * ### Anchoring Demo
3879  *
3880   <example module="anchoringExample"
3881            name="anchoringExample"
3882            id="anchoringExample"
3883            deps="angular-animate.js;angular-route.js"
3884            animations="true">
3885     <file name="index.html">
3886       <a href="#/">Home</a>
3887       <hr />
3888       <div class="view-container">
3889         <div ng-view class="view"></div>
3890       </div>
3891     </file>
3892     <file name="script.js">
3893       angular.module('anchoringExample', ['ngAnimate', 'ngRoute'])
3894         .config(['$routeProvider', function($routeProvider) {
3895           $routeProvider.when('/', {
3896             templateUrl: 'home.html',
3897             controller: 'HomeController as home'
3898           });
3899           $routeProvider.when('/profile/:id', {
3900             templateUrl: 'profile.html',
3901             controller: 'ProfileController as profile'
3902           });
3903         }])
3904         .run(['$rootScope', function($rootScope) {
3905           $rootScope.records = [
3906             { id:1, title: "Miss Beulah Roob" },
3907             { id:2, title: "Trent Morissette" },
3908             { id:3, title: "Miss Ava Pouros" },
3909             { id:4, title: "Rod Pouros" },
3910             { id:5, title: "Abdul Rice" },
3911             { id:6, title: "Laurie Rutherford Sr." },
3912             { id:7, title: "Nakia McLaughlin" },
3913             { id:8, title: "Jordon Blanda DVM" },
3914             { id:9, title: "Rhoda Hand" },
3915             { id:10, title: "Alexandrea Sauer" }
3916           ];
3917         }])
3918         .controller('HomeController', [function() {
3919           //empty
3920         }])
3921         .controller('ProfileController', ['$rootScope', '$routeParams', function($rootScope, $routeParams) {
3922           var index = parseInt($routeParams.id, 10);
3923           var record = $rootScope.records[index - 1];
3924
3925           this.title = record.title;
3926           this.id = record.id;
3927         }]);
3928     </file>
3929     <file name="home.html">
3930       <h2>Welcome to the home page</h1>
3931       <p>Please click on an element</p>
3932       <a class="record"
3933          ng-href="#/profile/{{ record.id }}"
3934          ng-animate-ref="{{ record.id }}"
3935          ng-repeat="record in records">
3936         {{ record.title }}
3937       </a>
3938     </file>
3939     <file name="profile.html">
3940       <div class="profile record" ng-animate-ref="{{ profile.id }}">
3941         {{ profile.title }}
3942       </div>
3943     </file>
3944     <file name="animations.css">
3945       .record {
3946         display:block;
3947         font-size:20px;
3948       }
3949       .profile {
3950         background:black;
3951         color:white;
3952         font-size:100px;
3953       }
3954       .view-container {
3955         position:relative;
3956       }
3957       .view-container > .view.ng-animate {
3958         position:absolute;
3959         top:0;
3960         left:0;
3961         width:100%;
3962         min-height:500px;
3963       }
3964       .view.ng-enter, .view.ng-leave,
3965       .record.ng-anchor {
3966         transition:0.5s linear all;
3967       }
3968       .view.ng-enter {
3969         transform:translateX(100%);
3970       }
3971       .view.ng-enter.ng-enter-active, .view.ng-leave {
3972         transform:translateX(0%);
3973       }
3974       .view.ng-leave.ng-leave-active {
3975         transform:translateX(-100%);
3976       }
3977       .record.ng-anchor-out {
3978         background:red;
3979       }
3980     </file>
3981   </example>
3982  *
3983  * ### How is the element transported?
3984  *
3985  * When an anchor animation occurs, ngAnimate will clone the starting element and position it exactly where the starting
3986  * element is located on screen via absolute positioning. The cloned element will be placed inside of the root element
3987  * of the application (where ng-app was defined) and all of the CSS classes of the starting element will be applied. The
3988  * element will then animate into the `out` and `in` animations and will eventually reach the coordinates and match
3989  * the dimensions of the destination element. During the entire animation a CSS class of `.ng-animate-shim` will be applied
3990  * to both the starting and destination elements in order to hide them from being visible (the CSS styling for the class
3991  * is: `visibility:hidden`). Once the anchor reaches its destination then it will be removed and the destination element
3992  * will become visible since the shim class will be removed.
3993  *
3994  * ### How is the morphing handled?
3995  *
3996  * CSS Anchoring relies on transitions and keyframes and the internal code is intelligent enough to figure out
3997  * what CSS classes differ between the starting element and the destination element. These different CSS classes
3998  * will be added/removed on the anchor element and a transition will be applied (the transition that is provided
3999  * in the anchor class). Long story short, ngAnimate will figure out what classes to add and remove which will
4000  * make the transition of the element as smooth and automatic as possible. Be sure to use simple CSS classes that
4001  * do not rely on DOM nesting structure so that the anchor element appears the same as the starting element (since
4002  * the cloned element is placed inside of root element which is likely close to the body element).
4003  *
4004  * Note that if the root element is on the `<html>` element then the cloned node will be placed inside of body.
4005  *
4006  *
4007  * ## Using $animate in your directive code
4008  *
4009  * So far we've explored how to feed in animations into an Angular application, but how do we trigger animations within our own directives in our application?
4010  * By injecting the `$animate` service into our directive code, we can trigger structural and class-based hooks which can then be consumed by animations. Let's
4011  * imagine we have a greeting box that shows and hides itself when the data changes
4012  *
4013  * ```html
4014  * <greeting-box active="onOrOff">Hi there</greeting-box>
4015  * ```
4016  *
4017  * ```js
4018  * ngModule.directive('greetingBox', ['$animate', function($animate) {
4019  *   return function(scope, element, attrs) {
4020  *     attrs.$observe('active', function(value) {
4021  *       value ? $animate.addClass(element, 'on') : $animate.removeClass(element, 'on');
4022  *     });
4023  *   });
4024  * }]);
4025  * ```
4026  *
4027  * Now the `on` CSS class is added and removed on the greeting box component. Now if we add a CSS class on top of the greeting box element
4028  * in our HTML code then we can trigger a CSS or JS animation to happen.
4029  *
4030  * ```css
4031  * /&#42; normally we would create a CSS class to reference on the element &#42;/
4032  * greeting-box.on { transition:0.5s linear all; background:green; color:white; }
4033  * ```
4034  *
4035  * The `$animate` service contains a variety of other methods like `enter`, `leave`, `animate` and `setClass`. To learn more about what's
4036  * possible be sure to visit the {@link ng.$animate $animate service API page}.
4037  *
4038  *
4039  * ### Preventing Collisions With Third Party Libraries
4040  *
4041  * Some third-party frameworks place animation duration defaults across many element or className
4042  * selectors in order to make their code small and reuseable. This can lead to issues with ngAnimate, which
4043  * is expecting actual animations on these elements and has to wait for their completion.
4044  *
4045  * You can prevent this unwanted behavior by using a prefix on all your animation classes:
4046  *
4047  * ```css
4048  * /&#42; prefixed with animate- &#42;/
4049  * .animate-fade-add.animate-fade-add-active {
4050  *   transition:1s linear all;
4051  *   opacity:0;
4052  * }
4053  * ```
4054  *
4055  * You then configure `$animate` to enforce this prefix:
4056  *
4057  * ```js
4058  * $animateProvider.classNameFilter(/animate-/);
4059  * ```
4060  *
4061  * This also may provide your application with a speed boost since only specific elements containing CSS class prefix
4062  * will be evaluated for animation when any DOM changes occur in the application.
4063  *
4064  * ## Callbacks and Promises
4065  *
4066  * When `$animate` is called it returns a promise that can be used to capture when the animation has ended. Therefore if we were to trigger
4067  * an animation (within our directive code) then we can continue performing directive and scope related activities after the animation has
4068  * ended by chaining onto the returned promise that animation method returns.
4069  *
4070  * ```js
4071  * // somewhere within the depths of the directive
4072  * $animate.enter(element, parent).then(function() {
4073  *   //the animation has completed
4074  * });
4075  * ```
4076  *
4077  * (Note that earlier versions of Angular prior to v1.4 required the promise code to be wrapped using `$scope.$apply(...)`. This is not the case
4078  * anymore.)
4079  *
4080  * In addition to the animation promise, we can also make use of animation-related callbacks within our directives and controller code by registering
4081  * an event listener using the `$animate` service. Let's say for example that an animation was triggered on our view
4082  * routing controller to hook into that:
4083  *
4084  * ```js
4085  * ngModule.controller('HomePageController', ['$animate', function($animate) {
4086  *   $animate.on('enter', ngViewElement, function(element) {
4087  *     // the animation for this route has completed
4088  *   }]);
4089  * }])
4090  * ```
4091  *
4092  * (Note that you will need to trigger a digest within the callback to get angular to notice any scope-related changes.)
4093  */
4094
4095 /**
4096  * @ngdoc service
4097  * @name $animate
4098  * @kind object
4099  *
4100  * @description
4101  * The ngAnimate `$animate` service documentation is the same for the core `$animate` service.
4102  *
4103  * Click here {@link ng.$animate to learn more about animations with `$animate`}.
4104  */
4105 angular.module('ngAnimate', [])
4106   .directive('ngAnimateSwap', ngAnimateSwapDirective)
4107
4108   .directive('ngAnimateChildren', $$AnimateChildrenDirective)
4109   .factory('$$rAFScheduler', $$rAFSchedulerFactory)
4110
4111   .provider('$$animateQueue', $$AnimateQueueProvider)
4112   .provider('$$animation', $$AnimationProvider)
4113
4114   .provider('$animateCss', $AnimateCssProvider)
4115   .provider('$$animateCssDriver', $$AnimateCssDriverProvider)
4116
4117   .provider('$$animateJs', $$AnimateJsProvider)
4118   .provider('$$animateJsDriver', $$AnimateJsDriverProvider);
4119
4120
4121 })(window, window.angular);