1111#if !TARGET_OS_OSX // [macOS]
1212#import < MobileCoreServices/UTCoreTypes.h>
1313#endif // [macOS]
14+ #if TARGET_OS_OSX // [macOS
15+ #import < QuartzCore/CAShapeLayer.h>
16+ #endif // macOS]
1417
1518#import < react/renderer/components/text/ParagraphComponentDescriptor.h>
1619#import < react/renderer/components/text/ParagraphProps.h>
2528#import " RCTConversions.h"
2629#import " RCTFabricComponentsPlugins.h"
2730
28- #import < QuartzCore/QuartzCore.h> // [macOS]
29-
3031using namespace facebook ::react;
3132
32- #if !TARGET_OS_OSX // [macOS]
3333// ParagraphTextView is an auxiliary view we set as contentView so the drawing
3434// can happen on top of the layers manipulated by RCTViewComponentView (the parent view)
3535@interface RCTParagraphTextView : RCTUIView // [macOS]
36- #else // [macOS
37- // On macOS, we also defer drawing to an NSTextView,
38- // in order to get more native behaviors like text selection.
39- @interface RCTParagraphTextView : NSTextView // [macOS]
40- #endif // macOS]
4136
4237@property (nonatomic ) ParagraphShadowNode::ConcreteState::Shared state;
4338@property (nonatomic ) ParagraphAttributes paragraphAttributes;
4439@property (nonatomic ) LayoutMetrics layoutMetrics;
4540
46- #if TARGET_OS_OSX // [macOS]
47- // / UIKit compatibility shim that simply calls `[self setNeedsDisplay:YES]`
48- - (void )setNeedsDisplay ;
49- #endif
50-
5141@end
5242
5343#if !TARGET_OS_OSX // [macOS]
5444@interface RCTParagraphComponentView () <UIEditMenuInteractionDelegate>
5545
5646@property (nonatomic , nullable ) UIEditMenuInteraction *editMenuInteraction API_AVAILABLE (ios(16.0 ));
5747
48+ @end
49+ #else // [macOS
50+ @interface RCTParagraphComponentView ()
5851@end
5952#endif // [macOS]
6053
@@ -64,7 +57,7 @@ @implementation RCTParagraphComponentView {
6457 RCTParagraphComponentAccessibilityProvider *_accessibilityProvider;
6558#if !TARGET_OS_OSX // [macOS]
6659 UILongPressGestureRecognizer *_longPressGestureRecognizer;
67- #endif // macOS]
60+ #endif // [ macOS]
6861 RCTParagraphTextView *_textView;
6962}
7063
@@ -73,30 +66,11 @@ - (instancetype)initWithFrame:(CGRect)frame
7366 if (self = [super initWithFrame: frame]) {
7467 _props = ParagraphShadowNode::defaultSharedProps ();
7568
76- #if !TARGET_OS_OSX // [macOS]
69+ #if !TARGET_OS_OSX // [macOS]
7770 self.opaque = NO ;
71+ #endif // [macOS]
7872 _textView = [RCTParagraphTextView new ];
7973 _textView.backgroundColor = RCTUIColor.clearColor ; // [macOS]
80- #else // [macOS
81- // Make the RCTParagraphComponentView accessible and available in the a11y hierarchy.
82- self.accessibilityElement = YES ;
83- self.accessibilityRole = NSAccessibilityStaticTextRole ;
84- // Fix blurry text on non-retina displays.
85- self.canDrawSubviewsIntoLayer = YES ;
86- // The NSTextView is responsible for drawing text and managing selection.
87- _textView = [[RCTParagraphTextView alloc ] initWithFrame: self .bounds];
88- // The RCTParagraphComponentUnfocusableTextView is only used for rendering and should not appear in the a11y hierarchy.
89- _textView.accessibilityElement = NO ;
90- _textView.usesFontPanel = NO ;
91- _textView.drawsBackground = NO ;
92- _textView.linkTextAttributes = @{};
93- _textView.editable = NO ;
94- _textView.selectable = NO ;
95- _textView.verticallyResizable = NO ;
96- _textView.layoutManager .usesFontLeading = NO ;
97- self.contentView = _textView;
98- self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
99- #endif // macOS]
10074 self.contentView = _textView;
10175 }
10276
@@ -153,9 +127,7 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
153127 } else {
154128 [self disableContextMenu ];
155129 }
156- #else // [macOS
157- _textView.selectable = newParagraphProps.isSelectable ;
158- #endif // macOS]
130+ #endif // [macOS]
159131 }
160132
161133 [super updateProps: props oldProps: oldProps];
@@ -213,7 +185,7 @@ - (BOOL)isAccessibilityElement
213185 return NO ;
214186}
215187
216- #if !TARGET_OS_OSX // [macOS]
188+ #if !TARGET_OS_OSX // [macOS
217189- (NSArray *)accessibilityElements
218190{
219191 const auto ¶graphProps = static_cast <const ParagraphProps &>(*_props);
@@ -249,7 +221,12 @@ - (UIAccessibilityTraits)accessibilityTraits
249221{
250222 return [super accessibilityTraits ] | UIAccessibilityTraitStaticText;
251223}
252- #endif // [macOS]
224+ #else // [macOS
225+ - (NSAccessibilityRole )accessibilityRole
226+ {
227+ return [super accessibilityRole ] ?: NSAccessibilityStaticTextRole ;
228+ }
229+ #endif // macOS]
253230
254231#pragma mark - RCTTouchableComponentViewProtocol
255232
@@ -333,6 +310,7 @@ - (BOOL)canBecomeFirstResponder
333310 return paragraphProps.isSelectable ;
334311}
335312
313+ #if !TARGET_OS_OSX // [macOS]
336314- (BOOL )canPerformAction : (SEL )action withSender : (id )sender
337315{
338316 const auto ¶graphProps = static_cast <const ParagraphProps &>(*_props);
@@ -341,12 +319,9 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
341319 return YES ;
342320 }
343321
344- #if !TARGET_OS_OSX // [macOS]
345322 return [self .nextResponder canPerformAction: action withSender: sender];
346- #else // [macOS
347- return NO ;
348- #endif // macOS]
349323}
324+ #endif // [macOS]
350325
351326- (void )copy : (id )sender
352327{
@@ -382,12 +357,10 @@ - (void)copy:(id)sender
382357}
383358
384359@implementation RCTParagraphTextView {
385- #if !TARGET_OS_OSX // [macOS]
386360 CAShapeLayer *_highlightLayer;
387- #endif // macOS]
388361}
389362
390- - (RCTUIView *)hitTest : (CGPoint)point withEvent : (UIEvent *)event // [macOS]
363+ - (RCTUIView *)hitTest : (CGPoint)point withEvent : (UIEvent *)event
391364{
392365 return nil ;
393366}
@@ -409,7 +382,6 @@ - (void)drawRect:(CGRect)rect
409382
410383 CGRect frame = RCTCGRectFromRect (_layoutMetrics.getContentFrame ());
411384
412- #if !TARGET_OS_OSX // [macOS]
413385 [nativeTextLayoutManager drawAttributedString: _state->getData ().attributedString
414386 paragraphAttributes: _paragraphAttributes
415387 frame: frame
@@ -421,54 +393,70 @@ - (void)drawRect:(CGRect)rect
421393 [self .layer addSublayer: self ->_highlightLayer];
422394 }
423395 self->_highlightLayer .position = frame.origin ;
396+
397+ #if !TARGET_OS_OSX // [macOS]
424398 self->_highlightLayer .path = highlightPath.CGPath ;
399+ #else // [macOS Update once our minimum is macOS 14
400+ self->_highlightLayer .path = UIBezierPathCreateCGPathRef (highlightPath);
401+ #endif // macOS]
425402 } else {
426403 [self ->_highlightLayer removeFromSuperlayer ];
427404 self->_highlightLayer = nil ;
428405 }
429406 }];
430- #else // [macOS
431- NSTextStorage *textStorage = [nativeTextLayoutManager getTextStorageForAttributedString: _state->getData ().attributedString paragraphAttributes: _paragraphAttributes size: frame.size];
432-
433- NSLayoutManager *layoutManager = textStorage.layoutManagers .firstObject ;
434- NSTextContainer *textContainer = layoutManager.textContainers .firstObject ;
435-
436- [self replaceTextContainer: textContainer];
437-
438- NSArray <NSLayoutManager *> *managers = [[textStorage layoutManagers ] copy ];
439- for (NSLayoutManager *manager in managers) {
440- [textStorage removeLayoutManager: manager];
441- }
442-
443- self.minSize = frame.size ;
444- self.maxSize = frame.size ;
445- self.frame = frame;
446- [[self textStorage ] setAttributedString: textStorage];
447-
448- [super drawRect: rect];
449- #endif
450407}
451408
452- #if TARGET_OS_OSX // [macOS
453- - (void )setNeedsDisplay
454- {
455- [self setNeedsDisplay: YES ];
456- }
457-
458- - (BOOL )canBecomeKeyView
459- {
460- return NO ;
461- }
409+ @end
462410
463- - (BOOL )resignFirstResponder
411+ #if TARGET_OS_OSX // [macOS
412+ // Copied from RCTUIKit
413+ CGPathRef UIBezierPathCreateCGPathRef (UIBezierPath *bezierPath)
464414{
465- // Don't relinquish first responder while selecting text.
466- if (self.selectable && NSRunLoop .currentRunLoop .currentMode == NSEventTrackingRunLoopMode ) {
467- return NO ;
415+ CGPathRef immutablePath = NULL ;
416+
417+ // Draw the path elements.
418+ NSInteger numElements = [bezierPath elementCount ];
419+ if (numElements > 0 )
420+ {
421+ CGMutablePathRef path = CGPathCreateMutable ();
422+ NSPoint points[3 ];
423+ BOOL didClosePath = YES ;
424+
425+ for (NSInteger i = 0 ; i < numElements; i++)
426+ {
427+ switch ([bezierPath elementAtIndex: i associatedPoints: points])
428+ {
429+ case NSBezierPathElementMoveTo:
430+ CGPathMoveToPoint (path, NULL , points[0 ].x , points[0 ].y );
431+ break ;
432+
433+ case NSBezierPathElementLineTo:
434+ CGPathAddLineToPoint (path, NULL , points[0 ].x , points[0 ].y );
435+ didClosePath = NO ;
436+ break ;
437+
438+ case NSBezierPathElementCurveTo:
439+ CGPathAddCurveToPoint (path, NULL , points[0 ].x , points[0 ].y ,
440+ points[1 ].x , points[1 ].y ,
441+ points[2 ].x , points[2 ].y );
442+ didClosePath = NO ;
443+ break ;
444+
445+ case NSBezierPathElementClosePath:
446+ CGPathCloseSubpath (path);
447+ didClosePath = YES ;
448+ break ;
449+ }
450+ }
451+
452+ // Be sure the path is closed or Quartz may not do valid hit detection.
453+ if (!didClosePath)
454+ CGPathCloseSubpath (path);
455+
456+ immutablePath = CGPathCreateCopy (path);
457+ CGPathRelease (path);
468458 }
469-
470- return [ super resignFirstResponder ] ;
459+
460+ return immutablePath ;
471461}
472462#endif // macOS]
473-
474- @end
0 commit comments