import * as React from 'react' ; import { ListViewProps , PullDownRefreshProps } from './interface' ; import Classnames from 'classnames' ; import './style' ; import Icon from '../icon' ; const { useState , useRef , useEffect , useImperativeHandle } = React ; const prefixCls = 'cp-ui-list-view' ; const timeout = ( delay ) => new Promise ( resolve => setTimeout ( resolve , delay ) ) ; interface ScrollHandleProps { listViewStatusRefs : React . MutableRefObject < { scrollTop : number ; touchX : number ; touchY : number ; time : number ; type : string ; pullDownDoneBacking : boolean ; } > ; pullUpStatus : number ; pullUpHide : boolean ; setPullUpStatus : React . Dispatch < React . SetStateAction < number > > ; } interface TouchStartHandleProps { wrapperElRef : React . MutableRefObject < HTMLDivElement | null > ; setMove : React . Dispatch < React . SetStateAction < boolean > > ; listViewStatusRefs : React . MutableRefObject < { scrollTop : number ; touchX : number ; touchY : number ; time : number ; type : string ; pullDownDoneBacking : boolean ; } > ; } interface TouchMoveHandleProps { move : boolean ; listViewStatusRefs : React . MutableRefObject < { scrollTop : number ; touchX : number ; touchY : number ; time : number ; type : string ; pullDownDoneBacking : boolean ; } > ; pullUpHide : boolean ; setMove : React . Dispatch < React . SetStateAction < boolean > > ; setTranslateY : React . Dispatch < React . SetStateAction < number > > ; } const defaultProps = { noMore : false , backTop : false , prefixCls : prefixCls , noMoreTip : '已经到底了~' } ; interface TouchEndHandleProps { setTranslateY : React . Dispatch < React . SetStateAction < number > > ; setMove : React . Dispatch < React . SetStateAction < boolean > > ; listViewStatusRefs : React . MutableRefObject < { scrollTop : number ; touchX : number ; touchY : number ; time : number ; type : string ; pullDownDoneBacking : boolean ; } > ; move : boolean ; translateY : number ; pullUpHide : boolean ; pullDownStatus : number ; pullUpStatus : number ; setPullUpStatus : React . Dispatch < React . SetStateAction < number > > ; setPullDownStatus : React . Dispatch < React . SetStateAction < number > > ; } const scrollHandle = ( e , props , { listViewStatusRefs , pullUpStatus , pullUpHide , setPullUpStatus } ) => { const { pullUpLoad , noMore } = props ; const target = e . target as HTMLDivElement ; const direction = target . scrollTop > listViewStatusRefs . current . scrollTop ? 'up' : 'down' ; listViewStatusRefs . current . scrollTop = target . scrollTop ; const scrollBottom = target . scrollHeight - target . scrollTop - target . clientHeight ; if ( scrollBottom < 10 && direction === 'up' ) { if ( pullUpLoad && ! pullUpHide && ! noMore && pullUpStatus !== 1 ) { _pullUpLoad ( props , setPullUpStatus ) ; } } } ; const touchStartHandle = ( e , { wrapperElRef , setMove , listViewStatusRefs } ) => { if ( e . touches . length > 1 ) return ; const touch = e . touches [ 0 ] ; if ( wrapperElRef . current && ( wrapperElRef . current . scrollTop <= 0 || wrapperElRef . current . scrollHeight === wrapperElRef . current . scrollTop + wrapperElRef . current . clientHeight ) ) { setMove ( true ) ; listViewStatusRefs . current . touchX = touch . clientX ; listViewStatusRefs . current . touchY = touch . clientY ; listViewStatusRefs . current . time = new Date ( ) . getTime ( ) ; if ( wrapperElRef . current . scrollTop <= 0 ) { listViewStatusRefs . current . type = 'pullDown' ; } else { listViewStatusRefs . current . type = 'pullUp' ; } } else { setMove ( false ) ; } } ; const touchMoveHandle = ( e , props , { move , listViewStatusRefs , pullUpHide , setMove , setTranslateY } ) => { if ( e . touches . length > 1 || ! move ) return ; const { pullDownRefresh , pullUpLoad } = props ; const touch = e . touches [ 0 ] ; const moveX = touch . clientX - listViewStatusRefs . current . touchX ; const moveY = touch . clientY - listViewStatusRefs . current . touchY ; if ( listViewStatusRefs . current . type === 'pullDown' && ( moveY < 0 || ! pullDownRefresh ) ) return ; if ( listViewStatusRefs . current . type === 'pullUp' && ( moveY > 0 || ! pullUpLoad || pullUpHide ) ) return ; if ( new Date ( ) . getTime ( ) - listViewStatusRefs . current . time < 20 ) { if ( Math . abs ( moveX / moveY ) > 0.8 ) { listViewStatusRefs . current . touchX = 0 ; listViewStatusRefs . current . touchY = 0 ; listViewStatusRefs . current . time = 0 ; move = false ; setMove ( false ) ; } } else { setTranslateY ( moveY * 0.3 ) ; } } ; const touchEndHandle = ( e , props , { listViewStatusRefs , move , translateY , pullUpHide , pullDownStatus , pullUpStatus , setTranslateY , setMove , setPullUpStatus , setPullDownStatus } ) => { if ( ! move ) return ; const touch = e . changedTouches [ 0 ] ; const moveY = touch . clientY - listViewStatusRefs . current . touchY ; if ( moveY === 0 ) return ; listViewStatusRefs . current . touchX = 0 ; listViewStatusRefs . current . touchY = 0 ; listViewStatusRefs . current . time = 0 ; move = false ; const { pullDownRefresh , pullUpLoad , noMore } = props ; if ( listViewStatusRefs . current . type === 'pullDown' ) { if ( pullDownStatus !== 0 ) { setTranslateY ( listViewStatusRefs . current . pullDownDoneBacking ? 0 : 40 ) ; return ; } if ( ! pullDownRefresh || translateY <= 40 ) { setMove ( false ) ; setTranslateY ( 0 ) ; } else { _pullDownRefresh ( props , { setMove , setTranslateY , setPullDownStatus , listViewStatusRefs } ) ; } } else if ( listViewStatusRefs . current . type === 'pullUp' ) { setMove ( false ) ; setTranslateY ( 0 ) ; if ( pullUpLoad && ! pullUpHide && ! noMore && pullUpStatus !== 1 && moveY < - 20 ) { _pullUpLoad ( props , setPullUpStatus ) ; } } listViewStatusRefs . current . type = '' ; } ; const _pullDownRefresh = async ( props , { setMove , setTranslateY , setPullDownStatus , listViewStatusRefs } ) => { const { pullDownRefresh } = props ; let customAsyncFn ; if ( ! customAsyncFn ) { if ( ! pullDownRefresh ) return ; customAsyncFn = pullDownRefresh ; } else { const fn = customAsyncFn ; customAsyncFn = ( ) => Promise . all ( [ fn ( ) , timeout ( 300 ) ] ) ; } setMove ( false ) ; setTranslateY ( 40 ) ; setPullDownStatus ( 1 ) ; try { await customAsyncFn ( ) ; setPullDownStatus ( 2 ) ; } catch ( e ) { setPullDownStatus ( 3 ) ; } await timeout ( 500 ) ; setTranslateY ( 0 ) ; listViewStatusRefs . current . pullDownDoneBacking = true ; await timeout ( 300 ) ; setPullDownStatus ( 0 ) ; listViewStatusRefs . current . pullDownDoneBacking = false ; } ; const _pullUpLoad = async ( props , setPullUpStatus ) => { const { pullUpLoad = ( ) => { } } = props ; setPullUpStatus ( 1 ) ; try { await pullUpLoad ( ) ; setPullUpStatus ( 2 ) ; } catch ( e ) { setPullUpStatus ( 3 ) ; } } ; const getClassNames = ( prefixCls , className ) => { return Classnames ( prefixCls , className ) ; } ; const ListView = props => { const { pullDownRefresh , pullUpLoad , children , noMore , noMoreTip , listViewHandleRefs , className , prefixCls , style , refreshTips , upLoadTips } = props ; const [ translateY , setTranslateY ] = useState ( 0 ) ; const [ move , setMove ] = useState ( false ) ; const [ pullUpHide , setPullUpHide ] = useState ( true ) ; const [ pullDownStatus , setPullDownStatus ] = useState ( 0 ) ; const [ pullUpStatus , setPullUpStatus ] = useState ( 0 ) ; const listViewStatusRefs = useRef ( { scrollTop : 0 , touchX : 0 , touchY : 0 , time : 0 , type : '' , pullDownDoneBacking : false } ) ; const innerElRef = useRef < HTMLDivElement | null > ( null ) ; const wrapperElRef = useRef < HTMLDivElement | null > ( null ) ; useEffect ( ( ) => { if ( innerElRef . current && wrapperElRef . current ) { const pullUpHide = innerElRef . current . clientHeight < wrapperElRef . current . clientHeight ; setPullUpHide ( pullUpHide ) ; } } , [ props . children ] ) ; useImperativeHandle ( listViewHandleRefs , ( ) => { return { pullDownRefreshRenderData : ( ) => { _pullDownRefresh ( props , { setMove , setTranslateY , setPullDownStatus , listViewStatusRefs } ) ; } } ; } ) ; const classStr = getClassNames ( prefixCls , className ) ; return ( < div className = { classStr } style = { style } ref = { wrapperElRef } onTouchStart = { e => { touchStartHandle ( e , { wrapperElRef , setMove , listViewStatusRefs } ) ; } } onTouchMove = { e => { touchMoveHandle ( e , props , { move , listViewStatusRefs , pullUpHide , setMove , setTranslateY } ) ; } } onTouchEnd = { e => { touchEndHandle ( e , props , { listViewStatusRefs , move , translateY , pullUpHide , pullDownStatus , pullUpStatus , setTranslateY , setMove , setPullUpStatus , setPullDownStatus } ) ; } } onScroll = { e => { scrollHandle ( e , props , { listViewStatusRefs , pullUpStatus , pullUpHide , setPullUpStatus } ) ; } } > < div style = { { transform : `translateY(${ translateY }px)` , transition : move ? '' : 'transform .3s ease' } } > { pullDownRefresh && ( < div className = { `${ prefixCls }-pull-down-tip` } > { pullDownStatus !== 0 ? ( < div > { pullDownStatus === 1 && ( refreshTips ?. loading ? ( refreshTips ?. loading . icon ) : ( < Icon type = "spinner" spin size = { 18 } / > ) ) } { pullDownStatus === 2 && ( refreshTips ?. success ? ( refreshTips ?. success . icon ) : ( < Icon type = { 'check-circle' } size = { 18 } / > ) ) } { pullDownStatus === 3 && ( refreshTips ?. error ? ( refreshTips ?. error . icon ) : ( < Icon type = { 'times-circle' } size = { 18 } / > ) ) } < span > { pullDownStatus === 1 && ( refreshTips ?. loading ?. text ? refreshTips ?. loading ?. text : '正在刷新' ) } { pullDownStatus === 2 && ( refreshTips ?. success ?. text ? refreshTips ?. success ?. text : '刷新成功' ) } { pullDownStatus === 3 && ( refreshTips ?. error ?. text ? refreshTips ?. error ?. text : '刷新失败' ) } < /span> < /div>) : (<div> < Icon type = "arrow-down" size = { 18 } / > < span > { translateY > 60 ? refreshTips ?. actionRelease ? refreshTips ?. actionRelease : '释放刷新' : refreshTips ?. actionDown ? refreshTips . actionDown : '下拉刷新' } < /span> < /div>)} < /div>)} < div ref = { innerElRef } > { children } < /div> { ! pullUpHide && pullUpLoad && ( < div className = { `${ prefixCls }-pull-up-tip` } > { noMore ? ( noMoreTip ) : pullUpStatus === 0 || pullUpStatus === 1 ? ( < div > { upLoadTips ?. loading ? ( upLoadTips ?. loading ) : ( < > < Icon type = "spinner" spin size = { 18 } / > < span > 加载中 < /span> < />)} < /div>) : pullUpStatus === 2 ? (<div> { upLoadTips ?. success ? ( upLoadTips ?. success ) : ( < > < Icon type = { 'check-circle' } size = { 18 } / > < span > 加载成功 < /span> < />)} < /div>) : pullUpStatus === 3 ? (<div> { upLoadTips ?. error ? ( upLoadTips ?. error ) : ( < > < Icon type = { 'times-circle' } size = { 18 } / > < span > 加载失败 < /span> < />)} < /div>) : ('')} < /div>)} < /div> < /div>); } ; ListView . defaultProps = defaultProps ; export default ListView ;