diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm index 6e3284b91c728e..637b3917cf6d68 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm @@ -11,6 +11,9 @@ #if !TARGET_OS_OSX // [macOS] #import #endif // [macOS] +#if TARGET_OS_OSX // [macOS +#import +#endif // macOS] #import #import @@ -25,29 +28,16 @@ #import "RCTConversions.h" #import "RCTFabricComponentsPlugins.h" -#import // [macOS] - using namespace facebook::react; -#if !TARGET_OS_OSX // [macOS] // ParagraphTextView is an auxiliary view we set as contentView so the drawing // can happen on top of the layers manipulated by RCTViewComponentView (the parent view) @interface RCTParagraphTextView : RCTUIView // [macOS] -#else // [macOS -// On macOS, we also defer drawing to an NSTextView, -// in order to get more native behaviors like text selection. -@interface RCTParagraphTextView : NSTextView // [macOS] -#endif // macOS] @property (nonatomic) ParagraphShadowNode::ConcreteState::Shared state; @property (nonatomic) ParagraphAttributes paragraphAttributes; @property (nonatomic) LayoutMetrics layoutMetrics; -#if TARGET_OS_OSX // [macOS] -/// UIKit compatibility shim that simply calls `[self setNeedsDisplay:YES]` -- (void)setNeedsDisplay; -#endif - @end #if !TARGET_OS_OSX // [macOS] @@ -55,6 +45,9 @@ @interface RCTParagraphComponentView () @property (nonatomic, nullable) UIEditMenuInteraction *editMenuInteraction API_AVAILABLE(ios(16.0)); +@end +#else // [macOS +@interface RCTParagraphComponentView () @end #endif // [macOS] @@ -64,7 +57,7 @@ @implementation RCTParagraphComponentView { RCTParagraphComponentAccessibilityProvider *_accessibilityProvider; #if !TARGET_OS_OSX // [macOS] UILongPressGestureRecognizer *_longPressGestureRecognizer; -#endif // macOS] +#endif // [macOS] RCTParagraphTextView *_textView; } @@ -73,30 +66,11 @@ - (instancetype)initWithFrame:(CGRect)frame if (self = [super initWithFrame:frame]) { _props = ParagraphShadowNode::defaultSharedProps(); -#if !TARGET_OS_OSX // [macOS] +#if !TARGET_OS_OSX // [macOS] self.opaque = NO; +#endif // [macOS] _textView = [RCTParagraphTextView new]; _textView.backgroundColor = RCTUIColor.clearColor; // [macOS] -#else // [macOS - // Make the RCTParagraphComponentView accessible and available in the a11y hierarchy. - self.accessibilityElement = YES; - self.accessibilityRole = NSAccessibilityStaticTextRole; - // Fix blurry text on non-retina displays. - self.canDrawSubviewsIntoLayer = YES; - // The NSTextView is responsible for drawing text and managing selection. - _textView = [[RCTParagraphTextView alloc] initWithFrame:self.bounds]; - // The RCTParagraphComponentUnfocusableTextView is only used for rendering and should not appear in the a11y hierarchy. - _textView.accessibilityElement = NO; - _textView.usesFontPanel = NO; - _textView.drawsBackground = NO; - _textView.linkTextAttributes = @{}; - _textView.editable = NO; - _textView.selectable = NO; - _textView.verticallyResizable = NO; - _textView.layoutManager.usesFontLeading = NO; - self.contentView = _textView; - self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize; -#endif // macOS] self.contentView = _textView; } @@ -153,9 +127,7 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & } else { [self disableContextMenu]; } -#else // [macOS - _textView.selectable = newParagraphProps.isSelectable; -#endif // macOS] +#endif // [macOS] } [super updateProps:props oldProps:oldProps]; @@ -213,7 +185,7 @@ - (BOOL)isAccessibilityElement return NO; } -#if !TARGET_OS_OSX // [macOS] +#if !TARGET_OS_OSX // [macOS - (NSArray *)accessibilityElements { const auto ¶graphProps = static_cast(*_props); @@ -249,7 +221,12 @@ - (UIAccessibilityTraits)accessibilityTraits { return [super accessibilityTraits] | UIAccessibilityTraitStaticText; } -#endif // [macOS] +#else // [macOS +- (NSAccessibilityRole)accessibilityRole +{ + return [super accessibilityRole] ?: NSAccessibilityStaticTextRole; +} +#endif // macOS] #pragma mark - RCTTouchableComponentViewProtocol @@ -333,6 +310,7 @@ - (BOOL)canBecomeFirstResponder return paragraphProps.isSelectable; } +#if !TARGET_OS_OSX // [macOS] - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { const auto ¶graphProps = static_cast(*_props); @@ -341,12 +319,9 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender return YES; } -#if !TARGET_OS_OSX // [macOS] return [self.nextResponder canPerformAction:action withSender:sender]; -#else // [macOS - return NO; -#endif // macOS] } +#endif // [macOS] - (void)copy:(id)sender { @@ -382,12 +357,10 @@ - (void)copy:(id)sender } @implementation RCTParagraphTextView { -#if !TARGET_OS_OSX // [macOS] CAShapeLayer *_highlightLayer; -#endif // macOS] } -- (RCTUIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event // [macOS] +- (RCTUIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { return nil; } @@ -409,7 +382,6 @@ - (void)drawRect:(CGRect)rect CGRect frame = RCTCGRectFromRect(_layoutMetrics.getContentFrame()); -#if !TARGET_OS_OSX // [macOS] [nativeTextLayoutManager drawAttributedString:_state->getData().attributedString paragraphAttributes:_paragraphAttributes frame:frame @@ -421,54 +393,70 @@ - (void)drawRect:(CGRect)rect [self.layer addSublayer:self->_highlightLayer]; } self->_highlightLayer.position = frame.origin; + +#if !TARGET_OS_OSX // [macOS] self->_highlightLayer.path = highlightPath.CGPath; +#else // [macOS Update once our minimum is macOS 14 + self->_highlightLayer.path = UIBezierPathCreateCGPathRef(highlightPath); +#endif // macOS] } else { [self->_highlightLayer removeFromSuperlayer]; self->_highlightLayer = nil; } }]; -#else // [macOS - NSTextStorage *textStorage = [nativeTextLayoutManager getTextStorageForAttributedString:_state->getData().attributedString paragraphAttributes:_paragraphAttributes size:frame.size]; - - NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject; - NSTextContainer *textContainer = layoutManager.textContainers.firstObject; - - [self replaceTextContainer:textContainer]; - - NSArray *managers = [[textStorage layoutManagers] copy]; - for (NSLayoutManager *manager in managers) { - [textStorage removeLayoutManager:manager]; - } - - self.minSize = frame.size; - self.maxSize = frame.size; - self.frame = frame; - [[self textStorage] setAttributedString:textStorage]; - - [super drawRect:rect]; -#endif } -#if TARGET_OS_OSX // [macOS -- (void)setNeedsDisplay -{ - [self setNeedsDisplay:YES]; -} - -- (BOOL)canBecomeKeyView -{ - return NO; -} +@end -- (BOOL)resignFirstResponder +#if TARGET_OS_OSX // [macOS +// Copied from RCTUIKit +CGPathRef UIBezierPathCreateCGPathRef(UIBezierPath *bezierPath) { - // Don't relinquish first responder while selecting text. - if (self.selectable && NSRunLoop.currentRunLoop.currentMode == NSEventTrackingRunLoopMode) { - return NO; + CGPathRef immutablePath = NULL; + + // Draw the path elements. + NSInteger numElements = [bezierPath elementCount]; + if (numElements > 0) + { + CGMutablePathRef path = CGPathCreateMutable(); + NSPoint points[3]; + BOOL didClosePath = YES; + + for (NSInteger i = 0; i < numElements; i++) + { + switch ([bezierPath elementAtIndex:i associatedPoints:points]) + { + case NSBezierPathElementMoveTo: + CGPathMoveToPoint(path, NULL, points[0].x, points[0].y); + break; + + case NSBezierPathElementLineTo: + CGPathAddLineToPoint(path, NULL, points[0].x, points[0].y); + didClosePath = NO; + break; + + case NSBezierPathElementCurveTo: + CGPathAddCurveToPoint(path, NULL, points[0].x, points[0].y, + points[1].x, points[1].y, + points[2].x, points[2].y); + didClosePath = NO; + break; + + case NSBezierPathElementClosePath: + CGPathCloseSubpath(path); + didClosePath = YES; + break; + } + } + + // Be sure the path is closed or Quartz may not do valid hit detection. + if (!didClosePath) + CGPathCloseSubpath(path); + + immutablePath = CGPathCreateCopy(path); + CGPathRelease(path); } - - return [super resignFirstResponder]; + + return immutablePath; } #endif // macOS] - -@end