diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 26583fc8d5..a83e0cf2a0 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -2007,6 +2007,7 @@ declare global { } namespace JSXElements { export interface IonNavAttributes extends HTMLAttributes { + animated?: boolean; root?: any; rootParams?: any; swipeBackEnabled?: boolean; diff --git a/core/src/components/animation-controller/animation-interface.tsx b/core/src/components/animation-controller/animation-interface.tsx index bca2143f5e..5f1b3b8f5a 100644 --- a/core/src/components/animation-controller/animation-interface.tsx +++ b/core/src/components/animation-controller/animation-interface.tsx @@ -1,4 +1,3 @@ -import { ViewController } from '../..'; export interface AnimationController { create(animationBuilder?: AnimationBuilder, baseEl?: any, opts?: any): Promise; @@ -48,19 +47,6 @@ export interface AnimationBuilder { (Animation: Animation, baseEl?: HTMLElement, opts?: any): Promise; } - -export interface AnimationOptions { - animation?: string; - duration?: number; - easing?: string; - direction?: string; - isRTL?: boolean; - ev?: any; - enteringView: ViewController; - leavingView: ViewController; - nav: HTMLIonNavElement; -} - export interface PlayOptions { duration?: number; promise?: boolean; diff --git a/core/src/components/animation-controller/animator.tsx b/core/src/components/animation-controller/animator.tsx index 432777c684..23c6ce3136 100644 --- a/core/src/components/animation-controller/animator.tsx +++ b/core/src/components/animation-controller/animator.tsx @@ -1,4 +1,4 @@ -import { AnimationOptions, EffectProperty, EffectState, PlayOptions } from './animation-interface'; +import { EffectProperty, EffectState, PlayOptions } from './animation-interface'; import { CSS_PROP, CSS_VALUE_REGEX, DURATION_MIN, TRANSITION_END_FALLBACK_PADDING_MS } from './constants'; import { transitionEnd } from './transition-end'; @@ -54,7 +54,6 @@ export class Animator { private _destroyed = false; parent: Animator|undefined; - opts: AnimationOptions; hasChildren = false; isPlaying = false; hasCompleted = false; diff --git a/core/src/components/app/app.scss b/core/src/components/app/app.scss index 815a2a0b13..fddb49de36 100644 --- a/core/src/components/app/app.scss +++ b/core/src/components/app/app.scss @@ -170,11 +170,6 @@ ion-app, contain: layout size style; } -.hide-page { - opacity: 0; -} - - // Misc // -------------------------------------------------- diff --git a/core/src/components/back-button/back-button.scss b/core/src/components/back-button/back-button.scss index b0e79993dd..f30e8460db 100644 --- a/core/src/components/back-button/back-button.scss +++ b/core/src/components/back-button/back-button.scss @@ -7,7 +7,7 @@ display: none; } -.back-button.can-back-back, +.can-go-back > ion-header .back-button, .back-button.show-back-button { display: inline-block; } diff --git a/core/src/components/back-button/back-button.tsx b/core/src/components/back-button/back-button.tsx index 19ef796604..c32d0678fd 100644 --- a/core/src/components/back-button/back-button.tsx +++ b/core/src/components/back-button/back-button.tsx @@ -35,15 +35,6 @@ export class BackButton { @Element() el: HTMLElement; - - hostData() { - return { - class: { - 'show-back-button': !!this.defaultHref - } - }; - } - private onClick(ev: Event) { const nav = this.el.closest('ion-nav'); if (nav && nav.canGoBack()) { @@ -54,6 +45,14 @@ export class BackButton { } } + hostData() { + return { + class: { + 'show-back-button': !!this.defaultHref + } + }; + } + render() { const backButtonIcon = this.icon || this.config.get('backButtonIcon', 'arrow-back'); const backButtonText = this.text || this.config.get('backButtonText', this.mode === 'ios' ? 'Back' : ''); diff --git a/core/src/components/nav/animations/ios.transition.ts b/core/src/components/nav/animations/ios.transition.ts index 979c871021..4c64f39633 100644 --- a/core/src/components/nav/animations/ios.transition.ts +++ b/core/src/components/nav/animations/ios.transition.ts @@ -1,5 +1,5 @@ -import { Animation, AnimationOptions } from '../../../index'; -import { isDef } from '../../../utils/helpers'; +import { Animation } from '../../../index'; +import { AnimationOptions } from '../transition'; const DURATION = 500; const EASING = 'cubic-bezier(0.36,0.66,0.04,1)'; @@ -8,27 +8,29 @@ const TRANSFORM = 'transform'; const TRANSLATEX = 'translateX'; const CENTER = '0%'; const OFF_OPACITY = 0.8; -const SHOW_BACK_BTN_CSS = 'can-back-back'; -export default function iosTransitionAnimation(Animation: Animation, _: HTMLElement, opts: AnimationOptions): Promise { +export default function iosTransitionAnimation(Animation: Animation, navEl: HTMLElement, opts: AnimationOptions): Promise { const isRTL = opts.isRTL; const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%'; const OFF_LEFT = isRTL ? '31%' : '-31%'; - const enteringEl = opts.enteringView ? opts.enteringView.element : undefined; - const leavingEl = opts.leavingView ? opts.leavingView.element : undefined; - const nav = opts.nav; + const enteringEl = opts.enteringEl; + const leavingEl = opts.leavingEl; const rootTransition = new Animation(); - rootTransition.duration(isDef(opts.duration) ? opts.duration : DURATION); - rootTransition.easing(isDef(opts.easing) ? opts.easing : EASING); - rootTransition.addElement(enteringEl); - rootTransition.beforeRemoveClass('hide-page'); + rootTransition + .addElement(enteringEl) + .duration(opts.duration || DURATION) + .easing(opts.easing || EASING) + .beforeRemoveClass('hide-page'); - if (leavingEl && nav) { + if (leavingEl && navEl) { const navDecor = new Animation(); - navDecor.addElement(nav.el).duringAddClass('show-decor'); + navDecor + .addElement(navEl) + .duringAddClass('show-decor'); + rootTransition.add(navDecor); } @@ -90,10 +92,8 @@ export default function iosTransitionAnimation(Animation: Animation, _: HTMLElem if (backDirection) { enteringTitle.fromTo(TRANSLATEX, OFF_LEFT, CENTER, true); - if (opts.enteringView.enableBack()) { - // back direction, entering page has a back button - enteringBackButton.beforeAddClass(SHOW_BACK_BTN_CSS).fromTo(OPACITY, 0.01, 1, true); - } + // back direction, entering page has a back button + enteringBackButton.fromTo(OPACITY, 0.01, 1, true); } else { // entering toolbar, forward direction enteringTitle.fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true); @@ -102,21 +102,15 @@ export default function iosTransitionAnimation(Animation: Animation, _: HTMLElem .beforeClearStyles([OPACITY]) .fromTo(OPACITY, 0.01, 1, true); - if (opts.enteringView.enableBack()) { - // forward direction, entering page has a back button - enteringBackButton - .beforeAddClass(SHOW_BACK_BTN_CSS) - .fromTo(OPACITY, 0.01, 1, true); + // forward direction, entering page has a back button + enteringBackButton.fromTo(OPACITY, 0.01, 1, true); + const enteringBackBtnText = new Animation(); + enteringBackBtnText + .addElement(enteringToolBarEle.querySelector('ion-back-button .button-text')) + .fromTo(TRANSLATEX, (isRTL ? '-100px' : '100px'), '0px'); - const enteringBackBtnText = new Animation(); - enteringBackBtnText.addElement(enteringToolBarEle.querySelector('ion-back-button .button-text')); - - enteringBackBtnText.fromTo(TRANSLATEX, (isRTL ? '-100px' : '100px'), '0px'); - enteringToolBar.add(enteringBackBtnText); - } else { - enteringBackButton.beforeRemoveClass(SHOW_BACK_BTN_CSS); - } + enteringToolBar.add(enteringBackBtnText); } } } diff --git a/core/src/components/nav/animations/md.transition.ts b/core/src/components/nav/animations/md.transition.ts index d2edacad4a..7554696fa7 100644 --- a/core/src/components/nav/animations/md.transition.ts +++ b/core/src/components/nav/animations/md.transition.ts @@ -1,61 +1,61 @@ -import { Animation, AnimationOptions } from '../../../index'; -import { isDef } from '../../../utils/helpers'; +import { Animation } from '../../../index'; +import { AnimationOptions } from '../transition'; const TRANSLATEY = 'translateY'; const OFF_BOTTOM = '40px'; const CENTER = '0px'; -const SHOW_BACK_BTN_CSS = 'can-back-back'; export default function mdTransitionAnimation(Animation: Animation, _: HTMLElement, opts: AnimationOptions): Promise { - const enteringEl = opts.enteringView ? opts.enteringView.element : undefined; - const leavingEl = opts.leavingView ? opts.leavingView.element : undefined; + const enteringEl = opts.enteringEl; + const leavingEl = opts.leavingEl; const ionPageElement = getIonPageElement(enteringEl); const rootTransition = new Animation(); - rootTransition.addElement(ionPageElement); - rootTransition.beforeRemoveClass('hide-page'); + rootTransition + .addElement(ionPageElement) + .beforeRemoveClass('hide-page'); const backDirection = (opts.direction === 'back'); if (enteringEl) { // animate the component itself if (backDirection) { - rootTransition.duration(isDef(opts.duration) ? opts.duration : 200).easing('cubic-bezier(0.47,0,0.745,0.715)'); - } else { - rootTransition.duration(isDef(opts.duration) ? opts.duration : 280).easing('cubic-bezier(0.36,0.66,0.04,1)'); - rootTransition - .fromTo(TRANSLATEY, OFF_BOTTOM, CENTER, true) - .fromTo('opacity', 0.01, 1, true); + .duration(opts.duration || 200) + .easing('cubic-bezier(0.47,0,0.745,0.715)'); + + } else { + rootTransition + .duration(opts.duration || 280) + .easing('cubic-bezier(0.36,0.66,0.04,1)') + .fromTo(TRANSLATEY, OFF_BOTTOM, CENTER, true) + .fromTo('opacity', 0.01, 1, true); } // Animate toolbar if it's there const enteringToolbarEle = ionPageElement.querySelector('ion-toolbar'); if (enteringToolbarEle) { - const enteringToolBar = new Animation(); enteringToolBar.addElement(enteringToolbarEle); rootTransition.add(enteringToolBar); - - const enteringBackButton = new Animation(); - enteringBackButton.addElement(enteringToolbarEle.querySelector('ion-back-button')); - rootTransition.add(enteringBackButton); - if (opts.enteringView.enableBack()) { - enteringBackButton.beforeAddClass(SHOW_BACK_BTN_CSS); - } else { - enteringBackButton.beforeRemoveClass(SHOW_BACK_BTN_CSS); - } } } // setup leaving view if (leavingEl && backDirection) { // leaving content - rootTransition.duration(opts.duration || 200).easing('cubic-bezier(0.47,0,0.745,0.715)'); + rootTransition + .duration(opts.duration || 200) + .easing('cubic-bezier(0.47,0,0.745,0.715)'); + const leavingPage = new Animation(); - leavingPage.addElement(getIonPageElement(leavingEl)); - rootTransition.add(leavingPage.fromTo(TRANSLATEY, CENTER, OFF_BOTTOM).fromTo('opacity', 1, 0)); + leavingPage + .addElement(getIonPageElement(leavingEl)) + .fromTo(TRANSLATEY, CENTER, OFF_BOTTOM) + .fromTo('opacity', 1, 0); + + rootTransition.add(leavingPage); } return Promise.resolve(rootTransition); diff --git a/core/src/components/nav/nav-util.ts b/core/src/components/nav/nav-util.ts index 17458bd836..b434694471 100644 --- a/core/src/components/nav/nav-util.ts +++ b/core/src/components/nav/nav-util.ts @@ -1,7 +1,5 @@ import { ViewController, isViewController } from './view-controller'; -import { NavControllerBase } from './nav'; -import { Transition } from './transition'; -import { FrameworkDelegate } from '../..'; +import { Animation, FrameworkDelegate } from '../..'; export function convertToView(page: any, params: any): ViewController { if (!page) { @@ -26,32 +24,12 @@ export function convertToViews(pages: any[]): ViewController[] { .filter(v => v !== null); } -export function setZIndex(nav: NavControllerBase, enteringView: ViewController, leavingView: ViewController, direction: string) { - if (enteringView) { - - leavingView = leavingView || nav.getPrevious(enteringView); - - if (leavingView && isPresent(leavingView._zIndex)) { - if (direction === NavDirection.back) { - enteringView._setZIndex(leavingView._zIndex - 1); - - } else { - enteringView._setZIndex(leavingView._zIndex + 1); - } - - } else { - enteringView._setZIndex(INIT_ZINDEX); - } - } -} - export function isPresent(val: any): val is any { return val !== undefined && val !== null; } export const enum ViewState { New = 1, - Initialized, Attached, Destroyed } @@ -61,15 +39,13 @@ export const enum NavDirection { forward = 'forward' } -export const INIT_ZINDEX = 100; - export type NavParams = {[key: string]: any}; export interface NavResult { hasCompleted: boolean; requiresTransition: boolean; - enteringName?: string; - leavingName?: string; + enteringView?: ViewController; + leavingView?: ViewController; direction?: string; } @@ -96,11 +72,11 @@ export interface TransitionResolveFn { } export interface TransitionRejectFn { - (rejectReason: any, transition?: Transition): void; + (rejectReason: any, transition?: Animation): void; } export interface TransitionDoneFn { - (hasCompleted: boolean, requiresTransition: boolean, enteringName?: string, leavingName?: string, direction?: string): void; + (hasCompleted: boolean, requiresTransition: boolean, enteringView?: ViewController, leavingView?: ViewController, direction?: string): void; } export interface TransitionInstruction { diff --git a/core/src/components/nav/nav.scss b/core/src/components/nav/nav.scss index 0e9fd5351a..bf4672e39f 100644 --- a/core/src/components/nav/nav.scss +++ b/core/src/components/nav/nav.scss @@ -6,16 +6,19 @@ ion-nav { @include position(0); position: absolute; - z-index: $z-index-page-container; - overflow: hidden; + width: 100%; height: 100%; contain: layout size style; } +.hide-page { + opacity: 0; +} + .nav-decor { display: none; } diff --git a/core/src/components/nav/nav.tsx b/core/src/components/nav/nav.tsx index 9577ad06d1..29b935dd5e 100644 --- a/core/src/components/nav/nav.tsx +++ b/core/src/components/nav/nav.tsx @@ -1,6 +1,5 @@ import { Build, Component, Element, Event, EventEmitter, Method, Prop, Watch } from '@stencil/core'; import { - INIT_ZINDEX, NavDirection, NavOptions, NavParams, @@ -10,21 +9,16 @@ import { ViewState, convertToViews, isPresent, - setZIndex } from './nav-util'; import { ViewController, isViewController } from './view-controller'; -import { AnimationOptions, Config, DomController, GestureDetail, NavOutlet } from '../..'; +import { Animation, Config, DomController, GestureDetail, NavOutlet } from '../..'; import { RouteID, RouteWrite } from '../router/utils/interfaces'; import { assert } from '../../utils/helpers'; -import { TransitionController } from './transition-controller'; -import { Transition } from './transition'; - import iosTransitionAnimation from './animations/ios.transition'; import mdTransitionAnimation from './animations/md.transition'; - -const TrnsCtrl = new TransitionController(); +import { AnimationOptions, ViewLifecycle, lifecycle, transition } from './transition'; @Component({ tag: 'ion-nav', @@ -36,13 +30,12 @@ export class NavControllerBase implements NavOutlet { private _ids = -1; private _init = false; private _queue: TransitionInstruction[] = []; - private _sbTrns: Transition; + private _sbTrns: Animation; private useRouter = false; isTransitioning = false; private _destroyed = false; _views: ViewController[] = []; - _trnsId: number = null; id: string; name: string; @@ -56,7 +49,8 @@ export class NavControllerBase implements NavOutlet { @Prop({context: 'config'}) config: Config; @Prop({ connect: 'ion-animation-controller' }) animationCtrl: HTMLIonAnimationControllerElement; - @Prop({mutable: true}) swipeBackEnabled: boolean; + @Prop({ mutable: true }) swipeBackEnabled: boolean; + @Prop({ mutable: true }) animated: boolean; @Prop() rootParams: any; @Prop() root: any; @Watch('root') @@ -73,14 +67,17 @@ export class NavControllerBase implements NavOutlet { @Event() ionNavChanged: EventEmitter; componentWillLoad() { + this.id = 'n' + (++ctrlIds); this.useRouter = !!document.querySelector('ion-router') && !this.el.closest('[no-router]'); if (this.swipeBackEnabled === undefined) { - this.swipeBackEnabled = this.mode === 'ios' && this.config.getBoolean('swipeBackEnabled', true); + this.swipeBackEnabled = this.config.getBoolean('swipeBackEnabled', this.mode === 'ios'); + } + if (this.animated === undefined) { + this.animated = this.config.getBoolean('animate', true); } } componentDidLoad() { - this.id = 'n' + (++ctrlIds); this.rootChanged(); } @@ -89,7 +86,7 @@ export class NavControllerBase implements NavOutlet { } @Method() - push(page: any, params?: NavParams, opts?: NavOptions, done?: TransitionDoneFn): Promise { + push(page: any, params?: NavParams, opts?: NavOptions, done?: TransitionDoneFn): Promise { return this._queueTrns({ insertStart: -1, insertViews: [{ page: page, params: params }], @@ -98,7 +95,7 @@ export class NavControllerBase implements NavOutlet { } @Method() - insert(insertIndex: number, page: any, params?: NavParams, opts?: NavOptions, done?: TransitionDoneFn): Promise { + insert(insertIndex: number, page: any, params?: NavParams, opts?: NavOptions, done?: TransitionDoneFn): Promise { return this._queueTrns({ insertStart: insertIndex, insertViews: [{ page: page, params: params }], @@ -107,7 +104,7 @@ export class NavControllerBase implements NavOutlet { } @Method() - insertPages(insertIndex: number, insertPages: any[], opts?: NavOptions, done?: TransitionDoneFn): Promise { + insertPages(insertIndex: number, insertPages: any[], opts?: NavOptions, done?: TransitionDoneFn): Promise { return this._queueTrns({ insertStart: insertIndex, insertViews: insertPages, @@ -116,7 +113,7 @@ export class NavControllerBase implements NavOutlet { } @Method() - pop(opts?: NavOptions, done?: TransitionDoneFn): Promise { + pop(opts?: NavOptions, done?: TransitionDoneFn): Promise { return this._queueTrns({ removeStart: -1, removeCount: 1, @@ -125,7 +122,7 @@ export class NavControllerBase implements NavOutlet { } @Method() - popTo(indexOrViewCtrl: any, opts?: NavOptions, done?: TransitionDoneFn): Promise { + popTo(indexOrViewCtrl: any, opts?: NavOptions, done?: TransitionDoneFn): Promise { const config: TransitionInstruction = { removeStart: -1, removeCount: -1, @@ -141,7 +138,7 @@ export class NavControllerBase implements NavOutlet { } @Method() - popToRoot(opts?: NavOptions, done?: TransitionDoneFn): Promise { + popToRoot(opts?: NavOptions, done?: TransitionDoneFn): Promise { return this._queueTrns({ removeStart: 1, removeCount: -1, @@ -150,8 +147,8 @@ export class NavControllerBase implements NavOutlet { } @Method() - popAll(): Promise { - const promises: any[] = []; + popAll(): Promise { + const promises: Promise[] = []; for (let i = this._views.length - 1; i >= 0; i--) { promises.push(this.pop(null)); } @@ -159,7 +156,7 @@ export class NavControllerBase implements NavOutlet { } @Method() - removeIndex(startIndex: number, removeCount = 1, opts?: NavOptions, done?: TransitionDoneFn): Promise { + removeIndex(startIndex: number, removeCount = 1, opts?: NavOptions, done?: TransitionDoneFn): Promise { return this._queueTrns({ removeStart: startIndex, removeCount: removeCount, @@ -168,7 +165,7 @@ export class NavControllerBase implements NavOutlet { } @Method() - removeView(viewController: ViewController, opts?: NavOptions, done?: TransitionDoneFn): Promise { + removeView(viewController: ViewController, opts?: NavOptions, done?: TransitionDoneFn): Promise { return this._queueTrns({ removeView: viewController, removeStart: 0, @@ -178,12 +175,12 @@ export class NavControllerBase implements NavOutlet { } @Method() - setRoot(pageOrViewCtrl: any, params?: any, opts?: NavOptions, done?: TransitionDoneFn): Promise { + setRoot(pageOrViewCtrl: any, params?: any, opts?: NavOptions, done?: TransitionDoneFn): Promise { return this.setPages([{ page: pageOrViewCtrl, params: params }], opts, done); } @Method() - setPages(pages: any[], opts?: NavOptions, done?: TransitionDoneFn): Promise { + setPages(pages: any[], opts?: NavOptions, done?: TransitionDoneFn): Promise { if (!opts) { opts = {}; } @@ -264,12 +261,10 @@ export class NavControllerBase implements NavOutlet { } @Method() - canGoBack(): boolean { - const activeView = this.getActive(); - return !!(activeView && activeView.enableBack()); + canGoBack(view = this.getActive()): boolean { + return !!(view && this.getPrevious(view)); } - @Method() getActive(): ViewController { return this._views[this._views.length - 1]; @@ -281,11 +276,7 @@ export class NavControllerBase implements NavOutlet { } @Method() - getPrevious(view?: ViewController): ViewController { - // returns the view controller which is before the given view controller. - if (!view) { - view = this.getActive(); - } + getPrevious(view = this.getActive()): ViewController { const views = this._views; const index = views.indexOf(view); return (index > 0) ? views[index - 1] : null; @@ -301,12 +292,7 @@ export class NavControllerBase implements NavOutlet { */ @Method() getViewById(id: string): ViewController { - for (const vc of this._views) { - if (vc && vc.id === id) { - return vc; - } - } - return null; + return this._views.find(vc => vc.id === id); } indexOf(viewController: ViewController) { @@ -355,7 +341,6 @@ export class NavControllerBase implements NavOutlet { return; } this._init = true; - this._trnsId = null; // ensure we're not transitioning here this.isTransitioning = false; @@ -373,8 +358,8 @@ export class NavControllerBase implements NavOutlet { ti.done( result.hasCompleted, result.requiresTransition, - result.enteringName, - result.leavingName, + result.enteringView, + result.leavingView, result.direction ); } @@ -386,7 +371,6 @@ export class NavControllerBase implements NavOutlet { this._fireError('nav controller was destroyed', ti); return; } - this._trnsId = null; this._queue.length = 0; // let's see if there's another to kick off @@ -422,50 +406,36 @@ export class NavControllerBase implements NavOutlet { } // set that this nav is actively transitioning - let enteringView: ViewController; - let leavingView: ViewController; + this.isTransitioning = true; - this._startTI(ti) - .then(() => { - this._prepareViewControllers(ti); - leavingView = this.getActive(); - enteringView = this._getEnteringView(ti, leavingView); + try { + this._prepareTI(ti); + const leavingView = this.getActive(); + const enteringView = this._getEnteringView(ti, leavingView); - if (!leavingView && !enteringView) { - throw new Error('no views in the stack to be removed'); - } + if (!leavingView && !enteringView) { + throw new Error('no views in the stack to be removed'); + } - // Needs transition? - ti.requiresTransition = (ti.enteringRequiresTransition || ti.leavingRequiresTransition) && enteringView !== leavingView; + // Needs transition? + ti.requiresTransition = (ti.enteringRequiresTransition || ti.leavingRequiresTransition) && enteringView !== leavingView; - if (enteringView && enteringView._state === ViewState.New) { - this._viewInit(enteringView); - } - }) - .then(() => this._postViewInit(enteringView, leavingView, ti)) - .then(() => this._transition(enteringView, leavingView, ti)) - .then((result) => this._success(result, ti)) - .catch((rejectReason) => this._failed(rejectReason, ti)); + if (enteringView && enteringView._state === ViewState.New) { + enteringView.init(this.el); + } + this._postViewInit(enteringView, leavingView, ti); + this._transition(enteringView, leavingView, ti) + .then((result) => this._success(result, ti)) + .catch((rejectReason) => this._failed(rejectReason, ti)); + + } catch (rejectReason) { + this._failed(rejectReason, ti); + } return true; } - private _waitUntilReady(enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction) { - const promises = []; - if (enteringView) { - promises.push(isReady(enteringView.element)); - } - if (leavingView) { - promises.push(isReady(leavingView.element)); - } - const promise = Promise.all(promises); - if (ti.opts.viewIsReady) { - return promise.then(ti.opts.viewIsReady); - } - return promise; - } - - private _startTI(ti: TransitionInstruction): Promise { + private _prepareTI(ti: TransitionInstruction) { const viewsLength = this._views.length; if (isPresent(ti.removeView)) { @@ -474,7 +444,7 @@ export class NavControllerBase implements NavOutlet { const index = this._views.indexOf(ti.removeView); if (index < 0) { - return Promise.reject('removeView was not found'); + throw new Error('removeView was not found'); } ti.removeStart += index; } @@ -496,11 +466,7 @@ export class NavControllerBase implements NavOutlet { } ti.enteringRequiresTransition = (ti.insertStart === viewsLength); } - this.isTransitioning = true; - return Promise.resolve(); - } - private _prepareViewControllers(ti: TransitionInstruction) { const insertViews = ti.insertViews; if (!insertViews) { return; @@ -553,7 +519,7 @@ export class NavControllerBase implements NavOutlet { assert(ti.resolve, 'resolve must be valid'); assert(ti.reject, 'reject must be valid'); - const opts = ti.opts || {}; + const opts = ti.opts = ti.opts || {}; const insertViews = ti.insertViews; const removeStart = ti.removeStart; const removeCount = ti.removeCount; @@ -612,9 +578,9 @@ export class NavControllerBase implements NavOutlet { if (destroyQueue && destroyQueue.length > 0) { for (let i = 0; i < destroyQueue.length; i++) { const view = destroyQueue[i]; - view._willLeave(true); - view._didLeave(); - view._willUnload(); + lifecycle(view.element, ViewLifecycle.WillLeave); + lifecycle(view.element, ViewLifecycle.DidLeave); + lifecycle(view.element, ViewLifecycle.WillUnload); } // once all lifecycle events has been delivered, we can safely detroy the views @@ -629,35 +595,6 @@ export class NavControllerBase implements NavOutlet { ? (leavingView || enteringView).getTransitionName(opts.direction) : (enteringView || leavingView).getTransitionName(opts.direction); } - ti.opts = opts; - } - - /** - * DOM WRITE - */ - private _viewInit(enteringView: ViewController) { - assert(enteringView, 'enteringView must be non null'); - assert(enteringView._state === ViewState.New, 'enteringView state must be NEW'); - - enteringView._state = ViewState.Initialized; - enteringView.init(); - } - - private _viewAttachToDOM(view: ViewController) { - assert(view._state === ViewState.Initialized, 'view state must be INITIALIZED'); - - // fire willLoad before change detection runs - view._willLoad(); - - // render the component ref instance to the DOM - // ******** DOM WRITE **************** - this.el.appendChild(view.element); - - view._state = ViewState.Attached; - - // successfully finished loading the entering view - // fire off the "didLoad" lifecycle events - view._didLoad(); } private _transition(enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction): Promise { @@ -671,200 +608,70 @@ export class NavControllerBase implements NavOutlet { requiresTransition: false }); } - - const opts = ti.opts; - - // figure out if this transition is the root one or a - // child of a parent nav that has the root transition - this._trnsId = TrnsCtrl.getRootTrnsId(this); - if (this._trnsId === null) { - // this is the root transition, meaning all child navs and their views - // should be added as a child transition to this one - this._trnsId = TrnsCtrl.nextId(); + if (this._sbTrns) { + this._sbTrns.destroy(); + this._sbTrns = null; } - // create the transition options - const animationOpts: AnimationOptions = { - animation: opts.animation, - direction: opts.direction, - duration: (opts.animate === false ? 0 : opts.duration), - easing: opts.easing, - isRTL: document.dir === 'rtl', - ev: opts.ev, - enteringView: enteringView, - leavingView: leavingView, - nav: this as any, - }; - - const animation = this.mode === 'ios' ? iosTransitionAnimation : mdTransitionAnimation; - - const transition = new Transition( - this.animationCtrl, - animation, - enteringView, - leavingView, - animationOpts - ); - TrnsCtrl.register(this._trnsId, transition); - - // ensure any swipeback transitions are cleared out - this._sbTrns && this._sbTrns.destroy(); - this._sbTrns = null; - - // swipe to go back root transition - if (!transition.parent && opts.progressAnimation) { - this._sbTrns = transition; - } - - // transition start has to be registered before attaching the view to the DOM! - const promise = new Promise(resolve => transition.registerStart(resolve)) - .then(() => this._waitUntilReady(enteringView, leavingView, ti)) - .then(() => this._transitionInit(transition, enteringView, leavingView, opts)) - .then(() => this._transitionStart(transition, enteringView, leavingView, opts)); - - if (enteringView && (enteringView._state === ViewState.Initialized)) { - // render the entering component in the DOM - // this would also render new child navs/views - // which may have their very own async canEnter/Leave tests - // ******** DOM WRITE **************** - this._viewAttachToDOM(enteringView); - } - - - // if (!transition.hasChildren) { - // lowest level transition, so kick it off and let it bubble up to start all of them - transition.start(); - // } - return promise; - } - - - private _transitionInit(transition: Transition, enteringView: ViewController, leavingView: ViewController, opts: NavOptions): Promise { - assert(this.isTransitioning, 'isTransitioning has to be true'); - - this._trnsId = null; - - // set the correct zIndex for the entering and leaving views - // ******** DOM WRITE **************** - setZIndex(this, enteringView, leavingView, opts.direction); - - // always ensure the entering view is viewable - // ******** DOM WRITE **************** - enteringView && enteringView._domShow(true); - - // always ensure the leaving view is viewable - // ******** DOM WRITE **************** - leavingView && leavingView._domShow(true); - - // initialize the transition - return transition.init(); - } - - private _transitionStart(transition: Transition, enteringView: ViewController, leavingView: ViewController, opts: NavOptions): Promise { - assert(this.isTransitioning, 'isTransitioning has to be true'); - // we should animate (duration > 0) if the pushed page is not the first one (startup) // or if it is a portal (modal, actionsheet, etc.) - const shouldNotAnimate = !this._init && this._views.length === 1; - const canNotAnimate = !this.config.getBoolean('animate', true); - if (shouldNotAnimate || canNotAnimate) { - opts.animate = false; - } + const shouldAnimate = this.animated && this._init && this._views.length > 1; - if (opts.animate === false) { - // if it was somehow set to not animation, then make the duration zero - transition.ani.duration(0); - } + const animationBuilder = (shouldAnimate) + ? this.mode === 'ios' ? iosTransitionAnimation : mdTransitionAnimation + : undefined; - // create a callback that needs to run within zone - // that will fire off the willEnter/Leave lifecycle events at the right time - transition.ani.beforeAddRead(this._viewsWillLifecycles.bind(this, enteringView, leavingView)); + const progressAnimation = ti.opts.progressAnimation + ? (animation: Animation) => this._sbTrns = animation + : undefined; - // create a callback for when the animation is done - const promise = new Promise(resolve => { - transition.ani.onFinish(resolve); - }); + const opts = ti.opts; + const enteringEl = enteringView && enteringView.element; + const leavingEl = leavingView && leavingView.element; + const animationOpts: AnimationOptions = { + animationCtrl: this.animationCtrl, + animationBuilder: animationBuilder, + animation: undefined, - if (transition.ani.isRoot()) { - // cool, let's do this, start the transition - if (opts.progressAnimation) { - // this is a swipe to go back, just get the transition progress ready - // kick off the swipe animation start - transition.ani.progressStart(); + direction: opts.direction as NavDirection, + duration: opts.duration, + easing: opts.easing, + viewIsReady: opts.viewIsReady, - } else { - // only the top level transition should actually start "play" - // kick it off and let it play through - // ******** DOM WRITE **************** - transition.ani.play(); - } - } - - return promise.then(() => { - return this._transitionFinish(transition, opts); - }); + showGoBack: this.canGoBack(enteringView), + isRTL: document.dir === 'rtl', + progressAnimation, + baseEl: this.el, + enteringEl, + leavingEl + }; + return transition(animationOpts) + .then(trns => this._transitionFinish(trns, enteringView, leavingView, ti.opts)); } - private _transitionFinish(transition: Transition, opts: NavOptions): NavResult { + private _transitionFinish(transition: Animation, enteringView: ViewController, leavingView: ViewController, opts: NavOptions): NavResult { - const hasCompleted = transition.ani.hasCompleted; - const enteringView = transition.enteringView; - const leavingView = transition.leavingView; - - // mainly for testing - let enteringName: string; - let leavingName: string; + const hasCompleted = transition ? transition.hasCompleted : true; if (hasCompleted) { - // transition has completed (went from 0 to 1) - if (enteringView) { - enteringName = enteringView.name; - enteringView._didEnter(); - } - - if (leavingView) { - leavingName = leavingView.name; - leavingView._didLeave(); - } - this._cleanup(enteringView); } else { - // If transition does not complete, we have to cleanup anyway, because - // previous pages in the stack are not hidden probably. this._cleanup(leavingView); } - if (transition.ani.isRoot()) { - // this is the root transition - // it's safe to destroy this transition - TrnsCtrl.destroy(transition.trnsId); - - // it's safe to enable the app again - // mark ourselves as not transitioning - `deepLinker navchange` requires this - // TODO - probably could be resolved in a better way - this.isTransitioning = false; - } + // this is the root transition + // it's safe to destroy this transition + transition && transition.destroy(); return { hasCompleted: hasCompleted, requiresTransition: true, - enteringName: enteringName, - leavingName: leavingName, + enteringView, + leavingView, direction: opts.direction }; } - private _viewsWillLifecycles(enteringView: ViewController, leavingView: ViewController) { - if (enteringView || leavingView) { - // Here, the order is important. WillLeave must be called before WillEnter. - if (leavingView) { - const willUnload = enteringView ? leavingView.index > enteringView.index : true; - leavingView._willLeave(willUnload); - } - enteringView && enteringView._willEnter(); - } - } - private _insertViewAt(view: ViewController, index: number) { const existingIndex = this._views.indexOf(view); if (existingIndex > -1) { @@ -915,52 +722,30 @@ export class NavControllerBase implements NavOutlet { if (!this._destroyed) { const activeViewIndex = this._views.indexOf(activeView); const views = this._views; - let reorderZIndexes = false; - let view: ViewController; - let i: number; - for (i = views.length - 1; i >= 0; i--) { - view = views[i]; + for (let i = views.length - 1; i >= 0; i--) { + const view = views[i]; if (i > activeViewIndex) { // this view comes after the active view // let's unload it - view._willUnload(); + lifecycle(view.element, ViewLifecycle.WillUnload); this._destroyView(view); } else if (i < activeViewIndex) { // this view comes before the active view // and it is not a portal then ensure it is hidden - view._domShow(false); - } - if (view._zIndex <= 0) { - reorderZIndexes = true; - } - } - - if (reorderZIndexes) { - for (i = 0; i < views.length; i++) { - view = views[i]; - // ******** DOM WRITE **************** - view._setZIndex(view._zIndex + INIT_ZINDEX + 1); + view.element.hidden = true; } } } } - // registerChildNav(container: NavigationContainer) { - // this._children.push(container); - // } - - // unregisterChildNav(nav: any) { - // this._children = this._children.filter(child => child !== nav); - // } - destroy() { const views = this._views; let view: ViewController; for (let i = 0; i < views.length; i++) { view = views[i]; - view._willUnload(); + lifecycle(view.element, ViewLifecycle.WillUnload); view._destroy(); } @@ -1006,7 +791,7 @@ export class NavControllerBase implements NavOutlet { const delta = detail.deltaX; const stepValue = delta / window.innerWidth; // set the transition animation's progress - this._sbTrns.ani.progressStep(stepValue); + this._sbTrns.progressStep(stepValue); } } @@ -1029,7 +814,7 @@ export class NavControllerBase implements NavOutlet { realDur = Math.min(dur, 300); } - this._sbTrns.ani.progressEnd(shouldComplete, stepValue, realDur); + this._sbTrns.progressEnd(shouldComplete, stepValue, realDur); } } @@ -1066,14 +851,3 @@ export class NavControllerBase implements NavOutlet { } let ctrlIds = -1; - -function isReady(el: HTMLElement): Promise { - if (!el) { - return Promise.resolve(); - } - if ((el as any).componentOnReady) { - return (el as any).componentOnReady(); - } else { - return Promise.all(Array.from(el.children).map(isReady)); - } -} diff --git a/core/src/components/nav/readme.md b/core/src/components/nav/readme.md index ef7bd67d0b..91ddbea7ff 100644 --- a/core/src/components/nav/readme.md +++ b/core/src/components/nav/readme.md @@ -7,6 +7,11 @@ ## Properties +#### animated + +boolean + + #### root any @@ -24,6 +29,11 @@ boolean ## Attributes +#### animated + +boolean + + #### root any diff --git a/core/src/components/nav/test/basic/index.html b/core/src/components/nav/test/basic/index.html index 5437f4379f..bf7ad1c1de 100644 --- a/core/src/components/nav/test/basic/index.html +++ b/core/src/components/nav/test/basic/index.html @@ -9,59 +9,56 @@ class PageOne extends HTMLElement { connectedCallback() { this.innerHTML = ` - - - - Page One - - - -

Page One

- - Go to Page Two - -
+ + + Page One + + + +

Page One

+ + Go to Page Two + +
`; } } class PageTwo extends HTMLElement { connectedCallback() { this.innerHTML = ` - - - - - - - Page Two - - - -

Page Two

-
- - Go to Page Two - -
-
+ + + + + + Page Two + + + +

Page Two

+
+ + Go to Page Two + +
+
`; } } class PageThree extends HTMLElement { connectedCallback() { this.innerHTML = ` - - - - - - - Page Three - - - -

Page Three

-
+ + + + + + Page Three + + + +

Page Three

+
`; } } diff --git a/core/src/components/nav/test/nav-controller.spec.ts b/core/src/components/nav/test/nav-controller.spec.ts index 6687e1512b..cf9c3773e8 100644 --- a/core/src/components/nav/test/nav-controller.spec.ts +++ b/core/src/components/nav/test/nav-controller.spec.ts @@ -21,21 +21,23 @@ describe('NavController', () => { const pop3Done = jest.fn(); // Push 1 - await nav.push(mockView(MockView1), null, {animate: false}, push1Done); + const view1 = mockView(MockView1); + await nav.push(view1, null, {animate: false}, push1Done); const hasCompleted = true; const requiresTransition = true; expect(push1Done).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view1', undefined, NavDirection.forward + hasCompleted, requiresTransition, view1, undefined, NavDirection.forward ); expect(nav.length()).toEqual(1); expect(nav.getByIndex(0).component).toEqual(MockView1); // Push 2 - await nav.push(mockView(MockView2), null, {animate: false}, push2Done); + const view2 = mockView(MockView2); + await nav.push(view2, null, {animate: false}, push2Done); expect(push2Done).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view2', 'mock-view1', NavDirection.forward + hasCompleted, requiresTransition, view2, view1, NavDirection.forward ); expect(nav.length()).toEqual(2); @@ -43,10 +45,11 @@ describe('NavController', () => { expect(nav.getByIndex(1).component).toEqual(MockView2); // Push 3 - await nav.push(mockView(MockView3), null, {animate: false}, push3Done); + const view3 = mockView(MockView3); + await nav.push(view3, null, {animate: false}, push3Done); expect(push3Done).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view3', 'mock-view2', NavDirection.forward + hasCompleted, requiresTransition, view3, view2, NavDirection.forward ); expect(nav.length()).toEqual(3); expect(nav.getByIndex(0).component).toEqual(MockView1); @@ -54,9 +57,10 @@ describe('NavController', () => { expect(nav.getByIndex(2).component).toEqual(MockView3); // Push 4 - await nav.push(mockView(MockView4), null, {animate: false}, push4Done); + const view4 = mockView(MockView4); + await nav.push(view4, null, {animate: false}, push4Done); expect(push4Done).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view4', 'mock-view3', NavDirection.forward + hasCompleted, requiresTransition, view4, view3, NavDirection.forward ); expect(nav.length()).toEqual(4); expect(nav.getByIndex(0).component).toEqual(MockView1); @@ -67,7 +71,7 @@ describe('NavController', () => { // Pop 1 await nav.pop({animate: false}, pop1Done); expect(pop1Done).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view3', 'mock-view4', NavDirection.back + hasCompleted, requiresTransition, view3, view4, NavDirection.back ); expect(nav.length()).toEqual(3); expect(nav.getByIndex(0).component).toEqual(MockView1); @@ -77,7 +81,7 @@ describe('NavController', () => { // Pop 2 await nav.pop({animate: false}, pop2Done); expect(pop2Done).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view2', 'mock-view3', NavDirection.back + hasCompleted, requiresTransition, view2, view3, NavDirection.back ); expect(nav.length()).toEqual(2); expect(nav.getByIndex(0).component).toEqual(MockView1); @@ -86,7 +90,7 @@ describe('NavController', () => { // Pop 3 await nav.pop({animate: false}, pop3Done); expect(pop3Done).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view1', 'mock-view2', NavDirection.back + hasCompleted, requiresTransition, view1, view2, NavDirection.back ); expect(nav.length()).toEqual(1); expect(nav.getByIndex(0).component).toEqual(MockView1); @@ -98,28 +102,30 @@ describe('NavController', () => { it('should push a component as the first view', async () => { - await nav.push(mockView(MockView1), null, null, trnsDone); + const view1 = mockView(MockView1); + await nav.push(view1, null, null, trnsDone); const hasCompleted = true; const requiresTransition = true; expect(trnsDone).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view1', undefined, NavDirection.forward + hasCompleted, requiresTransition, view1, undefined, NavDirection.forward ); expect(nav.length()).toEqual(1); expect(nav.getByIndex(0).component).toEqual(MockView1); expect(nav.isTransitioning).toEqual(false); - }, 10000); it('should push a component as the second view at the end', async () => { - mockViews(nav, [mockView(MockView1)]); + const view1 = mockView(MockView1); + mockViews(nav, [view1]); - await nav.push(mockView(MockView2), null, null, trnsDone); + const view2 = mockView(MockView2); + await nav.push(view2, null, null, trnsDone); const hasCompleted = true; const requiresTransition = true; expect(trnsDone).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view2', 'mock-view1', NavDirection.forward + hasCompleted, requiresTransition, view2, view1, NavDirection.forward ); expect(nav.length()).toEqual(2); expect(nav.getByIndex(0).component).toEqual(MockView1); @@ -139,7 +145,6 @@ describe('NavController', () => { await nav.push(view2, null, null, trnsDone); - expect(instance1.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance1.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance1.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance1.ionViewDidEnter).not.toHaveBeenCalled(); @@ -148,7 +153,6 @@ describe('NavController', () => { expect(instance1.ionViewDidLeave).toHaveBeenCalled(); expect(instance1.ionViewWillUnload).not.toHaveBeenCalled(); - expect(instance2.ionViewDidLoad).toHaveBeenCalled(); // expect(instance2.ionViewCanEnter).toHaveBeenCalled(); expect(instance2.ionViewWillEnter).toHaveBeenCalled(); expect(instance2.ionViewDidEnter).toHaveBeenCalled(); @@ -160,7 +164,7 @@ describe('NavController', () => { const hasCompleted = true; const requiresTransition = true; expect(trnsDone).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view', 'mock-view', NavDirection.forward + hasCompleted, requiresTransition, view2, view1, NavDirection.forward ); expect(nav.length()).toEqual(2); @@ -188,7 +192,6 @@ describe('NavController', () => { mockViews(nav, [mockView(MockView1), mockView(MockView2), mockView(MockView3)]); await nav.insert(0, view4, null, opts, trnsDone); - expect(instance4.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance4.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance4.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance4.ionViewDidEnter).not.toHaveBeenCalled(); @@ -210,13 +213,16 @@ describe('NavController', () => { it('should insert at the end when given -1', async () => { const opts: NavOptions = {}; - mockViews(nav, [mockView(MockView1)]); + const view1 = mockView(MockView1); - await nav.insert(-1, mockView(MockView2), null, opts, trnsDone); + mockViews(nav, [view1]); + + const view2 = mockView(MockView2); + await nav.insert(-1, view2, null, opts, trnsDone); const hasCompleted = true; const requiresTransition = true; expect(trnsDone).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view2', 'mock-view1', NavDirection.forward + hasCompleted, requiresTransition, view2, view1, NavDirection.forward ); expect(nav.length()).toEqual(2); expect(nav._views[nav._views.length - 1].component).toEqual(MockView2); @@ -224,13 +230,15 @@ describe('NavController', () => { }, 10000); it('should insert at the end when given a number greater than actual length', async () => { - mockViews(nav, [mockView(MockView1)]); + const view1 = mockView(MockView1); + mockViews(nav, [view1]); - await nav.insert(9999, mockView(MockView2), null, null, trnsDone); + const view2 = mockView(MockView2); + await nav.insert(9999, view2, null, null, trnsDone); const hasCompleted = true; const requiresTransition = true; expect(trnsDone).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view2', 'mock-view1', NavDirection.forward + hasCompleted, requiresTransition, view2, view1, NavDirection.forward ); expect(nav.length()).toEqual(2); expect(nav._views[nav._views.length - 1].component).toEqual(MockView2); @@ -307,10 +315,15 @@ describe('NavController', () => { it('should insert all pages in the middle', async () => { const view4 = mockView(MockView4); const instance4 = spyOnLifecycles(view4); - mockViews(nav, [mockView(MockView1), mockView(MockView2), mockView(MockView3)]); - await nav.insertPages(1, [view4, mockView(MockView5)], null, trnsDone); - expect(instance4.ionViewDidLoad).not.toHaveBeenCalled(); + const view1 = mockView(MockView1); + const view2 = mockView(MockView2); + const view3 = mockView(MockView3); + + mockViews(nav, [view1, view2, view3]); + + const view5 = mockView(MockView5); + await nav.insertPages(1, [view4, view5], null, trnsDone); // expect(instance4.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance4.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance4.ionViewDidEnter).not.toHaveBeenCalled(); @@ -366,7 +379,6 @@ describe('NavController', () => { await nav.pop(null, trnsDone); - expect(instance1.ionViewDidLoad).toHaveBeenCalled(); // expect(instance1.ionViewCanEnter).toHaveBeenCalled(); expect(instance1.ionViewWillEnter).toHaveBeenCalled(); expect(instance1.ionViewDidEnter).toHaveBeenCalled(); @@ -375,7 +387,6 @@ describe('NavController', () => { expect(instance1.ionViewDidLeave).not.toHaveBeenCalled(); expect(instance1.ionViewWillUnload).not.toHaveBeenCalled(); - expect(instance2.ionViewDidLoad).not.toHaveBeenCalled(); // expect).not.toHaveBeenCalled(); expect(instance2.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance2.ionViewDidEnter).not.toHaveBeenCalled(); @@ -387,7 +398,7 @@ describe('NavController', () => { const hasCompleted = true; const requiresTransition = true; expect(trnsDone).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view1', 'mock-view2', NavDirection.back + hasCompleted, requiresTransition, view1, view2, NavDirection.back ); expect(nav.length()).toEqual(1); expect(nav.getByIndex(0).component).toEqual(MockView1); @@ -411,7 +422,7 @@ describe('NavController', () => { const hasCompleted = true; const requiresTransition = true; expect(trnsDone).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view2', 'mock-view3', NavDirection.back + hasCompleted, requiresTransition, view2, view3, NavDirection.back ); expect(nav.length()).toEqual(2); expect(nav.getByIndex(0).component).toEqual(MockView1); @@ -432,7 +443,7 @@ describe('NavController', () => { const hasCompleted = true; const requiresTransition = true; expect(trnsDone).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view2', 'mock-view4', NavDirection.back + hasCompleted, requiresTransition, view2, view4, NavDirection.back ); expect(nav.length()).toEqual(2); expect(nav.getByIndex(0).component).toEqual(MockView1); @@ -455,7 +466,6 @@ describe('NavController', () => { await nav.popTo(0, null, trnsDone); - expect(instance1.ionViewDidLoad).toHaveBeenCalled(); // expect(instance1.ionViewCanEnter).toHaveBeenCalled(); expect(instance1.ionViewWillEnter).toHaveBeenCalled(); expect(instance1.ionViewDidEnter).toHaveBeenCalled(); @@ -464,7 +474,6 @@ describe('NavController', () => { expect(instance1.ionViewDidLeave).not.toHaveBeenCalled(); expect(instance1.ionViewWillUnload).not.toHaveBeenCalled(); - expect(instance2.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance2.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance2.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance2.ionViewDidEnter).not.toHaveBeenCalled(); @@ -473,7 +482,6 @@ describe('NavController', () => { expect(instance2.ionViewDidLeave).toHaveBeenCalled(); expect(instance2.ionViewWillUnload).toHaveBeenCalled(); - expect(instance3.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance3.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance3.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance3.ionViewDidEnter).not.toHaveBeenCalled(); @@ -482,7 +490,6 @@ describe('NavController', () => { expect(instance3.ionViewDidLeave).toHaveBeenCalled(); expect(instance3.ionViewWillUnload).toHaveBeenCalled(); - expect(instance4.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance4.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance4.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance4.ionViewDidEnter).not.toHaveBeenCalled(); @@ -494,7 +501,7 @@ describe('NavController', () => { const hasCompleted = true; const requiresTransition = true; expect(trnsDone).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view1', 'mock-view4', NavDirection.back + hasCompleted, requiresTransition, view1, view4, NavDirection.back ); expect(nav.length()).toEqual(1); expect(nav.getByIndex(0).component).toEqual(MockView1); @@ -519,7 +526,6 @@ describe('NavController', () => { await nav.popToRoot(null, trnsDone); - expect(instance1.ionViewDidLoad).toHaveBeenCalled(); // expect(instance1.ionViewCanEnter).toHaveBeenCalled(); expect(instance1.ionViewWillEnter).toHaveBeenCalled(); expect(instance1.ionViewDidEnter).toHaveBeenCalled(); @@ -528,7 +534,6 @@ describe('NavController', () => { expect(instance1.ionViewDidLeave).not.toHaveBeenCalled(); expect(instance1.ionViewWillUnload).not.toHaveBeenCalled(); - expect(instance2.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance2.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance2.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance2.ionViewDidEnter).not.toHaveBeenCalled(); @@ -537,7 +542,6 @@ describe('NavController', () => { expect(instance2.ionViewDidLeave).toHaveBeenCalled(); expect(instance2.ionViewWillUnload).toHaveBeenCalled(); - expect(instance3.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance3.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance3.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance3.ionViewDidEnter).not.toHaveBeenCalled(); @@ -546,7 +550,6 @@ describe('NavController', () => { expect(instance3.ionViewDidLeave).toHaveBeenCalled(); expect(instance3.ionViewWillUnload).toHaveBeenCalled(); - expect(instance4.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance4.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance4.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance4.ionViewDidEnter).not.toHaveBeenCalled(); @@ -558,7 +561,7 @@ describe('NavController', () => { const hasCompleted = true; const requiresTransition = true; expect(trnsDone).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view1', 'mock-view4', NavDirection.back + hasCompleted, requiresTransition, view1, view4, NavDirection.back ); expect(nav.length()).toEqual(1); expect(nav.getByIndex(0).component).toEqual(MockView1); @@ -600,7 +603,6 @@ describe('NavController', () => { await nav.removeIndex(0, 3, null, trnsDone); - expect(instance1.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance1.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance1.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance1.ionViewDidEnter).not.toHaveBeenCalled(); @@ -609,7 +611,6 @@ describe('NavController', () => { expect(instance1.ionViewDidLeave).toHaveBeenCalled(); expect(instance1.ionViewWillUnload).toHaveBeenCalled(); - expect(instance2.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance2.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance2.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance2.ionViewDidEnter).not.toHaveBeenCalled(); @@ -618,7 +619,6 @@ describe('NavController', () => { expect(instance2.ionViewDidLeave).toHaveBeenCalled(); expect(instance2.ionViewWillUnload).toHaveBeenCalled(); - expect(instance3.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance3.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance3.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance3.ionViewDidEnter).not.toHaveBeenCalled(); @@ -627,7 +627,6 @@ describe('NavController', () => { expect(instance3.ionViewDidLeave).toHaveBeenCalled(); expect(instance3.ionViewWillUnload).toHaveBeenCalled(); - expect(instance4.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance4.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance4.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance4.ionViewDidEnter).not.toHaveBeenCalled(); @@ -663,7 +662,6 @@ describe('NavController', () => { await nav.removeIndex(2, 2, null, trnsDone); - expect(instance1.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance1.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance1.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance1.ionViewDidEnter).not.toHaveBeenCalled(); @@ -672,7 +670,6 @@ describe('NavController', () => { expect(instance1.ionViewDidLeave).not.toHaveBeenCalled(); expect(instance1.ionViewWillUnload).not.toHaveBeenCalled(); - expect(instance2.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance2.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance2.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance2.ionViewDidEnter).not.toHaveBeenCalled(); @@ -681,7 +678,6 @@ describe('NavController', () => { expect(instance2.ionViewDidLeave).not.toHaveBeenCalled(); expect(instance2.ionViewWillUnload).not.toHaveBeenCalled(); - expect(instance3.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance3.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance3.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance3.ionViewDidEnter).not.toHaveBeenCalled(); @@ -690,7 +686,6 @@ describe('NavController', () => { expect(instance3.ionViewDidLeave).toHaveBeenCalled(); expect(instance3.ionViewWillUnload).toHaveBeenCalled(); - expect(instance4.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance4.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance4.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance4.ionViewDidEnter).not.toHaveBeenCalled(); @@ -699,7 +694,6 @@ describe('NavController', () => { expect(instance4.ionViewDidLeave).toHaveBeenCalled(); expect(instance4.ionViewWillUnload).toHaveBeenCalled(); - expect(instance5.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance5.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance5.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance5.ionViewDidEnter).not.toHaveBeenCalled(); @@ -735,7 +729,6 @@ describe('NavController', () => { await nav.removeIndex(2, 2, null, trnsDone); - expect(instance1.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance1.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance1.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance1.ionViewDidEnter).not.toHaveBeenCalled(); @@ -744,7 +737,6 @@ describe('NavController', () => { expect(instance1.ionViewDidLeave).not.toHaveBeenCalled(); expect(instance1.ionViewWillUnload).not.toHaveBeenCalled(); - expect(instance2.ionViewDidLoad).toHaveBeenCalled(); // expect(instance2.ionViewCanEnter).toHaveBeenCalled(); expect(instance2.ionViewWillEnter).toHaveBeenCalled(); expect(instance2.ionViewDidEnter).toHaveBeenCalled(); @@ -753,7 +745,6 @@ describe('NavController', () => { expect(instance2.ionViewDidLeave).not.toHaveBeenCalled(); expect(instance2.ionViewWillUnload).not.toHaveBeenCalled(); - expect(instance3.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance3.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance3.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance3.ionViewDidEnter).not.toHaveBeenCalled(); @@ -762,7 +753,6 @@ describe('NavController', () => { expect(instance3.ionViewDidLeave).toHaveBeenCalled(); expect(instance3.ionViewWillUnload).toHaveBeenCalled(); - expect(instance4.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance4.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance4.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance4.ionViewDidEnter).not.toHaveBeenCalled(); @@ -774,7 +764,7 @@ describe('NavController', () => { const hasCompleted = true; const requiresTransition = true; expect(trnsDone).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view2', 'mock-view4', NavDirection.back + hasCompleted, requiresTransition, view2, view4, NavDirection.back ); expect(nav.length()).toEqual(2); expect(nav.getByIndex(0).component).toEqual(MockView1); @@ -798,7 +788,6 @@ describe('NavController', () => { const instance3 = spyOnLifecycles(view3); await nav.setRoot(view3, null, null, trnsDone); - expect(instance1.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance1.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance1.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance1.ionViewDidEnter).not.toHaveBeenCalled(); @@ -807,7 +796,6 @@ describe('NavController', () => { expect(instance1.ionViewDidLeave).toHaveBeenCalled(); expect(instance1.ionViewWillUnload).toHaveBeenCalled(); - expect(instance2.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance2.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance2.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance2.ionViewDidEnter).not.toHaveBeenCalled(); @@ -816,7 +804,6 @@ describe('NavController', () => { expect(instance2.ionViewDidLeave).toHaveBeenCalled(); expect(instance2.ionViewWillUnload).toHaveBeenCalled(); - expect(instance3.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance3.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance3.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance3.ionViewDidEnter).not.toHaveBeenCalled(); @@ -847,7 +834,6 @@ describe('NavController', () => { const instance3 = spyOnLifecycles(view3); await nav.setRoot(view2, null, null, trnsDone); - expect(instance1.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance1.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance1.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance1.ionViewDidEnter).not.toHaveBeenCalled(); @@ -856,7 +842,6 @@ describe('NavController', () => { expect(instance1.ionViewDidLeave).toHaveBeenCalled(); expect(instance1.ionViewWillUnload).toHaveBeenCalled(); - expect(instance2.ionViewDidLoad).toHaveBeenCalled(); // expect(instance2.ionViewCanEnter).toHaveBeenCalled(); expect(instance2.ionViewWillEnter).toHaveBeenCalled(); expect(instance2.ionViewDidEnter).toHaveBeenCalled(); @@ -865,7 +850,6 @@ describe('NavController', () => { expect(instance2.ionViewDidLeave).not.toHaveBeenCalled(); expect(instance2.ionViewWillUnload).not.toHaveBeenCalled(); - expect(instance3.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance3.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance3.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance3.ionViewDidEnter).not.toHaveBeenCalled(); @@ -877,7 +861,7 @@ describe('NavController', () => { const hasCompleted = true; const requiresTransition = true; expect(trnsDone).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view2', 'mock-view3', NavDirection.back + hasCompleted, requiresTransition, view2, view3, NavDirection.back ); expect(nav.length()).toEqual(1); expect(nav.getByIndex(0).component).toEqual(MockView2); @@ -895,7 +879,6 @@ describe('NavController', () => { const instance3 = spyOnLifecycles(view3); await nav.setRoot(view1, null, null, trnsDone); - expect(instance1.ionViewDidLoad).toHaveBeenCalled(); // expect(instance1.ionViewCanEnter).toHaveBeenCalled(); expect(instance1.ionViewWillEnter).toHaveBeenCalled(); expect(instance1.ionViewDidEnter).toHaveBeenCalled(); @@ -904,7 +887,6 @@ describe('NavController', () => { expect(instance1.ionViewDidLeave).not.toHaveBeenCalled(); expect(instance1.ionViewWillUnload).not.toHaveBeenCalled(); - expect(instance2.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance2.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance2.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance2.ionViewDidEnter).not.toHaveBeenCalled(); @@ -913,7 +895,6 @@ describe('NavController', () => { expect(instance2.ionViewDidLeave).toHaveBeenCalled(); expect(instance2.ionViewWillUnload).toHaveBeenCalled(); - expect(instance3.ionViewDidLoad).not.toHaveBeenCalled(); // expect(instance3.ionViewCanEnter).not.toHaveBeenCalled(); expect(instance3.ionViewWillEnter).not.toHaveBeenCalled(); expect(instance3.ionViewDidEnter).not.toHaveBeenCalled(); @@ -925,7 +906,7 @@ describe('NavController', () => { const hasCompleted = true; const requiresTransition = true; expect(trnsDone).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view1', 'mock-view3', NavDirection.back + hasCompleted, requiresTransition, view1, view3, NavDirection.back ); expect(nav.length()).toEqual(1); expect(nav.getByIndex(0).component).toEqual(MockView1); @@ -944,7 +925,8 @@ describe('NavController', () => { const instance2 = spyOnLifecycles(view2); const instance3 = spyOnLifecycles(view3); - await nav.setRoot(mockView(MockView4), null, null, trnsDone); + const view4 = mockView(MockView4); + await nav.setRoot(view4, null, null, trnsDone); expect(instance1.ionViewWillUnload).toHaveBeenCalled(); expect(instance2.ionViewWillUnload).toHaveBeenCalled(); expect(instance3.ionViewWillUnload).toHaveBeenCalled(); @@ -952,7 +934,7 @@ describe('NavController', () => { const hasCompleted = true; const requiresTransition = true; expect(trnsDone).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view4', 'mock-view3', NavDirection.back + hasCompleted, requiresTransition, view4, view3, NavDirection.back ); expect(nav.length()).toEqual(1); expect(nav.getByIndex(0).component).toEqual(MockView4); @@ -971,10 +953,13 @@ describe('NavController', () => { const instance1 = spyOnLifecycles(view1); const instance2 = spyOnLifecycles(view2); + const view4 = mockView(MockView4); + const view5 = mockView(MockView5); + await nav.setPages([{ - page: mockView(MockView4) + page: view4 }, { - page: mockView(MockView5) + page: view5 }], null, trnsDone); expect(instance1.ionViewWillUnload).toHaveBeenCalled(); expect(instance2.ionViewWillUnload).toHaveBeenCalled(); @@ -982,7 +967,7 @@ describe('NavController', () => { const hasCompleted = true; const requiresTransition = true; expect(trnsDone).toHaveBeenCalledWith( - hasCompleted, requiresTransition, 'mock-view5', 'mock-view2', NavDirection.back + hasCompleted, requiresTransition, view5, view2, NavDirection.back ); expect(nav.length()).toEqual(2); expect(nav.getByIndex(0).component).toEqual(MockView4); @@ -1038,9 +1023,6 @@ describe('NavController', () => { function spyOnLifecycles(view: ViewController) { const element = view.element as any; Object.assign(element, { - ionViewDidLoad: () => { - return; - }, ionViewWillEnter: () => { return; }, @@ -1060,7 +1042,6 @@ describe('NavController', () => { const instance = { - ionViewDidLoad: jest.spyOn(element, 'ionViewDidLoad'), ionViewWillEnter: jest.spyOn(element, 'ionViewWillEnter'), ionViewDidEnter: jest.spyOn(element, 'ionViewDidEnter'), ionViewWillLeave: jest.spyOn(element, 'ionViewWillLeave'), @@ -1068,7 +1049,6 @@ describe('NavController', () => { ionViewWillUnload: jest.spyOn(element, 'ionViewWillUnload'), }; - element.addEventListener('ionViewDidLoad', element.ionViewDidLoad); element.addEventListener('ionViewWillEnter', element.ionViewWillEnter); element.addEventListener('ionViewDidEnter', element.ionViewDidEnter); element.addEventListener('ionViewWillLeave', element.ionViewWillLeave); @@ -1093,17 +1073,23 @@ const MockView4 = 'mock-view4'; const MockView5 = 'mock-view5'; const dom = mockDocument(); + +const win = global as any; +if (!win.CustomEvent) { + win.CustomEvent = function(name: string, params: any) { + console.log('"hkljhlkhljkhljk'); + const event = dom.createEvent('CustomEvent'); + event.initCustomEvent(name, false, false, params.detail); + return event; + }; +} + function mockView(component ?: any, data ?: any) { if (!component) { component = MockView; } const view = new ViewController(component, data); - view._lifecycle = function(lifecycle: string) { - const event = dom.createEvent('CustomEvent'); - event.initCustomEvent(`ionView${lifecycle}`, false, false, null); - this.element.dispatchEvent(event); - }; view.element = mockElement(component) as HTMLElement; return view; } @@ -1128,7 +1114,7 @@ function mockNavController(): NavControllerBase { ? mockElement(enteringView.component) as HTMLElement : enteringView.element = enteringView.component as HTMLElement; } - enteringView._state = ViewState.Initialized; + enteringView._state = ViewState.Attached; }; return nav; } diff --git a/core/src/components/nav/transition-controller.ts b/core/src/components/nav/transition-controller.ts deleted file mode 100644 index 480aea5897..0000000000 --- a/core/src/components/nav/transition-controller.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { isPresent } from './nav-util'; -import { Transition } from './transition'; -import { NavControllerBase } from './nav'; - -export class TransitionController { - private _ids = 0; - private _trns = new Map(); - - getRootTrnsId(nav: NavControllerBase): number { - nav = nav.parent; - while (nav) { - if (isPresent(nav._trnsId)) { - return nav._trnsId; - } - nav = nav.parent; - } - return null; - } - - nextId() { - return this._ids++; - } - - register(trnsId: number, trns: Transition) { - trns.trnsId = trnsId; - - const parent = this._trns.get(trnsId); - if (!parent) { - // we haven't created the root transition yet - this._trns.set(trnsId, trns); - - } else { - // we already have a root transition created - // add this new transition as a child to the root - parent.parent = trns; - } - } - - destroy(trnsId: number) { - const trans = this._trns.get(trnsId); - if (trans) { - trans.destroy(); - this._trns.delete(trnsId); - } - } -} diff --git a/core/src/components/nav/transition.ts b/core/src/components/nav/transition.ts index 00501f4907..1a0c57a74e 100644 --- a/core/src/components/nav/transition.ts +++ b/core/src/components/nav/transition.ts @@ -1,57 +1,183 @@ -import { ViewController } from './view-controller'; +import { NavDirection } from './nav-util'; import { Animation, AnimationBuilder } from '../..'; -/** - * @hidden - * - * - play - * - Add before classes - DOM WRITE - * - Remove before classes - DOM WRITE - * - Add before inline styles - DOM WRITE - * - set inline FROM styles - DOM WRITE - * - RAF - * - read toolbar dimensions - DOM READ - * - write content top/bottom padding - DOM WRITE - * - set css transition duration/easing - DOM WRITE - * - RAF - * - set inline TO styles - DOM WRITE - */ -export class Transition { - _trnsStart: Function; +export function transition(opts: AnimationOptions): Promise { + const enteringEl = opts.enteringEl; + const leavingEl = opts.leavingEl; - trnsId: number; - ani: Animation; - parent: Transition; + setZIndex(enteringEl, leavingEl, opts.direction); + showPages(enteringEl, leavingEl); + showGoBack(enteringEl, opts.showGoBack); - constructor( - private animationCtrl: HTMLIonAnimationControllerElement, - private builder: AnimationBuilder, - public enteringView: ViewController, - public leavingView: ViewController, - private opts: any - ) {} - - registerStart(trnsStart: Function) { - this._trnsStart = trnsStart; + // fast path for no animation + if (!opts.animationBuilder && !opts.animation) { + return noAnimation(opts); } - init() { - return this.animationCtrl.create(this.builder, null, this.opts).then((ani) => { - this.ani = ani; + // transition path + return waitDeepReady(opts) + .then(() => fireWillEvents(enteringEl, leavingEl)) + .then(() => createTransition(opts)) + .then((transition) => playTransition(transition, opts)) + .then((transition) => { + if (transition.hasCompleted) { + fireDidEvents(enteringEl, leavingEl); + } + return transition; }); - } - - start() { - this._trnsStart && this._trnsStart(); - this._trnsStart = null; - - // bubble up start - this.parent && this.parent.start(); - } - - destroy() { - this.ani && this.ani.destroy(); - this.ani = this._trnsStart = null; - } - +} + +function notifyViewReady(viewIsReady: undefined | (() => Promise)) { + if (viewIsReady) { + return viewIsReady(); + } + return Promise.resolve(); +} + +function noAnimation(opts: AnimationOptions) { + const enteringEl = opts.enteringEl; + const leavingEl = opts.leavingEl; + + enteringEl && enteringEl.classList.remove('hide-page'); + leavingEl && leavingEl.classList.remove('hide-page'); + + return waitShallowReady(opts).then(() => { + fireWillEvents(enteringEl, leavingEl); + fireDidEvents(enteringEl, leavingEl); + return undefined; + }); +} + +function waitDeepReady(opts: AnimationOptions) { + return Promise.all([ + deepReady(opts.enteringEl), + deepReady(opts.leavingEl) + ]).then(() => notifyViewReady(opts.viewIsReady)); +} + +function waitShallowReady(opts: AnimationOptions) { + return Promise.all([ + shallowReady(opts.enteringEl), + shallowReady(opts.leavingEl) + ]).then(() => notifyViewReady(opts.viewIsReady)); +} + +function showPages(enteringEl: HTMLElement, leavingEl: HTMLElement) { + if (enteringEl) { + enteringEl.hidden = false; + } + if (leavingEl) { + leavingEl.hidden = false; + } +} + +function showGoBack(enteringEl: HTMLElement, goBack: boolean) { + if (enteringEl) { + if (goBack) { + enteringEl.classList.add('can-go-back'); + } else { + enteringEl.classList.remove('can-go-back'); + } + } +} + +function createTransition(opts: AnimationOptions) { + if (opts.animation) { + return opts.animation; + } + return opts.animationCtrl.create(opts.animationBuilder, opts.baseEl, opts); +} + +function playTransition(transition: Animation, opts: AnimationOptions): Promise { + const progressAnimation = opts.progressAnimation; + const promise = new Promise(resolve => transition.onFinish(resolve)); + + // cool, let's do this, start the transition + if (progressAnimation) { + // this is a swipe to go back, just get the transition progress ready + // kick off the swipe animation start + transition.progressStart(); + progressAnimation(transition); + + } else { + // only the top level transition should actually start "play" + // kick it off and let it play through + // ******** DOM WRITE **************** + transition.play(); + } + // create a callback for when the animation is done + return promise; +} + +function fireWillEvents(enteringEl: HTMLElement, leavingEl: HTMLElement) { + lifecycle(leavingEl, ViewLifecycle.WillLeave); + lifecycle(enteringEl, ViewLifecycle.WillEnter); +} + +function fireDidEvents(enteringEl: HTMLElement, leavingEl: HTMLElement) { + lifecycle(enteringEl, ViewLifecycle.DidEnter); + lifecycle(leavingEl, ViewLifecycle.DidLeave); +} + +function setZIndex(enteringEl: HTMLElement, leavingEl: HTMLElement, direction: NavDirection) { + if (enteringEl) { + enteringEl.style.zIndex = (direction === NavDirection.back) + ? '99' + : '101'; + } + if (leavingEl) { + leavingEl.style.zIndex = '100'; + } +} + +export function lifecycle(el: HTMLElement, lifecycle: ViewLifecycle) { + if (el) { + const event = new CustomEvent(lifecycle, { + bubbles: false, + cancelable: false + }); + el.dispatchEvent(event); + } +} + +function shallowReady(el: HTMLElement): Promise { + if (el && (el as any).componentOnReady) { + return (el as any).componentOnReady(); + } + return Promise.resolve(); +} + +function deepReady(el: HTMLElement): Promise { + if (!el) { + return Promise.resolve(); + } + if ((el as any).componentOnReady) { + return (el as any).componentOnReady(); + } else { + return Promise.all(Array.from(el.children).map(deepReady)); + } +} + +export enum ViewLifecycle { + WillEnter = 'ionViewWillEnter', + DidEnter = 'ionViewDidEnter', + WillLeave = 'ionViewWillLeave', + DidLeave = 'ionViewDidLeave', + WillUnload = 'ionViewWillUnload' +} + +export interface AnimationOptions { + animationCtrl: HTMLIonAnimationControllerElement; + animationBuilder: AnimationBuilder; + animation: Animation|undefined; + direction: NavDirection; + duration: number|undefined; + easing: string|undefined; + isRTL: boolean; + showGoBack: boolean; + viewIsReady: undefined | (() => Promise); + progressAnimation?: Function; + enteringEl: HTMLElement; + leavingEl: HTMLElement; + baseEl: HTMLElement; } diff --git a/core/src/components/nav/view-controller.ts b/core/src/components/nav/view-controller.ts index e6e3be5c02..1aeda9f87d 100644 --- a/core/src/components/nav/view-controller.ts +++ b/core/src/components/nav/view-controller.ts @@ -2,6 +2,7 @@ import { NavOptions, ViewState } from './nav-util'; import { NavControllerBase } from './nav'; import { assert } from '../../utils/helpers'; +import { FrameworkDelegate } from '../..'; /** * @name ViewController @@ -23,45 +24,48 @@ import { assert } from '../../utils/helpers'; export class ViewController { private _cntDir: any; - private _isHidden = false; private _leavingOpts: NavOptions; - private _detached: boolean; _nav: NavControllerBase; - _zIndex: number; _state: ViewState = ViewState.New; /** @hidden */ id: string; - - /** @hidden */ - isOverlay = false; - element: HTMLElement; - /** @hidden */ - // @Output() private _emitter: EventEmitter = new EventEmitter(); constructor( public component: any, - public data?: any + public data: any, + private delegate?: FrameworkDelegate, ) {} /** * @hidden */ - init() { - if (this.element) { - return; - } + init(container: HTMLElement) { + this._state = ViewState.Attached; + const component = this.component; - this.element = (typeof component === 'string') + if (this.delegate) { + return this.delegate.attachViewToDom(container, component, this.data, ['ion-page']).then(el => { + this.element = el; + }); + } + const element = (this.element) + ? this.element + : typeof component === 'string' ? document.createElement(component) : component; - this.element.classList.add('ion-page'); + element.classList.add('ion-page'); + element.classList.add('hide-page'); + if (this.data) { - Object.assign(this.element, this.data); + Object.assign(element, this.data); } + container.appendChild(element); + this.element = element; + return Promise.resolve(); } _setNav(navCtrl: NavControllerBase) { @@ -89,164 +93,6 @@ export class ViewController { this._leavingOpts = opts; } - /** - * Check to see if you can go back in the navigation stack. - * @returns {boolean} Returns if it's possible to go back from this Page. - */ - enableBack(): boolean { - // update if it's possible to go back from this nav item - if (!this._nav) { - return false; - } - // the previous view may exist, but if it's about to be destroyed - // it shouldn't be able to go back to - const previousItem = this._nav.getPrevious(this); - return !!(previousItem); - } - - /** - * @hidden - */ - get name(): string { - const component = this.component; - if (typeof component === 'string') { - return component; - } - if (component.tagName) { - return component.tagName; - } - return this.element ? this.element.tagName : 'unknown'; - } - - /** - * @hidden - * DOM WRITE - */ - _domShow(shouldShow: boolean) { - // using hidden element attribute to display:none and not render views - // doing checks to make sure we only update the DOM when actually needed - // if it should render, then the hidden attribute should not be on the element - if (this.element && shouldShow === this._isHidden) { - this._isHidden = !shouldShow; - - // ******** DOM WRITE **************** - if (shouldShow) { - this.element.removeAttribute('hidden'); - } else { - this.element.setAttribute('hidden', ''); - } - - } - } - - /** - * @hidden - */ - getZIndex(): number { - return this._zIndex; - } - - /** - * @hidden - * DOM WRITE - */ - _setZIndex(zIndex: number) { - if (zIndex !== this._zIndex) { - this._zIndex = zIndex; - const pageEl = this.element; - if (pageEl) { - const el = pageEl as HTMLElement; - // ******** DOM WRITE **************** - el.style.zIndex = zIndex + ''; - } - } - } - - - /** - * @hidden - * The view has loaded. This event only happens once per view will be created. - * This event is fired before the component and his children have been initialized. - */ - _willLoad() { - assert(this._state === ViewState.Initialized, 'view state must be INITIALIZED'); - this._lifecycle('WillLoad'); - } - - /** - * @hidden - * The view has loaded. This event only happens once per view being - * created. If a view leaves but is cached, then this will not - * fire again on a subsequent viewing. This method is a good place - * to put your setup code for the view; however, it is not the - * recommended method to use when a view becomes active. - */ - _didLoad() { - assert(this._state === ViewState.Attached, 'view state must be ATTACHED'); - this._lifecycle('DidLoad'); - } - - /** - * @hidden - * The view is about to enter and become the active view. - */ - _willEnter() { - assert(this._state === ViewState.Attached, 'view state must be ATTACHED'); - - if (this._detached) { - // ensure this has been re-attached to the change detector - // TODO - // this._cmp.changeDetectorRef.reattach(); - this._detached = false; - } - - // this.willEnter.emit(null); - this._lifecycle('WillEnter'); - } - - /** - * @hidden - * The view has fully entered and is now the active view. This - * will fire, whether it was the first load or loaded from the cache. - */ - _didEnter() { - assert(this._state === ViewState.Attached, 'view state must be ATTACHED'); - - this._lifecycle('DidEnter'); - } - - /** - * @hidden - * The view is about to leave and no longer be the active view. - */ - _willLeave(_willUnload: boolean) { - this._lifecycle('WillLeave'); - } - - /** - * @hidden - * The view has finished leaving and is no longer the active view. This - * will fire, whether it is cached or unloaded. - */ - _didLeave() { - this._lifecycle('DidLeave'); - - // when this is not the active page - // we no longer need to detect changes - if (!this._detached) { - // TODO - // this._cmp.changeDetectorRef.detach(); - this._detached = true; - } - } - - /** - * @hidden - */ - _willUnload() { - this._lifecycle('WillUnload'); - } - /** * @hidden * DOM WRITE @@ -270,39 +116,8 @@ export class ViewController { get index(): number { return (this._nav ? this._nav.indexOf(this) : -1); } - - /** - * @hidden - */ - _lifecycleTest(_lifecycle: string): Promise { - // const instance = this.instance; - // const methodName = 'ionViewCan' + lifecycle; - // if (instance && instance[methodName]) { - // try { - // const result = instance[methodName](); - // if (result instanceof Promise) { - // return result; - // } else { - // // Any value but explitic false, should be true - // return Promise.resolve(result !== false); - // } - - // } catch (e) { - // return Promise.reject(`${this.name} ${methodName} error: ${e.message}`); - // } - // } - return Promise.resolve(true); - } - - _lifecycle(lifecycle: string) { - const event = new CustomEvent(`ionView${lifecycle}`, { - bubbles: false, - cancelable: false - }); - this.element.dispatchEvent(event); - } } export function isViewController(viewCtrl: any): viewCtrl is ViewController { - return !!(viewCtrl && (viewCtrl)._didLoad && (viewCtrl)._willUnload); + return viewCtrl instanceof ViewController; } diff --git a/core/src/components/router/utils/dom.ts b/core/src/components/router/utils/dom.ts index d9188ab907..d5174a6213 100644 --- a/core/src/components/router/utils/dom.ts +++ b/core/src/components/router/utils/dom.ts @@ -11,6 +11,7 @@ export function writeNavState(root: HTMLElement, chain: RouteChain|null, index: } return node.componentOnReady() .then(() => node.setRouteId(route.id, route.params, direction)) + .catch(() => ({changed: false, markVisible: undefined})) .then(result => { if (result.changed) { direction = 0; diff --git a/core/src/index.d.ts b/core/src/index.d.ts index f7cc5ce36d..237a1da237 100644 --- a/core/src/index.d.ts +++ b/core/src/index.d.ts @@ -8,7 +8,6 @@ export { Animation, AnimationBuilder, AnimationController, - AnimationOptions } from './components/animation-controller/animation-interface'; export { App } from './components/app/app'; export { Avatar } from './components/avatar/avatar'; diff --git a/core/src/utils/dom-framework-delegate.ts b/core/src/utils/dom-framework-delegate.ts index 71274f37cf..77b1e70dc6 100644 --- a/core/src/utils/dom-framework-delegate.ts +++ b/core/src/utils/dom-framework-delegate.ts @@ -1,5 +1,5 @@ export interface FrameworkDelegate { - attachViewToDom(container: any, component: any, propsOrDataObj?: any, cssClasses?: string[]): Promise; + attachViewToDom(container: any, component: any, propsOrDataObj?: any, cssClasses?: string[]): Promise; removeViewFromDom(container: any, component: any): Promise; }