@tbvjaos510/react-native-paste-input 0.9.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +47 -22
  2. package/android/build.gradle +5 -27
  3. package/android/src/main/java/com/mattermost/pasteinputtext/PasteInputActionCallback.kt +10 -9
  4. package/android/src/main/java/com/mattermost/pasteinputtext/PasteInputEditText.kt +1 -2
  5. package/android/src/main/java/com/mattermost/pasteinputtext/PasteInputListener.kt +91 -44
  6. package/android/src/main/java/com/mattermost/pasteinputtext/PasteTextInputManager.kt +33 -0
  7. package/ios/PasteTextInput.mm +15 -5
  8. package/ios/PasteTextInputManager.mm +28 -93
  9. package/lib/commonjs/PasteTextInput.js +5 -5
  10. package/lib/commonjs/PasteTextInput.js.map +1 -1
  11. package/lib/commonjs/PasteTextInputSpec.js +33 -0
  12. package/lib/commonjs/PasteTextInputSpec.js.map +1 -0
  13. package/lib/module/PasteTextInput.js +1 -1
  14. package/lib/module/PasteTextInput.js.map +1 -1
  15. package/lib/module/PasteTextInputSpec.js +31 -0
  16. package/lib/module/PasteTextInputSpec.js.map +1 -0
  17. package/lib/typescript/PasteTextInput.d.ts.map +1 -0
  18. package/lib/typescript/{src/PasteTextInputNativeComponent.d.ts → PasteTextInputSpec.d.ts} +4 -4
  19. package/lib/typescript/PasteTextInputSpec.d.ts.map +1 -0
  20. package/lib/typescript/index.d.ts.map +1 -0
  21. package/lib/typescript/types.d.ts.map +1 -0
  22. package/package.json +5 -17
  23. package/react-native-paste-input.podspec +12 -4
  24. package/react-native.config.js +5 -1
  25. package/src/PasteTextInput.tsx +1 -3
  26. package/{lib/module/PasteTextInputNativeComponent.ts → src/PasteTextInputSpec.ts} +34 -7
  27. package/android/generated/java/com/facebook/react/viewmanagers/PasteTextInputManagerDelegate.java +0 -236
  28. package/android/generated/java/com/facebook/react/viewmanagers/PasteTextInputManagerInterface.java +0 -84
  29. package/android/generated/jni/CMakeLists.txt +0 -36
  30. package/android/generated/jni/PasteTextInputSpecs-generated.cpp +0 -27
  31. package/android/generated/jni/PasteTextInputSpecs.h +0 -28
  32. package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/ComponentDescriptors.cpp +0 -22
  33. package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/ComponentDescriptors.h +0 -166
  34. package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/EventEmitters.cpp +0 -183
  35. package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/EventEmitters.h +0 -148
  36. package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/PasteTextInputSpecsJSI-generated.cpp +0 -17
  37. package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/PasteTextInputSpecsJSI.h +0 -19
  38. package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/Props.cpp +0 -640
  39. package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/Props.h +0 -144
  40. package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/ShadowNodes.cpp +0 -247
  41. package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/ShadowNodes.h +0 -95
  42. package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/States.cpp +0 -14
  43. package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/States.h +0 -19
  44. package/lib/commonjs/PasteTextInputNativeComponent.ts +0 -277
  45. package/lib/typescript/src/PasteTextInput.d.ts.map +0 -1
  46. package/lib/typescript/src/PasteTextInputNativeComponent.d.ts.map +0 -1
  47. package/lib/typescript/src/index.d.ts.map +0 -1
  48. package/lib/typescript/src/types.d.ts.map +0 -1
  49. package/src/PasteTextInputNativeComponent.ts +0 -277
  50. /package/lib/typescript/{src/PasteTextInput.d.ts → PasteTextInput.d.ts} +0 -0
  51. /package/lib/typescript/{src/index.d.ts → index.d.ts} +0 -0
  52. /package/lib/typescript/{src/types.d.ts → types.d.ts} +0 -0
package/README.md CHANGED
@@ -1,30 +1,51 @@
1
- # @mattermost/react-native-paste-input
1
+ # @tbvjaos510/react-native-paste-input
2
2
 
