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 MultilineTextInput paste support #1350

Merged
merged 1 commit into from
Sep 8, 2022
Merged
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
29 changes: 28 additions & 1 deletion Libraries/Components/TextInput/TextInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,27 @@ export type SettingChangeEvent = SyntheticEvent<
$ReadOnly<{|
enabled: boolean,
|}>,
>; // ]TODO(macOS GH#774)
>;

export type PasteEvent = SyntheticEvent<
$ReadOnly<{|
dataTransfer: {|
files: $ReadOnlyArray<{|
height: number,
size: number,
type: string,
uri: string,
width: number,
|}>,
items: $ReadOnlyArray<{|
kind: string,
type: string,
|}>,
types: $ReadOnlyArray<string>,
|},
|}>,
>;
// ]TODO(macOS GH#774)

type DataDetectorTypesType =
// iOS+macOS
Expand Down Expand Up @@ -740,6 +760,13 @@ 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
Expand Down
17 changes: 15 additions & 2 deletions Libraries/Text/TextInput/Multiline/RCTUITextView.m
Original file line number Diff line number Diff line change
Expand Up @@ -342,12 +342,25 @@ - (BOOL)performDragOperation:(id<NSDraggingInfo>)draggingInfo
}
return YES;
}
- (NSArray *)readablePasteboardTypes
{
NSArray *types = [super readablePasteboardTypes];
// TODO: Optionally support files/images with a prop
return [types arrayByAddingObjectsFromArray:@[NSFilenamesPboardType, NSPasteboardTypePNG, NSPasteboardTypeTIFF]];
Saadnajmi marked this conversation as resolved.
Show resolved Hide resolved
}

#endif // ]TODO(macOS GH#774)

- (void)paste:(id)sender
{
[super paste:sender];
_textWasPasted = YES;
#if TARGET_OS_OSX // TODO(macOS GH#774)
if ([self.textInputDelegate textInputShouldHandlePaste:self]) {
#endif
[super paste:sender];
_textWasPasted = YES;
#if TARGET_OS_OSX // TODO(macOS GH#774)
}
#endif
}

// Turn off scroll animation to fix flaky scrolling.
Expand Down
1 change: 1 addition & 0 deletions Libraries/Text/TextInput/RCTBackedTextInputDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)textInputDraggingExited:(id<NSDraggingInfo>)draggingInfo;
- (BOOL)textInputShouldHandleDragOperation:(id<NSDraggingInfo>)draggingInfo;
- (void)textInputDidCancel; // Handle `Escape` key press.
- (BOOL)textInputShouldHandlePaste:(id<RCTBackedTextInputViewProtocol>)sender; // Return `YES` to have the paste event handled normally. Return `NO` to disallow it and handle it yourself.
#endif // ]TODO(macOS GH#774)

