From a8a0c1763bb43070b4fd684dddb06582d9cf2748 Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Wed, 19 Jul 2023 14:09:43 -0700 Subject: [PATCH] [fix] Pressable button click regression Update keyboard handling code to avoid calling preventDefault for 'keydown' events occuring on a native button element. This was causing native 'click' event to be cancelled on buttons. Fix #2560 --- .../exports/Pressable/__tests__/index-test.js | 29 +++++++++++++++++++ .../modules/usePressEvents/PressResponder.js | 26 +++++++++++------ 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/packages/react-native-web/src/exports/Pressable/__tests__/index-test.js b/packages/react-native-web/src/exports/Pressable/__tests__/index-test.js index 5ff19e65..7efc90a9 100644 --- a/packages/react-native-web/src/exports/Pressable/__tests__/index-test.js +++ b/packages/react-native-web/src/exports/Pressable/__tests__/index-test.js @@ -246,6 +246,35 @@ describe('components/Pressable', () => { expect(container.firstChild).toMatchSnapshot(); }); + test('press interaction as button (keyboard)', () => { + const onPress = jest.fn(); + const preventDefault = jest.fn(); + const ref = React.createRef(); + + function TestCase() { + return ( + { + onPress(e); + }} + ref={ref} + role="button" + /> + ); + } + + act(() => { + render(); + }); + const target = createEventTarget(ref.current); + act(() => { + target.keydown({ key: ' ', preventDefault }); + jest.runAllTimers(); + }); + // Calling preventDefault prevents native 'click' event dispatch + expect(preventDefault).not.toHaveBeenCalled(); + }); + describe('prop "ref"', () => { test('value is set', () => { const ref = jest.fn(); diff --git a/packages/react-native-web/src/modules/usePressEvents/PressResponder.js b/packages/react-native-web/src/modules/usePressEvents/PressResponder.js index 2b03203d..857db94b 100644 --- a/packages/react-native-web/src/modules/usePressEvents/PressResponder.js +++ b/packages/react-native-web/src/modules/usePressEvents/PressResponder.js @@ -116,11 +116,15 @@ const Transitions = Object.freeze({ } }); +const getElementRole = (element) => element.getAttribute('role'); + +const getElementType = (element) => element.tagName.toLowerCase(); + const isActiveSignal = (signal) => signal === RESPONDER_ACTIVE_PRESS_START || signal === RESPONDER_ACTIVE_LONG_PRESS_START; -const isButtonRole = (element) => element.getAttribute('role') === 'button'; +const isButtonRole = (element) => getElementRole(element) === 'button'; const isPressStartSignal = (signal) => signal === RESPONDER_INACTIVE_PRESS_START || @@ -132,10 +136,10 @@ const isTerminalSignal = (signal) => const isValidKeyPress = (event) => { const { key, target } = event; - const role = target.getAttribute('role'); const isSpacebar = key === ' ' || key === 'Spacebar'; - - return key === 'Enter' || (isSpacebar && role === 'button'); + const isButtonish = + getElementType(target) === 'button' || isButtonRole(target); + return key === 'Enter' || (isSpacebar && isButtonish); }; const DEFAULT_LONG_PRESS_DELAY_MS = 450; // 500 - 50 @@ -307,7 +311,7 @@ export default class PressResponder { document.removeEventListener('keyup', keyupHandler); const role = target.getAttribute('role'); - const elementType = target.tagName.toLowerCase(); + const elementType = getElementType(target); const isNativeInteractiveElement = role === 'link' || @@ -345,11 +349,15 @@ export default class PressResponder { // focus is moved to another element during 'keydown'. document.addEventListener('keyup', keyupHandler); } - const role = target.getAttribute('role'); const isSpacebarKey = key === ' ' || key === 'Spacebar'; - const isButtonRole = role === 'button' || role === 'menuitem'; - if (isSpacebarKey && isButtonRole) { - // Prevent spacebar scrolling the window + const role = getElementRole(target); + const isButtonLikeRole = role === 'button' || role === 'menuitem'; + if ( + isSpacebarKey && + isButtonLikeRole && + getElementType(target) !== 'button' + ) { + // Prevent spacebar scrolling the window if using non-native button event.preventDefault(); } event.stopPropagation();