text: 'use strict';
  
  var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationProvider) {
    $$animationProvider.drivers.push('$$animateCssDriver');
  
    var NG_ANIMATE_SHIM_CLASS_NAME = 'ng-animate-shim';
    var NG_ANIMATE_ANCHOR_CLASS_NAME = 'ng-anchor';
  
    var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out';
    var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in';
  
    this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$$body', '$sniffer', '$$jqLite',
         function($animateCss,   $rootScope,   $$AnimateRunner,   $rootElement,   $$body,   $sniffer,   $$jqLite) {
  
      // only browsers that support these properties can render animations
      if (!$sniffer.animations && !$sniffer.transitions) return noop;
  
      var bodyNode = getDomNode($$body);
      var rootNode = getDomNode($rootElement);
  
      var rootBodyElement = jqLite(bodyNode.parentNode === rootNode ? bodyNode : rootNode);
  
      var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
  
      return function initDriverFn(animationDetails, onBeforeClassesAppliedCb) {
        return animationDetails.from && animationDetails.to
            ? prepareFromToAnchorAnimation(animationDetails.from,
                                           animationDetails.to,
                                           animationDetails.classes,
                                           animationDetails.anchors)
            : prepareRegularAnimation(animationDetails, onBeforeClassesAppliedCb);
      };
  
      function filterCssClasses(classes) {
        //remove all the `ng-` stuff
        return classes.replace(/\bng-\S+\b/g, '');
      }
  
      function getUniqueValues(a, b) {
        if (isString(a)) a = a.split(' ');
        if (isString(b)) b = b.split(' ');
        return a.filter(function(val) {
          return b.indexOf(val) === -1;
        }).join(' ');
      }
  
      function prepareAnchoredAnimation(classes, outAnchor, inAnchor) {
        var clone = jqLite(getDomNode(outAnchor).cloneNode(true));
        var startingClasses = filterCssClasses(getClassVal(clone));
  
        outAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);
        inAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);
  
        clone.addClass(NG_ANIMATE_ANCHOR_CLASS_NAME);
  
        rootBodyElement.append(clone);
  
        var animatorIn, animatorOut = prepareOutAnimation();
  
        // the user may not end up using the `out` animation and
        // only making use of the `in` animation or vice-versa.
        // In either case we should allow this and not assume the
        // animation is over unless both animations are not used.
        if (!animatorOut) {
          animatorIn = prepareInAnimation();
          if (!animatorIn) {
            return end();
          }
        }
  
        var startingAnimator = animatorOut || animatorIn;
  
        return {
          start: function() {
            var runner;
  
            var currentAnimation = startingAnimator.start();
            currentAnimation.done(function() {
              currentAnimation = null;
              if (!animatorIn) {
                animatorIn = prepareInAnimation();
                if (animatorIn) {
                  currentAnimation = animatorIn.start();
                  currentAnimation.done(function() {
                    currentAnimation = null;
                    end();
                    runner.complete();
                  });
                  return currentAnimation;
                }
              }
              // in the event that there is no `in` animation
              end();
              runner.complete();
            });
  
            runner = new $$AnimateRunner({
              end: endFn,
              cancel: endFn
            });
  
            return runner;
  
            function endFn() {
              if (currentAnimation) {
                currentAnimation.end();
              }
            }
          }
        };
  
        function calculateAnchorStyles(anchor) {
          var styles = {};
  
          var coords = getDomNode(anchor).getBoundingClientRect();
  
          // we iterate directly since safari messes up and doesn't return
          // all the keys for the coods object when iterated
          forEach(['width','height','top','left'], function(key) {
            var value = coords[key];
            switch (key) {
              case 'top':
                value += bodyNode.scrollTop;
                break;
              case 'left':
                value += bodyNode.scrollLeft;
                break;
            }
            styles[key] = Math.floor(value) + 'px';
          });
          return styles;
        }
  
        function prepareOutAnimation() {
          var animator = $animateCss(clone, {
            addClass: NG_OUT_ANCHOR_CLASS_NAME,
            delay: true,
            from: calculateAnchorStyles(outAnchor)
          });
  
          // read the comment within `prepareRegularAnimation` to understand
          // why this check is necessary
          return animator.$$willAnimate ? animator : null;
        }
  
        function getClassVal(element) {
          return element.attr('class') || '';
        }
  
        function prepareInAnimation() {
          var endingClasses = filterCssClasses(getClassVal(inAnchor));
          var toAdd = getUniqueValues(endingClasses, startingClasses);
          var toRemove = getUniqueValues(startingClasses, endingClasses);
  
          var animator = $animateCss(clone, {
            to: calculateAnchorStyles(inAnchor),
            addClass: NG_IN_ANCHOR_CLASS_NAME + ' ' + toAdd,
            removeClass: NG_OUT_ANCHOR_CLASS_NAME + ' ' + toRemove,
            delay: true
          });
  
          // read the comment within `prepareRegularAnimation` to understand
          // why this check is necessary
          return animator.$$willAnimate ? animator : null;
        }
  
        function end() {
          clone.remove();
          outAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);
          inAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);
        }
      }
  
      function prepareFromToAnchorAnimation(from, to, classes, anchors) {
        var fromAnimation = prepareRegularAnimation(from, noop);
        var toAnimation = prepareRegularAnimation(to, noop);
  
        var anchorAnimations = [];
        forEach(anchors, function(anchor) {
          var outElement = anchor['out'];
          var inElement = anchor['in'];
          var animator = prepareAnchoredAnimation(classes, outElement, inElement);
          if (animator) {
            anchorAnimations.push(animator);
          }
        });
  
        // no point in doing anything when there are no elements to animate
        if (!fromAnimation && !toAnimation && anchorAnimations.length === 0) return;
  
        return {
          start: function() {
            var animationRunners = [];
  
            if (fromAnimation) {
              animationRunners.push(fromAnimation.start());
            }
  
            if (toAnimation) {
              animationRunners.push(toAnimation.start());
            }
  
            forEach(anchorAnimations, function(animation) {
              animationRunners.push(animation.start());
            });
  
            var runner = new $$AnimateRunner({
              end: endFn,
              cancel: endFn // CSS-driven animations cannot be cancelled, only ended
            });
  
            $$AnimateRunner.all(animationRunners, function(status) {
              runner.complete(status);
            });
  
            return runner;
  
            function endFn() {
              forEach(animationRunners, function(runner) {
                runner.end();
              });
            }
          }
        };
      }
  
      function prepareRegularAnimation(animationDetails, onBeforeClassesAppliedCb) {
        var element = animationDetails.element;
        var options = animationDetails.options || {};
  
        // since the ng-EVENT, class-ADD and class-REMOVE classes are applied inside
        // of the animateQueue pre and postDigest stages then there is no need to add
        // then them here as well.
        options.$$skipPreparationClasses = true;
  
        // during the pre/post digest stages inside of animateQueue we also performed
        // the blocking (transition:-9999s) so there is no point in doing that again.
        options.skipBlocking = true;
  
        if (animationDetails.structural) {
          options.event = animationDetails.event;
  
          // we special case the leave animation since we want to ensure that
          // the element is removed as soon as the animation is over. Otherwise
          // a flicker might appear or the element may not be removed at all
          if (animationDetails.event === 'leave') {
            options.onDone = options.domOperation;
          }
        }
  
        // we apply the classes right away since the pre-digest took care of the
        // preparation classes.
        onBeforeClassesAppliedCb(element);
        applyAnimationClasses(element, options);
  
        // We assign the preparationClasses as the actual animation event since
        // the internals of $animateCss will just suffix the event token values
        // with `-active` to trigger the animation.
        if (options.preparationClasses) {
          options.event = concatWithSpace(options.event, options.preparationClasses);
        }
  
        var animator = $animateCss(element, options);
  
        // the driver lookup code inside of $$animation attempts to spawn a
        // driver one by one until a driver returns a.$$willAnimate animator object.
        // $animateCss will always return an object, however, it will pass in
        // a flag as a hint as to whether an animation was detected or not
        return animator.$$willAnimate ? animator : null;
      }
    }];
  }];
  