diff --git a/src/components/carousel/carousel.ts b/src/components/carousel/carousel.ts index 048bcb491..18efc9a78 100644 --- a/src/components/carousel/carousel.ts +++ b/src/components/carousel/carousel.ts @@ -102,6 +102,7 @@ export default class WaCarousel extends WebAwesomeElement { private dragStartPosition: [number, number] = [-1, -1]; private readonly localize = new LocalizeController(this); private mutationObserver: MutationObserver; + private pendingSlideChange = false; connectedCallback(): void { super.connectedCallback(); @@ -285,6 +286,9 @@ export default class WaCarousel extends WebAwesomeElement { @eventOptions({ passive: true }) private handleScroll() { this.scrolling = true; + if (!this.pendingSlideChange) { + this.synchronizeSlides(); + } } /** @internal Synchronizes the slides with the IntersectionObserver API. */ @@ -302,18 +306,29 @@ export default class WaCarousel extends WebAwesomeElement { const firstIntersecting = entries.find(entry => entry.isIntersecting); - if (firstIntersecting) { - if (this.loop && firstIntersecting.target.hasAttribute('data-clone')) { - const clonePosition = Number(firstIntersecting.target.getAttribute('data-clone')); - // Scrolls to the original slide without animating, so the user won't notice that the position has changed - this.goToSlide(clonePosition, 'instant'); - } else { - const slides = this.getSlides(); + if (!firstIntersecting) { + return; + } - // Update the current index based on the first visible slide - const slideIndex = slides.indexOf(firstIntersecting.target as WaCarouselItem); - // Set the index to the first "snappable" slide - this.activeSlide = Math.ceil(slideIndex / this.slidesPerMove) * this.slidesPerMove; + const slidesWithClones = this.getSlides({ excludeClones: false }); + const slidesCount = this.getSlides().length; + + // Update the current index based on the first visible slide + const slideIndex = slidesWithClones.indexOf(firstIntersecting.target as WaCarouselItem); + // Normalize the index to ignore clones + const normalizedIndex = this.loop ? slideIndex - this.slidesPerPage : slideIndex; + + if (firstIntersecting) { + // Set the index to the closest "snappable" slide + this.activeSlide = + (Math.ceil(normalizedIndex / this.slidesPerMove) * this.slidesPerMove + slidesCount) % slidesCount; + + if (!this.scrolling) { + if (this.loop && firstIntersecting.target.hasAttribute('data-clone')) { + const clonePosition = Number(firstIntersecting.target.getAttribute('data-clone')); + // Scrolls to the original slide without animating, so the user won't notice that the position has changed + this.goToSlide(clonePosition, 'instant'); + } } } }, @@ -334,6 +349,8 @@ export default class WaCarousel extends WebAwesomeElement { this.synchronizeSlides(); this.scrolling = false; + this.pendingSlideChange = false; + this.synchronizeSlides(); } private isCarouselItem(node: Node): node is WaCarouselItem { @@ -403,7 +420,7 @@ export default class WaCarousel extends WebAwesomeElement { } @watch('activeSlide') - handelSlideChange() { + handleSlideChange() { const slides = this.getSlides(); slides.forEach((slide, i) => { slide.classList.toggle('--is-active', i === this.activeSlide); @@ -508,11 +525,14 @@ export default class WaCarousel extends WebAwesomeElement { const nextLeft = nextSlideRect.left - scrollContainerRect.left; const nextTop = nextSlideRect.top - scrollContainerRect.top; - scrollContainer.scrollTo({ - left: nextLeft + scrollContainer.scrollLeft, - top: nextTop + scrollContainer.scrollTop, - behavior - }); + if (nextLeft || nextTop) { + this.pendingSlideChange = true; + scrollContainer.scrollTo({ + left: nextLeft + scrollContainer.scrollLeft, + top: nextTop + scrollContainer.scrollTop, + behavior + }); + } } render() {