Skip to content

Commit 4b76671

Browse files
Saadnajmiclaude
andcommitted
Add onClick, fix onAuxClick, and prevent Pressable style change on right-click
- Add onClick event for left single-click on plain Views (macOS) - Fix onAuxClick by adding missing VIEW_EVENT_CASE_MACOS entries in setProp - Override rightMouseDown: to prevent context menu modal from consuming rightMouseUp: - Add otherMouseDown:/otherMouseUp: for middle-click (button=1) via onAuxClick - Add pointerType:"mouse" to mouse event payload for Pressability guard - Add onAuxClick to Paper architecture (RCTView.h, RCTViewManager.m) - Guard Pressability _activate against non-primary buttons to prevent visual pressed state on right-click/middle-click Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d741a6a commit 4b76671

File tree

8 files changed

+65
-3
lines changed

8 files changed

+65
-3
lines changed

packages/react-native/Libraries/Pressability/Pressability.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -821,15 +821,22 @@ export default class Pressability {
821821
const isPrevActive = isActiveSignal(prevState);
822822
const isNextActive = isActiveSignal(nextState);
823823

824-
if (!isPrevActive && isNextActive) {
824+
// [macOS Don't activate press visual feedback for non-primary mouse buttons
825+
// (e.g. right-click, middle-click). They should fire onAuxClick, not onPress.
826+
const isPrimaryButton = this._isDefaultPressButton(
827+
getTouchFromPressEvent(event).button,
828+
);
829+
// macOS]
830+
831+
if (!isPrevActive && isNextActive && isPrimaryButton /* [macOS] */) {
825832
this._activate(event);
826833
} else if (isPrevActive && !isNextActive) {
827834
this._deactivate(event);
828835
}
829836

830837
if (isPressInSignal(prevState) && signal === 'RESPONDER_RELEASE') {
831838
// If we never activated (due to delays), activate and deactivate now.
832-
if (!isNextActive && !isPrevActive) {
839+
if (!isNextActive && !isPrevActive && isPrimaryButton /* [macOS] */) {
833840
this._activate(event);
834841
this._deactivate(event);
835842
}

packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2031,6 +2031,7 @@ - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
20312031
enum MouseEventType {
20322032
MouseEnter,
20332033
MouseLeave,
2034+
Click,
20342035
DoubleClick,
20352036
AuxClick,
20362037
};
@@ -2067,6 +2068,10 @@ - (void)emitMouseEvent:(MouseEventType)eventType button:(int)button
20672068
_eventEmitter->onMouseLeave(mouseEvent);
20682069
break;
20692070

2071+
case Click:
2072+
_eventEmitter->onClick(mouseEvent);
2073+
break;
2074+
20702075
case DoubleClick:
20712076
_eventEmitter->onDoubleClick(mouseEvent);
20722077
break;
@@ -2196,13 +2201,27 @@ - (void)mouseExited:(NSEvent *)event
21962201
- (void)mouseUp:(NSEvent *)event
21972202
{
21982203
BOOL hasDoubleClickEventHandler = _props->hostPlatformEvents[HostPlatformViewEvents::Offset::DoubleClick];
2204+
BOOL hasClickEventHandler = _props->hostPlatformEvents[HostPlatformViewEvents::Offset::Click];
21992205
if (hasDoubleClickEventHandler && event.clickCount == 2) {
2200-
[self emitMouseEvent :DoubleClick];
2206+
[self emitMouseEvent:DoubleClick];
2207+
} else if (hasClickEventHandler && event.clickCount == 1) {
2208+
[self emitMouseEvent:Click];
22012209
} else {
22022210
[super mouseUp:event];
22032211
}
22042212
}
22052213

2214+
- (void)rightMouseDown:(NSEvent *)event
2215+
{
2216+
// Accept rightMouseDown to prevent the default NSView behavior of passing it
2217+
// up the responder chain (which can trigger a context menu modal loop that
2218+
// consumes rightMouseUp).
2219+
BOOL hasAuxClickEventHandler = _props->hostPlatformEvents[HostPlatformViewEvents::Offset::AuxClick];
2220+
if (!hasAuxClickEventHandler) {
2221+
[super rightMouseDown:event];
2222+
}
2223+
}
2224+
22062225
- (void)rightMouseUp:(NSEvent *)event
22072226
{
22082227
BOOL hasAuxClickEventHandler = _props->hostPlatformEvents[HostPlatformViewEvents::Offset::AuxClick];
@@ -2212,6 +2231,25 @@ - (void)rightMouseUp:(NSEvent *)event
22122231
[super rightMouseUp:event];
22132232
}
22142233
}
2234+
2235+
- (void)otherMouseDown:(NSEvent *)event
2236+
{
2237+
// Accept otherMouseDown so that otherMouseUp is delivered to this view.
2238+
BOOL hasAuxClickEventHandler = _props->hostPlatformEvents[HostPlatformViewEvents::Offset::AuxClick];
2239+
if (!hasAuxClickEventHandler) {
2240+
[super otherMouseDown:event];
2241+
}
2242+
}
2243+
2244+
- (void)otherMouseUp:(NSEvent *)event
2245+
{
2246+
BOOL hasAuxClickEventHandler = _props->hostPlatformEvents[HostPlatformViewEvents::Offset::AuxClick];
2247+
if (hasAuxClickEventHandler) {
2248+
[self emitMouseEvent:AuxClick button:1];
2249+
} else {
2250+
[super otherMouseUp:event];
2251+
}
2252+
}
22152253
#endif // macOS]
22162254

22172255
- (SharedTouchEventEmitter)touchEventEmitterAtPoint:(CGPoint)point

packages/react-native/React/Views/RCTView.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ extern const UIAccessibilityTraits SwitchAccessibilityTrait;
145145
/**
146146
* (Experimental and unused for Paper) Pointer event handlers.
147147
*/
148+
@property (nonatomic, assign) RCTBubblingEventBlock onAuxClick;
148149
@property (nonatomic, assign) RCTBubblingEventBlock onClick;
149150
@property (nonatomic, assign) RCTBubblingEventBlock onPointerCancel;
150151
@property (nonatomic, assign) RCTBubblingEventBlock onPointerDown;

packages/react-native/React/Views/RCTViewManager.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,7 @@ - (void) updateAccessibilityRole:(RCTView *)view withDefaultView:(RCTView *)defa
881881
RCT_CUSTOM_VIEW_PROPERTY(onTouchCancel, BOOL, RCTView) {}
882882

883883
// Experimental/WIP Pointer Events (not yet ready for use)
884+
RCT_EXPORT_VIEW_PROPERTY(onAuxClick, RCTBubblingEventBlock)
884885
RCT_EXPORT_VIEW_PROPERTY(onClick, RCTBubblingEventBlock)
885886
RCT_EXPORT_VIEW_PROPERTY(onPointerCancel, RCTBubblingEventBlock)
886887
RCT_EXPORT_VIEW_PROPERTY(onPointerDown, RCTBubblingEventBlock)

packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ static jsi::Object mouseEventPayload(jsi::Runtime& runtime, const MouseEvent& ev
6464
payload.setProperty(runtime, "shiftKey", event.shiftKey);
6565
payload.setProperty(runtime, "metaKey", event.metaKey);
6666
payload.setProperty(runtime, "button", event.button);
67+
// pointerType lets Pressability's onClick guard distinguish native click
68+
// events from accessibility/responder-based clicks, avoiding double onPress.
69+
payload.setProperty(runtime, "pointerType", "mouse");
6770
return payload;
6871
};
6972

@@ -79,6 +82,12 @@ void HostPlatformViewEventEmitter::onMouseLeave(const MouseEvent& mouseEvent) co
7982
});
8083
}
8184

