Skip to content

Commit 20d8dee

Browse files
committed
fix: "persistent" mode has a bug. It pushes the content up, but when keyboard closes it doesn't move it down (even when we reached the end of the scroll view/beginning for inverted) and doesn't remove padding (Android)
1 parent b8df921 commit 20d8dee

2 files changed

Lines changed: 62 additions & 16 deletions

File tree

src/components/KeyboardChatScrollView/useChatKeyboard/__tests__/behaviors.android.spec.ts

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ afterAll(() => {
4444
});
4545

4646
describe("`useChatKeyboard` — Android behaviors", () => {
47-
it("persistent inverted: should NOT reset padding on close", () => {
47+
it("persistent inverted: should always reset padding on close", () => {
48+
mockOffset.value = 500;
4849
const { result } = render({
4950
inverted: true,
5051
keyboardLiftBehavior: "persistent",
@@ -54,10 +55,11 @@ describe("`useChatKeyboard` — Android behaviors", () => {
5455
handlers.onMove({ height: KEYBOARD });
5556
handlers.onEnd({ height: 0 });
5657

57-
expect(result.current.padding.value).toBe(KEYBOARD);
58+
expect(result.current.padding.value).toBe(0);
5859
});
5960

60-
it("persistent inverted: should not decrease shift", () => {
61+
it("persistent inverted: should not decrease shift when NOT at end", () => {
62+
mockOffset.value = 500;
6163
render({
6264
inverted: true,
6365
keyboardLiftBehavior: "persistent",
@@ -68,12 +70,50 @@ describe("`useChatKeyboard` — Android behaviors", () => {
6870
expect(mockScrollTo).toHaveBeenCalled();
6971
mockScrollTo.mockClear();
7072

71-
// currentShift = offsetBeforeScroll(0) + padding(300) - scroll(0) = 300
72-
// effective = 200 < 300 → return
73+
// currentShift = offsetBeforeScroll(500) + padding(300) - scroll(500) = 300
74+
// effective = 200 < 300 → return (not at end, so persistent holds)
7375
handlers.onMove({ height: 200 });
7476
expect(mockScrollTo).not.toHaveBeenCalled();
7577
});
7678

79+
it("persistent inverted: should scrollTo on close when at end", () => {
80+
mockOffset.value = 0;
81+
render({
82+
inverted: true,
83+
keyboardLiftBehavior: "persistent",
84+
});
85+
86+
handlers.onStart({ height: KEYBOARD });
87+
handlers.onMove({ height: KEYBOARD });
88+
mockScrollTo.mockClear();
89+
90+
// Simulate keyboard closing
91+
handlers.onStart({ height: 0 });
92+
// currentShift = 0 + 300 - 0 = 300, effective = 200 < 300
93+
// but at end → scrollTo called with snap to end
94+
handlers.onMove({ height: 200 });
95+
expect(mockScrollTo).toHaveBeenCalled();
96+
});
97+
98+
it("persistent non-inverted: should scroll back on close when at end", () => {
99+
// Position at end: offset + layout >= content - threshold → 1200 + 800 >= 2000 - 20
100+
mockOffset.value = 1200;
101+
render({ inverted: false, keyboardLiftBehavior: "persistent" });
102+
103+
handlers.onStart({ height: KEYBOARD });
104+
handlers.onMove({ height: KEYBOARD });
105+
mockScrollTo.mockClear();
106+
107+
// Simulate keyboard closing (re-capture in onStart)
108+
// offsetBeforeScroll = scroll(1200) - padding(300) = 900
109+
// But wasAtEnd check uses offsetBeforeScroll + padding = 900 + 300 = 1200
110+
// isScrollAtEnd(1200, 800, 2000) = 1200 + 800 >= 1980 → true
111+
mockOffset.value = 1500;
112+
handlers.onStart({ height: 0 });
113+
handlers.onMove({ height: 200 });
114+
expect(mockScrollTo).toHaveBeenCalled();
115+
});
116+
77117
it("never non-inverted: should not scroll", () => {
78118
mockOffset.value = 100;
79119
render({ inverted: false, keyboardLiftBehavior: "never" });

src/components/KeyboardChatScrollView/useChatKeyboard/index.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,12 @@ function useChatKeyboard(
202202
offsetBeforeScroll.value + padding.value - scroll.value;
203203

204204
if (effective < currentShift) {
205+
// When at end, allow scrolling back (snap to end + reduce padding)
206+
if (wasAtEnd) {
207+
padding.value = effective;
208+
scrollTo(scrollViewRef, 0, 0, false);
209+
}
210+
205211
return;
206212
}
207213
}
@@ -225,7 +231,17 @@ function useChatKeyboard(
225231
keyboardLiftBehavior === "persistent" &&
226232
effective < scroll.value - offsetBeforeScroll.value
227233
) {
228-
return;
234+
// When at end, allow scrolling back to natural end position
235+
const wasAtEnd = isScrollAtEnd(
236+
offsetBeforeScroll.value + padding.value,
237+
layout.value.height,
238+
size.value.height,
239+
false,
240+
);
241+
242+
if (!wasAtEnd) {
243+
return;
244+
}
229245
}
230246

231247
const target = clampedScrollTarget(
@@ -247,16 +263,6 @@ function useChatKeyboard(
247263

248264
const effective = getEffectiveHeight(e.height);
249265

250-
// Android inverted persistent: keep padding to maintain the shift
251-
if (
252-
OS !== "ios" &&
253-
inverted &&
254-
keyboardLiftBehavior === "persistent" &&
255-
e.height === 0
256-
) {
257-
return;
258-
}
259-
260266
padding.value = effective;
261267
},
262268
},

0 commit comments

Comments
 (0)