From cb85f51455e063c110c16bbc684ead49a2e4707d Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Thu, 8 Sep 2022 15:24:38 -0700 Subject: [PATCH] Add option to enforce pasting plain text There are use cases in which we want to ignore the base class [NSTextView readablePasteboardTypes] return values, which mostly, include RTF and formatted URL types We want to ignore them in favor of plain text (NSPasteboardTypeString). This change allows to opt into a custom list of supported paste types. This is a follow-up to https://github.com/microsoft/react-native-macos/pull/1350 --- Libraries/Components/TextInput/TextInput.js | 33 +++++++++++++++---- .../Multiline/RCTMultilineTextInputView.h | 2 +- .../Multiline/RCTMultilineTextInputView.m | 3 ++ .../RCTMultilineTextInputViewManager.m | 11 +++++++ .../Text/TextInput/Multiline/RCTUITextView.h | 1 + .../Text/TextInput/Multiline/RCTUITextView.m | 10 ++++-- .../Text/TextInput/RCTBaseTextInputView.m | 2 +- React/Base/RCTConvert.m | 9 ++--- .../TextInput/TextInputExample.ios.js | 1 + 9 files changed, 56 insertions(+), 16 deletions(-) diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 4264b5e04b96cf..4ead077bf77555 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -353,6 +353,28 @@ type IOSProps = $ReadOnly<{| textContentType?: ?TextContentType, |}>; +type MacOSProps = $ReadOnly<{| + /** + * Fired when a supported element is pasted + * + * @platform macos + */ + onPaste?: (event: PasteEvent) => void, // TODO(macOS GH#774) + + /** + * Enables Paste support for certain types of pasted types + * + * Possible values for `pastedTypes` are: + * + * - `'fileUrl'` + * - `'image'` + * - `'string'` + * + * @platform macos + */ + pastedTypes?: PastedTypesType, // TODO(macOS GH#774) +|}>; + type AndroidProps = $ReadOnly<{| /** * Specifies autocomplete hints for the system, so it can provide autofill. On Android, the system will always attempt to offer autofill by using heuristics to identify the type of content. @@ -512,9 +534,13 @@ type AndroidProps = $ReadOnly<{| underlineColorAndroid?: ?ColorValue, |}>; +export type PasteType = 'fileUrl' | 'image' | 'string'; // TODO(macOS GH#774) +export type PastedTypesType = PasteType | $ReadOnlyArray; // TODO(macOS GH#774) + export type Props = $ReadOnly<{| ...$Diff>, ...IOSProps, + ...MacOSProps, ...AndroidProps, /** @@ -760,13 +786,6 @@ export type Props = $ReadOnly<{| */ onPressOut?: ?(event: PressEvent) => mixed, - /** - * Fired when a supported element is pasted - * - * @platform macos - */ - onPaste?: (event: PasteEvent) => void, // TODO(macOS GH#774) - /** * Callback that is called when the text input selection is changed. * This will be called with diff --git a/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.h b/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.h index bd183de3a5953b..eef527c30e2ea5 100644 --- a/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.h +++ b/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.h @@ -10,7 +10,7 @@ NS_ASSUME_NONNULL_BEGIN @interface RCTMultilineTextInputView : RCTBaseTextInputView - +- (void)setReadablePasteBoardTypes:(NSArray *)readablePasteboardTypes; @end NS_ASSUME_NONNULL_END diff --git a/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.m b/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.m index b9297de45a3ffc..5abf53b63f64d2 100644 --- a/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.m +++ b/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputView.m @@ -99,6 +99,9 @@ - (void)setEnableFocusRing:(BOOL)enableFocusRing { } } +- (void)setReadablePasteBoardTypes:(NSArray *)readablePasteboardTypes { + [_backedTextInputView setReadablePasteBoardTypes:readablePasteboardTypes]; +} #endif // ]TODO(macOS GH#774) #pragma mark - UIScrollViewDelegate diff --git a/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.m b/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.m index 2880ee6203e04a..eb43bc6e94cdc0 100644 --- a/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.m +++ b/Libraries/Text/TextInput/Multiline/RCTMultilineTextInputViewManager.m @@ -7,6 +7,7 @@ #import #import +#import @implementation RCTMultilineTextInputViewManager @@ -22,4 +23,14 @@ - (RCTUIView *)view // TODO(macOS ISS#3536887) RCT_REMAP_NOT_OSX_VIEW_PROPERTY(dataDetectorTypes, backedTextInputView.dataDetectorTypes, UIDataDetectorTypes) // TODO(macOS GH#774) RCT_REMAP_OSX_VIEW_PROPERTY(dataDetectorTypes, backedTextInputView.enabledTextCheckingTypes, NSTextCheckingTypes) // TODO(macOS GH#774) +#if TARGET_OS_OSX // [TODO(macOS GH#774) +RCT_CUSTOM_VIEW_PROPERTY(pastedTypes, NSArray*, RCTUITextView) +{ + NSArray *types = json ? [RCTConvert NSPasteboardTypeArray:json] : nil; + if ([view respondsToSelector:@selector(setReadablePasteBoardTypes:)]) { + [view setReadablePasteBoardTypes: types]; + } +} +#endif // ]TODO(macOS GH#774) + @end diff --git a/Libraries/Text/TextInput/Multiline/RCTUITextView.h b/Libraries/Text/TextInput/Multiline/RCTUITextView.h index f96b45e81efde3..b0db9e3ec736d0 100644 --- a/Libraries/Text/TextInput/Multiline/RCTUITextView.h +++ b/Libraries/Text/TextInput/Multiline/RCTUITextView.h @@ -53,6 +53,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) NSTextAlignment textAlignment; @property (nonatomic, copy, nullable) NSAttributedString *attributedText; - (NSSize)sizeThatFits:(NSSize)size; +- (void)setReadablePasteBoardTypes:(NSArray *)readablePasteboardTypes; #endif // ]TODO(macOS GH#774) @end diff --git a/Libraries/Text/TextInput/Multiline/RCTUITextView.m b/Libraries/Text/TextInput/Multiline/RCTUITextView.m index 48780bc2045c14..39fe3fa43bb894 100644 --- a/Libraries/Text/TextInput/Multiline/RCTUITextView.m +++ b/Libraries/Text/TextInput/Multiline/RCTUITextView.m @@ -21,6 +21,7 @@ @implementation RCTUITextView #endif // TODO(macOS GH#774) RCTBackedTextViewDelegateAdapter *_textInputDelegateAdapter; NSDictionary *_defaultTextAttributes; + NSArray *_readablePasteboardTypes; } static UIFont *defaultPlaceholderFont() @@ -177,6 +178,11 @@ - (void)setText:(NSString *)text self.string = text; } +- (void)setReadablePasteBoardTypes:(NSArray *)readablePasteboardTypes +{ + _readablePasteboardTypes = readablePasteboardTypes; +} + - (void)setTypingAttributes:(__unused NSDictionary *)typingAttributes { // Prevent NSTextView from changing its own typing attributes out from under us. @@ -344,9 +350,7 @@ - (BOOL)performDragOperation:(id)draggingInfo } - (NSArray *)readablePasteboardTypes { - NSArray *types = [super readablePasteboardTypes]; - // TODO: Optionally support files/images with a prop - return [types arrayByAddingObjectsFromArray:@[NSFilenamesPboardType, NSPasteboardTypePNG, NSPasteboardTypeTIFF]]; + return _readablePasteboardTypes ? _readablePasteboardTypes : [super readablePasteboardTypes]; } #endif // ]TODO(macOS GH#774) diff --git a/Libraries/Text/TextInput/RCTBaseTextInputView.m b/Libraries/Text/TextInput/RCTBaseTextInputView.m index 6a9ded1dc218a1..c1f18d16dd20e7 100644 --- a/Libraries/Text/TextInput/RCTBaseTextInputView.m +++ b/Libraries/Text/TextInput/RCTBaseTextInputView.m @@ -639,7 +639,7 @@ - (BOOL)textInputShouldHandleKeyEvent:(NSEvent *)event { - (BOOL)textInputShouldHandlePaste:(__unused id)sender { NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; - NSPasteboardType fileType = [pasteboard availableTypeFromArray:@[NSFilenamesPboardType, NSPasteboardTypePNG, NSPasteboardTypeTIFF]]; + NSPasteboardType fileType = [pasteboard availableTypeFromArray:@[NSPasteboardTypeFileURL, NSPasteboardTypePNG, NSPasteboardTypeTIFF]]; // If there's a fileType that is of interest, notify JS. Also blocks notifying JS if it's a text paste if (_onPaste && fileType != nil) { diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index d3fae765980dbc..a2edde0774c288 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -1233,11 +1233,12 @@ + (NSArray *)CGColorArray:(id)json } if ([type isEqualToString:@"fileUrl"]) { - return @[NSFilenamesPboardType]; + return @[NSPasteboardTypeFileURL]; } else if ([type isEqualToString:@"image"]) { return @[NSPasteboardTypePNG, NSPasteboardTypeTIFF]; + } else if ([type isEqualToString:@"string"]) { + return @[NSPasteboardTypeString]; } - return @[]; } @@ -1248,9 +1249,9 @@ + (NSArray *)CGColorArray:(id)json } else if ([json isKindOfClass:[NSArray class]]) { NSMutableArray *mutablePastboardTypes = [NSMutableArray new]; for (NSString *type in json) { - [mutablePastboardTypes addObjectsFromArray:[RCTConvert NSPasteboardType:type]]; - return mutablePastboardTypes.copy; + [mutablePastboardTypes addObjectsFromArray:[RCTConvert NSPasteboardType:type]]; } + return mutablePastboardTypes.copy; } return @[]; } diff --git a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js index 2573db35e23659..1c4589078bb44d 100644 --- a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js +++ b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js @@ -422,6 +422,7 @@ function OnPaste(): React.Node { appendLog(JSON.stringify(e.nativeEvent.dataTransfer.types)); setImageUri(e.nativeEvent.dataTransfer.files[0].uri); }} + pastedTypes={['fileUrl', 'image', 'string']} placeholder="MULTI LINE with onPaste() for PNG and TIFF images" /> {log.join('\n')}