@ionic/react-router 8.7.12-dev.11765377112.16762e5b → 8.7.12-dev.11765390930.11e7051a

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
- if (!path) {
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 in nested routes (those that don't start with '/'),
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
- return '';
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) {
@@ -740,27 +751,36 @@ class ReactRouterViewStack extends ViewStacks {
740
751
  const combinedParams = Object.assign(Object.assign({}, accumulatedParentParams), ((_c = routeMatch === null || routeMatch === void 0 ? void 0 : routeMatch.params) !== null && _c !== void 0 ? _c : {}));
741
752
  // For relative route paths, we need to compute an absolute pathnameBase
742
753
  // 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
754
  const routePath = routeElement.props.path;
745
755
  const isRelativePath = routePath && !routePath.startsWith('/');
746
756
  const isIndexRoute = !!routeElement.props.index;
747
- if (isRelativePath || isIndexRoute) {
748
- // Get the parent's pathnameBase to build the absolute path
749
- const parentPathnameBase = parentMatches.length > 0 ? parentMatches[parentMatches.length - 1].pathnameBase : '/';
750
- // For relative paths, the matchPath returns a relative pathnameBase
751
- // We need to make it absolute by prepending the parent's base
752
- if ((routeMatch === null || routeMatch === void 0 ? void 0 : routeMatch.pathnameBase) && isRelativePath) {
753
- // Strip leading slash if present in the relative match
754
- const relativeBase = routeMatch.pathnameBase.startsWith('/')
755
- ? routeMatch.pathnameBase.slice(1)
756
- : routeMatch.pathnameBase;
757
- absolutePathnameBase =
758
- parentPathnameBase === '/' ? `/${relativeBase}` : `${parentPathnameBase}/${relativeBase}`;
759
- }
760
- else if (isIndexRoute) {
761
- // Index routes should use the parent's base as their base
762
- absolutePathnameBase = parentPathnameBase;
763
- }
757
+ const isSplatOnlyRoute = routePath === '*' || routePath === '/*';
758
+ // Get parent's pathnameBase for relative path resolution
759
+ const parentPathnameBase = parentMatches.length > 0 ? parentMatches[parentMatches.length - 1].pathnameBase : '/';
760
+ // Start with the match's pathnameBase, falling back to routeInfo.pathname
761
+ // BUT: splat-only routes should use parent's base (v7_relativeSplatPath behavior)
762
+ let absolutePathnameBase;
763
+ if (isSplatOnlyRoute) {
764
+ // Splat routes should NOT contribute their matched portion to pathnameBase
765
+ // This aligns with React Router v7's v7_relativeSplatPath behavior
766
+ // Without this, relative links inside splat routes get double path segments
767
+ absolutePathnameBase = parentPathnameBase;
768
+ }
769
+ else if (isRelativePath && (routeMatch === null || routeMatch === void 0 ? void 0 : routeMatch.pathnameBase)) {
770
+ // For relative paths with a pathnameBase, combine with parent
771
+ const relativeBase = routeMatch.pathnameBase.startsWith('/')
772
+ ? routeMatch.pathnameBase.slice(1)
773
+ : routeMatch.pathnameBase;
774
+ absolutePathnameBase =
775
+ parentPathnameBase === '/' ? `/${relativeBase}` : `${parentPathnameBase}/${relativeBase}`;
776
+ }
777
+ else if (isIndexRoute) {
778
+ // Index routes should use the parent's base as their base
779
+ absolutePathnameBase = parentPathnameBase;
780
+ }
781
+ else {
782
+ // Default: use the match's pathnameBase or the current pathname
783
+ absolutePathnameBase = (routeMatch === null || routeMatch === void 0 ? void 0 : routeMatch.pathnameBase) || routeInfo.pathname;
764
784
  }
765
785
  const contextMatches = [
766
786
  ...parentMatches,
@@ -804,7 +824,9 @@ class ReactRouterViewStack extends ViewStacks {
804
824
  let parentPath = undefined;
805
825
  try {
806
826
  // Only attempt parent path computation for non-root outlets
807
- if (outletId !== 'routerOutlet') {
827
+ // Root outlets have IDs like 'routerOutlet' or 'routerOutlet-2'
828
+ const isRootOutlet = outletId.startsWith('routerOutlet');
829
+ if (!isRootOutlet) {
808
830
  const routeChildren = extractRouteChildren(ionRouterOutlet.props.children);
809
831
  const { hasRelativeRoutes, hasIndexRoute, hasWildcardRoute } = analyzeRouteChildren(routeChildren);
810
832
  if (hasRelativeRoutes || hasIndexRoute) {
@@ -1046,7 +1068,7 @@ class ReactRouterViewStack extends ViewStacks {
1046
1068
  * Matches a view with no path prop (default fallback route) or index route.
1047
1069
  */
1048
1070
  function matchDefaultRoute(v) {
1049
- var _a;
1071
+ var _a, _b, _c;
1050
1072
  const childProps = v.routeData.childProps;
1051
1073
  const isDefaultRoute = childProps.path === undefined || childProps.path === '';
1052
1074
  const isIndexRoute = !!childProps.index;
@@ -1059,14 +1081,22 @@ class ReactRouterViewStack extends ViewStacks {
1059
1081
  }
1060
1082
  return false;
1061
1083
  }
1084
+ // For empty path routes, only match if we're at the same level as when the view was created.
1085
+ // This prevents an empty path view item from being reused for different routes.
1062
1086
  if (isDefaultRoute) {
1087
+ const previousPathnameBase = ((_b = (_a = v.routeData) === null || _a === void 0 ? void 0 : _a.match) === null || _b === void 0 ? void 0 : _b.pathnameBase) || '';
1088
+ const normalizedBase = normalizePathnameForComparison(previousPathnameBase);
1089
+ const normalizedPathname = normalizePathnameForComparison(pathname);
1090
+ if (normalizedPathname !== normalizedBase) {
1091
+ return false;
1092
+ }
1063
1093
  match = {
1064
1094
  params: {},
1065
1095
  pathname,
1066
1096
  pathnameBase: pathname === '' ? '/' : pathname,
1067
1097
  pattern: {
1068
1098
  path: '',
1069
- caseSensitive: (_a = childProps.caseSensitive) !== null && _a !== void 0 ? _a : false,
1099
+ caseSensitive: (_c = childProps.caseSensitive) !== null && _c !== void 0 ? _c : false,
1070
1100
  end: true,
1071
1101
  },
1072
1102
  };
@@ -1191,24 +1221,30 @@ class StackManager extends React.PureComponent {
1191
1221
  if (this.outletMountPath && !currentPathname.startsWith(this.outletMountPath)) {
1192
1222
  return undefined;
1193
1223
  }
1194
- // If this is a nested outlet (has an explicit ID like "main"),
1195
- // we need to figure out what part of the path was already matched
1196
- if (this.id !== 'routerOutlet' && this.ionRouterOutlet) {
1224
+ // Check if this outlet has route children to analyze
1225
+ if (this.ionRouterOutlet) {
1197
1226
  const routeChildren = extractRouteChildren(this.ionRouterOutlet.props.children);
1198
1227
  const { hasRelativeRoutes, hasIndexRoute, hasWildcardRoute } = analyzeRouteChildren(routeChildren);
1199
- const result = computeParentPath({
1200
- currentPathname,
1201
- outletMountPath: this.outletMountPath,
1202
- routeChildren,
1203
- hasRelativeRoutes,
1204
- hasIndexRoute,
1205
- hasWildcardRoute,
1206
- });
1207
- // Update the outlet mount path if it was set
1208
- if (result.outletMountPath && !this.outletMountPath) {
1209
- this.outletMountPath = result.outletMountPath;
1228
+ // Root outlets have IDs like 'routerOutlet' or 'routerOutlet-2'
1229
+ // But even outlets with auto-generated IDs may need parent path computation
1230
+ // if they have relative routes (indicating they're nested outlets)
1231
+ const isRootOutlet = this.id.startsWith('routerOutlet');
1232
+ const needsParentPath = !isRootOutlet || hasRelativeRoutes || hasIndexRoute;
1233
+ if (needsParentPath) {
1234
+ const result = computeParentPath({
1235
+ currentPathname,
1236
+ outletMountPath: this.outletMountPath,
1237
+ routeChildren,
1238
+ hasRelativeRoutes,
1239
+ hasIndexRoute,
1240
+ hasWildcardRoute,
1241
+ });
1242
+ // Update the outlet mount path if it was set
1243
+ if (result.outletMountPath && !this.outletMountPath) {
1244
+ this.outletMountPath = result.outletMountPath;
1245
+ }
1246
+ return result.parentPath;
1210
1247
  }
1211
- return result.parentPath;
1212
1248
  }
1213
1249
  return this.outletMountPath;
1214
1250
  }
@@ -1297,7 +1333,9 @@ class StackManager extends React.PureComponent {
1297
1333
  */
1298
1334
  handleOutOfContextNestedOutlet(parentPath, leavingViewItem) {
1299
1335
  var _a;
1300
- if (this.id === 'routerOutlet' || parentPath !== undefined || !this.ionRouterOutlet) {
1336
+ // Root outlets have IDs like 'routerOutlet' or 'routerOutlet-2'
1337
+ const isRootOutlet = this.id.startsWith('routerOutlet');
1338
+ if (isRootOutlet || parentPath !== undefined || !this.ionRouterOutlet) {
1301
1339
  return false;
1302
1340
  }
1303
1341
  const routesChildren = (_a = getRoutesChildren(this.ionRouterOutlet.props.children)) !== null && _a !== void 0 ? _a : this.ionRouterOutlet.props.children;
@@ -1322,7 +1360,9 @@ class StackManager extends React.PureComponent {
1322
1360
  * Returns true if the transition should be aborted.
1323
1361
  */
1324
1362
  handleNoMatchingRoute(enteringRoute, enteringViewItem, leavingViewItem) {
1325
- if (this.id === 'routerOutlet' || enteringRoute || enteringViewItem) {
1363
+ // Root outlets have IDs like 'routerOutlet' or 'routerOutlet-2'
1364
+ const isRootOutlet = this.id.startsWith('routerOutlet');
1365
+ if (isRootOutlet || enteringRoute || enteringViewItem) {
1326
1366
  return false;
1327
1367
  }
1328
1368
  // Hide any visible views in this outlet since it has no matching route
@@ -1869,27 +1909,59 @@ function findRouteByRouteInfo(node, routeInfo, parentPath) {
1869
1909
  });
1870
1910
  // For nested routes in React Router 6, we need to extract the relative path
1871
1911
  // that this outlet should be responsible for matching
1872
- let pathnameToMatch = routeInfo.pathname;
1912
+ const originalPathname = routeInfo.pathname;
1913
+ let relativePathnameToMatch = routeInfo.pathname;
1873
1914
  // Check if we have relative routes (routes that don't start with '/')
1874
1915
  const hasRelativeRoutes = sortedRoutes.some((r) => r.props.path && !r.props.path.startsWith('/'));
1875
1916
  const hasIndexRoute = sortedRoutes.some((r) => r.props.index);
1876
1917
  // SIMPLIFIED: Trust React Router 6's matching more, compute relative path when parent is known
1877
1918
  if ((hasRelativeRoutes || hasIndexRoute) && parentPath) {
1878
1919
  const parentPrefix = parentPath.replace('/*', '');
1879
- const normalizedParent = stripTrailingSlash(parentPrefix);
1920
+ // Normalize both paths to start with '/' for consistent comparison
1921
+ const normalizedParent = stripTrailingSlash(parentPrefix.startsWith('/') ? parentPrefix : `/${parentPrefix}`);
1880
1922
  const normalizedPathname = stripTrailingSlash(routeInfo.pathname);
1881
1923
  // Only compute relative path if pathname is within parent scope
1882
1924
  if (normalizedPathname.startsWith(normalizedParent + '/') || normalizedPathname === normalizedParent) {
1883
1925
  const pathSegments = routeInfo.pathname.split('/').filter(Boolean);
1884
1926
  const parentSegments = normalizedParent.split('/').filter(Boolean);
1885
1927
  const relativeSegments = pathSegments.slice(parentSegments.length);
1886
- pathnameToMatch = relativeSegments.join('/'); // Empty string is valid for index routes
1928
+ relativePathnameToMatch = relativeSegments.join('/'); // Empty string is valid for index routes
1887
1929
  }
1888
1930
  }
1889
1931
  // Find the first matching route
1890
1932
  for (const child of sortedRoutes) {
1933
+ const childPath = child.props.path;
1934
+ const isAbsoluteRoute = childPath && childPath.startsWith('/');
1935
+ // Determine which pathname to match against:
1936
+ // - For absolute routes: use the original full pathname
1937
+ // - For relative routes with a parent: use the computed relative pathname
1938
+ // - For relative routes at root level (no parent): use the original pathname
1939
+ // (matchPath will handle the relative-to-absolute normalization)
1940
+ const pathnameToMatch = isAbsoluteRoute ? originalPathname : relativePathnameToMatch;
1941
+ // Determine the path portion to match:
1942
+ // - For absolute routes: use derivePathnameToMatch
1943
+ // - For relative routes at root level (no parent): use original pathname
1944
+ // directly since matchPath normalizes both path and pathname
1945
+ // - For relative routes with parent: use derivePathnameToMatch for wildcards,
1946
+ // or the computed relative pathname for non-wildcards
1947
+ let pathForMatch;
1948
+ if (isAbsoluteRoute) {
1949
+ pathForMatch = derivePathnameToMatch(pathnameToMatch, childPath);
1950
+ }
1951
+ else if (!parentPath && childPath) {
1952
+ // Root-level relative route: use the full pathname and let matchPath
1953
+ // handle the normalization (it adds '/' to both path and pathname)
1954
+ pathForMatch = originalPathname;
1955
+ }
1956
+ else if (childPath && childPath.includes('*')) {
1957
+ // Relative wildcard route with parent path: use derivePathnameToMatch
1958
+ pathForMatch = derivePathnameToMatch(pathnameToMatch, childPath);
1959
+ }
1960
+ else {
1961
+ pathForMatch = pathnameToMatch;
1962
+ }
1891
1963
  const match = matchPath({
1892
- pathname: pathnameToMatch,
1964
+ pathname: pathForMatch,
1893
1965
  componentProps: child.props,
1894
1966
  });
1895
1967
  if (match) {
@@ -2147,16 +2219,10 @@ const IonRouter = ({ children, registerHistoryListener }) => {
2147
2219
  * tab and use its `pushedByRoute`.
2148
2220
  */
2149
2221
  const lastRoute = locationHistory.current.getCurrentRouteInfoForTab(routeInfo.tab);
2150
- /**
2151
- * Tab bar switches (direction 'none') should not create cross-tab back
2152
- * navigation. Only inherit pushedByRoute from the tab's own history.
2153
- */
2154
- if (routeInfo.routeDirection === 'none') {
2155
- routeInfo.pushedByRoute = lastRoute === null || lastRoute === void 0 ? void 0 : lastRoute.pushedByRoute;
2156
- }
2157
- else {
2158
- routeInfo.pushedByRoute = (_e = lastRoute === null || lastRoute === void 0 ? void 0 : lastRoute.pushedByRoute) !== null && _e !== void 0 ? _e : leavingLocationInfo.pathname;
2159
- }
2222
+ // This helps maintain correct back stack behavior within tabs.
2223
+ // If this is the first time entering this tab from a different context,
2224
+ // use the leaving route's pathname as the pushedByRoute to maintain the back stack.
2225
+ routeInfo.pushedByRoute = (_e = lastRoute === null || lastRoute === void 0 ? void 0 : lastRoute.pushedByRoute) !== null && _e !== void 0 ? _e : leavingLocationInfo.pathname;
2160
2226
  // Triggered by `history.replace()` or a `<Redirect />` component, etc.
2161
2227
  }
2162
2228
  else if (routeInfo.routeAction === 'replace') {
@@ -2323,14 +2389,11 @@ const IonRouter = ({ children, registerHistoryListener }) => {
2323
2389
  handleNavigate(defaultHref, 'pop', 'back', routeAnimation);
2324
2390
  }
2325
2391
  /**
2326
- * No `pushedByRoute` (e.g., initial page load or tab root).
2327
- * Tabs with no back history should not navigate.
2392
+ * No `pushedByRoute`
2393
+ * e.g., initial page load
2328
2394
  */
2329
2395
  }
2330
2396
  else {
2331
- if (routeInfo && routeInfo.tab) {
2332
- return;
2333
- }
2334
2397
  handleNavigate(defaultHref, 'pop', 'back', routeAnimation);
2335
2398
  }
2336
2399
  };