text: /**
   * @component Picker
   * @version 3.0.0
   * @description Picker组件，行为和特性与iOS原生的picker完全一致。
   *
   * 支持两种模式:静态模式和循环模式。
   *
   * 静态模式会完全按照传入的options渲染待选项，而循环模式会将options变成一个首尾循环的数据结构，可以无限地向上/向下滚动。
   * @instructions {instruInfo: ./picker.md}{instruUrl: picker.html?hideIcon}
   * @author jiao.shen
   */
  import PickerCore from './PickerCore';
  import React, { Component } from 'react';
  import PropTypes from 'prop-types';
  import Scroller from '../../scroller/src/scroller';
  import PickerItem from './PickerItem';
  import classNames from 'classnames';
  
  const SIZE = 1000000;
  const ITEM_HEIGHT = 30;
  
  export default class Picker extends Component {
  
      static propTypes = {
          /**
           * @property options
           * @type Array
           * @default null
           * @description picker的options，数组形式，元素的格式为{text:string,value:string}。
           *
           * text为option显示的文本，value为option对应的真实值(参考网页option标签)。
           *
           * text的缺省值为value，value必须传入，且只能为字符串/数字类型。
           */
          options: PropTypes.arrayOf(
              PropTypes.shape(
                  {
                      text: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
                      value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired
                  }
              )
          ).isRequired,
          /**
           * @property value
           * @type Number/String
           * @default null
           * @description 组件的value，参考网页select标签的value属性。
           *
           * value是一个严格受控属性，只能通过picker的父组件改变，你需要设置onChange属性来控制value属性的变化。
           */
          value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
          /**
           * @property onChange
           * @type Function
           * @default null
           * @param value 当前的option的value
           * @description 组件划动时触发的事件回调，如果不设置这个属性，这个picker的value将无法根据picker的滚动改变。
           *
           * onChange能够接收一个参数option，为当前选中的option的数据对象。
           *
           * 如果你不重新设置value，那么组件将会回滚到之前的值。
           *
           * onChange和value的使用和网页中select对应属性的使用完全一致。
           */
          onChange: PropTypes.func,
          /**
           * @property height
           * @type Number
           * @default 150
           * @description picker的高度，默认150。
           *
           * Picker组件不能自适应容器的高度，必须为Picker组件显式地指定高度。
           */
          height: PropTypes.number.isRequired,
          /**
           * @property looped
           * @type Bool
           * @default true
           * @description 是否采用循环模式，默认为true。
           */
          looped: PropTypes.bool,
          /**
           * @property unit
           * @type Number
           * @default null
           * @description 显示在picker右侧的单位。
           */
          unit: PropTypes.string,
          /**
           * @property extraClass
           * @type String
           * @default null
           * @description 附加给组件根节点的额外class。
           */
          extraClass: PropTypes.string,
          /**
           * @property stopPropagation
           * @type Bool
           * @default false
           * @description 是否阻止默认事件传播，默认为false不阻止。
           */
          stopPropagation: PropTypes.bool,
          /**
           * @property itemHeight
           * @version 3.0.9
           * @skip
           * @type Number
           * @default 30
           * @description 滚动Item的高度, 该属性的修改最好配合样式,如果设值为45，则需 extraClass传入 `yo-picker-xxx` 并扩展样式
           * ```
           * @include yo-picker(
           *      $name: 'xxx',
           *      $item-height: .45rem
           *  );
           * ```
           */
          itemHeight: PropTypes.number
      };
  
      static defaultProps = {
          value: null,
          onChange: () => {
          },
          height: 150,
          looped: true,
          unit: null,
          stopPropagation: false,
          extraClass: '',
          itemHeight: ITEM_HEIGHT
      };
  
      constructor(props) {
          super(props);
          const { options, value, height, looped, itemHeight } = props;
          const size = looped ? (SIZE * ITEM_HEIGHT) / itemHeight : options.length;
  
          this.pickerModel = new PickerCore({
              dataSource: options,
              value,
              loopedSize: size,
              containerHeight: height,
              itemHeight,
              looped
          });
          // 因为槽的数量和组件的高度有关,因此也在state中维护
          this.state = {
              thunks: this.pickerModel.thunks,
              height: this.pickerModel.containerHeight,
              contentHeight: this.pickerModel.contentHeight,
              visibleList: this.pickerModel.visibleList,
              offsetY: this.pickerModel.offsetY
          };
      }
  
      componentWillMount() {
          this.pickerModel
              .registerEventHandler('change', (visibleList) => {
                  this.setState({ visibleList });
              })
              .registerEventHandler('momentumStart', (newY) => {
                  if (this.scroller) { this.scroller.scrollTo(0, newY, 300); }
              })
              .registerEventHandler('resetValue', (newY, needRefresh) => {
                  this.setState({ offsetY: newY });
  
                  if (needRefresh) {
                      this.refreshOffsetY(newY);
                  }
              })
              .registerEventHandler('refresh', (offsetY, visibleList, options, height, contentHeight, thunks) => {
                  this.setState({ offsetY, visibleList, options, height, contentHeight, thunks });
                  this.refreshOffsetY(offsetY);
                  // 等待update结束,refresh scroller
                  if (this.scroller) {
                      setTimeout(() => {
                          this.scroller.refresh({
                              scrollerHeight: contentHeight,
                              wrapperHeight: height
                          });
                      }, 0);
                  }
              });
      }
  
      componentDidMount() {
          if (this.scroller) {
              this.scroller.refresh({
                  scrollerHeight: this.state.contentHeight,
                  wrapperHeight: this.state.height
              });
          }
      }
  
      /**
       * 如果仅仅是value发生了改变,其他属性都没有改变,只调用setValue
       * 这样可以跳过一些复杂的计算过程
       * @param nextProps
       */
      componentWillReceiveProps(nextProps) {
          const { value, options, height, looped, itemHeight } = nextProps;
  
          if (options === this.props.options
              && height === this.state.height
              && looped === this.props.looped
              && itemHeight === this.props.itemHeight
              ) {
              if (this.props.value !== value) {
                  this.pickerModel.setValue(value, true);
              }
          } else {
              this.pickerModel.refresh({
                  dataSource: options,
                  value,
                  containerHeight: height,
                  loopedSize: looped ? (SIZE * ITEM_HEIGHT) / itemHeight : options.length,
                  looped,
                  manually: true,
                  itemHeight
              });
          }
      }
  
      /**
       * 滚动停止时,校正位置(需要正好卡到某个option处)并触发onChange
       * 然后验证外部组件是否重置了value,如果没有,回滚到之前的option
       * @param evt
       */
      onScrollTerminate(evt) {
          // console.log('term')
          const currentY = evt.contentOffset.y,
              destination = this.pickerModel.getScrollDestination(currentY);
  
          // 滚动停止时再次校验是否滚到了正确的位置
          // 因为用户可能在惯性滑动时再次点击,终止惯性滚动,所以可能滑不到正确位置
          if (currentY !== destination.y) {
              this.pickerModel.onMomentumStart(currentY);
              return;
          }
          // 此处检验是否value真的发生了变化,这样可以减少很多onChange引起的render
          const lastStateY = this.state.offsetY,
              lastItem = this.pickerModel.getScrollDestination(lastStateY).item,
              selectedItem = destination.item;
          this.pickerModel.offsetY = currentY;
          if (selectedItem.value !== lastItem.value) {
              this.props.onChange(selectedItem);
          }
          // 在用户重设value之后验证value是否已经改变
          // 如果没有改变,回滚到上一个状态的offsety
          setTimeout(() => {
              if (this.state.offsetY === lastStateY) {
                  this.pickerModel.offsetY = lastStateY;
                  this.refreshOffsetY(lastStateY);
              }
          }, 0);
          // 重置isScrolling
          // 放在timeout里是为了让tap先触发
          // 200毫秒的延迟是为了防止使用者频繁点击导致错乱
          setTimeout(() => {
              this.isScrolling = false;
          }, 200);
      }
  
      /**
       * 根据当前的y重新计算visibleList
       * @param y
       */
      refreshOffsetY(y) {
          this.scroller && this.scroller.scrollTo(0, y);
          if (this.props.looped) {
              this.pickerModel.onScrollTo(y);
          }
      }
  
      render() {
          const { extraClass, unit, itemHeight } = this.props;
          const looped = this.pickerModel.looped;
          const { visibleList, offsetY, height, contentHeight, thunks } = this.state;
          return (
              <div className={classNames('yo-picker', extraClass)} style={{ height }}>
                  <span className="mask" />
                  <Scroller
                      contentOffset={{ x: 0, y: offsetY }}
                      ref={dom => {
                          this.scroller = dom;
                      }}
                      stopPropagation={this.props.stopPropagation}
                      wrapper={{ clientWidth: 0, clientHeight: height }}
                      useTransition={true}
                      onScroll={(evt) => {
                          if (looped) {
                              this.pickerModel.onScrollTo(evt.contentOffset.y);
                          }
                          this.isScrolling = true;
                      }}
                      autoRefresh={false}
                      onScrollEnd={(evt) => {
                          this.onScrollTerminate(evt);
                      }}
                      // scrollCancel对应着两种情况:1.在滚动过程中tap中断滚动,2.静止时tap,只有第一种情况
                      // 才应该执行onScrollTerminate回调,在第二种情况会执行option的onOptionTap回调(直接滚动到目标option)
                      // 因此此处必须加上isScrolling的判断
                      onScrollCancel={(evt) => {
                          if (this.isScrolling) {
                              this.onScrollTerminate(evt);
                          }
                      }}
                      onMomentumScrollBegin={(evt) => this.pickerModel.onMomentumStart(evt.param.targetY)}
                      deceleration={0.001}
                      bounceTime={looped ? 600 : 200}
                  >
                      <ul
                          className={'list'}
                          style={{
                              height: contentHeight
                          }}
                      >
                          {looped ?
                              thunks.map((_, order) => {
                                  const ele = visibleList.find((item) => item.order === order);
                                  return ele ?
                                      <PickerItem
                                          onOptionTap={(el) => {
                                              if (!this.isScrolling && this.scroller) {
                                                  this.scroller.scrollTo(0, this.pickerModel.getPositionByOpt(el), 300);
                                              }
                                          }}
                                          ele={ele}
                                          itemHeight={itemHeight}
                                          key={order}
                                          order={order}
                                      /> : null;
                              }) :
                              visibleList.map((item, i) => (
                                  <PickerItem
                                      onOptionTap={(ele) => {
                                          if (!this.isScrolling && this.scroller) {
                                              this.scroller.scrollTo(0, this.pickerModel.getPositionByOpt(ele), 300);
                                          }
                                      }}
                                      ele={item}
                                      itemHeight={itemHeight}
                                      key={`notLooped_${i}`}
                                      notLooped={true}
                                  />
                              ))}
                      </ul>
                  </Scroller>
                  {unit ? <span className="yo-select-item-tag unit">{unit}</span> : null}
              </div>
          );
      }
  }
  