Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import com.facebook.react.views.scroll.ScrollEventType;
import com.reactnativecommunity.webview.events.TopCustomMenuSelectionEvent;
import com.reactnativecommunity.webview.events.TopMessageEvent;
import com.reactnativecommunity.webview.events.TopNativeTouchEndEvent;

import org.json.JSONException;
import org.json.JSONObject;
Expand Down Expand Up @@ -130,6 +131,39 @@ public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}

/**
* Observe-only hook into the WebView's own MotionEvent stream. On a terminal action
* (UP / POINTER_UP / CANCEL) we emit {@code onNativeTouchEnd} so JS can detect a finger
* lift even when Chromium's native text-selection controller has seized the gesture and
* suppressed the usual RN/DOM release signals. Always returns super — no behavior change.
*/
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
String action = null;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_UP:
action = "up";
break;
case MotionEvent.ACTION_CANCEL:
action = "cancel";
break;
case MotionEvent.ACTION_POINTER_UP:
action = "pointerUp";
break;
default:
break;
}
if (action != null) {
WritableMap data = Arguments.createMap();
data.putString("action", action);
data.putInt("pointerCount", event.getPointerCount());
data.putDouble("x", event.getX());
data.putDouble("y", event.getY());
dispatchEvent(this, new TopNativeTouchEndEvent(RNCWebViewWrapper.getReactTagFromWebView(this), data));
}
return super.dispatchTouchEvent(event);
}

@Override
protected void onSizeChanged(int w, int h, int ow, int oh) {
super.onSizeChanged(w, h, ow, oh);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.reactnativecommunity.webview.events

import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.RCTEventEmitter

/**
* Event emitted from the WebView's own MotionEvent stream when a gesture ends
* (ACTION_UP / ACTION_POINTER_UP / ACTION_CANCEL). Unlike the RN touch responder,
* this observes the WebView's native touch stream directly.
*/
class TopNativeTouchEndEvent(viewId: Int, private val mEventData: WritableMap) :
Event<TopNativeTouchEndEvent>(viewId) {
companion object {
const val EVENT_NAME = "topNativeTouchEnd"
}

override fun getEventName(): String = EVENT_NAME

override fun canCoalesce(): Boolean = false

override fun getCoalescingKey(): Short = 0

override fun dispatch(rctEventEmitter: RCTEventEmitter) =
rctEventEmitter.receiveEvent(viewTag, eventName, mEventData)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.facebook.react.viewmanagers.RNCWebViewManagerInterface;
import com.facebook.react.views.scroll.ScrollEventType;
import com.reactnativecommunity.webview.events.TopCustomMenuSelectionEvent;
import com.reactnativecommunity.webview.events.TopNativeTouchEndEvent;
import com.reactnativecommunity.webview.events.SubResourceErrorEvent;
import com.reactnativecommunity.webview.events.TopHttpErrorEvent;
import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
Expand Down Expand Up @@ -547,6 +548,7 @@ public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
export.put(TopHttpErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onHttpError"));
export.put(TopRenderProcessGoneEvent.EVENT_NAME, MapBuilder.of("registrationName", "onRenderProcessGone"));
export.put(TopCustomMenuSelectionEvent.EVENT_NAME, MapBuilder.of("registrationName", "onCustomMenuSelection"));
export.put(TopNativeTouchEndEvent.EVENT_NAME, MapBuilder.of("registrationName", "onNativeTouchEnd"));
export.put(TopOpenWindowEvent.EVENT_NAME, MapBuilder.of("registrationName", "onOpenWindow"));
return export;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.views.scroll.ScrollEventType;
import com.reactnativecommunity.webview.events.TopCustomMenuSelectionEvent;
import com.reactnativecommunity.webview.events.TopNativeTouchEndEvent;
import com.reactnativecommunity.webview.events.SubResourceErrorEvent;
import com.reactnativecommunity.webview.events.TopHttpErrorEvent;
import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
Expand Down Expand Up @@ -305,6 +306,7 @@ public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
export.put(TopHttpErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onHttpError"));
export.put(TopRenderProcessGoneEvent.EVENT_NAME, MapBuilder.of("registrationName", "onRenderProcessGone"));
export.put(TopCustomMenuSelectionEvent.EVENT_NAME, MapBuilder.of("registrationName", "onCustomMenuSelection"));
export.put(TopNativeTouchEndEvent.EVENT_NAME, MapBuilder.of("registrationName", "onNativeTouchEnd"));
export.put(TopOpenWindowEvent.EVENT_NAME, MapBuilder.of("registrationName", "onOpenWindow"));
return export;
}
Expand Down
3 changes: 3 additions & 0 deletions apple/RNCWebViewImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
@property (nonatomic, copy) NSArray<NSDictionary *> * _Nullable menuItems;
@property (nonatomic, copy) NSArray<NSString *> * _Nullable suppressMenuItems;
@property (nonatomic, copy) RCTDirectEventBlock onCustomMenuSelection;
// Android-only event; declared here as a no-op so the shared codegen interface
// stays consistent across platforms. Never fired on iOS/macOS.
@property (nonatomic, copy) RCTDirectEventBlock onNativeTouchEnd;
#if !TARGET_OS_OSX
@property (nonatomic, assign) WKDataDetectorTypes dataDetectorTypes;
@property (nonatomic, weak) UIRefreshControl * _Nullable refreshControl;
Expand Down
2 changes: 2 additions & 0 deletions apple/RNCWebViewManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ - (RNCView *)view
RCT_CUSTOM_VIEW_PROPERTY(hasOnOpenWindowEvent, BOOL, RNCWebViewImpl) {}

RCT_EXPORT_VIEW_PROPERTY(onCustomMenuSelection, RCTDirectEventBlock)
// Android-only event; exported here as a no-op for codegen-interface parity. Never fired on iOS.
RCT_EXPORT_VIEW_PROPERTY(onNativeTouchEnd, RCTDirectEventBlock)
RCT_CUSTOM_VIEW_PROPERTY(pullToRefreshEnabled, BOOL, RNCWebViewImpl) {
view.pullToRefreshEnabled = json == nil ? false : [RCTConvert BOOL: json];
}
Expand Down
7 changes: 7 additions & 0 deletions lib/RNCWebViewNativeComponent.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export type WebViewMessageEvent = Readonly<{
export type WebViewOpenWindowEvent = Readonly<{
targetUrl: string;
}>;
export type WebViewNativeTouchEndEvent = Readonly<{
action: 'up' | 'cancel' | 'pointerUp';
pointerCount: Int32;
x: Double;
y: Double;
}>;
export type WebViewHttpErrorEvent = Readonly<{
url: string;
loading: boolean;
Expand Down Expand Up @@ -129,6 +135,7 @@ export interface NativeProps extends ViewProps {
nestedScrollEnabled?: boolean;
onContentSizeChange?: DirectEventHandler<WebViewNativeEvent>;
onRenderProcessGone?: DirectEventHandler<WebViewRenderProcessGoneEvent>;
onNativeTouchEnd?: DirectEventHandler<WebViewNativeTouchEndEvent>;
overScrollMode?: string;
saveFormDataDisabled?: boolean;
scalesPageToFit?: WithDefault<boolean, true>;
Expand Down
2 changes: 1 addition & 1 deletion lib/RNCWebViewNativeComponent.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading