"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.VirtualListBaseNative = exports.VirtualListBase = exports.ScrollableVirtualListNative = exports.ScrollableVirtualList = exports.default = void 0;

var _clamp = _interopRequireDefault(require("ramda/src/clamp"));

var _keymap = require("@enact/core/keymap");

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

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

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

var _Spottable = _interopRequireDefault(require("@enact/spotlight/Spottable"));

var _VirtualList = require("@enact/ui/VirtualList");

var _Scrollable = 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 _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }

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 SpotlightPlaceholder = (0, _Spottable.default)('div');

var dataContainerDisabledAttribute = 'data-spotlight-container-disabled',
    isDown = (0, _keymap.is)('down'),
    isLeft = (0, _keymap.is)('left'),
    isRight = (0, _keymap.is)('right'),
    isUp = (0, _keymap.is)('up'),
    JS = 'JS',
    Native = 'Native',
    isItemDisabledDefault = function isItemDisabledDefault() {
  return false;
};
/**
 * The base version of [VirtualListBase]{@link moonstone/VirtualList.VirtualListBase} and
 * [VirtualListBaseNative]{@link moonstone/VirtualList.VirtualListBaseNative}.
 *
 * @class VirtualListCore
 * @memberof moonstone/VirtualList
 * @ui
 * @public
 */


