@ionic/react-router 8.7.13-dev.11765907916.16a61ecf → 8.7.13-dev.11765921002.107104c2
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
|
@@ -13,13 +13,10 @@ const IonRouteInner = ({ path, element }) => {
|
|
|
13
13
|
* @see https://reactrouter.com/v6/utils/match-path
|
|
14
14
|
*/
|
|
15
15
|
const matchPath = ({ pathname, componentProps }) => {
|
|
16
|
+
var _a, _b;
|
|
16
17
|
const { path, index } = componentProps, restProps = __rest(componentProps, ["path", "index"]);
|
|
17
|
-
// Handle index routes
|
|
18
|
+
// Handle index routes - they match when pathname is empty or just "/"
|
|
18
19
|
if (index && !path) {
|
|
19
|
-
// Index routes match when there's no additional path after the parent route
|
|
20
|
-
// For example, in a nested outlet at /routing/*, the index route matches
|
|
21
|
-
// when the relative path is empty (i.e., we're exactly at /routing)
|
|
22
|
-
// If pathname is empty or just "/", it should match the index route
|
|
23
20
|
if (pathname === '' || pathname === '/') {
|
|
24
21
|
return {
|
|
25
22
|
params: {},
|
|
@@ -32,14 +29,25 @@ const matchPath = ({ pathname, componentProps }) => {
|
|
|
32
29
|
},
|
|
33
30
|
};
|
|
34
31
|
}
|
|
35
|
-
// Otherwise, index routes don't match when there's additional path
|
|
36
32
|
return null;
|
|
37
33
|
}
|
|
38
|
-
|
|
34
|
+
// Handle empty path routes - they match when pathname is also empty or just "/"
|
|
35
|
+
if (path === '' || path === undefined) {
|
|
36
|
+
if (pathname === '' || pathname === '/') {
|
|
37
|
+
return {
|
|
38
|
+
params: {},
|
|
39
|
+
pathname: pathname,
|
|
40
|
+
pathnameBase: pathname || '/',
|
|
41
|
+
pattern: {
|
|
42
|
+
path: '',
|
|
43
|
+
caseSensitive: (_a = restProps.caseSensitive) !== null && _a !== void 0 ? _a : false,
|
|
44
|
+
end: (_b = restProps.end) !== null && _b !== void 0 ? _b : true,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
39
48
|
return null;
|
|
40
49
|
}
|
|
41
|
-
// For relative paths
|
|
42
|
-
// use React Router's matcher against a normalized path.
|
|
50
|
+
// For relative paths (don't start with '/'), normalize both path and pathname for matching
|
|
43
51
|
if (!path.startsWith('/')) {
|
|
44
52
|
const matchOptions = Object.assign({ path: `/${path}` }, restProps);
|
|
45
53
|
if ((matchOptions === null || matchOptions === void 0 ? void 0 : matchOptions.end) === undefined) {
|
|
@@ -51,7 +59,6 @@ const matchPath = ({ pathname, componentProps }) => {
|
|
|
51
59
|
// Adjust the match to remove the leading '/' we added
|
|
52
60
|
return Object.assign(Object.assign({}, match), { pathname: pathname, pathnameBase: match.pathnameBase === '/' ? '' : match.pathnameBase.slice(1), pattern: Object.assign(Object.assign({}, match.pattern), { path: path }) });
|
|
53
61
|
}
|
|
54
|
-
// No match found
|
|
55
62
|
return null;
|
|
56
63
|
}
|
|
57
64
|
// For absolute paths, use React Router's matcher directly.
|
|
@@ -71,12 +78,16 @@ const matchPath = ({ pathname, componentProps }) => {
|
|
|
71
78
|
*/
|
|
72
79
|
const derivePathnameToMatch = (fullPathname, routePath) => {
|
|
73
80
|
var _a;
|
|
81
|
+
// For absolute or empty routes, use the full pathname as-is
|
|
74
82
|
if (!routePath || routePath === '' || routePath.startsWith('/')) {
|
|
75
83
|
return fullPathname;
|
|
76
84
|
}
|
|
77
85
|
const trimmedPath = fullPathname.startsWith('/') ? fullPathname.slice(1) : fullPathname;
|
|
78
86
|
if (!trimmedPath) {
|
|
79
|
-
|
|
87
|
+
// For root-level relative routes (pathname is "/" and routePath is relative),
|
|
88
|
+
// return the full pathname so matchPath can normalize both.
|
|
89
|
+
// This allows routes like <Route path="foo/*" .../> at root level to work correctly.
|
|
90
|
+
return fullPathname;
|
|
80
91
|
}
|
|
81
92
|
const fullSegments = trimmedPath.split('/').filter(Boolean);
|
|
82
93
|
if (fullSegments.length === 0) {
|
|
@@ -152,24 +163,36 @@ const computeCommonPrefix = (paths) => {
|
|
|
152
163
|
return commonSegments.length > 0 ? '/' + commonSegments.join('/') : '';
|
|
153
164
|
};
|
|
154
165
|
/**
|
|
155
|
-
* Checks if a route is a
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
166
|
+
* Checks if a route path is a "splat-only" route (just `*` or `/*`).
|
|
167
|
+
*/
|
|
168
|
+
const isSplatOnlyRoute = (routePath) => {
|
|
169
|
+
return routePath === '*' || routePath === '/*';
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* Checks if a route has an embedded wildcard (e.g., "tab1/*" but not "*" or "/*").
|
|
173
|
+
*/
|
|
174
|
+
const hasEmbeddedWildcard = (routePath) => {
|
|
175
|
+
return !!routePath && routePath.includes('*') && !isSplatOnlyRoute(routePath);
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* Checks if a route with an embedded wildcard matches a pathname.
|
|
179
|
+
*/
|
|
180
|
+
const matchesEmbeddedWildcardRoute = (route, pathname) => {
|
|
181
|
+
const routePath = route.props.path;
|
|
182
|
+
if (!hasEmbeddedWildcard(routePath)) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
return !!matchPath({ pathname, componentProps: route.props });
|
|
186
|
+
};
|
|
187
|
+
/**
|
|
188
|
+
* Checks if a route is a specific match (not wildcard-only or index).
|
|
160
189
|
*/
|
|
161
190
|
const isSpecificRouteMatch = (route, remainingPath) => {
|
|
162
191
|
const routePath = route.props.path;
|
|
163
|
-
|
|
164
|
-
const isIndex = route.props.index;
|
|
165
|
-
// Skip wildcards and index routes
|
|
166
|
-
if (isIndex || isWildcardOnly) {
|
|
192
|
+
if (route.props.index || isSplatOnlyRoute(routePath)) {
|
|
167
193
|
return false;
|
|
168
194
|
}
|
|
169
|
-
return !!matchPath({
|
|
170
|
-
pathname: remainingPath,
|
|
171
|
-
componentProps: route.props,
|
|
172
|
-
});
|
|
195
|
+
return !!matchPath({ pathname: remainingPath, componentProps: route.props });
|
|
173
196
|
};
|
|
174
197
|
/**
|
|
175
198
|
* Analyzes route children to determine their characteristics.
|
|
@@ -214,11 +237,13 @@ const computeParentPath = (options) => {
|
|
|
214
237
|
let firstSpecificMatch = undefined;
|
|
215
238
|
let firstWildcardMatch = undefined;
|
|
216
239
|
let indexMatchAtMount = undefined;
|
|
240
|
+
// Start at i = 1 (normal case: strip at least one segment for parent path)
|
|
217
241
|
for (let i = 1; i <= segments.length; i++) {
|
|
218
242
|
const parentPath = '/' + segments.slice(0, i).join('/');
|
|
219
243
|
const remainingPath = segments.slice(i).join('/');
|
|
220
|
-
// Check for specific (non-wildcard, non-index)
|
|
221
|
-
|
|
244
|
+
// Check for specific route matches (non-wildcard-only, non-index)
|
|
245
|
+
// Also check routes with embedded wildcards (e.g., "tab1/*")
|
|
246
|
+
const hasSpecificMatch = routeChildren.some((route) => isSpecificRouteMatch(route, remainingPath) || matchesEmbeddedWildcardRoute(route, remainingPath));
|
|
222
247
|
if (hasSpecificMatch && !firstSpecificMatch) {
|
|
223
248
|
firstSpecificMatch = parentPath;
|
|
224
249
|
// Found a specific match - this is our answer for non-index routes
|
|
@@ -265,6 +290,16 @@ const computeParentPath = (options) => {
|
|
|
265
290
|
}
|
|
266
291
|
}
|
|
267
292
|
}
|
|
293
|
+
// Fallback: check at root level (i = 0) for embedded wildcard routes.
|
|
294
|
+
// This handles outlets inside root-level splat routes where routes like
|
|
295
|
+
// "tab1/*" need to match the full pathname.
|
|
296
|
+
if (!firstSpecificMatch) {
|
|
297
|
+
const fullRemainingPath = segments.join('/');
|
|
298
|
+
const hasRootLevelMatch = routeChildren.some((route) => matchesEmbeddedWildcardRoute(route, fullRemainingPath));
|
|
299
|
+
if (hasRootLevelMatch) {
|
|
300
|
+
firstSpecificMatch = '/';
|
|
301
|
+
}
|
|
302
|
+
}
|
|
268
303
|
// Determine the best parent path:
|
|
269
304
|
// 1. Specific match (routes like tabs/*, favorites) - highest priority
|
|
270
305
|
// 2. Wildcard match (route path="*") - catches unmatched segments
|
|
@@ -740,27 +775,36 @@ class ReactRouterViewStack extends ViewStacks {
|
|
|
740
775
|
const combinedParams = Object.assign(Object.assign({}, accumulatedParentParams), ((_c = routeMatch === null || routeMatch === void 0 ? void 0 : routeMatch.params) !== null && _c !== void 0 ? _c : {}));
|
|
741
776
|
// For relative route paths, we need to compute an absolute pathnameBase
|
|
742
777
|
// by combining the parent's pathnameBase with the matched portion
|
|
743
|
-
let absolutePathnameBase = (routeMatch === null || routeMatch === void 0 ? void 0 : routeMatch.pathnameBase) || routeInfo.pathname;
|
|
744
778
|
const routePath = routeElement.props.path;
|
|
745
779
|
const isRelativePath = routePath && !routePath.startsWith('/');
|
|
746
780
|
const isIndexRoute = !!routeElement.props.index;
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
781
|
+
const isSplatOnlyRoute = routePath === '*' || routePath === '/*';
|
|
782
|
+
// Get parent's pathnameBase for relative path resolution
|
|
783
|
+
const parentPathnameBase = parentMatches.length > 0 ? parentMatches[parentMatches.length - 1].pathnameBase : '/';
|
|
784
|
+
// Start with the match's pathnameBase, falling back to routeInfo.pathname
|
|
785
|
+
// BUT: splat-only routes should use parent's base (v7_relativeSplatPath behavior)
|
|
786
|
+
let absolutePathnameBase;
|
|
787
|
+
if (isSplatOnlyRoute) {
|
|
788
|
+
// Splat routes should NOT contribute their matched portion to pathnameBase
|
|
789
|
+
// This aligns with React Router v7's v7_relativeSplatPath behavior
|
|
790
|
+
// Without this, relative links inside splat routes get double path segments
|
|
791
|
+
absolutePathnameBase = parentPathnameBase;
|
|
792
|
+
}
|
|
793
|
+
else if (isRelativePath && (routeMatch === null || routeMatch === void 0 ? void 0 : routeMatch.pathnameBase)) {
|
|
794
|
+
// For relative paths with a pathnameBase, combine with parent
|
|
795
|
+
const relativeBase = routeMatch.pathnameBase.startsWith('/')
|
|
796
|
+
? routeMatch.pathnameBase.slice(1)
|
|
797
|
+
: routeMatch.pathnameBase;
|
|
798
|
+
absolutePathnameBase =
|
|
799
|
+
parentPathnameBase === '/' ? `/${relativeBase}` : `${parentPathnameBase}/${relativeBase}`;
|
|
800
|
+
}
|
|
801
|
+
else if (isIndexRoute) {
|
|
802
|
+
// Index routes should use the parent's base as their base
|
|
803
|
+
absolutePathnameBase = parentPathnameBase;
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
// Default: use the match's pathnameBase or the current pathname
|
|
807
|
+
absolutePathnameBase = (routeMatch === null || routeMatch === void 0 ? void 0 : routeMatch.pathnameBase) || routeInfo.pathname;
|
|
764
808
|
}
|
|
765
809
|
const contextMatches = [
|
|
766
810
|
...parentMatches,
|
|
@@ -804,7 +848,9 @@ class ReactRouterViewStack extends ViewStacks {
|
|
|
804
848
|
let parentPath = undefined;
|
|
805
849
|
try {
|
|
806
850
|
// Only attempt parent path computation for non-root outlets
|
|
807
|
-
|
|
851
|
+
// Root outlets have IDs like 'routerOutlet' or 'routerOutlet-2'
|
|
852
|
+
const isRootOutlet = outletId.startsWith('routerOutlet');
|
|
853
|
+
if (!isRootOutlet) {
|
|
808
854
|
const routeChildren = extractRouteChildren(ionRouterOutlet.props.children);
|
|
809
855
|
const { hasRelativeRoutes, hasIndexRoute, hasWildcardRoute } = analyzeRouteChildren(routeChildren);
|
|
810
856
|
if (hasRelativeRoutes || hasIndexRoute) {
|
|
@@ -1046,7 +1092,7 @@ class ReactRouterViewStack extends ViewStacks {
|
|
|
1046
1092
|
* Matches a view with no path prop (default fallback route) or index route.
|
|
1047
1093
|
*/
|
|
1048
1094
|
function matchDefaultRoute(v) {
|
|
1049
|
-
var _a;
|
|
1095
|
+
var _a, _b, _c;
|
|
1050
1096
|
const childProps = v.routeData.childProps;
|
|
1051
1097
|
const isDefaultRoute = childProps.path === undefined || childProps.path === '';
|
|
1052
1098
|
const isIndexRoute = !!childProps.index;
|
|
@@ -1059,14 +1105,22 @@ class ReactRouterViewStack extends ViewStacks {
|
|
|
1059
1105
|
}
|
|
1060
1106
|
return false;
|
|
1061
1107
|
}
|
|
1108
|
+
// For empty path routes, only match if we're at the same level as when the view was created.
|
|
1109
|
+
// This prevents an empty path view item from being reused for different routes.
|
|
1062
1110
|
if (isDefaultRoute) {
|
|
1111
|
+
const previousPathnameBase = ((_b = (_a = v.routeData) === null || _a === void 0 ? void 0 : _a.match) === null || _b === void 0 ? void 0 : _b.pathnameBase) || '';
|
|
1112
|
+
const normalizedBase = normalizePathnameForComparison(previousPathnameBase);
|
|
1113
|
+
const normalizedPathname = normalizePathnameForComparison(pathname);
|
|
1114
|
+
if (normalizedPathname !== normalizedBase) {
|
|
1115
|
+
return false;
|
|
1116
|
+
}
|
|
1063
1117
|
match = {
|
|
1064
1118
|
params: {},
|
|
1065
1119
|
pathname,
|
|
1066
1120
|
pathnameBase: pathname === '' ? '/' : pathname,
|
|
1067
1121
|
pattern: {
|
|
1068
1122
|
path: '',
|
|
1069
|
-
caseSensitive: (
|
|
1123
|
+
caseSensitive: (_c = childProps.caseSensitive) !== null && _c !== void 0 ? _c : false,
|
|
1070
1124
|
end: true,
|
|
1071
1125
|
},
|
|
1072
1126
|
};
|
|
@@ -1191,24 +1245,30 @@ class StackManager extends React.PureComponent {
|
|
|
1191
1245
|
if (this.outletMountPath && !currentPathname.startsWith(this.outletMountPath)) {
|
|
1192
1246
|
return undefined;
|
|
1193
1247
|
}
|
|
1194
|
-
//
|
|
1195
|
-
|
|
1196
|
-
if (this.id !== 'routerOutlet' && this.ionRouterOutlet) {
|
|
1248
|
+
// Check if this outlet has route children to analyze
|
|
1249
|
+
if (this.ionRouterOutlet) {
|
|
1197
1250
|
const routeChildren = extractRouteChildren(this.ionRouterOutlet.props.children);
|
|
1198
1251
|
const { hasRelativeRoutes, hasIndexRoute, hasWildcardRoute } = analyzeRouteChildren(routeChildren);
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1252
|
+
// Root outlets have IDs like 'routerOutlet' or 'routerOutlet-2'
|
|
1253
|
+
// But even outlets with auto-generated IDs may need parent path computation
|
|
1254
|
+
// if they have relative routes (indicating they're nested outlets)
|
|
1255
|
+
const isRootOutlet = this.id.startsWith('routerOutlet');
|
|
1256
|
+
const needsParentPath = !isRootOutlet || hasRelativeRoutes || hasIndexRoute;
|
|
1257
|
+
if (needsParentPath) {
|
|
1258
|
+
const result = computeParentPath({
|
|
1259
|
+
currentPathname,
|
|
1260
|
+
outletMountPath: this.outletMountPath,
|
|
1261
|
+
routeChildren,
|
|
1262
|
+
hasRelativeRoutes,
|
|
1263
|
+
hasIndexRoute,
|
|
1264
|
+
hasWildcardRoute,
|
|
1265
|
+
});
|
|
1266
|
+
// Update the outlet mount path if it was set
|
|
1267
|
+
if (result.outletMountPath && !this.outletMountPath) {
|
|
1268
|
+
this.outletMountPath = result.outletMountPath;
|
|
1269
|
+
}
|
|
1270
|
+
return result.parentPath;
|
|
1210
1271
|
}
|
|
1211
|
-
return result.parentPath;
|
|
1212
1272
|
}
|
|
1213
1273
|
return this.outletMountPath;
|
|
1214
1274
|
}
|
|
@@ -1256,12 +1316,37 @@ class StackManager extends React.PureComponent {
|
|
|
1256
1316
|
* Determines if the leaving view item should be unmounted after a transition.
|
|
1257
1317
|
*/
|
|
1258
1318
|
shouldUnmountLeavingView(routeInfo, enteringViewItem, leavingViewItem) {
|
|
1319
|
+
var _a, _b, _c, _d;
|
|
1259
1320
|
if (!leavingViewItem) {
|
|
1260
1321
|
return false;
|
|
1261
1322
|
}
|
|
1262
1323
|
if (routeInfo.routeAction === 'replace') {
|
|
1263
|
-
|
|
1324
|
+
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;
|
|
1325
|
+
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;
|
|
1326
|
+
// Never unmount the root path "/" - it's the main entry point for back navigation
|
|
1327
|
+
if (leavingRoutePath === '/' || leavingRoutePath === '') {
|
|
1328
|
+
return false;
|
|
1329
|
+
}
|
|
1330
|
+
if (enteringRoutePath && leavingRoutePath) {
|
|
1331
|
+
// Get parent paths to check if routes share a common parent
|
|
1332
|
+
const getParentPath = (path) => {
|
|
1333
|
+
const normalized = path.replace(/\/\*$/, ''); // Remove trailing /*
|
|
1334
|
+
const lastSlash = normalized.lastIndexOf('/');
|
|
1335
|
+
return lastSlash > 0 ? normalized.substring(0, lastSlash) : '/';
|
|
1336
|
+
};
|
|
1337
|
+
const enteringParent = getParentPath(enteringRoutePath);
|
|
1338
|
+
const leavingParent = getParentPath(leavingRoutePath);
|
|
1339
|
+
// Unmount if:
|
|
1340
|
+
// 1. Routes are siblings (same parent, e.g., /page1 and /page2, or /foo/page1 and /foo/page2)
|
|
1341
|
+
// 2. Entering is a child of leaving (redirect, e.g., /tabs -> /tabs/tab1)
|
|
1342
|
+
const areSiblings = enteringParent === leavingParent && enteringParent !== '/';
|
|
1343
|
+
const isChildRedirect = enteringRoutePath.startsWith(leavingRoutePath) ||
|
|
1344
|
+
(leavingRoutePath.endsWith('/*') && enteringRoutePath.startsWith(leavingRoutePath.slice(0, -2)));
|
|
1345
|
+
return areSiblings || isChildRedirect;
|
|
1346
|
+
}
|
|
1347
|
+
return false;
|
|
1264
1348
|
}
|
|
1349
|
+
// For non-replace actions, only unmount for back navigation (not forward push)
|
|
1265
1350
|
const isForwardPush = routeInfo.routeAction === 'push' && routeInfo.routeDirection === 'forward';
|
|
1266
1351
|
if (!isForwardPush && routeInfo.routeDirection !== 'none' && enteringViewItem !== leavingViewItem) {
|
|
1267
1352
|
return true;
|
|
@@ -1297,7 +1382,9 @@ class StackManager extends React.PureComponent {
|
|
|
1297
1382
|
*/
|
|
1298
1383
|
handleOutOfContextNestedOutlet(parentPath, leavingViewItem) {
|
|
1299
1384
|
var _a;
|
|
1300
|
-
|
|
1385
|
+
// Root outlets have IDs like 'routerOutlet' or 'routerOutlet-2'
|
|
1386
|
+
const isRootOutlet = this.id.startsWith('routerOutlet');
|
|
1387
|
+
if (isRootOutlet || parentPath !== undefined || !this.ionRouterOutlet) {
|
|
1301
1388
|
return false;
|
|
1302
1389
|
}
|
|
1303
1390
|
const routesChildren = (_a = getRoutesChildren(this.ionRouterOutlet.props.children)) !== null && _a !== void 0 ? _a : this.ionRouterOutlet.props.children;
|
|
@@ -1322,7 +1409,9 @@ class StackManager extends React.PureComponent {
|
|
|
1322
1409
|
* Returns true if the transition should be aborted.
|
|
1323
1410
|
*/
|
|
1324
1411
|
handleNoMatchingRoute(enteringRoute, enteringViewItem, leavingViewItem) {
|
|
1325
|
-
|
|
1412
|
+
// Root outlets have IDs like 'routerOutlet' or 'routerOutlet-2'
|
|
1413
|
+
const isRootOutlet = this.id.startsWith('routerOutlet');
|
|
1414
|
+
if (isRootOutlet || enteringRoute || enteringViewItem) {
|
|
1326
1415
|
return false;
|
|
1327
1416
|
}
|
|
1328
1417
|
// Hide any visible views in this outlet since it has no matching route
|
|
@@ -1338,8 +1427,6 @@ class StackManager extends React.PureComponent {
|
|
|
1338
1427
|
*/
|
|
1339
1428
|
handleReadyEnteringView(routeInfo, enteringViewItem, leavingViewItem, shouldUnmountLeavingViewItem) {
|
|
1340
1429
|
var _a, _b;
|
|
1341
|
-
// Ensure the entering view is not hidden from previous navigations
|
|
1342
|
-
showIonPageElement(enteringViewItem.ionPageElement);
|
|
1343
1430
|
// Handle same view item case (e.g., parameterized route changes)
|
|
1344
1431
|
if (enteringViewItem === leavingViewItem) {
|
|
1345
1432
|
const routePath = (_b = (_a = enteringViewItem.reactElement) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.path;
|
|
@@ -1363,24 +1450,76 @@ class StackManager extends React.PureComponent {
|
|
|
1363
1450
|
if (!leavingViewItem && this.props.routeInfo.prevRouteLastPathname) {
|
|
1364
1451
|
leavingViewItem = this.context.findViewItemByPathname(this.props.routeInfo.prevRouteLastPathname, this.id);
|
|
1365
1452
|
}
|
|
1366
|
-
//
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
}
|
|
1453
|
+
// Ensure the entering view is marked as mounted.
|
|
1454
|
+
// This is critical for views that were previously unmounted (e.g., navigating back to home).
|
|
1455
|
+
// When mount=false, the ViewLifeCycleManager doesn't render the IonPage, so the
|
|
1456
|
+
// ionPageElement reference becomes stale. By setting mount=true, we ensure the view
|
|
1457
|
+
// gets re-rendered and a new IonPage is created.
|
|
1458
|
+
if (!enteringViewItem.mount) {
|
|
1459
|
+
enteringViewItem.mount = true;
|
|
1460
|
+
}
|
|
1461
|
+
// Check visibility state BEFORE showing the entering view.
|
|
1462
|
+
// This must be done before showIonPageElement to get accurate visibility state.
|
|
1463
|
+
const enteringWasVisible = enteringViewItem.ionPageElement && isViewVisible(enteringViewItem.ionPageElement);
|
|
1464
|
+
const leavingIsHidden = leavingViewItem !== undefined && leavingViewItem.ionPageElement && !isViewVisible(leavingViewItem.ionPageElement);
|
|
1374
1465
|
// Check for duplicate transition
|
|
1375
1466
|
const currentTransition = {
|
|
1376
1467
|
enteringId: enteringViewItem.id,
|
|
1377
1468
|
leavingId: leavingViewItem === null || leavingViewItem === void 0 ? void 0 : leavingViewItem.id,
|
|
1378
1469
|
};
|
|
1379
|
-
|
|
1470
|
+
const isDuplicateTransition = leavingViewItem &&
|
|
1380
1471
|
this.lastTransition &&
|
|
1381
1472
|
this.lastTransition.leavingId &&
|
|
1382
1473
|
this.lastTransition.enteringId === currentTransition.enteringId &&
|
|
1383
|
-
this.lastTransition.leavingId === currentTransition.leavingId
|
|
1474
|
+
this.lastTransition.leavingId === currentTransition.leavingId;
|
|
1475
|
+
// Skip transition if entering view was ALREADY visible and leaving view is not visible.
|
|
1476
|
+
// This indicates the transition has already been performed (e.g., via swipe gesture).
|
|
1477
|
+
// IMPORTANT: Only skip if both ionPageElements are the same as when the transition was last done.
|
|
1478
|
+
// If the leaving view's ionPageElement changed (e.g., component re-rendered with different IonPage),
|
|
1479
|
+
// we should NOT skip because the DOM state is inconsistent.
|
|
1480
|
+
if (enteringWasVisible && leavingIsHidden && isDuplicateTransition) {
|
|
1481
|
+
// For swipe-to-go-back, the transition animation was handled by the gesture.
|
|
1482
|
+
// We still need to set mount=false so React unmounts the leaving view.
|
|
1483
|
+
// Only do this when skipTransition is set (indicating gesture completion).
|
|
1484
|
+
if (this.skipTransition &&
|
|
1485
|
+
shouldUnmountLeavingViewItem &&
|
|
1486
|
+
leavingViewItem &&
|
|
1487
|
+
enteringViewItem !== leavingViewItem) {
|
|
1488
|
+
leavingViewItem.mount = false;
|
|
1489
|
+
// Call transitionPage with duration 0 to trigger ionViewDidLeave lifecycle
|
|
1490
|
+
// which is needed for ViewLifeCycleManager to remove the view.
|
|
1491
|
+
this.transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back');
|
|
1492
|
+
}
|
|
1493
|
+
// Clear skipTransition since we're not calling transitionPage which normally clears it
|
|
1494
|
+
this.skipTransition = false;
|
|
1495
|
+
// Must call forceUpdate to trigger re-render after mount state change
|
|
1496
|
+
this.forceUpdate();
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
// Ensure the entering view is not hidden from previous navigations
|
|
1500
|
+
// This must happen AFTER the visibility check above
|
|
1501
|
+
showIonPageElement(enteringViewItem.ionPageElement);
|
|
1502
|
+
// Skip if this is a duplicate transition (but visibility state didn't match above)
|
|
1503
|
+
// OR if skipTransition is set (swipe gesture already handled the animation)
|
|
1504
|
+
if (isDuplicateTransition || this.skipTransition) {
|
|
1505
|
+
// For swipe-to-go-back, we still need to handle unmounting even if visibility
|
|
1506
|
+
// conditions aren't fully met (animation might still be in progress)
|
|
1507
|
+
if (this.skipTransition &&
|
|
1508
|
+
shouldUnmountLeavingViewItem &&
|
|
1509
|
+
leavingViewItem &&
|
|
1510
|
+
enteringViewItem !== leavingViewItem) {
|
|
1511
|
+
leavingViewItem.mount = false;
|
|
1512
|
+
// For swipe-to-go-back, we need to call transitionPage with duration 0 to
|
|
1513
|
+
// trigger the ionViewDidLeave lifecycle event. The ViewLifeCycleManager
|
|
1514
|
+
// uses componentCanBeDestroyed callback to remove the view, which is
|
|
1515
|
+
// only called from ionViewDidLeave. Since the gesture animation already
|
|
1516
|
+
// completed before mount=false was set, we need to re-fire the lifecycle.
|
|
1517
|
+
this.transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back');
|
|
1518
|
+
}
|
|
1519
|
+
// Clear skipTransition since we're not calling transitionPage which normally clears it
|
|
1520
|
+
this.skipTransition = false;
|
|
1521
|
+
// Must call forceUpdate to trigger re-render after mount state change
|
|
1522
|
+
this.forceUpdate();
|
|
1384
1523
|
return;
|
|
1385
1524
|
}
|
|
1386
1525
|
this.lastTransition = currentTransition;
|
|
@@ -1392,14 +1531,28 @@ class StackManager extends React.PureComponent {
|
|
|
1392
1531
|
}
|
|
1393
1532
|
}
|
|
1394
1533
|
/**
|
|
1395
|
-
* Handles the delayed unmount of the leaving view item
|
|
1534
|
+
* Handles the delayed unmount of the leaving view item.
|
|
1535
|
+
* For 'replace' actions: handles container route transitions specially.
|
|
1536
|
+
* For back navigation: explicitly unmounts because the ionViewDidLeave lifecycle
|
|
1537
|
+
* fires DURING transitionPage, but mount=false is set AFTER.
|
|
1538
|
+
*
|
|
1539
|
+
* @param routeInfo Current route information
|
|
1540
|
+
* @param enteringViewItem The view being navigated to
|
|
1541
|
+
* @param leavingViewItem The view being navigated from
|
|
1396
1542
|
*/
|
|
1397
1543
|
handleLeavingViewUnmount(routeInfo, enteringViewItem, leavingViewItem) {
|
|
1398
1544
|
var _a, _b, _c, _d, _e, _f;
|
|
1399
|
-
if (
|
|
1545
|
+
if (!leavingViewItem.ionPageElement) {
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
// For push/pop actions, do NOT unmount - views are cached for navigation history.
|
|
1549
|
+
// Push: Forward navigation caches views for back navigation
|
|
1550
|
+
// Pop: Back navigation should not unmount the entering view's history
|
|
1551
|
+
// Only 'replace' actions should actually unmount views since they replace history.
|
|
1552
|
+
if (routeInfo.routeAction !== 'replace') {
|
|
1400
1553
|
return;
|
|
1401
1554
|
}
|
|
1402
|
-
//
|
|
1555
|
+
// For replace actions, check if we should skip removal for nested outlet redirects
|
|
1403
1556
|
const enteringRoutePath = (_b = (_a = enteringViewItem.reactElement) === null || _a === void 0 ? void 0 : _a.props) === null || _b === void 0 ? void 0 : _b.path;
|
|
1404
1557
|
const leavingRoutePath = (_d = (_c = leavingViewItem.reactElement) === null || _c === void 0 ? void 0 : _c.props) === null || _d === void 0 ? void 0 : _d.path;
|
|
1405
1558
|
const isEnteringContainerRoute = enteringRoutePath && enteringRoutePath.endsWith('/*');
|
|
@@ -1415,6 +1568,8 @@ class StackManager extends React.PureComponent {
|
|
|
1415
1568
|
const viewToUnmount = leavingViewItem;
|
|
1416
1569
|
setTimeout(() => {
|
|
1417
1570
|
this.context.unMountViewItem(viewToUnmount);
|
|
1571
|
+
// Trigger re-render to remove the view from DOM
|
|
1572
|
+
this.forceUpdate();
|
|
1418
1573
|
}, VIEW_UNMOUNT_DELAY_MS);
|
|
1419
1574
|
}
|
|
1420
1575
|
/**
|
|
@@ -1459,6 +1614,8 @@ class StackManager extends React.PureComponent {
|
|
|
1459
1614
|
this.transitionPage(routeInfo, latestEnteringView, latestLeavingView !== null && latestLeavingView !== void 0 ? latestLeavingView : undefined);
|
|
1460
1615
|
if (shouldUnmountLeavingViewItem && latestLeavingView && latestEnteringView !== latestLeavingView) {
|
|
1461
1616
|
latestLeavingView.mount = false;
|
|
1617
|
+
// Call handleLeavingViewUnmount to ensure the view is properly removed
|
|
1618
|
+
this.handleLeavingViewUnmount(routeInfo, latestEnteringView, latestLeavingView);
|
|
1462
1619
|
}
|
|
1463
1620
|
this.forceUpdate();
|
|
1464
1621
|
}
|
|
@@ -1582,7 +1739,12 @@ class StackManager extends React.PureComponent {
|
|
|
1582
1739
|
this.context.addViewItem(enteringViewItem);
|
|
1583
1740
|
}
|
|
1584
1741
|
// Handle transition based on ion-page element availability
|
|
1585
|
-
if
|
|
1742
|
+
// Check if the ionPageElement is still in the document.
|
|
1743
|
+
// If the view was previously unmounted (mount=false), the ViewLifeCycleManager
|
|
1744
|
+
// removes the React component from the tree, which removes the IonPage from the DOM.
|
|
1745
|
+
// The ionPageElement reference becomes stale and we need to wait for a new one.
|
|
1746
|
+
const ionPageIsInDocument = (enteringViewItem === null || enteringViewItem === void 0 ? void 0 : enteringViewItem.ionPageElement) && document.body.contains(enteringViewItem.ionPageElement);
|
|
1747
|
+
if (enteringViewItem && ionPageIsInDocument) {
|
|
1586
1748
|
// Clear waiting state
|
|
1587
1749
|
if (this.waitingForIonPage) {
|
|
1588
1750
|
this.waitingForIonPage = false;
|
|
@@ -1593,8 +1755,17 @@ class StackManager extends React.PureComponent {
|
|
|
1593
1755
|
}
|
|
1594
1756
|
this.handleReadyEnteringView(routeInfo, enteringViewItem, leavingViewItem, shouldUnmountLeavingViewItem);
|
|
1595
1757
|
}
|
|
1596
|
-
else if (enteringViewItem && !
|
|
1758
|
+
else if (enteringViewItem && !ionPageIsInDocument) {
|
|
1597
1759
|
// Wait for ion-page to mount
|
|
1760
|
+
// This handles both: no ionPageElement, or stale ionPageElement (not in document)
|
|
1761
|
+
// Clear stale reference if the element is no longer in the document
|
|
1762
|
+
if (enteringViewItem.ionPageElement && !document.body.contains(enteringViewItem.ionPageElement)) {
|
|
1763
|
+
enteringViewItem.ionPageElement = undefined;
|
|
1764
|
+
}
|
|
1765
|
+
// Ensure the view is marked as mounted so ViewLifeCycleManager renders the IonPage
|
|
1766
|
+
if (!enteringViewItem.mount) {
|
|
1767
|
+
enteringViewItem.mount = true;
|
|
1768
|
+
}
|
|
1598
1769
|
this.handleWaitingForIonPage(routeInfo, enteringViewItem, leavingViewItem, shouldUnmountLeavingViewItem);
|
|
1599
1770
|
return;
|
|
1600
1771
|
}
|
|
@@ -1626,6 +1797,19 @@ class StackManager extends React.PureComponent {
|
|
|
1626
1797
|
const foundView = this.context.findViewItemByRouteInfo(routeInfo, this.id);
|
|
1627
1798
|
if (foundView) {
|
|
1628
1799
|
const oldPageElement = foundView.ionPageElement;
|
|
1800
|
+
/**
|
|
1801
|
+
* FIX for issue #28878: Reject orphaned IonPage registrations.
|
|
1802
|
+
*
|
|
1803
|
+
* When a component conditionally renders different IonPages (e.g., list vs empty state)
|
|
1804
|
+
* using React keys, and state changes simultaneously with navigation, the new IonPage
|
|
1805
|
+
* tries to register for a route we're navigating away from. This creates a stale view.
|
|
1806
|
+
*
|
|
1807
|
+
* Only reject if both pageIds exist and differ, to allow nested outlet registrations.
|
|
1808
|
+
*/
|
|
1809
|
+
if (this.shouldRejectOrphanedPage(page, oldPageElement, routeInfo)) {
|
|
1810
|
+
this.hideAndRemoveOrphanedPage(page);
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1629
1813
|
foundView.ionPageElement = page;
|
|
1630
1814
|
foundView.ionRoute = true;
|
|
1631
1815
|
/**
|
|
@@ -1639,6 +1823,35 @@ class StackManager extends React.PureComponent {
|
|
|
1639
1823
|
}
|
|
1640
1824
|
this.handlePageTransition(routeInfo);
|
|
1641
1825
|
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Determines if a new IonPage registration should be rejected as orphaned.
|
|
1828
|
+
* This happens when a component re-renders with a different IonPage while navigating away.
|
|
1829
|
+
*/
|
|
1830
|
+
shouldRejectOrphanedPage(newPage, oldPageElement, routeInfo) {
|
|
1831
|
+
if (!oldPageElement || oldPageElement === newPage) {
|
|
1832
|
+
return false;
|
|
1833
|
+
}
|
|
1834
|
+
const newPageId = newPage.getAttribute('data-pageid');
|
|
1835
|
+
const oldPageId = oldPageElement.getAttribute('data-pageid');
|
|
1836
|
+
// Only reject if both pageIds exist and are different
|
|
1837
|
+
if (!newPageId || !oldPageId || newPageId === oldPageId) {
|
|
1838
|
+
return false;
|
|
1839
|
+
}
|
|
1840
|
+
// Reject only if we're navigating away from this route
|
|
1841
|
+
return this.props.routeInfo.pathname !== routeInfo.pathname;
|
|
1842
|
+
}
|
|
1843
|
+
/**
|
|
1844
|
+
* Hides an orphaned IonPage and schedules its removal from the DOM.
|
|
1845
|
+
*/
|
|
1846
|
+
hideAndRemoveOrphanedPage(page) {
|
|
1847
|
+
page.classList.add('ion-page-hidden');
|
|
1848
|
+
page.setAttribute('aria-hidden', 'true');
|
|
1849
|
+
setTimeout(() => {
|
|
1850
|
+
if (page.parentElement) {
|
|
1851
|
+
page.remove();
|
|
1852
|
+
}
|
|
1853
|
+
}, VIEW_UNMOUNT_DELAY_MS);
|
|
1854
|
+
}
|
|
1642
1855
|
/**
|
|
1643
1856
|
* Configures the router outlet for the swipe-to-go-back gesture.
|
|
1644
1857
|
*
|
|
@@ -1654,11 +1867,23 @@ class StackManager extends React.PureComponent {
|
|
|
1654
1867
|
}
|
|
1655
1868
|
const { routeInfo } = this.props;
|
|
1656
1869
|
const swipeBackRouteInfo = this.getSwipeBackRouteInfo();
|
|
1657
|
-
|
|
1870
|
+
// First try to find the view in the current outlet
|
|
1871
|
+
let enteringViewItem = this.context.findViewItemByRouteInfo(swipeBackRouteInfo, this.id, false);
|
|
1872
|
+
// If not found in current outlet, search all outlets (for cross-outlet swipe back)
|
|
1873
|
+
if (!enteringViewItem) {
|
|
1874
|
+
enteringViewItem = this.context.findViewItemByRouteInfo(swipeBackRouteInfo, undefined, false);
|
|
1875
|
+
}
|
|
1876
|
+
// Check if the ionPageElement is still in the document.
|
|
1877
|
+
// A view might have mount=false but still have its ionPageElement in the DOM
|
|
1878
|
+
// (due to timing differences in unmounting).
|
|
1879
|
+
const ionPageInDocument = Boolean((enteringViewItem === null || enteringViewItem === void 0 ? void 0 : enteringViewItem.ionPageElement) && document.body.contains(enteringViewItem.ionPageElement));
|
|
1658
1880
|
const canStartSwipe = !!enteringViewItem &&
|
|
1659
|
-
//
|
|
1660
|
-
//
|
|
1661
|
-
|
|
1881
|
+
// Check if we can swipe to this view. Either:
|
|
1882
|
+
// 1. The view is mounted (mount=true), OR
|
|
1883
|
+
// 2. The view's ionPageElement is still in the document
|
|
1884
|
+
// The second case handles views that have been marked for unmount but haven't
|
|
1885
|
+
// actually been removed from the DOM yet.
|
|
1886
|
+
(enteringViewItem.mount || ionPageInDocument) &&
|
|
1662
1887
|
// When on the first page it is possible for findViewItemByRouteInfo to
|
|
1663
1888
|
// return the exact same view you are currently on.
|
|
1664
1889
|
// Make sure that we are not swiping back to the same instances of a view.
|
|
@@ -1668,8 +1893,18 @@ class StackManager extends React.PureComponent {
|
|
|
1668
1893
|
const onStart = async () => {
|
|
1669
1894
|
const { routeInfo } = this.props;
|
|
1670
1895
|
const swipeBackRouteInfo = this.getSwipeBackRouteInfo();
|
|
1671
|
-
|
|
1896
|
+
// First try to find the view in the current outlet, then search all outlets
|
|
1897
|
+
let enteringViewItem = this.context.findViewItemByRouteInfo(swipeBackRouteInfo, this.id, false);
|
|
1898
|
+
if (!enteringViewItem) {
|
|
1899
|
+
enteringViewItem = this.context.findViewItemByRouteInfo(swipeBackRouteInfo, undefined, false);
|
|
1900
|
+
}
|
|
1672
1901
|
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
|
|
1902
|
+
// Ensure the entering view is mounted so React keeps rendering it during the gesture.
|
|
1903
|
+
// This is important when the view was previously marked for unmount but its
|
|
1904
|
+
// ionPageElement is still in the DOM.
|
|
1905
|
+
if (enteringViewItem && !enteringViewItem.mount) {
|
|
1906
|
+
enteringViewItem.mount = true;
|
|
1907
|
+
}
|
|
1673
1908
|
// When the gesture starts, kick off a transition controlled via swipe gesture
|
|
1674
1909
|
if (enteringViewItem && leavingViewItem) {
|
|
1675
1910
|
await this.transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true);
|
|
@@ -1686,7 +1921,11 @@ class StackManager extends React.PureComponent {
|
|
|
1686
1921
|
// Swipe gesture was aborted - re-hide the page that was going to enter
|
|
1687
1922
|
const { routeInfo } = this.props;
|
|
1688
1923
|
const swipeBackRouteInfo = this.getSwipeBackRouteInfo();
|
|
1689
|
-
|
|
1924
|
+
// First try to find the view in the current outlet, then search all outlets
|
|
1925
|
+
let enteringViewItem = this.context.findViewItemByRouteInfo(swipeBackRouteInfo, this.id, false);
|
|
1926
|
+
if (!enteringViewItem) {
|
|
1927
|
+
enteringViewItem = this.context.findViewItemByRouteInfo(swipeBackRouteInfo, undefined, false);
|
|
1928
|
+
}
|
|
1690
1929
|
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
|
|
1691
1930
|
// Don't hide if entering and leaving are the same (parameterized route edge case)
|
|
1692
1931
|
if (enteringViewItem !== leavingViewItem && (enteringViewItem === null || enteringViewItem === void 0 ? void 0 : enteringViewItem.ionPageElement) !== undefined) {
|
|
@@ -1869,27 +2108,59 @@ function findRouteByRouteInfo(node, routeInfo, parentPath) {
|
|
|
1869
2108
|
});
|
|
1870
2109
|
// For nested routes in React Router 6, we need to extract the relative path
|
|
1871
2110
|
// that this outlet should be responsible for matching
|
|
1872
|
-
|
|
2111
|
+
const originalPathname = routeInfo.pathname;
|
|
2112
|
+
let relativePathnameToMatch = routeInfo.pathname;
|
|
1873
2113
|
// Check if we have relative routes (routes that don't start with '/')
|
|
1874
2114
|
const hasRelativeRoutes = sortedRoutes.some((r) => r.props.path && !r.props.path.startsWith('/'));
|
|
1875
2115
|
const hasIndexRoute = sortedRoutes.some((r) => r.props.index);
|
|
1876
2116
|
// SIMPLIFIED: Trust React Router 6's matching more, compute relative path when parent is known
|
|
1877
2117
|
if ((hasRelativeRoutes || hasIndexRoute) && parentPath) {
|
|
1878
2118
|
const parentPrefix = parentPath.replace('/*', '');
|
|
1879
|
-
|
|
2119
|
+
// Normalize both paths to start with '/' for consistent comparison
|
|
2120
|
+
const normalizedParent = stripTrailingSlash(parentPrefix.startsWith('/') ? parentPrefix : `/${parentPrefix}`);
|
|
1880
2121
|
const normalizedPathname = stripTrailingSlash(routeInfo.pathname);
|
|
1881
2122
|
// Only compute relative path if pathname is within parent scope
|
|
1882
2123
|
if (normalizedPathname.startsWith(normalizedParent + '/') || normalizedPathname === normalizedParent) {
|
|
1883
2124
|
const pathSegments = routeInfo.pathname.split('/').filter(Boolean);
|
|
1884
2125
|
const parentSegments = normalizedParent.split('/').filter(Boolean);
|
|
1885
2126
|
const relativeSegments = pathSegments.slice(parentSegments.length);
|
|
1886
|
-
|
|
2127
|
+
relativePathnameToMatch = relativeSegments.join('/'); // Empty string is valid for index routes
|
|
1887
2128
|
}
|
|
1888
2129
|
}
|
|
1889
2130
|
// Find the first matching route
|
|
1890
2131
|
for (const child of sortedRoutes) {
|
|
2132
|
+
const childPath = child.props.path;
|
|
2133
|
+
const isAbsoluteRoute = childPath && childPath.startsWith('/');
|
|
2134
|
+
// Determine which pathname to match against:
|
|
2135
|
+
// - For absolute routes: use the original full pathname
|
|
2136
|
+
// - For relative routes with a parent: use the computed relative pathname
|
|
2137
|
+
// - For relative routes at root level (no parent): use the original pathname
|
|
2138
|
+
// (matchPath will handle the relative-to-absolute normalization)
|
|
2139
|
+
const pathnameToMatch = isAbsoluteRoute ? originalPathname : relativePathnameToMatch;
|
|
2140
|
+
// Determine the path portion to match:
|
|
2141
|
+
// - For absolute routes: use derivePathnameToMatch
|
|
2142
|
+
// - For relative routes at root level (no parent): use original pathname
|
|
2143
|
+
// directly since matchPath normalizes both path and pathname
|
|
2144
|
+
// - For relative routes with parent: use derivePathnameToMatch for wildcards,
|
|
2145
|
+
// or the computed relative pathname for non-wildcards
|
|
2146
|
+
let pathForMatch;
|
|
2147
|
+
if (isAbsoluteRoute) {
|
|
2148
|
+
pathForMatch = derivePathnameToMatch(pathnameToMatch, childPath);
|
|
2149
|
+
}
|
|
2150
|
+
else if (!parentPath && childPath) {
|
|
2151
|
+
// Root-level relative route: use the full pathname and let matchPath
|
|
2152
|
+
// handle the normalization (it adds '/' to both path and pathname)
|
|
2153
|
+
pathForMatch = originalPathname;
|
|
2154
|
+
}
|
|
2155
|
+
else if (childPath && childPath.includes('*')) {
|
|
2156
|
+
// Relative wildcard route with parent path: use derivePathnameToMatch
|
|
2157
|
+
pathForMatch = derivePathnameToMatch(pathnameToMatch, childPath);
|
|
2158
|
+
}
|
|
2159
|
+
else {
|
|
2160
|
+
pathForMatch = pathnameToMatch;
|
|
2161
|
+
}
|
|
1891
2162
|
const match = matchPath({
|
|
1892
|
-
pathname:
|
|
2163
|
+
pathname: pathForMatch,
|
|
1893
2164
|
componentProps: child.props,
|
|
1894
2165
|
});
|
|
1895
2166
|
if (match) {
|