"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.ScrollerNative = exports.ScrollerBase = exports.Scroller = exports.default = void 0;

var _Scroller = require("@enact/ui/Scroller");

var _target = require("@enact/spotlight/src/target");

var _propTypes = _interopRequireDefault(require("prop-types"));

var _react = _interopRequireWildcard(require("react"));

var _spotlight = require("@enact/spotlight");

var _resolution = _interopRequireDefault(require("@enact/ui/resolution"));

var _Scrollable = _interopRequireDefault(require("../Scrollable"));

var _ScrollableNative = _interopRequireDefault(require("../Scrollable/ScrollableNative"));

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }

function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

var dataContainerDisabledAttribute = 'data-spotlight-container-disabled',
    epsilon = 1,
    reverseDirections = {
  left: 'right',
  right: 'left'
};
/**
 * A Moonstone-styled base component for [Scroller]{@link moonstone/Scroller.Scroller}.
 * In most circumstances, you will want to use the
 * [SpotlightContainerDecorator]{@link spotlight/SpotlightContainerDecorator.SpotlightContainerDecorator}
 * and the Scrollable version, [Scroller]{@link moonstone/Scroller.Scroller}.
 *
 * @class ScrollerBase
 * @memberof moonstone/Scroller
 * @ui
 * @public
 */

