From e42ffdd859d4bf436ba6b3f2f6053b24820e9a2f Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Thu, 1 Apr 2021 10:42:32 -0700 Subject: [PATCH] Add support for text copy to Paragraph component Summary: Changelog: [internal] Add support for copying text. The implementation is copied over from old React Native renderer. Reviewed By: JoshuaGross Differential Revision: D27502474 fbshipit-source-id: 0d0df583f1adc584ca47e987f06bf78c32386fcc --- .../Text/RCTParagraphComponentView.mm | 87 ++++++++++++++++++- .../components/text/ParagraphProps.cpp | 7 +- 2 files changed, 89 insertions(+), 5 deletions(-) diff --git a/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm b/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm index 476c6567784..54c5178e24e 100644 --- a/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/Text/RCTParagraphComponentView.mm @@ -8,6 +8,7 @@ #import "RCTParagraphComponentView.h" #import "RCTParagraphComponentAccessibilityProvider.h" +#import #import #import #import @@ -28,6 +29,7 @@ using namespace facebook::react; ParagraphShadowNode::ConcreteState::Shared _state; ParagraphAttributes _paragraphAttributes; RCTParagraphComponentAccessibilityProvider *_accessibilityProvider; + UILongPressGestureRecognizer *_longPressGestureRecognizer; } - (instancetype)initWithFrame:(CGRect)frame @@ -80,10 +82,18 @@ using namespace facebook::react; - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps { - const auto ¶graphProps = std::static_pointer_cast(props); + auto const &oldParagraphProps = *std::static_pointer_cast(_props); + auto const &newParagraphProps = *std::static_pointer_cast(props); - assert(paragraphProps); - _paragraphAttributes = paragraphProps->paragraphAttributes; + _paragraphAttributes = newParagraphProps.paragraphAttributes; + + if (newParagraphProps.isSelectable != oldParagraphProps.isSelectable) { + if (newParagraphProps.isSelectable) { + [self enableContextMenu]; + } else { + [self disableContextMenu]; + } + } [super updateProps:props oldProps:oldProps]; } @@ -204,6 +214,77 @@ using namespace facebook::react; return std::static_pointer_cast(eventEmitter); } +#pragma mark - Context Menu + +- (void)enableContextMenu +{ + _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self + action:@selector(handleLongPress:)]; + [self addGestureRecognizer:_longPressGestureRecognizer]; +} + +- (void)disableContextMenu +{ + [self removeGestureRecognizer:_longPressGestureRecognizer]; + _longPressGestureRecognizer = nil; +} + +- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture +{ + // TODO: Adopt showMenuFromRect (necessary for UIKitForMac) +#if !TARGET_OS_UIKITFORMAC + UIMenuController *menuController = [UIMenuController sharedMenuController]; + + if (menuController.isMenuVisible) { + return; + } + + if (!self.isFirstResponder) { + [self becomeFirstResponder]; + } + + [menuController setTargetRect:self.bounds inView:self]; + [menuController setMenuVisible:YES animated:YES]; +#endif +} + +- (BOOL)canBecomeFirstResponder +{ + auto const ¶graphProps = *std::static_pointer_cast(_props); + return paragraphProps.isSelectable; +} + +- (BOOL)canPerformAction:(SEL)action withSender:(id)sender +{ + auto const ¶graphProps = *std::static_pointer_cast(_props); + + if (paragraphProps.isSelectable && action == @selector(copy:)) { + return YES; + } + + return [self.nextResponder canPerformAction:action withSender:sender]; +} + +- (void)copy:(id)sender +{ + NSAttributedString *attributedText = self.attributedText; + + NSMutableDictionary *item = [NSMutableDictionary new]; + + NSData *rtf = [attributedText dataFromRange:NSMakeRange(0, attributedText.length) + documentAttributes:@{NSDocumentTypeDocumentAttribute : NSRTFDTextDocumentType} + error:nil]; + + if (rtf) { + [item setObject:rtf forKey:(id)kUTTypeFlatRTFD]; + } + + [item setObject:attributedText.string forKey:(id)kUTTypeUTF8PlainText]; + + UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; + pasteboard.items = @[ item ]; +} + @end Class RCTParagraphCls(void) diff --git a/ReactCommon/react/renderer/components/text/ParagraphProps.cpp b/ReactCommon/react/renderer/components/text/ParagraphProps.cpp index a578929150c..7508c571a83 100644 --- a/ReactCommon/react/renderer/components/text/ParagraphProps.cpp +++ b/ReactCommon/react/renderer/components/text/ParagraphProps.cpp @@ -24,8 +24,11 @@ ParagraphProps::ParagraphProps( BaseTextProps(sourceProps, rawProps), paragraphAttributes( convertRawProp(rawProps, sourceProps.paragraphAttributes, {})), - isSelectable( - convertRawProp(rawProps, "selectable", sourceProps.isSelectable, {})), + isSelectable(convertRawProp( + rawProps, + "selectable", + sourceProps.isSelectable, + false)), onTextLayout(convertRawProp( rawProps, "onTextLayout",