diff --git a/src/components/Send/AmountInputField.tsx b/src/components/Send/AmountInputField.tsx index 745bea80..1a7a46a9 100644 --- a/src/components/Send/AmountInputField.tsx +++ b/src/components/Send/AmountInputField.tsx @@ -1,4 +1,4 @@ -import { PropsWithChildren, useMemo, useState } from 'react' +import { PropsWithChildren, RefObject, forwardRef, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import * as rb from 'react-bootstrap' import classNames from 'classnames' @@ -42,161 +42,162 @@ const formatBtcDisplayValue = (sats: Api.AmountSats) => { }` } -function UniversalBitcoinInput({ - label, - className, - disabled, - placeholder, - field, - form, - children, -}: PropsWithChildren) { - const [inputType, setInputType] = useState<{ type: 'text' | 'number'; inputMode?: 'decimal' }>({ - type: 'text', - inputMode: 'decimal', - }) +const UniversalBitcoinInput = forwardRef( + ( + { label, className, disabled, placeholder, field, form, children }: PropsWithChildren, + ref: React.Ref, + ) => { + const [inputType, setInputType] = useState<{ type: 'text' | 'number'; inputMode?: 'decimal' }>({ + type: 'text', + inputMode: 'decimal', + }) - const displayInputUnit = useMemo( - () => field.value?.userSelectedInputUnit ?? unitFromValue(field.value?.userRawInputValue), - [field], - ) + const displayInputUnit = useMemo( + () => field.value?.userSelectedInputUnit ?? unitFromValue(field.value?.userRawInputValue), + [field], + ) - return ( - <> - - {inputType.type === 'number' && ( - <> - { - e.preventDefault() // prevent losing focus of the current element + return ( + <> + + {inputType.type === 'number' && ( + <> + { + e.preventDefault() + }} + onClick={(e) => { + e.preventDefault() // prevent losing focus of the current element - const newUnit = displayInputUnit === 'sats' ? 'BTC' : 'sats' - - const userRawInputValue = - field.value?.value !== null - ? (newUnit === 'sats' - ? String(field.value.value) - : satsToBtc(String(field.value.value)) - ).toLocaleString('en-US', { - maximumFractionDigits: Math.log10(100_000_000), - useGrouping: false, - }) - : field.value?.userRawInputValue - - form.setFieldValue( - field.name, - { - ...field.value, - userRawInputValue: userRawInputValue, - userSelectedInputUnit: newUnit, - }, - true, - ) - }} - disabled={disabled} - > - {displayInputUnit === 'sats' && } - {displayInputUnit === 'BTC' && } - - - )} + const newUnit = displayInputUnit === 'sats' ? 'BTC' : 'sats' - { - setInputType({ type: 'number' }) - }} - onBlur={(e) => { - setInputType({ - type: 'text', - inputMode: 'decimal', - }) + const userRawInputValue = + field.value?.value !== null + ? (newUnit === 'sats' + ? String(field.value.value) + : satsToBtc(String(field.value.value)) + ).toLocaleString('en-US', { + maximumFractionDigits: Math.log10(100_000_000), + useGrouping: false, + }) + : field.value?.userRawInputValue - const displayValueInBtc = - field.value.value === null ? field.value.displayValue : formatBtcDisplayValue(field.value.value) + form.setFieldValue( + field.name, + { + ...field.value, + userRawInputValue: userRawInputValue, + userSelectedInputUnit: newUnit, + }, + true, + ) + }} + disabled={disabled} + > + {displayInputUnit === 'sats' && } + {displayInputUnit === 'BTC' && } + + + )} + { + setInputType({ type: 'number' }) + }} + onBlur={(e) => { + setInputType({ + type: 'text', + inputMode: 'decimal', + }) - form.setFieldValue( - field.name, - { - ...field.value, - displayValue: displayValueInBtc, - }, - false, - ) - field.onBlur(e) - }} - onChange={(e) => { - const valueOrNan = parseFloat(e.target.value ?? '') + const displayValueInBtc = + field.value.value === null ? field.value.displayValue : formatBtcDisplayValue(field.value.value) - if (!isValidNumber(valueOrNan)) { form.setFieldValue( field.name, { ...field.value, - value: null, - userRawInputValue: e.target.value, - displayValue: e.target.value, + displayValue: displayValueInBtc, }, - true, + false, ) - return - } else { - const value: number = valueOrNan + field.onBlur(e) + }} + onChange={(e) => { + const valueOrNan = parseFloat(e.target.value ?? '') - let numberValues: string | undefined - const unit = field.value.userSelectedInputUnit ?? unitFromValue(String(value)) - if (unit === 'BTC') { - const splitted = String(value).split('.') - const [integerPart, fractionalPart = ''] = splitted - const paddedFractionalPart = fractionalPart.padEnd(8, '0').substring(0, 8) - numberValues = `${integerPart}${paddedFractionalPart}` + if (!isValidNumber(valueOrNan)) { + form.setFieldValue( + field.name, + { + ...field.value, + value: null, + userRawInputValue: e.target.value, + displayValue: e.target.value, + }, + true, + ) + return } else { - numberValues = value.toLocaleString('en-US', { - maximumFractionDigits: 0, - useGrouping: false, - }) - } + const value: number = valueOrNan - form.setFieldValue( - field.name, - { - value: parseInt(numberValues, 10), - userRawInputValue: e.target.value, - userSelectedInputUnit: field.value?.userSelectedInputUnit, - displayValue: e.target.value, - fromJar: null, - }, - true, - ) - } - }} - isInvalid={form.touched[field.name] && !!form.errors[field.name]} - disabled={disabled} - /> - {children} - - <>{form.errors[field.name]} - - - - ) -} + let numberValues: string | undefined + const unit = field.value.userSelectedInputUnit ?? unitFromValue(String(value)) + if (unit === 'BTC') { + const splitted = String(value).split('.') + const [integerPart, fractionalPart = ''] = splitted + const paddedFractionalPart = fractionalPart.padEnd(8, '0').substring(0, 8) + numberValues = `${integerPart}${paddedFractionalPart}` + } else { + numberValues = value.toLocaleString('en-US', { + maximumFractionDigits: 0, + useGrouping: false, + }) + } + + form.setFieldValue( + field.name, + { + value: parseInt(numberValues, 10), + userRawInputValue: e.target.value, + userSelectedInputUnit: field.value?.userSelectedInputUnit, + displayValue: e.target.value, + fromJar: null, + }, + true, + ) + } + }} + isInvalid={form.touched[field.name] && !!form.errors[field.name]} + disabled={disabled} + /> + {children} + + <>{form.errors[field.name]} + + + + ) + }, +) export type AmountInputFieldProps = { name: string @@ -220,6 +221,7 @@ export const AmountInputField = ({ const { t } = useTranslation() const [field] = useField(name) const form = useFormikContext() + const ref = useRef(null) return ( <> @@ -249,6 +251,7 @@ export const AmountInputField = ({ className={styles.button} onClick={() => { form.setFieldValue(field.name, form.initialValues[field.name], true) + setTimeout(() => ref.current?.focus(), 4) }} disabled={disabled} > @@ -261,6 +264,7 @@ export const AmountInputField = ({ ) : (