UNPKG

rmc-list-view

Version:
534 lines (479 loc) 18.7 kB
import _extends from 'babel-runtime/helpers/extends'; import _objectWithoutProperties from 'babel-runtime/helpers/objectWithoutProperties'; import _classCallCheck from 'babel-runtime/helpers/classCallCheck'; import _createClass from 'babel-runtime/helpers/createClass'; import _possibleConstructorReturn from 'babel-runtime/helpers/possibleConstructorReturn'; import _inherits from 'babel-runtime/helpers/inherits'; import React from 'react'; import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; import DOMScroller from 'zscroller'; import classNames from 'classnames'; import { throttle, setTransform, setTransformOrigin } from './util'; // const SCROLLVIEW = 'ScrollViewRef'; // const INNERVIEW = 'InnerScrollViewRef'; // https://github.com/facebook/react-native/blob/0.26-stable/Libraries/Components/ScrollView/ScrollView.js /* eslint react/prop-types: 0, react/sort-comp: 0, no-unused-expressions: 0 */ var propTypes = { children: PropTypes.any, className: PropTypes.string, prefixCls: PropTypes.string, listPrefixCls: PropTypes.string, listViewPrefixCls: PropTypes.string, style: PropTypes.object, contentContainerStyle: PropTypes.object, onScroll: PropTypes.func, scrollEventThrottle: PropTypes.number, refreshControl: PropTypes.element }; var styles = { base: { position: 'relative', overflow: 'auto', WebkitOverflowScrolling: 'touch', flex: 1 }, zScroller: { position: 'relative', overflow: 'hidden', flex: 1 } }; var ScrollView = function (_React$Component) { _inherits(ScrollView, _React$Component); function ScrollView() { var _ref; var _temp, _this, _ret; _classCallCheck(this, ScrollView); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = ScrollView.__proto__ || Object.getPrototypeOf(ScrollView)).call.apply(_ref, [this].concat(args))), _this), _initialiseProps.call(_this), _temp), _possibleConstructorReturn(_this, _ret); } _createClass(ScrollView, [{ key: 'componentWillUpdate', value: function componentWillUpdate(nextProps) { // https://github.com/ant-design/ant-design-mobile/issues/1480 // https://stackoverflow.com/questions/1386696/make-scrollleft-scrolltop-changes-not-trigger-scroll-event // 问题情景:用户滚动内容后,改变 dataSource 触发 ListView componentWillReceiveProps // 内容变化后 scrollTop 如果改变、会自动触发 scroll 事件,而此事件应该避免被执行 if ((this.props.dataSource !== nextProps.dataSource || this.props.initialListSize !== nextProps.initialListSize) && this.tsExec) { // console.log('componentWillUpdate'); if (this.props.useBodyScroll) { window.removeEventListener('scroll', this.tsExec); } else if (!this.props.useZscroller) { // not handle useZscroller now. todo ReactDOM.findDOMNode(this.ScrollViewRef).removeEventListener('scroll', this.tsExec); } } } }, { key: 'componentDidUpdate', value: function componentDidUpdate(prevProps) { var _this2 = this; // console.log('componentDidUpdate'); if (prevProps.refreshControl && this.props.refreshControl) { var preRefreshing = prevProps.refreshControl.props.refreshing; var nowRefreshing = this.props.refreshControl.props.refreshing; if (preRefreshing && !nowRefreshing && !this._refreshControlTimer) { this.domScroller.scroller.finishPullToRefresh(); } else if (!this.manuallyRefresh && !preRefreshing && nowRefreshing) { this.domScroller.scroller.triggerPullToRefresh(); } } // handle componentWillUpdate accordingly if ((this.props.dataSource !== prevProps.dataSource || this.props.initialListSize !== prevProps.initialListSize) && this.tsExec) { // console.log('componentDidUpdate'); setTimeout(function () { if (_this2.props.useBodyScroll) { window.addEventListener('scroll', _this2.tsExec); } else if (!_this2.props.useZscroller) { // not handle useZscroller now. todo ReactDOM.findDOMNode(_this2.ScrollViewRef).addEventListener('scroll', _this2.tsExec); } }, 0); } // for pullUp if (this.props.pullUpEnabled) { var _preRefreshing = prevProps.pullUpRefreshing; var _nowRefreshing = this.props.pullUpRefreshing; if (_preRefreshing && !_nowRefreshing && !this._pullUpTimer) { this.pullUpFinish(); } } } }, { key: 'componentDidMount', value: function componentDidMount() { var _this3 = this; this.tsExec = this.throttleScroll(); // IE supports onresize on all HTML elements. // In all other Browsers the onresize is only available at the window object this.onLayout = function () { return _this3.props.onLayout({ nativeEvent: { layout: { width: window.innerWidth, height: window.innerHeight } } }); }; if (this.props.useBodyScroll) { window.addEventListener('scroll', this.tsExec); window.addEventListener('resize', this.onLayout); this.initPullUp(document.body); // for pullUp } else if (this.props.useZscroller) { this.renderZscroller(); } else { var ele = ReactDOM.findDOMNode(this.ScrollViewRef); ele.addEventListener('scroll', this.tsExec); this.initPullUp(ele); // for pullUp } } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { if (this.props.useBodyScroll) { window.removeEventListener('scroll', this.tsExec); window.removeEventListener('resize', this.onLayout); this.destroyPullUp(document.body); // for pullUp } else if (this.props.useZscroller) { this.domScroller.destroy(); } else { var ele = ReactDOM.findDOMNode(this.ScrollViewRef); ele.removeEventListener('scroll', this.tsExec); this.destroyPullUp(ele); // for pullUp } } }, { key: 'renderZscroller', value: function renderZscroller() { var _this4 = this; var _props = this.props, scrollerOptions = _props.scrollerOptions, refreshControl = _props.refreshControl; var _scrollingComplete = scrollerOptions.scrollingComplete, _onScroll = scrollerOptions.onScroll, restProps = _objectWithoutProperties(scrollerOptions, ['scrollingComplete', 'onScroll']); // console.log(scrollingComplete, onScroll, restProps); // console.log('onRefresh will not change', refreshControl.props.onRefresh.toString()); this.domScroller = new DOMScroller(this.getInnerViewNode(), _extends({ scrollingX: false, onScroll: function onScroll() { _this4.tsExec(); if (_onScroll) { _onScroll(); } }, scrollingComplete: function scrollingComplete() { _this4.scrollingComplete(); if (_scrollingComplete) { _scrollingComplete(); } } }, restProps)); if (refreshControl) { var scroller = this.domScroller.scroller; var _refreshControl$props = refreshControl.props, distanceToRefresh = _refreshControl$props.distanceToRefresh, onRefresh = _refreshControl$props.onRefresh; scroller.activatePullToRefresh(distanceToRefresh, function () { // console.log('reach to the distance'); _this4.manuallyRefresh = true; _this4.overDistanceThenRelease = false; _this4.RefreshControlRef && _this4.RefreshControlRef.setState({ active: true }); }, function () { // console.log('back to the distance'); _this4.manuallyRefresh = false; _this4.RefreshControlRef && _this4.RefreshControlRef.setState({ deactive: _this4.overDistanceThenRelease, active: false, loadingState: false }); }, function () { // console.log('Over distance and release to loading'); _this4.overDistanceThenRelease = true; _this4.RefreshControlRef && _this4.RefreshControlRef.setState({ deactive: false, loadingState: true }); _this4._refreshControlTimer = setTimeout(function () { if (!_this4.props.refreshControl.props.refreshing) { scroller.finishPullToRefresh(); } _this4._refreshControlTimer = undefined; }, 1000); onRefresh(); }); if (refreshControl.props.refreshing) { scroller.triggerPullToRefresh(); } } } }, { key: 'render', value: function render() { var _this5 = this; var _props2 = this.props, children = _props2.children, className = _props2.className, prefixCls = _props2.prefixCls, listPrefixCls = _props2.listPrefixCls, listViewPrefixCls = _props2.listViewPrefixCls, _props2$style = _props2.style, style = _props2$style === undefined ? {} : _props2$style, _props2$contentContai = _props2.contentContainerStyle, contentContainerStyle = _props2$contentContai === undefined ? {} : _props2$contentContai, useZscroller = _props2.useZscroller, refreshControl = _props2.refreshControl, useBodyScroll = _props2.useBodyScroll, pullUpEnabled = _props2.pullUpEnabled, pullUpRenderer = _props2.pullUpRenderer; var styleBase = styles.base; if (useBodyScroll) { styleBase = {}; } else if (useZscroller) { styleBase = styles.zScroller; } var preCls = prefixCls || listViewPrefixCls || ''; var containerProps = { ref: function ref(el) { return _this5.ScrollViewRef = el; }, style: _extends({}, styleBase, style), className: classNames(className, preCls + '-scrollview') }; var contentContainerProps = { ref: function ref(el) { return _this5.InnerScrollViewRef = el; }, style: _extends({ position: 'absolute', minWidth: '100%' }, contentContainerStyle), className: classNames(preCls + '-scrollview-content', listPrefixCls) }; if (refreshControl) { return React.createElement( 'div', containerProps, React.createElement( 'div', contentContainerProps, React.cloneElement(refreshControl, { ref: function ref(el) { return _this5.RefreshControlRef = el; } }), children ) ); } var createPullUp = function createPullUp() { var pullUpCls = classNames(preCls + '-pull-up-content', !_this5.state.isTouching && preCls + '-pull-up-dropped'); var defaultRenderer = _this5.pullUpDisplay.deactivate; switch (_this5.state.pullUp) { case 'activate': case 'deactivate': case 'release': case 'finish': default: defaultRenderer = _this5.pullUpDisplay[_this5.state.pullUp]; } return React.createElement( 'div', { className: pullUpCls, ref: function ref(el) { return _this5.pullUpContentRef = el; } }, children, React.createElement( 'div', { ref: function ref(el) { return _this5.pullUpIndicatorRef = el; }, className: preCls + '-pull-up-indicator' }, pullUpRenderer ? pullUpRenderer(_this5.state.pullUp) : defaultRenderer ) ); }; if (useBodyScroll) { if (pullUpEnabled) { containerProps.style.overflow = 'hidden'; return React.createElement( 'div', containerProps, createPullUp() ); } return React.createElement( 'div', containerProps, children ); } if (pullUpEnabled) { contentContainerProps.style.overflow = 'hidden'; return React.createElement( 'div', containerProps, React.createElement( 'div', contentContainerProps, createPullUp() ) ); } return React.createElement( 'div', containerProps, React.createElement( 'div', contentContainerProps, children ) ); } /** The following code was intended to implement the pull-up-to-load-more feature, Coincidentally, it solves a problem, if the content is not high enough, the `onScroll` and `onEndReached` event will not be fired. However, there should be a better solution for this issue. */ // https://github.com/yiminghe/zscroller/blob/2d97973287135745818a0537712235a39a6a62a1/src/Scroller.js#L355 // states: `activate` / `deactivate` / `release` / `finish` }]); return ScrollView; }(React.Component); ScrollView.propTypes = propTypes; var _initialiseProps = function _initialiseProps() { var _this6 = this; this.getInnerViewNode = function () { return ReactDOM.findDOMNode(_this6.InnerScrollViewRef); }; this.scrollTo = function () { if (_this6.props.useBodyScroll) { var _window; (_window = window).scrollTo.apply(_window, arguments); } else if (_this6.props.useZscroller) { var _domScroller$scroller; // it will change zScroller's dimensions on data loaded, so it needs fire reflow. _this6.domScroller.reflow(); (_domScroller$scroller = _this6.domScroller.scroller).scrollTo.apply(_domScroller$scroller, arguments); } else { var ele = ReactDOM.findDOMNode(_this6.ScrollViewRef); ele.scrollLeft = arguments.length <= 0 ? undefined : arguments[0]; ele.scrollTop = arguments.length <= 1 ? undefined : arguments[1]; } }; this.throttleScroll = function () { var handleScroll = function handleScroll() {}; if (_this6.props.scrollEventThrottle && _this6.props.onScroll) { handleScroll = throttle(function (e) { _this6.props.onScroll && _this6.props.onScroll(e); }, _this6.props.scrollEventThrottle); } return handleScroll; }; this.scrollingComplete = function () { // console.log('scrolling complete'); if (_this6.props.refreshControl && _this6.RefreshControlRef && _this6.RefreshControlRef.state.deactive) { _this6.RefreshControlRef.setState({ deactive: false }); } }; this.state = { pullUp: false, isTouching: false }; this.pullUpStats = { activate: 'activate', deactivate: 'deactivate', release: 'release', finish: 'finish' }; this.pullUpDisplay = { activate: '释放刷新', deactivate: '上拉 ↑', release: '加载中...', finish: '完成刷新' }; this.genEvtHandler = function (ele) { return { touchstart: _this6.onTouchStart.bind(_this6, ele), touchmove: _this6.onTouchMove.bind(_this6, ele), touchend: _this6.onTouchEnd.bind(_this6, ele), touchcancel: _this6.onTouchEnd.bind(_this6, ele) }; }; this.initPullUp = function (ele) { if (_this6.pullUpContentRef) { setTransformOrigin(_this6.pullUpContentRef.style, 'left top'); } _this6._to = _this6.genEvtHandler(ele); Object.keys(_this6._to).forEach(function (key) { ele.addEventListener(key, _this6._to[key]); }); }; this.destroyPullUp = function (ele) { Object.keys(_this6._to).forEach(function (key) { ele.removeEventListener(key, _this6._to[key]); }); }; this.onTouchStart = function (ele, e) { _this6._pullUpScreenY = _this6._pullUpStartScreenY = e.touches[0].screenY; _this6._pullUpLastScreenY = 0; if (_this6.props.pullUpEnabled) { _this6.setState({ isTouching: true }); } }; this.onTouchMove = function (ele, e) { if (!_this6.props.pullUpEnabled) { return; } // 使用 pageY 对比有问题 var _screenY = e.touches[0].screenY; if (_this6._pullUpStartScreenY - _screenY > 0) { // console.log('is pull up', _screenY); var isReachBottom = void 0; if (_this6.props.useBodyScroll) { // In chrome61 `document.body.scrollTop` is invalid, here `ele === document.body` var scrollNode = document.scrollingElement ? document.scrollingElement : ele; isReachBottom = ele.scrollHeight - scrollNode.scrollTop <= window.innerHeight; // console.log(ele.scrollHeight, scrollNode.scrollTop, window.innerHeight); } else { isReachBottom = ele.scrollHeight - ele.scrollTop === ele.clientHeight; } if (isReachBottom) { var _diff = Math.round(_screenY - _this6._pullUpScreenY); _this6._pullUpScreenY = _screenY; _this6._pullUpLastScreenY += _diff; setTransform(_this6.pullUpContentRef.style, 'translate3d(0px,' + _this6._pullUpLastScreenY + 'px,0)'); if (Math.abs(_this6._pullUpLastScreenY) < _this6.props.pullUpDistance) { if (_this6.state.pullUp !== _this6.pullUpStats.deactivate) { // console.log('back to the distance'); _this6.setState({ pullUp: _this6.pullUpStats.deactivate }); } } else { if (_this6.state.pullUp === _this6.pullUpStats.deactivate) { // console.log('reach to the distance'); _this6.setState({ pullUp: _this6.pullUpStats.activate }); } } } } }; this.onTouchEnd = function () { if (_this6.props.pullUpEnabled) { _this6.setState({ isTouching: false }); } if (_this6.state.pullUp === _this6.pullUpStats.deactivate) { _this6.pullUpFinish(); } else if (_this6.state.pullUp === _this6.pullUpStats.activate) { _this6.setState({ pullUp: _this6.pullUpStats.release }); _this6._pullUpTimer = setTimeout(function () { if (!_this6.props.pullUpRefreshing) { _this6.pullUpFinish(); } _this6._pullUpTimer = undefined; }, 1000); _this6.props.pullUpOnRefresh(); } }; this.pullUpFinish = function () { _this6._pullUpLastScreenY = 0; setTransform(_this6.pullUpContentRef.style, 'translate3d(0px,0px,0)'); if (_this6.state.pullUp === _this6.pullUpStats.release) { _this6.setState({ pullUp: _this6.pullUpStats.finish }); } }; }; export default ScrollView;