import { Directive , EventEmitter , Host , Input , NgZone , Output } from '@angular/core' ; import { Content } from '../content/content' ; import { GESTURE_PRIORITY_REFRESHER , GESTURE_REFRESHER , GestureController , GestureDelegate } from '../../gestures/gesture-controller' ; import { isTrueProperty } from '../../util/util' ; import { Platform } from '../../platform/platform' ; import { pointerCoord } from '../../util/dom' ; import { PointerEvents } from '../../gestures/pointer-events' ; import { UIEventManager } from '../../gestures/ui-event-manager' ; name description usage Component demo @ Directive ( { selector : 'ion-refresher' , host : { '[class.refresher-active]' : 'state !== "inactive"' , '[style.top]' : '_top' } } ) export class Refresher { _appliedStyles : boolean = false ; _didStart : boolean ; _lastCheck : number = 0 ; _isEnabled : boolean = true ; _gesture : GestureDelegate ; _events : UIEventManager ; _pointerEvents : PointerEvents ; _top : string = '' ; /**
     * The current state which the refresher is in. The refresher's states include:
     *
     * - `inactive` - The refresher is not being pulled down or refreshing and is currently hidden.
     * - `pulling` - The user is actively pulling down the refresher, but has not reached the point yet that if the user lets go, it'll refresh.
     * - `cancelling` - The user pulled down the refresher and let go, but did not pull down far enough to kick off the `refreshing` state. After letting go, the refresher is in the `cancelling` state while it is closing, and will go back to the `inactive` state once closed.
     * - `ready` - The user has pulled down the refresher far enough that if they let go, it'll begin the `refreshing` state.
     * - `refreshing` - The refresher is actively waiting on the async operation to end. Once the refresh handler calls `complete()` it will begin the `completing` state.
     * - `completing` - The `refreshing` state has finished and the refresher is in the process of closing itself. Once closed, the refresher will go back to the `inactive` state.
     */ state : string = STATE_INACTIVE ; /**
     * The Y coordinate of where the user started to the pull down the content.
     */ startY : number = null ; /**
     * The current touch or mouse event's Y coordinate.
     */ currentY : number = null ; /**
     * The distance between the start of the pull and the current touch or
     * mouse event's Y coordinate.
     */ deltaY : number = null ; /**
     * A number representing how far down the user has pulled.
     * The number `0` represents the user hasn't pulled down at all. The
     * number `1`, and anything greater than `1`, represents that the user
     * has pulled far enough down that when they let go then the refresh will
     * happen. If they let go and the number is less than `1`, then the
     * refresh will not happen, and the content will return to it's original
     * position.
     */ progress : number = 0 ; input @ Input ( ) pullMin : number = 60 ; input @ Input ( ) pullMax : number = this . pullMin + 60 ; input @ Input ( ) closeDuration : number = 280 ; input @ Input ( ) snapbackDuration : number = 280 ; input @ Input ( ) get enabled ( ) : boolean { return this . _isEnabled ; } set enabled ( val ) { this . _isEnabled = isTrueProperty ( val ) ; this . _setListeners ( this . _isEnabled ) ; } output @ Output ( ) ionRefresh : EventEmitter < Refresher > = new EventEmitter < Refresher > ( ) ; output @ Output ( ) ionPull : EventEmitter < Refresher > = new EventEmitter < Refresher > ( ) ; output @ Output ( ) ionStart : EventEmitter < Refresher > = new EventEmitter < Refresher > ( ) ; constructor ( private _plt , @ Host ( ) private _content , private _zone , gestureCtrl ) { this . _events = new UIEventManager ( _plt ) ; _content . _hasRefresher = true ; this . _gesture = gestureCtrl . createGesture ( { name : GESTURE_REFRESHER , priority : GESTURE_PRIORITY_REFRESHER } ) ; } _onStart ( ev ) { if ( ev . touches && ev . touches . length > 1 ) { return false ; } if ( this . state !== STATE_INACTIVE ) { return false ; } let scrollHostScrollTop = this . _content . getContentDimensions ( ) . scrollTop ; if ( scrollHostScrollTop > 0 ) { return false ; } if ( ! this . _gesture . canStart ( ) ) { return false ; } let coord = pointerCoord ( ev ) ; console . debug ( 'Pull-to-refresh, onStart' , ev . type , 'y:' , coord . y ) ; if ( this . _content . contentTop > 0 ) { let newTop = this . _content . contentTop + 'px' ; if ( this . _top !== newTop ) { this . _top = newTop ; } } this . startY = this . currentY = coord . y ; this . progress = 0 ; this . state = STATE_INACTIVE ; return true ; } _onMove ( ev ) { if ( ev . touches && ev . touches . length > 1 ) { return 1 ; } if ( ! this . _gesture . canStart ( ) ) { return 0 ; } if ( this . startY === null || this . state === STATE_REFRESHING || this . state === STATE_CANCELLING || this . state === STATE_COMPLETING ) { return 2 ; } let now = Date . now ( ) ; if ( this . _lastCheck + 16 > now ) { return 3 ; } this . _lastCheck = now ; let coord = pointerCoord ( ev ) ; this . currentY = coord . y ; this . deltaY = ( coord . y - this . startY ) ; if ( this . deltaY <= 0 ) { this . progress = 0 ; if ( this . state !== STATE_INACTIVE ) { this . _zone . run ( ( ) => { this . state = STATE_INACTIVE ; } ) ; } if ( this . _appliedStyles ) { this . _setCss ( 0 , '' , false , '' ) ; return 5 ; } return 6 ; } if ( this . state === STATE_INACTIVE ) { let scrollHostScrollTop = this . _content . getContentDimensions ( ) . scrollTop ; if ( scrollHostScrollTop > 0 ) { this . progress = 0 ; this . startY = null ; return 7 ; } this . state = STATE_PULLING ; } ev . preventDefault ( ) ; this . _setCss ( this . deltaY , '0ms' , true , '' ) ; if ( ! this . deltaY ) { this . progress = 0 ; return 8 ; } this . _zone . run ( ( ) => { this . _onMoveInZone ( ) ; } ) ; } _onMoveInZone ( ) { this . progress = ( this . deltaY / this . pullMin ) ; if ( ! this . _didStart ) { this . _didStart = true ; this . ionStart . emit ( this ) ; } this . ionPull . emit ( this ) ; if ( this . deltaY < this . pullMin ) { this . state = STATE_PULLING ; return 2 ; } if ( this . deltaY > this . pullMax ) { this . _beginRefresh ( ) ; return 3 ; } this . state = STATE_READY ; return 4 ; } _onEnd ( ) { if ( this . state === STATE_READY ) { this . _zone . run ( ( ) => { this . _beginRefresh ( ) ; } ) ; } else if ( this . state === STATE_PULLING ) { this . _zone . run ( ( ) => { this . cancel ( ) ; } ) ; } this . startY = null ; } _beginRefresh ( ) { this . state = STATE_REFRESHING ; this . _setCss ( this . pullMin , ( this . snapbackDuration + 'ms' ) , true , '' ) ; this . ionRefresh . emit ( this ) ; } /**
     * Call `complete()` when your async operation has completed.
     * For example, the `refreshing` state is while the app is performing
     * an asynchronous operation, such as receiving more data from an
     * AJAX request. Once the data has been received, you then call this
     * method to signify that the refreshing has completed and to close
     * the refresher. This method also changes the refresher's state from
     * `refreshing` to `completing`.
     */ complete ( ) { this . _close ( STATE_COMPLETING , '120ms' ) ; } /**
     * Changes the refresher's state from `refreshing` to `cancelling`.
     */ cancel ( ) { this . _close ( STATE_CANCELLING , '' ) ; } _close ( state , delay ) { var timer ; function close ( ev ) { if ( ev ) { clearTimeout ( timer ) ; } this . state = STATE_INACTIVE ; this . progress = 0 ; this . _didStart = this . startY = this . currentY = this . deltaY = null ; this . _setCss ( 0 , '0ms' , false , '' ) ; } timer = setTimeout ( close . bind ( this ) , 600 ) ; this . _content . onScrollElementTransitionEnd ( close . bind ( this ) ) ; this . state = state ; this . _setCss ( 0 , '' , true , delay ) ; if ( this . _pointerEvents ) { this . _pointerEvents . stop ( ) ; } } _setCss ( y , duration , overflowVisible , delay ) { this . _appliedStyles = ( y > 0 ) ; const content = this . _content ; const Css = this . _plt . Css ; content . setScrollElementStyle ( Css . transform , ( ( y > 0 ) ? 'translateY(' + y + 'px) translateZ(0px)' : 'translateZ(0px)' ) ) ; content . setScrollElementStyle ( Css . transitionDuration , duration ) ; content . setScrollElementStyle ( Css . transitionDelay , delay ) ; content . setScrollElementStyle ( 'overflow' , ( overflowVisible ? 'hidden' : '' ) ) ; } _setListeners ( shouldListen ) { this . _events . unlistenAll ( ) ; this . _pointerEvents = null ; if ( shouldListen ) { this . _pointerEvents = this . _events . pointerEvents ( { element : this . _content . getScrollElement ( ) , pointerDown : this . _onStart . bind ( this ) , pointerMove : this . _onMove . bind ( this ) , pointerUp : this . _onEnd . bind ( this ) , zone : false } ) ; } } hidden ngOnInit ( ) { this . _setListeners ( this . _isEnabled ) ; } hidden ngOnDestroy ( ) { this . _setListeners ( false ) ; this . _events . destroy ( ) ; this . _gesture . destroy ( ) ; } } const STATE_INACTIVE = 'inactive' ; const STATE_PULLING = 'pulling' ; const STATE_READY = 'ready' ; const STATE_REFRESHING = 'refreshing' ; const STATE_CANCELLING = 'cancelling' ; const STATE_COMPLETING = 'completing' ;