From e6068785f6371debdeea43d6d7e430491cdcbd20 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Wed, 10 Feb 2016 01:57:54 -0600 Subject: [PATCH] refactor(transitions): created Transition class Created interfaces for NavOptions, AnimationOptions and TransitionOptions Created Transition class and move transition ts files to their own folder. --- ionic/animations/animation.ts | 132 ++++++++++------- ionic/components/action-sheet/action-sheet.ts | 33 ++--- ionic/components/alert/alert.ts | 33 ++--- ionic/components/modal/modal.ts | 37 ++--- ionic/components/nav/nav-controller.ts | 136 +++++++++--------- ionic/components/nav/view-controller.ts | 6 +- ionic/ionic.ts | 4 +- .../transition-ios.ts} | 12 +- .../transition-md.ts} | 12 +- ionic/transitions/transition.ts | 41 ++++++ 10 files changed, 267 insertions(+), 179 deletions(-) rename ionic/{animations/ios-transition.ts => transitions/transition-ios.ts} (93%) rename ionic/{animations/md-transition.ts => transitions/transition-md.ts} (81%) create mode 100644 ionic/transitions/transition.ts diff --git a/ionic/animations/animation.ts b/ionic/animations/animation.ts index f09d6df4cb..07c761efd4 100644 --- a/ionic/animations/animation.ts +++ b/ionic/animations/animation.ts @@ -1,6 +1,5 @@ -import {ViewController} from '../components/nav/view-controller'; import {CSS, rafFrames, raf, transitionEnd} from '../util/dom'; -import {assign} from '../util/util'; +import {assign, isDefined} from '../util/util'; /** @@ -25,12 +24,14 @@ export class Animation { private _fOnceFns: Array; private _wChg: boolean = false; private _rv: boolean; + private _unregTrans: Function; + private _tmr; public isPlaying: boolean; public hasTween: boolean; public meta; - constructor(ele?, opts={}) { + constructor(ele?, opts: AnimationOptions = {}) { this._reset(); this.element(ele); @@ -54,6 +55,8 @@ export class Animation { this._fFns = []; this._fOnceFns = []; + this._clearAsync(); + this.isPlaying = this.hasTween = this._rv = false; this._el = this._easing = this._dur = null; } @@ -106,11 +109,11 @@ export class Animation { return this; } - from(prop: string, val: string): Animation { + from(prop: string, val): Animation { return this._addProp('from', prop, val); } - to(prop: string, val: string): Animation { + to(prop: string, val): Animation { return this._addProp('to', prop, val); } @@ -196,12 +199,12 @@ export class Animation { } } - play() { + play(opts: PlayOptions = {}) { var self = this; + var i; + var duration = isDefined(opts.duration) ? opts.duration : self._dur; - var i, fallbackTimerId, deregTransEnd; - - console.debug('Animation, play, duration', self._dur, 'easing', self._easing); + console.debug('Animation, play, duration', duration, 'easing', self._easing); // always default that an animation does not tween // a tween requires that an Animation class has an element @@ -226,7 +229,10 @@ export class Animation { // will recursively stage all child elements self._before(); - if (self._dur > 30) { + // ensure all past transition end events have been cleared + this._clearAsync(); + + if (duration > 30) { // this animation has a duration, so it should animate // place all the elements with their FROM properties @@ -235,9 +241,9 @@ export class Animation { self._willChange(true); - // set the TRANSITION END event + // set the async TRANSITION END event // and run onFinishes when the transition ends - self._asyncEnd(self._dur); + self._asyncEnd(duration); // begin each animation when everything is rendered in their place // and the transition duration/easing is ready to go @@ -245,7 +251,7 @@ export class Animation { // there's been a moment and the elements are in place // now set the TRANSITION duration/easing - self._setTrans(self._dur, false); + self._setTrans(duration, false); // wait a few moments again to wait for the transition // info to take hold in the DOM @@ -264,10 +270,43 @@ export class Animation { // just go straight to the TO properties and call it done self._progress(1); - // so there was no animation, immediately run the after + // since there was no animation, immediately run the after self._after(); - // so there was no animation, it's done + // since there was no animation, it's done + // fire off all the onFinishes + self._onFinish(); + } + } + + stop(opts: PlayOptions = {}) { + var self = this; + var duration = isDefined(opts.duration) ? opts.duration : 0; + var stepValue = isDefined(opts.stepValue) ? opts.stepValue : 1; + + // ensure all past transition end events have been cleared + this._clearAsync(); + + // set the TO properties + self._progress(stepValue); + + if (duration > 30) { + // this animation has a duration, so it should animate + // place all the elements with their TO properties + + // now set the TRANSITION duration + self._setTrans(duration, true); + + // set the async TRANSITION END event + // and run onFinishes when the transition ends + self._asyncEnd(duration); + + } else { + // this animation does not have a duration, so it should not animate + // just go straight to the TO properties and call it done + self._after(); + + // since there was no animation, it's done // fire off all the onFinishes self._onFinish(); } @@ -275,36 +314,29 @@ export class Animation { _asyncEnd(duration: number) { var self = this; - var deregTransEnd, fallbackTimerId; - // set the TRANSITION END event - deregTransEnd = transitionEnd(self._transEl(), function() { - // transition has completed - console.debug('Animation, transition end'); + function onTransitionEnd(ev) { + console.debug('Animation async end,', (ev ? 'transitionEnd event' : 'fallback timeout')); - // cancel the fallback timer so it doesn't fire also - clearTimeout(fallbackTimerId); + // ensure transition end events and timeouts have been cleared + self._clearAsync(); // set the after styles self._after(); self._willChange(false); self._onFinish(); - }); + } + + // set the TRANSITION END event on one of the transition elements + self._unregTrans = transitionEnd(self._transEl(), onTransitionEnd); // set a fallback timeout if the transition end event never fires - fallbackTimerId = setTimeout(function() { - // fallback timeout fired instead of the transition end - console.debug('Animation, fallback end'); + self._tmr = setTimeout(onTransitionEnd, duration + 300); + } - // deregister the transition end event listener - deregTransEnd(); - - // set the after styles - self._after(); - self._willChange(false); - self._onFinish(); - - }, duration + 300); + _clearAsync() { + this._unregTrans && this._unregTrans(); + clearTimeout(this._tmr); } _progress(stepValue: number) { @@ -565,7 +597,11 @@ export class Animation { return this; } - onFinish(callback: Function, onceTimeCallback: boolean = false) { + onFinish(callback: Function, onceTimeCallback: boolean = false, clearOnFinishCallacks: boolean = false) { + if (clearOnFinishCallacks) { + this._fFns = []; + this._fOnceFns = []; + } if (onceTimeCallback) { this._fOnceFns.push(callback); @@ -625,7 +661,7 @@ export class Animation { /* STATIC CLASSES */ - static create(name: string): Animation { + static create(name: string, opts: AnimationOptions = {}): Animation { let AnimationClass = AnimationRegistry[name]; if (!AnimationClass) { @@ -633,17 +669,7 @@ export class Animation { // fallback to just the base Animation class AnimationClass = Animation; } - return new AnimationClass(); - } - - static createTransition(enteringView: ViewController, leavingView: ViewController, opts: any = {}): Animation { - let TransitionClass = AnimationRegistry[opts.animation]; - if (!TransitionClass) { - // didn't find a transition animation, default to ios-transition - TransitionClass = AnimationRegistry['ios-transition']; - } - - return new TransitionClass(enteringView, leavingView, opts); + return new AnimationClass(null, opts); } static register(name: string, AnimationClass) { @@ -652,6 +678,16 @@ export class Animation { } +export interface AnimationOptions { + animation?: string; + renderDelay?: number; +} + +export interface PlayOptions { + duration?: number; + stepValue?: number; +} + const doc: any = document; const TRANSFORMS = [ 'translateX', 'translateY', 'translateZ', 'scale', 'scaleX', 'scaleY', 'scaleZ', diff --git a/ionic/components/action-sheet/action-sheet.ts b/ionic/components/action-sheet/action-sheet.ts index c6cc230aa4..42db9be437 100644 --- a/ionic/components/action-sheet/action-sheet.ts +++ b/ionic/components/action-sheet/action-sheet.ts @@ -2,6 +2,7 @@ import {Component, Renderer, ElementRef} from 'angular2/core'; import {NgFor, NgIf} from 'angular2/common'; import {Animation} from '../../animations/animation'; +import {Transition, TransitionOptions} from '../../transitions/transition'; import {Config} from '../../config/config'; import {Icon} from '../icon/icon'; import {isDefined} from '../../util/util'; @@ -272,9 +273,9 @@ class ActionSheetCmp { -class ActionSheetSlideIn extends Animation { - constructor(enteringView, leavingView, opts) { - super(null, opts); +class ActionSheetSlideIn extends Transition { + constructor(enteringView, leavingView, opts: TransitionOptions) { + super(opts); let ele = enteringView.pageRef().nativeElement; let backdrop = new Animation(ele.querySelector('.backdrop')); @@ -286,12 +287,12 @@ class ActionSheetSlideIn extends Animation { this.easing('cubic-bezier(.36,.66,.04,1)').duration(400).add(backdrop).add(wrapper); } } -Animation.register('action-sheet-slide-in', ActionSheetSlideIn); +Transition.register('action-sheet-slide-in', ActionSheetSlideIn); -class ActionSheetSlideOut extends Animation { - constructor(enteringView, leavingView, opts) { - super(null, opts); +class ActionSheetSlideOut extends Transition { + constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { + super(opts); let ele = leavingView.pageRef().nativeElement; let backdrop = new Animation(ele.querySelector('.backdrop')); @@ -303,12 +304,12 @@ class ActionSheetSlideOut extends Animation { this.easing('cubic-bezier(.36,.66,.04,1)').duration(300).add(backdrop).add(wrapper); } } -Animation.register('action-sheet-slide-out', ActionSheetSlideOut); +Transition.register('action-sheet-slide-out', ActionSheetSlideOut); -class ActionSheetMdSlideIn extends Animation { - constructor(enteringView, leavingView, opts) { - super(null, opts); +class ActionSheetMdSlideIn extends Transition { + constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { + super(opts); let ele = enteringView.pageRef().nativeElement; let backdrop = new Animation(ele.querySelector('.backdrop')); @@ -320,12 +321,12 @@ class ActionSheetMdSlideIn extends Animation { this.easing('cubic-bezier(.36,.66,.04,1)').duration(450).add(backdrop).add(wrapper); } } -Animation.register('action-sheet-md-slide-in', ActionSheetMdSlideIn); +Transition.register('action-sheet-md-slide-in', ActionSheetMdSlideIn); -class ActionSheetMdSlideOut extends Animation { - constructor(enteringView, leavingView, opts) { - super(null, opts); +class ActionSheetMdSlideOut extends Transition { + constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { + super(opts); let ele = leavingView.pageRef().nativeElement; let backdrop = new Animation(ele.querySelector('.backdrop')); @@ -337,4 +338,4 @@ class ActionSheetMdSlideOut extends Animation { this.easing('cubic-bezier(.36,.66,.04,1)').duration(450).add(backdrop).add(wrapper); } } -Animation.register('action-sheet-md-slide-out', ActionSheetMdSlideOut); +Transition.register('action-sheet-md-slide-out', ActionSheetMdSlideOut); diff --git a/ionic/components/alert/alert.ts b/ionic/components/alert/alert.ts index 3874a8e823..36cd2d367b 100644 --- a/ionic/components/alert/alert.ts +++ b/ionic/components/alert/alert.ts @@ -2,6 +2,7 @@ import {Component, ElementRef, Renderer} from 'angular2/core'; import {NgClass, NgSwitch, NgIf, NgFor} from 'angular2/common'; import {Animation} from '../../animations/animation'; +import {Transition, TransitionOptions} from '../../transitions/transition'; import {Config} from '../../config/config'; import {isDefined} from '../../util/util'; import {NavParams} from '../nav/nav-params'; @@ -484,9 +485,9 @@ class AlertCmp { /** * Animations for alerts */ -class AlertPopIn extends Animation { - constructor(enteringView, leavingView, opts) { - super(null, opts); +class AlertPopIn extends Transition { + constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { + super(opts); let ele = enteringView.pageRef().nativeElement; let backdrop = new Animation(ele.querySelector('.backdrop')); @@ -502,12 +503,12 @@ class AlertPopIn extends Animation { .add(wrapper); } } -Animation.register('alert-pop-in', AlertPopIn); +Transition.register('alert-pop-in', AlertPopIn); -class AlertPopOut extends Animation { - constructor(enteringView, leavingView, opts) { - super(null, opts); +class AlertPopOut extends Transition { + constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { + super(opts); let ele = leavingView.pageRef().nativeElement; let backdrop = new Animation(ele.querySelector('.backdrop')); @@ -523,12 +524,12 @@ class AlertPopOut extends Animation { .add(wrapper); } } -Animation.register('alert-pop-out', AlertPopOut); +Transition.register('alert-pop-out', AlertPopOut); -class AlertMdPopIn extends Animation { - constructor(enteringView, leavingView, opts) { - super(null, opts); +class AlertMdPopIn extends Transition { + constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { + super(opts); let ele = enteringView.pageRef().nativeElement; let backdrop = new Animation(ele.querySelector('.backdrop')); @@ -544,12 +545,12 @@ class AlertMdPopIn extends Animation { .add(wrapper); } } -Animation.register('alert-md-pop-in', AlertMdPopIn); +Transition.register('alert-md-pop-in', AlertMdPopIn); -class AlertMdPopOut extends Animation { - constructor(enteringView, leavingView, opts) { - super(null, opts); +class AlertMdPopOut extends Transition { + constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { + super(opts); let ele = leavingView.pageRef().nativeElement; let backdrop = new Animation(ele.querySelector('.backdrop')); @@ -565,6 +566,6 @@ class AlertMdPopOut extends Animation { .add(wrapper); } } -Animation.register('alert-md-pop-out', AlertMdPopOut); +Transition.register('alert-md-pop-out', AlertMdPopOut); let alertIds = -1; diff --git a/ionic/components/modal/modal.ts b/ionic/components/modal/modal.ts index 623540823f..57aa548aff 100644 --- a/ionic/components/modal/modal.ts +++ b/ionic/components/modal/modal.ts @@ -1,5 +1,6 @@ import {ViewController} from '../nav/view-controller'; import {Animation} from '../../animations/animation'; +import {Transition, TransitionOptions} from '../../transitions/transition'; /** * @name Modal @@ -129,10 +130,11 @@ export class Modal extends ViewController { /** * Animations for modals */ -class ModalSlideIn extends Animation { - constructor(enteringView, leavingView, opts) { - super(enteringView.pageRef(), opts); +class ModalSlideIn extends Transition { + constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { + super(opts); this + .element(enteringView.pageRef()) .easing('cubic-bezier(0.36,0.66,0.04,1)') .duration(400) .fromTo('translateY', '100%', '0%') @@ -146,25 +148,27 @@ class ModalSlideIn extends Animation { } } } -Animation.register('modal-slide-in', ModalSlideIn); +Transition.register('modal-slide-in', ModalSlideIn); -class ModalSlideOut extends Animation { - constructor(enteringView, leavingView, opts) { - super(leavingView.pageRef(), opts); +class ModalSlideOut extends Transition { + constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { + super(opts); this + .element(leavingView.pageRef()) .easing('ease-out') .duration(250) .fromTo('translateY', '0%', '100%'); } } -Animation.register('modal-slide-out', ModalSlideOut); +Transition.register('modal-slide-out', ModalSlideOut); -class ModalMDSlideIn extends Animation { - constructor(enteringView, leavingView, opts) { - super(enteringView.pageRef(), opts); +class ModalMDSlideIn extends Transition { + constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { + super(opts); this + .element(enteringView.pageRef()) .easing('cubic-bezier(0.36,0.66,0.04,1)') .duration(280) .fromTo('translateY', '40px', '0px') @@ -179,17 +183,18 @@ class ModalMDSlideIn extends Animation { } } } -Animation.register('modal-md-slide-in', ModalMDSlideIn); +Transition.register('modal-md-slide-in', ModalMDSlideIn); -class ModalMDSlideOut extends Animation { - constructor(enteringView, leavingView, opts) { - super(leavingView.pageRef(), opts); +class ModalMDSlideOut extends Transition { + constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { + super(opts); this + .element(leavingView.pageRef()) .duration(200) .easing('cubic-bezier(0.47,0,0.745,0.715)') .fromTo('translateY', '0px', '40px') .fadeOut(); } } -Animation.register('modal-md-slide-out', ModalMDSlideOut); +Transition.register('modal-md-slide-out', ModalMDSlideOut); diff --git a/ionic/components/nav/nav-controller.ts b/ionic/components/nav/nav-controller.ts index 5ca801f04f..d2e96c6e9b 100644 --- a/ionic/components/nav/nav-controller.ts +++ b/ionic/components/nav/nav-controller.ts @@ -1,16 +1,16 @@ import {Compiler, ElementRef, Injector, provide, NgZone, AppViewManager, Renderer, ResolvedProvider, Type} from 'angular2/core'; import {wtfLeave, wtfCreateScope, WtfScopeFn, wtfStartTimeRange, wtfEndTimeRange} from 'angular2/instrumentation'; -import {Animation} from '../../animations/animation'; import {Config} from '../../config/config'; import {Ion} from '../ion'; import {IonicApp} from '../app/app'; -import {isBoolean, array, pascalCaseToDashCase} from '../../util/util'; import {Keyboard} from '../../util/keyboard'; import {NavParams} from './nav-params'; import {NavRouter} from './nav-router'; -import {raf, rafFrames} from '../../util/dom'; +import {pascalCaseToDashCase} from '../../util/util'; +import {raf} from '../../util/dom'; import {SwipeBackGesture} from './swipe-back'; +import {Transition} from '../../transitions/transition'; import {ViewController} from './view-controller'; /** @@ -106,11 +106,11 @@ import {ViewController} from './view-controller'; export class NavController extends Ion { private _transIds = 0; private _init = false; - private _lastTrans; + private _lastTrans: Transition; protected _ids: number = -1; protected _sbEnabled: any; protected _sbThreshold: any; - protected _sbTrans: any = null; + protected _sbTrans: Transition = null; protected _trnsDelay: any; protected _trnsTime: number = 0; protected _views: Array = []; @@ -182,7 +182,7 @@ export class NavController extends Ion { * @param {object} [opts={}] Any options you want to use pass to transtion * @returns {Promise} Returns a promise when done */ - setRoot(page: Type, params: any = {}, opts: any = {}): Promise { + setRoot(page: Type, params: any = {}, opts: NavOptions = {}): Promise { return this.setPages([{page, params}], opts); } @@ -259,7 +259,7 @@ export class NavController extends Ion { * @param {object} [opts={}] Any options you want to use pass * @returns {Promise} Returns a promise when the pages are set */ - setPages(pages: Array<{page: Type, params?: any}>, opts: any = {}): Promise { + setPages(pages: Array<{page: Type, params?: any}>, opts: NavOptions = {}): Promise { if (!pages || !pages.length) { return Promise.resolve(); } @@ -307,7 +307,7 @@ export class NavController extends Ion { /** * @private */ - private setViews(components, opts = {}) { + private setViews(components, opts: NavOptions = {}) { console.warn('setViews() deprecated, use setPages() instead'); return this.setPages(components, opts); } @@ -377,14 +377,7 @@ export class NavController extends Ion { * @param {object} [opts={}] Any options you want to use pass to transtion * @returns {Promise} Returns a promise, which resolves when the transition has completed */ - push(page: Type, - params: any={}, - opts: { - animate?: boolean, - animation?: string - direction?: string - }={} - ) { + push(page: Type, params: any = {}, opts: NavOptions = {}) { return this.insertPages(-1, [{page: page, params: params}], opts); } @@ -413,7 +406,7 @@ export class NavController extends Ion { * @param {object} [opts={}] Any options you want to use pass to transtion * @returns {Promise} Returns a promise, which resolves when the transition has completed */ - present(enteringView: ViewController, opts: any = {}): Promise { + present(enteringView: ViewController, opts: NavOptions = {}): Promise { let rootNav = this.rootNav; if (rootNav['_tabs']) { @@ -424,7 +417,6 @@ export class NavController extends Ion { } opts.keyboardClose = false; - opts.skipCache = true; opts.direction = 'forward'; if (!opts.animation) { @@ -433,7 +425,6 @@ export class NavController extends Ion { enteringView.setLeavingOpts({ keyboardClose: false, - skipCache: true, direction: 'back', animation: enteringView.getTransitionName('back') }); @@ -465,7 +456,7 @@ export class NavController extends Ion { * @param {object} [opts={}] Any options you want to use pass to transtion * @returns {Promise} Returns a promise when the page has been inserted into the navigation stack */ - insert(insertIndex: number, page: Type, params: any = {}, opts: any = {}): Promise { + insert(insertIndex: number, page: Type, params: any = {}, opts: NavOptions = {}): Promise { return this.insertPages(insertIndex, [{page: page, params: params}], opts); } @@ -497,12 +488,12 @@ export class NavController extends Ion { * @param {object} [opts={}] Any options you want to use pass to transtion * @returns {Promise} Returns a promise when the pages have been inserted into the navigation stack */ - insertPages(insertIndex: number, insertPages: Array<{page: Type, params?: any}>, opts: any = {}): Promise { + insertPages(insertIndex: number, insertPages: Array<{page: Type, params?: any}>, opts: NavOptions = {}): Promise { let views = insertPages.map(p => new ViewController(p.page, p.params)); return this._insertViews(insertIndex, views, opts); } - private _insertViews(insertIndex: number, insertViews: Array, opts: any = {}): Promise { + private _insertViews(insertIndex: number, insertViews: Array, opts: NavOptions = {}): Promise { if (!insertViews || !insertViews.length) { return Promise.reject('invalid pages'); } @@ -526,7 +517,7 @@ export class NavController extends Ion { // transition in, but was simply inserted somewhere in the stack // go backwards through the stack and find the first active view // which could be active or one ready to enter - for (let i = this._views.length - 1; i >= 0; i--) { + for (var i = this._views.length - 1; i >= 0; i--) { if (this._views[i].state === STATE_ACTIVE || this._views[i].state === STATE_INIT_ENTER) { // found the view at the end of the stack that's either // already active or it is about to enter @@ -627,7 +618,7 @@ export class NavController extends Ion { * @param {object} [opts={}] Any options you want to use pass to transtion * @returns {Promise} Returns a promise when the transition is completed */ - pop(opts: any = {}): Promise { + pop(opts: NavOptions = {}): Promise { // get the index of the active view // which will become the view to be leaving let activeView = this.getByState(STATE_TRANS_ENTER) || @@ -640,7 +631,7 @@ export class NavController extends Ion { * Similar to `pop()`, this method let's you navigate back to the root of the stack, no matter how many views that is * @param {object} [opts={}] Any options you want to use pass to transtion */ - popToRoot(opts = {}): Promise { + popToRoot(opts: NavOptions = {}): Promise { return this.popTo(this.first(), opts); } @@ -649,7 +640,7 @@ export class NavController extends Ion { * @param {ViewController} view to pop to * @param {object} [opts={}] Any options you want to use pass to transtion */ - popTo(view: ViewController, opts: any = {}): Promise { + popTo(view: ViewController, opts: NavOptions = {}): Promise { let startIndex = this.indexOf(view); let activeView = this.getByState(STATE_TRANS_ENTER) || this.getByState(STATE_INIT_ENTER) || @@ -678,7 +669,7 @@ export class NavController extends Ion { * @param {object} [opts={}] Any options you want to use pass to transtion. * @returns {Promise} Returns a promise when the page has been removed. */ - remove(startIndex: number = -1, removeCount: number = 1, opts: any = {}): Promise { + remove(startIndex: number = -1, removeCount: number = 1, opts: NavOptions = {}): Promise { if (startIndex === -1) { startIndex = this._views.length - 1; @@ -705,13 +696,16 @@ export class NavController extends Ion { } if (this._lastTrans) { - this._lastTrans.playbackRate(100).onFinish(() => { - opts.animate = false; - this._transition(forcedActive, null, opts, () => { - // transition has completed!! - resolve(); - }); - }); + this._lastTrans + .onFinish(() => { + opts.animate = false; + this._transition(forcedActive, null, opts, () => { + // transition has completed!! + resolve(); + }); + }, false, true) + .stop(); + this._lastTrans.destroy(); this._lastTrans = null; } else { @@ -761,7 +755,7 @@ export class NavController extends Ion { let view: ViewController = null; // loop through each view that is set to be removed - for (let i = startIndex, ii = removeCount + startIndex; i < ii; i++) { + for (var i = startIndex, ii = removeCount + startIndex; i < ii; i++) { view = this.getByIndex(i); if (!view) break; @@ -792,7 +786,7 @@ export class NavController extends Ion { // from the index of the leaving view, go backwards and // find the first view that is inactive - for (let i = this.indexOf(view) - 1; i >= 0; i--) { + for (var i = this.indexOf(view) - 1; i >= 0; i--) { if (this._views[i].state === STATE_INACTIVE) { this._views[i].state = STATE_INIT_ENTER; break; @@ -813,7 +807,7 @@ export class NavController extends Ion { // from the index of the leaving view, go backwards and // find the first view that is inactive so it can be the entering - for (let i = this.indexOf(view) - 1; i >= 0; i--) { + for (var i = this.indexOf(view) - 1; i >= 0; i--) { if (this._views[i].state === STATE_INACTIVE) { this._views[i].state = STATE_INIT_ENTER; break; @@ -855,7 +849,7 @@ export class NavController extends Ion { /** * @private */ - private _transition(enteringView: ViewController, leavingView: ViewController, opts, done: Function) { + private _transition(enteringView: ViewController, leavingView: ViewController, opts: NavOptions, done: Function) { let transId = ++this._transIds; if (enteringView === leavingView) { @@ -902,7 +896,7 @@ export class NavController extends Ion { /** * @private */ - private _render(transId, enteringView: ViewController, leavingView: ViewController, opts: any, done: Function) { + private _render(transId, enteringView: ViewController, leavingView: ViewController, opts: NavOptions, done: Function) { // compile/load the view into the DOM if (enteringView.state === STATE_INACTIVE) { @@ -948,7 +942,7 @@ export class NavController extends Ion { /** * @private */ - private _postRender(transId, enteringView: ViewController, leavingView: ViewController, isAlreadyTransitioning: boolean, opts: any, done: Function) { + private _postRender(transId, enteringView: ViewController, leavingView: ViewController, isAlreadyTransitioning: boolean, opts: NavOptions, done: Function) { // called after _render has completed and the view is compiled/loaded if (enteringView.state === STATE_INACTIVE) { @@ -1007,7 +1001,7 @@ export class NavController extends Ion { /** * @private */ - private _beforeTrans(enteringView: ViewController, leavingView: ViewController, opts: any, done: Function) { + private _beforeTrans(enteringView: ViewController, leavingView: ViewController, opts: NavOptions, done: Function) { // called after one raf from postRender() // create the transitions animation, play the animation // when the transition ends call wait for it to end @@ -1025,15 +1019,20 @@ export class NavController extends Ion { this._zone.runOutsideAngular(() => { // init the transition animation - opts.renderDelay = opts.transitionDelay || this._trnsDelay; + let transitionOpts = { + animation: opts.animation, + direction: opts.direction, + duration: opts.duration, + easing: opts.easing, + renderDelay: opts.transitionDelay || this._trnsDelay, + isRTL: this.config.platform.isRTL() + }; - // set if this app is right-to-left or not - opts.isRTL = this.config.platform.isRTL(); - - let transAnimation = Animation.createTransition(enteringView, - leavingView, - opts); + let transAnimation = Transition.createTransition(enteringView, + leavingView, + transitionOpts); + this._lastTrans && this._lastTrans.destroy(); this._lastTrans = transAnimation; if (opts.animate === false) { @@ -1070,7 +1069,7 @@ export class NavController extends Ion { /** * @private */ - private _afterTrans(enteringView: ViewController, leavingView: ViewController, opts: any, done: Function) { + private _afterTrans(enteringView: ViewController, leavingView: ViewController, opts: NavOptions, done: Function) { // transition has completed, update each view's state // place back into the zone, run didEnter/didLeave // call the final callback when done @@ -1176,7 +1175,7 @@ export class NavController extends Ion { let activeViewIndex = this.indexOf(this.getActive()); let destroys = this._views.filter(v => v.state === STATE_REMOVE_AFTER_TRANS); - for (let i = activeViewIndex + 1; i < this._views.length; i++) { + for (var i = activeViewIndex + 1; i < this._views.length; i++) { if (this._views[i].state === STATE_INACTIVE) { destroys.push(this._views[i]); } @@ -1193,7 +1192,7 @@ export class NavController extends Ion { /** * @private */ - loadPage(view: ViewController, navbarContainerRef, opts: any, done: Function) { + loadPage(view: ViewController, navbarContainerRef, opts: NavOptions, done: Function) { let wtfTimeRangeScope = wtfStartTimeRange('NavController#loadPage', view.name); // guts of DynamicComponentLoader#loadIntoLocation @@ -1295,19 +1294,8 @@ export class NavController extends Ion { // wait for the new view to complete setup this._render(0, enteringView, leavingView, {}, () => { - this._zone.runOutsideAngular(() => { - // set that the new view pushed on the stack is staged to be entering/leaving - // staged state is important for the transition to find the correct view - // enteringView.state = STATE_RENDERED_ENTER; - // leavingView.state = STATE_RENDERED_LEAVE; - // init the swipe back transition animation - this._sbTrans = Animation.createTransition(enteringView, leavingView, opts); - this._sbTrans.easing('linear').progressStart(); - - }); }); - } /** @@ -1321,7 +1309,7 @@ export class NavController extends Ion { this.setTransitioning(true, 4000); // set the transition animation's progress - this._sbTrans.progress(value); + this._sbTrans.progressStep(value); } } @@ -1336,8 +1324,7 @@ export class NavController extends Ion { this._app.setEnabled(false); this.setTransitioning(true); - this._sbTrans.progressEnd(completeSwipeBack, rate).then(() => { - + this._sbTrans.onFinish(() => { this._zone.run(() => { // find the views that were entering and leaving let enteringView = null;// this._getStagedEntering(); @@ -1376,14 +1363,15 @@ export class NavController extends Ion { } // empty out and dispose the swipe back transition animation - this._sbTrans && this._sbTrans.dispose(); + this._sbTrans && this._sbTrans.destroy(); this._sbTrans = null; // all done! //this._transComplete(); }); - }); + }, true); + this._sbTrans.progressEnd(completeSwipeBack, 0.5); } /** @@ -1475,7 +1463,7 @@ export class NavController extends Ion { * @returns {ViewController} */ getByState(state: string): ViewController { - for (let i = this._views.length - 1; i >= 0; i--) { + for (var i = this._views.length - 1; i >= 0; i--) { if (this._views[i].state === state) { return this._views[i]; } @@ -1595,6 +1583,18 @@ export class NavController extends Ion { } +export interface NavOptions { + animate?: boolean; + animation?: string; + direction?: string; + duration?: number; + easing?: string; + keyboardClose?: boolean; + preload?: boolean; + transitionDelay?: number; + postLoad?: Function +} + const STATE_ACTIVE = 'active'; const STATE_INACTIVE = 'inactive'; const STATE_INIT_ENTER = 'init_enter'; diff --git a/ionic/components/nav/view-controller.ts b/ionic/components/nav/view-controller.ts index 1cabd98ff8..75339e61f1 100644 --- a/ionic/components/nav/view-controller.ts +++ b/ionic/components/nav/view-controller.ts @@ -1,7 +1,7 @@ import {Output, EventEmitter, Type, TemplateRef, ViewContainerRef, ElementRef, Renderer} from 'angular2/core'; import {Navbar} from '../navbar/navbar'; -import {NavController} from './nav-controller'; +import {NavController, NavOptions} from './nav-controller'; import {NavParams} from './nav-params'; @@ -112,7 +112,7 @@ export class ViewController { /** * @private */ - setLeavingOpts(opts) { + setLeavingOpts(opts: NavOptions) { this._leavingOpts = opts; } @@ -183,7 +183,7 @@ export class ViewController { * @private */ destroy() { - for (let i = 0; i < this._destroys.length; i++) { + for (var i = 0; i < this._destroys.length; i++) { this._destroys[i](); } this._destroys = []; diff --git a/ionic/ionic.ts b/ionic/ionic.ts index afa2b208a9..821c0aba60 100644 --- a/ionic/ionic.ts +++ b/ionic/ionic.ts @@ -24,5 +24,5 @@ export * from './translation/translate_pipe' import './config/modes' import './platform/registry' import './animations/builtins' -import './animations/ios-transition' -import './animations/md-transition' +import './transitions/transition-ios' +import './transitions/transition-md' diff --git a/ionic/animations/ios-transition.ts b/ionic/transitions/transition-ios.ts similarity index 93% rename from ionic/animations/ios-transition.ts rename to ionic/transitions/transition-ios.ts index 6beb0c7368..84b28726d7 100644 --- a/ionic/animations/ios-transition.ts +++ b/ionic/transitions/transition-ios.ts @@ -1,4 +1,6 @@ -import {Animation} from './animation'; +import {Animation} from '../animations/animation'; +import {Transition, TransitionOptions} from './transition'; +import {ViewController} from '../components/nav/view-controller'; const DURATION = 500; const EASING = 'cubic-bezier(0.36,0.66,0.04,1)'; @@ -11,10 +13,10 @@ const OFF_OPACITY = 0.8; const SHOW_BACK_BTN_CSS = 'show-back-button'; -class IOSTransition extends Animation { +class IOSTransition extends Transition { - constructor(enteringView, leavingView, opts) { - super(null, opts); + constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { + super(opts); this.duration(opts.duration || DURATION); this.easing(opts.easing || EASING); @@ -187,4 +189,4 @@ class IOSTransition extends Animation { } -Animation.register('ios-transition', IOSTransition); +Transition.register('ios-transition', IOSTransition); diff --git a/ionic/animations/md-transition.ts b/ionic/transitions/transition-md.ts similarity index 81% rename from ionic/animations/md-transition.ts rename to ionic/transitions/transition-md.ts index 479a00925e..e3909649ea 100644 --- a/ionic/animations/md-transition.ts +++ b/ionic/transitions/transition-md.ts @@ -1,4 +1,6 @@ -import {Animation} from './animation'; +import {Animation} from '../animations/animation'; +import {Transition, TransitionOptions} from './transition'; +import {ViewController} from '../components/nav/view-controller'; const TRANSLATEY = 'translateY'; const OFF_BOTTOM = '40px'; @@ -6,10 +8,10 @@ const CENTER = '0px' const SHOW_BACK_BTN_CSS = 'show-back-button'; -class MDTransition extends Animation { +class MDTransition extends Transition { - constructor(enteringView, leavingView, opts) { - super(null, opts); + constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { + super(opts); // what direction is the transition going let backDirection = (opts.direction === 'back'); @@ -60,4 +62,4 @@ class MDTransition extends Animation { } -Animation.register('md-transition', MDTransition); +Transition.register('md-transition', MDTransition); diff --git a/ionic/transitions/transition.ts b/ionic/transitions/transition.ts new file mode 100644 index 0000000000..0e0a88dd5c --- /dev/null +++ b/ionic/transitions/transition.ts @@ -0,0 +1,41 @@ +import {Animation} from '../animations/animation'; +import {ViewController} from '../components/nav/view-controller'; + + +/** + * @private + **/ +export class Transition extends Animation { + + constructor(opts: TransitionOptions) { + super(null, { + renderDelay: opts.renderDelay + }); + } + + static createTransition(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions): Transition { + let TransitionClass = TransitionRegistry[opts.animation]; + if (!TransitionClass) { + // didn't find a transition animation, default to ios-transition + TransitionClass = TransitionRegistry['ios-transition']; + } + + return new TransitionClass(enteringView, leavingView, opts); + } + + static register(name: string, TransitionClass) { + TransitionRegistry[name] = TransitionClass; + } + +} + +export interface TransitionOptions { + animation: string; + duration: number; + easing: string; + direction: string; + renderDelay?: number; + isRTL?: boolean; +} + +let TransitionRegistry = {};