Skip to content

Commit 809b5f7

Browse files
TMH-SEyihuiliaosnowystingerreidbarber
authored
fix: correct floating precision in snapValueToStep (#6113)
* fix: correct floating precision in snapValueToStep * add test --------- Co-authored-by: Yihui Liao <44729383+yihuiliao@users.noreply.github.com> Co-authored-by: Robert Snow <rsnow@adobe.com> Co-authored-by: Reid Barber <reid@reidbarber.com>
1 parent cebb81b commit 809b5f7

File tree

2 files changed

+27
-12
lines changed

2 files changed

+27
-12
lines changed

packages/@react-spectrum/numberfield/test/NumberField.test.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,16 @@ describe('NumberField', function () {
417417
expect(onChangeSpy).toHaveBeenCalledWith(0);
418418
});
419419

420+
it('does not lose precision', async () => {
421+
let {textField} = renderNumberField({minValue: 0.1, maxValue: 24, step: 0.1, onChange: onChangeSpy});
422+
423+
act(() => {textField.focus();});
424+
await user.keyboard('24');
425+
await user.tab();
426+
427+
expect(textField).toHaveAttribute('value', '24');
428+
});
429+
420430
it.each`
421431
Name
422432
${'NumberField'}

packages/@react-stately/utils/src/number.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,33 +18,38 @@ export function clamp(value: number, min: number = -Infinity, max: number = Infi
1818
return newValue;
1919
}
2020

21+
export function roundToStepPrecision(value: number, step: number) {
22+
let roundedValue = value;
23+
let stepString = step.toString();
24+
let pointIndex = stepString.indexOf('.');
25+
let precision = pointIndex >= 0 ? stepString.length - pointIndex : 0;
26+
if (precision > 0) {
27+
let pow = Math.pow(10, precision);
28+
roundedValue = Math.round(roundedValue * pow) / pow;
29+
}
30+
return roundedValue;
31+
}
32+
2133
export function snapValueToStep(value: number, min: number | undefined, max: number | undefined, step: number): number {
2234
min = Number(min);
2335
max = Number(max);
2436
let remainder = ((value - (isNaN(min) ? 0 : min)) % step);
25-
let snappedValue = Math.abs(remainder) * 2 >= step
37+
let snappedValue = roundToStepPrecision(Math.abs(remainder) * 2 >= step
2638
? value + Math.sign(remainder) * (step - Math.abs(remainder))
27-
: value - remainder;
39+
: value - remainder, step);
2840

2941
if (!isNaN(min)) {
3042
if (snappedValue < min) {
3143
snappedValue = min;
3244
} else if (!isNaN(max) && snappedValue > max) {
33-
snappedValue = min + Math.floor((max - min) / step) * step;
45+
snappedValue = min + Math.floor(roundToStepPrecision((max - min) / step, step)) * step;
3446
}
3547
} else if (!isNaN(max) && snappedValue > max) {
36-
snappedValue = Math.floor(max / step) * step;
48+
snappedValue = Math.floor(roundToStepPrecision(max / step, step)) * step;
3749
}
3850

3951
// correct floating point behavior by rounding to step precision
40-
let string = step.toString();
41-
let index = string.indexOf('.');
42-
let precision = index >= 0 ? string.length - index : 0;
43-
44-
if (precision > 0) {
45-
let pow = Math.pow(10, precision);
46-
snappedValue = Math.round(snappedValue * pow) / pow;
47-
}
52+
snappedValue = roundToStepPrecision(snappedValue, step);
4853

4954
return snappedValue;
5055
}

0 commit comments

Comments
 (0)