From 4aad76a93a351a575e5c132ab4dca51d8b408a0d Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Mon, 19 May 2025 13:09:11 -0700 Subject: [PATCH] chore(StackManager): upgrade to rr6 and add comments --- .../src/ReactRouter/StackManager.tsx | 148 ++++++++++++++---- 1 file changed, 120 insertions(+), 28 deletions(-) diff --git a/packages/react-router/src/ReactRouter/StackManager.tsx b/packages/react-router/src/ReactRouter/StackManager.tsx index 708de65139..fc8309f579 100644 --- a/packages/react-router/src/ReactRouter/StackManager.tsx +++ b/packages/react-router/src/ReactRouter/StackManager.tsx @@ -18,7 +18,7 @@ const isViewVisible = (el: HTMLElement) => !el.classList.contains('ion-page-invisible') && !el.classList.contains('ion-page-hidden'); export class StackManager extends React.PureComponent { - id: string; + id: string; // Unique id for the router outlet aka outletId context!: React.ContextType; ionRouterOutlet?: React.ReactElement; routerOutletElement: HTMLIonRouterOutletElement | undefined; @@ -79,25 +79,49 @@ export class StackManager extends React.PureComponent { + /** + * If we have a leavingView but no entering view/route, we are probably + * leaving to another outlet, so hide this leavingView. + * (e.g., /tabs/tab1 → /settings) + */ if (leavingViewItem.ionPageElement) { leavingViewItem.ionPageElement.classList.add('ion-page-hidden'); leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true'); } - // }, 250); } + // Force re-render so views update according to their new mount/visible status this.forceUpdate(); } } + /** + * Registers an `` DOM element with the `StackManager`. + * This is called when `` has been mounted. + * + * @param page The element of the rendered ``. + * @param routeInfo The route information that associates with ``. + */ registerIonPage(page: HTMLElement, routeInfo: RouteInfo) { const foundView = this.context.findViewItemByRouteInfo(routeInfo, this.id); if (foundView) { @@ -209,9 +255,15 @@ export class StackManager extends React.PureComponent`. + */ async setupRouterOutlet(routerOutlet: HTMLIonRouterOutletElement) { const canStart = () => { const config = getConfig(); + // Check if swipe back is enabled in config (default to true for iOS mode) const swipeEnabled = config && config.get('swipeBackEnabled', routerOutlet.mode === 'ios'); if (!swipeEnabled) { return false; @@ -219,10 +271,12 @@ export class StackManager extends React.PureComponent { const { routeInfo } = this.props; + // Determine the route to use for finding the view we would be navigating back to const propsToUse = this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute ? this.prevProps.routeInfo : ({ pathname: routeInfo.pushedByRoute || '' } as any); + // Find the view item for the route we are going back to const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false); + // Find the view item for the route we are going back from const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false); /** @@ -267,8 +324,10 @@ export class StackManager extends React.PureComponent { if (shouldContinue) { + // User finished the swipe gesture, so complete the back navigation this.skipTransition = true; this.context.goBack(); @@ -280,11 +339,14 @@ export class StackManager extends React.PureComponent { - this.forceUpdate(); + this.forceUpdate(); // TODO: investigate why this is needed }); return ( @@ -405,13 +491,16 @@ export class StackManager extends React.PureComponent { if (ionRouterOutlet.props.setRef) { + // Needed to handle external refs from devs. ionRouterOutlet.props.setRef(node); } if (ionRouterOutlet.props.forwardedRef) { + // Needed to handle external refs from devs. ionRouterOutlet.props.forwardedRef.current = node; } this.routerOutletElement = node; const { ref } = ionRouterOutlet as any; + // Check for legacy refs. if (typeof ref === 'function') { ref(node); } @@ -432,26 +521,29 @@ export default StackManager; function matchRoute(node: React.ReactNode, routeInfo: RouteInfo) { let matchedNode: React.ReactNode; - React.Children.forEach(node as React.ReactElement, (child: React.ReactElement) => { + for (const child of React.Children.toArray(node) as React.ReactElement[]) { const match = matchPath({ pathname: routeInfo.pathname, componentProps: child.props, }); + if (match) { matchedNode = child; + break; } - }); + } if (matchedNode) { return matchedNode; } // If we haven't found a node // try to find one that doesn't have a path or from prop, that will be our not found route - React.Children.forEach(node as React.ReactElement, (child: React.ReactElement) => { + for (const child of React.Children.toArray(node) as React.ReactElement[]) { if (!(child.props.path || child.props.from)) { matchedNode = child; + break; } - }); + } return matchedNode; } @@ -461,7 +553,7 @@ function matchComponent(node: React.ReactElement, pathname: string, forceExact?: pathname, componentProps: { ...node.props, - exact: forceExact, + end: forceExact, }, }); }