import _extends from "@babel/runtime/helpers/esm/extends"; import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose"; var _excluded = ["as", "bsPrefix", "slide", "fade", "controls", "indicators", "activeIndex", "onSelect", "onSlide", "onSlid", "interval", "keyboard", "onKeyDown", "pause", "onMouseOver", "onMouseOut", "wrap", "touch", "onTouchStart", "onTouchMove", "onTouchEnd", "prevIcon", "prevLabel", "nextIcon", "nextLabel", "className", "children"]; import useEventCallback from '@restart/hooks/useEventCallback'; import useUpdateEffect from '@restart/hooks/useUpdateEffect'; import useCommittedRef from '@restart/hooks/useCommittedRef'; import useTimeout from '@restart/hooks/useTimeout'; import classNames from 'classnames'; import Transition from 'react-transition-group/Transition'; import PropTypes from 'prop-types'; import React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import { useUncontrolled } from 'uncontrollable'; import CarouselCaption from './CarouselCaption'; import CarouselItem from './CarouselItem'; import { map, forEach } from './ElementChildren'; import SafeAnchor from './SafeAnchor'; import { useBootstrapPrefix } from './ThemeProvider'; import transitionEndListener from './transitionEndListener'; import triggerBrowserReflow from './triggerBrowserReflow'; var SWIPE_THRESHOLD = 40; var propTypes = { /** * @default 'carousel' */ bsPrefix: PropTypes.string, as: PropTypes.elementType, /** * Enables animation on the Carousel as it transitions between slides. */ slide: PropTypes.bool, /** Animates slides with a crossfade animation instead of the default slide animation */ fade: PropTypes.bool, /** * Show the Carousel previous and next arrows for changing the current slide */ controls: PropTypes.bool, /** * Show a set of slide position indicators */ indicators: PropTypes.bool, /** * Controls the current visible slide * * @controllable onSelect */ activeIndex: PropTypes.number, /** * Callback fired when the active item changes. * * ```js * (eventKey: number, event: Object | null) => void * ``` * * @controllable activeIndex */ onSelect: PropTypes.func, /** * Callback fired when a slide transition starts. * * ```js * (eventKey: number, direction: 'left' | 'right') => void */ onSlide: PropTypes.func, /** * Callback fired when a slide transition ends. * * ```js * (eventKey: number, direction: 'left' | 'right') => void */ onSlid: PropTypes.func, /** * The amount of time to delay between automatically cycling an item. If `null`, carousel will not automatically cycle. */ interval: PropTypes.number, /** Whether the carousel should react to keyboard events. */ keyboard: PropTypes.bool, /** * If set to `"hover"`, pauses the cycling of the carousel on `mouseenter` and resumes the cycling of the carousel on `mouseleave`. If set to `false`, hovering over the carousel won't pause it. * * On touch-enabled devices, when set to `"hover"`, cycling will pause on `touchend` (once the user finished interacting with the carousel) for two intervals, before automatically resuming. Note that this is in addition to the above mouse behavior. */ pause: PropTypes.oneOf(['hover', false]), /** Whether the carousel should cycle continuously or have hard stops. */ wrap: PropTypes.bool, /** * Whether the carousel should support left/right swipe interactions on touchscreen devices. */ touch: PropTypes.bool, /** Override the default button icon for the "previous" control */ prevIcon: PropTypes.node, /** * Label shown to screen readers only, can be used to show the previous element * in the carousel. * Set to null to deactivate. */ prevLabel: PropTypes.string, /** Override the default button icon for the "next" control */ nextIcon: PropTypes.node, /** * Label shown to screen readers only, can be used to show the next element * in the carousel. * Set to null to deactivate. */ nextLabel: PropTypes.string }; var defaultProps = { slide: true, fade: false, controls: true, indicators: true, defaultActiveIndex: 0, interval: 5000, keyboard: true, pause: 'hover', wrap: true, touch: true, prevIcon: /*#__PURE__*/React.createElement("span", { "aria-hidden": "true", className: "carousel-control-prev-icon" }), prevLabel: 'Previous', nextIcon: /*#__PURE__*/React.createElement("span", { "aria-hidden": "true", className: "carousel-control-next-icon" }), nextLabel: 'Next' }; function isVisible(element) { if (!element || !element.style || !element.parentNode || !element.parentNode.style) { return false; } var elementStyle = getComputedStyle(element); return elementStyle.display !== 'none' && elementStyle.visibility !== 'hidden' && getComputedStyle(element.parentNode).display !== 'none'; } function CarouselFunc(uncontrolledProps, ref) { var _useUncontrolled = useUncontrolled(uncontrolledProps, { activeIndex: 'onSelect' }), _useUncontrolled$as = _useUncontrolled.as, Component = _useUncontrolled$as === void 0 ? 'div' : _useUncontrolled$as, bsPrefix = _useUncontrolled.bsPrefix, slide = _useUncontrolled.slide, fade = _useUncontrolled.fade, controls = _useUncontrolled.controls, indicators = _useUncontrolled.indicators, activeIndex = _useUncontrolled.activeIndex, onSelect = _useUncontrolled.onSelect, onSlide = _useUncontrolled.onSlide, onSlid = _useUncontrolled.onSlid, interval = _useUncontrolled.interval, keyboard = _useUncontrolled.keyboard, onKeyDown = _useUncontrolled.onKeyDown, pause = _useUncontrolled.pause, onMouseOver = _useUncontrolled.onMouseOver, onMouseOut = _useUncontrolled.onMouseOut, wrap = _useUncontrolled.wrap, touch = _useUncontrolled.touch, onTouchStart = _useUncontrolled.onTouchStart, onTouchMove = _useUncontrolled.onTouchMove, onTouchEnd = _useUncontrolled.onTouchEnd, prevIcon = _useUncontrolled.prevIcon, prevLabel = _useUncontrolled.prevLabel, nextIcon = _useUncontrolled.nextIcon, nextLabel = _useUncontrolled.nextLabel, className = _useUncontrolled.className, children = _useUncontrolled.children, props = _objectWithoutPropertiesLoose(_useUncontrolled, _excluded); var prefix = useBootstrapPrefix(bsPrefix, 'carousel'); var nextDirectionRef = useRef(null); var _useState = useState('next'), direction = _useState[0], setDirection = _useState[1]; var _useState2 = useState(false), paused = _useState2[0], setPaused = _useState2[1]; var _useState3 = useState(false), isSliding = _useState3[0], setIsSliding = _useState3[1]; var _useState4 = useState(activeIndex || 0), renderedActiveIndex = _useState4[0], setRenderedActiveIndex = _useState4[1]; if (!isSliding && activeIndex !== renderedActiveIndex) { if (nextDirectionRef.current) { setDirection(nextDirectionRef.current); } else { setDirection((activeIndex || 0) > renderedActiveIndex ? 'next' : 'prev'); } if (slide) { setIsSliding(true); } setRenderedActiveIndex(activeIndex || 0); } useEffect(function () { if (nextDirectionRef.current) { nextDirectionRef.current = null; } }); var numChildren = 0; var activeChildInterval; // Iterate to grab all of the children's interval values // (and count them, too) forEach(children, function (child, index) { ++numChildren; if (index === activeIndex) { activeChildInterval = child.props.interval; } }); var activeChildIntervalRef = useCommittedRef(activeChildInterval); var prev = useCallback(function (event) { if (isSliding) { return; } var nextActiveIndex = renderedActiveIndex - 1; if (nextActiveIndex < 0) { if (!wrap) { return; } nextActiveIndex = numChildren - 1; } nextDirectionRef.current = 'prev'; if (onSelect) { onSelect(nextActiveIndex, event); } }, [isSliding, renderedActiveIndex, onSelect, wrap, numChildren]); // This is used in the setInterval, so it should not invalidate. var next = useEventCallback(function (event) { if (isSliding) { return; } var nextActiveIndex = renderedActiveIndex + 1; if (nextActiveIndex >= numChildren) { if (!wrap) { return; } nextActiveIndex = 0; } nextDirectionRef.current = 'next'; if (onSelect) { onSelect(nextActiveIndex, event); } }); var elementRef = useRef(); useImperativeHandle(ref, function () { return { element: elementRef.current, prev: prev, next: next }; }); // This is used in the setInterval, so it should not invalidate. var nextWhenVisible = useEventCallback(function () { if (!document.hidden && isVisible(elementRef.current)) { next(); } }); var slideDirection = direction === 'next' ? 'left' : 'right'; useUpdateEffect(function () { if (slide) { // These callbacks will be handled by the callbacks. return; } if (onSlide) { onSlide(renderedActiveIndex, slideDirection); } if (onSlid) { onSlid(renderedActiveIndex, slideDirection); } }, [renderedActiveIndex]); var orderClassName = prefix + "-item-" + direction; var directionalClassName = prefix + "-item-" + slideDirection; var handleEnter = useCallback(function (node) { triggerBrowserReflow(node); if (onSlide) { onSlide(renderedActiveIndex, slideDirection); } }, [onSlide, renderedActiveIndex, slideDirection]); var handleEntered = useCallback(function () { setIsSliding(false); if (onSlid) { onSlid(renderedActiveIndex, slideDirection); } }, [onSlid, renderedActiveIndex, slideDirection]); var handleKeyDown = useCallback(function (event) { if (keyboard && !/input|textarea/i.test(event.target.tagName)) { switch (event.key) { case 'ArrowLeft': event.preventDefault(); prev(event); return; case 'ArrowRight': event.preventDefault(); next(event); return; default: } } if (onKeyDown) { onKeyDown(event); } }, [keyboard, onKeyDown, prev, next]); var handleMouseOver = useCallback(function (event) { if (pause === 'hover') { setPaused(true); } if (onMouseOver) { onMouseOver(event); } }, [pause, onMouseOver]); var handleMouseOut = useCallback(function (event) { setPaused(false); if (onMouseOut) { onMouseOut(event); } }, [onMouseOut]); var touchStartXRef = useRef(0); var touchDeltaXRef = useRef(0); var touchUnpauseTimeout = useTimeout(); var handleTouchStart = useCallback(function (event) { touchStartXRef.current = event.touches[0].clientX; touchDeltaXRef.current = 0; if (pause === 'hover') { setPaused(true); } if (onTouchStart) { onTouchStart(event); } }, [pause, onTouchStart]); var handleTouchMove = useCallback(function (event) { if (event.touches && event.touches.length > 1) { touchDeltaXRef.current = 0; } else { touchDeltaXRef.current = event.touches[0].clientX - touchStartXRef.current; } if (onTouchMove) { onTouchMove(event); } }, [onTouchMove]); var handleTouchEnd = useCallback(function (event) { if (touch) { var touchDeltaX = touchDeltaXRef.current; if (Math.abs(touchDeltaX) > SWIPE_THRESHOLD) { if (touchDeltaX > 0) { prev(event); } else { next(event); } } } if (pause === 'hover') { touchUnpauseTimeout.set(function () { setPaused(false); }, interval || undefined); } if (onTouchEnd) { onTouchEnd(event); } }, [touch, pause, prev, next, touchUnpauseTimeout, interval, onTouchEnd]); var shouldPlay = interval != null && !paused && !isSliding; var intervalHandleRef = useRef(); useEffect(function () { var _ref, _activeChildIntervalR; if (!shouldPlay) { return undefined; } intervalHandleRef.current = window.setInterval(document.visibilityState ? nextWhenVisible : next, (_ref = (_activeChildIntervalR = activeChildIntervalRef.current) != null ? _activeChildIntervalR : interval) != null ? _ref : undefined); return function () { if (intervalHandleRef.current !== null) { clearInterval(intervalHandleRef.current); } }; }, [shouldPlay, next, activeChildIntervalRef, interval, nextWhenVisible]); var indicatorOnClicks = useMemo(function () { return indicators && Array.from({ length: numChildren }, function (_, index) { return function (event) { if (onSelect) { onSelect(index, event); } }; }); }, [indicators, numChildren, onSelect]); return /*#__PURE__*/React.createElement(Component, _extends({ ref: elementRef }, props, { onKeyDown: handleKeyDown, onMouseOver: handleMouseOver, onMouseOut: handleMouseOut, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, className: classNames(className, prefix, slide && 'slide', fade && prefix + "-fade") }), indicators && /*#__PURE__*/React.createElement("ol", { className: prefix + "-indicators" }, map(children, function (_child, index) { return /*#__PURE__*/React.createElement("li", { key: index, className: index === renderedActiveIndex ? 'active' : undefined, onClick: indicatorOnClicks ? indicatorOnClicks[index] : undefined }); })), /*#__PURE__*/React.createElement("div", { className: prefix + "-inner" }, map(children, function (child, index) { var isActive = index === renderedActiveIndex; return slide ? /*#__PURE__*/React.createElement(Transition, { in: isActive, onEnter: isActive ? handleEnter : undefined, onEntered: isActive ? handleEntered : undefined, addEndListener: transitionEndListener }, function (status) { return /*#__PURE__*/React.cloneElement(child, { className: classNames(child.props.className, isActive && status !== 'entered' && orderClassName, (status === 'entered' || status === 'exiting') && 'active', (status === 'entering' || status === 'exiting') && directionalClassName) }); }) : /*#__PURE__*/React.cloneElement(child, { className: classNames(child.props.className, isActive && 'active') }); })), controls && /*#__PURE__*/React.createElement(React.Fragment, null, (wrap || activeIndex !== 0) && /*#__PURE__*/React.createElement(SafeAnchor, { className: prefix + "-control-prev", onClick: prev }, prevIcon, prevLabel && /*#__PURE__*/React.createElement("span", { className: "sr-only" }, prevLabel)), (wrap || activeIndex !== numChildren - 1) && /*#__PURE__*/React.createElement(SafeAnchor, { className: prefix + "-control-next", onClick: next }, nextIcon, nextLabel && /*#__PURE__*/React.createElement("span", { className: "sr-only" }, nextLabel)))); } var Carousel = /*#__PURE__*/React.forwardRef(CarouselFunc); Carousel.displayName = 'Carousel'; Carousel.propTypes = propTypes; Carousel.defaultProps = defaultProps; Carousel.Caption = CarouselCaption; Carousel.Item = CarouselItem; export default Carousel;