From 0d12c6a8833ba4d3acbce03b9fca4f8911a23664 Mon Sep 17 00:00:00 2001 From: Mitchell Austin Date: Thu, 2 Sep 2021 08:23:07 -0700 Subject: [PATCH] Update roundClamp to match HTML number input stepping behavior - Rounds to steps starting from min - Uses rounding precision determined by the greater of min or step - If max is not on a step, uses the closest step less than max --- packages/components/src/utils/math.js | 18 +++++++++-- packages/components/src/utils/test/math.js | 35 ++++++++++++++++------ 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/packages/components/src/utils/math.js b/packages/components/src/utils/math.js index 76a3d66ed88ec..5a3b4b7debfe3 100644 --- a/packages/components/src/utils/math.js +++ b/packages/components/src/utils/math.js @@ -79,9 +79,21 @@ export function roundClamp( ) { const baseValue = getNumber( value ); const stepValue = getNumber( step ); - const precision = getPrecision( step ); - const rounded = Math.round( baseValue / stepValue ) * stepValue; - const clampedValue = clamp( rounded, min, max ); + const precision = Math.max( getPrecision( step ), getPrecision( min ) ); + const realMin = min === Infinity ? 0 : min; + + let tare = 0; + if ( realMin % stepValue ) { + tare = realMin; + } + + let maxOnStep = max; + if ( max % stepValue || Math.abs( tare ) ) { + maxOnStep = Math.floor( ( max - tare ) / stepValue ) * stepValue + tare; + } + + const rounded = Math.round( ( baseValue - tare ) / stepValue ) * stepValue; + const clampedValue = clamp( rounded + tare, min, maxOnStep ); return precision ? getNumber( clampedValue.toFixed( precision ) ) diff --git a/packages/components/src/utils/test/math.js b/packages/components/src/utils/test/math.js index 42d4bd5abc56e..5ca36115da0b6 100644 --- a/packages/components/src/utils/test/math.js +++ b/packages/components/src/utils/test/math.js @@ -59,15 +59,15 @@ describe( 'roundClamp', () => { } ); it( 'should clamp with step', () => { - expect( roundClamp( 40, 1, 100, 10 ) ).toBe( 40 ); - expect( roundClamp( 42, 1, 100, 10 ) ).toBe( 40 ); - expect( roundClamp( 45, 1, 100, 10 ) ).toBe( 50 ); - expect( roundClamp( 49, 1, 100, 10 ) ).toBe( 50 ); - expect( roundClamp( 50, 1, 100, 10 ) ).toBe( 50 ); - - expect( roundClamp( 50, 1, 100, 15 ) ).toBe( 45 ); - expect( roundClamp( 50, 1, 100, '15' ) ).toBe( 45 ); - expect( roundClamp( 50, 1, 100, 11 ) ).toBe( 55 ); + expect( roundClamp( 40, 0, 100, 10 ) ).toBe( 40 ); + expect( roundClamp( 42, 0, 100, 10 ) ).toBe( 40 ); + expect( roundClamp( 45, 0, 100, 10 ) ).toBe( 50 ); + expect( roundClamp( 49, 0, 100, 10 ) ).toBe( 50 ); + expect( roundClamp( 50, 0, 100, 10 ) ).toBe( 50 ); + + expect( roundClamp( 50, 0, 100, 15 ) ).toBe( 45 ); + expect( roundClamp( 50, 0, 100, '15' ) ).toBe( 45 ); + expect( roundClamp( 50, 0, 100, 11 ) ).toBe( 55 ); } ); it( 'should clamp with float in step', () => { @@ -77,4 +77,21 @@ describe( 'roundClamp', () => { expect( roundClamp( 40.06, 1, 100, 0.1 ) ).toBe( 40.1 ); expect( roundClamp( 40.123005, 1, 100, 0.001 ) ).toBe( 40.123 ); } ); + + it( 'should round to steps starting from min', () => { + expect( roundClamp( 10, 0.25, 100, 1 ) ).toBe( 10.25 ); + expect( roundClamp( 10, -20.25, 100, 1 ) ).toBe( 9.75 ); + expect( roundClamp( 10.5, 0.05, 100, 0.1 ) ).toBe( 10.45 ); + expect( roundClamp( 10.51, 0.05, 100, 0.1 ) ).toBe( 10.55 ); + } ); + + it( 'should restrain max to a value that is a multiple of step starting from min', () => { + expect( roundClamp( 10, 1, 10, 2 ) ).toBe( 9 ); + expect( roundClamp( 10.125, 0.1, 10, 0.125 ) ).toBe( 9.975 ); + } ); + + it( 'should round with a precision that’s the greater of min and step', () => { + expect( roundClamp( 10.061, 0.01, 20, 0.1 ) ).toBe( 10.11 ); + expect( roundClamp( 10.105, 0.1, 20, 0.01 ) ).toBe( 10.11 ); + } ); } );