UNPKG

@nafr/echo-ui

Version:

A UI library born for WAA

151 lines (150 loc) 6.34 kB
import { jsx as _jsx } from "react/jsx-runtime"; import { forwardRef, useEffect, useState, useRef, useCallback, useMemo } from 'react'; import { scaleLinear } from 'd3'; import { cn, halfRange, validValue } from '../../../lib/utils'; import { useCommandAltClick } from '../../../lib/hooks'; import { useStyle } from './styles'; import { MAX, MIN, STEP, SENSITIVITY, DRAGGING_OFFSET, PROGRESS_COLOR, TYPE, SIZE, RADIUS, } from './contants'; export const Input = forwardRef((props, ref) => { const { value: _value = MIN, type = TYPE, size = SIZE, radius = RADIUS, min = MIN, max = MAX, step = STEP, disabled = false, bilateral = false, prohibitDrag = false, sensitivity = SENSITIVITY, hideProgress = false, progressColor: _progressColor = PROGRESS_COLOR, onChange, onBlur, onMouseDown, ...restProps } = props; const [value, setValue] = useState(_value); const [isDragging, setIsDragging] = useState(false); const [nativeEvent, setNativeEvent] = useState(null); const [direction, setDirection] = useState('positive'); const startYRef = useRef(0); const deltaYRef = useRef(0); const isDraggingRef = useRef(false); const scale = scaleLinear().domain([min, max]).range([0, 100]); const radio = scale(value); // ============== Events ============== // useEffect(() => { if (disabled) return; const validatedValue = validValue(_value, min, max); setValue(validatedValue); }, [_value, min, max, disabled]); const handleResetClick = useCommandAltClick(() => { if (type !== 'number') return; if (bilateral) setValue(halfRange(min, max)); else setValue(min); onChange?.({ value: bilateral ? halfRange(min, max) : min, nativeEvent: nativeEvent, }); }); const handleInputChange = useCallback((e) => { let rawValue = e.target.value; if (type === 'number') rawValue = handleNumberValue(rawValue); setValue(rawValue); setNativeEvent(e); onChange?.({ value: rawValue, nativeEvent: e }); }, [type]); const handleNumberValue = (rawValue) => { if (rawValue === '-' || rawValue === '.' || rawValue === '') return rawValue; if (isNaN(rawValue)) return; const numericValue = validValue(Number(rawValue), min, max); return numericValue; }; const handleInputBlur = (e) => { if (type !== 'number') return; if (value === '') setValue(min); onBlur?.(e); }; useEffect(() => { if (disabled) return; if (type === 'number') setValue(handleNumberValue(_value)); else setValue(_value); }, [_value]); // ============== Dragging ============== // const setDragging = (draggingFlag) => { isDraggingRef.current = draggingFlag; setIsDragging(draggingFlag); }; const handleDragUpdateValue = useCallback((currentY) => { const deltaY = -(currentY - startYRef.current); const deltaValue = deltaY * (sensitivity / 10) * step; let newValue = value + deltaValue; newValue = parseFloat((Math.round(newValue / step) * step).toFixed(10)); newValue = Math.max(min, Math.min(newValue, max)); setValue(newValue); onChange?.({ value: newValue, nativeEvent: nativeEvent }); }, [value, min, max, step]); const startDragging = (e) => { onMouseDown?.(e); if (type !== 'number' || prohibitDrag || disabled) return; startYRef.current = e.clientY; document.addEventListener('mousemove', onDragging); document.addEventListener('mouseup', stopDragging); handleResetClick(e); }; const onDragging = (e) => { const currentY = e.clientY; // Only start dragging if the mouse has moved more than 20px if (!isDraggingRef.current && Math.abs(currentY - startYRef.current) > DRAGGING_OFFSET) { setDragging(true); deltaYRef.current = currentY > startYRef.current ? -DRAGGING_OFFSET : DRAGGING_OFFSET; } if (isDraggingRef.current) { requestAnimationFrame(() => handleDragUpdateValue(currentY + deltaYRef.current)); } }; const stopDragging = (e) => { setDragging(false); document.removeEventListener('mousemove', onDragging); document.removeEventListener('mouseup', stopDragging); handleResetClick(e); }; // ============== Styles ============== // useEffect(() => { if (isDragging) document.getElementsByTagName('html')[0].style.cursor = 'ns-resize'; else document.getElementsByTagName('html')[0].style.cursor = ''; }, [isDragging]); const progressColor = useMemo(() => { if (hideProgress) return 'transparent'; if (disabled) return 'var(--echo-muted)'; return _progressColor; }, [hideProgress, disabled, _progressColor, disabled]); const backgroundImage = useMemo(() => { if (type !== 'number') return ''; if (!bilateral) { return `linear-gradient(to right, ${progressColor} ${radio}%, transparent ${radio}%)`; } if (value >= halfRange(min, max)) { setDirection('positive'); return `linear-gradient(to right, transparent 50%, ${progressColor} 50%, ${progressColor} ${radio}%, transparent ${100 - radio}%)`; } else { setDirection('negative'); return `linear-gradient(to left, transparent 50%, ${progressColor} 50%, ${progressColor} ${100 - radio}%, transparent 0%)`; } }, [progressColor, radio]); return (_jsx("input", { ...restProps, ref: ref, "data-dragging": isDragging, "data-bilateral": direction, type: type, value: value, disabled: disabled, readOnly: isDragging || restProps.readOnly, className: cn(useStyle({ size, radius, isDragging, bilateral }), restProps.className), style: { ...restProps.style, backgroundImage, }, onChange: handleInputChange, onMouseDown: startDragging, onBlur: handleInputBlur })); });