@ionic/react-router 8.8.1-dev.11774273905.1c3b9598 → 8.8.1-dev.11774384072.1e807ca8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1556,6 +1556,26 @@ class StackManager extends React.PureComponent {
1556
1556
  this.forceUpdate();
1557
1557
  return true;
1558
1558
  }
1559
+ /**
1560
+ * Handles root navigation by unmounting all non-entering views in this outlet.
1561
+ * Fires ionViewWillLeave / ionViewDidLeave only on views that are currently visible.
1562
+ * Views that are mounted but not visible (e.g., pages earlier in the back stack)
1563
+ * are silently unmounted without lifecycle events, consistent with the behavior
1564
+ * of out-of-scope outlet cleanup.
1565
+ */
1566
+ handleRootNavigation(enteringViewItem) {
1567
+ const allViewsInOutlet = this.context.getViewItemsForOutlet(this.id);
1568
+ allViewsInOutlet.forEach((viewItem) => {
1569
+ if (viewItem === enteringViewItem) {
1570
+ return;
1571
+ }
1572
+ if (viewItem.ionPageElement && isViewVisible(viewItem.ionPageElement)) {
1573
+ viewItem.ionPageElement.dispatchEvent(new CustomEvent('ionViewWillLeave', { bubbles: false, cancelable: false }));
1574
+ viewItem.ionPageElement.dispatchEvent(new CustomEvent('ionViewDidLeave', { bubbles: false, cancelable: false }));
1575
+ }
1576
+ this.context.unMountViewItem(viewItem);
1577
+ });
1578
+ }
1559
1579
  /**
1560
1580
  * Handles nested outlet with relative routes but no parent path. Returns true to abort.
1561
1581
  */
