@@ -20,7 +20,7 @@ import {
2020import { derivePathnameToMatch , matchPath } from './utils/pathMatching' ;
2121import { stripTrailingSlash } from './utils/pathNormalization' ;
2222import { extractRouteChildren , getRoutesChildren , isNavigateElement } from './utils/routeElements' ;
23- import { isForwardPush , isOverMatchingRoute } from './utils/viewItemUtils' ;
23+ import { clearSwipeRevealed , isForwardPush , isOverMatchingRoute , markSwipeRevealed } from './utils/viewItemUtils' ;
2424
2525/**
2626 * Delay in milliseconds before unmounting a view after a transition completes.
@@ -119,6 +119,8 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
119119 routerOutletElement : HTMLIonRouterOutletElement | undefined ;
120120 prevProps ?: StackManagerProps ;
121121 skipTransition : boolean ;
122+ /** The view item the in-flight swipe gesture revealed, so the same one gets cleared. */
123+ private swipeRevealedViewItem ?: ViewItem ;
122124
123125 stackContextValue : StackContextState = {
124126 registerIonPage : this . registerIonPage . bind ( this ) ,
@@ -220,7 +222,9 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
220222 hasWildcardRoute,
221223 } ) ;
222224
223- if ( result . outletMountPath && ! this . outletMountPath ) {
225+ // A root outlet is mounted under nothing, so caching the inferred path would scope
226+ // it to whatever route was active and make every other pathname look out of scope.
227+ if ( ! this . isRootOutlet && result . outletMountPath && ! this . outletMountPath ) {
224228 this . outletMountPath = result . outletMountPath ;
225229 }
226230
@@ -305,6 +309,12 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
305309 return false ;
306310 }
307311
312+ /** Clears the reveal flag from the view item the gesture marked. */
313+ private clearSwipeRevealedView ( ) : void {
314+ clearSwipeRevealed ( this . swipeRevealedViewItem ) ;
315+ this . swipeRevealedViewItem = undefined ;
316+ }
317+
308318 /**
309319 * Handles out-of-scope outlet. Returns true if transition should be aborted.
310320 */
@@ -525,9 +535,7 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
525535 routeInfo . lastPathname . startsWith ( containerBase + '/' ) || routeInfo . lastPathname === containerBase ;
526536
527537 // A root-level "/*" leaves an empty base, so both checks above are true for every
528- // pathname and the shortcut would skip every navigation in this outlet. That only
529- // stops the shortcut misfiring. The root "/*" shape has other problems and is still
530- // not fully working, which framework-react.md covers.
538+ // pathname and the shortcut would otherwise skip every navigation in this outlet.
531539 if ( containerBase !== '' && currentInContainer && previousInContainer ) {
532540 const updatedMatch = matchComponent (
533541 enteringViewItem . reactElement ,
@@ -1089,6 +1097,7 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
10891097 }
10901098 this . waitingForIonPage = false ;
10911099 this . preservedViewItems . clear ( ) ;
1100+ this . clearSwipeRevealedView ( ) ;
10921101
10931102 // Hide all views in this outlet before clearing.
10941103 // This is critical for nested outlets - when the parent component unmounts,
@@ -1124,6 +1133,11 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
11241133 return ;
11251134 }
11261135
1136+ // A completed swipe keeps its reveal flag past goBack() so the deactivation scan does
1137+ // not re-hide the page between the gesture ending and the URL settling. Clear it at
1138+ // the start of the transition for the new pathname.
1139+ this . clearSwipeRevealedView ( ) ;
1140+
11271141 // Find entering and leaving view items
11281142 const viewItems = this . findViewItems ( routeInfo ) ;
11291143 let enteringViewItem = viewItems . enteringViewItem ;
@@ -1435,6 +1449,10 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
14351449 } ;
14361450
14371451 const onStart = async ( ) => {
1452+ // Core only calls onEnd when a progress animation exists, so with animations off a
1453+ // previous gesture can leave its mark behind. Drop any stale one before marking.
1454+ this . clearSwipeRevealedView ( ) ;
1455+
14381456 const { routeInfo } = this . props ;
14391457 const swipeBackRouteInfo = this . getSwipeBackRouteInfo ( ) ;
14401458 const enteringViewItem = this . findEnteringViewForSwipe ( swipeBackRouteInfo ) ;
@@ -1465,6 +1483,12 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
14651483
14661484 // When the gesture starts, kick off a transition controlled via swipe gesture
14671485 if ( enteringViewItem && leavingViewItem ) {
1486+ // Without a leaving view there is no progress animation, and core only calls
1487+ // swipeHandler.onEnd when one exists, so nothing would clear the mark until the
1488+ // next transition.
1489+ markSwipeRevealed ( enteringViewItem ) ;
1490+ this . swipeRevealedViewItem = enteringViewItem ;
1491+
14681492 await this . transitionPage ( routeInfo , enteringViewItem , leavingViewItem , 'back' , true ) ;
14691493 }
14701494
@@ -1492,6 +1516,8 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
14921516 const enteringViewItem = this . findEnteringViewForSwipe ( swipeBackRouteInfo ) ;
14931517 const leavingViewItem = this . context . findViewItemByRouteInfo ( routeInfo , this . id , false ) ;
14941518
1519+ this . clearSwipeRevealedView ( ) ;
1520+
14951521 // Don't hide if entering and leaving are the same (parameterized route edge case)
14961522 if ( enteringViewItem !== leavingViewItem && enteringViewItem ?. ionPageElement !== undefined ) {
14971523 hideIonPageElement ( enteringViewItem . ionPageElement ) ;
0 commit comments