var VirtualListBaseFactory = function VirtualListBaseFactory(type) {
  var _class, _temp, _initialiseProps;

  var UiBase = type === JS ? _VirtualList.VirtualListBase : _VirtualList.VirtualListBaseNative;
  return _temp = _class =
  /*#__PURE__*/
  function (_Component) {
    _inherits(VirtualListCore, _Component);

    /* No displayName here. We set displayName to returned components of this factory function. */
    function VirtualListCore(props) {
      var _this;

      _classCallCheck(this, VirtualListCore);

      _this = _possibleConstructorReturn(this, (VirtualListCore.__proto__ || Object.getPrototypeOf(VirtualListCore)).call(this, props));

      _initialiseProps.call(_assertThisInitialized(_this));

      var spotlightId = props.spotlightId;

      if (spotlightId) {
        _this.configureSpotlight(spotlightId);
      }

      return _this;
    }

    _createClass(VirtualListCore, [{
      key: "componentDidMount",
      value: function componentDidMount() {
        var _this2 = this;

        var containerNode = this.uiRef.containerRef;

        if (type === JS) {
          // prevent native scrolling by Spotlight
          this.preventScroll = function () {
            containerNode.scrollTop = 0;
            containerNode.scrollLeft = _this2.props.rtl ? containerNode.scrollWidth : 0;
          };

          if (containerNode && containerNode.addEventListener) {
            containerNode.addEventListener('scroll', this.preventScroll);
          }
        }

        if (containerNode && containerNode.addEventListener) {
          containerNode.addEventListener('keydown', this.onKeyDown);
        }

        setTimeout(function () {
          _this2.restoreFocus();
        }, 0);
      }
    }, {
      key: "componentWillReceiveProps",
      value: function componentWillReceiveProps(nextProps) {
        if (nextProps.spotlightId && nextProps.spotlightId !== this.props.spotlightId) {
          this.configureSpotlight(nextProps.spotlightId);
        }
      }
    }, {
      key: "componentDidUpdate",
      value: function componentDidUpdate() {
        this.restoreFocus();
      }
    }, {
      key: "componentWillUnmount",
      value: function componentWillUnmount() {
        var containerNode = this.uiRef.containerRef;

        if (type === JS) {
          // remove a function for preventing native scrolling by Spotlight
          if (containerNode && containerNode.removeEventListener) {
            containerNode.removeEventListener('scroll', this.preventScroll);
          }
        }

        if (containerNode && containerNode.removeEventListener) {
          containerNode.removeEventListener('keydown', this.onKeyDown);
        }

        this.resumeSpotlight();
        this.setContainerDisabled(false);
      }
    }, {
      key: "render",
      value: function render() {
        var _this3 = this;

        var _props = this.props,
            _itemRenderer = _props.itemRenderer,
            _itemsRenderer = _props.itemsRenderer,
            rest = _objectWithoutProperties(_props, ["itemRenderer", "itemsRenderer"]),
            needsScrollingPlaceholder = this.isNeededScrollingPlaceholder();

        delete rest.initUiChildRef;
        delete rest.isItemDisabled;
        delete rest.spotlightId;
        delete rest.wrap;
        return _react.default.createElement(UiBase, _extends({}, rest, {
          getComponentProps: this.getComponentProps,
          itemRenderer: function itemRenderer(_ref) {
            var _objectSpread2;

            var index = _ref.index,
                itemRest = _objectWithoutProperties(_ref, ["index"]);

            return (// eslint-disable-line react/jsx-no-bind
              _itemRenderer(_objectSpread({}, itemRest, (_objectSpread2 = {}, _defineProperty(_objectSpread2, _Scrollable.dataIndexAttribute, index), _defineProperty(_objectSpread2, "index", index), _objectSpread2)))
            );
          },
          ref: this.initUiRef,
          updateStatesAndBounds: this.updateStatesAndBounds,
          itemsRenderer: function itemsRenderer(props) {
            // eslint-disable-line react/jsx-no-bind
            return _itemsRenderer(_objectSpread({}, props, {
              handlePlaceholderFocus: _this3.handlePlaceholderFocus,
              needsScrollingPlaceholder: needsScrollingPlaceholder
            }));
          }
        }));
      }
    }]);

    return VirtualListCore;
  }(_react.Component), Object.defineProperty(_class, "propTypes", {
    configurable: true,
    enumerable: true,
    writable: true,
    value:
    /** @lends moonstone/VirtualList.VirtualListCore.prototype */
    {
      /**
       * The `render` function called for each item in the list.
       *
       * > NOTE: The list does NOT always render a component whenever its render function is called
       * due to performance optimization.
       *
       * Usage:
       * ```
       * renderItem = ({index, ...rest}) => {
       * 	return (
       * 		<MyComponent index={index} {...rest} />
       * 	);
       * }
       * ```
       *
       * @type {Function}
       * @param {Object} event
       * @param {Number} event.data-index It is required for Spotlight 5-way navigation. Pass to the root element in the component.
       * @param {Number} event.index The index number of the component to render
       * @param {Number} event.key It MUST be passed as a prop to the root element in the component for DOM recycling.
       *
       * @required
       * @public
       */
      itemRenderer: _propTypes.default.func.isRequired,

      /**
       * The render function for the items.
       *
       * @type {Function}
       * @required
       * @private
       */
      itemsRenderer: _propTypes.default.func.isRequired,

      /**
       * Callback method of scrollTo.
       * Normally, [Scrollable]{@link ui/Scrollable.Scrollable} should set this value.
       *
       * @type {Function}
       * @private
       */
      cbScrollTo: _propTypes.default.func,

      /**
       * Size of the data.
       *
       * @type {Number}
       * @default 0
       * @public
       */
      dataSize: _propTypes.default.number,

      /**
       * Passes the instance of [VirtualList]{@link ui/VirtualList.VirtualList}.
       *
       * @type {Object}
       * @param {Object} ref
       * @private
       */
      initUiChildRef: _propTypes.default.func,

      /**
       * The Function that returns `true` if the item at the index is disabled.
       * It is used to navigate a list properly with 5 way keys, page up key,
       * and page down key. If it is not supplied, it assumes that no items are disabled.
       *
       * Usage:
       * ```
       * isItemDisabled = (index) => (this.items[index].disabled)
       * render = () => {
       * 	return (
       * 		<VirtualList
       * 			dataSize={this.items.length}
       * 			isItemDisabled={isItemDisabled}
       * 			itemRenderer={this.renderItem}
       * 			itemSize={this.itemSize}
       * 		/>
       * 	);
       * }
       * ```
       *
       * @type {Function}
       * @param {Number} index
       * @public
       */
      isItemDisabled: _propTypes.default.func,

      /*
       * It scrolls by page when `true`, by item when `false`.
       *
       * @type {Boolean}
       * @default false
       * @private
       */
      pageScroll: _propTypes.default.bool,

      /**
       * `true` if rtl, `false` if ltr.
       * Normally, [Scrollable]{@link ui/Scrollable.Scrollable} should set this value.
       *
       * @type {Boolean}
       * @private
       */
      rtl: _propTypes.default.bool,

      /**
       * Spacing between items.
       *
       * @type {Number}
       * @default 0
       * @public
       */
      spacing: _propTypes.default.number,

      /**
       * Spotlight Id. It would be the same with [Scrollable]{@link ui/Scrollable.Scrollable}'s.
       *
       * @type {String}
       * @private
       */
      spotlightId: _propTypes.default.string,

      /**
       * When it's `true` and the spotlight focus cannot move to the given direction anymore by 5-way keys,
       * a list is scrolled with an animation to the other side and the spotlight focus moves in wraparound manner.
       *
       * When it's `'noAnimation'`, the spotlight focus moves in wraparound manner as same as when it's `true`
       * except that a list is scrolled without an animation.
       *
       * @type {Boolean|String}
       * @default false
       * @public
       */
      wrap: _propTypes.default.oneOfType([_propTypes.default.bool, _propTypes.default.oneOf(['noAnimation'])])
    }
  }), Object.defineProperty(_class, "defaultProps", {
    configurable: true,
    enumerable: true,
    writable: true,
    value: {
      dataSize: 0,
      isItemDisabled: isItemDisabledDefault,
      pageScroll: false,
      spacing: 0,
      wrap: false
    }
  }), _initialiseProps = function _initialiseProps() {
    var _this4 = this;

    Object.defineProperty(this, "isScrolledBy5way", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: false
    });
    Object.defineProperty(this, "isScrolledByJump", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: false
    });
    Object.defineProperty(this, "isWrappedBy5way", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: false
    });
    Object.defineProperty(this, "lastFocusedIndex", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: null
    });
    Object.defineProperty(this, "nodeIndexToBeFocused", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: null
    });
    Object.defineProperty(this, "preservedIndex", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: null
    });
    Object.defineProperty(this, "restoreLastFocused", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: false
    });
    Object.defineProperty(this, "setContainerDisabled", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(bool) {
        var spotlightId = _this4.props.spotlightId,
            containerNode = document.querySelector("[data-spotlight-id=\"".concat(spotlightId, "\"]"));

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

          if (bool) {
            document.addEventListener('keydown', _this4.handleGlobalKeyDown, {
              capture: true
            });
          } else {
            document.removeEventListener('keydown', _this4.handleGlobalKeyDown, {
              capture: true
            });
          }
        }
      }
    });
    Object.defineProperty(this, "configureSpotlight", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(spotlightId) {
        var spacing = _this4.props.spacing;

        _spotlight.default.set(spotlightId, {
          enterTo: 'last-focused',

          /*
           * Returns the data-index as the key for last focused
           */
          lastFocusedPersist: _this4.lastFocusedPersist,

          /*
           * Restores the data-index into the placeholder if its the only element. Tries to find a
           * matching child otherwise.
           */
          lastFocusedRestore: _this4.lastFocusedRestore,

          /*
           * Directs spotlight focus to favor straight elements that are within range of `spacing`
           * over oblique elements, like scroll buttons.
           */
          obliqueMultiplier: spacing > 0 ? spacing : 1
        });
      }
    });
    Object.defineProperty(this, "lastFocusedPersist", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value() {
        if (_this4.lastFocusedIndex != null) {
          return {
            container: false,
            element: true,
            key: _this4.lastFocusedIndex
          };
        }
      }
    });
    Object.defineProperty(this, "lastFocusedRestore", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(_ref2, all) {
        var key = _ref2.key;

        if (all.length === 1 && 'vlPlaceholder' in all[0].dataset) {
          all[0].dataset.index = key;
          return all[0];
        }

        return all.reduce(function (focused, node) {
          return focused || Number(node.dataset.index) === key && node;
        }, null);
      }
    });
    Object.defineProperty(this, "getExtentIndex", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(index) {
        return Math.floor(index / _this4.uiRef.dimensionToExtent);
      }
    });
    Object.defineProperty(this, "findSpottableItem", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(indexFrom, indexTo) {
        var _this4$props = _this4.props,
            dataSize = _this4$props.dataSize,
            isItemDisabled = _this4$props.isItemDisabled,
            safeIndexFrom = (0, _clamp.default)(0, dataSize - 1, indexFrom),
            safeIndexTo = (0, _clamp.default)(-1, dataSize, indexTo),
            delta = indexFrom < indexTo ? 1 : -1;

        if (indexFrom < 0 && indexTo < 0 || indexFrom >= dataSize && indexTo >= dataSize) {
          return -1;
        } else if (isItemDisabled === isItemDisabledDefault) {
          return safeIndexFrom;
        }

        if (safeIndexFrom !== safeIndexTo) {
          for (var i = safeIndexFrom; i !== safeIndexTo; i += delta) {
            if (!isItemDisabled(i)) {
              return i;
            }
          }
        }

        return -1;
      }
    });
    Object.defineProperty(this, "findSpottableItemWithPositionInExtent", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(indexFrom, indexTo, position) {
        var dataSize = _this4.props.dataSize,
            dimensionToExtent = _this4.uiRef.dimensionToExtent;

        if (0 <= indexFrom && indexFrom < dataSize && -1 <= indexTo && indexTo <= dataSize && 0 <= position && position < dimensionToExtent) {
          var isItemDisabled = _this4.props.isItemDisabled,
              direction = indexFrom < indexTo ? 1 : -1,
              delta = direction * dimensionToExtent,
              diffPosition = indexFrom % dimensionToExtent - position,
              startIndex = indexFrom - diffPosition + (direction * diffPosition > 0 ? delta : 0);

          for (var i = startIndex; direction * (indexTo - i) > 0; i += delta) {
            if (!isItemDisabled(i)) {
              return i;
            }
          }
        }

        return -1;
      }
    });
    Object.defineProperty(this, "findSpottableExtent", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(indexFrom, isForward) {
        var dataSize = _this4.props.dataSize,
            dimensionToExtent = _this4.uiRef.dimensionToExtent,
            findSpottableItem = _this4.findSpottableItem,
            getExtentIndex = _this4.getExtentIndex,
            firstIndexInExtent = getExtentIndex(indexFrom) * dimensionToExtent;
        var index;

        if (isForward) {
          index = findSpottableItem(firstIndexInExtent + dimensionToExtent, dataSize);
        } else {
          index = findSpottableItem(firstIndexInExtent - 1, -1);
        }

        return getExtentIndex(index);
      }
    });
    Object.defineProperty(this, "findNearestSpottableItemInExtent", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(index, extentIndex) {
        var _this4$props2 = _this4.props,
            dataSize = _this4$props2.dataSize,
            isItemDisabled = _this4$props2.isItemDisabled,
            dimensionToExtent = _this4.uiRef.dimensionToExtent,
            currentPosInExtent = (0, _clamp.default)(0, dataSize - 1, index) % dimensionToExtent,
            firstIndexInExtent = (0, _clamp.default)(0, _this4.getExtentIndex(dataSize - 1), extentIndex) * dimensionToExtent,
            lastIndexInExtent = (0, _clamp.default)(firstIndexInExtent, dataSize, firstIndexInExtent + dimensionToExtent);
        var minDistance = dimensionToExtent,
            distance,
            nearestIndex = -1;

        for (var i = firstIndexInExtent; i < lastIndexInExtent; ++i) {
          if (!isItemDisabled(i)) {
            distance = Math.abs(currentPosInExtent - i % dimensionToExtent);

            if (distance < minDistance) {
              minDistance = distance;
              nearestIndex = i;
            }
          }
        }

        return nearestIndex;
      }
    });
    Object.defineProperty(this, "getIndexToScroll", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(direction, currentIndex) {
        var _this4$props3 = _this4.props,
            dataSize = _this4$props3.dataSize,
            spacing = _this4$props3.spacing,
            _this4$uiRef = _this4.uiRef,
            dimensionToExtent = _this4$uiRef.dimensionToExtent,
            _this4$uiRef$primary = _this4$uiRef.primary,
            clientSize = _this4$uiRef$primary.clientSize,
            gridSize = _this4$uiRef$primary.gridSize,
            itemSize = _this4$uiRef$primary.itemSize,
            scrollPosition = _this4$uiRef.scrollPosition,
            findSpottableItem = _this4.findSpottableItem,
            numOfItemsInPage = Math.floor((clientSize + spacing) / gridSize) * dimensionToExtent,
            firstFullyVisibleIndex = Math.ceil(scrollPosition / gridSize) * dimensionToExtent,
            lastFullyVisibleIndex = Math.min(dataSize - 1, Math.floor((scrollPosition + clientSize - itemSize) / gridSize) * dimensionToExtent);
        var candidateIndex = -1;
        /* First, find a spottable item in this page */

        if (direction === 'down') {
          // Page Down
          if (lastFullyVisibleIndex - lastFullyVisibleIndex % dimensionToExtent > currentIndex) {
            // If a current focused item is in the last visible line.
            candidateIndex = findSpottableItem(lastFullyVisibleIndex, currentIndex - currentIndex % dimensionToExtent + dimensionToExtent - 1);
          }
        } else if (firstFullyVisibleIndex + dimensionToExtent <= currentIndex) {
          // Page Up,  if a current focused item is in the first visible line.
          candidateIndex = findSpottableItem(firstFullyVisibleIndex, currentIndex - currentIndex % dimensionToExtent);
        }
        /* Second, find a spottable item in the next page */


        if (candidateIndex === -1) {
          if (direction === 'down') {
            // Page Down
            candidateIndex = findSpottableItem(lastFullyVisibleIndex + numOfItemsInPage, lastFullyVisibleIndex);
          } else {
            // Page Up
            candidateIndex = findSpottableItem(firstFullyVisibleIndex - numOfItemsInPage, firstFullyVisibleIndex);
          }
        }
        /* Last, find a spottable item in a whole data */


        if (candidateIndex === -1) {
          if (direction === 'down') {
            // Page Down
            candidateIndex = findSpottableItem(lastFullyVisibleIndex + numOfItemsInPage + 1, dataSize);
          } else {
            // Page Up
            candidateIndex = findSpottableItem(firstFullyVisibleIndex - numOfItemsInPage - 1, -1);
          }
        }
        /* For grid lists, find the nearest item from the current item */


        if (candidateIndex !== -1) {
          return _this4.findNearestSpottableItemInExtent(currentIndex, _this4.getExtentIndex(candidateIndex));
        } else {
          return -1;
        }
      }
    });
    Object.defineProperty(this, "scrollToNextItem", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(_ref3) {
        var direction = _ref3.direction,
            focusedItem = _ref3.focusedItem;

        var cbScrollTo = _this4.props.cbScrollTo,
            _this4$uiRef$state = _this4.uiRef.state,
            firstIndex = _this4$uiRef$state.firstIndex,
            numOfItems = _this4$uiRef$state.numOfItems,
            focusedIndex = Number.parseInt(focusedItem.getAttribute(_Scrollable.dataIndexAttribute)),
            indexToScroll = _this4.getIndexToScroll(direction, focusedIndex);

        if (indexToScroll !== -1 && focusedIndex !== indexToScroll) {
          if (firstIndex <= indexToScroll && indexToScroll < firstIndex + numOfItems) {
            var node = _this4.uiRef.containerRef.querySelector("[data-index='".concat(indexToScroll, "'].spottable"));

            if (node) {
              _spotlight.default.focus(node);
            }
          } else {
            // Scroll to the next spottable item without animation
            if (!_spotlight.default.isPaused()) {
              _spotlight.default.pause();
            }

            focusedItem.blur();
            _this4.nodeIndexToBeFocused = _this4.lastFocusedIndex = indexToScroll;
          }

          cbScrollTo({
            index: indexToScroll,
            stickTo: direction === 'down' ? 'end' : 'start',
            animate: false
          });
        }
      }
    });
    Object.defineProperty(this, "setRestrict", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(bool) {
        _spotlight.default.set(_this4.props.spotlightId, {
          restrict: bool ? 'self-only' : 'self-first'
        });
      }
    });
    Object.defineProperty(this, "setSpotlightContainerRestrict", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(keyCode, target) {
        var dataSize = _this4.props.dataSize,
            _this4$uiRef2 = _this4.uiRef,
            isPrimaryDirectionVertical = _this4$uiRef2.isPrimaryDirectionVertical,
            dimensionToExtent = _this4$uiRef2.dimensionToExtent,
            index = Number.parseInt(target.getAttribute(_Scrollable.dataIndexAttribute)),
            canMoveBackward = index >= dimensionToExtent,
            canMoveForward = index < dataSize - ((dataSize - 1) % dimensionToExtent + 1);
        var isSelfOnly = false;

        if (isPrimaryDirectionVertical) {
          if (isUp(keyCode) && canMoveBackward || isDown(keyCode) && canMoveForward) {
            isSelfOnly = true;
          }
        } else if (isLeft(keyCode) && canMoveBackward || isRight(keyCode) && canMoveForward) {
          isSelfOnly = true;
        }

        _this4.setRestrict(isSelfOnly);
      }
    });
    Object.defineProperty(this, "jumpToSpottableItem", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(keyCode, repeat, target) {
        var _this4$props4 = _this4.props,
            cbScrollTo = _this4$props4.cbScrollTo,
            dataSize = _this4$props4.dataSize,
            isItemDisabled = _this4$props4.isItemDisabled,
            rtl = _this4$props4.rtl,
            wrap = _this4$props4.wrap,
            _this4$uiRef$state2 = _this4.uiRef.state,
            firstIndex = _this4$uiRef$state2.firstIndex,
            numOfItems = _this4$uiRef$state2.numOfItems,
            _this4$uiRef3 = _this4.uiRef,
            dimensionToExtent = _this4$uiRef3.dimensionToExtent,
            isPrimaryDirectionVertical = _this4$uiRef3.isPrimaryDirectionVertical,
            currentIndex = Number.parseInt(target.getAttribute(_Scrollable.dataIndexAttribute)),
            isForward = isPrimaryDirectionVertical && isDown(keyCode) || !isPrimaryDirectionVertical && (!rtl && isRight(keyCode) || rtl && isLeft(keyCode)) || null,
            isBackward = isPrimaryDirectionVertical && isUp(keyCode) || !isPrimaryDirectionVertical && (!rtl && isLeft(keyCode) || rtl && isRight(keyCode)) || null;
        _this4.isScrolledBy5way = false;
        _this4.isWrappedBy5way = false; // If the currently focused item is disabled, we assume that all items in a list are disabled.

        if (!wrap && isItemDisabled === isItemDisabledDefault || isItemDisabled(currentIndex) || !isForward && !isBackward) {
          return false;
        }

        var currentExtent = _this4.getExtentIndex(currentIndex);

        var nextIndex = -1,
            animate = true,
            isWrapped = false,
            spottableExtent = -1;

        if (isForward) {
          nextIndex = _this4.findSpottableItemWithPositionInExtent(currentIndex + 1, dataSize, currentIndex % dimensionToExtent);

          if (nextIndex === -1) {
            spottableExtent = _this4.findSpottableExtent(currentIndex, true);

            if (spottableExtent === -1) {
              if (wrap && !repeat) {
                var candidateExtent = _this4.getExtentIndex(_this4.findSpottableItem(0, dataSize)); // If currentExtent is equal to candidateExtent,
                // it means that the current extent is the only spottable extent.
                // So we find nextIndex only when currentExtent is different from candidateExtent.


                if (currentExtent !== candidateExtent) {
                  nextIndex = _this4.findNearestSpottableItemInExtent(currentIndex, candidateExtent);
                  animate = wrap === true;
                  isWrapped = true;
                }
              } // If there is no item which could get focus forward,
              // we need to set restriction option to `self-first`.


              if (nextIndex === -1) {
                _this4.setRestrict(false);
              }
            } else {
              nextIndex = _this4.findNearestSpottableItemInExtent(currentIndex, spottableExtent);
            }
          }
        } else {
          // isBackward
          nextIndex = _this4.findSpottableItemWithPositionInExtent(currentIndex - 1, -1, currentIndex % dimensionToExtent);

          if (nextIndex === -1) {
            spottableExtent = _this4.findSpottableExtent(currentIndex, false);

            if (spottableExtent === -1) {
              if (wrap && !repeat) {
                var _candidateExtent = _this4.getExtentIndex(_this4.findSpottableItem(dataSize - 1, -1)); // If currentExtent is equal to candidateExtent,
                // it means that the current extent is the only spottable extent.
                // So we find nextIndex only when currentExtent is different from candidateExtent.


                if (currentExtent !== _candidateExtent) {
                  nextIndex = _this4.findNearestSpottableItemInExtent(currentIndex, _candidateExtent);
                  animate = wrap === true;
                  isWrapped = true;
                }
              } // If there is no item which could get focus forward,
              // we need to set restriction option to `self-first`.


              if (nextIndex === -1) {
                _this4.setRestrict(false);
              }
            } else {
              nextIndex = _this4.findNearestSpottableItemInExtent(currentIndex, spottableExtent);
            }
          }
        }

        if (nextIndex !== -1) {
          if (isWrapped) {
            _this4.isWrappedBy5way = true;
          }

          if (firstIndex <= nextIndex && nextIndex < firstIndex + numOfItems) {
            _this4.focusOnItem(nextIndex);
          } else {
            _this4.nodeIndexToBeFocused = _this4.lastFocusedIndex = nextIndex;

            if (!_spotlight.default.isPaused()) {
              _spotlight.default.pause();
            }

            target.blur();
            _this4.isScrolledBy5way = true;
            cbScrollTo({
              index: nextIndex,
              stickTo: isForward ? 'end' : 'start',
              animate: animate
            });
          }

          return true;
        }

        return false;
      }
    });
    Object.defineProperty(this, "onKeyDown", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(ev) {
        var keyCode = ev.keyCode,
            repeat = ev.repeat,
            target = ev.target;
        _this4.isScrolledByJump = false;

        if ((0, _spotlight.getDirection)(keyCode)) {
          ev.preventDefault();

          _this4.setSpotlightContainerRestrict(keyCode, target);

          _spotlight.default.setPointerMode(false);

          if (_this4.jumpToSpottableItem(keyCode, repeat, target)) {
            // Pause Spotlight temporarily so we don't focus twice
            _spotlight.default.pause();

            document.addEventListener('keyup', _this4.resumeSpotlight);
          }
        }
      }
    });
    Object.defineProperty(this, "resumeSpotlight", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value() {
        _spotlight.default.resume();

        document.removeEventListener('keyup', _this4.resumeSpotlight);
      }
    });
    Object.defineProperty(this, "handleGlobalKeyDown", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value() {
        _this4.setContainerDisabled(false);
      }
    });
    Object.defineProperty(this, "focusOnNode", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(node) {
        if (node) {
          _spotlight.default.focus(node);
        }
      }
    });
    Object.defineProperty(this, "focusOnItem", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(index) {
        var item = _this4.uiRef.containerRef.querySelector("[data-index='".concat(index, "'].spottable"));

        if (_spotlight.default.isPaused()) {
          _spotlight.default.resume();
        }

        _this4.focusOnNode(item);

        _this4.nodeIndexToBeFocused = null;
        _this4.isScrolledByJump = false;
      }
    });
    Object.defineProperty(this, "initItemRef", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(ref, index) {
        if (ref) {
          if (type === JS) {
            _this4.focusOnItem(index);
          } else {
            // If focusing the item of VirtuallistNative, `onFocus` in Scrollable will be called.
            // Then VirtualListNative tries to scroll again differently from VirtualList.
            // So we would like to skip `focus` handling when focusing the item as a workaround.
            _this4.isScrolledByJump = true;

            _this4.focusOnItem(index);
          }
        }
      }
    });
    Object.defineProperty(this, "focusByIndex", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(index) {
        // We have to focus node async for now since list items are not yet ready when it reaches componentDid* lifecycle methods
        setTimeout(function () {
          _this4.focusOnItem(index);
        }, 0);
      }
    });
    Object.defineProperty(this, "isNeededScrollingPlaceholder", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value() {
        return _this4.nodeIndexToBeFocused != null && _spotlight.default.isPaused();
      }
    });
    Object.defineProperty(this, "handlePlaceholderFocus", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(ev) {
        var placeholder = ev.currentTarget;

        if (placeholder) {
          var index = placeholder.dataset.index;

          if (index) {
            _this4.preservedIndex = parseInt(index);
            _this4.restoreLastFocused = true;
          }
        }
      }
    });
    Object.defineProperty(this, "isPlaceholderFocused", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value() {
        var current = _spotlight.default.getCurrent();

        if (current && current.dataset.vlPlaceholder && _this4.uiRef.containerRef.contains(current)) {
          return true;
        }

        return false;
      }
    });
    Object.defineProperty(this, "restoreFocus", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value() {
        if (_this4.restoreLastFocused && !_this4.isPlaceholderFocused()) {
          var spotlightId = _this4.props.spotlightId,
              node = _this4.uiRef.containerRef.querySelector("[data-spotlight-id=\"".concat(spotlightId, "\"] [data-index=\"").concat(_this4.preservedIndex, "\"]"));

          if (node) {
            // if we're supposed to restore focus and virtual list has positioned a set of items
            // that includes lastFocusedIndex, clear the indicator
            _this4.restoreLastFocused = false; // try to focus the last focused item

            var foundLastFocused = _spotlight.default.focus(node); // but if that fails (because it isn't found or is disabled), focus the container so
            // spotlight isn't lost


            if (!foundLastFocused) {
              _this4.restoreLastFocused = true;

              _spotlight.default.focus(spotlightId);
            }
          }
        }
      }
    });
    Object.defineProperty(this, "calculatePositionOnFocus", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(_ref4) {
        var item = _ref4.item,
            _ref4$scrollPosition = _ref4.scrollPosition,
            scrollPosition = _ref4$scrollPosition === void 0 ? _this4.uiRef.scrollPosition : _ref4$scrollPosition;
        var pageScroll = _this4.props.pageScroll,
            numOfItems = _this4.uiRef.state.numOfItems,
            primary = _this4.uiRef.primary,
            offsetToClientEnd = primary.clientSize - primary.itemSize,
            focusedIndex = Number.parseInt(item.getAttribute(_Scrollable.dataIndexAttribute));

        if (!isNaN(focusedIndex)) {
          var gridPosition = _this4.uiRef.getGridPosition(focusedIndex);

          if (numOfItems > 0 && focusedIndex % numOfItems !== _this4.lastFocusedIndex % numOfItems) {
            var node = _this4.uiRef.getItemNode(_this4.lastFocusedIndex);

            if (node) {
              node.blur();
            }
          }

          _this4.nodeIndexToBeFocused = null;
          _this4.lastFocusedIndex = focusedIndex;

          if (primary.clientSize >= primary.itemSize) {
            if (gridPosition.primaryPosition > scrollPosition + offsetToClientEnd) {
              // forward over
              gridPosition.primaryPosition -= pageScroll ? 0 : offsetToClientEnd;
            } else if (gridPosition.primaryPosition >= scrollPosition) {
              // inside of client
              if (type === JS) {
                gridPosition.primaryPosition = scrollPosition;
              } else {
                // This code uses the trick to change the target position slightly which will not affect the actual result
                // since a browser ignore `scrollTo` method if the target position is same as the current position.
                gridPosition.primaryPosition = scrollPosition + (_this4.uiRef.scrollPosition === scrollPosition ? 0.1 : 0);
              }
            } else {
              // backward over
              gridPosition.primaryPosition -= pageScroll ? offsetToClientEnd : 0;
            }
          } // Since the result is used as a target position to be scrolled,
          // scrondaryPosition should be 0 here.


          gridPosition.secondaryPosition = 0;
          return _this4.uiRef.gridPositionToItemPosition(gridPosition);
        }
      }
    });
    Object.defineProperty(this, "shouldPreventScrollByFocus", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value() {
        return type === JS ? _this4.isScrolledBy5way : _this4.isScrolledBy5way || _this4.isScrolledByJump;
      }
    });
    Object.defineProperty(this, "shouldPreventOverscrollEffect", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value() {
        return _this4.isWrappedBy5way;
      }
    });
    Object.defineProperty(this, "setLastFocusedNode", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(node) {
        _this4.lastFocusedIndex = node.dataset && Number.parseInt(node.dataset.index);
      }
    });
    Object.defineProperty(this, "updateStatesAndBounds", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(_ref5) {
        var cbScrollTo = _ref5.cbScrollTo,
            dataSize = _ref5.dataSize,
            moreInfo = _ref5.moreInfo,
            numOfItems = _ref5.numOfItems;
        var preservedIndex = _this4.preservedIndex;

        if (_this4.restoreLastFocused && numOfItems > 0 && preservedIndex < dataSize && (preservedIndex < moreInfo.firstVisibleIndex || preservedIndex > moreInfo.lastVisibleIndex)) {
          // If we need to restore last focus and the index is beyond the screen,
          // we call `scrollTo` to create DOM for it.
          cbScrollTo({
            index: preservedIndex,
            animate: false,
            focus: true
          });
          _this4.isScrolledByJump = true;
          return true;
        } else {
          return false;
        }
      }
    });
    Object.defineProperty(this, "getScrollBounds", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value() {
        return _this4.uiRef.getScrollBounds();
      }
    });
    Object.defineProperty(this, "getComponentProps", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(index) {
        return index === _this4.nodeIndexToBeFocused ? {
          ref: function ref(_ref6) {
            return _this4.initItemRef(_ref6, index);
          }
        } : {};
      }
    });
    Object.defineProperty(this, "initUiRef", {
      configurable: true,
      enumerable: true,
      writable: true,
      value: function value(ref) {
        if (ref) {
          _this4.uiRef = ref;

          _this4.props.initUiChildRef(ref);
        }
      }
    });
  }, _temp;
};
/**
 * A Moonstone-styled base component for [VirtualList]{@link moonstone/VirtualList.VirtualList} and
 * [VirtualGridList]{@link moonstone/VirtualList.VirtualGridList}.
 *
 * @class VirtualListBase
 * @memberof moonstone/VirtualList
 * @extends ui/VirtualList.VirtualListBase
 * @ui
 * @public
 */


