Allow negative value inputs in InputNumber (#7540)
This commit is contained in:
parent
047871b776
commit
89941ba3bf
@ -29,6 +29,7 @@ interface Props {
|
||||
isSi?: boolean;
|
||||
isDecimal?: boolean;
|
||||
isWarning?: boolean;
|
||||
isSigned?: boolean;
|
||||
isZeroable?: boolean;
|
||||
label?: React.ReactNode;
|
||||
labelExtra?: React.ReactNode;
|
||||
@ -61,13 +62,13 @@ function getGlobalMaxValue (bitLength?: number): BN {
|
||||
return BN_TWO.pow(new BN(bitLength || DEFAULT_BITLENGTH)).isub(BN_ONE);
|
||||
}
|
||||
|
||||
function getRegex (isDecimal: boolean): RegExp {
|
||||
function getRegex (isDecimal: boolean, isSigned: boolean): RegExp {
|
||||
const decimal = '.';
|
||||
|
||||
return new RegExp(
|
||||
isDecimal
|
||||
? `^(0|[1-9]\\d*)(\\${decimal}\\d*)?$`
|
||||
: '^(0|[1-9]\\d*)$'
|
||||
? `^${isSigned ? '-?' : ''}(0|[1-9]\\d*)(\\${decimal}\\d*)?$`
|
||||
: `^${isSigned ? '-?' : ''}(0|[1-9]\\d*)$`
|
||||
);
|
||||
}
|
||||
|
||||
@ -92,10 +93,10 @@ function getSiPowers (si: SiDef | null, decimals?: number): [BN, number, number]
|
||||
return [new BN(basePower + si.power), basePower, si.power];
|
||||
}
|
||||
|
||||
function isValidNumber (bn: BN, bitLength: BitLength, isZeroable: boolean, maxValue?: BN): boolean {
|
||||
function isValidNumber (bn: BN, bitLength: BitLength, isSigned: boolean, isZeroable: boolean, maxValue?: BN): boolean {
|
||||
if (
|
||||
// cannot be negative
|
||||
bn.lt(BN_ZERO) ||
|
||||
(!isSigned && bn.lt(BN_ZERO)) ||
|
||||
// cannot be > than allowed max
|
||||
bn.gt(getGlobalMaxValue(bitLength)) ||
|
||||
// check if 0 and it should be a value
|
||||
@ -111,12 +112,11 @@ function isValidNumber (bn: BN, bitLength: BitLength, isZeroable: boolean, maxVa
|
||||
return true;
|
||||
}
|
||||
|
||||
function inputToBn (api: ApiPromise, input: string, si: SiDef | null, bitLength: BitLength, isZeroable: boolean, maxValue?: BN, decimals?: number): [BN, boolean] {
|
||||
function inputToBn (api: ApiPromise, input: string, si: SiDef | null, bitLength: BitLength, isSigned: boolean, isZeroable: boolean, maxValue?: BN, decimals?: number): [BN, boolean] {
|
||||
const [siPower, basePower, siUnitPower] = getSiPowers(si, decimals);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
|
||||
const isDecimalValue = input.match(/^(\d+)\.(\d+)$/);
|
||||
|
||||
let result;
|
||||
|
||||
if (isDecimalValue) {
|
||||
@ -133,47 +133,46 @@ function inputToBn (api: ApiPromise, input: string, si: SiDef | null, bitLength:
|
||||
.add(mod.mul(BN_TEN.pow(new BN(basePower + siUnitPower - modString.length))));
|
||||
} else {
|
||||
result = new BN(input.replace(/[^\d]/g, ''))
|
||||
.mul(BN_TEN.pow(siPower));
|
||||
.mul(BN_TEN.pow(siPower))
|
||||
.muln(isSigned && input.startsWith('-') ? -1 : 1);
|
||||
}
|
||||
|
||||
return [
|
||||
result,
|
||||
isValidNumber(result, bitLength, isZeroable, maxValue)
|
||||
isValidNumber(result, bitLength, isSigned, isZeroable, maxValue)
|
||||
];
|
||||
}
|
||||
|
||||
function getValuesFromString (api: ApiPromise, value: string, si: SiDef | null, bitLength: BitLength, isZeroable: boolean, maxValue?: BN, decimals?: number): [string, BN, boolean] {
|
||||
const [valueBn, isValid] = inputToBn(api, value, si, bitLength, isZeroable, maxValue, decimals);
|
||||
|
||||
function getValuesFromString (api: ApiPromise, value: string, si: SiDef | null, bitLength: BitLength, isSigned: boolean, isZeroable: boolean, maxValue?: BN, decimals?: number): [string, BN, boolean] {
|
||||
return [
|
||||
value,
|
||||
valueBn,
|
||||
isValid
|
||||
...inputToBn(api, value, si, bitLength, isSigned, isZeroable, maxValue, decimals)
|
||||
];
|
||||
}
|
||||
|
||||
function getValuesFromBn (valueBn: BN, si: SiDef | null, isZeroable: boolean, _decimals?: number): [string, BN, boolean] {
|
||||
function getValuesFromBn (valueBn: BN, si: SiDef | null, isSigned: boolean, isZeroable: boolean, _decimals?: number): [string, BN, boolean] {
|
||||
const decimals = isUndefined(_decimals)
|
||||
? formatBalance.getDefaults().decimals
|
||||
: _decimals;
|
||||
const value = si
|
||||
? valueBn.div(BN_TEN.pow(new BN(decimals + si.power))).toString()
|
||||
: valueBn.toString();
|
||||
|
||||
return [
|
||||
value,
|
||||
si
|
||||
? valueBn.div(BN_TEN.pow(new BN(decimals + si.power))).toString()
|
||||
: valueBn.toString(),
|
||||
valueBn,
|
||||
isZeroable ? true : valueBn.gt(BN_ZERO)
|
||||
isZeroable || isSigned
|
||||
? true
|
||||
: valueBn.gt(BN_ZERO)
|
||||
];
|
||||
}
|
||||
|
||||
function getValues (api: ApiPromise, value: BN | string = BN_ZERO, si: SiDef | null, bitLength: BitLength, isZeroable: boolean, maxValue?: BN, decimals?: number): [string, BN, boolean] {
|
||||
function getValues (api: ApiPromise, value: BN | string = BN_ZERO, si: SiDef | null, bitLength: BitLength, isSigned: boolean, isZeroable: boolean, maxValue?: BN, decimals?: number): [string, BN, boolean] {
|
||||
return isBn(value)
|
||||
? getValuesFromBn(value, si, isZeroable, decimals)
|
||||
: getValuesFromString(api, value, si, bitLength, isZeroable, maxValue, decimals);
|
||||
? getValuesFromBn(value, si, isSigned, isZeroable, decimals)
|
||||
: getValuesFromString(api, value, si, bitLength, isSigned, isZeroable, maxValue, decimals);
|
||||
}
|
||||
|
||||
function InputNumber ({ autoFocus, bitLength = DEFAULT_BITLENGTH, children, className = '', defaultValue, help, isDecimal, isFull, isSi, isDisabled, isError = false, isWarning, isZeroable = true, label, labelExtra, maxLength, maxValue, onChange, onEnter, onEscape, placeholder, siDecimals, siDefault, siSymbol, value: propsValue }: Props): React.ReactElement<Props> {
|
||||
function InputNumber ({ autoFocus, bitLength = DEFAULT_BITLENGTH, children, className = '', defaultValue, help, isDecimal, isFull, isSi, isDisabled, isError = false, isWarning, isSigned = false, isZeroable = true, label, labelExtra, maxLength, maxValue, onChange, onEnter, onEscape, placeholder, siDecimals, siDefault, siSymbol, value: propsValue }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const [si, setSi] = useState<SiDef | null>(() =>
|
||||
@ -182,7 +181,7 @@ function InputNumber ({ autoFocus, bitLength = DEFAULT_BITLENGTH, children, clas
|
||||
: null
|
||||
);
|
||||
const [[value, valueBn, isValid], setValues] = useState<[string, BN, boolean]>(() =>
|
||||
getValues(api, propsValue || defaultValue, si, bitLength, isZeroable, maxValue, siDecimals)
|
||||
getValues(api, propsValue || defaultValue, si, bitLength, isSigned, isZeroable, maxValue, siDecimals)
|
||||
);
|
||||
const [isPreKeyDown, setIsPreKeyDown] = useState(false);
|
||||
|
||||
@ -197,9 +196,9 @@ function InputNumber ({ autoFocus, bitLength = DEFAULT_BITLENGTH, children, clas
|
||||
|
||||
const _onChangeWithSi = useCallback(
|
||||
(input: string, si: SiDef | null) => setValues(
|
||||
getValuesFromString(api, input, si, bitLength, isZeroable, maxValue, siDecimals)
|
||||
getValuesFromString(api, input, si, bitLength, isSigned, isZeroable, maxValue, siDecimals)
|
||||
),
|
||||
[api, bitLength, isZeroable, maxValue, siDecimals]
|
||||
[api, bitLength, isSigned, isZeroable, maxValue, siDecimals]
|
||||
);
|
||||
|
||||
const _onChange = useCallback(
|
||||
@ -223,12 +222,12 @@ function InputNumber ({ autoFocus, bitLength = DEFAULT_BITLENGTH, children, clas
|
||||
const { selectionEnd: j, selectionStart: i, value } = event.target as HTMLInputElement;
|
||||
const newValue = `${value.substring(0, i || 0)}${event.key}${value.substring(j || 0)}`;
|
||||
|
||||
if (!getRegex(isDecimal || !!si).test(newValue)) {
|
||||
if (!getRegex(isDecimal || !!si, isSigned).test(newValue)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
},
|
||||
[isDecimal, isPreKeyDown, si]
|
||||
[isDecimal, isPreKeyDown, isSigned, si]
|
||||
);
|
||||
|
||||
const _onKeyUp = useCallback(
|
||||
@ -244,11 +243,11 @@ function InputNumber ({ autoFocus, bitLength = DEFAULT_BITLENGTH, children, clas
|
||||
(event: React.ClipboardEvent<Element>): void => {
|
||||
const { value: newValue } = event.target as HTMLInputElement;
|
||||
|
||||
if (!getRegex(isDecimal || !!si).test(newValue)) {
|
||||
if (!getRegex(isDecimal || !!si, isSigned).test(newValue)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
[isDecimal, si]
|
||||
[isDecimal, isSigned, si]
|
||||
);
|
||||
|
||||
const _onSelectSiUnit = useCallback(
|
||||
@ -285,7 +284,11 @@ function InputNumber ({ autoFocus, bitLength = DEFAULT_BITLENGTH, children, clas
|
||||
onKeyDown={_onKeyDown}
|
||||
onKeyUp={_onKeyUp}
|
||||
onPaste={_onPaste}
|
||||
placeholder={placeholder || t<string>('Positive number')}
|
||||
placeholder={placeholder || (
|
||||
isSigned
|
||||
? t<string>('Valid number')
|
||||
: t<string>('Positive number')
|
||||
)}
|
||||
type='text'
|
||||
value={value}
|
||||
>
|
||||
|
||||
@ -21,6 +21,12 @@ function getBitLength (registry: Registry, { type }: TypeDef): number {
|
||||
}
|
||||
|
||||
function Amount ({ className = '', defaultValue: { value }, isDisabled, isError, label, onChange, onEnter, registry, type, withLabel }: Props): React.ReactElement<Props> {
|
||||
const isSigned = useMemo(
|
||||
// Allow signed inputs for i{8, 16, 32, 64, 128, ...} types
|
||||
() => /^i\d*$/.test(type.type),
|
||||
[type]
|
||||
);
|
||||
|
||||
const defaultValue = useMemo(
|
||||
() => isDisabled
|
||||
? value instanceof registry.createClass('AccountIndex')
|
||||
@ -63,6 +69,7 @@ function Amount ({ className = '', defaultValue: { value }, isDisabled, isError,
|
||||
className='full'
|
||||
defaultValue={defaultValue}
|
||||
isError={isError}
|
||||
isSigned={isSigned}
|
||||
isZeroable
|
||||
label={label}
|
||||
onChange={_onChange}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user