react-native-keyboard-controller 1.21.3 → 1.21.5

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 (54) hide show
  1. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +2 -0
  2. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ReactContext.kt +10 -0
  3. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/View.kt +0 -40
  4. package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/WindowDimensionListener.kt +1 -0
  5. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardControllerViewManagerImpl.kt +5 -1
  6. package/android/src/main/java/com/reactnativekeyboardcontroller/modules/KeyboardControllerModuleImpl.kt +10 -10
  7. package/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt +19 -22
  8. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +2 -0
  9. package/ios/KeyboardControllerModule.mm +12 -0
  10. package/ios/observers/FocusedInputObserver.swift +9 -2
  11. package/ios/views/KeyboardExtenderContainerView.swift +14 -4
  12. package/lib/commonjs/bindings.js +4 -1
  13. package/lib/commonjs/bindings.js.map +1 -1
  14. package/lib/commonjs/components/KeyboardAwareScrollView/index.js +47 -10
  15. package/lib/commonjs/components/KeyboardAwareScrollView/index.js.map +1 -1
  16. package/lib/commonjs/components/KeyboardAwareScrollView/types.js.map +1 -1
  17. package/lib/commonjs/components/KeyboardToolbar/constants.js +2 -2
  18. package/lib/commonjs/components/KeyboardToolbar/constants.js.map +1 -1
  19. package/lib/commonjs/components/index.js.map +1 -1
  20. package/lib/commonjs/constants.js +3 -1
  21. package/lib/commonjs/constants.js.map +1 -1
  22. package/lib/commonjs/index.js.map +1 -1
  23. package/lib/commonjs/specs/NativeKeyboardController.js.map +1 -1
  24. package/lib/commonjs/types/module.js.map +1 -1
  25. package/lib/module/bindings.js +4 -1
  26. package/lib/module/bindings.js.map +1 -1
  27. package/lib/module/components/KeyboardAwareScrollView/index.js +48 -11
  28. package/lib/module/components/KeyboardAwareScrollView/index.js.map +1 -1
  29. package/lib/module/components/KeyboardAwareScrollView/types.js.map +1 -1
  30. package/lib/module/components/KeyboardToolbar/constants.js +2 -2
  31. package/lib/module/components/KeyboardToolbar/constants.js.map +1 -1
  32. package/lib/module/components/index.js.map +1 -1
  33. package/lib/module/constants.js +3 -0
  34. package/lib/module/constants.js.map +1 -1
  35. package/lib/module/index.js.map +1 -1
  36. package/lib/module/specs/NativeKeyboardController.js.map +1 -1
  37. package/lib/module/types/module.js.map +1 -1
  38. package/lib/typescript/components/KeyboardAwareScrollView/index.d.ts +1 -0
  39. package/lib/typescript/components/KeyboardAwareScrollView/types.d.ts +10 -0
  40. package/lib/typescript/components/index.d.ts +1 -1
  41. package/lib/typescript/constants.d.ts +1 -0
  42. package/lib/typescript/index.d.ts +1 -1
  43. package/lib/typescript/specs/NativeKeyboardController.d.ts +3 -1
  44. package/lib/typescript/types/module.d.ts +3 -0
  45. package/package.json +1 -1
  46. package/src/bindings.ts +3 -0
  47. package/src/components/KeyboardAwareScrollView/index.tsx +79 -29
  48. package/src/components/KeyboardAwareScrollView/types.ts +11 -0
  49. package/src/components/KeyboardToolbar/constants.ts +2 -3
  50. package/src/components/index.ts +1 -0
  51. package/src/constants.ts +4 -0
  52. package/src/index.ts +1 -0
  53. package/src/specs/NativeKeyboardController.ts +3 -1
  54. package/src/types/module.ts +4 -0