var ScrollerBase =
/*#__PURE__*/
function (_Component) {
  _inherits(ScrollerBase, _Component);

  function ScrollerBase() {
    var _ref;

    var _temp, _this;

    _classCallCheck(this, ScrollerBase);

    for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
      args[_key] = arguments[_key];
    }

    return _possibleConstructorReturn(_this, (_temp = _this = _possibleConstructorReturn(this, (_ref = ScrollerBase.__proto__ || Object.getPrototypeOf(ScrollerBase)).call.apply(_ref, [this].concat(args))), Object.defineProperty(_assertThisInitialized(_this), "getSpotlightContainerForNode", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(node) {
        do {
          if (node.dataset.spotlightId && node.dataset.spotlightContainer) {
            return node;
          }
        } while ((node = node.parentNode) && node !== _this.uiRef.containerRef);
      }
    }), Object.defineProperty(_assertThisInitialized(_this), "getFocusedItemBounds", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(node) {
        node = _this.getSpotlightContainerForNode(node) || node;
        return node.getBoundingClientRect();
      }
    }), Object.defineProperty(_assertThisInitialized(_this), "calculateScrollTop", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(focusedItem, itemTop, itemHeight, scrollInfo, scrollPosition) {
        var heightThreshold = _resolution.default.scale(24),
            _this$uiRef$scrollBou = _this.uiRef.scrollBounds,
            clientHeight = _this$uiRef$scrollBou.clientHeight,
            scrollHeight = _this$uiRef$scrollBou.scrollHeight,
            _this$uiRef$container = _this.uiRef.containerRef.getBoundingClientRect(),
            containerTop = _this$uiRef$container.top,
            currentScrollTop = scrollPosition ? scrollPosition : _this.uiRef.scrollPos.top,
            newItemTop = _this.uiRef.containerRef.scrollTop + (itemTop - containerTop),
            itemBottom = newItemTop + itemHeight,
            scrollBottom = clientHeight + currentScrollTop;

        var newScrollTop = _this.uiRef.scrollPos.top,
            scrollHeightChange = 0; // Calculations for when scrollHeight decrease.

        if (scrollInfo) {
          var scrollTop = scrollInfo.scrollTop,
              previousScrollHeight = scrollInfo.previousScrollHeight;
          scrollHeightChange = previousScrollHeight - scrollHeight;

          if (scrollHeightChange > 0) {
            newScrollTop = scrollTop;
            var itemBounds = focusedItem.getBoundingClientRect(),
                newItemBottom = newScrollTop + itemBounds.top + itemBounds.height - containerTop;

            if (newItemBottom < scrollBottom && scrollHeightChange + newItemBottom > scrollBottom) {
              // When `focusedItem` is not at the very bottom of the `Scroller` and
              // `scrollHeightChange` caused a scroll.
              var distanceFromBottom = scrollBottom - newItemBottom,
                  bottomOffset = scrollHeightChange - distanceFromBottom;

              if (bottomOffset < newScrollTop) {
                // guard against negative `scrollTop`
                newScrollTop -= bottomOffset;
              }
            } else if (newItemBottom === scrollBottom) {
              // when `focusedItem` is at the very bottom of the `Scroller`
              if (scrollHeightChange < newScrollTop) {
                // guard against negative `scrollTop`
                newScrollTop -= scrollHeightChange;
              }
            }
          }
        }

        if (itemHeight > clientHeight) {
          // Calculations for `containerHeight` that are bigger than `clientHeight`
          var _focusedItem$getBound = focusedItem.getBoundingClientRect(),
              top = _focusedItem$getBound.top,
              nestedItemHeight = _focusedItem$getBound.height,
              nestedItemTop = _this.uiRef.containerRef.scrollTop + (top - containerTop),
              nestedItemBottom = nestedItemTop + nestedItemHeight;

          if (nestedItemBottom - scrollBottom > epsilon) {
            // Calculate when 5-way focus down past the bottom.
            newScrollTop += nestedItemBottom - scrollBottom;
          } else if (nestedItemTop - currentScrollTop < epsilon) {
            // Calculate when 5-way focus up past the top.
            if (newItemTop > newScrollTop) {
              // Ensure that the adjusted scrollTop would at least scroll the container to the top of
              // the viewport (e.g. because the container is at the bottom of the scroller and the
              // nested item is at the top of the container)
              newScrollTop = newItemTop;
            } else {
              newScrollTop += nestedItemTop - currentScrollTop;
            }
          } else if (newItemTop - nestedItemHeight - currentScrollTop > epsilon) {
            // set scroll position so that the top of the container is at least on the top as a fallback.
            newScrollTop = newItemTop - nestedItemHeight;
          }
        } else if (itemBottom - scrollBottom > epsilon) {
          // Calculate when 5-way focus down past the bottom.
          // if the last item is focused and have an invisible area, scroll all the way to the bottom
          if (scrollHeight - itemBottom < heightThreshold) {
            newScrollTop += scrollHeight - scrollBottom;
          } else {
            newScrollTop += itemBottom - scrollBottom;
          }
        } else if (newItemTop - currentScrollTop < epsilon && scrollHeightChange <= 0) {
          // Calculate when 5-way focus up past the top.
          // if the first item is focused and have an invisible area, scroll all the way to the top
          if (newItemTop < heightThreshold) {
            newScrollTop = 0;
          } else {
            newScrollTop += newItemTop - currentScrollTop;
          }
        }

        return newScrollTop;
      }
    }), Object.defineProperty(_assertThisInitialized(_this), "calculatePositionOnFocus", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(_ref2) {
        var item = _ref2.item,
            scrollInfo = _ref2.scrollInfo,
            scrollPosition = _ref2.scrollPosition;

        if (!_this.uiRef.isVertical() && !_this.uiRef.isHorizontal() || !item || !_this.uiRef.containerRef.contains(item)) {
          return;
        }

        var _this$getFocusedItemB = _this.getFocusedItemBounds(item),
            itemTop = _this$getFocusedItemB.top,
            itemLeft = _this$getFocusedItemB.left,
            itemHeight = _this$getFocusedItemB.height,
            itemWidth = _this$getFocusedItemB.width;

        if (_this.uiRef.isVertical()) {
          _this.uiRef.scrollPos.top = _this.calculateScrollTop(item, itemTop, itemHeight, scrollInfo, scrollPosition);
        } else if (_this.uiRef.isHorizontal()) {
          var rtl = _this.props.rtl,
              clientWidth = _this.uiRef.scrollBounds.clientWidth,
              rtlDirection = rtl ? -1 : 1,
              _this$uiRef$container2 = _this.uiRef.containerRef.getBoundingClientRect(),
              containerLeft = _this$uiRef$container2.left,
              scrollLastPosition = scrollPosition ? scrollPosition : _this.uiRef.scrollPos.left,
              currentScrollLeft = rtl ? _this.uiRef.scrollBounds.maxLeft - scrollLastPosition : scrollLastPosition,
              newItemLeft = _this.uiRef.containerRef.scrollLeft + (itemLeft - containerLeft);

          if (newItemLeft + itemWidth > clientWidth + currentScrollLeft && itemWidth < clientWidth) {
            // If focus is moved to an element outside of view area (to the right), scroller will move
            // to the right just enough to show the current `focusedItem`. This does not apply to
            // `focusedItem` that has a width that is bigger than `this.scrollBounds.clientWidth`.
            _this.uiRef.scrollPos.left += rtlDirection * (newItemLeft + itemWidth - (clientWidth + currentScrollLeft));
          } else if (newItemLeft < currentScrollLeft) {
            // If focus is outside of the view area to the left, move scroller to the left accordingly.
            _this.uiRef.scrollPos.left += rtlDirection * (newItemLeft - currentScrollLeft);
          }
        }

        return _this.uiRef.scrollPos;
      }
    }), Object.defineProperty(_assertThisInitialized(_this), "focusOnNode", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(node) {
        if (node) {
          _spotlight.Spotlight.focus(node);
        }
      }
    }), Object.defineProperty(_assertThisInitialized(_this), "handleGlobalKeyDown", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value() {
        _this.setContainerDisabled(false);
      }
    }), Object.defineProperty(_assertThisInitialized(_this), "setContainerDisabled", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(bool) {
        var spotlightId = _this.props.spotlightId,
            containerNode = document.querySelector("[data-spotlight-id=\"".concat(spotlightId, "\"]"));

        if (containerNode) {
          containerNode.setAttribute(dataContainerDisabledAttribute, bool);

          if (bool) {
            document.addEventListener('keydown', _this.handleGlobalKeyDown, {
              capture: true
            });
          } else {
            document.removeEventListener('keydown', _this.handleGlobalKeyDown, {
              capture: true
            });
          }
        }
      }
    }), Object.defineProperty(_assertThisInitialized(_this), "getNextEndPoint", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(direction, oSpotBounds) {
        var bounds = _this.uiRef.getScrollBounds();

        var oPoint = {};

        switch (direction) {
          case 'up':
            oPoint.x = oSpotBounds.left;
            oPoint.y = oSpotBounds.top - bounds.clientHeight;
            break;

          case 'left':
            oPoint.x = oSpotBounds.left - bounds.clientWidth;
            oPoint.y = oSpotBounds.top;
            break;

          case 'down':
            oPoint.x = oSpotBounds.left;
            oPoint.y = oSpotBounds.top + oSpotBounds.height + bounds.clientHeight;
            break;

          case 'right':
            oPoint.x = oSpotBounds.left + oSpotBounds.width + bounds.clientWidth;
            oPoint.y = oSpotBounds.top;
            break;
        }

        return oPoint;
      }
    }), Object.defineProperty(_assertThisInitialized(_this), "scrollToNextPage", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(_ref3) {
        var direction = _ref3.direction,
            focusedItem = _ref3.focusedItem,
            reverseDirection = _ref3.reverseDirection,
            spotlightId = _ref3.spotlightId;

        var endPoint = _this.getNextEndPoint(direction, focusedItem.getBoundingClientRect());

        var candidateNode = null;
        /* Find a spottable item in the next page */

        candidateNode = (0, _target.getTargetByDirectionFromPosition)(reverseDirection, endPoint, spotlightId);
        /* Find a spottable item in a whole data */

        if (candidateNode === focusedItem) {
          candidateNode = (0, _target.getTargetByDirectionFromPosition)(direction, endPoint, spotlightId);
        }
        /* If there is no spottable item next to the current item */


        if (candidateNode === focusedItem) {
          return null;
        }

        return candidateNode;
      }
    }), Object.defineProperty(_assertThisInitialized(_this), "scrollToBoundary", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(direction) {
        var _this$uiRef = _this.uiRef,
            scrollBounds = _this$uiRef.scrollBounds,
            scrollPos = _this$uiRef.scrollPos,
            isVerticalDirection = direction === 'up' || direction === 'down';

        if (isVerticalDirection) {
          if (scrollPos.top > 0 && scrollPos.top < scrollBounds.maxTop) {
            _this.uiRef.props.cbScrollTo({
              align: direction === 'up' ? 'top' : 'bottom'
            });
          }
        } else if (scrollPos.left > 0 && scrollPos.left < scrollBounds.maxLeft) {
          _this.uiRef.props.cbScrollTo({
            align: _this.props.rtl ? reverseDirections[direction] : direction
          });
        }
      }
    }), Object.defineProperty(_assertThisInitialized(_this), "handleLeaveContainer", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(_ref4) {
        var direction = _ref4.direction,
            target = _ref4.target;
        var contentsContainer = _this.uiRef.containerRef; // ensure we only scroll to boundary from the contents and not a scroll button which
        // lie outside of this.uiRef.containerRef but within the spotlight container

        if (contentsContainer && contentsContainer.contains(target)) {
          _this.scrollToBoundary(direction);
        }
      }
    }), Object.defineProperty(_assertThisInitialized(_this), "initUiRef", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(ref) {
        if (ref) {
          _this.uiRef = ref;

          _this.props.initUiChildRef(ref);
        }
      }
    }), _temp));
  }

  _createClass(ScrollerBase, [{
    key: "componentDidMount",
    value: function componentDidMount() {
      this.configureSpotlight();
    }
  }, {
    key: "componentDidUpdate",
    value: function componentDidUpdate(prevProps) {
      var onUpdate = this.props.onUpdate;

      if (onUpdate) {
        onUpdate();
      }

      if (prevProps.spotlightId !== this.props.spotlightId) {
        this.configureSpotlight();
      }
    }
  }, {
    key: "componentWillUnmount",
    value: function componentWillUnmount() {
      this.setContainerDisabled(false);
    }
  }, {
    key: "configureSpotlight",
    value: function configureSpotlight() {
      _spotlight.Spotlight.set(this.props.spotlightId, {
        onLeaveContainer: this.handleLeaveContainer,
        onLeaveContainerFail: this.handleLeaveContainer
      });
    }
    /**
     * Returns the first spotlight container between `node` and the scroller
     *
     * @param {Node} node A DOM node
     *
     * @returns {Node|Null} Spotlight container for `node`
     * @private
     */

  }, {
    key: "render",
    value: function render() {
      var props = Object.assign({}, this.props);
      delete props.initUiChildRef;
      delete props.onUpdate;
      delete props.spotlightId;
      return _react.default.createElement(_Scroller.ScrollerBase, _extends({}, props, {
        ref: this.initUiRef
      }));
    }
  }]);

  return ScrollerBase;
}(_react.Component);
/**
 * A Moonstone-styled Scroller, Scrollable applied.
 *
 * Usage:
 * ```
 * <Scroller>Scroll me.</Scroller>
 * ```
 *
 * @class Scroller
 * @memberof moonstone/Scroller
 * @extends moonstone/Scrollable.Scrollable
 * @extends moonstone/Scroller.ScrollerBase
 * @ui
 * @public
 */


