diff --git a/src/packages/range/__test__/__snapshots__/range.spec.tsx.snap b/src/packages/range/__test__/__snapshots__/range.spec.tsx.snap index 53d536f872..71ea7bc4dd 100644 --- a/src/packages/range/__test__/__snapshots__/range.spec.tsx.snap +++ b/src/packages/range/__test__/__snapshots__/range.spec.tsx.snap @@ -15,7 +15,7 @@ exports[`range props test 1`] = ` class="nut-range" >
`; -exports[`range test 1`] = `"
0
30
60
100
"`; +exports[`range test 1`] = `"
0
30
60
100
"`; diff --git a/src/packages/range/demos/taro/demo11.tsx b/src/packages/range/demos/taro/demo11.tsx index d5caaac328..20ab0dacef 100644 --- a/src/packages/range/demos/taro/demo11.tsx +++ b/src/packages/range/demos/taro/demo11.tsx @@ -42,7 +42,6 @@ const Demo11 = () => { justifyContent: 'center', top: '50%', left: '50%', - // @ts-ignore transform: 'translate(-50%, -50%)', ...buttonNativeStyle, }} @@ -54,7 +53,7 @@ const Demo11 = () => { height: pxTransform(18), color: 'white', fontSize: pxTransform(10), - lineHeight: pxTransform(10), + lineHeight: 1, textAlign: 'center', paddingTop: pxTransform(4), paddingBottom: pxTransform(4), diff --git a/src/packages/range/range.scss b/src/packages/range/range.scss index 2e4ffe6f9c..77327e3710 100644 --- a/src/packages/range/range.scss +++ b/src/packages/range/range.scss @@ -46,7 +46,10 @@ max-height: 100%; background: $range-active-color; border-radius: 2px; - transition: all 0.2s; + + &-animate { + transition: all 0.2s; + } } &-button { @@ -204,7 +207,8 @@ position: absolute; width: 36px; height: 100%; - top: initial; + // top: initial; + top: 0px; right: 50%; overflow: visible; font-size: 12px; diff --git a/src/packages/range/range.taro.tsx b/src/packages/range/range.taro.tsx index 0be9e7c814..d2f7e0e34c 100644 --- a/src/packages/range/range.taro.tsx +++ b/src/packages/range/range.taro.tsx @@ -8,6 +8,7 @@ import React, { } from 'react' import classNames from 'classnames' import { Text, View } from '@tarojs/components' +import { useReady, nextTick } from '@tarojs/taro' import { pxTransform } from '@/utils/taro/px-transform' import { useTouch } from '@/hooks/use-touch' import { ComponentDefaults } from '@/utils/typings' @@ -16,6 +17,7 @@ import { getRectInMultiPlatform } from '@/utils/taro/get-rect' import { useRtl } from '../configprovider/index.taro' import { harmony } from '@/utils/taro/platform' import { TaroRangeProps, RangeValue } from '@/types' +import { mergeProps } from '@/utils/merge-props' const defaultProps = { ...ComponentDefaults, @@ -31,16 +33,11 @@ const isHm = harmony() const classPrefix = 'nut-range' const verticalClassPrefix = `${classPrefix}-vertical` -const isSameValue = (newValue: RangeValue, oldValue: RangeValue) => { - return JSON.stringify(newValue) === JSON.stringify(oldValue) -} +const isSameValue = (newValue: RangeValue, oldValue: RangeValue) => + JSON.stringify(newValue) === JSON.stringify(oldValue) -const handleOverlap = (value: number[]) => { - if (value[0] > value[1]) { - return value.slice(0).reverse() - } - return value -} +const handleOverlap = (value: number[]) => + value[0] > value[1] ? value.slice(0).reverse() : value export const Range: FunctionComponent< Partial & @@ -69,16 +66,17 @@ export const Range: FunctionComponent< onChange, onStart, onEnd, - } = { ...defaultProps, ...props } + } = mergeProps(defaultProps, props) const rtlClassPrefix = useMemo( () => `rtl-${vertical ? verticalClassPrefix : classPrefix}`, [vertical] ) - const [buttonIndex, setButtonIndex] = useState(0) - const [dragStatus, setDragStatus] = useState('start') + const buttonRef = useRef(0) + const dragStatusRef = useRef('start') const touch = useTouch() const root = useRef(null) + const rootRect = useRef(null) const [marksList, setMarksList] = useState([]) const [startValue, setStartValue] = useState(0) const scope = useMemo(() => { @@ -91,15 +89,13 @@ export const Range: FunctionComponent< const handleChange = (value: RangeValue) => { onChange && onChange(value) } - const [current, setCurrent] = usePropsValue({ + const [innerValue, setInnerValue] = usePropsValue({ value, defaultValue, finalValue: 0, onChange: handleChange, }) - const [exactValue, setExactValue] = useState( - () => value || defaultValue || 0 - ) + const exactValueRef = useRef(value || defaultValue || 0) const marksRef = useRef<{ [key: string]: any }>({}) useEffect(() => { if (marks) { @@ -122,11 +118,13 @@ export const Range: FunctionComponent< } } }, [marks, max, min]) + const classes = classNames(classPrefix, { [`${classPrefix}-disabled`]: disabled, [verticalClassPrefix]: vertical, [`${classPrefix}-native`]: isHm, }) + const containerClasses = classNames( `${classPrefix}-container`, { @@ -135,17 +133,18 @@ export const Range: FunctionComponent< }, className ) + const markClassName = useCallback( (mark: any) => { const classPrefix = 'nut-range-mark' const verticalClassPrefix = 'nut-range-vertical-mark' let lowerBound = min let upperBound = max - if (range && Array.isArray(current)) { - lowerBound = current[0] - upperBound = current[1] + if (range && Array.isArray(innerValue)) { + lowerBound = innerValue[0] + upperBound = innerValue[1] } else { - upperBound = current as number + upperBound = innerValue as number } const isActive = mark <= upperBound && mark >= lowerBound const classNames = [ @@ -165,7 +164,7 @@ export const Range: FunctionComponent< return classNames.join(' ') }, - [min, max, range, current, vertical, rtl, rtlClassPrefix] + [min, max, range, innerValue, vertical, rtl, rtlClassPrefix] ) const isRange = useCallback( @@ -175,33 +174,33 @@ export const Range: FunctionComponent< [range] ) - const calcMainAxis = useCallback(() => { - const modelVal = current as any + const calcMainAxis = useMemo(() => { + const modelVal = innerValue as any return isRange(modelVal) ? `${((modelVal[1] - modelVal[0]) * 100) / scope}%` : `${((modelVal - min) * 100) / scope}%` - }, [current, isRange, min, scope]) + }, [innerValue, isRange, min, scope]) - const calcOffset = useCallback(() => { - const modelVal = current as any - return isRange(modelVal) ? `${((modelVal[0] - min) * 100) / scope}%` : `0%` - }, [current, isRange, min, scope]) + const calcOffset = useMemo(() => { + const modelVal = innerValue as any + return isRange(modelVal) ? `${((modelVal[0] - min) * 100) / scope}%` : '0%' + }, [innerValue, isRange, min, scope]) - const barStyle = useCallback(() => { + const barStyle = useMemo(() => { if (vertical) { return { - height: calcMainAxis(), - top: calcOffset(), - transition: dragStatus ? 'none' : undefined, + height: calcMainAxis, + top: calcOffset, + transition: dragStatusRef.current ? 'none' : undefined, } } const dir = rtl ? 'right' : 'left' return { - width: calcMainAxis(), - [dir]: calcOffset(), - transition: dragStatus ? 'none' : undefined, + width: calcMainAxis, + [dir]: calcOffset, + transition: dragStatusRef.current ? 'none' : undefined, } - }, [calcMainAxis, calcOffset, dragStatus, rtl, vertical]) + }, [calcMainAxis, calcOffset, rtl, vertical]) const marksStyle = useCallback( (mark: any) => { @@ -221,12 +220,12 @@ export const Range: FunctionComponent< const tickClass = useCallback( (mark: any) => { - if (range && Array.isArray(current)) { - return mark <= current[1] && mark >= current[0] + if (range && Array.isArray(innerValue)) { + return mark <= innerValue[1] && mark >= innerValue[0] } - return mark <= current + return mark <= innerValue }, - [current, range] + [innerValue, range] ) const format = useCallback( @@ -244,40 +243,36 @@ export const Range: FunctionComponent< } else { value = format(value) } - if (!isSameValue(value, current)) { - setCurrent(value) + if (!isSameValue(value, innerValue)) { + setInnerValue(value) } end && onEnd && onEnd(value) }, - [current, format, isRange, onEnd, setCurrent] + [innerValue, format, isRange, onEnd, setInnerValue] ) - const click = useCallback( + const handleClick = useCallback( async (event: any) => { - if (disabled || !root.current) { - return - } - setDragStatus('') + if (disabled || !root.current) return + // TODO 鸿蒙获取clientX的数据有误,windowX 也变成了 undefined。暂不支持,待上游支持。 + if (isHm) return + dragStatusRef.current = '' const rect = await getRectInMultiPlatform(root.current) - let x = - typeof event.detail?.x !== 'undefined' ? event.detail.x : event.clientX - if (isHm) x = parseFloat(pxTransform(event.windowX)) + let x = event.detail?.x ?? event.clientX + if (isHm) x = parseFloat(pxTransform(event.windowX || x)) let delta = x - rect.left let total = rect.width if (vertical) { - let y = - typeof event.detail?.y !== 'undefined' - ? event.detail.y - : event.clientY - if (isHm) y = parseFloat(pxTransform(event.windowY)) + let y = event.detail?.y ?? event.clientY + if (isHm) y = parseFloat(pxTransform(event.windowY || y)) delta = y - rect.top total = rect.height } const value = min + (delta / total) * scope - setExactValue(current) - if (isRange(current)) { - const [left, right] = current as any + exactValueRef.current = innerValue + if (isRange(innerValue)) { + const [left, right] = innerValue as any const middle = (left + right) / 2 if (value <= middle) { updateValue([value, right], true) @@ -288,70 +283,75 @@ export const Range: FunctionComponent< updateValue(value, true) } }, - [current, disabled, isRange, min, scope, updateValue, vertical] + [innerValue, disabled, isRange, min, scope, updateValue, vertical] ) + useReady(() => { + const getRootRect = async () => { + if (root.current) { + const rect = await getRectInMultiPlatform(root.current) + rootRect.current = rect + } + } + nextTick(() => { + getRootRect() + }) + }) + const onTouchStart = useCallback( (event: any) => { - if (disabled) { - return - } + if (disabled) return touch.start(event) - setExactValue(current) - if (isRange(current)) { - setStartValue((current as number[]).map(format)) + exactValueRef.current = innerValue + if (isRange(innerValue)) { + setStartValue((innerValue as number[]).map(format)) } else { - setStartValue(format(current as number)) + setStartValue(format(innerValue as number)) } - - setDragStatus('start') + dragStatusRef.current = 'start' }, - [current, disabled, format, isRange, touch] + [innerValue, disabled, format, isRange, touch] ) const onTouchMove = useCallback( async (event: any) => { // @TODO RN、鸿蒙端垂直滑动时,页面会一同滑动,待解决 - if (disabled || !root.current) { - return - } - if (dragStatus === 'start') { + if (disabled || !root.current) return + if (dragStatusRef.current === 'start') { onStart && onStart() + dragStatusRef.current = 'draging' } - touch.move(event) - setDragStatus('draging') - const rect = await getRectInMultiPlatform(root.current) - if (!rect) return - let delta = isHm - ? parseFloat(pxTransform(touch.deltaX.current)) - : touch.deltaX.current - let total = rect.width - let diff = (delta / total) * scope - diff = rtl ? -diff : diff - if (vertical) { - delta = isHm - ? parseFloat(pxTransform(touch.deltaY.current)) - : touch.deltaY.current - total = rect.height - diff = (delta / total) * scope - } - let newValue - if (isRange(startValue)) { - newValue = (exactValue as number[]).slice() - newValue[buttonIndex] = startValue[buttonIndex] + diff - } else { - newValue = startValue + diff + const handleMove = async () => { + if (!rootRect.current) return + let delta = isHm + ? parseFloat(pxTransform(touch.deltaX.current)) + : touch.deltaX.current + let total = rootRect.current.width + let diff = (delta / total) * scope + diff = rtl ? -diff : diff + + if (vertical) { + delta = isHm + ? parseFloat(pxTransform(touch.deltaY.current)) + : touch.deltaY.current + total = rootRect.current.height + diff = (delta / total) * scope + } + + let newValue = startValue + diff + if (isRange(startValue)) { + newValue = (exactValueRef.current as number[]).slice() + newValue[buttonRef.current] = startValue[buttonRef.current] + diff + } + exactValueRef.current = newValue + updateValue(newValue) } - setExactValue(newValue) - updateValue(newValue) + requestAnimationFrame(handleMove) }, [ - buttonIndex, disabled, - dragStatus, - exactValue, isRange, onStart, rtl, @@ -364,82 +364,58 @@ export const Range: FunctionComponent< ) const onTouchEnd = useCallback(() => { - if (disabled) { - return - } - if (dragStatus === 'draging') { - updateValue(current, true) + if (disabled) return + if (dragStatusRef.current === 'draging') { + updateValue(innerValue, true) } - setDragStatus('') - }, [current, disabled, dragStatus, updateValue]) + dragStatusRef.current = '' + }, [innerValue, disabled, updateValue]) const curValue = useCallback( (idx?: number) => { - const modelVal = current as any + const modelVal = innerValue as any const value = typeof idx === 'number' ? modelVal[idx] : modelVal return value }, - [current] + [innerValue] ) - - const buttonTransform = useMemo(() => { - const borderRadis = { borderRadius: pxTransform(13) } - const transform = { - transform: 'translate(-50%, -50%)', - } - - if (isHm) { - return { - ...borderRadis, - ...transform, - } - } - return { - ...transform, - } - }, []) const buttonNumberTransform = useMemo(() => { return vertical ? 'translate(100%, -50%)' : 'translate(-50%, -100%)' }, [vertical]) const renderButton = useCallback( - (index?: number) => { - return ( - - {button || ( - - {currentDescription !== null && ( - - {currentDescription - ? currentDescription(curValue(index)) - : curValue(index)} - - )} - - )} - - ) - }, + (index?: number) => ( + <> + {button || ( + + {currentDescription !== null && ( + + {currentDescription + ? currentDescription(curValue(index)) + : curValue(index)} + + )} + + )} + + ), [ button, buttonNumberTransform, - buttonTransform, curValue, currentDescription, rtl, @@ -459,29 +435,27 @@ export const Range: FunctionComponent< }) return ( - {marksList.map((mark: any) => { - return ( + {marksList.map((mark: any) => ( + + + {Array.isArray(marks) ? marksRef.current[mark] : marks[mark]} + - - {Array.isArray(marks) ? marksRef.current[mark] : marks[mark]} - - - - ) - })} + className={classNames( + `${vertical ? verticalClassPrefix : classPrefix}-tick`, + { + [`${vertical ? verticalClassPrefix : classPrefix}-tick-active`]: + tickClass(mark), + [`${rtlClassPrefix}-tick`]: rtl, + } + )} + /> + + ))} ) }, [ @@ -495,39 +469,22 @@ export const Range: FunctionComponent< vertical, ]) - const wrapperTransform = useMemo(() => { - // @TODO 支持变量 - const wrapperTransformRN = [ - { translateX: pxTransform(vertical ? -12 : -13) }, - { translateY: pxTransform(-12) }, - ] - const wrapperTransform = 'translate(-50%, -50%)' - - return wrapperTransform - }, [vertical]) - const rangeWrapperTransform = useMemo(() => { - return 'translate(-50%, -50%)' - }, []) - const renderRangeButton = useCallback(() => { - return [0, 1].map((item, index) => { - const isLeft = index === 0 - const suffix = isLeft ? 'left' : 'right' + return [0, 1].map((_, index) => { + const suffix = index === 0 ? 'left' : 'right' const cls = classNames(`${classPrefix}-button-wrapper-${suffix}`, { [`${verticalClassPrefix}-button-wrapper-${suffix}`]: vertical, [`${rtlClassPrefix}-button-wrapper-${suffix}`]: rtl, }) - return ( { - setButtonIndex(index) + onTouchStart={(e) => { + buttonRef.current = index onTouchStart(e) }} onTouchMove={onTouchMove} @@ -544,22 +501,20 @@ export const Range: FunctionComponent< onTouchMove, onTouchStart, renderButton, - rangeWrapperTransform, vertical, rtl, rtlClassPrefix, ]) - const renderSingleButton = useCallback(() => { - return ( + const renderSingleButton = useCallback( + () => ( {renderButton()} - ) - }, [ - onTouchEnd, - onTouchMove, - onTouchStart, - renderButton, - wrapperTransform, - vertical, - ]) + ), + [onTouchEnd, onTouchMove, onTouchStart, renderButton, vertical] + ) const renderButtonWrapper = useCallback(() => { - if (range) { - return renderRangeButton() - } - - return renderSingleButton() - }, [renderRangeButton, renderSingleButton, range]) + return ( + + {range ? renderRangeButton() : renderSingleButton()} + + ) + }, [renderRangeButton, renderSingleButton, range, barStyle]) return ( {minDescription !== null && ( {minDescription || min} )} - click(e)}> + {renderMarks()} - - - {renderButtonWrapper()} - + {renderButtonWrapper()} {maxDescription !== null && ( {maxDescription || max} diff --git a/src/packages/range/range.tsx b/src/packages/range/range.tsx index 2efdf0b904..a6a623a142 100644 --- a/src/packages/range/range.tsx +++ b/src/packages/range/range.tsx @@ -248,11 +248,9 @@ export const Range: FunctionComponent< [current, format, isRange, onEnd, setCurrent] ) - const click = useCallback( + const handleClick = useCallback( (event: any) => { - if (disabled || !root.current) { - return - } + if (disabled || !root.current) return setDragStatus('') const rect = getRect(root.current) let delta = event.clientX - rect.left @@ -280,9 +278,7 @@ export const Range: FunctionComponent< const onTouchStart = useCallback( (event: any) => { - if (disabled) { - return - } + if (disabled) return touch.start(event) setExactValue(current) if (isRange(current)) { @@ -534,10 +530,13 @@ export const Range: FunctionComponent< {minDescription !== null && (
{minDescription || min}
)} -
click(e)}> +
{renderMarks()} -
+
{renderButtonWrapper()}
diff --git a/src/packages/range/utils.tsx b/src/packages/range/utils.tsx new file mode 100644 index 0000000000..1a86145f62 --- /dev/null +++ b/src/packages/range/utils.tsx @@ -0,0 +1,7 @@ +import { RangeValue } from '@/types' + +export const isSameValue = (newValue: RangeValue, oldValue: RangeValue) => + JSON.stringify(newValue) === JSON.stringify(oldValue) + +export const handleOverlap = (value: number[]) => + value[0] > value[1] ? value.slice(0).reverse() : value