85+
void HostPlatformViewEventEmitter::onClick(const MouseEvent& mouseEvent) const {
86+
dispatchEvent("click", [mouseEvent](jsi::Runtime& runtime) {
87+
return mouseEventPayload(runtime, mouseEvent);
88+
});
89+
}
90+
8291
void HostPlatformViewEventEmitter::onDoubleClick(const MouseEvent& mouseEvent) const {
8392
dispatchEvent("doubleClick", [mouseEvent](jsi::Runtime& runtime) {
8493
return mouseEventPayload(runtime, mouseEvent);

packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEventEmitter.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class HostPlatformViewEventEmitter : public BaseViewEventEmitter {
3333

3434
void onMouseEnter(MouseEvent const& mouseEvent) const;
3535
void onMouseLeave(MouseEvent const& mouseEvent) const;
36+
void onClick(MouseEvent const& mouseEvent) const;
3637
void onDoubleClick(MouseEvent const& mouseEvent) const;
3738
void onAuxClick(MouseEvent const& mouseEvent) const;
3839

packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewEvents.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ struct HostPlatformViewEvents {
3131
MouseLeave = 5,
3232
DoubleClick = 6,
3333
AuxClick = 7,
34+
Click = 8,
3435
};
3536

3637
constexpr bool operator[](const Offset offset) const {
@@ -77,6 +78,8 @@ static inline HostPlatformViewEvents convertRawProp(
7778
convertRawProp(context, rawProps, "onDoubleClick", sourceValue[Offset::DoubleClick], defaultValue[Offset::DoubleClick]);
7879
result[Offset::AuxClick] =
7980
convertRawProp(context, rawProps, "onAuxClick", sourceValue[Offset::AuxClick], defaultValue[Offset::AuxClick]);
81+
result[Offset::Click] =
82+
convertRawProp(context, rawProps, "onClick", sourceValue[Offset::Click], defaultValue[Offset::Click]);
8083

8184
return result;
8285
}

packages/react-native/ReactCommon/react/renderer/components/view/platform/macos/react/renderer/components/view/HostPlatformViewProps.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ void HostPlatformViewProps::setProp(
144144
VIEW_EVENT_CASE_MACOS(MouseEnter);
145145
VIEW_EVENT_CASE_MACOS(MouseLeave);
146146
VIEW_EVENT_CASE_MACOS(DoubleClick);
147+
VIEW_EVENT_CASE_MACOS(AuxClick);
148+
VIEW_EVENT_CASE_MACOS(Click);
147149
RAW_SET_PROP_SWITCH_CASE_BASIC(focusable);
148150
RAW_SET_PROP_SWITCH_CASE_BASIC(enableFocusRing);
149151
RAW_SET_PROP_SWITCH_CASE_BASIC(keyDownEvents);

0 commit comments

Comments
 (0)