From 6eceadb61633575bc596319a3cbba32892864b17 Mon Sep 17 00:00:00 2001 From: David Jerleke Date: Thu, 29 Feb 2024 14:48:42 +0100 Subject: [PATCH] Implement #39. --- package.json | 1 + .../src/components/AutoHeight.ts | 29 +- .../src/components/Options.ts | 10 +- packages/embla-carousel-docs/package.json | 1 + .../src/components/Examples/Plugins/Fade.tsx | 47 ++++ .../Examples/examplesCarouselStyles.ts | 27 +- .../src/components/Footer/FooterLinks.tsx | 1 + .../EmblaCarouselArrowButtons.tsx | 1 + .../EmblaCarouselDotButton.tsx | 1 + .../EmblaCarouselArrowButtons.tsx | 6 +- .../EmblaCarouselDotButton.tsx | 9 +- .../SandboxFilesSrc/Fade/EmblaCarousel.tsx | 70 +++++ .../SandboxFilesSrc/Opacity/EmblaCarousel.tsx | 1 + .../Parallax/EmblaCarousel.tsx | 1 + .../Progress/EmblaCarousel.tsx | 6 +- .../SandboxFilesSrc/Scale/EmblaCarousel.tsx | 1 + .../SandboxFilesSrc/Thumbs/EmblaCarousel.tsx | 4 +- .../SandboxGeneratorExampleArrowButtons.tsx | 3 +- .../SandboxGeneratorExampleDotButton.tsx | 4 +- .../CarouselGenerator/EmblaCarousel.ts | 8 +- .../EmblaCarouselSelectedSnapDisplay.ts | 7 +- .../Opacity/EmblaCarouselTweenOpacity.ts | 5 +- .../Parallax/EmblaCarouselTweenParallax.ts | 6 +- .../SandboxFilesSrc/Progress/EmblaCarousel.ts | 1 + .../Scale/EmblaCarouselTweenScale.ts | 6 +- .../src/content/pages/examples/predefined.mdx | 5 + .../src/content/pages/plugins/auto-height.mdx | 15 -- .../src/content/pages/plugins/fade.mdx | 89 ++++++ .../content/pages/plugins/wheel-gestures.mdx | 2 +- packages/embla-carousel-fade/.eslintignore | 5 + packages/embla-carousel-fade/.eslintrc.js | 28 ++ packages/embla-carousel-fade/.prettierrc.js | 1 + packages/embla-carousel-fade/README.md | 209 ++++++++++++++ packages/embla-carousel-fade/jest.config.js | 8 + packages/embla-carousel-fade/package.json | 77 ++++++ packages/embla-carousel-fade/rollup.config.js | 53 ++++ .../src/components/Fade.ts | 254 ++++++++++++++++++ .../src/components/Options.ts | 3 + .../src/components/utils.ts | 7 + packages/embla-carousel-fade/src/index.ts | 2 + packages/embla-carousel-fade/tsconfig.json | 15 ++ .../src/__tests__/events.test.ts | 15 +- .../src/__tests__/mocks/testElements.mock.ts | 19 ++ .../src/components/DragHandler.ts | 6 +- .../src/components/EmblaCarousel.ts | 1 + .../embla-carousel/src/components/Engine.ts | 3 +- .../src/components/EventHandler.ts | 11 +- .../src/components/ScrollTarget.ts | 2 +- .../src/components/SlideFocus.ts | 5 +- .../src/Carousel/Carousel.tsx | 9 +- .../src/main.css | 11 +- .../src/Carousel/Carousel.tsx | 8 +- .../src/main.css | 11 +- .../src/Carousel/setupButtons.ts | 1 + .../src/Carousel/setupSlides.ts | 2 +- .../src/main.css | 11 +- .../src/main.ts | 6 +- yarn.lock | 22 ++ 58 files changed, 1042 insertions(+), 130 deletions(-) create mode 100644 packages/embla-carousel-docs/src/components/Examples/Plugins/Fade.tsx create mode 100644 packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Fade/EmblaCarousel.tsx create mode 100644 packages/embla-carousel-docs/src/content/pages/plugins/fade.mdx create mode 100644 packages/embla-carousel-fade/.eslintignore create mode 100644 packages/embla-carousel-fade/.eslintrc.js create mode 100644 packages/embla-carousel-fade/.prettierrc.js create mode 100644 packages/embla-carousel-fade/README.md create mode 100644 packages/embla-carousel-fade/jest.config.js create mode 100644 packages/embla-carousel-fade/package.json create mode 100644 packages/embla-carousel-fade/rollup.config.js create mode 100644 packages/embla-carousel-fade/src/components/Fade.ts create mode 100644 packages/embla-carousel-fade/src/components/Options.ts create mode 100644 packages/embla-carousel-fade/src/components/utils.ts create mode 100644 packages/embla-carousel-fade/src/index.ts create mode 100644 packages/embla-carousel-fade/tsconfig.json diff --git a/package.json b/package.json index ee087c506..6ddfe134d 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "packages/embla-carousel-auto-scroll", "packages/embla-carousel-auto-height", "packages/embla-carousel-class-names", + "packages/embla-carousel-fade", "packages/embla-carousel-reactive-utils", "playgrounds/embla-carousel-playground-vanilla", "playgrounds/embla-carousel-playground-react", diff --git a/packages/embla-carousel-auto-height/src/components/AutoHeight.ts b/packages/embla-carousel-auto-height/src/components/AutoHeight.ts index 336ba315b..65e8ab32c 100644 --- a/packages/embla-carousel-auto-height/src/components/AutoHeight.ts +++ b/packages/embla-carousel-auto-height/src/components/AutoHeight.ts @@ -1,6 +1,5 @@ -import { defaultOptions, OptionsType } from './Options' +import { OptionsType } from './Options' import { CreatePluginType } from 'embla-carousel/components/Plugins' -import { OptionsHandlerType } from 'embla-carousel/components/OptionsHandler' import { EmblaCarouselType, EmblaEventType } from 'embla-carousel' declare module 'embla-carousel/components/Plugins' { @@ -14,22 +13,13 @@ export type AutoHeightType = CreatePluginType<{}, OptionsType> export type AutoHeightOptionsType = AutoHeightType['options'] function AutoHeight(userOptions: AutoHeightOptionsType = {}): AutoHeightType { - let options: OptionsType let emblaApi: EmblaCarouselType let slideHeights: number[] = [] - const heightEvents: EmblaEventType[] = ['select'] + const heightEvents: EmblaEventType[] = ['select', 'slideFocus'] - function init( - emblaApiInstance: EmblaCarouselType, - optionsHandler: OptionsHandlerType - ): void { + function init(emblaApiInstance: EmblaCarouselType): void { emblaApi = emblaApiInstance - const { mergeOptions, optionsAtMedia } = optionsHandler - const optionsBase = mergeOptions(defaultOptions, AutoHeight.globalOptions) - const allOptions = mergeOptions(optionsBase, userOptions) - options = optionsAtMedia(allOptions) - const { options: { axis }, slideRects @@ -45,7 +35,9 @@ function AutoHeight(userOptions: AutoHeightOptionsType = {}): AutoHeightType { function destroy(): void { heightEvents.forEach((evt) => emblaApi.off(evt, setContainerHeight)) - setContainerHeight(undefined, 'destroy') + const container = emblaApi.containerNode() + container.style.height = '' + if (!container.getAttribute('style')) container.removeAttribute('style') } function highestInView(): number { @@ -57,13 +49,8 @@ function AutoHeight(userOptions: AutoHeightOptionsType = {}): AutoHeightType { .reduce((a, b) => Math.max(a, b), 0) } - function setContainerHeight( - _?: EmblaCarouselType, - evt?: EmblaEventType - ): void { - const height = - evt === 'destroy' ? options.destroyHeight : `${highestInView()}px` - emblaApi.containerNode().style.height = height + function setContainerHeight(): void { + emblaApi.containerNode().style.height = `${highestInView()}px` } const self: AutoHeightType = { diff --git a/packages/embla-carousel-auto-height/src/components/Options.ts b/packages/embla-carousel-auto-height/src/components/Options.ts index 219dc255b..4500d9ff2 100644 --- a/packages/embla-carousel-auto-height/src/components/Options.ts +++ b/packages/embla-carousel-auto-height/src/components/Options.ts @@ -1,11 +1,3 @@ import { CreateOptionsType } from 'embla-carousel/components/Options' -export type OptionsType = CreateOptionsType<{ - destroyHeight: CSSStyleDeclaration['height'] -}> - -export const defaultOptions: OptionsType = { - active: true, - breakpoints: {}, - destroyHeight: 'auto' -} +export type OptionsType = CreateOptionsType<{}> diff --git a/packages/embla-carousel-docs/package.json b/packages/embla-carousel-docs/package.json index 6e172a8bf..59894c286 100644 --- a/packages/embla-carousel-docs/package.json +++ b/packages/embla-carousel-docs/package.json @@ -45,6 +45,7 @@ "embla-carousel-auto-scroll": "8.0.4", "embla-carousel-autoplay": "8.0.4", "embla-carousel-class-names": "8.0.4", + "embla-carousel-fade": "8.0.4", "embla-carousel-react": "8.0.4", "focus-trap-react": "^8.10.0", "gatsby": "^5.13.3", diff --git a/packages/embla-carousel-docs/src/components/Examples/Plugins/Fade.tsx b/packages/embla-carousel-docs/src/components/Examples/Plugins/Fade.tsx new file mode 100644 index 000000000..2538c5cca --- /dev/null +++ b/packages/embla-carousel-docs/src/components/Examples/Plugins/Fade.tsx @@ -0,0 +1,47 @@ +import React from 'react' +import { EmblaOptionsType } from 'embla-carousel' +import styled from 'styled-components' +import { useInView } from 'react-intersection-observer' +import CarouselFade from 'components/Sandbox/React/SandboxFilesSrc/Fade/EmblaCarousel' +import { examplesDefaultWrapperStyles } from 'components/Examples/examplesWrapperStyles' +import { examplesCarouselFadeStyles } from 'components/Examples/examplesCarouselStyles' +import { arrayFromNumber } from 'utils/arrayFromNumber' +import { SandboxSelection } from 'components/Sandbox/SandboxSelection' +import { sandboxStaticSandboxes } from 'components/Sandbox/sandboxStatic' +import { SandboxStaticSettingsType } from 'consts/sandbox' +import { EXAMPLES_INTERSECTION_OPTIONS } from 'consts/examples' + +const ID = 'embla-carousel-fade' +const SLIDES = arrayFromNumber(5) +const OPTIONS: EmblaOptionsType = { loop: true, duration: 40 } +const STYLES = examplesCarouselFadeStyles() + +const SANDBOX_CONFIG: SandboxStaticSettingsType = { + id: ID, + slides: SLIDES, + options: OPTIONS, + styles: STYLES +} + +const SANDBOXES = sandboxStaticSandboxes(SANDBOX_CONFIG, 'Fade') + +const Wrapper = styled.div` + ${examplesDefaultWrapperStyles}; + + &.${ID} { + ${STYLES}; + } +` + +export const Fade = () => { + const [inViewRef, inView] = useInView(EXAMPLES_INTERSECTION_OPTIONS) + + return ( + <> + + + {inView ? : null} + + + ) +} diff --git a/packages/embla-carousel-docs/src/components/Examples/examplesCarouselStyles.ts b/packages/embla-carousel-docs/src/components/Examples/examplesCarouselStyles.ts index b889add62..cd92460c6 100644 --- a/packages/embla-carousel-docs/src/components/Examples/examplesCarouselStyles.ts +++ b/packages/embla-carousel-docs/src/components/Examples/examplesCarouselStyles.ts @@ -332,7 +332,7 @@ export const THUMBS_STYLES = css` } ` -const PROGRESS_STYLES = css` +export const PROGRESS_STYLES = css` .embla__progress { ${CAROUSEL_SLIDE_RADIUS_STYLES}; ${CAROUSEL_BORDER_SHADOW_STYLES}; @@ -465,6 +465,12 @@ const CLASS_NAMES_STYLES = css` } ` +const FADE_STYLES = css` + .embla__slide__img { + user-select: none; + } +` + export const INFINITE_SCROLL_STYLES = css` .embla-infinite-scroll { position: relative; @@ -854,6 +860,25 @@ export const examplesCarouselClassNamesStyles = ( ) } +export const examplesCarouselFadeStyles = ( + slideSize?: string, + spacingSize?: string, + axis?: EmblaOptionsType['axis'] +): string => { + return examplesCarouselDefaultStyles( + slideSize, + spacingSize, + axis, + styledComponentsStylesToString( + IMAGE_STYLES, + CONTROLS_STYLES, + ARROWS_STYLES, + DOTS_STYLES, + FADE_STYLES + ) + ) +} + export const examplesCarouselLazyLoadStyles = ( slideSize?: string, spacingSize?: string, diff --git a/packages/embla-carousel-docs/src/components/Footer/FooterLinks.tsx b/packages/embla-carousel-docs/src/components/Footer/FooterLinks.tsx index 34a1ac858..3274a2d42 100644 --- a/packages/embla-carousel-docs/src/components/Footer/FooterLinks.tsx +++ b/packages/embla-carousel-docs/src/components/Footer/FooterLinks.tsx @@ -15,6 +15,7 @@ const FooterLinksWrapper = styled.ul` ${createGapStyles(LINK_SPACING, '', 'li')}; display: flex; flex-wrap: wrap; + justify-content: center; ` const Link = styled(LinkBare)` diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/CarouselGenerator/EmblaCarouselArrowButtons.tsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/CarouselGenerator/EmblaCarouselArrowButtons.tsx index f50986b69..dd853773a 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/CarouselGenerator/EmblaCarouselArrowButtons.tsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/CarouselGenerator/EmblaCarouselArrowButtons.tsx @@ -55,6 +55,7 @@ export const usePrevNextButtons = ( onSelect(emblaApi) emblaApi.on('reInit', onSelect) emblaApi.on('select', onSelect) + emblaApi.on('slideFocus', onSelect) }, [emblaApi, onSelect]) return { 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 2b44a7108..2de99190b 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 @@ -51,6 +51,7 @@ export const useDotButton = ( emblaApi.on('reInit', onInit) emblaApi.on('reInit', onSelect) emblaApi.on('select', onSelect) + emblaApi.on('slideFocus', onSelect) }, [emblaApi, onInit, onSelect]) return { diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/EmblaCarouselArrowButtons.tsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/EmblaCarouselArrowButtons.tsx index 3ab01c591..ac82d8a3a 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/EmblaCarouselArrowButtons.tsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/EmblaCarouselArrowButtons.tsx @@ -41,8 +41,10 @@ export const usePrevNextButtons = ( if (!emblaApi) return onSelect(emblaApi) - emblaApi.on('reInit', onSelect) - emblaApi.on('select', onSelect) + emblaApi + .on('reInit', onSelect) + .on('select', onSelect) + .on('slideFocus', onSelect) }, [emblaApi, onSelect]) return { 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 1653fca2c..e3d8ee31c 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 @@ -41,9 +41,12 @@ export const useDotButton = ( onInit(emblaApi) onSelect(emblaApi) - emblaApi.on('reInit', onInit) - emblaApi.on('reInit', onSelect) - emblaApi.on('select', onSelect) + + emblaApi + .on('reInit', onInit) + .on('reInit', onSelect) + .on('select', onSelect) + .on('slideFocus', onSelect) }, [emblaApi, onInit, onSelect]) return { diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Fade/EmblaCarousel.tsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Fade/EmblaCarousel.tsx new file mode 100644 index 000000000..7a749a888 --- /dev/null +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Fade/EmblaCarousel.tsx @@ -0,0 +1,70 @@ +import React from 'react' +import { EmblaOptionsType } from 'embla-carousel' +import useEmblaCarousel from 'embla-carousel-react' +import Fade from 'embla-carousel-fade' +import { + NextButton, + PrevButton, + usePrevNextButtons +} from '../EmblaCarouselArrowButtons' +import { DotButton, useDotButton } from '../EmblaCarouselDotButton' +import { sandboxImages } from 'components/Sandbox/sandboxImages' + +type PropType = { + slides: number[] + options?: EmblaOptionsType +} + +const EmblaCarousel: React.FC = (props) => { + const { slides, options } = props + const [emblaRef, emblaApi] = useEmblaCarousel(options, [Fade()]) + + const { selectedIndex, scrollSnaps, onDotButtonClick } = + useDotButton(emblaApi) + + const { + prevBtnDisabled, + nextBtnDisabled, + onPrevButtonClick, + onNextButtonClick + } = usePrevNextButtons(emblaApi) + + return ( +
+
+
+ {slides.map((index) => ( +
+ Your alt text +
+ ))} +
+
+ +
+
+ + +
+ +
+ {scrollSnaps.map((_, index) => ( + onDotButtonClick(index)} + className={'embla__dot'.concat( + index === selectedIndex ? ' embla__dot--selected' : '' + )} + /> + ))} +
+
+
+ ) +} + +export default EmblaCarousel diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Opacity/EmblaCarousel.tsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Opacity/EmblaCarousel.tsx index 0be53e929..260426f1d 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Opacity/EmblaCarousel.tsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Opacity/EmblaCarousel.tsx @@ -91,6 +91,7 @@ const EmblaCarousel: React.FC = (props) => { .on('reInit', setTweenFactor) .on('reInit', tweenOpacity) .on('scroll', tweenOpacity) + .on('slideFocus', tweenOpacity) }, [emblaApi, tweenOpacity]) return ( diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Parallax/EmblaCarousel.tsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Parallax/EmblaCarousel.tsx index a00266ea8..2ea13b83b 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Parallax/EmblaCarousel.tsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Parallax/EmblaCarousel.tsx @@ -98,6 +98,7 @@ const EmblaCarousel: React.FC = (props) => { .on('reInit', setTweenFactor) .on('reInit', tweenParallax) .on('scroll', tweenParallax) + .on('slideFocus', tweenParallax) }, [emblaApi, tweenParallax]) return ( diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Progress/EmblaCarousel.tsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Progress/EmblaCarousel.tsx index 9ff312b58..2f3f996e3 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Progress/EmblaCarousel.tsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Progress/EmblaCarousel.tsx @@ -33,8 +33,10 @@ const EmblaCarousel: React.FC = (props) => { if (!emblaApi) return onScroll(emblaApi) - emblaApi.on('reInit', onScroll) - emblaApi.on('scroll', onScroll) + emblaApi + .on('reInit', onScroll) + .on('scroll', onScroll) + .on('slideFocus', onScroll) }, [emblaApi, onScroll]) return ( diff --git a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Scale/EmblaCarousel.tsx b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Scale/EmblaCarousel.tsx index bc4a6e01d..73b0169c7 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Scale/EmblaCarousel.tsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/React/SandboxFilesSrc/Scale/EmblaCarousel.tsx @@ -101,6 +101,7 @@ const EmblaCarousel: React.FC = (props) => { .on('reInit', setTweenFactor) .on('reInit', tweenScale) .on('scroll', tweenScale) + .on('slideFocus', tweenScale) }, [emblaApi, tweenScale]) return ( 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 903d05a4c..ee7a1d927 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 @@ -34,8 +34,8 @@ const EmblaCarousel: React.FC = (props) => { useEffect(() => { if (!emblaMainApi) return onSelect() - emblaMainApi.on('select', onSelect) - emblaMainApi.on('reInit', onSelect) + + emblaMainApi.on('select', onSelect).on('reInit', onSelect) }, [emblaMainApi, onSelect]) return ( diff --git a/packages/embla-carousel-docs/src/components/Sandbox/SandboxGeneratorExampleArrowButtons.tsx b/packages/embla-carousel-docs/src/components/Sandbox/SandboxGeneratorExampleArrowButtons.tsx index 2504b99ed..6cef0c80c 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/SandboxGeneratorExampleArrowButtons.tsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/SandboxGeneratorExampleArrowButtons.tsx @@ -41,8 +41,7 @@ export const usePrevNextButtons = ( if (!emblaApi) return onSelect(emblaApi) - emblaApi.on('reInit', onSelect) - emblaApi.on('select', onSelect) + emblaApi.on('reInit', onSelect).on('select', onSelect) }, [emblaApi, onSelect]) return { diff --git a/packages/embla-carousel-docs/src/components/Sandbox/SandboxGeneratorExampleDotButton.tsx b/packages/embla-carousel-docs/src/components/Sandbox/SandboxGeneratorExampleDotButton.tsx index 1653fca2c..1f74adcc1 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/SandboxGeneratorExampleDotButton.tsx +++ b/packages/embla-carousel-docs/src/components/Sandbox/SandboxGeneratorExampleDotButton.tsx @@ -41,9 +41,7 @@ export const useDotButton = ( onInit(emblaApi) onSelect(emblaApi) - emblaApi.on('reInit', onInit) - emblaApi.on('reInit', onSelect) - emblaApi.on('select', onSelect) + emblaApi.on('reInit', onInit).on('reInit', onSelect).on('select', onSelect) }, [emblaApi, onInit, onSelect]) return { diff --git a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/CarouselGenerator/EmblaCarousel.ts b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/CarouselGenerator/EmblaCarousel.ts index 9e65bb08c..29bcd3105 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/CarouselGenerator/EmblaCarousel.ts +++ b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/CarouselGenerator/EmblaCarousel.ts @@ -84,10 +84,7 @@ const removeDotBtnsAndClickHandlers = addDotBtnsAndClickHandlers( ) /*__DOT_BUTTONS_REPLACE_END__*/ /*__SELECTED_SNAP_DISPLAY_REPLACE_START__*/ -const stopSelectedSnapDisplay = updateSelectedSnapDisplay( - emblaApi, - snapDisplayNode -) +updateSelectedSnapDisplay(emblaApi, snapDisplayNode) /*__SELECTED_SNAP_DISPLAY_REPLACE_END__*/ /*__PREV_NEXT_BUTTONS_REPLACE_START__*/ @@ -96,6 +93,3 @@ emblaApi.on('destroy', removePrevNextBtnsClickHandlers) /*__DOT_BUTTONS_REPLACE_START__*/ emblaApi.on('destroy', removeDotBtnsAndClickHandlers) /*__DOT_BUTTONS_REPLACE_END__*/ -/*__SELECTED_SNAP_DISPLAY_REPLACE_START__*/ -emblaApi.on('destroy', stopSelectedSnapDisplay) -/*__SELECTED_SNAP_DISPLAY_REPLACE_END__*/ diff --git a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/CarouselGenerator/EmblaCarouselSelectedSnapDisplay.ts b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/CarouselGenerator/EmblaCarouselSelectedSnapDisplay.ts index 253455ba6..0758faa51 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/CarouselGenerator/EmblaCarouselSelectedSnapDisplay.ts +++ b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/CarouselGenerator/EmblaCarouselSelectedSnapDisplay.ts @@ -3,7 +3,7 @@ import { EmblaCarouselType } from 'embla-carousel' export const updateSelectedSnapDisplay = ( emblaApi: EmblaCarouselType, snapDisplay: HTMLElement -): (() => void) => { +): void => { const updateSnapDisplay = (emblaApi: EmblaCarouselType) => { const selectedSnap = emblaApi.selectedScrollSnap() const snapCount = emblaApi.scrollSnapList().length @@ -11,9 +11,6 @@ export const updateSelectedSnapDisplay = ( } emblaApi.on('select', updateSnapDisplay).on('reInit', updateSnapDisplay) - updateSnapDisplay(emblaApi) - return () => { - emblaApi.off('select', updateSnapDisplay).off('reInit', updateSnapDisplay) - } + updateSnapDisplay(emblaApi) } diff --git a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Opacity/EmblaCarouselTweenOpacity.ts b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Opacity/EmblaCarouselTweenOpacity.ts index 52e6d56b9..6efb28e22 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Opacity/EmblaCarouselTweenOpacity.ts +++ b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Opacity/EmblaCarouselTweenOpacity.ts @@ -62,12 +62,9 @@ export const setupTweenOpacity = ( .on('reInit', setTweenFactor) .on('reInit', tweenOpacity) .on('scroll', tweenOpacity) + .on('slideFocus', tweenOpacity) return (): void => { slideNodes.forEach((slide) => slide.removeAttribute('style')) - emblaApi - .off('reInit', setTweenFactor) - .off('reInit', tweenOpacity) - .off('scroll', tweenOpacity) } } diff --git a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Parallax/EmblaCarouselTweenParallax.ts b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Parallax/EmblaCarouselTweenParallax.ts index a8a591d68..3445d6cbb 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Parallax/EmblaCarouselTweenParallax.ts +++ b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Parallax/EmblaCarouselTweenParallax.ts @@ -66,13 +66,9 @@ export const setupTweenParallax = ( .on('reInit', setTweenFactor) .on('reInit', tweenParallax) .on('scroll', tweenParallax) + .on('slideFocus', tweenParallax) return (): void => { tweenNodes.forEach((slide) => slide.removeAttribute('style')) - emblaApi - .off('reInit', setTweenNodes) - .off('reInit', setTweenFactor) - .off('reInit', tweenParallax) - .off('scroll', tweenParallax) } } diff --git a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Progress/EmblaCarousel.ts b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Progress/EmblaCarousel.ts index 17e93bb96..3d9ceadcd 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Progress/EmblaCarousel.ts +++ b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Progress/EmblaCarousel.ts @@ -31,5 +31,6 @@ emblaApi .on('init', applyProgress) .on('reInit', applyProgress) .on('scroll', applyProgress) + .on('slideFocus', applyProgress) .on('destroy', removeProgress) .on('destroy', removePrevNextBtnsClickHandlers) diff --git a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Scale/EmblaCarouselTweenScale.ts b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Scale/EmblaCarouselTweenScale.ts index a2e649de8..45e7608ea 100644 --- a/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Scale/EmblaCarouselTweenScale.ts +++ b/packages/embla-carousel-docs/src/components/Sandbox/Vanilla/SandboxFilesSrc/Scale/EmblaCarouselTweenScale.ts @@ -68,13 +68,9 @@ export const setupTweenScale = (emblaApi: EmblaCarouselType): (() => void) => { .on('reInit', setTweenFactor) .on('reInit', tweenScale) .on('scroll', tweenScale) + .on('slideFocus', tweenScale) return (): void => { tweenNodes.forEach((slide) => slide.removeAttribute('style')) - emblaApi - .off('reInit', setTweenNodes) - .off('reInit', setTweenFactor) - .off('reInit', tweenScale) - .off('scroll', tweenScale) } } diff --git a/packages/embla-carousel-docs/src/content/pages/examples/predefined.mdx b/packages/embla-carousel-docs/src/content/pages/examples/predefined.mdx index 692663bb8..3b9e9ea15 100644 --- a/packages/embla-carousel-docs/src/content/pages/examples/predefined.mdx +++ b/packages/embla-carousel-docs/src/content/pages/examples/predefined.mdx @@ -21,6 +21,7 @@ import { Thumbs } from 'components/Examples/Basic/Thumbs' import { Autoplay } from 'components/Examples/Plugins/Autoplay' import { AutoScroll } from 'components/Examples/Plugins/AutoScroll' import { AutoHeight } from 'components/Examples/Plugins/AutoHeight' +import { Fade } from 'components/Examples/Plugins/Fade' import { ClassNames } from 'components/Examples/Plugins/ClassNames' {/* Tween */} @@ -100,6 +101,10 @@ Extend your carousels with [plugins](/plugins/) and easily add features. +### Fade + + + ### Class Names diff --git a/packages/embla-carousel-docs/src/content/pages/plugins/auto-height.mdx b/packages/embla-carousel-docs/src/content/pages/plugins/auto-height.mdx index 79395670a..b5e030acb 100644 --- a/packages/embla-carousel-docs/src/content/pages/plugins/auto-height.mdx +++ b/packages/embla-carousel-docs/src/content/pages/plugins/auto-height.mdx @@ -80,18 +80,3 @@ If you've been following along with any of the guides in the [get started](/get- ``` - -## Options - -Below follows an exhaustive **list of all** `Auto Height` **options** and their default values. - ---- - -### destroyHeight - -Type: `CSSStyleDeclaration.height` -Default: `auto` - -Choose CSS height declaration that will be applied to the carousel container when the plugin is destroyed. - ---- diff --git a/packages/embla-carousel-docs/src/content/pages/plugins/fade.mdx b/packages/embla-carousel-docs/src/content/pages/plugins/fade.mdx new file mode 100644 index 000000000..aa3f0b5db --- /dev/null +++ b/packages/embla-carousel-docs/src/content/pages/plugins/fade.mdx @@ -0,0 +1,89 @@ +--- +title: Fade +description: Learn how to use the Fade plugin for Embla Carousel +order: 4 +date: 2024-05-15 +--- + +import { Tabs } from 'components/Tabs/Tabs' +import { TabsItem } from 'components/Tabs/TabsItem' +import { TABS_PACKAGE_MANAGER } from 'consts/tabs' +import { Fade } from 'components/Examples/Plugins/Fade' + +# Fade + + + View plugin on GitHub + + +This plugin is used to replace the Embla Carousel scroll functionality with **fade transitions**. + +--- + +## Example + + + +## Installation + +Start by installing the **npm package** and save it to your dependencies: + + + + + ```html + + ``` + + + + + ```shell + npm install embla-carousel-fade --save + ``` + + + + + ```shell + yarn add embla-carousel-fade + ``` + + + + + + When the Fade plugin is enabled, the + [inViewThreshold](/api/options#inviewthreshold) option no longer has any + effect. This is because the Fade plugin stacks any slides with an **opacity + higher** than `0` on top of each other, eliminating the concept of scrolling + and gradual appearance of slides. + + + + +If your slides are less than 100% of the viewport width, it's recommended to set these options when using the Fade plugin to avoid confusing UX: + +
+ +```ts +const options = { + align: 'center', + containScroll: false +} +``` + +
+
+ +However, `align: center` is default so you can omit setting the align option and achieve the same thing like so: + +
+ +```ts +const options = { + containScroll: false +} +``` + +
diff --git a/packages/embla-carousel-docs/src/content/pages/plugins/wheel-gestures.mdx b/packages/embla-carousel-docs/src/content/pages/plugins/wheel-gestures.mdx index 0f77e34b6..17c38528b 100644 --- a/packages/embla-carousel-docs/src/content/pages/plugins/wheel-gestures.mdx +++ b/packages/embla-carousel-docs/src/content/pages/plugins/wheel-gestures.mdx @@ -1,7 +1,7 @@ --- title: Wheel Gestures description: Learn how to add this Wheel Gesture plugin to Embla Carousel -order: 4 +order: 5 date: 2021-11-22 --- diff --git a/packages/embla-carousel-fade/.eslintignore b/packages/embla-carousel-fade/.eslintignore new file mode 100644 index 000000000..f5efc98ab --- /dev/null +++ b/packages/embla-carousel-fade/.eslintignore @@ -0,0 +1,5 @@ +docs +package.json +package-lock.json +yarn.lock +node_modules diff --git a/packages/embla-carousel-fade/.eslintrc.js b/packages/embla-carousel-fade/.eslintrc.js new file mode 100644 index 000000000..b17ac0422 --- /dev/null +++ b/packages/embla-carousel-fade/.eslintrc.js @@ -0,0 +1,28 @@ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module' + }, + extends: [ + 'eslint:recommended', + 'plugin:prettier/recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended' + ], + rules: { + 'no-debugger': 2, + 'no-console': 2, + '@typescript-eslint/no-inferrable-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/ban-types': [ + 'error', + { + types: { + '{}': false + }, + extendDefaults: true + } + ] + } +} diff --git a/packages/embla-carousel-fade/.prettierrc.js b/packages/embla-carousel-fade/.prettierrc.js new file mode 100644 index 000000000..5100fc98a --- /dev/null +++ b/packages/embla-carousel-fade/.prettierrc.js @@ -0,0 +1 @@ +module.exports = require('../../.prettierrc.js') diff --git a/packages/embla-carousel-fade/README.md b/packages/embla-carousel-fade/README.md new file mode 100644 index 000000000..7155d61e3 --- /dev/null +++ b/packages/embla-carousel-fade/README.md @@ -0,0 +1,209 @@ +
+
+

+ Embla Carousel + +

+ +

+ + + + + + +

+ + +

Embla Carousel Fade

+
+ +

+ Embla Carousel is a bare bones carousel library with great fluid motion and awesome swipe precision. It's library agnostic, dependency free and 100% open source. +

+ +
+ +

+ +  Examples  + +

+ +

+ +  Generator  + +

+ +

+ +  Installation  + +

+
+ +
+ +
+ +

Ready for

+
+ +

+ + + + + + + + + + + + + + + + + + + + + +

+
+ +
+ + + +
+ +
+ +

Special Thanks

+
+

+ + gunnarx2 - React wrapper useEmblaCarousel. + +
+ + LiamMartens - Solid wrapper createEmblaCarousel. + +
+ + donaldxdonald, zip-fa, JeanMeche - Angular wrapper EmblaCarouselDirective. + +
+ + xiel - Plugin Embla Carousel Wheel Gestures. + +
+ + zaaakher - Contributing guidelines. + +

+
+ +
+ +

Open Source

+ +

+ Embla is MIT licensed 💖.

+ Embla Carousel - Copyright © 2019-present.
+ Package created by David Jerleke. +

+ +

+ · · · +

+ +

+ Thanks BrowserStack. +

+ +

+ + + +

diff --git a/packages/embla-carousel-fade/jest.config.js b/packages/embla-carousel-fade/jest.config.js new file mode 100644 index 000000000..04a0181f9 --- /dev/null +++ b/packages/embla-carousel-fade/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + transform: { + '^.+\\.(t|j)sx?$': 'ts-jest' + }, + testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$', + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + testEnvironment: 'jsdom' +} diff --git a/packages/embla-carousel-fade/package.json b/packages/embla-carousel-fade/package.json new file mode 100644 index 000000000..165c4dc02 --- /dev/null +++ b/packages/embla-carousel-fade/package.json @@ -0,0 +1,77 @@ +{ + "name": "embla-carousel-fade", + "version": "8.0.4", + "author": "David Jerleke", + "description": "A fade plugin for Embla Carousel", + "repository": { + "type": "git", + "url": "git+https://github.com/davidjerleke/embla-carousel" + }, + "bugs": { + "url": "https://github.com/davidjerleke/embla-carousel/issues" + }, + "homepage": "https://www.embla-carousel.com", + "license": "MIT", + "keywords": [ + "slider", + "carousel", + "slideshow", + "gallery", + "lightweight", + "touch", + "javascript", + "typescript", + "react", + "vue", + "svelte", + "solid" + ], + "main": "embla-carousel-fade.umd.js", + "unpkg": "embla-carousel-fade.umd.js", + "module": "./esm/embla-carousel-fade.esm.js", + "types": "index.d.ts", + "sideEffects": false, + "files": [ + "embla-carousel-fade*", + "components/**/*", + "index.d.ts", + "esm/**/*", + "cjs/**/*" + ], + "scripts": { + "test": "echo \"Info: no tests specified\" && exit 0", + "build": "rollup --bundleConfigAsCjs -c", + "start": "rollup --bundleConfigAsCjs -c --watch --environment BUILD:development", + "eslint:report": "eslint \"src/**/*.{js,tsx,ts}\"" + }, + "devDependencies": { + "@types/jest": "^29.5.6", + "@typescript-eslint/eslint-plugin": "^6.9.0", + "@typescript-eslint/parser": "^6.9.0", + "eslint": "^8.52.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^4.0.0", + "jest": "^29.5.0", + "jest-environment-jsdom": "^29.5.0", + "prettier": "2.8.8", + "rollup": "^4.1.5", + "ts-jest": "^29.1.1", + "typescript": "^5.2.2" + }, + "peerDependencies": { + "embla-carousel": "8.0.2" + }, + "exports": { + "./package.json": "./package.json", + ".": { + "import": { + "types": "./esm/index.d.ts", + "default": "./esm/embla-carousel-fade.esm.js" + }, + "require": { + "types": "./cjs/index.d.ts", + "default": "./cjs/embla-carousel-fade.cjs.js" + } + } + } +} diff --git a/packages/embla-carousel-fade/rollup.config.js b/packages/embla-carousel-fade/rollup.config.js new file mode 100644 index 000000000..b9fda5710 --- /dev/null +++ b/packages/embla-carousel-fade/rollup.config.js @@ -0,0 +1,53 @@ +import packageJson from './package.json' +import { + FOLDERS, + CONFIG_BABEL, + CONFIG_TYPESCRIPT, + CONFIG_GLOBALS, + babel, + typescript, + resolve, + terser, + createBuildPath, + kebabToPascalCase, + createNodeNextSupport +} from '../../rollup.config' + +export default [ + { + input: 'src/index.ts', + output: [ + { + file: createBuildPath(packageJson, FOLDERS.CJS), + format: FOLDERS.CJS, + globals: CONFIG_GLOBALS, + strict: true, + sourcemap: true, + exports: 'auto' + }, + { + file: createBuildPath(packageJson, FOLDERS.ESM), + format: FOLDERS.ESM, + globals: CONFIG_GLOBALS, + strict: true, + sourcemap: true + }, + { + file: createBuildPath(packageJson, FOLDERS.UMD), + format: FOLDERS.UMD, + globals: CONFIG_GLOBALS, + strict: true, + sourcemap: false, + name: kebabToPascalCase(packageJson.name), + plugins: [terser()] + } + ], + external: Object.keys(CONFIG_GLOBALS), + plugins: [ + resolve(), + typescript(CONFIG_TYPESCRIPT), + babel(CONFIG_BABEL), + createNodeNextSupport() + ] + } +] diff --git a/packages/embla-carousel-fade/src/components/Fade.ts b/packages/embla-carousel-fade/src/components/Fade.ts new file mode 100644 index 000000000..2d4b9638c --- /dev/null +++ b/packages/embla-carousel-fade/src/components/Fade.ts @@ -0,0 +1,254 @@ +import { OptionsType } from './Options' +import { CreatePluginType } from 'embla-carousel/components/Plugins' +import { EmblaCarouselType } from 'embla-carousel' +import { isNumber, clampNumber } from './utils' +import { ScrollBodyType } from 'embla-carousel/components/ScrollBody' + +declare module 'embla-carousel/components/Plugins' { + interface EmblaPluginsType { + fade?: FadeType + } +} + +export type FadeType = CreatePluginType<{}, OptionsType> + +export type FadeOptionsType = FadeType['options'] + +function Fade(userOptions: FadeOptionsType = {}): FadeType { + const fullOpacity = 1 + const noOpacity = 0 + const fadeFriction = 0.68 + + let emblaApi: EmblaCarouselType + let opacities: number[] = [] + let fadeToNextDistance: number + let distanceFromPointerDown = 0 + let fadeVelocity = 0 + let progress = 0 + let defaultSettledBehaviour: ScrollBodyType['settled'] + let defaultProgressBehaviour: EmblaCarouselType['scrollProgress'] + + function init(emblaApiInstance: EmblaCarouselType): void { + emblaApi = emblaApiInstance + + const selectedSnap = emblaApi.selectedScrollSnap() + const { scrollBody, containerRect, axis } = emblaApi.internalEngine() + const containerSize = axis.measureSize(containerRect) + fadeToNextDistance = clampNumber(containerSize * 0.75, 200, 500) + + opacities = emblaApi + .scrollSnapList() + .map((_, index) => (index === selectedSnap ? fullOpacity : noOpacity)) + + disableScroll() + fadeToSelectedSnapInstantly() + + defaultSettledBehaviour = scrollBody.settled + defaultProgressBehaviour = emblaApi.scrollProgress + + scrollBody.settled = settled + emblaApi.scrollProgress = scrollProgress + + emblaApi + .on('select', select) + .on('slideFocus', fadeToSelectedSnapInstantly) + .on('pointerDown', pointerDown) + } + + function destroy(): void { + const { scrollBody } = emblaApi.internalEngine() + scrollBody.settled = defaultSettledBehaviour + emblaApi.scrollProgress = defaultProgressBehaviour + + emblaApi + .off('select', select) + .off('slideFocus', fadeToSelectedSnapInstantly) + .off('pointerDown', pointerDown) + + emblaApi.slideNodes().forEach((slideNode) => { + const slideStyle = slideNode.style + slideStyle.opacity = '' + slideStyle.transform = '' + slideStyle.pointerEvents = '' + if (!slideNode.getAttribute('style')) slideNode.removeAttribute('style') + }) + } + + function fadeToSelectedSnapInstantly(): void { + const selectedSnap = emblaApi.selectedScrollSnap() + setOpacities(selectedSnap, fullOpacity) + } + + function pointerDown(): void { + distanceFromPointerDown = 0 + fadeVelocity = 0 + } + + function select(): void { + const duration = emblaApi.internalEngine().scrollBody.duration() + fadeVelocity = duration ? 0 : fullOpacity + if (!duration) fadeToSelectedSnapInstantly() + } + + function getSlideTransform(position: number): string { + const { axis } = emblaApi.internalEngine() + const translateAxis = axis.scroll.toUpperCase() + return `translate${translateAxis}(${axis.direction(position)}px)` + } + + function disableScroll(): void { + const { translate, slideLooper } = emblaApi.internalEngine() + + translate.clear() + translate.toggleActive(false) + + slideLooper.loopPoints.forEach(({ translate }) => { + translate.clear() + translate.toggleActive(false) + }) + } + + function lockExcessiveScroll(fadeIndex: number | null): void { + const { scrollSnaps, location, target } = emblaApi.internalEngine() + if (!isNumber(fadeIndex) || opacities[fadeIndex] < 0.5) return + + location.set(scrollSnaps[fadeIndex]) + target.set(location) + } + + function setOpacities(fadeIndex: number, velocity: number): void { + const scrollSnaps = emblaApi.scrollSnapList() + + scrollSnaps.forEach((_, index) => { + const absVelocity = Math.abs(velocity) + const currentOpacity = opacities[index] + const isFadeIndex = index === fadeIndex + + const nextOpacity = isFadeIndex + ? currentOpacity + absVelocity + : currentOpacity - absVelocity + + const clampedOpacity = clampNumber(nextOpacity, noOpacity, fullOpacity) + opacities[index] = clampedOpacity + + if (isFadeIndex) setProgress(fadeIndex, clampedOpacity) + setOpacity(index) + }) + } + + function setOpacity(index: number): void { + const slidesInSnap = emblaApi.internalEngine().slideRegistry[index] + const { scrollSnaps, containerRect } = emblaApi.internalEngine() + const opacity = opacities[index] + + slidesInSnap.forEach((slideIndex) => { + const slideStyle = emblaApi.slideNodes()[slideIndex].style + const roundedOpacity = parseFloat(opacity.toFixed(2)) + const hasOpacity = roundedOpacity > noOpacity + const position = hasOpacity ? scrollSnaps[index] : containerRect.width + 2 + const transform = getSlideTransform(position) + + if (hasOpacity) slideStyle.transform = transform + + slideStyle.opacity = roundedOpacity.toString() + slideStyle.pointerEvents = opacity > 0.5 ? 'auto' : 'none' + + if (!hasOpacity) slideStyle.transform = transform + }) + } + + function setProgress(fadeIndex: number, opacity: number): void { + const { index, dragHandler, scrollSnaps } = emblaApi.internalEngine() + const pointerDown = dragHandler.pointerDown() + const snapFraction = 1 / (scrollSnaps.length - 1) + + let indexA = fadeIndex + let indexB = pointerDown + ? emblaApi.selectedScrollSnap() + : emblaApi.previousScrollSnap() + + if (pointerDown && indexA === indexB) { + const reverseSign = Math.sign(distanceFromPointerDown) * -1 + indexA = indexB + indexB = index.clone().set(indexB).add(reverseSign).get() + } + + const currentPosition = indexB * snapFraction + const diffPosition = (indexA - indexB) * snapFraction + progress = currentPosition + diffPosition * opacity + } + + function getFadeIndex(): number | null { + const { dragHandler, index, scrollBody } = emblaApi.internalEngine() + const selectedSnap = emblaApi.selectedScrollSnap() + + if (!dragHandler.pointerDown()) return selectedSnap + + const directionSign = Math.sign(scrollBody.velocity()) + const distanceSign = Math.sign(distanceFromPointerDown) + const nextSnap = index + .clone() + .set(selectedSnap) + .add(directionSign * -1) + .get() + + if (!directionSign || !distanceSign) return null + return distanceSign === directionSign ? nextSnap : selectedSnap + } + + const fade = (emblaApi: EmblaCarouselType): void => { + const { dragHandler, scrollBody } = emblaApi.internalEngine() + const pointerDown = dragHandler.pointerDown() + const velocity = scrollBody.velocity() + const duration = scrollBody.duration() + const fadeIndex = getFadeIndex() + const noFadeIndex = !isNumber(fadeIndex) + + if (pointerDown) { + if (!velocity) return + + distanceFromPointerDown += velocity + fadeVelocity = Math.abs(velocity / fadeToNextDistance) + lockExcessiveScroll(fadeIndex) + } + + if (!pointerDown) { + if (!duration || noFadeIndex) return + + fadeVelocity += (fullOpacity - opacities[fadeIndex]) / duration + fadeVelocity *= fadeFriction + } + + if (noFadeIndex) return + setOpacities(fadeIndex, fadeVelocity) + } + + function settled(): boolean { + const { target, location } = emblaApi.internalEngine() + const diffToTarget = target.get() - location.get() + const notReachedTarget = Math.abs(diffToTarget) >= 1 + const fadeIndex = getFadeIndex() + const noFadeIndex = !isNumber(fadeIndex) + + fade(emblaApi) + + if (noFadeIndex || notReachedTarget) return false + return opacities[fadeIndex] > 0.999 + } + + function scrollProgress(): number { + return progress + } + + const self: FadeType = { + name: 'fade', + options: userOptions, + init, + destroy + } + return self +} + +Fade.globalOptions = undefined + +export default Fade diff --git a/packages/embla-carousel-fade/src/components/Options.ts b/packages/embla-carousel-fade/src/components/Options.ts new file mode 100644 index 000000000..4500d9ff2 --- /dev/null +++ b/packages/embla-carousel-fade/src/components/Options.ts @@ -0,0 +1,3 @@ +import { CreateOptionsType } from 'embla-carousel/components/Options' + +export type OptionsType = CreateOptionsType<{}> diff --git a/packages/embla-carousel-fade/src/components/utils.ts b/packages/embla-carousel-fade/src/components/utils.ts new file mode 100644 index 000000000..1e91f287c --- /dev/null +++ b/packages/embla-carousel-fade/src/components/utils.ts @@ -0,0 +1,7 @@ +export function clampNumber(number: number, min: number, max: number): number { + return Math.min(Math.max(number, min), max) +} + +export function isNumber(value: number | null): value is number { + return typeof value === 'number' && !isNaN(value) +} diff --git a/packages/embla-carousel-fade/src/index.ts b/packages/embla-carousel-fade/src/index.ts new file mode 100644 index 000000000..cc640fc2b --- /dev/null +++ b/packages/embla-carousel-fade/src/index.ts @@ -0,0 +1,2 @@ +export { FadeType, FadeOptionsType } from './components/Fade' +export { default } from './components/Fade' diff --git a/packages/embla-carousel-fade/tsconfig.json b/packages/embla-carousel-fade/tsconfig.json new file mode 100644 index 000000000..95539428c --- /dev/null +++ b/packages/embla-carousel-fade/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2015", + "module": "esnext", + "lib": ["dom", "esnext"], + "esModuleInterop": true, + "strict": true, + "declaration": true, + "declarationDir": "./", + "moduleResolution": "node", + "noEmit": true + }, + "include": ["src/index.ts", "src/components"], + "exclude": ["node_modules", "**/*.test.ts"] +} diff --git a/packages/embla-carousel/src/__tests__/events.test.ts b/packages/embla-carousel/src/__tests__/events.test.ts index 6173efc3a..34a9cbfc1 100644 --- a/packages/embla-carousel/src/__tests__/events.test.ts +++ b/packages/embla-carousel/src/__tests__/events.test.ts @@ -29,6 +29,14 @@ describe('➡️ Events', () => { expect(callback2).toHaveBeenCalledTimes(1) }) + test('Calls the provided callback when emit() is triggered by the user', () => { + const callback = jest.fn() + + emblaApi.on('select', callback) + emblaApi.emit('select') + expect(callback).toHaveBeenCalledTimes(1) + }) + test('Will NOT fire a callback when a different event is emitted', () => { const callback = jest.fn() @@ -49,12 +57,15 @@ describe('➡️ Events', () => { expect(callback).toHaveBeenCalledTimes(1) }) - test('Calls the provided callback when emit() is triggered by the user', () => { + test('Will NOT fire any callback anymore when event store is destroyed', () => { const callback = jest.fn() emblaApi.on('select', callback) + emblaApi.on('scroll', callback) + emblaApi.destroy() emblaApi.emit('select') - expect(callback).toHaveBeenCalledTimes(1) + emblaApi.emit('scroll') + expect(callback).toHaveBeenCalledTimes(0) }) }) }) diff --git a/packages/embla-carousel/src/__tests__/mocks/testElements.mock.ts b/packages/embla-carousel/src/__tests__/mocks/testElements.mock.ts index 8d46a8620..907c11a3b 100644 --- a/packages/embla-carousel/src/__tests__/mocks/testElements.mock.ts +++ b/packages/embla-carousel/src/__tests__/mocks/testElements.mock.ts @@ -1,3 +1,22 @@ +/* +Get offsets in console to create fixtures +----------------------------------------- + +function mapToOffsets({ offsetWidth, offsetHeight, offsetTop, offsetLeft }) { + return { + offsetWidth, + offsetHeight, + offsetTop, + offsetLeft + } +} + +[ + mapToOffsets(emblaApi.containerNode()), + emblaApi.slideNodes().map(mapToOffsets) +] +*/ + type TestElementOffsetType = { offsetLeft: number offsetTop: number diff --git a/packages/embla-carousel/src/components/DragHandler.ts b/packages/embla-carousel/src/components/DragHandler.ts index 1159b6fd5..e52668c99 100644 --- a/packages/embla-carousel/src/components/DragHandler.ts +++ b/packages/embla-carousel/src/components/DragHandler.ts @@ -163,7 +163,7 @@ export function DragHandler( const diff = dragTracker.pointerMove(evt) if (diffScroll > dragThreshold) preventClick = true - scrollBody.useFriction(0.3).useDuration(1) + scrollBody.useFriction(0.3).useDuration(0.5) animation.start() target.add(direction(diff)) evt.preventDefault() @@ -201,8 +201,8 @@ export function DragHandler( const self: DragHandlerType = { init, - pointerDown, - destroy + destroy, + pointerDown } return self } diff --git a/packages/embla-carousel/src/components/EmblaCarousel.ts b/packages/embla-carousel/src/components/EmblaCarousel.ts index a55a10460..2d4feafcf 100644 --- a/packages/embla-carousel/src/components/EmblaCarousel.ts +++ b/packages/embla-carousel/src/components/EmblaCarousel.ts @@ -152,6 +152,7 @@ function EmblaCarousel( mediaHandlers.clear() deActivate() eventHandler.emit('destroy') + eventHandler.clear() } function scrollTo(index: number, jump?: boolean, direction?: number): void { diff --git a/packages/embla-carousel/src/components/Engine.ts b/packages/embla-carousel/src/components/Engine.ts index efde79b11..03d716807 100644 --- a/packages/embla-carousel/src/components/Engine.ts +++ b/packages/embla-carousel/src/components/Engine.ts @@ -256,7 +256,8 @@ export function Engine( slideRegistry, scrollTo, scrollBody, - eventStore + eventStore, + eventHandler ) // Engine diff --git a/packages/embla-carousel/src/components/EventHandler.ts b/packages/embla-carousel/src/components/EventHandler.ts index 7ea127381..aa911d748 100644 --- a/packages/embla-carousel/src/components/EventHandler.ts +++ b/packages/embla-carousel/src/components/EventHandler.ts @@ -17,6 +17,7 @@ export interface EmblaEventListType { destroy: 'destroy' reInit: 'reInit' resize: 'resize' + slideFocus: 'slideFocus' } export type EventHandlerType = { @@ -24,10 +25,11 @@ export type EventHandlerType = { emit: (evt: EmblaEventType) => EventHandlerType on: (evt: EmblaEventType, cb: CallbackType) => EventHandlerType off: (evt: EmblaEventType, cb: CallbackType) => EventHandlerType + clear: () => void } export function EventHandler(): EventHandlerType { - const listeners: ListenersType = {} + let listeners: ListenersType = {} let api: EmblaCarouselType function init(emblaApi: EmblaCarouselType): void { @@ -53,11 +55,16 @@ export function EventHandler(): EventHandlerType { return self } + function clear(): void { + listeners = {} + } + const self: EventHandlerType = { init, emit, off, - on + on, + clear } return self } diff --git a/packages/embla-carousel/src/components/ScrollTarget.ts b/packages/embla-carousel/src/components/ScrollTarget.ts index dd417dbb0..1408b2603 100644 --- a/packages/embla-carousel/src/components/ScrollTarget.ts +++ b/packages/embla-carousel/src/components/ScrollTarget.ts @@ -39,7 +39,7 @@ export function ScrollTarget( function shortcut(target: number, direction: number): number { const targets = [target, target + contentSize, target - contentSize] - if (!loop) return targets[0] + if (!loop) return target if (!direction) return minDistance(targets) const matchingTargets = targets.filter((t) => mathSign(t) === direction) diff --git a/packages/embla-carousel/src/components/SlideFocus.ts b/packages/embla-carousel/src/components/SlideFocus.ts index 1df0e4f5c..a101e6413 100644 --- a/packages/embla-carousel/src/components/SlideFocus.ts +++ b/packages/embla-carousel/src/components/SlideFocus.ts @@ -1,3 +1,4 @@ +import { EventHandlerType } from './EventHandler' import { EventStoreType } from './EventStore' import { ScrollBodyType } from './ScrollBody' import { ScrollToType } from './ScrollTo' @@ -14,7 +15,8 @@ export function SlideFocus( slideRegistry: SlideRegistryType['slideRegistry'], scrollTo: ScrollToType, scrollBody: ScrollBodyType, - eventStore: EventStoreType + eventStore: EventStoreType, + eventHandler: EventHandlerType ): SlideFocusType { let lastTabPressTime = 0 @@ -42,6 +44,7 @@ export function SlideFocus( scrollBody.useDuration(0) scrollTo.index(group, 0) + eventHandler.emit('slideFocus') } eventStore.add(slide, 'focus', focus, { diff --git a/playgrounds/embla-carousel-playground-react/src/Carousel/Carousel.tsx b/playgrounds/embla-carousel-playground-react/src/Carousel/Carousel.tsx index a754b912f..2ad513c67 100644 --- a/playgrounds/embla-carousel-playground-react/src/Carousel/Carousel.tsx +++ b/playgrounds/embla-carousel-playground-react/src/Carousel/Carousel.tsx @@ -44,9 +44,12 @@ export const EmblaCarousel: React.FC = (props) => { onInit(emblaApi) onSelect(emblaApi) - emblaApi.on('reInit', onInit) - emblaApi.on('reInit', onSelect) - emblaApi.on('select', onSelect) + + emblaApi + .on('reInit', onInit) + .on('reInit', onSelect) + .on('select', onSelect) + .on('slideFocus', onSelect) }, [emblaApi, onInit, onSelect]) return ( <> diff --git a/playgrounds/embla-carousel-playground-react/src/main.css b/playgrounds/embla-carousel-playground-react/src/main.css index b4c8be786..1aebc6865 100644 --- a/playgrounds/embla-carousel-playground-react/src/main.css +++ b/playgrounds/embla-carousel-playground-react/src/main.css @@ -1,22 +1,17 @@ .playground { padding-top: 8rem; padding-bottom: 8rem; + padding-right: 2.4rem; + padding-left: 2.4rem; width: 67rem; max-width: 100%; margin-left: auto; margin-right: auto; } -@media (min-width: 768px) { - .playground { - padding-left: 2.4rem; - padding-right: 2.4rem; - } -} - .playground__h1 { margin-bottom: 4rem; - font-size: 3.6rem; + font-size: 3.4rem; font-weight: 900; text-align: center; } diff --git a/playgrounds/embla-carousel-playground-solid/src/Carousel/Carousel.tsx b/playgrounds/embla-carousel-playground-solid/src/Carousel/Carousel.tsx index c3f937ee5..0d258250a 100644 --- a/playgrounds/embla-carousel-playground-solid/src/Carousel/Carousel.tsx +++ b/playgrounds/embla-carousel-playground-solid/src/Carousel/Carousel.tsx @@ -43,9 +43,11 @@ export const EmblaCarousel: Component = (props) => { onInit(api) onSelect(api) - api.on('reInit', onInit) - api.on('reInit', onSelect) - api.on('select', onSelect) + api + .on('reInit', onInit) + .on('reInit', onSelect) + .on('select', onSelect) + .on('slideFocus', onSelect) }) return ( diff --git a/playgrounds/embla-carousel-playground-solid/src/main.css b/playgrounds/embla-carousel-playground-solid/src/main.css index b4c8be786..1aebc6865 100644 --- a/playgrounds/embla-carousel-playground-solid/src/main.css +++ b/playgrounds/embla-carousel-playground-solid/src/main.css @@ -1,22 +1,17 @@ .playground { padding-top: 8rem; padding-bottom: 8rem; + padding-right: 2.4rem; + padding-left: 2.4rem; width: 67rem; max-width: 100%; margin-left: auto; margin-right: auto; } -@media (min-width: 768px) { - .playground { - padding-left: 2.4rem; - padding-right: 2.4rem; - } -} - .playground__h1 { margin-bottom: 4rem; - font-size: 3.6rem; + font-size: 3.4rem; font-weight: 900; text-align: center; } diff --git a/playgrounds/embla-carousel-playground-vanilla/src/Carousel/setupButtons.ts b/playgrounds/embla-carousel-playground-vanilla/src/Carousel/setupButtons.ts index 1fb4a61be..ef0a74dd7 100644 --- a/playgrounds/embla-carousel-playground-vanilla/src/Carousel/setupButtons.ts +++ b/playgrounds/embla-carousel-playground-vanilla/src/Carousel/setupButtons.ts @@ -17,6 +17,7 @@ const addTogglePrevNextBtnsActive = ( .on('select', togglePrevNextBtnsState) .on('init', togglePrevNextBtnsState) .on('reInit', togglePrevNextBtnsState) + .on('slideFocus', togglePrevNextBtnsState) return (): void => { prevBtn.removeAttribute('disabled') diff --git a/playgrounds/embla-carousel-playground-vanilla/src/Carousel/setupSlides.ts b/playgrounds/embla-carousel-playground-vanilla/src/Carousel/setupSlides.ts index c3675a2ae..41018c738 100644 --- a/playgrounds/embla-carousel-playground-vanilla/src/Carousel/setupSlides.ts +++ b/playgrounds/embla-carousel-playground-vanilla/src/Carousel/setupSlides.ts @@ -10,7 +10,7 @@ export const createSlides = ( (acc, index) => acc + template.innerHTML.replace( - '__replace_slide_index__', + /__replace_slide_index__/g, (index + 1).toString() ), '' diff --git a/playgrounds/embla-carousel-playground-vanilla/src/main.css b/playgrounds/embla-carousel-playground-vanilla/src/main.css index b4c8be786..1aebc6865 100644 --- a/playgrounds/embla-carousel-playground-vanilla/src/main.css +++ b/playgrounds/embla-carousel-playground-vanilla/src/main.css @@ -1,22 +1,17 @@ .playground { padding-top: 8rem; padding-bottom: 8rem; + padding-right: 2.4rem; + padding-left: 2.4rem; width: 67rem; max-width: 100%; margin-left: auto; margin-right: auto; } -@media (min-width: 768px) { - .playground { - padding-left: 2.4rem; - padding-right: 2.4rem; - } -} - .playground__h1 { margin-bottom: 4rem; - font-size: 3.6rem; + font-size: 3.4rem; font-weight: 900; text-align: center; } diff --git a/playgrounds/embla-carousel-playground-vanilla/src/main.ts b/playgrounds/embla-carousel-playground-vanilla/src/main.ts index f54b18bab..1797a33ff 100644 --- a/playgrounds/embla-carousel-playground-vanilla/src/main.ts +++ b/playgrounds/embla-carousel-playground-vanilla/src/main.ts @@ -80,8 +80,10 @@ emblaNodes.forEach((emblaNode) => { addPrevNextBtnsClickHandlers(emblaApi, prevBtnNode, nextBtnNode) addDotBtnsClickHandlers(emblaApi, dotNodes) - emblaApi.on('select', toggleDotButtonsActive) - emblaApi.on('init', toggleDotButtonsActive) + emblaApi + .on('init', toggleDotButtonsActive) + .on('select', toggleDotButtonsActive) + .on('slideFocus', toggleDotButtonsActive) //@ts-ignore window.embla = emblaApi diff --git a/yarn.lock b/yarn.lock index d0b25fa1e..2940df24c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8330,6 +8330,7 @@ __metadata: embla-carousel-auto-scroll: 8.0.4 embla-carousel-autoplay: 8.0.4 embla-carousel-class-names: 8.0.4 + embla-carousel-fade: 8.0.4 embla-carousel-react: 8.0.4 eslint: ^8.52.0 eslint-config-prettier: ^9.0.0 @@ -8365,6 +8366,27 @@ __metadata: languageName: unknown linkType: soft +"embla-carousel-fade@8.0.4, embla-carousel-fade@workspace:packages/embla-carousel-fade": + version: 0.0.0-use.local + resolution: "embla-carousel-fade@workspace:packages/embla-carousel-fade" + dependencies: + "@types/jest": ^29.5.6 + "@typescript-eslint/eslint-plugin": ^6.9.0 + "@typescript-eslint/parser": ^6.9.0 + eslint: ^8.52.0 + eslint-config-prettier: ^9.0.0 + eslint-plugin-prettier: ^4.0.0 + jest: ^29.5.0 + jest-environment-jsdom: ^29.5.0 + prettier: 2.8.8 + rollup: ^4.1.5 + ts-jest: ^29.1.1 + typescript: ^5.2.2 + peerDependencies: + embla-carousel: 8.0.2 + languageName: unknown + linkType: soft + "embla-carousel-monorepo@workspace:.": version: 0.0.0-use.local resolution: "embla-carousel-monorepo@workspace:."