exports.ScrollerBase = ScrollerBase;
Object.defineProperty(ScrollerBase, "displayName", {
  configurable: true,
  enumerable: true,
  writable: true,
  value: 'ScrollerBase'
});
Object.defineProperty(ScrollerBase, "propTypes", {
  configurable: true,
  enumerable: true,
  writable: true,
  value:
  /** @lends moonstone/Scroller.Scroller.prototype */
  {
    /**
     * Passes the instance of [Scroller]{@link ui/Scroller.Scroller}.
     *
     * @type {Object}
     * @param {Object} ref
     * @private
     */
    initUiChildRef: _propTypes.default.func,

    /**
     * Called when [Scroller]{@link moonstone/Scroller.Scroller} updates.
     *
     * @type {function}
     * @private
     */
    onUpdate: _propTypes.default.func,

    /**
     * `true` if rtl, `false` if ltr.
     *
     * @type {Boolean}
     * @private
     */
    rtl: _propTypes.default.bool,

    /**
     * The spotlight id for the component.
     *
     * @type {String}
     * @private
     */
    spotlightId: _propTypes.default.string
  }
});

var Scroller = function Scroller(props) {
  return _react.default.createElement(_Scrollable.default, _extends({}, props, {
    childRenderer: function childRenderer(scrollerProps) {
      // eslint-disable-line react/jsx-no-bind
      return _react.default.createElement(ScrollerBase, scrollerProps);
    }
  }));
};

