diff --git a/packages/react/src/number-field/decrement/NumberFieldDecrement.test.tsx b/packages/react/src/number-field/decrement/NumberFieldDecrement.test.tsx index e246cbdda1..ce9209c563 100644 --- a/packages/react/src/number-field/decrement/NumberFieldDecrement.test.tsx +++ b/packages/react/src/number-field/decrement/NumberFieldDecrement.test.tsx @@ -287,4 +287,32 @@ describe('', () => { expect(input).to.have.value('99'); }); + + it('always decrements on quick touch (touchend that occurs before TOUCH_TIMEOUT)', async () => { + await render( + + + + , + ); + + const button = screen.getByRole('button'); + const input = screen.getByRole('textbox'); + + fireEvent.touchStart(button); + fireEvent.mouseEnter(button); + fireEvent.pointerDown(button, { pointerType: 'touch' }); + fireEvent.touchEnd(button); + fireEvent.click(button, { detail: 1 }); + + expect(input).to.have.value('-1'); + + fireEvent.touchStart(button); + // No mouseenter occurs after the first focus + fireEvent.pointerDown(button, { pointerType: 'touch' }); + fireEvent.touchEnd(button); + fireEvent.click(button, { detail: 1 }); + + expect(input).to.have.value('-2'); + }); }); diff --git a/packages/react/src/number-field/increment/NumberFieldIncrement.test.tsx b/packages/react/src/number-field/increment/NumberFieldIncrement.test.tsx index a322f2c575..3152466e2c 100644 --- a/packages/react/src/number-field/increment/NumberFieldIncrement.test.tsx +++ b/packages/react/src/number-field/increment/NumberFieldIncrement.test.tsx @@ -287,4 +287,32 @@ describe('', () => { expect(input).to.have.value('101'); }); + + it('always increments on quick touch (touchend that occurs before TOUCH_TIMEOUT)', async () => { + await render( + + + + , + ); + + const button = screen.getByRole('button'); + const input = screen.getByRole('textbox'); + + fireEvent.touchStart(button); + fireEvent.mouseEnter(button); + fireEvent.pointerDown(button, { pointerType: 'touch' }); + fireEvent.click(button, { detail: 1 }); + fireEvent.touchEnd(button); + + expect(input).to.have.value('1'); + + fireEvent.touchStart(button); + // No mouseenter occurs after the first focus + fireEvent.pointerDown(button, { pointerType: 'touch' }); + fireEvent.click(button, { detail: 1 }); + fireEvent.touchEnd(button); + + expect(input).to.have.value('2'); + }); }); diff --git a/packages/react/src/number-field/root/useNumberFieldRoot.ts b/packages/react/src/number-field/root/useNumberFieldRoot.ts index 00f9780049..98eef85a07 100644 --- a/packages/react/src/number-field/root/useNumberFieldRoot.ts +++ b/packages/react/src/number-field/root/useNumberFieldRoot.ts @@ -127,6 +127,8 @@ export function useNumberFieldRoot( const unsubscribeFromGlobalContextMenuRef = React.useRef<() => void>(() => {}); const isTouchingButtonRef = React.useRef(false); const hasTouchedInputRef = React.useRef(false); + const ignoreClickRef = React.useRef(false); + const pointerTypeRef = React.useRef<'mouse' | 'touch' | 'pen' | ''>(''); useEnhancedEffect(() => { if (validityData.initialValue === null && value !== validityData.initialValue) { @@ -431,7 +433,7 @@ export function useNumberFieldRoot( event.defaultPrevented || isDisabled || // If it's not a keyboard/virtual click, ignore. - event.detail !== 0 + (pointerTypeRef.current === 'touch' ? ignoreClickRef.current : event.detail !== 0) ) { return; } @@ -449,6 +451,8 @@ export function useNumberFieldRoot( return; } + pointerTypeRef.current = event.pointerType; + ignoreClickRef.current = false; isPressedRef.current = true; incrementDownCoordsRef.current = { x: event.clientX, y: event.clientY }; @@ -466,6 +470,7 @@ export function useNumberFieldRoot( const moves = movesAfterTouchRef.current; movesAfterTouchRef.current = 0; if (moves < MAX_POINTER_MOVES_AFTER_TOUCH) { + ignoreClickRef.current = true; startAutoChange(isIncrement); } else { stopAutoChange();