UNPKG

@nutui/nutui-react

Version:

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

426 lines (425 loc) 16.1 kB
import { _ as __rest } from "./tslib.es6.js"; import React__default, { useState, useRef, useEffect, useImperativeHandle } from "react"; import classNames from "classnames"; import { P as Popup } from "./popup2.js"; import { S as SafeArea } from "./safearea2.js"; import { u as useTouch } from "./use-touch.js"; import { p as passiveSupported } from "./supports-passive.js"; import { u as useRefs } from "./use-refs.js"; import { useConfig } from "./ConfigProvider.js"; import { u as usePropsValue } from "./use-props-value.js"; import { C as ComponentDefaults } from "./typings.js"; const InternalPickerPanel = (props, ref) => { const { keyIndex = 0, defaultValue, options = [], threeDimensional = true, duration = 1e3, chooseItem } = props; const touch = useTouch(); const DEFAULT_DURATION = 200; const INERTIA_TIME = 300; const INERTIA_DISTANCE = 15; const [currIndex, setCurrIndex] = useState(1); const lineSpacing = 36; const [touchTime, setTouchTime] = useState(0); const [touchDeg, setTouchDeg] = useState("0deg"); const rotation = 20; const moving = useRef(false); let timer; const rollerRef = useRef(null); const PickerPanelRef = useRef(null); const [startTime, setStartTime] = useState(0); const [startY, setStartY] = useState(0); const transformY = useRef(0); const [scrollDistance, setScrollDistance] = useState(0); const isHidden = (index) => { if (index >= currIndex + 8 || index <= currIndex - 8) { return true; } return false; }; const setTransform = (type, deg, time = DEFAULT_DURATION, translateY = 0) => { let nTime = time; if (type !== "end") { nTime = 0; } setTouchTime(nTime); setTouchDeg(deg); setScrollDistance(translateY); }; const setMove = (move, type, time) => { let updateMove = move + transformY.current; if (type === "end") { if (updateMove > 0) { updateMove = 0; } if (updateMove < -(options.length - 1) * lineSpacing) { updateMove = -(options.length - 1) * lineSpacing; } const endMove = Math.round(updateMove / lineSpacing) * lineSpacing; const deg = `${(Math.abs(Math.round(endMove / lineSpacing)) + 1) * rotation}deg`; setTransform(type, deg, time, endMove); setCurrIndex(Math.abs(Math.round(endMove / lineSpacing)) + 1); } else { let deg = 0; const currentDeg = (-updateMove / lineSpacing + 1) * rotation; const maxDeg = (options.length + 1) * rotation; const minDeg = 0; deg = Math.min(Math.max(currentDeg, minDeg), maxDeg); if (minDeg < deg && deg < maxDeg) { setTransform("", `${deg}deg`, void 0, updateMove); setCurrIndex(Math.abs(Math.round(updateMove / lineSpacing)) + 1); } } }; const setChooseValue = (move) => { chooseItem === null || chooseItem === void 0 ? void 0 : chooseItem(options === null || options === void 0 ? void 0 : options[Math.round(-move / lineSpacing)], keyIndex); }; const touchStart = (event) => { touch.start(event); setStartY(touch.deltaY.current); setStartTime(Date.now()); transformY.current = scrollDistance; }; const touchMove = (event) => { touch.move(event); if (touch.isVertical) { moving.current = true; preventDefault(event); } const move = touch.deltaY.current - startY; setMove(move); }; const touchEnd = () => { if (!moving.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); setMove(distance, "end", +duration); } else { setMove(move, "end"); } setTimeout(() => { touch.reset(); }, 0); }; const momentum = (distance, duration2) => { let nDistance = distance; const speed = Math.abs(nDistance / duration2); nDistance = speed / 3e-3 * (nDistance < 0 ? -1 : 1); return nDistance; }; const modifyStatus = (type, val) => { const value = defaultValue; let index = -1; if (value) { options.some((item, idx) => { if (item.value === value) { index = idx; return true; } return false; }); } else { options.forEach((item, i) => { if (item.value === defaultValue) { index = i; } }); } setCurrIndex(index === -1 ? 1 : index + 1); const move = index === -1 ? 0 : index * lineSpacing; setMove(-move); }; const stopMomentum = () => { moving.current = false; setTouchTime(0); setChooseValue(scrollDistance); }; const preventDefault = (event, isStopPropagation) => { if (typeof event.cancelable !== "boolean" || event.cancelable) { event.preventDefault(); } { event.stopPropagation(); } }; const touchRollerStyle = () => { return { transition: `transform ${touchTime}ms cubic-bezier(0.17, 0.89, 0.45, 1)`, transform: `rotate3d(1, 0, 0, ${touchDeg})` }; }; const touchTileStyle = () => { return { transition: `transform ${touchTime}ms cubic-bezier(0.17, 0.89, 0.45, 1)`, transform: `translate3d(0, ${scrollDistance}px, 0)` }; }; useEffect(() => { setScrollDistance(0); transformY.current = 0; modifyStatus(); return () => { clearTimeout(timer); }; }, [options]); useImperativeHandle(ref, () => ({ stopMomentum, moving: moving.current })); useEffect(() => { var _a, _b, _c; const options2 = passiveSupported ? { passive: false } : false; (_a = PickerPanelRef.current) === null || _a === void 0 ? void 0 : _a.addEventListener("touchstart", touchStart, options2); (_b = PickerPanelRef.current) === null || _b === void 0 ? void 0 : _b.addEventListener("touchmove", touchMove, options2); (_c = PickerPanelRef.current) === null || _c === void 0 ? void 0 : _c.addEventListener("touchend", touchEnd, options2); return () => { var _a2, _b2, _c2; (_a2 = PickerPanelRef.current) === null || _a2 === void 0 ? void 0 : _a2.removeEventListener("touchstart", touchStart); (_b2 = PickerPanelRef.current) === null || _b2 === void 0 ? void 0 : _b2.removeEventListener("touchmove", touchMove); (_c2 = PickerPanelRef.current) === null || _c2 === void 0 ? void 0 : _c2.removeEventListener("touchend", touchEnd); }; }); return React__default.createElement( "div", { className: "nut-picker-list", ref: PickerPanelRef, onTouchStart: touchStart, onTouchMove: touchMove, onTouchEnd: touchEnd }, React__default.createElement( "div", { className: "nut-picker-roller", ref: rollerRef, style: threeDimensional ? touchRollerStyle() : touchTileStyle(), onTransitionEnd: stopMomentum }, threeDimensional && options.map((item, index) => { return React__default.createElement( "div", { className: `nut-picker-roller-item ${isHidden(index + 1) && "nut-picker-roller-item-hidden"}`, style: { transform: `rotate3d(1, 0, 0, ${-rotation * (index + 1)}deg) translate3d(0px, 0px, 104px)` }, key: item.value ? item.value : index }, React__default.createElement(React__default.Fragment, null, item.text) ); }), !threeDimensional && options.map((item, index) => { return React__default.createElement( "div", { className: "nut-picker-roller-item-title", key: item.value ? item.value : index }, React__default.createElement(React__default.Fragment, null, item.text) ); }) ), React__default.createElement("div", { className: "nut-picker-mask" }), React__default.createElement("div", { className: "nut-picker-indicator" }) ); }; const PickerPanel = React__default.forwardRef(InternalPickerPanel); const defaultProps = Object.assign(Object.assign({}, ComponentDefaults), { title: "", options: [], value: [], defaultValue: [], threeDimensional: true, closeOnOverlayClick: true, duration: 1e3 }); const InternalPicker = (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, afterClose, onChange } = _a, rest = __rest(_a, ["children", "visible", "title", "options", "closeOnOverlayClick", "popupProps", "defaultValue", "className", "style", "threeDimensional", "duration", "onConfirm", "onCancel", "onClose", "afterClose", "onChange"]); const classPrefix = "nut-picker"; const classes = classNames(classPrefix, className); const [selectedValue, setSelectedValue] = usePropsValue({ value: props.value, defaultValue: [...defaultValue], finalValue: [...defaultValue], onChange: (val) => { var _a2; (_a2 = props.onConfirm) === null || _a2 === void 0 ? void 0 : _a2.call(props, setSelectedOptions(), val); } }); const [innerVisible, setInnerVisible] = usePropsValue({ value: props.visible, defaultValue: false, finalValue: false, onChange: (val) => { var _a2; (_a2 = props.onClose) === null || _a2 === void 0 ? void 0 : _a2.call(props, setSelectedOptions(), innerValue); } }); const [innerValue, setInnerValue] = useState(selectedValue); const [columnIndex, setColumnIndex] = useState(0); const pickerRef = useRef(null); const [refs, setRefs] = useRefs(); const [columnsList, setColumnsList] = useState([]); const isConfirmEvent = useRef(false); const actions = { open: () => { setInnerVisible(true); }, close: () => { setInnerVisible(false); } }; useImperativeHandle(ref, () => actions); const formatCascade = (columns, values) => { const formatted = []; let columnOptions = { text: "", value: "", children: columns }; let columnIndex2 = 0; while (columnOptions && columnOptions.children) { const options2 = columnOptions.children; const value = values[columnIndex2]; let index = options2.findIndex((columnItem) => columnItem.value === value); if (index === -1) index = 0; columnOptions = columnOptions.children[index]; columnIndex2++; formatted.push(options2); } return formatted; }; const columnsType = () => { const firstColumn = options[0]; if (firstColumn) { if (Array.isArray(firstColumn)) { return "multiple"; } if ("children" in firstColumn) { return "cascade"; } } return "single"; }; const normalListData = (innerValue2) => { const type = columnsType(); switch (type) { case "multiple": return options; case "cascade": return formatCascade(options, innerValue2); default: return [options]; } }; const init = () => { const normalData = normalListData(innerValue); setColumnsList(normalData); const data = []; normalData.length > 0 && normalData.map((item) => { item[0] && data.push(item[0].value); return item; }); if (!innerValue.length && innerValue.length === 0) { setInnerValue([...data]); } }; useEffect(() => { setInnerValue(innerValue !== selectedValue ? selectedValue : innerValue); }, [innerVisible, selectedValue]); useEffect(() => { if (innerVisible) { init(); } }, [options, innerVisible]); useEffect(() => { onChange && onChange(setSelectedOptions(), innerValue, columnIndex); }, [innerValue, columnsList]); const setSelectedOptions = () => { const options2 = []; let currOptions = []; columnsList.forEach((columnOptions, index) => { currOptions = columnOptions.filter((item) => item.value === innerValue[index]); if (currOptions[0]) { options2.push(currOptions[0]); } else { columnOptions[0] && options2.push(columnOptions[0]); } }); return options2; }; const chooseItem = (columnOptions, columnIndex2) => { var _a2, _b; const values = []; const start = columnIndex2; if (columnOptions && Object.keys(columnOptions).length) { if (values[columnIndex2] !== columnOptions.value) { if (columnsType() === "cascade") { values[columnIndex2] = columnOptions.value || ""; while ((_a2 = columnOptions === null || columnOptions === void 0 ? void 0 : columnOptions.children) === null || _a2 === void 0 ? void 0 : _a2[0]) { values[columnIndex2 + 1] = columnOptions.children[0].value; columnIndex2++; columnOptions = columnOptions.children[0]; } if ((_b = columnOptions === null || columnOptions === void 0 ? void 0 : columnOptions.children) === null || _b === void 0 ? void 0 : _b.length) { values[columnIndex2 + 1] = ""; } const combineResult = [ ...innerValue.slice(0, start), ...values.splice(start) ]; setInnerValue(combineResult); setColumnsList(normalListData(combineResult)); } else { setInnerValue((data) => { const cdata = [...data]; cdata[columnIndex2] = Object.prototype.hasOwnProperty.call(columnOptions, "value") ? columnOptions.value : ""; return cdata; }); } setColumnIndex(columnIndex2); } } }; const confirm = () => { let moving = false; refs.forEach((ref2) => { if (ref2.moving) moving = true; ref2.stopMomentum(); }); if (moving) { isConfirmEvent.current = true; } else { setSelectedValue(innerValue, true); setInnerVisible(false); } setTimeout(() => { isConfirmEvent.current = false; }, 0); }; const renderTitleBar = () => { return React__default.createElement( "div", { className: `${classPrefix}-control` }, React__default.createElement("span", { className: `${classPrefix}-cancel-btn`, onClick: (e) => { e.stopPropagation(); onCancel === null || onCancel === void 0 ? void 0 : onCancel(); setInnerVisible(false); } }, locale === null || locale === void 0 ? void 0 : locale.cancel), React__default.createElement("div", { className: `${classPrefix}-title` }, title || ""), React__default.createElement("span", { className: `${classPrefix}-confirm-btn`, onClick: (e) => { e.stopPropagation(); confirm(); } }, locale.confirm) ); }; return React__default.createElement( React__default.Fragment, null, typeof children === "function" && children(selectedValue), React__default.createElement( Popup, Object.assign({}, popupProps, { visible: innerVisible, position: "bottom", onOverlayClick: () => { var _a2; if (closeOnOverlayClick) { (_a2 = props.onCancel) === null || _a2 === void 0 ? void 0 : _a2.call(props); setInnerVisible(false); } }, afterClose: () => { afterClose === null || afterClose === void 0 ? void 0 : afterClose(setSelectedOptions(), innerValue, pickerRef); } }), React__default.createElement( "div", Object.assign({ className: classes, style }, rest), renderTitleBar(), typeof children !== "function" && children, React__default.createElement("div", { className: `${classPrefix}-panel`, ref: pickerRef }, columnsList === null || columnsList === void 0 ? void 0 : columnsList.map((item, index) => { return React__default.createElement(PickerPanel, { ref: setRefs(index), defaultValue: innerValue === null || innerValue === void 0 ? void 0 : innerValue[index], options: item, threeDimensional, chooseItem: (value, index2) => chooseItem(value, index2), duration, key: index, keyIndex: index }); })) ), React__default.createElement(SafeArea, { position: "bottom" }) ) ); }; const Picker = React__default.forwardRef(InternalPicker); export { Picker as default };