react-native-keyboard-controller 1.9.5 → 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 (119) hide show
  1. package/README.md +1 -1
  2. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +4 -0
  3. package/android/src/main/java/com/reactnativekeyboardcontroller/events/FocusedInputTextChangedEvent.kt +20 -0
  4. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/EditText.kt +36 -0
  5. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/WindowInsetsAnimationCompat.kt +7 -0
  6. package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/FocusedInputObserver.kt +17 -0
  7. package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt +12 -0
  8. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardControllerViewManagerImpl.kt +2 -0
  9. package/android/src/main/java/com/reactnativekeyboardcontroller/modules/KeyboardControllerModuleImpl.kt +13 -0
  10. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +5 -0
  11. package/ios/KeyboardController.xcodeproj/project.pbxproj +10 -0
  12. package/ios/KeyboardControllerModule.mm +14 -0
  13. package/ios/events/FocusedInputTextChangedEvent.h +16 -0
  14. package/ios/events/FocusedInputTextChangedEvent.m +72 -0
  15. package/ios/observers/FocusedInputObserver.swift +29 -14
  16. package/ios/observers/TextChangeObserver.swift +41 -0
  17. package/ios/views/KeyboardControllerView.mm +53 -31
  18. package/ios/views/KeyboardControllerViewManager.mm +1 -0
  19. package/ios/views/KeyboardControllerViewManager.swift +21 -10
  20. package/jest/index.js +2 -0
  21. package/lib/commonjs/animated.js +20 -14
  22. package/lib/commonjs/animated.js.map +1 -1
  23. package/lib/commonjs/bindings.js +1 -0
  24. package/lib/commonjs/bindings.js.map +1 -1
  25. package/lib/commonjs/components/KeyboardAvoidingView/index.js +7 -4
  26. package/lib/commonjs/components/KeyboardAvoidingView/index.js.map +1 -1
  27. package/lib/commonjs/components/KeyboardAwareScrollView/index.js +199 -0
  28. package/lib/commonjs/components/KeyboardAwareScrollView/index.js.map +1 -0
  29. package/lib/commonjs/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js +100 -0
  30. package/lib/commonjs/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js.map +1 -0
  31. package/lib/commonjs/components/KeyboardAwareScrollView/utils.js +28 -0
  32. package/lib/commonjs/components/KeyboardAwareScrollView/utils.js.map +1 -0
  33. package/lib/commonjs/components/KeyboardStickyView/index.js +0 -1
  34. package/lib/commonjs/components/KeyboardStickyView/index.js.map +1 -1
  35. package/lib/commonjs/components/index.js +7 -0
  36. package/lib/commonjs/components/index.js.map +1 -1
  37. package/lib/commonjs/context.js +2 -1
  38. package/lib/commonjs/context.js.map +1 -1
  39. package/lib/commonjs/hooks.js +17 -2
  40. package/lib/commonjs/hooks.js.map +1 -1
  41. package/lib/commonjs/index.js +8 -1
  42. package/lib/commonjs/index.js.map +1 -1
  43. package/lib/commonjs/internal.js +2 -5
  44. package/lib/commonjs/internal.js.map +1 -1
  45. package/lib/commonjs/reanimated.js +5 -3
  46. package/lib/commonjs/reanimated.js.map +1 -1
  47. package/lib/commonjs/reanimated.native.js +20 -3
  48. package/lib/commonjs/reanimated.native.js.map +1 -1
  49. package/lib/commonjs/replicas.js +3 -1
  50. package/lib/commonjs/replicas.js.map +1 -1
  51. package/lib/commonjs/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
  52. package/lib/commonjs/specs/NativeKeyboardController.js.map +1 -1
  53. package/lib/commonjs/types.js.map +1 -1
  54. package/lib/module/animated.js +21 -15
  55. package/lib/module/animated.js.map +1 -1
  56. package/lib/module/bindings.js +1 -0
  57. package/lib/module/bindings.js.map +1 -1
  58. package/lib/module/components/KeyboardAvoidingView/index.js +8 -5
  59. package/lib/module/components/KeyboardAvoidingView/index.js.map +1 -1
  60. package/lib/module/components/KeyboardAwareScrollView/index.js +190 -0
  61. package/lib/module/components/KeyboardAwareScrollView/index.js.map +1 -0
  62. package/lib/module/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js +93 -0
  63. package/lib/module/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js.map +1 -0
  64. package/lib/module/components/KeyboardAwareScrollView/utils.js +21 -0
  65. package/lib/module/components/KeyboardAwareScrollView/utils.js.map +1 -0
  66. package/lib/module/components/KeyboardStickyView/index.js +0 -1
  67. package/lib/module/components/KeyboardStickyView/index.js.map +1 -1
  68. package/lib/module/components/index.js +1 -0
  69. package/lib/module/components/index.js.map +1 -1
  70. package/lib/module/context.js +2 -1
  71. package/lib/module/context.js.map +1 -1
  72. package/lib/module/hooks.js +16 -2
  73. package/lib/module/hooks.js.map +1 -1
  74. package/lib/module/index.js +1 -1
  75. package/lib/module/index.js.map +1 -1
  76. package/lib/module/internal.js +2 -5
  77. package/lib/module/internal.js.map +1 -1
  78. package/lib/module/reanimated.js +2 -1
  79. package/lib/module/reanimated.js.map +1 -1
  80. package/lib/module/reanimated.native.js +17 -1
  81. package/lib/module/reanimated.native.js.map +1 -1
  82. package/lib/module/replicas.js +5 -3
  83. package/lib/module/replicas.js.map +1 -1
  84. package/lib/module/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
  85. package/lib/module/specs/NativeKeyboardController.js.map +1 -1
  86. package/lib/module/types.js.map +1 -1
  87. package/lib/typescript/components/KeyboardAwareScrollView/index.d.ts +45 -0
  88. package/lib/typescript/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.d.ts +6 -0
  89. package/lib/typescript/components/KeyboardAwareScrollView/utils.d.ts +1 -0
  90. package/lib/typescript/components/index.d.ts +1 -0
  91. package/lib/typescript/context.d.ts +3 -2
  92. package/lib/typescript/hooks.d.ts +2 -1
  93. package/lib/typescript/index.d.ts +1 -1
  94. package/lib/typescript/internal.d.ts +8 -5
  95. package/lib/typescript/reanimated.d.ts +3 -2
  96. package/lib/typescript/reanimated.native.d.ts +3 -2
  97. package/lib/typescript/specs/KeyboardControllerViewNativeComponent.d.ts +4 -0
  98. package/lib/typescript/specs/NativeKeyboardController.d.ts +1 -0
  99. package/lib/typescript/types.d.ts +15 -2
  100. package/package.json +5 -2
  101. package/react-native-keyboard-controller.podspec +1 -1
  102. package/src/animated.tsx +27 -10
  103. package/src/bindings.ts +1 -0
  104. package/src/components/KeyboardAvoidingView/index.tsx +7 -5
  105. package/src/components/KeyboardAwareScrollView/index.tsx +262 -0
  106. package/src/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.ts +123 -0
  107. package/src/components/KeyboardAwareScrollView/utils.ts +26 -0
  108. package/src/components/KeyboardStickyView/index.tsx +1 -6
  109. package/src/components/index.ts +1 -0
  110. package/src/context.ts +9 -3
  111. package/src/hooks.ts +20 -3
  112. package/src/index.ts +5 -1
  113. package/src/internal.ts +17 -8
  114. package/src/reanimated.native.ts +27 -2
  115. package/src/reanimated.ts +8 -2
  116. package/src/replicas.ts +4 -3
  117. package/src/specs/KeyboardControllerViewNativeComponent.ts +7 -0
  118. package/src/specs/NativeKeyboardController.ts +1 -0
  119. 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! 😊
