import { CSSProperties, ReactNode, useEffect, useRef, useState } from 'react' import classNames from 'classnames' import { CommonComponentProps } from '../../utils/types' import { minmax } from '../../utils' import { PRESS_DOWN, PRESS_UP } from '../../strike' import { Icon } from '../icon/Icon' import { Input } from '../input/Input' import { useEvent, useStrike } from '../../use' import './Stepper.scss' export interface InputNumberProps extends CommonComponentProps { className?: string style?: CSSProperties children?: ReactNode inputWidth?: number | string value?: number defaultValue?: number min?: number max?: number step?: number precision?: number placeholder?: string disabled?: boolean readOnly?: boolean press?: boolean interval?: number onChange?: (value: number) => void onBlur?: () => void onFocus?: () => void } export function Stepper(props: InputNumberProps) { const { className, children, inputWidth, value, defaultValue, min = -Infinity, max = Infinity, step = 1, precision, placeholder, disabled, readOnly, press = true, interval = 150, onChange, onBlur, onFocus, ...restProps } = props const [innerValue, setInnerValue] = useState(value ?? defaultValue) const [inputValue, setInputValue] = useState( innerValue || '' ) // 受控 useEffect(() => { if (value != null) { setInnerValue(value) setInputValue(value) } }, [value]) const setValue = (val?: number) => { // 非受控 if (value == null) { setInnerValue(val) setInputValue(val == undefined ? '' : val) } if (innerValue !== val) { onChange?.(val as number) } } const adjustValue = useEvent((value: number) => { value = minmax(value, min, max) if (precision != null) { value = ~~(Math.pow(10, precision) * value) / Math.pow(10, precision) } return value }) const setByStep = useEvent((delta: number) => { setValue(adjustValue((innerValue || 0) + step * delta)) }) const handleDecrease = () => { !disabled && !readOnly && setByStep(-1) } const handleIncrease = () => { !disabled && !readOnly && setByStep(1) } const decreaseTimer = useRef(0) const increaseTimer = useRef(0) useEffect( () => () => { clearInterval(decreaseTimer.current) clearInterval(increaseTimer.current) }, [] ) const handlePressDown = useEvent((delta: number) => { setByStep(delta) const timer = setInterval(() => { setByStep(delta) }, interval) as unknown as number if (delta > 0) { increaseTimer.current = timer } else { decreaseTimer.current = timer } }) const handlePressUp = useEvent((delta: number) => { clearInterval(delta > 0 ? increaseTimer.current : decreaseTimer.current) }) const decreaseBinding = useStrike( (strike) => { strike.on(PRESS_DOWN, () => handlePressDown(-1)) strike.on(PRESS_UP, () => handlePressUp(-1)) }, { press: true, }, press && !disabled && !readOnly ) const increaseBinding = useStrike( (strike) => { strike.on(PRESS_DOWN, () => handlePressDown(1)) strike.on(PRESS_UP, () => handlePressUp(1)) }, { press: true, }, press && !disabled && !readOnly ) const handleChange = (value: string) => { setInputValue(value) } const handleBlur = () => { setValue( inputValue === '' ? undefined : adjustValue(Number(inputValue) || 0) ) } const StepperClass = classNames( 's-stepper', { 's-stepper-disabled': disabled, 's-stepper-readonly': readOnly, }, className ) const decreaseButtonClass = classNames( 's-stepper-button s-stepper-decrease', { 's-stepper-button-disabled': innerValue != null && innerValue <= min, } ) const increaseButtonClass = classNames( 's-stepper-button s-stepper-increase', { 's-stepper-button-disabled': innerValue != null && innerValue >= max, } ) return (
) } export default Stepper