PageSourceSearch

https://zeti.co/static/angular-animate/angular-animate.js

js zeti.co collected 2026-10-02 00:05:34 UTC 151,136 bytes, 4,154 lines download raw bytes

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