react-native-keyboard-controller 1.9.5 → 1.10.1

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 (169) 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/.clang-format +30 -30
  12. package/ios/.swiftlint.yml +1 -1
  13. package/ios/KeyboardController.xcodeproj/project.pbxproj +10 -0
  14. package/ios/KeyboardControllerModule.mm +14 -0
  15. package/ios/events/FocusedInputTextChangedEvent.h +16 -0
  16. package/ios/events/FocusedInputTextChangedEvent.m +72 -0
  17. package/ios/observers/FocusedInputObserver.swift +29 -14
  18. package/ios/observers/TextChangeObserver.swift +41 -0
  19. package/ios/views/KeyboardControllerView.mm +53 -31
  20. package/ios/views/KeyboardControllerViewManager.mm +1 -0
  21. package/ios/views/KeyboardControllerViewManager.swift +21 -10
  22. package/jest/index.js +6 -4
  23. package/lib/commonjs/animated.js +33 -27
  24. package/lib/commonjs/animated.js.map +1 -1
  25. package/lib/commonjs/bindings.js +1 -0
  26. package/lib/commonjs/bindings.js.map +1 -1
  27. package/lib/commonjs/bindings.native.js +6 -6
  28. package/lib/commonjs/bindings.native.js.map +1 -1
  29. package/lib/commonjs/components/KeyboardAvoidingView/hooks.js +5 -3
  30. package/lib/commonjs/components/KeyboardAvoidingView/hooks.js.map +1 -1
  31. package/lib/commonjs/components/KeyboardAvoidingView/index.js +17 -9
  32. package/lib/commonjs/components/KeyboardAvoidingView/index.js.map +1 -1
  33. package/lib/commonjs/components/KeyboardAwareScrollView/index.js +199 -0
  34. package/lib/commonjs/components/KeyboardAwareScrollView/index.js.map +1 -0
  35. package/lib/commonjs/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js +100 -0
  36. package/lib/commonjs/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js.map +1 -0
  37. package/lib/commonjs/components/KeyboardAwareScrollView/utils.js +28 -0
  38. package/lib/commonjs/components/KeyboardAwareScrollView/utils.js.map +1 -0
  39. package/lib/commonjs/components/KeyboardStickyView/index.js +7 -4
  40. package/lib/commonjs/components/KeyboardStickyView/index.js.map +1 -1
  41. package/lib/commonjs/components/hooks/useKeyboardInterpolation.js +81 -0
  42. package/lib/commonjs/components/hooks/useKeyboardInterpolation.js.map +1 -0
  43. package/lib/commonjs/components/index.js +7 -0
  44. package/lib/commonjs/components/index.js.map +1 -1
  45. package/lib/commonjs/constants.js.map +1 -1
  46. package/lib/commonjs/context.js +2 -1
  47. package/lib/commonjs/context.js.map +1 -1
  48. package/lib/commonjs/hooks.js +17 -2
  49. package/lib/commonjs/hooks.js.map +1 -1
  50. package/lib/commonjs/index.js +8 -1
  51. package/lib/commonjs/index.js.map +1 -1
  52. package/lib/commonjs/internal.js +3 -6
  53. package/lib/commonjs/internal.js.map +1 -1
  54. package/lib/commonjs/monkey-patch.android.js +1 -1
  55. package/lib/commonjs/monkey-patch.android.js.map +1 -1
  56. package/lib/commonjs/monkey-patch.js.map +1 -1
  57. package/lib/commonjs/reanimated.js +5 -3
  58. package/lib/commonjs/reanimated.js.map +1 -1
  59. package/lib/commonjs/reanimated.native.js +29 -12
  60. package/lib/commonjs/reanimated.native.js.map +1 -1
  61. package/lib/commonjs/replicas.js +7 -5
  62. package/lib/commonjs/replicas.js.map +1 -1
  63. package/lib/commonjs/specs/KeyboardControllerViewNativeComponent.js +1 -1
  64. package/lib/commonjs/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
  65. package/lib/commonjs/specs/KeyboardGestureAreaNativeComponent.js +2 -2
  66. package/lib/commonjs/specs/KeyboardGestureAreaNativeComponent.js.map +1 -1
  67. package/lib/commonjs/specs/NativeKeyboardController.js +1 -1
  68. package/lib/commonjs/specs/NativeKeyboardController.js.map +1 -1
  69. package/lib/commonjs/specs/NativeStatusBarManagerCompat.js +1 -1
  70. package/lib/commonjs/specs/NativeStatusBarManagerCompat.js.map +1 -1
  71. package/lib/commonjs/types.js.map +1 -1
  72. package/lib/commonjs/utils.js.map +1 -1
  73. package/lib/module/animated.js +41 -35
  74. package/lib/module/animated.js.map +1 -1
  75. package/lib/module/bindings.js +2 -1
  76. package/lib/module/bindings.js.map +1 -1
  77. package/lib/module/bindings.native.js +7 -7
  78. package/lib/module/bindings.native.js.map +1 -1
  79. package/lib/module/components/KeyboardAvoidingView/hooks.js +7 -5
  80. package/lib/module/components/KeyboardAvoidingView/hooks.js.map +1 -1
  81. package/lib/module/components/KeyboardAvoidingView/index.js +20 -13
  82. package/lib/module/components/KeyboardAvoidingView/index.js.map +1 -1
  83. package/lib/module/components/KeyboardAwareScrollView/index.js +190 -0
  84. package/lib/module/components/KeyboardAwareScrollView/index.js.map +1 -0
  85. package/lib/module/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js +93 -0
  86. package/lib/module/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js.map +1 -0
  87. package/lib/module/components/KeyboardAwareScrollView/utils.js +21 -0
  88. package/lib/module/components/KeyboardAwareScrollView/utils.js.map +1 -0
  89. package/lib/module/components/KeyboardStickyView/index.js +9 -7
  90. package/lib/module/components/KeyboardStickyView/index.js.map +1 -1
  91. package/lib/module/components/hooks/useKeyboardInterpolation.js +74 -0
  92. package/lib/module/components/hooks/useKeyboardInterpolation.js.map +1 -0
  93. package/lib/module/components/index.js +3 -2
  94. package/lib/module/components/index.js.map +1 -1
  95. package/lib/module/context.js +4 -3
  96. package/lib/module/context.js.map +1 -1
  97. package/lib/module/hooks.js +21 -7
  98. package/lib/module/hooks.js.map +1 -1
  99. package/lib/module/index.js +8 -8
  100. package/lib/module/index.js.map +1 -1
  101. package/lib/module/internal.js +6 -9
  102. package/lib/module/internal.js.map +1 -1
  103. package/lib/module/monkey-patch.android.js +2 -2
  104. package/lib/module/monkey-patch.android.js.map +1 -1
  105. package/lib/module/monkey-patch.js.map +1 -1
  106. package/lib/module/reanimated.js +2 -1
  107. package/lib/module/reanimated.js.map +1 -1
  108. package/lib/module/reanimated.native.js +27 -11
  109. package/lib/module/reanimated.native.js.map +1 -1
  110. package/lib/module/replicas.js +13 -11
  111. package/lib/module/replicas.js.map +1 -1
  112. package/lib/module/specs/KeyboardControllerViewNativeComponent.js +2 -2
  113. package/lib/module/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
  114. package/lib/module/specs/KeyboardGestureAreaNativeComponent.js +3 -3
  115. package/lib/module/specs/KeyboardGestureAreaNativeComponent.js.map +1 -1
  116. package/lib/module/specs/NativeKeyboardController.js +2 -2
  117. package/lib/module/specs/NativeKeyboardController.js.map +1 -1
  118. package/lib/module/specs/NativeStatusBarManagerCompat.js +2 -2
  119. package/lib/module/specs/NativeStatusBarManagerCompat.js.map +1 -1
  120. package/lib/module/types.js.map +1 -1
  121. package/lib/module/utils.js.map +1 -1
  122. package/lib/typescript/animated.d.ts +1 -1
  123. package/lib/typescript/bindings.d.ts +1 -1
  124. package/lib/typescript/bindings.native.d.ts +1 -1
  125. package/lib/typescript/components/KeyboardAvoidingView/index.d.ts +4 -4
  126. package/lib/typescript/components/KeyboardAwareScrollView/index.d.ts +45 -0
  127. package/lib/typescript/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.d.ts +6 -0
  128. package/lib/typescript/components/KeyboardAwareScrollView/utils.d.ts +1 -0
  129. package/lib/typescript/components/KeyboardStickyView/index.d.ts +2 -2
  130. package/lib/typescript/components/hooks/useKeyboardInterpolation.d.ts +20 -0
  131. package/lib/typescript/components/index.d.ts +3 -2
  132. package/lib/typescript/context.d.ts +6 -5
  133. package/lib/typescript/hooks.d.ts +4 -3
  134. package/lib/typescript/index.d.ts +8 -8
  135. package/lib/typescript/internal.d.ts +9 -6
  136. package/lib/typescript/reanimated.d.ts +3 -2
  137. package/lib/typescript/reanimated.native.d.ts +3 -2
  138. package/lib/typescript/replicas.d.ts +1 -1
  139. package/lib/typescript/specs/KeyboardControllerViewNativeComponent.d.ts +7 -3
  140. package/lib/typescript/specs/KeyboardGestureAreaNativeComponent.d.ts +4 -4
  141. package/lib/typescript/specs/NativeKeyboardController.d.ts +2 -1
  142. package/lib/typescript/specs/NativeStatusBarManagerCompat.d.ts +1 -1
  143. package/lib/typescript/types.d.ts +18 -5
  144. package/package.json +12 -11
  145. package/react-native-keyboard-controller.podspec +1 -1
  146. package/src/animated.tsx +59 -42
  147. package/src/bindings.native.ts +11 -11
  148. package/src/bindings.ts +4 -3
  149. package/src/components/KeyboardAvoidingView/hooks.ts +9 -6
  150. package/src/components/KeyboardAvoidingView/index.tsx +30 -27
  151. package/src/components/KeyboardAwareScrollView/index.tsx +262 -0
  152. package/src/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.ts +123 -0
  153. package/src/components/KeyboardAwareScrollView/utils.ts +26 -0
  154. package/src/components/KeyboardStickyView/index.tsx +12 -18
  155. package/src/components/hooks/useKeyboardInterpolation.ts +96 -0
  156. package/src/components/index.ts +3 -2
  157. package/src/context.ts +15 -9
  158. package/src/hooks.ts +30 -13
  159. package/src/index.ts +12 -8
  160. package/src/internal.ts +22 -13
  161. package/src/monkey-patch.android.ts +2 -2
  162. package/src/reanimated.native.ts +43 -18
  163. package/src/reanimated.ts +9 -3
  164. package/src/replicas.ts +18 -17
  165. package/src/specs/KeyboardControllerViewNativeComponent.ts +12 -5
  166. package/src/specs/KeyboardGestureAreaNativeComponent.ts +7 -7
  167. package/src/specs/NativeKeyboardController.ts +4 -3
  168. package/src/specs/NativeStatusBarManagerCompat.ts +3 -3
  169. package/src/types.ts +45 -19
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?) {
package/ios/.clang-format CHANGED
@@ -4,7 +4,7 @@ AlignAfterOpenBracket: AlwaysBreak
4
4
  AlignConsecutiveAssignments: false
5
5
  AlignConsecutiveDeclarations: false
6
6
  AlignEscapedNewlinesLeft: true
7
- AlignOperands: false
7
+ AlignOperands: false
8
8
  AlignTrailingComments: false
9
9
  AllowAllParametersOfDeclarationOnNextLine: false
10
10
  AllowShortBlocksOnASingleLine: false
@@ -18,45 +18,45 @@ AlwaysBreakTemplateDeclarations: true
18
18
  BinPackArguments: false
19
19
  BinPackParameters: false
20
20
  BraceWrapping:
21
- AfterClass: false
21
+ AfterClass: false
22
22
  AfterControlStatement: false
23
- AfterEnum: false
24
- AfterFunction: false
25
- AfterNamespace: false
23
+ AfterEnum: false
24
+ AfterFunction: false
25
+ AfterNamespace: false
26
26
  AfterObjCDeclaration: false
27
- AfterStruct: false
28
- AfterUnion: false
29
- BeforeCatch: false
30
- BeforeElse: false
31
- IndentBraces: false
27
+ AfterStruct: false
28
+ AfterUnion: false
29
+ BeforeCatch: false
30
+ BeforeElse: false
31
+ IndentBraces: false
32
32
  BreakBeforeBinaryOperators: None
33
33
  BreakBeforeBraces: Attach
34
34
  BreakBeforeTernaryOperators: true
35
35
  BreakConstructorInitializersBeforeComma: false
36
36
  BreakAfterJavaFieldAnnotations: false
37
37
  BreakStringLiterals: false
38
- ColumnLimit: 80
39
- CommentPragmas: '^ IWYU pragma:'
38
+ ColumnLimit: 80
39
+ CommentPragmas: "^ IWYU pragma:"
40
40
  ConstructorInitializerAllOnOneLineOrOnePerLine: true
41
41
  ConstructorInitializerIndentWidth: 4
42
42
  ContinuationIndentWidth: 4
43
43
  Cpp11BracedListStyle: true
44
44
  DerivePointerAlignment: false
45
- DisableFormat: false
46
- ForEachMacros: [ FOR_EACH_RANGE, FOR_EACH, ]
45
+ DisableFormat: false
46
+ ForEachMacros: [FOR_EACH_RANGE, FOR_EACH]
47
47
  IncludeCategories:
48
- - Regex: '^<.*\.h(pp)?>'
49
- Priority: 1
50
- - Regex: '^<.*'
51
- Priority: 2
52
- - Regex: '.*'
53
- Priority: 3
48
+ - Regex: '^<.*\.h(pp)?>'
49
+ Priority: 1
50
+ - Regex: "^<.*"
51
+ Priority: 2
52
+ - Regex: ".*"
53
+ Priority: 3
54
54
  IndentCaseLabels: true
55
- IndentWidth: 2
55
+ IndentWidth: 2
56
56
  IndentWrappedFunctionNames: false
57
57
  KeepEmptyLinesAtTheStartOfBlocks: false
58
- MacroBlockBegin: ''
59
- MacroBlockEnd: ''
58
+ MacroBlockBegin: ""
59
+ MacroBlockEnd: ""
60
60
  MaxEmptyLinesToKeep: 1
61
61
  NamespaceIndentation: None
62
62
  ObjCBlockIndentWidth: 2
@@ -69,22 +69,22 @@ PenaltyBreakString: 1000
69
69
  PenaltyExcessCharacter: 1000000
70
70
  PenaltyReturnTypeOnItsOwnLine: 200
71
71
  PointerAlignment: Right
72
- ReflowComments: true
73
- SortIncludes: true
72
+ ReflowComments: true
73
+ SortIncludes: true
74
74
  SpaceAfterCStyleCast: false
75
75
  SpaceBeforeAssignmentOperators: true
76
76
  SpaceBeforeParens: ControlStatements
77
77
  SpaceInEmptyParentheses: false
78
78
  SpacesBeforeTrailingComments: 1
79
- SpacesInAngles: false
79
+ SpacesInAngles: false
80
80
  SpacesInContainerLiterals: true
81
81
  SpacesInCStyleCastParentheses: false
82
82
  SpacesInParentheses: false
83
83
  SpacesInSquareBrackets: false
84
- Standard: Cpp11
85
- TabWidth: 8
86
- UseTab: Never
84
+ Standard: Cpp11
85
+ TabWidth: 8
86
+ UseTab: Never
87
87
  ---
88
88
  Language: ObjC
89
- ColumnLimit: 100
89
+ ColumnLimit: 100
90
90
  BreakBeforeBraces: WebKit
@@ -7,4 +7,4 @@ line_length:
7
7
  ignores_urls: true
8
8
 
9
9
  excluded:
10
- - Pods
10
+ - Pods
@@ -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
+ }