expo-rich-input 0.1.0 → 0.1.2
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.
|
@@ -5,11 +5,16 @@ import expo.modules.kotlin.modules.ModuleDefinition
|
|
|
5
5
|
|
|
6
6
|
class ExpoRichInputModule : Module() {
|
|
7
7
|
override fun definition() = ModuleDefinition {
|
|
8
|
+
|
|
8
9
|
Name("ExpoRichInput")
|
|
9
10
|
|
|
10
11
|
View(RichInputView::class) {
|
|
11
12
|
|
|
12
|
-
Events(
|
|
13
|
+
Events(
|
|
14
|
+
"onEditEvent",
|
|
15
|
+
"onKeyboardAction",
|
|
16
|
+
"onSelectionChange"
|
|
17
|
+
)
|
|
13
18
|
|
|
14
19
|
AsyncFunction("focus") {
|
|
15
20
|
view: RichInputView ->
|
|
@@ -20,19 +25,6 @@ class ExpoRichInputModule : Module() {
|
|
|
20
25
|
view: RichInputView ->
|
|
21
26
|
view.blurInput()
|
|
22
27
|
}
|
|
23
|
-
|
|
24
|
-
// Wire up event dispatchers into the view
|
|
25
|
-
OnViewDidUpdateProps {
|
|
26
|
-
view ->
|
|
27
|
-
view.onEditEvent = {
|
|
28
|
-
payload ->
|
|
29
|
-
view.dispatchEvent("onEditEvent", payload)
|
|
30
|
-
}
|
|
31
|
-
view.onKeyboardAction = {
|
|
32
|
-
payload ->
|
|
33
|
-
view.dispatchEvent("onKeyboardAction", payload)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
28
|
}
|
|
37
29
|
}
|
|
38
30
|
}
|
|
@@ -5,126 +5,150 @@ import android.os.Build
|
|
|
5
5
|
import android.text.InputType
|
|
6
6
|
import android.view.KeyEvent
|
|
7
7
|
import android.view.View
|
|
8
|
-
import android.view.inputmethod
|
|
9
|
-
import android.view.inputmethod.EditorInfo
|
|
10
|
-
import android.view.inputmethod.InputConnection
|
|
11
|
-
import android.view.inputmethod.InputMethodManager
|
|
8
|
+
import android.view.inputmethod.*
|
|
12
9
|
import expo.modules.kotlin.AppContext
|
|
13
10
|
import expo.modules.kotlin.views.ExpoView
|
|
14
11
|
|
|
15
12
|
class RichInputView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
private var cursorPosition = 0
|
|
15
|
+
private var selectionStart = 0
|
|
16
|
+
private var selectionEnd = 0
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
18
|
+
init {
|
|
19
|
+
isFocusable = true
|
|
20
|
+
isFocusableInTouchMode = true
|
|
21
|
+
}
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection {
|
|
29
|
-
outAttrs.inputType = InputType.TYPE_CLASS_TEXT or
|
|
30
|
-
InputType.TYPE_TEXT_FLAG_MULTI_LINE or
|
|
31
|
-
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
|
23
|
+
override fun onCheckIsTextEditor(): Boolean = true
|
|
32
24
|
|
|
33
|
-
outAttrs
|
|
34
|
-
|
|
25
|
+
override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection {
|
|
26
|
+
outAttrs.inputType = InputType.TYPE_CLASS_TEXT or
|
|
27
|
+
InputType.TYPE_TEXT_FLAG_MULTI_LINE or
|
|
28
|
+
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
outAttrs.initialCapsMode = 0
|
|
39
|
-
}
|
|
30
|
+
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN or
|
|
31
|
+
EditorInfo.IME_ACTION_NONE
|
|
40
32
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
override fun onCheckIsTextEditor(): Boolean = true
|
|
45
|
-
|
|
46
|
-
// MARK: Hardware key events (physical keyboard, back key etc.)
|
|
47
|
-
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
|
48
|
-
if (event.isCtrlPressed) {
|
|
49
|
-
when (keyCode) {
|
|
50
|
-
KeyEvent.KEYCODE_Z -> {
|
|
51
|
-
if (event.isShiftPressed) {
|
|
52
|
-
onKeyboardAction?.invoke(mapOf("action" to "redo"))
|
|
53
|
-
} else {
|
|
54
|
-
onKeyboardAction?.invoke(mapOf("action" to "undo"))
|
|
55
|
-
}
|
|
56
|
-
return true
|
|
33
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
34
|
+
outAttrs.initialCapsMode = 0
|
|
57
35
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return true
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return super.onKeyDown(keyCode, event)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// MARK: Focus control called from JS
|
|
68
|
-
fun focusInput() {
|
|
69
|
-
requestFocus()
|
|
70
|
-
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
71
|
-
imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
fun blurInput() {
|
|
75
|
-
clearFocus()
|
|
76
|
-
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
77
|
-
imm.hideSoftInputFromWindow(windowToken, 0)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// MARK: InputConnection inner class
|
|
81
|
-
inner class EditorInputConnection(view: View) : BaseInputConnection(view, false) {
|
|
82
|
-
|
|
83
|
-
// Regular text commit — typing, paste, swipe keyboard word
|
|
84
|
-
override fun commitText(text: CharSequence?, newCursorPosition: Int): Boolean {
|
|
85
|
-
val str = text?.toString() ?: return false
|
|
86
|
-
onEditEvent?.invoke(mapOf(
|
|
87
|
-
"type" to "insert",
|
|
88
|
-
"text" to str
|
|
89
|
-
))
|
|
90
|
-
return true
|
|
36
|
+
|
|
37
|
+
return EditorInputConnection(this)
|
|
91
38
|
}
|
|
92
39
|
|
|
93
|
-
//
|
|
94
|
-
override fun
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
40
|
+
// 🔥 Hardware keyboard support
|
|
41
|
+
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
|
42
|
+
if (event.isCtrlPressed) {
|
|
43
|
+
val action = when (keyCode) {
|
|
44
|
+
KeyEvent.KEYCODE_Z -> if (event.isShiftPressed) "redo" else "undo"
|
|
45
|
+
KeyEvent.KEYCODE_A -> "selectAll"
|
|
46
|
+
KeyEvent.KEYCODE_C -> "copy"
|
|
47
|
+
KeyEvent.KEYCODE_V -> "paste"
|
|
48
|
+
KeyEvent.KEYCODE_X -> "cut"
|
|
49
|
+
KeyEvent.KEYCODE_SLASH -> "toggleComment"
|
|
50
|
+
else -> null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
action?.let {
|
|
54
|
+
sendEvent("onKeyboardAction", mapOf("action" to it))
|
|
55
|
+
return true
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return super.onKeyDown(keyCode, event)
|
|
101
60
|
}
|
|
102
61
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
))
|
|
108
|
-
return true
|
|
62
|
+
fun focusInput() {
|
|
63
|
+
requestFocus()
|
|
64
|
+
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
65
|
+
imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
|
|
109
66
|
}
|
|
110
67
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
"type" to "delete",
|
|
116
|
-
"count" to beforeLength
|
|
117
|
-
))
|
|
118
|
-
}
|
|
119
|
-
return true
|
|
68
|
+
fun blurInput() {
|
|
69
|
+
clearFocus()
|
|
70
|
+
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
71
|
+
imm.hideSoftInputFromWindow(windowToken, 0)
|
|
120
72
|
}
|
|
121
73
|
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
74
|
+
// 🔥 Core Input Engine
|
|
75
|
+
inner class EditorInputConnection(view: View) : BaseInputConnection(view, false) {
|
|
76
|
+
|
|
77
|
+
override fun commitText(text: CharSequence?, newCursorPosition: Int): Boolean {
|
|
78
|
+
val str = text?.toString() ?: return false
|
|
79
|
+
|
|
80
|
+
sendEvent("onEditEvent", mapOf(
|
|
81
|
+
"type" to "insert",
|
|
82
|
+
"text" to str,
|
|
83
|
+
"cursor" to cursorPosition
|
|
84
|
+
))
|
|
85
|
+
|
|
86
|
+
cursorPosition += str.length
|
|
87
|
+
selectionStart = cursorPosition
|
|
88
|
+
selectionEnd = cursorPosition
|
|
89
|
+
|
|
90
|
+
return true
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
override fun setComposingText(text: CharSequence?, newCursorPosition: Int): Boolean {
|
|
94
|
+
val str = text?.toString() ?: ""
|
|
126
95
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
96
|
+
sendEvent("onEditEvent", mapOf(
|
|
97
|
+
"type" to "compose",
|
|
98
|
+
"text" to str,
|
|
99
|
+
"cursor" to cursorPosition
|
|
100
|
+
))
|
|
101
|
+
|
|
102
|
+
return true
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
override fun finishComposingText(): Boolean {
|
|
106
|
+
sendEvent("onEditEvent", mapOf(
|
|
107
|
+
"type" to "composeCommit",
|
|
108
|
+
"cursor" to cursorPosition
|
|
109
|
+
))
|
|
110
|
+
return true
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
override fun deleteSurroundingText(beforeLength: Int, afterLength: Int): Boolean {
|
|
114
|
+
if (beforeLength > 0) {
|
|
115
|
+
val deleteCount = minOf(beforeLength, cursorPosition)
|
|
116
|
+
|
|
117
|
+
sendEvent("onEditEvent", mapOf(
|
|
118
|
+
"type" to "delete",
|
|
119
|
+
"count" to deleteCount,
|
|
120
|
+
"cursor" to cursorPosition
|
|
121
|
+
))
|
|
122
|
+
|
|
123
|
+
cursorPosition -= deleteCount
|
|
124
|
+
selectionStart = cursorPosition
|
|
125
|
+
selectionEnd = cursorPosition
|
|
126
|
+
}
|
|
127
|
+
return true
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
override fun setSelection(start: Int, end: Int): Boolean {
|
|
131
|
+
selectionStart = start
|
|
132
|
+
selectionEnd = end
|
|
133
|
+
cursorPosition = end
|
|
134
|
+
|
|
135
|
+
sendEvent("onSelectionChange", mapOf(
|
|
136
|
+
"start" to start,
|
|
137
|
+
"end" to end
|
|
138
|
+
))
|
|
139
|
+
|
|
140
|
+
return true
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
override fun getExtractedText(
|
|
144
|
+
request: ExtractedTextRequest?,
|
|
145
|
+
flags: Int
|
|
146
|
+
): ExtractedText? = null
|
|
147
|
+
|
|
148
|
+
override fun getSurroundingText(
|
|
149
|
+
beforeLength: Int,
|
|
150
|
+
afterLength: Int,
|
|
151
|
+
flags: Int
|
|
152
|
+
): SurroundingText? = null
|
|
153
|
+
}
|
|
154
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-rich-input",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A native Expo module that replaces `TextInput` entirely, giving the editor raw OS-level edit deltas — insert, delete, and IME compose events — directly from `UIKeyInput` on iOS and `InputConnection` on Android, so the Rope never has to diff a full string.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"open:ios": "xed example/ios",
|
|
16
16
|
"open:android": "open -a \"Android Studio\" example/android"
|
|
17
17
|
},
|
|
18
|
-
|
|
18
|
+
"files": [
|
|
19
19
|
"build",
|
|
20
20
|
"android",
|
|
21
21
|
"ios",
|