diff --git a/ui/address/contract/methods/form/ContractMethodFieldInput.tsx b/ui/address/contract/methods/form/ContractMethodFieldInput.tsx index 7869c1690b..13e8d44d0f 100644 --- a/ui/address/contract/methods/form/ContractMethodFieldInput.tsx +++ b/ui/address/contract/methods/form/ContractMethodFieldInput.tsx @@ -28,7 +28,9 @@ interface Props { } const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDisabled, isOptional: isOptionalProp, level }: Props) => { - const ref = React.useRef(null); + const ref = React.useRef(); + + const [ intPower, setIntPower ] = React.useState(18); const isNativeCoin = data.fieldType === 'native_coin'; const isOptional = isOptionalProp || isNativeCoin; @@ -46,6 +48,8 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi const hasMultiplyButton = argTypeMatchInt && Number(argTypeMatchInt.power) >= 64; + React.useImperativeHandle(field.ref, () => ref.current); + const handleChange = React.useCallback((event: React.ChangeEvent) => { const formattedValue = format(event.target.value); field.onChange(formattedValue); // data send back to hook form @@ -83,6 +87,42 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi setValue(name, newValue, { shouldValidate: true }); }, [ format, name, setValue ]); + const handlePaste = React.useCallback((event: React.ClipboardEvent) => { + if (!argTypeMatchInt || !hasMultiplyButton) { + return; + } + + const value = Number(event.clipboardData.getData('text')); + + if (Object.is(value, NaN)) { + return; + } + + const isFloat = Number.isFinite(value) && !Number.isInteger(value); + + if (!isFloat) { + return; + } + + event.preventDefault(); + + if (field.value) { + return; + } + + const newValue = value * 10 ** intPower; + const formattedValue = format(newValue.toString()); + + field.onChange(formattedValue); + setValue(name, formattedValue, { shouldValidate: true }); + window.setTimeout(() => { + // move cursor to the end of the input + // but we have to wait for the input to get the new value + const END_OF_INPUT = 100; + ref.current?.setSelectionRange(END_OF_INPUT, END_OF_INPUT); + }, 100); + }, [ argTypeMatchInt, hasMultiplyButton, intPower, format, field, setValue, name ]); + const error = fieldState.error; return ( @@ -107,9 +147,14 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi thousandSeparator: ' ', decimalScale: 0, allowNegative: !argTypeMatchInt.isUnsigned, + getInputRef: (element: HTMLInputElement) => { + ref.current = element; + }, } : {}) } - ref={ ref } + // as we use mutable ref, we have to cast it to React.LegacyRef to trick chakra and typescript + ref={ ref as React.LegacyRef | undefined } onChange={ handleChange } + onPaste={ handlePaste } required={ !isOptional } isInvalid={ Boolean(error) } placeholder={ data.type } @@ -148,7 +193,14 @@ const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDi Max )) } - { hasMultiplyButton && } + { hasMultiplyButton && ( + + ) } { error && { error.message } } diff --git a/ui/address/contract/methods/form/ContractMethodMultiplyButton.tsx b/ui/address/contract/methods/form/ContractMethodMultiplyButton.tsx index 4bd208d43d..a8edadaf3a 100644 --- a/ui/address/contract/methods/form/ContractMethodMultiplyButton.tsx +++ b/ui/address/contract/methods/form/ContractMethodMultiplyButton.tsx @@ -20,10 +20,12 @@ import IconSvg from 'ui/shared/IconSvg'; interface Props { onClick: (power: number) => void; isDisabled?: boolean; + initialValue: number; + onChange: (power: number) => void; } -const ContractMethodMultiplyButton = ({ onClick, isDisabled }: Props) => { - const [ selectedOption, setSelectedOption ] = React.useState(18); +const ContractMethodMultiplyButton = ({ onClick, isDisabled, initialValue, onChange }: Props) => { + const [ selectedOption, setSelectedOption ] = React.useState(initialValue); const [ customValue, setCustomValue ] = React.useState(); const { isOpen, onToggle, onClose } = useDisclosure(); @@ -35,13 +37,16 @@ const ContractMethodMultiplyButton = ({ onClick, isDisabled }: Props) => { setSelectedOption((prev) => prev === id ? undefined : id); setCustomValue(undefined); onClose(); + onChange(id); } - }, [ onClose ]); + }, [ onClose, onChange ]); const handleInputChange = React.useCallback((event: React.ChangeEvent) => { - setCustomValue(Number(event.target.value)); + const value = Number(event.target.value); + setCustomValue(value); setSelectedOption(undefined); - }, []); + onChange(value); + }, [ onChange ]); const value = selectedOption || customValue;