diff --git a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js index 8cc1369..6b42a26 100644 --- a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js +++ b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js @@ -449,6 +449,21 @@ export type AndroidTextInputNativeProps = $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. */ @@ -640,6 +655,9 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = { topScroll: { registrationName: 'onScroll', }, + topPaste: { + registrationName: 'onPaste', + }, }, validAttributes: { acceptDragAndDropTypes: true, @@ -694,6 +712,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = { secureTextEntry: true, textBreakStrategy: true, onScroll: true, + onPaste: true, onContentSizeChange: true, disableFullscreenUI: true, includeFontPadding: true, diff --git a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js index 05bddcb..69c11a2 100644 --- a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js +++ b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js @@ -82,6 +82,9 @@ const RCTTextInputViewConfig: PartialViewConfigWithoutName = { topContentSizeChange: { registrationName: 'onContentSizeChange', }, + topPaste: { + registrationName: 'onPaste', + }, topChangeSync: { registrationName: 'onChangeSync', }, @@ -160,6 +163,7 @@ const RCTTextInputViewConfig: PartialViewConfigWithoutName = { onSelectionChange: true, onContentSizeChange: true, onScroll: true, + onPaste: true, onChangeSync: true, onKeyPressSync: true, }), diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts index b3ca156..2807af2 100644 --- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts +++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts @@ -565,6 +565,16 @@ export interface TextInputSubmitEditingEventData { export type TextInputSubmitEditingEvent = NativeSyntheticEvent; +/** + * @see TextInputProps.onPaste + */ +export interface TextInputPasteEventData extends TargetedEvent { +items: Array<{ + type: string; + data: string; + }>; +} + /** * @see https://reactnative.dev/docs/textinput#props */ @@ -896,6 +906,13 @@ export interface TextInputProps */ onKeyPress?: ((e: TextInputKeyPressEvent) => void) | undefined; + /** + * Invoked when the user performs the paste action. + */ + onPaste?: + | ((e: NativeSyntheticEvent) => void) + | undefined; + /** * The string that will be rendered before text input has been entered */ diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js index 5fa8811..ad2bf61 100644 --- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js +++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js @@ -137,6 +137,18 @@ export type TextInputSubmitEditingEvent = export type TextInputEditingEvent = NativeSyntheticEvent; +export type PasteEvent = SyntheticEvent< + $ReadOnly<{ + target: number, + items: $ReadOnlyArray< + $ReadOnly<{ + type: string, + data: string, + }>, + >, + }>, +>; + type DataDetectorTypesType = | 'phoneNumber' | 'link' @@ -884,6 +896,11 @@ type TextInputBaseProps = $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. */ diff --git a/node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm b/node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm index 6e9c384..2c509eb 100644 --- a/node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm +++ b/node_modules/react-native/Libraries/Text/TextInput/Multiline/RCTUITextView.mm @@ -13,6 +13,10 @@ #import #import +#import +#import +#import + @implementation RCTUITextView { UILabel *_placeholderView; UITextView *_detachedTextView; @@ -209,7 +213,31 @@ - (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 *filePath = RCTTempFilePath(fileExtension, nil); + 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. @@ -301,6 +329,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]; } diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h index 7187177..da00893 100644 --- a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h +++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegate.h @@ -37,6 +37,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)textInputDidChangeSelection; +- (void)textInputDidPaste:(NSString *)type withData:(NSString *)data; + @optional - (void)scrollViewDidScroll:(UIScrollView *)scrollView; diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.h b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.h index f1c32e6..0ce9dfe 100644 --- a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.h +++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.h @@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange; - (void)selectedTextRangeWasSet; +- (void)didPaste:(NSString *)type withData:(NSString *)data; @end @@ -30,6 +31,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithTextView:(UITextView *)backedTextInputView; - (void)skipNextTextInputDidChangeSelectionEventWithTextRange:(UITextRange *)textRange; +- (void)didPaste:(NSString *)type withData:(NSString *)data; @end diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm index 82d9a79..8cc48ec 100644 --- a/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm +++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm @@ -148,6 +148,11 @@ - (void)selectedTextRangeWasSet [self textFieldProbablyDidChangeSelection]; } +- (void)didPaste:(NSString *)type withData:(NSString *)data +{ + [_backedTextInputView.textInputDelegate textInputDidPaste:type withData:data]; +} + #pragma mark - Generalization - (void)textFieldProbablyDidChangeSelection @@ -330,6 +335,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 diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h index 4804624..90b7081 100644 --- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h +++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.h @@ -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; diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm index 6a2d4f8..b6e6060 100644 --- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm +++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm @@ -599,6 +599,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]; diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm index 47adc53..1865e0a 100644 --- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm +++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputViewManager.mm @@ -70,6 +70,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_SHADOW_PROPERTY(text, NSString) RCT_EXPORT_SHADOW_PROPERTY(placeholder, NSString) diff --git a/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm b/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm index 377f41e..b8f48e6 100644 --- a/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm +++ b/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTUITextField.mm @@ -12,6 +12,10 @@ #import #import +#import +#import +#import + @implementation RCTUITextField { RCTBackedTextFieldDelegateAdapter *_textInputDelegateAdapter; NSDictionary *_defaultTextAttributes; @@ -180,6 +184,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]; } @@ -263,7 +271,31 @@ - (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 *filePath = RCTTempFilePath(fileExtension, nil); + 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 diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 577bebe..bbde7fd 100644 --- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -516,6 +516,13 @@ - (void)textInputDidChangeSelection } } +- (void)textInputDidPaste:(NSString *)type withData:(NSString *)data +{ + if (_eventEmitter) { + static_cast(*_eventEmitter).onPaste(std::string([type UTF8String]), std::string([data UTF8String])); + } +} + #pragma mark - RCTBackedTextInputDelegate (UIScrollViewDelegate) - (void)scrollViewDidScroll:(UIScrollView *)scrollView diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/PasteWatcher.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/PasteWatcher.kt new file mode 100644 index 0000000..a684b87 --- /dev/null +++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/PasteWatcher.kt @@ -0,0 +1,17 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.views.textinput + +/** + * Implement this interface to be informed of paste event in the + * ReactTextEdit This is used by the ReactTextInputManager to forward events + * from the EditText to JS + */ +public fun interface PasteWatcher { + public fun onPaste(type: String, data: String) +} diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt index 42f6e03..2f1a865 100644 --- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt +++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt @@ -8,6 +8,10 @@ package com.facebook.react.views.textinput import android.annotation.SuppressLint +import android.content.ClipboardManager +import android.content.ClipData +import android.content.ClipDescription +import android.content.ContentResolver import android.content.Context import android.content.res.Configuration import android.graphics.Canvas @@ -15,6 +19,8 @@ import android.graphics.Color import android.graphics.Paint import android.graphics.Rect import android.graphics.drawable.Drawable +import android.net.Uri +import com.facebook.react.uimanager.UIManagerHelper.getReactContext import android.os.Build import android.os.Bundle import android.text.Editable @@ -128,6 +134,7 @@ public open class ReactEditText public constructor(context: Context) : AppCompat private var selectionWatcher: SelectionWatcher? = null private var contentSizeWatcher: ContentSizeWatcher? = null private var scrollWatcher: ScrollWatcher? + private var pasteWatcher: PasteWatcher? private var keyListener: InternalKeyListener? = null private var detectScrollMovement = false private var onKeyPress = false @@ -212,6 +219,7 @@ public open class ReactEditText public constructor(context: Context) : AppCompat keyListener = InternalKeyListener() } scrollWatcher = null + pasteWatcher = null textAttributes = TextAttributes() applyTextAttributes() @@ -356,9 +364,56 @@ public open class ReactEditText public constructor(context: Context) : AppCompat * Called when a context menu option for the text view is selected. * React Native replaces copy (as rich text) with copy as plain text. */ - override fun onTextContextMenuItem(id: Int): Boolean = - super.onTextContextMenuItem( - if (id == android.R.id.paste) android.R.id.pasteAsPlainText else id) + override fun onTextContextMenuItem(id: Int): Boolean { + val modifiedId = if (id == android.R.id.paste || id == android.R.id.pasteAsPlainText) { + android.R.id.pasteAsPlainText + } else { + id + } + + if (modifiedId == android.R.id.pasteAsPlainText) { + val clipboardManager = getContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clipData = clipboardManager.primaryClip + if (clipData != null) { + val item = clipData.getItemAt(0) + val itemUri = item.uri + var type: String? = null + var data: String? = null + + if (itemUri != null) { + // First try to get MIME type from ClipData description (more reliable for FileProvider URIs) + type = if (clipData.description.mimeTypeCount > 0) { + clipData.description.getMimeType(0) + } else { + // Fall back to ContentResolver + val cr = getReactContext(this).contentResolver + cr.getType(itemUri) + } + if (type != null && type != ClipDescription.MIMETYPE_TEXT_PLAIN) { + data = itemUri.toString() + if (pasteWatcher != null) { + pasteWatcher?.onPaste(type, data) + } + // Prevents default behavior to avoid inserting raw binary data into the text field + return true + } + } + + if (clipData.description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { + type = ClipDescription.MIMETYPE_TEXT_PLAIN + val text: CharSequence? = item.text + if (text != null) { + data = text.toString() + if (pasteWatcher != null) { + pasteWatcher?.onPaste(type, data) + } + // Don't return - let the system proceed with default text pasting behavior + } + } + } + } + return super.onTextContextMenuItem(id) + } internal fun clearFocusAndMaybeRefocus() { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P || !isInTouchMode) { @@ -421,6 +476,10 @@ public open class ReactEditText public constructor(context: Context) : AppCompat this.scrollWatcher = scrollWatcher } + public fun setPasteWatcher(pasteWatcher: PasteWatcher?) { + this.pasteWatcher = pasteWatcher + } + /** * Attempt to set a selection or fail silently. Intentionally meant to handle bad inputs. * EventCounter is the same one used as with text. diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.kt index 42c13a1..c39e240 100644 --- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.kt +++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.kt @@ -133,6 +133,8 @@ public open class ReactTextInputManager public constructor() : val eventTypeConstants = baseEventTypeConstants ?: mutableMapOf() eventTypeConstants.putAll( mapOf(getJSEventName(ScrollEventType.SCROLL) to mapOf("registrationName" to "onScroll"))) + eventTypeConstants.putAll( + mapOf("topPaste" to mapOf("registrationName" to "onPaste"))) return eventTypeConstants } @@ -327,6 +329,15 @@ public open class ReactTextInputManager public constructor() : } } + @ReactProp(name = "onPaste", defaultBoolean = false) + public fun setOnPaste(view: ReactEditText, onPaste: Boolean) { + if (onPaste) { + view.setPasteWatcher(ReactPasteWatcher(view)) + } else { + view.setPasteWatcher(null) + } + } + @ReactProp(name = "onKeyPress", defaultBoolean = false) public fun setOnKeyPress(view: ReactEditText, onKeyPress: Boolean) { view.setOnKeyPress(onKeyPress) @@ -944,6 +955,24 @@ public open class ReactTextInputManager public constructor() : } } + private class ReactPasteWatcher(editText: ReactEditText) : PasteWatcher { + private val mReactEditText: ReactEditText = editText + private val mEventDispatcher: EventDispatcher? + private val mSurfaceId: Int + + init { + val reactContext = UIManagerHelper.getReactContext(editText) + mEventDispatcher = getEventDispatcher(reactContext, editText) + mSurfaceId = UIManagerHelper.getSurfaceId(reactContext) + } + + override fun onPaste(type: String, data: String) { + mEventDispatcher?.dispatchEvent( + ReactTextInputPasteEvent(mSurfaceId, mReactEditText.id, type, data) + ) + } + } + override fun getExportedViewConstants(): Map = mapOf( "AutoCapitalizationType" to diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputPasteEvent.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputPasteEvent.kt new file mode 100644 index 0000000..6f5b10b --- /dev/null +++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputPasteEvent.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.views.textinput + +import androidx.annotation.Nullable +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.WritableArray +import com.facebook.react.uimanager.common.ViewUtil +import com.facebook.react.uimanager.events.Event + +/** + * Event emitted by EditText native view when clipboard content is pasted + */ +public class ReactTextInputPasteEvent : Event { + + public companion object { + private const val EVENT_NAME = "topPaste" + } + + private val mType: String + private val mData: String + + @Deprecated("Use constructor with surfaceId") + public constructor(viewId: Int, type: String, data: String) : + this(ViewUtil.NO_SURFACE_ID, viewId, type, data) + + public constructor(surfaceId: Int, viewId: Int, type: String, data: String) : + super(surfaceId, viewId) { + mType = type + mData = data + } + + override fun getEventName(): String { + return EVENT_NAME + } + + override fun canCoalesce(): Boolean { + return false + } + + @Nullable + override fun getEventData(): WritableMap? { + val eventData = Arguments.createMap() + + val items: WritableArray = Arguments.createArray() + val item: WritableMap = Arguments.createMap() + item.putString("type", mType) + item.putString("data", mData) + items.pushMap(item) + + eventData.putArray("items", items) + + return eventData + } +} diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp index a9bc219..7ebec12 100644 --- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp +++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp @@ -177,6 +177,19 @@ void TextInputEventEmitter::onScroll(const Metrics& textInputMetrics) const { }); } +void TextInputEventEmitter::onPaste(const std::string& type, const std::string& data) const { + dispatchEvent("onPaste", [type, data](jsi::Runtime& runtime) { + auto payload = jsi::Object(runtime); + auto items = jsi::Array(runtime, 1); + auto item = jsi::Object(runtime); + item.setProperty(runtime, "type", type); + item.setProperty(runtime, "data", data); + items.setValueAtIndex(runtime, 0, item); + payload.setProperty(runtime, "items", items); + return payload; + }); +} + void TextInputEventEmitter::dispatchTextInputEvent( const std::string& name, const Metrics& textInputMetrics, diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.h b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.h index dbce575..5b03581 100644 --- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.h +++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.h @@ -44,6 +44,7 @@ class TextInputEventEmitter : public ViewEventEmitter { void onSubmitEditing(const Metrics& textInputMetrics) const; void onKeyPress(const KeyPressMetrics& keyPressMetrics) const; void onScroll(const Metrics& textInputMetrics) const; + void onPaste(const std::string& type, const std::string& data) const; private: void dispatchTextInputEvent(