fix(segment): update segment view to scroll past disabled content

This commit is contained in:
Brandy Carney
2024-10-08 17:35:14 -04:00
parent 59e306bd6d
commit 279300fd3e
3 changed files with 89 additions and 18 deletions

View File

@@ -45,9 +45,8 @@ export class SegmentView implements ComponentInterface {
const { initialScrollLeft, previousScrollLeft } = this;
const { scrollLeft, offsetWidth } = ev.target as HTMLElement;
if (initialScrollLeft === undefined) {
this.initialScrollLeft = scrollLeft;
}
// Set initial scroll position if it's undefined
this.initialScrollLeft = initialScrollLeft ?? scrollLeft;
// Determine the scroll direction based on the previous scroll position
const scrollDirection = scrollLeft > previousScrollLeft ? 'right' : 'left';
@@ -55,7 +54,7 @@ export class SegmentView implements ComponentInterface {
// Calculate the distance scrolled based on the initial scroll position
// and then transform it to a percentage of the segment view width
const scrollDistance = scrollLeft - initialScrollLeft!;
const scrollDistance = scrollLeft - this.initialScrollLeft;
const scrollDistancePercentage = Math.abs(scrollDistance) / offsetWidth;
// Emit the scroll direction and distance
@@ -65,20 +64,28 @@ export class SegmentView implements ComponentInterface {
scrollDistancePercentage,
});
// Check if the scroll is at a snapping point and return if not
const atSnappingPoint = scrollLeft % offsetWidth === 0;
if (!atSnappingPoint) return;
const index = Math.round(scrollLeft / offsetWidth);
const segmentContent = this.getSegmentContents()[index];
// Find the current segment content based on the scroll position
const currentIndex = Math.round(scrollLeft / offsetWidth);
let segmentContent = this.getSegmentContents()[currentIndex];
if (segmentContent === null || segmentContent === undefined) {
return;
// Exit if no valid segment content found
if (!segmentContent) return;
// If the current content is disabled, find the next enabled content
if (segmentContent.disabled) {
const nextIndex = scrollDirection === 'right' ? currentIndex + 1 : currentIndex - 1;
segmentContent = this.getNextSegmentContent(nextIndex);
}
// Store the active `ion-segment-content` id so we can emit it when the scroll ends
// Update active content ID and scroll to the segment content
this.activeContentId = segmentContent.id;
this.setContent(segmentContent.id);
// Reset the timeout to check for scroll end
this.resetScrollEndTimeout();
}
@@ -120,11 +127,15 @@ export class SegmentView implements ComponentInterface {
/**
* 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.
* If the conditions are met (active content is enabled and no active touch),
* reset the scroll position and emit the scroll end event.
*/
private checkForScrollEnd() {
if (!this.isTouching) {
const activeContent = this.getSegmentContents().find(content => content.id === this.activeContentId);
// Only emit scroll end event if the active content is not disabled and
// the user is not touching the segment view
if (activeContent?.disabled === false && !this.isTouching) {
this.ionSegmentViewScrollEnd.emit({ activeContentId: this.activeContentId });
this.initialScrollLeft = undefined;
}
@@ -153,7 +164,12 @@ export class SegmentView implements ComponentInterface {
}
private getSegmentContents(): HTMLIonSegmentContentElement[] {
return Array.from(this.el.querySelectorAll('ion-segment-content:not([disabled])'));
return Array.from(this.el.querySelectorAll('ion-segment-content'));
}
private getNextSegmentContent(index: number): HTMLIonSegmentContentElement {
const contents = this.getSegmentContents();
return contents[index];
}
render() {

View File

@@ -35,6 +35,10 @@
ion-segment-content:nth-of-type(3) {
background: lightgreen;
}
ion-segment-content:nth-of-type(4) {
background: lightgoldenrodyellow;
}
</style>
</head>
@@ -77,6 +81,27 @@
<ion-segment-content id="top">Top</ion-segment-content>
</ion-segment-view>
<ion-segment value="a">
<ion-segment-button content-id="a" value="a">
<ion-label>a</ion-label>
</ion-segment-button>
<ion-segment-button disabled content-id="b" value="b">
<ion-label>b</ion-label>
</ion-segment-button>
<ion-segment-button disabled content-id="c" value="c">
<ion-label>c</ion-label>
</ion-segment-button>
<ion-segment-button content-id="d" value="d">
<ion-label>d</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view>
<ion-segment-content id="a">a</ion-segment-content>
<ion-segment-content disabled id="b">b</ion-segment-content>
<ion-segment-content disabled id="c">c</ion-segment-content>
<ion-segment-content id="d">d</ion-segment-content>
</ion-segment-view>
<ion-segment disabled value="reading-list">
<ion-segment-button content-id="bookmarks" value="bookmarks">
<ion-label>Bookmarks</ion-label>

View File

@@ -442,17 +442,47 @@ export class Segment implements ComponentInterface {
const segmentEl = this.el;
if (ev.composedPath().includes(segmentViewEl) || dispatchedFrom?.contains(segmentEl)) {
this.value = ev.detail.activeContentId;
if (this.scrolledIndicator) {
this.scrolledIndicator.style.transition = '';
this.scrolledIndicator.style.transform = '';
const computedStyle = window.getComputedStyle(this.scrolledIndicator);
const isTransitioning = computedStyle.transitionDuration !== '0s';
if (isTransitioning) {
// Add a transitionend listener if the indicator is transitioning
this.waitForTransitionEnd(this.scrolledIndicator, () => {
this.updateValueAfterTransition(ev.detail.activeContentId);
});
} else {
// Immediately update the value if there's no transition
this.updateValueAfterTransition(ev.detail.activeContentId);
}
} else {
// Immediately update the value if there's no indicator
this.updateValueAfterTransition(ev.detail.activeContentId);
}
this.isScrolling = false;
}
}
// Wait for the transition to end, then execute the callback
private waitForTransitionEnd(indicator: HTMLElement, callback: () => void) {
const onTransitionEnd = () => {
indicator.removeEventListener('transitionend', onTransitionEnd);
callback();
};
indicator.addEventListener('transitionend', onTransitionEnd);
}
// Update the Segment value after the ionSegmentViewScrollEnd transition has ended
private updateValueAfterTransition(activeContentId: string) {
this.value = activeContentId;
if (this.scrolledIndicator) {
this.scrolledIndicator.style.transition = '';
this.scrolledIndicator.style.transform = '';
}
}
/**
* Finds the related segment view and sets its current content
* based on the selected segment button. This method