From fd1e82a10f80c76bd37e7eef65c0dc2220c6bb8d Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Tue, 23 Aug 2022 02:25:11 -0700 Subject: [PATCH] Do not eat taps/clicks in ScrollView when soft-keyboard is detached from viewport Summary: If currently focused on a TextInput, clicking an item in a ScrollView takes two clicks. This is because of `keyboardShouldPersistTaps`, which will fire despite a lack of keyboard events on Android due to special-casing. This behavior is jarring in scenarios like VR where the soft keyboard is detached from the application. This change avoids eating taps, in this case, where a soft keyboard is open but not inset. Reviewed By: genkikondo Differential Revision: D38529237 fbshipit-source-id: a10c5dbf04e6288e0e9e0c805215054bc883339f --- Libraries/Components/ScrollView/ScrollView.js | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 768b48c6b242d2..bd04e7d61fc52f 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -1510,6 +1510,11 @@ class ScrollView extends React.Component { return false; } + // Let presses through if the soft keyboard is detached from the viewport + if (this._softKeyboardIsDetached()) { + return false; + } + if ( keyboardNeverPersistTaps && this._keyboardIsDismissible() && @@ -1548,6 +1553,15 @@ class ScrollView extends React.Component { return hasFocusedTextInput && softKeyboardMayBeOpen; }; + /** + * Whether an open soft keyboard is present which does not overlap the + * viewport. E.g. for a VR soft-keyboard which is detached from the app + * viewport. + */ + _softKeyboardIsDetached: () => boolean = () => { + return this._keyboardMetrics != null && this._keyboardMetrics.height === 0; + }; + /** * Invoke this from an `onTouchEnd` event. * @@ -1556,6 +1570,21 @@ class ScrollView extends React.Component { _handleTouchEnd: (e: PressEvent) => void = (e: PressEvent) => { const nativeEvent = e.nativeEvent; this._isTouching = nativeEvent.touches.length !== 0; + + const {keyboardShouldPersistTaps} = this.props; + const keyboardNeverPersistsTaps = + !keyboardShouldPersistTaps || keyboardShouldPersistTaps === 'never'; + + // Dismiss the keyboard now if we didn't become responder in capture phase + // to eat presses, but still want to dismiss on interaction. + if ( + this._softKeyboardIsDetached() && + this._keyboardIsDismissible() && + keyboardNeverPersistsTaps + ) { + TextInputState.blurTextInput(TextInputState.currentlyFocusedInput()); + } + this.props.onTouchEnd && this.props.onTouchEnd(e); };