Skip to content

Commit 1ae9a89

Browse files
SaadnajmiNick Lefevershwantontido64
authored
feat(fabric): Implement Paste for TextInput (#2715)
## Summary: Implement onPaste for TextInput. This is fairly heavy as it involves heavily refactoring the `DataTransfer` object that is created and sent to JS: Notable changes: - Expose a `dataTransferPayload` method on `HostPlatformViewEventEmitter`. This means we now have a public method that returns a `JSI::Value`, which feels a bit odd. - Refactor the payload to not just be an array of items, but an object with files/iterms/types. This matches the [paper implementation](https://github.com/microsoft/react-native-macos/blob/258ce1ffac76cee44368ae3bb3bc6f911a14260e/packages/react-native/React/Views/RCTView.m#L1690-L1692) and roughly the [DataTransfer Web API](https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer) - Add a `pastedTypes` prop that matches paper to Fabric TextInput ## Test Plan: Existing example in Drag and drop test page still works, and returns the same object payload as paper. --------- Co-authored-by: Nick Lefever <[email protected]> Co-authored-by: Shawn Dempsey <[email protected]> Co-authored-by: Tommy Nguyen <[email protected]>
1 parent 55dbdfe commit 1ae9a89

File tree

21 files changed

+298
-126
lines changed

21 files changed

+298
-126
lines changed

packages/react-native/Libraries/Components/TextInput/TextInput.flow.js

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import type {HostInstance} from '../../../src/private/types/HostInstance';
1212
import type {
13+
DataTransfer,
1314
GestureResponderEvent,
1415
NativeSyntheticEvent,
1516
ScrollEvent,
@@ -108,20 +109,7 @@ export type SettingChangeEvent = NativeSyntheticEvent<
108109

109110
export type PasteEvent = NativeSyntheticEvent<
110111
$ReadOnly<{|
111-
dataTransfer: {|
112-
files: $ReadOnlyArray<{|
113-
height: number,
114-
size: number,
115-
type: string,
116-
uri: string,
117-
width: number,
118-
|}>,
119-
items: $ReadOnlyArray<{|
120-
kind: string,
121-
type: string,
122-
|}>,
123-
types: $ReadOnlyArray<string>,
124-
|},
112+
dataTransfer: DataTransfer,
125113
|}>,
126114
>;
127115

packages/react-native/Libraries/Components/TextInput/TextInput.js

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import type {HostInstance} from '../../../src/private/types/HostInstance';
1212
import type {____TextStyle_Internal as TextStyleInternal} from '../../StyleSheet/StyleSheetTypes';
1313
import type {
14+
DataTransfer,
1415
GestureResponderEvent,
1516
KeyEvent, // [macOS]
1617
HandledKeyEvent, // [macOS]
@@ -145,20 +146,7 @@ export type SettingChangeEvent = NativeSyntheticEvent<
145146

146147
export type PasteEvent = NativeSyntheticEvent<
147148
$ReadOnly<{|
148-
dataTransfer: {|
149-
files: $ReadOnlyArray<{|
150-
height: number,
151-
size: number,
152-
type: string,
153-
uri: string,
154-
width: number,
155-
|}>,
156-
items: $ReadOnlyArray<{|
157-
kind: string,
158-
type: string,
159-
|}>,
160-
types: $ReadOnlyArray<string>,
161-
|},
149+
dataTransfer: DataTransfer,
162150
|}>,
163151
>;
164152

packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ NS_ASSUME_NONNULL_BEGIN
8888
#if TARGET_OS_OSX // [macOS
8989
// UITextInput method for OSX
9090
- (CGSize)sizeThatFits:(CGSize)size;
91+
- (void)setReadablePasteBoardTypes:(NSArray<NSPasteboardType> *)readablePasteboardTypes;
9192
#endif // macOS]
9293

9394
// This protocol disallows direct access to `text` property because

packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ NS_ASSUME_NONNULL_BEGIN
6969
@property (nonatomic, strong, nullable) RCTUIColor *selectionColor;
7070
@property (weak, nullable) id<RCTUITextFieldDelegate> delegate;
7171
@property (nonatomic, assign) CGFloat pointScaleFactor;
72+
73+
- (void)setReadablePasteBoardTypes:(NSArray<NSPasteboardType> *)readablePasteboardTypes;
7274
#endif // macOS]
7375

7476
@property (nonatomic, getter=isGhostTextChanging) BOOL ghostTextChanging; // [macOS]

packages/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ @implementation RCTUITextField {
9999
#endif // [macOS]
100100
#if TARGET_OS_OSX // [macOS
101101
BOOL _isUpdatingPlaceholderText;
102+
NSArray<NSPasteboardType> *_readablePasteboardTypes;
102103
#endif // macOS]
103104
}
104105

@@ -705,5 +706,12 @@ - (void)keyUp:(NSEvent *)event {
705706
}
706707
}
707708
#endif // macOS]
709+
710+
#if TARGET_OS_OSX // [macOS
711+
- (void)setReadablePasteBoardTypes:(NSArray<NSPasteboardType> *)readablePasteboardTypes
712+
{
713+
_readablePasteboardTypes = readablePasteboardTypes;
714+
}
715+
#endif // macOS]
708716

709717
@end

packages/react-native/Libraries/Types/CoreEventTypes.d.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -307,19 +307,24 @@ export interface FocusEvent extends NativeSyntheticEvent<NativeFocusEvent> {}
307307
export interface BlueEvent extends NativeSyntheticEvent<NativeBlurEvent> {}
308308

309309
// Drag and Drop types
310-
export interface DataTransferItem {
310+
export interface DataTransferFile {
311311
name: string;
312-
kind: string;
313-
type: string;
312+
type: string | null | undefined;
314313
uri: string;
315314
size?: number | undefined;
316315
width?: number | undefined;
317316
height?: number | undefined;
318317
}
319318

319+
export interface DataTransferItem {
320+
kind: string;
321+
type: string | null | undefined;
322+
}
323+
320324
export interface DataTransfer {
321-
files: ReadonlyArray<DataTransferItem>;
322-
types: ReadonlyArray<string>;
325+
files: ReadonlyArray<DataTransferFile>;
326+
items: ReadonlyArray<DataTransferItem>;
327+
types: ReadonlyArray<string | null | undefined>;
323328
}
324329

325330
export interface DragEvent extends MouseEvent {

packages/react-native/Libraries/Types/CoreEventTypes.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -333,19 +333,24 @@ export type MouseEvent = NativeSyntheticEvent<
333333
>;
334334

335335
// [macOS
336-
export type DataTransferItem = $ReadOnly<{
336+
export type DataTransferFile = $ReadOnly<{
337337
name: string,
338-
kind: string,
339-
type: string,
338+
type: ?string,
340339
uri: string,
341340
size?: number,
342341
width?: number,
343342
height?: number,
344343
}>;
345344

345+
export type DataTransferItem = $ReadOnly<{
346+
kind: string,
347+
type: ?string,
348+
}>;
349+
346350
export type DataTransfer = $ReadOnly<{
347-
files: $ReadOnlyArray<DataTransferItem>,
348-
types: $ReadOnlyArray<string>,
351+
files: $ReadOnlyArray<DataTransferFile>,
352+
items: $ReadOnlyArray<DataTransferItem>,
353+
types: $ReadOnlyArray<?string>,
349354
}>;
350355

351356
export type DragEvent = NativeSyntheticEvent<

packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap

Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2711,20 +2711,7 @@ export type SettingChangeEvent = NativeSyntheticEvent<
27112711
>;
27122712
export type PasteEvent = NativeSyntheticEvent<
27132713
$ReadOnly<{|
2714-
dataTransfer: {|
2715-
files: $ReadOnlyArray<{|
2716-
height: number,
2717-
size: number,
2718-
type: string,
2719-
uri: string,
2720-
width: number,
2721-
|}>,
2722-
items: $ReadOnlyArray<{|
2723-
kind: string,
2724-
type: string,
2725-
|}>,
2726-
types: $ReadOnlyArray<string>,
2727-
|},
2714+
dataTransfer: DataTransfer,
27282715
|}>,
27292716
>;
27302717
export type SubmitKeyEvent = $ReadOnly<{|
@@ -3119,20 +3106,7 @@ export type SettingChangeEvent = NativeSyntheticEvent<
31193106
>;
31203107
export type PasteEvent = NativeSyntheticEvent<
31213108
$ReadOnly<{|
3122-
dataTransfer: {|
3123-
files: $ReadOnlyArray<{|
3124-
height: number,
3125-
size: number,
3126-
type: string,
3127-
uri: string,
3128-
width: number,
3129-
|}>,
3130-
items: $ReadOnlyArray<{|
3131-
kind: string,
3132-
type: string,
3133-
|}>,
3134-
types: $ReadOnlyArray<string>,
3135-
|},
3109+
dataTransfer: DataTransfer,
31363110
|}>,
31373111
>;
31383112
export type SubmitKeyEvent = $ReadOnly<{|
@@ -8552,18 +8526,22 @@ export type MouseEvent = NativeSyntheticEvent<
85528526
timestamp: number,
85538527
}>,
85548528
>;
8555-
export type DataTransferItem = $ReadOnly<{
8529+
export type DataTransferFile = $ReadOnly<{
85568530
name: string,
8557-
kind: string,
8558-
type: string,
8531+
type: ?string,
85598532
uri: string,
85608533
size?: number,
85618534
width?: number,
85628535
height?: number,
85638536
}>;
8537+
export type DataTransferItem = $ReadOnly<{
8538+
kind: string,
8539+
type: ?string,
8540+
}>;
85648541
export type DataTransfer = $ReadOnly<{
8565-
files: $ReadOnlyArray<DataTransferItem>,
8566-
types: $ReadOnlyArray<string>,
8542+
files: $ReadOnlyArray<DataTransferFile>,
8543+
items: $ReadOnlyArray<DataTransferItem>,
8544+
types: $ReadOnlyArray<?string>,
85678545
}>;
85688546
export type DragEvent = NativeSyntheticEvent<
85698547
$ReadOnly<{

packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,13 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
344344
_backedTextInputView.disableKeyboardShortcuts = newTextInputProps.disableKeyboardShortcuts;
345345
}
346346

347+
#if TARGET_OS_OSX // [macOS
348+
if (newTextInputProps.traits.pastedTypes!= oldTextInputProps.traits.pastedTypes) {
349+
NSArray<NSPasteboardType> *types = RCTPasteboardTypeArrayFromProps(newTextInputProps.traits.pastedTypes);
350+
[_backedTextInputView setReadablePasteBoardTypes:types];
351+
}
352+
#endif // macOS]
353+
347354
[super updateProps:props oldProps:oldProps];
348355

349356
#if TARGET_OS_IOS // [macOS] [visionOS]
@@ -602,7 +609,19 @@ - (BOOL)textInputShouldHandleKeyEvent:(nonnull NSEvent *)event {
602609
}
603610

604611
- (BOOL)textInputShouldHandlePaste:(nonnull id<RCTBackedTextInputViewProtocol>)sender {
605-
return YES;
612+
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
613+
NSPasteboardType fileType = [pasteboard availableTypeFromArray:@[NSFilenamesPboardType, NSPasteboardTypePNG, NSPasteboardTypeTIFF]];
614+
NSArray<NSPasteboardType>* pastedTypes = ((RCTUITextView*) _backedTextInputView).readablePasteboardTypes;
615+
616+
// If there's a fileType that is of interest, notify JS. Also blocks notifying JS if it's a text paste
617+
if (_eventEmitter && fileType != nil && [pastedTypes containsObject:fileType]) {
618+
auto const &textInputEventEmitter = *std::static_pointer_cast<TextInputEventEmitter const>(_eventEmitter);
619+
DataTransfer dataTransfer = [self dataTransferForPasteboard:pasteboard];
620+
textInputEventEmitter.onPaste({.dataTransfer = std::move(dataTransfer)});
621+
}
622+
623+
// Only allow pasting text.
624+
return fileType == nil;
606625
}
607626

608627
#endif // macOS]

packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ NS_ASSUME_NONNULL_BEGIN
8585

8686
#if TARGET_OS_OSX // [macOS
8787
- (BOOL)handleKeyboardEvent:(NSEvent *)event;
88-
- (void)buildDataTransferItems:(std::vector<facebook::react::DataTransferItem> &)dataTransferItems forPasteboard:(NSPasteboard *)pasteboard;
88+
- (facebook::react::DataTransfer)dataTransferForPasteboard:(NSPasteboard *)pasteboard;
8989
#endif // macOS]
9090

9191
/*

0 commit comments

Comments
 (0)