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