UNPKG

@mijadesign/mjui-react-taro

Version:

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

472 lines (471 loc) 20.4 kB
import { a as __awaiter, _ as __rest } from "./tslib.es6-iWu3F_1J.js"; import classNames from "classnames"; import React, { useState, useRef, useEffect, useImperativeHandle, useMemo, useCallback } from "react"; import { View } from "@tarojs/components"; import isEqual from "lodash.isequal"; import { useUuid, useTouch, web, getRectInMultiPlatform, isEmpty, useConfig } from "@nutui/nutui-react-taro"; import { u as usePropsValue } from "./use-props-value-DuhZMy4U.js"; import { C as ComponentDefaults } from "./typings-DV9RBfhj.js"; import { p as preventDefault } from "./index-akbK29uE.js"; import { P as Popup } from "./popup.taro-EAlqxR4w.js"; const momentum = (distance, duration) => { const speed = Math.abs(distance / duration); return speed / 3e-3 * (distance < 0 ? -1 : 1); }; const useStyles = (touchTime, touchDeg, scrollDistance, lineSpacing, rotation) => { const getTransitionStyle = (transformValue) => ({ transition: `transform ${touchTime}ms cubic-bezier(0.17, 0.89, 0.45, 1)`, transform: transformValue }); const touchRollerStyle = () => getTransitionStyle(`rotate3d(1, 0, 0, ${touchDeg})`); const touchTiledStyle = () => getTransitionStyle(`translate3d(0, ${scrollDistance}px, 0)`); const rollerStyle = (index) => ({ transform: `rotate3d(1, 0, 0, ${-rotation * (index + 1)}deg) translate3d(0px, 0px, ${Math.round(lineSpacing.current * 3.2)}px)` }); return { touchRollerStyle, touchTiledStyle, rollerStyle }; }; const InternalPickerRoller = (props, ref) => { const { keyIndex = 0, options = [], threeDimensional = true, duration = 1e3, onSelect, renderLabel = (item) => { var _a; return (_a = item === null || item === void 0 ? void 0 : item.text) !== null && _a !== void 0 ? _a : item === null || item === void 0 ? void 0 : item.label; } } = props; const classPrefix = "nut-pickerview-roller"; const uuid = useUuid(); const DEFAULT_DURATION = 200; const INERTIA_TIME = 300; const INERTIA_DISTANCE = 15; const ROTATION = 20; const touch = useTouch(); const [currentIndex, setCurrentIndex] = useState(0); const lineSpacing = useRef(48); const [touchTime, setTouchTime] = useState(0); const [touchDeg, setTouchDeg] = useState("0deg"); const isMoving = useRef(false); const rollerRef = useRef(null); const pickerRollerRef = useRef(null); const placeholderRef = useRef(null); const [startTime, setStartTime] = useState(0); const [startY, setStartY] = useState(0); const transformY = useRef(0); const [scrollDistance, setScrollDistance] = useState(0); const [isGetLineSpacing, setGetStatus] = useState(web()); const { touchRollerStyle, touchTiledStyle, rollerStyle } = useStyles(touchTime, touchDeg, scrollDistance, lineSpacing, ROTATION); const isItemHidden = (index) => index >= currentIndex + 8 || index <= currentIndex - 8; const applyTransform = (type, deg, time = DEFAULT_DURATION, translateY = 0) => { setTouchTime(type !== "end" ? 0 : time); setTouchDeg(deg); setScrollDistance(translateY); }; const handleMove = (move, type, time) => { let updatedMove = move + transformY.current; if (type === "end") { updatedMove = Math.max(Math.min(updatedMove, 0), -(options.length - 1) * lineSpacing.current); const endMove = Math.round(updatedMove / lineSpacing.current) * lineSpacing.current; const deg = `${(Math.abs(Math.round(endMove / lineSpacing.current)) + 1) * ROTATION}deg`; applyTransform(type, deg, time, endMove); setCurrentIndex(Math.abs(Math.round(endMove / lineSpacing.current)) + 1); } else { const currentDeg = (-updatedMove / lineSpacing.current + 1) * ROTATION; const deg = Math.min(Math.max(currentDeg, 0), (options.length + 1) * ROTATION); if (deg >= 0 && deg < (options.length + 1) * ROTATION) { applyTransform("", `${deg}deg`, void 0, updatedMove); deg > 0 && setCurrentIndex(Math.abs(Math.round(updatedMove / lineSpacing.current)) + 1); } } }; const selectValue = (move) => { onSelect === null || onSelect === void 0 ? void 0 : onSelect(options === null || options === void 0 ? void 0 : options[Math.round(-move / lineSpacing.current)], keyIndex); }; const handleTouchStart = (event) => { touch.start(event); setStartY(touch.deltaY.current); setStartTime(Date.now()); transformY.current = scrollDistance; }; const handleTouchMove = (event) => { touch.move(event); if (touch.isVertical) { isMoving.current = true; preventDefault(event); } const move = touch.deltaY.current - startY; handleMove(move); }; const handleTouchEnd = () => { if (!isMoving.current) return; const move = touch.deltaY.current - startY; const moveTime = Date.now() - startTime; if (moveTime <= INERTIA_TIME && Math.abs(move) > INERTIA_DISTANCE) { const distance = momentum(move, moveTime); handleMove(distance, "end", +duration); } else { handleMove(move, "end"); } setTimeout(() => { touch.reset(); }, 0); }; const updateStatus = (shouldSelect, value) => { const selectedValue = props.value; const index = options.findIndex((item) => item.value === selectedValue); setCurrentIndex(index === -1 ? 0 : index + 1); const move = index === -1 ? 0 : index * lineSpacing.current; handleMove(-move); }; const stopMomentumScroll = () => { isMoving.current = false; setTouchTime(0); selectValue(scrollDistance); }; const getReactHeight = () => __awaiter(void 0, void 0, void 0, function* () { try { const placeholder = yield getRectInMultiPlatform(placeholderRef.current); const placeholderHeight = (placeholder === null || placeholder === void 0 ? void 0 : placeholder.height) || 48; return placeholderHeight; } catch (error) { console.error("获取高度失败:", error); return 48; } }); useEffect(() => { const element = pickerRollerRef.current; let currentLineSpacing; if (element && web()) { const computedStyle = getComputedStyle(element); currentLineSpacing = computedStyle.getPropertyValue("--nutui-picker-item-height"); if (currentLineSpacing) { lineSpacing.current = parseFloat(currentLineSpacing); } } else if (placeholderRef.current) { getReactHeight().then((height) => { currentLineSpacing = height; if (currentLineSpacing) { lineSpacing.current = currentLineSpacing; setGetStatus(true); } }); } }, [pickerRollerRef.current, placeholderRef.current]); useEffect(() => { isMoving.current = false; setScrollDistance(0); transformY.current = 0; updateStatus(); }, [options, props.value]); useImperativeHandle(ref, () => ({ stopMomentum: stopMomentumScroll, moving: isMoving.current })); useEffect(() => { var _a, _b, _c; const eventOptions = false; (_a = pickerRollerRef.current) === null || _a === void 0 ? void 0 : _a.addEventListener("touchstart", handleTouchStart, eventOptions); (_b = pickerRollerRef.current) === null || _b === void 0 ? void 0 : _b.addEventListener("touchmove", handleTouchMove, eventOptions); (_c = pickerRollerRef.current) === null || _c === void 0 ? void 0 : _c.addEventListener("touchend", handleTouchEnd, eventOptions); return () => { var _a2, _b2, _c2; (_a2 = pickerRollerRef.current) === null || _a2 === void 0 ? void 0 : _a2.removeEventListener("touchstart", handleTouchStart, eventOptions); (_b2 = pickerRollerRef.current) === null || _b2 === void 0 ? void 0 : _b2.removeEventListener("touchmove", handleTouchMove, eventOptions); (_c2 = pickerRollerRef.current) === null || _c2 === void 0 ? void 0 : _c2.removeEventListener("touchend", handleTouchEnd, eventOptions); }; }, [pickerRollerRef.current, handleTouchStart, handleTouchMove, handleTouchEnd]); return React.createElement( View, { className: "nut-pickerview-list", ref: pickerRollerRef }, React.createElement(View, { className: `${classPrefix}-placeholder`, ref: placeholderRef, id: `${classPrefix}-placeholder-${uuid}` }), React.createElement( View, { className: classPrefix, id: `${classPrefix}-${uuid}`, ref: rollerRef, style: threeDimensional ? touchRollerStyle() : touchTiledStyle(), onTransitionEnd: stopMomentumScroll }, threeDimensional && options.map((item, index) => { var _a; return React.createElement(React.Fragment, { key: (_a = item.value) !== null && _a !== void 0 ? _a : index }, isGetLineSpacing ? React.createElement(View, { className: classNames(`${classPrefix}-item`, { [`${classPrefix}-item-hidden`]: isItemHidden(index + 1), [`${classPrefix}-item-active`]: index + 1 === currentIndex }), style: rollerStyle(index) }, renderLabel(item)) : null); }), !threeDimensional && options.map((item, index) => { var _a; return React.createElement(View, { className: classNames(`${classPrefix}-item-tiled`, { [`${classPrefix}-item-active`]: index + 1 === currentIndex }), key: (_a = item.value) !== null && _a !== void 0 ? _a : index }, renderLabel(item)); }) ) ); }; const PickerRoller = React.forwardRef(InternalPickerRoller); const defaultProps$1 = Object.assign(Object.assign({}, ComponentDefaults), { options: [], defaultValue: [], value: void 0, renderLabel: (item) => { var _a; return (_a = item === null || item === void 0 ? void 0 : item.text) !== null && _a !== void 0 ? _a : item === null || item === void 0 ? void 0 : item.label; } }); const InternalPickerView = (props, ref) => { const { options, defaultValue = [], value, duration, threeDimensional, renderLabel, className, style, onChange } = Object.assign(Object.assign({}, defaultProps$1), props); const classPrefix = "nut-pickerview"; const cls = classNames(classPrefix, className); const [selectedValue] = usePropsValue({ value, defaultValue: [...defaultValue], finalValue: [...defaultValue] }); const [innerValue, setInnerValue] = useState(selectedValue); const [innerOptions, setInnerOptions] = useState([]); const changeIndex = useRef(0); const columnsType = useMemo(() => { const firstColumn = props.options[0] || []; if (Array.isArray(firstColumn) && firstColumn.length > 0 && "children" in firstColumn[0]) { return "cascade"; } return "multiple"; }, [props.options]); const formatCascadeOptions = (options2, value2) => { if (!options2.length) return []; const formatted = []; let columnOptions = { label: "", value: "", text: "", children: options2 }; let columnIndex = 0; while (columnOptions && columnOptions.children) { const currentOptions = columnOptions.children; formatted.push(currentOptions); const currentValue = value2 === null || value2 === void 0 ? void 0 : value2[columnIndex]; if (currentValue === 0) { columnOptions = currentOptions[0]; } else if (currentValue) { const index = currentOptions.findIndex((columnItem) => columnItem.value === currentValue); columnOptions = currentOptions[index === -1 ? 0 : index]; } else { break; } columnIndex++; } return formatted; }; const formatOptions = useMemo(() => { var _a; if (columnsType === "cascade") { return formatCascadeOptions((_a = props === null || props === void 0 ? void 0 : props.options) === null || _a === void 0 ? void 0 : _a[0], innerValue); } return props.options; }, [innerValue, options, columnsType]); useEffect(() => { const options2 = props.options; if (Array.isArray(options2) && options2.length && options2 !== innerOptions) { setInnerOptions(formatOptions); } }, [props.options, innerValue]); useEffect(() => { if (selectedValue !== innerValue) { setInnerValue(selectedValue); } }, [selectedValue]); const handleSelect = useCallback((option, index) => { var _a, _b, _c; const newValue = option === null || option === void 0 ? void 0 : option.value; if (isEmpty(newValue) || innerValue[index] === newValue) return; changeIndex.current = index; if (columnsType === "multiple") { setInnerValue((prev) => { const next = [...prev]; next[index] = newValue; return next; }); } else { const startIndex = index; const values = []; values[index] = option.value; while ((_a = option === null || option === void 0 ? void 0 : option.children) === null || _a === void 0 ? void 0 : _a[0]) { values[index + 1] = option.children[0].value; index++; option = option.children[0]; } if ((_b = option === null || option === void 0 ? void 0 : option.children) === null || _b === void 0 ? void 0 : _b.length) { values[index + 1] = ""; } const combineResult = [...innerValue.slice(0, startIndex), ...values.splice(startIndex)]; setInnerValue([...combineResult]); const optionFirst = (_c = props === null || props === void 0 ? void 0 : props.options) === null || _c === void 0 ? void 0 : _c[0]; if (!isEqual(formatCascadeOptions(optionFirst, combineResult), innerOptions)) { setInnerOptions(formatCascadeOptions(optionFirst, combineResult)); } } }, [innerValue, props.options, columnsType, innerOptions]); const selectedOptions = useMemo(() => { return innerOptions.map((columnOptions, index) => { const selectedOption = columnOptions.find((item) => item.value === innerValue[index]); return selectedOption; }).filter(Boolean); }, [innerOptions, innerValue]); useEffect(() => { onChange === null || onChange === void 0 ? void 0 : onChange({ value: innerValue, index: changeIndex.current, selectedOptions }); }, [innerValue, selectedOptions, onChange]); return React.createElement( View, { className: cls, style }, innerOptions.map((item, index) => { var _a; return React.createElement(PickerRoller, { height: props.height, ref: (_a = props === null || props === void 0 ? void 0 : props.setRefs) === null || _a === void 0 ? void 0 : _a.call(props, index), key: index, keyIndex: index, value: innerValue[index], options: item, renderLabel, onSelect: handleSelect, duration, threeDimensional }); }), (innerOptions === null || innerOptions === void 0 ? void 0 : innerOptions.length) ? React.createElement( React.Fragment, null, React.createElement(View, { className: "nut-pickerview-mask" }), React.createElement(View, { className: "nut-pickerview-indicator" }) ) : null ); }; const PickerView = React.forwardRef(InternalPickerView); function useRefs() { const refs = React.useRef([]); const setRefs = React.useCallback((index) => (el) => { if (el) refs.current[index] = el; }, []); const reset = React.useCallback(() => { refs.current = []; }, []); return [refs.current, setRefs, reset]; } const defaultProps = Object.assign(Object.assign({}, ComponentDefaults), { title: "", options: [], value: void 0, defaultValue: [], closeOnOverlayClick: true }); const InternalPicker$1 = (props, ref) => { const { locale } = useConfig(); const _a = Object.assign(Object.assign({}, defaultProps), props), { children, visible, title, options = [], closeOnOverlayClick, popupProps = {}, defaultValue = [], className, style, threeDimensional, duration, onConfirm, onCancel, onClose, onChange } = _a, rest = __rest(_a, ["children", "visible", "title", "options", "closeOnOverlayClick", "popupProps", "defaultValue", "className", "style", "threeDimensional", "duration", "onConfirm", "onCancel", "onClose", "onChange"]); const classPrefix = "nut-picker"; const classes = classNames(classPrefix, className); const [selectedValue, setSelectedValue] = usePropsValue({ value: props.value, defaultValue: [...defaultValue], finalValue: [...defaultValue], onChange: (value) => { var _a2; (_a2 = props.onConfirm) === null || _a2 === void 0 ? void 0 : _a2.call(props, selectedOptionsRef.current, value); } }); const [innerVisible, setInnerVisible] = usePropsValue({ value: props.visible, defaultValue: false, finalValue: false, onChange: (v) => { var _a2; if (!v) { (_a2 = props.onClose) === null || _a2 === void 0 ? void 0 : _a2.call(props, selectedOptionsRef.current, innerValue); } } }); const actions = { open: () => { setInnerVisible(true); }, close: () => { setInnerVisible(false); } }; useImperativeHandle(ref, () => actions); const [innerValue, setInnerValue] = useState([...selectedValue]); const innerValueRef = useRef(innerValue); const [innerOptions, setInnerOptions] = useState([]); const selectedOptionsRef = useRef([]); const [refs, setRefs] = useRefs(); useEffect(() => { if (innerVisible) { setInnerValue((selectedValue === null || selectedValue === void 0 ? void 0 : selectedValue.length) ? selectedValue : options === null || options === void 0 ? void 0 : options.map((i) => { var _a2; return (_a2 = i === null || i === void 0 ? void 0 : i[0]) === null || _a2 === void 0 ? void 0 : _a2.value; }).filter(Boolean)); setInnerOptions(options); } }, [selectedValue, innerOptions, innerVisible]); const onChangeItem = ({ value, index, selectedOptions }) => { if (selectedOptions === null || selectedOptions === void 0 ? void 0 : selectedOptions.length) { selectedOptionsRef.current = selectedOptions; } if (isEqual(value, innerValueRef.current)) return; innerValueRef.current = value; setInnerValue(value); innerVisible && (onChange === null || onChange === void 0 ? void 0 : onChange({ selectedOptions, value, index })); }; const onConfirmEvent = () => { let moving = false; refs.forEach((ref2) => { if (ref2.moving) moving = true; ref2.stopMomentum(); }); if (!moving) { setSelectedValue(innerValue, true); setInnerVisible(false); } }; const onCancelEvent = () => { setInnerValue(selectedValue); onCancel === null || onCancel === void 0 ? void 0 : onCancel(); setInnerVisible(false); }; const renderTitleBar = () => { return React.createElement( View, { className: `${classPrefix}-control` }, React.createElement(View, { className: `${classPrefix}-cancel-btn`, onClick: (e) => { e.stopPropagation(); onCancelEvent(); } }, locale === null || locale === void 0 ? void 0 : locale.cancel), React.createElement(View, { className: `${classPrefix}-title` }, title || ""), React.createElement(View, { className: `${classPrefix}-confirm-btn`, onClick: (e) => { e.stopPropagation(); onConfirmEvent(); } }, locale.confirm) ); }; const renderPickerElement = () => { return React.createElement( View, Object.assign({ className: classes, style }, rest), renderTitleBar(), typeof children !== "function" && children, React.createElement( View, { className: `${classPrefix}-panel` }, React.createElement(PickerView, { setRefs, value: innerValue, options: props.options, threeDimensional, duration, onChange: ({ value, index, selectedOptions }) => { onChangeItem({ value, index, selectedOptions }); } }) ) ); }; return React.createElement( React.Fragment, null, typeof children === "function" && children(selectedValue), React.createElement(Popup, Object.assign({}, popupProps, { visible: innerVisible, position: "bottom", onOverlayClick: () => { if (!closeOnOverlayClick) return; onCancelEvent(); } }), innerVisible ? React.createElement( React.Fragment, null, renderPickerElement(), " " ) : null) ); }; const Picker$1 = React.forwardRef(InternalPicker$1); const InternalPicker = (props) => { const { className, threeDimensional = false } = props, rest = __rest(props, ["className", "threeDimensional"]); return React.createElement(Picker$1, Object.assign({ threeDimensional, className: classNames("mj-picker", className) }, rest)); }; const Picker = InternalPicker; export { Picker as P };