diff --git a/packages/number/number.test.mts b/packages/number/number.test.mts index d7fd4e1fa..4ef7e16f9 100644 --- a/packages/number/number.test.mts +++ b/packages/number/number.test.mts @@ -350,4 +350,20 @@ describe('number prompt', () => { expect(getScreen()).toMatchInlineSnapshot(`"Q: Answer must be: 2 === _2_"`); }); + + it('handle decimal steps', async () => { + const { answer, events, getScreen } = await render(number, { + message: 'Enter a decimal number', + min: 1, + max: 100, + step: 0.01, + }); + + expect(getScreen()).toMatchInlineSnapshot(`"? Enter a decimal number"`); + + events.type('10.01'); + events.keypress('enter'); + await expect(answer).resolves.toEqual(10.01); + expect(getScreen()).toMatchInlineSnapshot(`"? Enter a decimal number 10.01"`); + }); }); diff --git a/packages/number/src/index.mts b/packages/number/src/index.mts index 3e0ce4954..f75e3d1fd 100644 --- a/packages/number/src/index.mts +++ b/packages/number/src/index.mts @@ -21,6 +21,14 @@ type NumberConfig = { theme?: PartialDeep; }; +function isStepOf(value: number, step: number, min: number): boolean { + const valuePow = value * Math.pow(10, 6); + const stepPow = step * Math.pow(10, 6); + const minPow = min * Math.pow(10, 6); + + return (valuePow - (Number.isFinite(min) ? minPow : 0)) % stepPow === 0; +} + function validateNumber( value: number | undefined, { @@ -37,7 +45,7 @@ function validateNumber( return false; } else if (value < min || value > max) { return `Value must be between ${min} and ${max}`; - } else if (step !== 'any' && (value - (Number.isFinite(min) ? min : 0)) % step !== 0) { + } else if (step !== 'any' && !isStepOf(value, step, min)) { return `Value must be a multiple of ${step}${Number.isFinite(min) ? ` starting from ${min}` : ''}`; }