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