1010
1111#if !TARGET_OS_OSX // [macOS]
1212#import < MobileCoreServices/UTCoreTypes.h>
13- #endif // [macOS]
13+ #else // [macOS
14+ #import < React/RCTSurfaceTouchHandler.h>
15+ #endif // macOS]
1416
1517#import < react/renderer/components/text/ParagraphComponentDescriptor.h>
1618#import < react/renderer/components/text/ParagraphProps.h>
@@ -43,10 +45,10 @@ @interface RCTParagraphTextView : NSTextView // [macOS]
4345@property (nonatomic ) ParagraphAttributes paragraphAttributes;
4446@property (nonatomic ) LayoutMetrics layoutMetrics;
4547
46- #if TARGET_OS_OSX // [macOS]
48+ #if TARGET_OS_OSX // [macOS
4749// / UIKit compatibility shim that simply calls `[self setNeedsDisplay:YES]`
4850- (void )setNeedsDisplay ;
49- #endif
51+ #endif // macOS]
5052
5153@end
5254
@@ -55,6 +57,9 @@ @interface RCTParagraphComponentView () <UIEditMenuInteractionDelegate>
5557
5658@property (nonatomic , nullable ) UIEditMenuInteraction *editMenuInteraction API_AVAILABLE (ios(16.0 ));
5759
60+ @end
61+ #else // [macOS
62+ @interface RCTParagraphComponentView () <NSTextViewDelegate >
5863@end
5964#endif // [macOS]
6065
@@ -83,9 +88,8 @@ - (instancetype)initWithFrame:(CGRect)frame
8388 self.accessibilityRole = NSAccessibilityStaticTextRole ;
8489 // Fix blurry text on non-retina displays.
8590 self.canDrawSubviewsIntoLayer = YES ;
86- // The NSTextView is responsible for drawing text and managing selection.
8791 _textView = [[RCTParagraphTextView alloc ] initWithFrame: self .bounds];
88- // The RCTParagraphComponentUnfocusableTextView is only used for rendering and should not appear in the a11y hierarchy.
92+ _textView. delegate = self;
8993 _textView.accessibilityElement = NO ;
9094 _textView.usesFontPanel = NO ;
9195 _textView.drawsBackground = NO ;
@@ -325,14 +329,89 @@ - (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
325329 [menuController showMenuFromView: self rect: self .bounds];
326330 }
327331}
328- #endif // [macOS]
332+ #else // [macOS
333+ - (NSView *)hitTest : (NSPoint )point
334+ {
335+ // We will forward mouse click events to the NSTextView ourselves to prevent NSTextView from swallowing events that may be handled in JS (e.g. long press).
336+ NSView *hitView = [super hitTest: point];
337+
338+ NSEventType eventType = NSApp .currentEvent .type ;
339+ BOOL isMouseClickEvent = NSEvent .pressedMouseButtons > 0 ;
340+ BOOL isMouseMoveEventType = eventType == NSEventTypeMouseMoved || eventType == NSEventTypeMouseEntered || eventType == NSEventTypeMouseExited || eventType == NSEventTypeCursorUpdate;
341+ BOOL isMouseMoveEvent = !isMouseClickEvent && isMouseMoveEventType;
342+ BOOL isTextViewClick = (hitView && hitView == _textView) && !isMouseMoveEvent;
343+
344+ return isTextViewClick ? self : hitView;
345+ }
346+
347+ - (void )mouseDown : (NSEvent *)event
348+ {
349+ if (!_textView.selectable ) {
350+ [super mouseDown: event];
351+ return ;
352+ }
353+
354+ // Double/triple-clicks should be forwarded to the NSTextView.
355+ BOOL shouldForward = event.clickCount > 1 ;
356+
357+ if (!shouldForward) {
358+ // Peek at next event to know if a selection should begin.
359+ NSEvent *nextEvent = [self .window nextEventMatchingMask: NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged
360+ untilDate: [NSDate distantFuture ]
361+ inMode: NSEventTrackingRunLoopMode
362+ dequeue: NO ];
363+ shouldForward = nextEvent.type == NSEventTypeLeftMouseDragged;
364+ }
365+
366+ if (shouldForward) {
367+ NSView *contentView = self.window .contentView ;
368+ // -[NSView hitTest:] takes coordinates in a view's superview coordinate system.
369+ NSPoint point = [contentView.superview convertPoint: event.locationInWindow fromView: nil ];
370+
371+ // Start selection if we're still selectable and hit-testable.
372+ if (_textView.selectable && [contentView hitTest: point] == self) {
373+ [[RCTSurfaceTouchHandler surfaceTouchHandlerForView: self ] cancelTouchWithEvent: event];
374+ [self .window makeFirstResponder: _textView];
375+ [_textView mouseDown: event];
376+ }
377+ } else {
378+ // Clear selection for single clicks.
379+ _textView.selectedRange = NSMakeRange (NSNotFound , 0 );
380+ }
381+ }
329382
383+ #pragma mark - Selection
384+
385+ - (void )textDidEndEditing : (NSNotification *)notification
386+ {
387+ _textView.selectedRange = NSMakeRange (NSNotFound , 0 );
388+ }
389+
390+ #endif // macOS]
391+
392+ #if !TARGET_OS_OSX // [macOS]
330393- (BOOL )canBecomeFirstResponder
331394{
332395 const auto ¶graphProps = static_cast <const ParagraphProps &>(*_props);
333396 return paragraphProps.isSelectable ;
334397}
398+ #else
399+ - (BOOL )becomeFirstResponder
400+ {
401+ if (![super becomeFirstResponder ]) {
402+ return NO ;
403+ }
404+
405+ return YES ;
406+ }
335407
408+ - (BOOL )canBecomeFirstResponder
409+ {
410+ return self.focusable ;
411+ }
412+ #endif // macOS]
413+
414+ #if !TARGET_OS_OSX // [macOS]
336415- (BOOL )canPerformAction : (SEL )action withSender : (id )sender
337416{
338417 const auto ¶graphProps = static_cast <const ParagraphProps &>(*_props);
@@ -347,6 +426,7 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
347426 return NO ;
348427#endif // macOS]
349428}
429+ #endif // [macOS]
350430
351431- (void )copy : (id )sender
352432{
0 commit comments