diff --git a/packages/components/src/number-control/README.md b/packages/components/src/number-control/README.md index 2e57f69fbc36c4..b3c62ebbcc687d 100644 --- a/packages/components/src/number-control/README.md +++ b/packages/components/src/number-control/README.md @@ -97,6 +97,20 @@ The minimum `value` allowed. - Required: No - Default: `-Infinity` +### onChange + +Callback fired whenever the value of the input changes. + +The callback receives two arguments: + +1. `newValue`: the new value of the input +2. `extra`: an object containing, under the `event` key, the original browser event. + +Note that the value received as the first argument of the callback is _not_ guaranteed to be a valid value (e.g. it could be outside of the range defined by the [`min`, `max`] props). In order to check the value's validity, check the `event.target.validity.valid` property from the callback's second argument. + +- Type: `(newValue, extra) => void` +- Required: No + ### required If `true` enforces a valid number within the control's min/max range. If `false` allows an empty string as a valid value. diff --git a/packages/components/src/number-control/stories/index.js b/packages/components/src/number-control/stories/index.js index 25106b0c364b83..9c8496cc908c2b 100644 --- a/packages/components/src/number-control/stories/index.js +++ b/packages/components/src/number-control/stories/index.js @@ -23,6 +23,7 @@ export default { function Example() { const [ value, setValue ] = useState( '0' ); + const [ isValidValue, setIsValidValue ] = useState( true ); const props = { disabled: boolean( 'disabled', false ), @@ -32,18 +33,24 @@ function Example() { label: text( 'label', 'Number' ), min: number( 'min', 0 ), max: number( 'max', 100 ), - placeholder: text( 'placeholder', 0 ), + placeholder: text( 'placeholder', '0' ), required: boolean( 'required', false ), shiftStep: number( 'shiftStep', 10 ), - step: text( 'step', 1 ), + step: text( 'step', '1' ), }; return ( - setValue( v ) } - /> + <> + { + setValue( v ); + setIsValidValue( extra.event.target.validity.valid ); + } } + /> +

Is valid? { isValidValue ? 'Yes' : 'No' }

+ ); } diff --git a/packages/components/src/number-control/test/index.js b/packages/components/src/number-control/test/index.js index 669ff9a9af29c6..7be791db232ec2 100644 --- a/packages/components/src/number-control/test/index.js +++ b/packages/components/src/number-control/test/index.js @@ -94,6 +94,37 @@ describe( 'NumberControl', () => { expect( spy ).toHaveBeenNthCalledWith( 2, 4 ); } ); } ); + + it( 'should call onChange callback when value is not valid', () => { + const spy = jest.fn(); + render( + + spy( v, extra.event.target.validity.valid ) + } + /> + ); + + const input = getInput(); + input.focus(); + fireEvent.change( input, { target: { value: 14 } } ); + + expect( input.value ).toBe( '14' ); + + fireKeyDown( { keyCode: ENTER } ); + + expect( input.value ).toBe( '10' ); + + expect( spy ).toHaveBeenCalledTimes( 2 ); + + // First call: invalid, unclamped value + expect( spy ).toHaveBeenNthCalledWith( 1, '14', false ); + // Second call: valid, clamped value + expect( spy ).toHaveBeenNthCalledWith( 2, 10, true ); + } ); } ); describe( 'Validation', () => {