PageSourceSearch

https://www.mfcf.com/js/angular/angular-animate.js?sv=8.6.3.1

js mfcf.com collected 2026-09-24 12:41:49 UTC 149,698 bytes, 4,010 lines download raw bytes

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

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.