fix(core): RootLayout shade cover asynchronous execution (#10228)

This commit is contained in:
Dimitris-Rafail Katsampas
2023-03-10 06:57:15 +02:00
committed by GitHub
parent 6779cdcb55
commit a19568c0d0

View File

@ -42,7 +42,9 @@ export class RootLayoutBase extends GridLayout {
// ability to add any view instance to composite views like layers // ability to add any view instance to composite views like layers
open(view: View, options: RootLayoutOptions = {}): Promise<void> { open(view: View, options: RootLayoutOptions = {}): Promise<void> {
return new Promise((resolve, reject) => { const enterAnimationDefinition = options.animation ? options.animation.enterFrom : null;
return new Promise<void>((resolve, reject) => {
if (!(view instanceof View)) { if (!(view instanceof View)) {
return reject(new Error(`Invalid open view: ${view}`)); return reject(new Error(`Invalid open view: ${view}`));
} }
@ -51,46 +53,57 @@ export class RootLayoutBase extends GridLayout {
return reject(new Error(`${view} has already been added`)); return reject(new Error(`${view} has already been added`));
} }
const enterAnimationDefinition = options.animation ? options.animation.enterFrom : null; resolve();
})
.then(() => {
// keep track of the views locally to be able to use their options later
this.popupViews.push({ view: view, options: options });
// keep track of the views locally to be able to use their options later if (options.shadeCover) {
this.popupViews.push({ view: view, options: options }); // perf optimization note: we only need 1 layer of shade cover
// we just update properties if needed by additional overlaid views
if (options.shadeCover) { if (this.shadeCover) {
// perf optimization note: we only need 1 layer of shade cover // overwrite current shadeCover options if topmost popupview has additional shadeCover configurations
// we just update properties if needed by additional overlaid views return this.updateShadeCover(this.shadeCover, options.shadeCover);
if (this.shadeCover) { }
// overwrite current shadeCover options if topmost popupview has additional shadeCover configurations return this.openShadeCover(options.shadeCover);
this.updateShadeCover(this.shadeCover, options.shadeCover);
} else {
this.openShadeCover(options.shadeCover);
} }
} })
.then(() => {
view.opacity = 0; // always begin with view invisible when adding dynamically
this.insertChild(view, this.getChildrenCount() + 1);
view.opacity = 0; // always begin with view invisible when adding dynamically return new Promise((resolve, reject) => {
this.insertChild(view, this.getChildrenCount() + 1); setTimeout(() => {
// only apply initial state and animate after the first tick - ensures safe areas and other measurements apply correctly
setTimeout(() => { this.applyInitialState(view, enterAnimationDefinition);
// only apply initial state and animate after the first tick - ensures safe areas and other measurements apply correctly this.getEnterAnimation(view, enterAnimationDefinition)
this.applyInitialState(view, enterAnimationDefinition); .play()
this.getEnterAnimation(view, enterAnimationDefinition) .then(() => {
.play() this.applyDefaultState(view);
.then(() => { view.notify({ eventName: 'opened', object: view });
this.applyDefaultState(view); resolve();
view.notify({ eventName: 'opened', object: view }); })
resolve(); .catch((ex) => {
}) reject(new Error(`Error playing enter animation: ${ex}`));
.catch((ex) => { });
reject(new Error(`Error playing enter animation: ${ex}`));
}); });
});
}); });
});
} }
// optional animation parameter to overwrite close animation declared when opening popup // optional animation parameter to overwrite close animation declared when opening popup
// ability to remove any view instance from composite views // ability to remove any view instance from composite views
close(view: View, exitTo?: TransitionAnimation): Promise<void> { close(view: View, exitTo?: TransitionAnimation): Promise<void> {
return new Promise((resolve, reject) => { const cleanupAndFinish = () => {
view.notify({ eventName: 'closed', object: view });
this.removeChild(view);
};
// use exitAnimation that is passed in and fallback to the exitAnimation passed in when opening
let exitAnimationDefinition = exitTo;
return new Promise<void>((resolve, reject) => {
if (!(view instanceof View)) { if (!(view instanceof View)) {
return reject(new Error(`Invalid close view: ${view}`)); return reject(new Error(`Invalid close view: ${view}`));
} }
@ -99,51 +112,51 @@ export class RootLayoutBase extends GridLayout {
return reject(new Error(`Unable to close popup. ${view} not found`)); return reject(new Error(`Unable to close popup. ${view} not found`));
} }
const popupIndex = this.getPopupIndex(view); resolve();
const poppedView = this.popupViews[popupIndex]; })
const cleanupAndFinish = () => { .then(() => {
view.notify({ eventName: 'closed', object: view }); const popupIndex = this.getPopupIndex(view);
this.removeChild(view); const poppedView = this.popupViews[popupIndex];
resolve();
};
// use exitAnimation that is passed in and fallback to the exitAnimation passed in when opening
const exitAnimationDefinition = exitTo || poppedView?.options?.animation?.exitTo;
// Remove view from tracked popupviews if (!exitAnimationDefinition) {
this.popupViews.splice(popupIndex, 1); exitAnimationDefinition = poppedView?.options?.animation?.exitTo;
}
if (this.shadeCover) { // Remove view from tracked popupviews
// update shade cover with the topmost popupView options (if not specifically told to ignore) this.popupViews.splice(popupIndex, 1);
if (!poppedView?.options?.shadeCover?.ignoreShadeRestore) {
const shadeCoverOptions = this.popupViews[this.popupViews.length - 1]?.options?.shadeCover; if (this.shadeCover) {
if (shadeCoverOptions) { // update shade cover with the topmost popupView options (if not specifically told to ignore)
this.updateShadeCover(this.shadeCover, shadeCoverOptions); if (!poppedView?.options?.shadeCover?.ignoreShadeRestore) {
const shadeCoverOptions = this.popupViews[this.popupViews.length - 1]?.options?.shadeCover;
if (shadeCoverOptions) {
return this.updateShadeCover(this.shadeCover, shadeCoverOptions);
}
}
// remove shade cover animation if this is the last opened popup view
if (this.popupViews.length === 0) {
return this.closeShadeCover(poppedView?.options?.shadeCover);
} }
} }
// remove shade cover animation if this is the last opened popup view })
if (this.popupViews.length === 0) { .then(() => {
this.closeShadeCover(poppedView?.options?.shadeCover); if (exitAnimationDefinition) {
return this.getExitAnimation(view, exitAnimationDefinition)
.play()
.then(cleanupAndFinish.bind(this))
.catch((ex) => Promise.reject(new Error(`Error playing exit animation: ${ex}`)));
} }
}
if (exitAnimationDefinition) {
this.getExitAnimation(view, exitAnimationDefinition)
.play()
.then(cleanupAndFinish.bind(this))
.catch((ex) => {
reject(new Error(`Error playing exit animation: ${ex}`));
});
} else {
cleanupAndFinish(); cleanupAndFinish();
} });
});
} }
closeAll(): Promise<void[]> { closeAll(): Promise<void[]> {
const toClose = []; const toClose = [];
const views = this.popupViews.map((popupView) => popupView.view);
// Close all views at the same time and wait for all of them // Close all views at the same time and wait for all of them
while (this.popupViews.length > 0) { for (const view of views) {
toClose.push(this.close(this.popupViews[this.popupViews.length - 1].view)); toClose.push(this.close(view));
} }
return Promise.all(toClose); return Promise.all(toClose);
} }
@ -342,7 +355,7 @@ export class RootLayoutBase extends GridLayout {
return shadeCover; return shadeCover;
} }
private updateShadeCover(shade: View, shadeOptions: ShadeCoverOptions = {}): void { private updateShadeCover(shade: View, shadeOptions: ShadeCoverOptions = {}): Promise<void> {
if (shadeOptions.tapToClose !== undefined && shadeOptions.tapToClose !== null) { if (shadeOptions.tapToClose !== undefined && shadeOptions.tapToClose !== null) {
shade.off('tap'); shade.off('tap');
if (shadeOptions.tapToClose) { if (shadeOptions.tapToClose) {
@ -351,7 +364,7 @@ export class RootLayoutBase extends GridLayout {
}); });
} }
} }
this._updateShadeCover(shade, shadeOptions); return this._updateShadeCover(shade, shadeOptions);
} }
private hasChild(view: View): boolean { private hasChild(view: View): boolean {