fix(overlays): missing events + playAnimation

This commit is contained in:
Manu Mtz.-Almeida
2018-02-23 19:41:03 +01:00
parent d26afdfea0
commit 11b65c245b
7 changed files with 205 additions and 258 deletions

View File

@ -131,7 +131,7 @@ export class ActionSheet implements OverlayInterface {
@Listen('ionBackdropTap') @Listen('ionBackdropTap')
protected onBackdropTap() { protected onBackdropTap() {
this.dismiss(null, BACKDROP); this.dismiss(null, BACKDROP).catch();
} }
/** /**

View File

@ -1,6 +1,6 @@
import { Component, CssClassMap, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core'; import { Component, CssClassMap, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core';
import { Animation, AnimationBuilder, AnimationController, Config, DomController, OverlayDismissEvent, OverlayDismissEventDetail } from '../../index'; import { Animation, AnimationBuilder, AnimationController, Config, DomController, OverlayDismissEvent, OverlayDismissEventDetail } from '../../index';
import { domControllerAsync, playAnimationAsync } from '../../utils/helpers'; import { domControllerAsync, playAnimationAsync, autoFocus } from '../../utils/helpers';
import { createThemedClasses, getClassMap } from '../../utils/theme'; import { createThemedClasses, getClassMap } from '../../utils/theme';
import { OverlayInterface, BACKDROP } from '../../utils/overlays'; import { OverlayInterface, BACKDROP } from '../../utils/overlays';
@ -137,7 +137,7 @@ export class Alert implements OverlayInterface {
@Listen('ionBackdropTap') @Listen('ionBackdropTap')
protected onBackdropTap() { protected onBackdropTap() {
this.dismiss(null, BACKDROP); this.dismiss(null, BACKDROP).catch();
} }
/** /**
@ -158,10 +158,7 @@ export class Alert implements OverlayInterface {
// build the animation and kick it off // build the animation and kick it off
return this.playAnimation(animationBuilder).then(() => { return this.playAnimation(animationBuilder).then(() => {
const firstInput = this.el.querySelector('[tabindex]') as HTMLElement; autoFocus(this.el);
if (firstInput) {
firstInput.focus();
}
this.ionAlertDidPresent.emit(); this.ionAlertDidPresent.emit();
}); });
} }
@ -181,6 +178,8 @@ export class Alert implements OverlayInterface {
const animationBuilder = this.leaveAnimation || this.config.get('alertLeave', this.mode === 'ios' ? iosLeaveAnimation : mdLeaveAnimation); const animationBuilder = this.leaveAnimation || this.config.get('alertLeave', this.mode === 'ios' ? iosLeaveAnimation : mdLeaveAnimation);
return this.playAnimation(animationBuilder).then(() => { return this.playAnimation(animationBuilder).then(() => {
this.ionAlertDidDismiss.emit({data, role});
return domControllerAsync(this.dom.write, () => { return domControllerAsync(this.dom.write, () => {
this.el.parentNode.removeChild(this.el); this.el.parentNode.removeChild(this.el);
}); });
@ -274,7 +273,6 @@ export class Alert implements OverlayInterface {
return this.animationCtrl.create(animationBuilder, this.el).then(animation => { return this.animationCtrl.create(animationBuilder, this.el).then(animation => {
this.animation = animation; this.animation = animation;
if (!this.willAnimate) { if (!this.willAnimate) {
// if the duration is 0, it won't actually animate I don't think
animation.duration(0); animation.duration(0);
} }
return playAnimationAsync(animation); return playAnimationAsync(animation);

View File

@ -129,19 +129,6 @@ export class Loading implements OverlayInterface {
this.ionLoadingDidLoad.emit(); this.ionLoadingDidLoad.emit();
} }
componentDidEnter() {
// blur the currently active element
const activeElement: any = document.activeElement;
activeElement && activeElement.blur && activeElement.blur();
// If there is a duration, dismiss after that amount of time
if (typeof this.duration === 'number' && this.duration > 10) {
this.durationTimeout = setTimeout(() => this.dismiss(), this.duration);
}
this.ionLoadingDidPresent.emit();
}
componentDidUnload() { componentDidUnload() {
this.ionLoadingDidUnload.emit(); this.ionLoadingDidUnload.emit();
} }
@ -156,7 +143,7 @@ export class Loading implements OverlayInterface {
@Listen('ionBackdropTap') @Listen('ionBackdropTap')
protected onBackdropTap() { protected onBackdropTap() {
this.dismiss(null, BACKDROP); this.dismiss(null, BACKDROP).catch();
} }
/** /**
@ -177,7 +164,15 @@ export class Loading implements OverlayInterface {
// build the animation and kick it off // build the animation and kick it off
return this.playAnimation(animationBuilder).then(() => { return this.playAnimation(animationBuilder).then(() => {
this.componentDidEnter(); // blur the currently active element
const activeElement: any = document.activeElement;
activeElement && activeElement.blur && activeElement.blur();
// If there is a duration, dismiss after that amount of time
if (typeof this.duration === 'number' && this.duration > 10) {
this.durationTimeout = setTimeout(() => this.dismiss(), this.duration);
}
this.ionLoadingDidPresent.emit();
}); });
} }

View File

@ -139,7 +139,7 @@ export class Modal implements OverlayInterface {
@Listen('ionBackdropTap') @Listen('ionBackdropTap')
protected onBackdropTap() { protected onBackdropTap() {
this.dismiss(null, BACKDROP); this.dismiss(null, BACKDROP).catch();
} }
/** /**
@ -151,12 +151,6 @@ export class Modal implements OverlayInterface {
return Promise.reject('overlay already presented'); return Promise.reject('overlay already presented');
} }
this.presented = true; this.presented = true;
if (this.animation) {
this.animation.destroy();
this.animation = null;
}
this.ionModalWillPresent.emit(); this.ionModalWillPresent.emit();
this.el.style.zIndex = `${20000 + this.overlayId}`; this.el.style.zIndex = `${20000 + this.overlayId}`;
@ -177,19 +171,11 @@ export class Modal implements OverlayInterface {
// add the modal by default to the data being passed // add the modal by default to the data being passed
this.data = this.data || {}; this.data = this.data || {};
this.data.modal = this.el; this.data.modal = this.el;
this.delegate.attachViewToDom(userComponentParent, this.component, this.data, cssClasses)
.then((mountingData) => {
this.usersComponentElement = mountingData.element;
});
return this.animationCtrl.create(animationBuilder, this.el) return this.delegate.attachViewToDom(userComponentParent, this.component, this.data, cssClasses)
.then(animation => { .then((mountingData) => this.usersComponentElement = mountingData.element)
this.animation = animation; .then(() => this.playAnimation(animationBuilder))
if (!this.willAnimate) this.animation = animation.duration(0); .then(() => {
return playAnimationAsync(animation);
})
.then((animation) => {
animation.destroy();
this.ionModalDidPresent.emit(); this.ionModalDidPresent.emit();
}); });
} }
@ -203,10 +189,6 @@ export class Modal implements OverlayInterface {
return Promise.reject('overlay is not presented'); return Promise.reject('overlay is not presented');
} }
this.presented = false; this.presented = false;
if (this.animation) {
this.animation.destroy();
this.animation = null;
}
this.ionModalWillDismiss.emit({data, role}); this.ionModalWillDismiss.emit({data, role});
if (!this.delegate) { if (!this.delegate) {
@ -216,20 +198,8 @@ export class Modal implements OverlayInterface {
// get the user's animation fn if one was provided // get the user's animation fn if one was provided
const animationBuilder = this.leaveAnimation || this.config.get('modalLeave', this.mode === 'ios' ? iosLeaveAnimation : mdLeaveAnimation); const animationBuilder = this.leaveAnimation || this.config.get('modalLeave', this.mode === 'ios' ? iosLeaveAnimation : mdLeaveAnimation);
return this.playAnimation(animationBuilder).then(() => {
return this.animationCtrl.create(animationBuilder, this.el)
.then(animation => {
this.animation = animation;
if (!this.willAnimate) {
this.animation = animation.duration(0);
}
return playAnimationAsync(animation);
})
.then((animation) => {
animation.destroy();
this.ionModalDidDismiss.emit({data, role}); this.ionModalDidDismiss.emit({data, role});
})
.then(() => {
return domControllerAsync(this.dom.write, () => { return domControllerAsync(this.dom.write, () => {
const userComponentParent = this.el.querySelector(`.${USER_COMPONENT_MODAL_CONTAINER_CLASS}`); const userComponentParent = this.el.querySelector(`.${USER_COMPONENT_MODAL_CONTAINER_CLASS}`);
this.delegate.removeViewFromDom(userComponentParent, this.usersComponentElement); this.delegate.removeViewFromDom(userComponentParent, this.usersComponentElement);
@ -238,6 +208,24 @@ export class Modal implements OverlayInterface {
}); });
} }
private playAnimation(animationBuilder: AnimationBuilder) {
if (this.animation) {
this.animation.destroy();
this.animation = null;
}
return this.animationCtrl.create(animationBuilder, this.el).then(animation => {
this.animation = animation;
if (!this.willAnimate) {
animation.duration(0);
}
return playAnimationAsync(animation);
}).then((animation) => {
animation.destroy();
this.animation = null;
});
}
@Method() @Method()
getUserComponentContainer(): HTMLElement { getUserComponentContainer(): HTMLElement {
return this.el.querySelector(`.${USER_COMPONENT_MODAL_CONTAINER_CLASS}`); return this.el.querySelector(`.${USER_COMPONENT_MODAL_CONTAINER_CLASS}`);

View File

@ -111,6 +111,47 @@ export class Picker implements OverlayInterface {
*/ */
@Event() ionPickerDidUnload: EventEmitter<PickerEventDetail>; @Event() ionPickerDidUnload: EventEmitter<PickerEventDetail>;
componentDidLoad() {
if (!this.spinner) {
let defaultSpinner = 'lines';
if (this.mode === 'md') {
defaultSpinner = 'crescent';
}
this.spinner = this.config.get('pickerSpinner') || defaultSpinner;
}
if (this.showSpinner === null || this.showSpinner === undefined) {
this.showSpinner = !!(this.spinner && this.spinner !== 'hide');
}
this.ionPickerDidLoad.emit();
}
componentDidUnload() {
this.ionPickerDidUnload.emit();
}
@Listen('ionDismiss')
protected onDismiss(ev: UIEvent) {
ev.stopPropagation();
ev.preventDefault();
this.dismiss();
}
@Listen('ionBackdropTap')
protected onBackdropTap() {
const cancelBtn = this.buttons.find(b => b.role === 'cancel');
if (cancelBtn) {
this.buttonClick(cancelBtn);
} else {
this.dismiss().catch();
}
}
/** /**
* Present the picker overlay after it has been created. * Present the picker overlay after it has been created.
*/ */
@ -134,17 +175,16 @@ export class Picker implements OverlayInterface {
const animationBuilder = this.enterAnimation || this.config.get('pickerEnter', iosEnterAnimation); const animationBuilder = this.enterAnimation || this.config.get('pickerEnter', iosEnterAnimation);
// build the animation and kick it off // build the animation and kick it off
return this.animationCtrl.create(animationBuilder, this.el).then(animation => { return this.playAnimation(animationBuilder).then(() => {
this.animation = animation; // blur the currently active element
if (!this.willAnimate) { const activeElement: any = document.activeElement;
// if the duration is 0, it won't actually animate I don't think activeElement && activeElement.blur && activeElement.blur();
// TODO - validate this
this.animation = animation.duration(0); // If there is a duration, dismiss after that amount of time
if (typeof this.duration === 'number' && this.duration > 10) {
this.durationTimeout = setTimeout(() => this.dismiss(), this.duration);
} }
return playAnimationAsync(animation); this.ionPickerDidPresent.emit();
}).then((animation) => {
animation.destroy();
this.componentDidEnter();
}); });
} }
@ -159,120 +199,16 @@ export class Picker implements OverlayInterface {
this.presented = false; this.presented = false;
clearTimeout(this.durationTimeout); clearTimeout(this.durationTimeout);
if (this.animation) { this.ionPickerWillDismiss.emit({data, role});
this.animation.destroy();
this.animation = null;
}
this.ionPickerWillDismiss.emit({
data,
role
});
const animationBuilder = this.leaveAnimation || this.config.get('pickerLeave', iosLeaveAnimation); const animationBuilder = this.leaveAnimation || this.config.get('pickerLeave', iosLeaveAnimation);
return this.animationCtrl.create(animationBuilder, this.el).then(animation => { return this.playAnimation(animationBuilder).then(() => {
this.animation = animation; this.ionPickerDidDismiss.emit({data, role});
return playAnimationAsync(animation);
}).then((animation) => {
animation.destroy();
return domControllerAsync(this.dom.write, () => { return domControllerAsync(this.dom.write, () => {
this.el.parentNode.removeChild(this.el); this.el.parentNode.removeChild(this.el);
}); });
}).then(() => {
this.ionPickerDidDismiss.emit({
data,
role
}); });
});
}
componentDidLoad() {
if (!this.spinner) {
let defaultSpinner = 'lines';
if (this.mode === 'md') {
defaultSpinner = 'crescent';
}
this.spinner = this.config.get('pickerSpinner') || defaultSpinner;
}
if (this.showSpinner === null || this.showSpinner === undefined) {
this.showSpinner = !!(this.spinner && this.spinner !== 'hide');
}
this.ionPickerDidLoad.emit();
}
componentDidEnter() {
// blur the currently active element
const activeElement: any = document.activeElement;
activeElement && activeElement.blur && activeElement.blur();
// If there is a duration, dismiss after that amount of time
if (typeof this.duration === 'number' && this.duration > 10) {
this.durationTimeout = setTimeout(() => this.dismiss(), this.duration);
}
this.ionPickerDidPresent.emit();
}
componentDidUnload() {
this.ionPickerDidUnload.emit();
}
@Listen('ionDismiss')
protected onDismiss(ev: UIEvent) {
ev.stopPropagation();
ev.preventDefault();
this.dismiss();
}
@Listen('ionBackdropTap')
protected onBackdropTap() {
const cancelBtn = this.buttons.find(b => b.role === 'cancel');
if (cancelBtn) {
this.buttonClick(cancelBtn);
} else {
this.dismiss();
}
}
buttonClick(button: PickerButton) {
// if (this.disabled) {
// return;
// }
// keep the time of the most recent button click
let shouldDismiss = true;
if (button.handler) {
// a handler has been provided, execute it
// pass the handler the values from the inputs
if (button.handler(this.getSelected()) === false) {
// if the return value of the handler is false then do not dismiss
shouldDismiss = false;
}
}
if (shouldDismiss) {
this.dismiss();
}
}
getSelected(): any {
const selected: {[k: string]: any} = {};
this.columns.forEach((col, index) => {
const selectedColumn = col.options[col.selectedIndex];
selected[col.name] = {
text: selectedColumn ? selectedColumn.text : null,
value: selectedColumn ? selectedColumn.value : null,
columnIndex: index,
};
});
return selected;
} }
@Method() @Method()
@ -295,11 +231,63 @@ export class Picker implements OverlayInterface {
return this.columns; return this.columns;
} }
private playAnimation(animationBuilder: AnimationBuilder) {
if (this.animation) {
this.animation.destroy();
this.animation = null;
}
return this.animationCtrl.create(animationBuilder, this.el).then(animation => {
this.animation = animation;
if (!this.willAnimate) {
animation.duration(0);
}
return playAnimationAsync(animation);
}).then(animation => {
animation.destroy();
this.animation = null;
})
}
private buttonClick(button: PickerButton) {
// if (this.disabled) {
// return;
// }
// keep the time of the most recent button click
let shouldDismiss = true;
if (button.handler) {
// a handler has been provided, execute it
// pass the handler the values from the inputs
if (button.handler(this.getSelected()) === false) {
// if the return value of the handler is false then do not dismiss
shouldDismiss = false;
}
}
if (shouldDismiss) {
this.dismiss();
}
}
private getSelected(): any {
const selected: {[k: string]: any} = {};
this.columns.forEach((col, index) => {
const selectedColumn = col.options[col.selectedIndex];
selected[col.name] = {
text: selectedColumn ? selectedColumn.text : null,
value: selectedColumn ? selectedColumn.value : null,
columnIndex: index,
};
});
return selected;
}
render() { render() {
// TODO: cssClass // TODO: cssClass
const buttons = this.buttons const buttons = this.buttons.map(b => {
.map(b => {
if (typeof b === 'string') { if (typeof b === 'string') {
b = { text: b }; b = { text: b };
} }

View File

@ -134,10 +134,6 @@ export class Popover implements OverlayInterface {
this.ionPopoverDidLoad.emit(); this.ionPopoverDidLoad.emit();
} }
componentDidEnter() {
this.ionPopoverDidPresent.emit();
}
componentDidUnload() { componentDidUnload() {
this.ionPopoverDidUnload.emit(); this.ionPopoverDidUnload.emit();
} }
@ -152,7 +148,7 @@ export class Popover implements OverlayInterface {
@Listen('ionBackdropTap') @Listen('ionBackdropTap')
protected onBackdropTap() { protected onBackdropTap() {
this.dismiss(null, BACKDROP); this.dismiss(null, BACKDROP).catch();
} }
/** /**
@ -165,10 +161,6 @@ export class Popover implements OverlayInterface {
} }
this.presented = true; this.presented = true;
if (this.animation) {
this.animation.destroy();
this.animation = null;
}
this.ionPopoverWillPresent.emit(); this.ionPopoverWillPresent.emit();
this.el.style.zIndex = `${10000 + this.overlayId}`; this.el.style.zIndex = `${10000 + this.overlayId}`;
@ -191,19 +183,11 @@ export class Popover implements OverlayInterface {
this.data.modal = this.el; this.data.modal = this.el;
return this.delegate.attachViewToDom(userComponentParent, this.component, this.data, cssClasses) return this.delegate.attachViewToDom(userComponentParent, this.component, this.data, cssClasses)
.then((mountingData) => { .then((mountingData) => this.usersComponentElement = mountingData.element)
this.usersComponentElement = mountingData.element; .then(() => domControllerAsync(this.dom.raf))
return domControllerAsync(this.dom.raf) .then(() => this.playAnimation(animationBuilder))
.then(() => this.animationCtrl.create(animationBuilder, this.el, this.ev)); .then(() => {
}) this.ionPopoverDidPresent.emit();
.then((animation) => {
this.animation = animation;
if (!this.willAnimate) this.animation = animation.duration(0);
return playAnimationAsync(animation);
})
.then((animation) => {
animation.destroy();
this.componentDidEnter();
}); });
} }
@ -229,16 +213,9 @@ export class Popover implements OverlayInterface {
const animationBuilder = this.leaveAnimation || this.config.get('popoverLeave', this.mode === 'ios' ? iosLeaveAnimation : mdLeaveAnimation); const animationBuilder = this.leaveAnimation || this.config.get('popoverLeave', this.mode === 'ios' ? iosLeaveAnimation : mdLeaveAnimation);
return this.animationCtrl.create(animationBuilder, this.el) return this.playAnimation(animationBuilder).then(() => {
.then(animation => {
this.animation = animation;
return playAnimationAsync(animation);
})
.then((animation) => {
animation.destroy();
this.ionPopoverDidDismiss.emit({ data, role }); this.ionPopoverDidDismiss.emit({ data, role });
})
.then(() => {
return domControllerAsync(this.dom.write, () => { return domControllerAsync(this.dom.write, () => {
const userComponentParent = this.el.querySelector(`.${USER_COMPONENT_POPOVER_CONTAINER_CLASS}`); const userComponentParent = this.el.querySelector(`.${USER_COMPONENT_POPOVER_CONTAINER_CLASS}`);
this.delegate.removeViewFromDom(userComponentParent, this.usersComponentElement); this.delegate.removeViewFromDom(userComponentParent, this.usersComponentElement);
@ -247,6 +224,24 @@ export class Popover implements OverlayInterface {
}); });
} }
private playAnimation(animationBuilder: AnimationBuilder) {
if (this.animation) {
this.animation.destroy();
this.animation = null;
}
return this.animationCtrl.create(animationBuilder, this.el, this.ev).then((animation) => {
this.animation = animation;
if (!this.willAnimate) {
animation.duration(0);
}
return playAnimationAsync(animation);
}).then(animation => {
animation.destroy();
this.animation = null;
})
}
hostData() { hostData() {
const themedClasses = this.translucent ? createThemedClasses(this.mode, this.color, 'popover-translucent') : {}; const themedClasses = this.translucent ? createThemedClasses(this.mode, this.color, 'popover-translucent') : {};

View File

@ -133,27 +133,17 @@ export class Toast implements OverlayInterface {
} }
this.presented = true; this.presented = true;
if (this.animation) {
this.animation.destroy();
this.animation = null;
}
this.ionToastWillPresent.emit(); 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
return this.animationCtrl.create(animationBuilder, this.el, this.position).then(animation => { return this.playAnimation(animationBuilder).then(() => {
this.animation = animation; this.ionToastDidPresent.emit();
if (!this.willAnimate) { if (this.duration) {
// if the duration is 0, it won't actually animate I don't think setTimeout(() => this.dismiss(), this.duration);
// TODO - validate this
this.animation = animation.duration(0);
} }
return playAnimationAsync(animation);
}).then((animation) => {
animation.destroy();
this.componentDidEnter();
}); });
} }
@ -167,45 +157,38 @@ export class Toast implements OverlayInterface {
} }
this.presented = false; this.presented = false;
if (this.animation) { this.ionToastWillDismiss.emit({data, role});
this.animation.destroy();
this.animation = null;
}
this.ionToastWillDismiss.emit({
data,
role
});
const animationBuilder = this.leaveAnimation || this.config.get('toastLeave', this.mode === 'ios' ? iosLeaveAnimation : mdLeaveAnimation); const animationBuilder = this.leaveAnimation || this.config.get('toastLeave', this.mode === 'ios' ? iosLeaveAnimation : mdLeaveAnimation);
return this.animationCtrl.create(animationBuilder, this.el, this.position).then(animation => { return this.playAnimation(animationBuilder).then(() => {
this.animation = animation; this.ionToastDidDismiss.emit({data, role});
return playAnimationAsync(animation);
}).then((animation) => {
animation.destroy();
this.ionToastDidDismiss.emit({
data,
role
});
}).then(() => {
return domControllerAsync(this.dom.write, () => { return domControllerAsync(this.dom.write, () => {
this.el.parentNode.removeChild(this.el); this.el.parentNode.removeChild(this.el);
}); });
}); });
} }
componentDidLoad() { playAnimation(animationBuilder: AnimationBuilder) {
this.ionToastDidLoad.emit(); if (this.animation) {
this.animation.destroy();
this.animation = null;
} }
componentDidEnter() { return this.animationCtrl.create(animationBuilder, this.el, this.position).then(animation => {
this.ionToastDidPresent.emit(); this.animation = animation;
if (this.duration) { if (!this.willAnimate) {
setTimeout(() => { animation.duration(0);
this.dismiss();
}, this.duration);
} }
return playAnimationAsync(animation);
}).then((animation) => {
animation.destroy();
this.animation = null;
});
}
componentDidLoad() {
this.ionToastDidLoad.emit();
} }
componentDidUnload() { componentDidUnload() {