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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +1 -0
  2. package/android/src/base/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt +1 -0
  3. package/android/src/fabric/java/com/reactnativekeyboardcontroller/ClippingScrollViewDecoratorViewManager.kt +7 -0
  4. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +8 -0
  5. package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardToolbarGroupViewManager.kt +23 -0
  6. package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ReactContext.kt +12 -0
  7. package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/FocusedInputObserver.kt +2 -1
  8. package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/WindowDimensionListener.kt +5 -1
  9. package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardToolbarGroupViewManagerImpl.kt +13 -0
  10. package/android/src/main/java/com/reactnativekeyboardcontroller/modal/ModalAttachedWatcher.kt +4 -6
  11. package/android/src/main/java/com/reactnativekeyboardcontroller/modules/KeyboardControllerModuleImpl.kt +26 -0
  12. package/android/src/main/java/com/reactnativekeyboardcontroller/traversal/ViewHierarchyNavigator.kt +31 -4
  13. package/android/src/main/java/com/reactnativekeyboardcontroller/views/ClippingScrollViewDecoratorView.kt +5 -0
  14. package/android/src/main/java/com/reactnativekeyboardcontroller/views/KeyboardToolbarGroupReactViewGroup.kt +13 -0
  15. package/android/src/main/jni/RNKC.h +2 -0
  16. package/android/src/paper/java/com/reactnativekeyboardcontroller/ClippingScrollViewDecoratorViewManager.kt +8 -0
  17. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +9 -0
  18. package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardToolbarGroupViewManager.kt +15 -0
  19. package/android/src/turbo/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt +2 -0
  20. package/common/cpp/react/renderer/components/RNKC/RNKCClippingScrollViewDecoratorViewComponentDescriptor.h +27 -0
  21. package/common/cpp/react/renderer/components/RNKC/RNKCClippingScrollViewDecoratorViewShadowNode.cpp +14 -0
  22. package/common/cpp/react/renderer/components/RNKC/RNKCClippingScrollViewDecoratorViewShadowNode.h +30 -0
  23. package/common/cpp/react/renderer/components/RNKC/RNKCClippingScrollViewDecoratorViewState.h +28 -0
  24. package/common/cpp/react/renderer/components/RNKC/RNKCKeyboardToolbarGroupViewComponentDescriptor.h +20 -0
  25. package/common/cpp/react/renderer/components/RNKC/RNKCKeyboardToolbarGroupViewShadowNode.cpp +7 -0
  26. package/common/cpp/react/renderer/components/RNKC/RNKCKeyboardToolbarGroupViewShadowNode.h +23 -0
  27. package/common/cpp/react/renderer/components/RNKC/RNKCKeyboardToolbarGroupViewState.h +21 -0
  28. package/ios/KeyboardControllerModule.mm +36 -0
  29. package/ios/extensions/UIApplication.swift +1 -1
  30. package/ios/observers/FocusedInputObserver.swift +2 -1
  31. package/ios/traversal/ViewHierarchyNavigator.swift +49 -3
  32. package/ios/views/ClippingScrollViewDecoratorViewManager.h +26 -0
  33. package/ios/views/ClippingScrollViewDecoratorViewManager.mm +231 -0
  34. package/ios/views/KeyboardToolbarGroupViewManager.h +28 -0
  35. package/ios/views/KeyboardToolbarGroupViewManager.mm +88 -0
  36. package/jest/index.js +11 -1
  37. package/lib/commonjs/bindings.js +18 -2
  38. package/lib/commonjs/bindings.js.map +1 -1
  39. package/lib/commonjs/bindings.native.js +3 -4
  40. package/lib/commonjs/bindings.native.js.map +1 -1
  41. package/lib/commonjs/components/KeyboardAvoidingView/index.js +30 -4
  42. package/lib/commonjs/components/KeyboardAvoidingView/index.js.map +1 -1
  43. package/lib/commonjs/components/KeyboardAwareScrollView/index.js +17 -4
  44. package/lib/commonjs/components/KeyboardAwareScrollView/index.js.map +1 -1
  45. package/lib/commonjs/components/KeyboardChatScrollView/index.js +33 -2
  46. package/lib/commonjs/components/KeyboardChatScrollView/index.js.map +1 -1
  47. package/lib/commonjs/components/KeyboardChatScrollView/types.js.map +1 -1
  48. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/helpers.js +107 -21
  49. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/helpers.js.map +1 -1
  50. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js +45 -17
  51. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js.map +1 -1
  52. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.js +61 -26
  53. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.js.map +1 -1
  54. package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/types.js.map +1 -1
  55. package/lib/commonjs/components/KeyboardChatScrollView/useExtraContentPadding/index.js +72 -0
  56. package/lib/commonjs/components/KeyboardChatScrollView/useExtraContentPadding/index.js.map +1 -0
  57. package/lib/commonjs/components/KeyboardToolbar/index.js +1 -0
  58. package/lib/commonjs/components/KeyboardToolbar/index.js.map +1 -1
  59. package/lib/commonjs/components/ScrollViewWithBottomPadding/index.js +18 -6
  60. package/lib/commonjs/components/ScrollViewWithBottomPadding/index.js.map +1 -1
  61. package/lib/commonjs/specs/ClippingScrollViewDecoratorViewNativeComponent.js +1 -2
  62. package/lib/commonjs/specs/ClippingScrollViewDecoratorViewNativeComponent.js.map +1 -1
  63. package/lib/commonjs/specs/KeyboardToolbarGroupViewNativeComponent.js +12 -0
  64. package/lib/commonjs/specs/KeyboardToolbarGroupViewNativeComponent.js.map +1 -0
  65. package/lib/commonjs/specs/NativeKeyboardController.js.map +1 -1
  66. package/lib/commonjs/types/module.js.map +1 -1
  67. package/lib/commonjs/types/views.js.map +1 -1
  68. package/lib/module/bindings.js +17 -1
  69. package/lib/module/bindings.js.map +1 -1
  70. package/lib/module/bindings.native.js +2 -3
  71. package/lib/module/bindings.native.js.map +1 -1
  72. package/lib/module/components/KeyboardAvoidingView/index.js +29 -4
  73. package/lib/module/components/KeyboardAvoidingView/index.js.map +1 -1
  74. package/lib/module/components/KeyboardAwareScrollView/index.js +17 -4
  75. package/lib/module/components/KeyboardAwareScrollView/index.js.map +1 -1
  76. package/lib/module/components/KeyboardChatScrollView/index.js +34 -3
  77. package/lib/module/components/KeyboardChatScrollView/index.js.map +1 -1
  78. package/lib/module/components/KeyboardChatScrollView/types.js.map +1 -1
  79. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/helpers.js +98 -16
  80. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/helpers.js.map +1 -1
  81. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js +46 -18
  82. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js.map +1 -1
  83. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.js +62 -27
  84. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.js.map +1 -1
  85. package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/types.js.map +1 -1
  86. package/lib/module/components/KeyboardChatScrollView/useExtraContentPadding/index.js +67 -0
  87. package/lib/module/components/KeyboardChatScrollView/useExtraContentPadding/index.js.map +1 -0
  88. package/lib/module/components/KeyboardToolbar/index.js +2 -1
  89. package/lib/module/components/KeyboardToolbar/index.js.map +1 -1
  90. package/lib/module/components/ScrollViewWithBottomPadding/index.js +19 -7
  91. package/lib/module/components/ScrollViewWithBottomPadding/index.js.map +1 -1
  92. package/lib/module/specs/ClippingScrollViewDecoratorViewNativeComponent.js +1 -2
  93. package/lib/module/specs/ClippingScrollViewDecoratorViewNativeComponent.js.map +1 -1
  94. package/lib/module/specs/KeyboardToolbarGroupViewNativeComponent.js +5 -0
  95. package/lib/module/specs/KeyboardToolbarGroupViewNativeComponent.js.map +1 -0
  96. package/lib/module/specs/NativeKeyboardController.js.map +1 -1
  97. package/lib/module/types/module.js.map +1 -1
  98. package/lib/module/types/views.js.map +1 -1
  99. package/lib/typescript/bindings.d.ts +12 -2
  100. package/lib/typescript/bindings.native.d.ts +2 -1
  101. package/lib/typescript/components/KeyboardAvoidingView/index.d.ts +44 -13
  102. package/lib/typescript/components/KeyboardChatScrollView/index.d.ts +3 -0
  103. package/lib/typescript/components/KeyboardChatScrollView/types.d.ts +41 -0
  104. package/lib/typescript/components/KeyboardChatScrollView/useChatKeyboard/helpers.d.ts +63 -8
  105. package/lib/typescript/components/KeyboardChatScrollView/useChatKeyboard/index.d.ts +1 -4
  106. package/lib/typescript/components/KeyboardChatScrollView/useChatKeyboard/types.d.ts +15 -0
  107. package/lib/typescript/components/KeyboardChatScrollView/useExtraContentPadding/index.d.ts +43 -0
  108. package/lib/typescript/components/KeyboardToolbar/index.d.ts +2 -0
  109. package/lib/typescript/components/ScrollViewWithBottomPadding/index.d.ts +3 -0
  110. package/lib/typescript/specs/ClippingScrollViewDecoratorViewNativeComponent.d.ts +1 -0
  111. package/lib/typescript/specs/KeyboardToolbarGroupViewNativeComponent.d.ts +6 -0
  112. package/lib/typescript/specs/NativeKeyboardController.d.ts +1 -0
  113. package/lib/typescript/types/module.d.ts +7 -0
  114. package/lib/typescript/types/views.d.ts +2 -0
  115. package/package.json +6 -2
  116. package/react-native.config.js +2 -0
  117. package/src/bindings.native.ts +4 -4
  118. package/src/bindings.ts +15 -1
  119. package/src/components/KeyboardAvoidingView/index.tsx +56 -29
  120. package/src/components/KeyboardAwareScrollView/index.tsx +20 -5
  121. package/src/components/KeyboardChatScrollView/index.tsx +47 -3
  122. package/src/components/KeyboardChatScrollView/types.ts +41 -0
  123. package/src/components/KeyboardChatScrollView/useChatKeyboard/helpers.ts +126 -21
  124. package/src/components/KeyboardChatScrollView/useChatKeyboard/index.ios.ts +101 -21
  125. package/src/components/KeyboardChatScrollView/useChatKeyboard/index.ts +116 -40
  126. package/src/components/KeyboardChatScrollView/useChatKeyboard/types.ts +9 -0
  127. package/src/components/KeyboardChatScrollView/useExtraContentPadding/index.ts +123 -0
  128. package/src/components/KeyboardToolbar/index.tsx +6 -1
  129. package/src/components/ScrollViewWithBottomPadding/index.tsx +31 -4
  130. package/src/specs/ClippingScrollViewDecoratorViewNativeComponent.ts +1 -1
  131. package/src/specs/KeyboardToolbarGroupViewNativeComponent.ts +10 -0
  132. package/src/specs/NativeKeyboardController.ts +1 -0
  133. package/src/types/module.ts +9 -0
  134. package/src/types/views.ts +2 -0
  135. package/android/.settings/org.eclipse.buildship.core.prefs +0 -2
  136. package/ios/KeyboardController.xcodeproj/xcuserdata/kirylziusko.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
  137. package/ios/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  138. package/ios/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  139. package/ios/Tests/Tests.xcodeproj/project.xcworkspace/xcuserdata/kirylziusko.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  140. package/ios/Tests/Tests.xcodeproj/xcuserdata/kirylziusko.xcuserdatad/xcschemes/xcschememanagement.plist +0 -32