@@ -11,6 +11,8 @@ class KeyboardControllerModule(
11
11
 
12
12
  override fun getName(): String = KeyboardControllerModuleImpl.NAME
13
13
 
14
+ override fun getTypedExportedConstants(): Map<String, Any> = module.getConstants()
15
+
14
16
  override fun setInputMode(mode: Double) {
15
17
  module.setInputMode(mode.toInt())
16
18
  }
@@ -2,6 +2,7 @@ package com.reactnativekeyboardcontroller.extensions
2
2
 
3
3
  import android.view.View
4
4
  import android.view.ViewGroup
5
+ import android.view.WindowManager
5
6
  import com.facebook.react.bridge.ReactContext
6
7
  import com.facebook.react.uimanager.UIManagerHelper
7
8
  import com.facebook.react.uimanager.common.UIManagerType
@@ -28,3 +29,12 @@ val ReactContext.content: ViewGroup?
28
29
  this.currentActivity?.window?.decorView?.rootView?.findViewById(
29
30
  androidx.appcompat.R.id.action_bar_root,
30
31
  )
32
+
33
+ val ReactContext.windowSoftInputMode: Int
34
+ get() =
35
+ this
36
+ .currentActivity
37
+ ?.window
38
+ ?.attributes
39
+ ?.softInputMode
40
+ ?: WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
@@ -4,9 +4,7 @@ import android.annotation.SuppressLint
4
4
  import android.graphics.Rect
5
5
  import android.os.Build
6
6
  import android.view.View
7
- import androidx.core.graphics.Insets
8
7
  import androidx.core.view.ViewCompat
9
- import androidx.core.view.WindowInsetsCompat
10
8
  import com.reactnativekeyboardcontroller.log.Logger
11
9
 
12
10
  /**
@@ -61,41 +59,3 @@ val View.screenLocation get(): IntArray {
61
59
 
62
60
  return point
63
61
  }
64
-
65
- /**
66
- * Safely replaces status bar insets so that when we edge-to-edge mode gets disabled/enabled
67
- * the app content is not jumping/resizing a window.
68
- * */
69
- @Suppress("DEPRECATION")
70
- fun View.replaceStatusBarInsets(
71
- insets: WindowInsetsCompat,
72
- isStatusBarTranslucent: Boolean,
73
- active: Boolean,
74
- ): WindowInsetsCompat {
75
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
76
- val sysBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
77
- val navBars = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
78
- val ime = insets.getInsets(WindowInsetsCompat.Type.ime())
79
- val adjustedTop = if (isStatusBarTranslucent) 0 else sysBars.top
80
- // pick bottom: use IME if present, otherwise nav bar bottom (respect translucency)
81
- val bottomFromImeOrNav = if (ime.bottom > 0) ime.bottom else navBars.bottom
82
- val adjustedInsets =
83
- WindowInsetsCompat
84
- .Builder(insets)
85
- .setInsets(
86
- WindowInsetsCompat.Type.systemBars(),
87
- Insets.of(sysBars.left, adjustedTop, sysBars.right, if (active) sysBars.bottom else bottomFromImeOrNav),
88
- ).build()
89
-
90
- return ViewCompat.onApplyWindowInsets(this, adjustedInsets)
91
- } else {
92
- val defaultInsets = ViewCompat.onApplyWindowInsets(this, insets)
93
-
94
- return defaultInsets.replaceSystemWindowInsets(
95
- defaultInsets.systemWindowInsetLeft,
96
- if (isStatusBarTranslucent) 0 else defaultInsets.systemWindowInsetTop,
97
- defaultInsets.systemWindowInsetRight,
98
- defaultInsets.systemWindowInsetBottom,
99
- )
100
- }
101
- }
@@ -43,6 +43,7 @@ class WindowDimensionListener(
43
43
  attachedContent?.viewTreeObserver?.removeOnGlobalLayoutListener(layoutListener)
44
44
  attachedContent = null
45
45
  layoutListener = null
46
+ listenerID = -1
46
47
  }
47
48
 
