10
10
11
11
#if !TARGET_OS_OSX // [macOS]
12
12
#import < MobileCoreServices/UTCoreTypes.h>
13
- #endif // [macOS]
13
+ #else // [macOS
14
+ #import < React/RCTSurfaceTouchHandler.h>
15
+ #endif // macOS]
14
16
15
17
#import < react/renderer/components/text/ParagraphComponentDescriptor.h>
16
18
#import < react/renderer/components/text/ParagraphProps.h>
@@ -43,10 +45,10 @@ @interface RCTParagraphTextView : NSTextView // [macOS]
43
45
@property (nonatomic ) ParagraphAttributes paragraphAttributes;
44
46
@property (nonatomic ) LayoutMetrics layoutMetrics;
45
47
46
- #if TARGET_OS_OSX // [macOS]
48
+ #if TARGET_OS_OSX // [macOS
47
49
// / UIKit compatibility shim that simply calls `[self setNeedsDisplay:YES]`
48
50
- (void )setNeedsDisplay ;
49
- #endif
51
+ #endif // macOS]
50
52
51
53
@end
52
54
@@ -55,6 +57,9 @@ @interface RCTParagraphComponentView () <UIEditMenuInteractionDelegate>
55
57
56
58
@property (nonatomic , nullable ) UIEditMenuInteraction *editMenuInteraction API_AVAILABLE (ios(16.0 ));
57
59
60
+ @end
61
+ #else // [macOS
62
+ @interface RCTParagraphComponentView () <NSTextViewDelegate >
58
63
@end
59
64
#endif // [macOS]
60
65
@@ -83,9 +88,8 @@ - (instancetype)initWithFrame:(CGRect)frame
83
88
self.accessibilityRole = NSAccessibilityStaticTextRole ;
84
89
// Fix blurry text on non-retina displays.
85
90
self.canDrawSubviewsIntoLayer = YES ;
86
- // The NSTextView is responsible for drawing text and managing selection.
87
91
_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;
89
93
_textView.accessibilityElement = NO ;
90
94
_textView.usesFontPanel = NO ;
91
95
_textView.drawsBackground = NO ;
@@ -325,14 +329,89 @@ - (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
325
329
[menuController showMenuFromView: self rect: self .bounds];
326
330
}
327
331
}
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
+ }
329
382
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]
330
393
- (BOOL )canBecomeFirstResponder
331
394
{
332
395
const auto ¶graphProps = static_cast <const ParagraphProps &>(*_props);
333
396
return paragraphProps.isSelectable ;
334
397
}
398
+ #else
399
+ - (BOOL )becomeFirstResponder
400
+ {
401
+ if (![super becomeFirstResponder ]) {
402
+ return NO ;
403
+ }
404
+
405
+ return YES ;
406
+ }
335
407
408
+ - (BOOL )canBecomeFirstResponder
409
+ {
410
+ return self.focusable ;
411
+ }
412
+ #endif // macOS]
413
+
414
+ #if !TARGET_OS_OSX // [macOS]
336
415
- (BOOL )canPerformAction : (SEL )action withSender : (id )sender
337
416
{
338
417
const auto ¶graphProps = static_cast <const ParagraphProps &>(*_props);
@@ -347,6 +426,7 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
347
426
return NO ;
348
427
#endif // macOS]
349
428
}
429
+ #endif // [macOS]
350
430
351
431
- (void )copy : (id )sender
352
432
{
0 commit comments