Skip to content

Commit 279300f

Browse files
committed
fix(segment): update segment view to scroll past disabled content
1 parent 59e306b commit 279300f

File tree

3 files changed

+89
-18
lines changed

3 files changed

+89
-18
lines changed

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

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,16 @@ export class SegmentView implements ComponentInterface {
4545
const { initialScrollLeft, previousScrollLeft } = this;
4646
const { scrollLeft, offsetWidth } = ev.target as HTMLElement;
4747

48-
if (initialScrollLeft === undefined) {
49-
this.initialScrollLeft = scrollLeft;
50-
}
48+
// Set initial scroll position if it's undefined
49+
this.initialScrollLeft = initialScrollLeft ?? scrollLeft;
5150

5251
// Determine the scroll direction based on the previous scroll position
5352
const scrollDirection = scrollLeft > previousScrollLeft ? 'right' : 'left';
5453
this.previousScrollLeft = scrollLeft;
5554

5655
// Calculate the distance scrolled based on the initial scroll position
5756
// and then transform it to a percentage of the segment view width
58-
const scrollDistance = scrollLeft - initialScrollLeft!;
57+
const scrollDistance = scrollLeft - this.initialScrollLeft;
5958
const scrollDistancePercentage = Math.abs(scrollDistance) / offsetWidth;
6059

6160
// Emit the scroll direction and distance
@@ -65,20 +64,28 @@ export class SegmentView implements ComponentInterface {
6564
scrollDistancePercentage,
6665
});
6766

67+
// Check if the scroll is at a snapping point and return if not
6868
const atSnappingPoint = scrollLeft % offsetWidth === 0;
69-
7069
if (!atSnappingPoint) return;
7170

72-
const index = Math.round(scrollLeft / offsetWidth);
73-
const segmentContent = this.getSegmentContents()[index];
71+
// Find the current segment content based on the scroll position
72+
const currentIndex = Math.round(scrollLeft / offsetWidth);
73+
let segmentContent = this.getSegmentContents()[currentIndex];
7474

75-
if (segmentContent === null || segmentContent === undefined) {
76-
return;
75+
// Exit if no valid segment content found
76+
if (!segmentContent) return;
77+
78+
// If the current content is disabled, find the next enabled content
79+
if (segmentContent.disabled) {
80+
const nextIndex = scrollDirection === 'right' ? currentIndex + 1 : currentIndex - 1;
81+
segmentContent = this.getNextSegmentContent(nextIndex);
7782
}
7883

79-
// Store the active `ion-segment-content` id so we can emit it when the scroll ends
84+
// Update active content ID and scroll to the segment content
8085
this.activeContentId = segmentContent.id;
86+
this.setContent(segmentContent.id);
8187

88+
// Reset the timeout to check for scroll end
8289
this.resetScrollEndTimeout();
8390
}
8491

