Skip to content

Commit b4f4db2

Browse files
authored
fix(Slider): fix floating-point precision issues (#18664)
1 parent b476702 commit b4f4db2

File tree

2 files changed

+52
-28
lines changed

2 files changed

+52
-28
lines changed

packages/react/src/components/Slider/Slider.tsx

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright IBM Corp. 2016, 2023
2+
* Copyright IBM Corp. 2016, 2025
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
@@ -635,20 +635,16 @@ class Slider extends PureComponent<SliderProps> {
635635
}
636636

637637
/**
638-
* Takes a value and ensures it fits to the steps of the range
639-
* @param value
640-
* @returns value of the nearest step
638+
* Rounds a given value to the nearest step defined by the slider's `step`
639+
* prop.
640+
*
641+
* @param value - The value to adjust to the nearest step. Defaults to `0`.
642+
* @returns The value rounded to the precision determined by the step.
641643
*/
642-
nearestStepValue(value) {
643-
const tempInput = document.createElement('input');
644-
645-
tempInput.type = 'range';
646-
tempInput.min = `${this.props.min}`;
647-
tempInput.max = `${this.props.max}`;
648-
tempInput.step = `${this.props.step}`;
649-
tempInput.value = `${value}`;
644+
nearestStepValue(value = 0) {
645+
const decimals = (this.props.step?.toString().split('.')[1] || '').length;
650646

651-
return parseFloat(tempInput.value);
647+
return Number(value.toFixed(decimals));
652648
}
653649

654650
/**
@@ -839,7 +835,11 @@ class Slider extends PureComponent<SliderProps> {
839835
? this.state.value
840836
: this.state.valueUpper;
841837
const { value, left } = this.calcValue({
842-
value: this.calcValueForDelta(currentValue, delta, this.props.step),
838+
value: this.calcValueForDelta(
839+
currentValue ?? this.props.min,
840+
delta,
841+
this.props.step
842+
),
843843
});
844844
this.setValueLeftForHandle(this.state.activeHandle, {
845845
value: this.nearestStepValue(value),
@@ -1111,21 +1111,22 @@ class Slider extends PureComponent<SliderProps> {
11111111
};
11121112

11131113
/**
1114-
* Given the current value, delta and step, calculate the new value.
1114+
* Calculates a new slider value based on the current value, a change delta,
1115+
* and a step.
11151116
*
1116-
* @param {number} currentValue
1117-
* Current value user is moving from.
1118-
* @param {number} delta
1119-
* Movement from the current value. Can be positive or negative.
1120-
* @param {number} step
1121-
* A value determining how much the value should increase/decrease by moving
1122-
* the thumb by mouse.
1117+
* @param currentValue - The starting value from which the slider is moving.
1118+
* @param delta - The amount to adjust the current value by.
1119+
* @param step - The step. Defaults to `1`.
1120+
* @returns The new slider value, rounded to the same number of decimal places
1121+
* as the step.
11231122
*/
1124-
calcValueForDelta = (currentValue, delta, step = 1) => {
1125-
return (
1126-
(delta > 0 ? Math.floor(currentValue / step) * step : currentValue) +
1127-
delta
1128-
);
1123+
calcValueForDelta = (currentValue: number, delta: number, step = 1) => {
1124+
const base =
1125+
delta > 0 ? Math.floor(currentValue / step) * step : currentValue;
1126+
const newValue = base + delta;
1127+
const decimals = (step.toString().split('.')[1] || '').length;
1128+
1129+
return Number(newValue.toFixed(decimals));
11291130
};
11301131

11311132
/**

packages/react/src/components/Slider/__test__/Slider-test.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright IBM Corp. 2016, 2023
2+
* Copyright IBM Corp. 2016, 2025
33
*
44
* This source code is licensed under the Apache-2.0 license found in the
55
* LICENSE file in the root directory of this source tree.
@@ -1183,6 +1183,29 @@ describe('Slider', () => {
11831183
mouseMove(container.firstChild, { clientX: 1000 });
11841184
expect(onRelease).not.toHaveBeenCalled();
11851185
});
1186+
1187+
it("should round the slider's value when using small step values", async () => {
1188+
renderTwoHandleSlider({
1189+
value: 0,
1190+
min: 0,
1191+
max: 1,
1192+
step: 0.1,
1193+
onChange,
1194+
});
1195+
const leftHandle = screen.getAllByRole('slider')[0];
1196+
1197+
await userEvent.click(leftHandle);
1198+
await userEvent.keyboard('{ArrowRight}');
1199+
await userEvent.keyboard('{ArrowRight}');
1200+
await userEvent.keyboard('{ArrowRight}');
1201+
1202+
// Retrieve the last call to `onChange`.
1203+
const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1][0];
1204+
1205+
// Assert that the value was correctly correctly (i.e., not
1206+
// 0.30000000000000004).
1207+
expect(lastCall.value).toBe(0.3);
1208+
});
11861209
});
11871210
});
11881211
});

0 commit comments

Comments
 (0)