fix(react): fixing inline overlays being a bit eager to dismiss

This commit is contained in:
ShaneK
2025-12-02 20:15:56 -08:00
parent 99dcb356df
commit a23f55519e
10 changed files with 85 additions and 64 deletions

View File

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

View File

@@ -2,18 +2,22 @@
"name": "test-app",
"version": "0.0.1",
"private": true,
"overrides": {
"@ionic/react-router": {
"react-router": "$react-router",
"react-router-dom": "$react-router-dom"
}
},
"dependencies": {
"@ionic/react": "^6.6.1",
"@ionic/react-router": "^6.6.1",
"@types/react": "^17.0.53",
"@types/react-dom": "^17.0.19",
"@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.3.3",
"ionicons": "^8.0.13",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router": "^5.3.4",
"react-router-dom": "^5.3.4",
"react-router": "^6.0.0",
"react-router-dom": "^6.0.0",
"react-scripts": "^5.0.0",
"typescript": "^4.1.3"
},

View File

@@ -2,14 +2,20 @@
"name": "test-app",
"version": "0.0.1",
"private": true,
"overrides": {
"@ionic/react-router": {
"react-router": "$react-router",
"react-router-dom": "$react-router-dom"
}
},
"dependencies": {
"@ionic/react": "^7.0.0",
"@ionic/react-router": "^7.0.0",
"ionicons": "^8.0.13",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^5.3.4",
"react-router-dom": "^5.3.4"
"react-router": "^6.0.0",
"react-router-dom": "^6.0.0"
},
"scripts": {
"dev": "vite",
@@ -27,8 +33,6 @@
"@testing-library/user-event": "^14.4.3",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-legacy": "^4.0.2",
"@vitejs/plugin-react": "^4.0.1",
"concurrently": "^6.3.0",

View File

@@ -2,14 +2,20 @@
"name": "test-app",
"version": "0.0.1",
"private": true,
"overrides": {
"@ionic/react-router": {
"react-router": "$react-router",
"react-router-dom": "$react-router-dom"
}
},
"dependencies": {
"@ionic/react": "^8.4.0",
"@ionic/react-router": "^8.4.0",
"ionicons": "^8.0.13",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-router": "^5.3.4",
"react-router-dom": "^5.3.4"
"react-router": "^6.0.0",
"react-router-dom": "^6.0.0"
},
"scripts": {
"dev": "vite",
@@ -27,8 +33,6 @@
"@testing-library/user-event": "^14.4.3",
"@types/react": "19.0.10",
"@types/react-dom": "19.0.4",
"@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-legacy": "^4.0.2",
"@vitejs/plugin-react": "^4.0.1",
"concurrently": "^6.3.0",

View File

@@ -15,4 +15,4 @@ npm pack ../../../
npm pack ../../../../react-router
# Install Dependencies
npm install *.tgz --no-save
npm install *.tgz --no-save --legacy-peer-deps

View File

@@ -1,7 +1,7 @@
import { IonApp, IonRouterOutlet, setupIonicReact } from '@ionic/react';
import { IonReactRouter } from '@ionic/react-router';
import React from 'react';
import { Route } from 'react-router-dom';
import { Route } from 'react-router';
/* Core CSS required for Ionic components to work properly */
import '@ionic/react/css/core.css';
@@ -45,32 +45,32 @@ const App: React.FC = () => (
<IonApp>
<IonReactRouter>
<IonRouterOutlet>
<Route exact path="/" component={Main} />
<Route path="/overlay-hooks" component={OverlayHooks} />
<Route path="/overlay-components" component={OverlayComponents} />
<Route path="/overlay-components/nested-popover" component={IonPopoverNested} />
<Route path="/" element={<Main />} />
<Route path="/overlay-hooks/*" element={<OverlayHooks />} />
<Route path="/overlay-components/*" element={<OverlayComponents />} />
<Route path="/overlay-components/nested-popover" element={<IonPopoverNested />} />
<Route
path="/overlay-components/modal-conditional-sibling"
component={IonModalConditionalSibling}
element={<IonModalConditionalSibling />}
/>
<Route path="/overlay-components/modal-conditional" component={IonModalConditional} />
<Route path="/overlay-components/modal-conditional" element={<IonModalConditional />} />
<Route
path="/overlay-components/modal-datetime-button"
component={IonModalDatetimeButton}
element={<IonModalDatetimeButton />}
/>
<Route
path="/overlay-components/modal-multiple-children"
component={IonModalMultipleChildren}
element={<IonModalMultipleChildren />}
/>
<Route path="/keep-contents-mounted" component={KeepContentsMounted} />
<Route path="/navigation" component={NavComponent} />
<Route path="/tabs" component={Tabs} />
<Route path="/tabs-basic" component={TabsBasic} />
<Route path="/tabs-direct-navigation" component={TabsDirectNavigation} />
<Route path="/icons" component={Icons} />
<Route path="/inputs" component={Inputs} />
<Route path="/reorder-group" component={ReorderGroup} />
<Route path="/accordion-group" component={AccordionGroup} />
<Route path="/keep-contents-mounted" element={<KeepContentsMounted />} />
<Route path="/navigation" element={<NavComponent />} />
<Route path="/tabs/*" element={<Tabs />} />
<Route path="/tabs-basic/*" element={<TabsBasic />} />
<Route path="/tabs-direct-navigation/*" element={<TabsDirectNavigation />} />
<Route path="/icons" element={<Icons />} />
<Route path="/inputs" element={<Inputs />} />
<Route path="/reorder-group" element={<ReorderGroup />} />
<Route path="/accordion-group" element={<AccordionGroup />} />
</IonRouterOutlet>
</IonReactRouter>
</IonApp>

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs, IonPage } from '@ionic/react';
import { Route, Redirect } from 'react-router';
import { Route, Navigate } from 'react-router';
interface TabsProps {}
@@ -9,8 +9,8 @@ const Tabs: React.FC<TabsProps> = () => {
<IonPage>
<IonTabs>
<IonRouterOutlet>
<Redirect from="/tabs" to="/tabs/tab1" exact />
<Route path="/tabs/tab1" render={() => <IonLabel>Tab 1</IonLabel>} />
<Route index element={<Navigate to="/tabs/tab1" replace />} />
<Route path="tab1" element={<IonLabel>Tab 1</IonLabel>} />
</IonRouterOutlet>
<IonTabBar slot="bottom">
<IonTabButton tab="tab1" onClick={() => window.alert('Tab was clicked')}>

View File

@@ -1,7 +1,7 @@
import { IonContent, IonHeader, IonIcon, IonLabel, IonPage, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs, IonTitle, IonToolbar } from '@ionic/react';
import { homeOutline, radioOutline, libraryOutline, searchOutline } from 'ionicons/icons';
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { Route, Navigate } from 'react-router';
const HomePage: React.FC = () => (
<IonPage>
@@ -59,11 +59,11 @@ const TabsDirectNavigation: React.FC = () => {
return (
<IonTabs data-testid="tabs-direct-navigation">
<IonRouterOutlet>
<Redirect exact path="/tabs-direct-navigation" to="/tabs-direct-navigation/home" />
<Route path="/tabs-direct-navigation/home" render={() => <HomePage />} exact={true} />
<Route path="/tabs-direct-navigation/radio" render={() => <RadioPage />} exact={true} />
<Route path="/tabs-direct-navigation/library" render={() => <LibraryPage />} exact={true} />
<Route path="/tabs-direct-navigation/search" render={() => <SearchPage />} exact={true} />
<Route index element={<Navigate to="/tabs-direct-navigation/home" replace />} />
<Route path="home" element={<HomePage />} />
<Route path="radio" element={<RadioPage />} />
<Route path="library" element={<LibraryPage />} />
<Route path="search" element={<SearchPage />} />
</IonRouterOutlet>
<IonTabBar slot="bottom" data-testid="tab-bar">

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
import { Route, Redirect } from 'react-router';
import { Route, Navigate } from 'react-router';
import {
addCircleOutline,
alarm,
@@ -26,16 +26,16 @@ const OverlayHooks: React.FC<OverlayHooksProps> = () => {
return (
<IonTabs>
<IonRouterOutlet>
<Redirect from="/overlay-components" to="/overlay-components/actionsheet" exact />
<Route path="/overlay-components/actionsheet" component={ActionSheetComponent} />
<Route path="/overlay-components/alert" component={AlertComponent} />
<Route path="/overlay-components/loading" component={LoadingComponent} />
<Route path="/overlay-components/modal-basic" component={ModalComponent} />
<Route path="/overlay-components/modal-focus-trap" component={ModalFocusTrap} />
<Route path="/overlay-components/modal-teleport" component={ModalTeleport} />
<Route path="/overlay-components/picker" component={PickerComponent} />
<Route path="/overlay-components/popover" component={PopoverComponent} />
<Route path="/overlay-components/toast" component={ToastComponent} />
<Route index element={<Navigate to="/overlay-components/actionsheet" replace />} />
<Route path="actionsheet" element={<ActionSheetComponent />} />
<Route path="alert" element={<AlertComponent />} />
<Route path="loading" element={<LoadingComponent />} />
<Route path="modal-basic" element={<ModalComponent />} />
<Route path="modal-focus-trap" element={<ModalFocusTrap />} />
<Route path="modal-teleport" element={<ModalTeleport />} />
<Route path="picker" element={<PickerComponent />} />
<Route path="popover" element={<PopoverComponent />} />
<Route path="toast" element={<ToastComponent />} />
</IonRouterOutlet>
<IonTabBar slot="bottom">
<IonTabButton tab="actionsheet" href="/overlay-components/actionsheet">

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
import { Route, Redirect } from 'react-router';
import { Route, Navigate } from 'react-router';
import ActionSheetHook from './ActionSheetHook';
import {
addCircleOutline,
@@ -24,14 +24,14 @@ const OverlayHooks: React.FC<OverlayHooksProps> = () => {
return (
<IonTabs>
<IonRouterOutlet>
<Redirect from="/overlay-hooks" to="/overlay-hooks/actionsheet" exact />
<Route path="/overlay-hooks/actionsheet" component={ActionSheetHook} />
<Route path="/overlay-hooks/alert" component={AlertHook} />
<Route path="/overlay-hooks/loading" component={LoadingHook} />
<Route path="/overlay-hooks/modal" component={ModalHook} />
<Route path="/overlay-hooks/picker" component={PickerHook} />
<Route path="/overlay-hooks/popover" component={PopoverHook} />
<Route path="/overlay-hooks/toast" component={ToastHook} />
<Route index element={<Navigate to="/overlay-hooks/actionsheet" replace />} />
<Route path="actionsheet" element={<ActionSheetHook />} />
<Route path="alert" element={<AlertHook />} />
<Route path="loading" element={<LoadingHook />} />
<Route path="modal" element={<ModalHook />} />
<Route path="picker" element={<PickerHook />} />
<Route path="popover" element={<PopoverHook />} />
<Route path="toast" element={<ToastHook />} />
</IonRouterOutlet>
<IonTabBar slot="bottom">
<IonTabButton tab="actionsheet" href="/overlay-hooks/actionsheet">