@@ -2044,14 +2064,20 @@ class StackManager extends React.PureComponent {
2044
2064
  // Find entering and leaving view items
2045
2065
  const viewItems = this.findViewItems(routeInfo);
2046
2066
  let enteringViewItem = viewItems.enteringViewItem;
2047
- const leavingViewItem = viewItems.leavingViewItem;
2048
- const shouldUnmountLeavingViewItem = this.shouldUnmountLeavingView(routeInfo, enteringViewItem, leavingViewItem);
2067
+ let leavingViewItem = viewItems.leavingViewItem;
2068
+ let shouldUnmountLeavingViewItem = this.shouldUnmountLeavingView(routeInfo, enteringViewItem, leavingViewItem);
2049
2069
  // Get parent path for nested outlets
2050
2070
  const parentPath = this.getParentPath();
2051
2071
  // Handle out-of-scope outlet (route outside mount path)
2052
2072
  if (this.handleOutOfScopeOutlet(routeInfo)) {
2053
2073
  return;
2054
2074
  }
2075
+ // Handle root navigation: unmount all non-entering views
2076
+ if (routeInfo.routeDirection === 'root') {
2077
+ this.handleRootNavigation(enteringViewItem);
2078
+ leavingViewItem = undefined;
2079
+ shouldUnmountLeavingViewItem = false;
2080
+ }
2055
2081
  // Clear any pending out-of-scope unmount timeout
2056
2082
  if (this.outOfScopeUnmountTimeout) {
2057
2083
  clearTimeout(this.outOfScopeUnmountTimeout);
@@ -2769,34 +2795,14 @@ const IonRouter = ({ children, registerHistoryListener }) => {
2769
2795
  * @param action The action that triggered the history change.
2770
2796
  */
2771
2797
  const handleHistoryChange = (location, action) => {
2772
- var _a, _b, _c, _d, _e;
2773
- let leavingLocationInfo;
2798
+ var _a, _b, _c, _d;
2774
2799
  /**
2775
- * A programmatic navigation was triggered.
2776
- * e.g., `<Navigate />`, `navigate()`, or `handleNavigate()`
2800
+ * The leaving location is always the current route, for both programmatic
2801
+ * and external navigations. Using `previous()` for replace actions was
2802
+ * incorrect: it caused the equality check below to skip navigation when
2803
+ * the replace destination matched the entry two slots back in history.
2777
2804
  */
2778
- if (incomingRouteParams.current) {
2779
- /**
2780
- * The current history entry is overwritten, so the previous entry
2781
- * is the one we are leaving.
2782
- */
2783
- if (((_a = incomingRouteParams.current) === null || _a === void 0 ? void 0 : _a.routeAction) === 'replace') {
2784
- leavingLocationInfo = locationHistory.current.previous();
2785
- }
2786
- else {
2787
- // If the action is 'push' or 'pop', we want to use the current route.
2788
- leavingLocationInfo = locationHistory.current.current();
2789
- }
2790
- }
2791
- else {
2792
- /**
2793
- * An external navigation was triggered
2794
- * e.g., browser back/forward button or direct link
2795
- *
2796
- * The leaving location is the current route.
2797
- */
2798
- leavingLocationInfo = locationHistory.current.current();
2799
- }
2805
+ const leavingLocationInfo = locationHistory.current.current();
2800
2806
  const leavingUrl = leavingLocationInfo.pathname + leavingLocationInfo.search;
2801
2807
  if (leavingUrl !== location.pathname + location.search) {
2802
2808
  if (!incomingRouteParams.current) {
@@ -2898,7 +2904,7 @@ const IonRouter = ({ children, registerHistoryListener }) => {
2898
2904
  * An existing id indicates that it's re-activating an existing route.
2899
2905
  * e.g., tab switching or navigating back to a previous route
2900
2906
  */
2901
- if ((_b = incomingRouteParams.current) === null || _b === void 0 ? void 0 : _b.id) {
2907
+ if ((_a = incomingRouteParams.current) === null || _a === void 0 ? void 0 : _a.id) {
2902
2908
  routeInfo = Object.assign(Object.assign({}, incomingRouteParams.current), { lastPathname: leavingLocationInfo.pathname });
2903
2909
  locationHistory.current.add(routeInfo);
2904
2910
  /**
@@ -2907,9 +2913,9 @@ const IonRouter = ({ children, registerHistoryListener }) => {
2907
2913
  */
2908
2914
  }
2909
2915
  else {
2910
- const isPushed = ((_c = incomingRouteParams.current) === null || _c === void 0 ? void 0 : _c.routeAction) === 'push' &&
2916
+ const isPushed = ((_b = incomingRouteParams.current) === null || _b === void 0 ? void 0 : _b.routeAction) === 'push' &&
2911
2917
  incomingRouteParams.current.routeDirection === 'forward';
2912
- routeInfo = Object.assign(Object.assign({ id: generateId('routeInfo') }, incomingRouteParams.current), { lastPathname: leavingLocationInfo.pathname, pathname: location.pathname, search: location.search, params: ((_d = incomingRouteParams.current) === null || _d === void 0 ? void 0 : _d.params)
2918
+ routeInfo = Object.assign(Object.assign({ id: generateId('routeInfo') }, incomingRouteParams.current), { lastPathname: leavingLocationInfo.pathname, pathname: location.pathname, search: location.search, params: ((_c = incomingRouteParams.current) === null || _c === void 0 ? void 0 : _c.params)
2913
2919
  ? filterUndefinedParams(incomingRouteParams.current.params)
2914
2920
  : {}, prevRouteLastPathname: leavingLocationInfo.lastPathname });
2915
2921
  if (isPushed) {
@@ -2949,7 +2955,7 @@ const IonRouter = ({ children, registerHistoryListener }) => {
2949
2955
  routeInfo.pushedByRoute = lastRoute === null || lastRoute === void 0 ? void 0 : lastRoute.pushedByRoute;
2950
2956
  }
2951
2957
  else {
2952
- routeInfo.pushedByRoute = (_e = lastRoute === null || lastRoute === void 0 ? void 0 : lastRoute.pushedByRoute) !== null && _e !== void 0 ? _e : leavingLocationInfo.pathname;
2958
+ routeInfo.pushedByRoute = (_d = lastRoute === null || lastRoute === void 0 ? void 0 : lastRoute.pushedByRoute) !== null && _d !== void 0 ? _d : leavingLocationInfo.pathname;
2953
2959
  }
2954
2960
  // Triggered by `navigate()` with replace or a `<Navigate />` component, etc.
2955
2961
  }
@@ -3058,6 +3064,7 @@ const IonRouter = ({ children, registerHistoryListener }) => {
3058
3064
  *
3059
3065
  * @param tab The tab to set as active.
3060
3066
  */
3067
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
3061
3068
  const handleSetCurrentTab = (tab, _routeInfo) => {
3062
3069
  currentTab.current = tab;
3063
3070
  const current = locationHistory.current.current();
@@ -3197,6 +3204,24 @@ const IonRouter = ({ children, registerHistoryListener }) => {
3197
3204
  routeAnimation, tab: navigationTab });
3198
3205
  navigate(path, { replace: routeAction !== 'push' });
3199
3206
  };
3207
+ /**
3208
+ * Navigates to a new root path, clearing Ionic's navigation history so that
3209
+ * canGoBack() returns false after the transition. All previously mounted views
3210
+ * are unmounted. Useful for post-login / post-logout root navigation.
3211
+ *
3212
+ * @param pathname The path to navigate to.
3213
+ * @param routeAnimation An optional custom animation builder.
3214
+ */
3215
+ const handleNavigateRoot = (pathname, routeAnimation) => {
3216
+ currentTab.current = undefined;
3217
+ forwardStack.current = [];
3218
+ incomingRouteParams.current = {
3219
+ routeAction: 'replace',
3220
+ routeDirection: 'root',
3221
+ routeAnimation,
3222
+ };
3223
+ navigate(pathname, { replace: true });
3224
+ };
3200
3225
  const routeMangerContextValue = {
3201
3226
  canGoBack: () => locationHistory.current.canGoBack(),
3202
3227
  clearOutlet: viewStack.current.clear,
@@ -3211,7 +3236,7 @@ const IonRouter = ({ children, registerHistoryListener }) => {
3211
3236
  unMountViewItem: viewStack.current.remove,
3212
3237
  };
3213
3238
  return (React.createElement(RouteManagerContext.Provider, { value: routeMangerContextValue },
3214
- React.createElement(NavManager, { ionRoute: IonRouteInner, stackManager: StackManager, routeInfo: routeInfo, onNativeBack: handleNativeBack, onNavigateBack: handleNavigateBack, onNavigate: handleNavigate, onSetCurrentTab: handleSetCurrentTab, onChangeTab: handleChangeTab, onResetTab: handleResetTab, locationHistory: locationHistory.current }, children)));
3239
+ React.createElement(NavManager, { ionRoute: IonRouteInner, stackManager: StackManager, routeInfo: routeInfo, onNativeBack: handleNativeBack, onNavigateBack: handleNavigateBack, onNavigate: handleNavigate, onNavigateRoot: handleNavigateRoot, onSetCurrentTab: handleSetCurrentTab, onChangeTab: handleChangeTab, onResetTab: handleResetTab, locationHistory: locationHistory.current }, children)));
3215
3240
  };
3216
3241
  IonRouter.displayName = 'IonRouter';
3217
3242