@ionic/react-router 9.0.6-dev.11790349282.1a877abd → 9.0.6-dev.11790615145.1d8c56a2
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 +55 -108
- package/dist/index.js.map +1 -1
- package/dist/types/ReactRouter/utils/viewItemUtils.d.ts +1 -27
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -499,41 +499,6 @@ const compareRouteSpecificity = (a, b) => {
|
|
|
499
499
|
}
|
|
500
500
|
return 0;
|
|
501
501
|
};
|
|
502
|
-
/**
|
|
503
|
-
* True when a route matches more pathnames than its own path, so a splat, an index route,
|
|
504
|
-
* or a route with an empty or absent path. A lookup can return one of these for a pathname
|
|
505
|
-
* a more specific sibling owns, so callers must confirm ownership against React Router's
|
|
506
|
-
* ranking before reusing the view item.
|
|
507
|
-
*
|
|
508
|
-
* This is deliberately wider than the catch-all checks in ReactRouterViewStack, which each
|
|
509
|
-
* gate on a narrower shape for a different reason. Don't unify them with this helper.
|
|
510
|
-
*/
|
|
511
|
-
const isOverMatchingRoute = (route) => {
|
|
512
|
-
const { path, index } = route;
|
|
513
|
-
return !path || path.includes('*') || !!index;
|
|
514
|
-
};
|
|
515
|
-
/** True when the navigation pushed a new page forward on top of the current one. */
|
|
516
|
-
const isForwardPush = (routeInfo) => routeInfo.routeAction === 'push' && routeInfo.routeDirection === 'forward';
|
|
517
|
-
const swipeRevealed = new WeakSet();
|
|
518
|
-
/**
|
|
519
|
-
* Marks the page a swipe-back gesture has revealed. For the length of the drag that page
|
|
520
|
-
* is on screen while a more specific sibling still matches the current pathname, and the
|
|
521
|
-
* deactivation scan in `renderViewItem` would otherwise re-hide it on the next render and
|
|
522
|
-
* leave the user dragging a blank page.
|
|
523
|
-
*/
|
|
524
|
-
const markSwipeRevealed = (viewItem) => {
|
|
525
|
-
if (viewItem) {
|
|
526
|
-
swipeRevealed.add(viewItem);
|
|
527
|
-
}
|
|
528
|
-
};
|
|
529
|
-
/** Drops the mark once the gesture ends, so the view is hidden normally again. */
|
|
530
|
-
const clearSwipeRevealed = (viewItem) => {
|
|
531
|
-
if (viewItem) {
|
|
532
|
-
swipeRevealed.delete(viewItem);
|
|
533
|
-
}
|
|
534
|
-
};
|
|
535
|
-
/** True while a swipe-back gesture is showing this view. */
|
|
536
|
-
const isSwipeRevealed = (viewItem) => swipeRevealed.has(viewItem);
|
|
537
502
|
/**
|
|
538
503
|
* Sorts view items by route specificity (most specific first).
|
|
539
504
|
*
|
|
@@ -895,7 +860,7 @@ class ReactRouterViewStack extends ViewStacks {
|
|
|
895
860
|
}
|
|
896
861
|
// Deactivate wildcard (catch-all) and empty-path (default) routes when a more-specific route matches.
|
|
897
862
|
// This prevents "Not found" or fallback pages from showing alongside valid routes.
|
|
898
|
-
if (routePath === '*' || routePath === '
|
|
863
|
+
if (routePath === '*' || routePath === '') {
|
|
899
864
|
// Check if any other view in this outlet has a match for the current route
|
|
900
865
|
const outletViews = this.getViewItemsForOutlet(viewItem.outletId);
|
|
901
866
|
// When parent path context is available, compute the relative pathname once
|
|
@@ -948,15 +913,8 @@ class ReactRouterViewStack extends ViewStacks {
|
|
|
948
913
|
}
|
|
949
914
|
}
|
|
950
915
|
}
|
|
951
|
-
if (hasSpecificMatch
|
|
952
|
-
|
|
953
|
-
// Pushed over it is the page underneath, so unmounting it destroys its state and
|
|
954
|
-
// leaves nothing for back to reveal. Hiding it below covers that. A view with no
|
|
955
|
-
// ion-page was never a page in the stack and has nothing to hide, so it still
|
|
956
|
-
// unmounts.
|
|
957
|
-
if (!isForwardPush(routeInfo) || !viewItem.ionPageElement) {
|
|
958
|
-
viewItem.mount = false;
|
|
959
|
-
}
|
|
916
|
+
if (hasSpecificMatch) {
|
|
917
|
+
viewItem.mount = false;
|
|
960
918
|
if (viewItem.ionPageElement) {
|
|
961
919
|
viewItem.ionPageElement.classList.add('ion-page-hidden');
|
|
962
920
|
viewItem.ionPageElement.setAttribute('aria-hidden', 'true');
|
|
@@ -1026,11 +984,8 @@ class ReactRouterViewStack extends ViewStacks {
|
|
|
1026
984
|
parentPath = result.parentPath;
|
|
1027
985
|
// Persist the mount path for subsequent calls, mirroring StackManager.outletMountPath.
|
|
1028
986
|
// Unlike outletParentPaths (cleared when parentPath is undefined), the mount path is
|
|
1029
|
-
// intentionally sticky
|
|
1030
|
-
|
|
1031
|
-
// would scope it to whatever route was active. parentPathnameBase is undefined
|
|
1032
|
-
// exactly when the outlet has no parent matches.
|
|
1033
|
-
if (parentPathnameBase && result.outletMountPath && !this.outletMountPaths.has(outletId)) {
|
|
987
|
+
// intentionally sticky — it anchors the outlet's scope and is only removed in clear().
|
|
988
|
+
if (result.outletMountPath && !this.outletMountPaths.has(outletId)) {
|
|
1034
989
|
this.outletMountPaths.set(outletId, result.outletMountPath);
|
|
1035
990
|
}
|
|
1036
991
|
}
|
|
@@ -1452,6 +1407,12 @@ const revealIonPageForSwipeBack = (element) => {
|
|
|
1452
1407
|
element.removeAttribute('aria-hidden');
|
|
1453
1408
|
}
|
|
1454
1409
|
};
|
|
1410
|
+
/** Dispatches a view lifecycle event the way core's `lifecycle()` does. */
|
|
1411
|
+
const dispatchLifecycleEvent = (element, eventName) => {
|
|
1412
|
+
if (element) {
|
|
1413
|
+
element.dispatchEvent(new CustomEvent(eventName, { bubbles: false, cancelable: false }));
|
|
1414
|
+
}
|
|
1415
|
+
};
|
|
1455
1416
|
/**
|
|
1456
1417
|
* A leaf view is "preservable" on browser-back (pop) when its React state
|
|
1457
1418
|
* should survive a forward-pop round-trip. Non-parameterized leaf paths
|
|
@@ -1480,8 +1441,6 @@ class StackManager extends React.PureComponent {
|
|
|
1480
1441
|
routerOutletElement;
|
|
1481
1442
|
prevProps;
|
|
1482
1443
|
skipTransition;
|
|
1483
|
-
/** The view item the in-flight swipe gesture revealed, so the same one gets cleared. */
|
|
1484
|
-
swipeRevealedViewItem;
|
|
1485
1444
|
stackContextValue = {
|
|
1486
1445
|
registerIonPage: this.registerIonPage.bind(this),
|
|
1487
1446
|
isInOutlet: () => true,
|
|
@@ -1573,9 +1532,7 @@ class StackManager extends React.PureComponent {
|
|
|
1573
1532
|
hasIndexRoute,
|
|
1574
1533
|
hasWildcardRoute,
|
|
1575
1534
|
});
|
|
1576
|
-
|
|
1577
|
-
// it to whatever route was active and make every other pathname look out of scope.
|
|
1578
|
-
if (!this.isRootOutlet && result.outletMountPath && !this.outletMountPath) {
|
|
1535
|
+
if (result.outletMountPath && !this.outletMountPath) {
|
|
1579
1536
|
this.outletMountPath = result.outletMountPath;
|
|
1580
1537
|
}
|
|
1581
1538
|
return result.parentPath;
|
|
@@ -1630,16 +1587,12 @@ class StackManager extends React.PureComponent {
|
|
|
1630
1587
|
return true;
|
|
1631
1588
|
}
|
|
1632
1589
|
// For non-replace actions, only unmount for back navigation
|
|
1633
|
-
|
|
1590
|
+
const isForwardPush = routeInfo.routeAction === 'push' && routeInfo.routeDirection === 'forward';
|
|
1591
|
+
if (!isForwardPush && routeInfo.routeDirection !== 'none' && enteringViewItem !== leavingViewItem) {
|
|
1634
1592
|
return true;
|
|
1635
1593
|
}
|
|
1636
1594
|
return false;
|
|
1637
1595
|
}
|
|
1638
|
-
/** Clears the reveal flag from the view item the gesture marked. */
|
|
1639
|
-
clearSwipeRevealedView() {
|
|
1640
|
-
clearSwipeRevealed(this.swipeRevealedViewItem);
|
|
1641
|
-
this.swipeRevealedViewItem = undefined;
|
|
1642
|
-
}
|
|
1643
1596
|
/**
|
|
1644
1597
|
* Handles out-of-scope outlet. Returns true if transition should be aborted.
|
|
1645
1598
|
*/
|
|
@@ -1688,8 +1641,8 @@ class StackManager extends React.PureComponent {
|
|
|
1688
1641
|
const allViewsInOutlet = this.context.getViewItemsForOutlet(this.id);
|
|
1689
1642
|
allViewsInOutlet.forEach((viewItem) => {
|
|
1690
1643
|
if (viewItem.ionPageElement && isViewVisible(viewItem.ionPageElement)) {
|
|
1691
|
-
viewItem.ionPageElement
|
|
1692
|
-
viewItem.ionPageElement
|
|
1644
|
+
dispatchLifecycleEvent(viewItem.ionPageElement, 'ionViewWillLeave');
|
|
1645
|
+
dispatchLifecycleEvent(viewItem.ionPageElement, 'ionViewDidLeave');
|
|
1693
1646
|
}
|
|
1694
1647
|
});
|
|
1695
1648
|
// Defer removal of view items to allow the parent outlet's leaving-page
|
|
@@ -1724,8 +1677,8 @@ class StackManager extends React.PureComponent {
|
|
|
1724
1677
|
return;
|
|
1725
1678
|
}
|
|
1726
1679
|
if (viewItem.ionPageElement && isViewVisible(viewItem.ionPageElement)) {
|
|
1727
|
-
viewItem.ionPageElement
|
|
1728
|
-
viewItem.ionPageElement
|
|
1680
|
+
dispatchLifecycleEvent(viewItem.ionPageElement, 'ionViewWillLeave');
|
|
1681
|
+
dispatchLifecycleEvent(viewItem.ionPageElement, 'ionViewDidLeave');
|
|
1729
1682
|
}
|
|
1730
1683
|
this.context.unMountViewItem(viewItem);
|
|
1731
1684
|
});
|
|
@@ -1809,9 +1762,7 @@ class StackManager extends React.PureComponent {
|
|
|
1809
1762
|
const containerBase = routePath.replace(/\/\*$/, '');
|
|
1810
1763
|
const currentInContainer = routeInfo.pathname.startsWith(containerBase + '/') || routeInfo.pathname === containerBase;
|
|
1811
1764
|
const previousInContainer = routeInfo.lastPathname.startsWith(containerBase + '/') || routeInfo.lastPathname === containerBase;
|
|
1812
|
-
|
|
1813
|
-
// pathname and the shortcut would otherwise skip every navigation in this outlet.
|
|
1814
|
-
if (containerBase !== '' && currentInContainer && previousInContainer) {
|
|
1765
|
+
if (currentInContainer && previousInContainer) {
|
|
1815
1766
|
const updatedMatch = matchComponent(enteringViewItem.reactElement, routeInfo.pathname, true, this.outletMountPath);
|
|
1816
1767
|
if (updatedMatch) {
|
|
1817
1768
|
enteringViewItem.routeData.match = updatedMatch;
|
|
@@ -2273,7 +2224,6 @@ class StackManager extends React.PureComponent {
|
|
|
2273
2224
|
}
|
|
2274
2225
|
this.waitingForIonPage = false;
|
|
2275
2226
|
this.preservedViewItems.clear();
|
|
2276
|
-
this.clearSwipeRevealedView();
|
|
2277
2227
|
// Hide all views in this outlet before clearing.
|
|
2278
2228
|
// This is critical for nested outlets - when the parent component unmounts,
|
|
2279
2229
|
// the nested outlet's componentDidUpdate won't be called, so we must hide
|
|
@@ -2305,32 +2255,13 @@ class StackManager extends React.PureComponent {
|
|
|
2305
2255
|
this.pendingPageTransition = true;
|
|
2306
2256
|
return;
|
|
2307
2257
|
}
|
|
2308
|
-
// A completed swipe keeps its reveal flag past goBack() so the deactivation scan does
|
|
2309
|
-
// not re-hide the page between the gesture ending and the URL settling. Clear it at
|
|
2310
|
-
// the start of the transition for the new pathname.
|
|
2311
|
-
this.clearSwipeRevealedView();
|
|
2312
2258
|
// Find entering and leaving view items
|
|
2313
2259
|
const viewItems = this.findViewItems(routeInfo);
|
|
2314
2260
|
let enteringViewItem = viewItems.enteringViewItem;
|
|
2315
2261
|
let leavingViewItem = viewItems.leavingViewItem;
|
|
2262
|
+
let shouldUnmountLeavingViewItem = this.shouldUnmountLeavingView(routeInfo, enteringViewItem, leavingViewItem);
|
|
2316
2263
|
// Get parent path for nested outlets
|
|
2317
2264
|
const parentPath = this.getParentPath();
|
|
2318
|
-
// Find the matching route element. This is React Router's own ranking of the outlet's
|
|
2319
|
-
// routes, so it decides which route owns the pathname.
|
|
2320
|
-
const enteringRoute = findRouteByRouteInfo(this.ionRouterOutlet?.props.children, routeInfo, parentPath);
|
|
2321
|
-
// The lookup above only matches view items that already exist, so a sibling route with
|
|
2322
|
-
// no view item yet never wins there and an over-matching route (splat, index, empty
|
|
2323
|
-
// path) comes back for pathnames it does not own. Drop it rather than reusing it,
|
|
2324
|
-
// because overwriting its reactElement below would swap its page for the winning
|
|
2325
|
-
// route's and unmount it. This has to resolve before shouldUnmountLeavingView and
|
|
2326
|
-
// handleRootNavigation, which both branch on the entering view item.
|
|
2327
|
-
if (enteringViewItem &&
|
|
2328
|
-
enteringRoute &&
|
|
2329
|
-
isOverMatchingRoute(enteringViewItem.routeData?.childProps ?? {}) &&
|
|
2330
|
-
enteringRoute.props.path !== enteringViewItem.routeData?.childProps?.path) {
|
|
2331
|
-
enteringViewItem = undefined;
|
|
2332
|
-
}
|
|
2333
|
-
let shouldUnmountLeavingViewItem = this.shouldUnmountLeavingView(routeInfo, enteringViewItem, leavingViewItem);
|
|
2334
2265
|
// Handle out-of-scope outlet (route outside mount path)
|
|
2335
2266
|
if (this.handleOutOfScopeOutlet(routeInfo)) {
|
|
2336
2267
|
return;
|
|
@@ -2350,6 +2281,8 @@ class StackManager extends React.PureComponent {
|
|
|
2350
2281
|
if (this.handleOutOfContextNestedOutlet(parentPath, leavingViewItem)) {
|
|
2351
2282
|
return;
|
|
2352
2283
|
}
|
|
2284
|
+
// Find the matching route element
|
|
2285
|
+
const enteringRoute = findRouteByRouteInfo(this.ionRouterOutlet?.props.children, routeInfo, parentPath);
|
|
2353
2286
|
// Handle nested outlet with no matching route
|
|
2354
2287
|
if (this.handleNoMatchingRoute(enteringRoute, enteringViewItem, leavingViewItem)) {
|
|
2355
2288
|
return;
|
|
@@ -2546,17 +2479,17 @@ class StackManager extends React.PureComponent {
|
|
|
2546
2479
|
const { routeInfo } = this.props;
|
|
2547
2480
|
const swipeBackRouteInfo = this.getSwipeBackRouteInfo();
|
|
2548
2481
|
const enteringViewItem = this.findEnteringViewForSwipe(swipeBackRouteInfo);
|
|
2549
|
-
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
|
|
2550
2482
|
// View might have mount=false but ionPageElement still in DOM
|
|
2551
2483
|
const ionPageInDocument = Boolean(enteringViewItem?.ionPageElement && document.body.contains(enteringViewItem.ionPageElement));
|
|
2552
|
-
//
|
|
2553
|
-
//
|
|
2554
|
-
//
|
|
2555
|
-
//
|
|
2484
|
+
// For wildcard/parameterized routes, the pattern path (e.g. "/foo/*") will
|
|
2485
|
+
// never equal the resolved pathname (e.g. "/foo/bar"), so the pattern check
|
|
2486
|
+
// alone isn't sufficient. Also, verify the entering view's resolved pathname
|
|
2487
|
+
// differs from the current pathname — if they match, the entering and leaving
|
|
2488
|
+
// views are the same and the swipe gesture shouldn't start.
|
|
2556
2489
|
const canStartSwipe = !!enteringViewItem &&
|
|
2557
|
-
!!leavingViewItem &&
|
|
2558
2490
|
(enteringViewItem.mount || ionPageInDocument) &&
|
|
2559
|
-
enteringViewItem !==
|
|
2491
|
+
enteringViewItem.routeData.match.pattern.path !== routeInfo.pathname &&
|
|
2492
|
+
enteringViewItem.routeData.match.pathname !== routeInfo.pathname;
|
|
2560
2493
|
debug('SwipeBackCanStart', () => ({
|
|
2561
2494
|
outletId: this.id,
|
|
2562
2495
|
routePathname: routeInfo.pathname,
|
|
@@ -2565,15 +2498,11 @@ class StackManager extends React.PureComponent {
|
|
|
2565
2498
|
enteringViewPath: enteringViewItem?.reactElement?.props?.path,
|
|
2566
2499
|
enteringMount: enteringViewItem?.mount,
|
|
2567
2500
|
ionPageInDocument,
|
|
2568
|
-
leavingViewId: leavingViewItem?.id,
|
|
2569
2501
|
canStartSwipe,
|
|
2570
2502
|
}));
|
|
2571
2503
|
return canStartSwipe;
|
|
2572
2504
|
};
|
|
2573
2505
|
const onStart = async () => {
|
|
2574
|
-
// Core only calls onEnd when a progress animation exists, so with animations off a
|
|
2575
|
-
// previous gesture can leave its mark behind. Drop any stale one before marking.
|
|
2576
|
-
this.clearSwipeRevealedView();
|
|
2577
2506
|
const { routeInfo } = this.props;
|
|
2578
2507
|
const swipeBackRouteInfo = this.getSwipeBackRouteInfo();
|
|
2579
2508
|
const enteringViewItem = this.findEnteringViewForSwipe(swipeBackRouteInfo);
|
|
@@ -2600,11 +2529,6 @@ class StackManager extends React.PureComponent {
|
|
|
2600
2529
|
revealIonPageForSwipeBack(enteringViewItem?.ionPageElement);
|
|
2601
2530
|
// When the gesture starts, kick off a transition controlled via swipe gesture
|
|
2602
2531
|
if (enteringViewItem && leavingViewItem) {
|
|
2603
|
-
// Without a leaving view there is no progress animation, and core only calls
|
|
2604
|
-
// swipeHandler.onEnd when one exists, so nothing would clear the mark until the
|
|
2605
|
-
// next transition.
|
|
2606
|
-
markSwipeRevealed(enteringViewItem);
|
|
2607
|
-
this.swipeRevealedViewItem = enteringViewItem;
|
|
2608
2532
|
await this.transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true);
|
|
2609
2533
|
}
|
|
2610
2534
|
debug('SwipeBackOnStart:exit', () => ({
|
|
@@ -2629,7 +2553,6 @@ class StackManager extends React.PureComponent {
|
|
|
2629
2553
|
const swipeBackRouteInfo = this.getSwipeBackRouteInfo();
|
|
2630
2554
|
const enteringViewItem = this.findEnteringViewForSwipe(swipeBackRouteInfo);
|
|
2631
2555
|
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
|
|
2632
|
-
this.clearSwipeRevealedView();
|
|
2633
2556
|
// Don't hide if entering and leaving are the same (parameterized route edge case)
|
|
2634
2557
|
if (enteringViewItem !== leavingViewItem && enteringViewItem?.ionPageElement !== undefined) {
|
|
2635
2558
|
hideIonPageElement(enteringViewItem.ionPageElement);
|
|
@@ -2856,10 +2779,34 @@ class StackManager extends React.PureComponent {
|
|
|
2856
2779
|
// Bail out if the component unmounted during waitForComponentsReady
|
|
2857
2780
|
if (!this._isMounted)
|
|
2858
2781
|
return;
|
|
2782
|
+
const isCurrent = myGeneration === this.transitionGeneration;
|
|
2783
|
+
// A page the newest transition is entering is not leaving after all.
|
|
2784
|
+
const isLeaving = isCurrent || leavingEl !== this.transitionEnteringElement;
|
|
2785
|
+
// Already hidden means the leave events have fired. This checks the class rather
|
|
2786
|
+
// than `isViewVisible` because a nested outlet marks its leaving page
|
|
2787
|
+
// `visibility: hidden` before we get here and still needs `ionViewDidLeave`.
|
|
2788
|
+
const announceLeaving = isLeaving && !leavingEl.classList.contains('ion-page-hidden');
|
|
2789
|
+
/**
|
|
2790
|
+
* Dispatch the lifecycle events, since we skipped `commit()`. Only the
|
|
2791
|
+
* newest transition fires the entering events, and the class swap follows
|
|
2792
|
+
* all four so the ordering matches core's `transition()`.
|
|
2793
|
+
*
|
|
2794
|
+
* These run after `waitForComponentsReady` because on a first mount the
|
|
2795
|
+
* page has not attached its listeners yet.
|
|
2796
|
+
*/
|
|
2797
|
+
if (announceLeaving) {
|
|
2798
|
+
dispatchLifecycleEvent(leavingEl, 'ionViewWillLeave');
|
|
2799
|
+
}
|
|
2800
|
+
if (isCurrent) {
|
|
2801
|
+
dispatchLifecycleEvent(enteringEl, 'ionViewWillEnter');
|
|
2802
|
+
dispatchLifecycleEvent(enteringEl, 'ionViewDidEnter');
|
|
2803
|
+
}
|
|
2804
|
+
if (announceLeaving) {
|
|
2805
|
+
dispatchLifecycleEvent(leavingEl, 'ionViewDidLeave');
|
|
2806
|
+
}
|
|
2859
2807
|
// Swap visibility synchronously - show entering, hide leaving
|
|
2860
|
-
// Skip hiding if a newer transition already made leavingEl the entering view
|
|
2861
2808
|
enteringEl.classList.remove('ion-page-invisible');
|
|
2862
|
-
if (
|
|
2809
|
+
if (isLeaving) {
|
|
2863
2810
|
leavingEl.classList.add('ion-page-hidden');
|
|
2864
2811
|
leavingEl.setAttribute('aria-hidden', 'true');
|
|
2865
2812
|
}
|