react-native-keyboard-controller 1.5.7 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerViewManager.kt +3 -1
  2. package/android/src/fabric/java/com/reactnativekeyboardcontroller/StatusBarManagerCompatModule.kt +2 -1
  3. package/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardAnimationCallback.kt +130 -18
  4. package/android/src/main/java/com/reactnativekeyboardcontroller/KeyboardAnimationController.kt +10 -2
  5. package/android/src/main/java/com/reactnativekeyboardcontroller/events/KeyboardTransitionEvent.kt +11 -2
  6. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ThemedReactContext.kt +7 -0
  7. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardControllerViewManagerImpl.kt +2 -1
  8. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardGestureAreaViewManagerImpl.kt +1 -0
  9. package/android/src/main/java/com/reactnativekeyboardcontroller/modules/KeyboardControllerModuleImpl.kt +6 -1
  10. package/android/src/main/java/com/reactnativekeyboardcontroller/modules/StatusBarManagerCompatModuleImpl.kt +9 -3
  11. package/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt +44 -27
  12. package/android/src/main/java/com/reactnativekeyboardcontroller/views/KeyboardGestureAreaReactViewGroup.kt +121 -85
  13. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +6 -4
  14. package/android/src/paper/java/com/reactnativekeyboardcontroller/StatusBarManagerCompatModule.kt +2 -1
  15. package/ios/Extensions.swift +31 -0
  16. package/ios/KeyboardController-Bridging-Header.h +1 -0
  17. package/ios/KeyboardControllerView.mm +25 -6
  18. package/ios/KeyboardControllerViewManager.swift +4 -2
  19. package/ios/KeyboardMoveEvent.h +3 -1
  20. package/ios/KeyboardMoveEvent.m +8 -0
  21. package/ios/KeyboardMovementObserver.swift +64 -12
  22. package/lib/commonjs/animated.js +4 -3
  23. package/lib/commonjs/animated.js.map +1 -1
  24. package/lib/commonjs/bindings.js +26 -0
  25. package/lib/commonjs/bindings.js.map +1 -0
  26. package/lib/commonjs/bindings.native.js +33 -0
  27. package/lib/commonjs/bindings.native.js.map +1 -0
  28. package/lib/commonjs/{native.js → constants.js} +2 -29
  29. package/lib/commonjs/constants.js.map +1 -0
  30. package/lib/commonjs/context.js +9 -1
  31. package/lib/commonjs/context.js.map +1 -1
  32. package/lib/commonjs/hooks.js +7 -6
  33. package/lib/commonjs/hooks.js.map +1 -1
  34. package/lib/commonjs/index.js +15 -4
  35. package/lib/commonjs/index.js.map +1 -1
  36. package/lib/commonjs/internal.js +0 -30
  37. package/lib/commonjs/internal.js.map +1 -1
  38. package/lib/commonjs/monkey-patch.android.js +34 -0
  39. package/lib/commonjs/monkey-patch.android.js.map +1 -0
  40. package/lib/commonjs/monkey-patch.js +1 -35
  41. package/lib/commonjs/monkey-patch.js.map +1 -1
  42. package/lib/commonjs/reanimated.js +9 -0
  43. package/lib/commonjs/reanimated.js.map +1 -0
  44. package/lib/commonjs/reanimated.native.js +37 -0
  45. package/lib/commonjs/reanimated.native.js.map +1 -0
  46. package/lib/commonjs/replicas.js +4 -3
  47. package/lib/commonjs/replicas.js.map +1 -1
  48. package/lib/commonjs/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
  49. package/lib/commonjs/types.js.map +1 -1
  50. package/lib/module/animated.js +3 -2
  51. package/lib/module/animated.js.map +1 -1
  52. package/lib/module/bindings.js +16 -0
  53. package/lib/module/bindings.js.map +1 -0
  54. package/lib/module/bindings.native.js +23 -0
  55. package/lib/module/bindings.native.js.map +1 -0
  56. package/lib/module/{native.js → constants.js} +1 -24
  57. package/lib/module/constants.js.map +1 -0
  58. package/lib/module/context.js +8 -1
  59. package/lib/module/context.js.map +1 -1
  60. package/lib/module/hooks.js +7 -6
  61. package/lib/module/hooks.js.map +1 -1
  62. package/lib/module/index.js +2 -1
  63. package/lib/module/index.js.map +1 -1
  64. package/lib/module/internal.js +1 -30
  65. package/lib/module/internal.js.map +1 -1
  66. package/lib/module/monkey-patch.android.js +29 -0
  67. package/lib/module/monkey-patch.android.js.map +1 -0
  68. package/lib/module/monkey-patch.js +1 -31
  69. package/lib/module/monkey-patch.js.map +1 -1
  70. package/lib/module/reanimated.js +2 -0
  71. package/lib/module/reanimated.js.map +1 -0
  72. package/lib/module/reanimated.native.js +30 -0
  73. package/lib/module/reanimated.native.js.map +1 -0
  74. package/lib/module/replicas.js +2 -1
  75. package/lib/module/replicas.js.map +1 -1
  76. package/lib/module/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
  77. package/lib/module/types.js.map +1 -1
  78. package/lib/typescript/bindings.d.ts +6 -0
  79. package/lib/typescript/bindings.native.d.ts +6 -0
  80. package/lib/typescript/constants.d.ts +16 -0
  81. package/lib/typescript/context.d.ts +1 -0
  82. package/lib/typescript/index.d.ts +2 -1
  83. package/lib/typescript/internal.d.ts +1 -7
  84. package/lib/typescript/monkey-patch.android.d.ts +1 -0
  85. package/lib/typescript/monkey-patch.d.ts +0 -1
  86. package/lib/typescript/reanimated.d.ts +2 -0
  87. package/lib/typescript/reanimated.native.d.ts +2 -0
  88. package/lib/typescript/specs/KeyboardControllerViewNativeComponent.d.ts +3 -1
  89. package/lib/typescript/types.d.ts +15 -1
  90. package/package.json +1 -1
  91. package/react-native-keyboard-controller.podspec +9 -1
  92. package/src/animated.tsx +3 -6
  93. package/src/{native.ts → bindings.native.ts} +4 -25
  94. package/src/bindings.ts +22 -0
  95. package/src/constants.ts +17 -0
  96. package/src/context.ts +12 -1
  97. package/src/hooks.ts +12 -7
  98. package/src/index.ts +2 -1
  99. package/src/internal.ts +2 -57
  100. package/src/monkey-patch.android.ts +33 -0
  101. package/src/monkey-patch.ts +1 -37
  102. package/src/reanimated.native.ts +51 -0
  103. package/src/reanimated.ts +6 -0
  104. package/src/replicas.ts +2 -1
  105. package/src/specs/KeyboardControllerViewNativeComponent.ts +3 -0
  106. package/src/types.ts +27 -5
  107. package/lib/commonjs/native.js.map +0 -1
  108. package/lib/module/native.js.map +0 -1
  109. package/lib/typescript/native.d.ts +0 -24
