From 7ec38b9f444492990ba6e91bb920c695deeb6515 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Thu, 19 Nov 2020 13:10:13 -0800 Subject: [PATCH] Avoid eating clicks/taps into ScrollView when using physical keyboard (#30374) Summary: This is an extension of https://github.com/facebook/react-native/issues/29798 which was reverted due to cases where the soft keyboard could not be dismissed. ## Changelog [General] [Fixed] - Avoid eating clicks/taps into ScrollView when using physical keyboard Pull Request resolved: https://github.com/facebook/react-native/pull/30374 Test Plan: Validated with iOS simulator that taps on default ScrollView will dismiss soft keyboard and be eaten if open, but taps are not eaten when emulating a connected physical keyboard. Reviewed By: kacieb Differential Revision: D24935077 Pulled By: lyahdav fbshipit-source-id: 19d9cf64547e40a35f9363896e3abbdccb95b546 --- Libraries/Components/ScrollResponder.js | 32 +++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js index 7aef247b62c869..c6b0794e2f4b7a 100644 --- a/Libraries/Components/ScrollResponder.js +++ b/Libraries/Components/ScrollResponder.js @@ -186,7 +186,7 @@ const ScrollResponderMixin = { if ( this.props.keyboardShouldPersistTaps === 'handled' && - currentlyFocusedInput != null && + this.scrollResponderKeyboardIsDismissible() && e.target !== currentlyFocusedInput ) { return true; @@ -223,7 +223,6 @@ const ScrollResponderMixin = { // and a new touch starts with a non-textinput target (in which case the // first tap should be sent to the scroll view and dismiss the keyboard, // then the second tap goes to the actual interior view) - const currentlyFocusedTextInput = TextInputState.currentlyFocusedInput(); const {keyboardShouldPersistTaps} = this.props; const keyboardNeverPersistTaps = !keyboardShouldPersistTaps || keyboardShouldPersistTaps === 'never'; @@ -240,7 +239,7 @@ const ScrollResponderMixin = { if ( keyboardNeverPersistTaps && - currentlyFocusedTextInput != null && + this.scrollResponderKeyboardIsDismissible() && e.target != null && !TextInputState.isTextInput(e.target) ) { @@ -250,6 +249,31 @@ const ScrollResponderMixin = { return false; }, + /** + * Do we consider there to be a dismissible soft-keyboard open? + */ + scrollResponderKeyboardIsDismissible: function(): boolean { + const currentlyFocusedInput = TextInputState.currentlyFocusedInput(); + + // We cannot dismiss the keyboard without an input to blur, even if a soft + // keyboard is open (e.g. when keyboard is open due to a native component + // not participating in TextInputState). It's also possible that the + // currently focused input isn't a TextInput (such as by calling ref.focus + // on a non-TextInput). + const hasFocusedTextInput = + currentlyFocusedInput != null && + TextInputState.isTextInput(currentlyFocusedInput); + + // Even if an input is focused, we may not have a keyboard to dismiss. E.g + // when using a physical keyboard. Ensure we have an event for an opened + // keyboard, except on Android where setting windowSoftInputMode to + // adjustNone leads to missing keyboard events. + const softKeyboardMayBeOpen = + this.keyboardWillOpenTo != null || Platform.OS === 'android'; + + return hasFocusedTextInput && softKeyboardMayBeOpen; + }, + /** * Invoke this from an `onResponderReject` event. * @@ -324,7 +348,7 @@ const ScrollResponderMixin = { if ( this.props.keyboardShouldPersistTaps !== true && this.props.keyboardShouldPersistTaps !== 'always' && - currentlyFocusedTextInput != null && + this.scrollResponderKeyboardIsDismissible() && e.target !== currentlyFocusedTextInput && !this.state.observedScrollSinceBecomingResponder && !this.state.becameResponderWhileAnimating