@ionic/react-router 8.7.13-dev.11765569652.136c6b13 → 8.7.13-dev.11765829391.14bc580c
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 +159 -24
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1316,12 +1316,39 @@ class StackManager extends React.PureComponent {
|
|
|
1316
1316
|
* Determines if the leaving view item should be unmounted after a transition.
|
|
1317
1317
|
*/
|
|
1318
1318
|
shouldUnmountLeavingView(routeInfo, enteringViewItem, leavingViewItem) {
|
|
1319
|
+
var _a, _b, _c, _d;
|
|
1319
1320
|
if (!leavingViewItem) {
|
|
1320
1321
|
return false;
|
|
1321
1322
|
}
|
|
1322
1323
|
if (routeInfo.routeAction === 'replace') {
|
|
1323
|
-
|
|
1324
|
+
// For replace actions, decide whether to unmount the leaving view.
|
|
1325
|
+
// The key question is: are these routes in the same navigation context?
|
|
1326
|
+
const enteringRoutePath = (_b = (_a = enteringViewItem === null || enteringViewItem === void 0 ? void 0 : enteringViewItem.reactElement) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.path;
|
|
1327
|
+
const leavingRoutePath = (_d = (_c = leavingViewItem === null || leavingViewItem === void 0 ? void 0 : leavingViewItem.reactElement) === null || _c === void 0 ? void 0 : _c.props) === null || _d === void 0 ? void 0 : _d.path;
|
|
1328
|
+
// Never unmount the root path "/" - it's the main entry point for back navigation
|
|
1329
|
+
if (leavingRoutePath === '/' || leavingRoutePath === '') {
|
|
1330
|
+
return false;
|
|
1331
|
+
}
|
|
1332
|
+
if (enteringRoutePath && leavingRoutePath) {
|
|
1333
|
+
// Get parent paths to check if routes share a common parent
|
|
1334
|
+
const getParentPath = (path) => {
|
|
1335
|
+
const normalized = path.replace(/\/\*$/, ''); // Remove trailing /*
|
|
1336
|
+
const lastSlash = normalized.lastIndexOf('/');
|
|
1337
|
+
return lastSlash > 0 ? normalized.substring(0, lastSlash) : '/';
|
|
1338
|
+
};
|
|
1339
|
+
const enteringParent = getParentPath(enteringRoutePath);
|
|
1340
|
+
const leavingParent = getParentPath(leavingRoutePath);
|
|
1341
|
+
// Unmount if:
|
|
1342
|
+
// 1. Routes are siblings (same parent, e.g., /page1 and /page2, or /foo/page1 and /foo/page2)
|
|
1343
|
+
// 2. Entering is a child of leaving (redirect, e.g., /tabs -> /tabs/tab1)
|
|
1344
|
+
const areSiblings = enteringParent === leavingParent && enteringParent !== '/';
|
|
1345
|
+
const isChildRedirect = enteringRoutePath.startsWith(leavingRoutePath) ||
|
|
1346
|
+
(leavingRoutePath.endsWith('/*') && enteringRoutePath.startsWith(leavingRoutePath.slice(0, -2)));
|
|
1347
|
+
return areSiblings || isChildRedirect;
|
|
1348
|
+
}
|
|
1349
|
+
return false;
|
|
1324
1350
|
}
|
|
1351
|
+
// For non-replace actions, only unmount for back navigation (not forward push)
|
|
1325
1352
|
const isForwardPush = routeInfo.routeAction === 'push' && routeInfo.routeDirection === 'forward';
|
|
1326
1353
|
if (!isForwardPush && routeInfo.routeDirection !== 'none' && enteringViewItem !== leavingViewItem) {
|
|
1327
1354
|
return true;
|
|
@@ -1402,8 +1429,6 @@ class StackManager extends React.PureComponent {
|
|
|
1402
1429
|
*/
|
|
1403
1430
|
handleReadyEnteringView(routeInfo, enteringViewItem, leavingViewItem, shouldUnmountLeavingViewItem) {
|
|
1404
1431
|
var _a, _b;
|
|
1405
|
-
// Ensure the entering view is not hidden from previous navigations
|
|
1406
|
-
showIonPageElement(enteringViewItem.ionPageElement);
|
|
1407
1432
|
// Handle same view item case (e.g., parameterized route changes)
|
|
1408
1433
|
if (enteringViewItem === leavingViewItem) {
|
|
1409
1434
|
const routePath = (_b = (_a = enteringViewItem.reactElement) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.path;
|
|
@@ -1427,24 +1452,76 @@ class StackManager extends React.PureComponent {
|
|
|
1427
1452
|
if (!leavingViewItem && this.props.routeInfo.prevRouteLastPathname) {
|
|
1428
1453
|
leavingViewItem = this.context.findViewItemByPathname(this.props.routeInfo.prevRouteLastPathname, this.id);
|
|
1429
1454
|
}
|
|
1430
|
-
//
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
}
|
|
1455
|
+
// Ensure the entering view is marked as mounted.
|
|
1456
|
+
// This is critical for views that were previously unmounted (e.g., navigating back to home).
|
|
1457
|
+
// When mount=false, the ViewLifeCycleManager doesn't render the IonPage, so the
|
|
1458
|
+
// ionPageElement reference becomes stale. By setting mount=true, we ensure the view
|
|
1459
|
+
// gets re-rendered and a new IonPage is created.
|
|
1460
|
+
if (!enteringViewItem.mount) {
|
|
1461
|
+
enteringViewItem.mount = true;
|
|
1462
|
+
}
|
|
1463
|
+
// Check visibility state BEFORE showing the entering view.
|
|
1464
|
+
// This must be done before showIonPageElement to get accurate visibility state.
|
|
1465
|
+
const enteringWasVisible = enteringViewItem.ionPageElement && isViewVisible(enteringViewItem.ionPageElement);
|
|
1466
|
+
const leavingIsHidden = leavingViewItem !== undefined && leavingViewItem.ionPageElement && !isViewVisible(leavingViewItem.ionPageElement);
|
|
1438
1467
|
// Check for duplicate transition
|
|
1439
1468
|
const currentTransition = {
|
|
1440
1469
|
enteringId: enteringViewItem.id,
|
|
1441
1470
|
leavingId: leavingViewItem === null || leavingViewItem === void 0 ? void 0 : leavingViewItem.id,
|
|
1442
1471
|
};
|
|
1443
|
-
|
|
1472
|
+
const isDuplicateTransition = leavingViewItem &&
|
|
1444
1473
|
this.lastTransition &&
|
|
1445
1474
|
this.lastTransition.leavingId &&
|
|
1446
1475
|
this.lastTransition.enteringId === currentTransition.enteringId &&
|
|
1447
|
-
this.lastTransition.leavingId === currentTransition.leavingId
|
|
1476
|
+
this.lastTransition.leavingId === currentTransition.leavingId;
|
|
1477
|
+
// Skip transition if entering view was ALREADY visible and leaving view is not visible.
|
|
1478
|
+
// This indicates the transition has already been performed (e.g., via swipe gesture).
|
|
1479
|
+
// IMPORTANT: Only skip if both ionPageElements are the same as when the transition was last done.
|
|
1480
|
+
// If the leaving view's ionPageElement changed (e.g., component re-rendered with different IonPage),
|
|
1481
|
+
// we should NOT skip because the DOM state is inconsistent.
|
|
1482
|
+
if (enteringWasVisible && leavingIsHidden && isDuplicateTransition) {
|
|
1483
|
+
// For swipe-to-go-back, the transition animation was handled by the gesture.
|
|
1484
|
+
// We still need to set mount=false so React unmounts the leaving view.
|
|
1485
|
+
// Only do this when skipTransition is set (indicating gesture completion).
|
|
1486
|
+
if (this.skipTransition &&
|
|
1487
|
+
shouldUnmountLeavingViewItem &&
|
|
1488
|
+
leavingViewItem &&
|
|
1489
|
+
enteringViewItem !== leavingViewItem) {
|
|
1490
|
+
leavingViewItem.mount = false;
|
|
1491
|
+
// Call transitionPage with duration 0 to trigger ionViewDidLeave lifecycle
|
|
1492
|
+
// which is needed for ViewLifeCycleManager to remove the view.
|
|
1493
|
+
this.transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back');
|
|
1494
|
+
}
|
|
1495
|
+
// Clear skipTransition since we're not calling transitionPage which normally clears it
|
|
1496
|
+
this.skipTransition = false;
|
|
1497
|
+
// Must call forceUpdate to trigger re-render after mount state change
|
|
1498
|
+
this.forceUpdate();
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1501
|
+
// Ensure the entering view is not hidden from previous navigations
|
|
1502
|
+
// This must happen AFTER the visibility check above
|
|
1503
|
+
showIonPageElement(enteringViewItem.ionPageElement);
|
|
1504
|
+
// Skip if this is a duplicate transition (but visibility state didn't match above)
|
|
1505
|
+
// OR if skipTransition is set (swipe gesture already handled the animation)
|
|
1506
|
+
if (isDuplicateTransition || this.skipTransition) {
|
|
1507
|
+
// For swipe-to-go-back, we still need to handle unmounting even if visibility
|
|
1508
|
+
// conditions aren't fully met (animation might still be in progress)
|
|
1509
|
+
if (this.skipTransition &&
|
|
1510
|
+
shouldUnmountLeavingViewItem &&
|
|
1511
|
+
leavingViewItem &&
|
|
1512
|
+
enteringViewItem !== leavingViewItem) {
|
|
1513
|
+
leavingViewItem.mount = false;
|
|
1514
|
+
// For swipe-to-go-back, we need to call transitionPage with duration 0 to
|
|
1515
|
+
// trigger the ionViewDidLeave lifecycle event. The ViewLifeCycleManager
|
|
1516
|
+
// uses componentCanBeDestroyed callback to remove the view, which is
|
|
1517
|
+
// only called from ionViewDidLeave. Since the gesture animation already
|
|
1518
|
+
// completed before mount=false was set, we need to re-fire the lifecycle.
|
|
1519
|
+
this.transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back');
|
|
1520
|
+
}
|
|
1521
|
+
// Clear skipTransition since we're not calling transitionPage which normally clears it
|
|
1522
|
+
this.skipTransition = false;
|
|
1523
|
+
// Must call forceUpdate to trigger re-render after mount state change
|
|
1524
|
+
this.forceUpdate();
|
|
1448
1525
|
return;
|
|
1449
1526
|
}
|
|
1450
1527
|
this.lastTransition = currentTransition;
|
|
@@ -1456,14 +1533,28 @@ class StackManager extends React.PureComponent {
|
|
|
1456
1533
|
}
|
|
1457
1534
|
}
|
|
1458
1535
|
/**
|
|
1459
|
-
* Handles the delayed unmount of the leaving view item
|
|
1536
|
+
* Handles the delayed unmount of the leaving view item.
|
|
1537
|
+
* For 'replace' actions: handles container route transitions specially.
|
|
1538
|
+
* For back navigation: explicitly unmounts because the ionViewDidLeave lifecycle
|
|
1539
|
+
* fires DURING transitionPage, but mount=false is set AFTER.
|
|
1540
|
+
*
|
|
1541
|
+
* @param routeInfo Current route information
|
|
1542
|
+
* @param enteringViewItem The view being navigated to
|
|
1543
|
+
* @param leavingViewItem The view being navigated from
|
|
1460
1544
|
*/
|
|
1461
1545
|
handleLeavingViewUnmount(routeInfo, enteringViewItem, leavingViewItem) {
|
|
1462
1546
|
var _a, _b, _c, _d, _e, _f;
|
|
1463
|
-
if (
|
|
1547
|
+
if (!leavingViewItem.ionPageElement) {
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
// For push/pop actions, do NOT unmount - views are cached for navigation history.
|
|
1551
|
+
// Push: Forward navigation caches views for back navigation
|
|
1552
|
+
// Pop: Back navigation should not unmount the entering view's history
|
|
1553
|
+
// Only 'replace' actions should actually unmount views since they replace history.
|
|
1554
|
+
if (routeInfo.routeAction !== 'replace') {
|
|
1464
1555
|
return;
|
|
1465
1556
|
}
|
|
1466
|
-
//
|
|
1557
|
+
// For replace actions, check if we should skip removal for nested outlet redirects
|
|
1467
1558
|
const enteringRoutePath = (_b = (_a = enteringViewItem.reactElement) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.path;
|
|
1468
1559
|
const leavingRoutePath = (_d = (_c = leavingViewItem.reactElement) === null || _c === void 0 ? void 0 : _c.props) === null || _d === void 0 ? void 0 : _d.path;
|
|
1469
1560
|
const isEnteringContainerRoute = enteringRoutePath && enteringRoutePath.endsWith('/*');
|
|
@@ -1479,6 +1570,8 @@ class StackManager extends React.PureComponent {
|
|
|
1479
1570
|
const viewToUnmount = leavingViewItem;
|
|
1480
1571
|
setTimeout(() => {
|
|
1481
1572
|
this.context.unMountViewItem(viewToUnmount);
|
|
1573
|
+
// Trigger re-render to remove the view from DOM
|
|
1574
|
+
this.forceUpdate();
|
|
1482
1575
|
}, VIEW_UNMOUNT_DELAY_MS);
|
|
1483
1576
|
}
|
|
1484
1577
|
/**
|
|
@@ -1523,6 +1616,8 @@ class StackManager extends React.PureComponent {
|
|
|
1523
1616
|
this.transitionPage(routeInfo, latestEnteringView, latestLeavingView !== null && latestLeavingView !== void 0 ? latestLeavingView : undefined);
|
|
1524
1617
|
if (shouldUnmountLeavingViewItem && latestLeavingView && latestEnteringView !== latestLeavingView) {
|
|
1525
1618
|
latestLeavingView.mount = false;
|
|
1619
|
+
// Call handleLeavingViewUnmount to ensure the view is properly removed
|
|
1620
|
+
this.handleLeavingViewUnmount(routeInfo, latestEnteringView, latestLeavingView);
|
|
1526
1621
|
}
|
|
1527
1622
|
this.forceUpdate();
|
|
1528
1623
|
}
|
|
@@ -1646,7 +1741,12 @@ class StackManager extends React.PureComponent {
|
|
|
1646
1741
|
this.context.addViewItem(enteringViewItem);
|
|
1647
1742
|
}
|
|
1648
1743
|
// Handle transition based on ion-page element availability
|
|
1649
|
-
if
|
|
1744
|
+
// Check if the ionPageElement is still in the document.
|
|
1745
|
+
// If the view was previously unmounted (mount=false), the ViewLifeCycleManager
|
|
1746
|
+
// removes the React component from the tree, which removes the IonPage from the DOM.
|
|
1747
|
+
// The ionPageElement reference becomes stale and we need to wait for a new one.
|
|
1748
|
+
const ionPageIsInDocument = (enteringViewItem === null || enteringViewItem === void 0 ? void 0 : enteringViewItem.ionPageElement) && document.body.contains(enteringViewItem.ionPageElement);
|
|
1749
|
+
if (enteringViewItem && ionPageIsInDocument) {
|
|
1650
1750
|
// Clear waiting state
|
|
1651
1751
|
if (this.waitingForIonPage) {
|
|
1652
1752
|
this.waitingForIonPage = false;
|
|
@@ -1657,8 +1757,17 @@ class StackManager extends React.PureComponent {
|
|
|
1657
1757
|
}
|
|
1658
1758
|
this.handleReadyEnteringView(routeInfo, enteringViewItem, leavingViewItem, shouldUnmountLeavingViewItem);
|
|
1659
1759
|
}
|
|
1660
|
-
else if (enteringViewItem && !
|
|
1760
|
+
else if (enteringViewItem && !ionPageIsInDocument) {
|
|
1661
1761
|
// Wait for ion-page to mount
|
|
1762
|
+
// This handles both: no ionPageElement, or stale ionPageElement (not in document)
|
|
1763
|
+
// Clear stale reference if the element is no longer in the document
|
|
1764
|
+
if (enteringViewItem.ionPageElement && !document.body.contains(enteringViewItem.ionPageElement)) {
|
|
1765
|
+
enteringViewItem.ionPageElement = undefined;
|
|
1766
|
+
}
|
|
1767
|
+
// Ensure the view is marked as mounted so ViewLifeCycleManager renders the IonPage
|
|
1768
|
+
if (!enteringViewItem.mount) {
|
|
1769
|
+
enteringViewItem.mount = true;
|
|
1770
|
+
}
|
|
1662
1771
|
this.handleWaitingForIonPage(routeInfo, enteringViewItem, leavingViewItem, shouldUnmountLeavingViewItem);
|
|
1663
1772
|
return;
|
|
1664
1773
|
}
|
|
@@ -1760,11 +1869,23 @@ class StackManager extends React.PureComponent {
|
|
|
1760
1869
|
}
|
|
1761
1870
|
const { routeInfo } = this.props;
|
|
1762
1871
|
const swipeBackRouteInfo = this.getSwipeBackRouteInfo();
|
|
1763
|
-
|
|
1872
|
+
// First try to find the view in the current outlet
|
|
1873
|
+
let enteringViewItem = this.context.findViewItemByRouteInfo(swipeBackRouteInfo, this.id, false);
|
|
1874
|
+
// If not found in current outlet, search all outlets (for cross-outlet swipe back)
|
|
1875
|
+
if (!enteringViewItem) {
|
|
1876
|
+
enteringViewItem = this.context.findViewItemByRouteInfo(swipeBackRouteInfo, undefined, false);
|
|
1877
|
+
}
|
|
1878
|
+
// Check if the ionPageElement is still in the document.
|
|
1879
|
+
// A view might have mount=false but still have its ionPageElement in the DOM
|
|
1880
|
+
// (due to timing differences in unmounting).
|
|
1881
|
+
const ionPageInDocument = Boolean((enteringViewItem === null || enteringViewItem === void 0 ? void 0 : enteringViewItem.ionPageElement) && document.body.contains(enteringViewItem.ionPageElement));
|
|
1764
1882
|
const canStartSwipe = !!enteringViewItem &&
|
|
1765
|
-
//
|
|
1766
|
-
//
|
|
1767
|
-
|
|
1883
|
+
// Check if we can swipe to this view. Either:
|
|
1884
|
+
// 1. The view is mounted (mount=true), OR
|
|
1885
|
+
// 2. The view's ionPageElement is still in the document
|
|
1886
|
+
// The second case handles views that have been marked for unmount but haven't
|
|
1887
|
+
// actually been removed from the DOM yet.
|
|
1888
|
+
(enteringViewItem.mount || ionPageInDocument) &&
|
|
1768
1889
|
// When on the first page it is possible for findViewItemByRouteInfo to
|
|
1769
1890
|
// return the exact same view you are currently on.
|
|
1770
1891
|
// Make sure that we are not swiping back to the same instances of a view.
|
|
@@ -1774,8 +1895,18 @@ class StackManager extends React.PureComponent {
|
|
|
1774
1895
|
const onStart = async () => {
|
|
1775
1896
|
const { routeInfo } = this.props;
|
|
1776
1897
|
const swipeBackRouteInfo = this.getSwipeBackRouteInfo();
|
|
1777
|
-
|
|
1898
|
+
// First try to find the view in the current outlet, then search all outlets
|
|
1899
|
+
let enteringViewItem = this.context.findViewItemByRouteInfo(swipeBackRouteInfo, this.id, false);
|
|
1900
|
+
if (!enteringViewItem) {
|
|
1901
|
+
enteringViewItem = this.context.findViewItemByRouteInfo(swipeBackRouteInfo, undefined, false);
|
|
1902
|
+
}
|
|
1778
1903
|
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
|
|
1904
|
+
// Ensure the entering view is mounted so React keeps rendering it during the gesture.
|
|
1905
|
+
// This is important when the view was previously marked for unmount but its
|
|
1906
|
+
// ionPageElement is still in the DOM.
|
|
1907
|
+
if (enteringViewItem && !enteringViewItem.mount) {
|
|
1908
|
+
enteringViewItem.mount = true;
|
|
1909
|
+
}
|
|
1779
1910
|
// When the gesture starts, kick off a transition controlled via swipe gesture
|
|
1780
1911
|
if (enteringViewItem && leavingViewItem) {
|
|
1781
1912
|
await this.transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true);
|
|
@@ -1792,7 +1923,11 @@ class StackManager extends React.PureComponent {
|
|
|
1792
1923
|
// Swipe gesture was aborted - re-hide the page that was going to enter
|
|
1793
1924
|
const { routeInfo } = this.props;
|
|
1794
1925
|
const swipeBackRouteInfo = this.getSwipeBackRouteInfo();
|
|
1795
|
-
|
|
1926
|
+
// First try to find the view in the current outlet, then search all outlets
|
|
1927
|
+
let enteringViewItem = this.context.findViewItemByRouteInfo(swipeBackRouteInfo, this.id, false);
|
|
1928
|
+
if (!enteringViewItem) {
|
|
1929
|
+
enteringViewItem = this.context.findViewItemByRouteInfo(swipeBackRouteInfo, undefined, false);
|
|
1930
|
+
}
|
|
1796
1931
|
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
|
|
1797
1932
|
// Don't hide if entering and leaving are the same (parameterized route edge case)
|
|
1798
1933
|
if (enteringViewItem !== leavingViewItem && (enteringViewItem === null || enteringViewItem === void 0 ? void 0 : enteringViewItem.ionPageElement) !== undefined) {
|