fix(carousel): various fixes and improvements (#1216)

* fix(carousel): don't resume autoplay if interacting

* fix(carousel): wrap pagination items

* chore(carousel): add unit tests

* feat(carousel): more reactive pagination dots

* fix(carousel): trigger scrollend when user scroll exactly over a snap point
pull/1217/head
Alessandro 2023-03-01 17:05:29 +01:00 zatwierdzone przez GitHub
rodzic 77b25f4581
commit ec036d8e61
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
5 zmienionych plików z 68 dodań i 19 usunięć

Wyświetl plik

@ -8,6 +8,7 @@ export class AutoplayController implements ReactiveController {
private host: ReactiveElement;
private timerId = 0;
private tickCallback: () => void;
private activeInteractions = 0;
paused = false;
stopped = true;
@ -57,12 +58,16 @@ export class AutoplayController implements ReactiveController {
}
pause = () => {
this.paused = true;
this.host.requestUpdate();
if (!this.activeInteractions++) {
this.paused = true;
this.host.requestUpdate();
}
};
resume = () => {
this.paused = false;
this.host.requestUpdate();
if (!--this.activeInteractions) {
this.paused = false;
this.host.requestUpdate();
}
};
}

Wyświetl plik

@ -30,6 +30,7 @@ export default css`
grid-area: pagination;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: var(--sl-spacing-small);
}

Wyświetl plik

@ -78,6 +78,34 @@ describe('<sl-carousel>', () => {
// 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<SlCarousel>(html`
<sl-carousel autoplay autoplay-interval="10">
<sl-carousel-item>Node 1</sl-carousel-item>
<sl-carousel-item>Node 2</sl-carousel-item>
<sl-carousel-item>Node 3</sl-carousel-item>
</sl-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;
});
});
describe('when `loop` attribute is provided', () => {

Wyświetl plik

@ -168,13 +168,21 @@ export default class SlCarousel extends ShoelaceElement {
goToSlide(index: number, behavior: ScrollBehavior = 'smooth') {
const { slidesPerPage, loop } = this;
const slides = this.getSlides();
const slidesWithClones = this.getSlides({ excludeClones: false });
const normalizedIndex = clamp(index + (loop ? slidesPerPage : 0), 0, slidesWithClones.length - 1);
const slide = slidesWithClones[normalizedIndex];
// Sets the next index without taking into account clones, if any.
const newActiveSlide = (index + slides.length) % slides.length;
this.activeSlide = newActiveSlide;
// 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 nextSlide = slidesWithClones[nextSlideIndex];
this.scrollContainer.scrollTo({
left: slide.offsetLeft,
top: slide.offsetTop,
left: nextSlide.offsetLeft,
top: nextSlide.offsetTop,
behavior: prefersReducedMotion() ? 'auto' : behavior
});
}
@ -307,13 +315,18 @@ export default class SlCarousel extends ShoelaceElement {
this.scrollController.mouseDragging = this.mouseDragging;
}
private renderPagination = () => {
const slides = this.getSlides();
const slidesCount = slides.length;
private getPageCount() {
return Math.ceil(this.getSlides().length / this.slidesPerPage);
}
const { activeSlide, slidesPerPage } = this;
const pagesCount = Math.ceil(slidesCount / slidesPerPage);
const currentPage = Math.floor(activeSlide / slidesPerPage);
private getCurrentPage() {
return Math.floor(this.activeSlide / this.slidesPerPage);
}
private renderPagination = () => {
const { slidesPerPage } = this;
const pagesCount = this.getPageCount();
const currentPage = this.getCurrentPage();
return html`
<nav part="pagination" role="tablist" class="carousel__pagination" aria-controls="scroll-container">
@ -338,11 +351,11 @@ export default class SlCarousel extends ShoelaceElement {
};
private renderNavigation = () => {
const { loop, activeSlide } = this;
const slides = this.getSlides();
const slidesCount = slides.length;
const prevEnabled = loop || activeSlide > 0;
const nextEnabled = loop || activeSlide < slidesCount - 1;
const { loop } = this;
const pagesCount = this.getPageCount();
const currentPage = this.getCurrentPage();
const prevEnabled = loop || currentPage > 0;
const nextEnabled = loop || currentPage < pagesCount - 1;
const isLtr = this.localize.dir() === 'ltr';
return html`

Wyświetl plik

@ -77,6 +77,8 @@ export class ScrollController<T extends ScrollHost> implements ReactiveControlle
})
);
this.host.requestUpdate();
} else {
this.handleScrollEnd();
}
}