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