refactor(toast): make api and events match other overlays

This commit is contained in:
Dan Bucholtz
2017-12-02 01:02:20 -06:00
parent 3f6b937cec
commit dd7a3f140f
3 changed files with 84 additions and 65 deletions

View File

@ -1,5 +1,5 @@
import { Component, Listen, Method } from '@stencil/core'; import { Component, Listen, Method } from '@stencil/core';
import { Toast, ToastEvent, ToastOptions } from '../../index'; import { ToastEvent, ToastOptions } from '../../index';
@Component({ @Component({
tag: 'ion-toast-controller' tag: 'ion-toast-controller'
@ -7,10 +7,10 @@ import { Toast, ToastEvent, ToastOptions } from '../../index';
export class ToastController { export class ToastController {
private ids = 0; private ids = 0;
private toastResolves: { [toastId: string]: Function } = {}; private toastResolves: { [toastId: string]: Function } = {};
private toasts: Toast[] = []; private toasts: HTMLIonToastElement[] = [];
@Method() @Method()
create(opts?: ToastOptions) { create(opts?: ToastOptions): Promise<HTMLIonToastElement> {
// create ionic's wrapping ion-toast component // create ionic's wrapping ion-toast component
const toast = document.createElement('ion-toast'); const toast = document.createElement('ion-toast');
const id = this.ids++; const id = this.ids++;
@ -28,14 +28,14 @@ export class ToastController {
appRoot.appendChild(toast as any); appRoot.appendChild(toast as any);
// store the resolve function to be called later up when the toast loads // store the resolve function to be called later up when the toast loads
return new Promise<Toast>(resolve => { return new Promise<HTMLIonToastElement>(resolve => {
this.toastResolves[toast.toastId] = resolve; this.toastResolves[toast.toastId] = resolve;
}); });
} }
@Listen('body:ionToastDidLoad') @Listen('body:ionToastDidLoad')
protected didLoad(ev: ToastEvent) { protected didLoad(ev: ToastEvent) {
const toast = ev.detail.toast; const toast = ev.target as HTMLIonToastElement;
const toastResolve = this.toastResolves[toast.toastId]; const toastResolve = this.toastResolves[toast.toastId];
if (toastResolve) { if (toastResolve) {
toastResolve(toast); toastResolve(toast);
@ -45,12 +45,12 @@ export class ToastController {
@Listen('body:ionToastWillPresent') @Listen('body:ionToastWillPresent')
protected willPresent(ev: ToastEvent) { protected willPresent(ev: ToastEvent) {
this.toasts.push(ev.detail.toast); this.toasts.push(ev.target as HTMLIonToastElement);
} }
@Listen('body:ionToastWillDismiss, body:ionToastDidUnload') @Listen('body:ionToastWillDismiss, body:ionToastDidUnload')
protected willDismiss(ev: ToastEvent) { protected willDismiss(ev: ToastEvent) {
const index = this.toasts.indexOf(ev.detail.toast); const index = this.toasts.indexOf(ev.target as HTMLIonToastElement);
if (index > -1) { if (index > -1) {
this.toasts.splice(index, 1); this.toasts.splice(index, 1);
} }

View File

@ -59,22 +59,22 @@
</ion-page> </ion-page>
</ion-app> </ion-app>
<script> <script>
function presentToast(position) { async function presentToast(position) {
var toastController = document.querySelector('ion-toast-controller'); const toastController = document.querySelector('ion-toast-controller');
toastController.create({ await toastController.componentOnReady();
const toastElement = await toastController.create({
message: 'Hellooo', message: 'Hellooo',
position, position,
duration: 2000 duration: 2000
}).then(toast => {
toast.present();
}); });
return await toastElement.present();
} }
function presentToastWithOptions(opts) { async function presentToastWithOptions(opts) {
var toastController = document.querySelector('ion-toast-controller'); const toastController = document.querySelector('ion-toast-controller');
toastController.create(opts).then(toast => { await toastController.componentOnReady();
toast.present(); const toastElement = await toastController.create(opts);
}); return await toastElement.present();
} }
</script> </script>

View File

@ -1,5 +1,14 @@
import { Component, Element, Event, EventEmitter, Listen, Prop } from '@stencil/core'; import { Component, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core';
import { Animation, AnimationBuilder, AnimationController, Config, CssClassMap } from '../../index'; import {
Animation,
AnimationBuilder,
AnimationController,
Config,
CssClassMap,
OverlayDismissEvent,
OverlayDismissEventDetail
} from '../../index';
import { domControllerAsync, playAnimationAsync } from '../../utils/helpers';
import { createThemedClasses } from '../../utils/theme'; import { createThemedClasses } from '../../utils/theme';
@ -30,32 +39,32 @@ export class Toast {
/** /**
* @output {ToastEvent} Emitted after the toast has loaded. * @output {ToastEvent} Emitted after the toast has loaded.
*/ */
@Event() ionToastDidLoad: EventEmitter; @Event() ionToastDidLoad: EventEmitter<ToastEventDetail>;
/** /**
* @output {ToastEvent} Emitted after the toast has presented. * @output {ToastEvent} Emitted after the toast has presented.
*/ */
@Event() ionToastDidPresent: EventEmitter; @Event() ionToastDidPresent: EventEmitter<ToastEventDetail>;
/** /**
* @output {ToastEvent} Emitted before the toast has presented. * @output {ToastEvent} Emitted before the toast has presented.
*/ */
@Event() ionToastWillPresent: EventEmitter; @Event() ionToastWillPresent: EventEmitter<ToastEventDetail>;
/** /**
* @output {ToastEvent} Emitted before the toast has dismissed. * @output {ToastEvent} Emitted before the toast has dismissed.
*/ */
@Event() ionToastWillDismiss: EventEmitter; @Event() ionToastWillDismiss: EventEmitter<ToastDismissEventDetail>;
/** /**
* @output {ToastEvent} Emitted after the toast has dismissed. * @output {ToastEvent} Emitted after the toast has dismissed.
*/ */
@Event() ionToastDidDismiss: EventEmitter; @Event() ionToastDidDismiss: EventEmitter<ToastDismissEventDetail>;
/** /**
* @output {ToastEvent} Emitted after the toast has unloaded. * @output {ToastEvent} Emitted after the toast has unloaded.
*/ */
@Event() ionToastDidUnload: EventEmitter; @Event() ionToastDidUnload: EventEmitter<ToastEventDetail>;
@Prop({ connect: 'ion-animation-controller' }) animationCtrl: AnimationController; @Prop({ connect: 'ion-animation-controller' }) animationCtrl: AnimationController;
@Prop({ context: 'config' }) config: Config; @Prop({ context: 'config' }) config: Config;
@ -69,73 +78,73 @@ export class Toast {
@Prop() position: string; @Prop() position: string;
@Prop() translucent: boolean = false; @Prop() translucent: boolean = false;
@Prop() toastId: string; @Prop() toastId: string;
@Prop() animate: boolean;
@Prop() enterAnimation: AnimationBuilder; @Prop() enterAnimation: AnimationBuilder;
@Prop() leaveAnimation: AnimationBuilder; @Prop() leaveAnimation: AnimationBuilder;
@Method()
present() { present() {
return new Promise<void>(resolve => {
this._present(resolve);
});
}
private _present(resolve: Function) {
if (this.animation) { if (this.animation) {
this.animation.destroy(); this.animation.destroy();
this.animation = null; this.animation = null;
} }
this.ionToastWillPresent.emit({ toast: this }); this.ionToastWillPresent.emit();
// get the user's animation fn if one was provided // get the user's animation fn if one was provided
const animationBuilder = this.enterAnimation || this.config.get('toastEnter', this.mode === 'ios' ? iosEnterAnimation : mdEnterAnimation); const animationBuilder = this.enterAnimation || this.config.get('toastEnter', this.mode === 'ios' ? iosEnterAnimation : mdEnterAnimation);
// build the animation and kick it off // build the animation and kick it off
this.animationCtrl.create(animationBuilder, this.el, this.position).then(animation => { return this.animationCtrl.create(animationBuilder, this.el).then(animation => {
this.animation = animation; this.animation = animation;
if (!this.animate) {
animation.onFinish((a: any) => { // if the duration is 0, it won't actually animate I don't think
a.destroy(); // TODO - validate this
this.componentDidEnter(); this.animation = animation.duration(0);
resolve(); }
}).play(); return playAnimationAsync(animation);
}).then((animation) => {
animation.destroy();
this.componentDidEnter();
}); });
} }
dismiss() { @Method()
dismiss(data?: any, role?: string) {
if (this.animation) { if (this.animation) {
this.animation.destroy(); this.animation.destroy();
this.animation = null; this.animation = null;
} }
return new Promise(resolve => {
this.ionToastWillDismiss.emit({ toast: this });
// get the user's animation fn if one was provided this.ionToastWillDismiss.emit({
const animationBuilder = this.leaveAnimation || this.config.get('toastLeave', this.mode === 'ios' ? iosLeaveAnimation : mdLeaveAnimation); data,
role
});
// build the animation and kick it off const animationBuilder = this.leaveAnimation || this.config.get('toastLeave', this.mode === 'ios' ? iosLeaveAnimation : mdLeaveAnimation);
this.animationCtrl.create(animationBuilder, this.el, this.position).then(animation => {
this.animation = animation;
animation.onFinish((a: any) => { return this.animationCtrl.create(animationBuilder, this.el).then(animation => {
a.destroy(); this.animation = animation;
this.ionToastDidDismiss.emit({ toast: this }); return playAnimationAsync(animation);
}).then((animation) => {
Context.dom.write(() => { animation.destroy();
this.el.parentNode.removeChild(this.el); return domControllerAsync(Context.dom.write, () => {
}); this.el.parentNode.removeChild(this.el);
});
resolve(); }).then(() => {
}).play(); this.ionToastDidDismiss.emit({
data,
role
}); });
}); });
} }
componentDidLoad() { componentDidLoad() {
this.ionToastDidLoad.emit({ toast: this }); this.ionToastDidLoad.emit();
} }
componentDidEnter() { componentDidEnter() {
this.ionToastDidPresent.emit({ toast: this }); this.ionToastDidPresent.emit();
if (this.duration) { if (this.duration) {
setTimeout(() => { setTimeout(() => {
this.dismiss(); this.dismiss();
@ -144,7 +153,7 @@ export class Toast {
} }
componentDidUnload() { componentDidUnload() {
this.ionToastDidUnload.emit({ toast: this }); this.ionToastDidUnload.emit();
} }
@Listen('ionDismiss') @Listen('ionDismiss')
@ -190,7 +199,7 @@ export class Toast {
? <div class='toast-message'>{this.message}</div> ? <div class='toast-message'>{this.message}</div>
: null} : null}
{this.showCloseButton {this.showCloseButton
? <ion-button fill="clear" color='light' class='toast-button' onClick={() => this.dismiss()}> ? <ion-button fill='clear' color='light' class='toast-button' onClick={() => this.dismiss()}>
{this.closeButtonText || 'Close'} {this.closeButtonText || 'Close'}
</ion-button> </ion-button>
: null} : null}
@ -214,10 +223,20 @@ export interface ToastOptions {
exitAnimation?: AnimationBuilder; exitAnimation?: AnimationBuilder;
} }
export interface ToastEvent { export interface ToastEvent extends CustomEvent {
detail: { detail: ToastEventDetail;
toast: Toast; }
};
export interface ToastEventDetail {
}
export interface ToastDismissEventDetail extends OverlayDismissEventDetail {
// keep this just for the sake of static types and potential future extensions
}
export interface ToastDismissEvent extends OverlayDismissEvent {
// keep this just for the sake of static types and potential future extensions
} }
export { export {