From 7f22b7f89ec6423d33e42b80aaac8706b093906d Mon Sep 17 00:00:00 2001 From: Vahid Nesro <63849626+Vahid1919@users.noreply.github.com> Date: Thu, 15 Aug 2024 14:06:31 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20=F0=9F=A4=94=20sd-carousel=20-=20numbere?= =?UTF-8?q?d=20variant=20-=20show=20total=20clicks=20(#1247)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/carousel/carousel.stories.ts | 2 +- .../src/components/carousel/carousel.test.ts | 87 +++++++++++++++++- .../src/components/carousel/carousel.ts | 88 ++++++++++++++++--- 3 files changed, 161 insertions(+), 16 deletions(-) diff --git a/packages/components/src/components/carousel/carousel.stories.ts b/packages/components/src/components/carousel/carousel.stories.ts index 4d979ee24..9e7f809fc 100644 --- a/packages/components/src/components/carousel/carousel.stories.ts +++ b/packages/components/src/components/carousel/carousel.stories.ts @@ -16,6 +16,7 @@ export default { type: 'slot', name: 'default', value: ` +
Default slot 1
Default slot 2
Default slot 3
@@ -154,7 +155,6 @@ export const SlidesPerMove = { y: { type: 'attribute', name: 'slides-per-move', values: [2] } }, constants: [ - { type: 'attribute', name: 'loop', value: 'true' }, { type: 'attribute', name: 'slides-per-page', value: 2 }, { type: 'slot', diff --git a/packages/components/src/components/carousel/carousel.test.ts b/packages/components/src/components/carousel/carousel.test.ts index 1ad401a6f..84d373d80 100644 --- a/packages/components/src/components/carousel/carousel.test.ts +++ b/packages/components/src/components/carousel/carousel.test.ts @@ -1,7 +1,7 @@ import { aTimeout, expect, fixture, html, oneEvent } from '@open-wc/testing'; import { clickOnElement } from '../../internal/test.js'; +import SdCarousel from './carousel'; import sinon from 'sinon'; -import type SdCarousel from './carousel'; describe('', () => { it('should render a carousel with default configuration', async () => { @@ -576,4 +576,89 @@ describe('', () => { }); }); }); + + describe('getPageCount', () => { + it('should return the correct page count when totalSlides is divisible by slidesPerPage', () => { + // Arrange + const totalSlides = 10; + const slidesPerPage = 2; + const slidesPerMove = 1; + + // Act + const pageCount = SdCarousel.getPageCount(totalSlides, slidesPerPage, slidesPerMove); + + // Assert + expect(pageCount).to.equal(9); + }); + + it('should return the correct page count when totalSlides is not divisible by slidesPerPage', () => { + // Arrange + const totalSlides = 11; + const slidesPerPage = 3; + const slidesPerMove = 2; + + // Act + const pageCount = SdCarousel.getPageCount(totalSlides, slidesPerPage, slidesPerMove); + + // Assert + expect(pageCount).to.equal(5); + }); + + it('should return 1 when totalSlides is less than or equal to slidesPerPage', () => { + // Arrange + const totalSlides = 3; + const slidesPerPage = 5; + const slidesPerMove = 1; + + // Act + const pageCount = SdCarousel.getPageCount(totalSlides, slidesPerPage, slidesPerMove); + + // Assert + expect(pageCount).to.equal(1); + }); + }); + + describe('getCurrentPage', () => { + it('should return the correct current page when the active slide is the first slide', () => { + // Arrange + const totalSlides = 5; + const activeSlide = 0; + const slidesPerPage = 2; + const slidesPerMove = 1; + + // Act + const currentPage = SdCarousel.getCurrentPage(totalSlides, activeSlide, slidesPerPage, slidesPerMove); + + // Assert + expect(currentPage).to.equal(1); + }); + + it('should return the correct current page when the active slide is in the middle of the carousel', () => { + // Arrange + const totalSlides = 5; + const activeSlide = 2; + const slidesPerPage = 2; + const slidesPerMove = 1; + + // Act + const currentPage = SdCarousel.getCurrentPage(totalSlides, activeSlide, slidesPerPage, slidesPerMove); + + // Assert + expect(currentPage).to.equal(3); + }); + + it('should return the correct current page when the active slide is the last slide', () => { + // Arrange + const totalSlides = 5; + const activeSlide = 4; + const slidesPerPage = 2; + const slidesPerMove = 1; + + // Act + const currentPage = SdCarousel.getCurrentPage(totalSlides, activeSlide, slidesPerPage, slidesPerMove); + + // Assert + expect(currentPage).to.equal(5); + }); + }); }); diff --git a/packages/components/src/components/carousel/carousel.ts b/packages/components/src/components/carousel/carousel.ts index 6ffea8ef3..938fc0793 100644 --- a/packages/components/src/components/carousel/carousel.ts +++ b/packages/components/src/components/carousel/carousel.ts @@ -84,6 +84,12 @@ export default class SdCarousel extends SolidElement { */ @state() activeSlide = 0; + /** + * The current page of the carousel + * @internal + */ + @state() currentPage = 1; + /** * Boolean keeping track of the autoplay pause/play button * @internal @@ -141,12 +147,24 @@ export default class SdCarousel extends SolidElement { this.mutationObserver.observe(this, { childList: true, subtree: false }); } - private getPageCount() { - return Math.ceil(this.getSlides().length / this.slidesPerPage); + static getPageCount(totalSlides: number, slidesPerPage: number, slidesPerMove: number) { + return Math.ceil((totalSlides - slidesPerPage) / slidesPerMove) + 1 > 0 + ? Math.ceil((totalSlides - slidesPerPage) / slidesPerMove) + 1 + : // Returns 1 if the total number of slides is less than the number of slides per page + 1; } - private getCurrentPage() { - return Math.ceil(this.activeSlide / this.slidesPerPage); + static getCurrentPage( + totalSlides: number, + activeSlide: number, + slidesPerPage: number, + slidesPerMove: number + ): number { + return ( + Math.ceil((totalSlides - slidesPerPage) / slidesPerMove) - + Math.ceil((totalSlides - slidesPerPage - activeSlide) / slidesPerMove) + + 1 + ); } private getSlides({ excludeClones = true }: { excludeClones?: boolean } = {}) { @@ -289,6 +307,13 @@ export default class SdCarousel extends SolidElement { @watch('activeSlide') handelSlideChange() { + this.currentPage = SdCarousel.getCurrentPage( + this.getSlides().length, + this.activeSlide, + this.slidesPerPage, + this.slidesPerMove + ); + const slides = this.getSlides(); slides.forEach((slide, i) => { slide.classList.toggle('--is-active', i === this.activeSlide); @@ -303,6 +328,10 @@ export default class SdCarousel extends SolidElement { } }); } + + if (this.currentPage > SdCarousel.getPageCount(this.getSlides().length, this.slidesPerPage, this.slidesPerMove)) { + this.nextTillFirst(); + } } @watch('slidesPerMove') @@ -318,6 +347,8 @@ export default class SdCarousel extends SolidElement { slide.style.setProperty('scroll-snap-align', 'none'); } }); + + // this.handleScrollEnd(); } @watch('autoplay') @@ -342,7 +373,11 @@ export default class SdCarousel extends SolidElement { canSnap = Math.abs(previousIndex - this.slidesPerMove) % this.slidesPerMove === 0; } - this.goToSlide(previousIndex, behavior); + if (this.currentPage - 1 === 0 && this.loop) { + this.goToSlide(this.activeSlide - this.slidesPerPage, behavior); + } else { + this.goToSlide(previousIndex, behavior); + } } /** @@ -351,7 +386,27 @@ export default class SdCarousel extends SolidElement { * @param behavior - The behavior used for scrolling. */ next(behavior: ScrollBehavior = 'smooth') { - this.goToSlide(this.activeSlide + this.slidesPerMove, behavior); + if ( + this.currentPage + 1 > SdCarousel.getPageCount(this.getSlides().length, this.slidesPerPage, this.slidesPerMove) && + this.loop + ) { + this.nextTillFirst(behavior); + } else { + this.goToSlide(this.activeSlide + this.slidesPerMove, behavior); + } + } + + nextTillFirst(behavior: ScrollBehavior = 'smooth') { + while (this.activeSlide !== 0) { + this.goToSlide(this.activeSlide + 1, behavior); + } + + this.currentPage = SdCarousel.getCurrentPage( + this.getSlides().length, + this.activeSlide, + this.slidesPerPage, + this.slidesPerMove + ); } /** @@ -374,10 +429,11 @@ export default class SdCarousel extends SolidElement { // Get the index of the next slide. For looping carousel it adds `slidesPerPage` // to normalize the starting index in order to ignore the first nth clones. - const nextSlideIndex = clamp(index + (loop ? slidesPerPage : 0), 0, slidesWithClones.length - 1); + const nextSlideIndex = clamp(index + (loop ? slidesPerPage : 0), 0, slidesWithClones.length + 1); const nextSlide = slidesWithClones[nextSlideIndex]; const scrollContainerRect = scrollContainer.getBoundingClientRect(); + const nextSlideRect = nextSlide.getBoundingClientRect(); scrollContainer.scrollTo({ @@ -389,10 +445,15 @@ export default class SdCarousel extends SolidElement { render() { const { scrollController, slidesPerPage } = this; - const pagesCount = this.getPageCount(); - const currentPage = this.getCurrentPage(); - const prevEnabled = this.loop || currentPage > 0; - const nextEnabled = this.loop || currentPage < pagesCount - 1; + const pagesCount = SdCarousel.getPageCount(this.getSlides().length, this.slidesPerPage, this.slidesPerMove); + const currentPage = SdCarousel.getCurrentPage( + this.getSlides().length, + this.activeSlide, + this.slidesPerPage, + this.slidesPerMove + ); + const prevEnabled = this.loop || currentPage > 1; + const nextEnabled = this.loop || currentPage < pagesCount; const isLtr = this.localize.dir() === 'ltr'; return html` @@ -451,7 +512,7 @@ export default class SdCarousel extends SolidElement { aria-controls="scroll-container" > ${map(range(pagesCount), index => { - const isActive = index === currentPage; + const isActive = index + 1 === currentPage; return html`