@@ -120,11 +127,15 @@ export class SegmentView implements ComponentInterface {
120127

121128
/**
122129
* Check if the scroll has ended and the user is not actively touching.
123-
* If both conditions are met, reset the initial scroll position and
124-
* emit the scroll end event.
130+
* If the conditions are met (active content is enabled and no active touch),
131+
* reset the scroll position and emit the scroll end event.
125132
*/
126133
private checkForScrollEnd() {
127-
if (!this.isTouching) {
134+
const activeContent = this.getSegmentContents().find(content => content.id === this.activeContentId);
135+
136+
// Only emit scroll end event if the active content is not disabled and
137+
// the user is not touching the segment view
138+
if (activeContent?.disabled === false && !this.isTouching) {
128139
this.ionSegmentViewScrollEnd.emit({ activeContentId: this.activeContentId });
129140
this.initialScrollLeft = undefined;
130141
}
@@ -153,7 +164,12 @@ export class SegmentView implements ComponentInterface {
153164
}
154165

155166
private getSegmentContents(): HTMLIonSegmentContentElement[] {
156-
return Array.from(this.el.querySelectorAll('ion-segment-content:not([disabled])'));
167+
return Array.from(this.el.querySelectorAll('ion-segment-content'));
168+
}
169+
170+
private getNextSegmentContent(index: number): HTMLIonSegmentContentElement {
171+
const contents = this.getSegmentContents();
172+
return contents[index];
157173
}
158174

159175
render() {

core/src/components/segment-view/test/disabled/index.html

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
ion-segment-content:nth-of-type(3) {
3636
background: lightgreen;
3737
}
38+
39+
ion-segment-content:nth-of-type(4) {
40+
background: lightgoldenrodyellow;
41+
}
3842
</style>
3943
</head>
4044

@@ -77,6 +81,27 @@
7781
<ion-segment-content id="top">Top</ion-segment-content>
7882
</ion-segment-view>
7983

84+
<ion-segment value="a">
85+
<ion-segment-button content-id="a" value="a">
86+
<ion-label>a</ion-label>
87+
</ion-segment-button>
88+
<ion-segment-button disabled content-id="b" value="b">
89+
<ion-label>b</ion-label>
90+
</ion-segment-button>
91+
<ion-segment-button disabled content-id="c" value="c">
92+
<ion-label>c</ion-label>
93+
</ion-segment-button>
94+
<ion-segment-button content-id="d" value="d">
95+
<ion-label>d</ion-label>
96+
</ion-segment-button>
97+
</ion-segment>
98+
<ion-segment-view>
99+
<ion-segment-content id="a">a</ion-segment-content>
100+
<ion-segment-content disabled id="b">b</ion-segment-content>
101+
<ion-segment-content disabled id="c">c</ion-segment-content>
102+
<ion-segment-content id="d">d</ion-segment-content>
103+
</ion-segment-view>
104+
80105
<ion-segment disabled value="reading-list">
81106
<ion-segment-button content-id="bookmarks" value="bookmarks">
82107
<ion-label>Bookmarks</ion-label>

core/src/components/segment/segment.tsx

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -442,17 +442,47 @@ export class Segment implements ComponentInterface {
442442
const segmentEl = this.el;
443443

444444
if (ev.composedPath().includes(segmentViewEl) || dispatchedFrom?.contains(segmentEl)) {
445-
this.value = ev.detail.activeContentId;
446-
447445
if (this.scrolledIndicator) {
448-
this.scrolledIndicator.style.transition = '';
449-
this.scrolledIndicator.style.transform = '';
446+
const computedStyle = window.getComputedStyle(this.scrolledIndicator);
447+
const isTransitioning = computedStyle.transitionDuration !== '0s';
448+
449+
if (isTransitioning) {
450+
// Add a transitionend listener if the indicator is transitioning
451+
this.waitForTransitionEnd(this.scrolledIndicator, () => {
452+
this.updateValueAfterTransition(ev.detail.activeContentId);
453+
});
454+
} else {
455+
// Immediately update the value if there's no transition
456+
this.updateValueAfterTransition(ev.detail.activeContentId);
457+
}
458+
} else {
459+
// Immediately update the value if there's no indicator
460+
this.updateValueAfterTransition(ev.detail.activeContentId);
450461
}
451462

452463
this.isScrolling = false;
453464
}
454465
}
455466

467+
// Wait for the transition to end, then execute the callback
468+
private waitForTransitionEnd(indicator: HTMLElement, callback: () => void) {
469+
const onTransitionEnd = () => {
470+
indicator.removeEventListener('transitionend', onTransitionEnd);
471+
callback();
472+
};
473+
indicator.addEventListener('transitionend', onTransitionEnd);
474+
}
475+
476+
// Update the Segment value after the ionSegmentViewScrollEnd transition has ended
477+
private updateValueAfterTransition(activeContentId: string) {
478+
this.value = activeContentId;
479+
480+
if (this.scrolledIndicator) {
481+
this.scrolledIndicator.style.transition = '';
482+
this.scrolledIndicator.style.transform = '';
483+
}
484+
}
485+
456486
/**
457487
* Finds the related segment view and sets its current content
458488
* based on the selected segment button. This method

0 commit comments

Comments
 (0)