react-native-keyboard-controller 1.21.0-beta.2 → 1.21.0-beta.3

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 (126) hide show
  1. package/android/src/fabric/java/com/reactnativekeyboardcontroller/ClippingScrollViewDecoratorViewManager.kt +7 -0
  2. package/android/src/main/java/com/reactnativekeyboardcontroller/views/ClippingScrollViewDecoratorView.kt +28 -1
  3. package/android/src/paper/java/com/reactnativekeyboardcontroller/ClippingScrollViewDecoratorViewManager.kt +8 -0
  4. package/ios/animations/KeyboardAnimation.swift +2 -2
  5. package/ios/animations/SpringAnimation.swift +1 -1
  6. package/ios/animations/TimingAnimation.swift +1 -3
  7. package/ios/delegates/KCTextInputCompositeDelegate.swift +2 -2
  8. package/ios/extensions/CGFloat.swift +1 -3
  9. package/ios/interactive/KeyboardAreaExtender.swift +2 -1
  10. package/ios/observers/FocusedInputObserver.swift +4 -2
  11. package/ios/observers/movement/KeyboardTrackingView.swift +4 -1
  12. package/ios/observers/movement/observer/KeyboardMovementObserver+Interactive.swift +2 -0
  13. package/ios/observers/movement/observer/KeyboardMovementObserver+Listeners.swift +30 -7
  14. package/ios/observers/movement/observer/KeyboardMovementObserver.swift +2 -48
  15. package/ios/protocols/TextInput.swift +1 -1
  16. package/ios/swizzling/UIResponderSwizzle.swift +2 -4
  17. package/ios/traversal/FocusedInputHolder.swift +3 -3
  18. package/ios/traversal/KeyboardView.swift +1 -1
  19. package/ios/traversal/ViewHierarchyNavigator.swift +2 -2
  20. package/ios/views/KeyboardControllerViewManager.swift +5 -5
  21. package/jest/index.js +1 -3
  22. package/lib/commonjs/components/KeyboardAwareScrollView/index.js +43 -11
  23. package/lib/commonjs/components/KeyboardAwareScrollView/index.js.map +1 -1
  24. package/lib/commonjs/components/{ChatKit → KeyboardChatScrollView}/hooks.js.map +1 -1
  25. package/lib/commonjs/components/{ChatKit → KeyboardChatScrollView}/index.js +37 -12
  26. package/lib/commonjs/components/KeyboardChatScrollView/index.js.map +1 -0
  27. package/lib/commonjs/components/KeyboardChatScrollView/types.js.map +1 -0
  28. package/lib/commonjs/components/{ChatKit → KeyboardChatScrollView}/useChatKeyboard/helpers.js +35 -1
  29. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/helpers.js.map +1 -0
  30. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js +121 -0
  31. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js.map +1 -0
  32. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.js +205 -0
  33. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.js.map +1 -0
  34. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/types.js +6 -0
  35. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/types.js.map +1 -0
  36. package/lib/commonjs/components/KeyboardStickyView/index.js +3 -1
  37. package/lib/commonjs/components/KeyboardStickyView/index.js.map +1 -1
  38. package/lib/commonjs/components/ScrollViewWithBottomPadding/index.js +18 -9
  39. package/lib/commonjs/components/ScrollViewWithBottomPadding/index.js.map +1 -1
  40. package/lib/commonjs/components/hooks/useScrollState.js +20 -1
  41. package/lib/commonjs/components/hooks/useScrollState.js.map +1 -1
  42. package/lib/commonjs/components/index.js +7 -7
  43. package/lib/commonjs/components/index.js.map +1 -1
  44. package/lib/commonjs/index.js +7 -7
  45. package/lib/commonjs/index.js.map +1 -1
  46. package/lib/commonjs/specs/ClippingScrollViewDecoratorViewNativeComponent.js.map +1 -1
  47. package/lib/commonjs/types/views.js.map +1 -1
  48. package/lib/module/components/KeyboardAwareScrollView/index.js +43 -11
  49. package/lib/module/components/KeyboardAwareScrollView/index.js.map +1 -1
  50. package/lib/module/components/{ChatKit → KeyboardChatScrollView}/hooks.js.map +1 -1
  51. package/lib/module/components/KeyboardChatScrollView/index.js +73 -0
  52. package/lib/module/components/KeyboardChatScrollView/index.js.map +1 -0
  53. package/lib/module/components/KeyboardChatScrollView/types.js.map +1 -0
  54. package/lib/module/components/{ChatKit → KeyboardChatScrollView}/useChatKeyboard/helpers.js +34 -1
  55. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/helpers.js.map +1 -0
  56. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js +115 -0
  57. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js.map +1 -0
  58. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.js +199 -0
  59. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.js.map +1 -0
  60. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/types.js +2 -0
  61. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/types.js.map +1 -0
  62. package/lib/module/components/KeyboardStickyView/index.js +3 -1
  63. package/lib/module/components/KeyboardStickyView/index.js.map +1 -1
  64. package/lib/module/components/ScrollViewWithBottomPadding/index.js +19 -10
  65. package/lib/module/components/ScrollViewWithBottomPadding/index.js.map +1 -1
  66. package/lib/module/components/hooks/useScrollState.js +21 -2
  67. package/lib/module/components/hooks/useScrollState.js.map +1 -1
  68. package/lib/module/components/index.js +1 -1
  69. package/lib/module/components/index.js.map +1 -1
  70. package/lib/module/index.js +1 -1
  71. package/lib/module/index.js.map +1 -1
  72. package/lib/module/specs/ClippingScrollViewDecoratorViewNativeComponent.js.map +1 -1
  73. package/lib/module/types/views.js.map +1 -1
  74. package/lib/typescript/components/KeyboardChatScrollView/hooks.d.ts +2 -0
  75. package/lib/typescript/components/KeyboardChatScrollView/index.d.ts +12 -0
  76. package/lib/typescript/components/{ChatKit → KeyboardChatScrollView}/types.d.ts +5 -6
  77. package/lib/typescript/components/{ChatKit → KeyboardChatScrollView}/useChatKeyboard/helpers.d.ts +23 -3
  78. package/lib/typescript/components/KeyboardChatScrollView/useChatKeyboard/index.d.ts +21 -0
  79. package/lib/typescript/components/KeyboardChatScrollView/useChatKeyboard/index.ios.d.ts +21 -0
  80. package/lib/typescript/components/KeyboardChatScrollView/useChatKeyboard/types.d.ts +22 -0
  81. package/lib/typescript/components/ScrollViewWithBottomPadding/index.d.ts +2 -4
  82. package/lib/typescript/components/hooks/useScrollState.d.ts +3 -0
  83. package/lib/typescript/components/index.d.ts +2 -2
  84. package/lib/typescript/index.d.ts +2 -2
  85. package/lib/typescript/specs/ClippingScrollViewDecoratorViewNativeComponent.d.ts +1 -0
  86. package/lib/typescript/types/views.d.ts +2 -0
  87. package/package.json +1 -1
  88. package/src/components/KeyboardAwareScrollView/index.tsx +53 -10
  89. package/src/components/KeyboardChatScrollView/hooks.ts +2 -0
  90. package/src/components/KeyboardChatScrollView/index.tsx +106 -0
  91. package/src/components/{ChatKit → KeyboardChatScrollView}/types.ts +5 -6
  92. package/src/components/{ChatKit → KeyboardChatScrollView}/useChatKeyboard/helpers.ts +46 -1
  93. package/src/components/KeyboardChatScrollView/useChatKeyboard/index.ios.ts +180 -0
  94. package/src/components/KeyboardChatScrollView/useChatKeyboard/index.ts +300 -0
  95. package/src/components/KeyboardChatScrollView/useChatKeyboard/types.ts +30 -0
  96. package/src/components/KeyboardStickyView/index.tsx +4 -3
  97. package/src/components/ScrollViewWithBottomPadding/index.tsx +22 -25
  98. package/src/components/hooks/useScrollState.ts +24 -2
  99. package/src/components/index.ts +2 -2
  100. package/src/index.ts +2 -2
  101. package/src/specs/ClippingScrollViewDecoratorViewNativeComponent.ts +1 -0
  102. package/src/types/views.ts +2 -0
  103. package/lib/commonjs/components/ChatKit/TODO.md +0 -20
  104. package/lib/commonjs/components/ChatKit/index.js.map +0 -1
  105. package/lib/commonjs/components/ChatKit/types.js.map +0 -1
  106. package/lib/commonjs/components/ChatKit/useChatKeyboard/helpers.js.map +0 -1
  107. package/lib/commonjs/components/ChatKit/useChatKeyboard/index.js +0 -147
  108. package/lib/commonjs/components/ChatKit/useChatKeyboard/index.js.map +0 -1
  109. package/lib/module/components/ChatKit/TODO.md +0 -20
  110. package/lib/module/components/ChatKit/index.js +0 -48
  111. package/lib/module/components/ChatKit/index.js.map +0 -1
  112. package/lib/module/components/ChatKit/types.js.map +0 -1
  113. package/lib/module/components/ChatKit/useChatKeyboard/helpers.js.map +0 -1
  114. package/lib/module/components/ChatKit/useChatKeyboard/index.js +0 -141
  115. package/lib/module/components/ChatKit/useChatKeyboard/index.js.map +0 -1
  116. package/lib/typescript/components/ChatKit/hooks.d.ts +0 -2
  117. package/lib/typescript/components/ChatKit/index.d.ts +0 -14
  118. package/lib/typescript/components/ChatKit/useChatKeyboard/index.d.ts +0 -36
  119. package/src/components/ChatKit/TODO.md +0 -20
  120. package/src/components/ChatKit/hooks.ts +0 -2
  121. package/src/components/ChatKit/index.tsx +0 -63
  122. package/src/components/ChatKit/useChatKeyboard/index.ts +0 -228
  123. /package/lib/commonjs/components/{ChatKit → KeyboardChatScrollView}/hooks.js +0 -0
  124. /package/lib/commonjs/components/{ChatKit → KeyboardChatScrollView}/types.js +0 -0
  125. /package/lib/module/components/{ChatKit → KeyboardChatScrollView}/hooks.js +0 -0
  126. /package/lib/module/components/{ChatKit → KeyboardChatScrollView}/types.js +0 -0
