react-native-keyboard-controller 1.21.0-beta.3 → 1.21.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 (140) hide show
  1. package/README.md +1 -0
  2. package/android/src/base/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt +1 -0
  3. package/android/src/fabric/java/com/reactnativekeyboardcontroller/ClippingScrollViewDecoratorViewManager.kt +7 -0
  4. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +8 -0
  5. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardToolbarGroupViewManager.kt +23 -0
  6. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ReactContext.kt +12 -0
  7. package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/FocusedInputObserver.kt +2 -1
  8. package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/WindowDimensionListener.kt +5 -1
  9. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardToolbarGroupViewManagerImpl.kt +13 -0
  10. package/android/src/main/java/com/reactnativekeyboardcontroller/modal/ModalAttachedWatcher.kt +4 -6
  11. package/android/src/main/java/com/reactnativekeyboardcontroller/modules/KeyboardControllerModuleImpl.kt +26 -0
  12. package/android/src/main/java/com/reactnativekeyboardcontroller/traversal/ViewHierarchyNavigator.kt +31 -4
  13. package/android/src/main/java/com/reactnativekeyboardcontroller/views/ClippingScrollViewDecoratorView.kt +5 -0
  14. package/android/src/main/java/com/reactnativekeyboardcontroller/views/KeyboardToolbarGroupReactViewGroup.kt +13 -0
  15. package/android/src/main/jni/RNKC.h +2 -0
  16. package/android/src/paper/java/com/reactnativekeyboardcontroller/ClippingScrollViewDecoratorViewManager.kt +8 -0
  17. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +9 -0
  18. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardToolbarGroupViewManager.kt +15 -0
  19. package/android/src/turbo/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt +2 -0
  20. package/common/cpp/react/renderer/components/RNKC/RNKCClippingScrollViewDecoratorViewComponentDescriptor.h +27 -0
  21. package/common/cpp/react/renderer/components/RNKC/RNKCClippingScrollViewDecoratorViewShadowNode.cpp +14 -0
  22. package/common/cpp/react/renderer/components/RNKC/RNKCClippingScrollViewDecoratorViewShadowNode.h +30 -0
  23. package/common/cpp/react/renderer/components/RNKC/RNKCClippingScrollViewDecoratorViewState.h +28 -0
  24. package/common/cpp/react/renderer/components/RNKC/RNKCKeyboardToolbarGroupViewComponentDescriptor.h +20 -0
  25. package/common/cpp/react/renderer/components/RNKC/RNKCKeyboardToolbarGroupViewShadowNode.cpp +7 -0
  26. package/common/cpp/react/renderer/components/RNKC/RNKCKeyboardToolbarGroupViewShadowNode.h +23 -0
  27. package/common/cpp/react/renderer/components/RNKC/RNKCKeyboardToolbarGroupViewState.h +21 -0
  28. package/ios/KeyboardControllerModule.mm +36 -0
  29. package/ios/extensions/UIApplication.swift +1 -1
  30. package/ios/observers/FocusedInputObserver.swift +2 -1
  31. package/ios/traversal/ViewHierarchyNavigator.swift +49 -3
  32. package/ios/views/ClippingScrollViewDecoratorViewManager.h +26 -0
  33. package/ios/views/ClippingScrollViewDecoratorViewManager.mm +231 -0
  34. package/ios/views/KeyboardToolbarGroupViewManager.h +28 -0
  35. package/ios/views/KeyboardToolbarGroupViewManager.mm +88 -0
  36. package/jest/index.js +11 -1
  37. package/lib/commonjs/bindings.js +18 -2
  38. package/lib/commonjs/bindings.js.map +1 -1
  39. package/lib/commonjs/bindings.native.js +3 -4
  40. package/lib/commonjs/bindings.native.js.map +1 -1
  41. package/lib/commonjs/components/KeyboardAvoidingView/index.js +30 -4
  42. package/lib/commonjs/components/KeyboardAvoidingView/index.js.map +1 -1
  43. package/lib/commonjs/components/KeyboardAwareScrollView/index.js +17 -4
  44. package/lib/commonjs/components/KeyboardAwareScrollView/index.js.map +1 -1
  45. package/lib/commonjs/components/KeyboardChatScrollView/index.js +33 -2
  46. package/lib/commonjs/components/KeyboardChatScrollView/index.js.map +1 -1
  47. package/lib/commonjs/components/KeyboardChatScrollView/types.js.map +1 -1
  48. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/helpers.js +107 -21
  49. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/helpers.js.map +1 -1
  50. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js +45 -17
  51. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js.map +1 -1
  52. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.js +61 -26
  53. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.js.map +1 -1
  54. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/types.js.map +1 -1
  55. package/lib/commonjs/components/KeyboardChatScrollView/useExtraContentPadding/index.js +72 -0
  56. package/lib/commonjs/components/KeyboardChatScrollView/useExtraContentPadding/index.js.map +1 -0
  57. package/lib/commonjs/components/KeyboardToolbar/index.js +1 -0
  58. package/lib/commonjs/components/KeyboardToolbar/index.js.map +1 -1
  59. package/lib/commonjs/components/ScrollViewWithBottomPadding/index.js +18 -6
  60. package/lib/commonjs/components/ScrollViewWithBottomPadding/index.js.map +1 -1
  61. package/lib/commonjs/specs/ClippingScrollViewDecoratorViewNativeComponent.js +1 -2
  62. package/lib/commonjs/specs/ClippingScrollViewDecoratorViewNativeComponent.js.map +1 -1
  63. package/lib/commonjs/specs/KeyboardToolbarGroupViewNativeComponent.js +12 -0
  64. package/lib/commonjs/specs/KeyboardToolbarGroupViewNativeComponent.js.map +1 -0
  65. package/lib/commonjs/specs/NativeKeyboardController.js.map +1 -1
  66. package/lib/commonjs/types/module.js.map +1 -1
  67. package/lib/commonjs/types/views.js.map +1 -1
  68. package/lib/module/bindings.js +17 -1
  69. package/lib/module/bindings.js.map +1 -1
  70. package/lib/module/bindings.native.js +2 -3
  71. package/lib/module/bindings.native.js.map +1 -1
  72. package/lib/module/components/KeyboardAvoidingView/index.js +29 -4
  73. package/lib/module/components/KeyboardAvoidingView/index.js.map +1 -1
  74. package/lib/module/components/KeyboardAwareScrollView/index.js +17 -4
  75. package/lib/module/components/KeyboardAwareScrollView/index.js.map +1 -1
  76. package/lib/module/components/KeyboardChatScrollView/index.js +34 -3
  77. package/lib/module/components/KeyboardChatScrollView/index.js.map +1 -1
  78. package/lib/module/components/KeyboardChatScrollView/types.js.map +1 -1
  79. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/helpers.js +98 -16
  80. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/helpers.js.map +1 -1
  81. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js +46 -18
  82. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js.map +1 -1
  83. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.js +62 -27
  84. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.js.map +1 -1
  85. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/types.js.map +1 -1
  86. package/lib/module/components/KeyboardChatScrollView/useExtraContentPadding/index.js +67 -0
  87. package/lib/module/components/KeyboardChatScrollView/useExtraContentPadding/index.js.map +1 -0
  88. package/lib/module/components/KeyboardToolbar/index.js +2 -1
  89. package/lib/module/components/KeyboardToolbar/index.js.map +1 -1
  90. package/lib/module/components/ScrollViewWithBottomPadding/index.js +19 -7
  91. package/lib/module/components/ScrollViewWithBottomPadding/index.js.map +1 -1
  92. package/lib/module/specs/ClippingScrollViewDecoratorViewNativeComponent.js +1 -2
  93. package/lib/module/specs/ClippingScrollViewDecoratorViewNativeComponent.js.map +1 -1
  94. package/lib/module/specs/KeyboardToolbarGroupViewNativeComponent.js +5 -0
  95. package/lib/module/specs/KeyboardToolbarGroupViewNativeComponent.js.map +1 -0
  96. package/lib/module/specs/NativeKeyboardController.js.map +1 -1
  97. package/lib/module/types/module.js.map +1 -1
  98. package/lib/module/types/views.js.map +1 -1
  99. package/lib/typescript/bindings.d.ts +12 -2
  100. package/lib/typescript/bindings.native.d.ts +2 -1
  101. package/lib/typescript/components/KeyboardAvoidingView/index.d.ts +44 -13
  102. package/lib/typescript/components/KeyboardChatScrollView/index.d.ts +3 -0
  103. package/lib/typescript/components/KeyboardChatScrollView/types.d.ts +41 -0
  104. package/lib/typescript/components/KeyboardChatScrollView/useChatKeyboard/helpers.d.ts +63 -8
  105. package/lib/typescript/components/KeyboardChatScrollView/useChatKeyboard/index.d.ts +1 -4
  106. package/lib/typescript/components/KeyboardChatScrollView/useChatKeyboard/types.d.ts +15 -0
  107. package/lib/typescript/components/KeyboardChatScrollView/useExtraContentPadding/index.d.ts +43 -0
  108. package/lib/typescript/components/KeyboardToolbar/index.d.ts +2 -0
  109. package/lib/typescript/components/ScrollViewWithBottomPadding/index.d.ts +3 -0
  110. package/lib/typescript/specs/ClippingScrollViewDecoratorViewNativeComponent.d.ts +1 -0
  111. package/lib/typescript/specs/KeyboardToolbarGroupViewNativeComponent.d.ts +6 -0
  112. package/lib/typescript/specs/NativeKeyboardController.d.ts +1 -0
  113. package/lib/typescript/types/module.d.ts +7 -0
  114. package/lib/typescript/types/views.d.ts +2 -0
  115. package/package.json +6 -2
  116. package/react-native.config.js +2 -0
  117. package/src/bindings.native.ts +4 -4
  118. package/src/bindings.ts +15 -1
  119. package/src/components/KeyboardAvoidingView/index.tsx +56 -29
  120. package/src/components/KeyboardAwareScrollView/index.tsx +20 -5
  121. package/src/components/KeyboardChatScrollView/index.tsx +47 -3
  122. package/src/components/KeyboardChatScrollView/types.ts +41 -0
  123. package/src/components/KeyboardChatScrollView/useChatKeyboard/helpers.ts +126 -21
  124. package/src/components/KeyboardChatScrollView/useChatKeyboard/index.ios.ts +101 -21
  125. package/src/components/KeyboardChatScrollView/useChatKeyboard/index.ts +116 -40
  126. package/src/components/KeyboardChatScrollView/useChatKeyboard/types.ts +9 -0
  127. package/src/components/KeyboardChatScrollView/useExtraContentPadding/index.ts +123 -0
  128. package/src/components/KeyboardToolbar/index.tsx +6 -1
  129. package/src/components/ScrollViewWithBottomPadding/index.tsx +31 -4
  130. package/src/specs/ClippingScrollViewDecoratorViewNativeComponent.ts +1 -1
  131. package/src/specs/KeyboardToolbarGroupViewNativeComponent.ts +10 -0
  132. package/src/specs/NativeKeyboardController.ts +1 -0
  133. package/src/types/module.ts +9 -0
  134. package/src/types/views.ts +2 -0
  135. package/android/.settings/org.eclipse.buildship.core.prefs +0 -2
  136. package/ios/KeyboardController.xcodeproj/xcuserdata/kirylziusko.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
  137. package/ios/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  138. package/ios/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  139. package/ios/Tests/Tests.xcodeproj/project.xcworkspace/xcuserdata/kirylziusko.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  140. package/ios/Tests/Tests.xcodeproj/xcuserdata/kirylziusko.xcuserdatad/xcschemes/xcschememanagement.plist +0 -32
package/README.md CHANGED
@@ -19,6 +19,7 @@ A universal keyboard handling solution for React Native — lightweight, fully c
19
19
  - 🌐 Display anything over the keyboard (without dismissing it) using `OverKeyboardView`
20
20
  - 🎨 Match keyboard background with `KeyboardBackgroundView`
21
21
  - 🧩 Extend keyboard with custom buttons/UI via `KeyboardExtender`
22
+ - 💬 Build chat interfaces (messenger and 🤖 AI chats) easily with `KeyboardChatScrollView`
22
23
  - 📝 Easy retrieval of focused input info
23
24
  - 🧭 Compatible with any navigation library
24
25
  - ✨ More coming soon... stay tuned! 😊
@@ -60,5 +60,6 @@ class KeyboardControllerPackage : BaseReactPackage() {
60
60
  OverKeyboardViewManager(),
61
61
  KeyboardBackgroundViewManager(),
62
62
  ClippingScrollViewDecoratorViewManager(),
63
+ KeyboardToolbarGroupViewManager(),
63
64
  )
64
65
  }
@@ -34,4 +34,11 @@ class ClippingScrollViewDecoratorViewManager :
34
34
  ) {
35
35
  view?.setContentInsetTop(value)
36
36
  }
37
+
38
+ override fun setApplyWorkaroundForContentInsetHitTestBug(
39
+ view: ClippingScrollViewDecoratorView?,
40
+ value: Boolean,
41
+ ) {
42
+ view?.setApplyWorkaroundForContentInsetHitTestBug(value)
43
+ }
37
44
  }
