@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.
- package/README.md +47 -22
- package/android/build.gradle +5 -27
- package/android/src/main/java/com/mattermost/pasteinputtext/PasteInputActionCallback.kt +10 -9
- package/android/src/main/java/com/mattermost/pasteinputtext/PasteInputEditText.kt +1 -2
- package/android/src/main/java/com/mattermost/pasteinputtext/PasteInputListener.kt +91 -44
- package/android/src/main/java/com/mattermost/pasteinputtext/PasteTextInputManager.kt +33 -0
- package/ios/PasteTextInput.mm +15 -5
- package/ios/PasteTextInputManager.mm +28 -93
- package/lib/commonjs/PasteTextInput.js +5 -5
- package/lib/commonjs/PasteTextInput.js.map +1 -1
- package/lib/commonjs/PasteTextInputSpec.js +33 -0
- package/lib/commonjs/PasteTextInputSpec.js.map +1 -0
- package/lib/module/PasteTextInput.js +1 -1
- package/lib/module/PasteTextInput.js.map +1 -1
- package/lib/module/PasteTextInputSpec.js +31 -0
- package/lib/module/PasteTextInputSpec.js.map +1 -0
- package/lib/typescript/PasteTextInput.d.ts.map +1 -0
- package/lib/typescript/{src/PasteTextInputNativeComponent.d.ts → PasteTextInputSpec.d.ts} +4 -4
- package/lib/typescript/PasteTextInputSpec.d.ts.map +1 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/package.json +5 -17
- package/react-native-paste-input.podspec +12 -4
- package/react-native.config.js +5 -1
- package/src/PasteTextInput.tsx +1 -3
- package/{lib/module/PasteTextInputNativeComponent.ts → src/PasteTextInputSpec.ts} +34 -7
- package/android/generated/java/com/facebook/react/viewmanagers/PasteTextInputManagerDelegate.java +0 -236
- package/android/generated/java/com/facebook/react/viewmanagers/PasteTextInputManagerInterface.java +0 -84
- package/android/generated/jni/CMakeLists.txt +0 -36
- package/android/generated/jni/PasteTextInputSpecs-generated.cpp +0 -27
- package/android/generated/jni/PasteTextInputSpecs.h +0 -28
- package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/ComponentDescriptors.cpp +0 -22
- package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/ComponentDescriptors.h +0 -166
- package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/EventEmitters.cpp +0 -183
- package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/EventEmitters.h +0 -148
- package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/PasteTextInputSpecsJSI-generated.cpp +0 -17
- package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/PasteTextInputSpecsJSI.h +0 -19
- package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/Props.cpp +0 -640
- package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/Props.h +0 -144
- package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/ShadowNodes.cpp +0 -247
- package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/ShadowNodes.h +0 -95
- package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/States.cpp +0 -14
- package/android/generated/jni/react/renderer/components/PasteTextInputSpecs/States.h +0 -19
- package/lib/commonjs/PasteTextInputNativeComponent.ts +0 -277
- package/lib/typescript/src/PasteTextInput.d.ts.map +0 -1
- package/lib/typescript/src/PasteTextInputNativeComponent.d.ts.map +0 -1
- package/lib/typescript/src/index.d.ts.map +0 -1
- package/lib/typescript/src/types.d.ts.map +0 -1
- package/src/PasteTextInputNativeComponent.ts +0 -277
- /package/lib/typescript/{src/PasteTextInput.d.ts → PasteTextInput.d.ts} +0 -0
- /package/lib/typescript/{src/index.d.ts → index.d.ts} +0 -0
- /package/lib/typescript/{src/types.d.ts → types.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,30 +1,51 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @tbvjaos510/react-native-paste-input
|
|
2
2
|
|
|
3
|
-
React Native `TextInput` component
|
|
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
|
-
|
|
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
|
|
18
|
+
npm install @tbvjaos510/react-native-paste-input
|
|
19
|
+
# or
|
|
20
|
+
yarn add @tbvjaos510/react-native-paste-input
|
|
14
21
|
```
|
|
15
22
|
|
|
16
|
-
##
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
```
|
|
46
|
+
```tsx
|
|
26
47
|
import React, { useRef } from 'react';
|
|
27
|
-
import PasteInput, { PastedFile, PasteInputRef } from "@
|
|
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
|
-
|
|
58
|
-
|
|
75
|
+
## Properties
|
|
76
|
+
|
|
77
|
+
All properties of the [TextInput](https://reactnative.dev/docs/textinput) component plus:
|
|
59
78
|
|
|
60
|
-
|
|
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
|
-
|
|
64
|
-
Callback that is called when
|
|
65
|
-
|
|
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
|
|
package/android/build.gradle
CHANGED
|
@@ -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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
47
|
+
val uriString: String = itemUri.toString()
|
|
23
48
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
val
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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",
|
|
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> {
|
package/ios/PasteTextInput.mm
CHANGED
|
@@ -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
|
-
|
|
35
|
-
|
|
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(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
81
|
+
// Note: For Fabric, the -view method is NOT needed.
|
|
82
|
+
// The PasteTextInput component is created via PasteTextInputCls() in PasteTextInput.mm
|
|
140
83
|
|
|
141
|
-
|
|
142
|
-
{
|
|
143
|
-
for (RCTBaseTextInputShadowView *shadowView in _shadowViews) {
|
|
144
|
-
[shadowView uiManagerWillPerformMounting];
|
|
145
|
-
}
|
|
146
|
-
}
|
|
84
|
+
@end
|
|
147
85
|
|
|
148
|
-
#
|
|
86
|
+
#else
|
|
87
|
+
#import <React/RCTMultilineTextInputViewManager.h>
|
|
149
88
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
104
|
+
|
|
171
105
|
@end
|
|
106
|
+
#endif
|