Skip to content

Commit 852fa4a

Browse files
authored
fix: dynamic bottomOffset over-scrolling (#1204)
## 📜 Description Fixed an issue when change of `bottomSheet` with opened keyboard leads to unexpectedly big scroll. ## 💡 Motivation and Context The issue is that our `point`/`relativeScrollTo` remains the same. But when we focus our input we get `scrollPosition.value === 0`. When scrolling has completed we update that value to an actual scroll position (let's say to 209). When `bottomOffset` gets changed we scroll from current position (remember `point` hasn't been changed) but we add already scrolled distance and we get an incorrect offset. To overcome this issue we need to reset our scroll position to "unscrolled" state. In fact we already use that pattern with scroll restoration (change scroll position, call `maybeScroll`, restore position). So I decided to create a utility function called `performScrollWithRestoration` that will substitute scroll to a new position, do a scrolling and restore original position. The fix is working well, it fixes a problem in example app and e2e tests successfully pass 🤞 Closes #1203 ## 📢 Changelog <!-- High level overview of important changes --> <!-- For example: fixed status bar manipulation; added new types declarations; --> <!-- If your changes don't affect one of platform/language below - then remove this platform/language --> ### JS - added `performScrollWithRestoration` utility function; - call `performScrollWithRestoration` when `bottomOffset` gets changed (passing `scrollBeforeKeyboardMovement` value). ## 🤔 How Has This Been Tested? Tested manually in Fabric example, iPhone 16 Pro (iOS 26.0). ## 📸 Screenshots (if appropriate): |Before|After| |------|------| |<video src="https://github.com/user-attachments/assets/f01235d5-e94a-47fd-9221-fb74d4b495eb">|<video src="https://github.com/user-attachments/assets/865f0a90-1766-43c7-ab9a-4ff961ba76fe">| ## 📝 Checklist - [x] CI successfully passed - [x] I added new mocks and corresponding unit-tests if library API was changed
1 parent 3679bac commit 852fa4a

18 files changed

Lines changed: 78 additions & 13 deletions

FabricExample/src/screens/Examples/AwareScrollViewStickyFooter/index.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useCallback, useEffect, useMemo, useState } from "react";
2-
import { Alert, Button, Text, View } from "react-native";
2+
import { Button, Text, View } from "react-native";
33
import {
44
KeyboardAwareScrollView,
55
KeyboardStickyView,
@@ -23,6 +23,7 @@ type Variant = (typeof variants)[number];
2323
export default function AwareScrollViewStickyFooter({ navigation }: Props) {
2424
const { bottom } = useSafeAreaInsets();
2525
const [footerHeight, setFooterHeight] = useState(0);
26+
const [additionalHeight, setAdditionalHeight] = useState(0);
2627
const [variant, setVariant] = useState<Variant>("v1");
2728

2829
const handleLayout = useCallback((evt: LayoutChangeEvent) => {
@@ -64,10 +65,11 @@ export default function AwareScrollViewStickyFooter({ navigation }: Props) {
6465
]}
6566
>
6667
<KeyboardAwareScrollView
67-
bottomOffset={(v1v2 ? footerHeight : 0) + 50}
68+
bottomOffset={(v1v2 ? footerHeight + additionalHeight : 0) + 50}
6869
contentContainerStyle={styles.content}
6970
keyboardShouldPersistTaps="handled"
7071
style={styles.container}
72+
testID="aware_scroll_sticky_view_scroll_container"
7173
>
7274
{new Array(10).fill(0).map((_, i) => (
7375
<TextInput
@@ -79,10 +81,17 @@ export default function AwareScrollViewStickyFooter({ navigation }: Props) {
7981
</KeyboardAwareScrollView>
8082
{v1v2 && (
8183
<KeyboardStickyView offset={offset}>
84+
<View
85+
style={{ height: additionalHeight, backgroundColor: "magenta" }}
86+
/>
8287
<View style={styles.footer} onLayout={handleLayout}>
8388
<Text style={styles.footerText}>A mocked sticky footer</Text>
8489
<TextInput placeholder="Amount" style={styles.inputInFooter} />
85-
<Button title="Click me" onPress={() => Alert.alert("Clicked")} />
90+
<Button
91+
testID="toggle_height"
92+
title="Toggle height"
93+
onPress={() => setAdditionalHeight(additionalHeight ? 0 : 50)}
94+
/>
8695
</View>
8796
</KeyboardStickyView>
8897
)}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { expectBitmapsToBeEqual } from "./asserts";
2+
import {
3+
scrollDownUntilElementIsVisible,
4+
waitAndTap,
5+
waitForExpect,
6+
} from "./helpers";
7+
8+
const BLINKING_CURSOR = 0.35;
9+
10+
describe("AwareScrollView with StickyView test cases", () => {
11+
it("should push input above keyboard on focus", async () => {
12+
await waitAndTap("aware_scroll_view_sticky_footer");
13+
await scrollDownUntilElementIsVisible(
14+
"aware_scroll_sticky_view_scroll_container",
15+
"TextInput#4",
16+
);
17+
await waitAndTap("TextInput#4");
18+
await waitForExpect(async () => {
19+
await expectBitmapsToBeEqual(
20+
"AwareScrollViewWithStickyViewFirstInputFocused",
21+
BLINKING_CURSOR,
22+
);
23+
});
24+
});
25+
26+
it("should react on `bottomOffset` change", async () => {
27+
await waitAndTap("toggle_height");
28+
await waitForExpect(async () => {
29+
await expectBitmapsToBeEqual(
30+
"AwareScrollViewWithStickyViewBottomOffsetChanged",
31+
BLINKING_CURSOR,
32+
);
33+
});
34+
});
35+
});
91.7 KB
Loading
92.6 KB
Loading
87.5 KB
Loading
94.4 KB
Loading
166 KB
Loading
166 KB
Loading
171 KB
Loading
171 KB
Loading

0 commit comments

Comments
 (0)