UNPKG

@nutui/nutui-react

Version:

京东风格的轻量级移动端 React 组件库,支持一套代码生成 H5 和小程序

334 lines (333 loc) 10.8 kB
import React__default, { useRef, useState, useEffect, useMemo } from "react"; import { useDrag } from "@use-gesture/react"; import { animated, useSpring } from "@react-spring/web"; import classNames from "classnames"; import Indicator__default from "./Indicator.js"; import { useRtl } from "./ConfigProvider.js"; import SwiperItem__default from "./SwiperItem.js"; const getRefValue = (ref) => { return ref.current; }; const useRefState = (param) => { const ref = useRef(param); const [, setState] = useState(param); const updateState = (p) => { ref.current = p; setState(p); }; return [ref, updateState]; }; const getPerSlidePosition$1 = (index, position, loop, count) => { const currentPosition = index * 100 + position; if (loop) { const cycle = count * 100; const shift = cycle / 2; const nextPosition = (currentPosition + shift) % cycle; const shiftedPosition = (nextPosition < 0 ? nextPosition + cycle : nextPosition) - shift; return `${shiftedPosition}%`; } return `${currentPosition}%`; }; const defaultEffect = (args) => { return React__default.Children.map(args.children, (child, index) => { const { isVertical, getSpringsAxis, loop, count } = args; const rtl = useRtl(); const position = rtl ? "right" : "left"; return React__default.createElement(animated.div, { className: "nut-swiper-slide", style: { [isVertical ? "y" : "x"]: getSpringsAxis().to((position2) => { return getPerSlidePosition$1(index, position2, loop, count); }), [isVertical ? "top" : position]: `-${index * 100}%` } }, child); }); }; const getPerSlidePosition = (index, position, loop, count) => { const currentPosition = index * 100 + position; if (loop) { const cycle = count * 100; const shift = cycle / 2; const nextPosition = (currentPosition + shift) % cycle; const shiftedPosition = (nextPosition < 0 ? nextPosition + cycle : nextPosition) - shift; return `${shiftedPosition}%`; } return `${currentPosition}%`; }; const focusEffect = (args) => { return React__default.Children.map(args.children, (child, index) => { const rtl = useRtl(); const position = rtl ? "right" : "left"; const { isVertical, springs, transforms, loop, count, swiperDirection, dragging, current, effect } = args; return React__default.createElement(animated.div, { className: "nut-swiper-slide", style: { [isVertical ? "y" : "x"]: springs[isVertical ? "y" : "x"].to((position2) => { return getPerSlidePosition(index, position2, loop, count); }), [isVertical ? "top" : position]: `-${index * 100}%`, scale: springs.s.to((ss) => { const scales = getRefValue(transforms); if (!scales) return 1; const scale = scales[index]; const currentRefValue = getRefValue(current); if (dragging === false) ss = 0; const ps = ss * scale; if (index === currentRefValue) { return Math.max(scale - ps, effect.scale); } if (index === currentRefValue + swiperDirection.current) { return Math.min(scale + ps, 1); } return scale; }) } }, child); }); }; const useList = (effect, count, current) => { const [transforms, setTransforms] = useRefState([]); useEffect(() => { setTransforms(Array.from({ length: count }).fill(1).map((scale, index) => index !== getRefValue(current) ? scale * (effect ? effect.scale : 1) : scale)); }, [count]); return [transforms, setTransforms]; }; const updateTransform = (transforms, setTransforms, effect, page) => { setTransforms(getRefValue(transforms).map((s, index) => ( // eslint-disable-next-line no-nested-ternary page === index ? 1 : effect ? effect.scale : 1 ))); }; const defaultProps = { direction: "horizontal", indicator: false, loop: false, duration: 3e3, autoPlay: false, defaultValue: 0, touchable: true, effect: void 0 }; const Swiper = React__default.forwardRef((props, ref) => { const classPrefix = "nut-swiper"; const { children, direction, indicator, loop, effect, autoPlay, touchable, defaultValue, duration, style, className } = Object.assign(Object.assign({}, defaultProps), props); const isVertical = direction === "vertical"; const count = useMemo(() => { let c = 0; React__default.Children.map(children, (child, index) => { c += 1; }); return c; }, [children]); const getSlideSize = () => { if (props.slideSize) return props.slideSize; if (stageRef.current) { if (isVertical) return stageRef.current.offsetHeight; return stageRef.current.offsetWidth; } return 0; }; const getSwiperSize = () => { if (swiperRef.current) { if (isVertical) return swiperRef.current.offsetHeight; return swiperRef.current.offsetWidth; } return 0; }; const bound = (v, min, max) => { if (min !== void 0) { v = Math.max(v, min); } if (max !== void 0) { v = Math.min(v, max); } return v; }; const timeoutRef = useRef(null); const [dragging, setDragging] = useState(false); const [current, setCurrent] = useRefState(defaultValue); const stageRef = useRef(null); const swiperRef = useRef(null); const [springs, api] = useSpring(() => ({ x: !isVertical ? current.current * 100 * -1 : 0, y: isVertical ? current.current * 100 * -1 : 0, s: 0, reset: () => { }, config: { tension: 200, friction: 30 } })); useEffect(() => { api.start({ [isVertical ? "y" : "x"]: boundIndex(current.current) * -1 * 100, immediate: true }); }, [swiperRef.current]); const swiperDirection = useRef(1); const [transforms, setTransforms] = useList(effect, count, current); const runTimeSwiper = () => { const durationNumber = typeof duration === "string" ? parseInt(duration) : duration; const d = typeof autoPlay === "number" ? autoPlay : durationNumber; timeoutRef.current = window.setTimeout(() => { next(); runTimeSwiper(); }, d); }; useEffect(() => { if (!autoPlay || dragging) return; runTimeSwiper(); return () => { if (timeoutRef.current) window.clearTimeout(timeoutRef.current); }; }, [autoPlay, duration, dragging, count]); function boundIndex(current2) { const min = 0; const max = count - 1; if (current2 === max && !loop && props.slideSize) { const slideSize = props.slideSize; const swiperSize = getSwiperSize(); const ratio = (swiperSize - slideSize) / slideSize; return bound(current2, min, max - ratio); } return current2; } const to = (index, immediate = false) => { var _a; let targetIndex = bound(index, 0, count - 1); if (loop) { const cycleIndex = index % count; targetIndex = cycleIndex < 0 ? cycleIndex + count : cycleIndex; } setCurrent(targetIndex); (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, targetIndex); if (effect) { updateTransform(transforms, setTransforms, effect, targetIndex); } api.start({ // 这里需要统一成百分比 [isVertical ? "y" : "x"]: (loop ? -index : boundIndex(targetIndex) * -1) * 100, s: 0, immediate }); }; const getSpringsAxis = () => { return springs[isVertical ? "y" : "x"]; }; const next = () => { to(Math.round(-getSpringsAxis().get() / 100) + 1); }; const prev = () => { to(Math.round(-getSpringsAxis().get() / 100) - 1); }; React__default.useImperativeHandle(ref, () => ({ to, next, prev })); const bind = useDrag((state) => { const axis = Number(isVertical); const slideSize = getSlideSize(); const offset = state.offset[axis]; setDragging(!!state.dragging); const distance = state.distance[axis]; swiperDirection.current = state.direction[axis]; if (state.last) { const swipeDirection = state.direction[axis]; const velocity = state.velocity[axis]; const minIndex = Math.floor(offset / slideSize); const maxIndex = minIndex + 1; const index = Math.round((offset + velocity * 2e3 * swipeDirection) / slideSize); to(bound(index, minIndex, maxIndex)); } else { api.start({ [isVertical ? "y" : "x"]: -(offset / slideSize * 100), s: distance / slideSize, immediate: true }); } }, { enabled: touchable, transform: ([x, y]) => [-x, -y], from: () => { const slideSize = getSlideSize(); const x = springs.x.get() / 100 * slideSize; const y = springs.y.get() / 100 * slideSize; return [-x, -y]; }, triggerAllEvents: true, bounds: () => { if (loop) return {}; const slideSize = getSlideSize(); getSwiperSize(); if (isVertical) { return { top: 0, bottom: (count - 1) * slideSize }; } return { left: 0, right: (count - 1) * slideSize }; }, preventDefault: true, rubberband: true, axis: isVertical ? "y" : "x", pointer: { touch: true } }); const renderIndicator = () => { if (React__default.isValidElement(indicator)) return indicator; if (!indicator) return null; return React__default.createElement( "div", { className: classNames({ [`${classPrefix}-indicator`]: true, [`${classPrefix}-indicator-vertical`]: isVertical, [`${classPrefix}-indicator-horizontal`]: !isVertical }) }, React__default.createElement(Indicator__default, { current: getRefValue(current), total: count, direction }) ); }; const renderEffect = () => { if (!effect) return defaultEffect({ children, getSpringsAxis, loop, count, isVertical }); if (effect && effect.name === "focus") { return focusEffect({ children, springs, loop, count, isVertical, effect, current, swiperDirection, dragging, transforms }); } }; const renderSlides = () => { return React__default.createElement("div", { ref: stageRef, className: classNames("nut-swiper-inner", { "nut-swiper-inner-vertical": isVertical, "nut-swiper-inner-horizontal": !isVertical }), style: Object.assign({}, props.slideSize ? { [isVertical ? "height" : "width"]: `${props.slideSize}px` } : {}) }, renderEffect()); }; return React__default.createElement( "div", Object.assign({ className: classNames("nut-swiper", className), style, ref: swiperRef }, bind()), renderSlides(), renderIndicator() ); }); Swiper.displayName = "NutSwiper"; const InnerSwiper = Swiper; InnerSwiper.Item = SwiperItem__default; export { InnerSwiper as default };