diff --git a/core/api.txt b/core/api.txt index f69b082ad6..694a7bf843 100644 --- a/core/api.txt +++ b/core/api.txt @@ -1613,6 +1613,8 @@ ion-segment-content,prop,disabled,boolean,false,false,false ion-segment-view,shadow ion-segment-view,prop,disabled,boolean,false,false,false ion-segment-view,method,setContent,setContent(id: string, smoothScroll?: boolean) => Promise +ion-segment-view,event,ionSegmentViewScroll,{ scrollDirection: string; scrollDistance: number; },true +ion-segment-view,event,ionSegmentViewScrollEnd,void,true ion-select,shadow ion-select,prop,cancelText,string,'Cancel',false,false diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 3cd732f5d7..b7e1aca02f 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -4443,6 +4443,7 @@ declare global { }; interface HTMLIonSegmentViewElementEventMap { "ionSegmentViewScroll": { scrollDirection: string; scrollDistance: number }; + "ionSegmentViewScrollEnd": void; } interface HTMLIonSegmentViewElement extends Components.IonSegmentView, HTMLStencilElement { addEventListener(type: K, listener: (this: HTMLIonSegmentViewElement, ev: IonSegmentViewCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; @@ -7534,7 +7535,14 @@ declare namespace LocalJSX { * If `true`, the segment view cannot be interacted with. */ "disabled"?: boolean; + /** + * Emitted when the segment view is scrolled. + */ "onIonSegmentViewScroll"?: (event: IonSegmentViewCustomEvent<{ scrollDirection: string; scrollDistance: number }>) => void; + /** + * Emitted when the segment view scroll has ended. + */ + "onIonSegmentViewScrollEnd"?: (event: IonSegmentViewCustomEvent) => void; } interface IonSelect { /** diff --git a/core/src/components/segment-view/segment-view.tsx b/core/src/components/segment-view/segment-view.tsx index 822a581266..abc932b8ce 100644 --- a/core/src/components/segment-view/segment-view.tsx +++ b/core/src/components/segment-view/segment-view.tsx @@ -10,8 +10,10 @@ import { Component, Element, Event, Host, Listen, Method, Prop, h } from '@stenc shadow: true, }) export class SegmentView implements ComponentInterface { - private initialScrollLeft = 0; + private initialScrollLeft?: number; private previousScrollLeft = 0; + private scrollEndTimeout: ReturnType | null = null; + private isTouching = false; @Element() el!: HTMLElement; @@ -20,21 +22,31 @@ export class SegmentView implements ComponentInterface { */ @Prop() disabled = false; + /** + * Emitted when the segment view is scrolled. + */ @Event() ionSegmentViewScroll!: EventEmitter<{ scrollDirection: string; scrollDistance: number }>; + /** + * Emitted when the segment view scroll has ended. + */ + @Event() ionSegmentViewScrollEnd!: EventEmitter; + @Listen('scroll') handleScroll(ev: Event) { const { initialScrollLeft, previousScrollLeft } = this; const { scrollLeft, offsetWidth } = ev.target as HTMLElement; + if (initialScrollLeft === undefined) { + this.initialScrollLeft = scrollLeft; + } + const scrollDirection = scrollLeft > previousScrollLeft ? 'right' : 'left'; this.previousScrollLeft = scrollLeft; - let scrollDistance = scrollLeft; - - if (scrollDirection === 'left') { - scrollDistance = initialScrollLeft - scrollLeft; - } + // If the scroll direction is left then we need to calculate where we started and subtract + // the current scrollLeft to get the distance scrolled. Otherwise, we use the scrollLeft. + const scrollDistance = scrollDirection === 'left' ? initialScrollLeft! - scrollLeft : scrollLeft; // Emit the scroll direction and distance this.ionSegmentViewScroll.emit({ @@ -59,11 +71,54 @@ export class SegmentView implements ComponentInterface { if (segment) { segment.value = segmentButton.value; } + + this.resetScrollEndTimeout(); } + /** + * Handle touch start event to know when the user is actively dragging the segment view. + */ @Listen('touchstart') - handleTouchStart() { - this.initialScrollLeft = this.el.scrollLeft; + handleScrollStart() { + if (this.scrollEndTimeout) { + clearTimeout(this.scrollEndTimeout); + this.scrollEndTimeout = null; + } + + this.isTouching = true; + } + + /** + * Handle touch end event to know when the user is no longer dragging the segment view. + */ + @Listen('touchend') + handleTouchEnd() { + this.isTouching = false; + } + + /** + * Reset the scroll end detection timer. This is called on every scroll event. + */ + private resetScrollEndTimeout() { + if (this.scrollEndTimeout) { + clearTimeout(this.scrollEndTimeout); + this.scrollEndTimeout = null; + } + this.scrollEndTimeout = setTimeout(() => { + this.checkForScrollEnd(); + }, 150); + } + + /** + * Check if the scroll has ended and the user is not actively touching. + * If both conditions are met, reset the initial scroll position and + * emit the scroll end event. + */ + private checkForScrollEnd() { + if (!this.isTouching) { + this.ionSegmentViewScrollEnd.emit(); + this.initialScrollLeft = undefined; + } } /** diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts index 55861be172..e944df5e9e 100644 --- a/packages/angular/src/directives/proxies.ts +++ b/packages/angular/src/directives/proxies.ts @@ -2043,14 +2043,20 @@ export class IonSegmentView { constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { c.detach(); this.el = r.nativeElement; - proxyOutputs(this, this.el, ['ionSegmentViewScroll']); + proxyOutputs(this, this.el, ['ionSegmentViewScroll', 'ionSegmentViewScrollEnd']); } } export declare interface IonSegmentView extends Components.IonSegmentView { - + /** + * Emitted when the segment view is scrolled. + */ ionSegmentViewScroll: EventEmitter>; + /** + * Emitted when the segment view scroll has ended. + */ + ionSegmentViewScrollEnd: EventEmitter>; } diff --git a/packages/angular/standalone/src/directives/proxies.ts b/packages/angular/standalone/src/directives/proxies.ts index d8a7b7d0ee..7be25d587b 100644 --- a/packages/angular/standalone/src/directives/proxies.ts +++ b/packages/angular/standalone/src/directives/proxies.ts @@ -1882,14 +1882,20 @@ export class IonSegmentView { constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { c.detach(); this.el = r.nativeElement; - proxyOutputs(this, this.el, ['ionSegmentViewScroll']); + proxyOutputs(this, this.el, ['ionSegmentViewScroll', 'ionSegmentViewScrollEnd']); } } export declare interface IonSegmentView extends Components.IonSegmentView { - + /** + * Emitted when the segment view is scrolled. + */ ionSegmentViewScroll: EventEmitter>; + /** + * Emitted when the segment view scroll has ended. + */ + ionSegmentViewScrollEnd: EventEmitter>; } diff --git a/packages/vue/src/proxies.ts b/packages/vue/src/proxies.ts index b90135296c..a0a56e1cf7 100644 --- a/packages/vue/src/proxies.ts +++ b/packages/vue/src/proxies.ts @@ -762,7 +762,8 @@ export const IonSegmentContent = /*@__PURE__*/ defineContainer('ion-segment-view', defineIonSegmentView, [ 'disabled', - 'ionSegmentViewScroll' + 'ionSegmentViewScroll', + 'ionSegmentViewScrollEnd' ]);