mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(modal): fixing safe area in certain situations, adding some tests
This commit is contained in:
@@ -75,6 +75,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
@State() private isSheetModal = false;
|
||||
private currentBreakpoint?: number;
|
||||
private wrapperEl?: HTMLElement;
|
||||
private shadowEl?: HTMLElement;
|
||||
private backdropEl?: HTMLIonBackdropElement;
|
||||
private dragHandleEl?: HTMLButtonElement;
|
||||
private sortedBreakpoints?: number[];
|
||||
@@ -914,13 +915,9 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
return;
|
||||
}
|
||||
|
||||
// Phone-sized fullscreen modals inherit safe areas and use wrapper padding
|
||||
if (!isTablet) {
|
||||
this.applyFullscreenSafeArea();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if tablet modal is fullscreen via CSS custom properties
|
||||
// Check if modal is fullscreen via CSS custom properties
|
||||
// This applies to both phone and tablet sizes - custom modals may have
|
||||
// non-fullscreen dimensions even on phones (e.g., --height: 70%)
|
||||
const computedStyle = getComputedStyle(this.el);
|
||||
const width = computedStyle.getPropertyValue('--width').trim();
|
||||
const height = computedStyle.getPropertyValue('--height').trim();
|
||||
@@ -928,9 +925,12 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
|
||||
if (isFullscreen) {
|
||||
this.applyFullscreenSafeArea();
|
||||
} else {
|
||||
// Centered dialog doesn't touch edges
|
||||
} else if (isTablet) {
|
||||
// Centered dialog on tablet doesn't touch edges
|
||||
this.zeroAllSafeAreas();
|
||||
} else {
|
||||
// Non-fullscreen modal on phone - use coordinate-based detection
|
||||
// to determine which edges it touches (e.g., bottom-aligned custom modals)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -953,19 +953,27 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates wrapper padding based on footer presence.
|
||||
* Updates wrapper and shadow padding based on footer presence.
|
||||
* Called initially and when footer is dynamically added/removed.
|
||||
* Both elements must be styled identically to prevent visual mismatches.
|
||||
*/
|
||||
private updateFooterPadding() {
|
||||
if (!this.wrapperEl) return;
|
||||
|
||||
const hasFooter = this.el.querySelector('ion-footer') !== null;
|
||||
// Apply to both wrapper and shadow to keep them in sync
|
||||
const elements = [this.wrapperEl, this.shadowEl].filter(Boolean) as HTMLElement[];
|
||||
|
||||
if (hasFooter) {
|
||||
this.wrapperEl.style.removeProperty('padding-bottom');
|
||||
this.wrapperEl.style.removeProperty('box-sizing');
|
||||
elements.forEach((el) => {
|
||||
el.style.removeProperty('padding-bottom');
|
||||
el.style.removeProperty('box-sizing');
|
||||
});
|
||||
} else {
|
||||
this.wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
|
||||
this.wrapperEl.style.setProperty('box-sizing', 'border-box');
|
||||
elements.forEach((el) => {
|
||||
el.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
|
||||
el.style.setProperty('box-sizing', 'border-box');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -993,11 +1001,13 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
this.footerObserver?.disconnect();
|
||||
this.footerObserver = undefined;
|
||||
|
||||
// Clear wrapper styles that may have been set for safe-area handling
|
||||
if (this.wrapperEl) {
|
||||
this.wrapperEl.style.removeProperty('padding-bottom');
|
||||
this.wrapperEl.style.removeProperty('box-sizing');
|
||||
}
|
||||
// Clear wrapper and shadow styles that may have been set for safe-area handling
|
||||
[this.wrapperEl, this.shadowEl].forEach((el) => {
|
||||
if (el) {
|
||||
el.style.removeProperty('padding-bottom');
|
||||
el.style.removeProperty('box-sizing');
|
||||
}
|
||||
});
|
||||
|
||||
// Clear safe-area CSS variable overrides
|
||||
const style = this.el.style;
|
||||
@@ -1613,7 +1623,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
part="backdrop"
|
||||
/>
|
||||
|
||||
{mode === 'ios' && <div class="modal-shadow"></div>}
|
||||
{mode === 'ios' && <div class="modal-shadow" ref={(el) => (this.shadowEl = el)}></div>}
|
||||
|
||||
<div
|
||||
/*
|
||||
|
||||
@@ -15,23 +15,66 @@
|
||||
<style>
|
||||
/**
|
||||
* Simulate safe-area insets for testing.
|
||||
* These values represent typical iPad safe areas.
|
||||
* Values represent combined scenarios: top/bottom from portrait devices,
|
||||
* left/right from landscape orientation or devices with side notches.
|
||||
*/
|
||||
:root {
|
||||
--ion-safe-area-top: 44px;
|
||||
--ion-safe-area-bottom: 34px;
|
||||
--ion-safe-area-left: 0px;
|
||||
--ion-safe-area-right: 0px;
|
||||
--ion-safe-area-left: 44px;
|
||||
--ion-safe-area-right: 44px;
|
||||
}
|
||||
|
||||
.fullscreen-modal {
|
||||
--width: 100%;
|
||||
--height: 100%;
|
||||
}
|
||||
|
||||
/* Visual indicators for safe areas */
|
||||
.safe-area-indicator {
|
||||
position: fixed;
|
||||
background: rgba(255, 0, 0, 0.2);
|
||||
pointer-events: none;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.safe-area-top {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: var(--ion-safe-area-top);
|
||||
}
|
||||
|
||||
.safe-area-bottom {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: var(--ion-safe-area-bottom);
|
||||
}
|
||||
|
||||
.safe-area-left {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: var(--ion-safe-area-left);
|
||||
}
|
||||
|
||||
.safe-area-right {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: var(--ion-safe-area-right);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Visual indicators for safe areas (red overlay) -->
|
||||
<div class="safe-area-indicator safe-area-top"></div>
|
||||
<div class="safe-area-indicator safe-area-bottom"></div>
|
||||
<div class="safe-area-indicator safe-area-left"></div>
|
||||
<div class="safe-area-indicator safe-area-right"></div>
|
||||
|
||||
<ion-app>
|
||||
<div class="ion-page" id="main-page">
|
||||
<ion-header>
|
||||
@@ -41,7 +84,11 @@
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<p>Test safe-area handling in modals.</p>
|
||||
<p>Test safe-area handling in modals. Red overlays indicate safe areas (top, bottom, left, right).</p>
|
||||
<p>
|
||||
<strong>Landscape simulation:</strong> Left and right safe areas are set to 44px to test devices with side
|
||||
notches.
|
||||
</p>
|
||||
|
||||
<ion-list>
|
||||
<ion-item-group>
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
/**
|
||||
* Simulate safe-area insets for testing.
|
||||
* These values represent typical Android edge-to-edge safe areas.
|
||||
* Left/right values simulate landscape orientation or devices with side notches.
|
||||
*/
|
||||
:root {
|
||||
--ion-safe-area-top: 44px;
|
||||
--ion-safe-area-bottom: 34px;
|
||||
--ion-safe-area-left: 0px;
|
||||
--ion-safe-area-right: 0px;
|
||||
--ion-safe-area-left: 44px;
|
||||
--ion-safe-area-right: 44px;
|
||||
}
|
||||
|
||||
/* Visual indicator for safe areas */
|
||||
@@ -46,6 +47,20 @@
|
||||
height: var(--ion-safe-area-bottom);
|
||||
}
|
||||
|
||||
.safe-area-left {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: var(--ion-safe-area-left);
|
||||
}
|
||||
|
||||
.safe-area-right {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: var(--ion-safe-area-right);
|
||||
}
|
||||
|
||||
/* Position triggers at different locations */
|
||||
.bottom-trigger {
|
||||
position: fixed;
|
||||
@@ -67,6 +82,8 @@
|
||||
<!-- Visual indicators for safe areas -->
|
||||
<div class="safe-area-indicator safe-area-top"></div>
|
||||
<div class="safe-area-indicator safe-area-bottom"></div>
|
||||
<div class="safe-area-indicator safe-area-left"></div>
|
||||
<div class="safe-area-indicator safe-area-right"></div>
|
||||
|
||||
<div class="ion-page" id="main-page">
|
||||
<ion-header>
|
||||
@@ -77,7 +94,11 @@
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<p>Test that popovers are <strong>positioned away from</strong> unsafe areas (shown in red).</p>
|
||||
<p>The popover should be moved up/down to avoid overlapping the safe-area zones.</p>
|
||||
<p>The popover should be moved up/down/left/right to avoid overlapping the safe-area zones.</p>
|
||||
<p>
|
||||
<strong>Landscape simulation:</strong> Left and right safe areas are set to 44px to test devices with side
|
||||
notches.
|
||||
</p>
|
||||
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
|
||||
Reference in New Issue
Block a user