From 720ae6765cc43f1af2f0cd37a923b5a4c4ac24cd Mon Sep 17 00:00:00 2001 From: David Jerleke Date: Fri, 13 Dec 2024 21:16:45 +0100 Subject: [PATCH] Implement #1043. --- .../src/components/Autoplay.ts | 2 +- .../Examples/Miscellaneous/InfiniteScroll.tsx | 4 +- .../EmblaCarouselDotButton.jsx | 2 +- .../EmblaCarouselDotButton.tsx | 2 +- .../EmblaCarouselDotButton.jsx | 2 +- .../EmblaCarouselDotButton.tsx | 2 +- .../InfiniteScroll/EmblaCarousel.jsx | 93 +++---- .../InfiniteScroll/EmblaCarousel.tsx | 34 ++- .../IosPicker/EmblaCarouselIosPickerItem.jsx | 2 +- .../IosPicker/EmblaCarouselIosPickerItem.tsx | 2 +- .../SandboxFilesDist/Thumbs/EmblaCarousel.jsx | 4 +- .../SandboxFilesDist/Thumbs/EmblaCarousel.tsx | 4 +- .../EmblaCarouselDotButton.tsx | 2 +- .../EmblaCarouselDotButton.tsx | 2 +- .../InfiniteScroll/EmblaCarousel.tsx | 34 ++- .../IosPicker/EmblaCarouselIosPickerItem.tsx | 2 +- .../SandboxFilesSrc/Thumbs/EmblaCarousel.tsx | 4 +- .../SandboxGeneratorExampleDotButton.tsx | 2 +- .../EmblaCarouselDotButton.js | 2 +- .../EmblaCarouselDotButton.ts | 2 +- .../EmblaCarouselDotButton.js | 2 +- .../EmblaCarouselDotButton.ts | 2 +- .../IosPicker/EmblaCarousel.js | 2 +- .../IosPicker/EmblaCarousel.ts | 2 +- .../Thumbs/EmblaCarouselThumbsButton.js | 4 +- .../Thumbs/EmblaCarouselThumbsButton.ts | 4 +- .../EmblaCarouselDotButton.ts | 2 +- .../SandboxFilesSrc/EmblaCarouselDotButton.ts | 2 +- .../IosPicker/EmblaCarousel.ts | 2 +- .../Thumbs/EmblaCarouselThumbsButton.ts | 4 +- .../src/content/pages/api/events.mdx | 2 +- .../src/content/pages/api/methods.mdx | 35 ++- .../fixtures/scrollToSnap-ltr.fixture.ts | 83 ++++++ .../fixtures/scrollToSnap-rtl.fixture.ts | 83 ++++++ .../fixtures/scrollToSnap-vertical.fixture.ts | 82 ++++++ .../src/__tests__/loop-ltr.test.ts | 2 +- .../src/__tests__/loop-rtl.test.ts | 12 +- .../src/__tests__/loop-vertical.test.ts | 12 +- .../src/__tests__/resize-ltr.test.ts | 4 +- .../src/__tests__/resize-rtl.test.ts | 6 +- .../src/__tests__/resize-vertical.test.ts | 6 +- .../src/__tests__/scrollBounds-ltr.test.ts | 2 +- .../src/__tests__/scrollBounds-rtl.test.ts | 2 +- .../__tests__/scrollBounds-vertical.test.ts | 2 +- .../src/__tests__/scrollToSlide-ltr.test.ts | 246 +++++++++++++++++ .../src/__tests__/scrollToSlide-rtl.test.ts | 250 ++++++++++++++++++ .../__tests__/scrollToSlide-vertical.test.ts | 250 ++++++++++++++++++ .../src/__tests__/scrollToSnap-ltr.test.ts | 117 ++++++++ .../src/__tests__/scrollToSnap-rtl.test.ts | 119 +++++++++ .../__tests__/scrollToSnap-vertical.test.ts | 119 +++++++++ .../selectedAndPreviousSnap-ltr.test.ts | 24 +- .../selectedAndPreviousSnap-rtl.test.ts | 16 +- .../selectedAndPreviousSnap-vertical.test.ts | 16 +- .../src/components/EmblaCarousel.ts | 29 +- .../src/Carousel/Carousel.tsx | 2 +- .../src/Carousel/Carousel.tsx | 2 +- .../src/Carousel/setupDots.ts | 2 +- 57 files changed, 1585 insertions(+), 169 deletions(-) create mode 100644 packages/embla-carousel/src/__tests__/fixtures/scrollToSnap-ltr.fixture.ts create mode 100644 packages/embla-carousel/src/__tests__/fixtures/scrollToSnap-rtl.fixture.ts create mode 100644 packages/embla-carousel/src/__tests__/fixtures/scrollToSnap-vertical.fixture.ts create mode 100644 packages/embla-carousel/src/__tests__/scrollToSlide-ltr.test.ts create mode 100644 packages/embla-carousel/src/__tests__/scrollToSlide-rtl.test.ts create mode 100644 packages/embla-carousel/src/__tests__/scrollToSlide-vertical.test.ts create mode 100644 packages/embla-carousel/src/__tests__/scrollToSnap-ltr.test.ts create mode 100644 packages/embla-carousel/src/__tests__/scrollToSnap-rtl.test.ts create mode 100644 packages/embla-carousel/src/__tests__/scrollToSnap-vertical.test.ts diff --git a/packages/embla-carousel-autoplay/src/components/Autoplay.ts b/packages/embla-carousel-autoplay/src/components/Autoplay.ts index e3720e09d..50fb15faa 100644 --- a/packages/embla-carousel-autoplay/src/components/Autoplay.ts +++ b/packages/embla-carousel-autoplay/src/components/Autoplay.ts @@ -204,7 +204,7 @@ function Autoplay(userOptions: AutoplayOptionsType = {}): AutoplayType { if (emblaApi.canScrollNext()) { emblaApi.scrollNext(jump) } else { - emblaApi.scrollTo(0, jump) + emblaApi.scrollToSnap(0, jump) } emblaApi.emit('autoplay:select', null) diff --git a/packages/embla-carousel-docs/src/components/Examples/Miscellaneous/InfiniteScroll.tsx b/packages/embla-carousel-docs/src/components/Examples/Miscellaneous/InfiniteScroll.tsx index b3ebb2694..9356d296f 100644 --- a/packages/embla-carousel-docs/src/components/Examples/Miscellaneous/InfiniteScroll.tsx +++ b/packages/embla-carousel-docs/src/components/Examples/Miscellaneous/InfiniteScroll.tsx @@ -16,8 +16,8 @@ const SLIDES = arrayFromNumber(5) const OPTIONS: EmblaOptionsType = { dragFree: true, containScroll: 'keepSnaps', - watchSlides: false, - watchResize: false + slideChanges: true, + resize: false } const STYLES = examplesCarouselInfiniteScrollStyles() diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/CarouselGenerator/EmblaCarouselDotButton.jsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/CarouselGenerator/EmblaCarouselDotButton.jsx index b63b0d96f..5168112ca 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/CarouselGenerator/EmblaCarouselDotButton.jsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/CarouselGenerator/EmblaCarouselDotButton.jsx @@ -12,7 +12,7 @@ export const useDotButton = ( const onDotButtonClick = useCallback( (index) => { if (!emblaApi) return - emblaApi.scrollTo(index) + emblaApi.scrollToSnap(index) /*__NAV_AUTOPLAY_REPLACE_START__*/ if (onButtonClick) onButtonClick(emblaApi) /*__NAV_AUTOPLAY_REPLACE_END__*/ diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/CarouselGenerator/EmblaCarouselDotButton.tsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/CarouselGenerator/EmblaCarouselDotButton.tsx index 8c98f8dc9..2511ffbbb 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/CarouselGenerator/EmblaCarouselDotButton.tsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/CarouselGenerator/EmblaCarouselDotButton.tsx @@ -24,7 +24,7 @@ export const useDotButton = ( const onDotButtonClick = useCallback( (index: number) => { if (!emblaApi) return - emblaApi.scrollTo(index) + emblaApi.scrollToSnap(index) /*__NAV_AUTOPLAY_REPLACE_START__*/ if (onButtonClick) onButtonClick(emblaApi) /*__NAV_AUTOPLAY_REPLACE_END__*/ diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/EmblaCarouselDotButton.jsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/EmblaCarouselDotButton.jsx index 03ebb1fb8..3a54891c9 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/EmblaCarouselDotButton.jsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/EmblaCarouselDotButton.jsx @@ -7,7 +7,7 @@ export const useDotButton = (emblaApi, onButtonClick) => { const onDotButtonClick = useCallback( (index) => { if (!emblaApi) return - emblaApi.scrollTo(index) + emblaApi.scrollToSnap(index) if (onButtonClick) onButtonClick(emblaApi) }, [emblaApi, onButtonClick] diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/EmblaCarouselDotButton.tsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/EmblaCarouselDotButton.tsx index c211e4466..a94f0cce7 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/EmblaCarouselDotButton.tsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/EmblaCarouselDotButton.tsx @@ -22,7 +22,7 @@ export const useDotButton = ( const onDotButtonClick = useCallback( (index: number) => { if (!emblaApi) return - emblaApi.scrollTo(index) + emblaApi.scrollToSnap(index) if (onButtonClick) onButtonClick(emblaApi) }, [emblaApi, onButtonClick] diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/InfiniteScroll/EmblaCarousel.jsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/InfiniteScroll/EmblaCarousel.jsx index 266e957db..4068c90c2 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/InfiniteScroll/EmblaCarousel.jsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/InfiniteScroll/EmblaCarousel.jsx @@ -21,51 +21,7 @@ const EmblaCarousel = (props) => { const [slides, setSlides] = useState(propSlides) const [hasMoreToLoad, setHasMoreToLoad] = useState(true) const [loadingMore, setLoadingMore] = useState(false) - - const [emblaRef, emblaApi] = useEmblaCarousel({ - ...options, - watchSlides: (emblaApi) => { - const reloadEmbla = () => { - const oldEngine = emblaApi.internalEngine() - - emblaApi.reInit() - const newEngine = emblaApi.internalEngine() - const copyEngineModules = [ - 'scrollBody', - 'location', - 'offsetLocation', - 'previousLocation', - 'target' - ] - copyEngineModules.forEach((engineModule) => { - Object.assign(newEngine[engineModule], oldEngine[engineModule]) - }) - - newEngine.translate.to(oldEngine.location.get()) - const { index } = newEngine.scrollTarget.byDistance(0, false) - newEngine.index.set(index) - newEngine.animation.start() - - setLoadingMore(false) - listenForScrollRef.current = true - } - - const reloadAfterPointerUp = () => { - emblaApi.off('pointerup', reloadAfterPointerUp) - reloadEmbla() - } - - const engine = emblaApi.internalEngine() - - if (hasMoreToLoadRef.current && engine.dragHandler.pointerDown()) { - const boundsActive = engine.limit.reachedMax(engine.target.get()) - engine.scrollBounds.toggleActive(boundsActive) - emblaApi.on('pointerup', reloadAfterPointerUp) - } else { - reloadEmbla() - } - } - }) + const [emblaRef, emblaApi] = useEmblaCarousel(options) const { prevBtnDisabled, @@ -74,6 +30,50 @@ const EmblaCarousel = (props) => { onNextButtonClick } = usePrevNextButtons(emblaApi) + const onSlideChanges = useCallback((emblaApi) => { + const reloadEmbla = () => { + const oldEngine = emblaApi.internalEngine() + + emblaApi.reInit() + const newEngine = emblaApi.internalEngine() + const copyEngineModules = [ + 'scrollBody', + 'location', + 'offsetLocation', + 'previousLocation', + 'target' + ] + copyEngineModules.forEach((engineModule) => { + Object.assign(newEngine[engineModule], oldEngine[engineModule]) + }) + + newEngine.translate.to(oldEngine.location.get()) + const { index } = newEngine.scrollTarget.byDistance(0, false) + newEngine.index.set(index) + newEngine.animation.start() + + setLoadingMore(false) + listenForScrollRef.current = true + } + + const reloadAfterPointerUp = () => { + emblaApi.off('pointerup', reloadAfterPointerUp) + reloadEmbla() + } + + const engine = emblaApi.internalEngine() + + if (hasMoreToLoadRef.current && engine.dragHandler.pointerDown()) { + const boundsActive = engine.limit.reachedMax(engine.target.get()) + engine.scrollBounds.toggleActive(boundsActive) + emblaApi.on('pointerup', reloadAfterPointerUp) + } else { + reloadEmbla() + } + + return false + }, []) + const onScroll = useCallback((emblaApi) => { if (!listenForScrollRef.current) return @@ -117,7 +117,8 @@ const EmblaCarousel = (props) => { const onResize = () => emblaApi.reInit() window.addEventListener('resize', onResize) emblaApi.on('destroy', () => window.removeEventListener('resize', onResize)) - }, [emblaApi, addScrollListener]) + emblaApi.onWatch('slideschanged', onSlideChanges) + }, [emblaApi, addScrollListener, onSlideChanges]) useEffect(() => { hasMoreToLoadRef.current = hasMoreToLoad diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/InfiniteScroll/EmblaCarousel.tsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/InfiniteScroll/EmblaCarousel.tsx index e7cd724ad..91da80887 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/InfiniteScroll/EmblaCarousel.tsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/InfiniteScroll/EmblaCarousel.tsx @@ -1,6 +1,10 @@ import React, { useCallback, useEffect, useRef, useState } from 'react' import { EngineType } from 'embla-carousel/components/Engine' -import { EmblaCarouselType, EmblaOptionsType } from 'embla-carousel' +import { + EmblaCarouselType, + EmblaOptionsType, + EmblaWatchCallbackType +} from 'embla-carousel' import useEmblaCarousel from 'embla-carousel-react' import { NextButton, @@ -32,10 +36,17 @@ const EmblaCarousel: React.FC = (props) => { const [slides, setSlides] = useState(propSlides) const [hasMoreToLoad, setHasMoreToLoad] = useState(true) const [loadingMore, setLoadingMore] = useState(false) + const [emblaRef, emblaApi] = useEmblaCarousel(options) - const [emblaRef, emblaApi] = useEmblaCarousel({ - ...options, - watchSlides: (emblaApi) => { + const { + prevBtnDisabled, + nextBtnDisabled, + onPrevButtonClick, + onNextButtonClick + } = usePrevNextButtons(emblaApi) + + const onSlideChanges: EmblaWatchCallbackType<'slideschanged'> = useCallback( + (emblaApi: EmblaCarouselType) => { const reloadEmbla = (): void => { const oldEngine = emblaApi.internalEngine() @@ -75,15 +86,11 @@ const EmblaCarousel: React.FC = (props) => { } else { reloadEmbla() } - } - }) - const { - prevBtnDisabled, - nextBtnDisabled, - onPrevButtonClick, - onNextButtonClick - } = usePrevNextButtons(emblaApi) + return false + }, + [] + ) const onScroll = useCallback((emblaApi: EmblaCarouselType) => { if (!listenForScrollRef.current) return @@ -128,7 +135,8 @@ const EmblaCarousel: React.FC = (props) => { const onResize = () => emblaApi.reInit() window.addEventListener('resize', onResize) emblaApi.on('destroy', () => window.removeEventListener('resize', onResize)) - }, [emblaApi, addScrollListener]) + emblaApi.onWatch('slideschanged', onSlideChanges) + }, [emblaApi, addScrollListener, onSlideChanges]) useEffect(() => { hasMoreToLoadRef.current = hasMoreToLoad diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/IosPicker/EmblaCarouselIosPickerItem.jsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/IosPicker/EmblaCarouselIosPickerItem.jsx index 4ecdd2567..5854b7ec7 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/IosPicker/EmblaCarouselIosPickerItem.jsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/IosPicker/EmblaCarouselIosPickerItem.jsx @@ -61,7 +61,7 @@ export const IosPickerItem = (props) => { axis: 'y', dragFree: true, containScroll: false, - watchSlides: false + slideChanges: false }) const rootNodeRef = useRef(null) const totalRadius = slideCount * WHEEL_ITEM_RADIUS diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/IosPicker/EmblaCarouselIosPickerItem.tsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/IosPicker/EmblaCarouselIosPickerItem.tsx index dfdcd1559..53b5c4300 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/IosPicker/EmblaCarouselIosPickerItem.tsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/IosPicker/EmblaCarouselIosPickerItem.tsx @@ -78,7 +78,7 @@ export const IosPickerItem: React.FC = (props) => { axis: 'y', dragFree: true, containScroll: false, - watchSlides: false + slideChanges: false }) const rootNodeRef = useRef(null) const totalRadius = slideCount * WHEEL_ITEM_RADIUS diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/Thumbs/EmblaCarousel.jsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/Thumbs/EmblaCarousel.jsx index abbb0d36e..242e2dd8e 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/Thumbs/EmblaCarousel.jsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/Thumbs/EmblaCarousel.jsx @@ -14,7 +14,7 @@ const EmblaCarousel = (props) => { const onThumbClick = useCallback( (index) => { if (!emblaMainApi || !emblaThumbsApi) return - emblaMainApi.scrollTo(index) + emblaMainApi.scrollToSnap(index) }, [emblaMainApi, emblaThumbsApi] ) @@ -22,7 +22,7 @@ const EmblaCarousel = (props) => { const onSelect = useCallback(() => { if (!emblaMainApi || !emblaThumbsApi) return setSelectedIndex(emblaMainApi.selectedScrollSnap()) - emblaThumbsApi.scrollTo(emblaMainApi.selectedScrollSnap()) + emblaThumbsApi.scrollToSnap(emblaMainApi.selectedScrollSnap()) }, [emblaMainApi, emblaThumbsApi, setSelectedIndex]) useEffect(() => { diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/Thumbs/EmblaCarousel.tsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/Thumbs/EmblaCarousel.tsx index 09f07f4e4..9416b2d69 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/Thumbs/EmblaCarousel.tsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesDist/Thumbs/EmblaCarousel.tsx @@ -20,7 +20,7 @@ const EmblaCarousel: React.FC = (props) => { const onThumbClick = useCallback( (index: number) => { if (!emblaMainApi || !emblaThumbsApi) return - emblaMainApi.scrollTo(index) + emblaMainApi.scrollToSnap(index) }, [emblaMainApi, emblaThumbsApi] ) @@ -28,7 +28,7 @@ const EmblaCarousel: React.FC = (props) => { const onSelect = useCallback(() => { if (!emblaMainApi || !emblaThumbsApi) return setSelectedIndex(emblaMainApi.selectedScrollSnap()) - emblaThumbsApi.scrollTo(emblaMainApi.selectedScrollSnap()) + emblaThumbsApi.scrollToSnap(emblaMainApi.selectedScrollSnap()) }, [emblaMainApi, emblaThumbsApi, setSelectedIndex]) useEffect(() => { diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/CarouselGenerator/EmblaCarouselDotButton.tsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/CarouselGenerator/EmblaCarouselDotButton.tsx index 8c98f8dc9..2511ffbbb 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/CarouselGenerator/EmblaCarouselDotButton.tsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/CarouselGenerator/EmblaCarouselDotButton.tsx @@ -24,7 +24,7 @@ export const useDotButton = ( const onDotButtonClick = useCallback( (index: number) => { if (!emblaApi) return - emblaApi.scrollTo(index) + emblaApi.scrollToSnap(index) /*__NAV_AUTOPLAY_REPLACE_START__*/ if (onButtonClick) onButtonClick(emblaApi) /*__NAV_AUTOPLAY_REPLACE_END__*/ diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/EmblaCarouselDotButton.tsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/EmblaCarouselDotButton.tsx index c211e4466..a94f0cce7 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/EmblaCarouselDotButton.tsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/EmblaCarouselDotButton.tsx @@ -22,7 +22,7 @@ export const useDotButton = ( const onDotButtonClick = useCallback( (index: number) => { if (!emblaApi) return - emblaApi.scrollTo(index) + emblaApi.scrollToSnap(index) if (onButtonClick) onButtonClick(emblaApi) }, [emblaApi, onButtonClick] diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/InfiniteScroll/EmblaCarousel.tsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/InfiniteScroll/EmblaCarousel.tsx index e7cd724ad..91da80887 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/InfiniteScroll/EmblaCarousel.tsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/InfiniteScroll/EmblaCarousel.tsx @@ -1,6 +1,10 @@ import React, { useCallback, useEffect, useRef, useState } from 'react' import { EngineType } from 'embla-carousel/components/Engine' -import { EmblaCarouselType, EmblaOptionsType } from 'embla-carousel' +import { + EmblaCarouselType, + EmblaOptionsType, + EmblaWatchCallbackType +} from 'embla-carousel' import useEmblaCarousel from 'embla-carousel-react' import { NextButton, @@ -32,10 +36,17 @@ const EmblaCarousel: React.FC = (props) => { const [slides, setSlides] = useState(propSlides) const [hasMoreToLoad, setHasMoreToLoad] = useState(true) const [loadingMore, setLoadingMore] = useState(false) + const [emblaRef, emblaApi] = useEmblaCarousel(options) - const [emblaRef, emblaApi] = useEmblaCarousel({ - ...options, - watchSlides: (emblaApi) => { + const { + prevBtnDisabled, + nextBtnDisabled, + onPrevButtonClick, + onNextButtonClick + } = usePrevNextButtons(emblaApi) + + const onSlideChanges: EmblaWatchCallbackType<'slideschanged'> = useCallback( + (emblaApi: EmblaCarouselType) => { const reloadEmbla = (): void => { const oldEngine = emblaApi.internalEngine() @@ -75,15 +86,11 @@ const EmblaCarousel: React.FC = (props) => { } else { reloadEmbla() } - } - }) - const { - prevBtnDisabled, - nextBtnDisabled, - onPrevButtonClick, - onNextButtonClick - } = usePrevNextButtons(emblaApi) + return false + }, + [] + ) const onScroll = useCallback((emblaApi: EmblaCarouselType) => { if (!listenForScrollRef.current) return @@ -128,7 +135,8 @@ const EmblaCarousel: React.FC = (props) => { const onResize = () => emblaApi.reInit() window.addEventListener('resize', onResize) emblaApi.on('destroy', () => window.removeEventListener('resize', onResize)) - }, [emblaApi, addScrollListener]) + emblaApi.onWatch('slideschanged', onSlideChanges) + }, [emblaApi, addScrollListener, onSlideChanges]) useEffect(() => { hasMoreToLoadRef.current = hasMoreToLoad diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/IosPicker/EmblaCarouselIosPickerItem.tsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/IosPicker/EmblaCarouselIosPickerItem.tsx index dfdcd1559..53b5c4300 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/IosPicker/EmblaCarouselIosPickerItem.tsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/IosPicker/EmblaCarouselIosPickerItem.tsx @@ -78,7 +78,7 @@ export const IosPickerItem: React.FC = (props) => { axis: 'y', dragFree: true, containScroll: false, - watchSlides: false + slideChanges: false }) const rootNodeRef = useRef(null) const totalRadius = slideCount * WHEEL_ITEM_RADIUS diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Thumbs/EmblaCarousel.tsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Thumbs/EmblaCarousel.tsx index 09f07f4e4..9416b2d69 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Thumbs/EmblaCarousel.tsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Thumbs/EmblaCarousel.tsx @@ -20,7 +20,7 @@ const EmblaCarousel: React.FC = (props) => { const onThumbClick = useCallback( (index: number) => { if (!emblaMainApi || !emblaThumbsApi) return - emblaMainApi.scrollTo(index) + emblaMainApi.scrollToSnap(index) }, [emblaMainApi, emblaThumbsApi] ) @@ -28,7 +28,7 @@ const EmblaCarousel: React.FC = (props) => { const onSelect = useCallback(() => { if (!emblaMainApi || !emblaThumbsApi) return setSelectedIndex(emblaMainApi.selectedScrollSnap()) - emblaThumbsApi.scrollTo(emblaMainApi.selectedScrollSnap()) + emblaThumbsApi.scrollToSnap(emblaMainApi.selectedScrollSnap()) }, [emblaMainApi, emblaThumbsApi, setSelectedIndex]) useEffect(() => { diff --git a/packages/embla-carousel-docs/src/components/Sandbox/SandboxGeneratorExampleDotButton.tsx b/packages/embla-carousel-docs/src/components/Sandbox/SandboxGeneratorExampleDotButton.tsx index 0d6065b93..e93a7b541 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/SandboxGeneratorExampleDotButton.tsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/SandboxGeneratorExampleDotButton.tsx @@ -22,7 +22,7 @@ export const useDotButton = ( const onDotButtonClick = useCallback( (index: number) => { if (!emblaApi) return - emblaApi.scrollTo(index) + emblaApi.scrollToSnap(index) if (onButtonClick) onButtonClick(emblaApi) }, [emblaApi, onButtonClick] diff --git a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/CarouselGenerator/EmblaCarouselDotButton.js b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/CarouselGenerator/EmblaCarouselDotButton.js index 174898375..d80ac83b5 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/CarouselGenerator/EmblaCarouselDotButton.js +++ b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/CarouselGenerator/EmblaCarouselDotButton.js @@ -14,7 +14,7 @@ export const addDotBtnsAndClickHandlers = ( .join('') const scrollTo = (index) => { - emblaApi.scrollTo(index) + emblaApi.scrollToSnap(index) /*__NAV_AUTOPLAY_REPLACE_START__*/ if (onButtonClick) onButtonClick(emblaApi) /*__NAV_AUTOPLAY_REPLACE_END__*/ diff --git a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/CarouselGenerator/EmblaCarouselDotButton.ts b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/CarouselGenerator/EmblaCarouselDotButton.ts index bb27a2599..7b80c4d24 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/CarouselGenerator/EmblaCarouselDotButton.ts +++ b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/CarouselGenerator/EmblaCarouselDotButton.ts @@ -16,7 +16,7 @@ export const addDotBtnsAndClickHandlers = ( .join('') const scrollTo = (index: number): void => { - emblaApi.scrollTo(index) + emblaApi.scrollToSnap(index) /*__NAV_AUTOPLAY_REPLACE_START__*/ if (onButtonClick) onButtonClick(emblaApi) /*__NAV_AUTOPLAY_REPLACE_END__*/ diff --git a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/EmblaCarouselDotButton.js b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/EmblaCarouselDotButton.js index 21a22933b..3d8935226 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/EmblaCarouselDotButton.js +++ b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/EmblaCarouselDotButton.js @@ -8,7 +8,7 @@ export const addDotBtnsAndClickHandlers = (emblaApi, dotsNode) => { .join('') const scrollTo = (index) => { - emblaApi.scrollTo(index) + emblaApi.scrollToSnap(index) } dotNodes = Array.from(dotsNode.querySelectorAll('.embla__dot')) diff --git a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/EmblaCarouselDotButton.ts b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/EmblaCarouselDotButton.ts index f24632dec..ee4bb9f61 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/EmblaCarouselDotButton.ts +++ b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/EmblaCarouselDotButton.ts @@ -13,7 +13,7 @@ export const addDotBtnsAndClickHandlers = ( .join('') const scrollTo = (index: number): void => { - emblaApi.scrollTo(index) + emblaApi.scrollToSnap(index) } dotNodes = Array.from(dotsNode.querySelectorAll('.embla__dot')) diff --git a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/IosPicker/EmblaCarousel.js b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/IosPicker/EmblaCarousel.js index 9d31f17ce..be288abca 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/IosPicker/EmblaCarousel.js +++ b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/IosPicker/EmblaCarousel.js @@ -15,6 +15,6 @@ const iosPickers = iosPickerNodes.map((iosPickerNode) => containScroll: false, loop: LOOP, axis: 'y', - watchSlides: false + slideChanges: false }) ) diff --git a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/IosPicker/EmblaCarousel.ts b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/IosPicker/EmblaCarousel.ts index de0f20acf..7b4d6a4fb 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/IosPicker/EmblaCarousel.ts +++ b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/IosPicker/EmblaCarousel.ts @@ -15,6 +15,6 @@ const iosPickers = iosPickerNodes.map((iosPickerNode) => containScroll: false, loop: LOOP, axis: 'y', - watchSlides: false + slideChanges: false }) ) diff --git a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/Thumbs/EmblaCarouselThumbsButton.js b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/Thumbs/EmblaCarouselThumbsButton.js index bb5278b5c..bf03a5c3f 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/Thumbs/EmblaCarouselThumbsButton.js +++ b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/Thumbs/EmblaCarouselThumbsButton.js @@ -2,7 +2,7 @@ export const addThumbBtnsClickHandlers = (emblaApiMain, emblaApiThumb) => { const slidesThumbs = emblaApiThumb.slideNodes() const scrollToIndex = slidesThumbs.map( - (_, index) => () => emblaApiMain.scrollTo(index) + (_, index) => () => emblaApiMain.scrollToSnap(index) ) slidesThumbs.forEach((slideNode, index) => { @@ -20,7 +20,7 @@ export const addToggleThumbBtnsActive = (emblaApiMain, emblaApiThumb) => { const slidesThumbs = emblaApiThumb.slideNodes() const toggleThumbBtnsState = () => { - emblaApiThumb.scrollTo(emblaApiMain.selectedScrollSnap()) + emblaApiThumb.scrollToSnap(emblaApiMain.selectedScrollSnap()) const previous = emblaApiMain.previousScrollSnap() const selected = emblaApiMain.selectedScrollSnap() slidesThumbs[previous].classList.remove('embla-thumbs__slide--selected') diff --git a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/Thumbs/EmblaCarouselThumbsButton.ts b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/Thumbs/EmblaCarouselThumbsButton.ts index bb2ba069d..e23bc8b77 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/Thumbs/EmblaCarouselThumbsButton.ts +++ b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesDist/Thumbs/EmblaCarouselThumbsButton.ts @@ -7,7 +7,7 @@ export const addThumbBtnsClickHandlers = ( const slidesThumbs = emblaApiThumb.slideNodes() const scrollToIndex = slidesThumbs.map( - (_, index) => (): void => emblaApiMain.scrollTo(index) + (_, index) => (): void => emblaApiMain.scrollToSnap(index) ) slidesThumbs.forEach((slideNode, index) => { @@ -28,7 +28,7 @@ export const addToggleThumbBtnsActive = ( const slidesThumbs = emblaApiThumb.slideNodes() const toggleThumbBtnsState = (): void => { - emblaApiThumb.scrollTo(emblaApiMain.selectedScrollSnap()) + emblaApiThumb.scrollToSnap(emblaApiMain.selectedScrollSnap()) const previous = emblaApiMain.previousScrollSnap() const selected = emblaApiMain.selectedScrollSnap() slidesThumbs[previous].classList.remove('embla-thumbs__slide--selected') diff --git a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/CarouselGenerator/EmblaCarouselDotButton.ts b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/CarouselGenerator/EmblaCarouselDotButton.ts index bb27a2599..7b80c4d24 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/CarouselGenerator/EmblaCarouselDotButton.ts +++ b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/CarouselGenerator/EmblaCarouselDotButton.ts @@ -16,7 +16,7 @@ export const addDotBtnsAndClickHandlers = ( .join('') const scrollTo = (index: number): void => { - emblaApi.scrollTo(index) + emblaApi.scrollToSnap(index) /*__NAV_AUTOPLAY_REPLACE_START__*/ if (onButtonClick) onButtonClick(emblaApi) /*__NAV_AUTOPLAY_REPLACE_END__*/ diff --git a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/EmblaCarouselDotButton.ts b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/EmblaCarouselDotButton.ts index f24632dec..ee4bb9f61 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/EmblaCarouselDotButton.ts +++ b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/EmblaCarouselDotButton.ts @@ -13,7 +13,7 @@ export const addDotBtnsAndClickHandlers = ( .join('') const scrollTo = (index: number): void => { - emblaApi.scrollTo(index) + emblaApi.scrollToSnap(index) } dotNodes = Array.from(dotsNode.querySelectorAll('.embla__dot')) diff --git a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/IosPicker/EmblaCarousel.ts b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/IosPicker/EmblaCarousel.ts index de0f20acf..7b4d6a4fb 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/IosPicker/EmblaCarousel.ts +++ b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/IosPicker/EmblaCarousel.ts @@ -15,6 +15,6 @@ const iosPickers = iosPickerNodes.map((iosPickerNode) => containScroll: false, loop: LOOP, axis: 'y', - watchSlides: false + slideChanges: false }) ) diff --git a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Thumbs/EmblaCarouselThumbsButton.ts b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Thumbs/EmblaCarouselThumbsButton.ts index bb2ba069d..e23bc8b77 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Thumbs/EmblaCarouselThumbsButton.ts +++ b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Thumbs/EmblaCarouselThumbsButton.ts @@ -7,7 +7,7 @@ export const addThumbBtnsClickHandlers = ( const slidesThumbs = emblaApiThumb.slideNodes() const scrollToIndex = slidesThumbs.map( - (_, index) => (): void => emblaApiMain.scrollTo(index) + (_, index) => (): void => emblaApiMain.scrollToSnap(index) ) slidesThumbs.forEach((slideNode, index) => { @@ -28,7 +28,7 @@ export const addToggleThumbBtnsActive = ( const slidesThumbs = emblaApiThumb.slideNodes() const toggleThumbBtnsState = (): void => { - emblaApiThumb.scrollTo(emblaApiMain.selectedScrollSnap()) + emblaApiThumb.scrollToSnap(emblaApiMain.selectedScrollSnap()) const previous = emblaApiMain.previousScrollSnap() const selected = emblaApiMain.selectedScrollSnap() slidesThumbs[previous].classList.remove('embla-thumbs__slide--selected') diff --git a/packages/embla-carousel-docs/src/content/pages/api/events.mdx b/packages/embla-carousel-docs/src/content/pages/api/events.mdx index e24c75029..416a0b946 100644 --- a/packages/embla-carousel-docs/src/content/pages/api/events.mdx +++ b/packages/embla-carousel-docs/src/content/pages/api/events.mdx @@ -510,7 +510,7 @@ Runs when the carousel has been destroyed using the [destroy](/api/methods/#dest Detail: `null` -Runs when the selected scroll snap changes. The select event is triggered by drag interactions or the [scrollNext](/api/methods/#scrollnext), [scrollPrev](/api/methods/#scrollPrev) or [scrollTo](/api/methods/#scrollto) methods. +Runs when the selected scroll snap changes. The select event is triggered by drag interactions or the [scrollNext](/api/methods/#scrollnext), [scrollPrev](/api/methods/#scrollPrev), [scrollToSnap](/api/methods/#scrolltosnap) or [scrollToSlide](/api/methods/#scrolltoslide) methods. --- diff --git a/packages/embla-carousel-docs/src/content/pages/api/methods.mdx b/packages/embla-carousel-docs/src/content/pages/api/methods.mdx index ff56fa1ec..39f4fb38a 100644 --- a/packages/embla-carousel-docs/src/content/pages/api/methods.mdx +++ b/packages/embla-carousel-docs/src/content/pages/api/methods.mdx @@ -307,7 +307,7 @@ Get all the slide nodes inside the container. This method can be useful when you Parameters: `jump?: boolean` Returns: `void` -Scroll to the next snap point if possible. When [loop](/api/options/#loop) is disabled and the carousel has reached the last snap point, this method won't do anything. Set the **jump** parameter to `true` when you want to go to the next slide instantly. +Scroll to the next scroll snap if possible. When [loop](/api/options/#loop) is disabled and the carousel has reached the last scroll snap, this method won't do anything. Set the **jump** parameter to `true` when you want to go to the next scroll snap instantly. --- @@ -316,16 +316,33 @@ Scroll to the next snap point if possible. When [loop](/api/options/#loop) is di Parameters: `jump?: boolean` Returns: `void` -Scroll to the previous snap point if possible. When [loop](/api/options/#loop) is disabled and the carousel has reached the first snap point, this method won't do anything. Set the **jump** parameter to `true` when you want to go to the previous slide instantly. +Scroll to the previous scroll snap if possible. When [loop](/api/options/#loop) is disabled and the carousel has reached the first scroll snap, this method won't do anything. Set the **jump** parameter to `true` when you want to go to the previous scroll snap instantly. --- -### scrollTo +### scrollToSnap Parameters: `index: number`, `jump?: boolean` Returns: `void` -Scroll to a snap point by its unique index. If [loop](/api/options/#loop) is enabled, Embla Carousel will choose the closest way to the target snap point. Set the **jump** parameter to `true` when you want to go to the desired slide instantly. +Scroll to a scroll snap by its unique index. If [loop](/api/options/#loop) is enabled, Embla Carousel will choose the closest way to the target scroll snap. Set the **jump** parameter to `true` when you want to go to the desired scroll snap instantly. + + + **Note:** A scroll snap isn't equvialent to a slide. A scroll snap can hold + multiple slides based on what options are set. For example, if + [slideToScroll](/api/options/#slidetoscroll) is set to anything more than `1` + or `auto` and multiple slides fit inside the viewport, a scroll snap will hold + multiple slides. + + +--- + +### scrollToSlide + +Parameters: `subject: number | HTMLElement`, `jump?: boolean` +Returns: `void` + +Scroll to a scroll snap by providing a slide index or a slide element. If [loop](/api/options/#loop) is enabled, Embla Carousel will choose the closest way to the target scroll snap. Set the **jump** parameter to `true` when you want to go to the desired scroll snap instantly. --- @@ -334,7 +351,7 @@ Scroll to a snap point by its unique index. If [loop](/api/options/#loop) is ena Parameters: `none` Returns: `boolean` -Check the possiblity to scroll to a next snap point. If [loop](/api/options/#loop) is enabled and the container holds any slides, this will always return `true`. +Check the possiblity to scroll to the next scroll snap. If [loop](/api/options/#loop) is enabled and the container holds any slides, this will always return `true`. --- @@ -343,7 +360,7 @@ Check the possiblity to scroll to a next snap point. If [loop](/api/options/#loo Parameters: `none` Returns: `boolean` -Check the possiblity to scroll to a previous snap point. If [loop](/api/options/#loop) is enabled and the container holds any slides, this will always return `true`. +Check the possiblity to scroll to the previous scroll snap. If [loop](/api/options/#loop) is enabled and the container holds any slides, this will always return `true`. --- @@ -352,7 +369,7 @@ Check the possiblity to scroll to a previous snap point. If [loop](/api/options/ Parameters: `none` Returns: `number` -Get the index of the selected snap point. +Get the index of the selected scroll snap. --- @@ -361,7 +378,7 @@ Get the index of the selected snap point. Parameters: `none` Returns: `number` -Get the index of the previously selected snap point. +Get the index of the previously selected scroll snap. --- @@ -370,7 +387,7 @@ Get the index of the previously selected snap point. Parameters: `none` Returns: `number[]` -Get an array containing all the snap point positions. Each position represents how far the carousel needs to progress in order to reach this position. +Get an array containing all the scroll snap positions. Each position represents how far the carousel needs to progress in order to reach this position. --- diff --git a/packages/embla-carousel/src/__tests__/fixtures/scrollToSnap-ltr.fixture.ts b/packages/embla-carousel/src/__tests__/fixtures/scrollToSnap-ltr.fixture.ts new file mode 100644 index 000000000..51f93aa26 --- /dev/null +++ b/packages/embla-carousel/src/__tests__/fixtures/scrollToSnap-ltr.fixture.ts @@ -0,0 +1,83 @@ +import { TestElementDimensionsType } from '../mocks/testElements.mock' + +/* +Fixture 1 + +- Horizontal +- LTR +- No slide margins +*/ +export const FIXTURE_SCROLL_TO_SNAP_LTR: TestElementDimensionsType = { + containerOffset: { + offsetWidth: 1000, + offsetHeight: 190, + offsetTop: 0, + offsetLeft: 0 + }, + slideOffsets: [ + { + offsetWidth: 500, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: 0 + }, + { + offsetWidth: 500, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: 500 + }, + { + offsetWidth: 250, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: 1000 + }, + { + offsetWidth: 250, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: 1250 + }, + { + offsetWidth: 250, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: 1500 + }, + { + offsetWidth: 250, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: 1750 + }, + { + offsetWidth: 500, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: 2000 + }, + { + offsetWidth: 500, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: 2500 + }, + { + offsetWidth: 500, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: 3000 + }, + { + offsetWidth: 501, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: 3500 + } + ], + endMargin: { + property: 'marginRight', + value: 0 + } +} diff --git a/packages/embla-carousel/src/__tests__/fixtures/scrollToSnap-rtl.fixture.ts b/packages/embla-carousel/src/__tests__/fixtures/scrollToSnap-rtl.fixture.ts new file mode 100644 index 000000000..81cf23bd4 --- /dev/null +++ b/packages/embla-carousel/src/__tests__/fixtures/scrollToSnap-rtl.fixture.ts @@ -0,0 +1,83 @@ +import { TestElementDimensionsType } from '../mocks/testElements.mock' + +/* +Fixture 1 + +- Horizontal +- RTL +- No slide margins +*/ +export const FIXTURE_SCROLL_TO_SNAP_RTL: TestElementDimensionsType = { + containerOffset: { + offsetWidth: 1000, + offsetHeight: 190, + offsetTop: 0, + offsetLeft: 0 + }, + slideOffsets: [ + { + offsetWidth: 500, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: 500 + }, + { + offsetWidth: 500, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: 0 + }, + { + offsetWidth: 250, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: -250 + }, + { + offsetWidth: 250, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: -500 + }, + { + offsetWidth: 250, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: -750 + }, + { + offsetWidth: 250, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: -1000 + }, + { + offsetWidth: 500, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: -1500 + }, + { + offsetWidth: 500, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: -2000 + }, + { + offsetWidth: 500, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: -2500 + }, + { + offsetWidth: 501, + offsetHeight: 0, + offsetTop: 0, + offsetLeft: -3001 + } + ], + endMargin: { + property: 'marginLeft', + value: 0 + } +} diff --git a/packages/embla-carousel/src/__tests__/fixtures/scrollToSnap-vertical.fixture.ts b/packages/embla-carousel/src/__tests__/fixtures/scrollToSnap-vertical.fixture.ts new file mode 100644 index 000000000..186dd5345 --- /dev/null +++ b/packages/embla-carousel/src/__tests__/fixtures/scrollToSnap-vertical.fixture.ts @@ -0,0 +1,82 @@ +import { TestElementDimensionsType } from '../mocks/testElements.mock' + +/* +Fixture 1 + +- Vertical +- No slide margins +*/ +export const FIXTURE_SCROLL_TO_SNAP_Y: TestElementDimensionsType = { + containerOffset: { + offsetWidth: 1000, + offsetHeight: 1000, + offsetTop: 0, + offsetLeft: 0 + }, + slideOffsets: [ + { + offsetWidth: 1000, + offsetHeight: 500, + offsetTop: 0, + offsetLeft: 0 + }, + { + offsetWidth: 1000, + offsetHeight: 500, + offsetTop: 500, + offsetLeft: 0 + }, + { + offsetWidth: 1000, + offsetHeight: 250, + offsetTop: 1000, + offsetLeft: 0 + }, + { + offsetWidth: 1000, + offsetHeight: 250, + offsetTop: 1250, + offsetLeft: 0 + }, + { + offsetWidth: 1000, + offsetHeight: 250, + offsetTop: 1500, + offsetLeft: 0 + }, + { + offsetWidth: 1000, + offsetHeight: 250, + offsetTop: 1750, + offsetLeft: 0 + }, + { + offsetWidth: 1000, + offsetHeight: 500, + offsetTop: 2000, + offsetLeft: 0 + }, + { + offsetWidth: 1000, + offsetHeight: 500, + offsetTop: 2500, + offsetLeft: 0 + }, + { + offsetWidth: 1000, + offsetHeight: 500, + offsetTop: 3000, + offsetLeft: 0 + }, + { + offsetWidth: 1000, + offsetHeight: 501, + offsetTop: 3500, + offsetLeft: 0 + } + ], + endMargin: { + property: 'marginBottom', + value: 0 + } +} diff --git a/packages/embla-carousel/src/__tests__/loop-ltr.test.ts b/packages/embla-carousel/src/__tests__/loop-ltr.test.ts index 3b0a771cf..b61dbac4a 100644 --- a/packages/embla-carousel/src/__tests__/loop-ltr.test.ts +++ b/packages/embla-carousel/src/__tests__/loop-ltr.test.ts @@ -6,7 +6,7 @@ import { FIXTURE_LOOP_LTR_2 } from './fixtures/loop-ltr.fixture' -export const scrollToLocationInstant = ( +const scrollToLocationInstant = ( engine: EngineType, location: number ): void => { diff --git a/packages/embla-carousel/src/__tests__/loop-rtl.test.ts b/packages/embla-carousel/src/__tests__/loop-rtl.test.ts index 62c30fdf2..b0cc264e1 100644 --- a/packages/embla-carousel/src/__tests__/loop-rtl.test.ts +++ b/packages/embla-carousel/src/__tests__/loop-rtl.test.ts @@ -1,11 +1,21 @@ import EmblaCarousel from '../components/EmblaCarousel' +import { EngineType } from '../components/Engine' import { mockTestElements } from './mocks' -import { scrollToLocationInstant } from './loop-ltr.test' import { FIXTURE_LOOP_RTL_1, FIXTURE_LOOP_RTL_2 } from './fixtures/loop-rtl.fixture' +const scrollToLocationInstant = ( + engine: EngineType, + location: number +): void => { + engine.target.set(location) + engine.scrollBody.useDuration(0) + engine.animation.update() + engine.animation.render(1) +} + describe('➡️ Loop - Horizontal RTL', () => { const WRAP_AROUND_JOINT_SAFETY = 0.1 diff --git a/packages/embla-carousel/src/__tests__/loop-vertical.test.ts b/packages/embla-carousel/src/__tests__/loop-vertical.test.ts index 81e274f5a..f5627c678 100644 --- a/packages/embla-carousel/src/__tests__/loop-vertical.test.ts +++ b/packages/embla-carousel/src/__tests__/loop-vertical.test.ts @@ -1,11 +1,21 @@ import EmblaCarousel from '../components/EmblaCarousel' +import { EngineType } from '../components/Engine' import { mockTestElements } from './mocks' -import { scrollToLocationInstant } from './loop-ltr.test' import { FIXTURE_LOOP_Y_1, FIXTURE_LOOP_Y_2 } from './fixtures/loop-vertical.fixture' +const scrollToLocationInstant = ( + engine: EngineType, + location: number +): void => { + engine.target.set(location) + engine.scrollBody.useDuration(0) + engine.animation.update() + engine.animation.render(1) +} + describe('➡️ Loop - Vertical', () => { const WRAP_AROUND_JOINT_SAFETY = 0.1 diff --git a/packages/embla-carousel/src/__tests__/resize-ltr.test.ts b/packages/embla-carousel/src/__tests__/resize-ltr.test.ts index 2475faf81..25a7513ab 100644 --- a/packages/embla-carousel/src/__tests__/resize-ltr.test.ts +++ b/packages/embla-carousel/src/__tests__/resize-ltr.test.ts @@ -3,8 +3,8 @@ import { mockTestElements } from './mocks' import { triggerResizeObserver } from './mocks/resizeObserver.mock' import { FIXTURE_RESIZE_LTR } from './fixtures/resize-ltr.fixture' -export const RESIZE_TRIGGER_THRESHOLD = 0.5 -export const BELOW_RESIZE_TRIGGER_THRESHOLD = 0.49 +const RESIZE_TRIGGER_THRESHOLD = 0.5 +const BELOW_RESIZE_TRIGGER_THRESHOLD = 0.49 describe('➡️ Resize - Horizontal LTR', () => { describe('When a slide is resized and the RESIZE option is set to TRUE', () => { diff --git a/packages/embla-carousel/src/__tests__/resize-rtl.test.ts b/packages/embla-carousel/src/__tests__/resize-rtl.test.ts index 778b89005..9cead9345 100644 --- a/packages/embla-carousel/src/__tests__/resize-rtl.test.ts +++ b/packages/embla-carousel/src/__tests__/resize-rtl.test.ts @@ -3,10 +3,8 @@ import { mockTestElements } from './mocks' import { triggerResizeObserver } from './mocks/resizeObserver.mock' import { FIXTURE_RESIZE_RTL } from './fixtures/resize-rtl.fixture' -import { - BELOW_RESIZE_TRIGGER_THRESHOLD, - RESIZE_TRIGGER_THRESHOLD -} from './resize-ltr.test' +export const RESIZE_TRIGGER_THRESHOLD = 0.5 +export const BELOW_RESIZE_TRIGGER_THRESHOLD = 0.49 describe('➡️ Resize - Horizontal RTL', () => { describe('When a slide is resized and the RESIZE option is set to TRUE', () => { diff --git a/packages/embla-carousel/src/__tests__/resize-vertical.test.ts b/packages/embla-carousel/src/__tests__/resize-vertical.test.ts index 18fc0efd2..24c68c3e1 100644 --- a/packages/embla-carousel/src/__tests__/resize-vertical.test.ts +++ b/packages/embla-carousel/src/__tests__/resize-vertical.test.ts @@ -3,10 +3,8 @@ import { mockTestElements } from './mocks' import { triggerResizeObserver } from './mocks/resizeObserver.mock' import { FIXTURE_RESIZE_Y } from './fixtures/resize-vertical.fixture' -import { - BELOW_RESIZE_TRIGGER_THRESHOLD, - RESIZE_TRIGGER_THRESHOLD -} from './resize-ltr.test' +export const RESIZE_TRIGGER_THRESHOLD = 0.5 +export const BELOW_RESIZE_TRIGGER_THRESHOLD = 0.49 describe('➡️ Resize - Vertical', () => { describe('When a slide is resized and the RESIZE option is set to TRUE', () => { diff --git a/packages/embla-carousel/src/__tests__/scrollBounds-ltr.test.ts b/packages/embla-carousel/src/__tests__/scrollBounds-ltr.test.ts index 10ee47c09..9ceef2a54 100644 --- a/packages/embla-carousel/src/__tests__/scrollBounds-ltr.test.ts +++ b/packages/embla-carousel/src/__tests__/scrollBounds-ltr.test.ts @@ -3,7 +3,7 @@ import { mockTestElements } from './mocks' import { SCROLL_BOUNDS_LTR_1 } from './fixtures/scrollBounds-ltr.fixture' import { EngineType } from '../components/Engine' -export const setLocationOutOfBounds = ( +const setLocationOutOfBounds = ( engine: EngineType, outOfBoundsLocation: number ): void => { diff --git a/packages/embla-carousel/src/__tests__/scrollBounds-rtl.test.ts b/packages/embla-carousel/src/__tests__/scrollBounds-rtl.test.ts index f268c77eb..211ed1e2f 100644 --- a/packages/embla-carousel/src/__tests__/scrollBounds-rtl.test.ts +++ b/packages/embla-carousel/src/__tests__/scrollBounds-rtl.test.ts @@ -3,7 +3,7 @@ import { mockTestElements } from './mocks' import { SCROLL_BOUNDS_RTL_1 } from './fixtures/scrollBounds-rtl.fixture' import { EngineType } from '../components/Engine' -export const setLocationOutOfBounds = ( +const setLocationOutOfBounds = ( engine: EngineType, outOfBoundsLocation: number ): void => { diff --git a/packages/embla-carousel/src/__tests__/scrollBounds-vertical.test.ts b/packages/embla-carousel/src/__tests__/scrollBounds-vertical.test.ts index 1daddfaa0..53f1680ec 100644 --- a/packages/embla-carousel/src/__tests__/scrollBounds-vertical.test.ts +++ b/packages/embla-carousel/src/__tests__/scrollBounds-vertical.test.ts @@ -3,7 +3,7 @@ import { mockTestElements } from './mocks' import { SCROLL_BOUNDS_Y_1 } from './fixtures/scrollBounds-vertical.fixture' import { EngineType } from '../components/Engine' -export const setLocationOutOfBounds = ( +const setLocationOutOfBounds = ( engine: EngineType, outOfBoundsLocation: number ): void => { diff --git a/packages/embla-carousel/src/__tests__/scrollToSlide-ltr.test.ts b/packages/embla-carousel/src/__tests__/scrollToSlide-ltr.test.ts new file mode 100644 index 000000000..9d4ebef1d --- /dev/null +++ b/packages/embla-carousel/src/__tests__/scrollToSlide-ltr.test.ts @@ -0,0 +1,246 @@ +import EmblaCarousel from '../components/EmblaCarousel' +import { mockTestElements } from './mocks' +import { FIXTURE_SCROLL_TO_SNAP_LTR } from './fixtures/scrollToSnap-ltr.fixture' + +const SLIDE_TO_SNAP_INDEX_MAP: { [key: number]: number } = { + 0: 0, + 1: 0, + 2: 1, + 3: 1, + 4: 1, + 5: 1, + 6: 2, + 7: 2, + 8: 3, + 9: 3 +} + +describe('➡️ ScrollToSlide - Horizontal LTR', () => { + describe('Starts scrolling to correct snap when jump parameter is FALSE and slidesToScroll is:', () => { + test('Default (1), and slide index is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_LTR) + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const animationStart = jest.spyOn( + emblaApi.internalEngine().animation, + 'start' + ) + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideIndex) + expect(emblaApi.selectedScrollSnap()).toBe(slideIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[slideIndex] + ) + expect(animationStart).toHaveBeenCalledTimes(slideIndex) + slideIndex += 1 + } + + done() + }) + + test('Default (1), and slide element is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_LTR) + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const slideNodes = emblaApi.slideNodes() + const animationStart = jest.spyOn( + emblaApi.internalEngine().animation, + 'start' + ) + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideNodes[slideIndex]) + expect(emblaApi.selectedScrollSnap()).toBe(slideIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[slideIndex] + ) + expect(animationStart).toHaveBeenCalledTimes(slideIndex) + slideIndex += 1 + } + + done() + }) + + test('"auto" and slide index is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_LTR), + { slidesToScroll: 'auto' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const animationStart = jest.spyOn( + emblaApi.internalEngine().animation, + 'start' + ) + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideIndex) + const snapIndex = SLIDE_TO_SNAP_INDEX_MAP[slideIndex] + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(animationStart).toHaveBeenCalledTimes(snapIndex) + slideIndex += 1 + } + + done() + }) + + test('"auto" and slide element is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_LTR), + { slidesToScroll: 'auto' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const slideNodes = emblaApi.slideNodes() + const animationStart = jest.spyOn( + emblaApi.internalEngine().animation, + 'start' + ) + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideNodes[slideIndex]) + const snapIndex = SLIDE_TO_SNAP_INDEX_MAP[slideIndex] + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(animationStart).toHaveBeenCalledTimes(snapIndex) + slideIndex += 1 + } + + done() + }) + }) + + describe('Instantly scrolls to correct snap when jump parameter is TRUE and slidesToScroll is:', () => { + test('Default (1), and slide index is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_LTR) + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const container = emblaApi.containerNode() + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideIndex, true) + expect(emblaApi.selectedScrollSnap()).toBe(slideIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[slideIndex] + ) + expect(container.style.transform).toBe( + `translate3d(${scrollSnaps[slideIndex]}px,0px,0px)` + ) + slideIndex += 1 + } + + done() + }) + + test('Default (1), and slide element is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_LTR) + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const slideNodes = emblaApi.slideNodes() + const container = emblaApi.containerNode() + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideNodes[slideIndex], true) + expect(emblaApi.selectedScrollSnap()).toBe(slideIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[slideIndex] + ) + expect(container.style.transform).toBe( + `translate3d(${scrollSnaps[slideIndex]}px,0px,0px)` + ) + slideIndex += 1 + } + + done() + }) + + test('"auto" and slide index is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_LTR), + { slidesToScroll: 'auto' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const container = emblaApi.containerNode() + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideIndex, true) + const snapIndex = SLIDE_TO_SNAP_INDEX_MAP[slideIndex] + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(container.style.transform).toBe( + `translate3d(${scrollSnaps[snapIndex]}px,0px,0px)` + ) + slideIndex += 1 + } + + done() + }) + + test('"auto" and slide element is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_LTR), + { slidesToScroll: 'auto' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const slideNodes = emblaApi.slideNodes() + const container = emblaApi.containerNode() + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideNodes[slideIndex], true) + const snapIndex = SLIDE_TO_SNAP_INDEX_MAP[slideIndex] + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(container.style.transform).toBe( + `translate3d(${scrollSnaps[snapIndex]}px,0px,0px)` + ) + slideIndex += 1 + } + + done() + }) + }) +}) diff --git a/packages/embla-carousel/src/__tests__/scrollToSlide-rtl.test.ts b/packages/embla-carousel/src/__tests__/scrollToSlide-rtl.test.ts new file mode 100644 index 000000000..f1b8f5cd2 --- /dev/null +++ b/packages/embla-carousel/src/__tests__/scrollToSlide-rtl.test.ts @@ -0,0 +1,250 @@ +import EmblaCarousel from '../components/EmblaCarousel' +import { mockTestElements } from './mocks' +import { FIXTURE_SCROLL_TO_SNAP_RTL } from './fixtures/scrollToSnap-rtl.fixture' + +const SLIDE_TO_SNAP_INDEX_MAP: { [key: number]: number } = { + 0: 0, + 1: 0, + 2: 1, + 3: 1, + 4: 1, + 5: 1, + 6: 2, + 7: 2, + 8: 3, + 9: 3 +} + +describe('➡️ ScrollToSlide - Horizontal RTL', () => { + describe('Starts scrolling to correct snap when jump parameter is FALSE and slidesToScroll is:', () => { + test('Default (1), and slide index is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_RTL), + { direction: 'rtl' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const animationStart = jest.spyOn( + emblaApi.internalEngine().animation, + 'start' + ) + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideIndex) + expect(emblaApi.selectedScrollSnap()).toBe(slideIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[slideIndex] + ) + expect(animationStart).toHaveBeenCalledTimes(slideIndex) + slideIndex += 1 + } + + done() + }) + + test('Default (1), and slide element is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_RTL), + { direction: 'rtl' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const slideNodes = emblaApi.slideNodes() + const animationStart = jest.spyOn( + emblaApi.internalEngine().animation, + 'start' + ) + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideNodes[slideIndex]) + expect(emblaApi.selectedScrollSnap()).toBe(slideIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[slideIndex] + ) + expect(animationStart).toHaveBeenCalledTimes(slideIndex) + slideIndex += 1 + } + + done() + }) + + test('"auto" and slide index is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_RTL), + { direction: 'rtl', slidesToScroll: 'auto' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const animationStart = jest.spyOn( + emblaApi.internalEngine().animation, + 'start' + ) + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideIndex) + const snapIndex = SLIDE_TO_SNAP_INDEX_MAP[slideIndex] + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(animationStart).toHaveBeenCalledTimes(snapIndex) + slideIndex += 1 + } + + done() + }) + + test('"auto" and slide element is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_RTL), + { direction: 'rtl', slidesToScroll: 'auto' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const slideNodes = emblaApi.slideNodes() + const animationStart = jest.spyOn( + emblaApi.internalEngine().animation, + 'start' + ) + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideNodes[slideIndex]) + const snapIndex = SLIDE_TO_SNAP_INDEX_MAP[slideIndex] + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(animationStart).toHaveBeenCalledTimes(snapIndex) + slideIndex += 1 + } + + done() + }) + }) + + describe('Instantly scrolls to correct snap when jump parameter is TRUE and slidesToScroll is:', () => { + test('Default (1), and slide index is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_RTL), + { direction: 'rtl' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const container = emblaApi.containerNode() + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideIndex, true) + expect(emblaApi.selectedScrollSnap()).toBe(slideIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[slideIndex] + ) + expect(container.style.transform).toBe( + `translate3d(${scrollSnaps[slideIndex] * -1}px,0px,0px)` + ) + slideIndex += 1 + } + + done() + }) + + test('Default (1), and slide element is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_RTL), + { direction: 'rtl' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const slideNodes = emblaApi.slideNodes() + const container = emblaApi.containerNode() + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideNodes[slideIndex], true) + expect(emblaApi.selectedScrollSnap()).toBe(slideIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[slideIndex] + ) + expect(container.style.transform).toBe( + `translate3d(${scrollSnaps[slideIndex] * -1}px,0px,0px)` + ) + slideIndex += 1 + } + + done() + }) + + test('"auto" and slide index is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_RTL), + { direction: 'rtl', slidesToScroll: 'auto' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const container = emblaApi.containerNode() + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideIndex, true) + const snapIndex = SLIDE_TO_SNAP_INDEX_MAP[slideIndex] + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(container.style.transform).toBe( + `translate3d(${scrollSnaps[snapIndex] * -1}px,0px,0px)` + ) + slideIndex += 1 + } + + done() + }) + + test('"auto" and slide element is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_RTL), + { direction: 'rtl', slidesToScroll: 'auto' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const slideNodes = emblaApi.slideNodes() + const container = emblaApi.containerNode() + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideNodes[slideIndex], true) + const snapIndex = SLIDE_TO_SNAP_INDEX_MAP[slideIndex] + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(container.style.transform).toBe( + `translate3d(${scrollSnaps[snapIndex] * -1}px,0px,0px)` + ) + slideIndex += 1 + } + + done() + }) + }) +}) diff --git a/packages/embla-carousel/src/__tests__/scrollToSlide-vertical.test.ts b/packages/embla-carousel/src/__tests__/scrollToSlide-vertical.test.ts new file mode 100644 index 000000000..18a8597c9 --- /dev/null +++ b/packages/embla-carousel/src/__tests__/scrollToSlide-vertical.test.ts @@ -0,0 +1,250 @@ +import EmblaCarousel from '../components/EmblaCarousel' +import { mockTestElements } from './mocks' +import { FIXTURE_SCROLL_TO_SNAP_Y } from './fixtures/scrollToSnap-vertical.fixture' + +const SLIDE_TO_SNAP_INDEX_MAP: { [key: number]: number } = { + 0: 0, + 1: 0, + 2: 1, + 3: 1, + 4: 1, + 5: 1, + 6: 2, + 7: 2, + 8: 3, + 9: 3 +} + +describe('➡️ ScrollToSlide - Vertical', () => { + describe('Starts scrolling to correct snap when jump parameter is FALSE and slidesToScroll is:', () => { + test('Default (1), and slide index is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_Y), + { axis: 'y' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const animationStart = jest.spyOn( + emblaApi.internalEngine().animation, + 'start' + ) + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideIndex) + expect(emblaApi.selectedScrollSnap()).toBe(slideIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[slideIndex] + ) + expect(animationStart).toHaveBeenCalledTimes(slideIndex) + slideIndex += 1 + } + + done() + }) + + test('Default (1), and slide element is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_Y), + { axis: 'y' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const slideNodes = emblaApi.slideNodes() + const animationStart = jest.spyOn( + emblaApi.internalEngine().animation, + 'start' + ) + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideNodes[slideIndex]) + expect(emblaApi.selectedScrollSnap()).toBe(slideIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[slideIndex] + ) + expect(animationStart).toHaveBeenCalledTimes(slideIndex) + slideIndex += 1 + } + + done() + }) + + test('"auto" and slide index is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_Y), + { axis: 'y', slidesToScroll: 'auto' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const animationStart = jest.spyOn( + emblaApi.internalEngine().animation, + 'start' + ) + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideIndex) + const snapIndex = SLIDE_TO_SNAP_INDEX_MAP[slideIndex] + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(animationStart).toHaveBeenCalledTimes(snapIndex) + slideIndex += 1 + } + + done() + }) + + test('"auto" and slide element is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_Y), + { axis: 'y', slidesToScroll: 'auto' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const slideNodes = emblaApi.slideNodes() + const animationStart = jest.spyOn( + emblaApi.internalEngine().animation, + 'start' + ) + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideNodes[slideIndex]) + const snapIndex = SLIDE_TO_SNAP_INDEX_MAP[slideIndex] + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(animationStart).toHaveBeenCalledTimes(snapIndex) + slideIndex += 1 + } + + done() + }) + }) + + describe('Instantly scrolls to correct snap when jump parameter is TRUE and slidesToScroll is:', () => { + test('Default (1), and slide index is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_Y), + { axis: 'y' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const container = emblaApi.containerNode() + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideIndex, true) + expect(emblaApi.selectedScrollSnap()).toBe(slideIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[slideIndex] + ) + expect(container.style.transform).toBe( + `translate3d(0px,${scrollSnaps[slideIndex]}px,0px)` + ) + slideIndex += 1 + } + + done() + }) + + test('Default (1), and slide element is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_Y), + { axis: 'y' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const slideNodes = emblaApi.slideNodes() + const container = emblaApi.containerNode() + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideNodes[slideIndex], true) + expect(emblaApi.selectedScrollSnap()).toBe(slideIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[slideIndex] + ) + expect(container.style.transform).toBe( + `translate3d(0px,${scrollSnaps[slideIndex]}px,0px)` + ) + slideIndex += 1 + } + + done() + }) + + test('"auto" and slide index is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_Y), + { axis: 'y', slidesToScroll: 'auto' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const container = emblaApi.containerNode() + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideIndex, true) + const snapIndex = SLIDE_TO_SNAP_INDEX_MAP[slideIndex] + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(container.style.transform).toBe( + `translate3d(0px,${scrollSnaps[snapIndex]}px,0px)` + ) + slideIndex += 1 + } + + done() + }) + + test('"auto" and slide element is provided', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_Y), + { axis: 'y', slidesToScroll: 'auto' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.slideNodes().length - 1 + const slideNodes = emblaApi.slideNodes() + const container = emblaApi.containerNode() + + let slideIndex = firstIndex + + while (slideIndex !== lastIndex) { + emblaApi.scrollToSlide(slideNodes[slideIndex], true) + const snapIndex = SLIDE_TO_SNAP_INDEX_MAP[slideIndex] + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(container.style.transform).toBe( + `translate3d(0px,${scrollSnaps[snapIndex]}px,0px)` + ) + slideIndex += 1 + } + + done() + }) + }) +}) diff --git a/packages/embla-carousel/src/__tests__/scrollToSnap-ltr.test.ts b/packages/embla-carousel/src/__tests__/scrollToSnap-ltr.test.ts new file mode 100644 index 000000000..92dd6e85c --- /dev/null +++ b/packages/embla-carousel/src/__tests__/scrollToSnap-ltr.test.ts @@ -0,0 +1,117 @@ +import EmblaCarousel from '../components/EmblaCarousel' +import { mockTestElements } from './mocks' +import { FIXTURE_SCROLL_TO_SNAP_LTR } from './fixtures/scrollToSnap-ltr.fixture' + +describe('➡️ ScrollToSnap - Horizontal LTR', () => { + describe('Starts scrolling to correct snap when jump parameter is FALSE and slidesToScroll is:', () => { + test('Default (1)', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_LTR) + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.scrollSnapList().length - 1 + const animationStart = jest.spyOn( + emblaApi.internalEngine().animation, + 'start' + ) + + let snapIndex = firstIndex + + while (snapIndex !== lastIndex) { + emblaApi.scrollToSnap(snapIndex) + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(animationStart).toHaveBeenCalledTimes(snapIndex) + snapIndex += 1 + } + + done() + }) + + test('"auto"', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_LTR), + { slidesToScroll: 'auto' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.scrollSnapList().length - 1 + const animationStart = jest.spyOn( + emblaApi.internalEngine().animation, + 'start' + ) + + let snapIndex = firstIndex + + while (snapIndex !== lastIndex) { + emblaApi.scrollToSnap(snapIndex) + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(animationStart).toHaveBeenCalledTimes(snapIndex) + snapIndex += 1 + } + + done() + }) + }) + + describe('Instantly scrolls to correct snap when jump parameter is TRUE and slidesToScroll is:', () => { + test('Default (1)', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_LTR) + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.scrollSnapList().length - 1 + const container = emblaApi.containerNode() + + let snapIndex = firstIndex + + while (snapIndex !== lastIndex) { + emblaApi.scrollToSnap(snapIndex, true) + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(container.style.transform).toBe( + `translate3d(${scrollSnaps[snapIndex]}px,0px,0px)` + ) + snapIndex += 1 + } + + done() + }) + + test('"auto"', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_LTR), + { slidesToScroll: 'auto' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.scrollSnapList().length - 1 + const container = emblaApi.containerNode() + + let snapIndex = firstIndex + + while (snapIndex !== lastIndex) { + emblaApi.scrollToSnap(snapIndex, true) + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(container.style.transform).toBe( + `translate3d(${scrollSnaps[snapIndex]}px,0px,0px)` + ) + snapIndex += 1 + } + + done() + }) + }) +}) diff --git a/packages/embla-carousel/src/__tests__/scrollToSnap-rtl.test.ts b/packages/embla-carousel/src/__tests__/scrollToSnap-rtl.test.ts new file mode 100644 index 000000000..01bb4cf9a --- /dev/null +++ b/packages/embla-carousel/src/__tests__/scrollToSnap-rtl.test.ts @@ -0,0 +1,119 @@ +import EmblaCarousel from '../components/EmblaCarousel' +import { mockTestElements } from './mocks' +import { FIXTURE_SCROLL_TO_SNAP_RTL } from './fixtures/scrollToSnap-rtl.fixture' + +describe('➡️ ScrollToSnap - Horizontal RTL', () => { + describe('Starts scrolling to correct snap when jump parameter is FALSE and slidesToScroll is:', () => { + test('Default (1)', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_RTL), + { direction: 'rtl' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.scrollSnapList().length - 1 + const animationStart = jest.spyOn( + emblaApi.internalEngine().animation, + 'start' + ) + + let snapIndex = firstIndex + + while (snapIndex !== lastIndex) { + emblaApi.scrollToSnap(snapIndex) + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(animationStart).toHaveBeenCalledTimes(snapIndex) + snapIndex += 1 + } + + done() + }) + + test('"auto"', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_RTL), + { direction: 'rtl', slidesToScroll: 'auto' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.scrollSnapList().length - 1 + const animationStart = jest.spyOn( + emblaApi.internalEngine().animation, + 'start' + ) + + let snapIndex = firstIndex + + while (snapIndex !== lastIndex) { + emblaApi.scrollToSnap(snapIndex) + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(animationStart).toHaveBeenCalledTimes(snapIndex) + snapIndex += 1 + } + + done() + }) + }) + + describe('Instantly scrolls to correct snap when jump parameter is TRUE and slidesToScroll is:', () => { + test('Default (1)', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_RTL), + { direction: 'rtl' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.scrollSnapList().length - 1 + const container = emblaApi.containerNode() + + let snapIndex = firstIndex + + while (snapIndex !== lastIndex) { + emblaApi.scrollToSnap(snapIndex, true) + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(container.style.transform).toBe( + `translate3d(${scrollSnaps[snapIndex] * -1}px,0px,0px)` + ) + snapIndex += 1 + } + + done() + }) + + test('"auto"', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_RTL), + { direction: 'rtl', slidesToScroll: 'auto' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.scrollSnapList().length - 1 + const container = emblaApi.containerNode() + + let snapIndex = firstIndex + + while (snapIndex !== lastIndex) { + emblaApi.scrollToSnap(snapIndex, true) + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(container.style.transform).toBe( + `translate3d(${scrollSnaps[snapIndex] * -1}px,0px,0px)` + ) + snapIndex += 1 + } + + done() + }) + }) +}) diff --git a/packages/embla-carousel/src/__tests__/scrollToSnap-vertical.test.ts b/packages/embla-carousel/src/__tests__/scrollToSnap-vertical.test.ts new file mode 100644 index 000000000..405b45211 --- /dev/null +++ b/packages/embla-carousel/src/__tests__/scrollToSnap-vertical.test.ts @@ -0,0 +1,119 @@ +import EmblaCarousel from '../components/EmblaCarousel' +import { mockTestElements } from './mocks' +import { FIXTURE_SCROLL_TO_SNAP_Y } from './fixtures/scrollToSnap-vertical.fixture' + +describe('➡️ ScrollToSnap - Vertical', () => { + describe('Starts scrolling to correct snap when jump parameter is FALSE and slidesToScroll is:', () => { + test('Default (1)', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_Y), + { axis: 'y' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.scrollSnapList().length - 1 + const animationStart = jest.spyOn( + emblaApi.internalEngine().animation, + 'start' + ) + + let snapIndex = firstIndex + + while (snapIndex !== lastIndex) { + emblaApi.scrollToSnap(snapIndex) + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(animationStart).toHaveBeenCalledTimes(snapIndex) + snapIndex += 1 + } + + done() + }) + + test('"auto"', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_Y), + { axis: 'y', slidesToScroll: 'auto' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.scrollSnapList().length - 1 + const animationStart = jest.spyOn( + emblaApi.internalEngine().animation, + 'start' + ) + + let snapIndex = firstIndex + + while (snapIndex !== lastIndex) { + emblaApi.scrollToSnap(snapIndex) + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(animationStart).toHaveBeenCalledTimes(snapIndex) + snapIndex += 1 + } + + done() + }) + }) + + describe('Instantly scrolls to correct snap when jump parameter is TRUE and slidesToScroll is:', () => { + test('Default (1)', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_Y), + { axis: 'y' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.scrollSnapList().length - 1 + const container = emblaApi.containerNode() + + let snapIndex = firstIndex + + while (snapIndex !== lastIndex) { + emblaApi.scrollToSnap(snapIndex, true) + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(container.style.transform).toBe( + `translate3d(0px,${scrollSnaps[snapIndex]}px,0px)` + ) + snapIndex += 1 + } + + done() + }) + + test('"auto"', (done) => { + const emblaApi = EmblaCarousel( + mockTestElements(FIXTURE_SCROLL_TO_SNAP_Y), + { axis: 'y', slidesToScroll: 'auto' } + ) + const { scrollSnaps } = emblaApi.internalEngine() + const firstIndex = 0 + const lastIndex = emblaApi.scrollSnapList().length - 1 + const container = emblaApi.containerNode() + + let snapIndex = firstIndex + + while (snapIndex !== lastIndex) { + emblaApi.scrollToSnap(snapIndex, true) + expect(emblaApi.selectedScrollSnap()).toBe(snapIndex) + expect(emblaApi.internalEngine().target.get()).toBe( + scrollSnaps[snapIndex] + ) + expect(container.style.transform).toBe( + `translate3d(0px,${scrollSnaps[snapIndex]}px,0px)` + ) + snapIndex += 1 + } + + done() + }) + }) +}) diff --git a/packages/embla-carousel/src/__tests__/selectedAndPreviousSnap-ltr.test.ts b/packages/embla-carousel/src/__tests__/selectedAndPreviousSnap-ltr.test.ts index 3eff3207f..9c1c23fbc 100644 --- a/packages/embla-carousel/src/__tests__/selectedAndPreviousSnap-ltr.test.ts +++ b/packages/embla-carousel/src/__tests__/selectedAndPreviousSnap-ltr.test.ts @@ -26,7 +26,7 @@ describe('➡️ SelectedScrollSnap & PreviousScrollSnap - Horizontal LTR', () expect(emblaApi.previousScrollSnap()).toBe(2) }) - test('User tries to scrollNext() past the last slide', () => { + test('User tries to scrollNext() past the last scroll snap', () => { emblaApi.reInit({ startIndex: lastIndex }) emblaApi.scrollNext() @@ -34,22 +34,22 @@ describe('➡️ SelectedScrollSnap & PreviousScrollSnap - Horizontal LTR', () expect(emblaApi.previousScrollSnap()).toBe(lastIndex) }) - test('User tries to scrollPrev before the first slide', () => { + test('User tries to scrollPrev() before the first scroll snap', () => { emblaApi.scrollPrev() expect(emblaApi.selectedScrollSnap()).toBe(firstIndex) expect(emblaApi.previousScrollSnap()).toBe(firstIndex) }) - test('User tries to scrollTo() an index more than last index', () => { - emblaApi.scrollTo(lastIndex + 1) + test('User tries to scrollToSnap() an index more than last index', () => { + emblaApi.scrollToSnap(lastIndex + 1) expect(emblaApi.selectedScrollSnap()).toBe(lastIndex) expect(emblaApi.previousScrollSnap()).toBe(firstIndex) }) - test('User tries to scrollTo() an index less than first index', () => { - emblaApi.scrollTo(firstIndex - 1) + test('User tries to scrollToSnap() an index less than first index', () => { + emblaApi.scrollToSnap(firstIndex - 1) expect(emblaApi.selectedScrollSnap()).toBe(firstIndex) expect(emblaApi.previousScrollSnap()).toBe(firstIndex) @@ -105,7 +105,7 @@ describe('➡️ SelectedScrollSnap & PreviousScrollSnap - Horizontal LTR', () expect(emblaApi.previousScrollSnap()).toBe(2) }) - test('User tries to scrollNext() past the last slide', () => { + test('User tries to scrollNext() past the last scroll snap', () => { emblaApi.reInit({ startIndex: lastIndex }) emblaApi.scrollNext() @@ -113,22 +113,22 @@ describe('➡️ SelectedScrollSnap & PreviousScrollSnap - Horizontal LTR', () expect(emblaApi.previousScrollSnap()).toBe(lastIndex) }) - test('User tries to scrollPrev before the first slide', () => { + test('User tries to scrollPrev() before the first scroll snap', () => { emblaApi.scrollPrev() expect(emblaApi.selectedScrollSnap()).toBe(lastIndex) expect(emblaApi.previousScrollSnap()).toBe(firstIndex) }) - test('User tries to scrollTo() an index more than last index', () => { - emblaApi.scrollTo(lastIndex + 1) + test('User tries to scrollToSnap() an index more than last index', () => { + emblaApi.scrollToSnap(lastIndex + 1) expect(emblaApi.selectedScrollSnap()).toBe(firstIndex) expect(emblaApi.previousScrollSnap()).toBe(firstIndex) }) - test('User tries to scrollTo() an index less than first index', () => { - emblaApi.scrollTo(firstIndex - 1) + test('User tries to scrollToSnap() an index less than first index', () => { + emblaApi.scrollToSnap(firstIndex - 1) expect(emblaApi.selectedScrollSnap()).toBe(lastIndex) expect(emblaApi.previousScrollSnap()).toBe(firstIndex) diff --git a/packages/embla-carousel/src/__tests__/selectedAndPreviousSnap-rtl.test.ts b/packages/embla-carousel/src/__tests__/selectedAndPreviousSnap-rtl.test.ts index a540c4937..4e2db8d28 100644 --- a/packages/embla-carousel/src/__tests__/selectedAndPreviousSnap-rtl.test.ts +++ b/packages/embla-carousel/src/__tests__/selectedAndPreviousSnap-rtl.test.ts @@ -42,15 +42,15 @@ describe('➡️ SelectedScrollSnap & PreviousScrollSnap - Horizontal RTL', () expect(emblaApi.previousScrollSnap()).toBe(firstIndex) }) - test('User tries to scrollTo() an index more than last index', () => { - emblaApi.scrollTo(lastIndex + 1) + test('User tries to scrollToSnap() an index more than last index', () => { + emblaApi.scrollToSnap(lastIndex + 1) expect(emblaApi.selectedScrollSnap()).toBe(lastIndex) expect(emblaApi.previousScrollSnap()).toBe(firstIndex) }) - test('User tries to scrollTo() an index less than first index', () => { - emblaApi.scrollTo(firstIndex - 1) + test('User tries to scrollToSnap() an index less than first index', () => { + emblaApi.scrollToSnap(firstIndex - 1) expect(emblaApi.selectedScrollSnap()).toBe(firstIndex) expect(emblaApi.previousScrollSnap()).toBe(firstIndex) @@ -124,15 +124,15 @@ describe('➡️ SelectedScrollSnap & PreviousScrollSnap - Horizontal RTL', () expect(emblaApi.previousScrollSnap()).toBe(firstIndex) }) - test('User tries to scrollTo() an index more than last index', () => { - emblaApi.scrollTo(lastIndex + 1) + test('User tries to scrollToSnap() an index more than last index', () => { + emblaApi.scrollToSnap(lastIndex + 1) expect(emblaApi.selectedScrollSnap()).toBe(firstIndex) expect(emblaApi.previousScrollSnap()).toBe(firstIndex) }) - test('User tries to scrollTo() an index less than first index', () => { - emblaApi.scrollTo(firstIndex - 1) + test('User tries to scrollToSnap() an index less than first index', () => { + emblaApi.scrollToSnap(firstIndex - 1) expect(emblaApi.selectedScrollSnap()).toBe(lastIndex) expect(emblaApi.previousScrollSnap()).toBe(firstIndex) diff --git a/packages/embla-carousel/src/__tests__/selectedAndPreviousSnap-vertical.test.ts b/packages/embla-carousel/src/__tests__/selectedAndPreviousSnap-vertical.test.ts index 8c61341bc..4f25b469b 100644 --- a/packages/embla-carousel/src/__tests__/selectedAndPreviousSnap-vertical.test.ts +++ b/packages/embla-carousel/src/__tests__/selectedAndPreviousSnap-vertical.test.ts @@ -44,15 +44,15 @@ describe('➡️ SelectedScrollSnap & PreviousScrollSnap - Vertical', () => { expect(emblaApi.previousScrollSnap()).toBe(firstIndex) }) - test('User tries to scrollTo() an index more than last index', () => { - emblaApi.scrollTo(lastIndex + 1) + test('User tries to scrollToSnap() an index more than last index', () => { + emblaApi.scrollToSnap(lastIndex + 1) expect(emblaApi.selectedScrollSnap()).toBe(lastIndex) expect(emblaApi.previousScrollSnap()).toBe(firstIndex) }) - test('User tries to scrollTo() an index less than first index', () => { - emblaApi.scrollTo(firstIndex - 1) + test('User tries to scrollToSnap() an index less than first index', () => { + emblaApi.scrollToSnap(firstIndex - 1) expect(emblaApi.selectedScrollSnap()).toBe(firstIndex) expect(emblaApi.previousScrollSnap()).toBe(firstIndex) @@ -126,15 +126,15 @@ describe('➡️ SelectedScrollSnap & PreviousScrollSnap - Vertical', () => { expect(emblaApi.previousScrollSnap()).toBe(firstIndex) }) - test('User tries to scrollTo() an index more than last index', () => { - emblaApi.scrollTo(lastIndex + 1) + test('User tries to scrollToSnap() an index more than last index', () => { + emblaApi.scrollToSnap(lastIndex + 1) expect(emblaApi.selectedScrollSnap()).toBe(firstIndex) expect(emblaApi.previousScrollSnap()).toBe(firstIndex) }) - test('User tries to scrollTo() an index less than first index', () => { - emblaApi.scrollTo(firstIndex - 1) + test('User tries to scrollToSnap() an index less than first index', () => { + emblaApi.scrollToSnap(firstIndex - 1) expect(emblaApi.selectedScrollSnap()).toBe(lastIndex) expect(emblaApi.previousScrollSnap()).toBe(firstIndex) diff --git a/packages/embla-carousel/src/components/EmblaCarousel.ts b/packages/embla-carousel/src/components/EmblaCarousel.ts index f15321a78..d5e68224c 100644 --- a/packages/embla-carousel/src/components/EmblaCarousel.ts +++ b/packages/embla-carousel/src/components/EmblaCarousel.ts @@ -6,7 +6,7 @@ import { defaultOptions, EmblaOptionsType, OptionsType } from './Options' import { OptionsHandler } from './OptionsHandler' import { PluginsHandler } from './PluginsHandler' import { EmblaPluginsType, EmblaPluginType } from './Plugins' -import { isString, WindowType } from './utils' +import { isNumber, isString, WindowType } from './utils' export type EmblaCarouselType = { canScrollNext: () => boolean @@ -28,7 +28,8 @@ export type EmblaCarouselType = { scrollPrev: (jump?: boolean) => void scrollProgress: () => number scrollSnapList: () => number[] - scrollTo: (index: number, jump?: boolean) => void + scrollToSnap: (index: number, jump?: boolean) => void + scrollToSlide: (subject: number | HTMLElement, jump?: boolean) => void selectedScrollSnap: () => number slideNodes: () => HTMLElement[] slidesInView: () => number[] @@ -164,7 +165,11 @@ function EmblaCarousel( watchHandler.clear() } - function scrollTo(index: number, jump?: boolean, direction?: number): void { + function scrollToSnap( + index: number, + jump?: boolean, + direction?: number + ): void { if (!options.active || destroyed) return engine.scrollBody .useBaseFriction() @@ -172,14 +177,25 @@ function EmblaCarousel( engine.scrollTo.index(index, direction || 0) } + function scrollToSlide( + subject: number | HTMLElement, + jump?: boolean, + direction?: number + ): void { + const index = isNumber(subject) ? subject : slides.indexOf(subject) + const snapIndex = engine.slideRegistry.findIndex((g) => g.includes(index)) + + if (isNumber(snapIndex)) scrollToSnap(snapIndex, jump, direction) + } + function scrollNext(jump?: boolean): void { const next = engine.index.add(1).get() - scrollTo(next, jump, -1) + scrollToSnap(next, jump, -1) } function scrollPrev(jump?: boolean): void { const prev = engine.index.add(-1).get() - scrollTo(prev, jump, 1) + scrollToSnap(prev, jump, 1) } function canScrollNext(): boolean { @@ -256,7 +272,8 @@ function EmblaCarousel( scrollPrev, scrollProgress, scrollSnapList, - scrollTo, + scrollToSnap, + scrollToSlide, selectedScrollSnap, slideNodes, slidesInView, diff --git a/playgrounds/embla-carousel-playground-react/src/Carousel/Carousel.tsx b/playgrounds/embla-carousel-playground-react/src/Carousel/Carousel.tsx index 710b43b39..6f9a177f8 100644 --- a/playgrounds/embla-carousel-playground-react/src/Carousel/Carousel.tsx +++ b/playgrounds/embla-carousel-playground-react/src/Carousel/Carousel.tsx @@ -25,7 +25,7 @@ export const EmblaCarousel: React.FC = (props) => { [emblaApi] ) const scrollTo = useCallback( - (index: number) => emblaApi && emblaApi.scrollTo(index), + (index: number) => emblaApi && emblaApi.scrollToSnap(index), [emblaApi] ) diff --git a/playgrounds/embla-carousel-playground-solid/src/Carousel/Carousel.tsx b/playgrounds/embla-carousel-playground-solid/src/Carousel/Carousel.tsx index 96016011b..a4dfb58a4 100644 --- a/playgrounds/embla-carousel-playground-solid/src/Carousel/Carousel.tsx +++ b/playgrounds/embla-carousel-playground-solid/src/Carousel/Carousel.tsx @@ -24,7 +24,7 @@ export const EmblaCarousel: Component = (props) => { } function scrollTo(index: number): void { - emblaApi()?.scrollTo(index) + emblaApi()?.scrollToSnap(index) } function onInit(emblaApi: EmblaCarouselType): void { diff --git a/playgrounds/embla-carousel-playground-vanilla/src/Carousel/setupDots.ts b/playgrounds/embla-carousel-playground-vanilla/src/Carousel/setupDots.ts index 1acf80b86..04d659e83 100644 --- a/playgrounds/embla-carousel-playground-vanilla/src/Carousel/setupDots.ts +++ b/playgrounds/embla-carousel-playground-vanilla/src/Carousel/setupDots.ts @@ -5,7 +5,7 @@ export const addDotBtnsClickHandlers = ( dotNodes: HTMLElement[] ): void => { dotNodes.forEach((dotNode, index) => { - dotNode.addEventListener('click', () => emblaApi.scrollTo(index), false) + dotNode.addEventListener('click', () => emblaApi.scrollToSnap(index), false) }) }