UNPKG

zarm

Version:

基于 React 的移动端UI库

436 lines (357 loc) 14.2 kB
import _extends from "@babel/runtime/helpers/extends"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; import _slicedToArray from "@babel/runtime/helpers/slicedToArray"; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } import { useDrag } from '@use-gesture/react'; import { createBEM } from '@zarm-design/bem'; import { SuccessCircle as SuccessCircleIcon, WarningCircle as WarningCircleIcon } from '@zarm-design/icons'; import throttle from 'lodash/throttle'; import React, { useEffect, useRef, useState } from 'react'; import { ConfigContext } from '../config-provider'; import Loading from '../loading'; import { getScrollParent, getScrollTop } from '../utils/dom'; import Events from '../utils/events'; import { useEventCallback } from '../utils/hooks'; import { LOAD_STATE, REFRESH_STATE } from './interface'; var Pull = /*#__PURE__*/React.forwardRef(function (props, ref) { var _props$load; var pullRef = ref || /*#__PURE__*/React.createRef(); var wrap = useRef(); var _useState = useState(false), _useState2 = _slicedToArray(_useState, 2), isMounted = _useState2[0], setIsMounted = _useState2[1]; var _useState3 = useState(0), _useState4 = _slicedToArray(_useState3, 2), offsetY = _useState4[0], setOffsetY = _useState4[1]; var _useState5 = useState(0), _useState6 = _slicedToArray(_useState5, 2), animationDuration = _useState6[0], setAnimationDuration = _useState6[1]; var _useState7 = useState(props.refresh.state), _useState8 = _slicedToArray(_useState7, 2), refreshState = _useState8[0], setRefreshState = _useState8[1]; var _useState9 = useState(props.load.state), _useState10 = _slicedToArray(_useState9, 2), loadState = _useState10[0], setLoadState = _useState10[1]; var prevLoad = useRef({}); var prevRefresh = useRef({}); var _React$useContext = React.useContext(ConfigContext), prefixCls = _React$useContext.prefixCls, locale = _React$useContext.locale; var bem = createBEM('pull', { prefixCls: prefixCls }); var onScroll = useEventCallback(function () { // window为滚动容器时,无法通过 window 直接取到 scrollHeight 和 clientHeight。 var _ref = wrap.current, _ref$scrollHeight = _ref.scrollHeight, scrollHeight = _ref$scrollHeight === void 0 ? document.body.clientHeight : _ref$scrollHeight, _ref$clientHeight = _ref.clientHeight, clientHeight = _ref$clientHeight === void 0 ? document.documentElement.clientHeight : _ref$clientHeight; var load = _objectSpread(_objectSpread({}, Pull.defaultProps.load), props.load); var handler = load.handler, distance = load.distance; if (refreshState !== REFRESH_STATE.normal || loadState !== LOAD_STATE.normal || scrollHeight <= clientHeight || // 内容高度 - 偏移值 - 修正距离 <= 容器可见高度‚ scrollHeight - getScrollTop(wrap.current) - distance > clientHeight) { return; } handler === null || handler === void 0 ? void 0 : handler(); }, [props === null || props === void 0 ? void 0 : (_props$load = props.load) === null || _props$load === void 0 ? void 0 : _props$load.handler]); var throttledScroll = throttle(onScroll, 250); var setScrollParent = function setScrollParent() { var _scrollContainer = getScrollParent(pullRef.current); // scrollContainer 未变更 if (wrap.current === _scrollContainer) return; // 重新获取 scrollContainer wrap.current = _scrollContainer; }; useEffect(function () { setScrollParent(); }); useEffect(function () { Events.on(wrap.current, 'scroll', throttledScroll); return function () { Events.off(wrap.current, 'scroll', throttledScroll); }; }, [wrap.current]); /** * 执行动画 * @param {number} options.offsetY 偏移距离 * @param {number} options.animationDuration 动画执行时间 */ var doTransition = function doTransition(_ref2) { var iOffsetY = _ref2.offsetY, iAnimationDuration = _ref2.animationDuration; setOffsetY(iOffsetY); setAnimationDuration(iAnimationDuration); }; /** * 执行加载动作 * @param {LOAD_STATE} iLoadState 加载状态 */ var doLoadAction = function doLoadAction(iLoadState) { var stayTime = props.stayTime; setLoadState(iLoadState); switch (iLoadState) { case LOAD_STATE.success: doLoadAction(LOAD_STATE.normal); break; case LOAD_STATE.failure: setTimeout(function () { if (!isMounted) return; doLoadAction(LOAD_STATE.abort); }, stayTime); break; default: } }; /** * 执行刷新动作 * @param {REFRESH_STATE} iRefreshState 刷新状态 * @param {number} iOffsetY 偏移距离 */ var doRefreshAction = function doRefreshAction(iRefreshState, iOffsetY) { var stayTime = props.stayTime; setRefreshState(iRefreshState); switch (iRefreshState) { case REFRESH_STATE.pull: case REFRESH_STATE.drop: doTransition({ offsetY: iOffsetY, animationDuration: 0 }); break; case REFRESH_STATE.loading: doTransition({ offsetY: 'auto', animationDuration: animationDuration }); break; case REFRESH_STATE.success: case REFRESH_STATE.failure: doTransition({ offsetY: 'auto', animationDuration: animationDuration }); setTimeout(function () { if (!isMounted) return; doRefreshAction(REFRESH_STATE.normal); doLoadAction(LOAD_STATE.normal); }, stayTime); break; default: doTransition({ offsetY: 0, animationDuration: props.animationDuration }); } }; var onDragMove = function onDragMove(state) { var _Pull$defaultProps; var movement = state.movement, event = state.event; var _movement = _slicedToArray(movement, 2), dragOffsetY = _movement[1]; var _ref3 = props.refresh, handler = _ref3.handler; if ( // 未设置刷新事件 !handler || // 上拉 dragOffsetY <= 0 || // 未滚动到顶部 dragOffsetY > 0 && getScrollTop(wrap.current) > 0 || // 已经触发过加载状态 refreshState >= REFRESH_STATE.loading) { return false; } if (event.cancelable) { event.preventDefault(); } var refresh = _objectSpread(_objectSpread({}, (_Pull$defaultProps = Pull.defaultProps) === null || _Pull$defaultProps === void 0 ? void 0 : _Pull$defaultProps.refresh), props === null || props === void 0 ? void 0 : props.refresh); var startDistance = refresh.startDistance, distance = refresh.distance; // 设置拖动距离衰减(实际下拉移动距离为拖动距离的1/3) var offset = state.movement[1] / 3; // 判断是否达到释放立即刷新的条件 var action = offset - startDistance < distance ? REFRESH_STATE.pull : REFRESH_STATE.drop; doRefreshAction(action, offset); return true; }; var onDragEnd = function onDragEnd(_ref4) { var iOffsetY = _ref4.offsetY; // 没有产生位移 if (!iOffsetY) { return; } // 当前状态为下拉状态时 if (refreshState === REFRESH_STATE.pull) { doRefreshAction(REFRESH_STATE.normal); return; } // 执行外部触发刷新的回调 var _ref5 = props.refresh, handler = _ref5.handler; handler === null || handler === void 0 ? void 0 : handler(); }; var bind = useDrag(function (state) { if (state.last) { setAnimationDuration(props.animationDuration); onDragEnd({ offsetY: offsetY }); return; } onDragMove(state); }, { enabled: true, pointer: { touch: true }, axis: 'y', eventOptions: { passive: !Events.supportsPassiveEvents } }); var load = props.load, refresh = props.refresh; if (prevLoad.current.state !== load.state) { doLoadAction(load.state); prevLoad.current = load; } if (prevRefresh.current.state !== refresh.state) { doRefreshAction(refresh.state); prevRefresh.current = refresh; } useEffect(function () { setIsMounted(true); pullRef.current && Events.on(pullRef.current, 'touchmove', function () {}); return function () { pullRef.current && Events.off(pullRef.current, 'touchmove', function () {}); setIsMounted(false); }; }, []); /** * 渲染刷新节点 */ var renderRefresh = function renderRefresh() { var _Pull$defaultProps2; var refreshProps = _objectSpread(_objectSpread({}, (_Pull$defaultProps2 = Pull.defaultProps) === null || _Pull$defaultProps2 === void 0 ? void 0 : _Pull$defaultProps2.refresh), props === null || props === void 0 ? void 0 : props.refresh); var startDistance = refreshProps.startDistance, distance = refreshProps.distance, render = refreshProps.render; var percent = 0; if (typeof offsetY === 'number' && offsetY >= startDistance) { percent = (offsetY - startDistance < distance ? offsetY - startDistance : distance) * 100 / distance; } if (typeof render === 'function') { return render(refreshState, percent); } var refreshCls = bem('control'); switch (refreshState) { case REFRESH_STATE.pull: return /*#__PURE__*/React.createElement("div", { className: refreshCls }, /*#__PURE__*/React.createElement(Loading, { loading: false, percent: percent }), /*#__PURE__*/React.createElement("span", null, locale.Pull.pullText)); case REFRESH_STATE.drop: return /*#__PURE__*/React.createElement("div", { className: refreshCls }, /*#__PURE__*/React.createElement(Loading, { loading: false, percent: 100 }), /*#__PURE__*/React.createElement("span", null, locale.Pull.dropText)); case REFRESH_STATE.loading: return /*#__PURE__*/React.createElement("div", { className: refreshCls }, /*#__PURE__*/React.createElement(Loading, { type: "spinner" }), /*#__PURE__*/React.createElement("span", null, locale.Pull.loadingText)); case REFRESH_STATE.success: return /*#__PURE__*/React.createElement("div", { className: refreshCls }, /*#__PURE__*/React.createElement(SuccessCircleIcon, { theme: "success" }), /*#__PURE__*/React.createElement("span", null, locale.Pull.successText)); case REFRESH_STATE.failure: return /*#__PURE__*/React.createElement("div", { className: refreshCls }, /*#__PURE__*/React.createElement(WarningCircleIcon, { theme: "danger" }), /*#__PURE__*/React.createElement("span", null, locale.Pull.failureText)); default: } }; /** * 渲染加载节点 */ var renderLoad = function renderLoad() { var _Pull$defaultProps3; var loadProps = _objectSpread(_objectSpread({}, (_Pull$defaultProps3 = Pull.defaultProps) === null || _Pull$defaultProps3 === void 0 ? void 0 : _Pull$defaultProps3.load), props === null || props === void 0 ? void 0 : props.load); var render = loadProps.render; if (typeof render === 'function') { return render(loadState); } var loadCls = bem('control'); switch (loadState) { case LOAD_STATE.loading: return /*#__PURE__*/React.createElement("div", { className: loadCls }, /*#__PURE__*/React.createElement(Loading, { type: "spinner" }), /*#__PURE__*/React.createElement("span", null, locale.Pull.loadingText)); case LOAD_STATE.failure: return /*#__PURE__*/React.createElement("div", { className: loadCls }, /*#__PURE__*/React.createElement(WarningCircleIcon, { theme: "danger" }), /*#__PURE__*/React.createElement("span", null, locale.Pull.failureText)); case LOAD_STATE.complete: return /*#__PURE__*/React.createElement("div", { className: loadCls }, /*#__PURE__*/React.createElement("span", null, locale.Pull.completeText)); default: } }; var className = props.className, style = props.style, children = props.children; var cls = bem([className]); var loadCls = bem('load', [{ show: loadState >= LOAD_STATE.loading }]); var contentStyle = { WebkitTransition: "all ".concat(animationDuration, "ms"), transition: "all ".concat(animationDuration, "ms") }; if (refreshState <= REFRESH_STATE.drop) { contentStyle.WebkitTransform = "translate3d(0, ".concat(offsetY, "px, 0)"); contentStyle.transform = "translate3d(0, ".concat(offsetY, "px, 0)"); } return /*#__PURE__*/React.createElement("div", _extends({ className: cls, style: style }, bind(), { ref: pullRef }), /*#__PURE__*/React.createElement("div", { className: bem('content'), style: contentStyle }, /*#__PURE__*/React.createElement("div", { className: bem('refresh') }, renderRefresh()), /*#__PURE__*/React.createElement("div", { className: bem('body') }, children), /*#__PURE__*/React.createElement("div", { className: loadCls }, renderLoad()))); }); Pull.defaultProps = { refresh: { state: REFRESH_STATE.normal, startDistance: 30, distance: 30 }, load: { state: LOAD_STATE.normal, distance: 0 }, animationDuration: 400, stayTime: 1000 }; export default Pull;