Skip to content

Commit 93f12eb

Browse files
Abbondanzofacebook-github-bot
authored andcommitted
Add prop to filter drag and drop pasting on iOS (facebook#50533)
Summary: Pull Request resolved: facebook#50533 Builds upon facebook#49446 On iOS, by default, every EditText accepts DragEvent and will automatically focus themselves to accept these data. In some rare cases, it might not be desirable to allow data from arbitrary drag and drop events to be pasted into a text input. This change adds a new prop `acceptDragAndDropTypes` to do exactly that: reject drag and drop events by telling the system to ignore certain types of drag data and, by proxy, disabling behavior that automatically focuses the text input. The prop accepts a list of [Uniform Type Identifiers](https://developer.apple.com/documentation/uniformtypeidentifiers) that iOS supports. It's important to note that these are *not* MIME types. A MIME type would be something like `text/plain` but the equivalent for iOS is `public.plain-text`. It's important to note that this is an experimental prop, as is evident by the `experimental_` prefix on the JS side. Its signature could change before the prop has fully matured, use at your own risk Changelog: [iOS][Added] - Add new prop for filtering drag and drop targeting to text inputs Reviewed By: javache Differential Revision: D70992749 fbshipit-source-id: 22b5aa1b4ced14147bf16a844361acf6f99c5a40
1 parent d10dd71 commit 93f12eb

File tree

15 files changed

+188
-43
lines changed

15 files changed

+188
-43
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ const RCTTextInputViewConfig = {
9090
},
9191
},
9292
validAttributes: {
93+
acceptDragAndDropTypes: true,
9394
dynamicTypeRamp: true,
9495
fontSize: true,
9596
fontWeight: true,

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

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -405,19 +405,6 @@ export type TextInputIOSProps = $ReadOnly<{
405405
}>;
406406

407407
export type TextInputAndroidProps = $ReadOnly<{
408-
/**
409-
* When provided, the text input will only accept drag and drop events for the specified
410-
* mime types. If null or not provided, the text input will accept all types of drag and drop
411-
* events.
412-
* Defaults to null.
413-
*
414-
* *NOTE*: This prop is experimental and its API may change in the future. Use at your own risk.
415-
*
416-
* @platform android
417-
* @see https://developer.android.com/reference/android/content/ClipData for more information on MIME types
418-
*/
419-
experimental_acceptDragAndDropTypes?: ?$ReadOnlyArray<string>,
420-
421408
/**
422409
* When provided it will set the color of the cursor (or "caret") in the component.
423410
* Unlike the behavior of `selectionColor` the cursor color will be set independently
@@ -527,6 +514,26 @@ export type TextInputAndroidProps = $ReadOnly<{
527514
}>;
528515

529516
type TextInputBaseProps = $ReadOnly<{
517+
/**
518+
* When provided, the text input will only accept drag and drop events for the specified
519+
* types. If null or not provided, the text input will accept all types of drag and drop events.
520+
* An empty array will accept no drag and drop events.
521+
* Defaults to null.
522+
*
523+
* On Android, types must correspond to MIME types from ClipData:
524+
* https://developer.android.com/reference/android/content/ClipData
525+
* (e.g. "text/plain" or "image/*")
526+
*
527+
* On iOS, types must correspond to UTIs:
528+
* https://developer.apple.com/documentation/uniformtypeidentifiers
529+
* (e.g. "public.plain-text" or "public.image")
530+
*
531+
* *NOTE*: This prop is experimental and its API may change in the future. Use at your own risk.
532+
*
533+
* @see https://developer.android.com/reference/android/content/ClipData for more information on MIME types
534+
*/
535+
experimental_acceptDragAndDropTypes?: ?$ReadOnlyArray<string>,
536+
530537
/**
531538
* Can tell `TextInput` to automatically capitalize certain characters.
532539
*

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,7 @@ function InternalTextInput(props: TextInputProps): React.Node {
675675
ref={(ref: $FlowFixMe)}
676676
{...otherProps}
677677
{...eventHandlers}
678+
acceptDragAndDropTypes={props.experimental_acceptDragAndDropTypes}
678679
accessibilityState={_accessibilityState}
679680
accessible={accessible}
680681
submitBehavior={submitBehavior}

packages/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ @implementation RCTUITextView {
2020
NSDictionary<NSAttributedStringKey, id> *_defaultTextAttributes;
2121
NSArray<UIBarButtonItemGroup *> *_initialValueLeadingBarButtonGroups;
2222
NSArray<UIBarButtonItemGroup *> *_initialValueTrailingBarButtonGroups;
23+
NSArray<NSString *> *_acceptDragAndDropTypes;
2324
}
2425

2526
static UIFont *defaultPlaceholderFont(void)
@@ -102,6 +103,16 @@ - (NSString *)accessibilityLabel
102103

103104
#pragma mark - Properties
104105

106+
- (void)setAcceptDragAndDropTypes:(NSArray<NSString *> *)acceptDragAndDropTypes
107+
{
108+
_acceptDragAndDropTypes = acceptDragAndDropTypes;
109+
}
110+
111+
- (nullable NSArray<NSString *> *)acceptDragAndDropTypes
112+
{
113+
return _acceptDragAndDropTypes;
114+
}
115+
105116
- (void)setPlaceholder:(NSString *)placeholder
106117
{
107118
_placeholder = placeholder;

packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
static void *TextFieldSelectionObservingContext = &TextFieldSelectionObservingContext;
1313

14-
@interface RCTBackedTextFieldDelegateAdapter () <UITextFieldDelegate>
14+
@interface RCTBackedTextFieldDelegateAdapter () <UITextFieldDelegate, UITextDropDelegate>
1515
@end
1616

1717
@implementation RCTBackedTextFieldDelegateAdapter {
@@ -25,6 +25,7 @@ - (instancetype)initWithTextField:(UITextField<RCTBackedTextInputViewProtocol> *
2525
if (self = [super init]) {
2626
_backedTextInputView = backedTextInputView;
2727
backedTextInputView.delegate = self;
28+
backedTextInputView.textDropDelegate = self;
2829

2930
[_backedTextInputView addTarget:self
3031
action:@selector(textFieldDidChange)
@@ -159,11 +160,47 @@ - (void)textFieldProbablyDidChangeSelection
159160
[_backedTextInputView.textInputDelegate textInputDidChangeSelection];
160161
}
161162

163+
#pragma mark - UITextDropDelegate
164+
165+
- (UITextDropEditability)textDroppableView:(UIView<UITextDroppable> *)textDroppableView
166+
willBecomeEditableForDrop:(id<UITextDropRequest>)drop
167+
{
168+
if (!_backedTextInputView.enabled) {
169+
return UITextDropEditabilityNo;
170+
}
171+
if ([self _shouldAcceptDrop:drop]) {
172+
return UITextDropEditabilityYes;
173+
} else {
174+
return UITextDropEditabilityNo;
175+
}
176+
}
177+
178+
- (UITextDropProposal *)textDroppableView:(UIView<UITextDroppable> *)textDroppableView
179+
proposalForDrop:(id<UITextDropRequest>)drop
180+
{
181+
if ([self _shouldAcceptDrop:drop]) {
182+
return drop.suggestedProposal;
183+
} else {
184+
return [[UITextDropProposal alloc] initWithDropOperation:UIDropOperationCancel];
185+
}
186+
}
187+
188+
- (bool)_shouldAcceptDrop:(id<UITextDropRequest>)drop
189+
{
190+
if (_backedTextInputView.acceptDragAndDropTypes) {
191+
// If we have accepted types, we should only accept drops that match one of them
192+
return [drop.dropSession hasItemsConformingToTypeIdentifiers:_backedTextInputView.acceptDragAndDropTypes];
193+
} else {
194+
// If we don't have any accepted types, we should accept any drop
195+
return true;
196+
}
197+
}
198+
162199
@end
163200

164201
#pragma mark - RCTBackedTextViewDelegateAdapter (for UITextView)
165202

166-
@interface RCTBackedTextViewDelegateAdapter () <UITextViewDelegate>
203+
@interface RCTBackedTextViewDelegateAdapter () <UITextViewDelegate, UITextDropDelegate>
167204
@end
168205

169206
@implementation RCTBackedTextViewDelegateAdapter {
@@ -179,6 +216,7 @@ - (instancetype)initWithTextView:(UITextView<RCTBackedTextInputViewProtocol> *)b
179216
if (self = [super init]) {
180217
_backedTextInputView = backedTextInputView;
181218
backedTextInputView.delegate = self;
219+
backedTextInputView.textDropDelegate = self;
182220
}
183221

184222
return self;
@@ -304,4 +342,40 @@ - (void)textViewProbablyDidChangeSelection
304342
[_backedTextInputView.textInputDelegate textInputDidChangeSelection];
305343
}
306344

345+
#pragma mark - UITextDropDelegate
346+
347+
- (UITextDropEditability)textDroppableView:(UIView<UITextDroppable> *)textDroppableView
348+
willBecomeEditableForDrop:(id<UITextDropRequest>)drop
349+
{
350+
if (!_backedTextInputView.isEditable) {
351+
return UITextDropEditabilityNo;
352+
}
353+
if ([self _shouldAcceptDrop:drop]) {
354+
return UITextDropEditabilityYes;
355+
} else {
356+
return UITextDropEditabilityNo;
357+
}
358+
}
359+
360+
- (UITextDropProposal *)textDroppableView:(UIView<UITextDroppable> *)textDroppableView
361+
proposalForDrop:(id<UITextDropRequest>)drop
362+
{
363+
if ([self _shouldAcceptDrop:drop]) {
364+
return drop.suggestedProposal;
365+
} else {
366+
return [[UITextDropProposal alloc] initWithDropOperation:UIDropOperationCancel];
367+
}
368+
}
369+
370+
- (bool)_shouldAcceptDrop:(id<UITextDropRequest>)drop
371+
{
372+
if (_backedTextInputView.acceptDragAndDropTypes) {
373+
// If we have accepted types, we should only accept drops that match one of them
374+
return [drop.dropSession hasItemsConformingToTypeIdentifiers:(_backedTextInputView.acceptDragAndDropTypes)];
375+
} else {
376+
// If we don't have any accepted types, we should accept any drop
377+
return true;
378+
}
379+
}
380+
307381
@end

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ NS_ASSUME_NONNULL_BEGIN
3838
@property (nonatomic, assign, readonly) CGPoint contentOffset;
3939
@property (nonatomic, assign, readonly) UIEdgeInsets contentInset;
4040
@property (nullable, nonatomic, copy) NSDictionary<NSAttributedStringKey, id> *typingAttributes;
41+
@property (nonatomic, strong, nullable) NSArray<NSString *> *acceptDragAndDropTypes;
4142

4243
// This protocol disallows direct access to `selectedTextRange` property because
4344
// unwise usage of it can break the `delegate` behavior. So, we always have to

packages/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ @implementation RCTBaseTextInputViewManager {
4848
RCT_REMAP_VIEW_PROPERTY(scrollEnabled, backedTextInputView.scrollEnabled, BOOL)
4949
RCT_REMAP_VIEW_PROPERTY(secureTextEntry, backedTextInputView.secureTextEntry, BOOL)
5050
RCT_REMAP_VIEW_PROPERTY(smartInsertDelete, backedTextInputView.smartInsertDeleteType, UITextSmartInsertDeleteType)
51+
5152
RCT_EXPORT_VIEW_PROPERTY(autoFocus, BOOL)
5253
RCT_EXPORT_VIEW_PROPERTY(submitBehavior, NSString)
5354
RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL)
@@ -60,17 +61,16 @@ @implementation RCTBaseTextInputViewManager {
6061
RCT_EXPORT_VIEW_PROPERTY(inputAccessoryViewButtonLabel, NSString)
6162
RCT_EXPORT_VIEW_PROPERTY(textContentType, NSString)
6263
RCT_EXPORT_VIEW_PROPERTY(passwordRules, NSString)
64+
RCT_EXPORT_VIEW_PROPERTY(mostRecentEventCount, NSInteger)
65+
RCT_EXPORT_VIEW_PROPERTY(disableKeyboardShortcuts, BOOL)
66+
RCT_EXPORT_VIEW_PROPERTY(acceptDragAndDropTypes, NSArray<NSString *>)
6367

6468
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
6569
RCT_EXPORT_VIEW_PROPERTY(onKeyPressSync, RCTDirectEventBlock)
6670
RCT_EXPORT_VIEW_PROPERTY(onChangeSync, RCTDirectEventBlock)
6771
RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)
6872
RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock)
6973

70-
RCT_EXPORT_VIEW_PROPERTY(mostRecentEventCount, NSInteger)
71-
72-
RCT_EXPORT_VIEW_PROPERTY(disableKeyboardShortcuts, BOOL)
73-
7474
RCT_EXPORT_SHADOW_PROPERTY(text, NSString)
7575
RCT_EXPORT_SHADOW_PROPERTY(placeholder, NSString)
7676
RCT_EXPORT_SHADOW_PROPERTY(onContentSizeChange, RCTDirectEventBlock)

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ @implementation RCTUITextField {
1717
NSDictionary<NSAttributedStringKey, id> *_defaultTextAttributes;
1818
NSArray<UIBarButtonItemGroup *> *_initialValueLeadingBarButtonGroups;
1919
NSArray<UIBarButtonItemGroup *> *_initialValueTrailingBarButtonGroups;
20+
NSArray<NSString *> *_acceptDragAndDropTypes;
2021
}
2122

2223
// This should not be needed but internal build were failing without it.
@@ -57,6 +58,16 @@ - (void)setIsAccessibilityElement:(BOOL)isAccessibilityElement
5758

5859
#pragma mark - Properties
5960

61+
- (void)setAcceptDragAndDropTypes:(NSArray<NSString *> *)acceptDragAndDropTypes
62+
{
63+
_acceptDragAndDropTypes = acceptDragAndDropTypes;
64+
}
65+
66+
- (nullable NSArray<NSString *> *)acceptDragAndDropTypes
67+
{
68+
return _acceptDragAndDropTypes;
69+
}
70+
6071
- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset
6172
{
6273
_textContainerInset = textContainerInset;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2831,7 +2831,6 @@ export type TextInputIOSProps = $ReadOnly<{
28312831
smartInsertDelete?: ?boolean,
28322832
}>;
28332833
export type TextInputAndroidProps = $ReadOnly<{
2834-
experimental_acceptDragAndDropTypes?: ?$ReadOnlyArray<string>,
28352834
cursorColor?: ?ColorValue,
28362835
selectionHandleColor?: ?ColorValue,
28372836
disableFullscreenUI?: ?boolean,
@@ -2852,6 +2851,7 @@ export type TextInputAndroidProps = $ReadOnly<{
28522851
underlineColorAndroid?: ?ColorValue,
28532852
}>;
28542853
type TextInputBaseProps = $ReadOnly<{
2854+
experimental_acceptDragAndDropTypes?: ?$ReadOnlyArray<string>,
28552855
autoCapitalize?: ?AutoCapitalize,
28562856
autoComplete?: ?(
28572857
| \\"additional-name\\"

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@
2929

3030
using namespace facebook::react;
3131

32-
@interface RCTTextInputComponentView () <RCTBackedTextInputDelegate, RCTTextInputViewProtocol>
32+
@interface RCTTextInputComponentView () <
33+
RCTBackedTextInputDelegate,
34+
RCTTextInputViewProtocol,
35+
UIDropInteractionDelegate>
3336
@end
3437

3538
static NSSet<NSNumber *> *returnKeyTypesSet;
@@ -307,6 +310,19 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
307310
_backedTextInputView.disableKeyboardShortcuts = newTextInputProps.disableKeyboardShortcuts;
308311
}
309312

313+
if (newTextInputProps.acceptDragAndDropTypes != oldTextInputProps.acceptDragAndDropTypes) {
314+
if (!newTextInputProps.acceptDragAndDropTypes.has_value()) {
315+
_backedTextInputView.acceptDragAndDropTypes = nil;
316+
} else {
317+
auto &vector = newTextInputProps.acceptDragAndDropTypes.value();
318+
NSMutableArray<NSString *> *array = [NSMutableArray arrayWithCapacity:vector.size()];
319+
for (const std::string &str : vector) {
320+
[array addObject:[NSString stringWithUTF8String:str.c_str()]];
321+
}
322+
_backedTextInputView.acceptDragAndDropTypes = array;
323+
}
324+
}
325+
310326
[super updateProps:props oldProps:oldProps];
311327

312328
[self setDefaultInputAccessoryView];

0 commit comments

Comments
 (0)