refactor(nav): transitions

This commit is contained in:
Manu Mtz.-Almeida
2018-03-20 14:12:16 +01:00
parent c85f7483c9
commit f16a9672b4
20 changed files with 489 additions and 874 deletions

View File

@ -2007,6 +2007,7 @@ declare global {
}
namespace JSXElements {
export interface IonNavAttributes extends HTMLAttributes {
animated?: boolean;
root?: any;
rootParams?: any;
swipeBackEnabled?: boolean;

View File

@ -1,4 +1,3 @@
import { ViewController } from '../..';
export interface AnimationController {
create(animationBuilder?: AnimationBuilder, baseEl?: any, opts?: any): Promise<Animation>;
@ -48,19 +47,6 @@ export interface AnimationBuilder {
(Animation: Animation, baseEl?: HTMLElement, opts?: any): Promise<Animation>;
}
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;

View File

@ -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;

View File

@ -170,11 +170,6 @@ ion-app,
contain: layout size style;
}
.hide-page {
opacity: 0;
}
// Misc
// --------------------------------------------------

View File

@ -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;
}

View File

@ -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' : '');

View File

@ -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<Animation> {
export default function iosTransitionAnimation(Animation: Animation, navEl: HTMLElement, opts: AnimationOptions): Promise<Animation> {
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);
}
}
}

View File

@ -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<Animation> {
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);

View File

@ -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 {

View File

@ -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;
}

View File

@ -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<any> {
push(page: any, params?: NavParams, opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
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<any> {
insert(insertIndex: number, page: any, params?: NavParams, opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
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<any> {
insertPages(insertIndex: number, insertPages: any[], opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
return this._queueTrns({
insertStart: insertIndex,
insertViews: insertPages,
@ -116,7 +113,7 @@ export class NavControllerBase implements NavOutlet {
}
@Method()
pop(opts?: NavOptions, done?: TransitionDoneFn): Promise<any> {
pop(opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
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<any> {
popTo(indexOrViewCtrl: any, opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
const config: TransitionInstruction = {
removeStart: -1,
removeCount: -1,
@ -141,7 +138,7 @@ export class NavControllerBase implements NavOutlet {
}
@Method()
popToRoot(opts?: NavOptions, done?: TransitionDoneFn): Promise<any> {
popToRoot(opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
return this._queueTrns({
removeStart: 1,
removeCount: -1,
@ -150,8 +147,8 @@ export class NavControllerBase implements NavOutlet {
}
@Method()
popAll(): Promise<any[]> {
const promises: any[] = [];
popAll(): Promise<boolean[]> {
const promises: Promise<boolean>[] = [];
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<any> {
removeIndex(startIndex: number, removeCount = 1, opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
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<any> {
removeView(viewController: ViewController, opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
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<any> {
setRoot(pageOrViewCtrl: any, params?: any, opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
return this.setPages([{ page: pageOrViewCtrl, params: params }], opts, done);
}
@Method()
setPages(pages: any[], opts?: NavOptions, done?: TransitionDoneFn): Promise<any> {
setPages(pages: any[], opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
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<void> {
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<NavResult> {
@ -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<void>(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<void> {
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<NavResult> {
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<any> {
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));
}
}

View File

@ -7,6 +7,11 @@
## Properties
#### animated
boolean
#### root
any
@ -24,6 +29,11 @@ boolean
## Attributes
#### animated
boolean
#### root
any

View File

@ -9,59 +9,56 @@
class PageOne extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-title>Page One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Page One</h1>
<ion-nav-push component="page-two">
<ion-button class="next">Go to Page Two</ion-button>
</ion-nav-push>
</ion-content>
<ion-header>
<ion-toolbar>
<ion-title>Page One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Page One</h1>
<ion-nav-push component="page-two">
<ion-button class="next">Go to Page Two</ion-button>
</ion-nav-push>
</ion-content>
`;
}
}
class PageTwo extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button text="Page One"></ion-back-button>
</ion-buttons>
<ion-title>Page Two</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Page Two</h1>
<div>
<ion-nav-push component="page-three">
<ion-button class="next">Go to Page Two</ion-button>
</ion-nav-push>
</div>
</ion-content>
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button text="Page One"></ion-back-button>
</ion-buttons>
<ion-title>Page Two</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Page Two</h1>
<div>
<ion-nav-push component="page-three">
<ion-button class="next">Go to Page Two</ion-button>
</ion-nav-push>
</div>
</ion-content>
`;
}
}
class PageThree extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button text="Page Two"></ion-back-button>
</ion-buttons>
<ion-title>Page Three</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Page Three</h1>
</ion-content>
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button text="Page Two"></ion-back-button>
</ion-buttons>
<ion-title>Page Three</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h1>Page Three</h1>
</ion-content>
`;
}
}

View File

@ -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;
}

View File

@ -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<number, Transition>();
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);
}
}
}

View File

@ -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<Animation|undefined> {
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<any>)) {
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<Animation> {
const progressAnimation = opts.progressAnimation;
const promise = new Promise<Animation>(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<any> {
if (el && (el as any).componentOnReady) {
return (el as any).componentOnReady();
}
return Promise.resolve();
}
function deepReady(el: HTMLElement): Promise<any> {
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<any>);
progressAnimation?: Function;
enteringEl: HTMLElement;
leavingEl: HTMLElement;
baseEl: HTMLElement;
}

View File

@ -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<any> = 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<any> {
// 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 && (<ViewController>viewCtrl)._didLoad && (<ViewController>viewCtrl)._willUnload);
return viewCtrl instanceof ViewController;
}

View File

@ -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;

1
core/src/index.d.ts vendored
View File

@ -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';

View File

@ -1,5 +1,5 @@
export interface FrameworkDelegate {
attachViewToDom(container: any, component: any, propsOrDataObj?: any, cssClasses?: string[]): Promise<any>;
attachViewToDom(container: any, component: any, propsOrDataObj?: any, cssClasses?: string[]): Promise<HTMLElement>;
removeViewFromDom(container: any, component: any): Promise<void>;
}