react-native-keyboard-controller 1.21.0-beta.2 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/android/src/base/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt +1 -0
- package/android/src/fabric/java/com/reactnativekeyboardcontroller/ClippingScrollViewDecoratorViewManager.kt +14 -0
- package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +8 -0
- package/android/src/fabric/java/com/reactnativekeyboardcontroller/KeyboardToolbarGroupViewManager.kt +23 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/extensions/ReactContext.kt +12 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/FocusedInputObserver.kt +2 -1
- package/android/src/main/java/com/reactnativekeyboardcontroller/listeners/WindowDimensionListener.kt +5 -1
- package/android/src/main/java/com/reactnativekeyboardcontroller/managers/KeyboardToolbarGroupViewManagerImpl.kt +13 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/modal/ModalAttachedWatcher.kt +4 -6
- package/android/src/main/java/com/reactnativekeyboardcontroller/modules/KeyboardControllerModuleImpl.kt +26 -0
- package/android/src/main/java/com/reactnativekeyboardcontroller/traversal/ViewHierarchyNavigator.kt +31 -4
- package/android/src/main/java/com/reactnativekeyboardcontroller/views/ClippingScrollViewDecoratorView.kt +33 -1
- package/android/src/main/java/com/reactnativekeyboardcontroller/views/KeyboardToolbarGroupReactViewGroup.kt +13 -0
- package/android/src/main/jni/RNKC.h +2 -0
- package/android/src/paper/java/com/reactnativekeyboardcontroller/ClippingScrollViewDecoratorViewManager.kt +16 -0
- package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardControllerModule.kt +9 -0
- package/android/src/paper/java/com/reactnativekeyboardcontroller/KeyboardToolbarGroupViewManager.kt +15 -0
- package/android/src/turbo/java/com/reactnativekeyboardcontroller/KeyboardControllerPackage.kt +2 -0
- package/common/cpp/react/renderer/components/RNKC/RNKCClippingScrollViewDecoratorViewComponentDescriptor.h +27 -0
- package/common/cpp/react/renderer/components/RNKC/RNKCClippingScrollViewDecoratorViewShadowNode.cpp +14 -0
- package/common/cpp/react/renderer/components/RNKC/RNKCClippingScrollViewDecoratorViewShadowNode.h +30 -0
- package/common/cpp/react/renderer/components/RNKC/RNKCClippingScrollViewDecoratorViewState.h +28 -0
- package/common/cpp/react/renderer/components/RNKC/RNKCKeyboardToolbarGroupViewComponentDescriptor.h +20 -0
- package/common/cpp/react/renderer/components/RNKC/RNKCKeyboardToolbarGroupViewShadowNode.cpp +7 -0
- package/common/cpp/react/renderer/components/RNKC/RNKCKeyboardToolbarGroupViewShadowNode.h +23 -0
- package/common/cpp/react/renderer/components/RNKC/RNKCKeyboardToolbarGroupViewState.h +21 -0
- package/ios/KeyboardControllerModule.mm +36 -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/extensions/UIApplication.swift +1 -1
- package/ios/interactive/KeyboardAreaExtender.swift +2 -1
- package/ios/observers/FocusedInputObserver.swift +6 -3
- 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 +51 -5
- package/ios/views/ClippingScrollViewDecoratorViewManager.h +26 -0
- package/ios/views/ClippingScrollViewDecoratorViewManager.mm +231 -0
- package/ios/views/KeyboardControllerViewManager.swift +5 -5
- package/ios/views/KeyboardToolbarGroupViewManager.h +28 -0
- package/ios/views/KeyboardToolbarGroupViewManager.mm +88 -0
- package/jest/index.js +12 -4
- package/lib/commonjs/bindings.js +18 -2
- package/lib/commonjs/bindings.js.map +1 -1
- package/lib/commonjs/bindings.native.js +3 -4
- package/lib/commonjs/bindings.native.js.map +1 -1
- package/lib/commonjs/components/KeyboardAvoidingView/index.js +30 -4
- package/lib/commonjs/components/KeyboardAvoidingView/index.js.map +1 -1
- package/lib/commonjs/components/KeyboardAwareScrollView/index.js +55 -14
- 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/KeyboardChatScrollView/index.js +111 -0
- package/lib/commonjs/components/KeyboardChatScrollView/index.js.map +1 -0
- package/lib/commonjs/components/KeyboardChatScrollView/types.js.map +1 -0
- package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/helpers.js +221 -0
- package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/helpers.js.map +1 -0
- package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js +149 -0
- package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js.map +1 -0
- package/lib/commonjs/components/KeyboardChatScrollView/useChatKeyboard/index.js +240 -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/KeyboardChatScrollView/useExtraContentPadding/index.js +72 -0
- package/lib/commonjs/components/KeyboardChatScrollView/useExtraContentPadding/index.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/KeyboardToolbar/index.js +1 -0
- package/lib/commonjs/components/KeyboardToolbar/index.js.map +1 -1
- package/lib/commonjs/components/ScrollViewWithBottomPadding/index.js +36 -15
- 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 +1 -2
- package/lib/commonjs/specs/ClippingScrollViewDecoratorViewNativeComponent.js.map +1 -1
- package/lib/commonjs/specs/KeyboardToolbarGroupViewNativeComponent.js +12 -0
- package/lib/commonjs/specs/KeyboardToolbarGroupViewNativeComponent.js.map +1 -0
- package/lib/commonjs/specs/NativeKeyboardController.js.map +1 -1
- package/lib/commonjs/types/module.js.map +1 -1
- package/lib/commonjs/types/views.js.map +1 -1
- package/lib/module/bindings.js +17 -1
- package/lib/module/bindings.js.map +1 -1
- package/lib/module/bindings.native.js +2 -3
- package/lib/module/bindings.native.js.map +1 -1
- package/lib/module/components/KeyboardAvoidingView/index.js +29 -4
- package/lib/module/components/KeyboardAvoidingView/index.js.map +1 -1
- package/lib/module/components/KeyboardAwareScrollView/index.js +55 -14
- 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 +104 -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/KeyboardChatScrollView/useChatKeyboard/helpers.js +207 -0
- package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/helpers.js.map +1 -0
- package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js +143 -0
- package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.ios.js.map +1 -0
- package/lib/module/components/KeyboardChatScrollView/useChatKeyboard/index.js +234 -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/KeyboardChatScrollView/useExtraContentPadding/index.js +67 -0
- package/lib/module/components/KeyboardChatScrollView/useExtraContentPadding/index.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/KeyboardToolbar/index.js +2 -1
- package/lib/module/components/KeyboardToolbar/index.js.map +1 -1
- package/lib/module/components/ScrollViewWithBottomPadding/index.js +38 -17
- 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 +1 -2
- package/lib/module/specs/ClippingScrollViewDecoratorViewNativeComponent.js.map +1 -1
- package/lib/module/specs/KeyboardToolbarGroupViewNativeComponent.js +5 -0
- package/lib/module/specs/KeyboardToolbarGroupViewNativeComponent.js.map +1 -0
- package/lib/module/specs/NativeKeyboardController.js.map +1 -1
- package/lib/module/types/module.js.map +1 -1
- package/lib/module/types/views.js.map +1 -1
- package/lib/typescript/bindings.d.ts +12 -2
- package/lib/typescript/bindings.native.d.ts +2 -1
- package/lib/typescript/components/KeyboardAvoidingView/index.d.ts +44 -13
- package/lib/typescript/components/KeyboardChatScrollView/hooks.d.ts +2 -0
- package/lib/typescript/components/KeyboardChatScrollView/index.d.ts +15 -0
- package/lib/typescript/components/{ChatKit → KeyboardChatScrollView}/types.d.ts +46 -6
- package/lib/typescript/components/KeyboardChatScrollView/useChatKeyboard/helpers.d.ts +132 -0
- package/lib/typescript/components/KeyboardChatScrollView/useChatKeyboard/index.d.ts +18 -0
- package/lib/typescript/components/KeyboardChatScrollView/useChatKeyboard/index.ios.d.ts +21 -0
- package/lib/typescript/components/KeyboardChatScrollView/useChatKeyboard/types.d.ts +37 -0
- package/lib/typescript/components/KeyboardChatScrollView/useExtraContentPadding/index.d.ts +43 -0
- package/lib/typescript/components/KeyboardToolbar/index.d.ts +2 -0
- package/lib/typescript/components/ScrollViewWithBottomPadding/index.d.ts +5 -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 +2 -0
- package/lib/typescript/specs/KeyboardToolbarGroupViewNativeComponent.d.ts +6 -0
- package/lib/typescript/specs/NativeKeyboardController.d.ts +1 -0
- package/lib/typescript/types/module.d.ts +7 -0
- package/lib/typescript/types/views.d.ts +4 -0
- package/package.json +6 -2
- package/react-native.config.js +2 -0
- package/src/bindings.native.ts +4 -4
- package/src/bindings.ts +15 -1
- package/src/components/KeyboardAvoidingView/index.tsx +56 -29
- package/src/components/KeyboardAwareScrollView/index.tsx +68 -13
- package/src/components/KeyboardChatScrollView/hooks.ts +2 -0
- package/src/components/KeyboardChatScrollView/index.tsx +150 -0
- package/src/components/{ChatKit → KeyboardChatScrollView}/types.ts +46 -6
- package/src/components/KeyboardChatScrollView/useChatKeyboard/helpers.ts +268 -0
- package/src/components/KeyboardChatScrollView/useChatKeyboard/index.ios.ts +260 -0
- package/src/components/KeyboardChatScrollView/useChatKeyboard/index.ts +376 -0
- package/src/components/KeyboardChatScrollView/useChatKeyboard/types.ts +39 -0
- package/src/components/KeyboardChatScrollView/useExtraContentPadding/index.ts +123 -0
- package/src/components/KeyboardStickyView/index.tsx +4 -3
- package/src/components/KeyboardToolbar/index.tsx +6 -1
- package/src/components/ScrollViewWithBottomPadding/index.tsx +53 -29
- 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 +2 -1
- package/src/specs/KeyboardToolbarGroupViewNativeComponent.ts +10 -0
- package/src/specs/NativeKeyboardController.ts +1 -0
- package/src/types/module.ts +9 -0
- package/src/types/views.ts +4 -0
- package/android/.settings/org.eclipse.buildship.core.prefs +0 -2
- package/ios/KeyboardController.xcodeproj/xcuserdata/kirylziusko.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- package/ios/Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/ios/Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/ios/Tests/Tests.xcodeproj/project.xcworkspace/xcuserdata/kirylziusko.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/ios/Tests/Tests.xcodeproj/xcuserdata/kirylziusko.xcuserdatad/xcschemes/xcschememanagement.plist +0 -32
- package/lib/commonjs/components/ChatKit/TODO.md +0 -20
- package/lib/commonjs/components/ChatKit/index.js +0 -55
- 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 +0 -101
- 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 +0 -92
- 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/helpers.d.ts +0 -57
- 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/helpers.ts +0 -118
- 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
|
@@ -16,6 +16,7 @@ import Reanimated, {
|
|
|
16
16
|
useSharedValue,
|
|
17
17
|
} from "react-native-reanimated";
|
|
18
18
|
|
|
19
|
+
import { KeyboardControllerNative } from "../../bindings";
|
|
19
20
|
import {
|
|
20
21
|
useFocusedInputHandler,
|
|
21
22
|
useReanimatedFocusedInput,
|
|
@@ -149,14 +150,26 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
149
150
|
const lastSelection =
|
|
150
151
|
useSharedValue<FocusedInputSelectionChangedEvent | null>(null);
|
|
151
152
|
const ghostViewSpace = useSharedValue(-1);
|
|
153
|
+
const pendingSelectionForFocus = useSharedValue(false);
|
|
154
|
+
const scrollViewPageY = useSharedValue(0);
|
|
152
155
|
|
|
153
156
|
const { height } = useWindowDimensions();
|
|
154
157
|
|
|
155
158
|
const onScrollViewLayout = useCallback(
|
|
156
|
-
(e: LayoutChangeEvent) => {
|
|
157
|
-
|
|
159
|
+
async (e: LayoutChangeEvent) => {
|
|
160
|
+
const handle = findNodeHandle(scrollViewAnimatedRef.current);
|
|
161
|
+
|
|
162
|
+
scrollViewTarget.value = handle;
|
|
158
163
|
|
|
159
164
|
onLayout?.(e);
|
|
165
|
+
|
|
166
|
+
if (handle !== null) {
|
|
167
|
+
const { y } = await KeyboardControllerNative.viewPositionInWindow(
|
|
168
|
+
handle,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
scrollViewPageY.value = y;
|
|
172
|
+
}
|
|
160
173
|
},
|
|
161
174
|
[onLayout],
|
|
162
175
|
);
|
|
@@ -204,7 +217,7 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
204
217
|
return interpolatedScrollTo;
|
|
205
218
|
}
|
|
206
219
|
|
|
207
|
-
if (point <
|
|
220
|
+
if (point < scrollViewPageY.value) {
|
|
208
221
|
const positionOnScreen = visibleRect - bottomOffset;
|
|
209
222
|
const topOfScreen = scrollPosition.value + point;
|
|
210
223
|
|
|
@@ -324,7 +337,18 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
324
337
|
lastSelection.value = e;
|
|
325
338
|
|
|
326
339
|
if (e.target !== lastTarget) {
|
|
327
|
-
|
|
340
|
+
if (pendingSelectionForFocus.value) {
|
|
341
|
+
// selection arrived after onStart - complete the deferred setup
|
|
342
|
+
pendingSelectionForFocus.value = false;
|
|
343
|
+
updateLayoutFromSelection();
|
|
344
|
+
|
|
345
|
+
// if keyboard was already visible (focus change, no onMove expected),
|
|
346
|
+
// perform the deferred scroll now
|
|
347
|
+
if (!keyboardWillAppear.value && keyboardHeight.value > 0) {
|
|
348
|
+
position.value += maybeScroll(keyboardHeight.value, true);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
328
352
|
return;
|
|
329
353
|
}
|
|
330
354
|
// caret in the end + end coordinates has been changed -> we moved to a new line
|
|
@@ -342,7 +366,12 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
342
366
|
|
|
343
367
|
onChangeTextHandler();
|
|
344
368
|
},
|
|
345
|
-
[
|
|
369
|
+
[
|
|
370
|
+
scrollFromCurrentPosition,
|
|
371
|
+
onChangeTextHandler,
|
|
372
|
+
updateLayoutFromSelection,
|
|
373
|
+
maybeScroll,
|
|
374
|
+
],
|
|
346
375
|
);
|
|
347
376
|
|
|
348
377
|
useFocusedInputHandler(
|
|
@@ -375,6 +404,7 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
375
404
|
// on back transition need to interpolate as [0, keyboardHeight]
|
|
376
405
|
initialKeyboardSize.value = 0;
|
|
377
406
|
scrollPosition.value = scrollBeforeKeyboardMovement.value;
|
|
407
|
+
pendingSelectionForFocus.value = false;
|
|
378
408
|
}
|
|
379
409
|
|
|
380
410
|
if (
|
|
@@ -391,17 +421,31 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
391
421
|
// focus was changed
|
|
392
422
|
if (focusWasChanged) {
|
|
393
423
|
tag.value = e.target;
|
|
394
|
-
|
|
395
|
-
|
|
424
|
+
|
|
425
|
+
if (lastSelection.value?.target === e.target) {
|
|
426
|
+
// selection arrived before onStart - use it to update layout
|
|
427
|
+
updateLayoutFromSelection();
|
|
428
|
+
pendingSelectionForFocus.value = false;
|
|
429
|
+
} else {
|
|
430
|
+
// selection hasn't arrived yet for the new target.
|
|
431
|
+
// use input layout as-is; will be refined when selection arrives.
|
|
432
|
+
if (input.value) {
|
|
433
|
+
layout.value = input.value;
|
|
434
|
+
}
|
|
435
|
+
pendingSelectionForFocus.value = true;
|
|
436
|
+
}
|
|
437
|
+
|
|
396
438
|
// save current scroll position - when keyboard will hide we'll reuse
|
|
397
439
|
// this value to achieve smooth hide effect
|
|
398
440
|
scrollBeforeKeyboardMovement.value = position.value;
|
|
399
441
|
}
|
|
400
442
|
|
|
401
443
|
if (focusWasChanged && !keyboardWillAppear.value) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
444
|
+
if (!pendingSelectionForFocus.value) {
|
|
445
|
+
// update position on scroll value, so `onEnd` handler
|
|
446
|
+
// will pick up correct values
|
|
447
|
+
position.value += maybeScroll(e.height, true);
|
|
448
|
+
}
|
|
405
449
|
}
|
|
406
450
|
|
|
407
451
|
ghostViewSpace.value =
|
|
@@ -435,6 +479,10 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
435
479
|
keyboardHeight.value = e.height;
|
|
436
480
|
scrollPosition.value = position.value;
|
|
437
481
|
|
|
482
|
+
if (e.height === 0) {
|
|
483
|
+
lastSelection.value = null;
|
|
484
|
+
}
|
|
485
|
+
|
|
438
486
|
syncKeyboardFrame(e);
|
|
439
487
|
},
|
|
440
488
|
},
|
|
@@ -461,11 +509,18 @@ const KeyboardAwareScrollView = forwardRef<
|
|
|
461
509
|
() => {
|
|
462
510
|
const scrollView = scrollViewRef.current;
|
|
463
511
|
|
|
464
|
-
|
|
512
|
+
if (scrollView) {
|
|
513
|
+
const scrollViewWithMethods =
|
|
514
|
+
scrollView as KeyboardAwareScrollViewRef;
|
|
465
515
|
|
|
466
|
-
|
|
467
|
-
|
|
516
|
+
scrollViewWithMethods.assureFocusedInputVisible = () => {
|
|
517
|
+
synchronize();
|
|
518
|
+
};
|
|
468
519
|
|
|
520
|
+
return scrollViewWithMethods;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return {
|
|
469
524
|
assureFocusedInputVisible: () => {
|
|
470
525
|
synchronize();
|
|
471
526
|
},
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import React, { forwardRef, useCallback, useMemo } from "react";
|
|
2
|
+
import { StyleSheet } from "react-native";
|
|
3
|
+
import {
|
|
4
|
+
makeMutable,
|
|
5
|
+
useAnimatedRef,
|
|
6
|
+
useAnimatedStyle,
|
|
7
|
+
useDerivedValue,
|
|
8
|
+
} from "react-native-reanimated";
|
|
9
|
+
import Reanimated from "react-native-reanimated";
|
|
10
|
+
|
|
11
|
+
import useCombinedRef from "../hooks/useCombinedRef";
|
|
12
|
+
import ScrollViewWithBottomPadding from "../ScrollViewWithBottomPadding";
|
|
13
|
+
|
|
14
|
+
import { useChatKeyboard } from "./useChatKeyboard";
|
|
15
|
+
import { useExtraContentPadding } from "./useExtraContentPadding";
|
|
16
|
+
|
|
17
|
+
import type { KeyboardChatScrollViewProps } from "./types";
|
|
18
|
+
import type { LayoutChangeEvent } from "react-native";
|
|
19
|
+
|
|
20
|
+
const ZERO_CONTENT_PADDING = makeMutable(0);
|
|
21
|
+
const ZERO_BLANK_SPACE = makeMutable(0);
|
|
22
|
+
|
|
23
|
+
const KeyboardChatScrollView = forwardRef<
|
|
24
|
+
Reanimated.ScrollView,
|
|
25
|
+
React.PropsWithChildren<KeyboardChatScrollViewProps>
|
|
26
|
+
>(
|
|
27
|
+
(
|
|
28
|
+
{
|
|
29
|
+
children,
|
|
30
|
+
ScrollViewComponent = Reanimated.ScrollView,
|
|
31
|
+
inverted = false,
|
|
32
|
+
keyboardLiftBehavior = "always",
|
|
33
|
+
freeze = false,
|
|
34
|
+
offset = 0,
|
|
35
|
+
extraContentPadding = ZERO_CONTENT_PADDING,
|
|
36
|
+
blankSpace = ZERO_BLANK_SPACE,
|
|
37
|
+
applyWorkaroundForContentInsetHitTestBug = false,
|
|
38
|
+
onLayout: onLayoutProp,
|
|
39
|
+
onContentSizeChange: onContentSizeChangeProp,
|
|
40
|
+
...rest
|
|
41
|
+
},
|
|
42
|
+
ref,
|
|
43
|
+
) => {
|
|
44
|
+
const scrollViewRef = useAnimatedRef<Reanimated.ScrollView>();
|
|
45
|
+
const onRef = useCombinedRef(ref, scrollViewRef);
|
|
46
|
+
const {
|
|
47
|
+
padding,
|
|
48
|
+
currentHeight,
|
|
49
|
+
contentOffsetY,
|
|
50
|
+
scroll,
|
|
51
|
+
layout,
|
|
52
|
+
size,
|
|
53
|
+
onLayout: onLayoutInternal,
|
|
54
|
+
onContentSizeChange: onContentSizeChangeInternal,
|
|
55
|
+
} = useChatKeyboard(scrollViewRef, {
|
|
56
|
+
inverted,
|
|
57
|
+
keyboardLiftBehavior,
|
|
58
|
+
freeze,
|
|
59
|
+
offset,
|
|
60
|
+
blankSpace,
|
|
61
|
+
extraContentPadding,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
useExtraContentPadding({
|
|
65
|
+
scrollViewRef,
|
|
66
|
+
extraContentPadding,
|
|
67
|
+
keyboardPadding: padding,
|
|
68
|
+
blankSpace,
|
|
69
|
+
scroll,
|
|
70
|
+
layout,
|
|
71
|
+
size,
|
|
72
|
+
inverted,
|
|
73
|
+
keyboardLiftBehavior,
|
|
74
|
+
freeze,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const totalPadding = useDerivedValue(() =>
|
|
78
|
+
Math.max(blankSpace.value, padding.value + extraContentPadding.value),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Scroll indicator inset = keyboard + extraContentPadding (excludes blankSpace).
|
|
82
|
+
// Apps that render into the unsafe area can supply a negative
|
|
83
|
+
// scrollIndicatorInsets adjustment at the application layer.
|
|
84
|
+
const indicatorPadding = useDerivedValue(
|
|
85
|
+
() => padding.value + extraContentPadding.value,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const onLayout = useCallback(
|
|
89
|
+
(e: LayoutChangeEvent) => {
|
|
90
|
+
onLayoutInternal(e);
|
|
91
|
+
onLayoutProp?.(e);
|
|
92
|
+
},
|
|
93
|
+
[onLayoutInternal, onLayoutProp],
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const onContentSizeChange = useCallback(
|
|
97
|
+
(w: number, h: number) => {
|
|
98
|
+
onContentSizeChangeInternal(w, h);
|
|
99
|
+
onContentSizeChangeProp?.(w, h);
|
|
100
|
+
},
|
|
101
|
+
[onContentSizeChangeInternal, onContentSizeChangeProp],
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Invisible view whose animated style changes every frame during keyboard
|
|
105
|
+
// animation. On Fabric, this forces Reanimated to schedule a commit,
|
|
106
|
+
// which flushes the scrollTo call in the same frame (fixing de-synchronization).
|
|
107
|
+
// see https://github.com/software-mansion/react-native-reanimated/issues/9000
|
|
108
|
+
const commitStyle = useAnimatedStyle(
|
|
109
|
+
() => ({
|
|
110
|
+
transform: [{ translateY: -currentHeight.value }],
|
|
111
|
+
}),
|
|
112
|
+
[],
|
|
113
|
+
);
|
|
114
|
+
const commit = useMemo(
|
|
115
|
+
() => [styles.commitView, commitStyle],
|
|
116
|
+
[commitStyle],
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<>
|
|
121
|
+
<ScrollViewWithBottomPadding
|
|
122
|
+
ref={onRef}
|
|
123
|
+
{...rest}
|
|
124
|
+
applyWorkaroundForContentInsetHitTestBug={
|
|
125
|
+
applyWorkaroundForContentInsetHitTestBug
|
|
126
|
+
}
|
|
127
|
+
bottomPadding={totalPadding}
|
|
128
|
+
contentOffsetY={contentOffsetY}
|
|
129
|
+
inverted={inverted}
|
|
130
|
+
scrollIndicatorPadding={indicatorPadding}
|
|
131
|
+
ScrollViewComponent={ScrollViewComponent}
|
|
132
|
+
onContentSizeChange={onContentSizeChange}
|
|
133
|
+
onLayout={onLayout}
|
|
134
|
+
>
|
|
135
|
+
{children}
|
|
136
|
+
</ScrollViewWithBottomPadding>
|
|
137
|
+
<Reanimated.View style={commit} />
|
|
138
|
+
</>
|
|
139
|
+
);
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const styles = StyleSheet.create({
|
|
144
|
+
commitView: {
|
|
145
|
+
display: "none",
|
|
146
|
+
position: "absolute",
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
export default KeyboardChatScrollView;
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import type { AnimatedScrollViewComponent } from "../ScrollViewWithBottomPadding";
|
|
2
|
+
import type { KeyboardLiftBehavior } from "./useChatKeyboard/types";
|
|
2
3
|
import type { ScrollViewProps } from "react-native";
|
|
4
|
+
import type { SharedValue } from "react-native-reanimated";
|
|
3
5
|
|
|
4
|
-
type
|
|
5
|
-
|
|
6
|
-
export type ChatKitScrollViewProps = {
|
|
6
|
+
export type KeyboardChatScrollViewProps = {
|
|
7
7
|
/** Custom component for `ScrollView`. Default is `ScrollView`. */
|
|
8
|
-
ScrollViewComponent
|
|
8
|
+
ScrollViewComponent?: AnimatedScrollViewComponent;
|
|
9
9
|
/** Whether list are using `inverted` prop. Default is `false`. */
|
|
10
10
|
inverted?: boolean;
|
|
11
11
|
/**
|
|
12
|
-
* The distance
|
|
13
|
-
* When the keyboard appears, the
|
|
12
|
+
* The distance between the bottom of the screen and the `ScrollView`.
|
|
13
|
+
* When the keyboard appears, the `ScrollView` will only push content by the effective
|
|
14
14
|
* distance (`keyboardHeight - offset`) instead of the full keyboard height.
|
|
15
15
|
*
|
|
16
16
|
* Useful when the input is not at the very bottom of the screen (e.g., above safe area, above
|
|
@@ -47,4 +47,44 @@ export type ChatKitScrollViewProps = {
|
|
|
47
47
|
* Default is `false`.
|
|
48
48
|
*/
|
|
49
49
|
freeze?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* A shared value representing additional padding from external elements
|
|
52
|
+
* (e.g., a growing multiline `TextInput` in a `KeyboardStickyView`).
|
|
53
|
+
*
|
|
54
|
+
* When this value changes:
|
|
55
|
+
* - The scrollable range is always extended/contracted (via `contentInset`).
|
|
56
|
+
* - The scroll position is conditionally adjusted based on `keyboardLiftBehavior`.
|
|
57
|
+
*
|
|
58
|
+
* Default is `undefined` (no extra padding).
|
|
59
|
+
*/
|
|
60
|
+
extraContentPadding?: SharedValue<number>;
|
|
61
|
+
/**
|
|
62
|
+
* When `true`, applies a runtime workaround for a React Native 0.81+ bug
|
|
63
|
+
* where the ScrollView's `contentInset` area does not respond to touch/scroll
|
|
64
|
+
* gestures (facebook/react-native#54123).
|
|
65
|
+
*
|
|
66
|
+
* This uses Objective-C runtime method swizzling on the ScrollView's container
|
|
67
|
+
* view, which is inherently fragile. Only enable if you are affected by the
|
|
68
|
+
* upstream bug and understand the risks.
|
|
69
|
+
*
|
|
70
|
+
* iOS only. Default is `false`.
|
|
71
|
+
*/
|
|
72
|
+
applyWorkaroundForContentInsetHitTestBug?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* A shared value representing a minimum inset floor (in pixels).
|
|
75
|
+
*
|
|
76
|
+
* When set, the total bottom padding is computed as:
|
|
77
|
+
* `max(blankSpace, keyboardPadding + extraContentPadding)`
|
|
78
|
+
*
|
|
79
|
+
* This means the keyboard "absorbs" into the minimum padding rather than adding to it:
|
|
80
|
+
* - When `blankSpace >= keyboard + extraContentPadding`: content does NOT move on keyboard open/close.
|
|
81
|
+
* - When `blankSpace < keyboard + extraContentPadding`: content moves, but only by the excess amount.
|
|
82
|
+
*
|
|
83
|
+
* Useful in AI chat apps where a sent message needs space below it (to push it to the top
|
|
84
|
+
* of the viewport) while the AI response streams in, without that space causing extra movement
|
|
85
|
+
* when the keyboard opens.
|
|
86
|
+
*
|
|
87
|
+
* Default is `undefined` (equivalent to `0` — no minimum floor).
|
|
88
|
+
*/
|
|
89
|
+
blankSpace?: SharedValue<number>;
|
|
50
90
|
} & ScrollViewProps;
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { interpolate } from "react-native-reanimated";
|
|
2
|
+
|
|
3
|
+
import type { KeyboardLiftBehavior } from "./types";
|
|
4
|
+
|
|
5
|
+
const AT_END_THRESHOLD = 20;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Map the current keyboard height to an effective height that accounts for a
|
|
9
|
+
* fixed offset (e.g. Bottom safe-area or tab-bar height)..
|
|
10
|
+
*
|
|
11
|
+
* @param height - Current keyboard height.
|
|
12
|
+
* @param targetKeyboardHeight - Full target keyboard height (captured on keyboard open).
|
|
13
|
+
* @param offset - Fixed distance between the scroll-view bottom and the screen bottom.
|
|
14
|
+
* @returns Effective height after subtracting the offset proportionally.
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* getEffectiveHeight(300, 300, 50); // 250
|
|
18
|
+
* getEffectiveHeight(150, 300, 50); // 125
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export const getEffectiveHeight = (
|
|
22
|
+
height: number,
|
|
23
|
+
targetKeyboardHeight: number,
|
|
24
|
+
offset: number,
|
|
25
|
+
): number => {
|
|
26
|
+
"worklet";
|
|
27
|
+
|
|
28
|
+
if (offset === 0 || targetKeyboardHeight === 0) {
|
|
29
|
+
return height;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return interpolate(
|
|
33
|
+
height,
|
|
34
|
+
[0, targetKeyboardHeight],
|
|
35
|
+
[0, Math.max(targetKeyboardHeight - offset, 0)],
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check whether the scroll view is at the end of its content.
|
|
41
|
+
*
|
|
42
|
+
* For non-inverted lists the "end" is the bottom of the content.
|
|
43
|
+
* For inverted lists the "end" is the top (scroll offset near 0),
|
|
44
|
+
* because that is where the latest messages are displayed.
|
|
45
|
+
*
|
|
46
|
+
* @param scrollOffset - Current vertical scroll offset.
|
|
47
|
+
* @param layoutHeight - Visible height of the scroll view.
|
|
48
|
+
* @param contentHeight - Total height of the scrollable content.
|
|
49
|
+
* @param inverted - Whether the list is inverted.
|
|
50
|
+
* @returns `true` if the scroll position is within the threshold of the content end.
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* const atEnd = isScrollAtEnd(100, 800, 920); // true (100 + 800 >= 920 - 20)
|
|
54
|
+
* const atEndInverted = isScrollAtEnd(5, 800, 2000, true); // true (5 <= 20)
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export const isScrollAtEnd = (
|
|
58
|
+
scrollOffset: number,
|
|
59
|
+
layoutHeight: number,
|
|
60
|
+
contentHeight: number,
|
|
61
|
+
inverted: boolean = false,
|
|
62
|
+
): boolean => {
|
|
63
|
+
"worklet";
|
|
64
|
+
|
|
65
|
+
if (inverted) {
|
|
66
|
+
return scrollOffset <= AT_END_THRESHOLD;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return scrollOffset + layoutHeight >= contentHeight - AT_END_THRESHOLD;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Decide whether content should be shifted based on the keyboard lift behavior.
|
|
74
|
+
*
|
|
75
|
+
* @param behavior - The configured keyboard lift behavior.
|
|
76
|
+
* @param isAtEnd - Whether the scroll view is currently at the end.
|
|
77
|
+
* @returns `true` if content should be shifted.
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* shouldShiftContent("always", false); // true
|
|
81
|
+
* shouldShiftContent("whenAtEnd", false); // false
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export const shouldShiftContent = (
|
|
85
|
+
behavior: KeyboardLiftBehavior,
|
|
86
|
+
isAtEnd: boolean,
|
|
87
|
+
): boolean => {
|
|
88
|
+
"worklet";
|
|
89
|
+
|
|
90
|
+
switch (behavior) {
|
|
91
|
+
case "always":
|
|
92
|
+
return true;
|
|
93
|
+
case "never":
|
|
94
|
+
return false;
|
|
95
|
+
case "whenAtEnd":
|
|
96
|
+
return isAtEnd;
|
|
97
|
+
case "persistent":
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Compute the fraction of minimum padding space currently visible in the viewport (0–1).
|
|
104
|
+
*
|
|
105
|
+
* The minimum padding space lives in the scroll view's contentInset, NOT in the
|
|
106
|
+
* content itself. So `contentHeight` (from onContentSizeChange / scroll
|
|
107
|
+
* events) does **not** include it. The visible portion is how far the
|
|
108
|
+
* viewport extends past the content boundary into the inset area.
|
|
109
|
+
*
|
|
110
|
+
* For non-inverted lists the padding is in contentInset.bottom.
|
|
111
|
+
* For inverted lists the padding is in contentInset.top (negative scroll).
|
|
112
|
+
*
|
|
113
|
+
* @param scrollOffset - Current vertical scroll offset.
|
|
114
|
+
* @param layoutHeight - Visible height of the scroll view.
|
|
115
|
+
* @param contentHeight - Height of the scroll content (excludes insets).
|
|
116
|
+
* @param blankSpace - Size of the minimum padding inset area.
|
|
117
|
+
* @param inverted - Whether the list is inverted.
|
|
118
|
+
* @returns A value between 0 (padding fully off-screen) and 1 (padding fully visible).
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* // Non-inverted: contentHeight=1500, layout=800, blankSpace=300
|
|
122
|
+
* getVisibleMinimumPaddingFraction(1500, 800, 1500, 300, false); // 1 (at end, viewport past content)
|
|
123
|
+
* getVisibleMinimumPaddingFraction(850, 800, 1500, 300, false); // 0.5 (half padding visible)
|
|
124
|
+
* getVisibleMinimumPaddingFraction(700, 800, 1500, 300, false); // 0 (padding off-screen)
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
export const getVisibleMinimumPaddingFraction = (
|
|
128
|
+
scrollOffset: number,
|
|
129
|
+
layoutHeight: number,
|
|
130
|
+
contentHeight: number,
|
|
131
|
+
blankSpace: number,
|
|
132
|
+
inverted: boolean,
|
|
133
|
+
): number => {
|
|
134
|
+
"worklet";
|
|
135
|
+
|
|
136
|
+
if (blankSpace <= 0) {
|
|
137
|
+
return 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (inverted) {
|
|
141
|
+
// Minimum padding is in contentInset.top; visible when scroll < 0
|
|
142
|
+
return Math.max(0, Math.min(1, -scrollOffset / blankSpace));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Minimum padding is in contentInset.bottom; visible when viewport extends past content
|
|
146
|
+
const pastContentEnd = scrollOffset + layoutHeight - contentHeight;
|
|
147
|
+
|
|
148
|
+
return Math.max(0, Math.min(1, pastContentEnd / blankSpace));
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Compute how much of the minimum content padding absorbs the keyboard + extraContentPadding.
|
|
153
|
+
*
|
|
154
|
+
* @param blankSpace - Minimum inset floor.
|
|
155
|
+
* @param extraContentPadding - Extra content padding from external elements.
|
|
156
|
+
* @returns The portion of blankSpace that absorbs keyboard displacement.
|
|
157
|
+
* @example
|
|
158
|
+
* ```ts
|
|
159
|
+
* getMinimumPaddingAbsorbed(500, 20); // 480
|
|
160
|
+
* getMinimumPaddingAbsorbed(0, 20); // 0
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export const getMinimumPaddingAbsorbed = (
|
|
164
|
+
blankSpace: number,
|
|
165
|
+
extraContentPadding: number,
|
|
166
|
+
): number => {
|
|
167
|
+
"worklet";
|
|
168
|
+
|
|
169
|
+
return Math.max(0, blankSpace - extraContentPadding);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Compute the effective scroll displacement after minimum padding absorption.
|
|
174
|
+
*
|
|
175
|
+
* @param rawEffective - Raw effective keyboard height.
|
|
176
|
+
* @param minimumPaddingAbsorbed - Amount absorbed by minimum content padding.
|
|
177
|
+
* @returns The scroll displacement after subtracting the absorbed portion.
|
|
178
|
+
* @example
|
|
179
|
+
* ```ts
|
|
180
|
+
* getScrollEffective(300, 200); // 100
|
|
181
|
+
* getScrollEffective(300, 400); // 0
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
export const getScrollEffective = (
|
|
185
|
+
rawEffective: number,
|
|
186
|
+
minimumPaddingAbsorbed: number,
|
|
187
|
+
): number => {
|
|
188
|
+
"worklet";
|
|
189
|
+
|
|
190
|
+
return Math.max(0, rawEffective - minimumPaddingAbsorbed);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Compute the clamped scroll target for non-inverted lists.
|
|
195
|
+
*
|
|
196
|
+
* @param offsetBeforeScroll - Scroll position before keyboard appeared.
|
|
197
|
+
* @param keyboardHeight - Current keyboard height (used for scroll displacement).
|
|
198
|
+
* @param contentHeight - Total height of the scrollable content.
|
|
199
|
+
* @param layoutHeight - Visible height of the scroll view.
|
|
200
|
+
* @param totalPaddingForMaxScroll - Total padding to use for maxScroll calculation. When provided, used instead of keyboardHeight for the scrollable range. Defaults to keyboardHeight.
|
|
201
|
+
* @returns Clamped scroll target between 0 and maxScroll.
|
|
202
|
+
* @example
|
|
203
|
+
* ```ts
|
|
204
|
+
* clampedScrollTarget(100, 300, 1000, 800); // 400
|
|
205
|
+
* clampedScrollTarget(100, 100, 1000, 800, 500); // 200, maxScroll uses 500
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
export const clampedScrollTarget = (
|
|
209
|
+
offsetBeforeScroll: number,
|
|
210
|
+
keyboardHeight: number,
|
|
211
|
+
contentHeight: number,
|
|
212
|
+
layoutHeight: number,
|
|
213
|
+
totalPaddingForMaxScroll?: number,
|
|
214
|
+
): number => {
|
|
215
|
+
"worklet";
|
|
216
|
+
|
|
217
|
+
const paddingForMax =
|
|
218
|
+
totalPaddingForMaxScroll !== undefined
|
|
219
|
+
? totalPaddingForMaxScroll
|
|
220
|
+
: keyboardHeight;
|
|
221
|
+
const maxScroll = Math.max(contentHeight - layoutHeight + paddingForMax, 0);
|
|
222
|
+
|
|
223
|
+
return Math.min(Math.max(offsetBeforeScroll + keyboardHeight, 0), maxScroll);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Compute contentOffset.y for iOS lists.
|
|
228
|
+
*
|
|
229
|
+
* @param relativeScroll - Scroll position relative to current inset.
|
|
230
|
+
* @param keyboardHeight - Target keyboard height (used for scroll displacement).
|
|
231
|
+
* @param contentHeight - Total height of the scrollable content.
|
|
232
|
+
* @param layoutHeight - Visible height of the scroll view.
|
|
233
|
+
* @param inverted - Whether the list is inverted.
|
|
234
|
+
* @param totalPaddingForMaxScroll - Total padding to use for maxScroll calculation. When provided, used instead of keyboardHeight for the scrollable range. Defaults to keyboardHeight.
|
|
235
|
+
* @returns The absolute contentOffset.y to set.
|
|
236
|
+
* @example
|
|
237
|
+
* ```ts
|
|
238
|
+
* computeIOSContentOffset(100, 300, 1000, 800, false); // 400
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
export const computeIOSContentOffset = (
|
|
242
|
+
relativeScroll: number,
|
|
243
|
+
keyboardHeight: number,
|
|
244
|
+
contentHeight: number,
|
|
245
|
+
layoutHeight: number,
|
|
246
|
+
inverted: boolean,
|
|
247
|
+
totalPaddingForMaxScroll?: number,
|
|
248
|
+
): number => {
|
|
249
|
+
"worklet";
|
|
250
|
+
|
|
251
|
+
const paddingForMax =
|
|
252
|
+
totalPaddingForMaxScroll !== undefined
|
|
253
|
+
? totalPaddingForMaxScroll
|
|
254
|
+
: keyboardHeight;
|
|
255
|
+
|
|
256
|
+
if (inverted) {
|
|
257
|
+
const maxScroll = Math.max(contentHeight - layoutHeight, 0);
|
|
258
|
+
|
|
259
|
+
return Math.max(
|
|
260
|
+
Math.min(relativeScroll - keyboardHeight, maxScroll),
|
|
261
|
+
-paddingForMax,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const maxScroll = Math.max(contentHeight - layoutHeight + paddingForMax, 0);
|
|
266
|
+
|
|
267
|
+
return Math.min(Math.max(keyboardHeight + relativeScroll, 0), maxScroll);
|
|
268
|
+
};
|