text: var COMPLETE = 'complete',
      CANCELED = 'canceled';
  
  function raf(task){
      if('requestAnimationFrame' in window){
          return window.requestAnimationFrame(task);
      }
  
      setTimeout(task, 16);
  }
  
  function setElementScroll(element, x, y){
      if(element.self === element){
          element.scrollTo(x, y);
      }else{
          element.scrollLeft = x;
          element.scrollTop = y;
      }
  }
  
  function getTargetScrollLocation(scrollSettings, parent){
      var align = scrollSettings.align,
          target = scrollSettings.target,
          targetPosition = target.getBoundingClientRect(),
          parentPosition,
          x,
          y,
          differenceX,
          differenceY,
          targetWidth,
          targetHeight,
          leftAlign = align && align.left != null ? align.left : 0.5,
          topAlign = align && align.top != null ? align.top : 0.5,
          leftOffset = align && align.leftOffset != null ? align.leftOffset : 0,
          topOffset = align && align.topOffset != null ? align.topOffset : 0,
          leftScalar = leftAlign,
          topScalar = topAlign;
  
      if(scrollSettings.isWindow(parent)){
          targetWidth = Math.min(targetPosition.width, parent.innerWidth);
          targetHeight = Math.min(targetPosition.height, parent.innerHeight);
          x = targetPosition.left + parent.pageXOffset - parent.innerWidth * leftScalar + targetWidth * leftScalar;
          y = targetPosition.top + parent.pageYOffset - parent.innerHeight * topScalar + targetHeight * topScalar;
          x -= leftOffset;
          y -= topOffset;
          differenceX = x - parent.pageXOffset;
          differenceY = y - parent.pageYOffset;
      }else{
          targetWidth = targetPosition.width;
          targetHeight = targetPosition.height;
          parentPosition = parent.getBoundingClientRect();
          var offsetLeft = targetPosition.left - (parentPosition.left - parent.scrollLeft);
          var offsetTop = targetPosition.top - (parentPosition.top - parent.scrollTop);
          x = offsetLeft + (targetWidth * leftScalar) - parent.clientWidth * leftScalar;
          y = offsetTop + (targetHeight * topScalar) - parent.clientHeight * topScalar;
          x -= leftOffset;
          y -= topOffset;
          x = Math.max(Math.min(x, parent.scrollWidth - parent.clientWidth), 0);
          y = Math.max(Math.min(y, parent.scrollHeight - parent.clientHeight), 0);
          differenceX = x - parent.scrollLeft;
          differenceY = y - parent.scrollTop;
      }
  
      return {
          x: x,
          y: y,
          differenceX: differenceX,
          differenceY: differenceY
      };
  }
  
  function animate(parent){
      var scrollSettings = parent._scrollSettings;
  
      if(!scrollSettings){
          return;
      }
  
      var maxSynchronousAlignments = scrollSettings.maxSynchronousAlignments;
  
      var location = getTargetScrollLocation(scrollSettings, parent),
          time = Date.now() - scrollSettings.startTime,
          timeValue = Math.min(1 / scrollSettings.time * time, 1);
  
      if(scrollSettings.endIterations >= maxSynchronousAlignments){
          setElementScroll(parent, location.x, location.y);
          parent._scrollSettings = null;
          return scrollSettings.end(COMPLETE);
      }
  
      var easeValue = 1 - scrollSettings.ease(timeValue);
  
      setElementScroll(parent,
          location.x - location.differenceX * easeValue,
          location.y - location.differenceY * easeValue
      );
  
      if(time >= scrollSettings.time){
          scrollSettings.endIterations++;
          return animate(parent);
      }
  
      raf(animate.bind(null, parent));
  }
  
  function defaultIsWindow(target){
      return target.self === target
  }
  
  function transitionScrollTo(target, parent, settings, callback){
      var idle = !parent._scrollSettings,
          lastSettings = parent._scrollSettings,
          now = Date.now(),
          cancelHandler,
          passiveOptions = { passive: true };
  
      if(lastSettings){
          lastSettings.end(CANCELED);
      }
  
      function end(endType){
          parent._scrollSettings = null;
          if(parent.parentElement && parent.parentElement._scrollSettings){
              parent.parentElement._scrollSettings.end(endType);
          }
  
          if(settings.debug){
              console.log('Scrolling ended with type', endType, 'for', parent)
          }
  
          callback(endType);
          if(cancelHandler){
              parent.removeEventListener('touchstart', cancelHandler, passiveOptions);
              parent.removeEventListener('wheel', cancelHandler, passiveOptions);
          }
      }
  
      var maxSynchronousAlignments = settings.maxSynchronousAlignments;
  
      if(maxSynchronousAlignments == null){
          maxSynchronousAlignments = 3;
      }
  
      parent._scrollSettings = {
          startTime: lastSettings ? lastSettings.startTime : Date.now(),
          endIterations: 0,
          target: target,
          time: settings.time + (lastSettings ? now - lastSettings.startTime : 0),
          ease: settings.ease,
          align: settings.align,
          isWindow: settings.isWindow || defaultIsWindow,
          maxSynchronousAlignments: maxSynchronousAlignments,
          end: end
      };
  
      if(!('cancellable' in settings) || settings.cancellable){
          cancelHandler = end.bind(null, CANCELED);
          parent.addEventListener('touchstart', cancelHandler, passiveOptions);
          parent.addEventListener('wheel', cancelHandler, passiveOptions);
      }
  
      if(idle){
          animate(parent);
      }
  }
  
  function defaultIsScrollable(element){
      return (
          'pageXOffset' in element ||
          (
              element.scrollHeight !== element.clientHeight ||
              element.scrollWidth !== element.clientWidth
          ) &&
          getComputedStyle(element).overflow !== 'hidden'
      );
  }
  
  function defaultValidTarget(){
      return true;
  }
  
  module.exports = function(target, settings, callback){
      if(!target){
          return;
      }
  
      if(typeof settings === 'function'){
          callback = settings;
          settings = null;
      }
  
      if(!settings){
          settings = {};
      }
  
      settings.time = isNaN(settings.time) ? 1000 : settings.time;
      settings.ease = settings.ease || function(v){return 1 - Math.pow(1 - v, v / 2);};
  
      var parent = target.parentElement,
          parents = 1;
  
      function done(endType){
          parents--;
          if(!parents){
              callback && callback(endType);
          }
      }
  
      var validTarget = settings.validTarget || defaultValidTarget;
      var isScrollable = settings.isScrollable;
  
      if(settings.debug){
          console.log('About to scroll to', target)
  
          if(!parent){
              console.error('Target did not have a parent, is it mounted in the DOM?')
          }
      }
  
      while(parent){
          if(parent.tagName === 'BODY'){
              parent = parent.ownerDocument;
              parent = parent.defaultView || parent.ownerWindow;
          }
  
          if(settings.debug){
              console.log('Scrolling parent node', parent)
          }
  
          if(validTarget(parent, parents) && (isScrollable ? isScrollable(parent, defaultIsScrollable) : defaultIsScrollable(parent))){
              parents++;
              transitionScrollTo(target, parent, settings, done);
          }
  
          parent = parent.parentElement;
  
          if(!parent){
              done(COMPLETE)
              break;
          }
      }
  };
  