react-native-keyboard-controller 1.12.6 → 1.13.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 (152) hide show
  1. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardGestureAreaViewManager.kt +5 -0
  2. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ReactContext.kt +13 -0
  3. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ThemedReactContext.kt +0 -10
  4. package/android/src/main/java/com/reactnativekeyboardcontroller/interactive/interpolators/Interpolator.kt +2 -1
  5. package/android/src/main/java/com/reactnativekeyboardcontroller/interactive/interpolators/IosInterpolator.kt +2 -1
  6. package/android/src/main/java/com/reactnativekeyboardcontroller/interactive/interpolators/LinearInterpolator.kt +1 -0
  7. package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/FocusedInputObserver.kt +15 -8
  8. package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt +71 -59
  9. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardGestureAreaViewManagerImpl.kt +4 -0
  10. package/android/src/main/java/com/reactnativekeyboardcontroller/modal/ModalAttachedWatcher.kt +80 -0
  11. package/android/src/main/java/com/reactnativekeyboardcontroller/modules/StatusBarManagerCompatModuleImpl.kt +6 -12
  12. package/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt +31 -4
  13. package/android/src/main/java/com/reactnativekeyboardcontroller/views/KeyboardGestureAreaReactViewGroup.kt +7 -0
  14. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardGestureAreaViewManager.kt +5 -0
  15. package/ios/core/KeyboardAnimation.swift +1 -1
  16. package/ios/core/TimingAnimation.swift +108 -0
  17. package/ios/extensions/UIApplication.swift +29 -0
  18. package/ios/extensions/UIView.swift +10 -0
  19. package/ios/observers/FocusedInputObserver.swift +2 -0
  20. package/ios/observers/KeyboardMovementObserver.swift +19 -10
  21. package/ios/traversal/ViewHierarchyNavigator.swift +1 -1
  22. package/ios/views/KeyboardControllerView.mm +30 -39
  23. package/lib/commonjs/animated.js +16 -30
  24. package/lib/commonjs/animated.js.map +1 -1
  25. package/lib/commonjs/bindings.js.map +1 -1
  26. package/lib/commonjs/bindings.native.js.map +1 -1
  27. package/lib/commonjs/components/KeyboardAvoidingView/hooks.js +3 -14
  28. package/lib/commonjs/components/KeyboardAvoidingView/hooks.js.map +1 -1
  29. package/lib/commonjs/components/KeyboardAvoidingView/index.js +5 -4
  30. package/lib/commonjs/components/KeyboardAvoidingView/index.js.map +1 -1
  31. package/lib/commonjs/components/KeyboardAwareScrollView/index.js +42 -16
  32. package/lib/commonjs/components/KeyboardAwareScrollView/index.js.map +1 -1
  33. package/lib/commonjs/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js +1 -0
  34. package/lib/commonjs/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js.map +1 -1
  35. package/lib/commonjs/components/KeyboardAwareScrollView/utils.js.map +1 -1
  36. package/lib/commonjs/components/KeyboardStickyView/index.js +2 -2
  37. package/lib/commonjs/components/KeyboardStickyView/index.js.map +1 -1
  38. package/lib/commonjs/components/KeyboardToolbar/Arrow.js +2 -2
  39. package/lib/commonjs/components/KeyboardToolbar/Arrow.js.map +1 -1
  40. package/lib/commonjs/components/KeyboardToolbar/Button.js +12 -12
  41. package/lib/commonjs/components/KeyboardToolbar/Button.js.map +1 -1
  42. package/lib/commonjs/components/KeyboardToolbar/index.js +33 -27
  43. package/lib/commonjs/components/KeyboardToolbar/index.js.map +1 -1
  44. package/lib/commonjs/components/index.js +2 -2
  45. package/lib/commonjs/components/index.js.map +1 -1
  46. package/lib/commonjs/context.js +3 -2
  47. package/lib/commonjs/context.js.map +1 -1
  48. package/lib/commonjs/event-handler.js +19 -0
  49. package/lib/commonjs/event-handler.js.map +1 -0
  50. package/lib/commonjs/event-handler.web.js +10 -0
  51. package/lib/commonjs/event-handler.web.js.map +1 -0
  52. package/lib/commonjs/event-mappings.js +9 -0
  53. package/lib/commonjs/event-mappings.js.map +1 -0
  54. package/lib/commonjs/hooks/index.js +8 -21
  55. package/lib/commonjs/hooks/index.js.map +1 -1
  56. package/lib/commonjs/hooks/useSyncEffect/index.js +37 -0
  57. package/lib/commonjs/hooks/useSyncEffect/index.js.map +1 -0
  58. package/lib/commonjs/internal.js +20 -43
  59. package/lib/commonjs/internal.js.map +1 -1
  60. package/lib/commonjs/monkey-patch.android.js +1 -1
  61. package/lib/commonjs/monkey-patch.android.js.map +1 -1
  62. package/lib/commonjs/monkey-patch.js.map +1 -1
  63. package/lib/commonjs/reanimated.js +1 -3
  64. package/lib/commonjs/reanimated.js.map +1 -1
  65. package/lib/commonjs/reanimated.native.js +1 -35
  66. package/lib/commonjs/reanimated.native.js.map +1 -1
  67. package/lib/commonjs/specs/KeyboardControllerViewNativeComponent.js +1 -1
  68. package/lib/commonjs/specs/KeyboardControllerViewNativeComponent.js.map +1 -1
  69. package/lib/commonjs/specs/KeyboardGestureAreaNativeComponent.js +1 -1
  70. package/lib/commonjs/specs/KeyboardGestureAreaNativeComponent.js.map +1 -1
  71. package/lib/commonjs/types.js.map +1 -1
  72. package/lib/module/animated.js +17 -32
  73. package/lib/module/animated.js.map +1 -1
  74. package/lib/module/bindings.js.map +1 -1
  75. package/lib/module/bindings.native.js.map +1 -1
  76. package/lib/module/components/KeyboardAvoidingView/hooks.js +3 -14
  77. package/lib/module/components/KeyboardAvoidingView/hooks.js.map +1 -1
  78. package/lib/module/components/KeyboardAvoidingView/index.js +4 -3
  79. package/lib/module/components/KeyboardAvoidingView/index.js.map +1 -1
  80. package/lib/module/components/KeyboardAwareScrollView/index.js +41 -15
  81. package/lib/module/components/KeyboardAwareScrollView/index.js.map +1 -1
  82. package/lib/module/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js +1 -0
  83. package/lib/module/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.js.map +1 -1
  84. package/lib/module/components/KeyboardAwareScrollView/utils.js.map +1 -1
  85. package/lib/module/components/KeyboardStickyView/index.js +1 -1
  86. package/lib/module/components/KeyboardToolbar/Button.js +10 -10
  87. package/lib/module/components/KeyboardToolbar/Button.js.map +1 -1
  88. package/lib/module/components/KeyboardToolbar/index.js +31 -25
  89. package/lib/module/components/KeyboardToolbar/index.js.map +1 -1
  90. package/lib/module/context.js +3 -2
  91. package/lib/module/context.js.map +1 -1
  92. package/lib/module/event-handler.js +13 -0
  93. package/lib/module/event-handler.js.map +1 -0
  94. package/lib/module/event-handler.web.js +5 -0
  95. package/lib/module/event-handler.web.js.map +1 -0
  96. package/lib/module/event-mappings.js +3 -0
  97. package/lib/module/event-mappings.js.map +1 -0
  98. package/lib/module/hooks/index.js +7 -21
  99. package/lib/module/hooks/index.js.map +1 -1
  100. package/lib/module/hooks/useSyncEffect/index.js +31 -0
  101. package/lib/module/hooks/useSyncEffect/index.js.map +1 -0
  102. package/lib/module/internal.js +21 -44
  103. package/lib/module/internal.js.map +1 -1
  104. package/lib/module/monkey-patch.js.map +1 -1
  105. package/lib/module/reanimated.js +0 -2
  106. package/lib/module/reanimated.js.map +1 -1
  107. package/lib/module/reanimated.native.js +0 -32
  108. package/lib/module/reanimated.native.js.map +1 -1
  109. package/lib/module/specs/KeyboardGestureAreaNativeComponent.js.map +1 -1
  110. package/lib/module/types.js.map +1 -1
  111. package/lib/typescript/components/KeyboardToolbar/Button.d.ts +2 -2
  112. package/lib/typescript/components/KeyboardToolbar/index.d.ts +4 -3
  113. package/lib/typescript/context.d.ts +3 -3
  114. package/lib/typescript/event-handler.d.ts +3 -0
  115. package/lib/typescript/event-mappings.d.ts +2 -0
  116. package/lib/typescript/hooks/index.d.ts +1 -1
  117. package/lib/typescript/hooks/useSyncEffect/index.d.ts +14 -0
  118. package/lib/typescript/internal.d.ts +5 -19
  119. package/lib/typescript/reanimated.d.ts +1 -3
  120. package/lib/typescript/reanimated.native.d.ts +1 -3
  121. package/lib/typescript/specs/KeyboardGestureAreaNativeComponent.d.ts +2 -1
  122. package/lib/typescript/types.d.ts +6 -0
  123. package/package.json +14 -7
  124. package/src/animated.tsx +21 -43
  125. package/src/bindings.native.ts +1 -0
  126. package/src/bindings.ts +1 -0
  127. package/src/components/KeyboardAvoidingView/hooks.ts +3 -13
  128. package/src/components/KeyboardAvoidingView/index.tsx +2 -1
  129. package/src/components/KeyboardAwareScrollView/index.tsx +51 -13
  130. package/src/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.ts +2 -0
  131. package/src/components/KeyboardAwareScrollView/utils.ts +1 -0
  132. package/src/components/KeyboardToolbar/Button.tsx +11 -11
  133. package/src/components/KeyboardToolbar/index.tsx +45 -25
  134. package/src/context.ts +8 -6
  135. package/src/event-handler.js +15 -0
  136. package/src/event-handler.ts +8 -0
  137. package/src/event-handler.web.js +5 -0
  138. package/src/event-mappings.ts +14 -0
  139. package/src/hooks/index.ts +9 -16
  140. package/src/hooks/useSyncEffect/index.ts +36 -0
  141. package/src/internal.ts +34 -53
  142. package/src/monkey-patch.ts +1 -0
  143. package/src/reanimated.native.ts +0 -50
  144. package/src/reanimated.ts +1 -12
  145. package/src/specs/KeyboardGestureAreaNativeComponent.ts +5 -1
  146. package/src/types.ts +6 -0
  147. package/lib/commonjs/utils.js +0 -9
  148. package/lib/commonjs/utils.js.map +0 -1
  149. package/lib/module/utils.js +0 -2
  150. package/lib/module/utils.js.map +0 -1
  151. package/lib/typescript/utils.d.ts +0 -1
  152. package/src/utils.ts +0 -1
