import { Flex, chakra } from '@chakra-ui/react'; import React from 'react'; import { useController, useFormContext } from 'react-hook-form'; import { NumericFormat } from 'react-number-format'; import type { ContractAbiItemInput } from '../types'; import { Button } from 'toolkit/chakra/button'; import { Field } from 'toolkit/chakra/field'; import { Input } from 'toolkit/chakra/input'; import { InputGroup } from 'toolkit/chakra/input-group'; import { ClearButton } from 'toolkit/components/buttons/ClearButton'; import { HOUR, SECOND } from 'toolkit/utils/consts'; import ContractMethodAddressButton from './ContractMethodAddressButton'; import ContractMethodFieldLabel from './ContractMethodFieldLabel'; import ContractMethodMultiplyButton from './ContractMethodMultiplyButton'; import useFormatFieldValue from './useFormatFieldValue'; import useValidateField from './useValidateField'; import { matchInt } from './utils'; const TIMESTAMP_BUTTON_REGEXP = /time|deadline|expiration|expiry/i; interface Props { data: ContractAbiItemInput; hideLabel?: boolean; path: string; className?: string; isDisabled: boolean; isOptional?: boolean; level: number; } const ContractMethodFieldInput = ({ data, hideLabel, path: name, className, isDisabled, isOptional: isOptionalProp, level }: Props) => { const ref = React.useRef(null); const [ intPower, setIntPower ] = React.useState(18); const isNativeCoin = data.fieldType === 'native_coin'; const isOptional = isOptionalProp || isNativeCoin; const argTypeMatchInt = React.useMemo(() => matchInt(data.type), [ data.type ]); const hasTimestampButton = React.useMemo(() => TIMESTAMP_BUTTON_REGEXP.test(data.name || ''), [ data.name ]); const validate = useValidateField({ isOptional, argType: data.type, argTypeMatchInt }); const format = useFormatFieldValue({ argType: data.type, argTypeMatchInt }); const { control, setValue, getValues } = useFormContext(); const { field, fieldState } = useController({ control, name, rules: { validate } }); const nativeCoinRowBgColor = { _light: 'gray.100', _dark: 'gray.700' }; 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 setValue(name, formattedValue); // UI state }, [ field, name, setValue, format ]); const handleClear = React.useCallback(() => { setValue(name, ''); ref.current?.focus(); }, [ name, setValue ]); const handleMultiplyButtonClick = React.useCallback((power: number) => { const zeroes = Array(power).fill('0').join(''); const value = getValues(name); const newValue = format(value ? value + zeroes : '1' + zeroes); setValue(name, newValue, { shouldValidate: true }); }, [ format, getValues, name, setValue ]); const handleMaxIntButtonClick = React.useCallback(() => { if (!argTypeMatchInt) { return; } const newValue = format(argTypeMatchInt.max.toString()); setValue(name, newValue, { shouldValidate: true }); }, [ format, name, setValue, argTypeMatchInt ]); const handleAddressButtonClick = React.useCallback((address: string) => { const newValue = format(address); setValue(name, newValue, { shouldValidate: true }); }, [ format, name, setValue ]); const handleTimestampButtonClick = React.useCallback(() => { const newValue = format(String(Math.floor((Date.now() + HOUR) / SECOND))); 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; const inputEndElement = ( { data.type === 'address' && } { argTypeMatchInt && !isNativeCoin && (hasTimestampButton ? ( ) : ( )) } { hasMultiplyButton && ( ) } ); const inputProps = { ...field, size: 'sm' as const, bgColor: 'bg.primary', onChange: handleChange, required: !isOptional, placeholder: data.type, autoComplete: 'off', 'data-1p-ignore': true, }; const getInputRef = React.useCallback((element: HTMLInputElement) => { ref.current = element; }, []); return ( { !hideLabel && } { argTypeMatchInt ? ( ) : | undefined }/> } ); }; export default React.memo(chakra(ContractMethodFieldInput));