mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-19 03:32:21 +08:00
fix(segment): scroll to active segment-button on first load (#28276)
Issue number: resolves #28096 --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> When a segment button is clicked, the segment will auto-scroll to position the newly active button fully in view. However, this behavior does not occur on first load. This means that when a segment is initialized with a `value` corresponding to an off-screen button, the button will remain off-screen. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> The same auto-scroll behavior from button click now also occurs on component load. ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. -->
This commit is contained in:
@ -1,6 +1,7 @@
|
|||||||
import type { ComponentInterface, EventEmitter } from '@stencil/core';
|
import type { ComponentInterface, EventEmitter } from '@stencil/core';
|
||||||
import { Component, Element, Event, Host, Listen, Prop, State, Watch, h, writeTask } from '@stencil/core';
|
import { Component, Element, Event, Host, Listen, Prop, State, Watch, h, writeTask } from '@stencil/core';
|
||||||
import type { Gesture, GestureDetail } from '@utils/gesture';
|
import type { Gesture, GestureDetail } from '@utils/gesture';
|
||||||
|
import { raf } from '@utils/helpers';
|
||||||
import { isRTL } from '@utils/rtl';
|
import { isRTL } from '@utils/rtl';
|
||||||
import { createColorClasses, hostContext } from '@utils/theme';
|
import { createColorClasses, hostContext } from '@utils/theme';
|
||||||
|
|
||||||
@ -83,31 +84,7 @@ export class Segment implements ComponentInterface {
|
|||||||
* Used by `ion-segment-button` to determine if the button should be checked.
|
* Used by `ion-segment-button` to determine if the button should be checked.
|
||||||
*/
|
*/
|
||||||
this.ionSelect.emit({ value });
|
this.ionSelect.emit({ value });
|
||||||
|
this.scrollActiveButtonIntoView();
|
||||||
if (this.scrollable) {
|
|
||||||
const buttons = this.getButtons();
|
|
||||||
const activeButton = buttons.find((button) => button.value === value);
|
|
||||||
if (activeButton !== undefined) {
|
|
||||||
/**
|
|
||||||
* Scrollable segment buttons should be
|
|
||||||
* centered within the view including
|
|
||||||
* buttons that are partially offscreen.
|
|
||||||
*/
|
|
||||||
activeButton.scrollIntoView({
|
|
||||||
behavior: 'smooth',
|
|
||||||
inline: 'center',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Segment should scroll on the
|
|
||||||
* horizontal axis. `block: 'nearest'`
|
|
||||||
* ensures that the vertical axis
|
|
||||||
* does not scroll if the segment
|
|
||||||
* as a whole is already in view.
|
|
||||||
*/
|
|
||||||
block: 'nearest',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -163,6 +140,14 @@ export class Segment implements ComponentInterface {
|
|||||||
async componentDidLoad() {
|
async componentDidLoad() {
|
||||||
this.setCheckedClasses();
|
this.setCheckedClasses();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We need to wait for the buttons to all be rendered
|
||||||
|
* before we can scroll.
|
||||||
|
*/
|
||||||
|
raf(() => {
|
||||||
|
this.scrollActiveButtonIntoView();
|
||||||
|
});
|
||||||
|
|
||||||
this.gesture = (await import('../../utils/gesture')).createGesture({
|
this.gesture = (await import('../../utils/gesture')).createGesture({
|
||||||
el: this.el,
|
el: this.el,
|
||||||
gestureName: 'segment',
|
gestureName: 'segment',
|
||||||
@ -320,6 +305,35 @@ export class Segment implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private scrollActiveButtonIntoView() {
|
||||||
|
const { scrollable, value } = this;
|
||||||
|
|
||||||
|
if (scrollable) {
|
||||||
|
const buttons = this.getButtons();
|
||||||
|
const activeButton = buttons.find((button) => button.value === value);
|
||||||
|
if (activeButton !== undefined) {
|
||||||
|
/**
|
||||||
|
* Scrollable segment buttons should be
|
||||||
|
* centered within the view including
|
||||||
|
* buttons that are partially offscreen.
|
||||||
|
*/
|
||||||
|
activeButton.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
inline: 'center',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Segment should scroll on the
|
||||||
|
* horizontal axis. `block: 'nearest'`
|
||||||
|
* ensures that the vertical axis
|
||||||
|
* does not scroll if the segment
|
||||||
|
* as a whole is already in view.
|
||||||
|
*/
|
||||||
|
block: 'nearest',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private setNextIndex(detail: GestureDetail, isEnd = false) {
|
private setNextIndex(detail: GestureDetail, isEnd = false) {
|
||||||
const rtl = isRTL(this.el);
|
const rtl = isRTL(this.el);
|
||||||
const activated = this.activated;
|
const activated = this.activated;
|
||||||
|
@ -2,7 +2,7 @@ import { expect } from '@playwright/test';
|
|||||||
import { configs, test } from '@utils/test/playwright';
|
import { configs, test } from '@utils/test/playwright';
|
||||||
|
|
||||||
configs().forEach(({ title, screenshot, config }) => {
|
configs().forEach(({ title, screenshot, config }) => {
|
||||||
test.describe(title('segment: scrollable'), () => {
|
test.describe(title('segment: scrollable (rendering)'), () => {
|
||||||
test('should not have visual regressions', async ({ page }) => {
|
test('should not have visual regressions', async ({ page }) => {
|
||||||
await page.setContent(
|
await page.setContent(
|
||||||
`
|
`
|
||||||
@ -45,3 +45,47 @@ configs().forEach(({ title, screenshot, config }) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||||
|
test.describe(title('segment: scrollable (functionality)'), () => {
|
||||||
|
test('should scroll active button into view when value is already set', async ({ page }) => {
|
||||||
|
await page.setContent(
|
||||||
|
`
|
||||||
|
<ion-segment scrollable="true" value="8">
|
||||||
|
<ion-segment-button value="1">
|
||||||
|
<ion-label>First</ion-label>
|
||||||
|
</ion-segment-button>
|
||||||
|
<ion-segment-button value="2">
|
||||||
|
<ion-label>Second</ion-label>
|
||||||
|
</ion-segment-button>
|
||||||
|
<ion-segment-button value="3">
|
||||||
|
<ion-label>Third</ion-label>
|
||||||
|
</ion-segment-button>
|
||||||
|
<ion-segment-button value="4">
|
||||||
|
<ion-label>Fourth</ion-label>
|
||||||
|
</ion-segment-button>
|
||||||
|
<ion-segment-button value="5">
|
||||||
|
<ion-label>Fifth</ion-label>
|
||||||
|
</ion-segment-button>
|
||||||
|
<ion-segment-button value="6">
|
||||||
|
<ion-label>Sixth</ion-label>
|
||||||
|
</ion-segment-button>
|
||||||
|
<ion-segment-button value="7">
|
||||||
|
<ion-label>Seventh</ion-label>
|
||||||
|
</ion-segment-button>
|
||||||
|
<ion-segment-button id="activeButton" value="8">
|
||||||
|
<ion-label>Eighth</ion-label>
|
||||||
|
</ion-segment-button>
|
||||||
|
<ion-segment-button value="9">
|
||||||
|
<ion-label>Ninth</ion-label>
|
||||||
|
</ion-segment-button>
|
||||||
|
</ion-segment>
|
||||||
|
`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
|
||||||
|
const activeButton = page.locator('#activeButton');
|
||||||
|
await expect(activeButton).toBeInViewport();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Reference in New Issue
Block a user