"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.useThemeVirtualList = exports["default"] = void 0;

var _jsxRuntime = require("react/jsx-runtime");

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

var _Accelerator = _interopRequireDefault(require("@enact/spotlight/Accelerator"));

var _Pause = _interopRequireDefault(require("@enact/spotlight/Pause"));

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

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

var _utilDOM = _interopRequireDefault(require("@enact/ui/useScroll/utilDOM"));

var _react = require("react");

var _useScroll = require("../useScroll");

var _useEvent = require("./useEvent");

var _usePreventScroll = _interopRequireDefault(require("./usePreventScroll"));

var _useSpotlight = require("./useSpotlight");

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

function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }

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

function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; 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 _objectWithoutPropertiesLoose(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]; } return target; }

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }

function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(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; }

var SpotlightAccelerator = new _Accelerator["default"]();
var SpotlightPlaceholder = (0, _Spottable.Spottable)('div');

var nop = function nop() {},
    getNumberValue = function getNumberValue(index) {
  // using '+ operator' for string > number conversion based on performance: https://jsperf.com/convert-string-to-number-techniques/7
  var number = +index; // should return -1 if index is not a number or a negative value

  return number >= 0 ? number : -1;
};

var useSpottable = function useSpottable(props, instances) {
  var scrollMode = props.scrollMode;
  var itemRefs = instances.itemRefs,
      scrollContainerRef = instances.scrollContainerRef,
      scrollContentHandle = instances.scrollContentHandle;

  var getItemNode = function getItemNode(index) {
    var itemNode = itemRefs.current[index % scrollContentHandle.current.state.numOfItems];
    return itemNode && parseInt(itemNode.dataset.index) === index ? itemNode : null;
  }; // Mutable value


  var mutableRef = (0, _react.useRef)({
    isScrolledBy5way: false,
    isScrolledByJump: false,
    isWrappedBy5way: false,
    lastFocusedIndex: null,
    pause: new _Pause["default"]('VirtualListBasic')
  });
  var pause = mutableRef.current.pause; // Hooks

  (0, _useSpotlight.useSpotlightConfig)(props, {
    spottable: mutableRef
  });

  var _useEventKey = (0, _useEvent.useEventKey)(props, instances, {
    handlePageUpDownKeyDown: function handlePageUpDownKeyDown() {
      mutableRef.current.isScrolledBy5way = false;
    },
    handleDirectionKeyDown: function handleDirectionKeyDown(ev, eventType, param) {
      switch (eventType) {
        case 'acceleratedKeyDown':
          onAcceleratedKeyDown(param);
          break;

        case 'keyDown':
          if (_spotlight["default"].move(param.direction)) {
            var nextTargetIndex = _spotlight["default"].getCurrent().dataset.index;

            ev.preventDefault();
            ev.stopPropagation();

            if (typeof nextTargetIndex === 'string') {
              onAcceleratedKeyDown(_objectSpread(_objectSpread({}, param), {}, {
                nextIndex: getNumberValue(nextTargetIndex)
              }));
            }
          }

          break;

        case 'keyLeave':
          SpotlightAccelerator.reset();
          break;
      }
    },
    handle5WayKeyUp: function handle5WayKeyUp() {
      SpotlightAccelerator.reset();
    },
    spotlightAcceleratorProcessKey: function spotlightAcceleratorProcessKey(ev) {
      return SpotlightAccelerator.processKey(ev, nop);
    }
  }),
      addGlobalKeyDownEventListener = _useEventKey.addGlobalKeyDownEventListener,
      removeGlobalKeyDownEventListener = _useEventKey.removeGlobalKeyDownEventListener;

  var _useSpotlightRestore = (0, _useSpotlight.useSpotlightRestore)(props, _objectSpread(_objectSpread({}, instances), {}, {
    spottable: mutableRef
  }), {
    focusByIndex: focusByIndex,
    getItemNode: getItemNode
  }),
      handlePlaceholderFocus = _useSpotlightRestore.handlePlaceholderFocus,
      handleRestoreLastFocus = _useSpotlightRestore.handleRestoreLastFocus,
      setPreservedIndex = _useSpotlightRestore.setPreservedIndex,
      updateStatesAndBounds = _useSpotlightRestore.updateStatesAndBounds;

  function pauseSpotlight(bool) {
    if (bool) {
      pause.pause();
    } else {
      pause.resume();
    }
  }

  var setContainerDisabled = (0, _react.useCallback)(function (bool) {
    if (scrollContainerRef.current) {
      scrollContainerRef.current.dataset.spotlightContainerDisabled = bool;

      if (bool) {
        addGlobalKeyDownEventListener(handleGlobalKeyDown);
      } else {
        removeGlobalKeyDownEventListener();
      }
    }
  }, [addGlobalKeyDownEventListener, handleGlobalKeyDown, removeGlobalKeyDownEventListener, scrollContainerRef]); // eslint-disable-next-line react-hooks/exhaustive-deps

  function handleGlobalKeyDown(ev) {
    // To prevent scrolling by native scroller
    if (scrollMode === 'native') {
      ev.preventDefault();
      ev.stopPropagation();
    }

    setContainerDisabled(false);
  }

  (0, _react.useEffect)(function () {
    return function () {
      // TODO: Fix eslint
      pause.resume(); // eslint-disable-line react-hooks/exhaustive-deps

      SpotlightAccelerator.reset();
      setContainerDisabled(false);
    };
  }, []); // eslint-disable-line react-hooks/exhaustive-deps
  // Functions

  function onAcceleratedKeyDown(_ref) {
    var isWrapped = _ref.isWrapped,
        keyCode = _ref.keyCode,
        nextIndex = _ref.nextIndex,
        repeat = _ref.repeat,
        target = _ref.target;
    var cbScrollTo = props.cbScrollTo,
        wrap = props.wrap;
    var _scrollContentHandle$ = scrollContentHandle.current,
        dimensionToExtent = _scrollContentHandle$.dimensionToExtent,
        _scrollContentHandle$2 = _scrollContentHandle$.primary,
        clientSize = _scrollContentHandle$2.clientSize,
        itemSize = _scrollContentHandle$2.itemSize,
        scrollPosition = _scrollContentHandle$.scrollPosition,
        scrollPositionTarget = _scrollContentHandle$.scrollPositionTarget;
    var index = getNumberValue(target.dataset.index);
    var direction = (0, _spotlight.getDirection)(keyCode);
    mutableRef.current.isScrolledBy5way = false;
    mutableRef.current.isScrolledByJump = false;

    if (nextIndex >= 0 && index >= 0) {
      var row = Math.floor(index / dimensionToExtent),
          nextRow = Math.floor(nextIndex / dimensionToExtent),
          start = scrollContentHandle.current.getGridPosition(nextIndex).primaryPosition,
          end = props.itemSizes ? scrollContentHandle.current.getItemBottomPosition(nextIndex) : start + itemSize,
          startBoundary = scrollMode === 'native' ? scrollPosition : scrollPositionTarget,
          endBoundary = startBoundary + clientSize;
      mutableRef.current.lastFocusedIndex = nextIndex;

      if (start >= startBoundary && end <= endBoundary) {
        // The next item could be still out of viewport. So we need to prevent scrolling into view with `isScrolledBy5way` flag.
        mutableRef.current.isScrolledBy5way = true;
        focusByIndex(nextIndex, direction);
        mutableRef.current.isScrolledBy5way = false;
      } else if (row === nextRow) {
        focusByIndex(nextIndex, direction);
      } else {
        var itemNode = getItemNode(nextIndex);
        var stickTo = Math.abs(endBoundary - end) < Math.abs(startBoundary - start) ? 'end' : 'start';
        mutableRef.current.isScrolledBy5way = true;
        mutableRef.current.isWrappedBy5way = isWrapped;

        if (isWrapped && wrap === true && itemNode === null) {
          pause.pause();
          target.blur();
        }

        focusByIndex(nextIndex, direction);
        cbScrollTo({
          index: nextIndex,
          stickTo: stickTo,
          animate: !(isWrapped && wrap === 'noAnimation')
        });
      }
    } else if (!repeat && _spotlight["default"].move(direction)) {
      SpotlightAccelerator.reset();
    }
  }

  function focusOnNode(node) {
    if (node) {
      return _spotlight["default"].focus(node);
    }

    return false;
  }

  function focusByIndex(index, direction) {
    var itemNode = getItemNode(index);
    var returnVal = false;

    if (!itemNode && index >= 0 && index < props.dataSize) {
      // Item is valid but since the the dom doesn't exist yet, we set the index to focus after the ongoing update
      setPreservedIndex(index, direction);
    } else {
      var current = _spotlight["default"].getCurrent(),
          candidate = current ? (0, _target.getTargetByDirectionFromElement)(direction, current) : itemNode; // Remove any preservedIndex


      setPreservedIndex(-1);

      if (mutableRef.current.isWrappedBy5way) {
        SpotlightAccelerator.reset();
        mutableRef.current.isWrappedBy5way = false;
      }

      pause.resume();

      if (_utilDOM["default"].containsDangerously(itemNode, candidate)) {
        returnVal = focusOnNode(candidate);
      } else {
        returnVal = focusOnNode(itemNode);
      }

      mutableRef.current.isScrolledBy5way = false;
      mutableRef.current.isScrolledByJump = false;
    }

    return returnVal;
  }

  function calculatePositionOnFocus(_ref2) {
    var item = _ref2.item,
        _ref2$scrollPosition = _ref2.scrollPosition,
        scrollPosition = _ref2$scrollPosition === void 0 ? scrollContentHandle.current.scrollPosition : _ref2$scrollPosition;
    var pageScroll = props.pageScroll,
        _scrollContentHandle$3 = scrollContentHandle.current,
        numOfItems = _scrollContentHandle$3.state.numOfItems,
        primary = _scrollContentHandle$3.primary,
        offsetToClientEnd = primary.clientSize - primary.itemSize,
        focusedIndex = getNumberValue(item.getAttribute(_useScroll.dataIndexAttribute));

    if (focusedIndex >= 0) {
      var gridPosition = scrollContentHandle.current.getGridPosition(focusedIndex);

      if (numOfItems > 0 && focusedIndex % numOfItems !== mutableRef.current.lastFocusedIndex % numOfItems) {
        var itemNode = getItemNode(mutableRef.current.lastFocusedIndex);

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

      mutableRef.current.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 (scrollMode === 'translate') {
            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 + (scrollContentHandle.current.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 scrollContentHandle.current.gridPositionToItemPosition(gridPosition);
    }
  }

  function shouldPreventScrollByFocus() {
    return scrollMode === 'translate' ? mutableRef.current.isScrolledBy5way : mutableRef.current.isScrolledBy5way || mutableRef.current.isScrolledByJump;
  }

  function setLastFocusedNode(node) {
    mutableRef.current.lastFocusedIndex = node.dataset && getNumberValue(node.dataset.index);
  }

  function getScrollBounds() {
    return scrollContentHandle.current.getScrollBounds();
  } // Return


  return {
    calculatePositionOnFocus: calculatePositionOnFocus,
    focusByIndex: focusByIndex,
    focusOnNode: focusOnNode,
    getScrollBounds: getScrollBounds,
    handlePlaceholderFocus: handlePlaceholderFocus,
    handleRestoreLastFocus: handleRestoreLastFocus,
    pauseSpotlight: pauseSpotlight,
    setContainerDisabled: setContainerDisabled,
    setLastFocusedNode: setLastFocusedNode,
    shouldPreventScrollByFocus: shouldPreventScrollByFocus,
    updateStatesAndBounds: updateStatesAndBounds
  };
};

var useThemeVirtualList = function useThemeVirtualList(props) {
  var itemRefs = props.itemRefs,
      scrollContainerRef = props.scrollContainerRef,
      scrollContentHandle = props.scrollContentHandle,
      scrollContentRef = props.scrollContentRef; // Hooks

  var instance = {
    itemRefs: itemRefs,
    scrollContainerRef: scrollContainerRef,
    scrollContentHandle: scrollContentHandle,
    scrollContentRef: scrollContentRef
  };

  var _useSpottable = useSpottable(props, instance),
      calculatePositionOnFocus = _useSpottable.calculatePositionOnFocus,
      focusByIndex = _useSpottable.focusByIndex,
      focusOnNode = _useSpottable.focusOnNode,
      getScrollBounds = _useSpottable.getScrollBounds,
      handlePlaceholderFocus = _useSpottable.handlePlaceholderFocus,
      handleRestoreLastFocus = _useSpottable.handleRestoreLastFocus,
      pauseSpotlight = _useSpottable.pauseSpotlight,
      setContainerDisabled = _useSpottable.setContainerDisabled,
      setLastFocusedNode = _useSpottable.setLastFocusedNode,
      shouldPreventScrollByFocus = _useSpottable.shouldPreventScrollByFocus,
      updateStatesAndBounds = _useSpottable.updateStatesAndBounds;

  (0, _usePreventScroll["default"])(props, instance);
  var handle = {
    calculatePositionOnFocus: calculatePositionOnFocus,
    focusByIndex: focusByIndex,
    focusOnNode: focusOnNode,
    getScrollBounds: getScrollBounds,
    pauseSpotlight: pauseSpotlight,
    setContainerDisabled: setContainerDisabled,
    setLastFocusedNode: setLastFocusedNode,
    shouldPreventScrollByFocus: shouldPreventScrollByFocus
  };
  props.setThemeScrollContentHandle(handle); // Render

  var _itemRenderer = props.itemRenderer,
      rest = _objectWithoutProperties(props, ["itemRenderer"]);

  delete rest.focusableScrollbar;
  delete rest.scrollAndFocusScrollbarButton;
  delete rest.scrollContainerContainsDangerously;
  delete rest.scrollContainerHandle;
  delete rest.scrollContainerRef;
  delete rest.scrollContentHandle;
  delete rest.spotlightId;
  delete rest.wrap;
  return _objectSpread(_objectSpread({}, rest), {}, {
    itemRenderer: function itemRenderer(_ref3) {
      var _objectSpread2;

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

      return _itemRenderer(_objectSpread(_objectSpread({}, itemRest), {}, (_objectSpread2 = {}, _defineProperty(_objectSpread2, _useScroll.dataIndexAttribute, index), _defineProperty(_objectSpread2, "index", index), _objectSpread2)));
    },
    placeholderRenderer: function placeholderRenderer(primary) {
      return _placeholderRenderer({
        handlePlaceholderFocus: handlePlaceholderFocus,
        primary: primary
      });
    },
    onUpdateItems: handleRestoreLastFocus,
    updateStatesAndBounds: updateStatesAndBounds
  });
};
/* eslint-disable enact/prop-types */


exports.useThemeVirtualList = useThemeVirtualList;

function _placeholderRenderer(_ref4) {
  var handlePlaceholderFocus = _ref4.handlePlaceholderFocus,
      primary = _ref4.primary;
  return primary ? null : /*#__PURE__*/(0, _jsxRuntime.jsx)(SpotlightPlaceholder, {
    "data-index": 0,
    "data-vl-placeholder": true,
    // a zero width/height element can't be focused by spotlight so we're giving
    // the placeholder a small size to ensure it is navigable
    onFocus: handlePlaceholderFocus,
    style: {
      width: 10
    }
  }, "placeholder");
}
/* eslint-enable enact/prop-types */


var _default = useThemeVirtualList;
exports["default"] = _default;