Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add onPaste to TextInput #45425

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,21 @@ export type NativeProps = $ReadOnly<{|
|}>,
>,

/**
* Invoked when the user performs the paste action.
*/
onPaste?: ?DirectEventHandler<
$ReadOnly<{|
target: Int32,
items: $ReadOnlyArray<
$ReadOnly<{|
type: string,
data: string,
|}>,
>,
|}>,
>,

/**
* The string that will be rendered before text input has been entered.
*/
Expand Down Expand Up @@ -658,6 +673,9 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = {
topScroll: {
registrationName: 'onScroll',
},
topPaste: {
registrationName: 'onPaste',
},
},
validAttributes: {
maxFontSizeMultiplier: true,
Expand Down Expand Up @@ -712,6 +730,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = {
textBreakStrategy: true,
onScroll: true,
onContentSizeChange: true,
onPaste: true,
disableFullscreenUI: true,
includeFontPadding: true,
fontWeight: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ const RCTTextInputViewConfig = {
topContentSizeChange: {
registrationName: 'onContentSizeChange',
},
topPaste: {
registrationName: 'onPaste',
},
},
validAttributes: {
fontSize: true,
Expand Down Expand Up @@ -150,6 +153,7 @@ const RCTTextInputViewConfig = {
onSelectionChange: true,
onContentSizeChange: true,
onScroll: true,
onPaste: true,
}),
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,16 @@ export interface TextInputSubmitEditingEventData {
text: string;
}

/**
* @see TextInputProps.onPaste
*/
export interface TextInputPasteEventData extends TargetedEvent {
items: Array<{
type: string;
data: string;
}>;
}

/**
* @see https://reactnative.dev/docs/textinput#props
*/
Expand Down Expand Up @@ -831,6 +841,13 @@ export interface TextInputProps
| ((e: NativeSyntheticEvent<TextInputKeyPressEventData>) => void)
| undefined;

/**
* Invoked when the user performs the paste action.
*/
onPaste?:
| ((e: NativeSyntheticEvent<TextInputPasteEventData>) => void)
| undefined;

/**
* The string that will be rendered before text input has been entered
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ export type EditingEvent = SyntheticEvent<
|}>,
>;

export type PasteEvent = SyntheticEvent<
$ReadOnly<{|
target: number,
items: $ReadOnlyArray<
$ReadOnly<{|
type: string,
data: string,
|}>,
>,
|}>,
>;

type DataDetectorTypesType =
| 'phoneNumber'
| 'link'
Expand Down Expand Up @@ -812,6 +824,11 @@ export type Props = $ReadOnly<{|
*/
onScroll?: ?(e: ScrollEvent) => mixed,

/**
* Invoked when the user performs the paste action.
*/
onPaste?: ?(e: PasteEvent) => mixed,

/**
* The string that will be rendered before text input has been entered.
*/
Expand Down
17 changes: 17 additions & 0 deletions packages/react-native/Libraries/Components/TextInput/TextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,18 @@ export type EditingEvent = SyntheticEvent<
|}>,
>;

export type PasteEvent = SyntheticEvent<
$ReadOnly<{|
target: number,
items: $ReadOnlyArray<
$ReadOnly<{|
type: string,
data: string,
|}>,
>,
|}>,
>;

type DataDetectorTypesType =
| 'phoneNumber'
| 'link'
Expand Down Expand Up @@ -812,6 +824,11 @@ export type Props = $ReadOnly<{|
*/
onScroll?: ?(e: ScrollEvent) => mixed,

/**
* Invoked when the user performs the paste action.
*/
onPaste?: ?(e: PasteEvent) => mixed,

/**
* The string that will be rendered before text input has been entered.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
#import <React/RCTBackedTextInputDelegateAdapter.h>
#import <React/RCTTextAttributes.h>

#import <MobileCoreServices/MobileCoreServices.h>
#import <MobileCoreServices/UTType.h>
#import <UIKit/UIKit.h>

@implementation RCTUITextView {
UILabel *_placeholderView;
UITextView *_detachedTextView;
Expand Down Expand Up @@ -172,7 +176,32 @@ - (void)scrollRangeToVisible:(NSRange)range
- (void)paste:(id)sender
{
_textWasPasted = YES;
[super paste:sender];
UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
if (clipboard.hasImages) {
for (NSItemProvider *itemProvider in clipboard.itemProviders) {
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) {
for (NSString *identifier in itemProvider.registeredTypeIdentifiers) {
if (UTTypeConformsTo((__bridge CFStringRef)identifier, kUTTypeImage)) {
NSString *MIMEType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)identifier, kUTTagClassMIMEType);
NSString *fileExtension = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)identifier, kUTTagClassFilenameExtension);
NSString *fileName = [NSString stringWithFormat:@"%@.%@", [[NSUUID UUID] UUIDString], fileExtension];
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
Comment on lines +187 to +188
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace with RCTTempFilePath

NSURL *fileURL = [NSURL fileURLWithPath:filePath];
NSData *fileData = [clipboard dataForPasteboardType:identifier];
[fileData writeToFile:filePath atomically:YES];
[_textInputDelegateAdapter didPaste:MIMEType withData:[fileURL absoluteString]];
break;
}
}
break;
}
}
} else {
if (clipboard.hasStrings) {
[_textInputDelegateAdapter didPaste:@"text/plain" withData:clipboard.string];
}
[super paste:sender];
}
}

// Turn off scroll animation to fix flaky scrolling.
Expand Down Expand Up @@ -264,6 +293,10 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
return NO;
}

if (action == @selector(paste:) && [UIPasteboard generalPasteboard].hasImages) {
return YES;
}

return [super canPerformAction:action withSender:sender];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)textInputDidChange;

- (void)textInputDidChangeSelection;
- (void)textInputDidPaste:(NSString *)type withData:(NSString *)data;

@optional

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN

- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange;
- (void)selectedTextRangeWasSet;
- (void)didPaste:(NSString *)type withData:(NSString *)data;

@end

Expand All @@ -30,6 +31,7 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithTextView:(UITextView<RCTBackedTextInputViewProtocol> *)backedTextInputView;

- (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange;
- (void)didPaste:(NSString *)type withData:(NSString *)data;

@end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ - (void)selectedTextRangeWasSet
[self textFieldProbablyDidChangeSelection];
}

- (void)didPaste:(NSString *)type withData:(NSString *)data
{
[_backedTextInputView.textInputDelegate textInputDidPaste:type withData:data];
}

#pragma mark - Generalization

- (void)textFieldProbablyDidChangeSelection
Expand Down Expand Up @@ -292,6 +297,11 @@ - (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)tex
_previousSelectedTextRange = textRange;
}

- (void)didPaste:(NSString *)type withData:(NSString *)data
{
[_backedTextInputView.textInputDelegate textInputDidPaste:type withData:data];
}

#pragma mark - Generalization

- (void)textViewProbablyDidChangeSelection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, copy, nullable) RCTDirectEventBlock onChange;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onChangeSync;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onScroll;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onPaste;

@property (nonatomic, assign) NSInteger mostRecentEventCount;
@property (nonatomic, assign, readonly) NSInteger nativeEventCount;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,26 @@ - (void)textInputDidChangeSelection
});
}

- (void)textInputDidPaste:(NSString *)type withData:(NSString *)data
{
if (!_onPaste) {
return;
}

NSMutableArray *items = [NSMutableArray new];
[items addObject:@{
@"type" : type,
@"data" : data,
}];

NSDictionary *payload = @{
@"target" : self.reactTag,
@"items" : items,
};

_onPaste(payload);
}

- (void)updateLocalData
{
[self enforceTextAttributesIfNeeded];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ @implementation RCTBaseTextInputViewManager {
RCT_EXPORT_VIEW_PROPERTY(onChangeSync, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPaste, RCTDirectEventBlock)

RCT_EXPORT_VIEW_PROPERTY(mostRecentEventCount, NSInteger)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
#import <React/RCTUtils.h>
#import <React/UIView+React.h>

#import <MobileCoreServices/MobileCoreServices.h>
#import <MobileCoreServices/UTType.h>
#import <UIKit/UIKit.h>

@implementation RCTUITextField {
RCTBackedTextFieldDelegateAdapter *_textInputDelegateAdapter;
NSDictionary<NSAttributedStringKey, id> *_defaultTextAttributes;
Expand Down Expand Up @@ -139,6 +143,10 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
return NO;
}

if (action == @selector(paste:) && [UIPasteboard generalPasteboard].hasImages) {
return YES;
}

return [super canPerformAction:action withSender:sender];
}

Expand Down Expand Up @@ -222,7 +230,32 @@ - (void)scrollRangeToVisible:(NSRange)range
- (void)paste:(id)sender
{
_textWasPasted = YES;
[super paste:sender];
UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
if (clipboard.hasImages) {
for (NSItemProvider *itemProvider in clipboard.itemProviders) {
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) {
for (NSString *identifier in itemProvider.registeredTypeIdentifiers) {
if (UTTypeConformsTo((__bridge CFStringRef)identifier, kUTTypeImage)) {
NSString *MIMEType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)identifier, kUTTagClassMIMEType);
NSString *fileExtension = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)identifier, kUTTagClassFilenameExtension);
NSString *fileName = [NSString stringWithFormat:@"%@.%@", [[NSUUID UUID] UUIDString], fileExtension];
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
Comment on lines +241 to +242
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same ^

NSURL *fileURL = [NSURL fileURLWithPath:filePath];
NSData *fileData = [clipboard dataForPasteboardType:identifier];
[fileData writeToFile:filePath atomically:YES];
[_textInputDelegateAdapter didPaste:MIMEType withData:[fileURL absoluteString]];
break;
}
}
break;
}
}
} else {
if (clipboard.hasStrings) {
[_textInputDelegateAdapter didPaste:@"text/plain" withData:clipboard.string];
}
[super paste:sender];
}
}

#pragma mark - Layout
Expand Down
Loading
Loading