Skip to content

Commit 2b64e17

Browse files
authored
fix(core): RootLayout view and shade cover should animate in parallel (#10256)
1 parent 7aaa1d8 commit 2b64e17

File tree

2 files changed

+127
-89
lines changed

2 files changed

+127
-89
lines changed

packages/core/ui/layouts/root-layout/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export class RootLayout extends GridLayout {
88
bringToFront(view: View, animated?: boolean): Promise<void>;
99
closeAll(): Promise<void[]>;
1010
getShadeCover(): View;
11-
openShadeCover(options: ShadeCoverOptions = {}): void;
11+
openShadeCover(options: ShadeCoverOptions = {}): Promise<void>;
1212
closeShadeCover(shadeCoverOptions: ShadeCoverOptions = {}): Promise<void>;
1313
}
1414

packages/core/ui/layouts/root-layout/root-layout-common.ts

Lines changed: 126 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,15 @@ export class RootLayoutBase extends GridLayout {
4040
return handled;
4141
}
4242

43-
// ability to add any view instance to composite views like layers
43+
/**
44+
* Ability to add any view instance to composite views like layers.
45+
*
46+
* @param view
47+
* @param options
48+
* @returns
49+
*/
4450
open(view: View, options: RootLayoutOptions = {}): Promise<void> {
45-
const enterAnimationDefinition = options.animation ? options.animation.enterFrom : null;
46-
47-
return new Promise<void>((resolve, reject) => {
51+
return new Promise((resolve, reject) => {
4852
if (!(view instanceof View)) {
4953
return reject(new Error(`Invalid open view: ${view}`));
5054
}
@@ -53,57 +57,68 @@ export class RootLayoutBase extends GridLayout {
5357
return reject(new Error(`${view} has already been added`));
5458
}
5559

56-
resolve();
57-
})
58-
.then(() => {
59-
// keep track of the views locally to be able to use their options later
60-
this.popupViews.push({ view: view, options: options });
61-
62-
if (options.shadeCover) {
63-
// perf optimization note: we only need 1 layer of shade cover
64-
// we just update properties if needed by additional overlaid views
65-
if (this.shadeCover) {
66-
// overwrite current shadeCover options if topmost popupview has additional shadeCover configurations
67-
return this.updateShadeCover(this.shadeCover, options.shadeCover);
68-
}
69-
return this.openShadeCover(options.shadeCover);
60+
const toOpen = [];
61+
const enterAnimationDefinition = options.animation ? options.animation.enterFrom : null;
62+
63+
// keep track of the views locally to be able to use their options later
64+
this.popupViews.push({ view: view, options: options });
65+
66+
if (options.shadeCover) {
67+
// perf optimization note: we only need 1 layer of shade cover
68+
// we just update properties if needed by additional overlaid views
69+
if (this.shadeCover) {
70+
// overwrite current shadeCover options if topmost popupview has additional shadeCover configurations
71+
toOpen.push(this.updateShadeCover(this.shadeCover, options.shadeCover));
72+
} else {
73+
toOpen.push(this.openShadeCover(options.shadeCover));
7074
}
71-
})
72-
.then(() => {
73-
view.opacity = 0; // always begin with view invisible when adding dynamically
74-
this.insertChild(view, this.getChildrenCount() + 1);
75+
}
7576

76-
return new Promise((resolve, reject) => {
77+
view.opacity = 0; // always begin with view invisible when adding dynamically
78+
this.insertChild(view, this.getChildrenCount() + 1);
79+
80+
toOpen.push(
81+
new Promise<void>((res, rej) => {
7782
setTimeout(() => {
7883
// only apply initial state and animate after the first tick - ensures safe areas and other measurements apply correctly
7984
this.applyInitialState(view, enterAnimationDefinition);
8085
this.getEnterAnimation(view, enterAnimationDefinition)
8186
.play()
82-
.then(() => {
83-
this.applyDefaultState(view);
84-
view.notify({ eventName: 'opened', object: view });
85-
resolve();
86-
})
87-
.catch((ex) => {
88-
reject(new Error(`Error playing enter animation: ${ex}`));
89-
});
87+
.then(
88+
() => {
89+
this.applyDefaultState(view);
90+
view.notify({ eventName: 'opened', object: view });
91+
res();
92+
},
93+
(err) => {
94+
rej(new Error(`Error playing enter animation: ${err}`));
95+
}
96+
);
9097
});
91-
});
92-
});
98+
})
99+
);
100+
101+
Promise.all(toOpen).then(
102+
() => {
103+
resolve();
104+
},
105+
(err) => {
106+
reject(err);
107+
}
108+
);
109+
});
93110
}
94111

95-
// optional animation parameter to overwrite close animation declared when opening popup
96-
// ability to remove any view instance from composite views
112+
/**
113+
* Ability to remove any view instance from composite views.
114+
* Optional animation parameter to overwrite close animation declared when opening popup.
115+
*
116+
* @param view
117+
* @param exitTo
118+
* @returns
119+
*/
97120
close(view: View, exitTo?: TransitionAnimation): Promise<void> {
98-
const cleanupAndFinish = () => {
99-
view.notify({ eventName: 'closed', object: view });
100-
this.removeChild(view);
101-
};
102-
103-
// use exitAnimation that is passed in and fallback to the exitAnimation passed in when opening
104-
let exitAnimationDefinition = exitTo;
105-
106-
return new Promise<void>((resolve, reject) => {
121+
return new Promise((resolve, reject) => {
107122
if (!(view instanceof View)) {
108123
return reject(new Error(`Invalid close view: ${view}`));
109124
}
@@ -112,42 +127,58 @@ export class RootLayoutBase extends GridLayout {
112127
return reject(new Error(`Unable to close popup. ${view} not found`));
113128
}
114129

115-
resolve();
116-
})
117-
.then(() => {
118-
const popupIndex = this.getPopupIndex(view);
119-
const poppedView = this.popupViews[popupIndex];
120-
121-
if (!exitAnimationDefinition) {
122-
exitAnimationDefinition = poppedView?.options?.animation?.exitTo;
123-
}
124-
125-
// Remove view from tracked popupviews
126-
this.popupViews.splice(popupIndex, 1);
130+
const toClose = [];
131+
const popupIndex = this.getPopupIndex(view);
132+
const poppedView = this.popupViews[popupIndex];
133+
const cleanupAndFinish = () => {
134+
view.notify({ eventName: 'closed', object: view });
135+
this.removeChild(view);
136+
resolve();
137+
};
138+
// use exitAnimation that is passed in and fallback to the exitAnimation passed in when opening
139+
const exitAnimationDefinition = exitTo || poppedView?.options?.animation?.exitTo;
140+
141+
// Remove view from tracked popupviews
142+
this.popupViews.splice(popupIndex, 1);
143+
144+
toClose.push(
145+
new Promise<void>((res, rej) => {
146+
if (exitAnimationDefinition) {
147+
this.getExitAnimation(view, exitAnimationDefinition)
148+
.play()
149+
.then(res, (err) => {
150+
rej(new Error(`Error playing exit animation: ${err}`));
151+
});
152+
} else {
153+
res();
154+
}
155+
})
156+
);
127157

128-
if (this.shadeCover) {
129-
// update shade cover with the topmost popupView options (if not specifically told to ignore)
158+
if (this.shadeCover) {
159+
// Update shade cover with the topmost popupView options (if not specifically told to ignore)
160+
if (this.popupViews.length) {
130161
if (!poppedView?.options?.shadeCover?.ignoreShadeRestore) {
131-
const shadeCoverOptions = this.popupViews[this.popupViews.length - 1]?.options?.shadeCover;
162+
const shadeCoverOptions = this.popupViews[this.popupViews.length - 1].options?.shadeCover;
132163
if (shadeCoverOptions) {
133-
return this.updateShadeCover(this.shadeCover, shadeCoverOptions);
164+
toClose.push(this.updateShadeCover(this.shadeCover, shadeCoverOptions));
134165
}
135166
}
136-
// remove shade cover animation if this is the last opened popup view
137-
if (this.popupViews.length === 0) {
138-
return this.closeShadeCover(poppedView?.options?.shadeCover);
139-
}
167+
} else {
168+
// Remove shade cover animation if this is the last opened popup view
169+
toClose.push(this.closeShadeCover(poppedView?.options?.shadeCover));
140170
}
141-
})
142-
.then(() => {
143-
if (exitAnimationDefinition) {
144-
return this.getExitAnimation(view, exitAnimationDefinition)
145-
.play()
146-
.then(cleanupAndFinish.bind(this))
147-
.catch((ex) => Promise.reject(new Error(`Error playing exit animation: ${ex}`)));
171+
}
172+
173+
Promise.all(toClose).then(
174+
() => {
175+
cleanupAndFinish();
176+
},
177+
(err) => {
178+
reject(err);
148179
}
149-
cleanupAndFinish();
150-
});
180+
);
181+
});
151182
}
152183

153184
closeAll(): Promise<void[]> {
@@ -165,17 +196,28 @@ export class RootLayoutBase extends GridLayout {
165196
return this.shadeCover;
166197
}
167198

168-
openShadeCover(options: ShadeCoverOptions = {}) {
169-
if (this.shadeCover) {
170-
if (Trace.isEnabled()) {
171-
Trace.write(`RootLayout shadeCover already open.`, Trace.categories.Layout, Trace.messageType.warn);
199+
openShadeCover(options: ShadeCoverOptions = {}): Promise<void> {
200+
return new Promise((resolve) => {
201+
if (this.shadeCover) {
202+
if (Trace.isEnabled()) {
203+
Trace.write(`RootLayout shadeCover already open.`, Trace.categories.Layout, Trace.messageType.warn);
204+
}
205+
resolve();
206+
} else {
207+
// Create the one and only shade cover
208+
const shadeCover = this.createShadeCover();
209+
shadeCover.on('loaded', () => {
210+
this._initShadeCover(shadeCover, options);
211+
this.updateShadeCover(shadeCover, options).then(() => {
212+
resolve();
213+
});
214+
});
215+
216+
this.shadeCover = shadeCover;
217+
// Insert shade cover at index right above the first layout
218+
this.insertChild(this.shadeCover, this.staticChildCount + 1);
172219
}
173-
} else {
174-
// create the one and only shade cover
175-
this.shadeCover = this.createShadeCover(options);
176-
// insert shade cover at index right above the first layout
177-
this.insertChild(this.shadeCover, this.staticChildCount + 1);
178-
}
220+
});
179221
}
180222

181223
closeShadeCover(shadeCoverOptions: ShadeCoverOptions = {}): Promise<void> {
@@ -345,13 +387,9 @@ export class RootLayoutBase extends GridLayout {
345387
};
346388
}
347389

348-
private createShadeCover(shadeOptions: ShadeCoverOptions = {}): View {
390+
private createShadeCover(): View {
349391
const shadeCover = new GridLayout();
350392
shadeCover.verticalAlignment = 'bottom';
351-
shadeCover.on('loaded', () => {
352-
this._initShadeCover(shadeCover, shadeOptions);
353-
this.updateShadeCover(shadeCover, shadeOptions);
354-
});
355393
return shadeCover;
356394
}
357395

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy