|
|
|
import {
|
|
|
|
cloneElement,
|
|
|
|
ForwardedRef,
|
|
|
|
forwardRef,
|
|
|
|
ReactElement,
|
|
|
|
useImperativeHandle,
|
|
|
|
useState,
|
|
|
|
} from 'react';
|
|
|
|
|
|
|
|
import createInputOnChangeHandler, {
|
|
|
|
CreateInputOnChangeHandlerOptions,
|
|
|
|
MapToStateSetter,
|
|
|
|
} from '../lib/createInputOnChangeHandler';
|
|
|
|
|
|
|
|
type InputWithRefOptionalProps<TypeName extends keyof MapToType> = {
|
|
|
|
createInputOnChangeHandlerOptions?: Omit<
|
|
|
|
CreateInputOnChangeHandlerOptions<TypeName>,
|
|
|
|
'set'
|
|
|
|
>;
|
|
|
|
valueType?: TypeName | 'string';
|
|
|
|
};
|
|
|
|
|
|
|
|
type InputWithRefProps<
|
|
|
|
TypeName extends keyof MapToType,
|
|
|
|
InputComponent extends ReactElement,
|
|
|
|
> = InputWithRefOptionalProps<TypeName> & {
|
|
|
|
input: InputComponent;
|
|
|
|
};
|
|
|
|
|
|
|
|
type InputForwardedRefContent<TypeName extends keyof MapToType> = {
|
|
|
|
getIsChangedByUser?: () => boolean;
|
|
|
|
getValue?: () => MapToType[TypeName];
|
|
|
|
setValue?: MapToStateSetter[TypeName];
|
|
|
|
};
|
|
|
|
|
|
|
|
const MAP_TO_INITIAL_VALUE: MapToType = {
|
|
|
|
number: 0,
|
|
|
|
string: '',
|
|
|
|
undefined,
|
|
|
|
};
|
|
|
|
|
|
|
|
const INPUT_WITH_REF_DEFAULT_PROPS: Required<
|
|
|
|
InputWithRefOptionalProps<'string'>
|
|
|
|
> = {
|
|
|
|
createInputOnChangeHandlerOptions: {},
|
|
|
|
valueType: 'string',
|
|
|
|
};
|
|
|
|
|
|
|
|
const InputWithRef = forwardRef(
|
|
|
|
<TypeName extends keyof MapToType, InputComponent extends ReactElement>(
|
|
|
|
{
|
|
|
|
createInputOnChangeHandlerOptions: {
|
|
|
|
postSet: postSetAppend,
|
|
|
|
...restCreateInputOnChangeHandlerOptions
|
|
|
|
} = INPUT_WITH_REF_DEFAULT_PROPS.createInputOnChangeHandlerOptions,
|
|
|
|
input,
|
|
|
|
valueType = INPUT_WITH_REF_DEFAULT_PROPS.valueType,
|
|
|
|
}: InputWithRefProps<TypeName, InputComponent>,
|
|
|
|
ref: ForwardedRef<InputForwardedRefContent<TypeName>>,
|
|
|
|
) => {
|
|
|
|
const {
|
|
|
|
props: { onChange: initOnChange, value: initValue, ...restInitProps },
|
|
|
|
} = input;
|
|
|
|
|
|
|
|
const [value, setValue] = useState<MapToType[TypeName]>(
|
|
|
|
initValue ?? MAP_TO_INITIAL_VALUE[valueType],
|
|
|
|
) as [MapToType[TypeName], MapToStateSetter[TypeName]];
|
|
|
|
const [isChangedByUser, setIsChangedByUser] = useState<boolean>(false);
|
|
|
|
|
|
|
|
const onChange = createInputOnChangeHandler<TypeName>({
|
|
|
|
postSet: (...args) => {
|
|
|
|
setIsChangedByUser(true);
|
|
|
|
initOnChange?.call(null, ...args);
|
|
|
|
postSetAppend?.call(null, ...args);
|
|
|
|
},
|
|
|
|
set: setValue,
|
|
|
|
setType: valueType,
|
|
|
|
...restCreateInputOnChangeHandlerOptions,
|
|
|
|
});
|
|
|
|
|
|
|
|
useImperativeHandle(
|
|
|
|
ref,
|
|
|
|
() => ({
|
|
|
|
getIsChangedByUser: () => isChangedByUser,
|
|
|
|
getValue: () => value,
|
|
|
|
setValue,
|
|
|
|
}),
|
|
|
|
[isChangedByUser, value],
|
|
|
|
);
|
|
|
|
|
|
|
|
return cloneElement(input, { ...restInitProps, onChange, value });
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
InputWithRef.defaultProps = INPUT_WITH_REF_DEFAULT_PROPS;
|
|
|
|
InputWithRef.displayName = 'InputWithRef';
|
|
|
|
|
|
|
|
export type { InputForwardedRefContent, InputWithRefProps };
|
|
|
|
|
|
|
|
export default InputWithRef;
|