@@ -33,7 +33,7 @@ interface StackManagerProps {
3333}
3434
3535const isViewVisible = ( el : HTMLElement ) =>
36- ! el . classList . contains ( 'ion-page-invisible' ) && ! el . classList . contains ( 'ion-page-hidden' ) ;
36+ ! el . classList . contains ( 'ion-page-invisible' ) && ! el . classList . contains ( 'ion-page-hidden' ) && el . style . display !== 'none' ;
3737
3838const hideIonPageElement = ( element : HTMLElement | undefined ) : void => {
3939 if ( element ) {
@@ -44,6 +44,7 @@ const hideIonPageElement = (element: HTMLElement | undefined): void => {
4444
4545const showIonPageElement = ( element : HTMLElement | undefined ) : void => {
4646 if ( element ) {
47+ element . style . removeProperty ( 'display' ) ;
4748 element . classList . remove ( 'ion-page-hidden' ) ;
4849 element . removeAttribute ( 'aria-hidden' ) ;
4950 }
@@ -322,8 +323,8 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
322323
323324 const enteringEl = enteringViewItem . ionPageElement ;
324325 if ( enteringEl ) {
325- enteringEl . classList . remove ( 'ion-page-hidden' , 'ion-page-invisible' ) ;
326- enteringEl . removeAttribute ( 'aria-hidden ') ;
326+ showIonPageElement ( enteringEl ) ;
327+ enteringEl . classList . remove ( 'ion-page-invisible ') ;
327328 }
328329
329330 this . forceUpdate ( ) ;
@@ -417,7 +418,9 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
417418
418419 this . lastTransition = currentTransition ;
419420
420- this . transitionPage ( routeInfo , enteringViewItem , leavingViewItem ) ;
421+ const shouldSkipAnimation = this . applySkipAnimationIfNeeded ( enteringViewItem , leavingViewItem ) ;
422+
423+ this . transitionPage ( routeInfo , enteringViewItem , leavingViewItem , undefined , false , shouldSkipAnimation ) ;
421424
422425 if ( shouldUnmountLeavingViewItem && leavingViewItem && enteringViewItem !== leavingViewItem ) {
423426 leavingViewItem . mount = false ;
@@ -547,6 +550,36 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
547550 }
548551 }
549552
553+ /**
554+ * Determines whether to skip the transition animation and, if so, immediately
555+ * hides the leaving view with inline `display:none`.
556+ *
557+ * Skips transitions in outlets nested inside a parent IonPage. These outlets
558+ * render pages inside a parent page's content area. The MD animation shows
559+ * both entering and leaving pages simultaneously, causing text overlap and
560+ * nested scrollbars (each page has its own IonContent). Top-level outlets
561+ * are unaffected and animate normally.
562+ *
563+ * Uses inline display:none rather than ion-page-hidden class because core's
564+ * beforeTransition() removes ion-page-hidden via setPageHidden().
565+ * Inline display:none survives that removal, keeping the page hidden
566+ * until React unmounts it after ionViewDidLeave fires.
567+ */
568+ private applySkipAnimationIfNeeded (
569+ enteringViewItem : ViewItem ,
570+ leavingViewItem : ViewItem | undefined
571+ ) : boolean {
572+ const isNestedOutlet = ! ! this . routerOutletElement ?. closest ( '.ion-page' ) ;
573+ const shouldSkip = isNestedOutlet && ! ! leavingViewItem && enteringViewItem !== leavingViewItem ;
574+
575+ if ( shouldSkip && leavingViewItem ?. ionPageElement ) {
576+ leavingViewItem . ionPageElement . style . setProperty ( 'display' , 'none' ) ;
577+ leavingViewItem . ionPageElement . setAttribute ( 'aria-hidden' , 'true' ) ;
578+ }
579+
580+ return shouldSkip ;
581+ }
582+
550583 /**
551584 * Handles entering view with no ion-page element yet (waiting for render).
552585 */
@@ -609,7 +642,8 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
609642 const latestLeavingView = this . context . findLeavingViewItemByRouteInfo ( routeInfo , this . id ) ?? leavingViewItem ;
610643
611644 if ( latestEnteringView ?. ionPageElement ) {
612- this . transitionPage ( routeInfo , latestEnteringView , latestLeavingView ?? undefined ) ;
645+ const shouldSkipAnimation = this . applySkipAnimationIfNeeded ( latestEnteringView , latestLeavingView ?? undefined ) ;
646+ this . transitionPage ( routeInfo , latestEnteringView , latestLeavingView ?? undefined , undefined , false , shouldSkipAnimation ) ;
613647
614648 if ( shouldUnmountLeavingViewItem && latestLeavingView && latestEnteringView !== latestLeavingView ) {
615649 latestLeavingView . mount = false ;
@@ -863,6 +897,17 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
863897 return ;
864898 }
865899
900+ /**
901+ * Don't let a nested element (e.g., ion-router-outlet with ionPage prop)
902+ * override an existing IonPage registration when the existing element is
903+ * an ancestor of the new one. This ensures ionPageElement always points
904+ * to the outermost IonPage, which is needed to properly hide the entire
905+ * page during back navigation (not just the inner outlet).
906+ */
907+ if ( oldPageElement && oldPageElement !== page && oldPageElement . isConnected && oldPageElement . contains ( page ) ) {
908+ return ;
909+ }
910+
866911 foundView . ionPageElement = page ;
867912 foundView . ionRoute = true ;
868913
@@ -1008,13 +1053,18 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
10081053 * @param progressAnimation Indicates if the transition is part of a
10091054 * gesture controlled animation (e.g., swipe to go back).
10101055 * Defaults to `false`.
1056+ * @param skipAnimation When true, forces `duration: 0` so the page
1057+ * swap is instant (no visible animation). Used for ionPage outlets
1058+ * and back navigations that unmount the leaving view to prevent
1059+ * overlapping content during the transition. Defaults to `false`.
10111060 */
10121061 async transitionPage (
10131062 routeInfo : RouteInfo ,
10141063 enteringViewItem : ViewItem ,
10151064 leavingViewItem ?: ViewItem ,
10161065 direction ?: 'forward' | 'back' ,
1017- progressAnimation = false
1066+ progressAnimation = false ,
1067+ skipAnimation = false
10181068 ) {
10191069 const runCommit = async ( enteringEl : HTMLElement , leavingEl ?: HTMLElement ) => {
10201070 const skipTransition = this . skipTransition ;
@@ -1055,7 +1105,7 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
10551105 }
10561106
10571107 await routerOutlet . commit ( enteringEl , leavingEl , {
1058- duration : skipTransition || directionToUse === undefined ? 0 : undefined ,
1108+ duration : skipTransition || skipAnimation || directionToUse === undefined ? 0 : undefined ,
10591109 direction : directionToUse ,
10601110 showGoBack : ! ! routeInfo . pushedByRoute ,
10611111 progressAnimation,
0 commit comments