@@ -27,4 +27,11 @@ class ClippingScrollViewDecoratorViewManager :
27
27
  ) {
28
28
  view?.setContentInsetBottom(value)
29
29
  }
30
+
31
+ override fun setContentInsetTop(
32
+ view: ClippingScrollViewDecoratorView?,
33
+ value: Double,
34
+ ) {
35
+ view?.setContentInsetTop(value)
36
+ }
30
37
  }
@@ -1,6 +1,7 @@
1
1
  package com.reactnativekeyboardcontroller.views
2
2
 
3
3
  import android.annotation.SuppressLint
4
+ import android.view.ViewGroup
4
5
  import android.widget.ScrollView
5
6
  import com.facebook.react.uimanager.ThemedReactContext
6
7
  import com.facebook.react.views.view.ReactViewGroup
@@ -11,6 +12,8 @@ class ClippingScrollViewDecoratorView(
11
12
  val reactContext: ThemedReactContext,
12
13
  ) : ReactViewGroup(reactContext) {
13
14
  private var insetBottom = 0.0
15
+ private var insetTop = 0.0
16
+ private var appliedTopInsetPx = 0
14
17
 
15
18
  override fun onAttachedToWindow() {
16
19
  super.onAttachedToWindow()
@@ -23,15 +26,39 @@ class ClippingScrollViewDecoratorView(
23
26
  decorateScrollView()
24
27
  }
25
28
 
29
+ fun setContentInsetTop(value: Double) {
30
+ insetTop = value
31
+ decorateScrollView()
32
+ }
33
+
26
34
  private fun decorateScrollView() {
27
35
  val scrollView = getChildAt(0) as? ScrollView ?: return
28
36
 
29
37
  scrollView.clipToPadding = false
38
+
39
+ val newTopInsetPx = insetTop.toFloat().px.toInt()
40
+
41
+ // Translate the content view as a whole — this keeps FlatList's
42
+ // virtualizer calculations correct (it reads layout positions,
43
+ // not translationY).
44
+ val contentView = scrollView.getChildAt(0) as? ViewGroup ?: return
45
+ (contentView.getChildAt(0) as? ViewGroup)?.translationY = newTopInsetPx.toFloat()
46
+
30
47
  scrollView.setPadding(
31
48
  scrollView.paddingLeft,
32
49
  scrollView.paddingTop,
33
50
  scrollView.paddingRight,
34
- insetBottom.toFloat().px.toInt(),
51
+ // pass accumulated value — visually both top and bottom insets
52
+ // extend the scroll range via bottom padding
53
+ (insetBottom + insetTop).toFloat().px.toInt(),
35
54
  )
55
+
56
+ // scroll by the delta to keep content visually in place
57
+ val delta = newTopInsetPx - appliedTopInsetPx
58
+ if (delta != 0) {
59
+ scrollView.scrollBy(0, delta)
60
+ }
61
+
62
+ appliedTopInsetPx = newTopInsetPx
36
63
  }
37
64
  }
@@ -21,4 +21,12 @@ class ClippingScrollViewDecoratorViewManager : ViewGroupManager<ClippingScrollVi
21
21
  ) {
22
22
  view.setContentInsetBottom(value)
23
23
  }
