fix(segment-view): scroll to correct content when height is not set (#30547)

Issue number: resolves #30543

---------

## What is the current behavior?
On desktop Safari and Android the segment view is not switching to the
right segment content when the height is dynamically set or read in as
0. This can be reproduced in the following StackBlitz on Android:
https://6dhropnr-aheyyprl.stackblitz.io/

## What is the new behavior?
- Sets `min-height` to `1px` on `ion-segment-content` so that it
continues to work with a dynamically set height
- Adds an e2e test for Safari to verify the correct content is displayed
when it contains a dynamically loaded image

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

## Other information

Dev build: `8.6.2-dev.11752524287.1d61cd78`


[Preview](https://ionic-framework-git-fw-6586-ionic1.vercel.app/src/components/segment-view/test/dynamic-height)

---------

Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
This commit is contained in:
Brandy Smith
2025-07-16 12:06:37 -04:00
committed by GitHub
parent 68ad860673
commit d14311fb65
3 changed files with 166 additions and 0 deletions

View File

@ -9,6 +9,12 @@
width: 100%;
// Workaround for a Safari/WebKit bug where flexbox children with dynamic
// height (e.g., height: fit-content) are not included in the scrollable area
// when using horizontal scrolling. This is needed to make the segment view
// scroll to the correct content.
min-height: 1px;
overflow-y: scroll;
/* Hide scrollbar in Firefox */

View File

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Segment View - Dynamic Height</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<style>
ion-segment-content {
display: flex;
align-items: center;
justify-content: center;
height: fit-content;
}
ion-segment-content:nth-of-type(1) {
background: lightpink;
}
ion-segment-content:nth-of-type(2) {
background: lightblue;
}
ion-segment-content:nth-of-type(3) {
background: lightgreen;
}
</style>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Segment View - Dynamic Height</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-segment>
<ion-segment-button value="first" content-id="first">
<ion-label>First</ion-label>
</ion-segment-button>
<ion-segment-button value="second" content-id="second">
<ion-label>Second</ion-label>
</ion-segment-button>
<ion-segment-button value="third" content-id="third">
<ion-label>Third</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view>
<ion-segment-content id="first">
Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro. De carne lumbering animata corpora
quaeritis. Summus brains sit, morbo vel maleficia? De apocalypsi gorger omero undead survivor dictum mauris.
Hi mindless mortuis soulless creaturas, imo evil stalking monstra adventus resi dentevil vultus comedat
cerebella viventium. Qui animated corpse, cricket bat max brucks terribilem incessu zomby. The voodoo
sacerdos flesh eater, suscitat mortuos comedere carnem virus. Zonbi tattered for solum oculi eorum defunctis
go lum cerebro. Nescio brains an Undead zombies. Sicut malus putrid voodoo horror. Nigh tofth eliv
</ion-segment-content>
<ion-segment-content id="second">
<ion-input value="" label="Email"></ion-input>
</ion-segment-content>
<ion-segment-content id="third">
<ion-img class="img-part" src="https://picsum.photos/200/300"></ion-img>
</ion-segment-content>
</ion-segment-view>
</ion-content>
</ion-app>
</body>
</html>

View File

@ -0,0 +1,85 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
/**
* This behavior does not vary across modes/directions
*/
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('segment-view: dynamic height'), () => {
test('should show the third content when clicking the third button', async ({ page, skip }) => {
// Skip this test on Chrome and Firefox
skip.browser('firefox', 'Original issue only happens on Safari.');
skip.browser('chromium', 'Original issue only happens on Safari.');
await page.setContent(
`
<style>
ion-segment-content {
display: flex;
align-items: center;
justify-content: center;
height: fit-content;
}
</style>
<ion-segment>
<ion-segment-button value="first" content-id="first">
<ion-label>First</ion-label>
</ion-segment-button>
<ion-segment-button value="second" content-id="second">
<ion-label>Second</ion-label>
</ion-segment-button>
<ion-segment-button value="third" content-id="third">
<ion-label>Third</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view>
<ion-segment-content id="first">
Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro. De carne lumbering animata corpora
quaeritis. Summus brains sit, morbo vel maleficia? De apocalypsi gorger omero undead survivor dictum
mauris. Hi mindless mortuis soulless creaturas, imo evil stalking monstra adventus resi dentevil vultus
comedat cerebella viventium. Qui animated corpse, cricket bat max brucks terribilem incessu zomby. The
voodoo sacerdos flesh eater, suscitat mortuos comedere carnem virus. Zonbi tattered for solum oculi eorum
defunctis go lum cerebro. Nescio brains an Undead zombies. Sicut malus putrid voodoo horror. Nigh tofth eliv
</ion-segment-content>
<ion-segment-content id="second">
<ion-input value="" label="Email"></ion-input>
</ion-segment-content>
<ion-segment-content id="third">
<ion-img class="img-part" src="https://picsum.photos/200/300"></ion-img>
</ion-segment-content>
</ion-segment-view>
`,
config
);
// Click the third button
await page.locator('ion-segment-button[value="third"]').click();
// Wait for the content to be scrolled
await page.waitForChanges();
// Wait for the image to load and be visible
const imgLocator = page.locator('ion-segment-content#third ion-img');
await imgLocator.waitFor({ state: 'visible', timeout: 10000 });
// Wait for any layout adjustments
await page.waitForChanges();
// Check that the third content is visible
const segmentView = page.locator('ion-segment-view');
const thirdContent = page.locator('ion-segment-content#third');
const viewBox = await segmentView.boundingBox();
const contentBox = await thirdContent.boundingBox();
if (!viewBox || !contentBox) throw new Error('Bounding box not found');
// Allow a small tolerance to account for subpixel rendering,
// scrollbars, or layout rounding differences
const tolerance = 10;
expect(contentBox.x).toBeGreaterThanOrEqual(viewBox.x);
expect(contentBox.x + contentBox.width).toBeLessThanOrEqual(viewBox.x + viewBox.width + tolerance);
});
});
});