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