From d5ab3770c0d8c65251db2924add03dd1ab78b81f Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Tue, 8 Sep 2020 12:32:00 -0700 Subject: [PATCH] [fix] PressResponder keyboard edge-case Fixes the state-machine logic for the press responder when focus is moved away from the target element during a 'keydown' event. --- .../hooks/usePressEvents/PressResponder.js | 24 ++++++++++--------- .../src/modules/createDOMProps/index.js | 8 ++++--- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/react-native-web/src/hooks/usePressEvents/PressResponder.js b/packages/react-native-web/src/hooks/usePressEvents/PressResponder.js index dd622f35..327cdc20 100644 --- a/packages/react-native-web/src/hooks/usePressEvents/PressResponder.js +++ b/packages/react-native-web/src/hooks/usePressEvents/PressResponder.js @@ -45,7 +45,6 @@ export type EventHandlers = $ReadOnly<{| onClick: (event: ClickEvent) => void, onContextMenu: (event: ClickEvent) => void, onKeyDown: (event: KeyboardEvent) => void, - onKeyUp: (event: KeyboardEvent) => void, onResponderGrant: (event: ResponderEvent) => void, onResponderMove: (event: ResponderEvent) => void, onResponderRelease: (event: ResponderEvent) => void, @@ -132,7 +131,9 @@ const isValidKeyPress = event => { const target = event.currentTarget; const role = target.getAttribute('role'); const isSpacebar = key === ' ' || key === 'Spacebar'; - return key === 'Enter' || (isSpacebar && (role === 'button' || role === 'menuitem')); + return ( + !event.repeat && (key === 'Enter' || (isSpacebar && (role === 'button' || role === 'menuitem'))) + ); }; const DEFAULT_LONG_PRESS_DELAY_MS = 450; // 500 - 50 @@ -297,6 +298,13 @@ export default class PressResponder { this._receiveSignal(RESPONDER_RELEASE, event); }; + const keyupHandler = (event: KeyboardEvent) => { + if (this._touchState !== NOT_RESPONDER) { + end(event); + document.removeEventListener('keyup', keyupHandler); + } + }; + return { onStartShouldSetResponder: (): boolean => { const { disabled } = this._config; @@ -310,15 +318,9 @@ export default class PressResponder { if (isValidKeyPress(event)) { if (this._touchState === NOT_RESPONDER) { start(event, false); - } - event.stopPropagation(); - } - }, - - onKeyUp: event => { - if (isValidKeyPress(event)) { - if (this._touchState !== NOT_RESPONDER) { - end(event); + // Listen to 'keyup' on document to account for situations where + // focus is moved to another element during 'keydown'. + document.addEventListener('keyup', keyupHandler); } event.stopPropagation(); } diff --git a/packages/react-native-web/src/modules/createDOMProps/index.js b/packages/react-native-web/src/modules/createDOMProps/index.js index c4c34dec..1c5e65f5 100644 --- a/packages/react-native-web/src/modules/createDOMProps/index.js +++ b/packages/react-native-web/src/modules/createDOMProps/index.js @@ -235,16 +235,18 @@ const createDOMProps = (component, props) => { // for keyboards. const onKeyDown = domProps.onKeyDown; domProps.onKeyDown = function(e) { - const key = e.key; + const { key, repeat } = e; const isSpacebarKey = key === ' ' || key === 'Spacebar'; const isButtonRole = role === 'button' || role === 'menuitem'; if (onKeyDown != null) { onKeyDown(e); } - if (key === 'Enter') { + if (!repeat && key === 'Enter') { onClick(e); } else if (isSpacebarKey && isButtonRole) { - onClick(e); + if (!repeat) { + onClick(e); + } // Prevent spacebar scrolling the window e.preventDefault(); }