feat(animations): init animation controller

This commit is contained in:
Adam Bradley
2017-08-03 08:50:27 -05:00
parent f0577e2aa7
commit f6781825ed
14 changed files with 245 additions and 214 deletions

View File

@ -1,70 +0,0 @@
export interface Animation {
new(elm?: Node|Node[]|NodeList): Animation;
add: (childAnimation: Animation) => Animation;
addElement: (elm: Node|Node[]|NodeList) => Animation;
afterAddClass: (className: string) => Animation;
afterClearStyles: (propertyNames: string[]) => Animation;
afterRemoveClass: (className: string) => Animation;
afterStyles: (styles: { [property: string]: any; }) => Animation;
beforeAddClass: (className: string) => Animation;
beforeClearStyles: (propertyNames: string[]) => Animation;
beforeRemoveClass: (className: string) => Animation;
beforeStyles: (styles: { [property: string]: any; }) => Animation;
destroy: () => void;
duration: (milliseconds: number) => Animation;
getDuration(opts?: PlayOptions): number;
easing: (name: string) => Animation;
easingReverse: (name: string) => Animation;
from: (prop: string, val: any) => Animation;
fromTo: (prop: string, fromVal: any, toVal: any, clearProperyAfterTransition?: boolean) => Animation;
hasCompleted: boolean;
isPlaying: boolean;
onFinish: (callback: (animation: Animation) => void, opts?: {oneTimeCallback?: boolean, clearExistingCallacks?: boolean}) => Animation;
play: (opts?: PlayOptions) => void;
syncPlay: () => void;
progressEnd: (shouldComplete: boolean, currentStepValue: number, dur: number) => void;
progressStep: (stepValue: number) => void;
progressStart: () => void;
reverse: (shouldReverse?: boolean) => Animation;
stop: (stepValue?: number) => void;
to: (prop: string, val: any, clearProperyAfterTransition?: boolean) => Animation;
}
export interface AnimationBuilder {
(elm?: HTMLElement): Animation;
}
export interface AnimationOptions {
animation?: string;
duration?: number;
easing?: string;
direction?: string;
isRTL?: boolean;
ev?: any;
}
export interface PlayOptions {
duration?: number;
promise?: boolean;
}
export interface EffectProperty {
effectName: string;
trans: boolean;
wc?: string;
to?: EffectState;
from?: EffectState;
[state: string]: any;
}
export interface EffectState {
val: any;
num: number;
effectUnit: string;
}

View File

@ -0,0 +1,24 @@
import { Component } from '@stencil/core';
import { Animation } from './animation-interface';
import { Animator } from './animator';
import { Ionic, IonicControllerApi } from '../../index';
@Component({
tag: 'ion-animation-controller'
})
export class AnimationController implements IonicControllerApi {
ionViewWillLoad() {
debugger;;;
Ionic.registerController('animation', this);
}
load(): Promise<Animation> {
return new Promise(resolve => {
debugger;
resolve((Animator as any));
});
}
}

View File

@ -0,0 +1,72 @@
export interface Animation {
new (): Animation;
parent: Animation;
addElement(elm: Node|Node[]|NodeList): Animation;
add(childAnimation: Animation): Animation;
duration(milliseconds: number): Animation;
easing(name: string): Animation;
easingReverse(name: string): Animation;
getDuration(opts?: PlayOptions): number;
getEasing(): string;
from(prop: string, val: any): Animation;
to(prop: string, val: any, clearProperyAfterTransition?: boolean): Animation;
fromTo(prop: string, fromVal: any, toVal: any, clearProperyAfterTransition?: boolean): Animation;
beforeAddClass(className: string): Animation;
beforeRemoveClass(className: string): Animation;
beforeStyles(styles: { [property: string]: any; }): Animation;
beforeClearStyles(propertyNames: string[]): Animation;
beforeAddRead(domReadFn: Function): Animation;
beforeAddWrite(domWriteFn: Function): Animation;
afterAddClass(className: string): Animation;
afterRemoveClass(className: string): Animation;
afterStyles(styles: { [property: string]: any; }): Animation;
afterClearStyles(propertyNames: string[]): Animation;
play(opts?: PlayOptions): void;
syncPlay(): void;
reverse(shouldReverse?: boolean): Animation;
stop(stepValue?: number): void;
progressStart(): void;
progressStep(stepValue: number): void;
progressEnd(shouldComplete: boolean, currentStepValue: number, dur: number): void;
onFinish(callback: (animation?: Animation) => void, opts?: {oneTimeCallback?: boolean, clearExistingCallacks?: boolean}): Animation;
destroy(): void;
}
export interface AnimationBuilder {
(Animation: Animation, baseElm?: HTMLElement): Animation;
}
export interface AnimationOptions {
animation?: string;
duration?: number;
easing?: string;
direction?: string;
isRTL?: boolean;
ev?: any;
}
export interface PlayOptions {
duration?: number;
promise?: boolean;
}
export interface EffectProperty {
effectName: string;
trans: boolean;
wc?: string;
to?: EffectState;
from?: EffectState;
[state: string]: any;
}
export interface EffectState {
val: any;
num: number;
effectUnit: string;
}