3
- React Native `TextInput` component have functionality to capture text input from a user
4
- by using the soft and hardware keyboards but lacks the ability to restrict copy & paste options
5
- as well as allwing pasting different files formats copied from other apps, like images & videos from
6
- the Photos gallery app.
3
+ React Native `TextInput` component with the ability to paste images and files from the clipboard.
7
4
 
8
- `PasteInput` is a `TextInput` replacement that solves this issues.
5
+ This is a fork of `@mattermost/react-native-paste-input` with support for React Native 0.76+ New Architecture (Fabric).
6
+
7
+ ## Features
8
+
9
+ - Paste images from clipboard (context menu)
10
+ - Paste images/GIFs from keyboard (Gboard, etc.)
11
+ - Drag & drop images (Android 12+)
12
+ - Disable copy/paste functionality
13
+ - Full New Architecture (Fabric) support
9
14
 
10
15
  ## Installation
11
16
 
12
17
  ```sh
13
- npm i --save-exact @mattermost/react-native-paste-input
18
+ npm install @tbvjaos510/react-native-paste-input
19
+ # or
20
+ yarn add @tbvjaos510/react-native-paste-input
14
21
  ```
15
22
 
16
- ## Demo
17
- | Android | iOS |
18
- |-- |-- |
19
- |![Android](/example/gifs/AndroidPasteInput.gif)|![iOS](/example/gifs/iOSPasteInput.gif)|
23
+ ## Requirements
24
+
25
+ - React Native 0.76+
26
+ - New Architecture enabled
27
+
28
+ ### Android Permissions
29
+
30
+ To paste images from the clipboard on Android 13+, your app needs the `READ_MEDIA_IMAGES` permission:
31
+
32
+ ```xml
33
+ <!-- android/app/src/main/AndroidManifest.xml -->
34
+ <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
35
+ ```
20
36
 
37
+ For Android 12 and below, use:
38
+ ```xml
39
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
40
+ ```
21
41
 
42
+ **Note:** Without these permissions, pasting images copied from the gallery will fail with a SecurityException. Images from other sources (like web browsers) may still work.
22
43
 
23
44
  ## Usage
24
45
 
25
- ```js
46
+ ```tsx
26
47
  import React, { useRef } from 'react';
27
- import PasteInput, { PastedFile, PasteInputRef } from "@mattermost/react-native-paste-input";
48
+ import PasteInput, { PastedFile, PasteInputRef } from "@tbvjaos510/react-native-paste-input";
28
49
 
29
50
  const YourTextInput = () => {
30
51
  const inputRef = useRef<PasteInputRef>(null);
@@ -46,23 +67,27 @@ const YourTextInput = () => {
46
67
  blurOnSubmit={false}
47
68
  underlineColorAndroid="transparent"
48
69
  keyboardType="default"
49
- disableFullscreenUI={true}
50
- textContentType="none"
51
- autoCompleteType="off"
52
70
  />
53
71
  );
54
72
  }
