Skip to content

Commit

Permalink
fix: 🤔 pause not working in sd-carousel (#1467)
Browse files Browse the repository at this point in the history
  • Loading branch information
Vahid1919 authored Oct 17, 2024
1 parent c463e06 commit e594b21
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 25 deletions.
16 changes: 0 additions & 16 deletions packages/components/src/components/carousel/autoplay-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -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`
<div>
<sd-carousel autoplay>
<sd-carousel autoplay loop>
<sd-carousel-item>
<div class="slot slot--border slot--text h-16">Default slot 1</div>
</sd-carousel-item>
Expand Down
121 changes: 121 additions & 0 deletions packages/components/src/components/carousel/carousel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -661,4 +661,125 @@ describe('<sd-carousel>', () => {
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<SdCarousel>(html`
<sd-carousel autoplay>
<sd-carousel-item>Node 1</sd-carousel-item>
<sd-carousel-item>Node 2</sd-carousel-item>
<sd-carousel-item>Node 3</sd-carousel-item>
</sd-carousel>
`);
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<SdCarousel>(html`
<sd-carousel autoplay>
<sd-carousel-item>Node 1</sd-carousel-item>
<sd-carousel-item>Node 2</sd-carousel-item>
<sd-carousel-item>Node 3</sd-carousel-item>
</sd-carousel>
`);
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<SdCarousel>(html`
<sd-carousel autoplay>
<sd-carousel-item>Node 1</sd-carousel-item>
<sd-carousel-item>Node 2</sd-carousel-item>
<sd-carousel-item>Node 3</sd-carousel-item>
</sd-carousel>
`);
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<SdCarousel>(html`
<sd-carousel autoplay>
<sd-carousel-item>Node 1</sd-carousel-item>
<sd-carousel-item>Node 2</sd-carousel-item>
<sd-carousel-item>Node 3</sd-carousel-item>
</sd-carousel>
`);
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;
});
});
});
29 changes: 21 additions & 8 deletions packages/components/src/components/carousel/carousel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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 })
Expand Down Expand Up @@ -347,8 +354,6 @@ export default class SdCarousel extends SolidElement {
slide.style.setProperty('scroll-snap-align', 'none');
}
});

// this.handleScrollEnd();
}

@watch('autoplay')
Expand Down Expand Up @@ -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();
}

/**
Expand All @@ -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') {
Expand Down

0 comments on commit e594b21

Please sign in to comment.