@@ -24,6 +24,7 @@ val interpolators = mapOf(
24
24
  "ios" to IosInterpolator(),
25
25
  )
26
26
 
27
+ @Suppress("detekt:TooManyFunctions")
27
28
  @SuppressLint("ViewConstructor")
28
29
  class KeyboardGestureAreaReactViewGroup(private val reactContext: ThemedReactContext) : ReactViewGroup(reactContext) {
29
30
  // internal state management
@@ -31,6 +32,7 @@ class KeyboardGestureAreaReactViewGroup(private val reactContext: ThemedReactCon
31
32
  private var lastTouchX = 0f
32
33
  private var lastTouchY = 0f
33
34
  private var lastWindowY = 0
35
+ private var keyboardHeight = 0
34
36
 
35
37
  // react props
36
38
  private var interpolator: Interpolator = LinearInterpolator()
@@ -51,96 +53,16 @@ class KeyboardGestureAreaReactViewGroup(private val reactContext: ThemedReactCon
51
53
  }
52
54
 
53
55
  when (event?.action) {
54
- MotionEvent.ACTION_DOWN -> {
55
- velocityTracker?.addMovement(event)
56
-
57
- lastTouchX = event.x
58
- lastTouchY = event.y
59
-
60
- this.copyBoundsInWindow(bounds)
61
- lastWindowY = bounds.top
62
- }
63
- MotionEvent.ACTION_MOVE -> {
64
- // Since the view is likely to be translated/moved as the WindowInsetsAnimation
65
- // progresses, we need to make sure we account for that change in our touch
66
- // handling. We do that by keeping track of the view's Y position in the window,
67
- // and detecting the difference between the current bounds.
68
- this.copyBoundsInWindow(bounds)
69
- val windowOffsetY = bounds.top - lastWindowY
70
-
71
- // We then make a copy of the MotionEvent, and offset it with the calculated
72
- // windowOffsetY. We can then pass it to the VelocityTracker.
73
- val velocityTrackerEvent = MotionEvent.obtain(event)
74
- velocityTrackerEvent.offsetLocation(0f, windowOffsetY.toFloat())
75
- velocityTracker?.addMovement(velocityTrackerEvent)
76
-
77
- val dx = velocityTrackerEvent.x - lastTouchX
78
- val dy = velocityTrackerEvent.y - lastTouchY
79
-
80
- if (!isHandling) {
81
- // If we're not currently handling the touch gesture, lets check if we should
82
- // start handling, by seeing if the gesture is majorly vertical, and
83
- // larger than the touch slop
84
- isHandling = dy.absoluteValue > dx.absoluteValue &&
85
- dy.absoluteValue >= ViewConfiguration.get(this.context).scaledTouchSlop
86
- }
87
-
88
- if (isHandling) {
89
- if (controller.isInsetAnimationInProgress()) {
90
- // If we currently have control, we can update the IME insets to 'scroll'
91
- // the IME in
92
- val moveBy = this.interpolator.interpolate(dy.roundToInt(), this.getWindowHeight() - event.rawY.toInt(), controller.getCurrentKeyboardHeight())
93
-
94
- if (moveBy != 0) {
95
- controller.insetBy(moveBy)
96
- }
97
- } else if (
98
- !controller.isInsetAnimationRequestPending() &&
99
- shouldStartRequest(
100
- dy = dy,
101
- imeVisible = ViewCompat.getRootWindowInsets(this)
102
- ?.isVisible(WindowInsetsCompat.Type.ime()) == true,
103
- )
104
- ) {
105
- // If we don't currently have control (and a request isn't pending),
106
- // the IME is not shown, the user is scrolling up, and the view can't
107
- // scroll up any more (i.e. over-scrolling), we can start to control
108
- // the IME insets
109
- controller.startControlRequest(this)
110
- }
111
-
112
- // Lastly we record the event X, Y, and view's Y window position, for the
113
- // next touch event
114
- lastTouchY = event.y
115
- lastTouchX = event.x
116
- lastWindowY = bounds.top
117
- }
118
- }
119
- MotionEvent.ACTION_UP -> {
120
- velocityTracker?.addMovement(event)
121
-
122
- // Calculate the current velocityY, over 500 milliseconds
123
- velocityTracker?.computeCurrentVelocity(500)
124
- val velocityY = velocityTracker?.yVelocity
125
-
126
- // If we received a ACTION_UP event, end any current WindowInsetsAnimation passing
127
- // in the calculated Y velocity
128
- controller.animateToFinish(velocityY)
129
-
130
- // Reset our touch handling state
131
- reset()
132
- }
133
- MotionEvent.ACTION_CANCEL -> {
134
- // If we received a ACTION_CANCEL event, cancel any current WindowInsetsAnimation
135
- controller.cancel()
136
- // Reset our touch handling state
137
- reset()
138
- }
56
+ MotionEvent.ACTION_DOWN -> this.onActionDown(event)
57
+ MotionEvent.ACTION_MOVE -> this.onActionMove(event)
58
+ MotionEvent.ACTION_UP -> this.onActionUp(event)
59
+ MotionEvent.ACTION_CANCEL -> this.onActionCancel()
139
60
  }
140
61
 
141
62
  return super.dispatchTouchEvent(event)
142
63
  }
143
64
 
65
+ // region Props setters
144
66
  fun setInterpolator(interpolator: String) {
145
67
  this.interpolator = interpolators[interpolator] ?: LinearInterpolator()
146
68
  }
@@ -152,6 +74,114 @@ class KeyboardGestureAreaReactViewGroup(private val reactContext: ThemedReactCon
152
74
  fun setScrollKeyboardOffScreenWhenVisible(scrollImeOffScreenWhenVisible: Boolean) {
153
75
  this.scrollKeyboardOffScreenWhenVisible = scrollImeOffScreenWhenVisible
154
76
  }
77
+ // endregion
78
+
79
+ // region Handlers
80
+ @RequiresApi(Build.VERSION_CODES.KITKAT)
81
+ private fun onActionDown(event: MotionEvent) {
82
+ velocityTracker?.addMovement(event)
83
+
84
+ lastTouchX = event.x
85
+ lastTouchY = event.y
86
+
87
+ this.copyBoundsInWindow(bounds)
88
+ lastWindowY = bounds.top
89
+ }
90
+
91
+ @RequiresApi(Build.VERSION_CODES.R)
92
+ private fun onActionMove(event: MotionEvent) {
93
+ // Since the view is likely to be translated/moved as the WindowInsetsAnimation
94
+ // progresses, we need to make sure we account for that change in our touch
95
+ // handling. We do that by keeping track of the view's Y position in the window,
96
+ // and detecting the difference between the current bounds.
97
+ this.copyBoundsInWindow(bounds)
98
+ val windowOffsetY = bounds.top - lastWindowY
99
+
100
+ // We then make a copy of the MotionEvent, and offset it with the calculated
101
+ // windowOffsetY. We can then pass it to the VelocityTracker.
102
+ val velocityTrackerEvent = MotionEvent.obtain(event)
103
+ velocityTrackerEvent.offsetLocation(0f, windowOffsetY.toFloat())
104
+ velocityTracker?.addMovement(velocityTrackerEvent)
105
+
106
+ val dx = velocityTrackerEvent.x - lastTouchX
107
+ val dy = velocityTrackerEvent.y - lastTouchY
108
+
109
+ if (!isHandling) {
110
+ // If we're not currently handling the touch gesture, lets check if we should
111
+ // start handling, by seeing if the gesture is majorly vertical, and
112
+ // larger than the touch slop
113
+ isHandling = dy.absoluteValue > dx.absoluteValue &&
114
+ dy.absoluteValue >= ViewConfiguration.get(this.context).scaledTouchSlop
115
+ }
116
+
117
+ if (isHandling) {
118
+ if (controller.isInsetAnimationInProgress()) {
119
+ if (keyboardHeight == 0) {
120
+ this.keyboardHeight = controller.getCurrentKeyboardHeight()
121
+ }
122
+ // If we currently have control, we can update the IME insets to 'scroll'
123
+ // the IME in
124
+ val moveBy = this.interpolator.interpolate(
125
+ dy.roundToInt(),
126
+ this.getWindowHeight() - event.rawY.toInt(),
127
+ controller.getCurrentKeyboardHeight(),
128
+ )
129
+
130
+ if (moveBy != 0) {
131
+ controller.insetBy(moveBy)
132
+ }
133
+ } else if (
134
+ !controller.isInsetAnimationRequestPending() &&
135
+ shouldStartRequest(
136
+ dy = dy,
137
+ imeVisible = ViewCompat.getRootWindowInsets(this)
138
+ ?.isVisible(WindowInsetsCompat.Type.ime()) == true,
139
+ )
140
+ ) {
141
+ // If we don't currently have control (and a request isn't pending),
142
+ // the IME is not shown, the user is scrolling up, and the view can't
143
+ // scroll up any more (i.e. over-scrolling), we can start to control
144
+ // the IME insets
145
+ controller.startControlRequest(this)
146
+ }
147
+
148
+ // Lastly we record the event X, Y, and view's Y window position, for the
149
+ // next touch event
150
+ lastTouchY = event.y
151
+ lastTouchX = event.x
152
+ lastWindowY = bounds.top
153
+ }
154
+ }
155
+
156
+ private fun onActionUp(event: MotionEvent) {
157
+ velocityTracker?.addMovement(event)
158
+ velocityTracker?.computeCurrentVelocity(VELOCITY_UNITS)
159
+
160
+ val velocityY = velocityTracker?.yVelocity
161
+ val isKeyboardPositionChanged =
162
+ // check `isInsetAnimationInProgress()` before, since direct usage of `getCurrentKeyboardHeight()`
163
+ // may throw exception
164
+ !controller.isInsetAnimationInProgress() ||
165
+ this.keyboardHeight != controller.getCurrentKeyboardHeight()
166
+ // if keyboard height was changed after finger movement -> we need to calculate final position
167
+ // and make an animated transition
168
+ val passedVelocityY = if (isKeyboardPositionChanged) velocityY else null
169
+
170
+ // If we received a ACTION_UP event, end any current WindowInsetsAnimation passing
171
+ // in the calculated Y velocity
172
+ controller.animateToFinish(passedVelocityY)
173
+
174
+ // Reset our touch handling state
175
+ reset()
176
+ }
177
+
178
+ private fun onActionCancel() {
179
+ // If we received a ACTION_CANCEL event, cancel any current WindowInsetsAnimation
180
+ controller.cancel()
181
+ // Reset our touch handling state
182
+ reset()
183
+ }
184
+ // endregion
155
185
 
156
186
  /**
157
187
  * Resets all of our internal state.
@@ -162,6 +192,7 @@ class KeyboardGestureAreaReactViewGroup(private val reactContext: ThemedReactCon
162
192
  lastTouchX = 0f
163
193
  lastTouchY = 0f
164
194
  lastWindowY = 0
195
+ keyboardHeight = 0
165
196
  bounds.setEmpty()
166
197
 
167
198
  velocityTracker?.recycle()
@@ -189,4 +220,9 @@ class KeyboardGestureAreaReactViewGroup(private val reactContext: ThemedReactCon
189
220
 
190
221
  return metrics?.bounds?.height() ?: 0
191
222
  }
223
+
224
+ companion object {
225
+ // Calculate the current velocity over 500 milliseconds
226
+ private const val VELOCITY_UNITS = 500
227
+ }
192
228
  }
@@ -10,23 +10,25 @@ class KeyboardControllerModule(mReactContext: ReactApplicationContext) : ReactCo
10
10
 
11
11
  override fun getName(): String = KeyboardControllerModuleImpl.NAME
12
12
 
13
- @ReactMethod(isBlockingSynchronousMethod = true)
13
+ @ReactMethod
14
14
  fun setInputMode(mode: Int) {
15
15
  module.setInputMode(mode)
16
16
  }
17
17
 
18
- @ReactMethod(isBlockingSynchronousMethod = true)
18
+ @ReactMethod
19
19
  fun setDefaultMode() {
20
20
  module.setDefaultMode()
21
21
  }
22
22
 
23
+ @Suppress("detekt:UnusedParameter")
23
24
  @ReactMethod
24
25
  fun addListener(eventName: String?) {
25
- /* Required for RN built-in Event Emitter Calls. */
26
+ // Required for RN built-in Event Emitter Calls
26
27
  }
27
28
 
29
+ @Suppress("detekt:UnusedParameter")
28
30
  @ReactMethod
29
31
  fun removeListeners(count: Int?) {
30
- /* Required for RN built-in Event Emitter Calls. */
32
+ // Required for RN built-in Event Emitter Calls
31
33
  }
32
34
  }
