"use strict";

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

function _react() {
  const data = _interopRequireWildcard(require("react"));

  _react = function () {
    return data;
  };

  return data;
}

function _jsxRuntime() {
  const data = require("react/jsx-runtime");

  _jsxRuntime = function () {
    return data;
  };

  return data;
}

function _clsx() {
  const data = _interopRequireDefault(require("clsx"));

  _clsx = function () {
    return data;
  };

  return data;
}

var _utils = require("../utils");

var _deepClone = require("../utils/deep-clone");

var _hooks = require("../hooks");

var _ConfigProviderContext = _interopRequireDefault(require("../config-provider/ConfigProviderContext"));

var _raf = require("../utils/raf");

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

function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }

function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && 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; }

/* eslint-disable @typescript-eslint/naming-convention */
const DEFAULT_DURATION = 200; // 惯性滑动思路:
// 在手指离开屏幕时，如果和上一次 move 时的间隔小于 `MOMENTUM_LIMIT_TIME` 且 move
// 距离大于 `MOMENTUM_LIMIT_DISTANCE` 时，执行惯性滑动

const MOMENTUM_LIMIT_TIME = 300;
const MOMENTUM_LIMIT_DISTANCE = 15;

function getElementTranslateY(element) {
  const style = window.getComputedStyle(element);
  const transform = style.transform || style.webkitTransform;
  const translateY = transform.slice(7, transform.length - 1).split(', ')[5];
  return Number(translateY);
}

function isOptionDisabled(option) {
  return (0, _utils.isObject)(option) && option.disabled;
}

