@rific/scroll-view 0.2.0 → 0.3.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/dist/index.mjs CHANGED
@@ -3,9 +3,10 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
 
5
5
  // src/CustomList.tsx
6
- import { memo, useCallback as useCallback2, useMemo as useMemo2, useRef as useRef2 } from "react";
6
+ import { memo, useCallback as useCallback2, useContext as useContext4, useLayoutEffect as useLayoutEffect2, useMemo as useMemo2, useRef as useRef2 } from "react";
7
7
  import { Dimensions, View } from "react-native";
8
8
  import { Gesture, GestureDetector } from "react-native-gesture-handler";
9
+ import { runOnUI, useSharedValue as useSharedValue4 } from "react-native-reanimated";
9
10
 
10
11
  // src/internal/RefreshControl.tsx
11
12
  import { RefreshControl as RNRefreshControl } from "react-native";
@@ -31,8 +32,8 @@ var styles = StyleSheet.create({
31
32
  });
32
33
 
33
34
  // src/internal/useScrollHandler.ts
34
- import { useContext } from "react";
35
- import { runOnJS, useAnimatedScrollHandler, useSharedValue, withTiming } from "react-native-reanimated";
35
+ import { useContext as useContext2 } from "react";
36
+ import { runOnJS, useAnimatedScrollHandler, useSharedValue as useSharedValue2, withTiming } from "react-native-reanimated";
36
37
 
37
38
  // src/ScrollViewContext.ts
38
39
  import { createContext } from "react";