48
49
  private fun updateWindowDimensions(content: ViewGroup?) {
@@ -13,11 +13,14 @@ import com.reactnativekeyboardcontroller.views.EdgeToEdgeReactViewGroup
13
13
 
14
14
  class KeyboardControllerViewManagerImpl {
15
15
  private var listener: WindowDimensionListener? = null
16
+ private var listenerContext: ThemedReactContext? = null
16
17
 
17
18
  fun createViewInstance(reactContext: ThemedReactContext): EdgeToEdgeReactViewGroup {
18
- if (listener == null) {
19
+ if (listener == null || listenerContext !== reactContext) {
20
+ listener?.detachListener()
19
21
  listener = WindowDimensionListener(reactContext)
20
22
  listener?.attachListener()
23
+ listenerContext = reactContext
21
24
  }
22
25
  return EdgeToEdgeReactViewGroup(reactContext)
23
26
  }
@@ -25,6 +28,7 @@ class KeyboardControllerViewManagerImpl {
25
28
  fun invalidate() {
26
29
  listener?.detachListener()
27
30
  listener = null
31
+ listenerContext = null
28
32
  }
29
33
 
30
34
  fun synchronizeFocusedInputLayout(view: EdgeToEdgeReactViewGroup) {
@@ -3,7 +3,6 @@ package com.reactnativekeyboardcontroller.modules
3
3
  import android.content.Context
4
4
  import android.os.Build
5
5
  import android.view.View
6
- import android.view.WindowManager
7
6
  import android.view.inputmethod.InputMethodManager
8
7
  import com.facebook.react.bridge.Arguments
9
8
  import com.facebook.react.bridge.Promise
@@ -12,6 +11,7 @@ import com.facebook.react.bridge.UiThreadUtil
12
11
  import com.reactnativekeyboardcontroller.extensions.dp
13
12
  import com.reactnativekeyboardcontroller.extensions.screenLocation
14
13
  import com.reactnativekeyboardcontroller.extensions.uiManager
14
+ import com.reactnativekeyboardcontroller.extensions.windowSoftInputMode
15
15
  import com.reactnativekeyboardcontroller.interactive.KeyboardAnimationController
16
16
  import com.reactnativekeyboardcontroller.traversal.FocusedInputHolder
17
17
  import com.reactnativekeyboardcontroller.traversal.ViewHierarchyNavigator
@@ -21,8 +21,9 @@ class KeyboardControllerModuleImpl(
21
21
  ) {
22
22
  private val uiManager = mReactContext.uiManager
23
23
  private val controller = KeyboardAnimationController()
24
- private val mDefaultMode: Int = getCurrentMode()
24
+ private val mDefaultMode: Int = mReactContext.windowSoftInputMode
25
25
 
26
+ // region Module methods
26
27
  fun setInputMode(mode: Int) {
27
28
  setSoftInputMode(mode)
28
29
  }
@@ -102,24 +103,23 @@ class KeyboardControllerModuleImpl(
102
103
  promise.resolve(map)
103
104
  }
104
105
  }
106
+ // endregion
105
107
 
108
+ // region Helpers
106
109
  private fun setSoftInputMode(mode: Int) {
107
110
  UiThreadUtil.runOnUiThread {
108
- if (getCurrentMode() != mode) {
111
+ if (mReactContext.windowSoftInputMode != mode) {
109
112
  mReactContext.currentActivity?.window?.setSoftInputMode(mode)
110
113
  }
111
114
  }
112
115
  }
116
+ // endregion
113
117
 
114
- private fun getCurrentMode(): Int =
115
- mReactContext
116
- .currentActivity
117
- ?.window
118
- ?.attributes
119
- ?.softInputMode
120
- ?: WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
118
+ // region Module constants
119
+ fun getConstants(): MutableMap<String, Any> = mutableMapOf("keyboardBorderRadius" to 0)
121
120
 
122
121
  companion object {
123
122
  const val NAME = "KeyboardController"
124
123
  }
124
+ // endregion
125
125
  }
@@ -5,6 +5,7 @@ import android.content.res.Configuration
5
5
  import android.os.Handler
6
6
  import android.os.Looper
7
7
  import android.view.WindowManager
8
+ import android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
8
9
  import android.widget.FrameLayout
9
10
  import androidx.core.view.ViewCompat
10
11
  import androidx.core.view.WindowCompat
@@ -14,14 +15,15 @@ import com.facebook.react.uimanager.ThemedReactContext
14
15
  import com.facebook.react.views.view.ReactViewGroup
15
16
  import com.reactnativekeyboardcontroller.extensions.content
16
17
  import com.reactnativekeyboardcontroller.extensions.removeSelf
17
- import com.reactnativekeyboardcontroller.extensions.replaceStatusBarInsets
18
18
  import com.reactnativekeyboardcontroller.extensions.requestApplyInsetsWhenAttached
19
19
  import com.reactnativekeyboardcontroller.extensions.rootView
20
+ import com.reactnativekeyboardcontroller.extensions.windowSoftInputMode
20
21
  import com.reactnativekeyboardcontroller.listeners.KeyboardAnimationCallback
21
22
  import com.reactnativekeyboardcontroller.listeners.KeyboardAnimationCallbackConfig
22
23
  import com.reactnativekeyboardcontroller.log.Logger
23
24
  import com.reactnativekeyboardcontroller.modal.ModalAttachedWatcher
24
25
  import java.lang.ref.WeakReference
26
+ import kotlin.math.max
25
27
 
26
28
  private val TAG = EdgeToEdgeReactViewGroup::class.qualifiedName
27
29
 
@@ -44,7 +46,6 @@ class EdgeToEdgeReactViewGroup(
44
46
  private var isStatusBarTranslucent = false
45
47
  private var isNavigationBarTranslucent = false
46
48
  private var isPreservingEdgeToEdge = false
47
- private var isEdgeToEdge = false
48
49
  var active: Boolean = false
49
50
  set(value) {
50
51
  field = value
@@ -110,47 +111,43 @@ class EdgeToEdgeReactViewGroup(
110
111
  FrameLayout.LayoutParams.MATCH_PARENT,
111
112
  )
112
113
 
113
- val shouldApplyZeroPaddingTop = !active || this.isStatusBarTranslucent
114
- val shouldApplyZeroPaddingBottom = !active || this.isNavigationBarTranslucent
114
+ val shouldApplyBottomPadding =
115
+ !active && reactContext.windowSoftInputMode == SOFT_INPUT_ADJUST_RESIZE && !isPreservingEdgeToEdge
115
116
  val navBarInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
116
117
  val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
118
+ val keyboardInsets =
119
+ if (!shouldApplyBottomPadding) 0 else insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
117
120
 
118
121
  params.setMargins(
119
122
  navBarInsets.left,
120
- if (shouldApplyZeroPaddingTop) {
123
+ if (this.isStatusBarTranslucent) {
121
124
  0
122
125
  } else {
123
126
  systemBarInsets.top
124
127
  },
125
128
  navBarInsets.right,
126
- if (shouldApplyZeroPaddingBottom) {
127
- 0
129
+ if (this.isNavigationBarTranslucent) {
130
+ keyboardInsets
128
131
  } else {
129
- navBarInsets.bottom
132
+ max(navBarInsets.bottom, keyboardInsets)
130
133
  },
131
134
  )
132
135
  content?.layoutParams = params
133
136
 
134
- v.replaceStatusBarInsets(insets, this.isStatusBarTranslucent, active)
137
+ ViewCompat.onApplyWindowInsets(v, insets)
135
138
  }
136
139
  }
137
140
  }
138
141
 
139
142
  fun setEdgeToEdge() {
140
- val nextValue = active || isPreservingEdgeToEdge
141
-
142
- if (isEdgeToEdge != nextValue) {
143
- isEdgeToEdge = nextValue
144
-
145
- reactContext.currentActivity?.let {
146
- WindowCompat.setDecorFitsSystemWindows(
147
- it.window,
148
- !isEdgeToEdge,
149
- )
150
- }
151
- // unclear legacy flag if it was set earlier
152
- reactContext.currentActivity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
143
+ reactContext.currentActivity?.let {
144
+ WindowCompat.setDecorFitsSystemWindows(
145
+ it.window,
146
+ false,
147
+ )
153
148
  }
149
+ // unclear legacy flag if it was set earlier
150
+ reactContext.currentActivity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
154
151
  }
155
152
 
156
153
  private fun setupKeyboardCallbacks() {
@@ -13,6 +13,8 @@ class KeyboardControllerModule(
13
13
 
14
14
  override fun getName(): String = KeyboardControllerModuleImpl.NAME
15
15
 
16
+ override fun getConstants(): MutableMap<String, Any> = module.getConstants()
17
+
16
18
  @ReactMethod
17
19
  fun setInputMode(mode: Int) {
18
20
  module.setInputMode(mode)
@@ -53,6 +53,18 @@ RCT_EXPORT_MODULE()
53
53
  return NO;
54
54
  }
55
55
 
56
+ - (NSDictionary *)constantsToExport
57
+ {
58
+ return @{
59
+ @"keyboardBorderRadius" : @([KeyboardExtenderContainerView keyboardBorderRadius]),
60
+ };
61
+ }
62
+
63
+ - (NSDictionary *)getConstants
64
+ {
65
+ return [self constantsToExport];
66
+ }
67
+
56
68
  #ifdef RCT_NEW_ARCH_ENABLED
57
69
  - (void)setDefaultMode
58
70
  #else
@@ -112,9 +112,16 @@ public class FocusedInputObserver: NSObject {
112
112
  NotificationCenter.default.removeObserver(self)
113
113
  }
114
114
 
115
- @objc func didReceiveFocus(_: Notification) {
115
+ @objc func didReceiveFocus(_ notification: Notification) {
116
116
  if UIResponder.current == currentResponder {
117
- // focus was already handled by keyboard event
117
+ // The same input is still focused — no need to re-run the full onFocus()
118
+ // setup (observers, delegate substitution, focusDidSet event). However,
119
+ // keyboardWillShowNotification also fires when the keyboard *resizes*
120
+ // (e.g. switching between text and emoji keyboards). In that case we must
121
+ // refresh the layout so consumers receive an up-to-date absoluteY.
122
+ if notification.name == UIResponder.keyboardWillShowNotification {
123
+ syncUpLayout()
124
+ }
118
125
  return
119
126
  }
120
127
 
@@ -9,16 +9,26 @@ import UIKit
9
9
 
10
10
  @objc
11
11
  public class KeyboardExtenderContainerView: NSObject {
12
- @objc public static func create(frame: CGRect, contentView: UIView) -> UIView {
12
+ private static func usesModernKeyboard() -> Bool {
13
13
  #if canImport(UIKit.UIGlassEffect)
14
14
  if #available(iOS 26.0, *) {
15
15
  let requiresCompat = Bundle.main.object(forInfoDictionaryKey: "UIDesignRequiresCompatibility") as? Bool ?? false
16
- if !requiresCompat {
17
- return ModernContainerView(frame: frame, contentView: contentView)
18
- }
16
+ return !requiresCompat
19
17
  }
20
18
  #endif
19
+ return false
20
+ }
21
+
22
+ @objc public static func keyboardBorderRadius() -> CGFloat {
23
+ return usesModernKeyboard() ? 30 : 0
24
+ }
21
25
 
26
+ @objc public static func create(frame: CGRect, contentView: UIView) -> UIView {
27
+ if usesModernKeyboard() {
28
+ if #available(iOS 26.0, *) {
29
+ return ModernContainerView(frame: frame, contentView: contentView)
30
+ }
31
+ }
22
32
  return LegacyContainerView(frame: frame, contentView: contentView)
23
33
  }
24
34
  }
@@ -19,7 +19,10 @@ const KeyboardControllerNative = exports.KeyboardControllerNative = {
19
19
  height: 0
20
20
  }),
21
21
  addListener: NOOP,
22
- removeListeners: NOOP
22
+ removeListeners: NOOP,
23
+ getConstants: () => ({
24
+ keyboardBorderRadius: 0
25
+ })
23
26
  };
24
27
  /**
25
28
  * An event emitter that provides a way to subscribe to next keyboard events:
@@ -1 +1 @@
1
- {"version":3,"names":["_reactNative","require","NOOP","KeyboardControllerNative","exports","setDefaultMode","setInputMode","preload","dismiss","setFocusTo","viewPositionInWindow","Promise","resolve","x","y","width","height","addListener","removeListeners","KeyboardEvents","remove","FocusedInputEvents","WindowDimensionsEvents","KeyboardControllerView","View","KeyboardControllerViewCommands","synchronizeFocusedInputLayout","_ref","KeyboardGestureArea","RCTOverKeyboardView","KeyboardBackgroundView","RCTKeyboardExtender","ClippingScrollView","RCTKeyboardToolbarGroupView"],"sources":["bindings.ts"],"sourcesContent":["import { View } from \"react-native\";\n\nimport type {\n ClippingScrollViewProps,\n FocusedInputEventsModule,\n KeyboardBackgroundViewProps,\n KeyboardControllerNativeModule,\n KeyboardControllerProps,\n KeyboardEventsModule,\n KeyboardExtenderProps,\n KeyboardGestureAreaProps,\n KeyboardToolbarGroupViewProps,\n OverKeyboardViewProps,\n WindowDimensionsEventsModule,\n} from \"./types\";\nimport type { EmitterSubscription } from \"react-native\";\n\nconst NOOP = () => {};\n\nexport const KeyboardControllerNative: KeyboardControllerNativeModule = {\n setDefaultMode: NOOP,\n setInputMode: NOOP,\n preload: NOOP,\n dismiss: NOOP,\n setFocusTo: NOOP,\n viewPositionInWindow: () =>\n Promise.resolve({ x: 0, y: 0, width: 0, height: 0 }),\n addListener: NOOP,\n removeListeners: NOOP,\n};\n/**\n * An event emitter that provides a way to subscribe to next keyboard events:\n * - `keyboardWillShow`;\n * - `keyboardDidShow`;\n * - `keyboardWillHide`;\n * - `keyboardDidHide`.\n *\n * Use `addListener` function to add your event listener for a specific keyboard event.\n */\nexport const KeyboardEvents: KeyboardEventsModule = {\n addListener: () => ({ remove: NOOP } as EmitterSubscription),\n};\n/**\n * This API is not documented, it's for internal usage only (for now), and is a subject to potential breaking changes in future.\n * Use it with cautious.\n */\nexport const FocusedInputEvents: FocusedInputEventsModule = {\n addListener: () => ({ remove: NOOP } as EmitterSubscription),\n};\nexport const WindowDimensionsEvents: WindowDimensionsEventsModule = {\n addListener: () => ({ remove: NOOP } as EmitterSubscription),\n};\n/**\n * A view that sends events whenever keyboard or focused events are happening.\n *\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller-view|Documentation} page for more details.\n */\nexport const KeyboardControllerView =\n View as unknown as React.FC<KeyboardControllerProps>;\nexport const KeyboardControllerViewCommands = {\n synchronizeFocusedInputLayout: (\n _ref: React.Component<KeyboardControllerProps> | null,\n ) => {},\n};\n/**\n * A view that defines a region on the screen, where gestures will control the keyboard position.\n *\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-gesture-area|Documentation} page for more details.\n */\nexport const KeyboardGestureArea =\n View as unknown as React.FC<KeyboardGestureAreaProps>;\nexport const RCTOverKeyboardView =\n View as unknown as React.FC<OverKeyboardViewProps>;\n/**\n * A view that matches keyboard background.\n *\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-background-view|Documentation} page for more details.\n */\nexport const KeyboardBackgroundView =\n View as unknown as React.FC<KeyboardBackgroundViewProps>;\n/**\n * A container that will embed its children into the keyboard\n * and will always show them above the keyboard.\n *\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-extender|Documentation} page for more details.\n */\nexport const RCTKeyboardExtender =\n View as unknown as React.FC<KeyboardExtenderProps>;\n/**\n * A decorator that will clip the content of the `ScrollView`. It helps to simulate `contentInset` behavior on Android\n * Supports only `bottom` property (`paddingBottom` is not supported property of `ScrollView.style`).\n * Using this component we can modify bottom inset without having a fake view.\n *\n * On iOS we use swizzling to apply runtime patches to fix some broken internal methods.\n * Ideally this component shouldn't exist and all its fixes/polyfills must be added directly to react-native and\n * we will port features/fixes back to upstream, but at the moment we use this view to\n * deliver desired functionality regardless of react-native version used.\n */\nexport const ClippingScrollView =\n View as unknown as React.FC<ClippingScrollViewProps>;\n/**\n * A View that defines a group of `TextInput`s.\n * Used in toolbar navigation to assure that you can navigate only between inputs withing the same group.\n */\nexport const RCTKeyboardToolbarGroupView =\n View as unknown as React.FC<KeyboardToolbarGroupViewProps>;\n"],"mappings":";;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAiBA,MAAMC,IAAI,GAAGA,CAAA,KAAM,CAAC,CAAC;AAEd,MAAMC,wBAAwD,GAAAC,OAAA,CAAAD,wBAAA,GAAG;EACtEE,cAAc,EAAEH,IAAI;EACpBI,YAAY,EAAEJ,IAAI;EAClBK,OAAO,EAAEL,IAAI;EACbM,OAAO,EAAEN,IAAI;EACbO,UAAU,EAAEP,IAAI;EAChBQ,oBAAoB,EAAEA,CAAA,KACpBC,OAAO,CAACC,OAAO,CAAC;IAAEC,CAAC,EAAE,CAAC;IAAEC,CAAC,EAAE,CAAC;IAAEC,KAAK,EAAE,CAAC;IAAEC,MAAM,EAAE;EAAE,CAAC,CAAC;EACtDC,WAAW,EAAEf,IAAI;EACjBgB,eAAe,EAAEhB;AACnB,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAMiB,cAAoC,GAAAf,OAAA,CAAAe,cAAA,GAAG;EAClDF,WAAW,EAAEA,CAAA,MAAO;IAAEG,MAAM,EAAElB;EAAK,CAAC;AACtC,CAAC;AACD;AACA;AACA;AACA;AACO,MAAMmB,kBAA4C,GAAAjB,OAAA,CAAAiB,kBAAA,GAAG;EAC1DJ,WAAW,EAAEA,CAAA,MAAO;IAAEG,MAAM,EAAElB;EAAK,CAAC;AACtC,CAAC;AACM,MAAMoB,sBAAoD,GAAAlB,OAAA,CAAAkB,sBAAA,GAAG;EAClEL,WAAW,EAAEA,CAAA,MAAO;IAAEG,MAAM,EAAElB;EAAK,CAAC;AACtC,CAAC;AACD;AACA;AACA;AACA;AACA;AACO,MAAMqB,sBAAsB,GAAAnB,OAAA,CAAAmB,sBAAA,GACjCC,iBAAoD;AAC/C,MAAMC,8BAA8B,GAAArB,OAAA,CAAAqB,8BAAA,GAAG;EAC5CC,6BAA6B,EAC3BC,IAAqD,IAClD,CAAC;AACR,CAAC;AACD;AACA;AACA;AACA;AACA;AACO,MAAMC,mBAAmB,GAAAxB,OAAA,CAAAwB,mBAAA,GAC9BJ,iBAAqD;AAChD,MAAMK,mBAAmB,GAAAzB,OAAA,CAAAyB,mBAAA,GAC9BL,iBAAkD;AACpD;AACA;AACA;AACA;AACA;AACO,MAAMM,sBAAsB,GAAA1B,OAAA,CAAA0B,sBAAA,GACjCN,iBAAwD;AAC1D;AACA;AACA;AACA;AACA;AACA;AACO,MAAMO,mBAAmB,GAAA3B,OAAA,CAAA2B,mBAAA,GAC9BP,iBAAkD;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAMQ,kBAAkB,GAAA5B,OAAA,CAAA4B,kBAAA,GAC7BR,iBAAoD;AACtD;AACA;AACA;AACA;AACO,MAAMS,2BAA2B,GAAA7B,OAAA,CAAA6B,2BAAA,GACtCT,iBAA0D","ignoreList":[]}
1
+ {"version":3,"names":["_reactNative","require","NOOP","KeyboardControllerNative","exports","setDefaultMode","setInputMode","preload","dismiss","setFocusTo","viewPositionInWindow","Promise","resolve","x","y","width","height","addListener","removeListeners","getConstants","keyboardBorderRadius","KeyboardEvents","remove","FocusedInputEvents","WindowDimensionsEvents","KeyboardControllerView","View","KeyboardControllerViewCommands","synchronizeFocusedInputLayout","_ref","KeyboardGestureArea","RCTOverKeyboardView","KeyboardBackgroundView","RCTKeyboardExtender","ClippingScrollView","RCTKeyboardToolbarGroupView"],"sources":["bindings.ts"],"sourcesContent":["import { View } from \"react-native\";\n\nimport type {\n ClippingScrollViewProps,\n FocusedInputEventsModule,\n KeyboardBackgroundViewProps,\n KeyboardControllerNativeModule,\n KeyboardControllerProps,\n KeyboardEventsModule,\n KeyboardExtenderProps,\n KeyboardGestureAreaProps,\n KeyboardToolbarGroupViewProps,\n OverKeyboardViewProps,\n WindowDimensionsEventsModule,\n} from \"./types\";\nimport type { EmitterSubscription } from \"react-native\";\n\nconst NOOP = () => {};\n\nexport const KeyboardControllerNative: KeyboardControllerNativeModule = {\n setDefaultMode: NOOP,\n setInputMode: NOOP,\n preload: NOOP,\n dismiss: NOOP,\n setFocusTo: NOOP,\n viewPositionInWindow: () =>\n Promise.resolve({ x: 0, y: 0, width: 0, height: 0 }),\n addListener: NOOP,\n removeListeners: NOOP,\n getConstants: () => ({\n keyboardBorderRadius: 0,\n }),\n};\n/**\n * An event emitter that provides a way to subscribe to next keyboard events:\n * - `keyboardWillShow`;\n * - `keyboardDidShow`;\n * - `keyboardWillHide`;\n * - `keyboardDidHide`.\n *\n * Use `addListener` function to add your event listener for a specific keyboard event.\n */\nexport const KeyboardEvents: KeyboardEventsModule = {\n addListener: () => ({ remove: NOOP } as EmitterSubscription),\n};\n/**\n * This API is not documented, it's for internal usage only (for now), and is a subject to potential breaking changes in future.\n * Use it with cautious.\n */\nexport const FocusedInputEvents: FocusedInputEventsModule = {\n addListener: () => ({ remove: NOOP } as EmitterSubscription),\n};\nexport const WindowDimensionsEvents: WindowDimensionsEventsModule = {\n addListener: () => ({ remove: NOOP } as EmitterSubscription),\n};\n/**\n * A view that sends events whenever keyboard or focused events are happening.\n *\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller-view|Documentation} page for more details.\n */\nexport const KeyboardControllerView =\n View as unknown as React.FC<KeyboardControllerProps>;\nexport const KeyboardControllerViewCommands = {\n synchronizeFocusedInputLayout: (\n _ref: React.Component<KeyboardControllerProps> | null,\n ) => {},\n};\n/**\n * A view that defines a region on the screen, where gestures will control the keyboard position.\n *\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-gesture-area|Documentation} page for more details.\n */\nexport const KeyboardGestureArea =\n View as unknown as React.FC<KeyboardGestureAreaProps>;\nexport const RCTOverKeyboardView =\n View as unknown as React.FC<OverKeyboardViewProps>;\n/**\n * A view that matches keyboard background.\n *\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-background-view|Documentation} page for more details.\n */\nexport const KeyboardBackgroundView =\n View as unknown as React.FC<KeyboardBackgroundViewProps>;\n/**\n * A container that will embed its children into the keyboard\n * and will always show them above the keyboard.\n *\n * @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-extender|Documentation} page for more details.\n */\nexport const RCTKeyboardExtender =\n View as unknown as React.FC<KeyboardExtenderProps>;\n/**\n * A decorator that will clip the content of the `ScrollView`. It helps to simulate `contentInset` behavior on Android\n * Supports only `bottom` property (`paddingBottom` is not supported property of `ScrollView.style`).\n * Using this component we can modify bottom inset without having a fake view.\n *\n * On iOS we use swizzling to apply runtime patches to fix some broken internal methods.\n * Ideally this component shouldn't exist and all its fixes/polyfills must be added directly to react-native and\n * we will port features/fixes back to upstream, but at the moment we use this view to\n * deliver desired functionality regardless of react-native version used.\n */\nexport const ClippingScrollView =\n View as unknown as React.FC<ClippingScrollViewProps>;\n/**\n * A View that defines a group of `TextInput`s.\n * Used in toolbar navigation to assure that you can navigate only between inputs withing the same group.\n */\nexport const RCTKeyboardToolbarGroupView =\n View as unknown as React.FC<KeyboardToolbarGroupViewProps>;\n"],"mappings":";;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAiBA,MAAMC,IAAI,GAAGA,CAAA,KAAM,CAAC,CAAC;AAEd,MAAMC,wBAAwD,GAAAC,OAAA,CAAAD,wBAAA,GAAG;EACtEE,cAAc,EAAEH,IAAI;EACpBI,YAAY,EAAEJ,IAAI;EAClBK,OAAO,EAAEL,IAAI;EACbM,OAAO,EAAEN,IAAI;EACbO,UAAU,EAAEP,IAAI;EAChBQ,oBAAoB,EAAEA,CAAA,KACpBC,OAAO,CAACC,OAAO,CAAC;IAAEC,CAAC,EAAE,CAAC;IAAEC,CAAC,EAAE,CAAC;IAAEC,KAAK,EAAE,CAAC;IAAEC,MAAM,EAAE;EAAE,CAAC,CAAC;EACtDC,WAAW,EAAEf,IAAI;EACjBgB,eAAe,EAAEhB,IAAI;EACrBiB,YAAY,EAAEA,CAAA,MAAO;IACnBC,oBAAoB,EAAE;EACxB,CAAC;AACH,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAMC,cAAoC,GAAAjB,OAAA,CAAAiB,cAAA,GAAG;EAClDJ,WAAW,EAAEA,CAAA,MAAO;IAAEK,MAAM,EAAEpB;EAAK,CAAC;AACtC,CAAC;AACD;AACA;AACA;AACA;AACO,MAAMqB,kBAA4C,GAAAnB,OAAA,CAAAmB,kBAAA,GAAG;EAC1DN,WAAW,EAAEA,CAAA,MAAO;IAAEK,MAAM,EAAEpB;EAAK,CAAC;AACtC,CAAC;AACM,MAAMsB,sBAAoD,GAAApB,OAAA,CAAAoB,sBAAA,GAAG;EAClEP,WAAW,EAAEA,CAAA,MAAO;IAAEK,MAAM,EAAEpB;EAAK,CAAC;AACtC,CAAC;AACD;AACA;AACA;AACA;AACA;AACO,MAAMuB,sBAAsB,GAAArB,OAAA,CAAAqB,sBAAA,GACjCC,iBAAoD;AAC/C,MAAMC,8BAA8B,GAAAvB,OAAA,CAAAuB,8BAAA,GAAG;EAC5CC,6BAA6B,EAC3BC,IAAqD,IAClD,CAAC;AACR,CAAC;AACD;AACA;AACA;AACA;AACA;AACO,MAAMC,mBAAmB,GAAA1B,OAAA,CAAA0B,mBAAA,GAC9BJ,iBAAqD;AAChD,MAAMK,mBAAmB,GAAA3B,OAAA,CAAA2B,mBAAA,GAC9BL,iBAAkD;AACpD;AACA;AACA;AACA;AACA;AACO,MAAMM,sBAAsB,GAAA5B,OAAA,CAAA4B,sBAAA,GACjCN,iBAAwD;AAC1D;AACA;AACA;AACA;AACA;AACA;AACO,MAAMO,mBAAmB,GAAA7B,OAAA,CAAA6B,mBAAA,GAC9BP,iBAAkD;AACpD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAMQ,kBAAkB,GAAA9B,OAAA,CAAA8B,kBAAA,GAC7BR,iBAAoD;AACtD;AACA;AACA;AACA;AACO,MAAMS,2BAA2B,GAAA/B,OAAA,CAAA+B,2BAAA,GACtCT,iBAA0D","ignoreList":[]}
@@ -77,6 +77,7 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
77
77
  disableScrollOnKeyboardHide = false,
78
78
  enabled = true,
79
79
  extraKeyboardSpace = 0,
80
+ mode = "insets",
80
81
  ScrollViewComponent = _reactNativeReanimated.default.ScrollView,
81
82
  snapToOffsets,
82
83
  ...rest
@@ -162,15 +163,21 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
162
163
  const removeGhostPadding = (0, _react.useCallback)(e => {
163
164
  "worklet";
164
165
 
165
- // new `ScrollViewWithBottomPadding` behavior: if we hide keyboard and we are in the end of `ScrollView`
166
- // then we always need to scroll back, because we apply a padding that doesn't change layout, so we will
167
- // not have auto scroll back in this case
166
+ // layout mode: the spacer view participates in layout, so the ScrollView
167
+ // reflows naturally when it shrinks no manual scroll correction needed.
168
+ if (mode === "layout") {
169
+ return false;
170
+ }
171
+
172
+ // insets mode: `ScrollViewWithBottomPadding` extends scrollable area without
173
+ // changing layout, so when the keyboard hides and we're at the end of the
174
+ // ScrollView we must manually scroll back.
168
175
  if (!keyboardWillAppear.value && ghostViewSpace.value > 0) {
169
176
  (0, _reactNativeReanimated.scrollTo)(scrollViewAnimatedRef, 0, scrollPosition.value - (0, _reactNativeReanimated.interpolate)(e, [initialKeyboardSize.value, keyboardHeight.value], [ghostViewSpace.value, 0]), false);
170
177
  return true;
171
178
  }
172
179
  return false;
173
- }, []);
180
+ }, [mode]);
174
181
  const performScrollWithPositionRestoration = (0, _react.useCallback)(newPosition => {
175
182
  "worklet";
176
183
 
@@ -280,8 +287,14 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
280
287
  scrollPosition.value = position.value;
281
288
  // just persist height - later will be used in interpolation
282
289
  keyboardHeight.value = e.height;
283
- // and update keyboard spacer size
284
- syncKeyboardFrame(e);
290
+
291
+ // insets mode: set the full contentInset upfront so that maybeScroll
292
+ // calculations are correct from the very first onMove frame.
293
+ // layout mode: do NOT set it here — the spacer must grow frame-by-frame
294
+ // in onMove to avoid a premature full-height jump before the keyboard moves.
295
+ if (mode === "insets") {
296
+ syncKeyboardFrame(e);
297
+ }
285
298
  }
286
299
 
287
300
  // focus was changed
@@ -318,9 +331,11 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
318
331
  position.value += maybeScroll(e.height, true);
319
332
  }
320
333
  }
321
- ghostViewSpace.value = position.value + scrollViewLayout.value.height - scrollViewContentSize.value.height;
322
- if (ghostViewSpace.value > 0) {
323
- scrollPosition.value = position.value;
334
+ if (mode === "insets") {
335
+ ghostViewSpace.value = position.value + scrollViewLayout.value.height - scrollViewContentSize.value.height;
336
+ if (ghostViewSpace.value > 0) {
337
+ scrollPosition.value = position.value;
338
+ }
324
339
  }
325
340
  },
326
341
  onMove: e => {
@@ -330,6 +345,11 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
330
345
  return;
331
346
  }
332
347
 
348
+ // layout mode: drive the spacer view animation frame-by-frame
349
+ if (mode === "layout") {
350
+ syncKeyboardFrame(e);
351
+ }
352
+
333
353
  // if the user has set disableScrollOnKeyboardHide, only auto-scroll when the keyboard opens
334
354
  if (!disableScrollOnKeyboardHide || keyboardWillAppear.value) {
335
355
  maybeScroll(e.height);
@@ -353,7 +373,7 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
353
373
  }
354
374
  syncKeyboardFrame(e);
355
375
  }
356
- }, [maybeScroll, removeGhostPadding, disableScrollOnKeyboardHide, syncKeyboardFrame]);
376
+ }, [mode, maybeScroll, removeGhostPadding, disableScrollOnKeyboardHide, syncKeyboardFrame]);
357
377
  const synchronize = (0, _react.useCallback)(async () => {
358
378
  await update();
359
379
  (0, _reactNativeReanimated.runOnUI)(() => {
@@ -389,6 +409,23 @@ const KeyboardAwareScrollView = /*#__PURE__*/(0, _react.forwardRef)(({
389
409
  }
390
410
  }, []);
391
411
  const padding = (0, _reactNativeReanimated.useDerivedValue)(() => enabled ? currentKeyboardFrameHeight.value : 0, [enabled]);
412
+ // layout mode only: a spacer view whose paddingBottom grows with the keyboard.
413
+ // The `+ 1` ensures the scroll view never reaches its absolute end during animation,
414
+ // avoiding the layout recalculation that triggers on every frame at the boundary.
415
+ // see: https://github.com/kirillzyusko/react-native-keyboard-controller/pull/342
416
+ const layoutSpacerStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => enabled && mode === "layout" ? {
417
+ paddingBottom: currentKeyboardFrameHeight.value + 1
418
+ } : {}, [enabled, mode]);
419
+ if (mode === "layout") {
420
+ return /*#__PURE__*/_react.default.createElement(ScrollViewComponent, _extends({
421
+ ref: onRef
422
+ }, rest, {
423
+ scrollEventThrottle: 16,
424
+ onLayout: onScrollViewLayout
425
+ }), children, enabled && /*#__PURE__*/_react.default.createElement(_reactNativeReanimated.default.View, {
426
+ style: layoutSpacerStyle
427
+ }));
428
+ }
392
429
  return /*#__PURE__*/_react.default.createElement(_ScrollViewWithBottomPadding.default, _extends({
393
430
  ref: onRef
394
431
  }, rest, {