Skip to content

Commit 6171749

Browse files
committed
created patch package
1 parent e00ecfa commit 6171749

File tree

1 file changed

+172
-0
lines changed

1 file changed

+172
-0
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
diff --git a/node_modules/@shopify/flash-list/src/recyclerview/RecyclerView.tsx b/node_modules/@shopify/flash-list/src/recyclerview/RecyclerView.tsx
2+
index 5447a5c..6898649 100644
3+
--- a/node_modules/@shopify/flash-list/src/recyclerview/RecyclerView.tsx
4+
+++ b/node_modules/@shopify/flash-list/src/recyclerview/RecyclerView.tsx
5+
@@ -97,6 +97,7 @@ const RecyclerViewComponent = <T,>(
6+
const firstChildViewRef = useRef<CompatView>(null);
7+
const containerViewSizeRef = useRef<RVDimension | undefined>(undefined);
8+
const pendingChildIds = useRef<Set<string>>(new Set()).current;
9+
+ const isInitialLayoutHandledRef = useRef(false);
10+
11+
// Track scroll position
12+
const scrollY = useRef(new Animated.Value(0)).current;
13+
@@ -130,6 +131,20 @@ const RecyclerViewComponent = <T,>(
14+
scrollAnchorRef
15+
);
16+
17+
+ // Track if we've attempted initial scroll to bottom
18+
+ const hasAttemptedInitialScrollToBottom = useRef(false);
19+
+ const lastDataLengthRef = useRef(recyclerViewManager.getDataLength());
20+
+
21+
+ // Reset scroll attempt when data length changes significantly (new messages loaded)
22+
+ useMemo(() => {
23+
+ const currentDataLength = recyclerViewManager.getDataLength();
24+
+ if (Math.abs(currentDataLength - lastDataLengthRef.current) > 5) {
25+
+ hasAttemptedInitialScrollToBottom.current = false;
26+
+ isInitialLayoutHandledRef.current = false;
27+
+ }
28+
+ lastDataLengthRef.current = currentDataLength;
29+
+ }, [recyclerViewManager.getDataLength()]);
30+
+
31+
// Initialize view holder collection ref
32+
const viewHolderCollectionRef = useRef<ViewHolderCollectionRef>(null);
33+
34+
@@ -436,10 +451,15 @@ const RecyclerViewComponent = <T,>(
35+
36+
const maintainVisibleContentPositionInternal = useMemo(() => {
37+
if (shouldMaintainVisibleContentPosition) {
38+
- return {
39+
+ const config: typeof maintainVisibleContentPosition = {
40+
...maintainVisibleContentPosition,
41+
- minIndexForVisible: 0,
42+
};
43+
+ // For inverted lists starting from bottom, don't set minIndexForVisible
44+
+ // as it interferes with maintaining position of items at the bottom
45+
+ if (!maintainVisibleContentPosition?.startRenderingFromBottom) {
46+
+ config.minIndexForVisible = 0;
47+
+ }
48+
+ return config;
49+
}
50+
return undefined;
51+
}, [maintainVisibleContentPosition, shouldMaintainVisibleContentPosition]);
52+
@@ -448,6 +468,69 @@ const RecyclerViewComponent = <T,>(
53+
recyclerViewManager.getDataLength() > 0 &&
54+
(maintainVisibleContentPosition?.startRenderingFromBottom ?? false);
55+
56+
+ // Handle onContentSizeChange to ensure we scroll to bottom when content size changes
57+
+ // This is more reliable than onLayout for ensuring we're at the bottom
58+
+ const handleContentSizeChange = useCallback(
59+
+ (contentWidth: number, contentHeight: number) => {
60+
+ if (
61+
+ shouldRenderFromBottom &&
62+
+ recyclerViewManager.getDataLength() > 0 &&
63+
+ contentHeight > 0
64+
+ ) {
65+
+ // Try scrolling to bottom, but allow multiple attempts if needed
66+
+ const attemptScroll = () => {
67+
+ if (scrollViewRef.current && handlerMethods) {
68+
+ // Try using scrollToEnd via handlerMethods
69+
+ handlerMethods.scrollToEnd({ animated: false });
70+
+
71+
+ // Also try direct scrollToEnd as fallback
72+
+ if (scrollViewRef.current.scrollToEnd) {
73+
+ scrollViewRef.current.scrollToEnd({ animated: false });
74+
+ }
75+
+ }
76+
+ };
77+
+
78+
+ // Use multiple requestAnimationFrame calls to ensure layout is complete
79+
+ requestAnimationFrame(() => {
80+
+ requestAnimationFrame(() => {
81+
+ attemptScroll();
82+
+ // Try one more time after a short delay to handle async layout
83+
+ setTimeout(() => {
84+
+ attemptScroll();
85+
+ }, 50);
86+
+ });
87+
+ });
88+
+
89+
+ hasAttemptedInitialScrollToBottom.current = true;
90+
+ }
91+
+ },
92+
+ [shouldRenderFromBottom, recyclerViewManager, handlerMethods, scrollViewRef]
93+
+ );
94+
+
95+
+ // Also handle onLayout as a fallback
96+
+ const handleScrollViewLayout = useCallback(() => {
97+
+ if (
98+
+ shouldRenderFromBottom &&
99+
+ recyclerViewManager.getIsFirstLayoutComplete() &&
100+
+ recyclerViewManager.getDataLength() > 0
101+
+ ) {
102+
+ const attemptScroll = () => {
103+
+ if (handlerMethods) {
104+
+ handlerMethods.scrollToEnd({ animated: false });
105+
+ }
106+
+ };
107+
+
108+
+ // Use requestAnimationFrame to ensure layout is complete
109+
+ requestAnimationFrame(() => {
110+
+ requestAnimationFrame(() => {
111+
+ attemptScroll();
112+
+ });
113+
+ });
114+
+
115+
+ isInitialLayoutHandledRef.current = true;
116+
+ }
117+
+ }, [shouldRenderFromBottom, recyclerViewManager, handlerMethods]);
118+
+
119+
// Create view for measuring bounded size
120+
const viewToMeasureBoundedSize = useMemo(() => {
121+
return (
122+
@@ -512,6 +595,8 @@ const RecyclerViewComponent = <T,>(
123+
horizontal={horizontal}
124+
ref={scrollViewRef}
125+
onScroll={animatedEvent}
126+
+ onLayout={handleScrollViewLayout}
127+
+ onContentSizeChange={handleContentSizeChange}
128+
maintainVisibleContentPosition={
129+
maintainVisibleContentPositionInternal
130+
}
131+
diff --git a/node_modules/@shopify/flash-list/src/recyclerview/hooks/useRecyclerViewController.tsx b/node_modules/@shopify/flash-list/src/recyclerview/hooks/useRecyclerViewController.tsx
132+
index 4d99406..5516f1d 100644
133+
--- a/node_modules/@shopify/flash-list/src/recyclerview/hooks/useRecyclerViewController.tsx
134+
+++ b/node_modules/@shopify/flash-list/src/recyclerview/hooks/useRecyclerViewController.tsx
135+
@@ -52,6 +52,7 @@ export function useRecyclerViewController<T>(
136+
const [_, setRenderId] = useState(0);
137+
const pauseOffsetCorrection = useRef(false);
138+
const initialScrollCompletedRef = useRef(false);
139+
+ const initialScrollToBottomAttemptedRef = useRef(false);
140+
const lastDataLengthRef = useRef(recyclerViewManager.getDataLength());
141+
const { setTimeout } = useUnmountAwareTimeout();
142+
143+
@@ -569,6 +570,9 @@ export function useRecyclerViewController<T>(
144+
const initialScrollIndex =
145+
recyclerViewManager.getInitialScrollIndex() ?? -1;
146+
const dataLength = data?.length ?? 0;
147+
+ const shouldRenderFromBottom =
148+
+ recyclerViewManager.props.maintainVisibleContentPosition?.startRenderingFromBottom ?? false;
149+
+
150+
if (
151+
initialScrollIndex >= 0 &&
152+
initialScrollIndex < dataLength &&
153+
@@ -598,6 +602,19 @@ export function useRecyclerViewController<T>(
154+
animated: false,
155+
skipFirstItemOffset: false,
156+
});
157+
+
158+
+ // For startRenderingFromBottom, ensure we scroll to actual bottom after layout
159+
+ // This handles variable height items where initial scroll index position might not be accurate
160+
+ if (shouldRenderFromBottom && dataLength > 0 && !initialScrollToBottomAttemptedRef.current) {
161+
+ initialScrollToBottomAttemptedRef.current = true;
162+
+ // Use multiple requestAnimationFrame calls to ensure layout measurements are complete
163+
+ requestAnimationFrame(() => {
164+
+ requestAnimationFrame(() => {
165+
+ // Scroll to end to ensure we're at the actual bottom
166+
+ handlerMethods.scrollToEnd({ animated: false });
167+
+ });
168+
+ });
169+
+ }
170+
}, 0);
171+
}
172+
}, [handlerMethods, recyclerViewManager, setTimeout]);

0 commit comments

Comments
 (0)