Skip to content

Commit d3de147

Browse files
boomlerwanghao
and
wanghao
authored
fix: should format max/min with precision (#385)
Co-authored-by: wanghao <[email protected]>
1 parent d65b0dc commit d3de147

File tree

4 files changed

+100
-7
lines changed

4 files changed

+100
-7
lines changed

src/InputNumber.tsx

+35-6
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,29 @@ import * as React from 'react';
22
import classNames from 'classnames';
33
import KeyCode from 'rc-util/lib/KeyCode';
44
import { composeRef } from 'rc-util/lib/ref';
5-
import getMiniDecimal, { DecimalClass, toFixed, ValueType } from './utils/MiniDecimal';
5+
import getMiniDecimal, {
6+
DecimalClass,
7+
roundDownUnsignedDecimal,
8+
roundUpUnsignedDecimal,
9+
toFixed,
10+
ValueType
11+
} from './utils/MiniDecimal';
612
import StepHandler from './StepHandler';
7-
import { getNumberPrecision, num2str, validateNumber } from './utils/numberUtil';
13+
import { getNumberPrecision, num2str, trimNumber, validateNumber } from './utils/numberUtil';
814
import useCursor from './hooks/useCursor';
915
import useUpdateEffect from './hooks/useUpdateEffect';
1016
import useFrame from './hooks/useFrame';
1117

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

23-
const getDecimalIfValidate = (value: ValueType) => {
37+
const getDecimalIfValidate = (value: ValueType, precision: number | undefined, isMax?: boolean) => {
2438
const decimal = getMiniDecimal(value);
25-
return decimal.isInvalidate() ? null : decimal;
39+
if (decimal.isInvalidate()) {
40+
return null;
41+
}
42+
43+
if (precision === undefined) {
44+
return decimal;
45+
}
46+
47+
const {negative, integerStr, decimalStr, negativeStr} = trimNumber(decimal.toString());
48+
const unSignedNumberStr = integerStr +'.' + decimalStr;
49+
50+
if ((isMax && !negative) || (!isMax && negative)) {
51+
return getMiniDecimal(negativeStr + roundDownUnsignedDecimal(unSignedNumberStr, precision));
52+
} else {
53+
return getMiniDecimal(negativeStr + roundUpUnsignedDecimal(unSignedNumberStr, precision));
54+
}
2655
};
2756

2857
export interface InputNumberProps<T extends ValueType = ValueType>
@@ -232,8 +261,8 @@ const InputNumber = React.forwardRef(
232261
}
233262

234263
// >>> Max & Min limit
235-
const maxDecimal = React.useMemo(() => getDecimalIfValidate(max), [max]);
236-
const minDecimal = React.useMemo(() => getDecimalIfValidate(min), [min]);
264+
const maxDecimal = React.useMemo(() => getDecimalIfValidate(max, precision, true), [max, precision]);
265+
const minDecimal = React.useMemo(() => getDecimalIfValidate(min, precision, false), [min, precision]);
237266

238267
const upDisabled = React.useMemo(() => {
239268
if (!maxDecimal || !decimalValue || decimalValue.isInvalidate()) {

src/utils/MiniDecimal.ts

+25
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,31 @@ export default function getMiniDecimal(value: ValueType): DecimalClass {
264264
return new NumberDecimal(value);
265265
}
266266

267+
/**
268+
* round up an unsigned number str, like: 1.4 -> 2, 1.5 -> 2
269+
*/
270+
export function roundUpUnsignedDecimal(numStr: string, precision: number) {
271+
const {integerStr, decimalStr} = trimNumber(numStr);
272+
const advancedDecimal = getMiniDecimal(integerStr + '.' + decimalStr).add(
273+
`0.${'0'.repeat(precision)}${5}`,
274+
);
275+
return toFixed(advancedDecimal.toString(), '.', precision);
276+
}
277+
278+
/**
279+
* round up an unsigned number str, like: 1.4 -> 1, 1.5 -> 1
280+
*/
281+
export function roundDownUnsignedDecimal(numStr: string, precision: number) {
282+
const {negativeStr, integerStr, decimalStr} = trimNumber(numStr);
283+
const numberWithoutDecimal = `${negativeStr}${integerStr}`;
284+
if (precision === 0) {
285+
return integerStr;
286+
}
287+
return `${numberWithoutDecimal}.${decimalStr
288+
.padEnd(precision, '0')
289+
.slice(0, precision)}`;
290+
}
291+
267292
/**
268293
* Align the logic of toFixed to around like 1.5 => 2
269294
*/

tests/input.test.tsx

+21
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,27 @@ describe('InputNumber.Input', () => {
5959
expect(wrapper.getInputValue()).toEqual('-98');
6060
});
6161

62+
it('negative min with higher precision', () => {
63+
const wrapper = prepareWrapper('-4', {min: -3.5, precision: 0});
64+
expect(wrapper.getInputValue()).toEqual('-3');
65+
});
66+
67+
it('positive min with higher precision', () => {
68+
const wrapper = prepareWrapper('4', {min: 3.5, precision: 0});
69+
expect(wrapper.getInputValue()).toEqual('4');
70+
});
71+
72+
it('negative max with higher precision', () => {
73+
const wrapper = prepareWrapper('-4', {max: -3.5, precision: 0});
74+
expect(wrapper.getInputValue()).toEqual('-4');
75+
});
76+
77+
it('positive max with higher precision', () => {
78+
const wrapper = prepareWrapper('4', {max: 3.5, precision: 0});
79+
expect(wrapper.getInputValue()).toEqual('3');
80+
});
81+
82+
6283
// https://github.com/ant-design/ant-design/issues/9439
6384
it('input negative zero', () => {
6485
const wrapper = prepareWrapper('-0', {}, true);

tests/util.test.tsx

+19-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import getMiniDecimal, {
22
BigIntDecimal,
33
DecimalClass,
44
NumberDecimal,
5-
ValueType,
5+
roundDownUnsignedDecimal,
6+
roundUpUnsignedDecimal,
67
toFixed,
8+
ValueType,
79
} from '../src/utils/MiniDecimal';
810

911
jest.mock('../src/utils/supportUtil');
@@ -150,5 +152,21 @@ describe('InputNumber.Util', () => {
150152
// expect(toFixed('77.88', '.', 1)).toEqual('77.9');
151153
expect(toFixed('-77.88', '.', 1)).toEqual('-77.9');
152154
});
155+
156+
it('round down', () => {
157+
expect(roundDownUnsignedDecimal('77.89', 1)).toEqual('77.8');
158+
expect(roundDownUnsignedDecimal('77.1', 2)).toEqual('77.10');
159+
expect(roundDownUnsignedDecimal('77.81', 1)).toEqual('77.8');
160+
expect(roundDownUnsignedDecimal('77.50', 1)).toEqual('77.5');
161+
expect(roundDownUnsignedDecimal('77.5999', 0)).toEqual('77');
162+
expect(roundDownUnsignedDecimal('77.0001', 0)).toEqual('77');
163+
})
164+
it('round up', () => {
165+
expect(roundUpUnsignedDecimal('77.89', 1)).toEqual('77.9');
166+
expect(roundUpUnsignedDecimal('77.81', 1)).toEqual('77.9');
167+
expect(roundUpUnsignedDecimal('77.89', 0)).toEqual('78');
168+
expect(roundUpUnsignedDecimal('77.599', 0)).toEqual('78');
169+
expect(roundUpUnsignedDecimal('77.01', 0)).toEqual('78');
170+
})
153171
});
154172
});

0 commit comments

Comments
 (0)