refactor(navigation): ion-nav and ion-nav-controller are separate components

* nav-nav-nav-nav-nav

* wipit

* holy async batman
This commit is contained in:
Dan Bucholtz
2017-08-24 13:31:42 -05:00
committed by GitHub
parent b4530b55dc
commit 2c6a4dcf34
18 changed files with 911 additions and 212 deletions

View File

@ -5,6 +5,7 @@ export interface AnimationController {
export interface Animation {
new (): Animation;
parent: Animation;
hasChildren: boolean;
addElement(elm: Node|Node[]|NodeList): Animation;
add(childAnimation: Animation): Animation;
duration(milliseconds: number): Animation;
@ -34,6 +35,9 @@ export interface Animation {
progressEnd(shouldComplete: boolean, currentStepValue: number, dur: number): void;
onFinish(callback: (animation?: Animation) => void, opts?: {oneTimeCallback?: boolean, clearExistingCallacks?: boolean}): Animation;
destroy(): void;
isRoot(): boolean;
create(): Animation;
hasCompleted: boolean;
}

View File

@ -1240,4 +1240,7 @@ export class Animator {
return (this._hasTweenEffect && this._hasDur && this._elementTotal ? this._elements[0] : null);
}
create() {
return new Animator();
}
}

View File

@ -0,0 +1,123 @@
import { Component, Element, Method, Prop } from '@stencil/core';
import { AnimationController, Config } from '../..';
import { ComponentDataPair, FrameworkDelegate, Nav, NavController, NavOptions, ViewController } from '../../navigation/nav-interfaces';
import { isReady } from '../../utils/helpers';
import {
insert as insertImpl,
insertPages as insertPagesImpl,
pop as popImpl,
popTo as popToImpl,
popToRoot as popToRootImpl,
push as pushImpl,
remove as removeImpl,
removeView as removeViewImpl,
setPages as setPagesImpl,
setRoot as setRootImpl,
} from '../../navigation/nav-controller-functions';
let defaultDelegate: FrameworkDelegate = null;
@Component({
tag: 'ion-nav-controller',
})
export class NavControllerImpl implements NavController {
@Element() element: HTMLElement;
@Prop({ connect: 'ion-animation-controller' }) animationCtrl: AnimationController;
@Prop({ context: 'config' }) config: Config;
@Prop() delegate: FrameworkDelegate;
constructor() {
}
@Method()
push(nav: Nav, component: any, data?: any, opts?: NavOptions): Promise<any> {
return getDelegate(this).then((delegate) => {
return pushImpl(nav, delegate, component, data, opts);
});
}
@Method()
pop(nav: Nav, opts?: NavOptions): Promise<any> {
return getDelegate(this).then((delegate) => {
return popImpl(nav, delegate, opts);
});
}
@Method()
setRoot(nav: Nav, component: any, data?: any, opts?: NavOptions): Promise<any> {
return getDelegate(this).then((delegate) => {
return setRootImpl(nav, delegate, component, data, opts);
});
}
@Method()
insert(nav: Nav, insertIndex: number, page: any, params?: any, opts?: NavOptions): Promise<any> {
return getDelegate(this).then((delegate) => {
return insertImpl(nav, delegate, insertIndex, page, params, opts);
});
}
@Method()
insertPages(nav: Nav, insertIndex: number, insertPages: any[], opts?: NavOptions): Promise<any> {
return getDelegate(this).then((delegate) => {
return insertPagesImpl(nav, delegate, insertIndex, insertPages, opts);
});
}
@Method()
popToRoot(nav: Nav, opts?: NavOptions): Promise<any> {
return getDelegate(this).then((delegate) => {
return popToRootImpl(nav, delegate, opts);
});
}
@Method()
popTo(nav: Nav, indexOrViewCtrl: any, opts?: NavOptions): Promise<any> {
return getDelegate(this).then((delegate) => {
return popToImpl(nav, delegate, indexOrViewCtrl, opts);
});
}
@Method()
remove(nav: Nav, startIndex: number, removeCount?: number, opts?: NavOptions): Promise<any> {
return getDelegate(this).then((delegate) => {
return removeImpl(nav, delegate, startIndex, removeCount, opts);
});
}
@Method()
removeView(nav: Nav, viewController: ViewController, opts?: NavOptions): Promise<any> {
return getDelegate(this).then((delegate) => {
return removeViewImpl(nav, delegate, viewController, opts);
});
}
@Method()
setPages(nav: Nav, componentDataPairs: ComponentDataPair[], opts? : NavOptions): Promise<any> {
return getDelegate(this).then((delegate) => {
return setPagesImpl(nav, delegate, componentDataPairs, opts);
});
}
render() {
return <slot></slot>;
}
}
export function getDelegate(navController: NavController): Promise<FrameworkDelegate> {
if (navController.delegate) {
return Promise.resolve(navController.delegate);
}
// no delegate is set, so fall back to inserting the stencil-ion-nav-delegate
const element = document.createElement('stencil-ion-nav-delegate');
document.body.appendChild(element);
return isReady(element).then(() => {
defaultDelegate = element as any as FrameworkDelegate;
return defaultDelegate;
})
}

View File

@ -0,0 +1,30 @@
import { Component, Method } from '@stencil/core';
import { StencilElement } from '../..';
import { FrameworkDelegate, Nav, ViewController } from '../../navigation/nav-interfaces';
@Component({
tag: 'stencil-ion-nav-delegate'
})
export class StencilNavDelegate implements FrameworkDelegate {
@Method()
attachViewToDom(nav: Nav, enteringView: ViewController): Promise<any> {
return new Promise((resolve) => {
const usersElement = document.createElement(enteringView.component);
const ionPage = document.createElement('ion-page');
enteringView.element = ionPage;
ionPage.appendChild(usersElement);
nav.element.appendChild(ionPage);
(ionPage as StencilElement).componentOnReady(() => {
resolve();
});
});
}
@Method()
removeViewFromDom(nav: Nav, leavingView: ViewController): Promise<any> {
nav.element.removeChild(leavingView.element);
return Promise.resolve();
}
}

View File

@ -1,16 +1,18 @@
import { Component, Element, Method, Prop } from '@stencil/core';
import { FrameworkDelegate, NavController, NavOptions, ViewController } from '../../navigation/nav-interfaces';
import { getNextNavId, getViews, pop, push, setRoot } from '../../navigation/nav-controller-functions';
import { AnimationController, Config } from '../..';
import { ComponentDataPair, FrameworkDelegate, Nav, NavController, NavOptions, ViewController } from '../../navigation/nav-interfaces';
import { delegate as defaultStencilDelegate } from '../../navigation/stencil-framework-delegate';
import { getActiveImpl, getFirstView, getPreviousImpl, getViews, init } from '../../navigation/nav-utils';
import { isReady } from '../../utils/helpers';
@Component({
tag: 'ion-nav',
})
export class Nav implements NavController {
export class IonNav implements Nav {
@Element() element: HTMLElement;
id: number;
parent: Nav;
views: ViewController[];
transitioning?: boolean;
destroyed?: boolean;
@ -18,10 +20,13 @@ export class Nav implements NavController {
isViewInitialized?: boolean;
isPortal: boolean;
swipeToGoBackTransition: any; // TODO Transition
childNavs?: NavController[];
childNavs?: Nav[];
navController?: NavController;
@Prop() root: any;
@Prop() delegate: FrameworkDelegate;
@Prop({ connect: 'ion-animation-controller' }) animationCtrl: AnimationController;
@Prop({ context: 'config' }) config: Config;
constructor() {
init(this);
@ -35,22 +40,79 @@ export class Nav implements NavController {
return getViews(this);
}
getParent(): NavController {
return null; // TODO
@Method()
push(component: any, data?: any, opts?: NavOptions) {
return pushImpl(this, component, data, opts);
}
@Method()
push(component: any, data?: any, opts: NavOptions = {}) {
return push(this, this.delegate || defaultStencilDelegate, component, data, opts);
pop(opts?: NavOptions) {
return popImpl(this, opts);
}
@Method()
pop(opts: NavOptions = {}) {
return pop(this, this.delegate || defaultStencilDelegate, opts);
setRoot(component: any, data?: any, opts?: NavOptions) {
return setRootImpl(this, component, data, opts);
}
setRoot(component: any, data?: any, opts: NavOptions = {}) {
return setRoot(this, this.delegate || defaultStencilDelegate, component, data, opts);
@Method()
insert(insertIndex: number, page: any, params?: any, opts?: NavOptions) {
return insertImpl(this, insertIndex, page, params, opts);
}
@Method()
insertPages(insertIndex: number, insertPages: any[], opts?: NavOptions) {
return insertPagesImpl(this, insertIndex, insertPages, opts);
}
@Method()
popToRoot(opts?: NavOptions) {
return popToRootImpl(this, opts);
}
@Method()
popTo(indexOrViewCtrl: any, opts?: NavOptions) {
return popToImpl(this, indexOrViewCtrl, opts);
}
@Method()
remove(startIndex: number, removeCount?: number, opts?: NavOptions) {
return removeImpl(this, startIndex, removeCount, opts);
}
@Method()
removeView(viewController: ViewController, opts?: NavOptions) {
return removeViewImpl(this, viewController, opts);
}
@Method()
setPages(componentDataPairs: ComponentDataPair[], opts? : NavOptions) {
return setPagesImpl(this, componentDataPairs, opts);
}
@Method()
getActive(): ViewController {
return getActiveImpl(this);
}
@Method()
getPrevious(view?: ViewController): ViewController {
return getPreviousImpl(this, view);
}
@Method()
canGoBack(nav: Nav) {
return nav.views && nav.views.length > 0;
}
@Method()
canSwipeBack() {
return true; // TODO, implement this for real
}
@Method()
getFirstView() {
return getFirstView(this);
}
render() {
@ -58,7 +120,70 @@ export class Nav implements NavController {
}
}
export function init(nav: NavController) {
nav.id = getNextNavId();
nav.views = [];
export function pushImpl(nav: Nav, component: any, data: any, opts: NavOptions) {
return getNavController(nav).then(() => {
return nav.navController.push(nav, component, data, opts);
});
}
export function popImpl(nav: Nav, opts: NavOptions) {
return getNavController(nav).then(() => {
return nav.navController.pop(nav, opts);
});
}
export function setRootImpl(nav: Nav, component: any, data: any, opts: NavOptions) {
return getNavController(nav).then(() => {
return nav.navController.setRoot(nav, component, data, opts);
});
}
export function insertImpl(nav: Nav, insertIndex: number, page: any, params: any, opts: NavOptions) {
return getNavController(nav).then(() => {
return nav.navController.insert(nav, insertIndex, page, params, opts);
});
}
export function insertPagesImpl(nav: Nav, insertIndex: number, insertPages: any[], opts: NavOptions) {
return getNavController(nav).then(() => {
return nav.navController.insertPages(nav, insertIndex, insertPages, opts);
});
}
export function popToRootImpl(nav: Nav, opts: NavOptions) {
return getNavController(nav).then(() => {
return nav.navController.popToRoot(nav, opts);
});
}
export function popToImpl(nav: Nav, indexOrViewCtrl: any, opts: NavOptions) {
return getNavController(nav).then(() => {
return nav.navController.popTo(nav, indexOrViewCtrl, opts);
});
}
export function removeImpl(nav: Nav, startIndex: number, removeCount: number, opts: NavOptions) {
return getNavController(nav).then(() => {
return nav.navController.remove(nav, startIndex, removeCount, opts);
});
}
export function removeViewImpl(nav: Nav, viewController: ViewController, opts?: NavOptions) {
return getNavController(nav).then(() => {
return nav.navController.removeView(nav, viewController, opts);
});
}
export function setPagesImpl(nav: Nav, componentDataPairs: ComponentDataPair[], opts? : NavOptions) {
return getNavController(nav).then(() => {
return nav.navController.setPages(nav, componentDataPairs, opts);
});
}
export function getNavController(nav: Nav): Promise<any> {
if (nav.navController) {
return Promise.resolve();
}
nav.navController = document.querySelector('ion-nav-controller') as any as NavController;
return isReady(nav.navController as any as HTMLElement);
}

View File

@ -7,6 +7,8 @@
<script src="/dist/ionic.js"></script>
</head>
<body>
<ion-nav-delegate></ion-nav-delegate>
<ion-nav-controller delegateType="stencil"></ion-nav-controller>
<ion-app>
<ion-nav root="page-one"></ion-nav>
</ion-app>

View File

@ -14,4 +14,12 @@ ion-page {
height: 100%;
contain: strict;
// do not show, but still render so we can get dimensions
opacity: 0;
}
ion-page.show-page {
// show the page now that it's ready
opacity: 1;
}

View File

@ -1,4 +1,6 @@
import { Animation, AnimationBuilder, AnimationController } from './components/animation-controller/animation-interface';
import * as Stencil from '@stencil/core';
import { Animation, AnimationBuilder, AnimationController, AnimationOptions } from './components/animation-controller/animation-interface';
import { ActionSheet, ActionSheetButton, ActionSheetEvent, ActionSheetOptions } from './components/action-sheet/action-sheet';
import { ActionSheetController } from './components/action-sheet-controller/action-sheet-controller';
import { Alert, AlertButton, AlertEvent, AlertInput, AlertOptions } from './components/alert/alert';
@ -19,12 +21,12 @@ import { PopoverController } from './components/popover-controller/popover-contr
import { Scroll, ScrollCallback, ScrollDetail } from './components/scroll/scroll';
import { Segment } from './components/segment/segment';
import { SegmentButton, SegmentButtonEvent } from './components/segment-button/segment-button';
import { Toast, ToastEvent, ToastOptions } from './components/toast/toast'
import { ToastController } from './components/toast-controller/toast-controller'
import * as Stencil from '@stencil/core';
import { TransitionBuilder } from './navigation/nav-interfaces';
export interface Config {
get: (key: string, fallback?: any) => any;
@ -67,6 +69,7 @@ export {
Animation,
AnimationBuilder,
AnimationController,
AnimationOptions,
Backdrop,
GestureCallback,
GestureDetail,
@ -91,8 +94,13 @@ export {
Segment,
SegmentButton,
SegmentButtonEvent,
TransitionBuilder,
Toast,
ToastEvent,
ToastOptions,
ToastController
}
export interface StencilElement extends HTMLElement {
componentOnReady?: (cb: (elm?: StencilElement) => void) => void;
}

View File

@ -1,10 +1,11 @@
import { AnimationOptions } from '../components/animation-controller/animation-interface';
import { Animation, AnimationOptions } from '../components/animation-controller/animation-interface';
import {
ComponentDataPair,
FrameworkDelegate,
NavController,
Nav,
NavOptions,
NavResult,
Transition,
TransitionInstruction,
ViewController
} from './nav-interfaces';
@ -14,47 +15,31 @@ import {
DIRECTION_FORWARD,
STATE_ATTACHED,
STATE_DESTROYED,
STATE_INITIALIZED,
STATE_NEW,
VIEW_ID_START,
destroyTransition,
getHydratedTransition,
getNextTransitionId,
getParentTransitionId,
isViewController,
setZIndex,
toggleHidden
toggleHidden,
transitionFactory,
} from './nav-utils';
import { ViewControllerImpl } from './view-controller-impl';
import { assert, isDef, isNumber } from '../utils/helpers';
import { NAV_ID_START, VIEW_ID_START } from '../utils/ids';
import { buildIOSTransition } from './transitions/transition.ios';
import { buildMdTransition } from './transitions/transition.md';
const queueMap = new Map<number, TransitionInstruction[]>();
// public api
export function canGoBack(nav: NavController) {
return nav.views && nav.views.length > 0;
}
export function canSwipeBack() {
return true;
// TODO - implement this for real
}
export function getFirstView(nav: NavController): ViewController {
return nav.views && nav.views.length > 0 ? nav.views[0] : null;
}
export function getActiveView(nav: NavController): ViewController {
return nav.views && nav.views.length > 0 ? nav.views[nav.views.length - 1] : null;
}
export function getActiveChildNavs(nav: NavController): NavController[] {
return nav.childNavs ? nav.childNavs : [];
}
export function getViews(nav: NavController): ViewController[] {
return nav.views ? nav.views : [];
}
export function push(nav: NavController, delegate: FrameworkDelegate, component: any, data?: any, opts?: NavOptions, done? : () => void): Promise<any> {
export function push(nav: Nav, delegate: FrameworkDelegate, component: any, data?: any, opts?: NavOptions, done? : () => void): Promise<any> {
return queueTransaction({
insertStart: -1,
insertViews: [{page: component, params: data}],
@ -65,7 +50,7 @@ export function push(nav: NavController, delegate: FrameworkDelegate, component:
}, done);
}
export function insert(nav: NavController, delegate: FrameworkDelegate, insertIndex: number, page: any, params?: any, opts?: NavOptions, done?: () => void): Promise<any> {
export function insert(nav: Nav, delegate: FrameworkDelegate, insertIndex: number, page: any, params?: any, opts?: NavOptions, done?: () => void): Promise<any> {
return queueTransaction({
insertStart: insertIndex,
insertViews: [{ page: page, params: params }],
@ -76,7 +61,7 @@ export function insert(nav: NavController, delegate: FrameworkDelegate, insertIn
}, done);
}
export function insertPages(nav: NavController, delegate: FrameworkDelegate, insertIndex: number, insertPages: any[], opts?: NavOptions, done?: () => void): Promise<any> {
export function insertPages(nav: Nav, delegate: FrameworkDelegate, insertIndex: number, insertPages: any[], opts?: NavOptions, done?: () => void): Promise<any> {
return queueTransaction({
insertStart: insertIndex,
insertViews: insertPages,
@ -87,7 +72,7 @@ export function insertPages(nav: NavController, delegate: FrameworkDelegate, ins
}, done);
}
export function pop(nav: NavController, delegate: FrameworkDelegate, opts?: NavOptions, done?: () => void): Promise<any> {
export function pop(nav: Nav, delegate: FrameworkDelegate, opts?: NavOptions, done?: () => void): Promise<any> {
return queueTransaction({
removeStart: -1,
removeCount: 1,
@ -98,7 +83,7 @@ export function pop(nav: NavController, delegate: FrameworkDelegate, opts?: NavO
}, done);
}
export function popToRoot(nav: NavController, delegate: FrameworkDelegate, opts?: NavOptions, done?: () => void): Promise<any> {
export function popToRoot(nav: Nav, delegate: FrameworkDelegate, opts?: NavOptions, done?: () => void): Promise<any> {
return queueTransaction({
removeStart: 1,
removeCount: -1,
@ -109,7 +94,7 @@ export function popToRoot(nav: NavController, delegate: FrameworkDelegate, opts?
}, done);
}
export function popTo(nav: NavController, delegate: FrameworkDelegate, indexOrViewCtrl: any, opts?: NavOptions, done?: () => void): Promise<any> {
export function popTo(nav: Nav, delegate: FrameworkDelegate, indexOrViewCtrl: any, opts?: NavOptions, done?: () => void): Promise<any> {
const config: TransitionInstruction = {
removeStart: -1,
removeCount: -1,
@ -127,7 +112,7 @@ export function popTo(nav: NavController, delegate: FrameworkDelegate, indexOrVi
return queueTransaction(config, done);
}
export function remove(nav: NavController, delegate: FrameworkDelegate, startIndex: number, removeCount: number = 1, opts?: NavOptions, done?: () => void): Promise<any> {
export function remove(nav: Nav, delegate: FrameworkDelegate, startIndex: number, removeCount: number = 1, opts?: NavOptions, done?: () => void): Promise<any> {
return queueTransaction({
removeStart: startIndex,
removeCount: removeCount,
@ -138,7 +123,7 @@ export function remove(nav: NavController, delegate: FrameworkDelegate, startInd
}, done);
}
export function removeView(nav: NavController, delegate: FrameworkDelegate, viewController: ViewController, opts?: NavOptions, done?: () => void): Promise<any> {
export function removeView(nav: Nav, delegate: FrameworkDelegate, viewController: ViewController, opts?: NavOptions, done?: () => void): Promise<any> {
return queueTransaction({
removeView: viewController,
removeStart: 0,
@ -150,11 +135,11 @@ export function removeView(nav: NavController, delegate: FrameworkDelegate, view
}, done);
}
export function setRoot(nav: NavController, delegate: FrameworkDelegate, page: any, params?: any, opts?: NavOptions, done?: () => void): Promise<any> {
export function setRoot(nav: Nav, delegate: FrameworkDelegate, page: any, params?: any, opts?: NavOptions, done?: () => void): Promise<any> {
return setPages(nav, delegate, [{ page: page, params: params }], opts, done);
}
export function setPages(nav: NavController, delegate: FrameworkDelegate, componentDataPars: ComponentDataPair[], opts? : NavOptions, done?: () => void): Promise<any> {
export function setPages(nav: Nav, delegate: FrameworkDelegate, componentDataPars: ComponentDataPair[], opts? : NavOptions, done?: () => void): Promise<any> {
if (!isDef(opts)) {
opts = {};
}
@ -207,7 +192,7 @@ export function queueTransaction(ti: TransitionInstruction, done: () => void): P
return promise;
}
export function nextTransaction(nav: NavController): Promise<any> {
export function nextTransaction(nav: Nav): Promise<any> {
if (nav.transitioning) {
return Promise.resolve();
@ -218,7 +203,13 @@ export function nextTransaction(nav: NavController): Promise<any> {
return Promise.resolve();
}
return initializeViewBeforeTransition(topTransaction).then(([enteringView, leavingView]) => {
let enteringView: ViewController;
let leavingView: ViewController;
return initializeViewBeforeTransition(topTransaction).then(([_enteringView, _leavingView]) => {
enteringView = _enteringView;
leavingView = _leavingView;
return attachViewToDom(nav, enteringView, topTransaction.delegate);
}).then(() => {
return loadViewAndTransition(nav, enteringView, leavingView, topTransaction);
}).then((result: NavResult) => {
return successfullyTransitioned(result, topTransaction);
@ -253,7 +244,6 @@ export function successfullyTransitioned(result: NavResult, ti: TransitionInstru
);
}
ti.resolve(result.hasCompleted);
console.log('success');
}
export function transitionFailed(error: Error, ti: TransitionInstruction) {
@ -274,7 +264,6 @@ export function transitionFailed(error: Error, ti: TransitionInstruction) {
nextTransaction(ti.nav);
fireError(error, ti);
console.log('fail');
}
export function fireError(error: Error, ti: TransitionInstruction) {
@ -288,7 +277,7 @@ export function fireError(error: Error, ti: TransitionInstruction) {
}
}
export function loadViewAndTransition(nav: NavController, enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction) {
export function loadViewAndTransition(nav: Nav, enteringView: ViewController, leavingView: ViewController, ti: TransitionInstruction) {
if (!ti.requiresTransition) {
// transition is not required, so we are already done!
// they're inserting/removing the views somewhere in the middle or
@ -300,8 +289,9 @@ export function loadViewAndTransition(nav: NavController, enteringView: ViewCont
});
}
// TODO - transitionId
nav.transitionId = 1; //getRootTransitionId(nav) || nextId();
let transition: Transition = null;
const transitionId = getParentTransitionId(nav);
nav.transitionId = transitionId >= 0 ? transitionId : getNextTransitionId();
// create the transition options
const animationOpts: AnimationOptions = {
@ -313,12 +303,11 @@ export function loadViewAndTransition(nav: NavController, enteringView: ViewCont
ev: ti.opts.event,
};
// TODO - need transition here
const transition: any = {
opts: animationOpts
};// new DanTransition(enteringView, leavingView, animationOpts);
//const transition = getTransition(stateData.transitionId, enteringView, animationOpts);
return nav.animationCtrl.create().then((animation: Animation) => {
const emptyTransition = transitionFactory(animation);
console.log('nav.config: ', nav.config);
console.log('mode: ', nav.config.get('mode'));
transition = getHydratedTransition(animationOpts.animation, nav.config, nav.transitionId, emptyTransition, enteringView, leavingView, animationOpts, buildMdTransition);
if (nav.swipeToGoBackTransition) {
nav.swipeToGoBackTransition.destroy();
@ -330,30 +319,17 @@ export function loadViewAndTransition(nav: NavController, enteringView: ViewCont
nav.swipeToGoBackTransition = transition;
}
// use the resolve function of this promise to trigger the
// beginTransitioning method
const promiseToReturn = new Promise<any>((resolve) => {
transition.registerStart(resolve);
});
return attachViewToDom(nav, enteringView, ti.delegate).then(() => {
if (!transition.hasChildren) {
// lowest level transition, so kick it off and let it bubble up to start all of them
transition.start();
}
return promiseToReturn;
}).then(() => {
// TODO - get the shouldAnimate param from the config
return executeAsyncTransition(nav, transition, enteringView, leavingView, ti.opts, false);
return executeAsyncTransition(nav, transition, enteringView, leavingView, ti.delegate, ti.opts, ti.nav.config.getBoolean('animate'));
});
}
// TODO - transition type
export function executeAsyncTransition(nav: NavController, transition: any, enteringView: ViewController, leavingView: ViewController, opts: NavOptions, configShouldAnimate: boolean): Promise<NavResult> {
export function executeAsyncTransition(nav: Nav, transition: Transition, enteringView: ViewController, leavingView: ViewController, delegate: FrameworkDelegate, opts: NavOptions, configShouldAnimate: boolean): Promise<NavResult> {
assert(nav.transitioning, 'must be transitioning');
nav.transitionId = null;
setZIndex(nav.isPortal, enteringView, leavingView, opts.direction);
setZIndex(nav, enteringView, leavingView, opts.direction);
// always ensure the entering view is viewable
// ******** DOM WRITE ****************
@ -364,15 +340,13 @@ export function executeAsyncTransition(nav: NavController, transition: any, ente
// ******** DOM WRITE ****************
leavingView && toggleHidden(leavingView.element, true, true);
// initialize the transition
transition.init()
const shouldNotAnimate = (!nav.isViewInitialized && nav.views.length === 1) && !nav.isPortal;
if (configShouldAnimate === false || shouldNotAnimate) {
const isFirstPage = !nav.isViewInitialized && nav.views.length === 1;
const shouldNotAnimate = isFirstPage && !nav.isPortal;
if (configShouldAnimate || shouldNotAnimate) {
opts.animate = false;
}
if (!opts.animate) {
if (opts.animate === false) {
// if it was somehow set to not animation, then make the duration zero
transition.duration(0);
}
@ -412,25 +386,27 @@ export function executeAsyncTransition(nav: NavController, transition: any, ente
}
return transitionCompletePromise.then(() => {
return transitionFinish(nav, transition, opts);
return transitionFinish(nav, transition, delegate, opts);
});
}
// TODO - transition type
export function transitionFinish(nav: NavController, transition: any, opts: NavOptions): NavResult {
export function transitionFinish(nav: Nav, transition: Transition, delegate: FrameworkDelegate, opts: NavOptions): Promise<NavResult> {
let promise: Promise<any> = null;
if (transition.hasCompleted) {
transition.enteringView && transition.enteringView.didEnter();
transition.leavingView && transition.leavingView.didLeave();
cleanUpView(nav, transition.enteringView);
promise = cleanUpView(nav, delegate, transition.enteringView);
} else {
cleanUpView(nav, transition.leavingView);
promise = cleanUpView(nav, delegate, transition.leavingView);
}
return promise.then(() => {
if (transition.isRoot()) {
// TODO - destroy the transition object
//destroy(transition.transitionId);
destroyTransition(transition.transitionId);
// TODO - enable app
@ -448,19 +424,23 @@ export function transitionFinish(nav: NavController, transition: any, opts: NavO
requiresTransition: true,
direction: opts.direction
}
});
}
export function cleanUpView(nav: NavController, activeViewController: ViewController) {
export function cleanUpView(nav: Nav, delegate: FrameworkDelegate, activeViewController: ViewController): Promise<any> {
if (nav.destroyed) {
return;
return Promise.resolve();
}
const activeIndex = nav.views.indexOf(activeViewController);
const promises: Promise<any>[] = [];
for (let i = nav.views.length - 1; i >= 0; i--) {
const inactiveViewController = nav.views[i];
if (i > activeIndex) {
// this view comes after the active view
inactiveViewController.willUnload();
destroyView(nav, inactiveViewController);
promises.push(destroyView(nav, delegate, inactiveViewController));
} else if ( i < activeIndex && !nav.isPortal) {
// this view comes before the active view
// and it is not a portal then ensure it is hidden
@ -468,6 +448,7 @@ export function cleanUpView(nav: NavController, activeViewController: ViewContro
}
// TODO - review existing z index code!
}
return Promise.all(promises);
}
@ -476,8 +457,14 @@ export function fireViewWillLifecycles(enteringView: ViewController, leavingView
enteringView && enteringView.willEnter();
}
export function attachViewToDom(nav: NavController, enteringView: ViewController, delegate: FrameworkDelegate) {
return delegate.attachViewToDom(nav, enteringView);
export function attachViewToDom(nav: Nav, enteringView: ViewController, delegate: FrameworkDelegate) {
if (enteringView && enteringView.state === STATE_NEW) {
return delegate.attachViewToDom(nav, enteringView).then(() => {
enteringView.state = STATE_ATTACHED;
});
}
// it's in the wrong state, so don't attach and just return
return Promise.resolve();
}
export function initializeViewBeforeTransition(ti: TransitionInstruction): Promise<ViewController[]> {
@ -486,7 +473,7 @@ export function initializeViewBeforeTransition(ti: TransitionInstruction): Promi
return startTransaction(ti).then(() => {
const viewControllers = convertComponentToViewController(ti);
ti.insertViews = viewControllers;
leavingView = getActiveView(ti.nav);
leavingView = ti.nav.getActive();
enteringView = getEnteringView(ti, ti.nav, leavingView);
if (!leavingView && !enteringView) {
@ -494,7 +481,7 @@ export function initializeViewBeforeTransition(ti: TransitionInstruction): Promi
}
// mark state as initialized
enteringView.state = STATE_INITIALIZED;
//enteringView.state = STATE_INITIALIZED;
ti.requiresTransition = (ti.enteringRequiresTransition || ti.leavingRequiresTransition) && enteringView !== leavingView;
return testIfViewsCanLeaveAndEnter(enteringView, leavingView, ti);
}).then(() => {
@ -570,7 +557,7 @@ export function updateNavStacks(enteringView: ViewController, leavingView: ViewC
const destroyQueuePromises: Promise<any>[] = [];
for (const viewController of destroyQueue) {
destroyQueuePromises.push(destroyView(ti.nav, viewController));
destroyQueuePromises.push(destroyView(ti.nav, ti.delegate, viewController));
}
return Promise.all(destroyQueuePromises);
}
@ -587,13 +574,13 @@ export function updateNavStacks(enteringView: ViewController, leavingView: ViewC
});
}
export function destroyView(nav: NavController, viewController: ViewController) {
return viewController.destroy().then(() => {
export function destroyView(nav: Nav, delegate: FrameworkDelegate, viewController: ViewController) {
return viewController.destroy(delegate).then(() => {
return removeViewFromList(nav, viewController);
});
}
export function removeViewFromList(nav: NavController, viewController: ViewController) {
export function removeViewFromList(nav: Nav, viewController: ViewController) {
assert(viewController.state === STATE_ATTACHED || viewController.state === STATE_DESTROYED, 'view state should be loaded or destroyed');
const index = nav.views.indexOf(viewController);
assert(index > -1, 'view must be part of the stack');
@ -602,7 +589,7 @@ export function removeViewFromList(nav: NavController, viewController: ViewContr
}
}
export function insertViewIntoNav(nav: NavController, view: ViewController, index: number) {
export function insertViewIntoNav(nav: Nav, view: ViewController, index: number) {
const existingIndex = nav.views.indexOf(view);
if (existingIndex > -1) {
// this view is already in the stack!!
@ -709,7 +696,7 @@ export function startTransaction(ti: TransitionInstruction): Promise<any> {
return Promise.resolve();
}
export function getEnteringView(ti: TransitionInstruction, nav: NavController, leavingView: ViewController): ViewController {
export function getEnteringView(ti: TransitionInstruction, nav: Nav, leavingView: ViewController): ViewController {
if (ti.insertViews && ti.insertViews.length) {
// grab the very last view of the views to be inserted
// and initialize it as the new entering view
@ -732,8 +719,7 @@ export function convertViewsToViewControllers(views: any[]): ViewController[] {
if (isViewController(view)) {
return view as ViewController;
}
// TODO - make this clean
return (new ViewControllerImpl(view.page, view.params) as any) as ViewController;
return new ViewControllerImpl(view.page, view.params);
}
return null;
}).filter(view => !!view);
@ -786,10 +772,6 @@ export function getTopTransaction(id: number) {
return toReturn;
}
export function getNextNavId() {
return navControllerIds++;
}
let navControllerIds = NAV_ID_START;
let viewIds = VIEW_ID_START;
const DISABLE_APP_MINIMUM_DURATION = 64;

View File

@ -1,21 +1,47 @@
import {
Animation,
AnimationController,
AnimationOptions,
Config
} from '..';
export interface FrameworkDelegate {
attachViewToDom(navController: NavController, enteringView: ViewController): Promise<any>;
removeViewFromDom(navController: NavController, leavingView: ViewController): Promise<any>;
destroy(viewController: ViewController): Promise<any>;
attachViewToDom(navController: Nav, enteringView: ViewController): Promise<any>;
removeViewFromDom(navController: Nav, leavingView: ViewController): Promise<any>;
}
export interface NavController {
id: number;
element: HTMLElement;
export interface Nav {
id?: number;
element?: HTMLElement;
views?: ViewController[];
transitioning?: boolean;
destroyed?: boolean;
transitionId?: number;
isViewInitialized?: boolean;
isPortal?: boolean;
zIndexOffset?: number;
swipeToGoBackTransition?: any; // TODO Transition
getParent(): NavController;
childNavs?: NavController[]; // TODO - make nav container
navController?: NavController;
parent?: Nav;
getActive(): ViewController;
getPrevious(view?: ViewController): ViewController;
childNavs?: Nav[]; // TODO - make nav container
animationCtrl?: AnimationController;
config?: Config;
}
export interface NavController {
push(nav: Nav, component: any, data: any, opts: NavOptions): Promise<any>;
pop(nav: Nav, opts: NavOptions): Promise<any>;
setRoot(nav: Nav, component: any, data: any, opts: NavOptions): Promise<any>;
insert(nav: Nav, insertIndex: number, page: any, params: any, opts: NavOptions): Promise<any>;
insertPages(nav: Nav, insertIndex: number, insertPages: any[], opts?: NavOptions): Promise<any>;
popToRoot(nav: Nav, opts: NavOptions): Promise<any>;
popTo(nav: Nav, indexOrViewCtrl: any, opts?: NavOptions): Promise<any>;
remove(nav: Nav, startIndex: number, removeCount: number, opts: NavOptions): Promise<any>;
removeView(nav: Nav, viewController: ViewController, opts?: NavOptions): Promise<any>;
setPages(nav: Nav, componentDataPairs: ComponentDataPair[], opts? : NavOptions): Promise<any>;
delegate?: FrameworkDelegate;
}
export interface ViewController {
@ -25,9 +51,9 @@ export interface ViewController {
element: HTMLElement;
instance: any;
state: number;
nav: NavController;
frameworkDelegate: FrameworkDelegate;
nav: Nav;
dismissProxy?: any;
zIndex: number;
// life cycle events
willLeave(unload: boolean): void;
@ -38,7 +64,7 @@ export interface ViewController {
didLoad(): void;
willUnload():void;
destroy(): Promise<any>;
destroy(delegate?: FrameworkDelegate): Promise<any>;
getTransitionName(direction: string): string;
onDidDismiss: (data: any, role: string) => void;
onWillDismiss: (data: any, role: string) => void;
@ -81,7 +107,7 @@ export interface TransitionInstruction {
enteringRequiresTransition?: boolean;
requiresTransition?: boolean;
id?: number;
nav?: NavController;
nav?: Nav;
delegate?: FrameworkDelegate;
}
@ -95,3 +121,17 @@ export interface ComponentDataPair {
page: any;
params: any;
}
export interface Transition extends Animation {
enteringView?: ViewController;
leavingView?: ViewController;
transitionStartFunction?: Function;
transitionId?: number;
registerTransitionStart(callback: Function): void;
start(): void;
originalDestroy(): void; // this is intended to be private, don't use this bad boy
}
export interface TransitionBuilder {
(rootTransition: Transition, enteringView: ViewController, leavingView: ViewController, opts: AnimationOptions ): Transition;
}

View File

@ -1,4 +1,6 @@
import { ViewController } from './nav-interfaces';
import { Nav, Transition, ViewController } from './nav-interfaces';
import { Animation, AnimationOptions, Config, TransitionBuilder } from '..';
import { isDef } from '../utils/helpers'
export const STATE_NEW = 1;
export const STATE_INITIALIZED = 2;
@ -6,6 +8,7 @@ export const STATE_ATTACHED = 3;
export const STATE_DESTROYED = 4;
export const INIT_ZINDEX = 100;
export const PORTAL_Z_INDEX_OFFSET = 0;
export const DIRECTION_BACK = 'back';
export const DIRECTION_FORWARD = 'forward';
@ -14,14 +17,49 @@ export const DIRECTION_SWITCH = 'switch';
export const NAV = 'nav';
export const TABS = 'tabs';
export let NAV_ID_START = 1000;
export let VIEW_ID_START = 2000;
let transitionIds = 0;
let activeTransitions = new Map<number, any>();
let portalZindex = 9999;
export function isViewController(object: any): boolean {
return !!(object && object.didLoad && object.willUnload);
}
export function setZIndex(_isPortal: boolean, _enteringView: ViewController, _leavingView: ViewController, _direction: string) {
// TODO
export function setZIndex(nav: Nav, enteringView: ViewController, leavingView: ViewController, direction: string) {
if (enteringView) {
if (nav.isPortal) {
if (direction === DIRECTION_FORWARD) {
updateZIndex(enteringView, nav.zIndexOffset + portalZindex);
}
portalZindex++;
return;
}
leavingView = leavingView || nav.getPrevious(enteringView);
if (leavingView && isDef(leavingView.zIndex)) {
if (direction === DIRECTION_BACK) {
updateZIndex(enteringView, leavingView.zIndex - 1);
} else {
updateZIndex(enteringView, leavingView.zIndex + 1);
}
} else {
updateZIndex(enteringView, INIT_ZINDEX + nav.zIndexOffset);
}
}
}
export function updateZIndex(viewController: ViewController, newZIndex: number) {
if (newZIndex !== viewController.zIndex) {
viewController.zIndex = newZIndex;
viewController.element.style.zIndex = '' + newZIndex;
}
}
export function toggleHidden(element: HTMLElement, isVisible: Boolean, shouldBeVisible: boolean) {
@ -29,3 +67,125 @@ export function toggleHidden(element: HTMLElement, isVisible: Boolean, shouldBeV
element.hidden = shouldBeVisible;
}
}
export function canNavGoBack(nav: Nav) {
if (!nav) {
return false;
}
return !!nav.getPrevious();
}
export function transitionFactory(animation: Animation): Transition {
(animation as any).registerTransitionStart = (callback: Function) => {
(animation as any).transitionStartFunction = callback;
}
(animation as any).start = function() {
this.transitionStartFunction && this.transitionStartFunction();
this.transitionStartFunction = null;
transitionStartImpl(animation as Transition);
};
(animation as any).originalDestroy = animation.destroy;
(animation as any).destroy = function() {
transitionDestroyImpl(animation as Transition);
};
return animation as Transition;
}
export function transitionStartImpl(transition: Transition) {
transition.transitionStartFunction && transition.transitionStartFunction();
transition.transitionStartFunction = null;
transition.parent && (transition.parent as Transition).start();
}
export function transitionDestroyImpl(transition: Transition) {
transition.originalDestroy();
transition.parent = transition.enteringView = transition.leavingView = transition.transitionStartFunction = null;
}
export function getParentTransitionId(nav: Nav) {
nav = nav.parent
while (nav) {
const transitionId = nav.transitionId;
if (isDef(transitionId)) {
return transitionId;
}
nav = nav.parent
}
return -1;
}
export function getNextTransitionId() {
return transitionIds++;
}
export function destroyTransition(transitionId: number) {
const transition = activeTransitions.get(transitionId);
if (transition) {
transition.destroy();
activeTransitions.delete(transitionId);
}
}
export function getHydratedTransition(name: string, config: Config, transitionId: number, emptyTransition: Transition, enteringView: ViewController, leavingView: ViewController, opts: AnimationOptions, defaultTransitionFactory: TransitionBuilder) {
const transitionFactory = config.get(name) as TransitionBuilder || defaultTransitionFactory;
const hydratedTransition = transitionFactory(emptyTransition, enteringView, leavingView, opts);
hydratedTransition.transitionId = transitionId;
if (!activeTransitions.has(transitionId)) {
// sweet, this is the root transition
activeTransitions.set(transitionId, hydratedTransition);
} else {
// we've got a parent transition going
// just append this transition to the existing one
activeTransitions.get(transitionId).add(hydratedTransition);
}
return hydratedTransition;
}
export function canGoBack(nav: Nav) {
return nav.views && nav.views.length > 0;
}
export function canSwipeBack(_nav: Nav) {
return true;
}
export function getFirstView(nav: Nav): ViewController {
return nav.views && nav.views.length > 0 ? nav.views[0] : null;
}
export function getActiveChildNavs(nav: Nav): Nav[] {
return nav.childNavs ? nav.childNavs : [];
}
export function getViews(nav: Nav): ViewController[] {
return nav.views ? nav.views : [];
}
export function init(nav: Nav) {
nav.id = getNextNavId();
nav.views = [];
}
export function getActiveImpl(nav: Nav): ViewController {
return nav.views && nav.views.length > 0 ? nav.views[nav.views.length - 1] : null;
}
export function getPreviousImpl(nav: Nav, viewController: ViewController): ViewController {
if (!viewController) {
viewController = nav.getActive();
}
return nav.views[nav.views.indexOf(viewController) - 1];
}
export function getNextNavId() {
return navControllerIds++;
}
let navControllerIds = NAV_ID_START;

View File

@ -1,32 +0,0 @@
import { NavController, ViewController } from './nav-interfaces';
export function attachViewToDom(nav: NavController, enteringView: ViewController): Promise<any> {
return new Promise((resolve) => {
const usersElement = document.createElement(enteringView.component);
const ionPage = document.createElement('ion-page');
enteringView.element = ionPage;
ionPage.appendChild(usersElement);
(ionPage as any).componentDidLoad = () => {
resolve();
};
nav.element.appendChild(ionPage);
});
}
export function removeViewFromDom(nav: NavController, leavingView: ViewController): Promise<any> {
nav.element.removeChild(leavingView.element);
return Promise.resolve();
}
export function destroy(_viewController: ViewController) {
return Promise.resolve();
}
const delegate = {
attachViewToDom: attachViewToDom,
removeViewFromDom: removeViewFromDom,
destroy: destroy
};
export { delegate };

View File

@ -0,0 +1,181 @@
import { AnimationOptions } from '../..';
import { Transition, ViewController } from '../nav-interfaces';
import { canNavGoBack } from '../nav-utils';
import { isDef } from '../../utils/helpers';
const DURATION = 500;
const EASING = 'cubic-bezier(0.36,0.66,0.04,1)';
const OPACITY = 'opacity';
const TRANSFORM = 'transform';
const TRANSLATEX = 'translateX';
const CENTER = '0%';
const OFF_OPACITY = 0.8;
const SHOW_BACK_BTN_CSS = 'show-back-button';
export function buildIOSTransition(rootTransition: Transition, enteringView: ViewController, leavingView: ViewController, opts: AnimationOptions): Transition {
rootTransition.enteringView = enteringView;
rootTransition.leavingView = leavingView;
const isRTL = document.dir === 'rtl';
const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%';
const OFF_LEFT = isRTL ? '33%' : '-33%';
rootTransition.duration(isDef(opts.duration) ? opts.duration : DURATION);
rootTransition.easing(isDef(opts.easing) ? opts.easing : EASING);
rootTransition.addElement(enteringView.element);
rootTransition.beforeAddClass('show-page');
const backDirection = (opts.direction === 'back');
if (enteringView) {
const enteringContent = rootTransition.create();
enteringContent.addElement(enteringView.element.querySelectorAll('ion-header > *:not(ion-navbar),ion-footer > *'));
rootTransition.add(enteringContent);
if (backDirection) {
enteringContent.fromTo(TRANSLATEX, OFF_LEFT, CENTER, true).fromTo(OPACITY, OFF_OPACITY, 1, true);
} else {
// entering content, forward direction
enteringContent.beforeClearStyles([OPACITY]).fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
}
const enteringNavbarEle = enteringView.element.querySelector('ion-navbar');
if (enteringNavbarEle) {
const enteringNavBar = rootTransition.create();
enteringNavBar.addElement(enteringNavbarEle);
rootTransition.add(enteringNavBar);
const enteringTitle = rootTransition.create();
enteringTitle.addElement(enteringNavbarEle.querySelector('ion-title'));
const enteringNavbarItems = rootTransition.create();
enteringNavbarItems.addElement(enteringNavbarEle.querySelectorAll('ion-buttons,[menuToggle]'));
const enteringNavbarBg = rootTransition.create();
enteringNavbarBg.addElement(enteringNavbarEle.querySelector('.toolbar-background'));
const enteringBackButton = rootTransition.create();
enteringBackButton.addElement(enteringNavbarEle.querySelector('.back-button'));
enteringNavBar
.add(enteringTitle)
.add(enteringNavbarItems)
.add(enteringNavbarBg)
.add(enteringBackButton);
enteringTitle.fromTo(OPACITY, 0.01, 1, true);
enteringNavbarItems.fromTo(OPACITY, 0.01, 1, true);
if (backDirection) {
enteringTitle.fromTo(TRANSLATEX, OFF_LEFT, CENTER, true);
if (canNavGoBack(enteringView.nav)) {
// back direction, entering page has a back button
enteringBackButton.beforeAddClass(SHOW_BACK_BTN_CSS).fromTo(OPACITY, 0.01, 1, true);
}
} else {
// entering navbar, forward direction
enteringTitle.fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
enteringNavbarBg.beforeClearStyles([OPACITY]).fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
if (canNavGoBack(enteringView.nav)) {
// forward direction, entering page has a back button
enteringBackButton.beforeAddClass(SHOW_BACK_BTN_CSS).fromTo(OPACITY, 0.01, 1, true);
const enteringBackBtnText = rootTransition.create();
enteringBackBtnText.addElement(enteringNavbarEle.querySelector('.back-button-text'));
enteringBackBtnText.fromTo(TRANSLATEX, (isRTL ? '-100px' : '100px'), '0px');
enteringNavBar.add(enteringBackBtnText);
} else {
enteringBackButton.beforeRemoveClass(SHOW_BACK_BTN_CSS);
}
}
}
}
// setup leaving view
if (leavingView) {
const leavingContent = rootTransition.create();
leavingContent.addElement(leavingView.element);
leavingContent.addElement(leavingView.element.querySelectorAll('ion-header > *:not(ion-navbar),ion-footer > *'));
rootTransition.add(leavingContent);
if (backDirection) {
// leaving content, back direction
leavingContent.beforeClearStyles([OPACITY]).fromTo(TRANSLATEX, CENTER, (isRTL ? '-100%' : '100%'));
} else {
// leaving content, forward direction
leavingContent
.fromTo(TRANSLATEX, CENTER, OFF_LEFT)
.fromTo(OPACITY, 1, OFF_OPACITY)
.afterClearStyles([TRANSFORM, OPACITY]);
}
const leavingNavbarEle = leavingView.element.querySelector('ion-navbar');
if (leavingNavbarEle) {
const leavingNavBar = rootTransition.create();
leavingNavBar.addElement(leavingNavbarEle)
const leavingTitle = rootTransition.create();
leavingTitle.addElement(leavingNavbarEle.querySelector('ion-title'));
const leavingNavbarItems = rootTransition.create();
leavingNavbarItems.addElement(leavingNavbarEle.querySelectorAll('ion-buttons,[menuToggle]'));
const leavingNavbarBg = rootTransition.create();
leavingNavbarBg.addElement(leavingNavbarEle.querySelector('.toolbar-background'));
const leavingBackButton = rootTransition.create();
leavingBackButton.addElement(leavingNavbarEle.querySelector('.back-button'));
leavingNavBar
.add(leavingTitle)
.add(leavingNavbarItems)
.add(leavingBackButton)
.add(leavingNavbarBg);
this.add(leavingNavBar);
// fade out leaving navbar items
leavingBackButton.fromTo(OPACITY, 0.99, 0);
leavingTitle.fromTo(OPACITY, 0.99, 0);
leavingNavbarItems.fromTo(OPACITY, 0.99, 0);
if (backDirection) {
// leaving navbar, back direction
leavingTitle.fromTo(TRANSLATEX, CENTER, (isRTL ? '-100%' : '100%'));
// leaving navbar, back direction, and there's no entering navbar
// should just slide out, no fading out
leavingNavbarBg
.beforeClearStyles([OPACITY])
.fromTo(TRANSLATEX, CENTER, (isRTL ? '-100%' : '100%'));
const leavingBackBtnText = rootTransition.create();
leavingBackBtnText.addElement(leavingNavbarEle.querySelector('.back-button-text'));
leavingBackBtnText.fromTo(TRANSLATEX, CENTER, (isRTL ? -300 : 300) + 'px');
leavingNavBar.add(leavingBackBtnText);
} else {
// leaving navbar, forward direction
leavingTitle
.fromTo(TRANSLATEX, CENTER, OFF_LEFT)
.afterClearStyles([TRANSFORM]);
leavingBackButton.afterClearStyles([OPACITY]);
leavingTitle.afterClearStyles([OPACITY]);
leavingNavbarItems.afterClearStyles([OPACITY]);
}
}
}
return rootTransition;
}

View File

@ -0,0 +1,60 @@
import { AnimationOptions } from '../..';
import { Transition, ViewController } from '../nav-interfaces';
import { canNavGoBack } from '../nav-utils';
import { isDef } from '../../utils/helpers';
const TRANSLATEY = 'translateY';
const OFF_BOTTOM = '40px';
const CENTER = '0px';
const SHOW_BACK_BTN_CSS = 'show-back-button';
export function buildMdTransition(rootTransition: Transition, enteringView: ViewController, leavingView: ViewController, opts: AnimationOptions): Transition {
rootTransition.enteringView = enteringView;
rootTransition.leavingView = leavingView;
rootTransition.addElement(enteringView.element);
rootTransition.beforeAddClass('show-page');
const backDirection = (opts.direction === 'back');
if (enteringView) {
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);
}
const enteringNavbarEle = enteringView.element.querySelector('ion-navbar');
if (enteringNavbarEle) {
const enteringNavBar = rootTransition.create();
enteringNavBar.addElement(enteringNavbarEle);
rootTransition.add(enteringNavBar);
const enteringBackButton = rootTransition.create();
enteringBackButton.addElement(enteringNavbarEle.querySelector('.back-button'));
rootTransition.add(enteringBackButton);
if (canNavGoBack(enteringView.nav)) {
enteringBackButton.beforeAddClass(SHOW_BACK_BTN_CSS);
} else {
enteringBackButton.beforeRemoveClass(SHOW_BACK_BTN_CSS);
}
}
}
// setup leaving view
if (leavingView && backDirection) {
// leaving content
rootTransition.duration(opts.duration || 200).easing('cubic-bezier(0.47,0,0.745,0.715)');
const leavingPage = rootTransition.create();
leavingPage.addElement(leavingView.element);
rootTransition.add(leavingPage.fromTo(TRANSLATEY, CENTER, OFF_BOTTOM).fromTo('opacity', 1, 0));
}
return rootTransition;
}

View File

@ -1,5 +1,5 @@
import { FrameworkDelegate, NavController, ViewController } from './nav-interfaces';
import { STATE_ATTACHED, STATE_DESTROYED, STATE_INITIALIZED } from './nav-utils';
import { FrameworkDelegate, Nav, ViewController } from './nav-interfaces';
import { STATE_ATTACHED, STATE_DESTROYED, STATE_NEW, STATE_INITIALIZED } from './nav-utils';
import { assert } from '../utils/helpers';
@ -10,16 +10,17 @@ export class ViewControllerImpl implements ViewController {
element: HTMLElement;
instance: any;
state: number;
nav: NavController;
nav: Nav;
overlay: boolean;
zIndex: number;
dismissProxy: any;
frameworkDelegate: FrameworkDelegate;
onDidDismiss: (data: any, role: string) => void;
onWillDismiss: (data: any, role: string) => void;
constructor(public component: any, data?: any) {
this.data = data || {};
initializeNewViewController(this, data);
}
/**
@ -62,8 +63,8 @@ export class ViewControllerImpl implements ViewController {
willUnloadImpl(this);
}
destroy(): Promise<any> {
return destroy(this);
destroy(delegate?: FrameworkDelegate): Promise<any> {
return destroy(this, delegate);
}
getTransitionName(_direction: string): string {
@ -96,21 +97,15 @@ export function dismiss(navCtrl: any, dismissProxy: any, data?: any, role?: stri
return navCtrl.removeView(this, options).then(() => data);
}
export function destroy(viewController: ViewController): Promise<any> {
return Promise.resolve().then(() => {
export function destroy(viewController: ViewController, delegate?: FrameworkDelegate): Promise<any> {
assert(viewController.state !== STATE_DESTROYED, 'view state must be attached');
return delegate ? delegate.removeViewFromDom(viewController.nav, viewController) : Promise.resolve().then(() => {
if (viewController.component) {
// TODO - consider removing classes and styles as thats what we do in ionic-angular
}
if (viewController.frameworkDelegate) {
return viewController.frameworkDelegate.destroy(viewController);
}
return null;
}).then(() => {
viewController.id = viewController.data = viewController.element = viewController.instance = viewController.nav = viewController.dismissProxy = viewController.frameworkDelegate = null;
viewController.id = viewController.data = viewController.element = viewController.instance = viewController.nav = viewController.dismissProxy = null;
viewController.state = STATE_DESTROYED;
});
}
@ -162,3 +157,8 @@ export function didLoadImpl(viewController: ViewController) {
assert(viewController.state === STATE_ATTACHED, 'view state must be ATTACHED');
callLifeCycleFunction(viewController.instance, 'ionViewDidLoad');
}
export function initializeNewViewController(viewController: ViewController, data: any) {
viewController.state = STATE_NEW;
viewController.data = data || {};
}

View File

@ -1,3 +1,5 @@
import { StencilElement } from '..';
export function isDef(v: any): boolean { return v !== undefined && v !== null; }
export function isUndef(v: any): boolean { return v === undefined || v === null; }
@ -155,10 +157,16 @@ export function swipeShouldReset(isResetDirection: boolean, isMovingFast: boolea
// 1 | 1 | 0 || 1
// 1 | 1 | 1 || 1
// The resulting expression was generated by resolving the K-map (Karnaugh map):
let shouldClose = (!isMovingFast && isOnResetZone) || (isResetDirection && isMovingFast);
return shouldClose;
return (!isMovingFast && isOnResetZone) || (isResetDirection && isMovingFast);
}
export function isReady(element: HTMLElement) {
return new Promise((resolve) => {
(element as StencilElement).componentOnReady((elm: HTMLElement) => {
resolve(elm);
});
});
/** @hidden */
export function deepCopy(obj: any) {
return JSON.parse(JSON.stringify(obj));

View File

@ -1,3 +0,0 @@
export let NAV_ID_START = 1000;
export let VIEW_ID_START = 2000;

View File

@ -29,8 +29,8 @@ exports.config = {
{ components: ['ion-spinner'] },
{ components: ['ion-tabs', 'ion-tab', 'ion-tab-bar', 'ion-tab-button', 'ion-tab-highlight'] },
{ components: ['ion-toggle'] },
{ components: ['ion-nav', 'ion-nav-controller', 'stencil-ion-nav-delegate','page-one', 'page-two', 'page-three'] },
{ components: ['ion-toast', 'ion-toast-controller'] },
{ components: ['ion-nav', 'page-one', 'page-two', 'page-three'] }
],
preamble: '(C) Ionic http://ionicframework.com - MIT License',
global: 'src/global/ionic-global.ts'