var VirtualListBase = VirtualListBaseFactory(JS);
exports.VirtualListBase = VirtualListBase;
VirtualListBase.displayName = 'VirtualListBase';
/**
 * Activates the component for voice control.
 *
 * @name data-webos-voice-focused
 * @memberof moonstone/VirtualList.VirtualListBase.prototype
 * @type {Boolean}
 * @public
 */

/**
 * The voice control group label.
 *
 * @name data-webos-voice-group-label
 * @memberof moonstone/VirtualList.VirtualListBase.prototype
 * @type {String}
 * @public
 */

/**
 * A Moonstone-styled base component for [VirtualListNative]{@link moonstone/VirtualList.VirtualListNative} and
 * [VirtualGridListNative]{@link moonstone/VirtualList.VirtualGridListNative}.
 *
 * @class VirtualListBaseNative
 * @memberof moonstone/VirtualList
 * @extends ui/VirtualList.VirtualListBaseNative
 * @ui
 * @private
 */

var VirtualListBaseNative = VirtualListBaseFactory(Native);
exports.VirtualListBaseNative = VirtualListBaseNative;
VirtualListBaseNative.displayName = 'VirtualListBaseNative';

var ScrollableVirtualList = function ScrollableVirtualList(props) {
  return (// eslint-disable-line react/jsx-no-bind
    _react.default.createElement(_Scrollable.Scrollable, _extends({}, props, {
      childRenderer: function childRenderer(childProps) {
        return (// eslint-disable-line react/jsx-no-bind
          _react.default.createElement(VirtualListBase, _extends({}, childProps, {
            itemsRenderer: function itemsRenderer(_ref7) {
              var cc = _ref7.cc,
                  handlePlaceholderFocus = _ref7.handlePlaceholderFocus,
                  initUiItemContainerRef = _ref7.initItemContainerRef,
                  needsScrollingPlaceholder = _ref7.needsScrollingPlaceholder,
                  primary = _ref7.primary;
              return (// eslint-disable-line react/jsx-no-bind
                [cc.length ? _react.default.createElement("div", {
                  key: "0",
                  ref: initUiItemContainerRef,
                  role: "list"
                }, cc) : null, primary ? null : _react.default.createElement(SpotlightPlaceholder, {
                  "data-index": 0,
                  "data-vl-placeholder": true,
                  key: "1",
                  onFocus: handlePlaceholderFocus
                }), needsScrollingPlaceholder ? _react.default.createElement(SpotlightPlaceholder, {
                  key: "2"
                }) : null]
              );
            }
          }))
        );
      }
    }))
  );
};

