import type { ChangeEvent, ChangeEventHandler, RefObject } from 'react' import { useState, useEffect } from 'react' interface ClearInputOptions { inputRef: RefObject onChange?: ChangeEventHandler value?: string | number | readonly string[] onRightIconClick?: () => void } /** * Clears an input field value, handling both controlled and uncontrolled components. * Works with React Aria Components Input which can be used standalone or within TextField. */ export function clearInput({ inputRef, onChange, value, }: ClearInputOptions): void { if (!inputRef.current) { return } const isControlled = value !== undefined if (isControlled && onChange) { // For controlled components: call onChange with a synthetic event const syntheticEvent = { target: { value: '' }, currentTarget: inputRef.current, } as ChangeEvent onChange(syntheticEvent) } else { // For uncontrolled components: use native input value setter // This triggers React's change detection properly const nativeInputValueSetter = Object.getOwnPropertyDescriptor( window.HTMLInputElement.prototype, 'value' )?.set if (nativeInputValueSetter) { nativeInputValueSetter.call(inputRef.current, '') } else { inputRef.current.value = '' } // Dispatch input event to trigger any listeners const inputEvent = new Event('input', { bubbles: true, cancelable: true, }) inputRef.current.dispatchEvent(inputEvent) // Also dispatch change event const changeEvent = new Event('change', { bubbles: true, cancelable: true, }) inputRef.current.dispatchEvent(changeEvent) } // Focus the input after clearing inputRef.current.focus() } /** * Hook to track whether an input has a value. * Works for both controlled and uncontrolled components. */ export function useInputHasValue( value: string | number | readonly string[] | undefined, inputRef: RefObject ): boolean { const [hasValue, setHasValue] = useState(() => { // For controlled components, check value prop if (value !== undefined) { return String(value ?? '').trim().length > 0 } // For uncontrolled, check ref return String(inputRef.current?.value ?? '').trim().length > 0 }) // Sync with controlled value changes useEffect(() => { if (value !== undefined) { setHasValue(String(value ?? '').trim().length > 0) } }, [value]) // For uncontrolled components, sync from ref on input events useEffect(() => { if (value !== undefined) { return // Controlled component, no need to listen } const input = inputRef.current if (!input) return const updateHasValue = (event?: Event) => { // Use setTimeout to ensure we read the value after React/React Aria Components // has fully processed the change, especially for operations like select-all + delete setTimeout(() => { const target = (event?.target as HTMLInputElement) || inputRef.current if (target) { setHasValue((target.value ?? '').trim().length > 0) } }, 0) } input.addEventListener('input', updateHasValue) // Also listen to change event as a fallback input.addEventListener('change', updateHasValue) return () => { input.removeEventListener('input', updateHasValue) input.removeEventListener('change', updateHasValue) } }, [value, inputRef]) return hasValue }