Skip to content
Closed
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
17 changes: 9 additions & 8 deletions .github/workflows/microsoft-backport.yml
Original file line number Diff line number Diff line change
Expand Up @@ -200,17 +200,10 @@ jobs:
contents: write
pull-requests: write
steps:
- name: Generate GitHub App token
uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}

- name: Find linked backport PRs
id: find-backports
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
GH_TOKEN: ${{ github.token }}
HEAD_BRANCH: ${{ github.event.pull_request.head.ref }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
Expand All @@ -234,6 +227,14 @@ jobs:
echo "$BACKPORT_PRS" > /tmp/backport-prs.txt
fi

- name: Generate GitHub App token
if: steps.find-backports.outputs.found == 'true'
uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}

- name: Checkout
if: steps.find-backports.outputs.found == 'true'
uses: actions/checkout@v4
Expand Down
29 changes: 29 additions & 0 deletions docsite/api/view-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,34 @@ Example:

---

### `onAuxClick`

Fired when the user clicks on the view with a non-primary button (e.g., right-click or middle-click). This follows the [W3C `auxclick` event](https://developer.mozilla.org/en-US/docs/Web/API/Element/auxclick_event) specification.

**Event Data:** Mouse event with the following properties:
- `clientX`: Horizontal position in the target view
- `clientY`: Vertical position in the target view
- `screenX`: Horizontal position in the window
- `screenY`: Vertical position in the window
- `altKey`: Whether Alt/Option key is pressed
- `ctrlKey`: Whether Control key is pressed
- `shiftKey`: Whether Shift key is pressed
- `metaKey`: Whether Command key is pressed
- `button`: The button number that was pressed (2 for right-click)

Example:
```javascript
<View onAuxClick={(event) => {
console.log('Right clicked, button:', event.nativeEvent.button);
}}>
<Text>Right click me</Text>
</View>
```

> **Note:** Right-clicking a `Pressable` will fire `onAuxClick` but will **not** trigger `onPress`. Only primary (left) button clicks trigger `onPress`.

---

### `onDoubleClick`

Fired when the user double-clicks on the view.
Expand All @@ -139,6 +167,7 @@ Fired when the user double-clicks on the view.
- `ctrlKey`: Whether Control key is pressed
- `shiftKey`: Whether Shift key is pressed
- `metaKey`: Whether Command key is pressed
- `button`: The button number that was pressed (0 for left-click)

Example:
```javascript
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const UIView = {
mouseDownCanMoveWindow: true,
enableFocusRing: true,
focusable: true,
onAuxClick: true,
onMouseEnter: true,
onMouseLeave: true,
onDoubleClick: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ type MouseEventProps = $ReadOnly<{

// Experimental/Work in Progress Pointer Event Callbacks (not yet ready for use)
type PointerEventProps = $ReadOnly<{
onAuxClick?: ?(event: PointerEvent) => void,
onAuxClickCapture?: ?(event: PointerEvent) => void,
onClick?: ?(event: PointerEvent) => void,
onClickCapture?: ?(event: PointerEvent) => void,
onPointerEnter?: ?(event: PointerEvent) => void,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ const bubblingEventTypes = {
},

// Experimental/Work in Progress Pointer Events (not yet ready for use)
topAuxClick: {
phasedRegistrationNames: {
captured: 'onAuxClickCapture',
bubbled: 'onAuxClick',
},
},
topClick: {
phasedRegistrationNames: {
captured: 'onClickCapture',
Expand Down Expand Up @@ -394,6 +400,8 @@ const validAttributesForEventProps = ConditionallyIgnoredEventHandlers({
onTouchCancel: true,

// Pointer events
onAuxClick: true,
onAuxClickCapture: true,
onClick: true,
onClickCapture: true,
onPointerUp: true,
Expand Down
31 changes: 29 additions & 2 deletions packages/react-native/Libraries/Pressability/Pressability.js
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,16 @@ export default class Pressability {
_touchActivateTime: ?number;
_touchState: TouchState = 'NOT_RESPONDER';

// [macOS
/**
* Returns true if the button from a press event is the default (primary/left)
* button. A button value of 0 or undefined is considered the default button.
*/
_isDefaultPressButton(button: ?number): boolean {
return !button;
}
// macOS]

constructor(config: PressabilityConfig) {
this.configure(config);
}
Expand Down Expand Up @@ -554,6 +564,13 @@ export default class Pressability {
return;
}

// [macOS Only fire onPress for primary (left) mouse button clicks.
// Non-primary buttons (right, middle) should not trigger onPress.
if (!this._isDefaultPressButton(event?.nativeEvent?.button)) {
return;
}
// macOS]

// for non-pointer click events (e.g. accessibility clicks), we should only dispatch when we're the "real" target
// in particular, we shouldn't respond to clicks from nested pressables
if (event?.currentTarget !== event?.target) {
Expand Down Expand Up @@ -791,7 +808,12 @@ export default class Pressability {

if (isPressInSignal(prevState) && signal === 'LONG_PRESS_DETECTED') {
const {onLongPress} = this._config;
if (onLongPress != null) {
if (
onLongPress != null &&
this._isDefaultPressButton(
getTouchFromPressEvent(event).button,
) /* [macOS] */
) {
onLongPress(event);
}
}
Expand All @@ -812,7 +834,12 @@ export default class Pressability {
this._deactivate(event);
}
const {onLongPress, onPress, android_disableSound} = this._config;
if (onPress != null) {
if (
onPress != null &&
this._isDefaultPressButton(
getTouchFromPressEvent(event).button,
) /* [macOS] */
) {
const isPressCanceledByLongPress =
onLongPress != null && prevState === 'RESPONDER_ACTIVE_LONG_PRESS_IN';
if (!isPressCanceledByLongPress) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2032,9 +2032,10 @@ - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
MouseEnter,
MouseLeave,
DoubleClick,
AuxClick,
};

- (void)emitMouseEvent:(MouseEventType)eventType
- (void)emitMouseEvent:(MouseEventType)eventType button:(int)button
{
if (!_eventEmitter) {
return;
Expand All @@ -2054,6 +2055,7 @@ - (void)emitMouseEvent:(MouseEventType)eventType
.ctrlKey = static_cast<bool>(modifierFlags & NSEventModifierFlagControl),
.shiftKey = static_cast<bool>(modifierFlags & NSEventModifierFlagShift),
.metaKey = static_cast<bool>(modifierFlags & NSEventModifierFlagCommand),
.button = button,
};

switch (eventType) {
Expand All @@ -2068,9 +2070,18 @@ - (void)emitMouseEvent:(MouseEventType)eventType
case DoubleClick:
_eventEmitter->onDoubleClick(mouseEvent);
break;

case AuxClick:
_eventEmitter->onAuxClick(mouseEvent);
break;
}
}

- (void)emitMouseEvent:(MouseEventType)eventType
{
[self emitMouseEvent:eventType button:0];
}

- (void)updateMouseOverIfNeeded
{
// When an enclosing scrollview is scrolled using the scrollWheel or trackpad,
Expand Down Expand Up @@ -2191,6 +2202,16 @@ - (void)mouseUp:(NSEvent *)event
[super mouseUp:event];
}
}

- (void)rightMouseUp:(NSEvent *)event
{
BOOL hasAuxClickEventHandler = _props->hostPlatformEvents[HostPlatformViewEvents::Offset::AuxClick];
if (hasAuxClickEventHandler) {
[self emitMouseEvent:AuxClick button:2];
} else {
[super rightMouseUp:event];
}
}
#endif // macOS]

- (SharedTouchEventEmitter)touchEventEmitterAtPoint:(CGPoint)point
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -703,8 +703,12 @@ - (void)_dispatchActivePointers:(std::vector<ActivePointer>)activePointers event
}
case RCTPointerEventTypeEnd: {
eventEmitter->onPointerUp(pointerEvent);
if (pointerEvent.isPrimary && pointerEvent.button == 0 && IsPointerWithinInitialTree(activePointer)) {
eventEmitter->onClick(std::move(pointerEvent));
if (pointerEvent.isPrimary && pointerEvent.button == 0) {
if (IsPointerWithinInitialTree(activePointer)) {
eventEmitter->onClick(std::move(pointerEvent));
}
} else if (IsPointerWithinInitialTree(activePointer)) {
eventEmitter->onAuxClick(std::move(pointerEvent));
}
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ void TouchEventEmitter::onTouchCancel(TouchEvent event) const {
"touchCancel", std::move(event), RawEvent::Category::ContinuousEnd);
}

void TouchEventEmitter::onAuxClick(PointerEvent event) const {
dispatchPointerEvent("auxClick", std::move(event), RawEvent::Category::Discrete);
}

void TouchEventEmitter::onClick(PointerEvent event) const {
dispatchPointerEvent("click", std::move(event), RawEvent::Category::Discrete);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class TouchEventEmitter : public EventEmitter {
void onTouchEnd(TouchEvent event) const;
void onTouchCancel(TouchEvent event) const;

void onAuxClick(PointerEvent event) const;
void onClick(PointerEvent event) const;
void onPointerCancel(PointerEvent event) const;
void onPointerDown(PointerEvent event) const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ static jsi::Object mouseEventPayload(jsi::Runtime& runtime, const MouseEvent& ev
payload.setProperty(runtime, "ctrlKey", event.ctrlKey);
payload.setProperty(runtime, "shiftKey", event.shiftKey);
payload.setProperty(runtime, "metaKey", event.metaKey);
payload.setProperty(runtime, "button", event.button);
return payload;
};

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

void HostPlatformViewEventEmitter::onAuxClick(const MouseEvent& mouseEvent) const {
dispatchEvent("auxClick", [mouseEvent](jsi::Runtime& runtime) {
return mouseEventPayload(runtime, mouseEvent);
});
}

#pragma mark - Drag and Drop Events

jsi::Value HostPlatformViewEventEmitter::dataTransferPayload(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class HostPlatformViewEventEmitter : public BaseViewEventEmitter {
void onMouseEnter(MouseEvent const& mouseEvent) const;
void onMouseLeave(MouseEvent const& mouseEvent) const;
void onDoubleClick(MouseEvent const& mouseEvent) const;
void onAuxClick(MouseEvent const& mouseEvent) const;

#pragma mark - Drag and Drop Events

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ struct HostPlatformViewEvents {
MouseEnter = 4,
MouseLeave = 5,
DoubleClick = 6,
AuxClick = 7,
};

constexpr bool operator[](const Offset offset) const {
Expand Down Expand Up @@ -74,6 +75,8 @@ static inline HostPlatformViewEvents convertRawProp(
convertRawProp(context, rawProps, "onMouseLeave", sourceValue[Offset::MouseLeave], defaultValue[Offset::MouseLeave]);
result[Offset::DoubleClick] =
convertRawProp(context, rawProps, "onDoubleClick", sourceValue[Offset::DoubleClick], defaultValue[Offset::DoubleClick]);
result[Offset::AuxClick] =
convertRawProp(context, rawProps, "onAuxClick", sourceValue[Offset::AuxClick], defaultValue[Offset::AuxClick]);

return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ struct MouseEvent {
* A flag indicating if the meta key is pressed.
*/
bool metaKey{false};

/**
* The button number that was pressed when the mouse event was fired:
* 0 = primary button (usually the left button)
* 1 = auxiliary button (usually the middle/wheel button)
* 2 = secondary button (usually the right button)
*/
int button{0};
};

struct DataTransferFile {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ struct ViewEvents {
PointerDownCapture = 35,
PointerUp = 36,
PointerUpCapture = 37,
AuxClick = 38,
AuxClickCapture = 39,
};

constexpr bool operator[](const Offset offset) const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,10 @@ static inline ViewEvents convertRawProp(
"onPointerOutCapture",
sourceValue[Offset::PointerOutCapture],
defaultValue[Offset::PointerOutCapture]);
result[Offset::AuxClick] =
convertRawProp(context, rawProps, "onAuxClick", sourceValue[Offset::AuxClick], defaultValue[Offset::AuxClick]);
result[Offset::AuxClickCapture] = convertRawProp(
context, rawProps, "onAuxClickCapture", sourceValue[Offset::AuxClickCapture], defaultValue[Offset::AuxClickCapture]);
result[Offset::Click] = convertRawProp(
context,
rawProps,
Expand Down
2 changes: 2 additions & 0 deletions packages/react-native/ReactNativeApi.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3676,6 +3676,8 @@ declare type PlatformType =
| WindowsPlatform
declare type PointerEvent = NativeSyntheticEvent<NativePointerEvent>
declare type PointerEventProps = {
readonly onAuxClick?: (event: PointerEvent) => void
readonly onAuxClickCapture?: (event: PointerEvent) => void
readonly onClick?: (event: PointerEvent) => void
readonly onClickCapture?: (event: PointerEvent) => void
readonly onGotPointerCapture?: (e: PointerEvent) => void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ function PressableFeedbackEvents() {
onDragLeave={() => appendEvent('dragLeave')}
onDrop={() => appendEvent('drop')}
draggedTypes={'fileUrl'}
onAuxClick={() => appendEvent('auxClick')}
onDoubleClick={() => appendEvent('doubleClick')}
// macOS]
onPress={() => appendEvent('press')}
Expand Down
Loading
Loading