Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 39 additions & 25 deletions core/src/components/segment/segment.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ComponentInterface, EventEmitter } 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 { raf } from '@utils/helpers';
import { isRTL } from '@utils/rtl';
import { createColorClasses, hostContext } from '@utils/theme';

Expand Down Expand Up @@ -83,31 +84,7 @@ export class Segment implements ComponentInterface {
* Used by `ion-segment-button` to determine if the button should be checked.
*/
this.ionSelect.emit({ value });

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',
});
}
}
this.scrollActiveButtonIntoView();
}

/**
Expand Down Expand Up @@ -163,6 +140,14 @@ export class Segment implements ComponentInterface {
async componentDidLoad() {
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({
el: this.el,
gestureName: 'segment',
Expand Down Expand Up @@ -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) {
const rtl = isRTL(this.el);
const activated = this.activated;
Expand Down
46 changes: 45 additions & 1 deletion core/src/components/segment/test/scrollable/segment.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';

configs().forEach(({ title, screenshot, config }) => {
test.describe(title('segment: scrollable'), () => {
test.describe(title('segment: scrollable (rendering)'), () => {
test('should not have visual regressions', async ({ page }) => {
await page.setContent(
`
Expand Down Expand Up @@ -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();
});
});
});