Skip to content

Commit 0c56c09

Browse files
Saadnajmiclaude
andcommitted
Add onAuxClick event for handling right-click on macOS
Implements onAuxClick following the same pattern as onDoubleClick, wired end-to-end from native rightMouseUp: through C++ event emitters to JS. Also adds a `button` field to MouseEvent and filters non-primary button clicks from triggering onPress in Pressability. Inspired by microsoft/react-native-windows#15920. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 92a0916 commit 0c56c09

File tree

11 files changed

+57
-1
lines changed

11 files changed

+57
-1
lines changed

packages/react-native/Libraries/Components/View/ReactNativeViewAttributes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const UIView = {
4444
mouseDownCanMoveWindow: true,
4545
enableFocusRing: true,
4646
focusable: true,
47+
onAuxClick: true,
4748
onMouseEnter: true,
4849
onMouseLeave: true,
4950
onDoubleClick: true,

packages/react-native/Libraries/Components/View/ViewPropTypes.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ export interface ViewPropsMacOS {
139139
enableFocusRing?: boolean | undefined;
140140
onMouseEnter?: ((event: MouseEvent) => void) | undefined;
141141
onMouseLeave?: ((event: MouseEvent) => void) | undefined;
142+
onAuxClick?: ((event: MouseEvent) => void) | undefined;
142143
onDoubleClick?: ((event: MouseEvent) => void) | undefined;
143144
onDragEnter?: ((event: DragEvent) => void) | undefined;
144145
onDragLeave?: ((event: DragEvent) => void) | undefined;

packages/react-native/Libraries/Components/View/ViewPropTypes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ export type KeyboardEventProps = $ReadOnly<{|
130130
type MouseEventProps = $ReadOnly<{
131131
onMouseEnter?: ?(event: MouseEvent) => void,
132132
onMouseLeave?: ?(event: MouseEvent) => void,
133+
onAuxClick?: ?(event: MouseEvent) => void, // [macOS]
133134
onDoubleClick?: ?(event: MouseEvent) => void, // [macOS]
134135
}>;
135136

packages/react-native/Libraries/NativeComponent/BaseViewConfig.macos.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ const bubblingEventTypes = {
3434

3535
const directEventTypes = {
3636
...PlatformBaseViewConfigIos.directEventTypes,
37+
topAuxClick: {
38+
registrationName: 'onAuxClick',
39+
},
3740
topDoubleClick: {
3841
registrationName: 'onDoubleClick',
3942
},
@@ -70,6 +73,7 @@ const validAttributesForNonEventProps = {
7073

7174
// Props for bubbling and direct events
7275
const validAttributesForEventProps = ConditionallyIgnoredEventHandlers({
76+
onAuxClick: true,
7377
onBlur: true,
7478
onDoubleClick: true,
7579
onDragEnter: true,

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,14 @@ export default class Pressability {
554554
return;
555555
}
556556

557+
// [macOS Only fire onPress for primary (left) mouse button clicks.
558+
// Non-primary buttons (right, middle) should not trigger onPress.
559+
const button = event?.nativeEvent?.button;
560+
if (button != null && button !== 0) {
561+
return;
562+
}
563+
// macOS]
564+
557565
// for non-pointer click events (e.g. accessibility clicks), we should only dispatch when we're the "real" target
558566
// in particular, we shouldn't respond to clicks from nested pressables
559567
if (event?.currentTarget !== event?.target) {

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2032,9 +2032,10 @@ - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
20322032
MouseEnter,
20332033
MouseLeave,
20342034
DoubleClick,
2035+
AuxClick,
20352036
};
20362037

2037-
- (void)emitMouseEvent:(MouseEventType)eventType
2038+
- (void)emitMouseEvent:(MouseEventType)eventType button:(int)button
20382039
{
20392040
if (!_eventEmitter) {
20402041
return;
@@ -2054,6 +2055,7 @@ - (void)emitMouseEvent:(MouseEventType)eventType
20542055
.ctrlKey = static_cast<bool>(modifierFlags & NSEventModifierFlagControl),
20552056
.shiftKey = static_cast<bool>(modifierFlags & NSEventModifierFlagShift),
20562057
.metaKey = static_cast<bool>(modifierFlags & NSEventModifierFlagCommand),
2058+
.button = button,
20572059
};
20582060

20592061
switch (eventType) {
@@ -2068,9 +2070,18 @@ - (void)emitMouseEvent:(MouseEventType)eventType
20682070
case DoubleClick:
20692071
_eventEmitter->onDoubleClick(mouseEvent);
20702072
break;
2073+
2074+
case AuxClick:
2075+
_eventEmitter->onAuxClick(mouseEvent);
2076+
break;
20712077
}
20722078
}
20732079

2080+
- (void)emitMouseEvent:(MouseEventType)eventType
2081+
{
2082+
[self emitMouseEvent:eventType button:0];
2083+
}
2084+
20742085
- (void)updateMouseOverIfNeeded
20752086
{
20762087
// When an enclosing scrollview is scrolled using the scrollWheel or trackpad,
@@ -2191,6 +2202,16 @@ - (void)mouseUp:(NSEvent *)event
21912202
[super mouseUp:event];
21922203
}
21932204
}
2205+
2206+
- (void)rightMouseUp:(NSEvent *)event
2207+
{
2208+
BOOL hasAuxClickEventHandler = _props->hostPlatformEvents[HostPlatformViewEvents::Offset::AuxClick];
2209+
if (hasAuxClickEventHandler) {
2210+
[self emitMouseEvent:AuxClick button:2];
2211+
} else {
2212+
[super rightMouseUp:event];
2213+
}
2214+
}
21942215
#endif // macOS]
21952216

21962217
- (SharedTouchEventEmitter)touchEventEmitterAtPoint:(CGPoint)point

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ static jsi::Object mouseEventPayload(jsi::Runtime& runtime, const MouseEvent& ev
6363
payload.setProperty(runtime, "ctrlKey", event.ctrlKey);
6464
payload.setProperty(runtime, "shiftKey", event.shiftKey);
6565
payload.setProperty(runtime, "metaKey", event.metaKey);
66+
payload.setProperty(runtime, "button", event.button);
6667
return payload;
6768
};
6869

@@ -84,6 +85,12 @@ void HostPlatformViewEventEmitter::onDoubleClick(const MouseEvent& mouseEvent) c
8485
});
8586
}
8687

88+
void HostPlatformViewEventEmitter::onAuxClick(const MouseEvent& mouseEvent) const {
89+
dispatchEvent("auxClick", [mouseEvent](jsi::Runtime& runtime) {
90+
return mouseEventPayload(runtime, mouseEvent);
91+
});
92+
}
93+
8794
#pragma mark - Drag and Drop Events
8895

8996
jsi::Value HostPlatformViewEventEmitter::dataTransferPayload(

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
@@ -34,6 +34,7 @@ class HostPlatformViewEventEmitter : public BaseViewEventEmitter {
3434
void onMouseEnter(MouseEvent const& mouseEvent) const;
3535
void onMouseLeave(MouseEvent const& mouseEvent) const;
3636
void onDoubleClick(MouseEvent const& mouseEvent) const;
37+
void onAuxClick(MouseEvent const& mouseEvent) const;
3738

3839
#pragma mark - Drag and Drop Events
3940

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
@@ -30,6 +30,7 @@ struct HostPlatformViewEvents {
3030
MouseEnter = 4,
3131
MouseLeave = 5,
3232
DoubleClick = 6,
33+
AuxClick = 7,
3334
};
3435

3536
constexpr bool operator[](const Offset offset) const {
@@ -74,6 +75,8 @@ static inline HostPlatformViewEvents convertRawProp(
7475
convertRawProp(context, rawProps, "onMouseLeave", sourceValue[Offset::MouseLeave], defaultValue[Offset::MouseLeave]);
7576
result[Offset::DoubleClick] =
7677
convertRawProp(context, rawProps, "onDoubleClick", sourceValue[Offset::DoubleClick], defaultValue[Offset::DoubleClick]);
78+
result[Offset::AuxClick] =
79+
convertRawProp(context, rawProps, "onAuxClick", sourceValue[Offset::AuxClick], defaultValue[Offset::AuxClick]);
7780

7881
return result;
7982
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ struct MouseEvent {
5454
* A flag indicating if the meta key is pressed.
5555
*/
5656
bool metaKey{false};
57+
58+
/**
59+
* The button number that was pressed when the mouse event was fired:
60+
* 0 = primary button (usually the left button)
61+
* 1 = auxiliary button (usually the middle/wheel button)
62+
* 2 = secondary button (usually the right button)
63+
*/
64+
int button{0};
5765
};
5866

5967
struct DataTransferFile {

0 commit comments

Comments
 (0)