@nutui/nutui-react
Version:
京东风格的轻量级移动端 React 组件库,支持一套代码生成 H5 和小程序
265 lines (264 loc) • 9.2 kB
JavaScript
import React__default, { useState, useRef, useEffect, useCallback } from "react";
import classNames from "classnames";
import { u as useTouch } from "./use-touch.js";
import { g as getRect } from "./use-client-rect.js";
import { C as ComponentDefaults } from "./typings.js";
import { u as usePropsValue } from "./use-props-value.js";
import { a as useRtl } from "./configprovider2.js";
const defaultProps = Object.assign(Object.assign({}, ComponentDefaults), { range: false, min: 0, max: 100, step: 1, vertical: false, marks: {} });
const Range = (props) => {
const rtl = useRtl();
const { className, range, disabled, button, vertical, marks, onChange, onStart, onEnd, minDescription, maxDescription, currentDescription, min, max, step, value, defaultValue } = Object.assign(Object.assign({}, defaultProps), props);
const classPrefix = "nut-range";
const [buttonIndex, setButtonIndex] = useState(0);
const [dragStatus, setDragStatus] = useState("start");
const touch = useTouch();
const root = useRef(null);
const [marksList, setMarksList] = useState([]);
const [startValue, setStartValue] = useState(0);
const handleChange = (value2) => {
onChange && onChange(value2);
};
const [current, setCurrent] = usePropsValue({
value,
defaultValue,
finalValue: 0,
onChange: handleChange
});
const [exactValue, setEaxctValue] = useState(() => value || defaultValue || 0);
const marksRef = useRef({});
useEffect(() => {
if (marks) {
if (Array.isArray(marks)) {
const list = marks.sort((a, b) => a.value - b.value).filter((point) => point.value >= min && point.value <= max);
setMarksList(list.map((mark) => mark.value));
list.forEach((mark) => {
marksRef.current[mark.value] = mark.label !== void 0 ? mark.label : mark.value;
});
} else {
const marksKeys = Object.keys(marks);
const list = marksKeys.map(parseFloat).sort((a, b) => a - b).filter((point) => point >= min && point <= max);
setMarksList(list);
}
}
}, [marks]);
const scope = () => {
return max - min;
};
const classes = classNames(classPrefix, {
[`${classPrefix}-disabled`]: disabled,
[`${classPrefix}-vertical`]: vertical
});
const containerClasses = classNames(`${classPrefix}-container`, {
[`${classPrefix}-container-vertical`]: vertical
}, className);
const markClassName = useCallback((mark) => {
const classPrefix2 = "nut-range-mark";
let lowerBound = min;
let upperBound = max;
if (range && Array.isArray(current)) {
lowerBound = current[0];
upperBound = current[1];
} else {
upperBound = current;
}
const isActive = mark <= upperBound && mark >= lowerBound;
return [
`${classPrefix2}-text`,
`${isActive ? `${classPrefix2}-text-active` : ""}`
].join(" ");
}, [range, current, min, max]);
const isRange = (val) => {
return !!range && Array.isArray(val);
};
const calcMainAxis = () => {
const modelVal = current;
if (isRange(modelVal)) {
return `${(modelVal[1] - modelVal[0]) * 100 / scope()}%`;
}
return `${(modelVal - min) * 100 / scope()}%`;
};
const calcOffset = () => {
const modelVal = current;
if (isRange(modelVal)) {
return `${(modelVal[0] - min) * 100 / scope()}%`;
}
return `0%`;
};
const barStyle = () => {
if (vertical) {
return {
height: calcMainAxis(),
top: calcOffset(),
transition: dragStatus ? "none" : void 0
};
}
const dir = rtl ? "right" : "left";
return {
width: calcMainAxis(),
[dir]: calcOffset(),
transition: dragStatus ? "none" : void 0
};
};
const marksStyle = (mark) => {
const dir = rtl ? "right" : "left";
let style = {
[dir]: `${(mark - min) / scope() * 100}%`
};
if (vertical) {
style = {
top: `${(mark - min) / scope() * 100}%`
};
}
return style;
};
const tickClass = (mark) => {
if (range && Array.isArray(current)) {
return mark <= current[1] && mark >= current[0];
}
return mark <= current;
};
const format = (value2) => {
value2 = Math.max(+min, Math.min(value2, +max));
return Math.round(value2 / +step) * +step;
};
const isSameValue = (newValue, oldValue) => {
return JSON.stringify(newValue) === JSON.stringify(oldValue);
};
const handleOverlap = (value2) => {
if (value2[0] > value2[1]) {
return value2.slice(0).reverse();
}
return value2;
};
const updateValue = (value2, end) => {
if (isRange(value2)) {
value2 = handleOverlap(value2).map(format);
} else {
value2 = format(value2);
}
if (!isSameValue(value2, current)) {
setCurrent(value2);
}
end && onEnd && onEnd(value2);
};
const click = (event) => {
if (disabled || !root.current) {
return;
}
setDragStatus("");
const rect = getRect(root.current);
let delta = event.clientX - rect.left;
let total = rect.width;
if (vertical) {
delta = event.clientY - rect.top;
total = rect.height;
}
const value2 = min + delta / total * scope();
setEaxctValue(current);
if (isRange(current)) {
const [left, right] = current;
const middle = (left + right) / 2;
if (value2 <= middle) {
updateValue([value2, right], true);
} else {
updateValue([left, value2], true);
}
} else {
updateValue(value2, true);
}
};
const onTouchStart = (event) => {
if (disabled) {
return;
}
touch.start(event);
setEaxctValue(current);
if (isRange(current)) {
setStartValue(current.map(format));
} else {
setStartValue(format(current));
}
setDragStatus("start");
};
const onTouchMove = (event) => {
event.stopPropagation();
if (disabled || !root.current) {
return;
}
if (dragStatus === "start") {
onStart && onStart();
}
touch.move(event);
setDragStatus("draging");
const rect = getRect(root.current);
let delta = touch.deltaX.current;
let total = rect.width;
let diff = delta / total * scope();
diff = rtl ? -diff : diff;
if (vertical) {
delta = touch.deltaY.current;
total = rect.height;
diff = delta / total * scope();
}
let newValue;
if (isRange(startValue)) {
newValue = exactValue.slice();
newValue[buttonIndex] = startValue[buttonIndex] + diff;
} else {
newValue = startValue + diff;
}
setEaxctValue(newValue);
updateValue(newValue);
};
const onTouchEnd = () => {
if (disabled) {
return;
}
if (dragStatus === "draging") {
updateValue(current, true);
}
setDragStatus("");
};
const curValue = (idx) => {
const modelVal = current;
const value2 = typeof idx === "number" ? modelVal[idx] : modelVal;
return value2;
};
const renderButton = (index) => {
return React__default.createElement(React__default.Fragment, null, button || React__default.createElement("div", { className: "nut-range-button" }, currentDescription !== null && React__default.createElement("div", { className: "number" }, currentDescription ? currentDescription(curValue(index)) : curValue(index))));
};
return React__default.createElement(
"div",
{ className: containerClasses },
minDescription !== null && React__default.createElement("div", { className: "min" }, minDescription || min),
React__default.createElement(
"div",
{ ref: root, className: classes, onClick: (e) => click(e) },
marksList.length > 0 && React__default.createElement("div", { className: "nut-range-mark" }, marksList.map((mark) => {
return React__default.createElement(
"span",
{ key: mark, className: markClassName(mark), style: marksStyle(mark) },
Array.isArray(marks) ? marksRef.current[mark] : marks[mark],
React__default.createElement("span", { className: classNames("nut-range-tick", {
active: tickClass(mark)
}) })
);
})),
React__default.createElement("div", { className: "nut-range-bar", style: barStyle() }, range ? [0, 1].map((item, index) => {
return React__default.createElement("div", { key: index, className: `${index === 0 ? "nut-range-button-wrapper-left" : ""}
${index === 1 ? "nut-range-button-wrapper-right" : ""}`, onTouchStart: (e) => {
if (typeof index === "number") {
setButtonIndex(index);
}
onTouchStart(e);
}, onTouchMove: (e) => onTouchMove(e), onTouchEnd, onTouchCancel: onTouchEnd, onClick: (e) => e.stopPropagation() }, renderButton(index));
}) : React__default.createElement("div", { className: "nut-range-button-wrapper", onTouchStart: (e) => onTouchStart(e), onTouchMove: (e) => onTouchMove(e), onTouchEnd, onTouchCancel: onTouchEnd, onClick: (e) => e.stopPropagation() }, renderButton()))
),
maxDescription !== null && React__default.createElement("div", { className: "max" }, maxDescription || max)
);
};
Range.displayName = "NutRange";
export {
Range as default
};