diff --git a/packages/core/ui/layouts/root-layout/root-layout-common.ts b/packages/core/ui/layouts/root-layout/root-layout-common.ts index 5af969c18..8725b9db0 100644 --- a/packages/core/ui/layouts/root-layout/root-layout-common.ts +++ b/packages/core/ui/layouts/root-layout/root-layout-common.ts @@ -42,7 +42,9 @@ export class RootLayoutBase extends GridLayout { // ability to add any view instance to composite views like layers open(view: View, options: RootLayoutOptions = {}): Promise { - return new Promise((resolve, reject) => { + const enterAnimationDefinition = options.animation ? options.animation.enterFrom : null; + + return new Promise((resolve, reject) => { if (!(view instanceof 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`)); } - 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 - this.popupViews.push({ view: view, options: options }); - - if (options.shadeCover) { - // perf optimization note: we only need 1 layer of shade cover - // we just update properties if needed by additional overlaid views - if (this.shadeCover) { - // overwrite current shadeCover options if topmost popupview has additional shadeCover configurations - this.updateShadeCover(this.shadeCover, options.shadeCover); - } else { - this.openShadeCover(options.shadeCover); + if (options.shadeCover) { + // perf optimization note: we only need 1 layer of shade cover + // we just update properties if needed by additional overlaid views + if (this.shadeCover) { + // overwrite current shadeCover options if topmost popupview has additional shadeCover configurations + return this.updateShadeCover(this.shadeCover, options.shadeCover); + } + return 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 - 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 - this.applyInitialState(view, enterAnimationDefinition); - this.getEnterAnimation(view, enterAnimationDefinition) - .play() - .then(() => { - this.applyDefaultState(view); - view.notify({ eventName: 'opened', object: view }); - resolve(); - }) - .catch((ex) => { - reject(new Error(`Error playing enter animation: ${ex}`)); + return new Promise((resolve, reject) => { + setTimeout(() => { + // only apply initial state and animate after the first tick - ensures safe areas and other measurements apply correctly + this.applyInitialState(view, enterAnimationDefinition); + this.getEnterAnimation(view, enterAnimationDefinition) + .play() + .then(() => { + this.applyDefaultState(view); + view.notify({ eventName: 'opened', object: view }); + resolve(); + }) + .catch((ex) => { + reject(new Error(`Error playing enter animation: ${ex}`)); + }); }); + }); }); - }); } // optional animation parameter to overwrite close animation declared when opening popup // ability to remove any view instance from composite views close(view: View, exitTo?: TransitionAnimation): Promise { - 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((resolve, reject) => { if (!(view instanceof 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`)); } - const popupIndex = this.getPopupIndex(view); - const poppedView = this.popupViews[popupIndex]; - const cleanupAndFinish = () => { - view.notify({ eventName: 'closed', object: view }); - this.removeChild(view); - resolve(); - }; - // use exitAnimation that is passed in and fallback to the exitAnimation passed in when opening - const exitAnimationDefinition = exitTo || poppedView?.options?.animation?.exitTo; + resolve(); + }) + .then(() => { + const popupIndex = this.getPopupIndex(view); + const poppedView = this.popupViews[popupIndex]; - // Remove view from tracked popupviews - this.popupViews.splice(popupIndex, 1); + if (!exitAnimationDefinition) { + exitAnimationDefinition = poppedView?.options?.animation?.exitTo; + } - if (this.shadeCover) { - // update shade cover with the topmost popupView options (if not specifically told to ignore) - if (!poppedView?.options?.shadeCover?.ignoreShadeRestore) { - const shadeCoverOptions = this.popupViews[this.popupViews.length - 1]?.options?.shadeCover; - if (shadeCoverOptions) { - this.updateShadeCover(this.shadeCover, shadeCoverOptions); + // Remove view from tracked popupviews + this.popupViews.splice(popupIndex, 1); + + if (this.shadeCover) { + // update shade cover with the topmost popupView options (if not specifically told to ignore) + 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) { - this.closeShadeCover(poppedView?.options?.shadeCover); + }) + .then(() => { + 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(); - } - }); + }); } closeAll(): Promise { const toClose = []; + const views = this.popupViews.map((popupView) => popupView.view); + // Close all views at the same time and wait for all of them - while (this.popupViews.length > 0) { - toClose.push(this.close(this.popupViews[this.popupViews.length - 1].view)); + for (const view of views) { + toClose.push(this.close(view)); } return Promise.all(toClose); } @@ -342,7 +355,7 @@ export class RootLayoutBase extends GridLayout { return shadeCover; } - private updateShadeCover(shade: View, shadeOptions: ShadeCoverOptions = {}): void { + private updateShadeCover(shade: View, shadeOptions: ShadeCoverOptions = {}): Promise { if (shadeOptions.tapToClose !== undefined && shadeOptions.tapToClose !== null) { shade.off('tap'); 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 {