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