diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js index e3049071b..26c3630a3 100644 --- a/Libraries/Components/ScrollResponder.js +++ b/Libraries/Components/ScrollResponder.js @@ -19,6 +19,7 @@ var Subscribable = require('Subscribable'); var TextInputState = require('TextInputState'); var UIManager = require('UIManager'); +var { getInstanceFromNode } = require('ReactNativeComponentTree'); var { ScrollViewManager } = require('NativeModules'); var invariant = require('fbjs/lib/invariant'); @@ -112,6 +113,15 @@ type State = { }; type Event = Object; +function isTagInstanceOfTextInput(tag) { + var instance = getInstanceFromNode(tag); + return instance && instance.viewConfig && ( + instance.viewConfig.uiViewClassName === 'AndroidTextInput' || + instance.viewConfig.uiViewClassName === 'RCTTextView' || + instance.viewConfig.uiViewClassName === 'RCTTextField' + ); +} + var ScrollResponderMixin = { mixins: [Subscribable.Mixin], scrollResponderMixinGetInitialState: function(): State { @@ -172,7 +182,7 @@ var ScrollResponderMixin = { * that *doesn't* give priority to nested views (hence the capture phase): * * - Currently animating. - * - Tapping anywhere that is not the focused input, while the keyboard is + * - Tapping anywhere that is not a text input, while the keyboard is * up (which should dismiss the keyboard). * * Invoke this from an `onStartShouldSetResponderCapture` event. @@ -182,7 +192,7 @@ var ScrollResponderMixin = { var currentlyFocusedTextInput = TextInputState.currentlyFocusedField(); if (!this.props.keyboardShouldPersistTaps && currentlyFocusedTextInput != null && - e.target !== currentlyFocusedTextInput) { + !isTagInstanceOfTextInput(e.target)) { return true; } return this.scrollResponderIsAnimating(); diff --git a/Libraries/ReactNative/requireNativeComponent.js b/Libraries/ReactNative/requireNativeComponent.js index 903f3b239..098d024bf 100644 --- a/Libraries/ReactNative/requireNativeComponent.js +++ b/Libraries/ReactNative/requireNativeComponent.js @@ -101,7 +101,7 @@ function requireNativeComponent( return createReactNativeComponentClass(viewConfig); } -var TypeToDifferMap = { +const TypeToDifferMap = { // iOS Types CATransform3D: matricesDiffer, CGPoint: pointsDiffer, @@ -115,7 +115,7 @@ function processColorArray(colors: []): [] { return colors && colors.map(processColor); } -var TypeToProcessorMap = { +const TypeToProcessorMap = { // iOS Types CGColor: processColor, CGColorArray: processColorArray, diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java index 4ef74fafc..6243edd19 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java @@ -200,6 +200,18 @@ public class TouchTargetHelper { float eventCoords[], View view) { PointerEvents pointerEvents = view instanceof ReactPointerEventsView ? ((ReactPointerEventsView) view).getPointerEvents() : PointerEvents.AUTO; + + // Views that are disabled should never be the target of pointer events. However, their children + // can be because some views (SwipeRefreshLayout) use enabled but still have children that can + // be valid targets. + if (!view.isEnabled()) { + if (pointerEvents == PointerEvents.AUTO) { + pointerEvents = PointerEvents.BOX_NONE; + } else if (pointerEvents == PointerEvents.BOX_ONLY) { + pointerEvents = PointerEvents.NONE; + } + } + if (pointerEvents == PointerEvents.NONE) { // This view and its children can't be the target return null; @@ -209,7 +221,7 @@ public class TouchTargetHelper { return view; } else if (pointerEvents == PointerEvents.BOX_NONE) { - // This view can't be the target, but its children might + // This view can't be the target, but its children might. if (view instanceof ViewGroup) { View targetView = findTouchTargetView(eventCoords, (ViewGroup) view); if (targetView != view) {