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.
Files changed (120) hide show
  1. package/README.md +1 -1
  2. package/android/build.gradle +3 -0
  3. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +4 -0
  4. package/android/src/main/java/com/reactnativekeyboardcontroller/events/FocusedInputTextChangedEvent.kt +20 -0
  5. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/EditText.kt +36 -0
  6. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/WindowInsetsAnimationCompat.kt +7 -0
  7. package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/{FocusedInputLayoutObserver.kt → FocusedInputObserver.kt} +18 -1
  8. package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt +14 -2
  9. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardControllerViewManagerImpl.kt +2 -0
  10. package/android/src/main/java/com/reactnativekeyboardcontroller/modules/KeyboardControllerModuleImpl.kt +13 -0
  11. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +5 -0
  12. package/ios/KeyboardController.xcodeproj/project.pbxproj +14 -4
  13. package/ios/KeyboardControllerModule.mm +14 -0
  14. package/ios/events/FocusedInputTextChangedEvent.h +16 -0
  15. package/ios/events/FocusedInputTextChangedEvent.m +72 -0
  16. package/ios/observers/{FocusedInputLayoutObserver.swift → FocusedInputObserver.swift} +32 -17
  17. package/ios/observers/TextChangeObserver.swift +41 -0
  18. package/ios/views/KeyboardControllerView.mm +54 -32
  19. package/ios/views/KeyboardControllerViewManager.mm +1 -0
  20. package/ios/views/KeyboardControllerViewManager.swift +22 -11
  21. package/jest/index.js +2 -0
  22. package/lib/commonjs/animated.js +20 -14
  23. package/lib/commonjs/animated.js.map +1 -1
  24. package/lib/commonjs/bindings.js +1 -0
  25. package/lib/commonjs/bindings.js.map +1 -1
  26. package/lib/commonjs/components/KeyboardAvoidingView/index.js +7 -4
  27. package/lib/commonjs/components/KeyboardAvoidingView/index.js.map +1 -1
  28. package/lib/commonjs/components/KeyboardAwareScrollView/index.js +199 -0
  29. package/lib/commonjs/components/KeyboardAwareScrollView/index.js.map +1 -0
  30. package/lib/commonjs/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js +100 -0
  31. package/lib/commonjs/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js.map +1 -0
  32. package/lib/commonjs/components/KeyboardAwareScrollView/utils.js +28 -0
  33. package/lib/commonjs/components/KeyboardAwareScrollView/utils.js.map +1 -0
  34. package/lib/commonjs/components/KeyboardStickyView/index.js +0 -1
  35. package/lib/commonjs/components/KeyboardStickyView/index.js.map +1 -1
  36. package/lib/commonjs/components/index.js +7 -0
  37. package/lib/commonjs/components/index.js.map +1 -1
  38. package/lib/commonjs/context.js +2 -1
  39. package/lib/commonjs/context.js.map +1 -1
  40. package/lib/commonjs/hooks.js +17 -2
  41. package/lib/commonjs/hooks.js.map +1 -1
  42. package/lib/commonjs/index.js +8 -1
  43. package/lib/commonjs/index.js.map +1 -1
  44. package/lib/commonjs/internal.js +2 -5
  45. package/lib/commonjs/internal.js.map +1 -1
  46. package/lib/commonjs/reanimated.js +5 -3
  47. package/lib/commonjs/reanimated.js.map +1 -1
  48. package/lib/commonjs/reanimated.native.js +20 -3
  49. package/lib/commonjs/reanimated.native.js.map +1 -1
  50. package/lib/commonjs/replicas.js +3 -1
  51. package/lib/commonjs/replicas.js.map +1 -1
  52. package/lib/commonjs/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
  53. package/lib/commonjs/specs/NativeKeyboardController.js.map +1 -1
  54. package/lib/commonjs/types.js.map +1 -1
  55. package/lib/module/animated.js +21 -15
  56. package/lib/module/animated.js.map +1 -1
  57. package/lib/module/bindings.js +1 -0
  58. package/lib/module/bindings.js.map +1 -1
  59. package/lib/module/components/KeyboardAvoidingView/index.js +8 -5
  60. package/lib/module/components/KeyboardAvoidingView/index.js.map +1 -1
  61. package/lib/module/components/KeyboardAwareScrollView/index.js +190 -0
  62. package/lib/module/components/KeyboardAwareScrollView/index.js.map +1 -0
  63. package/lib/module/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js +93 -0
  64. package/lib/module/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js.map +1 -0
  65. package/lib/module/components/KeyboardAwareScrollView/utils.js +21 -0
  66. package/lib/module/components/KeyboardAwareScrollView/utils.js.map +1 -0
  67. package/lib/module/components/KeyboardStickyView/index.js +0 -1
  68. package/lib/module/components/KeyboardStickyView/index.js.map +1 -1
  69. package/lib/module/components/index.js +1 -0
  70. package/lib/module/components/index.js.map +1 -1
  71. package/lib/module/context.js +2 -1
  72. package/lib/module/context.js.map +1 -1
  73. package/lib/module/hooks.js +16 -2
  74. package/lib/module/hooks.js.map +1 -1
  75. package/lib/module/index.js +1 -1
  76. package/lib/module/index.js.map +1 -1
  77. package/lib/module/internal.js +2 -5
  78. package/lib/module/internal.js.map +1 -1
  79. package/lib/module/reanimated.js +2 -1
  80. package/lib/module/reanimated.js.map +1 -1
  81. package/lib/module/reanimated.native.js +17 -1
  82. package/lib/module/reanimated.native.js.map +1 -1
  83. package/lib/module/replicas.js +5 -3
  84. package/lib/module/replicas.js.map +1 -1
  85. package/lib/module/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
  86. package/lib/module/specs/NativeKeyboardController.js.map +1 -1
  87. package/lib/module/types.js.map +1 -1
  88. package/lib/typescript/components/KeyboardAwareScrollView/index.d.ts +45 -0
  89. package/lib/typescript/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.d.ts +6 -0
  90. package/lib/typescript/components/KeyboardAwareScrollView/utils.d.ts +1 -0
  91. package/lib/typescript/components/index.d.ts +1 -0
  92. package/lib/typescript/context.d.ts +3 -2
  93. package/lib/typescript/hooks.d.ts +2 -1
  94. package/lib/typescript/index.d.ts +1 -1
  95. package/lib/typescript/internal.d.ts +8 -5
  96. package/lib/typescript/reanimated.d.ts +3 -2
  97. package/lib/typescript/reanimated.native.d.ts +3 -2
  98. package/lib/typescript/specs/KeyboardControllerViewNativeComponent.d.ts +4 -0
  99. package/lib/typescript/specs/NativeKeyboardController.d.ts +1 -0
  100. package/lib/typescript/types.d.ts +15 -2
  101. package/package.json +5 -2
  102. package/react-native-keyboard-controller.podspec +1 -1
  103. package/src/animated.tsx +27 -10
  104. package/src/bindings.ts +1 -0
  105. package/src/components/KeyboardAvoidingView/index.tsx +7 -5
  106. package/src/components/KeyboardAwareScrollView/index.tsx +262 -0
  107. package/src/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.ts +123 -0
  108. package/src/components/KeyboardAwareScrollView/utils.ts +26 -0
  109. package/src/components/KeyboardStickyView/index.tsx +1 -6
  110. package/src/components/index.ts +1 -0
  111. package/src/context.ts +9 -3
  112. package/src/hooks.ts +20 -3
  113. package/src/index.ts +5 -1
  114. package/src/internal.ts +17 -8
  115. package/src/reanimated.native.ts +27 -2
  116. package/src/reanimated.ts +8 -2
  117. package/src/replicas.ts +4 -3
  118. package/src/specs/KeyboardControllerViewNativeComponent.ts +7 -0
  119. package/src/specs/NativeKeyboardController.ts +1 -0
  120. 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! 😊
