diff --git a/src/components/carousel/carousel.component.ts b/src/components/carousel/carousel.component.ts index 12b7bd4a7d..4104460274 100644 --- a/src/components/carousel/carousel.component.ts +++ b/src/components/carousel/carousel.component.ts @@ -219,7 +219,7 @@ export default class SlCarousel extends ShoelaceElement { } } - handleMouseDragStart(event: MouseEvent) { + private handleMouseDragStart(event: MouseEvent) { const canDrag = this.mouseDragging && event.button === 0; if (canDrag) { event.preventDefault(); @@ -229,13 +229,11 @@ export default class SlCarousel extends ShoelaceElement { } } - handleMouseDrag = (event: MouseEvent) => { - const hasMoved = !!event.movementX || !!event.movementY; - if (!this.dragging && hasMoved) { + private handleMouseDrag = (event: MouseEvent) => { + if (!this.dragging) { // Start dragging if it hasn't yet - this.dragging = true; this.scrollContainer.style.setProperty('scroll-snap-type', 'none'); - } else { + this.dragging = true; this.scrollContainer.scrollBy({ left: -event.movementX, top: -event.movementY @@ -243,21 +241,27 @@ export default class SlCarousel extends ShoelaceElement { } }; - handleMouseDragEnd = () => { + private handleMouseDragEnd = () => { const scrollContainer = this.scrollContainer; document.removeEventListener('mousemove', this.handleMouseDrag); + // get the current scroll position const startLeft = scrollContainer.scrollLeft; const startTop = scrollContainer.scrollTop; + // remove the scroll-snap-type property so that the browser will snap the slide to the correct position scrollContainer.style.removeProperty('scroll-snap-type'); + // fix(safari): forcing a style recalculation doesn't seem to immediately update the scroll // position in Safari. Setting "overflow" to "hidden" should force this behavior. scrollContainer.style.setProperty('overflow', 'hidden'); + + // get the final scroll position to the slide snapped by the browser const finalLeft = scrollContainer.scrollLeft; const finalTop = scrollContainer.scrollTop; + // restore the scroll position to the original one, so that it can be smoothly animated if needed scrollContainer.style.removeProperty('overflow'); scrollContainer.style.setProperty('scroll-snap-type', 'none'); scrollContainer.scrollTo({ left: startLeft, top: startTop, behavior: 'instant' }); diff --git a/src/components/carousel/carousel.test.ts b/src/components/carousel/carousel.test.ts index bd581b37c4..b25c8bb2ee 100644 --- a/src/components/carousel/carousel.test.ts +++ b/src/components/carousel/carousel.test.ts @@ -1,12 +1,17 @@ import '../../../dist/shoelace.js'; -import { clickOnElement } from '../../internal/test.js'; +import { clickOnElement, dragElement, moveMouseOnElement } from '../../internal/test.js'; import { expect, fixture, html, oneEvent } from '@open-wc/testing'; import { map } from 'lit/directives/map.js'; import { range } from 'lit/directives/range.js'; +import { resetMouse, sendMouse } from '@web/test-runner-commands'; import sinon from 'sinon'; import type SlCarousel from './carousel.js'; describe('', () => { + afterEach(async () => { + await resetMouse(); + }); + it('should render a carousel with default configuration', async () => { // Arrange const el = await fixture(html` @@ -409,6 +414,51 @@ describe('', () => { }); }); + describe('when `mouse-dragging` attribute is provided', () => { + it.only('should be possible to drag the carousel using the mouse', async () => { + // Arrange + const el = await fixture(html` + + Node 1 + Node 2 + Node 3 + + `); + const carouselItem = el.querySelector('sl-carousel-item') as HTMLElement; + + // Act + await dragElement(carouselItem, -Math.round(carouselItem.offsetWidth * 0.75)); + + await oneEvent(el.scrollContainer, 'scrollend'); + await el.updateComplete; + + // Assert + expect(el.activeSlide).to.be.equal(1); + }); + + it('should be possible to interact with clickable elements', async () => { + // Arrange + const el = await fixture(html` + + + Node 2 + Node 3 + + `); + const button = el.querySelector('button')!; + + const clickSpy = sinon.spy(); + button.addEventListener('click', clickSpy); + + // Act + await moveMouseOnElement(button); + await clickOnElement(button); + + // Assert + expect(clickSpy).to.have.been.called; + }); + }); + describe('Navigation controls', () => { describe('when the user clicks the next button', () => { it('should scroll to the next slide', async () => {