exports.ScrollableVirtualList = ScrollableVirtualList;
ScrollableVirtualList.propTypes =
/** @lends moonstone/VirtualList.VirtualListBase.prototype */
{
  /**
   * Direction of the list.
   *
   * Valid values are:
   * * `'horizontal'`, and
   * * `'vertical'`.
   *
   * @type {String}
   * @default 'vertical'
   * @public
   */
  direction: _propTypes.default.oneOf(['horizontal', 'vertical'])
};
ScrollableVirtualList.defaultProps = {
  direction: 'vertical'
};

var ScrollableVirtualListNative = function ScrollableVirtualListNative(props) {
  return _react.default.createElement(_ScrollableNative.default, _extends({}, props, {
    childRenderer: function childRenderer(childProps) {
      return (// eslint-disable-line react/jsx-no-bind
        _react.default.createElement(VirtualListBaseNative, _extends({}, childProps, {
          itemsRenderer: function itemsRenderer(_ref8) {
            var cc = _ref8.cc,
                handlePlaceholderFocus = _ref8.handlePlaceholderFocus,
                initUiItemContainerRef = _ref8.initItemContainerRef,
                needsScrollingPlaceholder = _ref8.needsScrollingPlaceholder,
                primary = _ref8.primary;
            return (// eslint-disable-line react/jsx-no-bind
              [cc.length ? _react.default.createElement("div", {
                key: "0",
                ref: initUiItemContainerRef,
                role: "list"
              }, cc) : null, primary ? null : _react.default.createElement(SpotlightPlaceholder, {
                "data-index": 0,
                "data-vl-placeholder": true,
                key: "1",
                onFocus: handlePlaceholderFocus
              }), needsScrollingPlaceholder ? _react.default.createElement(SpotlightPlaceholder, {
                key: "2"
              }) : null]
            );
          }
        }))
      );
    }
  }));
};

exports.ScrollableVirtualListNative = ScrollableVirtualListNative;
ScrollableVirtualListNative.propTypes =
/** @lends moonstone/VirtualList.VirtualListBaseNative.prototype */
{
  /**
   * Direction of the list.
   *
   * Valid values are:
   * * `'horizontal'`, and
   * * `'vertical'`.
   *
   * @type {String}
   * @default 'vertical'
   * @public
   */
  direction: _propTypes.default.oneOf(['horizontal', 'vertical'])
};
ScrollableVirtualListNative.defaultProps = {
  direction: 'vertical'
};
var _default = VirtualListBase;
exports.default = _default;