24
+
25
+ @ReactProp(name = "contentInsetTop")
26
+ fun setContentInsetTop(
27
+ view: ClippingScrollViewDecoratorView,
28
+ value: Double,
29
+ ) {
30
+ view.setContentInsetTop(value)
31
+ }
24
32
  }
@@ -26,7 +26,7 @@ public class KeyboardAnimation: KeyboardAnimationProtocol {
26
26
  let duration: Double
27
27
  let speed: Double
28
28
  let timestamp: CFTimeInterval
29
- // internal state
29
+ /// internal state
30
30
  var lastValue: Double
31
31
 
32
32
  init(fromValue: Double, toValue: Double, animation: CAMediaTiming, duration: Double) {
@@ -39,7 +39,7 @@ public class KeyboardAnimation: KeyboardAnimationProtocol {
39
39
  timestamp = CACurrentMediaTime()
40
40
  }
41
41
 
42
- // public getters
42
+ /// public getters
43
43
  var startTime: CFTimeInterval {
44
44
  // when concurrent animation happens, then `.beginTime` remains the same
45
45
  return max(animation?.beginTime ?? timestamp, timestamp)
@@ -60,7 +60,7 @@ public final class SpringAnimation: KeyboardAnimation {
60
60
  super.init(fromValue: fromValue, toValue: toValue, animation: animation, duration: animation.settlingDuration)
61
61
  }
62
62
 
63
- // public functions
63
+ /// public functions
64
64
  override func valueAt(time: Double) -> Double {
65
65
  let t = time * speed
66
66
 
@@ -56,9 +56,7 @@ public final class TimingAnimation: KeyboardAnimation {
56
56
  let x = bezierX(t: t)
57
57
 
58
58
  let duration = animation?.duration ?? 0.0
59
- let time = x * duration / speed
60
-
61
- return time
59
+ return x * duration / speed
62
60
  }
63
61
 
64
62
  // private functions
@@ -51,7 +51,7 @@ class KCTextInputCompositeDelegate: NSObject, UITextViewDelegate, UITextFieldDel
51
51
  weak var textViewDelegate: UITextViewDelegate?
52
52
  weak var textFieldDelegate: UITextFieldDelegate?
53
53
 
54
- // Keep track of which textField we’re observing (iOS < 13 only)
54
+ /// Keep track of which textField we’re observing (iOS < 13 only)
55
55
  private weak var observedTextFieldForSelection: UITextField?
56
56
 
57
57
  init(
@@ -111,7 +111,7 @@ class KCTextInputCompositeDelegate: NSObject, UITextViewDelegate, UITextFieldDel
111
111
  return true
112
112
  }
113
113
 
114
- // Getter for the active delegate
114
+ /// Getter for the active delegate
115
115
  var activeDelegate: AnyObject? {
116
116
  return textViewDelegate ?? textFieldDelegate
117
117
  }
@@ -16,8 +16,6 @@ public extension CGFloat {
16
16
  let outputMax = outputRange.max() ?? 1
17
17
 
18
18
  let normalizedValue = (currentValue - inputMin) / (inputMax - inputMin)
19
- let interpolatedValue = outputMin + (outputMax - outputMin) * normalizedValue
20
-
21
- return interpolatedValue
19
+ return outputMin + (outputMax - outputMin) * normalizedValue
22
20
  }
23
21
  }
@@ -55,7 +55,8 @@ class KeyboardAreaExtender: NSObject {
55
55
  let responder = UIResponder.current
56
56
  if let activeTextInput = responder as? TextInput,
57
57
  let offset = KeyboardOffsetProvider.shared.getOffset(
58
- forTextInputNativeID: responder.nativeID),
58
+ forTextInputNativeID: responder.nativeID
59
+ ),
59
60
  responder?.inputAccessoryView == nil
60
61
  {
61
62
  currentInputAccessoryView = InvisibleInputAccessoryView(height: CGFloat(truncating: offset))
@@ -32,7 +32,7 @@ public class FocusedInputObserver: NSObject {
32
32
  var onFocusDidSet: (NSDictionary) -> Void
33
33
  /// delegates
34
34
  var delegate: KCTextInputCompositeDelegate
35
- // state variables
35
+ /// state variables
36
36
  private var isMounted = false
37
37
  // input tracking
38
38
  private var currentInput: UIView?
@@ -142,7 +142,9 @@ public class FocusedInputObserver: NSObject {
142
142
  setupObservers()
143
143
  // dispatch onSelectionChange on focus
144
144
  if let textInput = responder as? UITextInput {
145
- updateSelectionPosition(textInput: textInput, sendEvent: onSelectionChange)
145
+ DispatchQueue.main.async {
146
+ updateSelectionPosition(textInput: textInput, sendEvent: self.onSelectionChange)
147
+ }
146
148
  }
147
149
 
148
150
  syncUpLayout()
@@ -12,7 +12,10 @@ import UIKit
12
12
  * and uses `keyboardLayoutGuide` on iOS 26+.
13
13
  */
14
14
  public final class KeyboardTrackingView: UIView {
15
- private var keyboardView: UIView? { KeyboardViewLocator.shared.resolve() }
15
+ private var keyboardView: UIView? {
16
+ KeyboardViewLocator.shared.resolve()
17
+ }
18
+
16
19
  private var keyboardHeight = 0.0
17
20
  private weak var currentAttachedView: UIView?
18
21
  private var isAttaching = false
@@ -49,6 +49,8 @@ extension KeyboardMovementObserver {
49
49
 
50
50
  prevKeyboardPosition = position
51
51
 
52
+ keyboardDidTask?.cancel()
53
+
52
54
  onEvent(
53
55
  "onKeyboardMoveInteractive",
54
56
  position as NSNumber,
@@ -7,7 +7,7 @@
7
7
 
8
8
  extension KeyboardMovementObserver {
9
9
  @objc func keyboardWillAppear(_ notification: Notification) {
10
- guard !KeyboardEventsIgnorer.shared.shouldIgnore, !UIResponder.isKeyboardPreloading else { return }
10
+ guard !UIResponder.isKeyboardPreloading else { return }
11
11
 
12
12
  let (duration, frame) = notification.keyboardMetaData()
13
13
  if let keyboardFrame = frame {
@@ -15,6 +15,12 @@ extension KeyboardMovementObserver {
15
15
  let keyboardHeight = keyboardFrame.cgRectValue.size.height
16
16
  self.keyboardHeight = keyboardHeight
17
17
  self.notification = notification
18
+
19
+ guard !KeyboardEventsIgnorer.shared.shouldIgnore else {
20
+ KeyboardEventsIgnorer.shared.shouldIgnoreKeyboardEvents = false
21
+ return
22
+ }
23
+
18
24
  self.duration = duration
19
25
 
20
26
  onRequestAnimation()
@@ -23,6 +29,7 @@ extension KeyboardMovementObserver {
23
29
 
24
30
  setupKeyboardWatcher()
25
31
  initializeAnimation(fromValue: prevKeyboardPosition, toValue: self.keyboardHeight)
32
+ scheduleDidEvent(height: self.keyboardHeight, duration: animation?.duration ?? CGFloat(duration) / 1000)
26
33
  }
27
34
  }
28
35
 
@@ -44,6 +51,7 @@ extension KeyboardMovementObserver {
44
51
  setupKeyboardWatcher()
45
52
  removeKVObserver()
46
53
  initializeAnimation(fromValue: prevKeyboardPosition, toValue: 0)
54
+ scheduleDidEvent(height: 0, duration: animation?.duration ?? CGFloat(duration) / 1000)
47
55
  }
48
56
 
49
57
  @objc func keyboardDidAppear(_ notification: Notification) {
@@ -55,12 +63,7 @@ extension KeyboardMovementObserver {
55
63
  tag = UIResponder.current.reactViewTag
56
64
  self.keyboardHeight = keyboardHeight
57
65
 
58
- guard !KeyboardEventsIgnorer.shared.shouldIgnore else {
59
- KeyboardEventsIgnorer.shared.shouldIgnoreKeyboardEvents = false
60
- return
61
- }
62
-
63
- let height = self.keyboardHeight - KeyboardAreaExtender.shared.offset
66
+ let height = self.keyboardHeight
64
67
  // always limit progress to the maximum possible value
65
68
  let progress = min(height / self.keyboardHeight, 1.0)
66
69
 
@@ -90,4 +93,24 @@ extension KeyboardMovementObserver {
90
93
  removeKeyboardWatcher()
91
94
  animation = nil
92
95
  }
96
+
97
+ @objc func scheduleDidEvent(height: CGFloat, duration: CGFloat) {
98
+ keyboardDidTask?.cancel()
99
+
100
+ guard let notification = notification else {
101
+ return
102
+ }
103
+
104
+ let task = DispatchWorkItem { [weak self] in
105
+ guard let self = self else { return }
106
+ if height > 0 {
107
+ self.keyboardDidAppear(notification)
108
+ } else {
109
+ self.keyboardDidDisappear(notification)
110
+ }
111
+ }
112
+
113
+ keyboardDidTask = task
114
+ DispatchQueue.main.asyncAfter(deadline: .now() + duration, execute: task)
115
+ }
93
116
  }
@@ -19,30 +19,7 @@ public class KeyboardMovementObserver: NSObject {
19
19
  var onCancelAnimation: () -> Void
20
20
  // progress tracker
21
21
  @objc public var keyboardTrackingView = KeyboardTrackingView()
22
- var animation: KeyboardAnimation? {
23
- didSet {
24
- keyboardDidTask?.cancel()
25
-
26
- guard let animation = animation, let notification = notification else {
27
- return
28
- }
29
-
30
- let toValue = animation.toValue
31
- let duration = animation.duration
32
-
33
- let task = DispatchWorkItem { [weak self] in
34
- guard let self = self else { return }
35
- if toValue > 0 {
36
- self.keyboardDidAppear(notification)
37
- } else {
38
- self.keyboardDidDisappear(notification)
39
- }
40
- }
41
-
42
- keyboardDidTask = task
43
- DispatchQueue.main.asyncAfter(deadline: .now() + duration, execute: task)
44
- }
45
- }
22
+ var animation: KeyboardAnimation?
46
23
 
47
24
  var prevKeyboardPosition = 0.0
48
25
  var displayLink: CADisplayLink!
@@ -55,30 +32,7 @@ public class KeyboardMovementObserver: NSObject {
55
32
  set { _keyboardHeight = newValue }
56
33
  }
57
34
 
58
- var duration = 0 {
59
- didSet {
60
- keyboardDidTask?.cancel()
61
-
62
- guard let notification = notification,
63
- let height = notification.keyboardMetaData().1?.cgRectValue.size.height, duration == 0
64
- else {
65
- return
66
- }
67
-
68
- let task = DispatchWorkItem { [weak self] in
69
- guard let self = self else { return }
70
- if height > 0 {
71
- self.keyboardDidAppear(notification)
72
- } else {
73
- self.keyboardDidDisappear(notification)
74
- }
75
- }
76
-
77
- keyboardDidTask = task
78
- DispatchQueue.main.asyncAfter(deadline: .now() + UIUtils.nextFrame, execute: task)
79
- }
80
- }
81
-
35
+ var duration = 0
82
36
  var tag: NSNumber = -1
83
37
  // manual did events
84
38
  var notification: Notification?
@@ -15,7 +15,7 @@ public protocol TextInput: UIView {
15
15
  var inputView: UIView? { get set }
16
16
  var keyboardType: UIKeyboardType { get }
17
17
  var keyboardAppearance: UIKeyboardAppearance { get }
18
- // custom methods/properties
18
+ /// custom methods/properties
19
19
  func focus()
20
20
  }
21
21
 
@@ -94,8 +94,7 @@ extension UIResponder {
94
94
  let castOriginalResignFirstResponder = unsafeBitCast(
95
95
  originalResignFirstResponder, to: Function.self
96
96
  )
97
- let result = castOriginalResignFirstResponder(self, selector)
98
- return result
97
+ return castOriginalResignFirstResponder(self, selector)
99
98
  }
100
99
 
101
100
  private func callOriginalBecomeFirstResponder(_ selector: Selector) -> Bool {
@@ -104,7 +103,6 @@ extension UIResponder {
104
103
  let castOriginalBecomeFirstResponder = unsafeBitCast(
105
104
  originalBecomeFirstResponder, to: Function.self
106
105
  )
107
- let result = castOriginalBecomeFirstResponder(self, selector)
108
- return result
106
+ return castOriginalBecomeFirstResponder(self, selector)
109
107
  }
110
108
  }
@@ -11,10 +11,10 @@ import Foundation
11
11
  class FocusedInputHolder {
12
12
  static let shared = FocusedInputHolder()
13
13
 
14
- // Using TextInput protocol as the type for currentFocusedInput
14
+ /// Using TextInput protocol as the type for currentFocusedInput
15
15
  private weak var currentFocusedInput: TextInput?
16
16
 
17
- // Sets the currentFocusedInput to the passed instance conforming to TextInput or clears it if nil is passed
17
+ /// Sets the currentFocusedInput to the passed instance conforming to TextInput or clears it if nil is passed
18
18
  func set(_ input: TextInput?) {
19
19
  currentFocusedInput = input
20
20
  }
@@ -23,7 +23,7 @@ class FocusedInputHolder {
23
23
  return currentFocusedInput
24
24
  }
25
25
 
26
- // Requests focus for the currentFocusedInput if it's set
26
+ /// Requests focus for the currentFocusedInput if it's set
27
27
  func focus() {
28
28
  currentFocusedInput?.focus()
29
29
  }
@@ -13,7 +13,7 @@ enum KeyboardView {
13
13
  private static let windowPrefix = "<UITextEffectsWindow"
14
14
  private static let containerPrefixes = ["<UIInputSetContainerView", "<UITrackingWindowView"]
15
15
  private static let hostPrefixes = ["<UIInputSetHostView", "<UIKeyboardItemContainerView"]
16
- // inspired by https://stackoverflow.com/questions/32598490/show-uiview-with-buttons-over-keyboard-like-in-skype-viber-messengers-swift-i
16
+ /// inspired by https://stackoverflow.com/questions/32598490/show-uiview-with-buttons-over-keyboard-like-in-skype-viber-messengers-swift-i
17
17
  static func find() -> UIView? {
18
18
  let windows = UIApplication.shared.windows
19
19
  for window in windows {
@@ -36,7 +36,7 @@ public class ViewHierarchyNavigator: NSObject {
36
36
  return []
37
37
  }
38
38
 
39
- // Helper function to recursively search for TextInput views
39
+ /// Helper function to recursively search for TextInput views
40
40
  func findTextInputs(in view: UIView?) {
41
41
  guard let view = view else { return }
42
42
 
@@ -104,7 +104,7 @@ public class ViewHierarchyNavigator: NSObject {
104
104
  return nil // No valid UITextField or UITextView found
105
105
  }
106
106
 
107
- // Function to check if the view is a valid text field or text view
107
+ /// Function to check if the view is a valid text field or text view
108
108
  private static func isValidTextInput(_ view: UIView) -> TextInput? {
109
109
  if let textField = view as? UITextField, textField.isEnabled {
110
110
  return textField
@@ -27,7 +27,7 @@ class KeyboardControllerView: UIView {
27
27
  var inputObserver: FocusedInputObserver?
28
28
  private var eventDispatcher: RCTEventDispatcherProtocol
29
29
  private var bridge: RCTBridge
30
- // internal state
30
+ /// internal state
31
31
  private var lastScreenSize: CGSize = .zero
32
32
  // react callbacks
33
33
  /// keyboard
@@ -39,7 +39,7 @@ class KeyboardControllerView: UIView {
39
39
  @objc var onFocusedInputLayoutChanged: RCTDirectEventBlock?
40
40
  @objc var onFocusedInputTextChanged: RCTDirectEventBlock?
41
41
  @objc var onFocusedInputSelectionChanged: RCTDirectEventBlock?
42
- // react props
42
+ /// react props
43
43
  @objc var enabled: ObjCBool = true {
44
44
  didSet {
45
45
  if enabled.boolValue {
@@ -83,9 +83,9 @@ class KeyboardControllerView: UIView {
83
83
  fatalError("init(coder:) has not been implemented")
84
84
  }
85
85
 
86
- // for mounting/unmounting observers for lifecycle events we're using willMove(toSuperview) method
87
- // not willMove(toWindow)
88
- // see https://github.com/kirillzyusko/react-native-keyboard-controller/issues/271
86
+ /// for mounting/unmounting observers for lifecycle events we're using willMove(toSuperview) method
87
+ /// not willMove(toWindow)
88
+ /// see https://github.com/kirillzyusko/react-native-keyboard-controller/issues/271
89
89
  override func willMove(toSuperview newSuperview: UIView?) {
90
90
  super.willMove(toSuperview: newSuperview)
91
91
 
package/jest/index.js CHANGED
@@ -120,9 +120,7 @@ const mock = {
120
120
  KeyboardAvoidingView: View,
121
121
  KeyboardAwareScrollView: ScrollView,
122
122
  KeyboardToolbar: View,
123
- ChatKit: {
124
- ScrollView: ScrollView,
125
- },
123
+ KeyboardChatScrollView: ScrollView,
126
124
  // themes
127
125
  DefaultKeyboardToolbarTheme,
128
126
  };
@@ -103,6 +103,7 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
103
103
  const layout = (0, _reactNativeReanimated.useSharedValue)(null);
104
104
  const lastSelection = (0, _reactNativeReanimated.useSharedValue)(null);
105
105
  const ghostViewSpace = (0, _reactNativeReanimated.useSharedValue)(-1);
106
+ const pendingSelectionForFocus = (0, _reactNativeReanimated.useSharedValue)(false);
106
107
  const {
107
108
  height
108
109
  } = (0, _hooks.useWindowDimensions)();
@@ -215,7 +216,17 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
215
216
  const latestSelection = (_lastSelection$value3 = lastSelection.value) === null || _lastSelection$value3 === void 0 ? void 0 : _lastSelection$value3.selection;
216
217
  lastSelection.value = e;
217
218
  if (e.target !== lastTarget) {
218
- // ignore this event, because "focus changed" event handled in `useSmoothKeyboardHandler`
219
+ if (pendingSelectionForFocus.value) {
220
+ // selection arrived after onStart - complete the deferred setup
221
+ pendingSelectionForFocus.value = false;
222
+ updateLayoutFromSelection();
223
+
224
+ // if keyboard was already visible (focus change, no onMove expected),
225
+ // perform the deferred scroll now
226
+ if (!keyboardWillAppear.value && keyboardHeight.value > 0) {
227
+ position.value += maybeScroll(keyboardHeight.value, true);
228
+ }
229
+ }
219
230
  return;
220
231
  }
221
232
  // caret in the end + end coordinates has been changed -> we moved to a new line
@@ -228,7 +239,7 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
228
239
  return scrollFromCurrentPosition();
229
240
  }
230
241
  onChangeTextHandler();
231
- }, [scrollFromCurrentPosition, onChangeTextHandler]);
242
+ }, [scrollFromCurrentPosition, onChangeTextHandler, updateLayoutFromSelection, maybeScroll]);
232
243
  (0, _hooks.useFocusedInputHandler)({
233
244
  onSelectionChange: onSelectionChange
234
245
  }, [onSelectionChange]);
@@ -247,6 +258,7 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
247
258
  // on back transition need to interpolate as [0, keyboardHeight]
248
259
  initialKeyboardSize.value = 0;
249
260
  scrollPosition.value = scrollBeforeKeyboardMovement.value;
261
+ pendingSelectionForFocus.value = false;
250
262
  }
251
263
  if (keyboardWillAppear.value || keyboardWillChangeSize || focusWasChanged) {
252
264
  // persist scroll value
@@ -257,17 +269,31 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
257
269
 
258
270
  // focus was changed
259
271
  if (focusWasChanged) {
272
+ var _lastSelection$value4;
260
273
  tag.value = e.target;
261
- // save position of focused text input when keyboard starts to move
262
- updateLayoutFromSelection();
274
+ if (((_lastSelection$value4 = lastSelection.value) === null || _lastSelection$value4 === void 0 ? void 0 : _lastSelection$value4.target) === e.target) {
275
+ // selection arrived before onStart - use it to update layout
276
+ updateLayoutFromSelection();
277
+ pendingSelectionForFocus.value = false;
278
+ } else {
279
+ // selection hasn't arrived yet for the new target.
280
+ // use input layout as-is; will be refined when selection arrives.
281
+ if (input.value) {
282
+ layout.value = input.value;
283
+ }
284
+ pendingSelectionForFocus.value = true;
285
+ }
286
+
263
287
  // save current scroll position - when keyboard will hide we'll reuse
264
288
  // this value to achieve smooth hide effect
265
289
  scrollBeforeKeyboardMovement.value = position.value;
266
290
  }
267
291
  if (focusWasChanged && !keyboardWillAppear.value) {
268
- // update position on scroll value, so `onEnd` handler
269
- // will pick up correct values
270
- position.value += maybeScroll(e.height, true);
292
+ if (!pendingSelectionForFocus.value) {
293
+ // update position on scroll value, so `onEnd` handler
294
+ // will pick up correct values
295
+ position.value += maybeScroll(e.height, true);
296
+ }
271
297
  }
272
298
  ghostViewSpace.value = position.value + scrollViewLayout.value.height - scrollViewContentSize.value.height;
273
299
  if (ghostViewSpace.value > 0) {
@@ -293,6 +319,9 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
293
319
  removeGhostPadding(e.height);
294
320
  keyboardHeight.value = e.height;
295
321
  scrollPosition.value = position.value;
322
+ if (e.height === 0) {
323
+ lastSelection.value = null;
324
+ }
296
325
  syncKeyboardFrame(e);
297
326
  }
298
327
  }, [maybeScroll, removeGhostPadding, disableScrollOnKeyboardHide, syncKeyboardFrame]);
@@ -306,11 +335,14 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
306
335
  }, [update, scrollFromCurrentPosition]);
307
336
  (0, _react.useImperativeHandle)(ref, () => {
308
337
  const scrollView = scrollViewRef.current;
309
- const existingMethods = scrollView ? {
310
- ...scrollView
311
- } : {};
338
+ if (scrollView) {
339
+ const scrollViewWithMethods = scrollView;
340
+ scrollViewWithMethods.assureFocusedInputVisible = () => {
341
+ synchronize();
342
+ };
343
+ return scrollViewWithMethods;
344
+ }
312
345
  return {
313
- ...existingMethods,
314
346
  assureFocusedInputVisible: () => {
315
347
  synchronize();
316
348
  }