diff --git a/core/src/components/segment/segment.tsx b/core/src/components/segment/segment.tsx index 6aa98fa7556..c96425c8026 100644 --- a/core/src/components/segment/segment.tsx +++ b/core/src/components/segment/segment.tsx @@ -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'; @@ -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(); } /** @@ -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', @@ -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; diff --git a/core/src/components/segment/test/scrollable/segment.e2e.ts b/core/src/components/segment/test/scrollable/segment.e2e.ts index 3a21b9ac92b..1b015fcc68b 100644 --- a/core/src/components/segment/test/scrollable/segment.e2e.ts +++ b/core/src/components/segment/test/scrollable/segment.e2e.ts @@ -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( ` @@ -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( + ` + + + First + + + Second + + + Third + + + Fourth + + + Fifth + + + Sixth + + + Seventh + + + Eighth + + + Ninth + + + `, + config + ); + + const activeButton = page.locator('#activeButton'); + await expect(activeButton).toBeInViewport(); + }); + }); +});