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 }