@yamada-ui/react
Version:
React UI components of the Yamada, by the Yamada, for the Yamada built with React and Emotion
300 lines (296 loc) • 8.6 kB
JavaScript
"use client";
import { createContext as createContext$1 } from "../../utils/context.js";
import { runKeyAction, useIds } from "../../utils/dom.js";
import { useUpdateEffect } from "../../utils/effect.js";
import { assignRef, mergeRefs } from "../../utils/ref.js";
import { utils_exports } from "../../utils/index.js";
import { useControllableState } from "../../hooks/use-controllable-state/index.js";
import { useI18n } from "../../providers/i18n-provider/i18n-provider.js";
import { useBoolean } from "../../hooks/use-boolean/index.js";
import { useCallback, useEffect, useRef, useState } from "react";
import useEmblaCarousel from "embla-carousel-react";
//#region src/components/carousel/use-carousel.ts
const [CarouselContext, useCarouselContext] = createContext$1({ name: "CarouselContext" });
const useCarousel = ({ id, align = "center", autoplay = false, containScroll = false, controlRef, defaultIndex = 0, delay = 4e3, dragFree = false, draggable = true, duration = 25, index: indexProp, inViewThreshold = 0, loop = true, orientation = "horizontal", plugins = [], skipSnaps = false, slidesToScroll = 1, stopMouseEnterAutoplay = true, watchDrag = draggable, watchResize: watchResizeProp = true, watchSlides = true, onChange, onScrollProgress,...rest } = {}) => {
const { t } = useI18n("carousel");
const [index, setIndex] = useControllableState({
defaultValue: defaultIndex,
value: indexProp,
onChange
});
const [rootId, listId] = useIds();
const [hover, { off: onMouseLeave, on: onMouseEnter }] = useBoolean();
const timeoutId = useRef(null);
const indicatorMapRef = useRef(/* @__PURE__ */ new Map());
const listRef = useRef(null);
const horizontal = orientation === "horizontal";
const axis = horizontal ? "x" : "y";
const [snapCount, setSnapCount] = useState(0);
const [total, setTotal] = useState(0);
const watchResize = useCallback((methods, entries) => {
const result = (0, utils_exports.isFunction)(watchResizeProp) ? watchResizeProp(methods, entries) : true;
const snapCount$1 = methods.scrollSnapList().length;
const total$1 = methods.slideNodes().length;
setSnapCount(snapCount$1);
setTotal(total$1);
return result;
}, [watchResizeProp]);
const [carouselRef, carousel] = useEmblaCarousel({
align,
axis,
container: listRef.current,
containScroll,
dragFree,
duration,
inViewThreshold,
loop,
skipSnaps,
slidesToScroll,
startIndex: defaultIndex,
watchDrag,
watchResize,
watchSlides
}, plugins);
id ??= rootId;
const onInit = useCallback((methods) => {
const snapCount$1 = methods.scrollSnapList().length;
const total$1 = methods.slideNodes().length;
setSnapCount(snapCount$1);
setTotal(total$1);
}, []);
const onScroll = useCallback(() => {
if (!carousel) return;
const progress = Math.round(Math.max(0, Math.min(1, carousel.scrollProgress())) * 100);
onScrollProgress?.(progress);
}, [carousel, onScrollProgress]);
const onSelect = useCallback(() => {
if (!carousel) return;
setIndex(carousel.selectedScrollSnap());
}, [carousel, setIndex]);
const onFocusIndicator = useCallback((index$1) => {
indicatorMapRef.current.get(index$1)?.focus();
carousel?.scrollTo(index$1);
}, [carousel]);
const onKeyDown = useCallback((index$1) => (ev) => {
const lastIndex = snapCount - 1;
runKeyAction(ev, {
ArrowDown: () => {
if (horizontal) return;
index$1 = index$1 === lastIndex ? 0 : index$1 + 1;
onFocusIndicator(index$1);
},
ArrowLeft: () => {
if (!horizontal) return;
index$1 = index$1 === 0 ? lastIndex : index$1 - 1;
onFocusIndicator(index$1);
},
ArrowRight: () => {
if (!horizontal) return;
index$1 = index$1 === lastIndex ? 0 : index$1 + 1;
onFocusIndicator(index$1);
},
ArrowUp: () => {
if (horizontal) return;
index$1 = index$1 === 0 ? lastIndex : index$1 - 1;
onFocusIndicator(index$1);
},
End: () => {
onFocusIndicator(lastIndex);
},
Home: () => {
onFocusIndicator(0);
}
});
}, [
snapCount,
horizontal,
onFocusIndicator
]);
assignRef(controlRef, carousel);
useEffect(() => {
if (carousel) {
carousel.on("reInit", onInit);
carousel.on("select", onSelect);
carousel.on("scroll", onScroll);
onScroll();
return () => {
carousel.off("reInit", onInit);
carousel.off("select", onSelect);
carousel.off("scroll", onScroll);
};
}
}, [
carousel,
onInit,
onScroll,
onSelect
]);
useEffect(() => {
const stop = hover && stopMouseEnterAutoplay;
const last = !carousel?.canScrollNext();
if (carousel && autoplay && !stop && !last) timeoutId.current = setInterval(() => {
carousel.scrollNext();
}, delay);
else {
if (timeoutId.current) clearInterval(timeoutId.current);
timeoutId.current = null;
}
return () => {
if (timeoutId.current) clearInterval(timeoutId.current);
};
}, [
autoplay,
carousel,
delay,
hover,
stopMouseEnterAutoplay
]);
useUpdateEffect(() => {
if (!carousel) return;
if (indexProp === void 0) return;
carousel.scrollTo(indexProp);
}, [indexProp]);
useUpdateEffect(() => {
if (!carousel) return;
carousel.reInit();
}, [
carousel,
total,
align,
axis,
containScroll,
dragFree,
duration,
inViewThreshold,
loop,
skipSnaps,
slidesToScroll
]);
const getRootProps = useCallback(({ ref,...props } = {}) => ({
id,
"aria-roledescription": "carousel",
"data-orientation": orientation,
...rest,
...props,
ref: mergeRefs(ref, rest.ref, carouselRef),
onMouseEnter: (0, utils_exports.handlerAll)(props.onMouseEnter, onMouseEnter),
onMouseLeave: (0, utils_exports.handlerAll)(props.onMouseLeave, onMouseLeave)
}), [
id,
onMouseEnter,
onMouseLeave,
rest,
carouselRef,
orientation
]);
const getListProps = useCallback(({ ref,...props } = {}) => ({
id: listId,
"aria-live": autoplay ? "off" : "polite",
"data-orientation": orientation,
...props,
ref: mergeRefs(ref, listRef)
}), [
autoplay,
listId,
orientation
]);
const getItemProps = useCallback(({ index: indexProp$1,...props }) => {
const page = indexProp$1 + 1;
const selected = index === indexProp$1;
return {
id: `${listId}-${indexProp$1}`,
"aria-label": t("{page} of {total}", {
page,
total
}),
"aria-roledescription": "slide",
"data-index": indexProp$1.toString(),
"data-orientation": orientation,
"data-selected": (0, utils_exports.dataAttr)(selected),
role: "tabpanel",
...props
};
}, [
index,
listId,
total,
orientation,
t
]);
const getPrevTriggerProps = useCallback((props = {}) => ({
"aria-controls": listId,
"aria-label": t("Go to previous slide"),
"data-orientation": orientation,
disabled: !carousel?.canScrollPrev(),
...props,
onClick: (0, utils_exports.handlerAll)(props.onClick, () => carousel?.scrollPrev())
}), [
carousel,
listId,
orientation,
t
]);
const getNextTriggerProps = useCallback((props = {}) => ({
"aria-controls": listId,
"aria-label": t("Go to next slide"),
"data-orientation": orientation,
disabled: !carousel?.canScrollNext(),
...props,
onClick: (0, utils_exports.handlerAll)(props.onClick, () => carousel?.scrollNext())
}), [
carousel,
listId,
orientation,
t
]);
const getIndicatorsProps = useCallback((props = {}) => ({
"aria-label": t("Slides"),
"aria-orientation": orientation,
role: "tablist",
...props
}), [orientation, t]);
return {
carousel,
index,
setIndex,
snapCount,
total,
getIndicatorProps: useCallback(({ ref, index: indexProp$1,...props }) => {
const page = indexProp$1 + 1;
const selected = index === indexProp$1;
return {
type: "button",
"aria-controls": `${listId}-${indexProp$1}`,
"aria-label": t("Go to {page} slide", { page }),
"aria-selected": selected,
"data-index": indexProp$1.toString(),
"data-orientation": orientation,
"data-selected": (0, utils_exports.dataAttr)(selected),
role: "tab",
tabIndex: selected ? 0 : -1,
...props,
ref: mergeRefs(ref, (node) => {
indicatorMapRef.current.set(indexProp$1, node);
}),
onClick: (0, utils_exports.handlerAll)(props.onClick, () => carousel?.scrollTo(indexProp$1)),
onKeyDown: (0, utils_exports.handlerAll)(props.onKeyDown, onKeyDown(indexProp$1))
};
}, [
index,
listId,
t,
orientation,
onKeyDown,
carousel
]),
getIndicatorsProps,
getItemProps,
getListProps,
getNextTriggerProps,
getPrevTriggerProps,
getRootProps
};
};
//#endregion
export { CarouselContext, useCarousel, useCarouselContext };
//# sourceMappingURL=use-carousel.js.map