mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(modal): attempting to fix scenarios where the modal would become fully constrained
This commit is contained in:
@@ -158,12 +158,17 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation =>
|
||||
if (bottomValue !== undefined) {
|
||||
contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
|
||||
/**
|
||||
* When both top and bottom are set, we need to override the
|
||||
* height: var(--height) style to allow the top/bottom constraint
|
||||
* to determine the height. Setting height to 'auto' with both
|
||||
* top and bottom defined would cause bottom to be ignored.
|
||||
* When both top and bottom are explicitly constrained (isFullyConstrained),
|
||||
* we need to override the height: var(--height) style to allow the
|
||||
* top/bottom constraint to determine the height.
|
||||
*
|
||||
* We only do this when fully constrained because setting height: unset
|
||||
* when only bottom is set (without explicit top) would result in an
|
||||
* incorrectly sized popover.
|
||||
*/
|
||||
contentEl.style.setProperty('height', 'unset');
|
||||
if (isFullyConstrained) {
|
||||
contentEl.style.setProperty('height', 'unset');
|
||||
}
|
||||
}
|
||||
|
||||
contentEl.style.setProperty('top', `calc(${topValue} + var(--offset-y, 0))`);
|
||||
|
||||
@@ -65,6 +65,7 @@ export const mdEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation =>
|
||||
checkSafeAreaBottom,
|
||||
checkSafeAreaLeft,
|
||||
checkSafeAreaRight,
|
||||
isFullyConstrained,
|
||||
} = calculateWindowAdjustment(
|
||||
side,
|
||||
results.top,
|
||||
@@ -135,12 +136,17 @@ export const mdEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation =>
|
||||
if (bottomValue !== undefined) {
|
||||
contentEl.style.setProperty('bottom', `calc(${bottomValue})`);
|
||||
/**
|
||||
* When both top and bottom are set, we need to override the
|
||||
* height: var(--height) style to allow the top/bottom constraint
|
||||
* to determine the height. Setting height to 'auto' with both
|
||||
* top and bottom defined would cause bottom to be ignored.
|
||||
* When both top and bottom are explicitly constrained (isFullyConstrained),
|
||||
* we need to override the height: var(--height) style to allow the
|
||||
* top/bottom constraint to determine the height.
|
||||
*
|
||||
* We only do this when fully constrained because setting height: unset
|
||||
* when only bottom is set (without explicit top) would result in an
|
||||
* incorrectly sized popover.
|
||||
*/
|
||||
contentEl.style.setProperty('height', 'unset');
|
||||
if (isFullyConstrained) {
|
||||
contentEl.style.setProperty('height', 'unset');
|
||||
}
|
||||
}
|
||||
})
|
||||
.fromTo('transform', 'scale(0.8)', 'scale(1)');
|
||||
|
||||
@@ -25,17 +25,25 @@ configs({ modes: ['ios', 'md'], directions: ['ltr'] }).forEach(({ title, config
|
||||
description: 'https://github.com/ionic-team/ionic-framework/issues/30900',
|
||||
});
|
||||
|
||||
// Use a smaller viewport to force the popover to be constrained
|
||||
await page.setViewportSize({ width: 375, height: 500 });
|
||||
/**
|
||||
* Use a small viewport to force the popover to be fully constrained.
|
||||
* The large popover has 15 items (~700px), which will exceed the available
|
||||
* space in this viewport, causing it to be constrained with both top and
|
||||
* bottom edges near the safe areas.
|
||||
*
|
||||
* A 300px viewport ensures there's not enough space above OR below the
|
||||
* trigger for the full popover content, triggering the fully constrained path.
|
||||
*/
|
||||
await page.setViewportSize({ width: 375, height: 300 });
|
||||
|
||||
const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent');
|
||||
|
||||
// Click the trigger near the bottom of the screen
|
||||
await page.click('#bottom-trigger');
|
||||
// Click the large popover trigger which has enough content to extend toward the bottom
|
||||
await page.click('#large-popover-trigger');
|
||||
await ionPopoverDidPresent.next();
|
||||
|
||||
// Target the specific popover that was presented (the one with trigger="bottom-trigger")
|
||||
const popover = page.locator('ion-popover[trigger="bottom-trigger"]');
|
||||
// Target the specific popover that was presented
|
||||
const popover = page.locator('ion-popover[trigger="large-popover-trigger"]');
|
||||
const popoverContent = popover.locator('.popover-content');
|
||||
|
||||
// Get the computed bottom style - should include safe-area calc
|
||||
|
||||
@@ -884,7 +884,19 @@ export const calculateWindowAdjustment = (
|
||||
* margins.
|
||||
*/
|
||||
if (triggerTop + triggerHeight + contentHeight > bodyHeight && (side === 'top' || side === 'bottom')) {
|
||||
if (triggerTop - contentHeight > 0) {
|
||||
/**
|
||||
* Calculate available space above and below, accounting for safe areas.
|
||||
* This ensures we flip to whichever side has more usable space.
|
||||
*/
|
||||
const spaceAbove = (triggerCoordinates?.top ?? triggerTop) - bodyPadding - safeAreaMargin;
|
||||
const spaceBelow = bodyHeight - triggerTop - triggerHeight - bodyPadding - safeAreaMargin;
|
||||
|
||||
/**
|
||||
* Flip above if:
|
||||
* 1. Content fits entirely above the trigger, OR
|
||||
* 2. There's more usable space above than below (accounting for safe areas)
|
||||
*/
|
||||
if (triggerTop - contentHeight > 0 || spaceAbove > spaceBelow) {
|
||||
/**
|
||||
* While we strive to align the popover with the trigger
|
||||
* on smaller screens this is not always possible. As a result,
|
||||
@@ -910,25 +922,27 @@ export const calculateWindowAdjustment = (
|
||||
}
|
||||
|
||||
/**
|
||||
* After flipping above, check if popover still extends into bottom safe area.
|
||||
* This can happen when the popover is taller than the available space between
|
||||
* the top safe area and the trigger. In this case, constrain with bottom too.
|
||||
* After flipping above, check if popover will likely overflow the viewport.
|
||||
* This can happen when the popover is taller than the available space.
|
||||
*
|
||||
* We estimate the effective top by adding safeAreaMargin if checkSafeAreaTop
|
||||
* is true (since CSS will add the actual safe-area-top value).
|
||||
* When checkSafeAreaTop is true, the CSS will add safe-area-top to the
|
||||
* top position, pushing the popover down. Since we don't know the exact
|
||||
* CSS safe-area value, we use a threshold that accounts for likely
|
||||
* safe-area sizes. This only triggers when:
|
||||
* 1. We're already applying safe-area-top (checkSafeAreaTop), and
|
||||
* 2. The popover is close enough to overflowing that any safe-area
|
||||
* would push it past the viewport
|
||||
*/
|
||||
const estimatedTop = checkSafeAreaTop ? top + safeAreaMargin : top;
|
||||
if (estimatedTop + contentHeight > bodyHeight - safeAreaMargin) {
|
||||
if (checkSafeAreaTop && top + contentHeight > bodyHeight - safeAreaMargin - bodyPadding) {
|
||||
bottom = bodyPadding;
|
||||
checkSafeAreaBottom = true;
|
||||
isFullyConstrained = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If not enough room for popover to appear
|
||||
* above trigger, constrain to full viewport.
|
||||
* Pin both top and bottom to maximize visible area
|
||||
* and let the content scroll within those bounds.
|
||||
* If not enough room for popover to appear above trigger
|
||||
* (i.e., content is taller than space above), then constrain
|
||||
* the popover to fill the entire viewport from top to bottom.
|
||||
*/
|
||||
} else {
|
||||
top = bodyPadding;
|
||||
@@ -940,19 +954,19 @@ export const calculateWindowAdjustment = (
|
||||
}
|
||||
|
||||
/**
|
||||
* Final check: If the popover extends into any safe-area region,
|
||||
* constrain it to avoid overlapping system UI.
|
||||
* This handles cases where a side-positioned popover (left/right)
|
||||
* or a bottom-positioned popover extends into the safe area.
|
||||
* Check if popover is near edges and needs safe-area adjustments.
|
||||
* When the popover extends into the safe-area zone, set a bottom constraint
|
||||
* to push it up and out of the unsafe area. This is essential for
|
||||
* edge-to-edge displays on Android API 36+ and iOS devices with home indicators.
|
||||
*/
|
||||
const popoverBottom = bottom !== undefined ? bodyHeight - bottom : top + contentHeight;
|
||||
if (popoverBottom + safeAreaMargin > bodyHeight && bottom === undefined) {
|
||||
if (popoverBottom > bodyHeight - safeAreaMargin && bottom === undefined) {
|
||||
checkSafeAreaBottom = true;
|
||||
/**
|
||||
* Popover extends into bottom safe area but isn't already constrained.
|
||||
* Set bottom to constrain the popover and apply safe-area adjustment.
|
||||
* Set a bottom constraint to push the popover up out of the safe-area zone.
|
||||
* The animation will add the safe-area CSS variable to this value.
|
||||
*/
|
||||
bottom = bodyPadding;
|
||||
checkSafeAreaBottom = true;
|
||||
}
|
||||
if (top < safeAreaMargin) {
|
||||
checkSafeAreaTop = true;
|
||||
|
||||
Reference in New Issue
Block a user