diff --git a/packages/embla-carousel-class-names/src/components/ClassNames.ts b/packages/embla-carousel-class-names/src/components/ClassNames.ts index 14f4bbc1a..d7087dd52 100644 --- a/packages/embla-carousel-class-names/src/components/ClassNames.ts +++ b/packages/embla-carousel-class-names/src/components/ClassNames.ts @@ -1,5 +1,5 @@ -import { defaultOptions, OptionsType } from './Options' -import { nodeListToArray, addClass, removeClass } from './utils' +import { defaultOptions, OptionsType, ClassNamesListType } from './Options' +import { addClass, normalizeClassNames, removeClass } from './utils' import { CreatePluginType, OptionsHandlerType, @@ -22,9 +22,19 @@ function ClassNames(userOptions: ClassNamesOptionsType = {}): ClassNamesType { let emblaApi: EmblaCarouselType let root: HTMLElement let slides: HTMLElement[] + let snappedIndexes: number[] = [] + let inViewIndexes: number[] = [] + const selectedEvents: EmblaEventType[] = ['select'] const draggingEvents: EmblaEventType[] = ['pointerDown', 'pointerUp'] const inViewEvents: EmblaEventType[] = ['slidesInView'] + const classNames: ClassNamesListType = { + snapped: [], + inView: [], + draggable: [], + dragging: [], + loop: [] + } function init( emblaApiInstance: EmblaCarouselType, @@ -39,58 +49,91 @@ function ClassNames(userOptions: ClassNamesOptionsType = {}): ClassNamesType { root = emblaApi.rootNode() slides = emblaApi.slideNodes() - const isDraggable = !!emblaApi.internalEngine().options.watchDrag - if (isDraggable) { - addClass(root, options.draggable) + const { watchDrag, loop } = emblaApi.internalEngine().options + const isDraggable = !!watchDrag + + if (options.loop && loop) { + classNames.loop = normalizeClassNames(options.loop) + addClass(root, classNames.loop) } + + if (options.draggable && isDraggable) { + classNames.draggable = normalizeClassNames(options.draggable) + addClass(root, classNames.draggable) + } + if (options.dragging) { + classNames.dragging = normalizeClassNames(options.dragging) draggingEvents.forEach((evt) => emblaApi.on(evt, toggleDraggingClass)) } + if (options.snapped) { + classNames.snapped = normalizeClassNames(options.snapped) selectedEvents.forEach((evt) => emblaApi.on(evt, toggleSnappedClasses)) toggleSnappedClasses() } + if (options.inView) { + classNames.inView = normalizeClassNames(options.inView) inViewEvents.forEach((evt) => emblaApi.on(evt, toggleInViewClasses)) toggleInViewClasses() } } function destroy(): void { - removeClass(root, options.draggable) draggingEvents.forEach((evt) => emblaApi.off(evt, toggleDraggingClass)) selectedEvents.forEach((evt) => emblaApi.off(evt, toggleSnappedClasses)) inViewEvents.forEach((evt) => emblaApi.off(evt, toggleInViewClasses)) - slides.forEach((slide) => removeClass(slide, options.snapped)) + + removeClass(root, classNames.loop) + removeClass(root, classNames.draggable) + removeClass(root, classNames.dragging) + toggleSlideClasses([], snappedIndexes, classNames.snapped) + toggleSlideClasses([], inViewIndexes, classNames.inView) } function toggleDraggingClass( _: EmblaCarouselType, evt: EmblaEventType ): void { - if (evt === 'pointerDown') addClass(root, options.dragging) - else removeClass(root, options.dragging) + const toggleClass = evt === 'pointerDown' ? addClass : removeClass + toggleClass(root, classNames.dragging) } - function toggleSlideClasses(slideIndexes: number[], className: string): void { - const container = emblaApi.containerNode() - const slideNodeList = container.querySelectorAll(`.${className}`) - const removeClassSlides = nodeListToArray(slideNodeList) + function toggleSlideClasses( + addClassIndexes: number[] = [], + removeClassIndexes: number[] = [], + classNames: string[] + ): number[] { + const removeClassSlides = removeClassIndexes.map((index) => slides[index]) + const addClassSlides = addClassIndexes.map((index) => slides[index]) - removeClassSlides.forEach((slide) => removeClass(slide, className)) - slideIndexes.forEach((index) => addClass(slides[index], className)) + removeClassSlides.forEach((slide) => removeClass(slide, classNames)) + addClassSlides.forEach((slide) => addClass(slide, classNames)) + + return addClassIndexes } function toggleSnappedClasses(): void { const { slideRegistry } = emblaApi.internalEngine() - const slideIndexes = slideRegistry[emblaApi.selectedScrollSnap()] - toggleSlideClasses(slideIndexes, options.snapped) + const newSnappedIndexes = slideRegistry[emblaApi.selectedScrollSnap()] + + snappedIndexes = toggleSlideClasses( + newSnappedIndexes, + snappedIndexes, + classNames.snapped + ) } function toggleInViewClasses(): void { - const slideIndexes = emblaApi.slidesInView() - toggleSlideClasses(slideIndexes, options.inView) + const newInViewIndexes = emblaApi.slidesInView() + + inViewIndexes = toggleSlideClasses( + newInViewIndexes, + inViewIndexes, + classNames.inView + ) } const self: ClassNamesType = { diff --git a/packages/embla-carousel-class-names/src/components/Options.ts b/packages/embla-carousel-class-names/src/components/Options.ts index 1ad584aa3..ffba0d94d 100644 --- a/packages/embla-carousel-class-names/src/components/Options.ts +++ b/packages/embla-carousel-class-names/src/components/Options.ts @@ -1,10 +1,17 @@ import { CreateOptionsType } from 'embla-carousel' +export type ClassNameOptionType = string | string[] + +export type ClassNamesListType = { + [Key in keyof Omit]: string[] +} + export type OptionsType = CreateOptionsType<{ - snapped: string - inView: string - draggable: string - dragging: string + snapped: ClassNameOptionType + inView: ClassNameOptionType + draggable: ClassNameOptionType + dragging: ClassNameOptionType + loop: ClassNameOptionType }> export const defaultOptions: OptionsType = { @@ -13,5 +20,6 @@ export const defaultOptions: OptionsType = { snapped: 'is-snapped', inView: 'is-in-view', draggable: 'is-draggable', - dragging: 'is-dragging' + dragging: 'is-dragging', + loop: 'is-loop' } diff --git a/packages/embla-carousel-class-names/src/components/utils.ts b/packages/embla-carousel-class-names/src/components/utils.ts index 0e209bd91..b044ebcdc 100644 --- a/packages/embla-carousel-class-names/src/components/utils.ts +++ b/packages/embla-carousel-class-names/src/components/utils.ts @@ -1,15 +1,16 @@ -export function removeClass(node: HTMLElement, className: string): void { - if (!node || !className) return - const { classList } = node - if (classList.contains(className)) classList.remove(className) +import { ClassNameOptionType } from './Options' + +export function normalizeClassNames(classNames: ClassNameOptionType): string[] { + const normalized = Array.isArray(classNames) ? classNames : [classNames] + return normalized.filter(Boolean) } -export function addClass(node: HTMLElement, className: string): void { - if (!node || !className) return - const { classList } = node - if (!classList.contains(className)) classList.add(className) +export function removeClass(node: HTMLElement, classNames: string[]): void { + if (!node || !classNames.length) return + node.classList.remove(...classNames) } -export function nodeListToArray(nodeList: NodeListOf): HTMLElement[] { - return Array.from(nodeList) +export function addClass(node: HTMLElement, classNames: string[]): void { + if (!node || !classNames.length) return + node.classList.add(...classNames) } diff --git a/packages/embla-carousel-docs/src/content/pages/plugins/class-names.mdx b/packages/embla-carousel-docs/src/content/pages/plugins/class-names.mdx index 4d10670b8..eb91d0403 100644 --- a/packages/embla-carousel-docs/src/content/pages/plugins/class-names.mdx +++ b/packages/embla-carousel-docs/src/content/pages/plugins/class-names.mdx @@ -64,19 +64,19 @@ Below follows an exhaustive **list of all** `Class Names` **options** and their ### snapped -Type: `string` +Type: `string | string[]` Default: `is-snapped` -Choose a classname that will be applied to the snapped slides. Pass an empty string to opt-out. +Choose a class name that will be applied to the **snapped slides**. It's also possible to pass an array of class names. Pass an empty string to opt-out. --- ### inView -Type: `string` +Type: `string | string[]` Default: `is-in-view` -Choose a classname that will be applied to slides in view. Pass an empty string to opt-out. +Choose a class name that will be applied to **slides in view**. It's also possible to pass an array of class names. Pass an empty string to opt-out. This feature will honor the [inViewThreshold](/api/options/#inviewthreshold) @@ -87,18 +87,27 @@ Choose a classname that will be applied to slides in view. Pass an empty string ### draggable -Type: `string` +Type: `string | string[]` Default: `is-draggable` -Choose a classname that will be applied to a draggable carousel container. Pass an empty string to opt-out. +Choose a class name that will be applied to a **draggable carousel**. It's also possible to pass an array of class names. Pass an empty string to opt-out. --- ### dragging -Type: `string` +Type: `string | string[]` Default: `is-dragging` -Choose a classname that will be applied to the container when dragging. Pass an empty string to opt-out. +Choose a class name that will be applied to the container **when dragging**. It's also possible to pass an array of class names. Pass an empty string to opt-out. + +--- + +### loop + +Type: `string | string[]` +Default: `is-loop` + +Choose a class name that will be applied to a carousel with **loop activated**. It's also possible to pass an array of class names. Pass an empty string to opt-out. ---