parent
f941496c98
commit
782bbd2bfa
3 changed files with 150 additions and 0 deletions
@ -0,0 +1,106 @@ |
||||
import { |
||||
ForwardedRef, |
||||
ReactElement, |
||||
cloneElement, |
||||
forwardRef, |
||||
useCallback, |
||||
useEffect, |
||||
useImperativeHandle, |
||||
useMemo, |
||||
useState, |
||||
} from 'react'; |
||||
|
||||
import INPUT_TYPES from '../lib/consts/INPUT_TYPES'; |
||||
import MAP_TO_VALUE_CONVERTER from '../lib/consts/MAP_TO_VALUE_CONVERTER'; |
||||
|
||||
const UncontrolledInput = forwardRef( |
||||
<ValueType extends keyof MapToInputType, InputElement extends ReactElement>( |
||||
props: UncontrolledInputProps<InputElement>, |
||||
ref: ForwardedRef<UncontrolledInputForwardedRefContent<ValueType>>, |
||||
) => { |
||||
const { |
||||
input, |
||||
onChange = ({ handlers: { base, origin } }, ...args) => { |
||||
base?.call(null, ...args); |
||||
origin?.call(null, ...args); |
||||
}, |
||||
onMount, |
||||
onUnmount, |
||||
} = props; |
||||
const { props: inputProps } = input; |
||||
|
||||
const { valueKey, valueType } = useMemo(() => { |
||||
const { type } = inputProps; |
||||
|
||||
let vkey: 'checked' | 'value' = 'value'; |
||||
let vtype: keyof MapToInputType = 'string'; |
||||
|
||||
if (type === INPUT_TYPES.checkbox) { |
||||
vkey = 'checked'; |
||||
vtype = 'boolean'; |
||||
} |
||||
|
||||
return { |
||||
valueKey: vkey, |
||||
valueType: vtype, |
||||
}; |
||||
}, [inputProps]); |
||||
|
||||
const { |
||||
onChange: inputOnChange, |
||||
[valueKey]: inputValue, |
||||
...restInputProps |
||||
} = inputProps; |
||||
|
||||
const [value, setValue] = useState<MapToInputType[ValueType]>(inputValue); |
||||
|
||||
const baseChangeEventHandler = useCallback<ReactChangeEventHandler>( |
||||
({ target: { [valueKey]: changed } }) => { |
||||
const converted = MAP_TO_VALUE_CONVERTER[valueType]( |
||||
changed, |
||||
) as MapToInputType[ValueType]; |
||||
|
||||
setValue(converted); |
||||
}, |
||||
[valueKey, valueType], |
||||
); |
||||
|
||||
const changeEventHandler = useCallback<ReactChangeEventHandler>( |
||||
(...args) => |
||||
onChange?.call( |
||||
null, |
||||
{ handlers: { base: baseChangeEventHandler, origin: inputOnChange } }, |
||||
...args, |
||||
), |
||||
[baseChangeEventHandler, inputOnChange, onChange], |
||||
); |
||||
|
||||
// Handle mount/unmount events; these only happen once hence no deps
|
||||
useEffect(() => { |
||||
onMount?.call(null); |
||||
|
||||
return onUnmount; |
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []); |
||||
|
||||
useImperativeHandle( |
||||
ref, |
||||
() => ({ |
||||
get: () => value, |
||||
set: setValue, |
||||
}), |
||||
[value], |
||||
); |
||||
|
||||
return cloneElement(input, { |
||||
...restInputProps, |
||||
onChange: changeEventHandler, |
||||
[valueKey]: value, |
||||
}); |
||||
}, |
||||
); |
||||
|
||||
UncontrolledInput.displayName = 'UncontrolledInput'; |
||||
|
||||
export default UncontrolledInput; |
@ -0,0 +1,43 @@ |
||||
type MuiInputBaseProps = import('@mui/material').InputBaseProps; |
||||
|
||||
type ReactChangeEventHandler = |
||||
import('react').ChangeEventHandler<HTMLInputElement>; |
||||
|
||||
type MuiInputBasePropsBlurEventHandler = Exclude< |
||||
MuiInputBaseProps['onBlur'], |
||||
undefined |
||||
>; |
||||
|
||||
type MuiInputBasePropsFocusEventHandler = Exclude< |
||||
MuiInputBaseProps['onFocus'], |
||||
undefined |
||||
>; |
||||
|
||||
type UncontrolledInputEventHandler<HandlerType> = ( |
||||
toolbox: { handlers: { base?: HandlerType; origin?: HandlerType } }, |
||||
...rest: Parameters<HandlerType> |
||||
) => ReturnType<HandlerType>; |
||||
|
||||
type UncontrolledInputComponentMountEventHandler = () => void; |
||||
|
||||
type UncontrolledInputComponentUnmountEventHandler = () => void; |
||||
|
||||
type UncontrolledInputOptionalProps = { |
||||
onBlur?: UncontrolledInputEventHandler<MuiInputBasePropsBlurEventHandler>; |
||||
onChange?: UncontrolledInputEventHandler<ReactChangeEventHandler>; |
||||
onFocus?: UncontrolledInputEventHandler<MuiInputBasePropsFocusEventHandler>; |
||||
onMount?: UncontrolledInputComponentMountEventHandler; |
||||
onUnmount?: UncontrolledInputComponentUnmountEventHandler; |
||||
}; |
||||
|
||||
type UncontrolledInputProps<InputElement extends import('react').ReactElement> = |
||||
UncontrolledInputOptionalProps & { |
||||
input: InputElement; |
||||
}; |
||||
|
||||
type UncontrolledInputForwardedRefContent< |
||||
ValueType extends keyof MapToInputType, |
||||
> = { |
||||
get: () => MapToInputType[ValueType]; |
||||
set: (value: MapToInputType[ValueType]) => void; |
||||
}; |
Loading…
Reference in new issue