@@ -7,7 +7,8 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule
7
7
  import com.facebook.react.bridge.ReactMethod
8
8
  import com.reactnativekeyboardcontroller.modules.StatusBarManagerCompatModuleImpl
9
9
 
10
- class StatusBarManagerCompatModule(private val mReactContext: ReactApplicationContext) : ReactContextBaseJavaModule(mReactContext) {
10
+ class StatusBarManagerCompatModule(mReactContext: ReactApplicationContext) :
11
+ ReactContextBaseJavaModule(mReactContext) {
11
12
  private val module = StatusBarManagerCompatModuleImpl(mReactContext)
12
13
 
13
14
  override fun getName(): String = StatusBarManagerCompatModuleImpl.NAME
@@ -7,6 +7,7 @@
7
7
  //
8
8
 
9
9
  import Foundation
10
+ import UIKit
10
11
 
11
12
  public extension CGFloat {
12
13
  static func interpolate(inputRange: [CGFloat], outputRange: [CGFloat], currentValue: CGFloat) -> CGFloat {
@@ -21,3 +22,33 @@ public extension CGFloat {
21
22
  return interpolatedValue
22
23
  }
23
24
  }
25
+
26
+ public extension Date {
27
+ static var currentTimeStamp: Int64 {
28
+ return Int64(Date().timeIntervalSince1970 * 1000)
29
+ }
30
+ }
31
+
32
+ public extension UIResponder {
33
+ private weak static var _currentFirstResponder: UIResponder?
34
+
35
+ static var current: UIResponder? {
36
+ UIResponder._currentFirstResponder = nil
37
+ UIApplication.shared.sendAction(#selector(findFirstResponder(sender:)), to: nil, from: nil, for: nil)
38
+ return UIResponder._currentFirstResponder
39
+ }
40
+
41
+ @objc internal func findFirstResponder(sender _: AnyObject) {
42
+ UIResponder._currentFirstResponder = self
43
+ }
44
+ }
45
+
46
+ public extension Optional where Wrapped == UIResponder {
47
+ var reactViewTag: NSNumber {
48
+ #if KEYBOARD_CONTROLLER_NEW_ARCH_ENABLED
49
+ return ((self as? RCTUITextField)?.superview?.tag ?? -1) as NSNumber
50
+ #else
51
+ return (self as? RCTUITextField)?.superview?.reactTag ?? -1
52
+ #endif
53
+ }
54
+ }
@@ -1 +1,2 @@
1
+ #import <React/RCTUITextField.h>
1
2
  #import <React/RCTViewManager.h>
@@ -44,7 +44,12 @@ using namespace facebook::react;
44
44
  _props = defaultProps;
45
45
 
46
46
  observer = [[KeyboardMovementObserver alloc]
47
- initWithHandler:^(NSString *event, NSNumber *height, NSNumber *progress) {
47
+ initWithHandler:^(
48
+ NSString *event,
49
+ NSNumber *height,
50
+ NSNumber *progress,
51
+ NSNumber *duration,
52
+ NSNumber *target) {
48
53
  if (self->_eventEmitter) {
49
54
  // TODO: use reflection to reduce code duplication?
50
55
  if ([event isEqualToString:@"onKeyboardMoveStart"]) {
@@ -52,21 +57,30 @@ using namespace facebook::react;
52
57
  self->_eventEmitter)
53
58
  ->onKeyboardMoveStart(
54
59
  facebook::react::KeyboardControllerViewEventEmitter::OnKeyboardMoveStart{
55
- .height = [height doubleValue], .progress = [progress doubleValue]});
60
+ .height = [height doubleValue],
61
+ .progress = [progress doubleValue],
62
+ .duration = [duration intValue],
63
+ .target = [target intValue]});
56
64
  }
57
65
  if ([event isEqualToString:@"onKeyboardMove"]) {
58
66
  std::dynamic_pointer_cast<const facebook::react::KeyboardControllerViewEventEmitter>(
59
67
  self->_eventEmitter)
60
68
  ->onKeyboardMove(
61
69
  facebook::react::KeyboardControllerViewEventEmitter::OnKeyboardMove{
62
- .height = [height doubleValue], .progress = [progress doubleValue]});
70
+ .height = [height doubleValue],
71
+ .progress = [progress doubleValue],
72
+ .duration = [duration intValue],
73
+ .target = [target intValue]});
63
74
  }
64
75
  if ([event isEqualToString:@"onKeyboardMoveEnd"]) {
65
76
  std::dynamic_pointer_cast<const facebook::react::KeyboardControllerViewEventEmitter>(
66
77
  self->_eventEmitter)
67
78
  ->onKeyboardMoveEnd(
68
79
  facebook::react::KeyboardControllerViewEventEmitter::OnKeyboardMoveEnd{
69
- .height = [height doubleValue], .progress = [progress doubleValue]});
80
+ .height = [height doubleValue],
81
+ .progress = [progress doubleValue],
82
+ .duration = [duration intValue],
83
+ .target = [target intValue]});
70
84
  }
71
85
  }
