refactor(transition): deduplicates animation builder

This commit is contained in:
Manu Mtz.-Almeida
2018-04-27 00:15:06 +02:00
parent 89d5a358a7
commit aa53563944
7 changed files with 64 additions and 108 deletions

View File

@ -55,6 +55,7 @@ import {
PopoverOptions, PopoverOptions,
RangeInputChangeEvent, RangeInputChangeEvent,
RouteID, RouteID,
RouterOutletOptions,
RouteWrite, RouteWrite,
SelectInputChangeEvent, SelectInputChangeEvent,
SelectPopoverOption, SelectPopoverOption,
@ -79,9 +80,6 @@ import {
import { import {
ViewController, ViewController,
} from './components/nav/view-controller'; } from './components/nav/view-controller';
import {
RouterOutletOptions,
} from './components/router-outlet/route-outlet';
import { import {
RouterDirection, RouterDirection,
RouterEventDetail, RouterEventDetail,

View File

@ -1,4 +1,4 @@
import { Animation, ComponentRef, FrameworkDelegate } from '../../interface'; import { Animation, AnimationBuilder, ComponentRef, FrameworkDelegate, Mode } from '../../interface';
import { ViewController } from './view-controller'; import { ViewController } from './view-controller';
export { Nav } from './nav'; export { Nav } from './nav';
@ -15,16 +15,20 @@ export interface NavResult {
direction?: NavDirection; direction?: NavDirection;
} }
export interface NavOptions { export interface RouterOutletOptions {
animate?: boolean; animate?: boolean;
animation?: string; animationBuilder?: AnimationBuilder;
direction?: NavDirection;
duration?: number; duration?: number;
easing?: string; easing?: string;
id?: string; showGoBack?: boolean;
direction?: NavDirection;
deepWait?: boolean;
mode?: Mode;
keyboardClose?: boolean; keyboardClose?: boolean;
}
export interface NavOptions extends RouterOutletOptions {
progressAnimation?: boolean; progressAnimation?: boolean;
ev?: any;
updateURL?: boolean; updateURL?: boolean;
delegate?: FrameworkDelegate; delegate?: FrameworkDelegate;
viewIsReady?: (enteringEl: HTMLElement) => Promise<any>; viewIsReady?: (enteringEl: HTMLElement) => Promise<any>;

View File

@ -1,20 +1,15 @@
import { Build, Component, Element, Event, EventEmitter, Method, Prop, Watch } from '@stencil/core'; import { Build, Component, Element, Event, EventEmitter, Method, Prop, Watch } from '@stencil/core';
import { Animation, ComponentProps, Config, FrameworkDelegate, GestureDetail, Mode, NavOutlet, QueueController, RouteID, RouteWrite, RouterDirection } from '../../interface'; import { Animation, ComponentProps, Config, FrameworkDelegate, GestureDetail, Mode, NavOutlet, QueueController, RouteID, RouteWrite, RouterDirection } from '../../interface';
import { NavComponent, NavDirection, NavOptions, NavResult, TransitionDoneFn, TransitionInstruction } from '../../interface'; import { NavComponent, NavOptions, NavResult, TransitionDoneFn, TransitionInstruction } from '../../interface';
import { assert } from '../../utils/helpers'; import { assert } from '../../utils/helpers';
import { AnimationOptions, ViewLifecycle, lifecycle, transition } from '../../utils/transition'; import { TransitionOptions, ViewLifecycle, lifecycle, transition } from '../../utils/transition';
import { ViewController, ViewState, convertToViews, matches } from './view-controller'; import { ViewController, ViewState, convertToViews, matches } from './view-controller';
import iosTransitionAnimation from './animations/ios.transition';
import mdTransitionAnimation from './animations/md.transition';
@Component({ @Component({
tag: 'ion-nav', tag: 'ion-nav',
}) })
export class Nav implements NavOutlet { export class Nav implements NavOutlet {
private init = false;
private transInstr: TransitionInstruction[] = []; private transInstr: TransitionInstruction[] = [];
private sbTrns?: Animation; private sbTrns?: Animation;
private useRouter = false; private useRouter = false;
@ -288,7 +283,6 @@ export class Nav implements NavOutlet {
this.fireError('nav controller was destroyed', ti); this.fireError('nav controller was destroyed', ti);
return; return;
} }
this.init = true;
if (ti.done) { if (ti.done) {
ti.done( ti.done(
@ -557,30 +551,24 @@ export class Nav implements NavOutlet {
// or if it is a portal (modal, actionsheet, etc.) // or if it is a portal (modal, actionsheet, etc.)
const opts = ti.opts!; const opts = ti.opts!;
const animationBuilder = this.getAnimationBuilder(opts); const progressCallback = opts.progressAnimation
? (animation: Animation) => { this.sbTrns = animation; }
const progressAnimation = opts.progressAnimation
? (animation: Animation) => this.sbTrns = animation
: undefined; : undefined;
const enteringEl = enteringView.element!; const enteringEl = enteringView.element!;
const leavingEl = leavingView && leavingView.element!; const leavingEl = leavingView && leavingView.element!;
const animationOpts: AnimationOptions = { const animationOpts: TransitionOptions = {
animationCtrl: this.animationCtrl, mode: this.mode,
animationBuilder: animationBuilder, animate: this.animated,
animation: undefined,
direction: opts.direction as NavDirection,
duration: opts.duration,
easing: opts.easing,
viewIsReady: opts.viewIsReady,
showGoBack: this.canGoBack(enteringView), showGoBack: this.canGoBack(enteringView),
progressAnimation, animationCtrl: this.animationCtrl,
progressCallback,
window: this.win, window: this.win,
baseEl: this.el, baseEl: this.el,
enteringEl, enteringEl,
leavingEl leavingEl,
...opts
}; };
const trns = await transition(animationOpts); const trns = await transition(animationOpts);
return this.transitionFinish(trns, enteringView, leavingView, opts); return this.transitionFinish(trns, enteringView, leavingView, opts);
@ -607,14 +595,6 @@ export class Nav implements NavOutlet {
}; };
} }
private getAnimationBuilder(opts: NavOptions) {
if (opts.duration === 0 || opts.animate === false || !this.init || this.animated === false || this.views.length <= 1) {
return undefined;
}
const mode = opts.animation || this.config.get('pageTransition', this.mode);
return mode === 'ios' ? iosTransitionAnimation : mdTransitionAnimation;
}
private insertViewAt(view: ViewController, index: number) { private insertViewAt(view: ViewController, index: number) {
const views = this.views; const views = this.views;
const existingIndex = views.indexOf(view); const existingIndex = views.indexOf(view);

View File

@ -1,11 +1,8 @@
import { Component, Element, Event, EventEmitter, Method, Prop } from '@stencil/core'; import { Component, Element, Event, EventEmitter, Method, Prop } from '@stencil/core';
import { AnimationBuilder, ComponentProps, ComponentRef, Config, FrameworkDelegate, Mode, NavDirection, NavOutlet, RouteID, RouteWrite } from '../../interface'; import { AnimationBuilder, ComponentProps, ComponentRef, Config, FrameworkDelegate, Mode, NavOutlet, RouteID, RouteWrite, RouterOutletOptions } from '../../interface';
import { transition } from '../../utils'; import { transition } from '../../utils';
import { attachComponent, detachComponent } from '../../utils/framework-delegate'; import { attachComponent, detachComponent } from '../../utils/framework-delegate';
import iosTransitionAnimation from '../nav/animations/ios.transition';
import mdTransitionAnimation from '../nav/animations/md.transition';
@Component({ @Component({
tag: 'ion-router-outlet' tag: 'ion-router-outlet'
@ -76,18 +73,15 @@ export class RouterOutlet implements NavOutlet {
opts = opts || {}; opts = opts || {};
await transition({ await transition({
animationBuilder: this.getAnimationBuilder(opts), mode: this.mode,
direction: opts.direction, animate: this.animated,
duration: opts.duration,
easing: opts.easing,
deepWait: opts.deepWait,
animationCtrl: this.animationCtrl, animationCtrl: this.animationCtrl,
showGoBack: opts.showGoBack,
window: this.win, window: this.win,
enteringEl: enteringEl, enteringEl: enteringEl,
leavingEl: leavingEl, leavingEl: leavingEl,
baseEl: this.el, baseEl: this.el,
...opts
}); });
this.isTransitioning = false; this.isTransitioning = false;
@ -117,16 +111,6 @@ export class RouterOutlet implements NavOutlet {
} : undefined; } : undefined;
} }
private getAnimationBuilder(opts: RouterOutletOptions) {
if (opts.duration === 0 || this.animated === false) {
return undefined;
}
const mode = opts.mode || this.config.get('pageTransition', this.mode);
return opts.animationBuilder
|| this.animationBuilder
|| mode === 'ios' ? iosTransitionAnimation : mdTransitionAnimation;
}
render() { render() {
return [ return [
this.mode === 'ios' && <div class="nav-decor"/>, this.mode === 'ios' && <div class="nav-decor"/>,
@ -134,13 +118,3 @@ export class RouterOutlet implements NavOutlet {
]; ];
} }
} }
export interface RouterOutletOptions {
animationBuilder?: AnimationBuilder;
duration?: number;
easing?: string;
showGoBack?: boolean;
direction?: NavDirection;
deepWait?: boolean;
mode?: 'md' | 'ios';
}

View File

@ -1,5 +1,5 @@
import { Animation } from '../../../interface'; import { Animation } from '../../interface';
import { AnimationOptions } from '../../../utils/transition'; import { TransitionOptions } from '../../utils/transition';
const DURATION = 500; const DURATION = 500;
const EASING = 'cubic-bezier(0.36,0.66,0.04,1)'; const EASING = 'cubic-bezier(0.36,0.66,0.04,1)';
@ -9,7 +9,7 @@ const TRANSLATEX = 'translateX';
const CENTER = '0%'; const CENTER = '0%';
const OFF_OPACITY = 0.8; const OFF_OPACITY = 0.8;
export default function iosTransitionAnimation(Animation: Animation, navEl: HTMLElement, opts: AnimationOptions): Promise<Animation> { export default function iosTransitionAnimation(Animation: Animation, navEl: HTMLElement, opts: TransitionOptions): Promise<Animation> {
const isRTL = document.dir === 'rtl'; const isRTL = document.dir === 'rtl';
const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%'; const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%';

View File

@ -1,11 +1,11 @@
import { Animation } from '../../../interface'; import { Animation } from '../../interface';
import { AnimationOptions } from '../../../utils/transition'; import { TransitionOptions } from '../../utils/transition';
const TRANSLATEY = 'translateY'; const TRANSLATEY = 'translateY';
const OFF_BOTTOM = '40px'; const OFF_BOTTOM = '40px';
const CENTER = '0px'; const CENTER = '0px';
export default function mdTransitionAnimation(Animation: Animation, _: HTMLElement, opts: AnimationOptions): Promise<Animation> { export default function mdTransitionAnimation(Animation: Animation, _: HTMLElement, opts: TransitionOptions): Promise<Animation> {
const enteringEl = opts.enteringEl; const enteringEl = opts.enteringEl;
const leavingEl = opts.leavingEl; const leavingEl = opts.leavingEl;

View File

@ -1,15 +1,29 @@
import { Animation, AnimationBuilder, NavDirection } from '../interface'; import { Animation, AnimationBuilder, NavDirection, NavOptions } from '../interface';
import iosTransitionAnimation from './animations/ios.transition';
import mdTransitionAnimation from './animations/md.transition';
export function transition(opts: AnimationOptions): Promise<Animation|null> { export function transition(opts: TransitionOptions): Promise<Animation|null> {
beforeTransition(opts); beforeTransition(opts);
return (opts.leavingEl && (opts.animationBuilder || opts.animation)) const animationBuilder = getAnimationBuilder(opts);
? animation(opts) return (animationBuilder)
? animation(animationBuilder, opts)
: noAnimation(opts); // fast path for no animation : noAnimation(opts); // fast path for no animation
} }
function beforeTransition(opts: AnimationOptions) { function getAnimationBuilder(opts: TransitionOptions): AnimationBuilder | undefined {
if (!opts.leavingEl || opts.animate === false || opts.duration === 0) {
return undefined;
}
if (opts.animationBuilder) {
return opts.animationBuilder;
}
return opts.mode === 'ios' ? iosTransitionAnimation : mdTransitionAnimation;
}
function beforeTransition(opts: TransitionOptions) {
const enteringEl = opts.enteringEl; const enteringEl = opts.enteringEl;
const leavingEl = opts.leavingEl; const leavingEl = opts.leavingEl;
@ -26,10 +40,10 @@ function beforeTransition(opts: AnimationOptions) {
} }
} }
async function animation(opts: AnimationOptions): Promise<Animation> { async function animation(animationBuilder: AnimationBuilder, opts: TransitionOptions): Promise<Animation> {
await waitForReady(opts, true); await waitForReady(opts, true);
const trns = await createTransition(opts); const trns = await opts.animationCtrl.create(animationBuilder, opts.baseEl, opts);
fireWillEvents(opts.window, opts.enteringEl, opts.leavingEl); fireWillEvents(opts.window, opts.enteringEl, opts.leavingEl);
await playTransition(trns, opts); await playTransition(trns, opts);
@ -39,7 +53,7 @@ async function animation(opts: AnimationOptions): Promise<Animation> {
return trns; return trns;
} }
async function noAnimation(opts: AnimationOptions): Promise<null> { async function noAnimation(opts: TransitionOptions): Promise<null> {
const enteringEl = opts.enteringEl; const enteringEl = opts.enteringEl;
const leavingEl = opts.leavingEl; const leavingEl = opts.leavingEl;
if (enteringEl) { if (enteringEl) {
@ -55,7 +69,7 @@ async function noAnimation(opts: AnimationOptions): Promise<null> {
return null; return null;
} }
async function waitForReady(opts: AnimationOptions, defaultDeep: boolean) { async function waitForReady(opts: TransitionOptions, defaultDeep: boolean) {
const deep = opts.deepWait != null ? opts.deepWait : defaultDeep; const deep = opts.deepWait != null ? opts.deepWait : defaultDeep;
const promises = deep ? [ const promises = deep ? [
deepReady(opts.enteringEl), deepReady(opts.enteringEl),
@ -75,23 +89,17 @@ async function notifyViewReady(viewIsReady: undefined | ((enteringEl: HTMLElemen
} }
} }
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> { function playTransition(transition: Animation, opts: TransitionOptions): Promise<Animation> {
const progressAnimation = opts.progressAnimation; const progressCallback = opts.progressCallback;
const promise = new Promise<Animation>(resolve => transition.onFinish(resolve)); const promise = new Promise<Animation>(resolve => transition.onFinish(resolve));
// cool, let's do this, start the transition // cool, let's do this, start the transition
if (progressAnimation) { if (progressCallback) {
// this is a swipe to go back, just get the transition progress ready // this is a swipe to go back, just get the transition progress ready
// kick off the swipe animation start // kick off the swipe animation start
transition.progressStart(); transition.progressStart();
progressAnimation(transition); progressCallback(transition);
} else { } else {
// only the top level transition should actually start "play" // only the top level transition should actually start "play"
@ -176,19 +184,11 @@ export const enum ViewLifecycle {
WillUnload = 'ionViewWillUnload', WillUnload = 'ionViewWillUnload',
} }
export interface AnimationOptions { export interface TransitionOptions extends NavOptions {
animationCtrl: HTMLIonAnimationControllerElement; animationCtrl: HTMLIonAnimationControllerElement;
animationBuilder: AnimationBuilder|undefined; progressCallback?: ((ani: Animation) => void);
animation?: Animation;
direction?: NavDirection;
duration?: number;
easing?: string;
deepWait?: boolean;
viewIsReady?: (enteringEl: HTMLElement) => Promise<any>;
showGoBack?: boolean;
progressAnimation?: Function;
window: Window; window: Window;
baseEl: HTMLElement;
enteringEl: HTMLElement; enteringEl: HTMLElement;
leavingEl: HTMLElement|undefined; leavingEl: HTMLElement|undefined;
baseEl: HTMLElement;
} }