Skip to content

Commit a23f555

Browse files
committed
fix(react): fixing inline overlays being a bit eager to dismiss
1 parent 99dcb35 commit a23f555

File tree

10 files changed

+85
-64
lines changed

10 files changed

+85
-64
lines changed

packages/react/src/components/createInlineOverlayComponent.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@ export const createInlineOverlayComponent = <PropType, ElementType>(
7171

7272
setupPageVisibilityObserver() {
7373
/**
74-
* Watch for when ANY element in the document gets the ion-page-hidden class.
74+
* Watch for when ancestor pages get the ion-page-hidden class.
7575
* We use a subtree observer on a parent container because:
7676
* 1. The overlay's component might not have an IonPage wrapper
7777
* 2. Pages might be added dynamically after this component mounts
78-
* 3. We want to dismiss overlays when ANY navigation occurs
78+
* 3. We want to dismiss overlays when navigation hides the containing page
7979
*
8080
* This handles React Router 6 where pages stay mounted but get hidden.
8181
*/
@@ -87,8 +87,17 @@ export const createInlineOverlayComponent = <PropType, ElementType>(
8787
const target = mutation.target as HTMLElement;
8888
// If any element gets the ion-page-hidden or ion-page-invisible class, dismiss overlay
8989
if (target.classList.contains('ion-page-hidden') || target.classList.contains('ion-page-invisible')) {
90-
this.dismissOverlay();
91-
return;
90+
/**
91+
* Only dismiss the overlay if the hidden page is an ancestor of the overlay,
92+
* not a descendant. Pages inside the overlay (e.g., IonPage in modal content)
93+
* may get ion-page-invisible when they mount inside an outlet context, but
94+
* this should not dismiss the overlay.
95+
*/
96+
const overlayElement = this.ref.current;
97+
if (overlayElement && !overlayElement.contains(target)) {
98+
this.dismissOverlay();
99+
return;
100+
}
92101
}
93102
}
94103
}

packages/react/test/apps/react17/package.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,22 @@
22
"name": "test-app",
33
"version": "0.0.1",
44
"private": true,
5+
"overrides": {
6+
"@ionic/react-router": {
7+
"react-router": "$react-router",
8+
"react-router-dom": "$react-router-dom"
9+
}
10+
},
511
"dependencies": {
612
"@ionic/react": "^6.6.1",
713
"@ionic/react-router": "^6.6.1",
814
"@types/react": "^17.0.53",
915
"@types/react-dom": "^17.0.19",
10-
"@types/react-router": "^5.1.20",
11-
"@types/react-router-dom": "^5.3.3",
1216
"ionicons": "^8.0.13",
1317
"react": "^17.0.2",
1418
"react-dom": "^17.0.2",
15-
"react-router": "^5.3.4",
16-
"react-router-dom": "^5.3.4",
19+
"react-router": "^6.0.0",
20+
"react-router-dom": "^6.0.0",
1721
"react-scripts": "^5.0.0",
1822
"typescript": "^4.1.3"
1923
},

packages/react/test/apps/react18/package.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@
22
"name": "test-app",
33
"version": "0.0.1",
44
"private": true,
5+
"overrides": {
6+
"@ionic/react-router": {
7+
"react-router": "$react-router",
8+
"react-router-dom": "$react-router-dom"
9+
}
10+
},
511
"dependencies": {
612
"@ionic/react": "^7.0.0",
713
"@ionic/react-router": "^7.0.0",
814
"ionicons": "^8.0.13",
915
"react": "^18.2.0",
1016
"react-dom": "^18.2.0",
11-
"react-router": "^5.3.4",
12-
"react-router-dom": "^5.3.4"
17+
"react-router": "^6.0.0",
18+
"react-router-dom": "^6.0.0"
1319
},
1420
"scripts": {
1521
"dev": "vite",
@@ -27,8 +33,6 @@
2733
"@testing-library/user-event": "^14.4.3",
2834
"@types/react": "^18.0.27",
2935
"@types/react-dom": "^18.0.10",
30-
"@types/react-router": "^5.1.20",
31-
"@types/react-router-dom": "^5.3.3",
3236
"@vitejs/plugin-legacy": "^4.0.2",
3337
"@vitejs/plugin-react": "^4.0.1",
3438
"concurrently": "^6.3.0",

packages/react/test/apps/react19/package.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@
22
"name": "test-app",
33
"version": "0.0.1",
44
"private": true,
5+
"overrides": {
6+
"@ionic/react-router": {
7+
"react-router": "$react-router",
8+
"react-router-dom": "$react-router-dom"
9+
}
10+
},
511
"dependencies": {
612
"@ionic/react": "^8.4.0",
713
"@ionic/react-router": "^8.4.0",
814
"ionicons": "^8.0.13",
915
"react": "19.0.0",
1016
"react-dom": "19.0.0",
11-
"react-router": "^5.3.4",
12-
"react-router-dom": "^5.3.4"
17+
"react-router": "^6.0.0",
18+
"react-router-dom": "^6.0.0"
1319
},
1420
"scripts": {
1521
"dev": "vite",
@@ -27,8 +33,6 @@
2733
"@testing-library/user-event": "^14.4.3",
2834
"@types/react": "19.0.10",
2935
"@types/react-dom": "19.0.4",
30-
"@types/react-router": "^5.1.20",
31-
"@types/react-router-dom": "^5.3.3",
3236
"@vitejs/plugin-legacy": "^4.0.2",
3337
"@vitejs/plugin-react": "^4.0.1",
3438
"concurrently": "^6.3.0",

packages/react/test/base/scripts/sync.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ npm pack ../../../
1515
npm pack ../../../../react-router
1616

1717
# Install Dependencies
18-
npm install *.tgz --no-save
18+
npm install *.tgz --no-save --legacy-peer-deps

packages/react/test/base/src/App.tsx

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { IonApp, IonRouterOutlet, setupIonicReact } from '@ionic/react';
22
import { IonReactRouter } from '@ionic/react-router';
33
import React from 'react';
4-
import { Route } from 'react-router-dom';
4+
import { Route } from 'react-router';
55

66
/* Core CSS required for Ionic components to work properly */
77
import '@ionic/react/css/core.css';
@@ -45,32 +45,32 @@ const App: React.FC = () => (
4545
<IonApp>
4646
<IonReactRouter>
4747
<IonRouterOutlet>
48-
<Route exact path="/" component={Main} />
49-
<Route path="/overlay-hooks" component={OverlayHooks} />
50-
<Route path="/overlay-components" component={OverlayComponents} />
51-
<Route path="/overlay-components/nested-popover" component={IonPopoverNested} />
48+
<Route path="/" element={<Main />} />
49+
<Route path="/overlay-hooks/*" element={<OverlayHooks />} />
50+
<Route path="/overlay-components/*" element={<OverlayComponents />} />
51+
<Route path="/overlay-components/nested-popover" element={<IonPopoverNested />} />
5252
<Route
5353
path="/overlay-components/modal-conditional-sibling"
54-
component={IonModalConditionalSibling}
54+
element={<IonModalConditionalSibling />}
5555
/>
56-
<Route path="/overlay-components/modal-conditional" component={IonModalConditional} />
56+
<Route path="/overlay-components/modal-conditional" element={<IonModalConditional />} />
5757
<Route
5858
path="/overlay-components/modal-datetime-button"
59-
component={IonModalDatetimeButton}
59+
element={<IonModalDatetimeButton />}
6060
/>
6161
<Route
6262
path="/overlay-components/modal-multiple-children"
63-
component={IonModalMultipleChildren}
63+
element={<IonModalMultipleChildren />}
6464
/>
65-
<Route path="/keep-contents-mounted" component={KeepContentsMounted} />
66-
<Route path="/navigation" component={NavComponent} />
67-
<Route path="/tabs" component={Tabs} />
68-
<Route path="/tabs-basic" component={TabsBasic} />
69-
<Route path="/tabs-direct-navigation" component={TabsDirectNavigation} />
70-
<Route path="/icons" component={Icons} />
71-
<Route path="/inputs" component={Inputs} />
72-
<Route path="/reorder-group" component={ReorderGroup} />
73-
<Route path="/accordion-group" component={AccordionGroup} />
65+
<Route path="/keep-contents-mounted" element={<KeepContentsMounted />} />
66+
<Route path="/navigation" element={<NavComponent />} />
67+
<Route path="/tabs/*" element={<Tabs />} />
68+
<Route path="/tabs-basic/*" element={<TabsBasic />} />
69+
<Route path="/tabs-direct-navigation/*" element={<TabsDirectNavigation />} />
70+
<Route path="/icons" element={<Icons />} />
71+
<Route path="/inputs" element={<Inputs />} />
72+
<Route path="/reorder-group" element={<ReorderGroup />} />
73+
<Route path="/accordion-group" element={<AccordionGroup />} />
7474
</IonRouterOutlet>
7575
</IonReactRouter>
7676
</IonApp>

packages/react/test/base/src/pages/Tabs.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import { IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs, IonPage } from '@ionic/react';
3-
import { Route, Redirect } from 'react-router';
3+
import { Route, Navigate } from 'react-router';
44

55
interface TabsProps {}
66

@@ -9,8 +9,8 @@ const Tabs: React.FC<TabsProps> = () => {
99
<IonPage>
1010
<IonTabs>
1111
<IonRouterOutlet>
12-
<Redirect from="/tabs" to="/tabs/tab1" exact />
13-
<Route path="/tabs/tab1" render={() => <IonLabel>Tab 1</IonLabel>} />
12+
<Route index element={<Navigate to="/tabs/tab1" replace />} />
13+
<Route path="tab1" element={<IonLabel>Tab 1</IonLabel>} />
1414
</IonRouterOutlet>
1515
<IonTabBar slot="bottom">
1616
<IonTabButton tab="tab1" onClick={() => window.alert('Tab was clicked')}>

packages/react/test/base/src/pages/TabsDirectNavigation.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { IonContent, IonHeader, IonIcon, IonLabel, IonPage, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs, IonTitle, IonToolbar } from '@ionic/react';
22
import { homeOutline, radioOutline, libraryOutline, searchOutline } from 'ionicons/icons';
33
import React from 'react';
4-
import { Route, Redirect } from 'react-router-dom';
4+
import { Route, Navigate } from 'react-router';
55

66
const HomePage: React.FC = () => (
77
<IonPage>
@@ -59,11 +59,11 @@ const TabsDirectNavigation: React.FC = () => {
5959
return (
6060
<IonTabs data-testid="tabs-direct-navigation">
6161
<IonRouterOutlet>
62-
<Redirect exact path="/tabs-direct-navigation" to="/tabs-direct-navigation/home" />
63-
<Route path="/tabs-direct-navigation/home" render={() => <HomePage />} exact={true} />
64-
<Route path="/tabs-direct-navigation/radio" render={() => <RadioPage />} exact={true} />
65-
<Route path="/tabs-direct-navigation/library" render={() => <LibraryPage />} exact={true} />
66-
<Route path="/tabs-direct-navigation/search" render={() => <SearchPage />} exact={true} />
62+
<Route index element={<Navigate to="/tabs-direct-navigation/home" replace />} />
63+
<Route path="home" element={<HomePage />} />
64+
<Route path="radio" element={<RadioPage />} />
65+
<Route path="library" element={<LibraryPage />} />
66+
<Route path="search" element={<SearchPage />} />
6767
</IonRouterOutlet>
6868

6969
<IonTabBar slot="bottom" data-testid="tab-bar">

packages/react/test/base/src/pages/overlay-components/OverlayComponents.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
3-
import { Route, Redirect } from 'react-router';
3+
import { Route, Navigate } from 'react-router';
44
import {
55
addCircleOutline,
66
alarm,
@@ -26,16 +26,16 @@ const OverlayHooks: React.FC<OverlayHooksProps> = () => {
2626
return (
2727
<IonTabs>
2828
<IonRouterOutlet>
29-
<Redirect from="/overlay-components" to="/overlay-components/actionsheet" exact />
30-
<Route path="/overlay-components/actionsheet" component={ActionSheetComponent} />
31-
<Route path="/overlay-components/alert" component={AlertComponent} />
32-
<Route path="/overlay-components/loading" component={LoadingComponent} />
33-
<Route path="/overlay-components/modal-basic" component={ModalComponent} />
34-
<Route path="/overlay-components/modal-focus-trap" component={ModalFocusTrap} />
35-
<Route path="/overlay-components/modal-teleport" component={ModalTeleport} />
36-
<Route path="/overlay-components/picker" component={PickerComponent} />
37-
<Route path="/overlay-components/popover" component={PopoverComponent} />
38-
<Route path="/overlay-components/toast" component={ToastComponent} />
29+
<Route index element={<Navigate to="/overlay-components/actionsheet" replace />} />
30+
<Route path="actionsheet" element={<ActionSheetComponent />} />
31+
<Route path="alert" element={<AlertComponent />} />
32+
<Route path="loading" element={<LoadingComponent />} />
33+
<Route path="modal-basic" element={<ModalComponent />} />
34+
<Route path="modal-focus-trap" element={<ModalFocusTrap />} />
35+
<Route path="modal-teleport" element={<ModalTeleport />} />
36+
<Route path="picker" element={<PickerComponent />} />
37+
<Route path="popover" element={<PopoverComponent />} />
38+
<Route path="toast" element={<ToastComponent />} />
3939
</IonRouterOutlet>
4040
<IonTabBar slot="bottom">
4141
<IonTabButton tab="actionsheet" href="/overlay-components/actionsheet">

packages/react/test/base/src/pages/overlay-hooks/OverlayHooks.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
3-
import { Route, Redirect } from 'react-router';
3+
import { Route, Navigate } from 'react-router';
44
import ActionSheetHook from './ActionSheetHook';
55
import {
66
addCircleOutline,
@@ -24,14 +24,14 @@ const OverlayHooks: React.FC<OverlayHooksProps> = () => {
2424
return (
2525
<IonTabs>
2626
<IonRouterOutlet>
27-
<Redirect from="/overlay-hooks" to="/overlay-hooks/actionsheet" exact />
28-
<Route path="/overlay-hooks/actionsheet" component={ActionSheetHook} />
29-
<Route path="/overlay-hooks/alert" component={AlertHook} />
30-
<Route path="/overlay-hooks/loading" component={LoadingHook} />
31-
<Route path="/overlay-hooks/modal" component={ModalHook} />
32-
<Route path="/overlay-hooks/picker" component={PickerHook} />
33-
<Route path="/overlay-hooks/popover" component={PopoverHook} />
34-
<Route path="/overlay-hooks/toast" component={ToastHook} />
27+
<Route index element={<Navigate to="/overlay-hooks/actionsheet" replace />} />
28+
<Route path="actionsheet" element={<ActionSheetHook />} />
29+
<Route path="alert" element={<AlertHook />} />
30+
<Route path="loading" element={<LoadingHook />} />
31+
<Route path="modal" element={<ModalHook />} />
32+
<Route path="picker" element={<PickerHook />} />
33+
<Route path="popover" element={<PopoverHook />} />
34+
<Route path="toast" element={<ToastHook />} />
3535
</IonRouterOutlet>
3636
<IonTabBar slot="bottom">
3737
<IonTabButton tab="actionsheet" href="/overlay-hooks/actionsheet">

0 commit comments

Comments
 (0)