|
|
|
@ -1,9 +1,12 @@ |
|
|
|
|
import { InputBaseProps } from '@mui/material'; |
|
|
|
|
import { |
|
|
|
|
cloneElement, |
|
|
|
|
ForwardedRef, |
|
|
|
|
forwardRef, |
|
|
|
|
ReactElement, |
|
|
|
|
useEffect, |
|
|
|
|
useImperativeHandle, |
|
|
|
|
useMemo, |
|
|
|
|
useState, |
|
|
|
|
} from 'react'; |
|
|
|
|
|
|
|
|
@ -11,16 +14,29 @@ import createInputOnChangeHandler, { |
|
|
|
|
CreateInputOnChangeHandlerOptions, |
|
|
|
|
MapToStateSetter, |
|
|
|
|
} from '../lib/createInputOnChangeHandler'; |
|
|
|
|
import { createTestInputFunction } from '../lib/test_input'; |
|
|
|
|
import useIsFirstRender from '../hooks/useIsFirstRender'; |
|
|
|
|
|
|
|
|
|
type InputWithRefTypeMap = Pick<MapToType, 'number' | 'string'>; |
|
|
|
|
|
|
|
|
|
type InputWithRefOptionalProps<TypeName extends keyof InputWithRefTypeMap> = { |
|
|
|
|
type InputWithRefOptionalPropsWithDefault< |
|
|
|
|
TypeName extends keyof InputWithRefTypeMap, |
|
|
|
|
> = { |
|
|
|
|
createInputOnChangeHandlerOptions?: Omit< |
|
|
|
|
CreateInputOnChangeHandlerOptions<TypeName>, |
|
|
|
|
'set' |
|
|
|
|
>; |
|
|
|
|
required?: boolean; |
|
|
|
|
valueType?: TypeName | 'string'; |
|
|
|
|
}; |
|
|
|
|
type InputWithRefOptionalPropsWithoutDefault = { |
|
|
|
|
inputTestBatch?: InputTestBatch; |
|
|
|
|
onFirstRender?: (args: { isRequired: boolean }) => void; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
type InputWithRefOptionalProps<TypeName extends keyof InputWithRefTypeMap> = |
|
|
|
|
InputWithRefOptionalPropsWithDefault<TypeName> & |
|
|
|
|
InputWithRefOptionalPropsWithoutDefault; |
|
|
|
|
|
|
|
|
|
type InputWithRefProps< |
|
|
|
|
TypeName extends keyof InputWithRefTypeMap, |
|
|
|
@ -32,18 +48,22 @@ type InputWithRefProps< |
|
|
|
|
type InputForwardedRefContent<TypeName extends keyof InputWithRefTypeMap> = { |
|
|
|
|
getIsChangedByUser?: () => boolean; |
|
|
|
|
getValue?: () => InputWithRefTypeMap[TypeName]; |
|
|
|
|
isValid?: () => boolean; |
|
|
|
|
setValue?: MapToStateSetter[TypeName]; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const INPUT_TEST_ID = 'input'; |
|
|
|
|
const MAP_TO_INITIAL_VALUE: InputWithRefTypeMap = { |
|
|
|
|
number: 0, |
|
|
|
|
string: '', |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const INPUT_WITH_REF_DEFAULT_PROPS: Required< |
|
|
|
|
InputWithRefOptionalProps<'string'> |
|
|
|
|
> = { |
|
|
|
|
InputWithRefOptionalPropsWithDefault<'string'> |
|
|
|
|
> & |
|
|
|
|
InputWithRefOptionalPropsWithoutDefault = { |
|
|
|
|
createInputOnChangeHandlerOptions: {}, |
|
|
|
|
required: false, |
|
|
|
|
valueType: 'string', |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
@ -58,18 +78,67 @@ const InputWithRef = forwardRef( |
|
|
|
|
...restCreateInputOnChangeHandlerOptions |
|
|
|
|
} = INPUT_WITH_REF_DEFAULT_PROPS.createInputOnChangeHandlerOptions, |
|
|
|
|
input, |
|
|
|
|
inputTestBatch, |
|
|
|
|
onFirstRender, |
|
|
|
|
required: isRequired = INPUT_WITH_REF_DEFAULT_PROPS.required, |
|
|
|
|
valueType = INPUT_WITH_REF_DEFAULT_PROPS.valueType, |
|
|
|
|
}: InputWithRefProps<TypeName, InputComponent>, |
|
|
|
|
ref: ForwardedRef<InputForwardedRefContent<TypeName>>, |
|
|
|
|
) => { |
|
|
|
|
const { |
|
|
|
|
props: { onChange: initOnChange, value: initValue, ...restInitProps }, |
|
|
|
|
props: { |
|
|
|
|
onBlur: initOnBlur, |
|
|
|
|
onChange: initOnChange, |
|
|
|
|
onFocus: initOnFocus, |
|
|
|
|
value: initValue = MAP_TO_INITIAL_VALUE[valueType], |
|
|
|
|
...restInitProps |
|
|
|
|
}, |
|
|
|
|
} = input; |
|
|
|
|
|
|
|
|
|
const [value, setValue] = useState<InputWithRefTypeMap[TypeName]>( |
|
|
|
|
initValue ?? MAP_TO_INITIAL_VALUE[valueType], |
|
|
|
|
const isFirstRender = useIsFirstRender(); |
|
|
|
|
|
|
|
|
|
const [inputValue, setInputValue] = useState<InputWithRefTypeMap[TypeName]>( |
|
|
|
|
initValue, |
|
|
|
|
) as [InputWithRefTypeMap[TypeName], MapToStateSetter[TypeName]]; |
|
|
|
|
const [isChangedByUser, setIsChangedByUser] = useState<boolean>(false); |
|
|
|
|
const [isInputValid, setIsInputValid] = useState<boolean>(false); |
|
|
|
|
|
|
|
|
|
const testInput: TestInputFunction | undefined = useMemo(() => { |
|
|
|
|
let result; |
|
|
|
|
|
|
|
|
|
if (inputTestBatch) { |
|
|
|
|
inputTestBatch.isRequired = isRequired; |
|
|
|
|
|
|
|
|
|
result = createTestInputFunction({ |
|
|
|
|
[INPUT_TEST_ID]: inputTestBatch, |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
|
}, [inputTestBatch, isRequired]); |
|
|
|
|
|
|
|
|
|
const onBlur = useMemo<InputBaseProps['onBlur']>( |
|
|
|
|
() => |
|
|
|
|
initOnBlur ?? |
|
|
|
|
(testInput && |
|
|
|
|
(({ target: { value } }) => { |
|
|
|
|
const isValid = testInput({ |
|
|
|
|
inputs: { [INPUT_TEST_ID]: { value } }, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
setIsInputValid(isValid); |
|
|
|
|
})), |
|
|
|
|
[initOnBlur, testInput], |
|
|
|
|
); |
|
|
|
|
const onFocus = useMemo<InputBaseProps['onFocus']>( |
|
|
|
|
() => |
|
|
|
|
initOnFocus ?? |
|
|
|
|
(inputTestBatch && |
|
|
|
|
(() => { |
|
|
|
|
inputTestBatch.defaults?.onSuccess?.call(null, { append: {} }); |
|
|
|
|
})), |
|
|
|
|
[initOnFocus, inputTestBatch], |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
const onChange = createInputOnChangeHandler<TypeName>({ |
|
|
|
|
postSet: (...args) => { |
|
|
|
@ -77,22 +146,36 @@ const InputWithRef = forwardRef( |
|
|
|
|
initOnChange?.call(null, ...args); |
|
|
|
|
postSetAppend?.call(null, ...args); |
|
|
|
|
}, |
|
|
|
|
set: setValue, |
|
|
|
|
set: setInputValue, |
|
|
|
|
setType: valueType, |
|
|
|
|
...restCreateInputOnChangeHandlerOptions, |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
if (isFirstRender) { |
|
|
|
|
onFirstRender?.call(null, { isRequired }); |
|
|
|
|
} |
|
|
|
|
}, [isFirstRender, isRequired, onFirstRender]); |
|
|
|
|
|
|
|
|
|
useImperativeHandle( |
|
|
|
|
ref, |
|
|
|
|
() => ({ |
|
|
|
|
getIsChangedByUser: () => isChangedByUser, |
|
|
|
|
getValue: () => value, |
|
|
|
|
setValue, |
|
|
|
|
getValue: () => inputValue, |
|
|
|
|
isValid: () => isInputValid, |
|
|
|
|
setValue: setInputValue, |
|
|
|
|
}), |
|
|
|
|
[isChangedByUser, value], |
|
|
|
|
[inputValue, isChangedByUser, isInputValid], |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
return cloneElement(input, { ...restInitProps, onChange, value }); |
|
|
|
|
return cloneElement(input, { |
|
|
|
|
...restInitProps, |
|
|
|
|
onBlur, |
|
|
|
|
onChange, |
|
|
|
|
onFocus, |
|
|
|
|
required: isRequired, |
|
|
|
|
value: inputValue, |
|
|
|
|
}); |
|
|
|
|
}, |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|