Skip to content

Commit c7bae07

Browse files
committed
fix(segment-view): allow moving the indicator left on scroll without touch
1 parent 1d645c9 commit c7bae07

File tree

6 files changed

+91
-13
lines changed

6 files changed

+91
-13
lines changed

core/api.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,6 +1613,8 @@ ion-segment-content,prop,disabled,boolean,false,false,false
16131613
ion-segment-view,shadow
16141614
ion-segment-view,prop,disabled,boolean,false,false,false
16151615
ion-segment-view,method,setContent,setContent(id: string, smoothScroll?: boolean) => Promise<void>
1616+
ion-segment-view,event,ionSegmentViewScroll,{ scrollDirection: string; scrollDistance: number; },true
1617+
ion-segment-view,event,ionSegmentViewScrollEnd,void,true
16161618

16171619
ion-select,shadow
16181620
ion-select,prop,cancelText,string,'Cancel',false,false

core/src/components.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4443,6 +4443,7 @@ declare global {
44434443
};
44444444
interface HTMLIonSegmentViewElementEventMap {
44454445
"ionSegmentViewScroll": { scrollDirection: string; scrollDistance: number };
4446+
"ionSegmentViewScrollEnd": void;
44464447
}
44474448
interface HTMLIonSegmentViewElement extends Components.IonSegmentView, HTMLStencilElement {
44484449
addEventListener<K extends keyof HTMLIonSegmentViewElementEventMap>(type: K, listener: (this: HTMLIonSegmentViewElement, ev: IonSegmentViewCustomEvent<HTMLIonSegmentViewElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
@@ -7534,7 +7535,14 @@ declare namespace LocalJSX {
75347535
* If `true`, the segment view cannot be interacted with.
75357536
*/
75367537
"disabled"?: boolean;
7538+
/**
7539+
* Emitted when the segment view is scrolled.
7540+
*/
75377541
"onIonSegmentViewScroll"?: (event: IonSegmentViewCustomEvent<{ scrollDirection: string; scrollDistance: number }>) => void;
7542+
/**
7543+
* Emitted when the segment view scroll has ended.
7544+
*/
7545+
"onIonSegmentViewScrollEnd"?: (event: IonSegmentViewCustomEvent<void>) => void;
75387546
}
75397547
interface IonSelect {
75407548
/**

core/src/components/segment-view/segment-view.tsx

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ import { Component, Element, Event, Host, Listen, Method, Prop, h } from '@stenc
1010
shadow: true,
1111
})
1212
export class SegmentView implements ComponentInterface {
13-
private initialScrollLeft = 0;
13+
private initialScrollLeft?: number;
1414
private previousScrollLeft = 0;
15+
private scrollEndTimeout: ReturnType<typeof setTimeout> | null = null;
16+
private isTouching = false;
1517

1618
@Element() el!: HTMLElement;
1719

@@ -20,21 +22,31 @@ export class SegmentView implements ComponentInterface {
2022
*/
2123
@Prop() disabled = false;
2224

25+
/**
26+
* Emitted when the segment view is scrolled.
27+
*/
2328
@Event() ionSegmentViewScroll!: EventEmitter<{ scrollDirection: string; scrollDistance: number }>;
2429

30+
/**
31+
* Emitted when the segment view scroll has ended.
32+
*/
33+
@Event() ionSegmentViewScrollEnd!: EventEmitter<void>;
34+
2535
@Listen('scroll')
2636
handleScroll(ev: Event) {
2737
const { initialScrollLeft, previousScrollLeft } = this;
2838
const { scrollLeft, offsetWidth } = ev.target as HTMLElement;
2939

40+
if (initialScrollLeft === undefined) {
41+
this.initialScrollLeft = scrollLeft;
42+
}
43+
3044
const scrollDirection = scrollLeft > previousScrollLeft ? 'right' : 'left';
3145
this.previousScrollLeft = scrollLeft;
3246

33-
let scrollDistance = scrollLeft;
34-
35-
if (scrollDirection === 'left') {
36-
scrollDistance = initialScrollLeft - scrollLeft;
37-
}
47+
// If the scroll direction is left then we need to calculate where we started and subtract
48+
// the current scrollLeft to get the distance scrolled. Otherwise, we use the scrollLeft.
49+
const scrollDistance = scrollDirection === 'left' ? initialScrollLeft! - scrollLeft : scrollLeft;
3850

3951
// Emit the scroll direction and distance
4052
this.ionSegmentViewScroll.emit({
@@ -59,11 +71,54 @@ export class SegmentView implements ComponentInterface {
5971
if (segment) {
6072
segment.value = segmentButton.value;
6173
}
74+
75+
this.resetScrollEndTimeout();
6276
}
6377

78+
/**
79+
* Handle touch start event to know when the user is actively dragging the segment view.
80+
*/
6481
@Listen('touchstart')
65-
handleTouchStart() {
66-
this.initialScrollLeft = this.el.scrollLeft;
82+
handleScrollStart() {
83+
if (this.scrollEndTimeout) {
84+
clearTimeout(this.scrollEndTimeout);
85+
this.scrollEndTimeout = null;
86+
}
87+
88+
this.isTouching = true;
89+
}
90+
91+
/**
92+
* Handle touch end event to know when the user is no longer dragging the segment view.
93+
*/
94+
@Listen('touchend')
95+
handleTouchEnd() {
96+
this.isTouching = false;
97+
}
98+
99+
/**
100+
* Reset the scroll end detection timer. This is called on every scroll event.
101+
*/
102+
private resetScrollEndTimeout() {
103+
if (this.scrollEndTimeout) {
104+
clearTimeout(this.scrollEndTimeout);
105+
this.scrollEndTimeout = null;
106+
}
107+
this.scrollEndTimeout = setTimeout(() => {
108+
this.checkForScrollEnd();
109+
}, 150);
110+
}
111+
112+
/**
113+
* Check if the scroll has ended and the user is not actively touching.
114+
* If both conditions are met, reset the initial scroll position and
115+
* emit the scroll end event.
116+
*/
117+
private checkForScrollEnd() {
118+
if (!this.isTouching) {
119+
this.ionSegmentViewScrollEnd.emit();
120+
this.initialScrollLeft = undefined;
121+
}
67122
}
68123

69124
/**

packages/angular/src/directives/proxies.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2043,14 +2043,20 @@ export class IonSegmentView {
20432043
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
20442044
c.detach();
20452045
this.el = r.nativeElement;
2046-
proxyOutputs(this, this.el, ['ionSegmentViewScroll']);
2046+
proxyOutputs(this, this.el, ['ionSegmentViewScroll', 'ionSegmentViewScrollEnd']);
20472047
}
20482048
}
20492049

20502050

20512051
export declare interface IonSegmentView extends Components.IonSegmentView {
2052-
2052+
/**
2053+
* Emitted when the segment view is scrolled.
2054+
*/
20532055
ionSegmentViewScroll: EventEmitter<CustomEvent<{ scrollDirection: string; scrollDistance: number }>>;
2056+
/**
2057+
* Emitted when the segment view scroll has ended.
2058+
*/
2059+
ionSegmentViewScrollEnd: EventEmitter<CustomEvent<void>>;
20542060
}
20552061

20562062

packages/angular/standalone/src/directives/proxies.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1882,14 +1882,20 @@ export class IonSegmentView {
18821882
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
18831883
c.detach();
18841884
this.el = r.nativeElement;
1885-
proxyOutputs(this, this.el, ['ionSegmentViewScroll']);
1885+
proxyOutputs(this, this.el, ['ionSegmentViewScroll', 'ionSegmentViewScrollEnd']);
18861886
}
18871887
}
18881888

18891889

18901890
export declare interface IonSegmentView extends Components.IonSegmentView {
1891-
1891+
/**
1892+
* Emitted when the segment view is scrolled.
1893+
*/
18921894
ionSegmentViewScroll: EventEmitter<CustomEvent<{ scrollDirection: string; scrollDistance: number }>>;
1895+
/**
1896+
* Emitted when the segment view scroll has ended.
1897+
*/
1898+
ionSegmentViewScrollEnd: EventEmitter<CustomEvent<void>>;
18931899
}
18941900

18951901

packages/vue/src/proxies.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,8 @@ export const IonSegmentContent = /*@__PURE__*/ defineContainer<JSX.IonSegmentCon
762762

763763
export const IonSegmentView = /*@__PURE__*/ defineContainer<JSX.IonSegmentView>('ion-segment-view', defineIonSegmentView, [
764764
'disabled',
765-
'ionSegmentViewScroll'
765+
'ionSegmentViewScroll',
766+
'ionSegmentViewScrollEnd'
766767
]);
767768

768769

0 commit comments

Comments
 (0)