@optional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,12 @@ - (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doComman
}
//paste
} else if (commandSelector == @selector(paste:)) {
_backedTextInputView.textWasPasted = YES;
id<RCTBackedTextInputDelegate> textInputDelegate = [_backedTextInputView textInputDelegate];
if (textInputDelegate != nil && ![textInputDelegate textInputShouldHandlePaste:_backedTextInputView]) {
commandHandled = YES;
} else {
_backedTextInputView.textWasPasted = YES;
}
//escape
} else if (commandSelector == @selector(cancelOperation:)) {
[textInputDelegate textInputDidCancel];
Expand Down
1 change: 1 addition & 0 deletions Libraries/Text/TextInput/RCTBaseTextInputView.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, copy, nullable) RCTDirectEventBlock onContentSizeChange;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onSelectionChange;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onChange;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onPaste; // TODO(OSS Candidate GH#774)
@property (nonatomic, copy, nullable) RCTDirectEventBlock onChangeSync;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onTextInput;
@property (nonatomic, copy, nullable) RCTDirectEventBlock onScroll;
Expand Down
14 changes: 14 additions & 0 deletions Libraries/Text/TextInput/RCTBaseTextInputView.m
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,20 @@ - (void)textInputDidCancel {
- (BOOL)textInputShouldHandleKeyEvent:(NSEvent *)event {
return ![self handleKeyboardEvent:event];
}

- (BOOL)textInputShouldHandlePaste:(__unused id)sender
{
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
NSPasteboardType fileType = [pasteboard availableTypeFromArray:@[NSFilenamesPboardType, 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) {
_onPaste([self dataTransferInfoFromPasteboard:pasteboard]);
}

// Only allow pasting text.
return fileType == nil;
}
#endif // ]TODO(macOS GH#774)

- (void)updateLocalData
Expand Down
1 change: 1 addition & 0 deletions Libraries/Text/TextInput/RCTBaseTextInputViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ @implementation RCTBaseTextInputViewManager
RCT_EXPORT_VIEW_PROPERTY(onKeyPressSync, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onChangeSync, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPaste, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onTextInput, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock)

Expand Down
2 changes: 2 additions & 0 deletions React/Base/RCTConvert.m
Original file line number Diff line number Diff line change
Expand Up @@ -1234,6 +1234,8 @@ + (NSArray *)CGColorArray:(id)json

if ([type isEqualToString:@"fileUrl"]) {
return @[NSFilenamesPboardType];
} else if ([type isEqualToString:@"image"]) {
return @[NSPasteboardTypePNG, NSPasteboardTypeTIFF];
}

return @[];
Expand Down
1 change: 1 addition & 0 deletions React/Views/RCTView.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ extern const UIAccessibilityTraits SwitchAccessibilityTrait;
- (BOOL)resignFirstResponder;

#if TARGET_OS_OSX
- (NSDictionary*)dataTransferInfoFromPasteboard:(NSPasteboard*)pasteboard;
- (BOOL)handleKeyboardEvent:(NSEvent *)event;
#endif
// ]TODO(OSS Candidate ISS#2710739)
Expand Down
56 changes: 41 additions & 15 deletions React/Views/RCTView.m
Original file line number Diff line number Diff line change
Expand Up @@ -1527,13 +1527,9 @@ - (void)sendMouseEventWithBlock:(RCTDirectEventBlock)block
block(body);
}

- (NSDictionary*)dataTransferInfoFromPastboard:(NSPasteboard*)pastboard
- (NSDictionary*)dataTransferInfoFromPasteboard:(NSPasteboard*)pasteboard
{
if (![pastboard.types containsObject:NSFilenamesPboardType]) {
return @{};
}

NSArray *fileNames = [pastboard propertyListForType:NSFilenamesPboardType];
NSArray *fileNames = [pasteboard propertyListForType:NSFilenamesPboardType] ?: @[];
NSMutableArray *files = [[NSMutableArray alloc] initWithCapacity:fileNames.count];
NSMutableArray *items = [[NSMutableArray alloc] initWithCapacity:fileNames.count];
NSMutableArray *types = [[NSMutableArray alloc] initWithCapacity:fileNames.count];
Expand All @@ -1559,11 +1555,21 @@ - (NSDictionary*)dataTransferInfoFromPastboard:(NSPasteboard*)pastboard
BOOL success = [fileURL getResourceValue:&fileSizeValue
forKey:NSURLFileSizeKey
error:&fileSizeError];


NSNumber *width = nil;
NSNumber *height = nil;
if ([MIMETypeString hasPrefix:@"image/"]) {
NSImage *image = [[NSImage alloc] initWithContentsOfURL:fileURL];
width = @(image.size.width);
height = @(image.size.height);
}

[files addObject:@{@"name": RCTNullIfNil(fileURL.lastPathComponent),
@"type": RCTNullIfNil(MIMETypeString),
@"uri": RCTNullIfNil(fileURL.absoluteString),
@"size": success ? fileSizeValue : (id)kCFNull
@"uri": RCTNullIfNil(fileURL.path),
@"size": success ? fileSizeValue : (id)kCFNull,
@"width": RCTNullIfNil(width),
@"height": RCTNullIfNil(height)
}];

[items addObject:@{@"kind": @"file",
Expand All @@ -1573,7 +1579,27 @@ - (NSDictionary*)dataTransferInfoFromPastboard:(NSPasteboard*)pastboard
[types addObject:RCTNullIfNil(MIMETypeString)];
}
}


NSPasteboardType imageType = [pasteboard availableTypeFromArray:@[NSPasteboardTypePNG, NSPasteboardTypeTIFF]];
if (imageType && fileNames.count == 0) {
NSString *MIMETypeString = imageType == NSPasteboardTypePNG ? @"image/png" : @"image/tiff";
NSData *imageData = [pasteboard dataForType:imageType];
NSImage *image = [[NSImage alloc] initWithData:imageData];

[files addObject:@{@"type": RCTNullIfNil(MIMETypeString),
@"uri": RCTDataURL(MIMETypeString, imageData).absoluteString,
@"size": @(imageData.length),
@"width": @(image.size.width),
@"height": @(image.size.height),
}];

[items addObject:@{@"kind": @"image",
@"type": RCTNullIfNil(MIMETypeString),
}];

[types addObject:RCTNullIfNil(MIMETypeString)];
}

return @{@"dataTransfer": @{@"files": files,
@"items": items,
@"types": types}};
Expand All @@ -1598,9 +1624,9 @@ - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
[self sendMouseEventWithBlock:self.onDragEnter
locationInfo:[self locationInfoFromDraggingLocation:sender.draggingLocation]
modifierFlags:0
additionalData:[self dataTransferInfoFromPastboard:pboard]];
if ([pboard.types containsObject:NSFilenamesPboardType]) {
additionalData:[self dataTransferInfoFromPasteboard:pboard]];

if ([pboard availableTypeFromArray:self.registeredDraggedTypes]) {
if (sourceDragMask & NSDragOperationLink) {
return NSDragOperationLink;
} else if (sourceDragMask & NSDragOperationCopy) {
Expand All @@ -1615,15 +1641,15 @@ - (void)draggingExited:(id<NSDraggingInfo>)sender
[self sendMouseEventWithBlock:self.onDragLeave
locationInfo:[self locationInfoFromDraggingLocation:sender.draggingLocation]
modifierFlags:0
additionalData:[self dataTransferInfoFromPastboard:sender.draggingPasteboard]];
additionalData:[self dataTransferInfoFromPasteboard:sender.draggingPasteboard]];
}

- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
[self sendMouseEventWithBlock:self.onDrop
locationInfo:[self locationInfoFromDraggingLocation:sender.draggingLocation]
modifierFlags:0
additionalData:[self dataTransferInfoFromPastboard:[sender draggingPasteboard]]];
additionalData:[self dataTransferInfoFromPasteboard:sender.draggingPasteboard]];
return YES;
}
#endif // ]TODO(macOS GH#774)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const {
Text,
TextInput,
View,
Image, // TODO(macOS GH#774)
Platform, // TODO(macOS GH#774)
StyleSheet,
Slider,
Expand All @@ -27,6 +28,7 @@ const {
import type {
KeyboardType,
SettingChangeEvent,
PasteEvent,
} from 'react-native/Libraries/Components/TextInput/TextInput'; // [TODO(macOS GH#774)

const TextInputSharedExamples = require('./TextInputSharedExamples.js');
Expand Down Expand Up @@ -401,6 +403,41 @@ function OnDragEnterOnDragLeaveOnDrop(): React.Node {
</>
);
}

