UNPKG

@cainiaofe/cn-ui-m

Version:
293 lines (292 loc) • 12.6 kB
import { __assign } from "tslib"; /* eslint-disable react-hooks/rules-of-hooks */ import { bound } from "../../utils/bound"; import { devWarning } from "../../utils/dev-log"; import { useRefState } from "../../utils/use-ref-state"; import { mergeProps } from "../../utils/with-default-props"; import { withNativeProps } from '@cainiaofe/cn-ui-common'; import { animated, useSpring } from '@react-spring/web'; import { useDrag } from '@use-gesture/react'; import { useIsomorphicLayoutEffect, useUpdateEffect } from 'ahooks'; import classNames from 'classnames'; import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react'; import { staged } from 'staged-components'; import SliderArrow from './slider-arrow'; import { SliderIndicator } from './slider-indicator'; import { CnSliderItem } from './slider-item'; import { isRTL } from '@cainiaofe/cn-i18n'; var rtl = isRTL(); var classPrefix = 'cn-ui-m-slider'; var currentUid; var getCssValue = function (value) { if (typeof value === 'number') { return "".concat(value, "px"); } return value; }; export var CnSlider = forwardRef(staged(function (p, ref) { var props = mergeProps(CnSlider.defaultProps, p); if (props.height) { !props.style && (props.style = {}); props.style['--height'] = getCssValue(props.height); } var uid = useState({})[0]; var isVertical = props.direction === 'vertical'; var slideRatio = props.slideSize / 100; var offsetRatio = props.trackOffset / 100; var _a = useMemo(function () { var _count = 0; var _validChildren = React.Children.map(props.children, function (child) { if (!React.isValidElement(child)) return null; if (child.type !== CnSliderItem) { devWarning('CnSlider', 'The children of `CnSlider` must be `CnSliderItem` components.'); return null; } _count++; return child; }); return { validChildren: _validChildren, count: _count, }; }, [props.children]), validChildren = _a.validChildren, count = _a.count; if (count === 0 || !validChildren) { devWarning('CnSlider', '`CnSlider` needs at least one child.'); return null; } return function () { var _a; var loop = props.loop; if (slideRatio * (count - 1) < 1) { loop = false; } var trackRef = useRef(null); function getSlidePixels() { var track = trackRef.current; if (!track) return 0; var trackPixels = isVertical ? track.offsetHeight : track.offsetWidth; return (trackPixels * props.slideSize) / 100; } var _b = useState(props.index || props.defaultIndex || 0), current = _b[0], setCurrent = _b[1]; useUpdateEffect(function () { var _a; (_a = props.onIndexChange) === null || _a === void 0 ? void 0 : _a.call(props, current); }, [current]); useEffect(function () { if (typeof props.index === 'number' && props.index !== current) { swipeTo(props.index); } }, [props.index]); var _c = useRefState(false), dragging = _c[0], setDragging = _c[1], draggingRef = _c[2]; function boundIndex(currentIndex) { var min = 0; var max = count - 1; if (props.stuckAtBoundary) { min += offsetRatio / slideRatio; max -= (1 - slideRatio - offsetRatio) / slideRatio; } return bound(currentIndex, min, max); } var _d = useSpring(function () { return ({ position: boundIndex(current) * 100, config: { tension: 200, friction: 30 }, onRest: function () { if (draggingRef.current) return; if (!loop) return; var rawX = position.get(); var totalWidth = 100 * count; var standardPosition = modulus(rawX, totalWidth); if (standardPosition === rawX) return; api.start({ position: standardPosition, immediate: true, }); }, }); }, [count]), position = _d[0].position, api = _d[1]; var dragCancelRef = useRef(null); function forceCancelDrag() { var _a; (_a = dragCancelRef.current) === null || _a === void 0 ? void 0 : _a.call(dragCancelRef); draggingRef.current = false; } var bind = useDrag(function (state) { dragCancelRef.current = state.cancel; if (!state.intentional) return; if (state.first && !currentUid) { currentUid = uid; } if (currentUid !== uid) return; currentUid = state.last ? undefined : uid; var slidePixels = getSlidePixels(); if (!slidePixels) return; var paramIndex = isVertical ? 1 : 0; var offset = state.offset[paramIndex]; var direction = state.direction[paramIndex]; var velocity = state.velocity[paramIndex]; setDragging(true); if (!state.last) { api.start({ position: (offset * 100) / slidePixels, immediate: true, }); } else { var minIndex = Math.floor(offset / slidePixels); var maxIndex = minIndex + 1; var index = Math.round((offset + velocity * 2000 * direction) / slidePixels); swipeTo(bound(index, minIndex, maxIndex)); window.setTimeout(function () { setDragging(false); }); } }, { transform: function (_a) { var x = _a[0], y = _a[1]; return [rtl ? x : -x, -y]; }, from: function () { var slidePixels = getSlidePixels(); return [ (position.get() / 100) * slidePixels, (position.get() / 100) * slidePixels, ]; }, triggerAllEvents: true, bounds: function () { if (loop) return {}; var slidePixels = getSlidePixels(); var lowerBound = boundIndex(0) * slidePixels; var upperBound = boundIndex(count - 1) * slidePixels; return isVertical ? { top: lowerBound, bottom: upperBound, } : { left: lowerBound, right: upperBound, }; }, rubberBand: props.rubberBand, axis: isVertical ? 'y' : 'x', preventScroll: !isVertical, pointer: { touch: true, }, }); function swipeTo(index, immediate) { if (immediate === void 0) { immediate = false; } var roundedIndex = Math.round(index); var targetIndex = loop ? modulus(roundedIndex, count) : bound(roundedIndex, 0, count - 1); setCurrent(targetIndex); api.start({ position: (loop ? roundedIndex : boundIndex(roundedIndex)) * 100, immediate: immediate, }); } function swipeNext() { swipeTo(Math.round(position.get() / 100) + 1); } function swipePrev() { swipeTo(Math.round(position.get() / 100) - 1); } useImperativeHandle(ref, function () { return ({ swipeTo: swipeTo, swipeNext: swipeNext, swipePrev: swipePrev, }); }); useIsomorphicLayoutEffect(function () { var maxIndex = validChildren.length - 1; if (current > maxIndex) { swipeTo(maxIndex, true); } }); var autoplay = props.autoplay, autoplayInterval = props.autoplayInterval; useEffect(function () { if (!autoplay || dragging) return function () { return null; }; var interval = window.setInterval(function () { swipeNext(); }, autoplayInterval); return function () { window.clearInterval(interval); }; }, [autoplay, autoplayInterval, dragging, count]); function renderTrackInner() { var _a; var getDir = function () { return (rtl ? 'right' : 'left'); }; if (loop) { return (React.createElement("div", { className: "".concat(classPrefix, "-track-inner") }, React.Children.map(validChildren, function (child, index) { var _a; return (React.createElement(animated.div, { className: "".concat(classPrefix, "-slide"), style: (_a = {}, _a[isVertical ? 'y' : 'x'] = position.to(function (_position) { var finalPosition = -_position + index * 100; var totalWidth = count * 100; var flagWidth = totalWidth / 2; finalPosition = rtl ? flagWidth - modulus(finalPosition + flagWidth, totalWidth) : modulus(finalPosition + flagWidth, totalWidth) - flagWidth; return "".concat(finalPosition, "%"); }), _a[isVertical ? 'top' : getDir()] = "-".concat(index * 100, "%"), _a) }, child)); }))); } else { return (React.createElement(animated.div, { className: "".concat(classPrefix, "-track-inner"), style: (_a = {}, _a[isVertical ? 'y' : 'x'] = position.to(function (_position) { return rtl && !isVertical ? "".concat(_position, "%") : "-".concat(_position, "%"); }), _a) }, React.Children.map(validChildren, function (child) { return React.createElement("div", { className: "".concat(classPrefix, "-slide") }, child); }))); } } var style = { '--slide-size': "".concat(props.slideSize, "%"), '--track-offset': "".concat(props.trackOffset, "%"), }; return withNativeProps(props, React.createElement("div", { className: classNames(CN_UI_HASH_CLASS_NAME, classPrefix, "".concat(classPrefix, "-").concat(props.direction)), style: style }, React.createElement("div", __assign({ ref: trackRef, className: classNames("".concat(classPrefix, "-track"), (_a = {}, _a["".concat(classPrefix, "-track-allow-touch-move")] = props.allowTouchMove, _a)), onClickCapture: function (e) { if (draggingRef.current) { e.stopPropagation(); } forceCancelDrag(); } }, (props.allowTouchMove ? bind() : {})), renderTrackInner()), props.showArrow && (React.createElement(SliderArrow, { total: count, current: current, swipeNext: swipeNext, swipePrev: swipePrev })), props.indicator === undefined ? (React.createElement("div", { className: "".concat(classPrefix, "-indicator") }, React.createElement(SliderIndicator, __assign({}, props.indicatorProps, { total: count, current: current, onClick: swipeTo })))) : (props.indicator(count, current, swipeTo)))); }; })); CnSlider.defaultProps = { defaultIndex: 0, allowTouchMove: true, autoplay: false, autoplayInterval: 3000, loop: false, direction: 'horizontal', slideSize: 100, trackOffset: 0, stuckAtBoundary: true, rubberBand: true, }; CnSlider.displayName = 'CnSlider'; function modulus(value, division) { var remainder = value % division; return remainder < 0 ? remainder + division : remainder; }