72
86
  if ([event isEqualToString:@"onKeyboardMoveInteractive"]) {
@@ -74,7 +88,10 @@ using namespace facebook::react;
74
88
  self->_eventEmitter)
75
89
  ->onKeyboardMoveInteractive(
76
90
  facebook::react::KeyboardControllerViewEventEmitter::OnKeyboardMoveInteractive{
77
- .height = [height doubleValue], .progress = [progress doubleValue]});
91
+ .height = [height doubleValue],
92
+ .progress = [progress doubleValue],
93
+ .duration = [duration intValue],
94
+ .target = [target intValue]});
78
95
  }
79
96
 
80
97
  // TODO: use built-in _eventEmitter once NativeAnimated module will use ModernEventemitter
@@ -84,7 +101,9 @@ using namespace facebook::react;
84
101
  [[KeyboardMoveEvent alloc] initWithReactTag:@(self.tag)
85
102
  event:event
86
103
  height:height
87
- progress:progress];
104
+ progress:progress
105
+ duration:duration
106
+ target:target];
88
107
  [bridge.eventDispatcher sendEvent:keyboardMoveEvent];
89
108
  }
90
109
  }
@@ -46,7 +46,7 @@ class KeyboardControllerView: UIView {
46
46
  }
47
47
  }
48
48
 