function OnPaste(): React.Node {
const [log, setLog] = React.useState([]);
const appendLog = (line: string) => {
const limit = 3;
let newLog = log.slice(0, limit - 1);
newLog.unshift(line);
setLog(newLog);
};
const [imageUri, setImageUri] = React.useState('');
return (
<>
<TextInput
multiline={true}
style={styles.multiline}
onPaste={(e: PasteEvent) => {
appendLog(JSON.stringify(e.nativeEvent.dataTransfer.types));
setImageUri(e.nativeEvent.dataTransfer.files[0].uri);
}}
placeholder="MULTI LINE with onPaste() for PNG and TIFF images"
/>
<Text style={{height: 30}}>{log.join('\n')}</Text>
<Image
source={{uri: imageUri}}
style={{
width: 128,
height: 128,
margin: 4,
borderWidth: 1,
borderColor: 'white',
}}
/>
</>
);
}
// ]TODO(macOS GH#774)

exports.displayName = (undefined: ?string);
Expand Down Expand Up @@ -915,6 +952,12 @@ if (Platform.OS === 'macos') {
return <OnDragEnterOnDragLeaveOnDrop />;
},
},
{
title: 'onPaste - MultiLineTextInput',
render: function (): React.Node {
return <OnPaste />;
},
},
);
}
// ]TODO(macOS GH#774)