text: /**
  @license
  Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
  This code may only be used under the BSD style license found at
  http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
  http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
  found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
  part of the polymer project is also subject to an additional IP rights grant
  found at http://polymer.github.io/PATENTS.txt
  */
  import "../polymer/polymer-legacy.js";
  import { dom } from "../polymer/lib/legacy/polymer.dom.js";
  /**
   * Used to calculate the scroll direction during touch events.
   * @type {!Object}
   */
  
  var lastTouchPosition = {
    pageX: 0,
    pageY: 0
  };
  /**
   * Used to avoid computing event.path and filter scrollable nodes (better perf).
   * @type {?EventTarget}
   */
  
  var lastRootTarget = null;
  /**
   * @type {!Array<!Node>}
   */
  
  var lastScrollableNodes = [];
  /**
   * @type {!Array<string>}
   */
  
  var scrollEvents = [// Modern `wheel` event for mouse wheel scrolling:
  'wheel', // Older, non-standard `mousewheel` event for some FF:
  'mousewheel', // IE:
  'DOMMouseScroll', // Touch enabled devices
  'touchstart', 'touchmove']; // must be defined for modulizer
  
  var _boundScrollHandler;
  
  var currentLockingElement;
  /**
   * The IronScrollManager is intended to provide a central source
   * of authority and control over which elements in a document are currently
   * allowed to scroll.
   *
   */
  
  `TODO(modulizer): A namespace named Polymer.IronScrollManager was
  declared here. The surrounding comments should be reviewed,
  and this string can then be deleted`;
  /**
   * The current element that defines the DOM boundaries of the
   * scroll lock. This is always the most recently locking element.
   *
   * @return {!Node|undefined}
   */
  
  export { currentLockingElement };
  /**
   * Returns true if the provided element is "scroll locked", which is to
   * say that it cannot be scrolled via pointer or keyboard interactions.
   *
   * @param {!HTMLElement} element An HTML element instance which may or may
   * not be scroll locked.
   */
  
  export function elementIsScrollLocked(element) {
    var lockingElement = currentLockingElement;
  
    if (lockingElement === undefined) {
      return false;
    }
  
    var scrollLocked;
  
    if (_hasCachedLockedElement(element)) {
      return true;
    }
  
    if (_hasCachedUnlockedElement(element)) {
      return false;
    }
  
    scrollLocked = !!lockingElement && lockingElement !== element && !_composedTreeContains(lockingElement, element);
  
    if (scrollLocked) {
      _lockedElementCache.push(element);
    } else {
      _unlockedElementCache.push(element);
    }
  
    return scrollLocked;
  }
  /**
   * Push an element onto the current scroll lock stack. The most recently
   * pushed element and its children will be considered scrollable. All
   * other elements will not be scrollable.
   *
   * Scroll locking is implemented as a stack so that cases such as
   * dropdowns within dropdowns are handled well.
   *
   * @param {!HTMLElement} element The element that should lock scroll.
   */
  
  export function pushScrollLock(element) {
    // Prevent pushing the same element twice
    if (_lockingElements.indexOf(element) >= 0) {
      return;
    }
  
    if (_lockingElements.length === 0) {
      _lockScrollInteractions();
    }
  
    _lockingElements.push(element);
  
    currentLockingElement = _lockingElements[_lockingElements.length - 1];
    _lockedElementCache = [];
    _unlockedElementCache = [];
  }
  /**
   * Remove an element from the scroll lock stack. The element being
   * removed does not need to be the most recently pushed element. However,
   * the scroll lock constraints only change when the most recently pushed
   * element is removed.
   *
   * @param {!HTMLElement} element The element to remove from the scroll
   * lock stack.
   */
  
  export function removeScrollLock(element) {
    var index = _lockingElements.indexOf(element);
  
    if (index === -1) {
      return;
    }
  
    _lockingElements.splice(index, 1);
  
    currentLockingElement = _lockingElements[_lockingElements.length - 1];
    _lockedElementCache = [];
    _unlockedElementCache = [];
  
    if (_lockingElements.length === 0) {
      _unlockScrollInteractions();
    }
  }
  export const _lockingElements = [];
  export let _lockedElementCache = null;
  export let _unlockedElementCache = null;
  export function _hasCachedLockedElement(element) {
    return _lockedElementCache.indexOf(element) > -1;
  }
  export function _hasCachedUnlockedElement(element) {
    return _unlockedElementCache.indexOf(element) > -1;
  }
  export function _composedTreeContains(element, child) {
    // NOTE(cdata): This method iterates over content elements and their
    // corresponding distributed nodes to implement a contains-like method
    // that pierces through the composed tree of the ShadowDOM. Results of
    // this operation are cached (elsewhere) on a per-scroll-lock basis, to
    // guard against potentially expensive lookups happening repeatedly as
    // a user scrolls / touchmoves.
    var contentElements;
    var distributedNodes;
    var contentIndex;
    var nodeIndex;
  
    if (element.contains(child)) {
      return true;
    }
  
    contentElements = dom(element).querySelectorAll('content,slot');
  
    for (contentIndex = 0; contentIndex < contentElements.length; ++contentIndex) {
      distributedNodes = dom(contentElements[contentIndex]).getDistributedNodes();
  
      for (nodeIndex = 0; nodeIndex < distributedNodes.length; ++nodeIndex) {
        // Polymer 2.x returns slot.assignedNodes which can contain text nodes.
        if (distributedNodes[nodeIndex].nodeType !== Node.ELEMENT_NODE) continue;
  
        if (_composedTreeContains(distributedNodes[nodeIndex], child)) {
          return true;
        }
      }
    }
  
    return false;
  }
  export function _scrollInteractionHandler(event) {
    // Avoid canceling an event with cancelable=false, e.g. scrolling is in
    // progress and cannot be interrupted.
    if (event.cancelable && _shouldPreventScrolling(event)) {
      event.preventDefault();
    } // If event has targetTouches (touch event), update last touch position.
  
  
    if (event.targetTouches) {
      var touch = event.targetTouches[0];
      lastTouchPosition.pageX = touch.pageX;
      lastTouchPosition.pageY = touch.pageY;
    }
  }
  /**
   * @private
   */
  
  export { _boundScrollHandler };
  export function _lockScrollInteractions() {
    _boundScrollHandler = _boundScrollHandler || _scrollInteractionHandler.bind(undefined);
  
    for (var i = 0, l = scrollEvents.length; i < l; i++) {
      // NOTE: browsers that don't support objects as third arg will
      // interpret it as boolean, hence useCapture = true in this case.
      document.addEventListener(scrollEvents[i], _boundScrollHandler, {
        capture: true,
        passive: false
      });
    }
  }
  export function _unlockScrollInteractions() {
    for (var i = 0, l = scrollEvents.length; i < l; i++) {
      // NOTE: browsers that don't support objects as third arg will
      // interpret it as boolean, hence useCapture = true in this case.
      document.removeEventListener(scrollEvents[i], _boundScrollHandler, {
        capture: true,
        passive: false
      });
    }
  }
  /**
   * Returns true if the event causes scroll outside the current locking
   * element, e.g. pointer/keyboard interactions, or scroll "leaking"
   * outside the locking element when it is already at its scroll boundaries.
   * @param {!Event} event
   * @return {boolean}
   * @private
   */
  
  export function _shouldPreventScrolling(event) {
    // Update if root target changed. For touch events, ensure we don't
    // update during touchmove.
    var target = dom(event).rootTarget;
  
    if (event.type !== 'touchmove' && lastRootTarget !== target) {
      lastRootTarget = target;
      lastScrollableNodes = _getScrollableNodes(dom(event).path);
    } // Prevent event if no scrollable nodes.
  
  
    if (!lastScrollableNodes.length) {
      return true;
    } // Don't prevent touchstart event inside the locking element when it has
    // scrollable nodes.
  
  
    if (event.type === 'touchstart') {
      return false;
    } // Get deltaX/Y.
  
  
    var info = _getScrollInfo(event); // Prevent if there is no child that can scroll.
  
  
    return !_getScrollingNode(lastScrollableNodes, info.deltaX, info.deltaY);
  }
  /**
   * Returns an array of scrollable nodes up to the current locking element,
   * which is included too if scrollable.
   * @param {!Array<!Node>} nodes
   * @return {!Array<!Node>} scrollables
   * @private
   */
  
  export function _getScrollableNodes(nodes) {
    var scrollables = [];
    var lockingIndex = nodes.indexOf(currentLockingElement); // Loop from root target to locking element (included).
  
    for (var i = 0; i <= lockingIndex; i++) {
      // Skip non-Element nodes.
      if (nodes[i].nodeType !== Node.ELEMENT_NODE) {
        continue;
      }
  
      var node =
      /** @type {!Element} */
      nodes[i]; // Check inline style before checking computed style.
  
      var style = node.style;
  
      if (style.overflow !== 'scroll' && style.overflow !== 'auto') {
        style = window.getComputedStyle(node);
      }
  
      if (style.overflow === 'scroll' || style.overflow === 'auto') {
        scrollables.push(node);
      }
    }
  
    return scrollables;
  }
  /**
   * Returns the node that is scrolling. If there is no scrolling,
   * returns undefined.
   * @param {!Array<!Node>} nodes
   * @param {number} deltaX Scroll delta on the x-axis
   * @param {number} deltaY Scroll delta on the y-axis
   * @return {!Node|undefined}
   * @private
   */
  
  export function _getScrollingNode(nodes, deltaX, deltaY) {
    // No scroll.
    if (!deltaX && !deltaY) {
      return;
    } // Check only one axis according to where there is more scroll.
    // Prefer vertical to horizontal.
  
  
    var verticalScroll = Math.abs(deltaY) >= Math.abs(deltaX);
  
    for (var i = 0; i < nodes.length; i++) {
      var node = nodes[i];
      var canScroll = false;
  
      if (verticalScroll) {
        // delta < 0 is scroll up, delta > 0 is scroll down.
        canScroll = deltaY < 0 ? node.scrollTop > 0 : node.scrollTop < node.scrollHeight - node.clientHeight;
      } else {
        // delta < 0 is scroll left, delta > 0 is scroll right.
        canScroll = deltaX < 0 ? node.scrollLeft > 0 : node.scrollLeft < node.scrollWidth - node.clientWidth;
      }
  
      if (canScroll) {
        return node;
      }
    }
  }
  /**
   * Returns scroll `deltaX` and `deltaY`.
   * @param {!Event} event The scroll event
   * @return {{deltaX: number, deltaY: number}} Object containing the
   * x-axis scroll delta (positive: scroll right, negative: scroll left,
   * 0: no scroll), and the y-axis scroll delta (positive: scroll down,
   * negative: scroll up, 0: no scroll).
   * @private
   */
  
  export function _getScrollInfo(event) {
    var info = {
      deltaX: event.deltaX,
      deltaY: event.deltaY
    }; // Already available.
  
    if ('deltaX' in event) {} // do nothing, values are already good.
    // Safari has scroll info in `wheelDeltaX/Y`.
    else if ('wheelDeltaX' in event && 'wheelDeltaY' in event) {
        info.deltaX = -event.wheelDeltaX;
        info.deltaY = -event.wheelDeltaY;
      } // IE10 has only vertical scroll info in `wheelDelta`.
      else if ('wheelDelta' in event) {
          info.deltaX = 0;
          info.deltaY = -event.wheelDelta;
        } // Firefox has scroll info in `detail` and `axis`.
        else if ('axis' in event) {
            info.deltaX = event.axis === 1 ? event.detail : 0;
            info.deltaY = event.axis === 2 ? event.detail : 0;
          } // On mobile devices, calculate scroll direction.
          else if (event.targetTouches) {
              var touch = event.targetTouches[0]; // Touch moves from right to left => scrolling goes right.
  
              info.deltaX = lastTouchPosition.pageX - touch.pageX; // Touch moves from down to up => scrolling goes down.
  
              info.deltaY = lastTouchPosition.pageY - touch.pageY;
            }
  
    return info;
  }