UNPKG

@nutui/nutui-react

Version:

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

265 lines (264 loc) 9.2 kB
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 };