diff --git a/packages/@react-aria/numberfield/src/useNumberField.ts b/packages/@react-aria/numberfield/src/useNumberField.ts index 5c117bc7767..06e2365e6c9 100644 --- a/packages/@react-aria/numberfield/src/useNumberField.ts +++ b/packages/@react-aria/numberfield/src/useNumberField.ts @@ -110,7 +110,11 @@ export function useNumberField(props: AriaNumberFieldProps, state: NumberFieldSt let inputId = useId(id); let {focusProps} = useFocus({ onBlur() { - commitAndAnnounce(); + // Only commit if the input value has actually changed from the state's input value. + // This prevents validation from being reset when the user focuses and blurs without editing. + if (inputRef.current?.value !== inputValue) { + commitAndAnnounce(); + } } }); diff --git a/packages/react-aria-components/test/NumberField.test.js b/packages/react-aria-components/test/NumberField.test.js index ae7fb7c4130..cd3516072e1 100644 --- a/packages/react-aria-components/test/NumberField.test.js +++ b/packages/react-aria-components/test/NumberField.test.js @@ -13,7 +13,7 @@ jest.mock('@react-aria/live-announcer'); import {act, pointerMap, render} from '@react-spectrum/test-utils-internal'; import {announce} from '@react-aria/live-announcer'; -import {Button, FieldError, Group, Input, Label, NumberField, NumberFieldContext, Text} from '../'; +import {Button, FieldError, Form, Group, Input, Label, NumberField, NumberFieldContext, Text} from '../'; import React from 'react'; import userEvent from '@testing-library/user-event'; @@ -250,4 +250,40 @@ describe('NumberField', () => { await user.keyboard('{Enter}'); expect(input).toHaveValue('200'); }); + + it('should not reset validation errors on blur when value has not changed', async () => { + let {getByRole} = render( +
+ + + + + + + + + +
+ ); + + let input = getByRole('textbox'); + let numberfield = input.closest('.react-aria-NumberField'); + + // Validation error should be displayed + expect(numberfield).toHaveAttribute('data-invalid'); + expect(input).toHaveAttribute('aria-describedby'); + expect(document.getElementById(input.getAttribute('aria-describedby').split(' ')[0])).toHaveTextContent('This field has an error.'); + + // Focus the field without changing the value + act(() => { input.focus(); }); + expect(numberfield).toHaveAttribute('data-invalid'); + + // Blur the field without changing the value + act(() => { input.blur(); }); + + // Validation error should still be displayed because the value didn't change + expect(numberfield).toHaveAttribute('data-invalid'); + expect(input).toHaveAttribute('aria-describedby'); + expect(document.getElementById(input.getAttribute('aria-describedby').split(' ')[0])).toHaveTextContent('This field has an error.'); + }); });