react-native-keyboard-controller 1.21.0-beta.2 → 1.21.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/fabric/java/com/reactnativekeyboardcontroller/ClippingScrollViewDecoratorViewManager.kt +7 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/views/ClippingScrollViewDecoratorView.kt +28 -1
- package/android/src/paper/java/com/reactnativekeyboardcontroller/ClippingScrollViewDecoratorViewManager.kt +8 -0
- package/ios/animations/KeyboardAnimation.swift +2 -2
- package/ios/animations/SpringAnimation.swift +1 -1
- package/ios/animations/TimingAnimation.swift +1 -3
- package/ios/delegates/KCTextInputCompositeDelegate.swift +2 -2
- package/ios/extensions/CGFloat.swift +1 -3
- package/ios/interactive/KeyboardAreaExtender.swift +2 -1
- package/ios/observers/FocusedInputObserver.swift +4 -2
- package/ios/observers/movement/KeyboardTrackingView.swift +4 -1
- package/ios/observers/movement/observer/KeyboardMovementObserver+Interactive.swift +2 -0
- package/ios/observers/movement/observer/KeyboardMovementObserver+Listeners.swift +30 -7
- package/ios/observers/movement/observer/KeyboardMovementObserver.swift +2 -48
- package/ios/protocols/TextInput.swift +1 -1
- package/ios/swizzling/UIResponderSwizzle.swift +2 -4
- package/ios/traversal/FocusedInputHolder.swift +3 -3
- package/ios/traversal/KeyboardView.swift +1 -1
- package/ios/traversal/ViewHierarchyNavigator.swift +2 -2
- package/ios/views/KeyboardControllerViewManager.swift +5 -5
- package/jest/index.js +1 -3
- package/lib/commonjs/components/KeyboardAwareScrollView/index.js +43 -11
- package/lib/commonjs/components/KeyboardAwareScrollView/index.js.map +1 -1
- package/lib/commonjs/components/{ChatKit → KeyboardChatScrollView}/hooks.js.map +1 -1
- package/lib/commonjs/components/{ChatKit → KeyboardChatScrollView}/index.js +37 -12
- package/lib/commonjs/components/KeyboardChatScrollView/index.js.map +1 -0
- package/lib/commonjs/components/KeyboardChatScrollView/types.js.map +1 -0
- package/lib/commonjs/components/{ChatKit → KeyboardChatScrollView}/useChatKeyboard/helpers.js +35 -1
- package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/helpers.js.map +1 -0
- package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js +121 -0
- package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js.map +1 -0
- package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.js +205 -0
- package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.js.map +1 -0
- package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/types.js +6 -0
- package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/types.js.map +1 -0
- package/lib/commonjs/components/KeyboardStickyView/index.js +3 -1
- package/lib/commonjs/components/KeyboardStickyView/index.js.map +1 -1
- package/lib/commonjs/components/ScrollViewWithBottomPadding/index.js +18 -9
- package/lib/commonjs/components/ScrollViewWithBottomPadding/index.js.map +1 -1
- package/lib/commonjs/components/hooks/useScrollState.js +20 -1
- package/lib/commonjs/components/hooks/useScrollState.js.map +1 -1
- package/lib/commonjs/components/index.js +7 -7
- package/lib/commonjs/components/index.js.map +1 -1
- package/lib/commonjs/index.js +7 -7
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/specs/ClippingScrollViewDecoratorViewNativeComponent.js.map +1 -1
- package/lib/commonjs/types/views.js.map +1 -1
- package/lib/module/components/KeyboardAwareScrollView/index.js +43 -11
- package/lib/module/components/KeyboardAwareScrollView/index.js.map +1 -1
- package/lib/module/components/{ChatKit → KeyboardChatScrollView}/hooks.js.map +1 -1
- package/lib/module/components/KeyboardChatScrollView/index.js +73 -0
- package/lib/module/components/KeyboardChatScrollView/index.js.map +1 -0
- package/lib/module/components/KeyboardChatScrollView/types.js.map +1 -0
- package/lib/module/components/{ChatKit → KeyboardChatScrollView}/useChatKeyboard/helpers.js +34 -1
- package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/helpers.js.map +1 -0
- package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js +115 -0
- package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js.map +1 -0
- package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.js +199 -0
- package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.js.map +1 -0
- package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/types.js +2 -0
- package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/types.js.map +1 -0
- package/lib/module/components/KeyboardStickyView/index.js +3 -1
- package/lib/module/components/KeyboardStickyView/index.js.map +1 -1
- package/lib/module/components/ScrollViewWithBottomPadding/index.js +19 -10
- package/lib/module/components/ScrollViewWithBottomPadding/index.js.map +1 -1
- package/lib/module/components/hooks/useScrollState.js +21 -2
- package/lib/module/components/hooks/useScrollState.js.map +1 -1
- package/lib/module/components/index.js +1 -1
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/specs/ClippingScrollViewDecoratorViewNativeComponent.js.map +1 -1
- package/lib/module/types/views.js.map +1 -1
- package/lib/typescript/components/KeyboardChatScrollView/hooks.d.ts +2 -0
- package/lib/typescript/components/KeyboardChatScrollView/index.d.ts +12 -0
- package/lib/typescript/components/{ChatKit → KeyboardChatScrollView}/types.d.ts +5 -6
- package/lib/typescript/components/{ChatKit → KeyboardChatScrollView}/useChatKeyboard/helpers.d.ts +23 -3
- package/lib/typescript/components/KeyboardChatScrollView/useChatKeyboard/index.d.ts +21 -0
- package/lib/typescript/components/KeyboardChatScrollView/useChatKeyboard/index.ios.d.ts +21 -0
- package/lib/typescript/components/KeyboardChatScrollView/useChatKeyboard/types.d.ts +22 -0
- package/lib/typescript/components/ScrollViewWithBottomPadding/index.d.ts +2 -4
- package/lib/typescript/components/hooks/useScrollState.d.ts +3 -0
- package/lib/typescript/components/index.d.ts +2 -2
- package/lib/typescript/index.d.ts +2 -2
- package/lib/typescript/specs/ClippingScrollViewDecoratorViewNativeComponent.d.ts +1 -0
- package/lib/typescript/types/views.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/KeyboardAwareScrollView/index.tsx +53 -10
- package/src/components/KeyboardChatScrollView/hooks.ts +2 -0
- package/src/components/KeyboardChatScrollView/index.tsx +106 -0
- package/src/components/{ChatKit → KeyboardChatScrollView}/types.ts +5 -6
- package/src/components/{ChatKit → KeyboardChatScrollView}/useChatKeyboard/helpers.ts +46 -1
- package/src/components/KeyboardChatScrollView/useChatKeyboard/index.ios.ts +180 -0
- package/src/components/KeyboardChatScrollView/useChatKeyboard/index.ts +300 -0
- package/src/components/KeyboardChatScrollView/useChatKeyboard/types.ts +30 -0
- package/src/components/KeyboardStickyView/index.tsx +4 -3
- package/src/components/ScrollViewWithBottomPadding/index.tsx +22 -25
- package/src/components/hooks/useScrollState.ts +24 -2
- package/src/components/index.ts +2 -2
- package/src/index.ts +2 -2
- package/src/specs/ClippingScrollViewDecoratorViewNativeComponent.ts +1 -0
- package/src/types/views.ts +2 -0
- package/lib/commonjs/components/ChatKit/TODO.md +0 -20
- package/lib/commonjs/components/ChatKit/index.js.map +0 -1
- package/lib/commonjs/components/ChatKit/types.js.map +0 -1
- package/lib/commonjs/components/ChatKit/useChatKeyboard/helpers.js.map +0 -1
- package/lib/commonjs/components/ChatKit/useChatKeyboard/index.js +0 -147
- package/lib/commonjs/components/ChatKit/useChatKeyboard/index.js.map +0 -1
- package/lib/module/components/ChatKit/TODO.md +0 -20
- package/lib/module/components/ChatKit/index.js +0 -48
- package/lib/module/components/ChatKit/index.js.map +0 -1
- package/lib/module/components/ChatKit/types.js.map +0 -1
- package/lib/module/components/ChatKit/useChatKeyboard/helpers.js.map +0 -1
- package/lib/module/components/ChatKit/useChatKeyboard/index.js +0 -141
- package/lib/module/components/ChatKit/useChatKeyboard/index.js.map +0 -1
- package/lib/typescript/components/ChatKit/hooks.d.ts +0 -2
- package/lib/typescript/components/ChatKit/index.d.ts +0 -14
- package/lib/typescript/components/ChatKit/useChatKeyboard/index.d.ts +0 -36
- package/src/components/ChatKit/TODO.md +0 -20
- package/src/components/ChatKit/hooks.ts +0 -2
- package/src/components/ChatKit/index.tsx +0 -63
- package/src/components/ChatKit/useChatKeyboard/index.ts +0 -228
- /package/lib/commonjs/components/{ChatKit → KeyboardChatScrollView}/hooks.js +0 -0
- /package/lib/commonjs/components/{ChatKit → KeyboardChatScrollView}/types.js +0 -0
- /package/lib/module/components/{ChatKit → KeyboardChatScrollView}/hooks.js +0 -0
- /package/lib/module/components/{ChatKit → KeyboardChatScrollView}/types.js +0 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { scrollTo, useSharedValue } from "react-native-reanimated";
|
|
2
|
+
|
|
3
|
+
import { useKeyboardHandler } from "../../../hooks";
|
|
4
|
+
import useScrollState from "../../hooks/useScrollState";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
clampedScrollTarget,
|
|
8
|
+
getEffectiveHeight,
|
|
9
|
+
isScrollAtEnd,
|
|
10
|
+
shouldShiftContent,
|
|
11
|
+
} from "./helpers";
|
|
12
|
+
|
|
13
|
+
import type { UseChatKeyboardOptions, UseChatKeyboardReturn } from "./types";
|
|
14
|
+
import type { AnimatedRef } from "react-native-reanimated";
|
|
15
|
+
import type Reanimated from "react-native-reanimated";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Hook that manages keyboard-driven scrolling for chat-style scroll views.
|
|
19
|
+
* Calculates padding (extra scrollable space) and content shift values,
|
|
20
|
+
* using per-frame scrollTo updates (Android and other platforms).
|
|
21
|
+
*
|
|
22
|
+
* @param scrollViewRef - Animated ref to the scroll view.
|
|
23
|
+
* @param options - Configuration for inverted and keyboardLiftBehavior.
|
|
24
|
+
* @returns Shared values for padding and contentOffsetY (always `undefined`).
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* const { padding, contentOffsetY } = useChatKeyboard(ref, {
|
|
28
|
+
* inverted: false,
|
|
29
|
+
* keyboardLiftBehavior: "always",
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
function useChatKeyboard(
|
|
34
|
+
scrollViewRef: AnimatedRef<Reanimated.ScrollView>,
|
|
35
|
+
options: UseChatKeyboardOptions,
|
|
36
|
+
): UseChatKeyboardReturn {
|
|
37
|
+
const { inverted, keyboardLiftBehavior, freeze, offset } = options;
|
|
38
|
+
|
|
39
|
+
const padding = useSharedValue(0);
|
|
40
|
+
const currentHeight = useSharedValue(0);
|
|
41
|
+
const offsetBeforeScroll = useSharedValue(0);
|
|
42
|
+
const targetKeyboardHeight = useSharedValue(0);
|
|
43
|
+
const closing = useSharedValue(false);
|
|
44
|
+
|
|
45
|
+
const {
|
|
46
|
+
layout,
|
|
47
|
+
size,
|
|
48
|
+
offset: scroll,
|
|
49
|
+
onLayout,
|
|
50
|
+
onContentSizeChange,
|
|
51
|
+
} = useScrollState(scrollViewRef);
|
|
52
|
+
|
|
53
|
+
const clampScrollIfNeeded = (effective: number) => {
|
|
54
|
+
"worklet";
|
|
55
|
+
|
|
56
|
+
const maxScroll = Math.max(
|
|
57
|
+
size.value.height - layout.value.height + effective,
|
|
58
|
+
0,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (scroll.value > maxScroll) {
|
|
62
|
+
scrollTo(scrollViewRef, 0, maxScroll, false);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
useKeyboardHandler(
|
|
67
|
+
{
|
|
68
|
+
onStart: (e) => {
|
|
69
|
+
"worklet";
|
|
70
|
+
|
|
71
|
+
if (freeze) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (e.height > 0) {
|
|
76
|
+
// eslint-disable-next-line react-compiler/react-compiler
|
|
77
|
+
targetKeyboardHeight.value = e.height;
|
|
78
|
+
closing.value = false;
|
|
79
|
+
} else {
|
|
80
|
+
closing.value = true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const effective = getEffectiveHeight(
|
|
84
|
+
e.height,
|
|
85
|
+
targetKeyboardHeight.value,
|
|
86
|
+
offset,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const atEnd = isScrollAtEnd(
|
|
90
|
+
scroll.value,
|
|
91
|
+
layout.value.height,
|
|
92
|
+
size.value.height,
|
|
93
|
+
inverted,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (inverted && e.duration === -1) {
|
|
97
|
+
// Android inverted: skip post-interactive snap-back events
|
|
98
|
+
// (duration === -1 means the keyboard is re-establishing its
|
|
99
|
+
// position after an interactive gesture, not a real animation)
|
|
100
|
+
return;
|
|
101
|
+
} else if (e.height > 0) {
|
|
102
|
+
// Android: keyboard opening — set padding + capture scroll position
|
|
103
|
+
padding.value = effective;
|
|
104
|
+
offsetBeforeScroll.value = scroll.value;
|
|
105
|
+
|
|
106
|
+
if (!inverted && keyboardLiftBehavior === "whenAtEnd" && !atEnd) {
|
|
107
|
+
// Sentinel: don't scroll in onMove (non-inverted only)
|
|
108
|
+
offsetBeforeScroll.value = -1;
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
// Android: keyboard closing — re-capture scroll position
|
|
112
|
+
if (inverted) {
|
|
113
|
+
offsetBeforeScroll.value = scroll.value;
|
|
114
|
+
} else {
|
|
115
|
+
// Preserve "whenAtEnd" sentinel: if open didn't shift, close shouldn't either
|
|
116
|
+
if (offsetBeforeScroll.value !== -1) {
|
|
117
|
+
// Non-inverted: subtract padding to get the "natural" position
|
|
118
|
+
// so onMove smoothly scrolls back from where the user is now
|
|
119
|
+
offsetBeforeScroll.value = scroll.value - padding.value;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
onMove: (e) => {
|
|
125
|
+
"worklet";
|
|
126
|
+
|
|
127
|
+
if (freeze) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
currentHeight.value = e.height;
|
|
132
|
+
|
|
133
|
+
if (inverted) {
|
|
134
|
+
// Skip post-interactive snap-back (duration === -1)
|
|
135
|
+
if (e.duration === -1) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const effective = getEffectiveHeight(
|
|
140
|
+
e.height,
|
|
141
|
+
targetKeyboardHeight.value,
|
|
142
|
+
offset,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Check if we should shift content based on position when keyboard started
|
|
146
|
+
const wasAtEnd = isScrollAtEnd(
|
|
147
|
+
offsetBeforeScroll.value,
|
|
148
|
+
layout.value.height,
|
|
149
|
+
size.value.height,
|
|
150
|
+
inverted,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// "never" at end: scroll along when keyboard closes to avoid jump
|
|
154
|
+
if (
|
|
155
|
+
keyboardLiftBehavior === "never" &&
|
|
156
|
+
wasAtEnd &&
|
|
157
|
+
effective < padding.value
|
|
158
|
+
) {
|
|
159
|
+
padding.value = effective;
|
|
160
|
+
scrollTo(scrollViewRef, 0, 0, false);
|
|
161
|
+
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!shouldShiftContent(keyboardLiftBehavior, wasAtEnd)) {
|
|
166
|
+
// Closing, not shifting: reduce padding to avoid gap
|
|
167
|
+
if (closing.value && effective < padding.value) {
|
|
168
|
+
padding.value = effective;
|
|
169
|
+
clampScrollIfNeeded(effective);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Persistent: don't let shift decrease
|
|
176
|
+
if (keyboardLiftBehavior === "persistent") {
|
|
177
|
+
const currentShift =
|
|
178
|
+
offsetBeforeScroll.value + padding.value - scroll.value;
|
|
179
|
+
|
|
180
|
+
if (effective < currentShift) {
|
|
181
|
+
// When at end, allow scrolling back (snap to end + reduce padding)
|
|
182
|
+
if (wasAtEnd) {
|
|
183
|
+
padding.value = effective;
|
|
184
|
+
scrollTo(scrollViewRef, 0, 0, false);
|
|
185
|
+
} else if (closing.value) {
|
|
186
|
+
// Not at end: reduce padding to avoid gap
|
|
187
|
+
padding.value = effective;
|
|
188
|
+
clampScrollIfNeeded(effective);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const target = offsetBeforeScroll.value + padding.value - effective;
|
|
196
|
+
|
|
197
|
+
scrollTo(scrollViewRef, 0, target, false);
|
|
198
|
+
} else {
|
|
199
|
+
const effective = getEffectiveHeight(
|
|
200
|
+
e.height,
|
|
201
|
+
targetKeyboardHeight.value,
|
|
202
|
+
offset,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// "never" closing: scroll along when at end to avoid jump
|
|
206
|
+
if (
|
|
207
|
+
keyboardLiftBehavior === "never" &&
|
|
208
|
+
closing.value &&
|
|
209
|
+
effective < padding.value
|
|
210
|
+
) {
|
|
211
|
+
const wasAtEnd = isScrollAtEnd(
|
|
212
|
+
offsetBeforeScroll.value + padding.value,
|
|
213
|
+
layout.value.height,
|
|
214
|
+
size.value.height,
|
|
215
|
+
false,
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
if (wasAtEnd) {
|
|
219
|
+
const target = clampedScrollTarget(
|
|
220
|
+
offsetBeforeScroll.value,
|
|
221
|
+
effective,
|
|
222
|
+
size.value.height,
|
|
223
|
+
layout.value.height,
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
scrollTo(scrollViewRef, 0, target, false);
|
|
227
|
+
} else {
|
|
228
|
+
// Clamp to valid range as padding shrinks
|
|
229
|
+
clampScrollIfNeeded(effective);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (!shouldShiftContent(keyboardLiftBehavior, true)) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// "whenAtEnd" sentinel check
|
|
240
|
+
if (offsetBeforeScroll.value === -1) {
|
|
241
|
+
if (closing.value) {
|
|
242
|
+
// Keyboard didn't shift on open; ensure valid position on close
|
|
243
|
+
clampScrollIfNeeded(effective);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// "persistent" closing: maintain position, clamped to valid range
|
|
250
|
+
if (keyboardLiftBehavior === "persistent" && closing.value) {
|
|
251
|
+
const keepAt = offsetBeforeScroll.value + padding.value;
|
|
252
|
+
const maxScroll = Math.max(
|
|
253
|
+
size.value.height - layout.value.height + effective,
|
|
254
|
+
0,
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
scrollTo(scrollViewRef, 0, Math.min(keepAt, maxScroll), false);
|
|
258
|
+
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const target = clampedScrollTarget(
|
|
263
|
+
offsetBeforeScroll.value,
|
|
264
|
+
effective,
|
|
265
|
+
size.value.height,
|
|
266
|
+
layout.value.height,
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
scrollTo(scrollViewRef, 0, target, false);
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
onEnd: (e) => {
|
|
273
|
+
"worklet";
|
|
274
|
+
|
|
275
|
+
if (freeze) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const effective = getEffectiveHeight(
|
|
280
|
+
e.height,
|
|
281
|
+
targetKeyboardHeight.value,
|
|
282
|
+
offset,
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
padding.value = effective;
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
[inverted, keyboardLiftBehavior, freeze, offset],
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
padding,
|
|
293
|
+
currentHeight,
|
|
294
|
+
contentOffsetY: undefined,
|
|
295
|
+
onLayout,
|
|
296
|
+
onContentSizeChange,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export { useChatKeyboard };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { LayoutChangeEvent } from "react-native";
|
|
2
|
+
import type { SharedValue } from "react-native-reanimated";
|
|
3
|
+
|
|
4
|
+
type KeyboardLiftBehavior = "always" | "whenAtEnd" | "persistent" | "never";
|
|
5
|
+
|
|
6
|
+
type UseChatKeyboardOptions = {
|
|
7
|
+
inverted: boolean;
|
|
8
|
+
keyboardLiftBehavior: KeyboardLiftBehavior;
|
|
9
|
+
freeze: boolean;
|
|
10
|
+
offset: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type UseChatKeyboardReturn = {
|
|
14
|
+
/** Extra scrollable space (= keyboard height). Used as contentInset on iOS, contentInsetBottom/contentInsetTop on Android. */
|
|
15
|
+
padding: SharedValue<number>;
|
|
16
|
+
/** Raw keyboard height updated every frame in onMove. Used to force Reanimated commits on Fabric. */
|
|
17
|
+
currentHeight: SharedValue<number>;
|
|
18
|
+
/** Absolute Y content offset for iOS (set once in onStart). `undefined` on Android. */
|
|
19
|
+
contentOffsetY: SharedValue<number> | undefined;
|
|
20
|
+
/** Callback to attach to ScrollView's onLayout prop to capture initial viewport dimensions. */
|
|
21
|
+
onLayout: (e: LayoutChangeEvent) => void;
|
|
22
|
+
/** Callback to attach to ScrollView's onContentSizeChange prop to capture initial content dimensions. */
|
|
23
|
+
onContentSizeChange: (w: number, h: number) => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type {
|
|
27
|
+
KeyboardLiftBehavior,
|
|
28
|
+
UseChatKeyboardOptions,
|
|
29
|
+
UseChatKeyboardReturn,
|
|
30
|
+
};
|
|
@@ -59,11 +59,12 @@ const KeyboardStickyView = forwardRef<
|
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
const styles = useMemo(() => {
|
|
62
|
+
const disabled = Animated.add(Animated.multiply(height, 0), closed);
|
|
63
|
+
const active = Animated.add(height, offset);
|
|
64
|
+
|
|
62
65
|
return [
|
|
63
66
|
{
|
|
64
|
-
transform: [
|
|
65
|
-
{ translateY: enabled ? Animated.add(height, offset) : closed },
|
|
66
|
-
],
|
|
67
|
+
transform: [{ translateY: enabled ? active : disabled }],
|
|
67
68
|
},
|
|
68
69
|
style,
|
|
69
70
|
];
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React, { forwardRef } from "react";
|
|
2
|
-
import { Platform } from "react-native";
|
|
2
|
+
import { Platform, View } from "react-native";
|
|
3
3
|
import Reanimated, { useAnimatedProps } from "react-native-reanimated";
|
|
4
4
|
|
|
5
5
|
import { ClippingScrollView } from "../../bindings";
|
|
6
6
|
|
|
7
7
|
import styles from "./styles";
|
|
8
8
|
|
|
9
|
-
import type { ScrollViewProps
|
|
9
|
+
import type { ScrollViewProps } from "react-native";
|
|
10
10
|
import type { SharedValue } from "react-native-reanimated";
|
|
11
11
|
|
|
12
12
|
const OS = Platform.OS;
|
|
@@ -28,10 +28,8 @@ type ScrollViewWithBottomPaddingProps = {
|
|
|
28
28
|
children?: React.ReactNode;
|
|
29
29
|
inverted?: boolean;
|
|
30
30
|
bottomPadding: SharedValue<number>;
|
|
31
|
-
/** Absolute Y content offset (iOS only, for
|
|
31
|
+
/** Absolute Y content offset (iOS only, for KeyboardChatScrollView). */
|
|
32
32
|
contentOffsetY?: SharedValue<number>;
|
|
33
|
-
/** Style applied to the container wrapper (Android only, for ChatKit translateY). */
|
|
34
|
-
containerStyle?: StyleProp<ViewStyle>;
|
|
35
33
|
} & ScrollViewProps;
|
|
36
34
|
|
|
37
35
|
const ScrollViewWithBottomPadding = forwardRef<
|
|
@@ -46,20 +44,16 @@ const ScrollViewWithBottomPadding = forwardRef<
|
|
|
46
44
|
scrollIndicatorInsets,
|
|
47
45
|
inverted,
|
|
48
46
|
contentOffsetY,
|
|
49
|
-
containerStyle,
|
|
50
47
|
children,
|
|
51
|
-
style,
|
|
52
48
|
...rest
|
|
53
49
|
},
|
|
54
50
|
ref,
|
|
55
51
|
) => {
|
|
56
52
|
const animatedProps = useAnimatedProps(() => {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const top =
|
|
61
|
-
? 0
|
|
62
|
-
: bottomPadding.value + (contentInset?.top || 0);
|
|
53
|
+
const insetTop = inverted ? bottomPadding.value : 0;
|
|
54
|
+
const insetBottom = !inverted ? bottomPadding.value : 0;
|
|
55
|
+
const bottom = insetBottom + (contentInset?.bottom || 0);
|
|
56
|
+
const top = insetTop + (contentInset?.top || 0);
|
|
63
57
|
|
|
64
58
|
const result: Record<string, unknown> = {
|
|
65
59
|
// iOS prop
|
|
@@ -76,7 +70,8 @@ const ScrollViewWithBottomPadding = forwardRef<
|
|
|
76
70
|
left: scrollIndicatorInsets?.left,
|
|
77
71
|
},
|
|
78
72
|
// Android prop
|
|
79
|
-
contentInsetBottom:
|
|
73
|
+
contentInsetBottom: insetBottom,
|
|
74
|
+
contentInsetTop: insetTop,
|
|
80
75
|
};
|
|
81
76
|
|
|
82
77
|
if (contentOffsetY) {
|
|
@@ -100,18 +95,20 @@ const ScrollViewWithBottomPadding = forwardRef<
|
|
|
100
95
|
return (
|
|
101
96
|
<ReanimatedClippingScrollView
|
|
102
97
|
animatedProps={animatedProps}
|
|
103
|
-
style={
|
|
104
|
-
styles.container,
|
|
105
|
-
OS === "android" ? containerStyle : undefined,
|
|
106
|
-
]}
|
|
98
|
+
style={styles.container}
|
|
107
99
|
>
|
|
108
|
-
<ScrollViewComponent
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
100
|
+
<ScrollViewComponent ref={ref} animatedProps={animatedProps} {...rest}>
|
|
101
|
+
{inverted ? (
|
|
102
|
+
// The only thing it can break is `StickyHeader`, but it's already broken in FlatList and other lists
|
|
103
|
+
// don't support this functionality, so we can add additional view here
|
|
104
|
+
// The correct fix would be to add a new prop in ScrollView that allows
|
|
105
|
+
// to customize children extraction logic and skip custom view
|
|
106
|
+
<View collapsable={false} nativeID="container">
|
|
107
|
+
{children}
|
|
108
|
+
</View>
|
|
109
|
+
) : (
|
|
110
|
+
children
|
|
111
|
+
)}
|
|
115
112
|
</ScrollViewComponent>
|
|
116
113
|
</ReanimatedClippingScrollView>
|
|
117
114
|
);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { useEffect } from "react";
|
|
1
|
+
import { useCallback, useEffect } from "react";
|
|
2
2
|
import { useEvent, useSharedValue } from "react-native-reanimated";
|
|
3
3
|
|
|
4
4
|
import { useEventHandlerRegistration } from "../../internal";
|
|
5
5
|
|
|
6
|
+
import type { LayoutChangeEvent } from "react-native";
|
|
6
7
|
import type { AnimatedRef } from "react-native-reanimated";
|
|
7
8
|
import type Reanimated from "react-native-reanimated";
|
|
8
9
|
|
|
@@ -53,7 +54,28 @@ const useScrollState = (ref: AnimatedRef<Reanimated.ScrollView>) => {
|
|
|
53
54
|
};
|
|
54
55
|
}, []);
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
// `onContentSizeChange` is synthesized in JS (from the content container's
|
|
58
|
+
// onLayout) and `onLayout` has a different payload shape than scroll events,
|
|
59
|
+
// so neither can be reliably captured via native event registration.
|
|
60
|
+
// Instead we expose callbacks for the consumer to attach as props.
|
|
61
|
+
const onLayout = useCallback(
|
|
62
|
+
(e: LayoutChangeEvent) => {
|
|
63
|
+
layout.value = {
|
|
64
|
+
width: e.nativeEvent.layout.width,
|
|
65
|
+
height: e.nativeEvent.layout.height,
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
[layout],
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const onContentSizeChange = useCallback(
|
|
72
|
+
(w: number, h: number) => {
|
|
73
|
+
size.value = { width: w, height: h };
|
|
74
|
+
},
|
|
75
|
+
[size],
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
return { offset, layout, size, onLayout, onContentSizeChange };
|
|
57
79
|
};
|
|
58
80
|
|
|
59
81
|
export default useScrollState;
|
package/src/components/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ export {
|
|
|
5
5
|
default as KeyboardToolbar,
|
|
6
6
|
DefaultKeyboardToolbarTheme,
|
|
7
7
|
} from "./KeyboardToolbar";
|
|
8
|
-
export { default as
|
|
8
|
+
export { default as KeyboardChatScrollView } from "./KeyboardChatScrollView";
|
|
9
9
|
export type { KeyboardAvoidingViewProps } from "./KeyboardAvoidingView";
|
|
10
10
|
export type { KeyboardStickyViewProps } from "./KeyboardStickyView";
|
|
11
11
|
export type {
|
|
@@ -13,4 +13,4 @@ export type {
|
|
|
13
13
|
KeyboardAwareScrollViewRef,
|
|
14
14
|
} from "./KeyboardAwareScrollView";
|
|
15
15
|
export type { KeyboardToolbarProps } from "./KeyboardToolbar";
|
|
16
|
-
export type {
|
|
16
|
+
export type { KeyboardChatScrollViewProps } from "./KeyboardChatScrollView/types";
|
package/src/index.ts
CHANGED
|
@@ -8,7 +8,7 @@ export * from "./types";
|
|
|
8
8
|
export * from "./compat";
|
|
9
9
|
|
|
10
10
|
export {
|
|
11
|
-
|
|
11
|
+
KeyboardChatScrollView,
|
|
12
12
|
KeyboardAvoidingView,
|
|
13
13
|
KeyboardStickyView,
|
|
14
14
|
KeyboardAwareScrollView,
|
|
@@ -17,7 +17,7 @@ export {
|
|
|
17
17
|
DefaultKeyboardToolbarTheme,
|
|
18
18
|
} from "./components";
|
|
19
19
|
export type {
|
|
20
|
-
|
|
20
|
+
KeyboardChatScrollViewProps,
|
|
21
21
|
KeyboardAvoidingViewProps,
|
|
22
22
|
KeyboardStickyViewProps,
|
|
23
23
|
KeyboardAwareScrollViewProps,
|
package/src/types/views.ts
CHANGED
|
@@ -53,5 +53,7 @@ export type ClippingScrollViewProps = PropsWithChildren<
|
|
|
53
53
|
ViewProps & {
|
|
54
54
|
/** An additional space that gets applied to the bottom of the `ScrollView` (inside a scrollable content). Default is `0`. */
|
|
55
55
|
contentInsetBottom?: number;
|
|
56
|
+
/** An additional space that gets applied to the top of the `ScrollView` (inside a scrollable content). Default is `0`. */
|
|
57
|
+
contentInsetTop?: number;
|
|
56
58
|
}
|
|
57
59
|
>;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# Findings
|
|
2
|
-
|
|
3
|
-
1. We can not translate whole `ScrollView` because interactive dismissal will have double scroll if we move it by `translateY` or content will be truncated if we don't do it (same with external container).
|
|
4
|
-
2. So we need to move only content inside `ScrollView`.
|
|
5
|
-
3. If we use a separate `scrollTo` we still may have a random jump. The only one way is to use `contentOffset` + `padding` on `ScrollView`.
|
|
6
|
-
4. Input must be always in `KeyboardStickyView` because it's always pushing above the keyboard (unlike content, which may stay in place).
|
|
7
|
-
5. Changing `contentOffset` on `ScrollView` will not work. On iOS it works, but on Android sometimes it doesn't scroll to correct position (when you scroll list to the end) and on paper architecture it is flickering.
|
|
8
|
-
6. Next idea is to use `contentInset` on iOS and create custom `ClippingScrollView` on Android (with polyfill `contentInsetBottom` prop).
|
|
9
|
-
|
|
10
|
-
## To Do
|
|
11
|
-
|
|
12
|
-
- [x] check how `ClippingScrollView` works in `KeyboardAwareScrollView` on Android (seems to be working well)
|
|
13
|
-
- [x] introduce `useCombinedRef` hook in separate PR
|
|
14
|
-
- [x] make sure, that style property doesn't affect the behavior with/without `ClippingScrollView` (test on a ScrollView that doesn't tke full screen?) <-- tested by comparing iOS with Android + `ClippingScrollView` (`contentInsetBottom` never override `paddingBottom` from `style`/`contentContainerStyle` even if ScrollView has `maxHeight=150` and we have only 2 inputs limitation + also tested `ScrollView` without height restrictions, in this case `paddingBottom` on `style` is not getting applied (it doesn't have an effect on both iOS/Android), but `contentContainerStyle` + `paddingBottom` works as expected and if I add `contentInsetBottom` to `ClippingScrollView` it doesn't overwrite a padding and increases scrollable area)
|
|
15
|
-
- [x] `ClippingScrollView` + `KeyboardAwareScrollView` check by e2e tests (seems to be working, though on Android emulator + e2e tests sometimes when keyboard disappear we still have keyboard space)
|
|
16
|
-
- [x] introduce `ClippingScrollView` in separate PR
|
|
17
|
-
- [x] create a polyfill-version of ScrollView `that` can add scrollable padding in the bottom of the content? How to wrap custom `ScrollView` with it (cause we may pass one from react-native-gesture-handler)? Should just adjust `contentInset` (scroll management should belong to `KeyboardAwareScrollView`) (ScrollViewWithKeyboardPadding/ScrollViewWithKeyboardSpace)
|
|
18
|
-
- [x] use `ClippingScrollView` in `KeyboardAwareScrollView` in separate PR
|
|
19
|
-
- [-] introduce `ChatKit`
|
|
20
|
-
- [] create issue in reanimated repo about `scrollTo` performance regression
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNativeReanimated","_useCombinedRef","_interopRequireDefault","_ScrollViewWithBottomPadding","_useChatKeyboard","e","__esModule","default","t","WeakMap","r","n","o","i","f","__proto__","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","_extends","assign","bind","arguments","length","apply","ChatScrollView","forwardRef","children","ScrollViewComponent","Reanimated","ScrollView","inverted","keyboardLiftBehavior","freeze","offset","rest","ref","scrollViewRef","useAnimatedRef","onRef","useCombinedRef","padding","contentOffsetY","containerTranslateY","useChatKeyboard","containerStyle","useAnimatedStyle","transform","translateY","value","createElement","bottomPadding","ChatKit","_default","exports"],"sources":["index.tsx"],"sourcesContent":["import React, { forwardRef } from \"react\";\nimport { useAnimatedRef, useAnimatedStyle } from \"react-native-reanimated\";\nimport Reanimated from \"react-native-reanimated\";\n\nimport useCombinedRef from \"../hooks/useCombinedRef\";\nimport ScrollViewWithBottomPadding from \"../ScrollViewWithBottomPadding\";\n\nimport { useChatKeyboard } from \"./useChatKeyboard\";\n\nimport type { ChatKitScrollViewProps } from \"./types\";\n\nconst ChatScrollView = forwardRef<\n Reanimated.ScrollView,\n React.PropsWithChildren<ChatKitScrollViewProps>\n>(\n (\n {\n children,\n ScrollViewComponent = Reanimated.ScrollView,\n inverted = false,\n keyboardLiftBehavior = \"always\",\n freeze = false,\n offset = 0,\n ...rest\n },\n ref,\n ) => {\n const scrollViewRef = useAnimatedRef<Reanimated.ScrollView>();\n const onRef = useCombinedRef(ref, scrollViewRef);\n\n const { padding, contentOffsetY, containerTranslateY } = useChatKeyboard(\n scrollViewRef,\n { inverted, keyboardLiftBehavior, freeze, offset },\n );\n\n const containerStyle = useAnimatedStyle(\n () => ({\n transform: [{ translateY: containerTranslateY.value }],\n }),\n [],\n );\n\n return (\n <ScrollViewWithBottomPadding\n ref={onRef}\n {...rest}\n bottomPadding={padding}\n containerStyle={containerStyle}\n contentOffsetY={contentOffsetY}\n inverted={inverted}\n ScrollViewComponent={ScrollViewComponent}\n >\n {children}\n </ScrollViewWithBottomPadding>\n );\n },\n);\n\nconst ChatKit = {\n ScrollView: ChatScrollView,\n};\n\nexport default ChatKit;\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":[],"sources":["types.ts"],"sourcesContent":["import type { AnimatedScrollViewComponent } from \"../ScrollViewWithBottomPadding\";\nimport type { ScrollViewProps } from \"react-native\";\n\ntype KeyboardLiftBehavior = \"always\" | \"whenAtEnd\" | \"persistent\" | \"never\";\n\nexport type ChatKitScrollViewProps = {\n /** Custom component for `ScrollView`. Default is `ScrollView`. */\n ScrollViewComponent: AnimatedScrollViewComponent;\n /** Whether list are using `inverted` prop. Default is `false`. */\n inverted?: boolean;\n /**\n * The distance (in pixels) between the bottom of the screen and the `ScrollView`.\n * When the keyboard appears, the scroll view will only push content by the effective\n * distance (`keyboardHeight - offset`) instead of the full keyboard height.\n *\n * Useful when the input is not at the very bottom of the screen (e.g., above safe area, above\n * bottom tabs, etc. - in this case offset should be equal to the height of the elements between\n * `ScrollView` and bottom of the screen).\n *\n * Default is `0`.\n */\n offset?: number;\n /**\n * Determines how the chat content should behave when the keyboard appears, specifically whether\n * the scroll view should automatically lift its content to keep it visible above the keyboard.\n *\n * Possible values:\n * - `'always'`: The content always lifts along with the keyboard, ensuring the messages from the bottom part of screen\n * remain visible regardless of the current scroll position. This is the default behavior for most chat applications (used in Telegram).\n * - `'whenAtEnd'`: The content lifts only if the scroll view is at the end (i.e., the last message\n * is visible or near the bottom). This prevents unnecessary adjustments when the user is scrolling\n * through older messages (ChatGPT mobile app behavior).\n * - `'persistent'`: The content always lifts when the keyboard appears (similar to `'always'`), but\n * does not reset (lower) when the keyboard hides. This mimics behaviors where the view remains adjusted\n * after keyboard dismissal to maintain focus on the latest content without shifting back (Claude mobile app behavior).\n * - `'never'`: The content does not lift at all when the keyboard appears. Use this for scenarios\n * when you don't want to disturb user attention with animations (Perplexity mobile app behavior).\n *\n * Default is `'always'`.\n */\n keyboardLiftBehavior?: KeyboardLiftBehavior;\n /**\n * When `true`, freezes all keyboard-driven layout changes (padding, content offset, scroll position).\n * Useful when dismissing the keyboard to open a bottom sheet — prevents visual disruption\n * while the sheet is visible.\n *\n * Default is `false`.\n */\n freeze?: boolean;\n} & ScrollViewProps;\n"],"mappings":"","ignoreList":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["AT_END_THRESHOLD","isScrollAtEnd","scrollOffset","layoutHeight","contentHeight","shouldShiftContent","behavior","isAtEnd","clampedScrollTarget","offsetBeforeScroll","keyboardHeight","maxScroll","Math","max","min","computeIOSContentOffset","relativeScroll","inverted"],"sources":["helpers.ts"],"sourcesContent":["type KeyboardLiftBehavior = \"always\" | \"whenAtEnd\" | \"persistent\" | \"never\";\n\nconst AT_END_THRESHOLD = 20;\n\n/**\n * Check whether the scroll view is at the end of its content.\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 * @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 * ```\n */\nexport function isScrollAtEnd(\n scrollOffset: number,\n layoutHeight: number,\n contentHeight: number,\n): boolean {\n \"worklet\";\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":";;;;;;;;;AAEA,MAAMA,gBAAgB,GAAG,EAAE;;AAE3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,aAAaA,CAC3BC,YAAoB,EACpBC,YAAoB,EACpBC,aAAqB,EACZ;EACT,SAAS;;EAET,OAAOF,YAAY,GAAGC,YAAY,IAAIC,aAAa,GAAGJ,gBAAgB;AACxE;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASK,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;AACO,SAASC,mBAAmBA,CACjCC,kBAA0B,EAC1BC,cAAsB,EACtBN,aAAqB,EACrBD,YAAoB,EACZ;EACR,SAAS;;EAET,MAAMQ,SAAS,GAAGC,IAAI,CAACC,GAAG,CAACT,aAAa,GAAGD,YAAY,GAAGO,cAAc,EAAE,CAAC,CAAC;EAE5E,OAAOE,IAAI,CAACE,GAAG,CAACF,IAAI,CAACC,GAAG,CAACJ,kBAAkB,GAAGC,cAAc,EAAE,CAAC,CAAC,EAAEC,SAAS,CAAC;AAC9E;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASI,uBAAuBA,CACrCC,cAAsB,EACtBN,cAAsB,EACtBN,aAAqB,EACrBD,YAAoB,EACpBc,QAAiB,EACT;EACR,SAAS;;EAET,IAAIA,QAAQ,EAAE;IACZ,MAAMN,SAAS,GAAGC,IAAI,CAACC,GAAG,CAACT,aAAa,GAAGD,YAAY,EAAE,CAAC,CAAC;IAE3D,OAAOS,IAAI,CAACC,GAAG,CACbD,IAAI,CAACE,GAAG,CAACE,cAAc,GAAGN,cAAc,EAAEC,SAAS,CAAC,EACpD,CAACD,cACH,CAAC;EACH;EAEA,MAAMC,SAAS,GAAGC,IAAI,CAACC,GAAG,CAACT,aAAa,GAAGD,YAAY,GAAGO,cAAc,EAAE,CAAC,CAAC;EAE5E,OAAOE,IAAI,CAACE,GAAG,CAACF,IAAI,CAACC,GAAG,CAACH,cAAc,GAAGM,cAAc,EAAE,CAAC,CAAC,EAAEL,SAAS,CAAC;AAC1E","ignoreList":[]}
|