@ionic/react 8.8.4-dev.11775078622.1402ffa2 → 8.8.4-nightly.20260403

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
@@ -380,7 +380,9 @@ const useIonViewDidLeave = (callback, deps = []) => {
380
380
  };
381
381
 
382
382
  const NavContext = /*@__PURE__*/ React.createContext({
383
+ getIonRedirect: () => undefined,
383
384
  getIonRoute: () => undefined,
385
+ getPageManager: () => undefined,
384
386
  getStackManager: () => undefined,
385
387
  goBack: (route) => {
386
388
  if (typeof window !== 'undefined') {
@@ -729,11 +731,6 @@ const createRoutingComponent = (tagName, customElement) => {
729
731
  this.handleClick = (e) => {
730
732
  const { routerLink, routerDirection, routerOptions, routerAnimation } = this.props;
731
733
  if (routerLink !== undefined) {
732
- // Allow modifier key clicks (ctrl/cmd/shift) to open the link in a new tab/window
733
- // without triggering SPA navigation on the current page.
734
- if (e.metaKey || e.ctrlKey || e.shiftKey) {
735
- return;
736
- }
737
734
  e.preventDefault();
738
735
  this.context.navigate(routerLink, routerDirection, undefined, routerAnimation, routerOptions);
739
736
  }
@@ -1140,18 +1137,7 @@ class PageManager extends React.PureComponent {
1140
1137
  super(props);
1141
1138
  this.ionPageElementRef = React.createRef();
1142
1139
  // React refs must be stable (not created inline).
1143
- // Wrap merged refs to add ion-page-invisible synchronously when element is created
1144
- const baseMergedRefs = mergeRefs(this.ionPageElementRef, this.props.forwardedRef);
1145
- this.stableMergedRefs = (node) => {
1146
- if (node && !node.classList.contains('ion-page-invisible') && !node.classList.contains('ion-page-hidden')) {
1147
- // Add ion-page-invisible synchronously before first paint (if in an outlet)
1148
- // This prevents the flash that occurs when componentDidMount runs after paint
1149
- if (this.context?.isInOutlet?.()) {
1150
- node.classList.add('ion-page-invisible');
1151
- }
1152
- }
1153
- baseMergedRefs(node);
1154
- };
1140
+ this.stableMergedRefs = mergeRefs(this.ionPageElementRef, this.props.forwardedRef);
1155
1141
  /**
1156
1142
  * This binds the scope of the following methods to the class scope.
1157
1143
  * The `.bind` method returns a new function, so we need to assign it
@@ -1163,38 +1149,11 @@ class PageManager extends React.PureComponent {
1163
1149
  this.ionViewWillLeaveHandler = this.ionViewWillLeaveHandler.bind(this);
1164
1150
  this.ionViewDidLeaveHandler = this.ionViewDidLeaveHandler.bind(this);
1165
1151
  }
1166
- parseClasses(className) {
1167
- if (!className)
1168
- return new Set();
1169
- return new Set(className.split(/\s+/).filter(Boolean));
1170
- }
1171
- /**
1172
- * Updates classList by diffing old/new className props.
1173
- * Preserves framework-added classes (can-go-back, ion-page-invisible, etc.).
1174
- */
1175
- updateUserClasses(oldClassName, newClassName) {
1176
- if (!this.ionPageElementRef.current)
1177
- return;
1178
- const oldClasses = this.parseClasses(oldClassName);
1179
- const newClasses = this.parseClasses(newClassName);
1180
- oldClasses.forEach((cls) => {
1181
- if (!newClasses.has(cls)) {
1182
- this.ionPageElementRef.current.classList.remove(cls);
1183
- }
1184
- });
1185
- newClasses.forEach((cls) => {
1186
- if (!oldClasses.has(cls)) {
1187
- this.ionPageElementRef.current.classList.add(cls);
1188
- }
1189
- });
1190
- }
1191
1152
  componentDidMount() {
1192
1153
  if (this.ionPageElementRef.current) {
1193
- // Add user classes via DOM manipulation to preserve framework-added classes.
1194
- // We only set "ion-page" in JSX; user classes are added here.
1195
- // Note: ion-page-invisible is added in the ref callback (stableMergedRefs) to prevent flash.
1196
- // The ref callback runs synchronously when the element is created, before the browser paints.
1197
- this.updateUserClasses(undefined, this.props.className);
1154
+ if (this.context.isInOutlet()) {
1155
+ this.ionPageElementRef.current.classList.add('ion-page-invisible');
1156
+ }
1198
1157
  this.context.registerIonPage(this.ionPageElementRef.current, this.props.routeInfo);
1199
1158
  this.ionPageElementRef.current.addEventListener('ionViewWillEnter', this.ionViewWillEnterHandler);
1200
1159
  this.ionPageElementRef.current.addEventListener('ionViewDidEnter', this.ionViewDidEnterHandler);
@@ -1202,11 +1161,6 @@ class PageManager extends React.PureComponent {
1202
1161
  this.ionPageElementRef.current.addEventListener('ionViewDidLeave', this.ionViewDidLeaveHandler);
1203
1162
  }
1204
1163
  }
1205
- componentDidUpdate(prevProps) {
1206
- if (prevProps.className !== this.props.className) {
1207
- this.updateUserClasses(prevProps.className, this.props.className);
1208
- }
1209
- }
1210
1164
  componentWillUnmount() {
1211
1165
  if (this.ionPageElementRef.current) {
1212
1166
  this.ionPageElementRef.current.removeEventListener('ionViewWillEnter', this.ionViewWillEnterHandler);
@@ -1236,11 +1190,9 @@ class PageManager extends React.PureComponent {
1236
1190
  render() {
1237
1191
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
1238
1192
  const { className, children, routeInfo, forwardedRef, ...props } = this.props;
1239
- // Only set "ion-page" in JSX. User classes are managed via DOM in componentDidMount/componentDidUpdate
1240
- // to preserve framework-added classes (can-go-back, ion-page-invisible, etc.) when className prop changes.
1241
1193
  return (jsx(IonLifeCycleContext.Consumer, { children: (context) => {
1242
1194
  this.ionLifeCycleContext = context;
1243
- return (jsx("div", { className: "ion-page", ref: this.stableMergedRefs, ...props, children: children }));
1195
+ return (jsx("div", { className: className ? `${className} ion-page` : `ion-page`, ref: this.stableMergedRefs, ...props, children: children }));
1244
1196
  } }));
1245
1197
  }
1246
1198
  static get contextType() {
@@ -1379,10 +1331,10 @@ class OutletPageManager extends React.Component {
1379
1331
  this.ionLifeCycleContext.ionViewDidLeave();
1380
1332
  }
1381
1333
  render() {
1382
- const { StackManager, children, routeInfo, id, ...props } = this.props;
1334
+ const { StackManager, children, routeInfo, ...props } = this.props;
1383
1335
  return (jsx(IonLifeCycleContext.Consumer, { children: (context) => {
1384
1336
  this.ionLifeCycleContext = context;
1385
- return (jsx(StackManager, { id: id, routeInfo: routeInfo, children: jsx(IonRouterOutletInner, { id: id, setRef: (val) => (this.ionRouterOutlet = val), ...props, children: children }) }));
1337
+ return (jsx(StackManager, { routeInfo: routeInfo, children: jsx(IonRouterOutletInner, { setRef: (val) => (this.ionRouterOutlet = val), ...props, children: children }) }));
1386
1338
  } }));
1387
1339
  }
1388
1340
  static get contextType() {
@@ -1393,13 +1345,11 @@ class OutletPageManager extends React.Component {
1393
1345
  class IonRouterOutletContainer extends React.Component {
1394
1346
  constructor(props) {
1395
1347
  super(props);
1396
- this.outletId = props.id ?? `routerOutlet-${generateId('routerOutlet')}`;
1397
1348
  }
1398
1349
  render() {
1399
1350
  const StackManager = this.context.getStackManager();
1400
1351
  const { children, forwardedRef, ...props } = this.props;
1401
- const outletId = props.id ?? this.outletId;
1402
- return this.context.hasIonicRouter() ? (props.ionPage ? (jsx(OutletPageManager, { StackManager: StackManager, routeInfo: this.context.routeInfo, ...props, children: children })) : (jsx(StackManager, { routeInfo: this.context.routeInfo, id: outletId, children: jsx(IonRouterOutletInner, { ...props, id: outletId, forwardedRef: forwardedRef, children: children }) }))) : (jsx(IonRouterOutletInner, { ref: forwardedRef, ...this.props, children: this.props.children }));
1352
+ return this.context.hasIonicRouter() ? (props.ionPage ? (jsx(OutletPageManager, { StackManager: StackManager, routeInfo: this.context.routeInfo, ...props, children: children })) : (jsx(StackManager, { routeInfo: this.context.routeInfo, children: jsx(IonRouterOutletInner, { ...props, forwardedRef: forwardedRef, children: children }) }))) : (jsx(IonRouterOutletInner, { ref: forwardedRef, ...this.props, children: this.props.children }));
1403
1353
  }
1404
1354
  static get contextType() {
1405
1355
  return NavContext;
@@ -1596,9 +1546,7 @@ const matchesTab = (pathname, href) => {
1596
1546
  if (href === undefined) {
1597
1547
  return false;
1598
1548
  }
1599
- // Strip query string before comparing — href may contain search params (e.g., "/tabs/home?foo=bar")
1600
- const hrefPathname = href.split('?')[0];
1601
- const normalizedHref = hrefPathname.endsWith('/') && hrefPathname !== '/' ? hrefPathname.slice(0, -1) : hrefPathname;
1549
+ const normalizedHref = href.endsWith('/') && href !== '/' ? href.slice(0, -1) : href;
1602
1550
  return pathname === normalizedHref || pathname.startsWith(normalizedHref + '/');
1603
1551
  };
1604
1552
  class IonTabBarUnwrapped extends React.PureComponent {
@@ -1690,7 +1638,7 @@ class IonTabBarUnwrapped extends React.PureComponent {
1690
1638
  const prevHref = state.tabs[prevActiveTab].currentHref;
1691
1639
  const prevRouteOptions = state.tabs[prevActiveTab].currentRouteOptions;
1692
1640
  if (activeTab !== prevActiveTab ||
1693
- prevHref !== (props.routeInfo?.pathname || '') + (props.routeInfo?.search || '') ||
1641
+ prevHref !== props.routeInfo?.pathname ||
1694
1642
  prevRouteOptions !== props.routeInfo?.routeOptions) {
1695
1643
  tabs[activeTab] = {
1696
1644
  originalHref: tabs[activeTab].originalHref,
@@ -1757,7 +1705,7 @@ class IonTabBarUnwrapped extends React.PureComponent {
1757
1705
  return (child) => {
1758
1706
  if (child != null && child.props && (child.type === IonTabButton || child.type.isTabButton)) {
1759
1707
  const href = child.props.tab === activeTab
1760
- ? (this.props.routeInfo?.pathname || '') + (this.props.routeInfo?.search || '')
1708
+ ? this.props.routeInfo?.pathname
1761
1709
  : this.state.tabs[child.props.tab].currentHref;
1762
1710
  const routeOptions = child.props.tab === activeTab
1763
1711
  ? this.props.routeInfo?.routeOptions
@@ -1882,6 +1830,20 @@ class IonRoute extends React.PureComponent {
1882
1830
  }
1883
1831
  }
1884
1832
 
1833
+ class IonRedirect extends React.PureComponent {
1834
+ render() {
1835
+ const IonRedirectInner = this.context.getIonRedirect();
1836
+ if (!this.context.hasIonicRouter() || !IonRedirect) {
1837
+ console.error('You either do not have an Ionic Router package, or your router does not support using <IonRedirect>');
1838
+ return null;
1839
+ }
1840
+ return jsx(IonRedirectInner, { ...this.props });
1841
+ }
1842
+ static get contextType() {
1843
+ return NavContext;
1844
+ }
1845
+ }
1846
+
1885
1847
  const IonRouterContext = React.createContext({
1886
1848
  routeInfo: undefined, // TODO(FW-2959): type
1887
1849
  push: () => {
@@ -1890,9 +1852,6 @@ const IonRouterContext = React.createContext({
1890
1852
  back: () => {
1891
1853
  throw new Error('An Ionic Router is required for IonRouterContext');
1892
1854
  },
1893
- navigateRoot: () => {
1894
- throw new Error('An Ionic Router is required for IonRouterContext');
1895
- },
1896
1855
  canGoBack: () => {
1897
1856
  throw new Error('An Ionic Router is required for IonRouterContext');
1898
1857
  },
@@ -1909,10 +1868,9 @@ function useIonRouter() {
1909
1868
  back: context.back,
1910
1869
  push: context.push,
1911
1870
  goBack: context.back,
1912
- navigateRoot: context.navigateRoot,
1913
1871
  canGoBack: context.canGoBack,
1914
1872
  routeInfo: context.routeInfo,
1915
- }), [context.back, context.push, context.navigateRoot, context.canGoBack, context.routeInfo]);
1873
+ }), [context.back, context.push, context.canGoBack, context.routeInfo]);
1916
1874
  }
1917
1875
 
1918
1876
  class CreateAnimation extends React.PureComponent {
@@ -2282,7 +2240,6 @@ const RouteManagerContext = /*@__PURE__*/ React.createContext({
2282
2240
  findLeavingViewItemByRouteInfo: () => undefined,
2283
2241
  findViewItemByRouteInfo: () => undefined,
2284
2242
  getChildrenToRender: () => undefined,
2285
- getViewItemsForOutlet: () => [],
2286
2243
  goBack: () => undefined,
2287
2244
  unMountViewItem: () => undefined,
2288
2245
  });
@@ -2401,14 +2358,7 @@ class LocationHistory {
2401
2358
  _replace(routeInfo) {
2402
2359
  const routeInfos = this._getRouteInfosByKey(routeInfo.tab);
2403
2360
  routeInfos && routeInfos.pop();
2404
- // Get the current route that's being replaced
2405
- const currentRoute = this.locationHistory[this.locationHistory.length - 1];
2406
- // Only pop from global history if we're replacing in the same outlet context.
2407
- // Don't pop if we're entering a nested outlet (current route has no tab, new route has a tab)
2408
- const isEnteringNestedOutlet = currentRoute && !currentRoute.tab && !!routeInfo.tab;
2409
- if (!isEnteringNestedOutlet) {
2410
- this.locationHistory.pop();
2411
- }
2361
+ this.locationHistory.pop();
2412
2362
  this._add(routeInfo);
2413
2363
  }
2414
2364
  _clear() {
@@ -2440,20 +2390,6 @@ class LocationHistory {
2440
2390
  }
2441
2391
  return undefined;
2442
2392
  }
2443
- /**
2444
- * Returns the most recent RouteInfo in global history (excluding the current
2445
- * entry) whose pathname matches the given value. Unlike findLastLocation,
2446
- * this search is tab-agnostic. Used by the multi-step back detection.
2447
- */
2448
- findLastLocationByPathname(pathname) {
2449
- for (let i = this.locationHistory.length - 2; i >= 0; i--) {
2450
- const ri = this.locationHistory[i];
2451
- if (ri && ri.pathname === pathname) {
2452
- return ri;
2453
- }
2454
- }
2455
- return undefined;
2456
- }
2457
2393
  findLastLocation(routeInfo) {
2458
2394
  const routeInfos = this._getRouteInfosByKey(routeInfo.tab);
2459
2395
  if (routeInfos) {
@@ -2485,17 +2421,6 @@ class LocationHistory {
2485
2421
  canGoBack() {
2486
2422
  return this.locationHistory.length > 1;
2487
2423
  }
2488
- findTabForPathname(pathname) {
2489
- for (const tab of Object.keys(this.tabHistory)) {
2490
- const routeInfos = this.tabHistory[tab];
2491
- for (let i = routeInfos.length - 1; i >= 0; i--) {
2492
- if (routeInfos[i].pathname === pathname) {
2493
- return tab;
2494
- }
2495
- }
2496
- }
2497
- return undefined;
2498
- }
2499
2424
  }
2500
2425
 
2501
2426
  class NavManager extends React.PureComponent {
@@ -2508,9 +2433,6 @@ class NavManager extends React.PureComponent {
2508
2433
  back: (animationBuilder) => {
2509
2434
  this.goBack(undefined, animationBuilder);
2510
2435
  },
2511
- navigateRoot: (pathname, animationBuilder) => {
2512
- this.props.onNavigateRoot(pathname, animationBuilder);
2513
- },
2514
2436
  canGoBack: () => this.props.locationHistory.canGoBack(),
2515
2437
  nativeBack: () => this.props.onNativeBack(),
2516
2438
  routeInfo: this.props.routeInfo,
@@ -2519,8 +2441,10 @@ class NavManager extends React.PureComponent {
2519
2441
  goBack: this.goBack.bind(this),
2520
2442
  hasIonicRouter: () => true,
2521
2443
  navigate: this.navigate.bind(this),
2444
+ getIonRedirect: this.getIonRedirect.bind(this),
2522
2445
  getIonRoute: this.getIonRoute.bind(this),
2523
2446
  getStackManager: this.getStackManager.bind(this),
2447
+ getPageManager: this.getPageManager.bind(this),
2524
2448
  routeInfo: this.props.routeInfo,
2525
2449
  setCurrentTab: this.props.onSetCurrentTab,
2526
2450
  changeTab: this.props.onChangeTab,
@@ -2553,6 +2477,12 @@ class NavManager extends React.PureComponent {
2553
2477
  navigate(path, direction = 'forward', action = 'push', animationBuilder, options, tab) {
2554
2478
  this.props.onNavigate(path, action, direction, animationBuilder, options, tab);
2555
2479
  }
2480
+ getPageManager() {
2481
+ return PageManager;
2482
+ }
2483
+ getIonRedirect() {
2484
+ return this.props.ionRedirect;
2485
+ }
2556
2486
  getIonRoute() {
2557
2487
  return this.props.ionRoute;
2558
2488
  }
@@ -2582,7 +2512,10 @@ class ViewStacks {
2582
2512
  }
2583
2513
  }
2584
2514
  clear(outletId) {
2585
- delete this.viewStacks[outletId];
2515
+ // Give some time for the leaving views to transition before removing
2516
+ return setTimeout(() => {
2517
+ delete this.viewStacks[outletId];
2518
+ }, 500);
2586
2519
  }
2587
2520
  getViewItemsForOutlet(outletId) {
2588
2521
  return this.viewStacks[outletId] || [];
@@ -2611,5 +2544,5 @@ class ViewStacks {
2611
2544
  }
2612
2545
  }
2613
2546
 
2614
- export { CreateAnimation, DefaultIonLifeCycleContext, IonAccordion, IonAccordionGroup, IonActionSheet, IonAlert, IonApp, IonAvatar, IonBackButton, IonBackdrop, IonBadge, IonBreadcrumb, IonBreadcrumbs, IonButton, IonButtons, IonCard, IonCardContent, IonCardHeader, IonCardSubtitle, IonCardTitle, IonCheckbox, IonChip, IonCol, IonContent, IonDatetime, IonDatetimeButton, IonFab, IonFabButton, IonFabList, IonFooter, IonGrid, IonHeader, IonIcon, IonImg, IonInfiniteScroll, IonInfiniteScrollContent, IonInput, IonInputOtp, IonInputPasswordToggle, IonItem, IonItemDivider, IonItemGroup, IonItemOption, IonItemOptions, IonItemSliding, IonLabel, IonLifeCycleContext, IonList, IonListHeader, IonLoading, IonMenu, IonMenuButton, IonMenuToggle, IonModal, IonNav, IonNavLink, IonNote, IonPage, IonPicker, IonPickerColumn, IonPickerColumnOption, IonPickerLegacy, IonPopover, IonProgressBar, IonRadio, IonRadioGroup, IonRange, IonRefresher, IonRefresherContent, IonReorder, IonReorderGroup, IonRippleEffect, IonRoute, IonRouterContext, IonRouterLink, IonRouterOutlet, IonRow, IonSearchbar, IonSegment, IonSegmentButton, IonSegmentContent, IonSegmentView, IonSelect, IonSelectModal, IonSelectOption, IonSkeletonText, IonSpinner, IonSplitPane, IonTab, IonTabBar, IonTabButton, IonTabs, IonTabsContext, IonText, IonTextarea, IonThumbnail, IonTitle, IonToast, IonToggle, IonToolbar, LocationHistory, NavContext, NavManager, RouteManagerContext, StackContext, ViewLifeCycleManager, ViewStacks, generateId, getConfig, getPlatforms, isPlatform, setupIonicReact, useIonActionSheet, useIonAlert, useIonLoading, useIonModal, useIonPicker, useIonPopover, useIonRouter, useIonToast, useIonViewDidEnter, useIonViewDidLeave, useIonViewWillEnter, useIonViewWillLeave, withIonLifeCycle };
2547
+ export { CreateAnimation, DefaultIonLifeCycleContext, IonAccordion, IonAccordionGroup, IonActionSheet, IonAlert, IonApp, IonAvatar, IonBackButton, IonBackdrop, IonBadge, IonBreadcrumb, IonBreadcrumbs, IonButton, IonButtons, IonCard, IonCardContent, IonCardHeader, IonCardSubtitle, IonCardTitle, IonCheckbox, IonChip, IonCol, IonContent, IonDatetime, IonDatetimeButton, IonFab, IonFabButton, IonFabList, IonFooter, IonGrid, IonHeader, IonIcon, IonImg, IonInfiniteScroll, IonInfiniteScrollContent, IonInput, IonInputOtp, IonInputPasswordToggle, IonItem, IonItemDivider, IonItemGroup, IonItemOption, IonItemOptions, IonItemSliding, IonLabel, IonLifeCycleContext, IonList, IonListHeader, IonLoading, IonMenu, IonMenuButton, IonMenuToggle, IonModal, IonNav, IonNavLink, IonNote, IonPage, IonPicker, IonPickerColumn, IonPickerColumnOption, IonPickerLegacy, IonPopover, IonProgressBar, IonRadio, IonRadioGroup, IonRange, IonRedirect, IonRefresher, IonRefresherContent, IonReorder, IonReorderGroup, IonRippleEffect, IonRoute, IonRouterContext, IonRouterLink, IonRouterOutlet, IonRow, IonSearchbar, IonSegment, IonSegmentButton, IonSegmentContent, IonSegmentView, IonSelect, IonSelectModal, IonSelectOption, IonSkeletonText, IonSpinner, IonSplitPane, IonTab, IonTabBar, IonTabButton, IonTabs, IonTabsContext, IonText, IonTextarea, IonThumbnail, IonTitle, IonToast, IonToggle, IonToolbar, LocationHistory, NavContext, NavManager, RouteManagerContext, StackContext, ViewLifeCycleManager, ViewStacks, generateId, getConfig, getPlatforms, isPlatform, setupIonicReact, useIonActionSheet, useIonAlert, useIonLoading, useIonModal, useIonPicker, useIonPopover, useIonRouter, useIonToast, useIonViewDidEnter, useIonViewDidLeave, useIonViewWillEnter, useIonViewWillLeave, withIonLifeCycle };
2615
2548
  //# sourceMappingURL=index.js.map