import { Box, Slider, TextField } from '@mui/material' import { useState, useMemo, type FocusEvent, type KeyboardEvent } from 'react' import { widgetStoreActions } from '../../stores/widget-store' import { useWidgetSelector } from '../../stores/use-widget-selector' import type { RangeItemProps, RangeWidgetState } from '../types' import { styles } from '../style' import { defaultFormatter } from '../../utils/formatter' type EditingState = '' | 'min' | 'max' /** * Renders a single range slider with editable min/max text inputs, reading its configuration from the widget store. */ export function RangeItem({ id, index }: RangeItemProps) { const { item, onChange, formatter: _formatter, } = useWidgetSelector(id, (w) => { const rw = w as RangeWidgetState | undefined return { item: rw?.data[index], onChange: rw?.onChange, formatter: rw?.formatter, } }) const formatter = _formatter ?? defaultFormatter const currentValue = useMemo( () => (item ? (item.value ?? [item.min, item.max]) : [0, 0]), [item], ) // Local state for input values - track if user is editing const [isEditing, setIsEditing] = useState('') if (!item) return null const handleSliderChange = (_: Event, newValue: number | number[]) => { if (Array.isArray(newValue)) { const [min, max] = newValue const data = widgetStoreActions.getWidget(id)?.data ?? [] data[index] = { ...item, value: newValue, } widgetStoreActions.setWidget(id, { data, }) onChange?.([min!, max!], index) } } const handleInputFocus = (e: FocusEvent) => { setIsEditing(e.target.name as EditingState) } const handleInputChange = (event: React.ChangeEvent) => { const { name, value } = event.target let newValue: [number, number] if (name === 'min') { newValue = [ Math.min( Math.max(item.min, parseFloat(value) || item.min), currentValue[1]!, ), currentValue[1] ?? 0, ] } else { newValue = [ currentValue[0] ?? 0, Math.max( Math.min(item.max, parseFloat(value) || item.max), currentValue[0]!, ), ] } const data = widgetStoreActions.getWidget(id)?.data ?? [] widgetStoreActions.setWidget(id, { data: data.map((d: RangeWidgetState['data'][number], i: number) => i === index ? { ...d, value: newValue, } : d, ), }) onChange?.([newValue[0], newValue[1]], index) } const handleInputBlur = (e: FocusEvent) => { setIsEditing('') handleInputChange(e) } const handleInputKeyDown = (event: KeyboardEvent) => { if (event.key === 'Enter') { handleInputBlur(event as unknown as FocusEvent) event.currentTarget.blur() } } return ( ) } function Input({ isEditing, name, disabled, value, formatter, onFocus: onFocus, onBlur: onBlur, onKeyDown: onKeyDown, inputProps, }: { isEditing: EditingState name: 'min' | 'max' disabled?: boolean value: number formatter: (value: number) => string onFocus: (e: FocusEvent) => void onBlur: (e: FocusEvent) => void onKeyDown: (e: KeyboardEvent) => void inputProps?: React.InputHTMLAttributes }) { const [currentValue, setCurrentValue] = useState(String(value)) // Compute display values when not editing const displayMinValue = useMemo( () => formatter(Number(currentValue)), [currentValue, formatter], ) return ( { setCurrentValue(e.target.value) }} onFocus={onFocus} onBlur={onBlur} onKeyDown={onKeyDown} disabled={disabled} size='small' sx={styles.input} inputProps={inputProps} /> ) }