@@ -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')
@@ -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 FocusedInputLayoutObserver(val view: ReactViewGroup, private val context: ThemedReactContext?) {
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: FocusedInputLayoutObserver? = null
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 = FocusedInputLayoutObserver(view = view, context = context)
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) {
@@ -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 /* FocusedInputLayoutObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084AEEC72ACF4AB2001A3069 /* FocusedInputLayoutObserver.swift */; };
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 /* FocusedInputLayoutObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusedInputLayoutObserver.swift; sourceTree = "<group>"; };
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 /* FocusedInputLayoutObserver.swift */,
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 /* FocusedInputLayoutObserver.swift in Sources */,
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
- // FocusedInputLayoutObserver.swift
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(FocusedInputLayoutObserver)
25
- public class FocusedInputLayoutObserver: NSObject {
24
+ @objc(FocusedInputObserver)
25
+ public class FocusedInputObserver: NSObject {
26
26
  // class members
27
- var onEvent: (NSDictionary) -> Void
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 hasKVObserver = false
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
- handler: @escaping (NSDictionary) -> Void
39
+ onLayoutChangedHandler: @escaping (NSDictionary) -> Void,
40
+ onTextChangedHandler: @escaping (String) -> Void
37
41
  ) {
38
- onEvent = handler
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
- removeKVObserver()
74
+ removeObservers()
70
75
  currentInput = (UIResponder.current as? UIView)?.superview as UIView?
71
- setupKVObserver()
76
+ setupObservers()
72
77
  syncUpLayout()
73
78
  }
74
79
 
75
80
  @objc func keyboardWillHide(_: Notification) {
76
- removeKVObserver()
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
- onEvent(data as NSDictionary)
119
+ onLayoutChangedHandler(data as NSDictionary)
107
120
  }
108
121
 
109
- private func setupKVObserver() {
110
- if hasKVObserver {
122
+ private func setupObservers() {
123
+ if hasObservers {
111
124
  return
112
125
  }
113
126
 
114
127
  if currentInput != nil {
115
- hasKVObserver = true
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 removeKVObserver() {
121
- if !hasKVObserver {
134
+ private func removeObservers() {
135
+ if !hasObservers {
122
136
  return
123
137
  }
124
138
 
125
- hasKVObserver = false
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
+ }