diff --git a/packages/components/src/components/carousel/autoplay-controller.ts b/packages/components/src/components/carousel/autoplay-controller.ts
index 83a4f09f8..7fc6addac 100644
--- a/packages/components/src/components/carousel/autoplay-controller.ts
+++ b/packages/components/src/components/carousel/autoplay-controller.ts
@@ -70,20 +70,4 @@ export class AutoplayController implements ReactiveController {
this.host.requestUpdate();
}
};
-
- /**
- * Used to manually pause autoplay without disrupting mouse/focus/touch events.
- */
- controlledPause = () => {
- this.paused = true;
- this.host.requestUpdate();
- };
-
- /**
- * Used to manually resume autoplay without disrupting mouse/focus/touch events.
- */
- controlledResume = () => {
- this.paused = false;
- this.host.requestUpdate();
- };
}
diff --git a/packages/components/src/components/carousel/carousel.stories.ts b/packages/components/src/components/carousel/carousel.stories.ts
index 201acc306..70503e9e6 100644
--- a/packages/components/src/components/carousel/carousel.stories.ts
+++ b/packages/components/src/components/carousel/carousel.stories.ts
@@ -152,12 +152,14 @@ export const Loop = {
/**
* Use the `autoplay` attribute to toggle autoplay.
+ *
+ * __Hint:__ Autoplay is automatically paused when the user interacts with the carousel or when the pause button is clicked.
*/
export const Autoplay = {
render: () => html`
-
+
Default slot 1
diff --git a/packages/components/src/components/carousel/carousel.test.ts b/packages/components/src/components/carousel/carousel.test.ts
index 84d373d80..241ad110b 100644
--- a/packages/components/src/components/carousel/carousel.test.ts
+++ b/packages/components/src/components/carousel/carousel.test.ts
@@ -661,4 +661,125 @@ describe('', () => {
expect(currentPage).to.equal(5);
});
});
+
+ describe('when the user interacts with the carousel', () => {
+ let clock: sinon.SinonFakeTimers;
+
+ beforeEach(() => {
+ clock = sinon.useFakeTimers({
+ now: new Date()
+ });
+ });
+
+ afterEach(() => {
+ clock.restore();
+ });
+
+ it('should pause the autoplay', async () => {
+ // Arrange
+ const el = await fixture(html`
+
+ Node 1
+ Node 2
+ Node 3
+
+ `);
+ sinon.stub(el, 'next');
+
+ await el.updateComplete;
+
+ // Act
+ el.dispatchEvent(new Event('mouseenter'));
+ await el.updateComplete;
+ clock.next();
+ clock.next();
+
+ // Assert
+ expect(el.next).not.to.have.been.called;
+ });
+
+ it('should not resume if the user is still interacting', async () => {
+ // Arrange
+ const el = await fixture(html`
+
+ Node 1
+ Node 2
+ Node 3
+
+ `);
+ sinon.stub(el, 'next');
+
+ await el.updateComplete;
+
+ // Act
+ el.dispatchEvent(new Event('mouseenter'));
+ el.dispatchEvent(new Event('focusin'));
+ await el.updateComplete;
+
+ el.dispatchEvent(new Event('mouseleave'));
+ await el.updateComplete;
+
+ clock.next();
+ clock.next();
+
+ // Assert
+ expect(el.next).not.to.have.been.called;
+ });
+
+ it('should not resume if the user clicks the pause button', async () => {
+ // Arrange
+ const el = await fixture(html`
+
+ Node 1
+ Node 2
+ Node 3
+
+ `);
+ sinon.stub(el, 'next');
+
+ await el.updateComplete;
+
+ // Act
+ el.autoplayControls.click();
+ await el.updateComplete;
+ clock.next();
+ clock.next();
+
+ // Assert
+ expect(el.next).not.to.have.been.called;
+ });
+
+ it('should resume if the user clicks the resume button', async () => {
+ // Arrange
+ const el = await fixture(html`
+
+ Node 1
+ Node 2
+ Node 3
+
+ `);
+ sinon.stub(el, 'next');
+
+ await el.updateComplete;
+
+ // Act
+ el.autoplayControls.click();
+ await el.updateComplete;
+ clock.next();
+ clock.next();
+
+ // Assert
+ expect(el.next).not.to.have.been.called;
+
+ // Act
+
+ el.autoplayControls.click();
+ await el.updateComplete;
+ clock.next();
+ clock.next();
+
+ // Assert
+ expect(el.next).to.have.been.called;
+ });
+ });
});
diff --git a/packages/components/src/components/carousel/carousel.ts b/packages/components/src/components/carousel/carousel.ts
index 938fc0793..cdb484b23 100644
--- a/packages/components/src/components/carousel/carousel.ts
+++ b/packages/components/src/components/carousel/carousel.ts
@@ -52,6 +52,10 @@ import SolidElement from '../../internal/solid-element.js';
*/
@customElement('sd-carousel')
export default class SdCarousel extends SolidElement {
+ @query('[part~="autoplay-controls"]') autoplayControls: HTMLElement;
+ @query('[part~="navigation-button--previous"]') previousButton: HTMLButtonElement;
+ @query('[part~="navigation-button--next"]') nextButton: HTMLButtonElement;
+
/** Determines the counting system for the carousel. */
@property({ type: String, reflect: true }) variant: 'dot' | 'number' = 'number';
/** Inverts the carousel */
@@ -251,10 +255,13 @@ export default class SdCarousel extends SolidElement {
@watch('pausedAutoplay')
handlePausedAutoplay() {
if (this.pausedAutoplay) {
- this.autoplayController.controlledPause();
+ this.autoplayController.stop();
} else if (this.autoplay) {
- this.autoplayController.controlledResume();
+ this.autoplayController.start(3000);
}
+
+ // This is necessary to allow autoplay since focus is not removed when the button is clicked.
+ this.autoplayControls?.blur();
}
@watch('loop', { waitUntilFirstUpdate: true })
@@ -347,8 +354,6 @@ export default class SdCarousel extends SolidElement {
slide.style.setProperty('scroll-snap-align', 'none');
}
});
-
- // this.handleScrollEnd();
}
@watch('autoplay')
@@ -378,6 +383,9 @@ export default class SdCarousel extends SolidElement {
} else {
this.goToSlide(previousIndex, behavior);
}
+
+ // This keeps the carousel from pausing autoplay due to the lingering focus
+ this.previousButton?.blur();
}
/**
@@ -387,13 +395,18 @@ export default class SdCarousel extends SolidElement {
*/
next(behavior: ScrollBehavior = 'smooth') {
if (
- this.currentPage + 1 > SdCarousel.getPageCount(this.getSlides().length, this.slidesPerPage, this.slidesPerMove) &&
- this.loop
+ this.currentPage + 1 <=
+ SdCarousel.getPageCount(this.getSlides().length, this.slidesPerPage, this.slidesPerMove)
) {
- this.nextTillFirst(behavior);
- } else {
this.goToSlide(this.activeSlide + this.slidesPerMove, behavior);
+ } else {
+ if (this.loop) {
+ this.nextTillFirst(behavior);
+ }
}
+
+ // This keeps the carousel from pausing autoplay due to the lingering focus
+ this.nextButton?.blur();
}
nextTillFirst(behavior: ScrollBehavior = 'smooth') {