@@ -1,5 +1,6 @@
1
1
  package com.reactnativekeyboardcontroller
2
2
 
3
+ import com.facebook.react.bridge.Promise
3
4
  import com.facebook.react.bridge.ReactApplicationContext
4
5
  import com.reactnativekeyboardcontroller.modules.KeyboardControllerModuleImpl
5
6
 
@@ -33,6 +34,13 @@ class KeyboardControllerModule(
33
34
  module.setFocusTo(direction)
34
35
  }
35
36
 
37
+ override fun viewPositionInWindow(
38
+ viewTag: Double,
39
+ promise: Promise,
40
+ ) {
41
+ module.viewPositionInWindow(viewTag, promise)
42
+ }
43
+
36
44
  override fun addListener(eventName: String?) {
37
45
  // Required for RN built-in Event Emitter Calls.
38
46
  }
@@ -0,0 +1,23 @@
1
+ package com.reactnativekeyboardcontroller
2
+
3
+ import com.facebook.react.uimanager.ThemedReactContext
4
+ import com.facebook.react.uimanager.ViewGroupManager
5
+ import com.facebook.react.uimanager.ViewManagerDelegate
6
+ import com.facebook.react.viewmanagers.KeyboardToolbarGroupViewManagerDelegate
7
+ import com.facebook.react.viewmanagers.KeyboardToolbarGroupViewManagerInterface
8
+ import com.reactnativekeyboardcontroller.managers.KeyboardToolbarGroupViewManagerImpl
9
+ import com.reactnativekeyboardcontroller.views.KeyboardToolbarGroupReactViewGroup
10
+
11
+ class KeyboardToolbarGroupViewManager :
12
+ ViewGroupManager<KeyboardToolbarGroupReactViewGroup>(),
13
+ KeyboardToolbarGroupViewManagerInterface<KeyboardToolbarGroupReactViewGroup> {
14
+ private val manager = KeyboardToolbarGroupViewManagerImpl()
15
+ private val mDelegate = KeyboardToolbarGroupViewManagerDelegate(this)
16
+
17
+ override fun getDelegate(): ViewManagerDelegate<KeyboardToolbarGroupReactViewGroup> = mDelegate
18
+
19
+ override fun getName(): String = KeyboardToolbarGroupViewManagerImpl.NAME
20
+
21
+ override fun createViewInstance(context: ThemedReactContext): KeyboardToolbarGroupReactViewGroup =
22
+ manager.createViewInstance(context)
23
+ }
@@ -3,6 +3,18 @@ package com.reactnativekeyboardcontroller.extensions
3
3
  import android.view.View
4
4
  import android.view.ViewGroup
