Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 deletions src/InputNumber.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,29 @@ import * as React from 'react';
import classNames from 'classnames';
import KeyCode from 'rc-util/lib/KeyCode';
import { composeRef } from 'rc-util/lib/ref';
import getMiniDecimal, { DecimalClass, toFixed, ValueType } from './utils/MiniDecimal';
import getMiniDecimal, {
DecimalClass,
roundDownUnsignedDecimal,
roundUpUnsignedDecimal,
toFixed,
ValueType
} from './utils/MiniDecimal';
import StepHandler from './StepHandler';
import { getNumberPrecision, num2str, validateNumber } from './utils/numberUtil';
import { getNumberPrecision, num2str, trimNumber, validateNumber } from './utils/numberUtil';
import useCursor from './hooks/useCursor';
import useUpdateEffect from './hooks/useUpdateEffect';
import useFrame from './hooks/useFrame';

/**
* We support `stringMode` which need handle correct type when user call in onChange
* format max or min value
* 1. if isInvalid return null
* 2. if precision is undefined, return decimal
* 3. format with precision
* I. if max > 0, round down with precision. Example: max= 3.5, precision=0 afterFormat: 3
* II. if max < 0, round up with precision. Example: max= -3.5, precision=0 afterFormat: -4
* III. if min > 0, round up with precision. Example: min= 3.5, precision=0 afterFormat: 4
* IV. if min < 0, round down with precision. Example: max= -3.5, precision=0 afterFormat: -3
*/
const getDecimalValue = (stringMode: boolean, decimalValue: DecimalClass) => {
if (stringMode || decimalValue.isEmpty()) {
Expand All @@ -20,9 +34,24 @@ const getDecimalValue = (stringMode: boolean, decimalValue: DecimalClass) => {
return decimalValue.toNumber();
};

const getDecimalIfValidate = (value: ValueType) => {
const getDecimalIfValidate = (value: ValueType, precision: number | undefined, isMax?: boolean) => {
const decimal = getMiniDecimal(value);
return decimal.isInvalidate() ? null : decimal;
if (decimal.isInvalidate()) {
return null;
}

if (precision === undefined) {
return decimal;
}

const {negative, integerStr, decimalStr, negativeStr} = trimNumber(decimal.toString());
const unSignedNumberStr = integerStr +'.' + decimalStr;

if ((isMax && !negative) || (!isMax && negative)) {
return getMiniDecimal(negativeStr + roundDownUnsignedDecimal(unSignedNumberStr, precision));
} else {
return getMiniDecimal(negativeStr + roundUpUnsignedDecimal(unSignedNumberStr, precision));
}
};

export interface InputNumberProps<T extends ValueType = ValueType>
Expand Down Expand Up @@ -232,8 +261,8 @@ const InputNumber = React.forwardRef(
}

// >>> Max & Min limit
const maxDecimal = React.useMemo(() => getDecimalIfValidate(max), [max]);
const minDecimal = React.useMemo(() => getDecimalIfValidate(min), [min]);
const maxDecimal = React.useMemo(() => getDecimalIfValidate(max, precision, true), [max, precision]);
const minDecimal = React.useMemo(() => getDecimalIfValidate(min, precision, false), [min, precision]);

const upDisabled = React.useMemo(() => {
if (!maxDecimal || !decimalValue || decimalValue.isInvalidate()) {
Expand Down
25 changes: 25 additions & 0 deletions src/utils/MiniDecimal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,31 @@ export default function getMiniDecimal(value: ValueType): DecimalClass {
return new NumberDecimal(value);
}

/**
* round up an unsigned number str, like: 1.4 -> 2, 1.5 -> 2
*/
export function roundUpUnsignedDecimal(numStr: string, precision: number) {
const {integerStr, decimalStr} = trimNumber(numStr);
const advancedDecimal = getMiniDecimal(integerStr + '.' + decimalStr).add(
`0.${'0'.repeat(precision)}${5}`,
);
return toFixed(advancedDecimal.toString(), '.', precision);
}

/**
* round up an unsigned number str, like: 1.4 -> 1, 1.5 -> 1
*/
export function roundDownUnsignedDecimal(numStr: string, precision: number) {
const {negativeStr, integerStr, decimalStr} = trimNumber(numStr);
const numberWithoutDecimal = `${negativeStr}${integerStr}`;
if (precision === 0) {
return integerStr;
}
return `${numberWithoutDecimal}.${decimalStr
.padEnd(precision, '0')
.slice(0, precision)}`;
}

/**
* Align the logic of toFixed to around like 1.5 => 2
*/
Expand Down
21 changes: 21 additions & 0 deletions tests/input.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,27 @@ describe('InputNumber.Input', () => {
expect(wrapper.getInputValue()).toEqual('-98');
});

it('negative min with higher precision', () => {
const wrapper = prepareWrapper('-4', {min: -3.5, precision: 0});
expect(wrapper.getInputValue()).toEqual('-3');
});

it('positive min with higher precision', () => {
const wrapper = prepareWrapper('4', {min: 3.5, precision: 0});
expect(wrapper.getInputValue()).toEqual('4');
});

it('negative max with higher precision', () => {
const wrapper = prepareWrapper('-4', {max: -3.5, precision: 0});
expect(wrapper.getInputValue()).toEqual('-4');
});

it('positive max with higher precision', () => {
const wrapper = prepareWrapper('4', {max: 3.5, precision: 0});
expect(wrapper.getInputValue()).toEqual('3');
});


// https://github.com/ant-design/ant-design/issues/9439
it('input negative zero', () => {
const wrapper = prepareWrapper('-0', {}, true);
Expand Down
20 changes: 19 additions & 1 deletion tests/util.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import getMiniDecimal, {
BigIntDecimal,
DecimalClass,
NumberDecimal,
ValueType,
roundDownUnsignedDecimal,
roundUpUnsignedDecimal,
toFixed,
ValueType,
} from '../src/utils/MiniDecimal';

jest.mock('../src/utils/supportUtil');
Expand Down Expand Up @@ -150,5 +152,21 @@ describe('InputNumber.Util', () => {
// expect(toFixed('77.88', '.', 1)).toEqual('77.9');
expect(toFixed('-77.88', '.', 1)).toEqual('-77.9');
});

it('round down', () => {
expect(roundDownUnsignedDecimal('77.89', 1)).toEqual('77.8');
expect(roundDownUnsignedDecimal('77.1', 2)).toEqual('77.10');
expect(roundDownUnsignedDecimal('77.81', 1)).toEqual('77.8');
expect(roundDownUnsignedDecimal('77.50', 1)).toEqual('77.5');
expect(roundDownUnsignedDecimal('77.5999', 0)).toEqual('77');
expect(roundDownUnsignedDecimal('77.0001', 0)).toEqual('77');
})
it('round up', () => {
expect(roundUpUnsignedDecimal('77.89', 1)).toEqual('77.9');
expect(roundUpUnsignedDecimal('77.81', 1)).toEqual('77.9');
expect(roundUpUnsignedDecimal('77.89', 0)).toEqual('78');
expect(roundUpUnsignedDecimal('77.599', 0)).toEqual('78');
expect(roundUpUnsignedDecimal('77.01', 0)).toEqual('78');
})
});
});