exports.Scroller = Scroller;
Scroller.propTypes =
/** @lends moonstone/Scroller.Scroller.prototype */
{
  /**
   * Direction of the scroller.
   *
   * * Values: `'both'`, `'horizontal'`, `'vertical'`.
   *
   * @type {String}
   * @default 'both'
   * @public
   */
  direction: _propTypes.default.oneOf(['both', 'horizontal', 'vertical'])
};
Scroller.defaultProps = {
  direction: 'both'
};
/**
 * A Moonstone-styled native Scroller, Scrollable applied.
 *
 * For smooth native scrolling, web engine with below Chromium 61, should be launched
 * with the flag '--enable-blink-features=CSSOMSmoothScroll' to support it.
 * The one with Chromium 61 or above, is launched to support it by default.
 *
 * Usage:
 * ```
 * <ScrollerNative>Scroll me.</ScrollerNative>
 * ```
 *
 * @class ScrollerNative
 * @memberof moonstone/Scroller
 * @extends moonstone/Scrollable.ScrollableNative
 * @extends moonstone/Scroller.ScrollerBase
 * @ui
 * @private
 */

var ScrollerNative = function ScrollerNative(props) {
  return _react.default.createElement(_ScrollableNative.default, _extends({}, props, {
    childRenderer: function childRenderer(scrollerProps) {
      // eslint-disable-line react/jsx-no-bind
      return _react.default.createElement(ScrollerBase, scrollerProps);
    }
  }));
};

exports.ScrollerNative = ScrollerNative;
ScrollerNative.propTypes =
/** @lends moonstone/Scroller.ScrollerNative.prototype */
{
  /**
   * Direction of the scroller.
   *
   * * Values: `'both'`, `'horizontal'`, `'vertical'`.
   *
   * @type {String}
   * @default 'both'
   * @public
   */
  direction: _propTypes.default.oneOf(['both', 'horizontal', 'vertical'])
};
ScrollerNative.defaultProps = {
  direction: 'both'
};
var _default = Scroller;
exports.default = _default;