From a275eac56e21b8f3631bcd03ace86c07a3b0c0ba Mon Sep 17 00:00:00 2001 From: Sophie Alpert Date: Mon, 16 Apr 2018 13:03:50 -0700 Subject: [PATCH] Clean up some grossness in ScrollResponder Summary: Still gross but less gross. Reviewed By: sebmarkbage Differential Revision: D7107180 fbshipit-source-id: 31f1639a8f44e4ab247c338001a4a5c9b4b83cdf --- Libraries/Components/ScrollResponder.js | 28 ++++++------ Libraries/Components/TextInput/TextInput.js | 44 +++++++++++-------- .../Components/TextInput/TextInputState.js | 16 ++++++- 3 files changed, 54 insertions(+), 34 deletions(-) diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js index 9d60c462e..6c926b9d4 100644 --- a/Libraries/Components/ScrollResponder.js +++ b/Libraries/Components/ScrollResponder.js @@ -23,7 +23,6 @@ const performanceNow = require('fbjs/lib/performanceNow'); const warning = require('fbjs/lib/warning'); const { ScrollViewManager } = require('NativeModules'); -const { getInstanceFromNode } = require('ReactNativeComponentTree'); /** * Mixin that can be integrated in order to handle scrolling that plays well @@ -114,15 +113,6 @@ type State = { }; type Event = Object; -function isTagInstanceOfTextInput(tag) { - const instance = getInstanceFromNode(tag); - return instance && instance.viewConfig && ( - instance.viewConfig.uiViewClassName === 'AndroidTextInput' || - instance.viewConfig.uiViewClassName === 'RCTMultilineTextInputView' || - instance.viewConfig.uiViewClassName === 'RCTSinglelineTextInputView' - ); -} - const ScrollResponderMixin = { mixins: [Subscribable.Mixin], scrollResponderMixinGetInitialState: function(): State { @@ -196,17 +186,27 @@ const ScrollResponderMixin = { * Invoke this from an `onStartShouldSetResponderCapture` event. */ scrollResponderHandleStartShouldSetResponderCapture: function(e: Event): boolean { - // First see if we want to eat taps while the keyboard is up + // The scroll view should receive taps instead of its descendants if: + // * it is already animating/decelerating + if (this.scrollResponderIsAnimating()) { + return true; + } + + // * the keyboard is up, keyboardShouldPersistTaps is 'never' (the default), + // 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.currentlyFocusedField(); const {keyboardShouldPersistTaps} = this.props; const keyboardNeverPersistTaps = !keyboardShouldPersistTaps || keyboardShouldPersistTaps === 'never'; if (keyboardNeverPersistTaps && - currentlyFocusedTextInput != null && - !isTagInstanceOfTextInput(e.target)) { + currentlyFocusedTextInput != null + /* && !TextInputState.isTextInput(e.target) */) { return true; } - return this.scrollResponderIsAnimating(); + + return false; }, /** diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index eb838174f..fd67b8ec5 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -192,10 +192,6 @@ const DataDetectorTypes = [ const TextInput = createReactClass({ displayName: 'TextInput', - statics: { - /* TODO(brentvatne) docs are needed for this */ - State: TextInputState, - }, propTypes: { ...ViewPropTypes, @@ -667,24 +663,30 @@ const TextInput = createReactClass({ componentDidMount: function() { this._lastNativeText = this.props.value; - if (!this.context.focusEmitter) { + const tag = ReactNative.findNodeHandle(this._inputRef); + if (tag != null) { + // tag is null only in unit tests + TextInputState.registerInput(tag); + } + + if (this.context.focusEmitter) { + this._focusSubscription = this.context.focusEmitter.addListener( + 'focus', + el => { + if (this === el) { + this.requestAnimationFrame(this.focus); + } else if (this.isFocused()) { + this.blur(); + } + }, + ); + if (this.props.autoFocus) { + this.context.onFocusRequested(this); + } + } else { if (this.props.autoFocus) { this.requestAnimationFrame(this.focus); } - return; - } - this._focusSubscription = this.context.focusEmitter.addListener( - 'focus', - el => { - if (this === el) { - this.requestAnimationFrame(this.focus); - } else if (this.isFocused()) { - this.blur(); - } - }, - ); - if (this.props.autoFocus) { - this.context.onFocusRequested(this); } }, @@ -693,6 +695,10 @@ const TextInput = createReactClass({ if (this.isFocused()) { this.blur(); } + const tag = ReactNative.findNodeHandle(this._inputRef); + if (tag != null) { + TextInputState.unregisterInput(tag); + } }, getChildContext(): ViewChildContext { diff --git a/Libraries/Components/TextInput/TextInputState.js b/Libraries/Components/TextInput/TextInputState.js index afcca82bf..cb41eef0c 100644 --- a/Libraries/Components/TextInput/TextInputState.js +++ b/Libraries/Components/TextInput/TextInputState.js @@ -16,6 +16,8 @@ const Platform = require('Platform'); const UIManager = require('UIManager'); +const inputs = new Set(); + const TextInputState = { /** * Internal state @@ -68,7 +70,19 @@ const TextInputState = { ); } } - } + }, + + registerInput: function(textFieldID: number) { + inputs.add(textFieldID); + }, + + unregisterInput: function(textFieldID: number) { + inputs.delete(textFieldID); + }, + + isTextInput: function(textFieldID: number) { + return inputs.has(textFieldID); + }, }; module.exports = TextInputState;