@@ -49,6 +49,8 @@ @implementation RCTViewComponentView {
49
49
BOOL _needsInvalidateLayer;
50
50
BOOL _isJSResponder;
51
51
BOOL _removeClippedSubviews;
52
+ BOOL _hasMouseOver; // [macOS]
53
+ NSTrackingArea *_trackingArea; // [macOS]
52
54
NSMutableArray <RCTUIView *> *_reactSubviews; // [macOS]
53
55
NSSet <NSString *> *_Nullable _propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN;
54
56
RCTPlatformView *_containerView; // [macOS]
@@ -645,6 +647,8 @@ - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask
645
647
646
648
_needsInvalidateLayer = NO ;
647
649
[self invalidateLayer ];
650
+
651
+ [self updateTrackingAreas ];
648
652
}
649
653
650
654
- (void )prepareForRecycle
@@ -1656,6 +1660,108 @@ - (void)keyUp:(NSEvent *)event {
1656
1660
}
1657
1661
}
1658
1662
1663
+
1664
+ #pragma mark - Mouse Events
1665
+
1666
+ - (void )sendMouseEvent:(BOOL )isMouseOver {
1667
+ NSPoint locationInWindow = self.window .mouseLocationOutsideOfEventStream ;
1668
+ NSPoint locationInView = [self convertPoint: locationInWindow fromView: nil ];
1669
+
1670
+ NSEventModifierFlags modifierFlags = self.window .currentEvent .modifierFlags ;
1671
+
1672
+ MouseEvent mouseEvent = {
1673
+ .clientX = locationInView.x ,
1674
+ .clientY = locationInView.y ,
1675
+ .screenX = locationInWindow.x ,
1676
+ .screenY = locationInWindow.y ,
1677
+ .altKey = static_cast <bool >(modifierFlags & NSEventModifierFlagOption),
1678
+ .ctrlKey = static_cast <bool >(modifierFlags & NSEventModifierFlagControl),
1679
+ .shiftKey = static_cast <bool >(modifierFlags & NSEventModifierFlagShift),
1680
+ .metaKey = static_cast <bool >(modifierFlags & NSEventModifierFlagCommand),
1681
+ };
1682
+
1683
+ if (isMouseOver) {
1684
+ _eventEmitter->onMouseEnter (mouseEvent);
1685
+ } else {
1686
+ _eventEmitter->onMouseLeave (mouseEvent);
1687
+ }
1688
+ }
1689
+
1690
+ - (void )updateMouseOverIfNeeded
1691
+ {
1692
+ // When an enclosing scrollview is scrolled using the scrollWheel or trackpad,
1693
+ // the mouseExited: event does not get called on the view where mouseEntered: was previously called.
1694
+ // This creates an unnatural pairing of mouse enter and exit events and can cause problems.
1695
+ // We therefore explicitly check for this here and handle them by calling the appropriate callbacks.
1696
+
1697
+ BOOL hasMouseOver = _hasMouseOver;
1698
+ NSPoint locationInWindow = self.window .mouseLocationOutsideOfEventStream ;
1699
+ NSPoint locationInView = [self convertPoint: locationInWindow fromView: nil ];
1700
+ BOOL insideBounds = NSPointInRect (locationInView, self.visibleRect );
1701
+
1702
+ // On macOS 14.0 visibleRect can be larger than the view bounds
1703
+ insideBounds &= NSPointInRect (locationInView, self.bounds );
1704
+
1705
+ if (hasMouseOver && !insideBounds) {
1706
+ hasMouseOver = NO ;
1707
+ } else if (!hasMouseOver && insideBounds) {
1708
+ // The window's frame view must be used for hit testing against `locationInWindow`
1709
+ NSView *hitView = [self .window.contentView.superview hitTest: locationInWindow];
1710
+ hasMouseOver = [hitView isDescendantOf: self ];
1711
+ }
1712
+
1713
+ if (hasMouseOver != _hasMouseOver) {
1714
+ _hasMouseOver = hasMouseOver;
1715
+ [self sendMouseEvent: hasMouseOver];
1716
+ }
1717
+ }
1718
+
1719
+ - (void )updateTrackingAreas
1720
+ {
1721
+ if (_trackingArea) {
1722
+ [self removeTrackingArea: _trackingArea];
1723
+ }
1724
+
1725
+ if (
1726
+ _props->macOSViewEvents [facebook::react::MacOSViewEvents::Offset::MouseEnter] ||
1727
+ _props->macOSViewEvents [facebook::react::MacOSViewEvents::Offset::MouseLeave]
1728
+ ) {
1729
+ _trackingArea = [[NSTrackingArea alloc ] initWithRect: self .bounds
1730
+ options: NSTrackingActiveAlways | NSTrackingMouseEnteredAndExited
1731
+ owner: self
1732
+ userInfo: nil ];
1733
+ [self addTrackingArea: _trackingArea];
1734
+ [self updateMouseOverIfNeeded ];
1735
+ }
1736
+
1737
+ [super updateTrackingAreas ];
1738
+ }
1739
+
1740
+ - (void )mouseEntered:(NSEvent *)event
1741
+ {
1742
+ if (_hasMouseOver) {
1743
+ return ;
1744
+ }
1745
+
1746
+ // The window's frame view must be used for hit testing against `locationInWindow`
1747
+ NSView *hitView = [self .window.contentView.superview hitTest: event.locationInWindow];
1748
+ if (![hitView isDescendantOf: self ]) {
1749
+ return ;
1750
+ }
1751
+
1752
+ _hasMouseOver = YES ;
1753
+ [self sendMouseEvent: _hasMouseOver];
1754
+ }
1755
+
1756
+ - (void )mouseExited:(NSEvent *)event
1757
+ {
1758
+ if (!_hasMouseOver) {
1759
+ return ;
1760
+ }
1761
+
1762
+ _hasMouseOver = NO ;
1763
+ [self sendMouseEvent: _hasMouseOver];
1764
+ }
1659
1765
#endif // macOS]
1660
1766
1661
1767
- (SharedTouchEventEmitter)touchEventEmitterAtPoint:(CGPoint)point
0 commit comments