react-native-keyboard-controller 1.9.4 → 1.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 +1 -1
- package/android/build.gradle +3 -0
- package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +4 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/events/FocusedInputTextChangedEvent.kt +20 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/EditText.kt +36 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/WindowInsetsAnimationCompat.kt +7 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/{FocusedInputLayoutObserver.kt → FocusedInputObserver.kt} +18 -1
- package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt +14 -2
- package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardControllerViewManagerImpl.kt +2 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/modules/KeyboardControllerModuleImpl.kt +13 -0
- package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +5 -0
- package/ios/KeyboardController.xcodeproj/project.pbxproj +14 -4
- package/ios/KeyboardControllerModule.mm +14 -0
- package/ios/events/FocusedInputTextChangedEvent.h +16 -0
- package/ios/events/FocusedInputTextChangedEvent.m +72 -0
- package/ios/observers/{FocusedInputLayoutObserver.swift → FocusedInputObserver.swift} +32 -17
- package/ios/observers/TextChangeObserver.swift +41 -0
- package/ios/views/KeyboardControllerView.mm +54 -32
- package/ios/views/KeyboardControllerViewManager.mm +1 -0
- package/ios/views/KeyboardControllerViewManager.swift +22 -11
- package/jest/index.js +2 -0
- package/lib/commonjs/animated.js +20 -14
- package/lib/commonjs/animated.js.map +1 -1
- package/lib/commonjs/bindings.js +1 -0
- package/lib/commonjs/bindings.js.map +1 -1
- package/lib/commonjs/components/KeyboardAvoidingView/index.js +7 -4
- package/lib/commonjs/components/KeyboardAvoidingView/index.js.map +1 -1
- package/lib/commonjs/components/KeyboardAwareScrollView/index.js +199 -0
- package/lib/commonjs/components/KeyboardAwareScrollView/index.js.map +1 -0
- package/lib/commonjs/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js +100 -0
- package/lib/commonjs/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js.map +1 -0
- package/lib/commonjs/components/KeyboardAwareScrollView/utils.js +28 -0
- package/lib/commonjs/components/KeyboardAwareScrollView/utils.js.map +1 -0
- package/lib/commonjs/components/KeyboardStickyView/index.js +0 -1
- package/lib/commonjs/components/KeyboardStickyView/index.js.map +1 -1
- package/lib/commonjs/components/index.js +7 -0
- package/lib/commonjs/components/index.js.map +1 -1
- package/lib/commonjs/context.js +2 -1
- package/lib/commonjs/context.js.map +1 -1
- package/lib/commonjs/hooks.js +17 -2
- package/lib/commonjs/hooks.js.map +1 -1
- package/lib/commonjs/index.js +8 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/internal.js +2 -5
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/commonjs/reanimated.js +5 -3
- package/lib/commonjs/reanimated.js.map +1 -1
- package/lib/commonjs/reanimated.native.js +20 -3
- package/lib/commonjs/reanimated.native.js.map +1 -1
- package/lib/commonjs/replicas.js +3 -1
- package/lib/commonjs/replicas.js.map +1 -1
- package/lib/commonjs/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
- package/lib/commonjs/specs/NativeKeyboardController.js.map +1 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/module/animated.js +21 -15
- package/lib/module/animated.js.map +1 -1
- package/lib/module/bindings.js +1 -0
- package/lib/module/bindings.js.map +1 -1
- package/lib/module/components/KeyboardAvoidingView/index.js +8 -5
- package/lib/module/components/KeyboardAvoidingView/index.js.map +1 -1
- package/lib/module/components/KeyboardAwareScrollView/index.js +190 -0
- package/lib/module/components/KeyboardAwareScrollView/index.js.map +1 -0
- package/lib/module/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js +93 -0
- package/lib/module/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js.map +1 -0
- package/lib/module/components/KeyboardAwareScrollView/utils.js +21 -0
- package/lib/module/components/KeyboardAwareScrollView/utils.js.map +1 -0
- package/lib/module/components/KeyboardStickyView/index.js +0 -1
- package/lib/module/components/KeyboardStickyView/index.js.map +1 -1
- package/lib/module/components/index.js +1 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/context.js +2 -1
- package/lib/module/context.js.map +1 -1
- package/lib/module/hooks.js +16 -2
- package/lib/module/hooks.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/internal.js +2 -5
- package/lib/module/internal.js.map +1 -1
- package/lib/module/reanimated.js +2 -1
- package/lib/module/reanimated.js.map +1 -1
- package/lib/module/reanimated.native.js +17 -1
- package/lib/module/reanimated.native.js.map +1 -1
- package/lib/module/replicas.js +5 -3
- package/lib/module/replicas.js.map +1 -1
- package/lib/module/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
- package/lib/module/specs/NativeKeyboardController.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/components/KeyboardAwareScrollView/index.d.ts +45 -0
- package/lib/typescript/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.d.ts +6 -0
- package/lib/typescript/components/KeyboardAwareScrollView/utils.d.ts +1 -0
- package/lib/typescript/components/index.d.ts +1 -0
- package/lib/typescript/context.d.ts +3 -2
- package/lib/typescript/hooks.d.ts +2 -1
- package/lib/typescript/index.d.ts +1 -1
- package/lib/typescript/internal.d.ts +8 -5
- package/lib/typescript/reanimated.d.ts +3 -2
- package/lib/typescript/reanimated.native.d.ts +3 -2
- package/lib/typescript/specs/KeyboardControllerViewNativeComponent.d.ts +4 -0
- package/lib/typescript/specs/NativeKeyboardController.d.ts +1 -0
- package/lib/typescript/types.d.ts +15 -2
- package/package.json +5 -2
- package/react-native-keyboard-controller.podspec +1 -1
- package/src/animated.tsx +27 -10
- package/src/bindings.ts +1 -0
- package/src/components/KeyboardAvoidingView/index.tsx +7 -5
- package/src/components/KeyboardAwareScrollView/index.tsx +262 -0
- package/src/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.ts +123 -0
- package/src/components/KeyboardAwareScrollView/utils.ts +26 -0
- package/src/components/KeyboardStickyView/index.tsx +1 -6
- package/src/components/index.ts +1 -0
- package/src/context.ts +9 -3
- package/src/hooks.ts +20 -3
- package/src/index.ts +5 -1
- package/src/internal.ts +17 -8
- package/src/reanimated.native.ts +27 -2
- package/src/reanimated.ts +8 -2
- package/src/replicas.ts +4 -3
- package/src/specs/KeyboardControllerViewNativeComponent.ts +7 -0
- package/src/specs/NativeKeyboardController.ts +1 -0
- package/src/types.ts +28 -2
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Keyboard manager which works in identical way on both iOS and Android.
|
|
|
13
13
|
- module for changing soft input mode on Android 🤔
|
|
14
14
|
- reanimated support 🚀
|
|
15
15
|
- interactive keyboard dismissing 👆📱
|
|
16
|
-
- prebuilt components (`KeyboardStickyView`, re-worked `KeyboardAvoidingView`) 📚
|
|
16
|
+
- prebuilt components (`KeyboardStickyView`, `KeyboardAwareScrollView`, re-worked `KeyboardAvoidingView`) 📚
|
|
17
17
|
- easy focused input information retrieval 📝 🔮
|
|
18
18
|
- works with any navigation library 🧭
|
|
19
19
|
- and more is coming... Stay tuned! 😊
|
package/android/build.gradle
CHANGED
|
@@ -43,6 +43,9 @@ android {
|
|
|
43
43
|
def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
|
|
44
44
|
if (agpVersion.tokenize('.')[0].toInteger() >= 7) {
|
|
45
45
|
namespace "com.reactnativekeyboardcontroller"
|
|
46
|
+
buildFeatures {
|
|
47
|
+
buildConfig true
|
|
48
|
+
}
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
|
package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt
CHANGED
|
@@ -16,6 +16,10 @@ class KeyboardControllerModule(mReactContext: ReactApplicationContext) : NativeK
|
|
|
16
16
|
module.setDefaultMode()
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
override fun dismiss() {
|
|
20
|
+
module.dismiss()
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
override fun addListener(eventName: String?) {
|
|
20
24
|
/* Required for RN built-in Event Emitter Calls. */
|
|
21
25
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package com.reactnativekeyboardcontroller.events
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
import com.facebook.react.uimanager.events.Event
|
|
6
|
+
|
|
7
|
+
class FocusedInputTextChangedEvent(
|
|
8
|
+
surfaceId: Int,
|
|
9
|
+
viewId: Int,
|
|
10
|
+
private val text: String,
|
|
11
|
+
) : Event<KeyboardTransitionEvent>(surfaceId, viewId) {
|
|
12
|
+
override fun getEventName() = "topFocusedInputTextChanged"
|
|
13
|
+
|
|
14
|
+
// All events for a given view can be coalesced
|
|
15
|
+
override fun getCoalescingKey(): Short = 0
|
|
16
|
+
|
|
17
|
+
override fun getEventData(): WritableMap? = Arguments.createMap().apply {
|
|
18
|
+
putString("text", text)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
package com.reactnativekeyboardcontroller.extensions
|
|
2
|
+
|
|
3
|
+
import android.text.Editable
|
|
4
|
+
import android.text.TextWatcher
|
|
5
|
+
import android.widget.EditText
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Adds a listener that will be fired only once for each unique value.
|
|
9
|
+
*
|
|
10
|
+
* This is needed because `onTextChanged` can be fired two and more times with the same value.
|
|
11
|
+
* And in this helper we filter out all subsequent calls with the same value.
|
|
12
|
+
*/
|
|
13
|
+
fun EditText.addOnTextChangedListener(action: (String) -> Unit): TextWatcher {
|
|
14
|
+
var lastText: String? = null
|
|
15
|
+
|
|
16
|
+
val listener = object : TextWatcher {
|
|
17
|
+
@Suppress("detekt:EmptyFunctionBlock")
|
|
18
|
+
override fun afterTextChanged(s: Editable?) {}
|
|
19
|
+
|
|
20
|
+
@Suppress("detekt:EmptyFunctionBlock")
|
|
21
|
+
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
|
22
|
+
|
|
23
|
+
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
|
24
|
+
val currentText = s.toString()
|
|
25
|
+
|
|
26
|
+
if (currentText != lastText) {
|
|
27
|
+
lastText = currentText
|
|
28
|
+
action(currentText)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
addTextChangedListener(listener)
|
|
34
|
+
|
|
35
|
+
return listener
|
|
36
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
package com.reactnativekeyboardcontroller.extensions
|
|
2
|
+
|
|
3
|
+
import androidx.core.view.WindowInsetsAnimationCompat
|
|
4
|
+
import androidx.core.view.WindowInsetsCompat
|
|
5
|
+
|
|
6
|
+
val WindowInsetsAnimationCompat.isKeyboardAnimation: Boolean
|
|
7
|
+
get() = typeMask and WindowInsetsCompat.Type.ime() != 0
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package com.reactnativekeyboardcontroller.listeners
|
|
2
2
|
|
|
3
|
+
import android.text.TextWatcher
|
|
3
4
|
import android.view.View.OnLayoutChangeListener
|
|
4
5
|
import android.view.ViewTreeObserver.OnGlobalFocusChangeListener
|
|
5
6
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
@@ -8,6 +9,8 @@ import com.facebook.react.views.textinput.ReactEditText
|
|
|
8
9
|
import com.facebook.react.views.view.ReactViewGroup
|
|
9
10
|
import com.reactnativekeyboardcontroller.events.FocusedInputLayoutChangedEvent
|
|
10
11
|
import com.reactnativekeyboardcontroller.events.FocusedInputLayoutChangedEventData
|
|
12
|
+
import com.reactnativekeyboardcontroller.events.FocusedInputTextChangedEvent
|
|
13
|
+
import com.reactnativekeyboardcontroller.extensions.addOnTextChangedListener
|
|
11
14
|
import com.reactnativekeyboardcontroller.extensions.dispatchEvent
|
|
12
15
|
import com.reactnativekeyboardcontroller.extensions.dp
|
|
13
16
|
import com.reactnativekeyboardcontroller.extensions.screenLocation
|
|
@@ -22,29 +25,43 @@ val noFocusedInputEvent = FocusedInputLayoutChangedEventData(
|
|
|
22
25
|
target = -1,
|
|
23
26
|
)
|
|
24
27
|
|
|
25
|
-
class
|
|
28
|
+
class FocusedInputObserver(val view: ReactViewGroup, private val context: ThemedReactContext?) {
|
|
26
29
|
// constructor variables
|
|
27
30
|
private val surfaceId = UIManagerHelper.getSurfaceId(view)
|
|
28
31
|
|
|
29
32
|
// state variables
|
|
30
33
|
private var lastFocusedInput: ReactEditText? = null
|
|
31
34
|
private var lastEventDispatched: FocusedInputLayoutChangedEventData = noFocusedInputEvent
|
|
35
|
+
private var textWatcher: TextWatcher? = null
|
|
32
36
|
|
|
33
37
|
// listeners
|
|
34
38
|
private val layoutListener =
|
|
35
39
|
OnLayoutChangeListener { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
|
|
36
40
|
this.syncUpLayout()
|
|
37
41
|
}
|
|
42
|
+
private val textListener: (String) -> Unit = { text ->
|
|
43
|
+
syncUpLayout()
|
|
44
|
+
context.dispatchEvent(
|
|
45
|
+
view.id,
|
|
46
|
+
FocusedInputTextChangedEvent(
|
|
47
|
+
surfaceId,
|
|
48
|
+
view.id,
|
|
49
|
+
text = text,
|
|
50
|
+
),
|
|
51
|
+
)
|
|
52
|
+
}
|
|
38
53
|
private val focusListener = OnGlobalFocusChangeListener { oldFocus, newFocus ->
|
|
39
54
|
// unfocused or focused was changed
|
|
40
55
|
if (newFocus == null || oldFocus != null) {
|
|
41
56
|
lastFocusedInput?.removeOnLayoutChangeListener(layoutListener)
|
|
57
|
+
lastFocusedInput?.removeTextChangedListener(textWatcher)
|
|
42
58
|
lastFocusedInput = null
|
|
43
59
|
}
|
|
44
60
|
if (newFocus is ReactEditText) {
|
|
45
61
|
lastFocusedInput = newFocus
|
|
46
62
|
newFocus.addOnLayoutChangeListener(layoutListener)
|
|
47
63
|
this.syncUpLayout()
|
|
64
|
+
textWatcher = newFocus.addOnTextChangedListener(textListener)
|
|
48
65
|
}
|
|
49
66
|
// unfocused
|
|
50
67
|
if (newFocus == null) {
|
|
@@ -22,6 +22,7 @@ import com.reactnativekeyboardcontroller.InteractiveKeyboardProvider
|
|
|
22
22
|
import com.reactnativekeyboardcontroller.events.KeyboardTransitionEvent
|
|
23
23
|
import com.reactnativekeyboardcontroller.extensions.dispatchEvent
|
|
24
24
|
import com.reactnativekeyboardcontroller.extensions.dp
|
|
25
|
+
import com.reactnativekeyboardcontroller.extensions.isKeyboardAnimation
|
|
25
26
|
import kotlin.math.abs
|
|
26
27
|
|
|
27
28
|
private val TAG = KeyboardAnimationCallback::class.qualifiedName
|
|
@@ -84,7 +85,7 @@ class KeyboardAnimationCallback(
|
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
|
-
private var layoutObserver:
|
|
88
|
+
private var layoutObserver: FocusedInputObserver? = null
|
|
88
89
|
|
|
89
90
|
init {
|
|
90
91
|
require(persistentInsetTypes and deferredInsetTypes == 0) {
|
|
@@ -92,7 +93,7 @@ class KeyboardAnimationCallback(
|
|
|
92
93
|
" same WindowInsetsCompat.Type values"
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
layoutObserver =
|
|
96
|
+
layoutObserver = FocusedInputObserver(view = view, context = context)
|
|
96
97
|
view.viewTreeObserver.addOnGlobalFocusChangeListener(focusListener)
|
|
97
98
|
}
|
|
98
99
|
|
|
@@ -142,6 +143,10 @@ class KeyboardAnimationCallback(
|
|
|
142
143
|
animation: WindowInsetsAnimationCompat,
|
|
143
144
|
bounds: WindowInsetsAnimationCompat.BoundsCompat,
|
|
144
145
|
): WindowInsetsAnimationCompat.BoundsCompat {
|
|
146
|
+
if (!animation.isKeyboardAnimation) {
|
|
147
|
+
return bounds
|
|
148
|
+
}
|
|
149
|
+
|
|
145
150
|
isTransitioning = true
|
|
146
151
|
isKeyboardVisible = isKeyboardVisible()
|
|
147
152
|
duration = animation.durationMillis.toInt()
|
|
@@ -181,6 +186,9 @@ class KeyboardAnimationCallback(
|
|
|
181
186
|
): WindowInsetsCompat {
|
|
182
187
|
// onProgress() is called when any of the running animations progress...
|
|
183
188
|
|
|
189
|
+
// ignore non-keyboard animation
|
|
190
|
+
runningAnimations.find { it.isKeyboardAnimation } ?: return insets
|
|
191
|
+
|
|
184
192
|
// First we get the insets which are potentially deferred
|
|
185
193
|
val typesInset = insets.getInsets(deferredInsetTypes)
|
|
186
194
|
// Then we get the persistent inset types which are applied as padding during layout
|
|
@@ -226,6 +234,10 @@ class KeyboardAnimationCallback(
|
|
|
226
234
|
override fun onEnd(animation: WindowInsetsAnimationCompat) {
|
|
227
235
|
super.onEnd(animation)
|
|
228
236
|
|
|
237
|
+
if (!animation.isKeyboardAnimation) {
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
|
|
229
241
|
isTransitioning = false
|
|
230
242
|
duration = animation.durationMillis.toInt()
|
|
231
243
|
|
|
@@ -35,6 +35,8 @@ class KeyboardControllerViewManagerImpl(mReactContext: ReactApplicationContext)
|
|
|
35
35
|
MapBuilder.of("registrationName", "onKeyboardMoveInteractive"),
|
|
36
36
|
"topFocusedInputLayoutChanged",
|
|
37
37
|
MapBuilder.of("registrationName", "onFocusedInputLayoutChanged"),
|
|
38
|
+
"topFocusedInputTextChanged",
|
|
39
|
+
MapBuilder.of("registrationName", "onFocusedInputTextChanged"),
|
|
38
40
|
)
|
|
39
41
|
|
|
40
42
|
return map
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
package com.reactnativekeyboardcontroller.modules
|
|
2
2
|
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.view.View
|
|
3
5
|
import android.view.WindowManager
|
|
6
|
+
import android.view.inputmethod.InputMethodManager
|
|
4
7
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
8
|
import com.facebook.react.bridge.UiThreadUtil
|
|
6
9
|
|
|
@@ -15,6 +18,16 @@ class KeyboardControllerModuleImpl(private val mReactContext: ReactApplicationCo
|
|
|
15
18
|
setSoftInputMode(mDefaultMode)
|
|
16
19
|
}
|
|
17
20
|
|
|
21
|
+
fun dismiss() {
|
|
22
|
+
val activity = mReactContext.currentActivity
|
|
23
|
+
val view: View? = activity?.currentFocus
|
|
24
|
+
|
|
25
|
+
if (view != null) {
|
|
26
|
+
val imm = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
|
27
|
+
imm?.hideSoftInputFromWindow(view.windowToken, 0)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
18
31
|
private fun setSoftInputMode(mode: Int) {
|
|
19
32
|
UiThreadUtil.runOnUiThread {
|
|
20
33
|
if (getCurrentMode() != mode) {
|
package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt
CHANGED
|
@@ -20,6 +20,11 @@ class KeyboardControllerModule(mReactContext: ReactApplicationContext) : ReactCo
|
|
|
20
20
|
module.setDefaultMode()
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
@ReactMethod
|
|
24
|
+
fun dismiss() {
|
|
25
|
+
module.dismiss()
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
@Suppress("detekt:UnusedParameter")
|
|
24
29
|
@ReactMethod
|
|
25
30
|
fun addListener(eventName: String?) {
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
/* Begin PBXBuildFile section */
|
|
10
10
|
0807071E2A34807B00C05A19 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0807071D2A34807B00C05A19 /* Extensions.swift */; };
|
|
11
|
+
083FB9852B15171600C0EFF0 /* TextChangeObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083FB9842B15171600C0EFF0 /* TextChangeObserver.swift */; };
|
|
12
|
+
083FB9872B15174C00C0EFF0 /* FocusedInputTextChangedEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 083FB9862B15174C00C0EFF0 /* FocusedInputTextChangedEvent.m */; };
|
|
11
13
|
084AEEC62ACF49A8001A3069 /* FocusedInputLayoutChangedEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 084AEEC52ACF49A8001A3069 /* FocusedInputLayoutChangedEvent.m */; };
|
|
12
|
-
084AEEC82ACF4AB2001A3069 /*
|
|
14
|
+
084AEEC82ACF4AB2001A3069 /* FocusedInputObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084AEEC72ACF4AB2001A3069 /* FocusedInputObserver.swift */; };
|
|
13
15
|
F333F8D428996B8D0015B05F /* KeyboardControllerView.mm in Sources */ = {isa = PBXBuildFile; fileRef = F333F8D228996B8D0015B05F /* KeyboardControllerView.mm */; };
|
|
14
16
|
F359D34F28133C26000B6AFE /* KeyboardControllerModule.mm in Sources */ = {isa = PBXBuildFile; fileRef = F359D34E28133C26000B6AFE /* KeyboardControllerModule.mm */; };
|
|
15
17
|
F3626A0728B3FE760021B2D9 /* KeyboardMovementObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3626A0628B3FE760021B2D9 /* KeyboardMovementObserver.swift */; };
|
|
@@ -31,9 +33,12 @@
|
|
|
31
33
|
|
|
32
34
|
/* Begin PBXFileReference section */
|
|
33
35
|
0807071D2A34807B00C05A19 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
|
|
36
|
+
083FB9842B15171600C0EFF0 /* TextChangeObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextChangeObserver.swift; sourceTree = "<group>"; };
|
|
37
|
+
083FB9862B15174C00C0EFF0 /* FocusedInputTextChangedEvent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FocusedInputTextChangedEvent.m; sourceTree = "<group>"; };
|
|
38
|
+
083FB9892B15177C00C0EFF0 /* FocusedInputTextChangedEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FocusedInputTextChangedEvent.h; sourceTree = "<group>"; };
|
|
34
39
|
084AEEC22ACF479A001A3069 /* FocusedInputLayoutChangedEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FocusedInputLayoutChangedEvent.h; sourceTree = "<group>"; };
|
|
35
40
|
084AEEC52ACF49A8001A3069 /* FocusedInputLayoutChangedEvent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FocusedInputLayoutChangedEvent.m; sourceTree = "<group>"; };
|
|
36
|
-
084AEEC72ACF4AB2001A3069 /*
|
|
41
|
+
084AEEC72ACF4AB2001A3069 /* FocusedInputObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusedInputObserver.swift; sourceTree = "<group>"; };
|
|
37
42
|
134814201AA4EA6300B7C361 /* libKeyboardController.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libKeyboardController.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
38
43
|
B3E7B5891CC2AC0600A0062D /* KeyboardControllerViewManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyboardControllerViewManager.mm; sourceTree = "<group>"; };
|
|
39
44
|
F333F8D128996B1C0015B05F /* KeyboardControllerView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyboardControllerView.h; sourceTree = "<group>"; };
|
|
@@ -65,6 +70,8 @@
|
|
|
65
70
|
F3F50668289E653B003091D6 /* KeyboardMoveEvent.m */,
|
|
66
71
|
084AEEC22ACF479A001A3069 /* FocusedInputLayoutChangedEvent.h */,
|
|
67
72
|
084AEEC52ACF49A8001A3069 /* FocusedInputLayoutChangedEvent.m */,
|
|
73
|
+
083FB9892B15177C00C0EFF0 /* FocusedInputTextChangedEvent.h */,
|
|
74
|
+
083FB9862B15174C00C0EFF0 /* FocusedInputTextChangedEvent.m */,
|
|
68
75
|
);
|
|
69
76
|
path = events;
|
|
70
77
|
sourceTree = "<group>";
|
|
@@ -73,7 +80,8 @@
|
|
|
73
80
|
isa = PBXGroup;
|
|
74
81
|
children = (
|
|
75
82
|
F3626A0628B3FE760021B2D9 /* KeyboardMovementObserver.swift */,
|
|
76
|
-
084AEEC72ACF4AB2001A3069 /*
|
|
83
|
+
084AEEC72ACF4AB2001A3069 /* FocusedInputObserver.swift */,
|
|
84
|
+
083FB9842B15171600C0EFF0 /* TextChangeObserver.swift */,
|
|
77
85
|
);
|
|
78
86
|
path = observers;
|
|
79
87
|
sourceTree = "<group>";
|
|
@@ -172,10 +180,12 @@
|
|
|
172
180
|
F3626A0728B3FE760021B2D9 /* KeyboardMovementObserver.swift in Sources */,
|
|
173
181
|
0807071E2A34807B00C05A19 /* Extensions.swift in Sources */,
|
|
174
182
|
F359D34F28133C26000B6AFE /* KeyboardControllerModule.mm in Sources */,
|
|
175
|
-
084AEEC82ACF4AB2001A3069 /*
|
|
183
|
+
084AEEC82ACF4AB2001A3069 /* FocusedInputObserver.swift in Sources */,
|
|
176
184
|
F4FF95D7245B92E800C19C63 /* KeyboardControllerViewManager.swift in Sources */,
|
|
177
185
|
F333F8D428996B8D0015B05F /* KeyboardControllerView.mm in Sources */,
|
|
178
186
|
F3F50669289E653B003091D6 /* KeyboardMoveEvent.m in Sources */,
|
|
187
|
+
083FB9852B15171600C0EFF0 /* TextChangeObserver.swift in Sources */,
|
|
188
|
+
083FB9872B15174C00C0EFF0 /* FocusedInputTextChangedEvent.m in Sources */,
|
|
179
189
|
);
|
|
180
190
|
runOnlyForDeploymentPostprocessing = 0;
|
|
181
191
|
};
|
|
@@ -61,6 +61,20 @@ RCT_EXPORT_METHOD(setInputMode : (nonnull NSNumber *)mode)
|
|
|
61
61
|
{
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
65
|
+
- (void)dismiss
|
|
66
|
+
#else
|
|
67
|
+
RCT_EXPORT_METHOD(dismiss)
|
|
68
|
+
#endif
|
|
69
|
+
{
|
|
70
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
71
|
+
[[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder)
|
|
72
|
+
to:nil
|
|
73
|
+
from:nil
|
|
74
|
+
forEvent:nil];
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
64
78
|
+ (KeyboardController *)shared
|
|
65
79
|
{
|
|
66
80
|
return shared;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//
|
|
2
|
+
// FocusedInputTextChangedEvent.h
|
|
3
|
+
// KeyboardController
|
|
4
|
+
//
|
|
5
|
+
// Created by Kiryl Ziusko on 27/11/2023.
|
|
6
|
+
// Copyright © 2023 Facebook. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
#import <Foundation/Foundation.h>
|
|
10
|
+
#import <React/RCTEventDispatcherProtocol.h>
|
|
11
|
+
|
|
12
|
+
@interface FocusedInputTextChangedEvent : NSObject <RCTEvent>
|
|
13
|
+
|
|
14
|
+
- (instancetype)initWithReactTag:(NSNumber *)reactTag text:(NSString *)text;
|
|
15
|
+
|
|
16
|
+
@end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
//
|
|
2
|
+
// FocusedInputTextChangedEvent.m
|
|
3
|
+
// KeyboardController
|
|
4
|
+
//
|
|
5
|
+
// Created by Kiryl Ziusko on 27/11/2023.
|
|
6
|
+
// Copyright © 2023 Facebook. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
#import "FocusedInputTextChangedEvent.h"
|
|
10
|
+
#import <React/RCTAssert.h>
|
|
11
|
+
|
|
12
|
+
@implementation FocusedInputTextChangedEvent {
|
|
13
|
+
NSString *_text;
|
|
14
|
+
uint16_t _coalescingKey;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
- (NSString *)eventName
|
|
18
|
+
{
|
|
19
|
+
return @"onFocusedInputTextChanged";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@synthesize viewTag = _viewTag;
|
|
23
|
+
|
|
24
|
+
- (instancetype)initWithReactTag:(NSNumber *)reactTag text:(NSString *)text
|
|
25
|
+
{
|
|
26
|
+
RCTAssertParam(reactTag);
|
|
27
|
+
|
|
28
|
+
if ((self = [super init])) {
|
|
29
|
+
_viewTag = reactTag;
|
|
30
|
+
_text = text;
|
|
31
|
+
_coalescingKey = 0;
|
|
32
|
+
}
|
|
33
|
+
return self;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
RCT_NOT_IMPLEMENTED(-(instancetype)init)
|
|
37
|
+
|
|
38
|
+
- (uint16_t)coalescingKey
|
|
39
|
+
{
|
|
40
|
+
return _coalescingKey;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
- (NSDictionary *)body
|
|
44
|
+
{
|
|
45
|
+
NSDictionary *body = @{
|
|
46
|
+
@"text" : _text,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return body;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
- (BOOL)canCoalesce
|
|
53
|
+
{
|
|
54
|
+
return NO;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
- (FocusedInputTextChangedEvent *)coalesceWithEvent:(FocusedInputTextChangedEvent *)newEvent
|
|
58
|
+
{
|
|
59
|
+
return newEvent;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
+ (NSString *)moduleDotMethod
|
|
63
|
+
{
|
|
64
|
+
return @"RCTEventEmitter.receiveEvent";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
- (NSArray *)arguments
|
|
68
|
+
{
|
|
69
|
+
return @[ self.viewTag, RCTNormalizeInputEventName(self.eventName), [self body] ];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//
|
|
2
|
-
//
|
|
2
|
+
// FocusedInputObserver.swift
|
|
3
3
|
// KeyboardController
|
|
4
4
|
//
|
|
5
5
|
// Created by Kiryl Ziusko on 05/10/2023.
|
|
@@ -21,21 +21,26 @@ let noFocusedInputEvent: [String: Any] = [
|
|
|
21
21
|
],
|
|
22
22
|
]
|
|
23
23
|
|
|
24
|
-
@objc(
|
|
25
|
-
public class
|
|
24
|
+
@objc(FocusedInputObserver)
|
|
25
|
+
public class FocusedInputObserver: NSObject {
|
|
26
26
|
// class members
|
|
27
|
-
var
|
|
27
|
+
var onLayoutChangedHandler: (NSDictionary) -> Void
|
|
28
|
+
var onTextChangedHandler: (String) -> Void
|
|
28
29
|
// state variables
|
|
29
30
|
private var isMounted = false
|
|
30
31
|
// input tracking
|
|
31
32
|
private var currentInput: UIView?
|
|
32
|
-
private var
|
|
33
|
+
private var hasObservers = false
|
|
33
34
|
private var lastEventDispatched: [AnyHashable: Any] = noFocusedInputEvent
|
|
35
|
+
// observers
|
|
36
|
+
private let textChangeObserver = TextChangeObserver()
|
|
34
37
|
|
|
35
38
|
@objc public init(
|
|
36
|
-
|
|
39
|
+
onLayoutChangedHandler: @escaping (NSDictionary) -> Void,
|
|
40
|
+
onTextChangedHandler: @escaping (String) -> Void
|
|
37
41
|
) {
|
|
38
|
-
|
|
42
|
+
self.onLayoutChangedHandler = onLayoutChangedHandler
|
|
43
|
+
self.onTextChangedHandler = onTextChangedHandler
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
@objc public func mount() {
|
|
@@ -66,14 +71,14 @@ public class FocusedInputLayoutObserver: NSObject {
|
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
@objc func keyboardWillShow(_: Notification) {
|
|
69
|
-
|
|
74
|
+
removeObservers()
|
|
70
75
|
currentInput = (UIResponder.current as? UIView)?.superview as UIView?
|
|
71
|
-
|
|
76
|
+
setupObservers()
|
|
72
77
|
syncUpLayout()
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
@objc func keyboardWillHide(_: Notification) {
|
|
76
|
-
|
|
81
|
+
removeObservers()
|
|
77
82
|
dispatchEventToJS(data: noFocusedInputEvent)
|
|
78
83
|
}
|
|
79
84
|
|
|
@@ -97,33 +102,43 @@ public class FocusedInputLayoutObserver: NSObject {
|
|
|
97
102
|
dispatchEventToJS(data: data)
|
|
98
103
|
}
|
|
99
104
|
|
|
105
|
+
private func onTextChanged(text: String?) {
|
|
106
|
+
syncUpLayout()
|
|
107
|
+
|
|
108
|
+
if let string = text {
|
|
109
|
+
onTextChangedHandler(string)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
100
113
|
private func dispatchEventToJS(data: [String: Any]) {
|
|
101
114
|
if NSDictionary(dictionary: data).isEqual(to: lastEventDispatched) {
|
|
102
115
|
return
|
|
103
116
|
}
|
|
104
117
|
|
|
105
118
|
lastEventDispatched = data
|
|
106
|
-
|
|
119
|
+
onLayoutChangedHandler(data as NSDictionary)
|
|
107
120
|
}
|
|
108
121
|
|
|
109
|
-
private func
|
|
110
|
-
if
|
|
122
|
+
private func setupObservers() {
|
|
123
|
+
if hasObservers {
|
|
111
124
|
return
|
|
112
125
|
}
|
|
113
126
|
|
|
114
127
|
if currentInput != nil {
|
|
115
|
-
|
|
128
|
+
hasObservers = true
|
|
116
129
|
currentInput?.addObserver(self, forKeyPath: "center", options: .new, context: nil)
|
|
130
|
+
textChangeObserver.observeTextChanges(for: UIResponder.current, handler: onTextChanged)
|
|
117
131
|
}
|
|
118
132
|
}
|
|
119
133
|
|
|
120
|
-
private func
|
|
121
|
-
if !
|
|
134
|
+
private func removeObservers() {
|
|
135
|
+
if !hasObservers {
|
|
122
136
|
return
|
|
123
137
|
}
|
|
124
138
|
|
|
125
|
-
|
|
139
|
+
hasObservers = false
|
|
126
140
|
currentInput?.removeObserver(self, forKeyPath: "center", context: nil)
|
|
141
|
+
textChangeObserver.removeObserver()
|
|
127
142
|
}
|
|
128
143
|
|
|
129
144
|
// swiftlint:disable:next block_based_kvo
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//
|
|
2
|
+
// TextChangeObserver.swift
|
|
3
|
+
// KeyboardController
|
|
4
|
+
//
|
|
5
|
+
// Created by Kiryl Ziusko on 27/11/2023.
|
|
6
|
+
// Copyright © 2023 Facebook. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
public class TextChangeObserver {
|
|
10
|
+
private var observer: Any?
|
|
11
|
+
|
|
12
|
+
func observeTextChanges(for input: UIResponder?, handler: @escaping (String?) -> Void) {
|
|
13
|
+
if input == nil {
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
if let textField = input as? UITextField {
|
|
17
|
+
observer = NotificationCenter.default.addObserver(
|
|
18
|
+
forName: UITextField.textDidChangeNotification,
|
|
19
|
+
object: textField,
|
|
20
|
+
queue: nil
|
|
21
|
+
) { _ in
|
|
22
|
+
handler(textField.text)
|
|
23
|
+
}
|
|
24
|
+
} else if let textView = input as? UITextView {
|
|
25
|
+
observer = NotificationCenter.default.addObserver(
|
|
26
|
+
forName: UITextView.textDidChangeNotification,
|
|
27
|
+
object: textView,
|
|
28
|
+
queue: nil
|
|
29
|
+
) { _ in
|
|
30
|
+
handler(textView.text)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func removeObserver() {
|
|
36
|
+
if let observer = observer {
|
|
37
|
+
NotificationCenter.default.removeObserver(observer)
|
|
38
|
+
self.observer = nil
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|