@@ -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
@@ -29,22 +32,36 @@ class FocusedInputObserver(val view: ReactViewGroup, private val context: Themed
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
@@ -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,6 +8,8 @@
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
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 */; };
@@ -31,6 +33,9 @@
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
41
  084AEEC72ACF4AB2001A3069 /* FocusedInputObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusedInputObserver.swift; 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>";
@@ -74,6 +81,7 @@
74
81
  children = (
75
82
  F3626A0628B3FE760021B2D9 /* KeyboardMovementObserver.swift */,
76
83
  084AEEC72ACF4AB2001A3069 /* FocusedInputObserver.swift */,
84
+ 083FB9842B15171600C0EFF0 /* TextChangeObserver.swift */,
77
85
  );
78
86
  path = observers;
79
87
  sourceTree = "<group>";
@@ -176,6 +184,8 @@
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
@@ -24,18 +24,23 @@ let noFocusedInputEvent: [String: Any] = [
24
24
  @objc(FocusedInputObserver)
25
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 FocusedInputObserver: 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 FocusedInputObserver: 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
+ }
@@ -10,6 +10,7 @@
10
10
  #ifdef RCT_NEW_ARCH_ENABLED
11
11
  #import "KeyboardControllerView.h"
12
12
  #import "FocusedInputLayoutChangedEvent.h"
13
+ #import "FocusedInputTextChangedEvent.h"
13
14
  #import "KeyboardMoveEvent.h"
14
15
  #import "react_native_keyboard_controller-Swift.h"
15
16
 
@@ -45,38 +46,59 @@ using namespace facebook::react;
45
46
  static const auto defaultProps = std::make_shared<const KeyboardControllerViewProps>();
46
47
  _props = defaultProps;
47
48
 
