@cainiaofe/cn-ui-m
Version:
293 lines (292 loc) • 12.6 kB
JavaScript
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;
}