Files
web/packages/design-system/lib/components/Input/utils.ts
Rasmus Langvad d0546926a9 Merged in fix/3697-prettier-configs (pull request #3396)
fix(SW-3691): Setup one prettier config for whole repo

* Setup prettierrc in root and remove other configs


Approved-by: Joakim Jäderberg
Approved-by: Linus Flood
2026-01-07 12:45:50 +00:00

121 lines
3.5 KiB
TypeScript

import type { ChangeEvent, ChangeEventHandler, RefObject } from "react"
import { useState, useEffect } from "react"
interface ClearInputOptions {
inputRef: RefObject<HTMLInputElement | null>
onChange?: ChangeEventHandler<HTMLInputElement>
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<HTMLInputElement>
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<HTMLInputElement | null>
): 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
}