Skip to content

Commit 33241b7

Browse files
committed
fix: inverted + FlatList + whenAtEnd behavior
1 parent 5f0608c commit 33241b7

5 files changed

Lines changed: 70 additions & 6 deletions

File tree

src/components/KeyboardChatScrollView/useChatKeyboard/__tests__/helpers.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,29 @@ describe("`isScrollAtEnd` specification", () => {
2727
it("should return true when at scroll position 0 with matching content", () => {
2828
expect(isScrollAtEnd(0, 800, 800)).toBe(true);
2929
});
30+
31+
describe("inverted", () => {
32+
it("should return true when scroll offset is 0 (latest messages)", () => {
33+
expect(isScrollAtEnd(0, 800, 2000, true)).toBe(true);
34+
});
35+
36+
it("should return true when within threshold of 0", () => {
37+
expect(isScrollAtEnd(15, 800, 2000, true)).toBe(true);
38+
});
39+
40+
it("should return false when scrolled away from latest messages", () => {
41+
expect(isScrollAtEnd(100, 800, 2000, true)).toBe(false);
42+
});
43+
44+
it("should return true for negative offsets (keyboard inset)", () => {
45+
expect(isScrollAtEnd(-300, 800, 2000, true)).toBe(true);
46+
});
47+
48+
it("should return false at traditional scroll end (oldest messages)", () => {
49+
// offset 1200 = contentHeight(2000) - layoutHeight(800) → oldest messages
50+
expect(isScrollAtEnd(1200, 800, 2000, true)).toBe(false);
51+
});
52+
});
3053
});
3154

3255
describe("`shouldShiftContent` specification", () => {

src/components/KeyboardChatScrollView/useChatKeyboard/__tests__/index.ios.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,30 @@ describe("`useChatKeyboard` — iOS behaviors", () => {
211211
expect(result.current.contentOffsetY!.value).toBe(0);
212212
});
213213

214+
it("whenAtEnd + inverted: should shift when at latest messages (offset near 0)", () => {
215+
mockOffset.value = 0;
216+
const { result } = render({
217+
inverted: true,
218+
keyboardLiftBehavior: "whenAtEnd",
219+
});
220+
221+
handlers.onStart({ height: KEYBOARD });
222+
223+
expect(result.current.contentOffsetY!.value).toBe(-KEYBOARD);
224+
});
225+
226+
it("whenAtEnd + inverted: should NOT shift when scrolled to older messages", () => {
227+
mockOffset.value = 500;
228+
const { result } = render({
229+
inverted: true,
230+
keyboardLiftBehavior: "whenAtEnd",
231+
});
232+
233+
handlers.onStart({ height: KEYBOARD });
234+
235+
expect(result.current.contentOffsetY!.value).toBe(0);
236+
});
237+
214238
it("onEnd: should finalize padding", () => {
215239
const { result } = render({
216240
inverted: false,

src/components/KeyboardChatScrollView/useChatKeyboard/__tests__/persistent.ios.spec.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,30 +137,35 @@ describe("`useChatKeyboard` — iOS persistent inverted", () => {
137137
});
138138

139139
it("should NOT adjust contentOffsetY on close when NOT at end", () => {
140-
mockOffset.value = 0;
140+
// scrolled to older messages (not at end for inverted)
141+
mockOffset.value = 500;
141142
const { result } = render({
142143
inverted: true,
143144
keyboardLiftBehavior: "persistent",
144145
});
145146

146147
handlers.onStart({ height: KEYBOARD });
147-
mockOffset.value = -300;
148+
const contentOffsetAfterOpen = result.current.contentOffsetY!.value;
149+
150+
// user stays at old messages while keyboard is open
151+
mockOffset.value = 200;
148152
handlers.onStart({ height: 0 });
149153

150154
expect(result.current.padding.value).toBe(0);
151-
expect(result.current.contentOffsetY!.value).toBe(-KEYBOARD);
155+
expect(result.current.contentOffsetY!.value).toBe(contentOffsetAfterOpen);
152156
});
153157

154158
it("should snap to end on keyboard close when at end", () => {
155-
// scroll to oldest messages (at end for inverted)
156-
mockOffset.value = 1200;
159+
// at newest messages (at end for inverted = offset near 0)
160+
mockOffset.value = 0;
157161
const { result } = render({
158162
inverted: true,
159163
keyboardLiftBehavior: "persistent",
160164
});
161165

162166
handlers.onStart({ height: KEYBOARD });
163-
mockOffset.value = 1200;
167+
// user stays at newest messages with keyboard inset
168+
mockOffset.value = -300;
164169
handlers.onStart({ height: 0 });
165170

166171
expect(result.current.padding.value).toBe(0);

src/components/KeyboardChatScrollView/useChatKeyboard/helpers.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,33 @@ const AT_END_THRESHOLD = 20;
55
/**
66
* Check whether the scroll view is at the end of its content.
77
*
8+
* For non-inverted lists the "end" is the bottom of the content.
9+
* For inverted lists the "end" is the top (scroll offset near 0),
10+
* because that is where the latest messages are displayed.
11+
*
812
* @param scrollOffset - Current vertical scroll offset.
913
* @param layoutHeight - Visible height of the scroll view.
1014
* @param contentHeight - Total height of the scrollable content.
15+
* @param inverted - Whether the list is inverted.
1116
* @returns `true` if the scroll position is within the threshold of the content end.
1217
* @example
1318
* ```ts
1419
* const atEnd = isScrollAtEnd(100, 800, 920); // true (100 + 800 >= 920 - 20)
20+
* const atEndInverted = isScrollAtEnd(5, 800, 2000, true); // true (5 <= 20)
1521
* ```
1622
*/
1723
export function isScrollAtEnd(
1824
scrollOffset: number,
1925
layoutHeight: number,
2026
contentHeight: number,
27+
inverted: boolean = false,
2128
): boolean {
2229
"worklet";
2330

31+
if (inverted) {
32+
return scrollOffset <= AT_END_THRESHOLD;
33+
}
34+
2435
return scrollOffset + layoutHeight >= contentHeight - AT_END_THRESHOLD;
2536
}
2637

src/components/KeyboardChatScrollView/useChatKeyboard/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ function useChatKeyboard(
9898
scroll.value,
9999
layout.value.height,
100100
size.value.height,
101+
inverted,
101102
);
102103

103104
if (OS === "ios") {

0 commit comments

Comments
 (0)