@@ -3,7 +3,7 @@ const AT_END_THRESHOLD = 20;
3
3
 
4
4
  /**
5
5
  * Map the current keyboard height to an effective height that accounts for a
6
- * fixed offset (e.g. bottom safe-area or tab-bar height).
6
+ * fixed offset (e.g. Bottom safe-area or tab-bar height)..
7
7
  *
8
8
  * @param height - Current keyboard height.
9
9
  * @param targetKeyboardHeight - Full target keyboard height (captured on keyboard open).
@@ -15,14 +15,14 @@ const AT_END_THRESHOLD = 20;
15
15
  * getEffectiveHeight(150, 300, 50); // 125
16
16
  * ```
17
17
  */
18
- export function getEffectiveHeight(height, targetKeyboardHeight, offset) {
18
+ export const getEffectiveHeight = (height, targetKeyboardHeight, offset) => {
19
19
  "worklet";
20
20
 
21
21
  if (offset === 0 || targetKeyboardHeight === 0) {
22
22
  return height;
23
23
  }
24
24
  return interpolate(height, [0, targetKeyboardHeight], [0, Math.max(targetKeyboardHeight - offset, 0)]);
25
- }
25
+ };
26
26
 
27
27
  /**
28
28
  * Check whether the scroll view is at the end of its content.
@@ -42,14 +42,14 @@ export function getEffectiveHeight(height, targetKeyboardHeight, offset) {
42
42
  * const atEndInverted = isScrollAtEnd(5, 800, 2000, true); // true (5 <= 20)
43
43
  * ```
44
44
  */
45
- export function isScrollAtEnd(scrollOffset, layoutHeight, contentHeight, inverted = false) {
45
+ export const isScrollAtEnd = (scrollOffset, layoutHeight, contentHeight, inverted = false) => {
46
46
  "worklet";
47
47
 
48
48
  if (inverted) {
49
49
  return scrollOffset <= AT_END_THRESHOLD;
50
50
  }
51
51
  return scrollOffset + layoutHeight >= contentHeight - AT_END_THRESHOLD;
52
- }
52
+ };
53
53
 
54
54
  /**
55
55
  * Decide whether content should be shifted based on the keyboard lift behavior.
@@ -63,7 +63,7 @@ export function isScrollAtEnd(scrollOffset, layoutHeight, contentHeight, inverte
63
63
  * shouldShiftContent("whenAtEnd", false); // false
64
64
  * ```
65
65
  */
