@nutui/nutui-react
Version:
京东风格的轻量级移动端 React 组件库,支持一套代码生成 H5 和小程序
334 lines (333 loc) • 10.8 kB
JavaScript
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
};