5
5
  import com.facebook.react.bridge.ReactContext
6
+ import com.facebook.react.uimanager.UIManagerHelper
7
+ import com.facebook.react.uimanager.common.UIManagerType
8
+ import com.facebook.react.uimanager.events.EventDispatcher
9
+ import com.reactnativekeyboardcontroller.BuildConfig
10
+
11
+ private val archType = if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) UIManagerType.FABRIC else UIManagerType.DEFAULT
12
+
13
+ val ReactContext.uiManager
14
+ get() = UIManagerHelper.getUIManager(this, archType)
15
+
16
+ val ReactContext.eventDispatcher: EventDispatcher?
17
+ get() = UIManagerHelper.getEventDispatcher(this, archType)
6
18
 
7
19
  val ReactContext.rootView: View?
8
20
  get() =
@@ -120,7 +120,8 @@ class FocusedInputObserver(
120
120
  selectionSubscription = newFocus.addOnSelectionChangedListener(selectionListener)
121
121
  FocusedInputHolder.set(newFocus)
122
122
 
123
- val allInputFields = ViewHierarchyNavigator.getAllInputFields(context?.rootView)
123
+ val groupAncestor = ViewHierarchyNavigator.findGroupAncestor(newFocus)
124
+ val allInputFields = ViewHierarchyNavigator.getAllInputFields(groupAncestor ?: context?.rootView)
124
125
  val currentIndex = allInputFields.indexOf(newFocus)
125
126
 
126
127
  context.emitEvent(
@@ -18,6 +18,7 @@ class WindowDimensionListener(
18
18
  ) {
19
19
  private var lastDispatchedDimensions = Dimensions(0.0, 0.0)
20
20
  private var layoutListener: ViewTreeObserver.OnGlobalLayoutListener? = null
21
+ private var attachedContent: ViewGroup? = null
21
22
 
22
23
  public fun attachListener() {
23
24
  // attach to content view only once per app instance
@@ -25,6 +26,7 @@ class WindowDimensionListener(
25
26
  listenerID = context.hashCode()
26
27
 
27
28
  val content = context.content
29
+ attachedContent = content
28
30
 
29
31
  updateWindowDimensions(content)
30
32
 
@@ -38,7 +40,9 @@ class WindowDimensionListener(
38
40
  }
39
41
 
40
42
  public fun detachListener() {
41
- context?.content?.viewTreeObserver?.removeOnGlobalLayoutListener(layoutListener)
43
+ attachedContent?.viewTreeObserver?.removeOnGlobalLayoutListener(layoutListener)
44
+ attachedContent = null
45
+ layoutListener = null
42
46
  }
43
47
 
44
48
  private fun updateWindowDimensions(content: ViewGroup?) {
@@ -0,0 +1,13 @@
1
+ package com.reactnativekeyboardcontroller.managers
2
+
3
+ import com.facebook.react.uimanager.ThemedReactContext
4
+ import com.reactnativekeyboardcontroller.views.KeyboardToolbarGroupReactViewGroup
5
+
6
+ class KeyboardToolbarGroupViewManagerImpl {
7
+ fun createViewInstance(reactContext: ThemedReactContext): KeyboardToolbarGroupReactViewGroup =
8
+ KeyboardToolbarGroupReactViewGroup(reactContext)
9
+
10
+ companion object {
11
+ const val NAME = "KeyboardToolbarGroupView"
12
+ }
13
+ }
@@ -4,15 +4,14 @@ import android.view.ViewGroup
4
4
  import android.view.WindowManager
5
5
  import androidx.core.view.ViewCompat
6
6
  import com.facebook.react.uimanager.ThemedReactContext
7
- import com.facebook.react.uimanager.UIManagerHelper
8
- import com.facebook.react.uimanager.common.UIManagerType
9
7
  import com.facebook.react.uimanager.events.Event
10
8
  import com.facebook.react.uimanager.events.EventDispatcherListener
11
9
  import com.facebook.react.views.modal.ReactModalHostView
12
10
  import com.facebook.react.views.view.ReactViewGroup
13
- import com.reactnativekeyboardcontroller.BuildConfig
14
11
  import com.reactnativekeyboardcontroller.constants.Keyboard
12
+ import com.reactnativekeyboardcontroller.extensions.eventDispatcher
15
13
  import com.reactnativekeyboardcontroller.extensions.removeSelf
14
+ import com.reactnativekeyboardcontroller.extensions.uiManager
16
15
  import com.reactnativekeyboardcontroller.listeners.KeyboardAnimationCallback
17
16
  import com.reactnativekeyboardcontroller.listeners.KeyboardAnimationCallbackConfig
18
17
  import com.reactnativekeyboardcontroller.log.Logger
@@ -26,9 +25,8 @@ class ModalAttachedWatcher(
26
25
  private val config: KeyboardAnimationCallbackConfig,
27
26
  private var callback: () -> KeyboardAnimationCallback?,
28
27
  ) : EventDispatcherListener {
29
- private val archType = if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) UIManagerType.FABRIC else UIManagerType.DEFAULT
30
- private val uiManager = UIManagerHelper.getUIManager(reactContext.reactApplicationContext, archType)
31
- private val eventDispatcher = UIManagerHelper.getEventDispatcher(reactContext.reactApplicationContext, archType)
28
+ private val uiManager = reactContext.uiManager
29
+ private val eventDispatcher = reactContext.eventDispatcher
32
30
 
33
31
  override fun onEventDispatch(event: Event<*>) {
34
32
  if (event.eventName != MODAL_SHOW_EVENT) {
@@ -5,8 +5,13 @@ import android.os.Build
5
5
  import android.view.View
6
6
  import android.view.WindowManager
7
7
  import android.view.inputmethod.InputMethodManager
8
+ import com.facebook.react.bridge.Arguments
9
+ import com.facebook.react.bridge.Promise
8
10
  import com.facebook.react.bridge.ReactApplicationContext
9
11
  import com.facebook.react.bridge.UiThreadUtil
12
+ import com.reactnativekeyboardcontroller.extensions.dp
13
+ import com.reactnativekeyboardcontroller.extensions.screenLocation
14
+ import com.reactnativekeyboardcontroller.extensions.uiManager
10
15
  import com.reactnativekeyboardcontroller.interactive.KeyboardAnimationController
11
16
  import com.reactnativekeyboardcontroller.traversal.FocusedInputHolder
12
17
  import com.reactnativekeyboardcontroller.traversal.ViewHierarchyNavigator
@@ -14,6 +19,7 @@ import com.reactnativekeyboardcontroller.traversal.ViewHierarchyNavigator
14
19
  class KeyboardControllerModuleImpl(
15
20
  private val mReactContext: ReactApplicationContext,
16
21
  ) {
22
+ private val uiManager = mReactContext.uiManager
17
23
  private val controller = KeyboardAnimationController()
18
24
  private val mDefaultMode: Int = getCurrentMode()
19
25
 
@@ -77,6 +83,26 @@ class KeyboardControllerModuleImpl(
77
83
  }
78
84
  }
79
85
 
86
+ fun viewPositionInWindow(
87
+ viewTag: Double,
88
+ promise: Promise,
89
+ ) {
90
+ UiThreadUtil.runOnUiThread {
91
+ val view = uiManager?.resolveView(viewTag.toInt())
92
+ if (view == null) {
93
+ promise.reject("E_VIEW_NOT_FOUND", "Could not find view for tag")
94
+ return@runOnUiThread
95
+ }
96
+ val location = view.screenLocation
97
+ val map = Arguments.createMap()
98
+ map.putDouble("x", location[0].toFloat().dp)
99
+ map.putDouble("y", location[1].toFloat().dp)
100
+ map.putDouble("width", view.width.toFloat().dp)
101
+ map.putDouble("height", view.height.toFloat().dp)
102
+ promise.resolve(map)
103
+ }
104
+ }
105
+
80
106
  private fun setSoftInputMode(mode: Int) {
81
107
  UiThreadUtil.runOnUiThread {
82
108
  if (getCurrentMode() != mode) {
@@ -5,6 +5,7 @@ import android.view.ViewGroup
5
5
  import android.widget.EditText
6
6
  import com.facebook.react.bridge.UiThreadUtil
7
7
  import com.reactnativekeyboardcontroller.extensions.focus
8
+ import com.reactnativekeyboardcontroller.views.KeyboardToolbarGroupReactViewGroup
8
9
 
9
10
  object ViewHierarchyNavigator {
10
11
  fun setFocusTo(
@@ -25,19 +26,40 @@ object ViewHierarchyNavigator {
25
26
  fun findEditTexts(view: View?) {
26
27
  if (isValidTextInput(view)) {
27
28
  editTexts.add(view as EditText)
28
- } else if (view is ViewGroup) {
29
+ } else if (view is ViewGroup && view !is KeyboardToolbarGroupReactViewGroup) {
29
30
  for (i in 0 until view.childCount) {
30
31
  findEditTexts(view.getChildAt(i))
31
32
  }
32
33
  }
33
34
  }
34
35
 
35
- // Start the search with the provided viewGroup
36
- findEditTexts(viewGroup)
36
+ // If the root is a group itself, search within it (for group-scoped queries)
37
+ if (viewGroup is KeyboardToolbarGroupReactViewGroup) {
38
+ for (i in 0 until viewGroup.childCount) {
39
+ findEditTexts(viewGroup.getChildAt(i))
40
+ }
41
+ } else {
42
+ findEditTexts(viewGroup)
43
+ }
37
44
 
38
45
  return editTexts
39
46
  }
40
47
 
48
+ /**
49
+ * Finds the closest [KeyboardToolbarGroupReactViewGroup] ancestor of the given view.
50
+ * Returns null if the view is not inside any group.
51
+ */
52
+ fun findGroupAncestor(view: View?): KeyboardToolbarGroupReactViewGroup? {
53
+ var current = view?.parent
54
+ while (current != null) {
55
+ if (current is KeyboardToolbarGroupReactViewGroup) {
56
+ return current
57
+ }
58
+ current = current.parent
59
+ }
60
+ return null
61
+ }
62
+
41
63
  private fun findNextEditText(currentFocus: View): EditText? = findEditTextInDirection(currentFocus, 1)
42
64
 
43
65
  private fun findPreviousEditText(currentFocus: View): EditText? = findEditTextInDirection(currentFocus, -1)
@@ -64,6 +86,11 @@ object ViewHierarchyNavigator {
64
86
  i += direction
65
87
  }
66
88
 
89
+ // Don't navigate outside the group boundary
90
+ if (parentViewGroup is KeyboardToolbarGroupReactViewGroup) {
91
+ return null
92
+ }
93
+
67
94
  // Recurse to the parent's parent if no sibling EditText is found
68
95
  return findEditTextInDirection(parentViewGroup, direction)
69
96
  }
@@ -91,7 +118,7 @@ object ViewHierarchyNavigator {
91
118
 
92
119
  if (isValidTextInput(child)) {
93
120
  result = child as EditText
94
- } else if (child is ViewGroup) {
121
+ } else if (child is ViewGroup && child !is KeyboardToolbarGroupReactViewGroup) {
95
122
  // If the child is a ViewGroup, check its children recursively
96
123
  result = findEditTextInHierarchy(child, direction)
97
124
  }
@@ -31,6 +31,11 @@ class ClippingScrollViewDecoratorView(
31
31
  decorateScrollView()
32
32
  }
33
33
 
34
+ @Suppress("detekt:UnusedParameter")
35
+ fun setApplyWorkaroundForContentInsetHitTestBug(value: Boolean) {
36
+ // iOS-only workaround; no-op on Android
37
+ }
38
+
34
39
  private fun decorateScrollView() {
35
40
  val scrollView = getChildAt(0) as? ScrollView ?: return
36
41
 
@@ -0,0 +1,13 @@
1
+ package com.reactnativekeyboardcontroller.views
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.content.Context
5
+ import com.facebook.react.uimanager.ThemedReactContext
6
+ import com.facebook.react.views.view.ReactViewGroup
7
+
8
+ @SuppressLint("ViewConstructor")
9
+ class KeyboardToolbarGroupReactViewGroup : ReactViewGroup {
10
+ constructor(reactContext: ThemedReactContext) : super(reactContext)
11
+ internal constructor(context: Context) : super(context)
12
+ // semantic view used in KeyboardToolbar traverse algorithm
13
+ }
@@ -14,6 +14,8 @@
14
14
  #include <react/renderer/components/RNKC/RNKCKeyboardGestureAreaComponentDescriptor.h>
15
15
  #include <react/renderer/components/RNKC/RNKCOverKeyboardViewComponentDescriptor.h>
16
16
  #include <react/renderer/components/RNKC/RNKCKeyboardBackgroundViewComponentDescriptor.h>
17
+ #include <react/renderer/components/RNKC/RNKCClippingScrollViewDecoratorViewComponentDescriptor.h>
18
+ #include <react/renderer/components/RNKC/RNKCKeyboardToolbarGroupViewComponentDescriptor.h>
17
19
 
18
20
  #include <memory>
19
21
  #include <string>
@@ -29,4 +29,12 @@ class ClippingScrollViewDecoratorViewManager : ViewGroupManager<ClippingScrollVi
29
29
  ) {
30
30
  view.setContentInsetTop(value)
31
31
  }
32
+
33
+ @ReactProp(name = "applyWorkaroundForContentInsetHitTestBug")
34
+ fun setApplyWorkaroundForContentInsetHitTestBug(
35
+ view: ClippingScrollViewDecoratorView,
36
+ value: Boolean,
37
+ ) {
38
+ view.setApplyWorkaroundForContentInsetHitTestBug(value)
39
+ }
32
40
  }
@@ -1,5 +1,6 @@
1
1
  package com.reactnativekeyboardcontroller
2
2
 
3
+ import com.facebook.react.bridge.Promise
3
4
  import com.facebook.react.bridge.ReactApplicationContext
4
5
  import com.facebook.react.bridge.ReactContextBaseJavaModule
5
6
  import com.facebook.react.bridge.ReactMethod
@@ -40,6 +41,14 @@ class KeyboardControllerModule(
40
41
  module.setFocusTo(direction)
41
42
  }
42
43
 
44
+ @ReactMethod
45
+ fun viewPositionInWindow(
46
+ viewTag: Double,
47
+ promise: Promise,
48
+ ) {
49
+ module.viewPositionInWindow(viewTag, promise)
50
+ }
51
+
43
52
  @Suppress("detekt:UnusedParameter")
44
53
  @ReactMethod
45
54
  fun addListener(eventName: String?) {
@@ -0,0 +1,15 @@
1
+ package com.reactnativekeyboardcontroller
2
+
3
+ import com.facebook.react.uimanager.ThemedReactContext
4
+ import com.facebook.react.views.view.ReactViewManager
5
+ import com.reactnativekeyboardcontroller.managers.KeyboardToolbarGroupViewManagerImpl
6
+ import com.reactnativekeyboardcontroller.views.KeyboardToolbarGroupReactViewGroup
7
+
8
+ class KeyboardToolbarGroupViewManager : ReactViewManager() {
9
+ private val manager = KeyboardToolbarGroupViewManagerImpl()
10
+
11
+ override fun getName(): String = KeyboardToolbarGroupViewManagerImpl.NAME
12
+
13
+ override fun createViewInstance(reactContext: ThemedReactContext): KeyboardToolbarGroupReactViewGroup =
14
+ manager.createViewInstance(reactContext)
15
+ }
@@ -60,5 +60,7 @@ class KeyboardControllerPackage : TurboReactPackage() {
60
60
  KeyboardGestureAreaViewManager(),
61
61
  OverKeyboardViewManager(),
62
62
  KeyboardBackgroundViewManager(),
63
+ ClippingScrollViewDecoratorViewManager(),
64
+ KeyboardToolbarGroupViewManager(),
63
65
  )
64
66
  }
@@ -0,0 +1,27 @@
1
+ //
2
+ // RNKCClippingScrollViewDecoratorViewComponentDescriptor.h
3
+ // Pods
4
+ //
5
+ // Created by Kiryl Ziusko on 03/03/2025.
6
+ //
7
+
8
+ #pragma once
9
+
10
+ #include "RNKCClippingScrollViewDecoratorViewShadowNode.h"
11
+
12
+ #include <react/debug/react_native_assert.h>
13
+ #include <react/renderer/components/RNKC/Props.h>
14
+ #include <react/renderer/core/ConcreteComponentDescriptor.h>
15
+
16
+ namespace facebook::react {
17
+ class ClippingScrollViewDecoratorViewComponentDescriptor final
18
+ : public ConcreteComponentDescriptor<ClippingScrollViewDecoratorViewShadowNode> {
19
+ public:
20
+ using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
21
+ void adopt(ShadowNode &shadowNode) const override {
22
+ react_native_assert(dynamic_cast<ClippingScrollViewDecoratorViewShadowNode *>(&shadowNode));
23
+ ConcreteComponentDescriptor::adopt(shadowNode);
24
+ }
25
+ };
26
+
27
+ } // namespace facebook::react
@@ -0,0 +1,14 @@
1
+ //
2
+ // RNKCClippingScrollViewDecoratorViewShadowNode.cpp
3
+ // Pods
4
+ //
5
+ // Created by Kiryl Ziusko on 03/03/2025.
6
+ //
7
+
8
+ #include "RNKCClippingScrollViewDecoratorViewShadowNode.h"
9
+
10
+ namespace facebook::react {
11
+
12
+ extern const char ClippingScrollViewDecoratorViewComponentName[] = "ClippingScrollViewDecoratorView";
13
+
14
+ } // namespace facebook::react
@@ -0,0 +1,30 @@
1
+ //
2
+ // RNKCClippingScrollViewDecoratorViewShadowNode.h
3
+ // Pods
4
+ //
5
+ // Created by Kiryl Ziusko on 03/03/2025.
6
+ //
7
+
8
+ #pragma once
9
+
10
+ #include "RNKCClippingScrollViewDecoratorViewState.h"
11
+
12
+ #include <react/renderer/components/RNKC/EventEmitters.h>
13
+ #include <react/renderer/components/RNKC/Props.h>
14
+ #include <react/renderer/components/view/ConcreteViewShadowNode.h>
15
+ #include <jsi/jsi.h>
16
+
17
+ namespace facebook::react {
18
+
19
+ JSI_EXPORT extern const char ClippingScrollViewDecoratorViewComponentName[];
20
+
21
+ /*
22
+ * `ShadowNode` for <ClippingScrollViewDecoratorView> component.
23
+ */
24
+ using ClippingScrollViewDecoratorViewShadowNode = ConcreteViewShadowNode<
25
+ ClippingScrollViewDecoratorViewComponentName,
26
+ ClippingScrollViewDecoratorViewProps,
27
+ ClippingScrollViewDecoratorViewEventEmitter,
28
+ ClippingScrollViewDecoratorViewState>;
29
+
30
+ } // namespace facebook::react
@@ -0,0 +1,28 @@
1
+ //
2
+ // RNKCClippingScrollViewDecoratorViewState.h
3
+ // Pods
4
+ //
5
+ // Created by Kiryl Ziusko on 03/03/2025.
6
+ //
7
+
8
+ #pragma once
9
+
10
+ #ifdef ANDROID
11
+ #include <folly/dynamic.h>
12
+ #endif
13
+
14
+ namespace facebook::react {
15
+
16
+ class ClippingScrollViewDecoratorViewState {
17
+ public:
18
+ ClippingScrollViewDecoratorViewState() = default;
19
+
20
+ #ifdef ANDROID
21
+ ClippingScrollViewDecoratorViewState(ClippingScrollViewDecoratorViewState const &previousState, folly::dynamic data) {}
22
+ folly::dynamic getDynamic() const {
23
+ return {};
24
+ }
25
+ #endif
26
+ };
27
+
28
+ } // namespace facebook::react
@@ -0,0 +1,20 @@
1
+ #pragma once
2
+
3
+ #include "RNKCKeyboardToolbarGroupViewShadowNode.h"
4
+
5
+ #include <react/debug/react_native_assert.h>
6
+ #include <react/renderer/components/RNKC/Props.h>
7
+ #include <react/renderer/core/ConcreteComponentDescriptor.h>
8
+
9
+ namespace facebook::react {
10
+ class KeyboardToolbarGroupViewComponentDescriptor final
11
+ : public ConcreteComponentDescriptor<KeyboardToolbarGroupViewShadowNode> {
12
+ public:
13
+ using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
14
+ void adopt(ShadowNode &shadowNode) const override {
15
+ react_native_assert(dynamic_cast<KeyboardToolbarGroupViewShadowNode *>(&shadowNode));
16
+ ConcreteComponentDescriptor::adopt(shadowNode);
17
+ }
18
+ };
19
+
20
+ } // namespace facebook::react
@@ -0,0 +1,7 @@
1
+ #include "RNKCKeyboardToolbarGroupViewShadowNode.h"
2
+
3
+ namespace facebook::react {
4
+
5
+ extern const char KeyboardToolbarGroupViewComponentName[] = "KeyboardToolbarGroupView";
6
+
7
+ } // namespace facebook::react
@@ -0,0 +1,23 @@
1
+ #pragma once
2
+
3
+ #include "RNKCKeyboardToolbarGroupViewState.h"
4
+
5
+ #include <react/renderer/components/RNKC/EventEmitters.h>
6
+ #include <react/renderer/components/RNKC/Props.h>
7
+ #include <react/renderer/components/view/ConcreteViewShadowNode.h>
8
+ #include <jsi/jsi.h>
9
+
10
+ namespace facebook::react {
11
+
12
+ JSI_EXPORT extern const char KeyboardToolbarGroupViewComponentName[];
13
+
14
+ /*
15
+ * `ShadowNode` for <KeyboardToolbarGroupView> component.
16
+ */
17
+ using KeyboardToolbarGroupViewShadowNode = ConcreteViewShadowNode<
18
+ KeyboardToolbarGroupViewComponentName,
19
+ KeyboardToolbarGroupViewProps,
20
+ KeyboardToolbarGroupViewEventEmitter,
21
+ KeyboardToolbarGroupViewState>;
22
+
23
+ } // namespace facebook::react
@@ -0,0 +1,21 @@
1
+ #pragma once
2
+
3
+ #ifdef ANDROID
4
+ #include <folly/dynamic.h>
5
+ #endif
6
+
7
+ namespace facebook::react {
8
+
9
+ class KeyboardToolbarGroupViewState {
10
+ public:
11
+ KeyboardToolbarGroupViewState() = default;
12
+
13
+ #ifdef ANDROID
14
+ KeyboardToolbarGroupViewState(KeyboardToolbarGroupViewState const &previousState, folly::dynamic data) {}
15
+ folly::dynamic getDynamic() const {
16
+ return {};
17
+ }
18
+ #endif
19
+ };
20
+
21
+ } // namespace facebook::react
@@ -23,6 +23,9 @@
23
23
  #endif
24
24
 
25
25
  #import <React/RCTEventDispatcherProtocol.h>
26
+ #ifndef RCT_NEW_ARCH_ENABLED
27
+ #import <React/RCTUIManager.h>
28
+ #endif
26
29
 
27
30
  #ifdef RCT_NEW_ARCH_ENABLED
28
31
  @interface KeyboardController () <NativeKeyboardControllerSpec>
@@ -95,6 +98,39 @@ RCT_EXPORT_METHOD(setFocusTo : (nonnull NSString *)direction)
95
98
  [ViewHierarchyNavigator setFocusToDirection:direction];
96
99
  }
97
100
 
101
+ #ifdef RCT_NEW_ARCH_ENABLED
102
+ - (void)viewPositionInWindow:(double)viewTag
103
+ resolve:(RCTPromiseResolveBlock)resolve
104
+ reject:(RCTPromiseRejectBlock)reject
105
+ #else
106
+ RCT_EXPORT_METHOD(viewPositionInWindow
107
+ : (nonnull NSNumber *)viewTag resolve
108
+ : (RCTPromiseResolveBlock)resolve reject
109
+ : (RCTPromiseRejectBlock)reject)
110
+ #endif
111
+ {
112
+ dispatch_async(dispatch_get_main_queue(), ^{
113
+ UIView *view = nil;
114
+ #ifdef RCT_NEW_ARCH_ENABLED
115
+ NSInteger tag = (NSInteger)viewTag;
116
+ view = [UIApplication.sharedApplication.activeWindow viewWithTag:tag];
117
+ #else
118
+ view = [self.bridge.uiManager viewForReactTag:viewTag];
119
+ #endif
120
+ if (!view || !view.superview) {
121
+ reject(@"E_VIEW_NOT_FOUND", @"Could not find view for tag", nil);
122
+ return;
123
+ }
124
+ CGRect frame = [view.superview convertRect:view.frame toView:nil];
125
+ resolve(@{
126
+ @"x" : @(frame.origin.x),
127
+ @"y" : @(frame.origin.y),
128
+ @"width" : @(frame.size.width),
129
+ @"height" : @(frame.size.height),
130
+ });
131
+ });
132
+ }
133
+
98
134
  + (KeyboardController *)shared
99
135
  {
100
136
  return shared;
@@ -9,7 +9,7 @@ import Foundation
9
9
  import UIKit
10
10
 
11
11
  public extension UIApplication {
12
- var activeWindow: UIWindow? {
12
+ @objc var activeWindow: UIWindow? {
13
13
  if #available(iOS 13.0, *) {
14
14
  for scene in connectedScenes {
15
15
  if scene.activationState == .foregroundActive,
@@ -151,7 +151,8 @@ public class FocusedInputObserver: NSObject {
151
151
 
152
152
  FocusedInputHolder.shared.set(currentResponder as? TextInput)
153
153
 
154
- let allInputFields = ViewHierarchyNavigator.getAllInputFields()
154
+ let groupAncestor = ViewHierarchyNavigator.findGroupAncestor(currentResponder as? UIView)
155
+ let allInputFields = ViewHierarchyNavigator.getAllInputFields(root: groupAncestor)
155
156
  let currentIndex = allInputFields.firstIndex(where: { $0 == currentResponder }) ?? -1
156
157
 
157
158
  onFocusDidSet([