diff --git a/packages/@react-stately/utils/src/number.ts b/packages/@react-stately/utils/src/number.ts index b6184ecd7ff..42cd38eadee 100644 --- a/packages/@react-stately/utils/src/number.ts +++ b/packages/@react-stately/utils/src/number.ts @@ -20,9 +20,18 @@ export function clamp(value: number, min: number = -Infinity, max: number = Infi export function roundToStepPrecision(value: number, step: number): number { let roundedValue = value; + let precision = 0; let stepString = step.toString(); let pointIndex = stepString.indexOf('.'); - let precision = pointIndex >= 0 ? stepString.length - pointIndex : 0; + if (pointIndex >= 0) { + precision = stepString.length - pointIndex; + } else { + // Handle negative exponents in exponential notation (e.g., "1e-7" → precision 8) + let eIndex = stepString.toLowerCase().indexOf('e-'); + if (eIndex > 0) { + precision = Math.abs(Number(stepString.slice(eIndex + 1))) + 1; + } + } if (precision > 0) { let pow = Math.pow(10, precision); roundedValue = Math.round(roundedValue * pow) / pow; diff --git a/packages/@react-stately/utils/test/number.test.ts b/packages/@react-stately/utils/test/number.test.ts index 0cfdef0b6e5..6e8e0a4b684 100644 --- a/packages/@react-stately/utils/test/number.test.ts +++ b/packages/@react-stately/utils/test/number.test.ts @@ -1,4 +1,4 @@ -import {clamp, snapValueToStep} from '../src/number'; +import {clamp, roundToStepPrecision, snapValueToStep} from '../src/number'; describe('number utils', () => { describe('clamp', () => { @@ -8,6 +8,30 @@ describe('number utils', () => { }); }); + describe('roundToStepPrecision', () => { + it('should return the input unchanged for integer steps', () => { + expect(roundToStepPrecision(7.123, 1)).toBe(7.123); + expect(roundToStepPrecision(5, 10)).toBe(5); + }); + it('should round to the correct decimal places for steps with decimals', () => { + expect(roundToStepPrecision(1.24, 0.1)).toBe(1.24); + expect(roundToStepPrecision(1.456, 0.01)).toBe(1.456); + // Should not overcount precision + expect(roundToStepPrecision(2.349, 0.100)).toBe(2.35); + expect(roundToStepPrecision(2.35, 0.100)).toBe(2.35); + // Should handle negative values + expect(roundToStepPrecision(-1.456, 0.01)).toBe(-1.456); + // Should handle zero value + expect(roundToStepPrecision(0, 0.01)).toBe(0); + }); + it('should handle rounding for exponential step values', () => { + expect(roundToStepPrecision(0.123456789, 1e-3)).toBe(0.1235); + expect(roundToStepPrecision(0.123456789, 1e-7)).toBe(0.12345679); + // Should handle exponential notation steps regardless of e/E case + expect(roundToStepPrecision(0.123456789, 1E-8)).toBe(0.123456789); + }); + }); + describe('snapValueToStep', () => { it('should snap value to nearest step based on min and max', () => { expect(snapValueToStep(2, -0.5, 100, 3)).toBe(2.5);