@react-vant-next/campaign
Version:
React Mobile UI Components based on Vant UI - Next Generation
95 lines (92 loc) • 4.12 kB
JavaScript
import { __rest } from 'tslib';
import { jsxs, jsx } from 'react/jsx-runtime';
import { useSpring, animated } from '@react-spring/web';
import { useTouch, useEventListener } from '@react-vant-next/hooks';
import { createNamespace, getScrollTop, preventDefault, bound, getVisibleHeight } from '@react-vant-next/utils';
import cls from 'clsx';
import { useMemo, useRef, useImperativeHandle } from 'react';
const [bem] = createNamespace("floating-panel");
/** Check if EL is scrolling reach its bottom */
function scrollReachBottom(el) {
const scrollTop = getScrollTop(el);
return scrollTop >= el.scrollHeight - getVisibleHeight(el);
}
function FloatingPanel(_a) {
var { ref } = _a, props = __rest(_a, ["ref"]);
const { className, style, onHeightChange, anchors = [100] } = props;
const sortAnchors = useMemo(() => anchors.sort((a, b) => a - b), [anchors]);
const [minAnchor, maxAnchor] = [
sortAnchors[0],
sortAnchors[Math.max(0, sortAnchors.length - 1)],
];
const root = useRef(void 0);
const header = useRef(void 0);
const body = useRef(void 0);
const dragging = useRef(false);
const draggingStartAt = useRef(null);
const touch = useTouch();
const [{ visibleH }, api] = useSpring(() => ({
visibleH: minAnchor,
config: { tension: 300 },
onChange: () => onHeightChange === null || onHeightChange === void 0 ? void 0 : onHeightChange(visibleH.get()),
}), [minAnchor]);
useImperativeHandle(ref, () => ({
moveTo: height => api.start({ visibleH: height }),
}));
const onTouchStart = (event) => {
touch.start(event);
draggingStartAt.current = visibleH.get();
dragging.current = true;
};
const onTouchMove = (event) => {
const [headerEL, bodyEL] = [header.current, body.current];
touch.move(event);
if (visibleH.goal >= maxAnchor && bodyEL) {
if (touch.firstMove.current
// try going up to body top
&& ((touch.deltaY.current > 0 && getScrollTop(bodyEL) > 0)
// try going down to body bottom
|| (touch.deltaY.current < 0 && !scrollReachBottom(bodyEL)))) {
dragging.current = false;
}
}
if (headerEL && headerEL.contains(event.target)) {
dragging.current = true;
}
if (!dragging.current)
return;
preventDefault(event, true);
api.start({
visibleH: bound(draggingStartAt.current + -touch.deltaY.current, minAnchor, maxAnchor),
});
};
const onTouchEnd = () => {
const memoDraggingStartAt = draggingStartAt.current;
dragging.current = false;
draggingStartAt.current = null;
touch.reset();
if (memoDraggingStartAt) {
const nearestAnchor = findNearestAnchor(sortAnchors, visibleH.get());
api.start({
visibleH: nearestAnchor,
from: { visibleH: visibleH.get() },
});
}
};
useEventListener("touchstart", onTouchStart, {
target: root,
passive: false,
});
useEventListener("touchmove", onTouchMove, { target: root, passive: false });
useEventListener("touchend", onTouchEnd, { target: root, passive: false });
// 使用类型断言解决 animated.div 不接受 children 属性的问题
const AnimatedDiv = animated.div;
return (jsxs(AnimatedDiv, { ref: root, className: cls(bem(), className), style: Object.assign({ height: maxAnchor, transform: visibleH.to(h => `translateY(calc(100% - ${h}px))`) }, style), children: [jsx("div", { ref: header, className: cls(bem("header")), children: jsx("div", { className: cls(bem("thumb")) }) }), jsx(AnimatedDiv, { ref: body, className: cls(bem("body")), children: props.children })] }));
}
function findNearestAnchor(anchors, target) {
return anchors.reduce((pre, cur) => {
return Math.abs(target - pre) < Math.abs(target - cur) ? pre : cur;
});
}
export { FloatingPanel as default };
//# sourceMappingURL=FloatingPanel.js.map