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