import { ComponentFactoryResolver , ComponentRef , ElementRef , ErrorHandler , EventEmitter , Input , NgZone , ReflectiveInjector , Renderer , ViewContainerRef } from '@angular/core' ; import { AnimationOptions } from '../animations/animation' ; import { App } from '../components/app/app' ; import { Config } from '../config/config' ; import { DIRECTION_BACK , DIRECTION_FORWARD , INIT_ZINDEX , NavOptions , NavResult , STATE_ATTACHED , STATE_DESTROYED , STATE_INITIALIZED , STATE_NEW , TransitionDoneFn , TransitionInstruction , convertToViews , } from './nav-util' ; import { setZIndex } from './nav-util' ; import { DeepLinker } from './deep-linker' ; import { DomController } from '../platform/dom-controller' ; import { GestureController } from '../gestures/gesture-controller' ; import { assert , isBlank , isNumber , isPresent , isTrueProperty } from '../util/util' ; import { ViewController , isViewController } from './view-controller' ; import { Ion } from '../components/ion' ; import { NavigationContainer } from './navigation-container' ; import { NavController } from './nav-controller' ; import { NavParams } from './nav-params' ; import { Platform } from '../platform/platform' ; import { SwipeBackGesture } from './swipe-back' ; import { Transition } from '../transitions/transition' ; import { TransitionController } from '../transitions/transition-controller' ; hidden export class NavControllerBase extends Ion implements NavController { _children : NavigationContainer [ ] ; _ids : number = - 1 ; _init = false ; _isPortal : boolean ; _queue : TransitionInstruction [ ] = [ ] ; _sbEnabled : boolean ; _sbGesture : SwipeBackGesture ; _sbTrns : Transition ; _trnsId : number = null ; _trnsTm : boolean = false ; _viewport : ViewContainerRef ; _views : ViewController [ ] = [ ] ; _zIndexOffset : number = 0 ; _destroyed : boolean ; viewDidLoad : EventEmitter < any > = new EventEmitter ( ) ; viewWillEnter : EventEmitter < any > = new EventEmitter ( ) ; viewDidEnter : EventEmitter < any > = new EventEmitter ( ) ; viewWillLeave : EventEmitter < any > = new EventEmitter ( ) ; viewDidLeave : EventEmitter < any > = new EventEmitter ( ) ; viewWillUnload : EventEmitter < any > = new EventEmitter ( ) ; id : string ; name : string ; @ Input ( ) get swipeBackEnabled ( ) : boolean { return this . _sbEnabled ; } set swipeBackEnabled ( val ) { this . _sbEnabled = isTrueProperty ( val ) ; this . _swipeBackCheck ( ) ; } constructor ( public parent , public _app , public config , public plt , elementRef , public _zone , renderer , public _cfr , public _gestureCtrl , public _trnsCtrl , public _linker , private _domCtrl , private _errHandler ) { super ( config , elementRef , renderer ) ; this . _sbEnabled = config . getBoolean ( 'swipeBackEnabled' ) ; this . _children = [ ] ; this . id = 'n' + ( ++ ctrlIds ) ; this . _destroyed = false ; } push ( page , params ? , opts ? , done ? ) { return this . _queueTrns ( { insertStart : - 1 , insertViews : [ { page : page , params : params } ] , opts : opts , } , done ) ; } insert ( insertIndex , page , params ? , opts ? , done ? ) { return this . _queueTrns ( { insertStart : insertIndex , insertViews : [ { page : page , params : params } ] , opts : opts , } , done ) ; } insertPages ( insertIndex , insertPages , opts ? , done ? ) { return this . _queueTrns ( { insertStart : insertIndex , insertViews : insertPages , opts : opts , } , done ) ; } pop ( opts ? , done ? ) { return this . _queueTrns ( { removeStart : - 1 , removeCount : 1 , opts : opts , } , done ) ; } popTo ( indexOrViewCtrl , opts ? , done ? ) { let config = { removeStart : - 1 , removeCount : - 1 , opts : opts } ; if ( isViewController ( indexOrViewCtrl ) ) { config . removeView = indexOrViewCtrl ; config . removeStart = 1 ; } else if ( isNumber ( indexOrViewCtrl ) ) { config . removeStart = indexOrViewCtrl + 1 ; } return this . _queueTrns ( config , done ) ; } popToRoot ( opts ? , done ? ) { return this . _queueTrns ( { removeStart : 1 , removeCount : - 1 , opts : opts , } , done ) ; } popAll ( ) { let promises = [ ] ; for ( var i = this . _views . length - 1 ; i >= 0 ; i -- ) { promises . push ( this . pop ( null ) ) ; } return Promise . all ( promises ) ; } remove ( startIndex , removeCount = 1 , opts ? , done ? ) { return this . _queueTrns ( { removeStart : startIndex , removeCount : removeCount , opts : opts , } , done ) ; } removeView ( viewController , opts ? , done ? ) { return this . _queueTrns ( { removeView : viewController , removeStart : 0 , removeCount : 1 , opts : opts , } , done ) ; } setRoot ( pageOrViewCtrl , params ? , opts ? , done ? ) { return this . setPages ( [ { page : pageOrViewCtrl , params : params } ] , opts , done ) ; } setPages ( viewControllers , opts ? , done ? ) { if ( isBlank ( opts ) ) { opts = { } ; } if ( opts . animate !== true ) { opts . animate = false ; } return this . _queueTrns ( { insertStart : 0 , insertViews : viewControllers , removeStart : 0 , removeCount : - 1 , opts : opts } , done ) ; } _queueTrns ( ti , done ) { const promise = new Promise < boolean > ( ( resolve , reject ) => { ti . resolve = resolve ; ti . reject = reject ; } ) ; ti . done = done ; if ( ti . insertViews && ti . insertViews . length === 0 ) { ti . insertViews = undefined ; } this . _queue . push ( ti ) ; this . _nextTrns ( ) ; return promise ; } _success ( result , ti ) { if ( this . _queue === null ) { this . _fireError ( 'nav controller was destroyed' , ti ) ; return ; } this . _init = true ; this . _trnsId = null ; this . setTransitioning ( false ) ; this . _swipeBackCheck ( ) ; this . _nextTrns ( ) ; if ( ti . done ) { ti . done ( result . hasCompleted , result . requiresTransition , result . enteringName , result . leavingName , result . direction ) ; } ti . resolve ( result . hasCompleted ) ; } _failed ( rejectReason , ti ) { if ( this . _queue === null ) { this . _fireError ( 'nav controller was destroyed' , ti ) ; return ; } this . _trnsId = null ; this . _queue . length = 0 ; this . setTransitioning ( false ) ; this . _swipeBackCheck ( ) ; this . _nextTrns ( ) ; this . _fireError ( rejectReason , ti ) ; } _fireError ( rejectReason , ti ) { if ( ti . done ) { ti . done ( false , false , rejectReason ) ; } if ( ti . reject && ! this . _destroyed ) { ti . reject ( rejectReason ) ; } else { ti . resolve ( false ) ; } } _nextTrns ( ) { if ( this . isTransitioning ( ) ) { return false ; } const ti = this . _queue . shift ( ) ; if ( ! ti ) { return false ; } let enteringView ; let leavingView ; this . _startTI ( ti ) . then ( ( ) => this . _loadLazyLoading ( ti ) ) . then ( ( ) => { leavingView = this . getActive ( ) ; enteringView = this . _getEnteringView ( ti , leavingView ) ; if ( ! leavingView && ! enteringView ) { throw 'no views in the stack to be removed' ; } if ( enteringView && enteringView . _state === STATE_NEW ) { this . _viewInit ( enteringView ) ; } ti . requiresTransition = ( ti . enteringRequiresTransition || ti . leavingRequiresTransition ) && enteringView !== leavingView ; } ) . then ( ( ) => this . _viewTest ( enteringView , leavingView , ti ) ) . then ( ( ) => this . _postViewInit ( enteringView , leavingView , ti ) ) . then ( ( ) => this . _transition ( enteringView , leavingView , ti ) ) . then ( ( result ) => this . _success ( result , ti ) ) . catch ( ( rejectReason ) => this . _failed ( rejectReason , ti ) ) ; return true ; } _startTI ( ti ) { const viewsLength = this . _views . length ; if ( isPresent ( ti . removeView ) ) { assert ( isPresent ( ti . removeStart ) , 'removeView needs removeStart' ) ; assert ( isPresent ( ti . removeCount ) , 'removeView needs removeCount' ) ; const index = this . indexOf ( ti . removeView ) ; if ( index < 0 ) { return Promise . reject ( 'removeView was not found' ) ; } ti . removeStart += index ; } if ( isPresent ( ti . removeStart ) ) { if ( ti . removeStart < 0 ) { ti . removeStart = ( viewsLength - 1 ) ; } if ( ti . removeCount < 0 ) { ti . removeCount = ( viewsLength - ti . removeStart ) ; } ti . leavingRequiresTransition = ( ti . removeCount > 0 ) && ( ( ti . removeStart + ti . removeCount ) === viewsLength ) ; } if ( ti . insertViews ) { if ( ti . insertStart < 0 || ti . insertStart > viewsLength ) { ti . insertStart = viewsLength ; } ti . enteringRequiresTransition = ( ti . insertStart === viewsLength ) ; } this . setTransitioning ( true ) ; return Promise . resolve ( ) ; } _loadLazyLoading ( ti ) { const insertViews = ti . insertViews ; if ( insertViews ) { assert ( insertViews . length > 0 , 'length can not be zero' ) ; return convertToViews ( this . _linker , insertViews ) . then ( ( viewControllers ) => { assert ( insertViews . length === viewControllers . length , 'lengths does not match' ) ; viewControllers = viewControllers . filter ( v => v !== null ) ; if ( viewControllers . length === 0 ) { throw 'invalid views to insert' ; } for ( var i = 0 ; i < viewControllers . length ; i ++ ) { var view = viewControllers [ i ] ; var nav = view . _nav ; if ( nav && nav !== this ) { throw 'inserted view was already inserted' ; } if ( view . _state === STATE_DESTROYED ) { throw 'inserted view was already destroyed' ; } } ti . insertViews = viewControllers ; } ) ; } return Promise . resolve ( ) ; } _getEnteringView ( ti , leavingView ) { const insertViews = ti . insertViews ; if ( insertViews ) { return insertViews [ insertViews . length - 1 ] ; } const removeStart = ti . removeStart ; if ( isPresent ( removeStart ) ) { var views = this . _views ; var removeEnd = removeStart + ti . removeCount ; var i ; var view ; for ( i = views . length - 1 ; i >= 0 ; i -- ) { view = views [ i ] ; if ( ( i < removeStart || i >= removeEnd ) && view !== leavingView ) { return view ; } } } return null ; } _postViewInit ( enteringView , leavingView , ti ) { assert ( leavingView || enteringView , 'Both leavingView and enteringView are null' ) ; assert ( ti . resolve , 'resolve must be valid' ) ; assert ( ti . reject , 'reject must be valid' ) ; const opts = ti . opts || { } ; const insertViews = ti . insertViews ; const removeStart = ti . removeStart ; const removeCount = ti . removeCount ; let view ; let i ; let destroyQueue ; if ( isPresent ( removeStart ) ) { assert ( removeStart >= 0 , 'removeStart can not be negative' ) ; assert ( removeCount >= 0 , 'removeCount can not be negative' ) ; destroyQueue = [ ] ; for ( i = 0 ; i < removeCount ; i ++ ) { view = this . _views [ i + removeStart ] ; if ( view && view !== enteringView && view !== leavingView ) { destroyQueue . push ( view ) ; } } opts . direction = opts . direction || DIRECTION_BACK ; } const finalBalance = this . _views . length + ( insertViews ? insertViews . length : 0 ) - ( removeCount ? removeCount : 0 ) ; assert ( finalBalance >= 0 , 'final balance can not be negative' ) ; if ( finalBalance === 0 && ! this . _isPortal ) { console . warn ( `You can't remove all the pages in the navigation stack. nav.pop() is probably called too many times.` , this , this . getNativeElement ( ) ) ; throw 'navigation stack needs at least one root page' ; } if ( insertViews ) { if ( isPresent ( opts . id ) ) { enteringView . id = opts . id ; } for ( i = 0 ; i < insertViews . length ; i ++ ) { view = insertViews [ i ] ; this . _insertViewAt ( view , ti . insertStart + i ) ; } if ( ti . enteringRequiresTransition ) { opts . direction = opts . direction || DIRECTION_FORWARD ; } } if ( destroyQueue && destroyQueue . length > 0 ) { this . _zone . run ( ( ) => { for ( i = 0 ; i < destroyQueue . length ; i ++ ) { view = destroyQueue [ i ] ; this . _willLeave ( view , true ) ; this . _didLeave ( view ) ; this . _willUnload ( view ) ; } } ) ; for ( i = 0 ; i < destroyQueue . length ; i ++ ) { this . _destroyView ( destroyQueue [ i ] ) ; } } if ( ti . requiresTransition && ! opts . animation ) { if ( isPresent ( ti . removeStart ) ) { opts . animation = ( leavingView || enteringView ) . getTransitionName ( opts . direction ) ; } else { opts . animation = ( enteringView || leavingView ) . getTransitionName ( opts . direction ) ; } } ti . opts = opts ; } /**
     * DOM WRITE
     */ _viewInit ( enteringView ) { assert ( enteringView , 'enteringView must be non null' ) ; assert ( enteringView . _state === STATE_NEW , 'enteringView state must be NEW' ) ; const componentProviders = ReflectiveInjector . resolve ( [ { provide : NavController , useValue : this } , { provide : ViewController , useValue : enteringView } , { provide : NavParams , useValue : enteringView . getNavParams ( ) } ] ) ; const componentFactory = this . _linker . resolveComponent ( enteringView . component ) ; const childInjector = ReflectiveInjector . fromResolvedProviders ( componentProviders , this . _viewport . parentInjector ) ; enteringView . init ( componentFactory . create ( childInjector , [ ] ) ) ; enteringView . _state = STATE_INITIALIZED ; this . _preLoad ( enteringView ) ; } _viewAttachToDOM ( view , componentRef , viewport ) { assert ( view . _state === STATE_INITIALIZED , 'view state must be INITIALIZED' ) ; assert ( componentRef , 'componentRef can not be null' ) ; this . _willLoad ( view ) ; viewport . insert ( componentRef . hostView , viewport . length ) ; view . _state = STATE_ATTACHED ; if ( view . _cssClass ) { var pageElement = componentRef . location . nativeElement ; this . _renderer . setElementClass ( pageElement , view . _cssClass , true ) ; } componentRef . changeDetectorRef . detectChanges ( ) ; this . _zone . run ( this . _didLoad . bind ( this , view ) ) ; } _viewTest ( enteringView , leavingView , ti ) { if ( ! ti . requiresTransition ) { return Promise . resolve ( ) ; } const promises = [ ] ; if ( leavingView ) { promises . push ( leavingView . _lifecycleTest ( 'Leave' ) ) ; } if ( enteringView ) { promises . push ( enteringView . _lifecycleTest ( 'Enter' ) ) ; } if ( promises . length === 0 ) { return Promise . resolve ( ) ; } return Promise . all ( promises ) . then ( ( values ) => { if ( values . some ( result => result === false ) ) { throw 'canEnter/Leave returned false' ; } } ) . catch ( ( reason ) => { ti . reject = null ; throw reason ; } ) ; } _transition ( enteringView , leavingView , ti ) { if ( ! ti . requiresTransition ) { return Promise . resolve ( { hasCompleted : true , requiresTransition : false } ) ; } const opts = ti . opts ; this . _trnsId = this . _trnsCtrl . getRootTrnsId ( this ) ; if ( this . _trnsId === null ) { this . _trnsId = this . _trnsCtrl . nextId ( ) ; } const animationOpts = { animation : opts . animation , direction : opts . direction , duration : ( opts . animate === false ? 0 : opts . duration ) , easing : opts . easing , isRTL : this . _config . plt . isRTL , ev : opts . ev , } ; const transition = this . _trnsCtrl . get ( this . _trnsId , enteringView , leavingView , animationOpts ) ; this . _sbTrns && this . _sbTrns . destroy ( ) ; this . _sbTrns = null ; if ( transition . isRoot ( ) && opts . progressAnimation ) { this . _sbTrns = transition ; } const promise = new Promise < void > ( resolve => transition . registerStart ( resolve ) ) . then ( ( ) => { return this . _transitionStart ( transition , enteringView , leavingView , opts ) ; } ) ; if ( enteringView && ( enteringView . _state === STATE_INITIALIZED ) ) { this . _viewAttachToDOM ( enteringView , enteringView . _cmp , this . _viewport ) ; } if ( ! transition . hasChildren ) { transition . start ( ) ; } return promise ; } _transitionStart ( transition , enteringView , leavingView , opts ) { assert ( this . isTransitioning ( ) , 'isTransitioning() has to be true' ) ; this . _trnsId = null ; setZIndex ( this , enteringView , leavingView , opts . direction , this . _renderer ) ; enteringView && enteringView . _domShow ( true , this . _renderer ) ; leavingView && leavingView . _domShow ( true , this . _renderer ) ; transition . init ( ) ; const isFirstPage = ! this . _init && this . _views . length === 1 ; const shouldNotAnimate = isFirstPage && ! this . _isPortal ; const canNotAnimate = this . _config . get ( 'animate' ) === false ; if ( shouldNotAnimate || canNotAnimate ) { opts . animate = false ; } if ( opts . animate === false ) { transition . duration ( 0 ) ; } transition . beforeAddRead ( this . _viewsWillLifecycles . bind ( this , enteringView , leavingView ) ) ; const duration = transition . getDuration ( ) ; const promise = new Promise ( resolve => { transition . onFinish ( resolve ) ; } ) ; if ( transition . isRoot ( ) ) { if ( duration > DISABLE_APP_MINIMUM_DURATION && opts . disableApp !== false ) { this . _app . setEnabled ( false , duration + ACTIVE_TRANSITION_OFFSET , opts . minClickBlockDuration ) ; } else { console . debug ( 'transition is running but app has not been disabled' ) ; } if ( opts . progressAnimation ) { transition . progressStart ( ) ; } else { transition . play ( ) ; } } return promise . then ( ( ) => this . _zone . run ( ( ) => { return this . _transitionFinish ( transition , opts ) ; } ) ) ; } _transitionFinish ( transition , opts ) { const hasCompleted = transition . hasCompleted ; const enteringView = transition . enteringView ; const leavingView = transition . leavingView ; let enteringName ; let leavingName ; if ( hasCompleted ) { if ( enteringView ) { enteringName = enteringView . name ; this . _didEnter ( enteringView ) ; } if ( leavingView ) { leavingName = leavingView . name ; this . _didLeave ( leavingView ) ; } this . _cleanup ( enteringView ) ; /**
             * On iOS 12.2 there is a bug that
             * causes scrolling to not
             * be re-enabled unless there
             * is some kind of CSS reflow triggered
             */ const platform = this . plt ; if ( enteringView && enteringView . getIONContentRef && enteringView . getIONContentRef ( ) && platform . is ( 'ios' ) ) { platform . timeout ( ( ) => { platform . raf ( ( ) => { const content = enteringView . getIONContentRef ( ) . nativeElement ; content . style . zIndex = '1' ; platform . raf ( ( ) => { content . style . zIndex = '' ; } ) ; } ) ; } , 500 ) ; } } else { this . _cleanup ( leavingView ) ; } if ( transition . isRoot ( ) ) { this . _trnsCtrl . destroy ( transition . trnsId ) ; this . _app . setEnabled ( true ) ; this . setTransitioning ( false ) ; if ( ! this . hasChildren ( ) && opts . updateUrl !== false ) { this . _linker . navChange ( opts . direction ) ; } if ( opts . keyboardClose !== false ) { this . plt . focusOutActiveElement ( ) ; } } return { hasCompleted : hasCompleted , requiresTransition : true , enteringName : enteringName , leavingName : leavingName , direction : opts . direction } ; } _viewsWillLifecycles ( enteringView , leavingView ) { if ( enteringView || leavingView ) { this . _zone . run ( ( ) => { if ( leavingView ) { const willUnload = enteringView ? leavingView . index > enteringView . index : true ; this . _willLeave ( leavingView , willUnload ) ; } enteringView && this . _willEnter ( enteringView ) ; } ) ; } } _insertViewAt ( view , index ) { const existingIndex = this . _views . indexOf ( view ) ; if ( existingIndex > - 1 ) { assert ( view . _nav === this , 'view is not part of the nav' ) ; this . _views . splice ( index , 0 , this . _views . splice ( existingIndex , 1 ) [ 0 ] ) ; } else { assert ( ! view . _nav || ( this . _isPortal && view . _nav === this ) , 'nav is used' ) ; view . _setNav ( this ) ; this . _ids ++ ; if ( ! view . id ) { view . id = `${ this . id }-${ this . _ids }` ; } this . _views . splice ( index , 0 , view ) ; } } _removeView ( view ) { assert ( view . _state === STATE_ATTACHED || view . _state === STATE_DESTROYED , 'view state should be loaded or destroyed' ) ; const views = this . _views ; const index = views . indexOf ( view ) ; assert ( index > - 1 , 'view must be part of the stack' ) ; if ( index >= 0 ) { views . splice ( index , 1 ) ; } } _destroyView ( view ) { view . _destroy ( this . _renderer ) ; this . _removeView ( view ) ; } /**
     * DOM WRITE
     */ _cleanup ( activeView ) { if ( ! this . _destroyed ) { const activeViewIndex = this . _views . indexOf ( activeView ) ; const views = this . _views ; let reorderZIndexes = false ; let view ; let i ; for ( i = views . length - 1 ; i >= 0 ; i -- ) { view = views [ i ] ; if ( i > activeViewIndex ) { this . _willUnload ( view ) ; this . _destroyView ( view ) ; } else if ( i < activeViewIndex && ! this . _isPortal ) { view . _domShow ( false , this . _renderer ) ; } if ( view . _zIndex <= 0 ) { reorderZIndexes = true ; } } if ( ! this . _isPortal && reorderZIndexes ) { for ( i = 0 ; i < views . length ; i ++ ) { view = views [ i ] ; view . _setZIndex ( view . _zIndex + INIT_ZINDEX + 1 , this . _renderer ) ; } } } } _preLoad ( view ) { assert ( this . isTransitioning ( ) , 'nav controller should be transitioning' ) ; view . _preLoad ( ) ; } _willLoad ( view ) { assert ( this . isTransitioning ( ) , 'nav controller should be transitioning' ) ; try { view . _willLoad ( ) ; } catch ( e ) { this . _errHandler && this . _errHandler . handleError ( e ) ; } } _didLoad ( view ) { assert ( this . isTransitioning ( ) , 'nav controller should be transitioning' ) ; assert ( NgZone . isInAngularZone ( ) , 'callback should be zoned' ) ; try { view . _didLoad ( ) ; this . viewDidLoad . emit ( view ) ; this . _app . viewDidLoad . emit ( view ) ; } catch ( e ) { this . _errHandler && this . _errHandler . handleError ( e ) ; } } _willEnter ( view ) { assert ( this . isTransitioning ( ) , 'nav controller should be transitioning' ) ; assert ( NgZone . isInAngularZone ( ) , 'callback should be zoned' ) ; try { view . _willEnter ( ) ; this . viewWillEnter . emit ( view ) ; this . _app . viewWillEnter . emit ( view ) ; } catch ( e ) { this . _errHandler && this . _errHandler . handleError ( e ) ; } } _didEnter ( view ) { assert ( this . isTransitioning ( ) , 'nav controller should be transitioning' ) ; assert ( NgZone . isInAngularZone ( ) , 'callback should be zoned' ) ; try { view . _didEnter ( ) ; this . viewDidEnter . emit ( view ) ; this . _app . viewDidEnter . emit ( view ) ; } catch ( e ) { this . _errHandler && this . _errHandler . handleError ( e ) ; } } _willLeave ( view , willUnload ) { assert ( this . isTransitioning ( ) , 'nav controller should be transitioning' ) ; assert ( NgZone . isInAngularZone ( ) , 'callback should be zoned' ) ; try { view . _willLeave ( willUnload ) ; this . viewWillLeave . emit ( view ) ; this . _app . viewWillLeave . emit ( view ) ; } catch ( e ) { this . _errHandler && this . _errHandler . handleError ( e ) ; } } _didLeave ( view ) { assert ( this . isTransitioning ( ) , 'nav controller should be transitioning' ) ; assert ( NgZone . isInAngularZone ( ) , 'callback should be zoned' ) ; try { view . _didLeave ( ) ; this . viewDidLeave . emit ( view ) ; this . _app . viewDidLeave . emit ( view ) ; } catch ( e ) { this . _errHandler && this . _errHandler . handleError ( e ) ; } } _willUnload ( view ) { assert ( this . isTransitioning ( ) , 'nav controller should be transitioning' ) ; assert ( NgZone . isInAngularZone ( ) , 'callback should be zoned' ) ; try { view . _willUnload ( ) ; this . viewWillUnload . emit ( view ) ; this . _app . viewWillUnload . emit ( view ) ; } catch ( e ) { this . _errHandler && this . _errHandler . handleError ( e ) ; } } hasChildren ( ) { return this . _children && this . _children . length > 0 ; } getActiveChildNavs ( ) { return this . _children ; } getAllChildNavs ( ) { return this . _children ; } registerChildNav ( container ) { this . _children . push ( container ) ; } unregisterChildNav ( nav ) { this . _children = this . _children . filter ( child => child !== nav ) ; } destroy ( ) { const views = this . _views ; let view ; for ( var i = 0 ; i < views . length ; i ++ ) { view = views [ i ] ; view . _willUnload ( ) ; view . _destroy ( this . _renderer ) ; } this . _sbGesture && this . _sbGesture . destroy ( ) ; this . _sbTrns && this . _sbTrns . destroy ( ) ; this . _queue = this . _views = this . _sbGesture = this . _sbTrns = null ; if ( this . parent && this . parent . unregisterChildNav ) { this . parent . unregisterChildNav ( this ) ; } else if ( this . _app ) { this . _app . unregisterRootNav ( this ) ; } this . _destroyed = true ; } swipeBackStart ( ) { if ( this . isTransitioning ( ) || this . _queue . length > 0 ) { return ; } const opts = { direction : DIRECTION_BACK , progressAnimation : true } ; this . _queueTrns ( { removeStart : - 1 , removeCount : 1 , opts : opts , } , null ) ; } swipeBackProgress ( stepValue ) { if ( this . _sbTrns && this . _sbGesture ) { this . _app . setEnabled ( false , ACTIVE_TRANSITION_DEFAULT ) ; this . setTransitioning ( true ) ; this . _sbTrns . progressStep ( stepValue ) ; } } swipeBackEnd ( shouldComplete , currentStepValue , velocity ) { if ( this . _sbTrns && this . _sbGesture ) { var dur = this . _sbTrns . getDuration ( ) / ( Math . abs ( velocity ) + 1 ) ; this . _sbTrns . progressEnd ( shouldComplete , currentStepValue , dur ) ; } } _swipeBackCheck ( ) { if ( this . canSwipeBack ( ) ) { if ( ! this . _sbGesture ) { this . _sbGesture = new SwipeBackGesture ( this . plt , this , this . _gestureCtrl , this . _domCtrl ) ; } this . _sbGesture . listen ( ) ; } else if ( this . _sbGesture ) { this . _sbGesture . unlisten ( ) ; } } canSwipeBack ( ) { return ( this . _sbEnabled && ! this . _isPortal && ! this . _children . length && ! this . isTransitioning ( ) && this . _app . isEnabled ( ) && this . canGoBack ( ) ) ; } canGoBack ( ) { const activeView = this . getActive ( ) ; return ! ! ( activeView && activeView . enableBack ( ) ) ; } isTransitioning ( ) { return this . _trnsTm ; } setTransitioning ( isTransitioning ) { this . _trnsTm = isTransitioning ; } getActive ( ) { return this . _views [ this . _views . length - 1 ] ; } isActive ( view ) { return ( view === this . getActive ( ) ) ; } getByIndex ( index ) { return this . _views [ index ] ; } getPrevious ( view ? ) { if ( ! view ) { view = this . getActive ( ) ; } const views = this . _views ; const index = views . indexOf ( view ) ; return ( index > 0 ) ? views [ index - 1 ] : null ; } first ( ) { return this . _views [ 0 ] ; } last ( ) { const views = this . _views ; return views [ views . length - 1 ] ; } indexOf ( view ) { return this . _views . indexOf ( view ) ; } length ( ) { return this . _views . length ; } getViews ( ) { return this . _views ; } /**
     * Return a view controller
     */ getViewById ( id ) { for ( const vc of this . _views ) { if ( vc && vc . id === id ) { return vc ; } } return null ; } isSwipeBackEnabled ( ) { return this . _sbEnabled ; } dismissPageChangeViews ( ) { for ( let view of this . _views ) { if ( view . data && view . data . dismissOnPageChange ) { view . dismiss ( ) . catch ( ( ) => { } ) ; } } } setViewport ( val ) { this . _viewport = val ; } resize ( ) { const active = this . getActive ( ) ; if ( ! active ) { return ; } const content = active . getIONContent ( ) ; content && content . resize ( ) ; } goToRoot ( _opts ) { return Promise . reject ( new Error ( 'goToRoot needs to be implemented by child class' ) ) ; } getType ( ) { return 'nav' ; } getSecondaryIdentifier ( ) { return null ; } /**
     * Returns the active child navigation.
     */ getActiveChildNav ( ) { console . warn ( '(getActiveChildNav) is deprecated and will be removed in the next major release. Use getActiveChildNavs instead.' ) ; return this . _children [ this . _children . length - 1 ] ; } } let ctrlIds = - 1 ; const DISABLE_APP_MINIMUM_DURATION = 64 ; const ACTIVE_TRANSITION_DEFAULT = 5000 ; const ACTIVE_TRANSITION_OFFSET = 2000 ;