49
- func onEvent(event: NSString, height: NSNumber, progress: NSNumber) {
49
+ func onEvent(event: NSString, height: NSNumber, progress: NSNumber, duration: NSNumber, target: NSNumber) {
50
50
  // we don't want to send event to JS before the JS thread is ready
51
51
  if bridge.value(forKey: "_jsThread") == nil {
52
52
  return
@@ -56,7 +56,9 @@ class KeyboardControllerView: UIView {
56
56
  reactTag: reactTag,
57
57
  event: event as String,
58
58
  height: height,
59
- progress: progress
59
+ progress: progress,
60
+ duration: duration,
61
+ target: target
60
62
  )
61
63
  )
62
64
  }
@@ -14,6 +14,8 @@
14
14
  - (instancetype)initWithReactTag:(NSNumber *)reactTag
15
15
  event:(NSString *)event
16
16
  height:(NSNumber *)height
17
- progress:(NSNumber *)progress;
17
+ progress:(NSNumber *)progress
18
+ duration:(NSNumber *)duration
19
+ target:(NSNumber *)target;
18
20
 
19
21
  @end
@@ -12,6 +12,8 @@
12
12
  @implementation KeyboardMoveEvent {
13
13
  NSNumber *_progress;
14
14
  NSNumber *_height;
15
+ NSNumber *_duration;
16
+ NSNumber *_target;
15
17
  uint16_t _coalescingKey;
16
18
  }
17
19
 
@@ -22,6 +24,8 @@
22
24
  event:(NSString *)event
23
25
  height:(NSNumber *)height
24
26
  progress:(NSNumber *)progress
27
+ duration:(NSNumber *)duration
28
+ target:(NSNumber *)target
25
29
  {
26
30
  RCTAssertParam(reactTag);
27
31
 
@@ -30,6 +34,8 @@
30
34
  _viewTag = reactTag;
31
35
  _progress = progress;
32
36
  _height = height;
37
+ _duration = duration;
38
+ _target = target;
33
39
  _coalescingKey = 0;
34
40
  }
35
41
  return self;
@@ -47,6 +53,8 @@ RCT_NOT_IMPLEMENTED(-(instancetype)init)
47
53
  NSDictionary *body = @{
48
54
  @"progress" : _progress,
49
55
  @"height" : _height,
56
+ @"duration" : _duration,
57
+ @"target" : _target,
50
58
  };
51
59
 
52
60
  return body;
@@ -7,11 +7,12 @@
7
7
  //
8
8
 
9
9
  import Foundation
10
+ import UIKit
10
11
 
11
12
  @objc(KeyboardMovementObserver)
12
13
  public class KeyboardMovementObserver: NSObject {
13
14
  // class members
14
- var onEvent: (NSString, NSNumber, NSNumber) -> Void
15
+ var onEvent: (NSString, NSNumber, NSNumber, NSNumber, NSNumber) -> Void
15
16
  var onNotify: (String, Any) -> Void
16
17
  // progress tracker
17
18
  private var _keyboardView: UIView?
@@ -29,11 +30,14 @@ public class KeyboardMovementObserver: NSObject {
29
30
  private var _windowsCount: Int = 0
30
31
  private var prevKeyboardPosition = 0.0
31
32
  private var displayLink: CADisplayLink?
32
- private var keyboardHeight: CGFloat = 0.0
33
33
  private var hasKVObserver = false
34
+ // state variables
35
+ private var keyboardHeight: CGFloat = 0.0
36
+ private var duration = 0
37
+ private var tag: NSNumber = -1
34
38
 
35
39
  @objc public init(
36
- handler: @escaping (NSString, NSNumber, NSNumber) -> Void,
40
+ handler: @escaping (NSString, NSNumber, NSNumber, NSNumber, NSNumber) -> Void,
37
41
  onNotify: @escaping (String, Any) -> Void
38
42
  ) {
39
43
  onEvent = handler
@@ -100,6 +104,11 @@ public class KeyboardMovementObserver: NSObject {
100
104
  if displayLink != nil {
101
105
  return
102
106
  }
107
+ // if keyboard height is not equal to its bounds - we can ignore
108
+ // values, since they'll be invalid and will cause UI jumps
109
+ if keyboardView?.bounds.size.height != keyboardHeight {
110
+ return
111
+ }
103
112
 
104
113
  // swiftlint:disable:next force_cast
105
114
  let keyboardFrameY = (change?[.newKey] as! NSValue).cgPointValue.y
@@ -118,7 +127,13 @@ public class KeyboardMovementObserver: NSObject {
118
127
  return
119
128
  }
120
129
 
121
- onEvent("onKeyboardMoveInteractive", position as NSNumber, position / CGFloat(keyboardHeight) as NSNumber)
130
+ onEvent(
131
+ "onKeyboardMoveInteractive",
132
+ position as NSNumber,
133
+ position / CGFloat(keyboardHeight) as NSNumber,
134
+ -1,
135
+ tag
136
+ )
122
137
  }
123
138
  }
124
139
 
@@ -129,24 +144,41 @@ public class KeyboardMovementObserver: NSObject {
129
144
 
130
145
  @objc func keyboardWillAppear(_ notification: Notification) {
131
146
  if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
147
+ tag = UIResponder.current.reactViewTag
132
148
  let keyboardHeight = keyboardFrame.cgRectValue.size.height
149
+ let duration = Int(
150
+ (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double ?? 0) * 1000
151
+ )
133
152
  self.keyboardHeight = keyboardHeight
153
+ self.duration = duration
134
154
 
135
155
  var data = [AnyHashable: Any]()
136
156
  data["height"] = keyboardHeight
157
+ data["duration"] = duration
158
+ data["timestamp"] = Date.currentTimeStamp
159
+ data["target"] = tag
137
160
 
138
- onEvent("onKeyboardMoveStart", Float(keyboardHeight) as NSNumber, 1)
161
+ onEvent("onKeyboardMoveStart", Float(keyboardHeight) as NSNumber, 1, duration as NSNumber, tag)
139
162
  onNotify("KeyboardController::keyboardWillShow", data)
140
163
 
141
164
  setupKeyboardWatcher()
142
165
  }
143
166
  }
144
167
 
145
- @objc func keyboardWillDisappear() {
168
+ @objc func keyboardWillDisappear(_ notification: Notification) {
169
+ let duration = Int(
170
+ (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double ?? 0) * 1000
171
+ )
172
+ tag = UIResponder.current.reactViewTag
173
+ self.duration = duration
174
+
146
175
  var data = [AnyHashable: Any]()
147
176
  data["height"] = 0
177
+ data["duration"] = duration
178
+ data["timestamp"] = Date.currentTimeStamp
179
+ data["target"] = tag
148
180
 
149
- onEvent("onKeyboardMoveStart", 0, 0)
181
+ onEvent("onKeyboardMoveStart", 0, 0, duration as NSNumber, tag)
150
182
  onNotify("KeyboardController::keyboardWillHide", data)
151
183
 
152
184
  setupKeyboardWatcher()
@@ -156,12 +188,19 @@ public class KeyboardMovementObserver: NSObject {
156
188
  @objc func keyboardDidAppear(_ notification: Notification) {
157
189
  if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
158
190
  let keyboardHeight = keyboardFrame.cgRectValue.size.height
191
+ tag = UIResponder.current.reactViewTag
159
192
  self.keyboardHeight = keyboardHeight
193
+ let duration = Int(
194
+ (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double ?? 0) * 1000
195
+ )
160
196
 
161
197
  var data = [AnyHashable: Any]()
162
198
  data["height"] = keyboardHeight
199
+ data["duration"] = duration
200
+ data["timestamp"] = Date.currentTimeStamp
201
+ data["target"] = tag
163
202
 
164
- onEvent("onKeyboardMoveEnd", keyboardHeight as NSNumber, 1)
203
+ onEvent("onKeyboardMoveEnd", keyboardHeight as NSNumber, 1, duration as NSNumber, tag)
165
204
  onNotify("KeyboardController::keyboardDidShow", data)
166
205
 
167
206
  removeKeyboardWatcher()
@@ -169,11 +208,18 @@ public class KeyboardMovementObserver: NSObject {
169
208
  }
170
209
  }
171
210
 
172
- @objc func keyboardDidDisappear() {
211
+ @objc func keyboardDidDisappear(_ notification: Notification) {
212
+ let duration = Int(
213
+ (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double ?? 0) * 1000
214
+ )
215
+ tag = UIResponder.current.reactViewTag
173
216
  var data = [AnyHashable: Any]()
174
217
  data["height"] = 0
218
+ data["duration"] = duration
219
+ data["timestamp"] = Date.currentTimeStamp
220
+ data["target"] = tag
175
221
 
176
- onEvent("onKeyboardMoveEnd", 0 as NSNumber, 0)
222
+ onEvent("onKeyboardMoveEnd", 0 as NSNumber, 0, duration as NSNumber, tag)
177
223
  onNotify("KeyboardController::keyboardDidHide", data)
178
224
 
179
225
  removeKeyboardWatcher()
@@ -208,7 +254,7 @@ public class KeyboardMovementObserver: NSObject {
208
254
  if subview.description.hasPrefix("<UIInputSetContainerView") {
209
255
  for hostView in subview.subviews {
210
256
  if hostView.description.hasPrefix("<UIInputSetHostView") {
211
- result = hostView as? UIView
257
+ result = hostView
212
258
  break
213
259
  }
214
260
  }
@@ -235,6 +281,12 @@ public class KeyboardMovementObserver: NSObject {
235
281
  }
236
282
 
237
283
  prevKeyboardPosition = keyboardPosition
238
- onEvent("onKeyboardMove", keyboardPosition as NSNumber, keyboardPosition / CGFloat(keyboardHeight) as NSNumber)
284
+ onEvent(
285
+ "onKeyboardMove",
286
+ keyboardPosition as NSNumber,
287
+ keyboardPosition / CGFloat(keyboardHeight) as NSNumber,
288
+ duration as NSNumber,
289
+ tag
290
+ )
239
291
  }
240
292
  }
@@ -9,10 +9,11 @@ var _reactNative = require("react-native");
9
9
  var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated"));
10
10
  var _context = require("./context");
11
11
  var _internal = require("./internal");
12
- var _native = require("./native");
12
+ var _bindings = require("./bindings");
13
+ var _reanimated = require("./reanimated");
13
14
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
14
15
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
15
- const KeyboardControllerViewAnimated = _reactNativeReanimated.default.createAnimatedComponent(_reactNative.Animated.createAnimatedComponent(_native.KeyboardControllerView));
16
+ const KeyboardControllerViewAnimated = _reactNativeReanimated.default.createAnimatedComponent(_reactNative.Animated.createAnimatedComponent(_bindings.KeyboardControllerView));
16
17
  const styles = _reactNative.StyleSheet.create({
17
18
  container: {
18
19
  flex: 1
@@ -74,7 +75,7 @@ const KeyboardProvider = _ref => {
74
75
  heightSV.value = -event.height;
75
76
  }
76
77
  };
77
- const handler = (0, _internal.useAnimatedKeyboardHandler)({
78
+ const handler = (0, _reanimated.useAnimatedKeyboardHandler)({
78
79
  onKeyboardMoveStart: event => {
79
80
  'worklet';
80
81