55
73
  ```
56
74
 
57
- ### Properties
58
- All properties of the [TextInput](!https://reactnative.dev/docs/textinput) component plus:
75
+ ## Properties
76
+
77
+ All properties of the [TextInput](https://reactnative.dev/docs/textinput) component plus:
59
78
 
60
- ##### `disableCopyPaste: boolean`
79
+ ### `disableCopyPaste: boolean`
61
80
  Indicates if the menu items for *cut*, *copy*, *paste* and *share* should not be present in the context menu.
62
81
 
63
- ##### `onPaste: (error, files) => void`
64
- Callback that is called when the pasting files into the text input.
65
- *Note: On Android this callback is also called when selecting and image / gif from the soft keyboard.*
82
+ ### `onPaste: (error, files) => void`
83
+ Callback that is called when pasting files into the text input.
84
+
85
+ **Note:** On Android, this callback is also called when selecting an image/GIF from the soft keyboard.
86
+
87
+ ## Known Limitations
88
+
89
+ - **Samsung Keyboard Clipboard:** Images from Samsung keyboard's clipboard panel are not supported due to Samsung's proprietary implementation.
90
+ - **Android Media Permissions:** Pasting images copied from the gallery requires `READ_MEDIA_IMAGES` permission on Android 13+.
66
91
 
67
92
  ## Contributing
68
93
 
@@ -18,8 +18,7 @@ buildscript {
18
18
 
19
19
  apply plugin: "com.android.library"
20
20
  apply plugin: "kotlin-android"
21
-
22
- apply plugin: "com.facebook.react"
21
+ // NOT using com.facebook.react plugin - this is a Paper-only component using interop
23
22
 
24
23
  def getExtOrIntegerDefault(name) {
25
24
  return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["TestItOut_" + name]).toInteger()
@@ -33,11 +32,6 @@ android {
33
32
  defaultConfig {
34
33
  minSdkVersion getExtOrIntegerDefault("minSdkVersion")
35
34
  targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
36
- externalNativeBuild {
37
- cmake {
38
- cppFlags '-DBUILD_DEBUG'
39
- }
40
- }
41
35
  }
42
36
 
43
37
  buildFeatures {
@@ -59,21 +53,8 @@ android {
59
53
  targetCompatibility JavaVersion.VERSION_1_8
60
54
  }
61
55
 
62
- sourceSets {
63
- main {
64
- java.srcDirs += [
65
- "generated/java",
66
- "generated/jni"
67
- ]
68
- }
69
- }
70
-
71
- externalNativeBuild {
72
- cmake {
73
- path file('generated/jni/CMakeLists.txt')
74
- version '3.22.1'
75
- }
76
- }
56
+ // Note: Codegen generates Java delegate files at build time
57
+ // sourceSets not needed as codegen output is automatically included
77
58
  }
78
59
 
79
60
  def kotlin_version = getExtOrDefault("kotlinVersion")
@@ -85,10 +66,7 @@ repositories {
85
66
 
86
67
  dependencies {
87
68
  implementation 'com.facebook.react:react-native:+'
69
+ implementation 'androidx.core:core-ktx:1.12.0'
88
70
  }
89
71
 
90
- react {
91
- jsRootDir = file("../src/")
92
- libraryName = "react-native-paste-input"
93
- codegenJavaPackageName = "com.mattermost.pastetextinput"
94
- }
72
+ // No react {} block - using Paper interop mode instead of Fabric codegen
@@ -13,12 +13,10 @@ class PasteInputActionCallback(editText: PasteInputEditText, disabled: Boolean,
13
13
  private val mEditText = editText
14
14
  private val mEventDispatcher = eventDispatcher
15
15
 
16
-
17
16
  override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
18
17
  if (isDisabled) {
19
18
  disableMenus(menu)
20
19
  }
21
-
22
20
  return true
23
21
  }
24
22
 
@@ -34,12 +32,10 @@ class PasteInputActionCallback(editText: PasteInputEditText, disabled: Boolean,
34
32
  } else {
35
33
  mEditText.onTextContextMenuItem(item!!.itemId)
36
34
  }
37
-
38
35
  return true
39
36
  }
40
37
 
41
38
  override fun onDestroyActionMode(mode: ActionMode?) {
42
-
43
39
  }
44
40
 
45
41
  private fun disableMenus(menu: Menu?) {
@@ -61,12 +57,17 @@ class PasteInputActionCallback(editText: PasteInputEditText, disabled: Boolean,
61
57
  val clipboardManager = mEditText.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
62
58
  val clipData = clipboardManager.primaryClip ?: return null
63
59
  val item = clipData.getItemAt(0) ?: return null
64
- val chars = item.text ?: return null
65
60
 
66
- val text = chars.toString()
67
- return if (text.isNotEmpty()) {
68
- null
69
- } else item.uri
61
+ // Check for URI first (for images)
62
+ val uri = item.uri
63
+ if (uri != null) {
64
+ return uri
65
+ }
70
66
 
67
+ // If no URI, check if it's text content
68
+ val chars = item.text
69
+ return if (chars != null && chars.toString().isNotEmpty()) {
70
+ null // It's text, not an image
71
+ } else null
71
72
  }
72
73
  }
@@ -9,7 +9,6 @@ import androidx.core.view.inputmethod.InputConnectionCompat
9
9
  import com.facebook.react.uimanager.ThemedReactContext
10
10
  import com.facebook.react.uimanager.events.EventDispatcher
11
11
  import com.facebook.react.views.textinput.ReactEditText
12
- import java.lang.Exception
13
12
 
14
13
 
15
14
  @SuppressLint("ViewConstructor")
@@ -42,7 +41,7 @@ class PasteInputEditText(context: ThemedReactContext) : ReactEditText(context) {
42
41
  val lacksPermission = (flags and InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0
43
42
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 && lacksPermission) {
44
43
  try {
45
- inputContentInfo.requestPermission()
44
+ inputContentInfo.requestPermission()
46
45
  } catch (e: Exception) {
47
46
  return@OnCommitContentListener false
48
47
  }
@@ -5,79 +5,126 @@ import android.content.Context
5
5
  import android.net.Uri
6
6
  import android.util.Patterns
7
7
  import android.webkit.MimeTypeMap
8
- import android.webkit.URLUtil
9
8
  import com.facebook.react.bridge.Arguments
10
9
  import com.facebook.react.bridge.ReactContext
11
- import com.facebook.react.bridge.WritableArray
12
- import com.facebook.react.bridge.WritableMap
13
10
  import com.facebook.react.uimanager.events.EventDispatcher
14
- import java.io.FileNotFoundException
11
+ import java.io.File
12
+ import java.io.FileOutputStream
15
13
 
16
14
  class PasteInputListener(editText: PasteInputEditText, surfaceId: Int) : IPasteInputListener {
17
15
  private val mEditText = editText
18
16
  private val mSurfaceId = surfaceId
19
17
 
18
+ /**
19
+ * Copy content from URI to app's cache directory.
20
+ * This avoids SecurityException when accessing clipboard URIs.
21
+ */
22
+ private fun copyUriToCache(context: Context, uri: Uri, mimeType: String): File? {
23
+ try {
24
+ val cacheDir = File(context.cacheDir, PasteTextInputManager.CACHE_DIR_NAME)
25
+ if (!cacheDir.exists()) {
26
+ cacheDir.mkdirs()
27
+ }
28
+
29
+ val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) ?: "jpg"
30
+ val fileName = "paste_${System.currentTimeMillis()}.$extension"
31
+ val destFile = File(cacheDir, fileName)
32
+
33
+ context.contentResolver.openInputStream(uri)?.use { input ->
34
+ FileOutputStream(destFile).use { output ->
35
+ input.copyTo(output)
36
+ }
37
+ } ?: return null
38
+
39
+ return destFile
40
+ } catch (e: Exception) {
41
+ return null
42
+ }
43
+ }
44
+
20
45
  override fun onPaste(itemUri: Uri, eventDispatcher: EventDispatcher?) {
21
46
  val reactContext = mEditText.context as ReactContext
22
- reactContext.contentResolver.getType(itemUri) ?: return
47
+ val uriString: String = itemUri.toString()
23
48
 
24
- var uriString: String = itemUri.toString()
25
- val mimeType: String
26
- val fileSize: Long
27
- var files: WritableArray? = null
28
- var error: WritableMap? = null
49
+ // Handle HTTP URLs separately
50
+ if (uriString.startsWith("http")) {
51
+ val pastImageFromUrlThread = Thread(PasteInputFileFromUrl(
52
+ mEditText,
53
+ uriString,
54
+ mSurfaceId,
55
+ eventDispatcher
56
+ ))
57
+ pastImageFromUrlThread.start()
58
+ return
59
+ }
60
+
61
+ // Try to get content type
62
+ var mimeType = reactContext.contentResolver.getType(itemUri)
63
+
64
+ // If content type is null, try to get from ClipData
65
+ if (mimeType == null) {
66
+ val clipboardManager = reactContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
67
+ val clipData = clipboardManager.primaryClip
68
+ if (clipData != null && clipData.description.mimeTypeCount > 0) {
69
+ mimeType = clipData.description.getMimeType(0)
70
+ }
71
+ }
72
+
73
+ // Default to image/jpeg if still null
74
+ if (mimeType == null) {
75
+ mimeType = "image/jpeg"
76
+ }
29
77
 
30
78
  // Special handle for Google docs
31
79
  if (uriString == "content://com.google.android.apps.docs.editors.kix.editors.clipboard") {
32
80
  val clipboardManager = reactContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
33
81
  val clipData = clipboardManager.primaryClip ?: return
34
82
  val item = clipData.getItemAt(0) ?: return
35
- val htmlText = item.htmlText
83
+ val htmlText = item.htmlText ?: return
36
84
 
37
85
  // Find uri from html
38
86
  val matcher = Patterns.WEB_URL.matcher(htmlText)
39
87
  if (matcher.find()) {
40
- uriString = htmlText.substring(matcher.start(1), matcher.end())
88
+ val urlString = htmlText.substring(matcher.start(1), matcher.end())
89
+ val pastImageFromUrlThread = Thread(PasteInputFileFromUrl(
90
+ mEditText,
91
+ urlString,
92
+ mSurfaceId,
93
+ eventDispatcher
94
+ ))
95
+ pastImageFromUrlThread.start()
41
96
  }
42
- } else if (uriString.startsWith("http")) {
43
- val pastImageFromUrlThread = Thread(PasteInputFileFromUrl(
44
- mEditText,
45
- uriString,
46
- mSurfaceId,
47
- eventDispatcher
48
- ))
49
- pastImageFromUrlThread.start()
50
97
  return
51
- } else {
52
- uriString = RealPathUtil.getRealPathFromURI(reactContext, itemUri) ?: return
53
98
  }
54
99
 
55
- val extension: String = MimeTypeMap.getFileExtensionFromUrl(uriString) ?: return
56
- mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) ?: return
57
- val fileName: String = URLUtil.guessFileName(uriString, null, mimeType)
58
-
59
- try {
60
- val contentResolver = reactContext.contentResolver
61
- val assetFileDescriptor = contentResolver.openAssetFileDescriptor(itemUri, "r") ?: return
62
- val file = Arguments.createMap()
63
-
64
- files = Arguments.createArray()
65
- fileSize = assetFileDescriptor.length
66
- file.putString("type", mimeType)
67
- file.putDouble("fileSize", fileSize.toDouble())
68
- file.putString("fileName", fileName)
69
- file.putString("uri", "file://$uriString")
70
-
71
- files.pushMap(file)
72
- assetFileDescriptor.close()
73
- } catch (e: FileNotFoundException) {
74
- error = Arguments.createMap()
75
- error.putString("message", e.localizedMessage)
100
+ // Copy content URI to cache file
101
+ val cachedFile = copyUriToCache(reactContext, itemUri, mimeType)
102
+ if (cachedFile == null) {
103
+ val error = Arguments.createMap()
104
+ error.putString("message", "Failed to copy image from clipboard")
105
+ val event = Arguments.createMap()
106
+ event.putArray("data", null)
107
+ event.putMap("error", error)
108
+ eventDispatcher?.dispatchEvent(PasteTextInputPasteEvent(mSurfaceId, mEditText.id, event))
109
+ return
76
110
  }
77
111
 
112
+ val fileName = cachedFile.name
113
+ val fileSize = cachedFile.length()
114
+ val filePath = cachedFile.absolutePath
115
+
116
+ val file = Arguments.createMap()
117
+ file.putString("type", mimeType)
118
+ file.putDouble("fileSize", fileSize.toDouble())
119
+ file.putString("fileName", fileName)
120
+ file.putString("uri", "file://$filePath")
121
+
122
+ val files = Arguments.createArray()
123
+ files.pushMap(file)
124
+
78
125
  val event = Arguments.createMap()
79
126
  event.putArray("data", files)
80
- event.putMap("error", error)
127
+ event.putMap("error", null)
81
128
 
82
129
  eventDispatcher?.dispatchEvent(PasteTextInputPasteEvent(mSurfaceId, mEditText.id, event))
83
130
  }
@@ -1,6 +1,7 @@
1
1
  package com.mattermost.pasteinputtext
2
2
 
3
3
  import android.text.InputType
4
+ import androidx.core.view.ViewCompat
4
5
  import com.facebook.react.bridge.ReactApplicationContext
5
6
  import com.facebook.react.bridge.ReactContext
6
7
  import com.facebook.react.common.MapBuilder
@@ -51,6 +52,38 @@ class PasteTextInputManager(context: ReactApplicationContext) : ReactTextInputMa
51
52
  val pasteInputEditText = editText as PasteInputEditText
52
53
  val eventDispatcher = getEventDispatcher(reactContext, editText)
53
54
  pasteInputEditText.setOnPasteListener(PasteInputListener(pasteInputEditText, reactContext.surfaceId), eventDispatcher)
55
+
56
+ // Set up OnReceiveContentListener for unified content handling (drag & drop, etc.)
57
+ setupOnReceiveContentListener(pasteInputEditText, eventDispatcher)
58
+ }
59
+
60
+ private fun setupOnReceiveContentListener(
61
+ editText: PasteInputEditText,
62
+ eventDispatcher: EventDispatcher?
63
+ ) {
64
+ val mimeTypes = arrayOf("image/*")
65
+
66
+ ViewCompat.setOnReceiveContentListener(
67
+ editText,
68
+ mimeTypes,
69
+ androidx.core.view.OnReceiveContentListener { _, payload ->
70
+ val clip = payload.clip
71
+ val description = clip.description
72
+
73
+ if (description != null && description.hasMimeType("image/*")) {
74
+ for (i in 0 until clip.itemCount) {
75
+ val item = clip.getItemAt(i)
76
+ val uri = item.uri
77
+ if (uri != null) {
78
+ editText.getOnPasteListener().onPaste(uri, eventDispatcher)
79
+ }
80
+ }
81
+ null
82
+ } else {
83
+ payload
84
+ }
85
+ }
86
+ )
54
87
  }
55
88
 
56
89
  override fun getExportedCustomBubblingEventTypeConstants(): MutableMap<String, Any> {
@@ -17,9 +17,15 @@
17
17
  #import "RCTTextInputUtils.h"
18
18
 
19
19
  #import "RCTFabricComponentsPlugins.h"
20
+ #import <React/RCTComponentViewFactory.h>
20
21
 
21
22
  using namespace facebook::react;
22
23
 
24
+ // Register the component with Fabric's component registry
25
+ __attribute__((constructor)) static void registerPasteTextInput() {
26
+ [RCTComponentViewFactory.currentComponentViewFactory registerComponentViewClass:[PasteTextInput class]];
27
+ }
28
+
23
29
  @interface PasteTextInput () <RCTBackedTextInputDelegate, RCTPasteTextInputViewProtocol>
24
30
  @end
25
31
 
@@ -86,18 +92,20 @@ std::int32_t convertNSDictionaryValueToStdInt(NSDictionary *dictionary, NSString
86
92
  if (self = [super initWithFrame:frame]) {
87
93
  static const auto defaultProps = std::make_shared<const PasteTextInputProps>();
88
94
  _props = defaultProps;
89
-
95
+
90
96
  _backedTextInputView = [[PasteInputTextView alloc] initWithFrame:self.bounds];
91
97
  _backedTextInputView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
92
98
  _backedTextInputView.textInputDelegate = self;
99
+ _backedTextInputView.editable = YES;
100
+ _backedTextInputView.userInteractionEnabled = YES;
93
101
  [self _setOnPaste];
94
102
  _ignoreNextTextInputCall = NO;
95
103
  _comingFromJS = NO;
96
104
  _didMoveToWindow = NO;
97
-
105
+
98
106
  [self addSubview:_backedTextInputView];
99
107
  }
100
-
108
+
101
109
  return self;
102
110
  }
103
111
 
@@ -201,11 +209,13 @@ std::int32_t convertNSDictionaryValueToStdInt(NSDictionary *dictionary, NSString
201
209
  // because they are being checked on-demand.
202
210
 
203
211
  // Other props:
204
- if (newTextInputProps.placeholder != oldTextInputProps.placeholder) {
212
+ if (newTextInputProps.placeholder != oldTextInputProps.placeholder ||
213
+ (!newTextInputProps.placeholder.empty() && _backedTextInputView.placeholder.length == 0)) {
205
214
  _backedTextInputView.placeholder = RCTNSStringFromString(newTextInputProps.placeholder);
206
215
  }
207
216
 
208
- if (newTextInputProps.placeholderTextColor != oldTextInputProps.placeholderTextColor) {
217
+ if (newTextInputProps.placeholderTextColor != oldTextInputProps.placeholderTextColor ||
218
+ (newTextInputProps.placeholderTextColor && !_backedTextInputView.placeholderColor)) {
209
219
  _backedTextInputView.placeholderColor = RCTUIColorFromSharedColor(newTextInputProps.placeholderTextColor);
210
220
  }
211
221
 
@@ -17,81 +17,23 @@
17
17
  #ifdef RCT_NEW_ARCH_ENABLED
18
18
  @interface PasteTextInputManager : RCTViewManager
19
19
  @end
20
- #else
21
- #import <React/RCTMultilineTextInputViewManager.h>
22
-
23
- @interface PasteTextInputManager : RCTMultilineTextInputViewManager
24
-
25
- @end
26
- #endif
27
20
 
28
- @implementation PasteTextInputManager {
29
- NSHashTable<RCTBaseTextInputShadowView *> *_shadowViews;
30
- }
21
+ @implementation PasteTextInputManager
31
22
 
32
23
  RCT_EXPORT_MODULE(PasteTextInput)
33
24
 
34
- RCT_EXPORT_VIEW_PROPERTY(disableCopyPaste, BOOL)
35
- RCT_EXPORT_VIEW_PROPERTY(smartPunctuation, NSString)
36
-
37
- RCT_EXPORT_VIEW_PROPERTY(onPaste, RCTBubblingEventBlock)
38
-
39
- #pragma mark - Unified <PasteTextInput> properties
40
- #ifdef RCT_NEW_ARCH_ENABLED
41
- RCT_REMAP_VIEW_PROPERTY(autoCapitalize, backedTextInputView.autocapitalizationType, UITextAutocapitalizationType)
42
- RCT_REMAP_VIEW_PROPERTY(autoCorrect, backedTextInputView.autocorrectionType, UITextAutocorrectionType)
43
- RCT_REMAP_VIEW_PROPERTY(contextMenuHidden, backedTextInputView.contextMenuHidden, BOOL)
44
- RCT_REMAP_VIEW_PROPERTY(editable, backedTextInputView.editable, BOOL)
45
- RCT_REMAP_VIEW_PROPERTY(enablesReturnKeyAutomatically, backedTextInputView.enablesReturnKeyAutomatically, BOOL)
46
- RCT_REMAP_VIEW_PROPERTY(keyboardAppearance, backedTextInputView.keyboardAppearance, UIKeyboardAppearance)
47
- RCT_REMAP_VIEW_PROPERTY(placeholder, backedTextInputView.placeholder, NSString)
48
- RCT_REMAP_VIEW_PROPERTY(placeholderTextColor, backedTextInputView.placeholderColor, UIColor)
49
- RCT_REMAP_VIEW_PROPERTY(returnKeyType, backedTextInputView.returnKeyType, UIReturnKeyType)
50
- RCT_REMAP_VIEW_PROPERTY(selectionColor, backedTextInputView.tintColor, UIColor)
51
- RCT_REMAP_VIEW_PROPERTY(spellCheck, backedTextInputView.spellCheckingType, UITextSpellCheckingType)
52
- RCT_REMAP_VIEW_PROPERTY(caretHidden, backedTextInputView.caretHidden, BOOL)
53
- RCT_REMAP_VIEW_PROPERTY(clearButtonMode, backedTextInputView.clearButtonMode, UITextFieldViewMode)
54
- RCT_REMAP_VIEW_PROPERTY(scrollEnabled, backedTextInputView.scrollEnabled, BOOL)
55
- RCT_REMAP_VIEW_PROPERTY(secureTextEntry, backedTextInputView.secureTextEntry, BOOL)
56
- RCT_REMAP_VIEW_PROPERTY(smartInsertDelete, backedTextInputView.smartInsertDeleteType, UITextSmartInsertDeleteType)
57
- RCT_EXPORT_VIEW_PROPERTY(autoFocus, BOOL)
58
- RCT_EXPORT_VIEW_PROPERTY(submitBehavior, NSString)
59
- RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL)
60
- RCT_EXPORT_VIEW_PROPERTY(keyboardType, UIKeyboardType)
61
- RCT_EXPORT_VIEW_PROPERTY(showSoftInputOnFocus, BOOL)
62
- RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber)
63
- RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL)
64
- RCT_EXPORT_VIEW_PROPERTY(selection, RCTTextSelection)
65
- RCT_EXPORT_VIEW_PROPERTY(inputAccessoryViewID, NSString)
66
- RCT_EXPORT_VIEW_PROPERTY(textContentType, NSString)
67
- RCT_EXPORT_VIEW_PROPERTY(passwordRules, NSString)
68
-
69
- RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
25
+ // Export TextInput events for Fabric Paper interop
26
+ // These are inherited from TextInputEventEmitter in native code
27
+ RCT_EXPORT_VIEW_PROPERTY(onContentSizeChange, RCTDirectEventBlock)
70
28
  RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)
71
29
  RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock)
72
-
73
- RCT_EXPORT_VIEW_PROPERTY(mostRecentEventCount, NSInteger)
74
-
75
- RCT_EXPORT_SHADOW_PROPERTY(text, NSString)
76
- RCT_EXPORT_SHADOW_PROPERTY(placeholder, NSString)
77
- RCT_EXPORT_SHADOW_PROPERTY(onContentSizeChange, RCTDirectEventBlock)
78
-
79
- RCT_CUSTOM_VIEW_PROPERTY(multiline, BOOL, UIView)
80
- {
81
- // No op.
82
- // This View Manager doesn't use this prop but it must be exposed here via ViewConfig to enable Fabric component use
83
- // it.
84
- }
85
-
86
- - (RCTShadowView *)shadowView
87
- {
88
- RCTBaseTextInputShadowView *shadowView = [[RCTBaseTextInputShadowView alloc] initWithBridge:self.bridge];
89
- shadowView.textAttributes.fontSizeMultiplier =
90
- [[[self.bridge moduleForName:@"AccessibilityManager"
91
- lazilyLoadIfNecessary:YES] valueForKey:@"multiplier"] floatValue];
92
- [_shadowViews addObject:shadowView];
93
- return shadowView;
94
- }
30
+ RCT_EXPORT_VIEW_PROPERTY(onBlur, RCTBubblingEventBlock)
31
+ RCT_EXPORT_VIEW_PROPERTY(onFocus, RCTBubblingEventBlock)
32
+ RCT_EXPORT_VIEW_PROPERTY(onEndEditing, RCTBubblingEventBlock)
33
+ RCT_EXPORT_VIEW_PROPERTY(onSubmitEditing, RCTBubblingEventBlock)
34
+ RCT_EXPORT_VIEW_PROPERTY(onKeyPress, RCTBubblingEventBlock)
35
+ RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
36
+ RCT_EXPORT_VIEW_PROPERTY(onPaste, RCTBubblingEventBlock)
95
37
 
96
38
  RCT_EXPORT_METHOD(focus : (nonnull NSNumber *)viewTag)
97
39
  {
@@ -136,36 +78,29 @@ RCT_EXPORT_METHOD(setTextAndSelection
136
78
  }];
137
79
  }
138
80
 
139
- #pragma mark - RCTUIManagerObserver
81
+ // Note: For Fabric, the -view method is NOT needed.
82
+ // The PasteTextInput component is created via PasteTextInputCls() in PasteTextInput.mm
140
83
 
141
- - (void)uiManagerWillPerformMounting:(__unused RCTUIManager *)uiManager
142
- {
143
- for (RCTBaseTextInputShadowView *shadowView in _shadowViews) {
144
- [shadowView uiManagerWillPerformMounting];
145
- }
146
- }
84
+ @end
147
85
 
148
- #pragma mark - Font Size Multiplier
86
+ #else
87
+ #import <React/RCTMultilineTextInputViewManager.h>
149
88
 
150
- - (void)handleDidUpdateMultiplierNotification
151
- {
152
- CGFloat fontSizeMultiplier =
153
- [[[self.bridge moduleForName:@"AccessibilityManager"] valueForKey:@"multiplier"] floatValue];
154
-
155
- NSHashTable<RCTBaseTextInputShadowView *> *shadowViews = _shadowViews;
156
- RCTExecuteOnUIManagerQueue(^{
157
- for (RCTBaseTextInputShadowView *shadowView in shadowViews) {
158
- shadowView.textAttributes.fontSizeMultiplier = fontSizeMultiplier;
159
- [shadowView dirtyLayout];
160
- }
89
+ @interface PasteTextInputManager : RCTMultilineTextInputViewManager
90
+ @end
91
+
92
+ @implementation PasteTextInputManager
93
+
94
+ RCT_EXPORT_MODULE(PasteTextInput)
95
+
96
+ RCT_EXPORT_VIEW_PROPERTY(disableCopyPaste, BOOL)
97
+ RCT_EXPORT_VIEW_PROPERTY(smartPunctuation, NSString)
98
+ RCT_EXPORT_VIEW_PROPERTY(onPaste, RCTBubblingEventBlock)
161
99
 
162
- [self.bridge.uiManager setNeedsLayout];
163
- });
164
- }
165
- #else
166
100
  - (UIView *)view
167
101
  {
168
102
  return [[PasteInputView alloc] initWithBridge:self.bridge];
169
103
  }
170
- #endif
104
+
171
105
  @end
106
+ #endif