View File

@ -1,16 +1,17 @@
import { AnimationOptions, EffectProperty, EffectState, PlayOptions } from './interfaces';
import { AnimationOptions, EffectProperty, EffectState, PlayOptions } from './animation-interface';
import { CSS_PROP, CSS_VALUE_REGEX, DURATION_MIN, TRANSITION_END_FALLBACK_PADDING_MS, TRANSFORM_PROPS } from './constants';
import { transitionEnd } from './transition-end';
export class Animation {
export class Animator {
private _afterAddClasses: string[];
private _afterRemoveClasses: string[];
private _afterStyles: { [property: string]: any; };
private _beforeAddClasses: string[];
private _beforeRemoveClasses: string[];
private _beforeStyles: { [property: string]: any; };
private _childAnimations: Animation[];
private _childAnimations: Animator[];
private _childAnimationTotal: number;
private _duration: number = null;
private _easingName: string = null;
@ -30,22 +31,17 @@ export class Animation {
private _writeCallbacks: Function[];
private _destroyed: boolean = false;
parent: Animation;
parent: Animator;
opts: AnimationOptions;
hasChildren: boolean = false;
isPlaying: boolean = false;
hasCompleted: boolean = false;
constructor(elm?: Node|Node[]|NodeList) {
this.addElement(elm);
}
addElement(elm: Node|Node[]|NodeList): Animation {
addElement(elm: Node|Node[]|NodeList): Animator {
if (elm) {
if ((<NodeList>elm).length) {
for (var i = 0; i < (<NodeList>elm).length; i++) {
this._addElm((<any>elm)[i]);
if ((elm as NodeList).length) {
for (var i = 0; i < (elm as NodeList).length; i++) {
this._addElm((elm as any)[i]);
}
} else {
@ -68,7 +64,7 @@ export class Animation {
/**
* Add a child animation to this animation.
*/
add(childAnimation: Animation): Animation {
add(childAnimation: Animator): Animator {
childAnimation.parent = this;
this.hasChildren = true;
this._childAnimationTotal = (this._childAnimations = this._childAnimations || []).push(childAnimation);
@ -100,7 +96,7 @@ export class Animation {
/**
* Set the duration for this animation.
*/
duration(milliseconds: number): Animation {
duration(milliseconds: number): Animator {
this._duration = milliseconds;
return this;
}
@ -119,7 +115,7 @@ export class Animation {
/**
* Set the easing for this animation.
*/
easing(name: string): Animation {
easing(name: string) {
this._easingName = name;
return this;
}
@ -127,7 +123,7 @@ export class Animation {
/**
* Set the easing for this reversed animation.
*/
easingReverse(name: string): Animation {
easingReverse(name: string) {
this._reversedEasingName = name;
return this;
}
@ -135,7 +131,7 @@ export class Animation {
/**
* Add the "from" value for a specific property.
*/
from(prop: string, val: any): Animation {
from(prop: string, val: any): Animator {
this._addProp('from', prop, val);
return this;
}
@ -143,7 +139,7 @@ export class Animation {
/**
* Add the "to" value for a specific property.
*/
to(prop: string, val: any, clearProperyAfterTransition?: boolean): Animation {
to(prop: string, val: any, clearProperyAfterTransition?: boolean): Animator {
var fx = this._addProp('to', prop, val);
if (clearProperyAfterTransition) {
@ -158,7 +154,7 @@ export class Animation {
/**
* Shortcut to add both the "from" and "to" for the same property.
*/
fromTo(prop: string, fromVal: any, toVal: any, clearProperyAfterTransition?: boolean): Animation {
fromTo(prop: string, fromVal: any, toVal: any, clearProperyAfterTransition?: boolean): Animator {
return this.from(prop, fromVal).to(prop, toVal, clearProperyAfterTransition);
}
@ -183,13 +179,13 @@ export class Animation {
if (!fxProp) {
// first time we've see this EffectProperty
var shouldTrans = (TRANSFORM_PROPS[prop] === 1);
fxProp = <EffectProperty>{
fxProp = {
effectName: prop,
trans: shouldTrans,
// add the will-change property for transforms or opacity
wc: (shouldTrans ? CSS_PROP.transformProp : prop)
};
} as EffectProperty;
this._fxProperties.push(fxProp);
}
@ -221,7 +217,7 @@ export class Animation {
* Add CSS class to this animation's elements
* before the animation begins.
*/
beforeAddClass(className: string): Animation {
beforeAddClass(className: string): Animator {
(this._beforeAddClasses = this._beforeAddClasses || []).push(className);
return this;
}
@ -230,7 +226,7 @@ export class Animation {
* Remove CSS class from this animation's elements
* before the animation begins.
*/
beforeRemoveClass(className: string): Animation {
beforeRemoveClass(className: string): Animator {
(this._beforeRemoveClasses = this._beforeRemoveClasses || []).push(className);
return this;
}
@ -239,7 +235,7 @@ export class Animation {
* Set CSS inline styles to this animation's elements
* before the animation begins.
*/
beforeStyles(styles: { [property: string]: any; }): Animation {
beforeStyles(styles: { [property: string]: any; }): Animator {
this._beforeStyles = styles;
return this;
}
@ -248,7 +244,7 @@ export class Animation {
* Clear CSS inline styles from this animation's elements
* before the animation begins.
*/
beforeClearStyles(propertyNames: string[]): Animation {
beforeClearStyles(propertyNames: string[]): Animator {
this._beforeStyles = this._beforeStyles || {};
for (var i = 0; i < propertyNames.length; i++) {
this._beforeStyles[propertyNames[i]] = '';
@ -260,7 +256,7 @@ export class Animation {
* Add a function which contains DOM reads, which will run
* before the animation begins.
*/
beforeAddRead(domReadFn: Function): Animation {
beforeAddRead(domReadFn: Function): Animator {
(this._readCallbacks = this._readCallbacks || []).push(domReadFn);
return this;
}
@ -269,7 +265,7 @@ export class Animation {
* Add a function which contains DOM writes, which will run
* before the animation begins.
*/
beforeAddWrite(domWriteFn: Function): Animation {
beforeAddWrite(domWriteFn: Function): Animator {
(this._writeCallbacks = this._writeCallbacks || []).push(domWriteFn);
return this;
}
@ -278,7 +274,7 @@ export class Animation {
* Add CSS class to this animation's elements
* after the animation finishes.
*/
afterAddClass(className: string): Animation {
afterAddClass(className: string): Animator {
(this._afterAddClasses = this._afterAddClasses || []).push(className);
return this;
}
@ -287,7 +283,7 @@ export class Animation {
* Remove CSS class from this animation's elements
* after the animation finishes.
*/
afterRemoveClass(className: string): Animation {
afterRemoveClass(className: string): Animator {
(this._afterRemoveClasses = this._afterRemoveClasses || []).push(className);
return this;
}
@ -296,7 +292,7 @@ export class Animation {
* Set CSS inline styles to this animation's elements
* after the animation finishes.
*/
afterStyles(styles: { [property: string]: any; }): Animation {
afterStyles(styles: { [property: string]: any; }): Animator {
this._afterStyles = styles;
return this;
}
@ -305,7 +301,7 @@ export class Animation {
* Clear CSS inline styles from this animation's elements
* after the animation finishes.
*/
afterClearStyles(propertyNames: string[]): Animation {
afterClearStyles(propertyNames: string[]): Animator {
this._afterStyles = this._afterStyles || {};
for (var i = 0; i < propertyNames.length; i++) {
this._afterStyles[propertyNames[i]] = '';
@ -689,7 +685,7 @@ export class Animation {
} else {
for (j = 0; j < nuElements; j++) {
// ******** DOM WRITE ****************
(<any>elements[j].style)[prop] = val;
(elements[j].style as any)[prop] = val;
}
}
}
@ -704,7 +700,7 @@ export class Animation {
for (i = 0; i < elements.length; i++) {
// ******** DOM WRITE ****************
(<any>elements[i].style)[CSS_PROP.transformProp] = finalTransform;
(elements[i].style as any)[CSS_PROP.transformProp] = finalTransform;
}
}
}
@ -818,7 +814,7 @@ export class Animation {
if (this._beforeStyles) {
for (prop in this._beforeStyles) {
// ******** DOM WRITE ****************
(<any>elm).style[prop] = this._beforeStyles[prop];
(elm as any).style[prop] = this._beforeStyles[prop];
}
}
}
@ -887,7 +883,7 @@ export class Animation {
// remove the transition duration/easing
// ******** DOM WRITE ****************
(<any>elm).style[CSS_PROP.transitionDurationProp] = (<any>elm).style[CSS_PROP.transitionTimingFnProp] = '';
(elm as any).style[CSS_PROP.transitionDurationProp] = (elm as any).style[CSS_PROP.transitionTimingFnProp] = '';
if (this._isReverse) {
// finished in reverse direction
@ -912,7 +908,7 @@ export class Animation {
if (this._beforeStyles) {
for (prop in this._beforeStyles) {
// ******** DOM WRITE ****************
(<any>elm).style[prop] = '';
(elm as any).style[prop] = '';
}
}
@ -939,7 +935,7 @@ export class Animation {
if (this._afterStyles) {
for (prop in this._afterStyles) {
// ******** DOM WRITE ****************
(<any>elm).style[prop] = this._afterStyles[prop];
(elm as any).style[prop] = this._afterStyles[prop];
}
}
}
@ -976,7 +972,7 @@ export class Animation {
for (i = 0; i < this._elementTotal; i++) {
// ******** DOM WRITE ****************
(<any>this._elements[i]).style.willChange = willChange;
(this._elements[i] as any).style.willChange = willChange;
}
}
@ -1117,7 +1113,7 @@ export class Animation {
/**
* Add a callback to fire when the animation has finished.
*/
onFinish(callback: (animation?: Animation) => void, opts?: {oneTimeCallback?: boolean, clearExistingCallacks?: boolean}): Animation {
onFinish(callback: (animation?: any) => void, opts?: {oneTimeCallback?: boolean, clearExistingCallacks?: boolean}): Animator {
if (opts && opts.clearExistingCallacks) {
this._onFinishCallbacks = this._onFinishOneTimeCallbacks = undefined;
}
@ -1176,7 +1172,7 @@ export class Animation {
/**
* Reverse the animation.
*/
reverse(shouldReverse?: boolean): Animation {
reverse(shouldReverse?: boolean): Animator {
if (shouldReverse === undefined) {
shouldReverse = true;
}

View File

@ -1,15 +1,16 @@
import { Animation } from '../../../index';
/**
* iOS Loading Enter Animation
*/
export default function(baseElm: HTMLElement) {
const baseAnimation = new Ionic.Animation();
export default function(Animation: Animation, baseElm: HTMLElement) {
const baseAnimation = new Animation();
const backdropAnimation = new Ionic.Animation();
const backdropAnimation = new Animation();
backdropAnimation.addElement(baseElm.querySelector('.loading-backdrop'));
const wrapperAnimation = new Ionic.Animation();
const wrapperAnimation = new Animation();
wrapperAnimation.addElement(baseElm.querySelector('.loading-wrapper'));
backdropAnimation.fromTo('opacity', 0.01, 0.3);

View File

@ -1,15 +1,16 @@
import { Animation } from '../../../index';
/**
* iOS Loading Leave Animation
*/
export default function(baseElm: HTMLElement) {
const baseAnimation = new Ionic.Animation();
export default function(Animation: Animation, baseElm: HTMLElement) {
const baseAnimation = new Animation();
const backdropAnimation = new Ionic.Animation();
const backdropAnimation = new Animation();
backdropAnimation.addElement(baseElm.querySelector('.loading-backdrop'));
const wrapperAnimation = new Ionic.Animation();
const wrapperAnimation = new Animation();
wrapperAnimation.addElement(baseElm.querySelector('.loading-wrapper'));
backdropAnimation.fromTo('opacity', 0.3, 0);

View File

@ -1,5 +1,5 @@
import { Animation, AnimationBuilder, Ionic } from '../../index';
import { Component, Element, Event, EventEmitter, Listen, Prop, State } from '@stencil/core';
import { AnimationBuilder, Animation } from '../../index';
import iOSEnterAnimation from './animations/ios.enter';
import iOSLeaveAnimation from './animations/ios.leave';
@ -98,13 +98,17 @@ export class Loading {
}
// build the animation and kick it off
this.animation = animationBuilder(this.el);
Ionic.controller('animation').then(Animation => {
this.animation = new Animation();
this.animation.onFinish((a: any) => {
a.destroy();
this.ionViewDidEnter();
resolve();
}).play();
});
}
dismiss() {
@ -115,7 +119,8 @@ export class Loading {
this.animation = null;
}
return new Promise<void>(resolve => {
return Ionic.controller('animation').then(Animation => {
return new Promise(resolve => {
this.ionLoadingWillDismiss.emit({ loading: this } as LoadingEvent);
// get the user's animation fn if one was provided
@ -129,7 +134,7 @@ export class Loading {
}
// build the animation and kick it off
this.animation = animationBuilder(this.el);
this.animation = animationBuilder(Animation, this.el);
this.animation.onFinish((a: any) => {
a.destroy();
this.ionLoadingDidDismiss.emit({ loading: this } as LoadingEvent);
@ -139,8 +144,10 @@ export class Loading {
});
resolve();
}).play();
});
});
}
ionViewDidUnload() {

View File

@ -1,4 +1,4 @@
import { Animation, Menu } from '../../index';
import { Animation } from '../../index';
/**
@ -14,11 +14,13 @@ export class MenuType {
isOpening: boolean;
constructor() {
this.ani = new Ionic.Animation();
this.ani
.easing('cubic-bezier(0.0, 0.0, 0.2, 1)')
.easingReverse('cubic-bezier(0.4, 0.0, 0.6, 1)')
.duration(280);
// Ionic.createAnimation().then(Animation => {
// this.ani = new Animation();
// });;
// this.ani
// .easing('cubic-bezier(0.0, 0.0, 0.2, 1)')
// .easingReverse('cubic-bezier(0.4, 0.0, 0.6, 1)')
// .duration(280);
}
setOpen(shouldOpen: boolean, animated: boolean, done: (animation: Animation) => void) {
@ -80,14 +82,14 @@ export class MenuType {
*/
export class MenuRevealType extends MenuType {
constructor(menu: Menu) {
super();
// constructor(menu: Menu) {
// super();
const openedX = (menu.width() * (menu.isRightSide ? -1 : 1)) + 'px';
const contentOpen = new Ionic.Animation(menu.getContentElement());
contentOpen.fromTo('translateX', '0px', openedX);
this.ani.add(contentOpen);
}
// const openedX = (menu.width() * (menu.isRightSide ? -1 : 1)) + 'px';
// const contentOpen = Ionic.createAnimation(menu.getContentElement());
// contentOpen.fromTo('translateX', '0px', openedX);
// this.ani.add(contentOpen);
// }
}
@ -100,32 +102,32 @@ export class MenuRevealType extends MenuType {
*/
export class MenuPushType extends MenuType {
constructor(menu: Menu) {
super();
// constructor(menu: Menu) {
// super();
let contentOpenedX: string, menuClosedX: string, menuOpenedX: string;
const width = menu.width();
// let contentOpenedX: string, menuClosedX: string, menuOpenedX: string;
// const width = menu.width();
if (menu.isRightSide) {
// right side
contentOpenedX = -width + 'px';
menuClosedX = width + 'px';
menuOpenedX = '0px';
// if (menu.isRightSide) {
// // right side
// contentOpenedX = -width + 'px';
// menuClosedX = width + 'px';
// menuOpenedX = '0px';
} else {
contentOpenedX = width + 'px';
menuOpenedX = '0px';
menuClosedX = -width + 'px';
}
// } else {
// contentOpenedX = width + 'px';
// menuOpenedX = '0px';
// menuClosedX = -width + 'px';
// }
const menuAni = new Ionic.Animation(menu.getMenuElement());
menuAni.fromTo('translateX', menuClosedX, menuOpenedX);
this.ani.add(menuAni);
// const menuAni = Ionic.createAnimation(menu.getMenuElement());
// menuAni.fromTo('translateX', menuClosedX, menuOpenedX);
// this.ani.add(menuAni);
const contentApi = new Ionic.Animation(menu.getContentElement());
contentApi.fromTo('translateX', '0px', contentOpenedX);
this.ani.add(contentApi);
}
// const contentApi = Ionic.createAnimation(menu.getContentElement());
// contentApi.fromTo('translateX', '0px', contentOpenedX);
// this.ani.add(contentApi);
// }
}
@ -138,30 +140,30 @@ export class MenuPushType extends MenuType {
*/
export class MenuOverlayType extends MenuType {
constructor(menu: Menu) {
super();
// constructor(menu: Menu) {
// super();
let closedX: string, openedX: string;
const width = menu.width();
// let closedX: string, openedX: string;
// const width = menu.width();
if (menu.isRightSide) {
// right side
closedX = 8 + width + 'px';
openedX = '0px';
// if (menu.isRightSide) {
// // right side
// closedX = 8 + width + 'px';
// openedX = '0px';
} else {
// left side
closedX = -(8 + width) + 'px';
openedX = '0px';
}
// } else {
// // left side
// closedX = -(8 + width) + 'px';
// openedX = '0px';
// }
const menuAni = new Ionic.Animation(menu.getMenuElement());
menuAni.fromTo('translateX', closedX, openedX);
this.ani.add(menuAni);
// const menuAni = Ionic.createAnimation(menu.getMenuElement());
// menuAni.fromTo('translateX', closedX, openedX);
// this.ani.add(menuAni);
const backdropApi = new Ionic.Animation(menu.getBackdropElement());
backdropApi.fromTo('opacity', 0.01, 0.35);
this.ani.add(backdropApi);
}
// const backdropApi = Ionic.createAnimation(menu.getBackdropElement());
// backdropApi.fromTo('opacity', 0.01, 0.35);
// this.ani.add(backdropApi);
// }
}

View File

@ -1,4 +1,5 @@
import { Component, Element, Event, EventEmitter, Prop, PropDidChange } from '@stencil/core';
import { Ionic } from '../../index';
import { MenuController } from './menu-controller';
import { MenuType } from './menu-types';
@ -97,12 +98,6 @@ export class Menu {
@Prop() maxEdgeStart: number;
constructor() {
// get or create the MenuController singleton
this._ctrl = Ionic.controllers.menu = (Ionic.controllers.menu || new MenuController());
}
/**
* @hidden
*/

View File

@ -1,15 +1,16 @@
import { Animation } from '../../../index';
/**
* iOS Modal Enter Animation
*/
export default function(baseElm: HTMLElement) {
const baseAnimation = new Ionic.Animation();
export default function(Animation: Animation, baseElm: HTMLElement) {
const baseAnimation = new Animation();
const backdropAnimation = new Ionic.Animation();
const backdropAnimation = new Animation();
backdropAnimation.addElement(baseElm.querySelector('.modal-backdrop'));
const wrapperAnimation = new Ionic.Animation();
const wrapperAnimation = new Animation();
wrapperAnimation.addElement(baseElm.querySelector('.modal-wrapper'));
wrapperAnimation.beforeStyles({ 'opacity': 1 })

View File

@ -1,15 +1,16 @@
import { Animation } from '../../../index';
/**
* iOS Modal Leave Animation
*/
export default function(baseElm: HTMLElement) {
const baseAnimation = new Ionic.Animation();
export default function(Animation: Animation, baseElm: HTMLElement) {
const baseAnimation = new Animation();
const backdropAnimation = new Ionic.Animation();
const backdropAnimation = new Animation();
backdropAnimation.addElement(baseElm.querySelector('.modal-backdrop'));
const wrapperAnimation = new Ionic.Animation();
const wrapperAnimation = new Animation();
const wrapperElm = baseElm.querySelector('.modal-wrapper');
wrapperAnimation.addElement(wrapperElm);
const wrapperElmRect = wrapperElm.getBoundingClientRect();

View File

@ -4,6 +4,7 @@ exports.config = {
publicPath: '/dist',
generateCollection: true,
bundles: [
{ components: ['ion-animation-controller'] },
{ components: ['ion-app', 'ion-content', 'ion-fixed', 'ion-footer', 'ion-header', 'ion-navbar', 'ion-page', 'ion-title', 'ion-toolbar'] },
{ components: ['ion-avatar', 'ion-badge', 'ion-thumbnail'] },
{ components: ['ion-button', 'ion-buttons', 'ion-icon'] },