@@ -27,6 +27,11 @@ class KeyboardGestureAreaViewManager(mReactContext: ReactApplicationContext) :
27
27
  return manager.createViewInstance(context)
28
28
  }
29
29
 
30
+ @ReactProp(name = "offset")
31
+ override fun setOffset(view: ReactViewGroup, value: Double) {
32
+ manager.setOffset(view as KeyboardGestureAreaReactViewGroup, value)
33
+ }
34
+
30
35
  @ReactProp(name = "interpolator")
31
36
  override fun setInterpolator(view: ReactViewGroup, value: String?) {
32
37
  manager.setInterpolator(view as KeyboardGestureAreaReactViewGroup, value ?: "linear")
@@ -0,0 +1,13 @@
1
+ package com.reactnativekeyboardcontroller.extensions
2
+
3
+ import android.view.View
4
+ import android.view.ViewGroup
5
+ import com.facebook.react.bridge.ReactContext
6
+
7
+ val ReactContext.rootView: View?
8
+ get() = this.currentActivity?.window?.decorView?.rootView
9
+
10
+ val ReactContext.content: ViewGroup?
11
+ get() = this.currentActivity?.window?.decorView?.rootView?.findViewById(
12
+ androidx.appcompat.R.id.action_bar_root,
13
+ )
@@ -1,8 +1,6 @@
1
1
  package com.reactnativekeyboardcontroller.extensions
2
2
 
3
3
  import android.util.Log
4
- import android.view.View
5
- import android.view.ViewGroup
6
4
  import com.facebook.react.bridge.ReactContext
7
5
  import com.facebook.react.bridge.WritableMap
8
6
  import com.facebook.react.modules.core.DeviceEventManagerModule
@@ -12,14 +10,6 @@ import com.facebook.react.uimanager.events.Event
12
10
  import com.facebook.react.uimanager.events.EventDispatcher
13
11
  import com.reactnativekeyboardcontroller.listeners.WindowDimensionListener
14
12
 
15
- val ThemedReactContext.rootView: View?
16
- get() = this.currentActivity?.window?.decorView?.rootView
17
-
18
- val ThemedReactContext.content: ViewGroup?
19
- get() = this.currentActivity?.window?.decorView?.rootView?.findViewById(
20
- androidx.appcompat.R.id.action_bar_root,
21
- )
22
-
23
13
  fun ThemedReactContext.setupWindowDimensionsListener() {
24
14
  WindowDimensionListener(this)
25
15
  }
@@ -8,7 +8,8 @@ interface Interpolator {
8
8
  * @param dy the distance that the finger has moved relative to the previous location.
9
9
  * @param absoluteFingerPosition current position of the finger.
10
10
  * @param keyboardPosition current keyboard position.
11
+ * @param offset extra space to the keyboard to activate a gesture
11
12
  * @return the distance the keyboard should be moved from its current location.
12
13
  */
13
- fun interpolate(dy: Int, absoluteFingerPosition: Int, keyboardPosition: Int): Int
14
+ fun interpolate(dy: Int, absoluteFingerPosition: Int, keyboardPosition: Int, offset: Int): Int
14
15
  }
@@ -5,9 +5,10 @@ class IosInterpolator : Interpolator {
5
5
  dy: Int,
6
6
  absoluteFingerPosition: Int,
7
7
  keyboardPosition: Int,
8
+ offset: Int,
8
9
  ): Int {
9
10
  if (
10
- absoluteFingerPosition <= keyboardPosition || // user over scrolled keyboard
11
+ absoluteFingerPosition <= keyboardPosition + offset || // user over scrolled keyboard
11
12
  dy <= 0 // user scrolls up
12
13
  ) {
13
14
  return dy
@@ -5,6 +5,7 @@ class LinearInterpolator : Interpolator {
5
5
  dy: Int,
6
6
  absoluteFingerPosition: Int,
7
7
  keyboardPosition: Int,
8
+ offset: Int,
8
9
  ): Int {
9
10
  return dy
10
11
  }
@@ -1,6 +1,7 @@
1
1
  package com.reactnativekeyboardcontroller.listeners
2
2
 
3
3
  import android.text.TextWatcher
4
+ import android.view.View
4
5
  import android.view.View.OnLayoutChangeListener
5
6
  import android.view.ViewTreeObserver.OnGlobalFocusChangeListener
6
7
  import com.facebook.react.bridge.Arguments
@@ -35,7 +36,11 @@ val noFocusedInputEvent = FocusedInputLayoutChangedEventData(
35
36
  parentScrollViewTarget = -1,
36
37
  )
37
38
 
38
- class FocusedInputObserver(val view: ReactViewGroup, private val context: ThemedReactContext?) {
39
+ class FocusedInputObserver(
40
+ val view: View,
41
+ private val eventPropagationView: ReactViewGroup,
42
+ private val context: ThemedReactContext?,
43
+ ) {
39
44
  // constructor variables
40
45
  private val surfaceId = UIManagerHelper.getSurfaceId(view)
41
46
 
@@ -53,10 +58,10 @@ class FocusedInputObserver(val view: ReactViewGroup, private val context: Themed
53
58
  private val textListener: (String) -> Unit = { text ->
54
59
  syncUpLayout()
55
60
  context.dispatchEvent(
56
- view.id,
61
+ eventPropagationView.id,
57
62
  FocusedInputTextChangedEvent(
58
63
  surfaceId,
59
- view.id,
64
+ eventPropagationView.id,
60
65
  text = text,
61
66
  ),
62
67
  )
@@ -70,11 +75,13 @@ class FocusedInputObserver(val view: ReactViewGroup, private val context: Themed
70
75
  endY: Double,
71
76
  ) -> Unit = listener@{ start, end, startX, startY, endX, endY ->
72
77
  val input = lastFocusedInput ?: return@listener
78
+
79
+ syncUpLayout()
73
80
  context.dispatchEvent(
74
- view.id,
81
+ eventPropagationView.id,
75
82
  FocusedInputSelectionChangedEvent(
76
83
  surfaceId,
77
- view.id,
84
+ eventPropagationView.id,
78
85
  event = FocusedInputSelectionChangedEventData(
79
86
  target = input.id,
80
87
  start = start,
@@ -88,7 +95,7 @@ class FocusedInputObserver(val view: ReactViewGroup, private val context: Themed
88
95
  )
89
96
  }
90
97
  private val focusListener = OnGlobalFocusChangeListener { oldFocus, newFocus ->
91
- // unfocused or focused was changed
98
+ // unfocused or focus was changed
92
99
  if (newFocus == null || oldFocus != null) {
93
100
  lastFocusedInput?.removeOnLayoutChangeListener(layoutListener)
94
101
  lastFocusedInput?.removeTextChangedListener(textWatcher)
@@ -150,10 +157,10 @@ class FocusedInputObserver(val view: ReactViewGroup, private val context: Themed
150
157
  if (event != lastEventDispatched) {
151
158
  lastEventDispatched = event
152
159
  context.dispatchEvent(
153
- view.id,
160
+ eventPropagationView.id,
154
161
  FocusedInputLayoutChangedEvent(
155
162
  surfaceId,
156
- view.id,
163
+ eventPropagationView.id,
157
164
  event = event,
158
165
  ),
159
166
  )
@@ -26,15 +26,20 @@ import kotlin.math.abs
26
26
  private val TAG = KeyboardAnimationCallback::class.qualifiedName
27
27
  private val isResizeHandledInCallbackMethods = Build.VERSION.SDK_INT < Build.VERSION_CODES.R
28
28
 
29
- class KeyboardAnimationCallback(
30
- val view: ReactViewGroup,
29
+ data class KeyboardAnimationCallbackConfig(
31
30
  val persistentInsetTypes: Int,
32
31
  val deferredInsetTypes: Int,
33
- dispatchMode: Int = DISPATCH_MODE_STOP,
34
- val context: ThemedReactContext?,
32
+ val dispatchMode: Int = WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP,
35
33
  val hasTranslucentNavigationBar: Boolean = false,
36
- ) : WindowInsetsAnimationCompat.Callback(dispatchMode), OnApplyWindowInsetsListener {
37
- private val surfaceId = UIManagerHelper.getSurfaceId(view)
34
+ )
35
+
36
+ class KeyboardAnimationCallback(
37
+ val eventPropagationView: ReactViewGroup,
38
+ val view: View,
39
+ val context: ThemedReactContext?,
40
+ private val config: KeyboardAnimationCallbackConfig,
41
+ ) : WindowInsetsAnimationCompat.Callback(config.dispatchMode), OnApplyWindowInsetsListener {
42
+ private val surfaceId = UIManagerHelper.getSurfaceId(eventPropagationView)
38
43
 
39
44
  // state variables
40
45
  private var persistentKeyboardHeight = 0.0
@@ -58,10 +63,10 @@ class KeyboardAnimationCallback(
58
63
  // 100% included in onStart/onMove/onEnd life cycles, but triggering onStart/onEnd several time
59
64
  // can bring breaking changes
60
65
  context.dispatchEvent(
61
- view.id,
66
+ eventPropagationView.id,
62
67
  KeyboardTransitionEvent(
63
68
  surfaceId,
64
- view.id,
69
+ eventPropagationView.id,
65
70
  "topKeyboardMoveStart",
66
71
  this.persistentKeyboardHeight,
67
72
  1.0,
@@ -70,10 +75,10 @@ class KeyboardAnimationCallback(
70
75
  ),
71
76
  )
72
77
  context.dispatchEvent(
73
- view.id,
78
+ eventPropagationView.id,
74
79
  KeyboardTransitionEvent(
75
80
  surfaceId,
76
- view.id,
81
+ eventPropagationView.id,
77
82
  "topKeyboardMoveEnd",
78
83
  this.persistentKeyboardHeight,
79
84
  1.0,
@@ -89,12 +94,12 @@ class KeyboardAnimationCallback(
89
94
  private var layoutObserver: FocusedInputObserver? = null
90
95
 
91
96
  init {
92
- require(persistentInsetTypes and deferredInsetTypes == 0) {
97
+ require(config.persistentInsetTypes and config.deferredInsetTypes == 0) {
93
98
  "persistentInsetTypes and deferredInsetTypes can not contain any of " +
94
99
  " same WindowInsetsCompat.Type values"
95
100
  }
96
101
 
97
- layoutObserver = FocusedInputObserver(view = view, context = context)
102
+ layoutObserver = FocusedInputObserver(view = view, eventPropagationView = eventPropagationView, context = context)
98
103
  view.viewTreeObserver.addOnGlobalFocusChangeListener(focusListener)
99
104
  }
100
105
 
@@ -137,7 +142,7 @@ class KeyboardAnimationCallback(
137
142
  this.onKeyboardResized(keyboardHeight)
138
143
  }
139
144
 
140
- return insets
145
+ return WindowInsetsCompat.CONSUMED
141
146
  }
142
147
 
143
148
  @Suppress("detekt:ReturnCount")
@@ -179,10 +184,10 @@ class KeyboardAnimationCallback(
179
184
 
180
185
  Log.i(TAG, "HEIGHT:: $keyboardHeight TAG:: $viewTagFocused")
181
186
  context.dispatchEvent(
182
- view.id,
187
+ eventPropagationView.id,
183
188
  KeyboardTransitionEvent(
184
189
  surfaceId,
185
- view.id,
190
+ eventPropagationView.id,
186
191
  "topKeyboardMoveStart",
187
192
  keyboardHeight,
188
193
  if (!isKeyboardVisible) 0.0 else 1.0,
@@ -204,13 +209,13 @@ class KeyboardAnimationCallback(
204
209
  runningAnimations.find { it.isKeyboardAnimation && !animationsToSkip.contains(it) } ?: return insets
205
210
 
206
211
  // First we get the insets which are potentially deferred
207
- val typesInset = insets.getInsets(deferredInsetTypes)
212
+ val typesInset = insets.getInsets(config.deferredInsetTypes)
208
213
  // Then we get the persistent inset types which are applied as padding during layout
209
- var otherInset = insets.getInsets(persistentInsetTypes)
214
+ var otherInset = insets.getInsets(config.persistentInsetTypes)
210
215
 
211
216
  // Now that we subtract the two insets, to calculate the difference. We also coerce
212
217
  // the insets to be >= 0, to make sure we don't use negative insets.
213
- if (hasTranslucentNavigationBar) {
218
+ if (config.hasTranslucentNavigationBar) {
214
219
  otherInset = Insets.NONE
215
220
  }
216
221
  val diff = Insets.subtract(typesInset, otherInset).let {
@@ -233,10 +238,10 @@ class KeyboardAnimationCallback(
233
238
 
234
239
  val event = if (InteractiveKeyboardProvider.isInteractive) "topKeyboardMoveInteractive" else "topKeyboardMove"
235
240
  context.dispatchEvent(
236
- view.id,
241
+ eventPropagationView.id,
237
242
  KeyboardTransitionEvent(
238
243
  surfaceId,
239
- view.id,
244
+ eventPropagationView.id,
240
245
  event,
241
246
  height,
242
247
  progress,
@@ -284,10 +289,10 @@ class KeyboardAnimationCallback(
284
289
  getEventParams(keyboardHeight),
285
290
  )
286
291
  context.dispatchEvent(
287
- view.id,
292
+ eventPropagationView.id,
288
293
  KeyboardTransitionEvent(
289
294
  surfaceId,
290
- view.id,
295
+ eventPropagationView.id,
291
296
  "topKeyboardMoveEnd",
292
297
  keyboardHeight,
293
298
  if (!isKeyboardVisible) 0.0 else 1.0,
@@ -300,6 +305,35 @@ class KeyboardAnimationCallback(
300
305
  duration = 0
301
306
  }
302
307
 
308
+ fun syncKeyboardPosition() {
309
+ val keyboardHeight = getCurrentKeyboardHeight()
310
+ // update internal state
311
+ isKeyboardVisible = isKeyboardVisible()
312
+ prevKeyboardHeight = keyboardHeight
313
+ isTransitioning = false
314
+ duration = 0
315
+
316
+ context.emitEvent(
317
+ "KeyboardController::" + if (!isKeyboardVisible) "keyboardDidHide" else "keyboardDidShow",
318
+ getEventParams(keyboardHeight),
319
+ )
320
+ // dispatch `onMove` to update RN animated value and `onEnd` to indicate that transition finished
321
+ listOf("topKeyboardMove", "topKeyboardMoveEnd").forEach { eventName ->
322
+ context.dispatchEvent(
323
+ eventPropagationView.id,
324
+ KeyboardTransitionEvent(
325
+ surfaceId,
326
+ eventPropagationView.id,
327
+ eventName,
328
+ keyboardHeight,
329
+ if (!isKeyboardVisible) 0.0 else 1.0,
330
+ duration,
331
+ viewTagFocused,
332
+ ),
333
+ )
334
+ }
335
+ }
336
+
303
337
  fun destroy() {
304
338
  view.viewTreeObserver.removeOnGlobalFocusChangeListener(focusListener)
305
339
  layoutObserver?.destroy()
@@ -312,42 +346,20 @@ class KeyboardAnimationCallback(
312
346
  duration = 0
313
347
 
314
348
  context.emitEvent("KeyboardController::keyboardWillShow", getEventParams(keyboardHeight))
315
- context.dispatchEvent(
316
- view.id,
317
- KeyboardTransitionEvent(
318
- surfaceId,
319
- view.id,
320
- "topKeyboardMoveStart",
321
- keyboardHeight,
322
- 1.0,
323
- 0,
324
- viewTagFocused,
325
- ),
326
- )
327
- context.dispatchEvent(
328
- view.id,
329
- KeyboardTransitionEvent(
330
- surfaceId,
331
- view.id,
332
- "topKeyboardMove",
333
- keyboardHeight,
334
- 1.0,
335
- 0,
336
- viewTagFocused,
337
- ),
338
- )
339
- context.dispatchEvent(
340
- view.id,
341
- KeyboardTransitionEvent(
342
- surfaceId,
343
- view.id,
344
- "topKeyboardMoveEnd",
345
- keyboardHeight,
346
- 1.0,
347
- 0,
348
- viewTagFocused,
349
- ),
350
- )
349
+ listOf("topKeyboardMoveStart", "topKeyboardMove", "topKeyboardMoveEnd").forEach { eventName ->
350
+ context.dispatchEvent(
351
+ eventPropagationView.id,
352
+ KeyboardTransitionEvent(
353
+ surfaceId,
354
+ eventPropagationView.id,
355
+ eventName,
356
+ keyboardHeight,
357
+ 1.0,
358
+ 0,
359
+ viewTagFocused,
360
+ ),
361
+ )
362
+ }
351
363
  context.emitEvent("KeyboardController::keyboardDidShow", getEventParams(keyboardHeight))
352
364
 
353
365
  this.persistentKeyboardHeight = keyboardHeight
@@ -363,7 +375,7 @@ class KeyboardAnimationCallback(
363
375
  val insets = ViewCompat.getRootWindowInsets(view)
364
376
  val keyboardHeight = insets?.getInsets(WindowInsetsCompat.Type.ime())?.bottom ?: 0
365
377
  val navigationBar =
366
- if (hasTranslucentNavigationBar) {
378
+ if (config.hasTranslucentNavigationBar) {
367
379
  0
368
380
  } else {
369
381
  insets?.getInsets(WindowInsetsCompat.Type.navigationBars())?.bottom ?: 0
@@ -10,6 +10,10 @@ class KeyboardGestureAreaViewManagerImpl(mReactContext: ReactApplicationContext)
10
10
  return KeyboardGestureAreaReactViewGroup(reactContext)
11
11
  }
12
12
 
13
+ fun setOffset(view: KeyboardGestureAreaReactViewGroup, offset: Double) {
14
+ view.setOffset(offset)
15
+ }
16
+
13
17
  fun setInterpolator(view: KeyboardGestureAreaReactViewGroup, interpolator: String) {
14
18
  view.setInterpolator(interpolator)
15
19
  }
@@ -0,0 +1,80 @@
1
+ package com.reactnativekeyboardcontroller.modal
2
+
3
+ import android.util.Log
4
+ import android.view.WindowManager
5
+ import androidx.core.view.ViewCompat
6
+ import com.facebook.react.uimanager.ThemedReactContext
7
+ import com.facebook.react.uimanager.UIManagerHelper
8
+ import com.facebook.react.uimanager.common.UIManagerType
9
+ import com.facebook.react.uimanager.events.Event
10
+ import com.facebook.react.uimanager.events.EventDispatcherListener
11
+ import com.facebook.react.views.modal.ReactModalHostView
12
+ import com.facebook.react.views.view.ReactViewGroup
13
+ import com.reactnativekeyboardcontroller.BuildConfig
14
+ import com.reactnativekeyboardcontroller.listeners.KeyboardAnimationCallback
15
+ import com.reactnativekeyboardcontroller.listeners.KeyboardAnimationCallbackConfig
16
+
17
+ private val TAG = ModalAttachedWatcher::class.qualifiedName
18
+
19
+ class ModalAttachedWatcher(
20
+ private val view: ReactViewGroup,
21
+ private val reactContext: ThemedReactContext,
22
+ private val config: () -> KeyboardAnimationCallbackConfig,
23
+ ) : EventDispatcherListener {
24
+ private val archType = if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) UIManagerType.FABRIC else UIManagerType.DEFAULT
25
+ private val uiManager = UIManagerHelper.getUIManager(reactContext.reactApplicationContext, archType)
26
+ private val eventDispatcher = UIManagerHelper.getEventDispatcher(reactContext.reactApplicationContext, archType)
27
+
28
+ override fun onEventDispatch(event: Event<out Event<*>>?) {
29
+ if (event?.eventName != MODAL_SHOW_EVENT) {
30
+ return
31
+ }
32
+
33
+ val modal = try {
34
+ uiManager?.resolveView(event.viewTag) as? ReactModalHostView
35
+ } catch (ignore: Exception) {
36
+ Log.w(TAG, "Can not resolve view for Modal#${event.viewTag}", ignore)
37
+ null
38
+ }
39
+
40
+ if (modal == null) {
41
+ return
42
+ }
43
+
44
+ val dialog = modal.dialog
45
+ val window = dialog?.window
46
+ val rootView = window?.decorView?.rootView
47
+
48
+ if (rootView != null) {
49
+ val callback = KeyboardAnimationCallback(
50
+ view = rootView,
51
+ eventPropagationView = view,
52
+ context = reactContext,
53
+ config = config(),
54
+ )
55
+
56
+ ViewCompat.setWindowInsetsAnimationCallback(rootView, callback)
57
+ ViewCompat.setOnApplyWindowInsetsListener(rootView, callback)
58
+
59
+ dialog.setOnDismissListener {
60
+ callback.syncKeyboardPosition()
61
+ callback.destroy()
62
+ }
63
+
64
+ // imitating edge-to-edge mode behavior
65
+ window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
66
+ }
67
+ }
68
+
69
+ fun enable() {
70
+ eventDispatcher?.addListener(this)
71
+ }
72
+
73
+ fun disable() {
74
+ eventDispatcher?.removeListener(this)
75
+ }
76
+
77
+ companion object {
78
+ private const val MODAL_SHOW_EVENT = "topShow"
79
+ }
80
+ }
@@ -9,6 +9,8 @@ import androidx.core.view.WindowInsetsCompat
9
9
  import androidx.core.view.WindowInsetsControllerCompat
10
10
  import com.facebook.react.bridge.ReactApplicationContext
11
11
  import com.facebook.react.bridge.UiThreadUtil
12
+ import com.reactnativekeyboardcontroller.extensions.rootView
13
+ import com.reactnativekeyboardcontroller.views.EdgeToEdgeReactViewGroup
12
14
 
13
15
  private val TAG = StatusBarManagerCompatModuleImpl::class.qualifiedName
14
16
 
@@ -50,19 +52,11 @@ class StatusBarManagerCompatModuleImpl(private val mReactContext: ReactApplicati
50
52
  }
51
53
  }
52
54
 
53
- @Suppress("detekt:UnusedParameter")
54
55
  fun setTranslucent(translucent: Boolean) {
55
- // the status bar is translucent by default (once you wrapped App in Provider,
56
- // and EdgeToEdgeReactViewGroup has been mounted and called
57
- // `setDecorFitsSystemWindows(window, false)`. By default this library applies default padding
58
- // which equal to StatusBar height, so it will have a default RN app behavior. Though once you
59
- // need to set StatusBar as translucent, you will need to use `statusBarTranslucent` prop on
60
- // `KeyboardProvider` (it will preventing of applying additional padding, and status bar will be
61
- // translucent. Though it's important to note, that this value is not reactive (i. e. if you change
62
- // `statusBarTranslucent` in runtime it will not have any effect. Just theoretically I could make
63
- // it reactive, but I know, that most of apps or don't use StatusBar translucency at all or they are
64
- // specifying it for entire app, so I don't see a lot of sense to make it reactive as of now. If your
65
- // app requires to dynamically manage it - just shoot an issue and I will try to add a support fot that.
56
+ UiThreadUtil.runOnUiThread {
57
+ val view = mReactContext.rootView?.findViewWithTag<EdgeToEdgeReactViewGroup>(EdgeToEdgeReactViewGroup.VIEW_TAG)
58
+ view?.forceStatusBarTranslucent(translucent)
59
+ }
66
60
  }
67
61
 
68
62
  fun setStyle(style: String) {
@@ -17,6 +17,8 @@ import com.reactnativekeyboardcontroller.extensions.requestApplyInsetsWhenAttach
17
17
  import com.reactnativekeyboardcontroller.extensions.rootView
18
18
  import com.reactnativekeyboardcontroller.extensions.setupWindowDimensionsListener
19
19
  import com.reactnativekeyboardcontroller.listeners.KeyboardAnimationCallback
20
+ import com.reactnativekeyboardcontroller.listeners.KeyboardAnimationCallbackConfig
21
+ import com.reactnativekeyboardcontroller.modal.ModalAttachedWatcher
20
22
 
21
23
  private val TAG = EdgeToEdgeReactViewGroup::class.qualifiedName
22
24
 
@@ -32,9 +34,20 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
32
34
  private var eventView: ReactViewGroup? = null
33
35
  private var wasMounted = false
34
36
  private var callback: KeyboardAnimationCallback? = null
37
+ private val config: KeyboardAnimationCallbackConfig
38
+ get() = KeyboardAnimationCallbackConfig(
39
+ persistentInsetTypes = WindowInsetsCompat.Type.systemBars(),
40
+ deferredInsetTypes = WindowInsetsCompat.Type.ime(),
41
+ dispatchMode = WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE,
42
+ hasTranslucentNavigationBar = isNavigationBarTranslucent,
43
+ )
44
+
45
+ // managers/watchers
46
+ private val modalAttachedWatcher = ModalAttachedWatcher(this, reactContext, ::config)
35
47
 
36
48
  init {
37
49
  reactContext.setupWindowDimensionsListener()
50
+ tag = VIEW_TAG
38
51
  }
39
52
 
40
53
  // region View life cycles
@@ -122,11 +135,9 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
122
135
 
123
136
  callback = KeyboardAnimationCallback(
124
137
  view = this,
125
- persistentInsetTypes = WindowInsetsCompat.Type.systemBars(),
126
- deferredInsetTypes = WindowInsetsCompat.Type.ime(),
127
- dispatchMode = WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE,
138
+ eventPropagationView = this,
128
139
  context = reactContext,
129
- hasTranslucentNavigationBar = isNavigationBarTranslucent,
140
+ config = config,
130
141
  )
131
142
 
132
143
  eventView?.let {
@@ -160,12 +171,14 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
160
171
  this.goToEdgeToEdge(true)
161
172
  this.setupWindowInsets()
162
173
  this.setupKeyboardCallbacks()
174
+ modalAttachedWatcher.enable()
163
175
  }
164
176
 
165
177
  private fun disable() {
166
178
  this.goToEdgeToEdge(false)
167
179
  this.setupWindowInsets()
168
180
  this.removeKeyboardCallbacks()
181
+ modalAttachedWatcher.disable()
169
182
  }
170
183
  // endregion
171
184
 
@@ -188,4 +201,18 @@ class EdgeToEdgeReactViewGroup(private val reactContext: ThemedReactContext) : R
188
201
  }
189
202
  }
190
203
  // endregion
204
+
205
+ // region external methods
206
+ fun forceStatusBarTranslucent(isStatusBarTranslucent: Boolean) {
207
+ if (active && this.isStatusBarTranslucent != isStatusBarTranslucent) {
208
+ this.isStatusBarTranslucent = isStatusBarTranslucent
209
+ this.setupWindowInsets()
210
+ this.requestApplyInsetsWhenAttached()
211
+ }
212
+ }
213
+ // endregion
214
+
215
+ companion object {
216
+ val VIEW_TAG = EdgeToEdgeReactViewGroup::class.simpleName
217
+ }
191
218
  }
@@ -12,6 +12,7 @@ import androidx.core.view.WindowInsetsCompat
12
12
  import com.facebook.react.uimanager.ThemedReactContext
13
13
  import com.facebook.react.views.view.ReactViewGroup
14
14
  import com.reactnativekeyboardcontroller.extensions.copyBoundsInWindow
15
+ import com.reactnativekeyboardcontroller.extensions.px
15
16
  import com.reactnativekeyboardcontroller.interactive.KeyboardAnimationController
16
17
  import com.reactnativekeyboardcontroller.interactive.interpolators.Interpolator
17
18
  import com.reactnativekeyboardcontroller.interactive.interpolators.IosInterpolator
@@ -35,6 +36,7 @@ class KeyboardGestureAreaReactViewGroup(private val reactContext: ThemedReactCon
35
36
  private var keyboardHeight = 0
36
37
 
37
38
  // react props
39
+ private var offset = 0
38
40
  private var interpolator: Interpolator = LinearInterpolator()
39
41
  private var scrollKeyboardOnScreenWhenNotVisible = false
40
42
  private var scrollKeyboardOffScreenWhenVisible = true
@@ -63,6 +65,10 @@ class KeyboardGestureAreaReactViewGroup(private val reactContext: ThemedReactCon
63
65
  }
64
66
 
65
67
  // region Props setters
68
+ fun setOffset(offset: Double) {
69
+ this.offset = offset.toFloat().px.toInt()
70
+ }
71
+
66
72
  fun setInterpolator(interpolator: String) {
67
73
  this.interpolator = interpolators[interpolator] ?: LinearInterpolator()
68
74
  }
@@ -125,6 +131,7 @@ class KeyboardGestureAreaReactViewGroup(private val reactContext: ThemedReactCon
125
131
  dy.roundToInt(),
126
132
  this.getWindowHeight() - event.rawY.toInt(),
127
133
  controller.getCurrentKeyboardHeight(),
134
+ offset,
128
135
  )
129
136
 
130
137
  if (moveBy != 0) {
@@ -16,6 +16,11 @@ class KeyboardGestureAreaViewManager(mReactContext: ReactApplicationContext) : R
16
16
  return manager.createViewInstance(reactContext)
17
17
  }
18
18
 
19
+ @ReactProp(name = "offset")
20
+ fun setInterpolator(view: KeyboardGestureAreaReactViewGroup, offset: Double) {
21
+ manager.setOffset(view, offset)
22
+ }
23
+
19
24
  @ReactProp(name = "interpolator")
20
25
  fun setInterpolator(view: KeyboardGestureAreaReactViewGroup, interpolator: String) {
21
26
  manager.setInterpolator(view, interpolator)
@@ -17,7 +17,7 @@ protocol KeyboardAnimationProtocol {
17
17
  }
18
18
 
19
19
  public class KeyboardAnimation: KeyboardAnimationProtocol {
20
- private weak var animation: CAMediaTiming?
20
+ weak var animation: CAMediaTiming?
21
21
 
22
22
  // constructor variables
23
23
  let fromValue: Double