66
- export function shouldShiftContent(behavior, isAtEnd) {
66
+ export const shouldShiftContent = (behavior, isAtEnd) => {
67
67
  "worklet";
68
68
 
69
69
  switch (behavior) {
@@ -76,50 +76,132 @@ export function shouldShiftContent(behavior, isAtEnd) {
76
76
  case "persistent":
77
77
  return true;
78
78
  }
79
- }
79
+ };
80
+
81
+ /**
82
+ * Compute the fraction of minimum padding space currently visible in the viewport (0–1).
83
+ *
84
+ * The minimum padding space lives in the scroll view's contentInset, NOT in the
85
+ * content itself. So `contentHeight` (from onContentSizeChange / scroll
86
+ * events) does **not** include it. The visible portion is how far the
87
+ * viewport extends past the content boundary into the inset area.
88
+ *
89
+ * For non-inverted lists the padding is in contentInset.bottom.
90
+ * For inverted lists the padding is in contentInset.top (negative scroll).
91
+ *
92
+ * @param scrollOffset - Current vertical scroll offset.
93
+ * @param layoutHeight - Visible height of the scroll view.
94
+ * @param contentHeight - Height of the scroll content (excludes insets).
95
+ * @param blankSpace - Size of the minimum padding inset area.
96
+ * @param inverted - Whether the list is inverted.
97
+ * @returns A value between 0 (padding fully off-screen) and 1 (padding fully visible).
98
+ * @example
99
+ * ```ts
100
+ * // Non-inverted: contentHeight=1500, layout=800, blankSpace=300
101
+ * getVisibleMinimumPaddingFraction(1500, 800, 1500, 300, false); // 1 (at end, viewport past content)
102
+ * getVisibleMinimumPaddingFraction(850, 800, 1500, 300, false); // 0.5 (half padding visible)
103
+ * getVisibleMinimumPaddingFraction(700, 800, 1500, 300, false); // 0 (padding off-screen)
104
+ * ```
105
+ */
106
+ export const getVisibleMinimumPaddingFraction = (scrollOffset, layoutHeight, contentHeight, blankSpace, inverted) => {
107
+ "worklet";
108
+
109
+ if (blankSpace <= 0) {
110
+ return 0;
111
+ }
112
+ if (inverted) {
113
+ // Minimum padding is in contentInset.top; visible when scroll < 0
114
+ return Math.max(0, Math.min(1, -scrollOffset / blankSpace));
115
+ }
116
+
117
+ // Minimum padding is in contentInset.bottom; visible when viewport extends past content
118
+ const pastContentEnd = scrollOffset + layoutHeight - contentHeight;
119
+ return Math.max(0, Math.min(1, pastContentEnd / blankSpace));
120
+ };
121
+
122
+ /**
123
+ * Compute how much of the minimum content padding absorbs the keyboard + extraContentPadding.
124
+ *
125
+ * @param blankSpace - Minimum inset floor.
126
+ * @param extraContentPadding - Extra content padding from external elements.
127
+ * @returns The portion of blankSpace that absorbs keyboard displacement.
128
+ * @example
129
+ * ```ts
130
+ * getMinimumPaddingAbsorbed(500, 20); // 480
131
+ * getMinimumPaddingAbsorbed(0, 20); // 0
132
+ * ```
133
+ */
134
+ export const getMinimumPaddingAbsorbed = (blankSpace, extraContentPadding) => {
135
+ "worklet";
136
+
137
+ return Math.max(0, blankSpace - extraContentPadding);
138
+ };
139
+
140
+ /**
141
+ * Compute the effective scroll displacement after minimum padding absorption.
142
+ *
143
+ * @param rawEffective - Raw effective keyboard height.
144
+ * @param minimumPaddingAbsorbed - Amount absorbed by minimum content padding.
145
+ * @returns The scroll displacement after subtracting the absorbed portion.
146
+ * @example
147
+ * ```ts
148
+ * getScrollEffective(300, 200); // 100
149
+ * getScrollEffective(300, 400); // 0
150
+ * ```
151
+ */
152
+ export const getScrollEffective = (rawEffective, minimumPaddingAbsorbed) => {
153
+ "worklet";
154
+
155
+ return Math.max(0, rawEffective - minimumPaddingAbsorbed);
156
+ };
80
157
 
81
158
  /**
82
159
  * Compute the clamped scroll target for non-inverted lists.
83
160
  *
84
161
  * @param offsetBeforeScroll - Scroll position before keyboard appeared.
85
- * @param keyboardHeight - Current keyboard height.
162
+ * @param keyboardHeight - Current keyboard height (used for scroll displacement).
86
163
  * @param contentHeight - Total height of the scrollable content.
87
164
  * @param layoutHeight - Visible height of the scroll view.
165
+ * @param totalPaddingForMaxScroll - Total padding to use for maxScroll calculation. When provided, used instead of keyboardHeight for the scrollable range. Defaults to keyboardHeight.
88
166
  * @returns Clamped scroll target between 0 and maxScroll.
89
167
  * @example
90
168
  * ```ts
91
169
  * clampedScrollTarget(100, 300, 1000, 800); // 400
170
+ * clampedScrollTarget(100, 100, 1000, 800, 500); // 200, maxScroll uses 500
92
171
  * ```
93
172
  */
94
- export function clampedScrollTarget(offsetBeforeScroll, keyboardHeight, contentHeight, layoutHeight) {
173
+ export const clampedScrollTarget = (offsetBeforeScroll, keyboardHeight, contentHeight, layoutHeight, totalPaddingForMaxScroll) => {
95
174
  "worklet";
96
175
 
97
- const maxScroll = Math.max(contentHeight - layoutHeight + keyboardHeight, 0);
176
+ const paddingForMax = totalPaddingForMaxScroll !== undefined ? totalPaddingForMaxScroll : keyboardHeight;
177
+ const maxScroll = Math.max(contentHeight - layoutHeight + paddingForMax, 0);
98
178
  return Math.min(Math.max(offsetBeforeScroll + keyboardHeight, 0), maxScroll);
99
- }
179
+ };
100
180
 
101
181
  /**
102
182
  * Compute contentOffset.y for iOS lists.
103
183
  *
104
184
  * @param relativeScroll - Scroll position relative to current inset.
105
- * @param keyboardHeight - Target keyboard height.
185
+ * @param keyboardHeight - Target keyboard height (used for scroll displacement).
106
186
  * @param contentHeight - Total height of the scrollable content.
107
187
  * @param layoutHeight - Visible height of the scroll view.
108
188
  * @param inverted - Whether the list is inverted.
189
+ * @param totalPaddingForMaxScroll - Total padding to use for maxScroll calculation. When provided, used instead of keyboardHeight for the scrollable range. Defaults to keyboardHeight.
109
190
  * @returns The absolute contentOffset.y to set.
110
191
  * @example
111
192
  * ```ts
112
193
  * computeIOSContentOffset(100, 300, 1000, 800, false); // 400
113
194
  * ```
114
195
  */
115
- export function computeIOSContentOffset(relativeScroll, keyboardHeight, contentHeight, layoutHeight, inverted) {
196
+ export const computeIOSContentOffset = (relativeScroll, keyboardHeight, contentHeight, layoutHeight, inverted, totalPaddingForMaxScroll) => {
116
197
  "worklet";
117
198
 
199
+ const paddingForMax = totalPaddingForMaxScroll !== undefined ? totalPaddingForMaxScroll : keyboardHeight;
118
200
  if (inverted) {
119
201
  const maxScroll = Math.max(contentHeight - layoutHeight, 0);
120
- return Math.max(Math.min(relativeScroll - keyboardHeight, maxScroll), -keyboardHeight);
202
+ return Math.max(Math.min(relativeScroll - keyboardHeight, maxScroll), -paddingForMax);
121
203
  }
122
- const maxScroll = Math.max(contentHeight - layoutHeight + keyboardHeight, 0);
204
+ const maxScroll = Math.max(contentHeight - layoutHeight + paddingForMax, 0);
123
205
  return Math.min(Math.max(keyboardHeight + relativeScroll, 0), maxScroll);
124
- }
206
+ };
125
207
  //# sourceMappingURL=helpers.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["interpolate","AT_END_THRESHOLD","getEffectiveHeight","height","targetKeyboardHeight","offset","Math","max","isScrollAtEnd","scrollOffset","layoutHeight","contentHeight","inverted","shouldShiftContent","behavior","isAtEnd","clampedScrollTarget","offsetBeforeScroll","keyboardHeight","maxScroll","min","computeIOSContentOffset","relativeScroll"],"sources":["helpers.ts"],"sourcesContent":["import { interpolate } from \"react-native-reanimated\";\n\nimport type { KeyboardLiftBehavior } from \"./types\";\n\nconst AT_END_THRESHOLD = 20;\n\n/**\n * Map the current keyboard height to an effective height that accounts for a\n * fixed offset (e.g. bottom safe-area or tab-bar height).\n *\n * @param height - Current keyboard height.\n * @param targetKeyboardHeight - Full target keyboard height (captured on keyboard open).\n * @param offset - Fixed distance between the scroll-view bottom and the screen bottom.\n * @returns Effective height after subtracting the offset proportionally.\n * @example\n * ```ts\n * getEffectiveHeight(300, 300, 50); // 250\n * getEffectiveHeight(150, 300, 50); // 125\n * ```\n */\nexport function getEffectiveHeight(\n height: number,\n targetKeyboardHeight: number,\n offset: number,\n): number {\n \"worklet\";\n\n if (offset === 0 || targetKeyboardHeight === 0) {\n return height;\n }\n\n return interpolate(\n height,\n [0, targetKeyboardHeight],\n [0, Math.max(targetKeyboardHeight - offset, 0)],\n );\n}\n\n/**\n * Check whether the scroll view is at the end of its content.\n *\n * For non-inverted lists the \"end\" is the bottom of the content.\n * For inverted lists the \"end\" is the top (scroll offset near 0),\n * because that is where the latest messages are displayed.\n *\n * @param scrollOffset - Current vertical scroll offset.\n * @param layoutHeight - Visible height of the scroll view.\n * @param contentHeight - Total height of the scrollable content.\n * @param inverted - Whether the list is inverted.\n * @returns `true` if the scroll position is within the threshold of the content end.\n * @example\n * ```ts\n * const atEnd = isScrollAtEnd(100, 800, 920); // true (100 + 800 >= 920 - 20)\n * const atEndInverted = isScrollAtEnd(5, 800, 2000, true); // true (5 <= 20)\n * ```\n */\nexport function isScrollAtEnd(\n scrollOffset: number,\n layoutHeight: number,\n contentHeight: number,\n inverted: boolean = false,\n): boolean {\n \"worklet\";\n\n if (inverted) {\n return scrollOffset <= AT_END_THRESHOLD;\n }\n\n return scrollOffset + layoutHeight >= contentHeight - AT_END_THRESHOLD;\n}\n\n/**\n * Decide whether content should be shifted based on the keyboard lift behavior.\n *\n * @param behavior - The configured keyboard lift behavior.\n * @param isAtEnd - Whether the scroll view is currently at the end.\n * @returns `true` if content should be shifted.\n * @example\n * ```ts\n * shouldShiftContent(\"always\", false); // true\n * shouldShiftContent(\"whenAtEnd\", false); // false\n * ```\n */\nexport function shouldShiftContent(\n behavior: KeyboardLiftBehavior,\n isAtEnd: boolean,\n): boolean {\n \"worklet\";\n\n switch (behavior) {\n case \"always\":\n return true;\n case \"never\":\n return false;\n case \"whenAtEnd\":\n return isAtEnd;\n case \"persistent\":\n return true;\n }\n}\n\n/**\n * Compute the clamped scroll target for non-inverted lists.\n *\n * @param offsetBeforeScroll - Scroll position before keyboard appeared.\n * @param keyboardHeight - Current keyboard height.\n * @param contentHeight - Total height of the scrollable content.\n * @param layoutHeight - Visible height of the scroll view.\n * @returns Clamped scroll target between 0 and maxScroll.\n * @example\n * ```ts\n * clampedScrollTarget(100, 300, 1000, 800); // 400\n * ```\n */\nexport function clampedScrollTarget(\n offsetBeforeScroll: number,\n keyboardHeight: number,\n contentHeight: number,\n layoutHeight: number,\n): number {\n \"worklet\";\n\n const maxScroll = Math.max(contentHeight - layoutHeight + keyboardHeight, 0);\n\n return Math.min(Math.max(offsetBeforeScroll + keyboardHeight, 0), maxScroll);\n}\n\n/**\n * Compute contentOffset.y for iOS lists.\n *\n * @param relativeScroll - Scroll position relative to current inset.\n * @param keyboardHeight - Target keyboard height.\n * @param contentHeight - Total height of the scrollable content.\n * @param layoutHeight - Visible height of the scroll view.\n * @param inverted - Whether the list is inverted.\n * @returns The absolute contentOffset.y to set.\n * @example\n * ```ts\n * computeIOSContentOffset(100, 300, 1000, 800, false); // 400\n * ```\n */\nexport function computeIOSContentOffset(\n relativeScroll: number,\n keyboardHeight: number,\n contentHeight: number,\n layoutHeight: number,\n inverted: boolean,\n): number {\n \"worklet\";\n\n if (inverted) {\n const maxScroll = Math.max(contentHeight - layoutHeight, 0);\n\n return Math.max(\n Math.min(relativeScroll - keyboardHeight, maxScroll),\n -keyboardHeight,\n );\n }\n\n const maxScroll = Math.max(contentHeight - layoutHeight + keyboardHeight, 0);\n\n return Math.min(Math.max(keyboardHeight + relativeScroll, 0), maxScroll);\n}\n"],"mappings":"AAAA,SAASA,WAAW,QAAQ,yBAAyB;AAIrD,MAAMC,gBAAgB,GAAG,EAAE;;AAE3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,kBAAkBA,CAChCC,MAAc,EACdC,oBAA4B,EAC5BC,MAAc,EACN;EACR,SAAS;;EAET,IAAIA,MAAM,KAAK,CAAC,IAAID,oBAAoB,KAAK,CAAC,EAAE;IAC9C,OAAOD,MAAM;EACf;EAEA,OAAOH,WAAW,CAChBG,MAAM,EACN,CAAC,CAAC,EAAEC,oBAAoB,CAAC,EACzB,CAAC,CAAC,EAAEE,IAAI,CAACC,GAAG,CAACH,oBAAoB,GAAGC,MAAM,EAAE,CAAC,CAAC,CAChD,CAAC;AACH;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASG,aAAaA,CAC3BC,YAAoB,EACpBC,YAAoB,EACpBC,aAAqB,EACrBC,QAAiB,GAAG,KAAK,EAChB;EACT,SAAS;;EAET,IAAIA,QAAQ,EAAE;IACZ,OAAOH,YAAY,IAAIR,gBAAgB;EACzC;EAEA,OAAOQ,YAAY,GAAGC,YAAY,IAAIC,aAAa,GAAGV,gBAAgB;AACxE;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASY,kBAAkBA,CAChCC,QAA8B,EAC9BC,OAAgB,EACP;EACT,SAAS;;EAET,QAAQD,QAAQ;IACd,KAAK,QAAQ;MACX,OAAO,IAAI;IACb,KAAK,OAAO;MACV,OAAO,KAAK;IACd,KAAK,WAAW;MACd,OAAOC,OAAO;IAChB,KAAK,YAAY;MACf,OAAO,IAAI;EACf;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,mBAAmBA,CACjCC,kBAA0B,EAC1BC,cAAsB,EACtBP,aAAqB,EACrBD,YAAoB,EACZ;EACR,SAAS;;EAET,MAAMS,SAAS,GAAGb,IAAI,CAACC,GAAG,CAACI,aAAa,GAAGD,YAAY,GAAGQ,cAAc,EAAE,CAAC,CAAC;EAE5E,OAAOZ,IAAI,CAACc,GAAG,CAACd,IAAI,CAACC,GAAG,CAACU,kBAAkB,GAAGC,cAAc,EAAE,CAAC,CAAC,EAAEC,SAAS,CAAC;AAC9E;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASE,uBAAuBA,CACrCC,cAAsB,EACtBJ,cAAsB,EACtBP,aAAqB,EACrBD,YAAoB,EACpBE,QAAiB,EACT;EACR,SAAS;;EAET,IAAIA,QAAQ,EAAE;IACZ,MAAMO,SAAS,GAAGb,IAAI,CAACC,GAAG,CAACI,aAAa,GAAGD,YAAY,EAAE,CAAC,CAAC;IAE3D,OAAOJ,IAAI,CAACC,GAAG,CACbD,IAAI,CAACc,GAAG,CAACE,cAAc,GAAGJ,cAAc,EAAEC,SAAS,CAAC,EACpD,CAACD,cACH,CAAC;EACH;EAEA,MAAMC,SAAS,GAAGb,IAAI,CAACC,GAAG,CAACI,aAAa,GAAGD,YAAY,GAAGQ,cAAc,EAAE,CAAC,CAAC;EAE5E,OAAOZ,IAAI,CAACc,GAAG,CAACd,IAAI,CAACC,GAAG,CAACW,cAAc,GAAGI,cAAc,EAAE,CAAC,CAAC,EAAEH,SAAS,CAAC;AAC1E","ignoreList":[]}
1
+ {"version":3,"names":["interpolate","AT_END_THRESHOLD","getEffectiveHeight","height","targetKeyboardHeight","offset","Math","max","isScrollAtEnd","scrollOffset","layoutHeight","contentHeight","inverted","shouldShiftContent","behavior","isAtEnd","getVisibleMinimumPaddingFraction","blankSpace","min","pastContentEnd","getMinimumPaddingAbsorbed","extraContentPadding","getScrollEffective","rawEffective","minimumPaddingAbsorbed","clampedScrollTarget","offsetBeforeScroll","keyboardHeight","totalPaddingForMaxScroll","paddingForMax","undefined","maxScroll","computeIOSContentOffset","relativeScroll"],"sources":["helpers.ts"],"sourcesContent":["import { interpolate } from \"react-native-reanimated\";\n\nimport type { KeyboardLiftBehavior } from \"./types\";\n\nconst AT_END_THRESHOLD = 20;\n\n/**\n * Map the current keyboard height to an effective height that accounts for a\n * fixed offset (e.g. Bottom safe-area or tab-bar height)..\n *\n * @param height - Current keyboard height.\n * @param targetKeyboardHeight - Full target keyboard height (captured on keyboard open).\n * @param offset - Fixed distance between the scroll-view bottom and the screen bottom.\n * @returns Effective height after subtracting the offset proportionally.\n * @example\n * ```ts\n * getEffectiveHeight(300, 300, 50); // 250\n * getEffectiveHeight(150, 300, 50); // 125\n * ```\n */\nexport const getEffectiveHeight = (\n height: number,\n targetKeyboardHeight: number,\n offset: number,\n): number => {\n \"worklet\";\n\n if (offset === 0 || targetKeyboardHeight === 0) {\n return height;\n }\n\n return interpolate(\n height,\n [0, targetKeyboardHeight],\n [0, Math.max(targetKeyboardHeight - offset, 0)],\n );\n};\n\n/**\n * Check whether the scroll view is at the end of its content.\n *\n * For non-inverted lists the \"end\" is the bottom of the content.\n * For inverted lists the \"end\" is the top (scroll offset near 0),\n * because that is where the latest messages are displayed.\n *\n * @param scrollOffset - Current vertical scroll offset.\n * @param layoutHeight - Visible height of the scroll view.\n * @param contentHeight - Total height of the scrollable content.\n * @param inverted - Whether the list is inverted.\n * @returns `true` if the scroll position is within the threshold of the content end.\n * @example\n * ```ts\n * const atEnd = isScrollAtEnd(100, 800, 920); // true (100 + 800 >= 920 - 20)\n * const atEndInverted = isScrollAtEnd(5, 800, 2000, true); // true (5 <= 20)\n * ```\n */\nexport const isScrollAtEnd = (\n scrollOffset: number,\n layoutHeight: number,\n contentHeight: number,\n inverted: boolean = false,\n): boolean => {\n \"worklet\";\n\n if (inverted) {\n return scrollOffset <= AT_END_THRESHOLD;\n }\n\n return scrollOffset + layoutHeight >= contentHeight - AT_END_THRESHOLD;\n};\n\n/**\n * Decide whether content should be shifted based on the keyboard lift behavior.\n *\n * @param behavior - The configured keyboard lift behavior.\n * @param isAtEnd - Whether the scroll view is currently at the end.\n * @returns `true` if content should be shifted.\n * @example\n * ```ts\n * shouldShiftContent(\"always\", false); // true\n * shouldShiftContent(\"whenAtEnd\", false); // false\n * ```\n */\nexport const shouldShiftContent = (\n behavior: KeyboardLiftBehavior,\n isAtEnd: boolean,\n): boolean => {\n \"worklet\";\n\n switch (behavior) {\n case \"always\":\n return true;\n case \"never\":\n return false;\n case \"whenAtEnd\":\n return isAtEnd;\n case \"persistent\":\n return true;\n }\n};\n\n/**\n * Compute the fraction of minimum padding space currently visible in the viewport (0–1).\n *\n * The minimum padding space lives in the scroll view's contentInset, NOT in the\n * content itself. So `contentHeight` (from onContentSizeChange / scroll\n * events) does **not** include it. The visible portion is how far the\n * viewport extends past the content boundary into the inset area.\n *\n * For non-inverted lists the padding is in contentInset.bottom.\n * For inverted lists the padding is in contentInset.top (negative scroll).\n *\n * @param scrollOffset - Current vertical scroll offset.\n * @param layoutHeight - Visible height of the scroll view.\n * @param contentHeight - Height of the scroll content (excludes insets).\n * @param blankSpace - Size of the minimum padding inset area.\n * @param inverted - Whether the list is inverted.\n * @returns A value between 0 (padding fully off-screen) and 1 (padding fully visible).\n * @example\n * ```ts\n * // Non-inverted: contentHeight=1500, layout=800, blankSpace=300\n * getVisibleMinimumPaddingFraction(1500, 800, 1500, 300, false); // 1 (at end, viewport past content)\n * getVisibleMinimumPaddingFraction(850, 800, 1500, 300, false); // 0.5 (half padding visible)\n * getVisibleMinimumPaddingFraction(700, 800, 1500, 300, false); // 0 (padding off-screen)\n * ```\n */\nexport const getVisibleMinimumPaddingFraction = (\n scrollOffset: number,\n layoutHeight: number,\n contentHeight: number,\n blankSpace: number,\n inverted: boolean,\n): number => {\n \"worklet\";\n\n if (blankSpace <= 0) {\n return 0;\n }\n\n if (inverted) {\n // Minimum padding is in contentInset.top; visible when scroll < 0\n return Math.max(0, Math.min(1, -scrollOffset / blankSpace));\n }\n\n // Minimum padding is in contentInset.bottom; visible when viewport extends past content\n const pastContentEnd = scrollOffset + layoutHeight - contentHeight;\n\n return Math.max(0, Math.min(1, pastContentEnd / blankSpace));\n};\n\n/**\n * Compute how much of the minimum content padding absorbs the keyboard + extraContentPadding.\n *\n * @param blankSpace - Minimum inset floor.\n * @param extraContentPadding - Extra content padding from external elements.\n * @returns The portion of blankSpace that absorbs keyboard displacement.\n * @example\n * ```ts\n * getMinimumPaddingAbsorbed(500, 20); // 480\n * getMinimumPaddingAbsorbed(0, 20); // 0\n * ```\n */\nexport const getMinimumPaddingAbsorbed = (\n blankSpace: number,\n extraContentPadding: number,\n): number => {\n \"worklet\";\n\n return Math.max(0, blankSpace - extraContentPadding);\n};\n\n/**\n * Compute the effective scroll displacement after minimum padding absorption.\n *\n * @param rawEffective - Raw effective keyboard height.\n * @param minimumPaddingAbsorbed - Amount absorbed by minimum content padding.\n * @returns The scroll displacement after subtracting the absorbed portion.\n * @example\n * ```ts\n * getScrollEffective(300, 200); // 100\n * getScrollEffective(300, 400); // 0\n * ```\n */\nexport const getScrollEffective = (\n rawEffective: number,\n minimumPaddingAbsorbed: number,\n): number => {\n \"worklet\";\n\n return Math.max(0, rawEffective - minimumPaddingAbsorbed);\n};\n\n/**\n * Compute the clamped scroll target for non-inverted lists.\n *\n * @param offsetBeforeScroll - Scroll position before keyboard appeared.\n * @param keyboardHeight - Current keyboard height (used for scroll displacement).\n * @param contentHeight - Total height of the scrollable content.\n * @param layoutHeight - Visible height of the scroll view.\n * @param totalPaddingForMaxScroll - Total padding to use for maxScroll calculation. When provided, used instead of keyboardHeight for the scrollable range. Defaults to keyboardHeight.\n * @returns Clamped scroll target between 0 and maxScroll.\n * @example\n * ```ts\n * clampedScrollTarget(100, 300, 1000, 800); // 400\n * clampedScrollTarget(100, 100, 1000, 800, 500); // 200, maxScroll uses 500\n * ```\n */\nexport const clampedScrollTarget = (\n offsetBeforeScroll: number,\n keyboardHeight: number,\n contentHeight: number,\n layoutHeight: number,\n totalPaddingForMaxScroll?: number,\n): number => {\n \"worklet\";\n\n const paddingForMax =\n totalPaddingForMaxScroll !== undefined\n ? totalPaddingForMaxScroll\n : keyboardHeight;\n const maxScroll = Math.max(contentHeight - layoutHeight + paddingForMax, 0);\n\n return Math.min(Math.max(offsetBeforeScroll + keyboardHeight, 0), maxScroll);\n};\n\n/**\n * Compute contentOffset.y for iOS lists.\n *\n * @param relativeScroll - Scroll position relative to current inset.\n * @param keyboardHeight - Target keyboard height (used for scroll displacement).\n * @param contentHeight - Total height of the scrollable content.\n * @param layoutHeight - Visible height of the scroll view.\n * @param inverted - Whether the list is inverted.\n * @param totalPaddingForMaxScroll - Total padding to use for maxScroll calculation. When provided, used instead of keyboardHeight for the scrollable range. Defaults to keyboardHeight.\n * @returns The absolute contentOffset.y to set.\n * @example\n * ```ts\n * computeIOSContentOffset(100, 300, 1000, 800, false); // 400\n * ```\n */\nexport const computeIOSContentOffset = (\n relativeScroll: number,\n keyboardHeight: number,\n contentHeight: number,\n layoutHeight: number,\n inverted: boolean,\n totalPaddingForMaxScroll?: number,\n): number => {\n \"worklet\";\n\n const paddingForMax =\n totalPaddingForMaxScroll !== undefined\n ? totalPaddingForMaxScroll\n : keyboardHeight;\n\n if (inverted) {\n const maxScroll = Math.max(contentHeight - layoutHeight, 0);\n\n return Math.max(\n Math.min(relativeScroll - keyboardHeight, maxScroll),\n -paddingForMax,\n );\n }\n\n const maxScroll = Math.max(contentHeight - layoutHeight + paddingForMax, 0);\n\n return Math.min(Math.max(keyboardHeight + relativeScroll, 0), maxScroll);\n};\n"],"mappings":"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","ignoreList":[]}
@@ -1,7 +1,7 @@
1
1
  import { useSharedValue } from "react-native-reanimated";
2
2
  import { useKeyboardHandler } from "../../../hooks";
3
3
  import useScrollState from "../../hooks/useScrollState";
4
- import { computeIOSContentOffset, getEffectiveHeight, isScrollAtEnd, shouldShiftContent } from "./helpers";
4
+ import { computeIOSContentOffset, getEffectiveHeight, getScrollEffective, getVisibleMinimumPaddingFraction, isScrollAtEnd, shouldShiftContent } from "./helpers";
5
5
  /**
6
6
  * Hook that manages keyboard-driven scrolling for chat-style scroll views.
7
7
  * Calculates padding (extra scrollable space) and content shift values,
@@ -23,12 +23,15 @@ function useChatKeyboard(scrollViewRef, options) {
23
23
  inverted,
24
24
  keyboardLiftBehavior,
25
25
  freeze,
26
- offset
26
+ offset,
27
+ blankSpace,
28
+ extraContentPadding
27
29
  } = options;
28
30
  const padding = useSharedValue(0);
29
31
  const currentHeight = useSharedValue(0);
30
32
  const contentOffsetY = useSharedValue(0);
31
33
  const targetKeyboardHeight = useSharedValue(0);
34
+ const prevAbsorption = useSharedValue(0);
32
35
  const {
33
36
  layout,
34
37
  size,
@@ -50,43 +53,65 @@ function useChatKeyboard(scrollViewRef, options) {
50
53
  const effective = getEffectiveHeight(e.height, targetKeyboardHeight.value, offset);
51
54
  const atEnd = isScrollAtEnd(scroll.value, layout.value.height, size.value.height, inverted);
52
55
 
53
- // persistent mode: when keyboard shrinks, snap to end or hold position
56
+ // Scale minimum padding absorption by how much of it is visible.
57
+ // Fully visible → full absorption; fully off-screen → no absorption.
58
+ const visibleFraction = getVisibleMinimumPaddingFraction(scroll.value, layout.value.height, size.value.height, blankSpace.value, inverted);
59
+ const visiblePadding = visibleFraction * blankSpace.value;
60
+ const minimumPaddingAbsorbed = Math.max(0, visiblePadding - extraContentPadding.value);
61
+ const scrollEffective = getScrollEffective(effective, minimumPaddingAbsorbed);
62
+ const actualTotalPadding = Math.max(blankSpace.value, effective + extraContentPadding.value);
63
+
64
+ // persistent mode: when keyboard shrinks, clamp to valid range
54
65
  if (keyboardLiftBehavior === "persistent" && effective < padding.value) {
55
66
  padding.value = effective;
56
- if (atEnd) {
57
- if (inverted) {
58
- contentOffsetY.value = -effective;
59
- } else {
60
- contentOffsetY.value = Math.max(size.value.height - layout.value.height + effective, 0);
61
- }
67
+ prevAbsorption.value = minimumPaddingAbsorbed;
68
+ if (inverted) {
69
+ const maxScroll = Math.max(size.value.height - layout.value.height, 0);
70
+ contentOffsetY.value = Math.max(-actualTotalPadding, Math.min(scroll.value, maxScroll));
62
71
  } else {
63
- // Preserve current scroll position so the animated props
64
- // don't re-apply the stale contentOffset from keyboard open
65
- contentOffsetY.value = scroll.value;
72
+ const maxScroll = Math.max(size.value.height - layout.value.height + actualTotalPadding, 0);
73
+ contentOffsetY.value = Math.max(0, Math.min(scroll.value, maxScroll));
66
74
  }
67
75
  return;
68
76
  }
69
77
 
70
- // never mode: when keyboard shrinks and at end, snap to end
78
+ // never mode: when keyboard shrinks, clamp to valid range
71
79
  // to avoid ghost padding
72
80
  if (keyboardLiftBehavior === "never" && effective < padding.value && atEnd) {
73
81
  padding.value = effective;
82
+ prevAbsorption.value = minimumPaddingAbsorbed;
74
83
  if (inverted) {
75
- contentOffsetY.value = -effective;
84
+ const maxScroll = Math.max(size.value.height - layout.value.height, 0);
85
+ contentOffsetY.value = Math.max(-actualTotalPadding, Math.min(scroll.value, maxScroll));
76
86
  } else {
77
- contentOffsetY.value = Math.max(size.value.height - layout.value.height + effective, 0);
87
+ const maxScroll = Math.max(size.value.height - layout.value.height + actualTotalPadding, 0);
88
+ contentOffsetY.value = Math.max(0, Math.min(scroll.value, maxScroll));
78
89
  }
79
90
  return;
80
91
  }
81
- const relativeScroll = inverted ? scroll.value + padding.value : scroll.value - padding.value;
92
+
93
+ // Undo only the scroll displacement that was actually applied
94
+ // (not the full padding, which includes the absorbed portion).
95
+ // Use the stored absorption from the previous event so that
96
+ // the unwind matches the shift that was originally applied.
97
+ const prevScrollEffective = getScrollEffective(padding.value, prevAbsorption.value);
98
+ const relativeScroll = inverted ? scroll.value + prevScrollEffective : scroll.value - prevScrollEffective;
82
99
  padding.value = effective;
100
+ prevAbsorption.value = minimumPaddingAbsorbed;
83
101
  if (!shouldShiftContent(keyboardLiftBehavior, atEnd)) {
84
102
  // Preserve current scroll position so animated props
85
103
  // don't re-apply a stale contentOffset when padding changes
86
104
  contentOffsetY.value = scroll.value;
87
105
  return;
88
106
  }
89
- contentOffsetY.value = computeIOSContentOffset(relativeScroll, effective, size.value.height, layout.value.height, inverted);
107
+
108
+ // When blankSpace fully absorbs the keyboard opening, preserve current scroll position
109
+ // (only when keyboard is open — effective > 0 — not when closing)
110
+ if (scrollEffective === 0 && minimumPaddingAbsorbed > 0 && effective > 0) {
111
+ contentOffsetY.value = scroll.value;
112
+ return;
113
+ }
114
+ contentOffsetY.value = computeIOSContentOffset(relativeScroll, scrollEffective, size.value.height, layout.value.height, inverted, actualTotalPadding);
90
115
  },
91
116
  onMove: () => {
92
117
  "worklet";
@@ -102,11 +127,14 @@ function useChatKeyboard(scrollViewRef, options) {
102
127
  const effective = getEffectiveHeight(e.height, targetKeyboardHeight.value, offset);
103
128
  padding.value = effective;
104
129
  }
105
- }, [inverted, keyboardLiftBehavior, freeze, offset]);
130
+ }, [inverted, keyboardLiftBehavior, freeze, offset, extraContentPadding]);
106
131
  return {
107
132
  padding,
108
133
  currentHeight,
109
134
  contentOffsetY,
135
+ scroll,
136
+ layout,
137
+ size,
110
138
  onLayout,
111
139
  onContentSizeChange
112
140
  };
@@ -1 +1 @@
1
- {"version":3,"names":["useSharedValue","useKeyboardHandler","useScrollState","computeIOSContentOffset","getEffectiveHeight","isScrollAtEnd","shouldShiftContent","useChatKeyboard","scrollViewRef","options","inverted","keyboardLiftBehavior","freeze","offset","padding","currentHeight","contentOffsetY","targetKeyboardHeight","layout","size","scroll","onLayout","onContentSizeChange","onStart","e","height","value","effective","atEnd","Math","max","relativeScroll","onMove","onEnd"],"sources":["index.ios.ts"],"sourcesContent":["import { useSharedValue } from \"react-native-reanimated\";\n\nimport { useKeyboardHandler } from \"../../../hooks\";\nimport useScrollState from \"../../hooks/useScrollState\";\n\nimport {\n computeIOSContentOffset,\n getEffectiveHeight,\n isScrollAtEnd,\n shouldShiftContent,\n} from \"./helpers\";\n\nimport type { UseChatKeyboardOptions, UseChatKeyboardReturn } from \"./types\";\nimport type { AnimatedRef } from \"react-native-reanimated\";\nimport type Reanimated from \"react-native-reanimated\";\n\n/**\n * Hook that manages keyboard-driven scrolling for chat-style scroll views.\n * Calculates padding (extra scrollable space) and content shift values,\n * using iOS-specific strategy (contentOffset set once in onStart).\n *\n * @param scrollViewRef - Animated ref to the scroll view.\n * @param options - Configuration for inverted and keyboardLiftBehavior.\n * @returns Shared values for padding and contentOffsetY.\n * @example\n * ```tsx\n * const { padding, contentOffsetY } = useChatKeyboard(ref, {\n * inverted: false,\n * keyboardLiftBehavior: \"always\",\n * });\n * ```\n */\nfunction useChatKeyboard(\n scrollViewRef: AnimatedRef<Reanimated.ScrollView>,\n options: UseChatKeyboardOptions,\n): UseChatKeyboardReturn {\n const { inverted, keyboardLiftBehavior, freeze, offset } = options;\n\n const padding = useSharedValue(0);\n const currentHeight = useSharedValue(0);\n const contentOffsetY = useSharedValue(0);\n const targetKeyboardHeight = useSharedValue(0);\n\n const {\n layout,\n size,\n offset: scroll,\n onLayout,\n onContentSizeChange,\n } = useScrollState(scrollViewRef);\n\n useKeyboardHandler(\n {\n onStart: (e) => {\n \"worklet\";\n\n if (freeze) {\n return;\n }\n\n if (e.height > 0) {\n // eslint-disable-next-line react-compiler/react-compiler\n targetKeyboardHeight.value = e.height;\n }\n\n const effective = getEffectiveHeight(\n e.height,\n targetKeyboardHeight.value,\n offset,\n );\n\n const atEnd = isScrollAtEnd(\n scroll.value,\n layout.value.height,\n size.value.height,\n inverted,\n );\n\n // persistent mode: when keyboard shrinks, snap to end or hold position\n if (\n keyboardLiftBehavior === \"persistent\" &&\n effective < padding.value\n ) {\n padding.value = effective;\n\n if (atEnd) {\n if (inverted) {\n contentOffsetY.value = -effective;\n } else {\n contentOffsetY.value = Math.max(\n size.value.height - layout.value.height + effective,\n 0,\n );\n }\n } else {\n // Preserve current scroll position so the animated props\n // don't re-apply the stale contentOffset from keyboard open\n contentOffsetY.value = scroll.value;\n }\n\n return;\n }\n\n // never mode: when keyboard shrinks and at end, snap to end\n // to avoid ghost padding\n if (\n keyboardLiftBehavior === \"never\" &&\n effective < padding.value &&\n atEnd\n ) {\n padding.value = effective;\n\n if (inverted) {\n contentOffsetY.value = -effective;\n } else {\n contentOffsetY.value = Math.max(\n size.value.height - layout.value.height + effective,\n 0,\n );\n }\n\n return;\n }\n\n const relativeScroll = inverted\n ? scroll.value + padding.value\n : scroll.value - padding.value;\n\n padding.value = effective;\n\n if (!shouldShiftContent(keyboardLiftBehavior, atEnd)) {\n // Preserve current scroll position so animated props\n // don't re-apply a stale contentOffset when padding changes\n contentOffsetY.value = scroll.value;\n\n return;\n }\n\n contentOffsetY.value = computeIOSContentOffset(\n relativeScroll,\n effective,\n size.value.height,\n layout.value.height,\n inverted,\n );\n },\n onMove: () => {\n \"worklet\";\n\n // iOS doesn't need per-frame updates (contentOffset handles it)\n },\n onEnd: (e) => {\n \"worklet\";\n\n if (freeze) {\n return;\n }\n\n const effective = getEffectiveHeight(\n e.height,\n targetKeyboardHeight.value,\n offset,\n );\n\n padding.value = effective;\n },\n },\n [inverted, keyboardLiftBehavior, freeze, offset],\n );\n\n return {\n padding,\n currentHeight,\n contentOffsetY,\n onLayout,\n onContentSizeChange,\n };\n}\n\nexport { useChatKeyboard };\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["useSharedValue","useKeyboardHandler","useScrollState","computeIOSContentOffset","getEffectiveHeight","getScrollEffective","getVisibleMinimumPaddingFraction","isScrollAtEnd","shouldShiftContent","useChatKeyboard","scrollViewRef","options","inverted","keyboardLiftBehavior","freeze","offset","blankSpace","extraContentPadding","padding","currentHeight","contentOffsetY","targetKeyboardHeight","prevAbsorption","layout","size","scroll","onLayout","onContentSizeChange","onStart","e","height","value","effective","atEnd","visibleFraction","visiblePadding","minimumPaddingAbsorbed","Math","max","scrollEffective","actualTotalPadding","maxScroll","min","prevScrollEffective","relativeScroll","onMove","onEnd"],"sources":["index.ios.ts"],"sourcesContent":["import { useSharedValue } from \"react-native-reanimated\";\n\nimport { useKeyboardHandler } from \"../../../hooks\";\nimport useScrollState from \"../../hooks/useScrollState\";\n\nimport {\n computeIOSContentOffset,\n getEffectiveHeight,\n getScrollEffective,\n getVisibleMinimumPaddingFraction,\n isScrollAtEnd,\n shouldShiftContent,\n} from \"./helpers\";\n\nimport type { UseChatKeyboardOptions, UseChatKeyboardReturn } from \"./types\";\nimport type { AnimatedRef } from \"react-native-reanimated\";\nimport type Reanimated from \"react-native-reanimated\";\n\n/**\n * Hook that manages keyboard-driven scrolling for chat-style scroll views.\n * Calculates padding (extra scrollable space) and content shift values,\n * using iOS-specific strategy (contentOffset set once in onStart).\n *\n * @param scrollViewRef - Animated ref to the scroll view.\n * @param options - Configuration for inverted and keyboardLiftBehavior.\n * @returns Shared values for padding and contentOffsetY.\n * @example\n * ```tsx\n * const { padding, contentOffsetY } = useChatKeyboard(ref, {\n * inverted: false,\n * keyboardLiftBehavior: \"always\",\n * });\n * ```\n */\nfunction useChatKeyboard(\n scrollViewRef: AnimatedRef<Reanimated.ScrollView>,\n options: UseChatKeyboardOptions,\n): UseChatKeyboardReturn {\n const {\n inverted,\n keyboardLiftBehavior,\n freeze,\n offset,\n blankSpace,\n extraContentPadding,\n } = options;\n\n const padding = useSharedValue(0);\n const currentHeight = useSharedValue(0);\n const contentOffsetY = useSharedValue(0);\n const targetKeyboardHeight = useSharedValue(0);\n const prevAbsorption = useSharedValue(0);\n\n const {\n layout,\n size,\n offset: scroll,\n onLayout,\n onContentSizeChange,\n } = useScrollState(scrollViewRef);\n\n useKeyboardHandler(\n {\n onStart: (e) => {\n \"worklet\";\n\n if (freeze) {\n return;\n }\n\n if (e.height > 0) {\n // eslint-disable-next-line react-compiler/react-compiler\n targetKeyboardHeight.value = e.height;\n }\n\n const effective = getEffectiveHeight(\n e.height,\n targetKeyboardHeight.value,\n offset,\n );\n\n const atEnd = isScrollAtEnd(\n scroll.value,\n layout.value.height,\n size.value.height,\n inverted,\n );\n\n // Scale minimum padding absorption by how much of it is visible.\n // Fully visible → full absorption; fully off-screen → no absorption.\n const visibleFraction = getVisibleMinimumPaddingFraction(\n scroll.value,\n layout.value.height,\n size.value.height,\n blankSpace.value,\n inverted,\n );\n const visiblePadding = visibleFraction * blankSpace.value;\n const minimumPaddingAbsorbed = Math.max(\n 0,\n visiblePadding - extraContentPadding.value,\n );\n const scrollEffective = getScrollEffective(\n effective,\n minimumPaddingAbsorbed,\n );\n const actualTotalPadding = Math.max(\n blankSpace.value,\n effective + extraContentPadding.value,\n );\n\n // persistent mode: when keyboard shrinks, clamp to valid range\n if (\n keyboardLiftBehavior === \"persistent\" &&\n effective < padding.value\n ) {\n padding.value = effective;\n prevAbsorption.value = minimumPaddingAbsorbed;\n\n if (inverted) {\n const maxScroll = Math.max(\n size.value.height - layout.value.height,\n 0,\n );\n\n contentOffsetY.value = Math.max(\n -actualTotalPadding,\n Math.min(scroll.value, maxScroll),\n );\n } else {\n const maxScroll = Math.max(\n size.value.height - layout.value.height + actualTotalPadding,\n 0,\n );\n\n contentOffsetY.value = Math.max(\n 0,\n Math.min(scroll.value, maxScroll),\n );\n }\n\n return;\n }\n\n // never mode: when keyboard shrinks, clamp to valid range\n // to avoid ghost padding\n if (\n keyboardLiftBehavior === \"never\" &&\n effective < padding.value &&\n atEnd\n ) {\n padding.value = effective;\n prevAbsorption.value = minimumPaddingAbsorbed;\n\n if (inverted) {\n const maxScroll = Math.max(\n size.value.height - layout.value.height,\n 0,\n );\n\n contentOffsetY.value = Math.max(\n -actualTotalPadding,\n Math.min(scroll.value, maxScroll),\n );\n } else {\n const maxScroll = Math.max(\n size.value.height - layout.value.height + actualTotalPadding,\n 0,\n );\n\n contentOffsetY.value = Math.max(\n 0,\n Math.min(scroll.value, maxScroll),\n );\n }\n\n return;\n }\n\n // Undo only the scroll displacement that was actually applied\n // (not the full padding, which includes the absorbed portion).\n // Use the stored absorption from the previous event so that\n // the unwind matches the shift that was originally applied.\n const prevScrollEffective = getScrollEffective(\n padding.value,\n prevAbsorption.value,\n );\n const relativeScroll = inverted\n ? scroll.value + prevScrollEffective\n : scroll.value - prevScrollEffective;\n\n padding.value = effective;\n prevAbsorption.value = minimumPaddingAbsorbed;\n\n if (!shouldShiftContent(keyboardLiftBehavior, atEnd)) {\n // Preserve current scroll position so animated props\n // don't re-apply a stale contentOffset when padding changes\n contentOffsetY.value = scroll.value;\n\n return;\n }\n\n // When blankSpace fully absorbs the keyboard opening, preserve current scroll position\n // (only when keyboard is open — effective > 0 — not when closing)\n if (\n scrollEffective === 0 &&\n minimumPaddingAbsorbed > 0 &&\n effective > 0\n ) {\n contentOffsetY.value = scroll.value;\n\n return;\n }\n\n contentOffsetY.value = computeIOSContentOffset(\n relativeScroll,\n scrollEffective,\n size.value.height,\n layout.value.height,\n inverted,\n actualTotalPadding,\n );\n },\n onMove: () => {\n \"worklet\";\n\n // iOS doesn't need per-frame updates (contentOffset handles it)\n },\n onEnd: (e) => {\n \"worklet\";\n\n if (freeze) {\n return;\n }\n\n const effective = getEffectiveHeight(\n e.height,\n targetKeyboardHeight.value,\n offset,\n );\n\n padding.value = effective;\n },\n },\n [inverted, keyboardLiftBehavior, freeze, offset, extraContentPadding],\n );\n\n return {\n padding,\n currentHeight,\n contentOffsetY,\n scroll,\n layout,\n size,\n onLayout,\n onContentSizeChange,\n };\n}\n\nexport { useChatKeyboard };\n"],"mappings":"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","ignoreList":[]}
@@ -1,7 +1,7 @@
1
1
  import { scrollTo, useSharedValue } from "react-native-reanimated";
2
2
  import { useKeyboardHandler } from "../../../hooks";
3
3
  import useScrollState from "../../hooks/useScrollState";
4
- import { clampedScrollTarget, getEffectiveHeight, isScrollAtEnd, shouldShiftContent } from "./helpers";
4
+ import { clampedScrollTarget, getEffectiveHeight, getMinimumPaddingAbsorbed, getScrollEffective, getVisibleMinimumPaddingFraction, isScrollAtEnd, shouldShiftContent } from "./helpers";
5
5
  /**
6
6
  * Hook that manages keyboard-driven scrolling for chat-style scroll views.
7
7
  * Calculates padding (extra scrollable space) and content shift values,
@@ -12,10 +12,7 @@ import { clampedScrollTarget, getEffectiveHeight, isScrollAtEnd, shouldShiftCont
12
12
  * @returns Shared values for padding and contentOffsetY (always `undefined`).
13
13
  * @example
14
14
  * ```tsx
15
- * const { padding, contentOffsetY } = useChatKeyboard(ref, {
16
- * inverted: false,
17
- * keyboardLiftBehavior: "always",
18
- * });
15
+ * const { padding } = useChatKeyboard(ref, { inverted: false, keyboardLiftBehavior: "always" });
19
16
  * ```
20
17
  */
21
18
  function useChatKeyboard(scrollViewRef, options) {
@@ -23,13 +20,17 @@ function useChatKeyboard(scrollViewRef, options) {
23
20
  inverted,
24
21
  keyboardLiftBehavior,
25
22
  freeze,
26
- offset
23
+ offset,
24
+ blankSpace,
25
+ extraContentPadding
27
26
  } = options;
28
27
  const padding = useSharedValue(0);
29
28
  const currentHeight = useSharedValue(0);
30
29
  const offsetBeforeScroll = useSharedValue(0);
31
30
  const targetKeyboardHeight = useSharedValue(0);
32
31
  const closing = useSharedValue(false);
32
+ const minimumPaddingFractionOnOpen = useSharedValue(0);
33
+ const actualOpenShift = useSharedValue(0);
33
34
  const {
34
35
  layout,
35
36
  size,
@@ -37,10 +38,11 @@ function useChatKeyboard(scrollViewRef, options) {
37
38
  onLayout,
38
39
  onContentSizeChange
39
40
  } = useScrollState(scrollViewRef);
40
- const clampScrollIfNeeded = effective => {
41
+ const clampScrollIfNeeded = (effective, totalPaddingForMaxScroll) => {
41
42
  "worklet";
42
43
 
43
- const maxScroll = Math.max(size.value.height - layout.value.height + effective, 0);
44
+ const paddingForMax = totalPaddingForMaxScroll !== undefined ? totalPaddingForMaxScroll : effective;
45
+ const maxScroll = Math.max(size.value.height - layout.value.height + paddingForMax, 0);
44
46
  if (scroll.value > maxScroll) {
45
47
  scrollTo(scrollViewRef, 0, maxScroll, false);
46
48
  }
@@ -61,6 +63,12 @@ function useChatKeyboard(scrollViewRef, options) {
61
63
  }
62
64
  const effective = getEffectiveHeight(e.height, targetKeyboardHeight.value, offset);
63
65
  const atEnd = isScrollAtEnd(scroll.value, layout.value.height, size.value.height, inverted);
66
+
67
+ // Scale minimum padding absorption by how much of it is visible.
68
+ // Fully visible → full absorption; fully off-screen → no absorption.
69
+ const visibleFraction = getVisibleMinimumPaddingFraction(scroll.value, layout.value.height, size.value.height, blankSpace.value, inverted);
70
+ const minimumPaddingAbsorbed = visibleFraction >= 1 ? getMinimumPaddingAbsorbed(blankSpace.value, extraContentPadding.value) : 0;
71
+ const scrollEffective = getScrollEffective(effective, minimumPaddingAbsorbed);
64
72
  if (inverted && e.duration === -1) {
65
73
  // Android inverted: skip post-interactive snap-back events
66
74
  // (duration === -1 means the keyboard is re-establishing its
@@ -68,11 +76,18 @@ function useChatKeyboard(scrollViewRef, options) {
68
76
  return;
69
77
  } else if (e.height > 0) {
70
78
  // Android: keyboard opening — set padding + capture scroll position
79
+ minimumPaddingFractionOnOpen.value = visibleFraction >= 1 ? 1 : 0;
71
80
  padding.value = effective;
72
81
  offsetBeforeScroll.value = scroll.value;
73
82
  if (!inverted && keyboardLiftBehavior === "whenAtEnd" && !atEnd) {
74
83
  // Sentinel: don't scroll in onMove (non-inverted only)
75
84
  offsetBeforeScroll.value = -1;
85
+ } else if (!inverted && scrollEffective === 0) {
86
+ // blankSpace fully absorbs the keyboard — prevent scroll
87
+ offsetBeforeScroll.value = -1;
88
+ } else if (inverted && scrollEffective === 0) {
89
+ // blankSpace fully absorbs the keyboard — guard for inverted
90
+ offsetBeforeScroll.value = scroll.value;
76
91
  }
77
92
  } else {
78
93
  // Android: keyboard closing — re-capture scroll position
@@ -81,9 +96,9 @@ function useChatKeyboard(scrollViewRef, options) {
81
96
  } else {
82
97
  // Preserve "whenAtEnd" sentinel: if open didn't shift, close shouldn't either
83
98
  if (offsetBeforeScroll.value !== -1) {
84
- // Non-inverted: subtract padding to get the "natural" position
85
- // so onMove smoothly scrolls back from where the user is now
86
- offsetBeforeScroll.value = scroll.value - padding.value;
99
+ // Use the actual displacement recorded at end of open animation
100
+ // (not the theoretical value) so close is symmetric with open
101
+ offsetBeforeScroll.value = scroll.value - actualOpenShift.value;
87
102
  }
88
103
  }
89
104
  }
@@ -101,6 +116,9 @@ function useChatKeyboard(scrollViewRef, options) {
101
116
  return;
102
117
  }
103
118
  const effective = getEffectiveHeight(e.height, targetKeyboardHeight.value, offset);
119
+ const minimumPaddingAbsorbed = getMinimumPaddingAbsorbed(blankSpace.value, extraContentPadding.value) * minimumPaddingFractionOnOpen.value;
120
+ const scrollEffective = getScrollEffective(effective, minimumPaddingAbsorbed);
121
+ const actualTotalPadding = Math.max(blankSpace.value, effective + extraContentPadding.value);
104
122
 
105
123
  // Check if we should shift content based on position when keyboard started
106
124
  const wasAtEnd = isScrollAtEnd(offsetBeforeScroll.value, layout.value.height, size.value.height, inverted);
@@ -108,6 +126,9 @@ function useChatKeyboard(scrollViewRef, options) {
108
126
  // "never" at end: scroll along when keyboard closes to avoid jump
109
127
  if (keyboardLiftBehavior === "never" && wasAtEnd && effective < padding.value) {
110
128
  padding.value = effective;
129
+ if (scrollEffective === 0 && minimumPaddingAbsorbed > 0) {
130
+ return;
131
+ }
111
132
  scrollTo(scrollViewRef, 0, 0, false);
112
133
  return;
113
134
  }
@@ -115,11 +136,16 @@ function useChatKeyboard(scrollViewRef, options) {
115
136
  // Closing, not shifting: reduce padding to avoid gap
116
137
  if (closing.value && effective < padding.value) {
117
138
  padding.value = effective;
118
- clampScrollIfNeeded(effective);
139
+ clampScrollIfNeeded(effective, actualTotalPadding);
119
140
  }
120
141
  return;
121
142
  }
122
143
 
144
+ // When blankSpace fully absorbs the keyboard, skip scroll
145
+ if (scrollEffective === 0 && minimumPaddingAbsorbed > 0) {
146
+ return;
147
+ }
148
+
123
149
  // Persistent: don't let shift decrease
124
150
  if (keyboardLiftBehavior === "persistent") {
125
151
  const currentShift = offsetBeforeScroll.value + padding.value - scroll.value;
@@ -131,37 +157,33 @@ function useChatKeyboard(scrollViewRef, options) {
131
157
  } else if (closing.value) {
132
158
  // Not at end: reduce padding to avoid gap
133
159
  padding.value = effective;
134
- clampScrollIfNeeded(effective);
160
+ clampScrollIfNeeded(effective, actualTotalPadding);
135
161
  }
136
162
  return;
137
163
  }
138
164
  }
139
- const target = offsetBeforeScroll.value + padding.value - effective;
165
+ const target = offsetBeforeScroll.value + padding.value - scrollEffective;
140
166
  scrollTo(scrollViewRef, 0, target, false);
141
167
  } else {
142
168
  const effective = getEffectiveHeight(e.height, targetKeyboardHeight.value, offset);
169
+ const minimumPaddingAbsorbed = getMinimumPaddingAbsorbed(blankSpace.value, extraContentPadding.value) * minimumPaddingFractionOnOpen.value;
170
+ const scrollEffective = getScrollEffective(effective, minimumPaddingAbsorbed);
171
+ const actualTotalPadding = Math.max(blankSpace.value, effective + extraContentPadding.value);
143
172
 
144
- // "never" closing: scroll along when at end to avoid jump
173
+ // "never" closing: clamp scroll to valid range as inset shrinks
145
174
  if (keyboardLiftBehavior === "never" && closing.value && effective < padding.value) {
146
- const wasAtEnd = isScrollAtEnd(offsetBeforeScroll.value + padding.value, layout.value.height, size.value.height, false);
147
- if (wasAtEnd) {
148
- const target = clampedScrollTarget(offsetBeforeScroll.value, effective, size.value.height, layout.value.height);
149
- scrollTo(scrollViewRef, 0, target, false);
150
- } else {
151
- // Clamp to valid range as padding shrinks
152
- clampScrollIfNeeded(effective);
153
- }
175
+ clampScrollIfNeeded(effective, actualTotalPadding);
154
176
  return;
155
177
  }
156
178
  if (!shouldShiftContent(keyboardLiftBehavior, true)) {
157
179
  return;
158
180
  }
159
181
 
160
- // "whenAtEnd" sentinel check
182
+ // "whenAtEnd" sentinel check (also used for blankSpace full absorption)
161
183
  if (offsetBeforeScroll.value === -1) {
162
184
  if (closing.value) {
163
185
  // Keyboard didn't shift on open; ensure valid position on close
164
- clampScrollIfNeeded(effective);
186
+ clampScrollIfNeeded(effective, actualTotalPadding);
165
187
  }
166
188
  return;
167
189
  }
@@ -169,12 +191,17 @@ function useChatKeyboard(scrollViewRef, options) {
169
191
  // "persistent" closing: maintain position, clamped to valid range
170
192
  if (keyboardLiftBehavior === "persistent" && closing.value) {
171
193
  const keepAt = offsetBeforeScroll.value + padding.value;
172
- const maxScroll = Math.max(size.value.height - layout.value.height + effective, 0);
194
+ const maxScroll = Math.max(size.value.height - layout.value.height + actualTotalPadding, 0);
173
195
  scrollTo(scrollViewRef, 0, Math.min(keepAt, maxScroll), false);
174
196
  return;
175
197
  }
176
- const target = clampedScrollTarget(offsetBeforeScroll.value, effective, size.value.height, layout.value.height);
198
+ const target = clampedScrollTarget(offsetBeforeScroll.value, scrollEffective, size.value.height, layout.value.height, actualTotalPadding);
177
199
  scrollTo(scrollViewRef, 0, target, false);
200
+
201
+ // Track actual (clamped) displacement during open for symmetric close
202
+ if (!closing.value) {
203
+ actualOpenShift.value = target - offsetBeforeScroll.value;
204
+ }
178
205
  }
179
206
  },
180
207
  onEnd: e => {
@@ -185,12 +212,20 @@ function useChatKeyboard(scrollViewRef, options) {
185
212
  }
186
213
  const effective = getEffectiveHeight(e.height, targetKeyboardHeight.value, offset);
187
214
  padding.value = effective;
215
+
216
+ // Record actual scroll displacement so close can be symmetric
217
+ if (effective > 0 && offsetBeforeScroll.value !== -1) {
218
+ actualOpenShift.value = scroll.value - offsetBeforeScroll.value;
219
+ }
188
220
  }
189
221
  }, [inverted, keyboardLiftBehavior, freeze, offset]);
190
222
  return {
191
223
  padding,
192
224
  currentHeight,
193
225
  contentOffsetY: undefined,
226
+ scroll,
227
+ layout,
228
+ size,
194
229
  onLayout,
195
230
  onContentSizeChange
196
231
  };