const PickerColumn = (0, _react().forwardRef)((props, ref) => {
  const {
    prefixCls,
    createNamespace
  } = (0, _react().useContext)(_ConfigProviderContext.default);
  const [bem] = createNamespace('picker-column', prefixCls);
  const {
    itemHeight,
    visibleItemCount,
    defaultIndex,
    initialOptions
  } = props;
  const root = (0, _react().useRef)(null);
  const wrapper = (0, _react().useRef)(null);
  const moving = (0, _react().useRef)(false);
  const startOffset = (0, _react().useRef)(0);
  const transitionEndTrigger = (0, _react().useRef)(null);
  const touchStartTime = (0, _react().useRef)(0);
  const momentumOffset = (0, _react().useRef)(0);
  const [state, updateState] = (0, _hooks.useSetState)({
    index: defaultIndex,
    offset: 0,
    duration: 0,
    options: (0, _deepClone.deepClone)(initialOptions)
  });
  const touch = (0, _hooks.useTouch)();
  const count = (0, _react().useMemo)(() => state.options.length, [state.options.length]);
  const baseOffset = (0, _react().useMemo)(() => {
    // 默认转入第一个选项的位置
    return itemHeight * (+visibleItemCount - 1) / 2;
  }, [itemHeight, visibleItemCount]);

  const adjustIndex = index => {
    index = (0, _utils.range)(index, 0, count);

    for (let i = index; i < count; i += 1) {
      if (!isOptionDisabled(state.options[i])) return i;
    }

    for (let i = index - 1; i >= 0; i -= 1) {
      if (!isOptionDisabled(state.options[i])) return i;
    }

    return null;
  };

  const setIndex = (index, emitChange) => {
    index = adjustIndex(index) || 0;
    const offset = -index * props.itemHeight;

    const trigger = () => {
      if (index !== state.index) {
        updateState({
          index
        });

        if (emitChange && props.onChange) {
          (0, _raf.raf)(() => {
            props.onChange(index);
          });
        }
      }
    }; // trigger the change event after transitionend when moving


    if (moving.current && offset !== state.offset) {
      transitionEndTrigger.current = trigger;
    } else {
      trigger();
    }

    updateState({
      offset
    });
  };

  const setOptions = options => {
    if (JSON.stringify(options) !== JSON.stringify(state.options)) {
      updateState({
        options: (0, _deepClone.deepClone)(options)
      });
      setIndex(props.defaultIndex);
    }
  };

  const onClickItem = index => {
    if (moving.current || props.readonly) {
      return;
    }

    transitionEndTrigger.current = null;
    updateState({
      duration: DEFAULT_DURATION
    });
    setIndex(index, true);
  };

  const getOptionText = option => {
    if ((0, _utils.isObject)(option) && props.textKey in option) {
      return option[props.textKey];
    }

    return option;
  };

  const getIndexByOffset = offset => (0, _utils.range)(Math.round(-offset / props.itemHeight), 0, count - 1);

  const momentum = (distance, _duration) => {
    const speed = Math.abs(distance / _duration);
    distance = state.offset + speed / 0.003 * (distance < 0 ? -1 : 1);
    const index = getIndexByOffset(distance);
    updateState({
      duration: +props.swipeDuration
    });
    setIndex(index, true);
  };

  const stopMomentum = () => {
    moving.current = false;
    updateState({
      duration: 0
    });

    if (transitionEndTrigger.current) {
      transitionEndTrigger.current();
      transitionEndTrigger.current = null;
    }
  };

  const onTouchStart = event => {
    if (props.readonly) {
      return;
    }

    touch.start(event);
    let {
      offset
    } = state;

    if (moving.current) {
      const translateY = getElementTranslateY(wrapper.current);
      offset = Math.min(0, translateY - baseOffset);
      startOffset.current = offset;
    } else {
      startOffset.current = offset;
    }

    updateState({
      duration: 0,
      offset
    });
    touchStartTime.current = Date.now();
    momentumOffset.current = startOffset.current;
    transitionEndTrigger.current = null;
  };

  const onTouchMove = event => {
    if (props.readonly) {
      return;
    }

    touch.move(event);

    if (touch.isVertical()) {
      moving.current = true;
    }

    const offset = (0, _utils.range)(startOffset.current + touch.deltaY.current, -(count * props.itemHeight), props.itemHeight);
    updateState({
      offset
    });
    const now = Date.now();

    if (now - touchStartTime.current > MOMENTUM_LIMIT_TIME) {
      touchStartTime.current = now;
      momentumOffset.current = offset;
    }
  };

  const onTouchEnd = () => {
    if (props.readonly) {
      return;
    }

    const distance = state.offset - momentumOffset.current;
    const duration = Date.now() - touchStartTime.current;
    const allowMomentum = duration < MOMENTUM_LIMIT_TIME && Math.abs(distance) > MOMENTUM_LIMIT_DISTANCE;

    if (allowMomentum) {
      momentum(distance, duration);
      return;
    }

    const index = getIndexByOffset(state.offset);
    updateState({
      duration: DEFAULT_DURATION
    });
    setIndex(index, true); // compatible with desktop scenario
    // use setTimeout to skip the click event triggered after touchstart

    setTimeout(() => {
      moving.current = false;
    }, 0);
  };

  const renderOptions = () => {
    const optionStyle = {
      height: `${props.itemHeight}px`
    };
    return state.options.map((option, index) => {
      const text = getOptionText(option);
      const disabled = isOptionDisabled(option);
      const data = {
        role: 'button',
        style: optionStyle,
        tabIndex: disabled ? -1 : 0,
        className: (0, _clsx().default)(bem('item', {
          disabled,
          selected: index === state.index
        })),
        onClick: () => {
          onClickItem(index);
        }
      };
      const childData = {
        className: 'rv-ellipsis',
        children: text
      };
      return (0, _react().createElement)("li", Object.assign({}, data, {
        key: index
      }), props.optionRender ? props.optionRender(option) : (0, _jsxRuntime().jsx)("div", Object.assign({}, childData)));
    });
  };

  const setValue = value => {
    const {
      options
    } = state;

    for (let i = 0; i < options.length; i += 1) {
      if (getOptionText(options[i]) === value) {
        return setIndex(i);
      }
    }

    return null;
  };

  const getValue = (0, _react().useCallback)(() => state.options[state.index], [state.index, state.options]);
  (0, _react().useEffect)(() => {
    setIndex(defaultIndex); // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [defaultIndex]);
  (0, _hooks.useUpdateEffect)(() => {
    setOptions(initialOptions);
  }, [initialOptions]);
  (0, _react().useImperativeHandle)(ref, () => ({
    state,
    setIndex,
    getValue,
    setValue,
    setOptions,
    stopMomentum
  }));
  return (0, _jsxRuntime().jsx)("div", Object.assign({
    ref: root,
    className: (0, _clsx().default)(bem(), props.className),
    onTouchStart: onTouchStart,
    onTouchMove: onTouchMove,
    onTouchEnd: onTouchEnd,
    onTouchCancel: onTouchEnd
  }, {
    children: (0, _jsxRuntime().jsx)("ul", Object.assign({
      ref: wrapper,
      style: {
        transform: `translate3d(0, ${state.offset + baseOffset}px, 0)`,
        transitionDuration: `${state.duration}ms`,
        transitionProperty: state.duration ? 'all' : 'none'
      },
      className: (0, _clsx().default)(bem('wrapper')),
      onTransitionEnd: stopMomentum
    }, {
      children: renderOptions()
    }))
  }));
});
PickerColumn.defaultProps = {
  initialOptions: [],
  defaultIndex: 0
};
var _default = PickerColumn;
exports.default = _default;