import * as React from 'react' ; import * as ReactDOM from 'react-dom' ; import classNames from 'classnames' ; import klass from './cssClasses' ; import Gesture from '@jdcfe/yep-gesture' ; import { CarouselProps } from './types' ; import noop from '../_utils/noop' ; const defaultStatusFormatter = ( current , total ) => `${ current } / ${ total }` ; const CSSTranslate = ( position , vertical ) => { const positionCss = ! vertical ? [ position , 0 , 0 ] : [ 0 , position , 0 ] ; const transitionProp = 'translate3d' ; const translatedPosition = '(' + positionCss . join ( ',' ) + ')' ; return transitionProp + translatedPosition ; } ; class Carousel extends React . Component < CarouselProps , any > { static defaultProps = { prefixCls : 'Yep-carousel' , style : { } , dots : true , showPager : false , isInfinite : false , initPage : 0 , vertical : false , verticalSwipe : 'standard' , width : '100%' , autoPlay : false , transitionTime : 350 , distance : 5 , dynamicHeight : false , onClickItem : noop , onClickThumb : noop , onChange : noop , renderPage : defaultStatusFormatter , centerMode : false , centerSlidePercentage : 80 , onSwipeStart : ( ) => { } , onSwipeEnd : ( ) => { } , onSwipeMove : ( ) => { } , renderIndicator : ( onClickHandler , isSelected , index , label ) => { return ( < li className = { klass . DOT ( isSelected ) } onClick = { onClickHandler } onKeyDown = { onClickHandler } value = { index } key = { index } role = "button" tabIndex = { 0 } aria - label = { `${ label } ${ index + 1 }` } />); } , renderItem : ( item ) => { return item ; } , } ; constructor ( props ) { super ( props ) ; this . state = { initialized : false , selectedItem : props . initPage , hasMount : false , isMouseEntered : false , autoPlay : props . autoPlay , } ; } carouselWrapperRef : any ; listRef : any ; itemsWrapperRef : any ; itemsRef : any ; timer : any ; componentDidMount ( ) { if ( ! this . props . children ) { return ; } this . setupCarousel ( ) ; } componentDidUpdate ( prevProps , prevState ) { if ( ! prevProps . children && this . props . children && ! this . state . initialized ) { this . setupCarousel ( ) ; } if ( prevState . swiping && ! this . state . swiping ) { this . resetPosition ( ) ; } if ( prevProps . initPage !== this . props . initPage || prevProps . centerMode !== this . props . centerMode ) { this . updateSizes ( ) ; this . moveTo ( this . props . initPage ! ) ; } if ( prevProps . autoPlay !== this . props . autoPlay ) { if ( this . props . autoPlay ) { this . setupAutoPlay ( ) ; } else { this . destroyAutoPlay ( ) ; } this . setState ( { autoPlay : this . props . autoPlay } ) ; } } componentWillUnmount ( ) { this . destroyCarousel ( ) ; } setCarouselWrapperRef = ( node ) => { this . carouselWrapperRef = node ; } ; setListRef = ( node ) => { this . listRef = node ; } ; setItemsWrapperRef = ( node ) => { this . itemsWrapperRef = node ; } ; setItemsRef = ( node , index ) => { if ( ! this . itemsRef ) { this . itemsRef = [ ] ; } this . itemsRef [ index ] = node ; } ; setupCarousel ( ) { this . bindEvents ( ) ; if ( this . state . autoPlay && React . Children . count ( this . props . children ) > 1 ) { this . setupAutoPlay ( ) ; } this . setState ( { initialized : true , } ) ; const initialImage = this . getInitialImage ( ) ; if ( initialImage ) { initialImage . addEventListener ( 'load' , this . setMountState ) ; } else { this . setMountState ( ) ; } } destroyCarousel ( ) { if ( this . state . initialized ) { this . unbindEvents ( ) ; this . destroyAutoPlay ( ) ; } } setupAutoPlay ( ) { this . autoPlay ( ) ; } destroyAutoPlay ( ) { this . clearAutoPlay ( ) ; } bindEvents ( ) { window . addEventListener ( 'resize' , this . updateSizes ) ; window . addEventListener ( 'DOMContentLoaded' , this . updateSizes ) ; } unbindEvents ( ) { window . removeEventListener ( 'resize' , this . updateSizes ) ; window . removeEventListener ( 'DOMContentLoaded' , this . updateSizes ) ; const initialImage = this . getInitialImage ( ) ; if ( initialImage ) { initialImage . removeEventListener ( 'load' , this . setMountState ) ; } } autoPlay = ( ) => { if ( ! this . state . autoPlay || React . Children . count ( this . props . children ) <= 1 ) { return ; } clearTimeout ( this . timer ) ; this . timer = setTimeout ( ( ) => { this . increment ( ) ; } , this . props . autoPlay ) ; } ; clearAutoPlay = ( ) => { if ( ! this . state . autoPlay ) { return ; } clearTimeout ( this . timer ) ; } ; resetAutoPlay = ( ) => { this . clearAutoPlay ( ) ; this . autoPlay ( ) ; } ; updateSizes = ( ) => { if ( ! this . state . initialized ) { return ; } const isHorizontal = ! this . props . vertical ; const firstItem = this . itemsRef [ 0 ] ; const itemSize = isHorizontal ? firstItem . clientWidth : firstItem . clientHeight ; this . setState ( ( _state ) => ( { itemSize : itemSize , } ) ) ; } ; setMountState = ( ) => { this . setState ( { hasMount : true } ) ; this . updateSizes ( ) ; } ; handleClickItem = ( index , item ) => { if ( React . Children . count ( this . props . children ) === 0 ) { return ; } if ( this . state . cancelClick ) { this . setState ( { cancelClick : false , } ) ; return ; } this . props . onClickItem && this . props . onClickItem ( index , item ) ; if ( index !== this . state . selectedItem ) { this . setState ( { selectedItem : index , } ) ; } } ; handleOnChange = ( index , item ) => { if ( React . Children . count ( this . props . children ) <= 1 ) { return ; } this . props . onTransitionEnd && this . props . onTransitionEnd ( index , item ) ; this . props . onChange && this . props . onChange ( index , item ) ; } ; onSwipeStart = ( event ) => { this . setState ( { swiping : true , } ) ; this . props . onSwipeStart && this . props . onSwipeStart ( event ) ; this . clearAutoPlay ( ) ; } ; onSwipeEnd = ( event ) => { this . setState ( { swiping : false , cancelClick : false , } ) ; this . props . onSwipeEnd && this . props . onSwipeEnd ( event ) ; this . autoPlay ( ) ; } ; onSwipeMove = ( delta , event ) => { this . props . onSwipeMove && this . props . onSwipeMove ( event ) ; const isHorizontal = this . props . vertical === false ; const childrenLength = React . Children . count ( this . props . children ) ; const initialBoundry = 0 ; const currentPosition = this . getPosition ( this . state . selectedItem ) ; const finalBoundry = this . props . isInfinite ? this . getPosition ( childrenLength - 1 ) - 100 : this . getPosition ( childrenLength - 1 ) ; const axisDelta = isHorizontal ? delta . x : delta . y ; let handledDelta = axisDelta ; if ( currentPosition === initialBoundry && axisDelta > 0 ) { handledDelta = 0 ; } if ( currentPosition === finalBoundry && axisDelta < 0 ) { handledDelta = 0 ; } let position = currentPosition + 100 / ( this . state . itemSize / handledDelta ) ; if ( this . props . isInfinite ) { if ( this . state . selectedItem === 0 && position > - 100 ) { position -= childrenLength * 100 ; } else if ( this . state . selectedItem === childrenLength - 1 && position < - childrenLength * 100 ) { position += childrenLength * 100 ; } } position += '%' ; this . setPosition ( position ) ; const hasMoved = Math . abs ( axisDelta ) > this . props . distance ! ; if ( hasMoved && ! this . state . cancelClick ) { this . setState ( { cancelClick : true , } ) ; } return hasMoved ; } ; getPosition = ( index ) => { const { centerSlidePercentage } = this . props ; if ( this . props . isInfinite ) { ++ index ; } if ( index === 0 ) { return 0 ; } const childrenLength = React . Children . count ( this . props . children ) ; if ( this . props . centerMode && this . props . vertical === false ) { let currentPosition = - index * centerSlidePercentage ! ; const lastPosition = childrenLength - 1 ; if ( index && ( index !== lastPosition || this . props . isInfinite ) ) { currentPosition += ( 100 - centerSlidePercentage ! ) / 2 ; } else if ( index === lastPosition ) { currentPosition += 100 - centerSlidePercentage ! ; } return currentPosition ; } return - index * 100 ; } ; setPosition = ( position , forceReflow ) => { const list = ReactDOM . findDOMNode ( this . listRef ) ; [ 'WebkitTransform' , 'transform' ] . forEach ( prop => { list . style [ prop ] = CSSTranslate ( position , this . props . vertical ) ; } ) ; if ( forceReflow ) { list . offsetLeft ; } } ; resetPosition = ( ) => { const currentPosition = this . getPosition ( this . state . selectedItem ) + '%' ; this . setPosition ( currentPosition ) ; } ; decrement = ( positions = 1 , fromSwipe = false ) => { this . moveTo ( this . state . selectedItem - ( typeof positions === 'number' ? positions : 1 ) , fromSwipe ) ; } ; increment = ( positions = 1 , fromSwipe = false ) => { this . moveTo ( this . state . selectedItem + ( typeof positions === 'number' ? positions : 1 ) , fromSwipe ) ; } ; moveTo = ( position , fromSwipe ? ) => { const { centerSlidePercentage } = this . props ; const lastPosition = React . Children . count ( this . props . children ) - 1 ; const needClonedSlide = this . props . isInfinite && ! fromSwipe && ( position < 0 || position > lastPosition ) ; const oldPosition = position ; if ( position < 0 ) { position = this . props . isInfinite ? lastPosition : 0 ; } if ( position > lastPosition ) { position = this . props . isInfinite ? 0 : lastPosition ; } if ( needClonedSlide ) { this . setState ( { swiping : true , } , ( ) => { if ( oldPosition < 0 ) { if ( this . props . centerMode && ! this . props . vertical ) { this . setPosition ( `-${ ( lastPosition + 2 ) * centerSlidePercentage ! - ( 100 - centerSlidePercentage ! ) / 2 }%` , true ) ; } else { this . setPosition ( `-${ ( lastPosition + 2 ) * 100 }%` , true ) ; } } else if ( oldPosition > lastPosition ) { this . setPosition ( 0 , true ) ; } this . selectItem ( { selectedItem : position , swiping : false , } ) ; } ) ; } else { this . selectItem ( { selectedItem : position , } ) ; } if ( this . state . autoPlay && this . state . isMouseEntered === false ) { this . resetAutoPlay ( ) ; } } ; onSwipeForward = ( ) => { this . increment ( 1 , true ) ; } ; onSwipeBackwards = ( ) => { this . decrement ( 1 , true ) ; } ; changeItem = ( e ) => { if ( ! e . key || e . key === 'Enter' ) { const newIndex = e . target . value ; this . selectItem ( { selectedItem : newIndex , } ) ; } } ; selectItem = ( state , cb ) => { this . setState ( state , cb ) ; this . handleOnChange ( state . selectedItem , React . Children . toArray ( this . props . children ) [ state . selectedItem ] ) ; } ; getInitialImage = ( ) => { const selectedItem = this . props . initPage ; const item = this . itemsRef && this . itemsRef [ selectedItem ! ] ; const images = item && item . getElementsByTagName ( 'img' ) ; return images && images [ selectedItem ! ] ; } ; getVariableImageHeight = ( position ) => { const item = this . itemsRef && this . itemsRef [ position ] ; const images = item && item . getElementsByTagName ( 'img' ) ; if ( this . state . hasMount && images . length > 0 ) { const image = images [ 0 ] ; if ( ! image . complete ) { const onImageLoad = ( ) => { this . forceUpdate ( ) ; image . removeEventListener ( 'load' , onImageLoad ) ; } ; image . addEventListener ( 'load' , onImageLoad ) ; } const height = image . clientHeight ; return height > 0 ? height : null ; } return null ; } ; renderItems ( isClone ? ) { return React . Children . map ( this . props . children , ( item , index ) => { const slideProps = { ref : ( e ) => this . setItemsRef ( e , index ) , key : 'itemKey' + index + ( isClone ? 'clone' : '' ) , className : klass . ITEM ( true , index === this . state . selectedItem ) , onClick : this . handleClickItem . bind ( this , index , item ) , } ; if ( this . props . centerMode && ! this . props . vertical ) { slideProps . style = { minWidth : this . props . centerSlidePercentage + '%' , } ; } return < li { ... slideProps } > { this . props . renderItem ! ( item , { isSelected : index === this . state . selectedItem } ) } < /li>; } ) ; } renderControls ( ) { const { dots , dotsClass } = this . props ; if ( ! dots ) { return null ; } return ( < ul className = { classNames ( 'Yep-control-dots' , { [ `${ dotsClass }` ] : ! ! dotsClass } ) } > { React . Children . map ( this . props . children , ( item , index ) => { return this . props . renderIndicator ! ( this . changeItem , index === this . state . selectedItem , index , item ) ; } ) } < /ul>); } renderStatus ( ) { if ( ! this . props . showPager ) { return null ; } return ( < p className = { `${ this . props . prefixCls }-status` } > { this . props . renderPage ! ( this . state . selectedItem + 1 , React . Children . count ( this . props . children ) ) } < /p>); } render ( ) { if ( ! this . props . children || React . Children . count ( this . props . children ) === 0 ) { return null ; } const isHorizontal = ! this . props . vertical ; let itemListStyles = { } ; const currentPosition = this . getPosition ( this . state . selectedItem ) ; const transformProp = CSSTranslate ( currentPosition + '%' , this . props . vertical ! ) ; const transitionTime = this . props . transitionTime + 'ms' ; itemListStyles = { WebkitTransform : transformProp , transform : transformProp , } ; if ( ! this . state . swiping ) { itemListStyles = { ... itemListStyles , WebkitTransitionDuration : transitionTime , transitionDuration : transitionTime , } ; } const itemsClone = this . renderItems ( true ) ; const firstClone = itemsClone ! . shift ( ) ; const lastClone = itemsClone ! . pop ( ) ; let swiperProps = { onSwipeMove : this . onSwipeMove , onSwipeStart : this . onSwipeStart , onSwipeEnd : this . onSwipeEnd , } ; let ulProps = { style : { height : 'auto' , } , } as any ; const containerStyles = { } ; if ( isHorizontal ) { swiperProps . onSwipeLeft = this . onSwipeForward ; swiperProps . onSwipeRight = this . onSwipeBackwards ; if ( this . props . dynamicHeight ) { const itemHeight = this . getVariableImageHeight ( this . state . selectedItem ) ; ulProps . style . height = itemHeight ; containerStyles . height = itemHeight || 'auto' ; } } else { swiperProps . onSwipeUp = this . props . verticalSwipe === 'natural' ? this . onSwipeBackwards : this . onSwipeForward ; swiperProps . onSwipeDown = this . props . verticalSwipe === 'natural' ? this . onSwipeForward : this . onSwipeBackwards ; ulProps . style . height = this . state . itemSize ; containerStyles . height = this . state . itemSize ; } return ( < div className = { klass . ROOT ( this . props . className ) } style = { this . props . style } ref = { this . setCarouselWrapperRef } > < div className = { klass . CAROUSEL ( true ) } style = { { width : this . props . width } } > < div className = { klass . WRAPPER ( true , this . props . vertical ! ) } style = { containerStyles } ref = { this . setItemsWrapperRef } > < Gesture ref = { this . setListRef } { ... swiperProps } > < ul style = { { ... itemListStyles , ... ulProps } } className = { klass . SLIDER ( true , this . state . swiping ) } > { this . props . isInfinite && lastClone } { this . renderItems ( ) } { this . props . isInfinite && firstClone } < /ul> < /Gesture> < /div> { this . renderControls ( ) } { this . renderStatus ( ) } < /div> < /div>); } } export default Carousel ;