@@ -42,10 +43,16 @@ var ScrollViewContext = createContext({
42
43
  footerHeightShared: null,
43
44
  footerFixed: false,
44
45
  footerOffset: null,
45
- headerHeight: 0,
46
+ headerHeight: null,
46
47
  headerHeightShared: null,
47
48
  headerFixed: false,
48
49
  headerOffset: null,
50
+ jsListGeneration: { current: 0 },
51
+ listGeneration: null,
52
+ onJsListUnmount: () => {
53
+ },
54
+ onListUnmount: () => {
55
+ },
49
56
  progress: null,
50
57
  progressing: false,
51
58
  pullSearchHeightShared: null,
@@ -64,17 +71,207 @@ var ScrollViewContext = createContext({
64
71
  tabBarHeight: 60
65
72
  });
66
73
 
74
+ // src/useScrollInit.ts
75
+ import { createElement, useCallback, useContext, useEffect, useLayoutEffect, useRef, useState } from "react";
76
+ import { useSharedValue } from "react-native-reanimated";
77
+ var REMOUNT_SYNC_MAX_ATTEMPTS = 8;
78
+ var REMOUNT_SYNC_TOLERANCE = 1;
79
+ var REMOUNT_SYNC_FALLBACK_MS = 500;
80
+ var REMOUNT_PRIME_FRAMES = 40;
81
+ var SNAP_VERIFY_TOLERANCE = 1;
82
+ var SNAP_VERIFY_MAX_ATTEMPTS = 5;
83
+ var SNAP_VERIFY_INITIAL_DELAY_MS = 400;
84
+ var SNAP_VERIFY_RETRY_DELAY_MS = 150;
85
+ function useScrollInit({ listHeaderComponent, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight, scrollTo }) {
86
+ const { headerHeight, scrollPosition, setProgressing } = useContext(ScrollViewContext);
87
+ const hasPullSearch = pullSearchHeight !== void 0;
88
+ const [phase, setPhase] = useState(hasPullSearch && !headerHeight ? "measuring" : "ready");
89
+ const [refreshing, setRefreshing] = useState(false);
90
+ const refreshingRef = useRef(refreshing);
91
+ useEffect(() => {
92
+ refreshingRef.current = refreshing;
93
+ }, [refreshing]);
94
+ const dragStartY = useRef(null);
95
+ const pendingSnapTarget = useRef(null);
96
+ const snapVerifyTimeout = useRef(null);
97
+ const snapVerifyAttempts = useRef(0);
98
+ const scheduleSnapVerification = useCallback(
99
+ (target, delay) => {
100
+ if (snapVerifyTimeout.current !== null) clearTimeout(snapVerifyTimeout.current);
101
+ pendingSnapTarget.current = target;
102
+ snapVerifyAttempts.current = 0;
103
+ const check = () => {
104
+ snapVerifyTimeout.current = null;
105
+ if (pendingSnapTarget.current !== target) return;
106
+ if (Math.abs(scrollPosition.value - target) <= SNAP_VERIFY_TOLERANCE) {
107
+ pendingSnapTarget.current = null;
108
+ return;
109
+ }
110
+ snapVerifyAttempts.current += 1;
111
+ if (snapVerifyAttempts.current > SNAP_VERIFY_MAX_ATTEMPTS || refreshingRef.current) {
112
+ pendingSnapTarget.current = null;
113
+ return;
114
+ }
115
+ scrollTo(target, false);
116
+ snapVerifyTimeout.current = setTimeout(check, SNAP_VERIFY_RETRY_DELAY_MS);
117
+ };
118
+ snapVerifyTimeout.current = setTimeout(check, delay);
119
+ },
120
+ [scrollPosition, scrollTo]
121
+ );
122
+ useEffect(
123
+ () => () => {
124
+ if (snapVerifyTimeout.current !== null) clearTimeout(snapVerifyTimeout.current);
125
+ },
126
+ []
127
+ );
128
+ const [remountTarget] = useState(() => headerHeight !== null ? -headerHeight + (pullSearchHeight ?? 0) : null);
129
+ const remountSyncTarget = useSharedValue(remountTarget);
130
+ const [scrollReady, setScrollReady] = useState(remountTarget === null);
131
+ const remountAttempts = useRef(0);
132
+ const onRemountSynced = useCallback(
133
+ (y) => {
134
+ scrollPosition.value = y;
135
+ setScrollReady(true);
136
+ },
137
+ [scrollPosition]
138
+ );
139
+ const onRemountSyncRetry = useCallback(
140
+ (currentY) => {
141
+ if (remountTarget === null) return;
142
+ remountAttempts.current += 1;
143
+ if (remountAttempts.current > REMOUNT_SYNC_MAX_ATTEMPTS) {
144
+ remountSyncTarget.value = null;
145
+ onRemountSynced(currentY);
146
+ return;
147
+ }
148
+ scrollTo(remountTarget, false);
149
+ },
150
+ [onRemountSynced, remountSyncTarget, remountTarget, scrollTo]
151
+ );
152
+ useEffect(() => {
153
+ if (remountTarget === null) return;
154
+ scrollTo(remountTarget, false);
155
+ const fallback = setTimeout(() => {
156
+ if (remountSyncTarget.value === null) return;
157
+ remountSyncTarget.value = null;
158
+ onRemountSynced(remountTarget);
159
+ }, REMOUNT_SYNC_FALLBACK_MS);
160
+ return () => clearTimeout(fallback);
161
+ }, []);
162
+ useEffect(() => {
163
+ if (phase === "ready" || !headerHeight || !pullSearchHeight) return;
164
+ scrollPosition.value = -headerHeight + pullSearchHeight;
165
+ setPhase("ready");
166
+ }, [phase, headerHeight, pullSearchHeight, scrollPosition]);
167
+ const headerInitialized = useRef(false);
168
+ useLayoutEffect(() => {
169
+ if (!headerHeight || headerInitialized.current) return;
170
+ headerInitialized.current = true;
171
+ const target = remountTarget ?? -headerHeight;
172
+ scrollPosition.value = target;
173
+ scrollTo(target, false);
174
+ if (remountTarget === null) return;
175
+ let framesLeft = REMOUNT_PRIME_FRAMES;
176
+ let handle = null;
177
+ const tick = () => {
178
+ const offset = framesLeft === 1 ? target : framesLeft % 2 === 0 ? target + 30 : target - 30;
179
+ scrollTo(offset, false);
180
+ framesLeft -= 1;
181
+ if (framesLeft > 0) handle = requestAnimationFrame(tick);
182
+ };
183
+ handle = requestAnimationFrame(tick);
184
+ return () => {
185
+ if (handle !== null) cancelAnimationFrame(handle);
186
+ };
187
+ }, [remountTarget, headerHeight, scrollPosition, scrollTo]);
188
+ const pullSearchInitialized = useRef(false);
189
+ useLayoutEffect(() => {
190
+ if (remountTarget !== null || !hasPullSearch || phase !== "ready" || pullSearchInitialized.current || !headerHeight || !pullSearchHeight || !scrollReady) return;
191
+ pullSearchInitialized.current = true;
192
+ scrollPosition.value = -headerHeight + pullSearchHeight;
193
+ scrollTo(-headerHeight + pullSearchHeight, false);
194
+ }, [remountTarget, hasPullSearch, phase, headerHeight, pullSearchHeight, scrollPosition, scrollReady, scrollTo]);
195
+ useEffect(() => {
196
+ if (pullSearchHeight) setProgressing(refreshing);
197
+ }, [pullSearchHeight, refreshing, setProgressing]);
198
+ const handleScrollBeginDrag = useCallback(
199
+ (e) => {
200
+ if (snapVerifyTimeout.current !== null) {
201
+ clearTimeout(snapVerifyTimeout.current);
202
+ snapVerifyTimeout.current = null;
203
+ }
204
+ pendingSnapTarget.current = null;
205
+ if (pullSearchHeight) dragStartY.current = e.nativeEvent.contentOffset.y;
206
+ externalScrollBeginDrag?.(e);
207
+ },
208
+ [externalScrollBeginDrag, pullSearchHeight]
209
+ );
210
+ const handleScrollEndDrag = useCallback(
211
+ (e) => {
212
+ if (pullSearchHeight && headerHeight) {
213
+ const y = e.nativeEvent.contentOffset.y;
214
+ const showY = -headerHeight;
215
+ const hideY = -headerHeight + pullSearchHeight;
216
+ const startY = dragStartY.current;
217
+ dragStartY.current = null;
218
+ const pulledDown = (startY ?? hideY) <= hideY;
219
+ const dragDistance = startY !== null ? startY - y : 0;
220
+ if (pulledDown && dragDistance >= pullSearchHeight * 0.5) {
221
+ if (y <= showY && !refreshing && onRefresh) {
222
+ setRefreshing(true);
223
+ Promise.resolve(onRefresh()).finally(() => setRefreshing(false));
224
+ }
225
+ scrollTo(showY, true);
226
+ scheduleSnapVerification(showY, SNAP_VERIFY_INITIAL_DELAY_MS);
227
+ } else if (y < hideY) {
228
+ scrollTo(hideY, true);
229
+ scheduleSnapVerification(hideY, SNAP_VERIFY_INITIAL_DELAY_MS);
230
+ }
231
+ }
232
+ externalScrollEndDrag?.(e);
233
+ },
234
+ [externalScrollEndDrag, headerHeight, onRefresh, pullSearchHeight, refreshing, scheduleSnapVerification, scrollTo]
235
+ );
236
+ const handleMomentumScrollEnd = useCallback(
237
+ (e) => {
238
+ if (pullSearchHeight && headerHeight && !refreshing && pendingSnapTarget.current === null) {
239
+ const y = e.nativeEvent.contentOffset.y;
240
+ const hideY = -headerHeight + pullSearchHeight;
241
+ if (y < hideY - 1) {
242
+ scrollTo(hideY, true);
243
+ scheduleSnapVerification(hideY, SNAP_VERIFY_INITIAL_DELAY_MS);
244
+ }
245
+ }
246
+ externalMomentumScrollEnd?.(e);
247
+ },
248
+ [externalMomentumScrollEnd, headerHeight, pullSearchHeight, refreshing, scheduleSnapVerification, scrollTo]
249
+ );
250
+ const activeListHeader = hasPullSearch && phase === "measuring" ? null : listHeaderComponent;
251
+ const hiddenHeader = phase === "measuring" && listHeaderComponent != null ? typeof listHeaderComponent === "function" ? createElement(listHeaderComponent) : listHeaderComponent : null;
252
+ return { activeListHeader, handleMomentumScrollEnd, handleScrollBeginDrag, handleScrollEndDrag, hiddenHeader, onRemountSyncRetry, onRemountSynced, pullSearchMinHeight: pullSearchHeight ?? 0, remountSyncTarget, remountTarget, scrollReady };
253
+ }
254
+
67
255
  // src/internal/useScrollHandler.ts
68
- function useScrollHandler({ chipHidden, chipThreshold = 100, footerFixed, headerFixed, isHorizontal, onPullSearchZoneEnter }) {
69
- const { footerHeightShared, footerOffset, headerHeightShared, headerOffset, pullSearchHeightShared, scrollPosition, snapBackFooterShared, snapBackHeaderShared } = useContext(ScrollViewContext);
70
- const snapUpAccum = useSharedValue(0);
71
- const fromBottomBounce = useSharedValue(false);
72
- const isMomentum = useSharedValue(false);
73
- const momentumPullSearchFired = useSharedValue(false);
256
+ function useScrollHandler({ capturedGeneration, chipHidden, chipThreshold = 100, footerFixed, headerFixed, isHorizontal, listGeneration, onRemountSyncRetry, onRemountSynced, remountSyncTarget }) {
257
+ const { footerHeightShared, footerOffset, headerHeightShared, headerOffset, pullSearchHeightShared, scrollPosition, snapBackFooterShared, snapBackHeaderShared } = useContext2(ScrollViewContext);
258
+ const snapUpAccum = useSharedValue2(0);
259
+ const fromBottomBounce = useSharedValue2(false);
74
260
  return useAnimatedScrollHandler(
75
261
  {
76
262
  onScroll: ({ contentOffset: { x, y }, contentSize: { height: contentHeight }, layoutMeasurement: { height: layoutHeight } }) => {
77
263
  "worklet";
264
+ if (remountSyncTarget && remountSyncTarget.value !== null) {
265
+ const target = remountSyncTarget.value;
266
+ if (Math.abs(y - target) < REMOUNT_SYNC_TOLERANCE) {
267
+ remountSyncTarget.value = null;
268
+ if (onRemountSynced) runOnJS(onRemountSynced)(y);
269
+ } else if (onRemountSyncRetry) {
270
+ runOnJS(onRemountSyncRetry)(y);
271
+ }
272
+ return;
273
+ }
274
+ if (listGeneration.value !== capturedGeneration.value) return;
78
275
  if (isHorizontal) {
79
276
  scrollPosition.value = x;
80
277
  chipHidden.value = x < chipThreshold ? 1 : 0;
@@ -90,13 +287,6 @@ function useScrollHandler({ chipHidden, chipThreshold = 100, footerFixed, header
90
287
  } else if (fromBottomBounce.value) {
91
288
  fromBottomBounce.value = false;
92
289
  }
93
- if (isMomentum.value && pullSearchHeightShared.value > 0 && !momentumPullSearchFired.value) {
94
- const hideY = -headerHeightShared.value + pullSearchHeightShared.value;
95
- if (y < hideY) {
96
- momentumPullSearchFired.value = true;
97
- if (onPullSearchZoneEnter) runOnJS(onPullSearchZoneEnter)();
98
- }
99
- }
100
290
  const snapHeader = snapBackHeaderShared.value && !headerFixed;
101
291
  const snapFooter = snapBackFooterShared.value && !footerFixed;
102
292
  if (snapHeader || snapFooter) {
@@ -122,16 +312,9 @@ function useScrollHandler({ chipHidden, chipThreshold = 100, footerFixed, header
122
312
  },
123
313
  onBeginDrag: () => {
124
314
  "worklet";
125
- isMomentum.value = false;
126
- momentumPullSearchFired.value = false;
127
315
  fromBottomBounce.value = false;
128
316
  snapUpAccum.value = 0;
129
317
  },
130
- onMomentumBegin: () => {
131
- "worklet";
132
- isMomentum.value = true;
133
- momentumPullSearchFired.value = false;
134
- },
135
318
  onEndDrag: ({ contentOffset: { y }, contentSize: { height: contentHeight }, layoutMeasurement: { height: layoutHeight } }) => {
136
319
  "worklet";
137
320
  if (isHorizontal) return;
@@ -142,28 +325,26 @@ function useScrollHandler({ chipHidden, chipThreshold = 100, footerFixed, header
142
325
  },
143
326
  onMomentumEnd: () => {
144
327
  "worklet";
145
- isMomentum.value = false;
146
- momentumPullSearchFired.value = false;
147
328
  fromBottomBounce.value = false;
148
329
  snapUpAccum.value = 0;
149
330
  }
150
331
  },
151
- [headerFixed, footerFixed, isHorizontal, chipThreshold, onPullSearchZoneEnter]
332
+ [capturedGeneration, headerFixed, footerFixed, isHorizontal, chipThreshold, listGeneration, onRemountSyncRetry, onRemountSynced, remountSyncTarget]
152
333
  );
153
334
  }
154
335
 
155
336
  // src/internal/useScrollList.ts
156
- import { useContext as useContext2, useEffect, useMemo } from "react";
337
+ import { useContext as useContext3, useEffect as useEffect2, useMemo, useState as useState3 } from "react";
157
338
  import { StyleSheet as StyleSheet2 } from "react-native";
158
- import { useAnimatedStyle, useSharedValue as useSharedValue2, withTiming as withTiming2 } from "react-native-reanimated";
339
+ import { useAnimatedStyle, useSharedValue as useSharedValue3, withTiming as withTiming2 } from "react-native-reanimated";
159
340
  import { useSafeAreaInsets } from "react-native-safe-area-context";
160
341
 
161
342
  // src/useKeyboardInset.ts
162
- import { useState } from "react";
343
+ import { useState as useState2 } from "react";
163
344
  import { useKeyboardHandler } from "react-native-keyboard-controller";
164
345
  import { runOnJS as runOnJS2 } from "react-native-reanimated";
165
346
  var useKeyboardInset = () => {
166
- const [keyboardHeight, setKeyboardHeight] = useState(0);
347
+ const [keyboardHeight, setKeyboardHeight] = useState2(0);
167
348
  useKeyboardHandler(
168
349
  {
169
350
  onMove: (e) => {
@@ -185,28 +366,30 @@ var CHIP_SLIDE = 48;
185
366
  function useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, isHorizontal, keyboardAware, pullSearchHeight, style } = {}) {
186
367
  const insets = useSafeAreaInsets();
187
368
  const keyboardHeight = useKeyboardInset();
188
- const { footerHeight, footerFixed: contextFooterFixed, headerHeight, headerFixed: contextHeaderFixed, headerOffset, pullSearchHeightShared, scrollPosition, snapBackHeaderShared } = useContext2(ScrollViewContext);
369
+ const { footerHeight, footerFixed: contextFooterFixed, headerHeight, headerFixed: contextHeaderFixed, headerOffset, pullSearchHeightShared, scrollPosition, snapBackHeaderShared } = useContext3(ScrollViewContext);
189
370
  const headerFixed = isHorizontal ? true : headerFixedProp ?? contextHeaderFixed;
190
371
  const footerFixed = isHorizontal ? true : footerFixedProp ?? contextFooterFixed;
191
- useEffect(() => {
372
+ useEffect2(() => {
192
373
  pullSearchHeightShared.value = pullSearchHeight ?? 0;
193
374
  }, [pullSearchHeight, pullSearchHeightShared]);
194
- const containerStyle = useMemo(() => headerHeight === 0 ? [{ flex: 1 }, style] : [StyleSheet2.absoluteFill, style], [headerHeight, style]);
195
- const contentInset = useMemo(() => ({ bottom: (footerFixed ? footerHeight || insets.bottom : insets.bottom) + (keyboardAware ? keyboardHeight : 0), top: headerHeight }), [footerFixed, footerHeight, insets.bottom, headerHeight, keyboardAware, keyboardHeight]);
196
- const contentOffset = useMemo(() => ({ x: 0, y: -contentInset.top }), [contentInset.top]);
197
- const chipHidden = useSharedValue2(1);
375
+ const containerStyle = useMemo(() => headerHeight === null ? [{ flex: 1, opacity: 0 }, style] : [StyleSheet2.absoluteFill, style], [headerHeight, style]);
376
+ const contentInset = useMemo(() => ({ bottom: (footerFixed ? footerHeight || insets.bottom : insets.bottom) + (keyboardAware ? keyboardHeight : 0), top: headerHeight ?? 0 }), [footerFixed, footerHeight, insets.bottom, headerHeight, keyboardAware, keyboardHeight]);
377
+ const [startedWithHeader] = useState3(headerHeight !== null);
378
+ const contentOffset = useMemo(() => ({ x: 0, y: headerHeight !== null ? -headerHeight + (startedWithHeader && !isHorizontal && pullSearchHeight ? pullSearchHeight : 0) : 0 }), [headerHeight, isHorizontal, pullSearchHeight, startedWithHeader]);
379
+ const chipHidden = useSharedValue3(1);
198
380
  const chipStyle = useAnimatedStyle(() => {
199
381
  const pointerEvents = chipHidden.value ? "none" : "box-none";
200
382
  if (isHorizontal) {
201
383
  return {
202
384
  opacity: chipHidden.value ? withTiming2(0) : withTiming2(1),
203
385
  pointerEvents,
204
- top: Math.max(headerHeight, insets.top) + 4,
386
+ top: Math.max(headerHeight ?? 0, insets.top) + 4,
205
387
  transform: [{ translateX: chipHidden.value ? withTiming2(-CHIP_SLIDE) : withTiming2(0) }]
206
388
  };
207
389
  }
208
- const slide = headerFixed ? 0 : snapBackHeaderShared.value ? -headerOffset.value : Math.max(0, scrollPosition.value + headerHeight - pullSearchHeightShared.value);
209
- const top = Math.max(headerHeight - slide, insets.top) + 4;
390
+ const h = headerHeight ?? 0;
391
+ const slide = headerFixed ? 0 : snapBackHeaderShared.value ? -headerOffset.value : Math.max(0, scrollPosition.value + h - pullSearchHeightShared.value);
392
+ const top = Math.max(h - slide, insets.top) + 4;
210
393
  return {
211
394
  opacity: chipHidden.value ? withTiming2(0) : withTiming2(1),
212
395
  pointerEvents,
@@ -217,90 +400,21 @@ function useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedP
217
400
  return { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed, headerHeight, insets, isHorizontal: isHorizontal ?? false };
218
401
  }
219
402
 
220
- // src/useScrollInit.ts
221
- import { createElement, useCallback, useContext as useContext3, useEffect as useEffect2, useLayoutEffect, useRef, useState as useState2 } from "react";
222
- function useScrollInit({ listHeaderComponent, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight, scrollTo }) {
223
- const { headerHeight, scrollPosition, setProgressing } = useContext3(ScrollViewContext);
224
- const hasPullSearch = pullSearchHeight !== void 0;
225
- const [phase, setPhase] = useState2(hasPullSearch ? "measuring" : "ready");
226
- const [refreshing, setRefreshing] = useState2(false);
227
- const dragStartY = useRef(null);
228
- const deliberatelyOpened = useRef(false);
229
- useEffect2(() => {
230
- if (phase === "ready" || !headerHeight || !pullSearchHeight) return;
231
- scrollPosition.value = -headerHeight + pullSearchHeight;
232
- setPhase("ready");
233
- }, [phase, headerHeight, pullSearchHeight, scrollPosition]);
234
- const headerInitialized = useRef(false);
235
- useLayoutEffect(() => {
236
- if (!headerHeight || headerInitialized.current) return;
237
- headerInitialized.current = true;
238
- scrollPosition.value = -headerHeight;
239
- scrollTo(-headerHeight, false);
240
- }, [headerHeight, scrollPosition, scrollTo]);
241
- const pullSearchInitialized = useRef(false);
242
- useLayoutEffect(() => {
243
- if (!hasPullSearch || phase !== "ready" || pullSearchInitialized.current || !headerHeight || !pullSearchHeight) return;
244
- pullSearchInitialized.current = true;
245
- scrollPosition.value = -headerHeight + pullSearchHeight;
246
- scrollTo(-headerHeight + pullSearchHeight, false);
247
- }, [hasPullSearch, phase, headerHeight, pullSearchHeight, scrollPosition, scrollTo]);
248
- useEffect2(() => {
249
- if (pullSearchHeight) setProgressing(refreshing);
250
- }, [pullSearchHeight, refreshing, setProgressing]);
251
- const handleScrollBeginDrag = useCallback(
252
- (e) => {
253
- deliberatelyOpened.current = false;
254
- if (pullSearchHeight) dragStartY.current = e.nativeEvent.contentOffset.y;
255
- externalScrollBeginDrag?.(e);
256
- },
257
- [externalScrollBeginDrag, pullSearchHeight]
258
- );
259
- const handleScrollEndDrag = useCallback(
260
- (e) => {
261
- if (pullSearchHeight && headerHeight) {
262
- const y = e.nativeEvent.contentOffset.y;
263
- const showY = -headerHeight;
264
- const hideY = -headerHeight + pullSearchHeight;
265
- const pulledDown = (dragStartY.current ?? hideY) <= hideY;
266
- dragStartY.current = null;
267
- if (pulledDown && y <= showY + pullSearchHeight * 0.5) {
268
- deliberatelyOpened.current = true;
269
- if (y <= showY && !refreshing && onRefresh) {
270
- setRefreshing(true);
271
- Promise.resolve(onRefresh()).finally(() => setRefreshing(false));
272
- }
273
- scrollTo(showY, true);
274
- } else if (y < hideY) {
275
- scrollTo(hideY, true);
276
- }
277
- }
278
- externalScrollEndDrag?.(e);
279
- },
280
- [externalScrollEndDrag, headerHeight, onRefresh, pullSearchHeight, refreshing, scrollTo]
281
- );
282
- const handleMomentumScrollEnd = useCallback(
283
- (e) => {
284
- if (pullSearchHeight && headerHeight && !deliberatelyOpened.current && !refreshing) {
285
- const y = e.nativeEvent.contentOffset.y;
286
- const hideY = -headerHeight + pullSearchHeight;
287
- if (y < hideY - 1) scrollTo(hideY, true);
288
- }
289
- deliberatelyOpened.current = false;
290
- externalMomentumScrollEnd?.(e);
291
- },
292
- [externalMomentumScrollEnd, headerHeight, pullSearchHeight, refreshing, scrollTo]
293
- );
294
- const activeListHeader = hasPullSearch && phase === "measuring" ? null : listHeaderComponent;
295
- const hiddenHeader = phase === "measuring" && listHeaderComponent != null ? typeof listHeaderComponent === "function" ? createElement(listHeaderComponent) : listHeaderComponent : null;
296
- return { activeListHeader, handleMomentumScrollEnd, handleScrollBeginDrag, handleScrollEndDrag, hiddenHeader, pullSearchMinHeight: pullSearchHeight ?? 0 };
297
- }
298
-
299
403
  // src/CustomList.tsx
300
404
  import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
301
405
  var CustomListInner = ({ chipProps, chipThreshold, component: List, renderFilters, footerFixed: footerFixedProp, gesture, headerFixed: headerFixedProp, keyboardAware, onChipPress, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight, scrollRef, style, ...props }) => {
302
406
  const { ListHeaderComponent: listHeaderComponent, contentContainerStyle: externalContentContainerStyle, ...restProps } = props;
303
407
  const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, keyboardAware, pullSearchHeight, style });
408
+ const { listGeneration, onListUnmount } = useContext4(ScrollViewContext);
409
+ const capturedGeneration = useSharedValue4(-1);
410
+ useLayoutEffect2(() => {
411
+ const generation = listGeneration.value;
412
+ runOnUI((gen) => {
413
+ "worklet";
414
+ capturedGeneration.value = gen;
415
+ })(generation);
416
+ }, [capturedGeneration, listGeneration]);
417
+ useLayoutEffect2(() => () => onListUnmount(), [onListUnmount]);
304
418
  const scrollViewInternal = useRef2(null);
305
419
  const handleRef = useCallback2(
306
420
  (el) => {
@@ -318,7 +432,10 @@ var CustomListInner = ({ chipProps, chipThreshold, component: List, renderFilter
318
432
  handleScrollBeginDrag,
319
433
  handleScrollEndDrag,
320
434
  hiddenHeader,
321
- pullSearchMinHeight
435
+ onRemountSyncRetry,
436
+ onRemountSynced,
437
+ pullSearchMinHeight,
438
+ remountSyncTarget
322
439
  } = useScrollInit({
323
440
  listHeaderComponent,
324
441
  onMomentumScrollEnd: externalMomentumScrollEnd,
@@ -337,11 +454,7 @@ var CustomListInner = ({ chipProps, chipThreshold, component: List, renderFilter
337
454
  ] });
338
455
  }, [pullSearchHeader, renderFilters]);
339
456
  const contentContainerStyle = useMemo2(() => [{ minHeight: Dimensions.get("window").height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle], [contentInset.bottom, contentInset.top, externalContentContainerStyle, pullSearchMinHeight]);
340
- const onPullSearchZoneEnter = useCallback2(() => {
341
- if (!pullSearchHeight) return;
342
- scrollViewInternal.current?.scrollToOffset({ offset: -contentInset.top + pullSearchHeight, animated: true });
343
- }, [contentInset.top, pullSearchHeight]);
344
- const onScroll = useScrollHandler({ chipHidden, chipThreshold, footerFixed, headerFixed, onPullSearchZoneEnter: pullSearchHeight ? onPullSearchZoneEnter : void 0 });
457
+ const onScroll = useScrollHandler({ capturedGeneration, chipHidden, chipThreshold, footerFixed, headerFixed, listGeneration, onRemountSyncRetry, onRemountSynced, remountSyncTarget });
345
458
  const handleScrollToTop = useCallback2(() => {
346
459
  const offset = pullSearchHeight ? -contentInset.top + pullSearchHeight : -contentInset.top;
347
460
  scrollViewInternal.current?.scrollToOffset({ offset, animated: true });
@@ -362,14 +475,13 @@ var styles2 = { measureContainer: { left: 0, opacity: 0, position: "absolute", r
362
475
  var CustomList = memo(CustomListInner);
363
476
 
364
477
  // src/FlatList.tsx
365
- import { memo as memo2, useCallback as useCallback3, useMemo as useMemo3, useRef as useRef3 } from "react";
366
- import { Dimensions as Dimensions2, View as View3 } from "react-native";
478
+ import { Fragment as Fragment2, memo as memo2, useCallback as useCallback4, useContext as useContext7, useLayoutEffect as useLayoutEffect3, useMemo as useMemo3, useRef as useRef4 } from "react";
479
+ import { Dimensions as Dimensions2, FlatList as RNFlatList, View as View3 } from "react-native";
367
480
  import { Gesture as Gesture2, GestureDetector as GestureDetector2 } from "react-native-gesture-handler";
368
- import Animated3 from "react-native-reanimated";
369
481
 
370
482
  // src/internal/HorizontalDots.tsx
371
483
  import { BlurView } from "@rific/auto-paper";
372
- import { useContext as useContext4 } from "react";
484
+ import { useContext as useContext5 } from "react";
373
485
  import { StyleSheet as StyleSheet3, useWindowDimensions, View as View2 } from "react-native";
374
486
  import { useTheme } from "react-native-paper";
375
487
  import Animated2, { interpolateColor, useAnimatedStyle as useAnimatedStyle2 } from "react-native-reanimated";
@@ -398,7 +510,7 @@ var Dot = ({ activeColor, index, inactiveColor, pageWidth, scrollPosition }) =>
398
510
  return /* @__PURE__ */ jsx4(View2, { style: styles3.dotContainer, children: /* @__PURE__ */ jsx4(Animated2.View, { style: circleStyle }) });
399
511
  };
400
512
  var HorizontalDots = ({ total }) => {
401
- const { blur, footerHeightShared, scrollPosition } = useContext4(ScrollViewContext);
513
+ const { blur, footerHeightShared, scrollPosition } = useContext5(ScrollViewContext);
402
514
  const { width } = useWindowDimensions();
403
515
  const insets = useSafeAreaInsets2();
404
516
  const theme = useTheme();
@@ -434,14 +546,144 @@ var styles3 = StyleSheet3.create({
434
546
  pillContainer: { alignItems: "center", left: 0, position: "absolute", right: 0, zIndex: 3 }
435
547
  });
436
548
 
549
+ // src/internal/useScrollHandlerJS.ts
550
+ import { useCallback as useCallback3, useContext as useContext6, useRef as useRef3 } from "react";
551
+ import { withTiming as withTiming3 } from "react-native-reanimated";
552
+ var REMOUNT_RETRY_TOLERANCE = 1;
553
+ var REMOUNT_RETRY_MAX_ATTEMPTS = 8;
554
+ function useScrollHandlerJS({ capturedGeneration, chipHidden, chipThreshold = 100, footerFixed, headerFixed, isHorizontal, jsListGeneration, remountTarget = null, scrollTo }) {
555
+ const { footerHeightShared, footerOffset, headerHeightShared, headerOffset, pullSearchHeightShared, scrollPosition, snapBackFooterShared, snapBackHeaderShared } = useContext6(ScrollViewContext);
556
+ const snapUpAccum = useRef3(0);
557
+ const fromBottomBounce = useRef3(false);
558
+ const pendingRemountTarget = useRef3(remountTarget);
559
+ const remountAttempts = useRef3(0);
560
+ const onScroll = useCallback3(
561
+ (event) => {
562
+ if (jsListGeneration.current !== capturedGeneration.current) return;
563
+ const {
564
+ contentOffset: { x, y },
565
+ contentSize: { height: contentHeight },
566
+ layoutMeasurement: { height: layoutHeight }
567
+ } = event.nativeEvent;
568
+ if (pendingRemountTarget.current !== null) {
569
+ const target = pendingRemountTarget.current;
570
+ if (Math.abs(y - target) < REMOUNT_RETRY_TOLERANCE) {
571
+ pendingRemountTarget.current = null;
572
+ } else {
573
+ remountAttempts.current += 1;
574
+ if (remountAttempts.current > REMOUNT_RETRY_MAX_ATTEMPTS) {
575
+ pendingRemountTarget.current = null;
576
+ } else {
577
+ scrollTo?.(target, false);
578
+ return;
579
+ }
580
+ }
581
+ }
582
+ if (isHorizontal) {
583
+ scrollPosition.value = x;
584
+ chipHidden.value = x < chipThreshold ? 1 : 0;
585
+ return;
586
+ }
587
+ const delta = y - scrollPosition.value;
588
+ scrollPosition.value = y;
589
+ chipHidden.value = y < chipThreshold ? 1 : 0;
590
+ const maxScroll = contentHeight - layoutHeight;
591
+ if (maxScroll > 0 && y >= maxScroll) {
592
+ fromBottomBounce.current = true;
593
+ snapUpAccum.current = 0;
594
+ } else if (fromBottomBounce.current) {
595
+ fromBottomBounce.current = false;
596
+ }
597
+ const snapHeader = snapBackHeaderShared.value && !headerFixed;
598
+ const snapFooter = snapBackFooterShared.value && !footerFixed;
599
+ if (snapHeader || snapFooter) {
600
+ if (y <= -headerHeightShared.value) {
601
+ snapUpAccum.current = 0;
602
+ if (snapHeader) headerOffset.value = 0;
603
+ if (snapFooter) footerOffset.value = 0;
604
+ } else if (delta > 0) {
605
+ snapUpAccum.current = 0;
606
+ if (y >= -headerHeightShared.value + pullSearchHeightShared.value) {
607
+ if (snapHeader) headerOffset.value = Math.max(-headerHeightShared.value, Math.min(0, headerOffset.value - delta));
608
+ if (snapFooter) footerOffset.value = Math.max(0, Math.min(footerHeightShared.value, footerOffset.value + delta));
609
+ }
610
+ } else if (delta < 0 && !fromBottomBounce.current) {
611
+ if (y >= -headerHeightShared.value + pullSearchHeightShared.value) snapUpAccum.current -= delta;
612
+ if (snapUpAccum.current >= 10) {
613
+ snapUpAccum.current = 999;
614
+ if (snapHeader) headerOffset.value = withTiming3(0, { duration: 200 });
615
+ if (snapFooter) footerOffset.value = withTiming3(0, { duration: 200 });
616
+ }
617
+ }
618
+ }
619
+ },
620
+ [capturedGeneration, chipHidden, chipThreshold, footerFixed, footerHeightShared, footerOffset, headerFixed, headerHeightShared, headerOffset, isHorizontal, jsListGeneration, pullSearchHeightShared, scrollPosition, scrollTo, snapBackFooterShared, snapBackHeaderShared]
621
+ );
622
+ const onScrollBeginDrag = useCallback3(() => {
623
+ fromBottomBounce.current = false;
624
+ snapUpAccum.current = 0;
625
+ }, []);
626
+ const onMomentumScrollBegin = useCallback3(() => {
627
+ }, []);
628
+ const onScrollEndDrag = useCallback3(
629
+ (event) => {
630
+ if (isHorizontal) return;
631
+ const {
632
+ contentOffset: { y },
633
+ contentSize: { height: contentHeight },
634
+ layoutMeasurement: { height: layoutHeight }
635
+ } = event.nativeEvent;
636
+ if (y >= contentHeight - layoutHeight - 10) {
637
+ fromBottomBounce.current = true;
638
+ snapUpAccum.current = 0;
639
+ }
640
+ },
641
+ [isHorizontal]
642
+ );
643
+ const onMomentumScrollEnd = useCallback3(() => {
644
+ fromBottomBounce.current = false;
645
+ snapUpAccum.current = 0;
646
+ }, []);
647
+ return { onMomentumScrollBegin, onMomentumScrollEnd, onScroll, onScrollBeginDrag, onScrollEndDrag };
648
+ }
649
+
437
650
  // src/FlatList.tsx
438
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
439
- var FlatListInner = ({ chipProps, chipThreshold, contentContainerStyle: externalContentContainerStyle, data, initialNumToRender = 20, maxToRenderPerBatch = 50, renderFilters, footerFixed: footerFixedProp, gesture, headerFixed: headerFixedProp, horizontal, keyboardAware, ListHeaderComponent: externalListHeaderComponent, onChipPress, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pagingEnabled, pullSearchHeight, removeClippedSubviews = false, showsHorizontalScrollIndicator, showsVerticalScrollIndicator, style, onContentSizeChange, ref: externalRef, windowSize = 100, ...props }) => {
440
- const scrollView = useRef3(null);
651
+ import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
652
+ var FlatListInner = ({ chipProps, chipThreshold, columnWrapperStyle, contentContainerStyle: externalContentContainerStyle, data, initialNumToRender = 20, keyExtractor, maxToRenderPerBatch = 50, numColumns, renderFilters, renderItem, footerFixed: footerFixedProp, gesture, headerFixed: headerFixedProp, horizontal, keyboardAware, ListHeaderComponent: externalListHeaderComponent, onChipPress, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pagingEnabled, pullSearchHeight, removeClippedSubviews = false, showsHorizontalScrollIndicator, showsVerticalScrollIndicator, style, onContentSizeChange, ref: externalRef, windowSize = 100, ...props }) => {
653
+ const scrollView = useRef4(null);
441
654
  const isHorizontal = horizontal === true;
442
655
  const showDots = isHorizontal && pagingEnabled === true && (data?.length ?? 0) > 1;
656
+ const cols = numColumns && numColumns > 1 ? numColumns : 1;
657
+ const rowData = useMemo3(() => {
658
+ if (cols <= 1 || !data) return data;
659
+ const items = Array.from(data);
660
+ const rows = [];
661
+ for (let i = 0; i < items.length; i += cols) rows.push(items.slice(i, i + cols));
662
+ return rows;
663
+ }, [cols, data]);
664
+ const rowKeyExtractor = useCallback4(
665
+ (row, rowIndex) => {
666
+ if (cols <= 1) return keyExtractor ? keyExtractor(row, rowIndex) : defaultKeyExtractor(row, rowIndex);
667
+ return row.map((item, kk) => keyExtractor ? keyExtractor(item, rowIndex * cols + kk) : defaultKeyExtractor(item, rowIndex * cols + kk)).join(":");
668
+ },
669
+ [cols, keyExtractor]
670
+ );
671
+ const rowRenderItem = useCallback4(
672
+ (info) => {
673
+ if (cols <= 1) return renderItem ? renderItem(info) : null;
674
+ const row = info.item;
675
+ return /* @__PURE__ */ jsx5(View3, { style: [styles4.row, columnWrapperStyle], children: row.map((item, kk) => /* @__PURE__ */ jsx5(Fragment2, { children: renderItem ? renderItem({ index: info.index * cols + kk, item, separators: info.separators }) : null }, kk)) });
676
+ },
677
+ [cols, columnWrapperStyle, renderItem]
678
+ );
443
679
  const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, isHorizontal, keyboardAware, pullSearchHeight, style });
444
- const scrollTo = useCallback3((offset, animated) => {
680
+ const { jsListGeneration, onJsListUnmount } = useContext7(ScrollViewContext);
681
+ const capturedGeneration = useRef4(jsListGeneration.current);
682
+ useLayoutEffect3(() => {
683
+ capturedGeneration.current = jsListGeneration.current;
684
+ }, [jsListGeneration]);
685
+ useLayoutEffect3(() => () => onJsListUnmount(), [onJsListUnmount]);
686
+ const scrollTo = useCallback4((offset, animated) => {
445
687
  scrollView.current?.scrollToOffset({ offset, animated });
446
688
  }, []);
447
689
  const {
@@ -450,7 +692,8 @@ var FlatListInner = ({ chipProps, chipThreshold, contentContainerStyle: external
450
692
  handleScrollBeginDrag,
451
693
  handleScrollEndDrag,
452
694
  hiddenHeader,
453
- pullSearchMinHeight
695
+ pullSearchMinHeight,
696
+ remountTarget
454
697
  } = useScrollInit({
455
698
  listHeaderComponent: externalListHeaderComponent,
456
699
  onMomentumScrollEnd: externalMomentumScrollEnd,
@@ -463,18 +706,35 @@ var FlatListInner = ({ chipProps, chipThreshold, contentContainerStyle: external
463
706
  const activeListHeader = useMemo3(() => {
464
707
  if (!renderFilters) return pullSearchHeader;
465
708
  const Inner = pullSearchHeader;
466
- return () => /* @__PURE__ */ jsxs3(Fragment2, { children: [
709
+ return () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
467
710
  Inner ? typeof Inner === "function" ? /* @__PURE__ */ jsx5(Inner, {}) : Inner : null,
468
711
  renderFilters
469
712
  ] });
470
713
  }, [pullSearchHeader, renderFilters]);
471
714
  const contentContainerStyle = useMemo3(() => isHorizontal ? externalContentContainerStyle : [{ minHeight: Dimensions2.get("window").height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle], [isHorizontal, contentInset.bottom, contentInset.top, externalContentContainerStyle, pullSearchMinHeight]);
472
- const onPullSearchZoneEnter = useCallback3(() => {
473
- if (!pullSearchHeight) return;
474
- scrollView.current?.scrollToOffset({ offset: -contentInset.top + pullSearchHeight, animated: true });
475
- }, [contentInset.top, pullSearchHeight]);
476
- const handleScroll = useScrollHandler({ chipHidden, chipThreshold, footerFixed, headerFixed, isHorizontal, onPullSearchZoneEnter: pullSearchHeight ? onPullSearchZoneEnter : void 0 });
477
- const handleScrollToTop = useCallback3(() => {
715
+ const scrollHandler = useScrollHandlerJS({ capturedGeneration, chipHidden, chipThreshold, footerFixed, headerFixed, isHorizontal, jsListGeneration, remountTarget, scrollTo });
716
+ const onScrollBeginDrag = useCallback4(
717
+ (e) => {
718
+ scrollHandler.onScrollBeginDrag(e);
719
+ handleScrollBeginDrag(e);
720
+ },
721
+ [scrollHandler, handleScrollBeginDrag]
722
+ );
723
+ const onScrollEndDrag = useCallback4(
724
+ (e) => {
725
+ scrollHandler.onScrollEndDrag(e);
726
+ handleScrollEndDrag(e);
727
+ },
728
+ [scrollHandler, handleScrollEndDrag]
729
+ );
730
+ const onMomentumScrollEnd = useCallback4(
731
+ (e) => {
732
+ scrollHandler.onMomentumScrollEnd(e);
733
+ handleMomentumScrollEnd(e);
734
+ },
735
+ [scrollHandler, handleMomentumScrollEnd]
736
+ );
737
+ const handleScrollToTop = useCallback4(() => {
478
738
  if (isHorizontal) {
479
739
  scrollView.current?.scrollToOffset({ offset: 0, animated: true });
480
740
  return;
@@ -482,7 +742,7 @@ var FlatListInner = ({ chipProps, chipThreshold, contentContainerStyle: external
482
742
  const offset = pullSearchHeight ? -contentInset.top + pullSearchHeight : -contentInset.top;
483
743
  scrollView.current?.scrollToOffset({ offset, animated: true });
484
744
  }, [isHorizontal, contentInset.top, pullSearchHeight]);
485
- const handleContentSizeChange = useCallback3(
745
+ const handleContentSizeChange = useCallback4(
486
746
  (w, h) => {
487
747
  if (onContentSizeChange) onContentSizeChange(w, h);
488
748
  },
@@ -494,23 +754,27 @@ var FlatListInner = ({ chipProps, chipThreshold, contentContainerStyle: external
494
754
  const detectorGesture = gesture !== void 0 ? combinedGesture : void 0;
495
755
  const content = /* @__PURE__ */ jsxs3(View3, { style: containerStyle, children: [
496
756
  /* @__PURE__ */ jsx5(
497
- Animated3.FlatList,
757
+ RNFlatList,
498
758
  {
499
759
  ...props,
500
760
  contentContainerStyle,
501
761
  contentInset,
762
+ contentInsetAdjustmentBehavior: "never",
502
763
  contentOffset,
503
- data,
764
+ data: cols > 1 ? rowData : data,
504
765
  horizontal: isHorizontal,
505
766
  initialNumToRender,
767
+ keyExtractor: cols > 1 ? rowKeyExtractor : keyExtractor,
506
768
  ListHeaderComponent: activeListHeader,
507
769
  maxToRenderPerBatch,
508
770
  onContentSizeChange: handleContentSizeChange,
509
- onMomentumScrollEnd: handleMomentumScrollEnd,
510
- onScroll: handleScroll,
511
- onScrollBeginDrag: handleScrollBeginDrag,
512
- onScrollEndDrag: handleScrollEndDrag,
771
+ onMomentumScrollBegin: scrollHandler.onMomentumScrollBegin,
772
+ onMomentumScrollEnd,
773
+ onScroll: scrollHandler.onScroll,
774
+ onScrollBeginDrag,
775
+ onScrollEndDrag,
513
776
  pagingEnabled,
777
+ renderItem: cols > 1 ? rowRenderItem : renderItem,
514
778
  ref: (el) => {
515
779
  if (externalRef) externalRef.current = el;
516
780
  scrollView.current = el;
@@ -529,22 +793,26 @@ var FlatListInner = ({ chipProps, chipThreshold, contentContainerStyle: external
529
793
  ] });
530
794
  return detectorGesture ? /* @__PURE__ */ jsx5(GestureDetector2, { gesture: detectorGesture, children: content }) : content;
531
795
  };
532
- var styles4 = { measureContainer: { left: 0, opacity: 0, position: "absolute", right: 0, top: -9999 } };
796
+ var defaultKeyExtractor = (item, index) => {
797
+ const key = item?.key ?? item?.id;
798
+ return key != null ? String(key) : String(index);
799
+ };
800
+ var styles4 = { measureContainer: { left: 0, opacity: 0, position: "absolute", right: 0, top: -9999 }, row: { flexDirection: "row" } };
533
801
  var FlatList = memo2(FlatListInner);
534
802
 
535
803
  // src/PullSearch.tsx
536
- import { forwardRef, useCallback as useCallback4, useContext as useContext5, useEffect as useEffect3, useImperativeHandle, useRef as useRef4, useState as useState3 } from "react";
804
+ import { forwardRef, useCallback as useCallback5, useContext as useContext8, useEffect as useEffect3, useImperativeHandle, useRef as useRef5, useState as useState4 } from "react";
537
805
  import { StyleSheet as StyleSheet4 } from "react-native";
538
806
  import { Searchbar, useTheme as useTheme2 } from "react-native-paper";
539
- import Animated4, { Extrapolation, interpolate, runOnJS as runOnJS3, useAnimatedReaction, useAnimatedStyle as useAnimatedStyle3 } from "react-native-reanimated";
807
+ import Animated3, { Extrapolation, interpolate, runOnJS as runOnJS3, useAnimatedReaction, useAnimatedStyle as useAnimatedStyle3 } from "react-native-reanimated";
540
808
  import { jsx as jsx6 } from "react/jsx-runtime";
541
809
  var PullSearch = forwardRef(function PullSearch2({ debounce, onChangeText, onHeightChange, placeholder, value: propValue }, ref) {
542
- const { headerHeight, scrollPosition } = useContext5(ScrollViewContext);
810
+ const { headerHeight, scrollPosition } = useContext8(ScrollViewContext);
543
811
  const theme = useTheme2();
544
- const inputRef = useRef4(null);
545
- const [barHeight, setBarHeight] = useState3(0);
546
- const [value, setValue] = useState3(propValue ?? "");
547
- const prevPropValueRef = useRef4(propValue);
812
+ const inputRef = useRef5(null);
813
+ const [barHeight, setBarHeight] = useState4(0);
814
+ const [value, setValue] = useState4(propValue ?? "");
815
+ const prevPropValueRef = useRef5(propValue);
548
816
  useEffect3(() => {
549
817
  if (prevPropValueRef.current === propValue) return;
550
818
  prevPropValueRef.current = propValue;
@@ -554,22 +822,22 @@ var PullSearch = forwardRef(function PullSearch2({ debounce, onChangeText, onHei
554
822
  blur: () => inputRef.current?.blur(),
555
823
  focus: () => inputRef.current?.focus()
556
824
  }));
557
- const blurInput = useCallback4(() => inputRef.current?.blur(), []);
825
+ const blurInput = useCallback5(() => inputRef.current?.blur(), []);
558
826
  useAnimatedReaction(
559
827
  () => {
560
828
  if (barHeight === 0) return false;
561
- return scrollPosition.value >= -headerHeight + barHeight;
829
+ return scrollPosition.value >= -(headerHeight ?? 0) + barHeight;
562
830
  },
563
831
  (isHidden, wasHidden) => {
564
832
  if (isHidden && !wasHidden) runOnJS3(blurInput)();
565
833
  },
566
834
  [barHeight, headerHeight, blurInput]
567
835
  );
568
- const handleClear = useCallback4(() => {
836
+ const handleClear = useCallback5(() => {
569
837
  setValue("");
570
838
  onChangeText("");
571
839
  }, [onChangeText]);
572
- const handleLayout = useCallback4(
840
+ const handleLayout = useCallback5(
573
841
  ({
574
842
  nativeEvent: {
575
843
  layout: { height }
@@ -590,11 +858,12 @@ var PullSearch = forwardRef(function PullSearch2({ debounce, onChangeText, onHei
590
858
  }, [debounce, onChangeText, value]);
591
859
  const animatedStyle = useAnimatedStyle3(() => {
592
860
  if (barHeight === 0) return { opacity: 0 };
593
- const showY = -headerHeight;
594
- const hideY = -headerHeight + barHeight;
861
+ const h = headerHeight ?? 0;
862
+ const showY = -h;
863
+ const hideY = -h + barHeight;
595
864
  return { opacity: interpolate(scrollPosition.value, [showY, hideY], [1, 0], Extrapolation.CLAMP) };
596
865
  }, [barHeight, headerHeight]);
597
- return /* @__PURE__ */ jsx6(Animated4.View, { onLayout: handleLayout, style: [styles5.container, animatedStyle], children: /* @__PURE__ */ jsx6(Searchbar, { autoCorrect: false, onChangeText: (text) => setValue((text || "").trimStart()), onClearIconPress: handleClear, placeholder, placeholderTextColor: theme.colors.onSurfaceVariant, ref: inputRef, spellCheck: false, style: styles5.input, value }) });
866
+ return /* @__PURE__ */ jsx6(Animated3.View, { onLayout: handleLayout, style: [styles5.container, animatedStyle], children: /* @__PURE__ */ jsx6(Searchbar, { autoCorrect: false, onChangeText: (text) => setValue((text || "").trimStart()), onClearIconPress: handleClear, placeholder, placeholderTextColor: theme.colors.onSurfaceVariant, ref: inputRef, spellCheck: false, style: styles5.input, value }) });
598
867
  });
599
868
  var styles5 = StyleSheet4.create({
600
869
  container: { paddingVertical: 8 },
@@ -1095,6 +1364,24 @@ var objectTraps = {
1095
1364
  get(state, prop) {
1096
1365
  if (prop === DRAFT_STATE)
1097
1366
  return state;
1367
+ if (prop === "constructor" || prop === "__proto__") {
1368
+ const source2 = latest(state);
1369
+ const value2 = source2[prop];
1370
+ return new Proxy(value2 || {}, {
1371
+ get: (target, key) => {
1372
+ if (key === "__proto__" || key === "prototype") {
1373
+ return Object.freeze(/* @__PURE__ */ Object.create(null));
1374
+ }
1375
+ return Reflect.get(target, key);
1376
+ },
1377
+ set: () => {
1378
+ return true;
1379
+ },
1380
+ apply: (target, thisArg, args) => {
1381
+ return Reflect.apply(target, thisArg, args);
1382
+ }
1383
+ });
1384
+ }
1098
1385
  let arrayPlugin = state.scope_.arrayMethodsPlugin_;
1099
1386
  const isArrayWithStringProp = state.type_ === 1 && typeof prop === "string";
1100
1387
  if (isArrayWithStringProp) {
@@ -1124,12 +1411,18 @@ var objectTraps = {
1124
1411
  return value;
1125
1412
  },
1126
1413
  has(state, prop) {
1414
+ if (prop === "constructor" || prop === "__proto__" || prop === "prototype") {
1415
+ return false;
1416
+ }
1127
1417
  return prop in latest(state);
1128
1418
  },
1129
1419
  ownKeys(state) {
1130
1420
  return Reflect.ownKeys(latest(state));
1131
1421
  },
1132
1422
  set(state, prop, value) {
1423
+ if (prop === "constructor" || prop === "__proto__" || prop === "prototype") {
1424
+ return true;
1425
+ }
1133
1426
  const desc = getDescriptorFromProto(latest(state), prop);
1134
1427
  if (desc?.set) {
1135
1428
  desc.set.call(state.draft_, value);
@@ -1149,7 +1442,7 @@ var objectTraps = {
1149
1442
  markChanged(state);
1150
1443
  }
1151
1444
  if (state.copy_[prop] === value && // special case: handle new props with value 'undefined'
1152
- (value !== void 0 || prop in state.copy_) || // special case: NaN
1445
+ (value !== void 0 || has(state.copy_, prop, state.type_)) || // special case: NaN
1153
1446
  Number.isNaN(value) && Number.isNaN(state.copy_[prop]))
1154
1447
  return true;
1155
1448
  state.copy_[prop] = value;
@@ -2165,16 +2458,26 @@ var scrollViewActions = slice.actions;
2165
2458
  var scrollViewReducer = slice.reducer;
2166
2459
 
2167
2460
  // src/ScrollView.tsx
2168
- import { memo as memo3, useCallback as useCallback5, useMemo as useMemo4, useRef as useRef5 } from "react";
2461
+ import { memo as memo3, useCallback as useCallback6, useContext as useContext9, useLayoutEffect as useLayoutEffect4, useMemo as useMemo4, useRef as useRef6 } from "react";
2169
2462
  import { View as View4 } from "react-native";
2170
2463
  import { Gesture as Gesture3, GestureDetector as GestureDetector3 } from "react-native-gesture-handler";
2171
- import Animated5 from "react-native-reanimated";
2464
+ import Animated4, { runOnUI as runOnUI2, useSharedValue as useSharedValue5 } from "react-native-reanimated";
2172
2465
  import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
2173
2466
  var ScrollViewInner = ({ chipProps, chipThreshold, children, footerFixed: footerFixedProp, gesture, headerFixed: headerFixedProp, horizontal, keyboardAware, onChipPress, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight, refreshing, style, ...props }) => {
2174
- const scrollView = useRef5(null);
2467
+ const scrollView = useRef6(null);
2175
2468
  const isHorizontal = horizontal === true;
2176
2469
  const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, isHorizontal, keyboardAware, pullSearchHeight, style });
2177
- const scrollTo = useCallback5((offset, animated) => {
2470
+ const { listGeneration, onListUnmount } = useContext9(ScrollViewContext);
2471
+ const capturedGeneration = useSharedValue5(-1);
2472
+ useLayoutEffect4(() => {
2473
+ const generation = listGeneration.value;
2474
+ runOnUI2((gen) => {
2475
+ "worklet";
2476
+ capturedGeneration.value = gen;
2477
+ })(generation);
2478
+ }, [capturedGeneration, listGeneration]);
2479
+ useLayoutEffect4(() => () => onListUnmount(), [onListUnmount]);
2480
+ const scrollTo = useCallback6((offset, animated) => {
2178
2481
  scrollView.current?.scrollTo({ y: offset, animated });
2179
2482
  }, []);
2180
2483
  const { handleMomentumScrollEnd, handleScrollBeginDrag, handleScrollEndDrag } = useScrollInit({
@@ -2184,12 +2487,8 @@ var ScrollViewInner = ({ chipProps, chipThreshold, children, footerFixed: footer
2184
2487
  pullSearchHeight,
2185
2488
  scrollTo
2186
2489
  });
2187
- const onPullSearchZoneEnter = useCallback5(() => {
2188
- if (!pullSearchHeight) return;
2189
- scrollTo(-contentInset.top + pullSearchHeight, true);
2190
- }, [contentInset.top, pullSearchHeight, scrollTo]);
2191
- const handleScroll = useScrollHandler({ chipHidden, chipThreshold, footerFixed, headerFixed, isHorizontal, onPullSearchZoneEnter: pullSearchHeight ? onPullSearchZoneEnter : void 0 });
2192
- const handleScrollToTop = useCallback5(() => {
2490
+ const handleScroll = useScrollHandler({ capturedGeneration, chipHidden, chipThreshold, footerFixed, headerFixed, isHorizontal, listGeneration });
2491
+ const handleScrollToTop = useCallback6(() => {
2193
2492
  if (isHorizontal) {
2194
2493
  scrollView.current?.scrollTo({ x: 0, animated: true });
2195
2494
  return;
@@ -2203,7 +2502,7 @@ var ScrollViewInner = ({ chipProps, chipThreshold, children, footerFixed: footer
2203
2502
  const detectorGesture = gesture !== void 0 ? combinedGesture : void 0;
2204
2503
  const content = /* @__PURE__ */ jsxs4(View4, { style: containerStyle, children: [
2205
2504
  /* @__PURE__ */ jsx7(
2206
- Animated5.ScrollView,
2505
+ Animated4.ScrollView,
2207
2506
  {
2208
2507
  ...props,
2209
2508
  contentInset,
@@ -2229,13 +2528,13 @@ var ScrollView = memo3(ScrollViewInner);
2229
2528
 
2230
2529
  // src/ScrollViewFooter.tsx
2231
2530
  import { BlurView as BlurView2 } from "@rific/auto-paper";
2232
- import { useContext as useContext6, useEffect as useEffect4 } from "react";
2531
+ import { useContext as useContext10, useEffect as useEffect4 } from "react";
2233
2532
  import { StyleSheet as StyleSheet5, View as View5 } from "react-native";
2234
- import Animated6, { useAnimatedStyle as useAnimatedStyle4 } from "react-native-reanimated";
2533
+ import Animated5, { useAnimatedStyle as useAnimatedStyle4 } from "react-native-reanimated";
2235
2534
  import { useSafeAreaInsets as useSafeAreaInsets3 } from "react-native-safe-area-context";
2236
2535
  import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
2237
2536
  var ScrollViewFooter = ({ children, style }) => {
2238
- const { blur, footerHeight, footerFixed, footerOffset, headerHeightShared, pullSearchHeightShared, scrollPosition, setFooterHeight, snapBackFooterShared } = useContext6(ScrollViewContext);
2537
+ const { blur, footerHeight, footerFixed, footerOffset, headerHeightShared, pullSearchHeightShared, scrollPosition, setFooterHeight, snapBackFooterShared } = useContext10(ScrollViewContext);
2239
2538
  const insets = useSafeAreaInsets3();
2240
2539
  useEffect4(
2241
2540
  () => () => {
@@ -2257,7 +2556,7 @@ var ScrollViewFooter = ({ children, style }) => {
2257
2556
  if (effective <= 0) return { transform: [{ translateY: 0 }] };
2258
2557
  return { transform: [{ translateY: Math.min(effective, footerHeight) }] };
2259
2558
  }, [footerHeight, footerFixed]);
2260
- return /* @__PURE__ */ jsxs5(Animated6.View, { onLayout: handleLayout, pointerEvents: "box-none", style: [styles6.footer, footerStyle], children: [
2559
+ return /* @__PURE__ */ jsxs5(Animated5.View, { onLayout: handleLayout, pointerEvents: "box-none", style: [styles6.footer, footerStyle], children: [
2261
2560
  footerHeight > 0 && /* @__PURE__ */ jsx8(BlurView2, { blur, style: StyleSheet5.absoluteFill }),
2262
2561
  /* @__PURE__ */ jsx8(View5, { style: [styles6.row, { paddingBottom: insets.bottom }, style], children })
2263
2562
  ] });
@@ -2269,16 +2568,16 @@ var styles6 = StyleSheet5.create({
2269
2568
 
2270
2569
  // src/ScrollViewHeader.tsx
2271
2570
  import { BlurView as BlurView3 } from "@rific/auto-paper";
2272
- import { useContext as useContext7 } from "react";
2571
+ import { useContext as useContext11 } from "react";
2273
2572
  import { StyleSheet as StyleSheet6, Text, View as View6 } from "react-native";
2274
2573
  import { Appbar, ProgressBar, Surface, useTheme as useTheme3 } from "react-native-paper";
2275
- import Animated7, { useAnimatedStyle as useAnimatedStyle5 } from "react-native-reanimated";
2574
+ import Animated6, { useAnimatedStyle as useAnimatedStyle5 } from "react-native-reanimated";
2276
2575
  import { useSafeAreaInsets as useSafeAreaInsets4 } from "react-native-safe-area-context";
2277
- import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
2576
+ import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
2278
2577
  var ActionBg = ({ blur, style }) => blur ? /* @__PURE__ */ jsx9(View6, { style: [StyleSheet6.absoluteFill, styles7.actionBg, style], children: /* @__PURE__ */ jsx9(BlurView3, { blur: true, style: StyleSheet6.absoluteFill }) }) : /* @__PURE__ */ jsx9(View6, { style: [StyleSheet6.absoluteFill, styles7.actionBg, style], pointerEvents: "none", children: /* @__PURE__ */ jsx9(Surface, { style: StyleSheet6.absoluteFill, elevation: 1, children: null }) });
2279
2578
  var ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, backActionFixed, caption, centerContent, children, iconSize, style, title, topInset = true, trailingAction, trailingActionFixed = true }) => {
2280
- const { blur, headerHeight, headerFixed, headerOffset, progress, progressing, pullSearchHeightShared, scrollPosition, setHeaderHeight, snapBackHeaderShared } = useContext7(ScrollViewContext);
2281
- const { settings } = useContext7(ScrollViewSettingsContext);
2579
+ const { blur, headerHeight, headerFixed, headerOffset, progress, progressing, pullSearchHeightShared, scrollPosition, setHeaderHeight, snapBackHeaderShared } = useContext11(ScrollViewContext);
2580
+ const { settings } = useContext11(ScrollViewSettingsContext);
2282
2581
  const effectiveBackActionFixed = backActionFixed ?? settings.backActionFixed;
2283
2582
  const insets = useSafeAreaInsets4();
2284
2583
  const theme = useTheme3();
@@ -2293,20 +2592,22 @@ var ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, backActionFi
2293
2592
  const translateStyle = useAnimatedStyle5(() => {
2294
2593
  if (headerFixed) return { transform: [{ translateY: 0 }] };
2295
2594
  if (snapBackHeaderShared.value) return { transform: [{ translateY: headerOffset.value }] };
2296
- const effective = scrollPosition.value + headerHeight - pullSearchHeightShared.value;
2595
+ const effective = scrollPosition.value + (headerHeight ?? 0) - pullSearchHeightShared.value;
2297
2596
  if (effective <= 0) return { transform: [{ translateY: 0 }] };
2298
2597
  return { transform: [{ translateY: -effective }] };
2299
2598
  }, [headerFixed, headerHeight]);
2300
2599
  const blurStyle = useAnimatedStyle5(() => {
2301
2600
  if (!headerHeight) return { height: 0 };
2302
2601
  if (headerFixed) return { height: headerHeight };
2303
- const translateY = snapBackHeaderShared.value ? headerOffset.value : -Math.max(0, scrollPosition.value + headerHeight - pullSearchHeightShared.value);
2304
- return { height: Math.max(headerHeight + translateY, top) };
2602
+ const h = headerHeight ?? 0;
2603
+ const translateY = snapBackHeaderShared.value ? headerOffset.value : -Math.max(0, scrollPosition.value + h - pullSearchHeightShared.value);
2604
+ return { height: Math.max(h + translateY, top) };
2305
2605
  }, [headerFixed, headerHeight, top]);
2306
2606
  const progressStyle = useAnimatedStyle5(() => {
2307
- if (headerFixed) return { top: headerHeight };
2308
- const slide = snapBackHeaderShared.value ? -headerOffset.value : Math.max(0, scrollPosition.value + headerHeight - pullSearchHeightShared.value);
2309
- return { top: Math.max(headerHeight - slide, top) };
2607
+ const h = headerHeight ?? 0;
2608
+ if (headerFixed) return { top: h };
2609
+ const slide = snapBackHeaderShared.value ? -headerOffset.value : Math.max(0, scrollPosition.value + h - pullSearchHeightShared.value);
2610
+ return { top: Math.max(h - slide, top) };
2310
2611
  }, [headerFixed, headerHeight, top]);
2311
2612
  const actionMargin = 4;
2312
2613
  const contentMinHeight = actionSize + 2 * actionMargin;
@@ -2315,32 +2616,31 @@ var ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, backActionFi
2315
2616
  const actionShadow = { borderRadius: actionSize / 2, elevation: 2, shadowColor: "#000", shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.15, shadowRadius: 2 };
2316
2617
  const leadingStyle = { alignItems: "center", height: actionSize, justifyContent: "center", left: actionMargin, position: "absolute", top: actionTop, width: actionSize, zIndex: 3, ...actionShadow };
2317
2618
  const trailingStyle = { alignItems: "center", height: actionSize, justifyContent: "center", position: "absolute", right: actionMargin, top: actionTop, width: actionSize, zIndex: 3, ...actionShadow };
2318
- return /* @__PURE__ */ jsxs6(Fragment3, { children: [
2319
- /* @__PURE__ */ jsx9(Animated7.View, { pointerEvents: "none", style: [styles7.blur, blurStyle], children: headerHeight > 0 && /* @__PURE__ */ jsx9(BlurView3, { blur, style: [styles7.blurInner, { height: headerHeight }] }) }),
2320
- /* @__PURE__ */ jsxs6(Animated7.View, { onLayout: handleLayout, pointerEvents: "box-none", style: [headerHeight === 0 ? styles7.headerInit : styles7.header, translateStyle], children: [
2321
- /* @__PURE__ */ jsx9(View6, { style: [{ paddingTop: top }, style], children: /* @__PURE__ */ jsxs6(View6, { style: [styles7.content, { minHeight: contentMinHeight }], children: [
2619
+ return /* @__PURE__ */ jsxs6(Fragment4, { children: [
2620
+ /* @__PURE__ */ jsx9(Animated6.View, { pointerEvents: "none", style: [styles7.blur, blurStyle], children: headerHeight !== null && headerHeight > 0 && /* @__PURE__ */ jsx9(BlurView3, { blur, style: [styles7.blurInner, { height: headerHeight }] }) }),
2621
+ /* @__PURE__ */ jsxs6(Animated6.View, { onLayout: handleLayout, pointerEvents: "box-none", style: [headerHeight === null ? styles7.headerInit : styles7.header, translateStyle], children: [
2622
+ /* @__PURE__ */ jsx9(View6, { style: [{ paddingTop: top }, style], children: children ?? /* @__PURE__ */ jsxs6(View6, { style: [styles7.content, { minHeight: contentMinHeight }], children: [
2322
2623
  /* @__PURE__ */ jsx9(View6, { style: styles7.side }),
2323
2624
  /* @__PURE__ */ jsx9(View6, { style: styles7.spacer }),
2324
- /* @__PURE__ */ jsx9(View6, { style: styles7.side, children }),
2325
- (centerContent || title || caption) && /* @__PURE__ */ jsx9(View6, { style: [StyleSheet6.absoluteFill, styles7.titleContainer], pointerEvents: centerContent ? "box-none" : "none", children: centerContent ?? /* @__PURE__ */ jsxs6(Fragment3, { children: [
2625
+ (centerContent || title || caption) && /* @__PURE__ */ jsx9(View6, { style: [StyleSheet6.absoluteFill, styles7.titleContainer], pointerEvents: centerContent ? "box-none" : "none", children: centerContent ?? /* @__PURE__ */ jsxs6(Fragment4, { children: [
2326
2626
  title && /* @__PURE__ */ jsx9(Text, { numberOfLines: 1, style: [styles7.title, { color: theme.colors.onSurface }], children: title }),
2327
2627
  caption && /* @__PURE__ */ jsx9(Text, { numberOfLines: 1, style: [styles7.caption, { color: theme.colors.onSurfaceVariant }], children: caption })
2328
2628
  ] }) })
2329
2629
  ] }) }),
2330
2630
  !blur && /* @__PURE__ */ jsx9(View6, { style: [styles7.divider, { backgroundColor: theme.colors.outlineVariant }] })
2331
2631
  ] }),
2332
- /* @__PURE__ */ jsx9(Animated7.View, { pointerEvents: "none", style: [styles7.progress, progressStyle], children: /* @__PURE__ */ jsx9(ProgressBar, { indeterminate: progress === null, visible: progressing, progress: progress ?? void 0 }) }),
2333
- backAction && (effectiveBackActionFixed ? /* @__PURE__ */ jsxs6(Animated7.View, { style: leadingStyle, children: [
2632
+ /* @__PURE__ */ jsx9(Animated6.View, { pointerEvents: "none", style: [styles7.progress, progressStyle], children: /* @__PURE__ */ jsx9(ProgressBar, { indeterminate: progress === null, visible: progressing, progress: progress ?? void 0 }) }),
2633
+ backAction && (effectiveBackActionFixed ? /* @__PURE__ */ jsxs6(Animated6.View, { style: leadingStyle, children: [
2334
2634
  /* @__PURE__ */ jsx9(ActionBg, { blur, style: actionStyle }),
2335
2635
  /* @__PURE__ */ jsx9(Appbar.BackAction, { onPress: backAction, size: iconSize ?? actionSize / 2, style: buttonStyle })
2336
- ] }) : /* @__PURE__ */ jsxs6(Animated7.View, { style: [leadingStyle, translateStyle], children: [
2636
+ ] }) : /* @__PURE__ */ jsxs6(Animated6.View, { style: [leadingStyle, translateStyle], children: [
2337
2637
  /* @__PURE__ */ jsx9(ActionBg, { blur, style: actionStyle }),
2338
2638
  /* @__PURE__ */ jsx9(Appbar.BackAction, { onPress: backAction, size: iconSize ?? actionSize / 2, style: buttonStyle })
2339
2639
  ] })),
2340
2640
  trailingAction && (trailingActionFixed ? /* @__PURE__ */ jsxs6(View6, { style: trailingStyle, children: [
2341
2641
  /* @__PURE__ */ jsx9(ActionBg, { blur, style: actionStyle }),
2342
2642
  trailingAction
2343
- ] }) : /* @__PURE__ */ jsxs6(Animated7.View, { style: [trailingStyle, translateStyle], children: [
2643
+ ] }) : /* @__PURE__ */ jsxs6(Animated6.View, { style: [trailingStyle, translateStyle], children: [
2344
2644
  /* @__PURE__ */ jsx9(ActionBg, { blur, style: actionStyle }),
2345
2645
  trailingAction
2346
2646
  ] }))
@@ -2364,62 +2664,77 @@ var styles7 = StyleSheet6.create({
2364
2664
 
2365
2665
  // src/ScrollViewProvider.tsx
2366
2666
  import { useBlur } from "@rific/auto-paper";
2367
- import { useCallback as useCallback6, useContext as useContext8, useEffect as useEffect5, useMemo as useMemo5, useState as useState4 } from "react";
2667
+ import { useCallback as useCallback7, useContext as useContext12, useEffect as useEffect5, useMemo as useMemo5, useRef as useRef7, useState as useState5 } from "react";
2368
2668
  import { Dimensions as Dimensions3 } from "react-native";
2369
- import { useSharedValue as useSharedValue3 } from "react-native-reanimated";
2669
+ import { useSharedValue as useSharedValue6 } from "react-native-reanimated";
2370
2670
  import { jsx as jsx10 } from "react/jsx-runtime";
2371
2671
  var ScrollViewProvider = ({ blur, children, fixed = false, footerFixed, headerFixed, snapBack, snapBackFooter, snapBackHeader, tabBarHeight = 60 }) => {
2372
- const { settings } = useContext8(ScrollViewSettingsContext);
2672
+ const { settings } = useContext12(ScrollViewSettingsContext);
2373
2673
  const effectiveBlur = useBlur(blur);
2374
2674
  const effectiveSnapBack = snapBack ?? settings.snapBack;
2375
2675
  const effectiveSnapBackHeader = snapBackHeader ?? settings.snapBackHeader;
2376
2676
  const effectiveSnapBackFooter = snapBackFooter ?? settings.snapBackFooter;
2377
- const [headerHeight, setHeaderHeightState] = useState4(0);
2378
- const [footerHeight, setFooterHeightState] = useState4(0);
2379
- const [progress, setProgress] = useState4(null);
2380
- const [progressing, setProgressing] = useState4(false);
2381
- const scrollPosition = useSharedValue3(0);
2382
- const headerHeightShared = useSharedValue3(0);
2383
- const footerHeightShared = useSharedValue3(0);
2384
- const headerOffset = useSharedValue3(0);
2385
- const footerOffset = useSharedValue3(0);
2386
- const pullSearchHeightShared = useSharedValue3(0);
2387
- const snapBackHeaderShared = useSharedValue3(false);
2388
- const snapBackFooterShared = useSharedValue3(false);
2677
+ const [headerHeight, setHeaderHeightState] = useState5(null);
2678
+ const [footerHeight, setFooterHeightState] = useState5(0);
2679
+ const [progress, setProgress] = useState5(null);
2680
+ const [progressing, setProgressing] = useState5(false);
2681
+ const scrollPosition = useSharedValue6(0);
2682
+ const listGeneration = useSharedValue6(0);
2683
+ const jsListGeneration = useRef7(0);
2684
+ const headerHeightShared = useSharedValue6(0);
2685
+ const footerHeightShared = useSharedValue6(0);
2686
+ const headerOffset = useSharedValue6(0);
2687
+ const footerOffset = useSharedValue6(0);
2688
+ const pullSearchHeightShared = useSharedValue6(0);
2689
+ const snapBackHeaderShared = useSharedValue6(false);
2690
+ const snapBackFooterShared = useSharedValue6(false);
2389
2691
  useEffect5(() => {
2390
2692
  snapBackHeaderShared.value = effectiveSnapBackHeader ?? effectiveSnapBack;
2391
2693
  snapBackFooterShared.value = effectiveSnapBackFooter ?? effectiveSnapBack;
2392
2694
  }, [effectiveSnapBack, effectiveSnapBackFooter, effectiveSnapBackHeader, snapBackFooterShared, snapBackHeaderShared]);
2393
- const setHeaderHeight = useCallback6(
2695
+ const setHeaderHeight = useCallback7(
2394
2696
  (h) => {
2395
2697
  setHeaderHeightState(h);
2396
- headerHeightShared.value = h;
2698
+ headerHeightShared.value = h ?? 0;
2397
2699
  },
2398
2700
  [headerHeightShared]
2399
2701
  );
2400
- const setFooterHeight = useCallback6(
2702
+ const onListUnmount = useCallback7(() => {
2703
+ listGeneration.value += 1;
2704
+ }, [listGeneration]);
2705
+ const onJsListUnmount = useCallback7(() => {
2706
+ jsListGeneration.current += 1;
2707
+ }, []);
2708
+ useEffect5(() => {
2709
+ if (!__DEV__ || headerHeight !== null) return;
2710
+ const timeout = setTimeout(() => {
2711
+ console.warn("[scroll-view] No ScrollViewHeader ever measured inside this ScrollViewProvider \u2014 scroll content stays hidden (opacity: 0) until one does. Render a <ScrollViewHeader> (it can be empty / zero height) inside this provider.");
2712
+ }, 3e3);
2713
+ return () => clearTimeout(timeout);
2714
+ }, [headerHeight]);
2715
+ const setFooterHeight = useCallback7(
2401
2716
  (h) => {
2402
2717
  setFooterHeightState(h);
2403
2718
  footerHeightShared.value = h;
2404
2719
  },
2405
2720
  [footerHeightShared]
2406
2721
  );
2407
- const scrollHeight = useMemo5(() => Dimensions3.get("window").height - headerHeight - footerHeight, [headerHeight, footerHeight]);
2722
+ const scrollHeight = useMemo5(() => Dimensions3.get("window").height - (headerHeight ?? 0) - footerHeight, [headerHeight, footerHeight]);
2408
2723
  const effectiveHeaderFixed = fixed || (headerFixed ?? settings.headerFixed);
2409
2724
  const effectiveFooterFixed = fixed || (footerFixed ?? settings.footerFixed);
2410
- const value = useMemo5(() => ({ blur: effectiveBlur, footerHeight, footerHeightShared, footerFixed: effectiveFooterFixed, footerOffset, headerHeight, headerHeightShared, headerFixed: effectiveHeaderFixed, headerOffset, progress, pullSearchHeightShared, progressing, scrollHeight, scrollPosition, setFooterHeight, setHeaderHeight, setProgress, setProgressing, snapBackFooterShared, snapBackHeaderShared, tabBarHeight }), [effectiveBlur, effectiveFooterFixed, effectiveHeaderFixed, footerHeight, footerHeightShared, footerOffset, headerHeight, headerHeightShared, headerOffset, progress, pullSearchHeightShared, progressing, scrollHeight, scrollPosition, setFooterHeight, setHeaderHeight, snapBackFooterShared, snapBackHeaderShared, tabBarHeight]);
2725
+ const value = useMemo5(() => ({ blur: effectiveBlur, footerHeight, footerHeightShared, footerFixed: effectiveFooterFixed, footerOffset, headerHeight, headerHeightShared, headerFixed: effectiveHeaderFixed, headerOffset, jsListGeneration, listGeneration, onJsListUnmount, onListUnmount, progress, pullSearchHeightShared, progressing, scrollHeight, scrollPosition, setFooterHeight, setHeaderHeight, setProgress, setProgressing, snapBackFooterShared, snapBackHeaderShared, tabBarHeight }), [effectiveBlur, effectiveFooterFixed, effectiveHeaderFixed, footerHeight, footerHeightShared, footerOffset, headerHeight, headerHeightShared, headerOffset, jsListGeneration, listGeneration, onJsListUnmount, onListUnmount, progress, pullSearchHeightShared, progressing, scrollHeight, scrollPosition, setFooterHeight, setHeaderHeight, snapBackFooterShared, snapBackHeaderShared, tabBarHeight]);
2411
2726
  return /* @__PURE__ */ jsx10(ScrollViewContext.Provider, { value, children });
2412
2727
  };
2413
2728
 
2414
2729
  // src/ScrollViewSettingsProvider.tsx
2415
- import { useCallback as useCallback7, useMemo as useMemo6, useState as useState5 } from "react";
2730
+ import { useCallback as useCallback8, useMemo as useMemo6, useState as useState6 } from "react";
2416
2731
  import { jsx as jsx11 } from "react/jsx-runtime";
2417
2732
  var ScrollViewSettingsProvider = ({ children, onChange, initialValue }) => {
2418
- const [settings, setSettings] = useState5(() => ({
2733
+ const [settings, setSettings] = useState6(() => ({
2419
2734
  ...defaultScrollViewSettings,
2420
2735
  ...initialValue
2421
2736
  }));
2422
- const set2 = useCallback7(
2737
+ const set2 = useCallback8(
2423
2738
  (patch) => {
2424
2739
  setSettings((prev) => {
2425
2740
  const next = { ...prev, ...patch };
@@ -2434,27 +2749,27 @@ var ScrollViewSettingsProvider = ({ children, onChange, initialValue }) => {
2434
2749
  };
2435
2750
 
2436
2751
  // src/SectionList.tsx
2437
- import { memo as memo4, useCallback as useCallback9, useEffect as useEffect6, useLayoutEffect as useLayoutEffect3, useMemo as useMemo7, useRef as useRef7 } from "react";
2752
+ import { memo as memo4, useCallback as useCallback10, useContext as useContext14, useEffect as useEffect6, useLayoutEffect as useLayoutEffect6, useMemo as useMemo7, useRef as useRef9 } from "react";
2438
2753
  import { Dimensions as Dimensions4, SectionList as RNSectionList, View as View7 } from "react-native";
2439
2754
  import { Gesture as Gesture4, GestureDetector as GestureDetector4 } from "react-native-gesture-handler";
2440
- import Animated8, { createAnimatedComponent } from "react-native-reanimated";
2755
+ import Animated7, { runOnUI as runOnUI3, useSharedValue as useSharedValue8 } from "react-native-reanimated";
2441
2756
 
2442
2757
  // src/internal/useStickyHeaders.ts
2443
- import { useCallback as useCallback8, useContext as useContext9, useLayoutEffect as useLayoutEffect2, useRef as useRef6, useState as useState6 } from "react";
2444
- import { runOnJS as runOnJS4, useAnimatedReaction as useAnimatedReaction2, useAnimatedStyle as useAnimatedStyle6, useSharedValue as useSharedValue4 } from "react-native-reanimated";
2758
+ import { useCallback as useCallback9, useContext as useContext13, useLayoutEffect as useLayoutEffect5, useRef as useRef8, useState as useState7 } from "react";
2759
+ import { runOnJS as runOnJS4, useAnimatedReaction as useAnimatedReaction2, useAnimatedStyle as useAnimatedStyle6, useSharedValue as useSharedValue7 } from "react-native-reanimated";
2445
2760
  import { useSafeAreaInsets as useSafeAreaInsets5 } from "react-native-safe-area-context";
2446
2761
  function useStickyHeaders(headerFixed, enabled) {
2447
- const { headerHeightShared, headerOffset, pullSearchHeightShared, scrollPosition, snapBackHeaderShared } = useContext9(ScrollViewContext);
2762
+ const { headerHeightShared, headerOffset, pullSearchHeightShared, scrollPosition, snapBackHeaderShared } = useContext13(ScrollViewContext);
2448
2763
  const insetsTop = useSafeAreaInsets5().top;
2449
- const sectionYPositions = useSharedValue4([]);
2450
- const sectionHeights = useSharedValue4([]);
2451
- const [activeIndex, setActiveIndex] = useState6(-1);
2452
- const contentIndexShared = useSharedValue4(-1);
2453
- useLayoutEffect2(() => {
2764
+ const sectionYPositions = useSharedValue7([]);
2765
+ const sectionHeights = useSharedValue7([]);
2766
+ const [activeIndex, setActiveIndex] = useState7(-1);
2767
+ const contentIndexShared = useSharedValue7(-1);
2768
+ useLayoutEffect5(() => {
2454
2769
  contentIndexShared.value = activeIndex;
2455
2770
  }, [activeIndex, contentIndexShared]);
2456
- const positionsCache = useRef6([]);
2457
- const heightsCache = useRef6([]);
2771
+ const positionsCache = useRef8([]);
2772
+ const heightsCache = useRef8([]);
2458
2773
  useAnimatedReaction2(
2459
2774
  () => {
2460
2775
  if (!enabled) return -1;
@@ -2526,7 +2841,7 @@ function useStickyHeaders(headerFixed, enabled) {
2526
2841
  }
2527
2842
  return { opacity: 1, top: 0, transform: [{ translateY }] };
2528
2843
  }, [enabled, headerFixed, insetsTop]);
2529
- const measureHeader = useCallback8(
2844
+ const measureHeader = useCallback9(
2530
2845
  (index, pageY, height) => {
2531
2846
  const contentY = pageY + scrollPosition.value;
2532
2847
  positionsCache.current[index] = contentY;
@@ -2536,7 +2851,7 @@ function useStickyHeaders(headerFixed, enabled) {
2536
2851
  },
2537
2852
  [scrollPosition, sectionHeights, sectionYPositions]
2538
2853
  );
2539
- const resetPositions = useCallback8(
2854
+ const resetPositions = useCallback9(
2540
2855
  (count) => {
2541
2856
  positionsCache.current = Array.from({ length: count }, () => -1);
2542
2857
  sectionYPositions.value = positionsCache.current.slice();
@@ -2549,18 +2864,27 @@ function useStickyHeaders(headerFixed, enabled) {
2549
2864
  }
2550
2865
 
2551
2866
  // src/SectionList.tsx
2552
- import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
2553
- var AnimatedSectionList = createAnimatedComponent(RNSectionList);
2867
+ import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
2868
+ var AnimatedSectionList = Animated7.createAnimatedComponent(RNSectionList);
2554
2869
  var SectionListInner = ({ chipProps, chipThreshold, contentContainerStyle: externalContentContainerStyle, initialNumToRender = 20, maxToRenderPerBatch = 50, renderFilters, footerFixed: footerFixedProp, gesture, headerFixed: headerFixedProp, keyboardAware, ListHeaderComponent: externalListHeaderComponent, onChipPress, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onContentSizeChange, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight, refreshing, removeClippedSubviews = false, sections, showsVerticalScrollIndicator, style, ref: externalRef, windowSize = 100, ...props }) => {
2555
- const scrollView = useRef7(null);
2870
+ const scrollView = useRef9(null);
2556
2871
  const { stickySectionHeadersEnabled = true, renderSectionHeader, ...passThroughProps } = props;
2557
2872
  const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed, headerHeight } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, keyboardAware, pullSearchHeight, style });
2873
+ const { listGeneration, onListUnmount } = useContext14(ScrollViewContext);
2874
+ const capturedGeneration = useSharedValue8(-1);
2875
+ useLayoutEffect6(() => {
2876
+ const generation = listGeneration.value;
2877
+ runOnUI3((gen) => {
2878
+ "worklet";
2879
+ capturedGeneration.value = gen;
2880
+ })(generation);
2881
+ }, [capturedGeneration, listGeneration]);
2558
2882
  const usesCustomSticky = stickySectionHeadersEnabled && renderSectionHeader != null;
2559
2883
  const { activeIndex, clipStyle, measureHeader, overlayStyle, resetPositions } = useStickyHeaders(headerFixed, usesCustomSticky);
2560
- useLayoutEffect3(() => {
2884
+ useLayoutEffect6(() => {
2561
2885
  if (usesCustomSticky) resetPositions(sections.length);
2562
2886
  }, [sections, usesCustomSticky, resetPositions, headerHeight]);
2563
- const sectionViewRefs = useRef7(/* @__PURE__ */ new Map());
2887
+ const sectionViewRefs = useRef9(/* @__PURE__ */ new Map());
2564
2888
  const wrappedRenderSectionHeader = useMemo7(() => {
2565
2889
  if (!usesCustomSticky) return renderSectionHeader ?? void 0;
2566
2890
  return (info) => {
@@ -2582,7 +2906,8 @@ var SectionListInner = ({ chipProps, chipThreshold, contentContainerStyle: exter
2582
2906
  );
2583
2907
  };
2584
2908
  }, [usesCustomSticky, renderSectionHeader, sections, measureHeader]);
2585
- const scrollTo = useCallback9((offset, animated) => {
2909
+ useLayoutEffect6(() => () => onListUnmount(), [onListUnmount]);
2910
+ const scrollTo = useCallback10((offset, animated) => {
2586
2911
  scrollView.current?.getScrollResponder()?.scrollTo({ y: offset, animated });
2587
2912
  }, []);
2588
2913
  const {
@@ -2603,12 +2928,12 @@ var SectionListInner = ({ chipProps, chipThreshold, contentContainerStyle: exter
2603
2928
  const activeListHeader = useMemo7(() => {
2604
2929
  if (!renderFilters) return pullSearchHeader;
2605
2930
  const Inner = pullSearchHeader;
2606
- return () => /* @__PURE__ */ jsxs7(Fragment4, { children: [
2931
+ return () => /* @__PURE__ */ jsxs7(Fragment5, { children: [
2607
2932
  Inner ? typeof Inner === "function" ? /* @__PURE__ */ jsx12(Inner, {}) : Inner : null,
2608
2933
  renderFilters
2609
2934
  ] });
2610
2935
  }, [pullSearchHeader, renderFilters]);
2611
- const pullSearchSettled = useRef7(false);
2936
+ const pullSearchSettled = useRef9(false);
2612
2937
  useEffect6(() => {
2613
2938
  if (!usesCustomSticky || activeListHeader == null || pullSearchSettled.current) return;
2614
2939
  pullSearchSettled.current = true;
@@ -2622,16 +2947,12 @@ var SectionListInner = ({ chipProps, chipThreshold, contentContainerStyle: exter
2622
2947
  return () => cancelAnimationFrame(handle);
2623
2948
  }, [activeListHeader, usesCustomSticky, measureHeader]);
2624
2949
  const contentContainerStyle = useMemo7(() => [{ minHeight: Dimensions4.get("window").height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle], [contentInset.bottom, contentInset.top, externalContentContainerStyle, pullSearchMinHeight]);
2625
- const onPullSearchZoneEnter = useCallback9(() => {
2626
- if (!pullSearchHeight) return;
2627
- scrollTo(-contentInset.top + pullSearchHeight, true);
2628
- }, [contentInset.top, pullSearchHeight, scrollTo]);
2629
- const handleScroll = useScrollHandler({ chipHidden, chipThreshold, footerFixed, headerFixed, onPullSearchZoneEnter: pullSearchHeight ? onPullSearchZoneEnter : void 0 });
2630
- const handleScrollToTop = useCallback9(() => {
2950
+ const handleScroll = useScrollHandler({ capturedGeneration, chipHidden, chipThreshold, footerFixed, headerFixed, listGeneration });
2951
+ const handleScrollToTop = useCallback10(() => {
2631
2952
  const offset = pullSearchHeight ? -contentInset.top + pullSearchHeight : -contentInset.top;
2632
2953
  scrollView.current?.getScrollResponder()?.scrollTo({ y: offset, animated: true });
2633
2954
  }, [contentInset.top, pullSearchHeight]);
2634
- const handleContentSizeChange = useCallback9(
2955
+ const handleContentSizeChange = useCallback10(
2635
2956
  (w, h) => {
2636
2957
  if (onContentSizeChange) onContentSizeChange(w, h);
2637
2958
  },
@@ -2670,11 +2991,11 @@ var SectionListInner = ({ chipProps, chipThreshold, contentContainerStyle: exter
2670
2991
  stickySectionHeadersEnabled: false,
2671
2992
  windowSize
2672
2993
  },
2673
- headerHeight > 0 ? 1 : 0
2994
+ headerHeight !== null && headerHeight > 0 ? 1 : 0
2674
2995
  ),
2675
2996
  hiddenHeader && /* @__PURE__ */ jsx12(View7, { pointerEvents: "none", style: styles8.measureContainer, children: hiddenHeader }),
2676
2997
  /* @__PURE__ */ jsx12(ScrollViewChip, { chipProps, onChipPress, onPress: handleScrollToTop, style: chipStyle }),
2677
- usesCustomSticky && /* @__PURE__ */ jsx12(Animated8.View, { pointerEvents: "none", style: [styles8.stickyClip, clipStyle], children: activeIndex >= 0 && activeIndex < sections.length && /* @__PURE__ */ jsx12(Animated8.View, { style: [styles8.stickyOverlay, overlayStyle], children: renderSectionHeader({ section: sections[activeIndex] }) }) })
2998
+ usesCustomSticky && /* @__PURE__ */ jsx12(Animated7.View, { pointerEvents: "none", style: [styles8.stickyClip, clipStyle], children: activeIndex >= 0 && activeIndex < sections.length && /* @__PURE__ */ jsx12(Animated7.View, { style: [styles8.stickyOverlay, overlayStyle], children: renderSectionHeader({ section: sections[activeIndex] }) }) })
2678
2999
  ] });
2679
3000
  return detectorGesture ? /* @__PURE__ */ jsx12(GestureDetector4, { gesture: detectorGesture, children: content }) : content;
2680
3001
  };
@@ -2686,15 +3007,15 @@ var styles8 = {
2686
3007
  var SectionList = memo4(SectionListInner);
2687
3008
 
2688
3009
  // src/useScrollView.ts
2689
- import { useContext as useContext10 } from "react";
3010
+ import { useContext as useContext15 } from "react";
2690
3011
  var useScrollView = () => {
2691
- const { progress, progressing, scrollHeight, scrollPosition, setProgress, setProgressing } = useContext10(ScrollViewContext);
3012
+ const { progress, progressing, scrollHeight, scrollPosition, setProgress, setProgressing } = useContext15(ScrollViewContext);
2692
3013
  return { progress, progressing, scrollHeight, scrollPosition, setProgress, setProgressing };
2693
3014
  };
2694
3015
 
2695
3016
  // src/useScrollViewSettings.ts
2696
- import { useContext as useContext11 } from "react";
2697
- var useScrollViewSettings = () => useContext11(ScrollViewSettingsContext);
3017
+ import { useContext as useContext16 } from "react";
3018
+ var useScrollViewSettings = () => useContext16(ScrollViewSettingsContext);
2698
3019
  export {
2699
3020
  CustomList,
2700
3021
  FlatList,