diff --git a/Libraries/Components/Touchable/TouchableBounce.js b/Libraries/Components/Touchable/TouchableBounce.js index 04817aef02bacf..5dd3a46afc8a74 100644 --- a/Libraries/Components/Touchable/TouchableBounce.js +++ b/Libraries/Components/Touchable/TouchableBounce.js @@ -184,6 +184,12 @@ const TouchableBounce = ((createReactClass({ nativeID={this.props.nativeID} testID={this.props.testID} hitSlop={this.props.hitSlop} + clickable={ + this.props.clickable !== false && + this.props.onPress !== undefined && + !this.props.disabled + } + onClick={this.touchableHandlePress} onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder} onResponderTerminationRequest={ this.touchableHandleResponderTerminationRequest diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js index fa0e083fa8a985..1b5afa6ef70b87 100644 --- a/Libraries/Components/Touchable/TouchableHighlight.js +++ b/Libraries/Components/Touchable/TouchableHighlight.js @@ -422,6 +422,10 @@ const TouchableHighlight = ((createReactClass({ nextFocusLeft={this.props.nextFocusLeft} nextFocusRight={this.props.nextFocusRight} nextFocusUp={this.props.nextFocusUp} + clickable={ + this.props.clickable !== false && this.props.onPress !== undefined + } + onClick={this.touchableHandlePress} onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder} onResponderTerminationRequest={ this.touchableHandleResponderTerminationRequest diff --git a/Libraries/Components/Touchable/TouchableNativeFeedback.android.js b/Libraries/Components/Touchable/TouchableNativeFeedback.android.js index 77b6e84ea7c60f..f23223a4ac2ec9 100644 --- a/Libraries/Components/Touchable/TouchableNativeFeedback.android.js +++ b/Libraries/Components/Touchable/TouchableNativeFeedback.android.js @@ -325,6 +325,11 @@ const TouchableNativeFeedback = createReactClass({ nextFocusRight: this.props.nextFocusRight, nextFocusUp: this.props.nextFocusUp, hasTVPreferredFocus: this.props.hasTVPreferredFocus, + clickable: + this.props.clickable !== false && + this.props.onPress !== undefined && + !this.props.disabled, + onClick: this.touchableHandlePress, onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder, onResponderTerminationRequest: this .touchableHandleResponderTerminationRequest, diff --git a/Libraries/Components/Touchable/TouchableOpacity.js b/Libraries/Components/Touchable/TouchableOpacity.js index 98a2c5d0afd099..b26b34b999deb8 100644 --- a/Libraries/Components/Touchable/TouchableOpacity.js +++ b/Libraries/Components/Touchable/TouchableOpacity.js @@ -324,6 +324,10 @@ const TouchableOpacity = ((createReactClass({ hasTVPreferredFocus={this.props.hasTVPreferredFocus} tvParallaxProperties={this.props.tvParallaxProperties} hitSlop={this.props.hitSlop} + clickable={ + this.props.clickable !== false && this.props.onPress !== undefined + } + onClick={this.touchableHandlePress} onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder} onResponderTerminationRequest={ this.touchableHandleResponderTerminationRequest diff --git a/Libraries/Components/Touchable/TouchableWithoutFeedback.js b/Libraries/Components/Touchable/TouchableWithoutFeedback.js index a78de287e41e77..a19311e66f580d 100755 --- a/Libraries/Components/Touchable/TouchableWithoutFeedback.js +++ b/Libraries/Components/Touchable/TouchableWithoutFeedback.js @@ -267,6 +267,9 @@ const TouchableWithoutFeedback = ((createReactClass({ return (React: any).cloneElement(child, { ...overrides, accessible: this.props.accessible !== false, + clickable: + this.props.clickable !== false && this.props.onPress !== undefined, + onClick: this.touchableHandlePress, onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder, onResponderTerminationRequest: this .touchableHandleResponderTerminationRequest, diff --git a/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableHighlight-test.js.snap b/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableHighlight-test.js.snap index 34d71cc12a523e..00b32eed85ee5c 100644 --- a/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableHighlight-test.js.snap +++ b/Libraries/Components/Touchable/__tests__/__snapshots__/TouchableHighlight-test.js.snap @@ -3,7 +3,9 @@ exports[`TouchableHighlight renders correctly 1`] = ` void, |}>; type IOSViewProps = $ReadOnly<{| diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java index 3e6354f041080e..43c504a4174a48 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java @@ -6,10 +6,9 @@ package com.facebook.react.uimanager; import android.graphics.Color; -import android.os.Build; -import androidx.core.view.ViewCompat; import android.view.View; import android.view.ViewParent; +import androidx.core.view.ViewCompat; import com.facebook.react.R; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.uimanager.AccessibilityDelegateUtil.AccessibilityRole; diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java index 36c3162cb77b52..6ca9380ba6422b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java @@ -83,6 +83,7 @@ .put("topLoadingStart", MapBuilder.of(rn, "onLoadingStart")) .put("topSelectionChange", MapBuilder.of(rn, "onSelectionChange")) .put("topMessage", MapBuilder.of(rn, "onMessage")) + .put("topClick", MapBuilder.of(rn, "onClick")) // Scroll events are added as per task T22348735. // Subject for further improvement. .put("topScrollBeginDrag", MapBuilder.of(rn, "onScrollBeginDrag")) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java index 31178517860232..69dce016617fde 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java @@ -12,6 +12,7 @@ import android.os.Build; import android.view.View; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; +import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.common.MapBuilder; @@ -21,10 +22,12 @@ import com.facebook.react.uimanager.PointerEvents; import com.facebook.react.uimanager.Spacing; import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.react.uimanager.ViewProps; import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactPropGroup; +import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.yoga.YogaConstants; import java.util.Locale; import java.util.Map; @@ -223,6 +226,28 @@ public void setCollapsable(ReactViewGroup view, boolean collapsable) { // handled in NativeViewHierarchyOptimizer } + @ReactProp(name = "clickable") + public void setClickable(final ReactViewGroup view, boolean clickable) { + if (clickable) { + view.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + final EventDispatcher mEventDispatcher = ((ReactContext)view.getContext()).getNativeModule(UIManagerModule.class) + .getEventDispatcher(); + mEventDispatcher.dispatchEvent(new ViewGroupClickEvent(view.getId())); + }}); + + // Clickable elements are focusable. On API 26, this is taken care by setClickable. + // Explicitly calling setFocusable here for backward compatibility. + view.setFocusable(true /*isFocusable*/); + } + else { + view.setOnClickListener(null); + view.setClickable(false); + } + } + @ReactProp(name = ViewProps.OVERFLOW) public void setOverflow(ReactViewGroup view, String overflow) { view.setOverflow(overflow); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ViewGroupClickEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ViewGroupClickEvent.java new file mode 100644 index 00000000000000..16fa8a2cce6005 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ViewGroupClickEvent.java @@ -0,0 +1,32 @@ +package com.facebook.react.views.view; + + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.uimanager.events.Event; +import com.facebook.react.uimanager.events.RCTEventEmitter; + +/** + * Represents a Click on the ReactViewGroup + */ +public class ViewGroupClickEvent extends Event { + private static final String EVENT_NAME = "topClick"; + + public ViewGroupClickEvent(int viewId) { + super(viewId); + } + + @Override + public String getEventName() { + return EVENT_NAME; + } + + @Override + public boolean canCoalesce() { + return false; + } + + @Override + public void dispatch(RCTEventEmitter rctEventEmitter) { + rctEventEmitter.receiveEvent(getViewTag(), getEventName(), Arguments.createMap()); + } +}