UNPKG

@mantine/carousel

Version:
285 lines (284 loc) 9.4 kB
"use client"; import { CarouselProvider } from "./Carousel.context.mjs"; import Carousel_module_default from "./Carousel.module.mjs"; import { CarouselSlide } from "./CarouselSlide/CarouselSlide.mjs"; import { CarouselContainerVariables, CarouselVariables } from "./CarouselVariables/CarouselVariables.mjs"; import { getChevronRotation } from "./get-chevron-rotation.mjs"; import { Children, createElement, useCallback, useEffect, useState } from "react"; import useEmblaCarousel from "embla-carousel-react"; import { AccordionChevron, Box, UnstyledButton, VisuallyHidden, createVarsResolver, factory, getSpacing, rem, useDirection, useProps, useRandomClassName, useStyles } from "@mantine/core"; import { clamp, useId } from "@mantine/hooks"; import { jsx, jsxs } from "react/jsx-runtime"; //#region packages/@mantine/carousel/src/Carousel.tsx const defaultProps = { controlSize: 26, controlsOffset: "sm", slideSize: "100%", slideGap: 0, orientation: "horizontal", includeGapInSize: true, initialSlide: 0, withControls: true, withIndicators: false, withKeyboardEvents: true, type: "media" }; const defaultEmblaOptions = { align: "center", loop: false, slidesToScroll: 1, dragFree: false, inViewThreshold: 0, skipSnaps: false, containScroll: "trimSnaps" }; const varsResolver = createVarsResolver((_, { height, controlSize, controlsOffset }) => ({ root: { "--carousel-height": rem(height), "--carousel-control-size": rem(controlSize), "--carousel-controls-offset": getSpacing(controlsOffset) } })); const Carousel = factory((_props) => { const props = useProps("Carousel", defaultProps, _props); const { classNames, className, style, styles, unstyled, vars, children, getEmblaApi, onNextSlide, onPreviousSlide, onSlideChange, nextControlProps, previousControlProps, controlSize, controlsOffset, slideSize, slideGap, orientation, height, includeGapInSize, draggable, initialSlide, withControls, withIndicators, plugins, nextControlIcon, previousControlIcon, withKeyboardEvents, mod, type, emblaOptions, attributes, getIndicatorProps, id, ...others } = props; const getStyles = useStyles({ name: "Carousel", classes: Carousel_module_default, props, className, style, classNames, styles, unstyled, attributes, vars, varsResolver }); const _id = useId(id); const responsiveClassName = useRandomClassName(); const { dir } = useDirection(); const [emblaRefElement, embla] = useEmblaCarousel({ axis: orientation === "horizontal" ? "x" : "y", direction: orientation === "horizontal" ? dir : void 0, startIndex: initialSlide, ...defaultEmblaOptions, ...emblaOptions }, plugins); const [selected, setSelected] = useState(0); const [slidesCount, setSlidesCount] = useState(0); const handleScroll = useCallback((index) => embla && embla.scrollTo(index), [embla]); const handleSelect = useCallback(() => { if (!embla) return; const slide = embla.selectedScrollSnap(); setSelected(slide); slide !== selected && onSlideChange?.(slide); }, [ embla, setSelected, onSlideChange, selected ]); const handlePrevious = useCallback(() => { embla?.scrollPrev(); onPreviousSlide?.(); }, [embla]); const handleNext = useCallback(() => { embla?.scrollNext(); onNextSlide?.(); }, [embla]); const handleKeydown = useCallback((event) => { if (withKeyboardEvents) { if (event.key === "ArrowRight") { event.preventDefault(); handleNext(); } if (event.key === "ArrowLeft") { event.preventDefault(); handlePrevious(); } if (event.key === "Home") { event.preventDefault(); embla?.scrollTo(0); } if (event.key === "End") { event.preventDefault(); embla?.scrollTo(embla.scrollSnapList().length - 1); } } }, [ embla, handleNext, handlePrevious ]); useEffect(() => { if (embla) { getEmblaApi?.(embla); handleSelect(); setSlidesCount(embla.scrollSnapList().length); embla.on("select", handleSelect); return () => { embla.off("select", handleSelect); }; } }, [ embla, emblaOptions?.slidesToScroll, handleSelect ]); useEffect(() => { if (embla) { embla.reInit(); setSlidesCount(embla.scrollSnapList().length); setSelected((currentSelected) => clamp(currentSelected, 0, Children.toArray(children).length - 1)); } }, [Children.toArray(children).length, emblaOptions?.slidesToScroll]); const canScrollPrev = embla?.canScrollPrev() || false; const canScrollNext = embla?.canScrollNext() || false; const handleIndicatorKeyDown = useCallback((event, index) => { const isHorizontal = orientation === "horizontal"; const nextKey = isHorizontal ? "ArrowRight" : "ArrowDown"; const prevKey = isHorizontal ? "ArrowLeft" : "ArrowUp"; if (event.key === nextKey) { event.preventDefault(); const nextIndex = index < slidesCount - 1 ? index + 1 : 0; handleScroll(nextIndex); (event.currentTarget.parentElement?.children[nextIndex])?.focus(); } if (event.key === prevKey) { event.preventDefault(); const prevIndex = index > 0 ? index - 1 : slidesCount - 1; handleScroll(prevIndex); (event.currentTarget.parentElement?.children[prevIndex])?.focus(); } if (event.key === "Home") { event.preventDefault(); handleScroll(0); (event.currentTarget.parentElement?.children[0])?.focus(); } if (event.key === "End") { event.preventDefault(); handleScroll(slidesCount - 1); (event.currentTarget.parentElement?.children[slidesCount - 1])?.focus(); } }, [ orientation, slidesCount, handleScroll ]); const indicators = Array(slidesCount).fill(0).map((_, index) => /* @__PURE__ */ createElement(UnstyledButton, { ...getStyles("indicator"), key: index, role: "tab", "aria-label": `Go to slide ${index + 1}`, "aria-selected": index === selected, tabIndex: index === selected ? 0 : -1, "data-active": index === selected || void 0, onClick: () => handleScroll(index), onKeyDown: (event) => handleIndicatorKeyDown(event, index), "data-orientation": orientation, onMouseDown: (event) => event.preventDefault(), ...getIndicatorProps?.(index) })); return /* @__PURE__ */ jsxs(CarouselProvider, { value: { getStyles, orientation }, children: [type === "container" ? /* @__PURE__ */ jsx(CarouselContainerVariables, { ...props, selector: `.${responsiveClassName}` }) : /* @__PURE__ */ jsx(CarouselVariables, { ...props, selector: `.${responsiveClassName}` }), /* @__PURE__ */ jsxs(Box, { role: "region", "aria-roledescription": "carousel", ...getStyles("root", { className: responsiveClassName }), ...others, id: _id, mod: [{ orientation, "include-gap-in-size": includeGapInSize }, mod], onKeyDownCapture: handleKeydown, children: [ /* @__PURE__ */ jsx(VisuallyHidden, { role: "status", "aria-live": "polite", "aria-atomic": "true", children: slidesCount > 0 && `Slide ${selected + 1} of ${slidesCount}` }), withControls && /* @__PURE__ */ jsxs("div", { ...getStyles("controls"), "data-orientation": orientation, children: [/* @__PURE__ */ jsx(UnstyledButton, { "aria-controls": _id, "aria-label": "Previous slide", "aria-disabled": !canScrollPrev, "data-inactive": !canScrollPrev || void 0, "data-type": "previous", tabIndex: canScrollPrev ? 0 : -1, ...previousControlProps, ...getStyles("control", { className: previousControlProps?.className, style: previousControlProps?.style }), onClick: (event) => { handlePrevious(); previousControlProps?.onClick?.(event); }, children: typeof previousControlIcon !== "undefined" ? previousControlIcon : /* @__PURE__ */ jsx(AccordionChevron, { style: { transform: `rotate(${getChevronRotation({ dir, orientation, direction: "previous" })}deg)` } }) }), /* @__PURE__ */ jsx(UnstyledButton, { "aria-controls": _id, "aria-label": "Next slide", "aria-disabled": !canScrollNext, "data-inactive": !canScrollNext || void 0, "data-type": "next", tabIndex: canScrollNext ? 0 : -1, ...getStyles("control", { className: nextControlProps?.className, style: nextControlProps?.style }), ...nextControlProps, onClick: (event) => { handleNext(); nextControlProps?.onClick?.(event); }, children: typeof nextControlIcon !== "undefined" ? nextControlIcon : /* @__PURE__ */ jsx(AccordionChevron, { style: { transform: `rotate(${getChevronRotation({ dir, orientation, direction: "next" })}deg)` } }) })] }), /* @__PURE__ */ jsx("div", { ...getStyles("viewport"), ref: emblaRefElement, "data-type": type, children: /* @__PURE__ */ jsx("div", { ...getStyles("container", { className: responsiveClassName }), "data-orientation": orientation, children }) }), withIndicators && /* @__PURE__ */ jsx("div", { ...getStyles("indicators"), role: "tablist", "aria-label": "Slides", "data-orientation": orientation, children: indicators }) ] })] }); }); Carousel.classes = Carousel_module_default; Carousel.varsResolver = varsResolver; Carousel.displayName = "@mantine/carousel/Carousel"; Carousel.Slide = CarouselSlide; //#endregion export { Carousel }; //# sourceMappingURL=Carousel.mjs.map