Merged in feat/use-new-input-component (pull request #3324)
feat(SW-3659): Use new input component * Use new input component * Update error formatter * Merged master into feat/use-new-input-component * Merged master into feat/use-new-input-component * Merge branch 'master' into feat/use-new-input-component * Merged master into feat/use-new-input-component * Update Input stories * Merge branch 'feat/use-new-input-component' of bitbucket.org:scandic-swap/web into feat/use-new-input-component * Update Storybook logo * Add some new demo icon input story * Fix the clear content button position * Fix broken password input icon * Merged master into feat/use-new-input-component * Merged master into feat/use-new-input-component * Add aria-hidden to required asterisk * Merge branch 'feat/use-new-input-component' of bitbucket.org:scandic-swap/web into feat/use-new-input-component * Merge branch 'master' into feat/use-new-input-component Approved-by: Bianca Widstam Approved-by: Matilda Landström
This commit is contained in:
120
packages/design-system/lib/components/Input/utils.ts
Normal file
120
packages/design-system/lib/components/Input/utils.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user