48
- inputObserver = [[FocusedInputObserver alloc] initWithHandler:^(NSDictionary *event) {
49
- if (self->_eventEmitter) {
50
- int target = [event[@"target"] integerValue];
51
- double absoluteY = [event[@"layout"][@"absoluteY"] doubleValue];
52
- double absoulteX = [event[@"layout"][@"absoluteX"] doubleValue];
53
- double y = [event[@"layout"][@"y"] doubleValue];
54
- double x = [event[@"layout"][@"x"] doubleValue];
55
- double width = [event[@"layout"][@"width"] doubleValue];
56
- double height = [event[@"layout"][@"height"] doubleValue];
57
-
58
- std::dynamic_pointer_cast<const facebook::react::KeyboardControllerViewEventEmitter>(
59
- self->_eventEmitter)
60
- ->onFocusedInputLayoutChanged(
61
- facebook::react::KeyboardControllerViewEventEmitter::OnFocusedInputLayoutChanged{
62
- .target = target,
63
- .layout = facebook::react::KeyboardControllerViewEventEmitter::
64
- OnFocusedInputLayoutChangedLayout{
65
- .absoluteY = absoluteY,
66
- .absoluteX = absoulteX,
67
- .height = height,
68
- .width = width,
69
- .x = x,
70
- .y = y}});
71
- // TODO: use built-in _eventEmitter once NativeAnimated module will use ModernEventemitter
72
- RCTBridge *bridge = [RCTBridge currentBridge];
73
- if (bridge && [bridge valueForKey:@"_jsThread"]) {
74
- FocusedInputLayoutChangedEvent *inputChangedEvent =
75
- [[FocusedInputLayoutChangedEvent alloc] initWithReactTag:@(self.tag) event:event];
76
- [bridge.eventDispatcher sendEvent:inputChangedEvent];
49
+ inputObserver = [[FocusedInputObserver alloc]
50
+ initOnLayoutChangedHandler:^(NSDictionary *event) {
51
+ if (self->_eventEmitter) {
52
+ int target = [event[@"target"] integerValue];
53
+ double absoluteY = [event[@"layout"][@"absoluteY"] doubleValue];
54
+ double absoulteX = [event[@"layout"][@"absoluteX"] doubleValue];
55
+ double y = [event[@"layout"][@"y"] doubleValue];
56
+ double x = [event[@"layout"][@"x"] doubleValue];
57
+ double width = [event[@"layout"][@"width"] doubleValue];
58
+ double height = [event[@"layout"][@"height"] doubleValue];
59
+
60
+ std::dynamic_pointer_cast<const facebook::react::KeyboardControllerViewEventEmitter>(
61
+ self->_eventEmitter)
62
+ ->onFocusedInputLayoutChanged(
63
+ facebook::react::KeyboardControllerViewEventEmitter::
64
+ OnFocusedInputLayoutChanged{
65
+ .target = target,
66
+ .layout = facebook::react::KeyboardControllerViewEventEmitter::
67
+ OnFocusedInputLayoutChangedLayout{
68
+ .absoluteY = absoluteY,
69
+ .absoluteX = absoulteX,
70
+ .height = height,
71
+ .width = width,
72
+ .x = x,
73
+ .y = y}});
74
+ // TODO: use built-in _eventEmitter once NativeAnimated module will use
75
+ // ModernEventemitter
76
+ RCTBridge *bridge = [RCTBridge currentBridge];
77
+ if (bridge && [bridge valueForKey:@"_jsThread"]) {
78
+ FocusedInputLayoutChangedEvent *inputChangedEvent =
79
+ [[FocusedInputLayoutChangedEvent alloc] initWithReactTag:@(self.tag) event:event];
80
+ [bridge.eventDispatcher sendEvent:inputChangedEvent];
81
+ }
82
+ }
77
83
  }
78
- }
79
- }];
84
+ onTextChangedHandler:^(NSString *text) {
85
+ if (self->_eventEmitter) {
86
+ std::dynamic_pointer_cast<const facebook::react::KeyboardControllerViewEventEmitter>(
87
+ self->_eventEmitter)
88
+ ->onFocusedInputTextChanged(
89
+ facebook::react::KeyboardControllerViewEventEmitter::OnFocusedInputTextChanged{
90
+ .text = std::string([text UTF8String])});
91
+
92
+ // TODO: use built-in _eventEmitter once NativeAnimated module will use
93
+ // ModernEventemitter
94
+ RCTBridge *bridge = [RCTBridge currentBridge];
95
+ if (bridge && [bridge valueForKey:@"_jsThread"]) {
96
+ FocusedInputTextChangedEvent *textChangedEvent =
97
+ [[FocusedInputTextChangedEvent alloc] initWithReactTag:@(self.tag) text:text];
98
+ [bridge.eventDispatcher sendEvent:textChangedEvent];
99
+ }
100
+ }
101
+ }];
80
102
  keyboardObserver = [[KeyboardMovementObserver alloc]
81
103
  initWithHandler:^(
82
104
  NSString *event,
@@ -13,5 +13,6 @@ RCT_EXPORT_VIEW_PROPERTY(onKeyboardMoveEnd, RCTDirectEventBlock);
13
13
  RCT_EXPORT_VIEW_PROPERTY(onKeyboardMoveInteractive, RCTDirectEventBlock);
14
14
  /// input callbacks
15
15
  RCT_EXPORT_VIEW_PROPERTY(onFocusedInputLayoutChanged, RCTDirectEventBlock);
16
+ RCT_EXPORT_VIEW_PROPERTY(onFocusedInputTextChanged, RCTDirectEventBlock);
16
17
 
17
18
  @end