@ionic/react-router 8.8.1-dev.11773873479.192398dc → 8.8.1-dev.11774029927.130994f5
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
|
@@ -1495,32 +1495,18 @@ class StackManager extends React.PureComponent {
|
|
|
1495
1495
|
return { enteringViewItem, leavingViewItem };
|
|
1496
1496
|
}
|
|
1497
1497
|
shouldUnmountLeavingView(routeInfo, enteringViewItem, leavingViewItem) {
|
|
1498
|
-
var _a, _b
|
|
1498
|
+
var _a, _b;
|
|
1499
1499
|
if (!leavingViewItem) {
|
|
1500
1500
|
return false;
|
|
1501
1501
|
}
|
|
1502
1502
|
if (routeInfo.routeAction === 'replace') {
|
|
1503
|
-
const
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
if (leavingRoutePath === '/' || leavingRoutePath === '') {
|
|
1503
|
+
const leavingRoutePath = (_b = (_a = leavingViewItem === null || leavingViewItem === void 0 ? void 0 : leavingViewItem.reactElement) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.path;
|
|
1504
|
+
// Never unmount root path or views without a path - needed for back navigation
|
|
1505
|
+
if (!leavingRoutePath || leavingRoutePath === '/' || leavingRoutePath === '') {
|
|
1507
1506
|
return false;
|
|
1508
1507
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
const normalized = path.replace(/\/\*$/, '');
|
|
1512
|
-
const lastSlash = normalized.lastIndexOf('/');
|
|
1513
|
-
return lastSlash > 0 ? normalized.substring(0, lastSlash) : '/';
|
|
1514
|
-
};
|
|
1515
|
-
const enteringParent = getParentPath(enteringRoutePath);
|
|
1516
|
-
const leavingParent = getParentPath(leavingRoutePath);
|
|
1517
|
-
// Unmount if routes are siblings or entering is a child of leaving (redirect)
|
|
1518
|
-
const areSiblings = enteringParent === leavingParent && enteringParent !== '/';
|
|
1519
|
-
const isChildRedirect = enteringRoutePath.startsWith(leavingRoutePath) ||
|
|
1520
|
-
(leavingRoutePath.endsWith('/*') && enteringRoutePath.startsWith(leavingRoutePath.slice(0, -2)));
|
|
1521
|
-
return areSiblings || isChildRedirect;
|
|
1522
|
-
}
|
|
1523
|
-
return false;
|
|
1508
|
+
// Replace actions unmount the leaving view since it's being replaced in history.
|
|
1509
|
+
return true;
|
|
1524
1510
|
}
|
|
1525
1511
|
// For non-replace actions, only unmount for back navigation
|
|
1526
1512
|
const isForwardPush = routeInfo.routeAction === 'push' && routeInfo.routeDirection === 'forward';
|
|
@@ -1540,6 +1526,19 @@ class StackManager extends React.PureComponent {
|
|
|
1540
1526
|
clearTimeout(this.outOfScopeUnmountTimeout);
|
|
1541
1527
|
this.outOfScopeUnmountTimeout = undefined;
|
|
1542
1528
|
}
|
|
1529
|
+
// Fire lifecycle events on any visible view before unmounting.
|
|
1530
|
+
// When navigating away from a tabbed section, the parent outlet fires
|
|
1531
|
+
// ionViewDidLeave on the tabs container, but the active tab child page
|
|
1532
|
+
// never receives its own lifecycle events because the core transition
|
|
1533
|
+
// dispatches events with bubbles:false. This ensures tab child pages
|
|
1534
|
+
// get ionViewWillLeave/ionViewDidLeave so useIonViewDidLeave fires.
|
|
1535
|
+
const allViewsInOutlet = this.context.getViewItemsForOutlet(this.id);
|
|
1536
|
+
allViewsInOutlet.forEach((viewItem) => {
|
|
1537
|
+
if (viewItem.ionPageElement && isViewVisible(viewItem.ionPageElement)) {
|
|
1538
|
+
viewItem.ionPageElement.dispatchEvent(new CustomEvent('ionViewWillLeave', { bubbles: false, cancelable: false }));
|
|
1539
|
+
viewItem.ionPageElement.dispatchEvent(new CustomEvent('ionViewDidLeave', { bubbles: false, cancelable: false }));
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1543
1542
|
// Remove view items from the stack but do NOT apply ion-page-hidden.
|
|
1544
1543
|
// ion-page-hidden sets display:none which immediately removes content
|
|
1545
1544
|
// from the layout, causing the parent outlet's leaving page to flash
|
|
@@ -1551,7 +1550,6 @@ class StackManager extends React.PureComponent {
|
|
|
1551
1550
|
// commit() captures the current DOM state (with content visible) before
|
|
1552
1551
|
// React processes the removal. The compositor's cached layer is unaffected
|
|
1553
1552
|
// by subsequent DOM changes during the animation.
|
|
1554
|
-
const allViewsInOutlet = this.context.getViewItemsForOutlet(this.id);
|
|
1555
1553
|
allViewsInOutlet.forEach((viewItem) => {
|
|
1556
1554
|
this.context.unMountViewItem(viewItem);
|
|
1557
1555
|
});
|
|
@@ -1691,7 +1689,12 @@ class StackManager extends React.PureComponent {
|
|
|
1691
1689
|
const shouldSkipAnimation = this.applySkipAnimationIfNeeded(enteringViewItem, leavingViewItem);
|
|
1692
1690
|
this.transitionPage(routeInfo, enteringViewItem, leavingViewItem, undefined, false, shouldSkipAnimation);
|
|
1693
1691
|
if (shouldUnmountLeavingViewItem && leavingViewItem && enteringViewItem !== leavingViewItem) {
|
|
1694
|
-
|
|
1692
|
+
// For non-replace actions (back nav), set mount=false here to hide the view.
|
|
1693
|
+
// For replace actions, handleLeavingViewUnmount sets mount=false only after
|
|
1694
|
+
// its container-to-container guard passes, avoiding zombie state.
|
|
1695
|
+
if (routeInfo.routeAction !== 'replace') {
|
|
1696
|
+
leavingViewItem.mount = false;
|
|
1697
|
+
}
|
|
1695
1698
|
this.handleLeavingViewUnmount(routeInfo, enteringViewItem, leavingViewItem);
|
|
1696
1699
|
}
|
|
1697
1700
|
// Clean up orphaned sibling views after replace actions (redirects)
|
|
@@ -1702,13 +1705,19 @@ class StackManager extends React.PureComponent {
|
|
|
1702
1705
|
*/
|
|
1703
1706
|
handleLeavingViewUnmount(routeInfo, enteringViewItem, leavingViewItem) {
|
|
1704
1707
|
var _a, _b, _c, _d, _e, _f;
|
|
1705
|
-
if (!leavingViewItem.ionPageElement) {
|
|
1706
|
-
return;
|
|
1707
|
-
}
|
|
1708
1708
|
// Only replace actions unmount views; push/pop cache for navigation history
|
|
1709
1709
|
if (routeInfo.routeAction !== 'replace') {
|
|
1710
1710
|
return;
|
|
1711
1711
|
}
|
|
1712
|
+
if (!leavingViewItem.ionPageElement) {
|
|
1713
|
+
leavingViewItem.mount = false;
|
|
1714
|
+
const viewToUnmount = leavingViewItem;
|
|
1715
|
+
setTimeout(() => {
|
|
1716
|
+
this.context.unMountViewItem(viewToUnmount);
|
|
1717
|
+
this.forceUpdate();
|
|
1718
|
+
}, VIEW_UNMOUNT_DELAY_MS);
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1712
1721
|
const enteringRoutePath = (_b = (_a = enteringViewItem.reactElement) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.path;
|
|
1713
1722
|
const leavingRoutePath = (_d = (_c = leavingViewItem.reactElement) === null || _c === void 0 ? void 0 : _c.props) === null || _d === void 0 ? void 0 : _d.path;
|
|
1714
1723
|
const isEnteringContainerRoute = enteringRoutePath && enteringRoutePath.endsWith('/*');
|
|
@@ -1722,6 +1731,7 @@ class StackManager extends React.PureComponent {
|
|
|
1722
1731
|
if (isEnteringContainerRoute && !isLeavingSpecificRoute) {
|
|
1723
1732
|
return;
|
|
1724
1733
|
}
|
|
1734
|
+
leavingViewItem.mount = false;
|
|
1725
1735
|
const viewToUnmount = leavingViewItem;
|
|
1726
1736
|
setTimeout(() => {
|
|
1727
1737
|
this.context.unMountViewItem(viewToUnmount);
|
|
@@ -1764,10 +1774,24 @@ class StackManager extends React.PureComponent {
|
|
|
1764
1774
|
}
|
|
1765
1775
|
const getParent = (path) => {
|
|
1766
1776
|
const normalized = path.replace(/\/\*$/, '');
|
|
1767
|
-
const
|
|
1768
|
-
|
|
1777
|
+
const segments = normalized.split('/').filter(Boolean);
|
|
1778
|
+
// Strip trailing parameter segments (e.g., :id) so that
|
|
1779
|
+
// sibling routes like /items/list/:id and /items/detail/:id
|
|
1780
|
+
// resolve to the same parent (/items).
|
|
1781
|
+
while (segments.length > 0 && segments[segments.length - 1].startsWith(':')) {
|
|
1782
|
+
segments.pop();
|
|
1783
|
+
}
|
|
1784
|
+
segments.pop();
|
|
1785
|
+
return segments.length > 0 ? '/' + segments.join('/') : '/';
|
|
1769
1786
|
};
|
|
1770
|
-
|
|
1787
|
+
const parent = getParent(path1);
|
|
1788
|
+
// Exclude root-level routes from sibling detection to avoid unintended
|
|
1789
|
+
// cleanup of unrelated top-level routes. Also covers single-depth param
|
|
1790
|
+
// routes (e.g., /items/:id) which resolve to root after param stripping.
|
|
1791
|
+
if (parent === '/') {
|
|
1792
|
+
return false;
|
|
1793
|
+
}
|
|
1794
|
+
return parent === getParent(path2);
|
|
1771
1795
|
};
|
|
1772
1796
|
for (const viewItem of allViewsInOutlet) {
|
|
1773
1797
|
const viewRoutePath = (_g = (_f = viewItem.reactElement) === null || _f === void 0 ? void 0 : _f.props) === null || _g === void 0 ? void 0 : _g.path;
|
|
@@ -1775,11 +1799,19 @@ class StackManager extends React.PureComponent {
|
|
|
1775
1799
|
(leavingViewItem && viewItem.id === leavingViewItem.id) ||
|
|
1776
1800
|
!viewItem.mount ||
|
|
1777
1801
|
!viewRoutePath ||
|
|
1802
|
+
// Don't clean up container routes when entering a container route
|
|
1803
|
+
// (e.g., /tabs/* and /settings/* coexist for tab switching)
|
|
1778
1804
|
(viewRoutePath.endsWith('/*') && enteringRoutePath.endsWith('/*'));
|
|
1779
1805
|
if (shouldSkip) {
|
|
1780
1806
|
continue;
|
|
1781
1807
|
}
|
|
1782
|
-
|
|
1808
|
+
const isOrphanedSpecificRoute = !viewRoutePath.endsWith('/*');
|
|
1809
|
+
// Clean up sibling non-container routes that are no longer reachable.
|
|
1810
|
+
let shouldCleanup = false;
|
|
1811
|
+
if ((isReplaceAction || isPushToContainer) && isOrphanedSpecificRoute) {
|
|
1812
|
+
shouldCleanup = areSiblingRoutes(enteringRoutePath, viewRoutePath);
|
|
1813
|
+
}
|
|
1814
|
+
if (shouldCleanup) {
|
|
1783
1815
|
hideIonPageElement(viewItem.ionPageElement);
|
|
1784
1816
|
viewItem.mount = false;
|
|
1785
1817
|
const viewToRemove = viewItem;
|
|
@@ -1858,7 +1890,10 @@ class StackManager extends React.PureComponent {
|
|
|
1858
1890
|
});
|
|
1859
1891
|
// Don't unmount if entering and leaving are the same view item
|
|
1860
1892
|
if (shouldUnmountLeavingViewItem && leavingViewItem && enteringViewItem !== leavingViewItem) {
|
|
1861
|
-
|
|
1893
|
+
if (routeInfo.routeAction !== 'replace') {
|
|
1894
|
+
leavingViewItem.mount = false;
|
|
1895
|
+
}
|
|
1896
|
+
this.handleLeavingViewUnmount(routeInfo, enteringViewItem, leavingViewItem);
|
|
1862
1897
|
}
|
|
1863
1898
|
this.forceUpdate();
|
|
1864
1899
|
return;
|
|
@@ -1884,8 +1919,9 @@ class StackManager extends React.PureComponent {
|
|
|
1884
1919
|
const shouldSkipAnimation = this.applySkipAnimationIfNeeded(latestEnteringView, latestLeavingView !== null && latestLeavingView !== void 0 ? latestLeavingView : undefined);
|
|
1885
1920
|
this.transitionPage(routeInfo, latestEnteringView, latestLeavingView !== null && latestLeavingView !== void 0 ? latestLeavingView : undefined, undefined, false, shouldSkipAnimation);
|
|
1886
1921
|
if (shouldUnmountLeavingViewItem && latestLeavingView && latestEnteringView !== latestLeavingView) {
|
|
1887
|
-
|
|
1888
|
-
|
|
1922
|
+
if (routeInfo.routeAction !== 'replace') {
|
|
1923
|
+
latestLeavingView.mount = false;
|
|
1924
|
+
}
|
|
1889
1925
|
this.handleLeavingViewUnmount(routeInfo, latestEnteringView, latestLeavingView);
|
|
1890
1926
|
}
|
|
1891
1927
|
this.forceUpdate();
|
|
@@ -1935,18 +1971,6 @@ class StackManager extends React.PureComponent {
|
|
|
1935
1971
|
}
|
|
1936
1972
|
componentDidMount() {
|
|
1937
1973
|
this._isMounted = true;
|
|
1938
|
-
if (this.clearOutletTimeout) {
|
|
1939
|
-
/**
|
|
1940
|
-
* The clearOutlet integration with React Router is a bit hacky.
|
|
1941
|
-
* It uses a timeout to clear the outlet after a transition.
|
|
1942
|
-
* In React v18, components are mounted and unmounted in development mode
|
|
1943
|
-
* to check for side effects.
|
|
1944
|
-
*
|
|
1945
|
-
* This clearTimeout prevents the outlet from being cleared when the component is re-mounted,
|
|
1946
|
-
* which should only happen in development mode and as a result of a hot reload.
|
|
1947
|
-
*/
|
|
1948
|
-
clearTimeout(this.clearOutletTimeout);
|
|
1949
|
-
}
|
|
1950
1974
|
if (this.routerOutletElement) {
|
|
1951
1975
|
this.setupRouterOutlet(this.routerOutletElement);
|
|
1952
1976
|
this.handlePageTransition(this.props.routeInfo);
|
|
@@ -1994,7 +2018,7 @@ class StackManager extends React.PureComponent {
|
|
|
1994
2018
|
allViewsInOutlet.forEach((viewItem) => {
|
|
1995
2019
|
hideIonPageElement(viewItem.ionPageElement);
|
|
1996
2020
|
});
|
|
1997
|
-
this.
|
|
2021
|
+
this.context.clearOutlet(this.id);
|
|
1998
2022
|
}
|
|
1999
2023
|
/**
|
|
2000
2024
|
* Sets the transition between pages within this router outlet.
|
|
@@ -2199,9 +2223,15 @@ class StackManager extends React.PureComponent {
|
|
|
2199
2223
|
}
|
|
2200
2224
|
// View might have mount=false but ionPageElement still in DOM
|
|
2201
2225
|
const ionPageInDocument = Boolean((enteringViewItem === null || enteringViewItem === void 0 ? void 0 : enteringViewItem.ionPageElement) && document.body.contains(enteringViewItem.ionPageElement));
|
|
2226
|
+
// For wildcard/parameterized routes, the pattern path (e.g. "/foo/*") will
|
|
2227
|
+
// never equal the resolved pathname (e.g. "/foo/bar"), so the pattern check
|
|
2228
|
+
// alone isn't sufficient. Also, verify the entering view's resolved pathname
|
|
2229
|
+
// differs from the current pathname — if they match, the entering and leaving
|
|
2230
|
+
// views are the same and the swipe gesture shouldn't start.
|
|
2202
2231
|
const canStartSwipe = !!enteringViewItem &&
|
|
2203
2232
|
(enteringViewItem.mount || ionPageInDocument) &&
|
|
2204
|
-
enteringViewItem.routeData.match.pattern.path !== routeInfo.pathname
|
|
2233
|
+
enteringViewItem.routeData.match.pattern.path !== routeInfo.pathname &&
|
|
2234
|
+
enteringViewItem.routeData.match.pathname !== routeInfo.pathname;
|
|
2205
2235
|
return canStartSwipe;
|
|
2206
2236
|
};
|
|
2207
2237
|
const onStart = async () => {
|