@@ -18,34 +18,51 @@ const focusController = createFocusController();
1818
1919// TODO(FW-2832): types
2020
21+ /**
22+ * Executes the main page transition.
23+ * It also manages the lifecycle of header visibility (if any)
24+ * to prevent visual flickering in iOS. The flickering only
25+ * occurs for a condensed header that is placed above the content.
26+ *
27+ * @param opts Options for the transition.
28+ * @returns A promise that resolves when the transition is complete.
29+ */
2130export const transition = ( opts : TransitionOptions ) : Promise < TransitionResult > => {
2231 return new Promise ( ( resolve , reject ) => {
2332 writeTask ( ( ) => {
24- beforeTransition ( opts ) ;
25- runTransition ( opts ) . then (
26- ( result ) => {
27- if ( result . animation ) {
28- result . animation . destroy ( ) ;
33+ const transitioningInactiveHeader = getIosIonHeader ( opts ) ;
34+ beforeTransition ( opts , transitioningInactiveHeader ) ;
35+ runTransition ( opts )
36+ . then (
37+ ( result ) => {
38+ if ( result . animation ) {
39+ result . animation . destroy ( ) ;
40+ }
41+ afterTransition ( opts ) ;
42+ resolve ( result ) ;
43+ } ,
44+ ( error ) => {
45+ afterTransition ( opts ) ;
46+ reject ( error ) ;
2947 }
30- afterTransition ( opts ) ;
31- resolve ( result ) ;
32- } ,
33- ( error ) => {
34- afterTransition ( opts ) ;
35- reject ( error ) ;
36- }
37- ) ;
48+ )
49+ . finally ( ( ) => {
50+ // Ensure that the header is restored to its original state.
51+ setHeaderTransitionClass ( transitioningInactiveHeader , false ) ;
52+ } ) ;
3853 } ) ;
3954 } ) ;
4055} ;
4156
42- const beforeTransition = ( opts : TransitionOptions ) => {
57+ const beforeTransition = ( opts : TransitionOptions , transitioningInactiveHeader : HTMLElement | null ) => {
4358 const enteringEl = opts . enteringEl ;
4459 const leavingEl = opts . leavingEl ;
4560
4661 focusController . saveViewFocus ( leavingEl ) ;
4762
4863 setZIndex ( enteringEl , leavingEl , opts . direction ) ;
64+ // Prevent flickering of the header by adding a class.
65+ setHeaderTransitionClass ( transitioningInactiveHeader , true ) ;
4966
5067 if ( opts . showGoBack ) {
5168 enteringEl . classList . add ( 'can-go-back' ) ;
@@ -278,6 +295,36 @@ const setZIndex = (
278295 }
279296} ;
280297
298+ /**
299+ * Add a class to ensure that the inactive header (if any)
300+ * does not flicker during the transition. By adding the
301+ * transitioning class, we ensure that the header has
302+ * the necessary styles to prevent the following flickers:
303+ * 1. When entering a page with a condensed header, the
304+ * inactive header should never be visible. However,
305+ * it briefly renders the background color while
306+ * the transition is occurring.
307+ * 2. When leaving a page with a condensed header, the
308+ * inactive header has an opacity of 0 and the pages
309+ * have a z-index which causes the entering page to
310+ * briefly show it's content underneath the leaving page.
311+ *
312+ * @param header The header element to modify.
313+ * @param isTransitioning Whether the transition is occurring.
314+ */
315+ const setHeaderTransitionClass = ( header : HTMLElement | null , isTransitioning : boolean ) => {
316+ if ( ! header ) {
317+ return ;
318+ }
319+
320+ const transitionClass = 'header-transitioning' ;
321+ if ( isTransitioning ) {
322+ header . classList . add ( transitionClass ) ;
323+ } else {
324+ header . classList . remove ( transitionClass ) ;
325+ }
326+ } ;
327+
281328export const getIonPageElement = ( element : HTMLElement ) => {
282329 if ( element . classList . contains ( 'ion-page' ) ) {
283330 return element ;
@@ -291,6 +338,32 @@ export const getIonPageElement = (element: HTMLElement) => {
291338 return element ;
292339} ;
293340
341+ /**
342+ * Retrieves the ion-header element from a page based on the
343+ * direction of the transition.
344+ *
345+ * @param opts Options for the transition.
346+ * @returns The ion-header element or null if not found or not in 'ios' mode.
347+ */
348+ const getIosIonHeader = ( opts : TransitionOptions ) : HTMLElement | null => {
349+ const enteringEl = opts . enteringEl ;
350+ const leavingEl = opts . leavingEl ;
351+ const direction = opts . direction ;
352+ const mode = opts . mode ;
353+
354+ if ( mode !== 'ios' ) {
355+ return null ;
356+ }
357+
358+ const element = direction === 'back' ? leavingEl : enteringEl ;
359+
360+ if ( ! element ) {
361+ return null ;
362+ }
363+
364+ return element . querySelector ( 'ion-header' ) ;
365+ } ;
366+
294367export interface TransitionOptions extends NavOptions {
295368 progressCallback ?: ( ani : Animation | undefined ) => void ;
296369 baseEl : any ;
0 commit comments