anvil/striker-ui/components/InputWithRef.tsx

105 lines
2.8 KiB
TypeScript
Raw Normal View History

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