[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.
This commit is contained in:
Nicolas Gallagher
2020-09-08 12:32:00 -07:00
parent 397de88137
commit d5ab3770c0
2 changed files with 18 additions and 14 deletions
@@ -45,7 +45,6 @@ export type EventHandlers = $ReadOnly<{|
onClick: (event: ClickEvent) => void, onClick: (event: ClickEvent) => void,
onContextMenu: (event: ClickEvent) => void, onContextMenu: (event: ClickEvent) => void,
onKeyDown: (event: KeyboardEvent) => void, onKeyDown: (event: KeyboardEvent) => void,
onKeyUp: (event: KeyboardEvent) => void,
onResponderGrant: (event: ResponderEvent) => void, onResponderGrant: (event: ResponderEvent) => void,
onResponderMove: (event: ResponderEvent) => void, onResponderMove: (event: ResponderEvent) => void,
onResponderRelease: (event: ResponderEvent) => void, onResponderRelease: (event: ResponderEvent) => void,
@@ -132,7 +131,9 @@ const isValidKeyPress = event => {
const target = event.currentTarget; const target = event.currentTarget;
const role = target.getAttribute('role'); const role = target.getAttribute('role');
const isSpacebar = key === ' ' || key === 'Spacebar'; 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 const DEFAULT_LONG_PRESS_DELAY_MS = 450; // 500 - 50
@@ -297,6 +298,13 @@ export default class PressResponder {
this._receiveSignal(RESPONDER_RELEASE, event); this._receiveSignal(RESPONDER_RELEASE, event);
}; };
const keyupHandler = (event: KeyboardEvent) => {
if (this._touchState !== NOT_RESPONDER) {
end(event);
document.removeEventListener('keyup', keyupHandler);
}
};
return { return {
onStartShouldSetResponder: (): boolean => { onStartShouldSetResponder: (): boolean => {
const { disabled } = this._config; const { disabled } = this._config;
@@ -310,15 +318,9 @@ export default class PressResponder {
if (isValidKeyPress(event)) { if (isValidKeyPress(event)) {
if (this._touchState === NOT_RESPONDER) { if (this._touchState === NOT_RESPONDER) {
start(event, false); start(event, false);
} // Listen to 'keyup' on document to account for situations where
event.stopPropagation(); // focus is moved to another element during 'keydown'.
} document.addEventListener('keyup', keyupHandler);
},
onKeyUp: event => {
if (isValidKeyPress(event)) {
if (this._touchState !== NOT_RESPONDER) {
end(event);
} }
event.stopPropagation(); event.stopPropagation();
} }
@@ -235,16 +235,18 @@ const createDOMProps = (component, props) => {
// for keyboards. // for keyboards.
const onKeyDown = domProps.onKeyDown; const onKeyDown = domProps.onKeyDown;
domProps.onKeyDown = function(e) { domProps.onKeyDown = function(e) {
const key = e.key; const { key, repeat } = e;
const isSpacebarKey = key === ' ' || key === 'Spacebar'; const isSpacebarKey = key === ' ' || key === 'Spacebar';
const isButtonRole = role === 'button' || role === 'menuitem'; const isButtonRole = role === 'button' || role === 'menuitem';
if (onKeyDown != null) { if (onKeyDown != null) {
onKeyDown(e); onKeyDown(e);
} }
if (key === 'Enter') { if (!repeat && key === 'Enter') {
onClick(e); onClick(e);
} else if (isSpacebarKey && isButtonRole) { } else if (isSpacebarKey && isButtonRole) {
onClick(e); if (!repeat) {
onClick(e);
}
// Prevent spacebar scrolling the window // Prevent spacebar scrolling the window
e.preventDefault(); e.preventDefault();
} }