@ionic/react 8.8.1-dev.11772745200.1f0e21b1 → 8.8.1-dev.11773168858.1f9c0eb8

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,9 +380,7 @@ const useIonViewDidLeave = (callback, deps = []) => {
380
380
  };
381
381
 
382
382
  const NavContext = /*@__PURE__*/ React.createContext({
383
- getIonRedirect: () => undefined,
384
383
  getIonRoute: () => undefined,
385
- getPageManager: () => undefined,
386
384
  getStackManager: () => undefined,
387
385
  goBack: (route) => {
388
386
  if (typeof window !== 'undefined') {
@@ -1137,7 +1135,18 @@ class PageManager extends React.PureComponent {
1137
1135
  super(props);
1138
1136
  this.ionPageElementRef = React.createRef();
1139
1137
  // React refs must be stable (not created inline).
1140
- this.stableMergedRefs = mergeRefs(this.ionPageElementRef, this.props.forwardedRef);
1138
+ // Wrap merged refs to add ion-page-invisible synchronously when element is created
1139
+ const baseMergedRefs = mergeRefs(this.ionPageElementRef, this.props.forwardedRef);
1140
+ this.stableMergedRefs = (node) => {
1141
+ if (node && !node.classList.contains('ion-page-invisible') && !node.classList.contains('ion-page-hidden')) {
1142
+ // Add ion-page-invisible synchronously before first paint (if in an outlet)
1143
+ // This prevents the flash that occurs when componentDidMount runs after paint
1144
+ if (this.context?.isInOutlet?.()) {
1145
+ node.classList.add('ion-page-invisible');
1146
+ }
1147
+ }
1148
+ baseMergedRefs(node);
1149
+ };
1141
1150
  /**
1142
1151
  * This binds the scope of the following methods to the class scope.
1143
1152
  * The `.bind` method returns a new function, so we need to assign it
@@ -1149,11 +1158,38 @@ class PageManager extends React.PureComponent {
1149
1158
  this.ionViewWillLeaveHandler = this.ionViewWillLeaveHandler.bind(this);
1150
1159
  this.ionViewDidLeaveHandler = this.ionViewDidLeaveHandler.bind(this);
1151
1160
  }
1161
+ parseClasses(className) {
1162
+ if (!className)
1163
+ return new Set();
1164
+ return new Set(className.split(/\s+/).filter(Boolean));
1165
+ }
1166
+ /**
1167
+ * Updates classList by diffing old/new className props.
1168
+ * Preserves framework-added classes (can-go-back, ion-page-invisible, etc.).
1169
+ */
1170
+ updateUserClasses(oldClassName, newClassName) {
1171
+ if (!this.ionPageElementRef.current)
1172
+ return;
1173
+ const oldClasses = this.parseClasses(oldClassName);
1174
+ const newClasses = this.parseClasses(newClassName);
1175
+ oldClasses.forEach((cls) => {
1176
+ if (!newClasses.has(cls)) {
1177
+ this.ionPageElementRef.current.classList.remove(cls);
1178
+ }
1179
+ });
1180
+ newClasses.forEach((cls) => {
1181
+ if (!oldClasses.has(cls)) {
1182
+ this.ionPageElementRef.current.classList.add(cls);
1183
+ }
1184
+ });
1185
+ }
1152
1186
  componentDidMount() {
1153
1187
  if (this.ionPageElementRef.current) {
1154
- if (this.context.isInOutlet()) {
1155
- this.ionPageElementRef.current.classList.add('ion-page-invisible');
1156
- }
1188
+ // Add user classes via DOM manipulation to preserve framework-added classes.
1189
+ // We only set "ion-page" in JSX; user classes are added here.
1190
+ // Note: ion-page-invisible is added in the ref callback (stableMergedRefs) to prevent flash.
1191
+ // The ref callback runs synchronously when the element is created, before the browser paints.
1192
+ this.updateUserClasses(undefined, this.props.className);
1157
1193
  this.context.registerIonPage(this.ionPageElementRef.current, this.props.routeInfo);
1158
1194
  this.ionPageElementRef.current.addEventListener('ionViewWillEnter', this.ionViewWillEnterHandler);
1159
1195
  this.ionPageElementRef.current.addEventListener('ionViewDidEnter', this.ionViewDidEnterHandler);
@@ -1161,6 +1197,11 @@ class PageManager extends React.PureComponent {
1161
1197
  this.ionPageElementRef.current.addEventListener('ionViewDidLeave', this.ionViewDidLeaveHandler);
1162
1198
  }
1163
1199
  }
1200
+ componentDidUpdate(prevProps) {
1201
+ if (prevProps.className !== this.props.className) {
1202
+ this.updateUserClasses(prevProps.className, this.props.className);
1203
+ }
1204
+ }
1164
1205
  componentWillUnmount() {
1165
1206
  if (this.ionPageElementRef.current) {
1166
1207
  this.ionPageElementRef.current.removeEventListener('ionViewWillEnter', this.ionViewWillEnterHandler);
@@ -1190,9 +1231,11 @@ class PageManager extends React.PureComponent {
1190
1231
  render() {
1191
1232
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
1192
1233
  const { className, children, routeInfo, forwardedRef, ...props } = this.props;
1234
+ // Only set "ion-page" in JSX. User classes are managed via DOM in componentDidMount/componentDidUpdate
1235
+ // to preserve framework-added classes (can-go-back, ion-page-invisible, etc.) when className prop changes.
1193
1236
  return (jsx(IonLifeCycleContext.Consumer, { children: (context) => {
1194
1237
  this.ionLifeCycleContext = context;
1195
- return (jsx("div", { className: className ? `${className} ion-page` : `ion-page`, ref: this.stableMergedRefs, ...props, children: children }));
1238
+ return (jsx("div", { className: "ion-page", ref: this.stableMergedRefs, ...props, children: children }));
1196
1239
  } }));
1197
1240
  }
1198
1241
  static get contextType() {
@@ -1331,10 +1374,10 @@ class OutletPageManager extends React.Component {
1331
1374
  this.ionLifeCycleContext.ionViewDidLeave();
1332
1375
  }
1333
1376
  render() {
1334
- const { StackManager, children, routeInfo, ...props } = this.props;
1377
+ const { StackManager, children, routeInfo, id, ...props } = this.props;
1335
1378
  return (jsx(IonLifeCycleContext.Consumer, { children: (context) => {
1336
1379
  this.ionLifeCycleContext = context;
1337
- return (jsx(StackManager, { routeInfo: routeInfo, children: jsx(IonRouterOutletInner, { setRef: (val) => (this.ionRouterOutlet = val), ...props, children: children }) }));
1380
+ return (jsx(StackManager, { id: id, routeInfo: routeInfo, children: jsx(IonRouterOutletInner, { id: id, setRef: (val) => (this.ionRouterOutlet = val), ...props, children: children }) }));
1338
1381
  } }));
1339
1382
  }
1340
1383
  static get contextType() {
@@ -1345,11 +1388,13 @@ class OutletPageManager extends React.Component {
1345
1388
  class IonRouterOutletContainer extends React.Component {
1346
1389
  constructor(props) {
1347
1390
  super(props);
1391
+ this.outletId = props.id ?? `routerOutlet-${generateId('routerOutlet')}`;
1348
1392
  }
1349
1393
  render() {
1350
1394
  const StackManager = this.context.getStackManager();
1351
1395
  const { children, forwardedRef, ...props } = this.props;
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 }));
1396
+ const outletId = props.id ?? this.outletId;
1397
+ 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 }));
1353
1398
  }
1354
1399
  static get contextType() {
1355
1400
  return NavContext;
@@ -1830,20 +1875,6 @@ class IonRoute extends React.PureComponent {
1830
1875
  }
1831
1876
  }
1832
1877
 
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
-
1847
1878
  const IonRouterContext = React.createContext({
1848
1879
  routeInfo: undefined, // TODO(FW-2959): type
1849
1880
  push: () => {
@@ -2240,6 +2271,7 @@ const RouteManagerContext = /*@__PURE__*/ React.createContext({
2240
2271
  findLeavingViewItemByRouteInfo: () => undefined,
2241
2272
  findViewItemByRouteInfo: () => undefined,
2242
2273
  getChildrenToRender: () => undefined,
2274
+ getViewItemsForOutlet: () => [],
2243
2275
  goBack: () => undefined,
2244
2276
  unMountViewItem: () => undefined,
2245
2277
  });
@@ -2358,7 +2390,14 @@ class LocationHistory {
2358
2390
  _replace(routeInfo) {
2359
2391
  const routeInfos = this._getRouteInfosByKey(routeInfo.tab);
2360
2392
  routeInfos && routeInfos.pop();
2361
- this.locationHistory.pop();
2393
+ // Get the current route that's being replaced
2394
+ const currentRoute = this.locationHistory[this.locationHistory.length - 1];
2395
+ // Only pop from global history if we're replacing in the same outlet context.
2396
+ // Don't pop if we're entering a nested outlet (current route has no tab, new route has a tab)
2397
+ const isEnteringNestedOutlet = currentRoute && !currentRoute.tab && !!routeInfo.tab;
2398
+ if (!isEnteringNestedOutlet) {
2399
+ this.locationHistory.pop();
2400
+ }
2362
2401
  this._add(routeInfo);
2363
2402
  }
2364
2403
  _clear() {
@@ -2441,10 +2480,8 @@ class NavManager extends React.PureComponent {
2441
2480
  goBack: this.goBack.bind(this),
2442
2481
  hasIonicRouter: () => true,
2443
2482
  navigate: this.navigate.bind(this),
2444
- getIonRedirect: this.getIonRedirect.bind(this),
2445
2483
  getIonRoute: this.getIonRoute.bind(this),
2446
2484
  getStackManager: this.getStackManager.bind(this),
2447
- getPageManager: this.getPageManager.bind(this),
2448
2485
  routeInfo: this.props.routeInfo,
2449
2486
  setCurrentTab: this.props.onSetCurrentTab,
2450
2487
  changeTab: this.props.onChangeTab,
@@ -2477,12 +2514,6 @@ class NavManager extends React.PureComponent {
2477
2514
  navigate(path, direction = 'forward', action = 'push', animationBuilder, options, tab) {
2478
2515
  this.props.onNavigate(path, action, direction, animationBuilder, options, tab);
2479
2516
  }
2480
- getPageManager() {
2481
- return PageManager;
2482
- }
2483
- getIonRedirect() {
2484
- return this.props.ionRedirect;
2485
- }
2486
2517
  getIonRoute() {
2487
2518
  return this.props.ionRoute;
2488
2519
  }
@@ -2544,5 +2575,5 @@ class ViewStacks {
2544
2575
  }
2545
2576
  }
2546
2577
 
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 };
2578
+ 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 };
2548
2579
  //# sourceMappingURL=index.js.map