PageSourceSearch

https://www.matatransit.com/cms/includes/js/angular-animate.js?Gui…d2hgCvu5LEM0P%2FoYLfYABRMVRNlFfE7TQT7apNl9o

js matatransit.com collected 2026-09-24 09:56:09 UTC 153,561 bytes, 4,115 lines download raw bytes

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

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