@rific/scroll-view 0.4.1 → 0.4.2

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.d.mts CHANGED
@@ -116,7 +116,7 @@ type ScrollViewProps = ScrollViewProps$1 & {
116
116
  ref?: RefObject<ScrollView$1 | null>;
117
117
  refreshing?: boolean;
118
118
  };
119
- declare const ScrollViewInner: ({ chipProps, chipThreshold, children, footerFixed: footerFixedProp, gesture, headerFixed: headerFixedProp, horizontal, keyboardAware, onChipPress, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight, refreshing, style, ...props }: ScrollViewProps) => react.JSX.Element;
119
+ declare const ScrollViewInner: ({ chipProps, chipThreshold, children, contentContainerStyle: externalContentContainerStyle, footerFixed: footerFixedProp, gesture, headerFixed: headerFixedProp, horizontal, keyboardAware, onChipPress, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight, refreshing, style, ...props }: ScrollViewProps) => react.JSX.Element;
120
120
  declare const ScrollView: typeof ScrollViewInner;
121
121
 
122
122
  type ScrollViewContextType = {
package/dist/index.d.ts CHANGED
@@ -116,7 +116,7 @@ type ScrollViewProps = ScrollViewProps$1 & {
116
116
  ref?: RefObject<ScrollView$1 | null>;
117
117
  refreshing?: boolean;
118
118
  };
119
- declare const ScrollViewInner: ({ chipProps, chipThreshold, children, footerFixed: footerFixedProp, gesture, headerFixed: headerFixedProp, horizontal, keyboardAware, onChipPress, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight, refreshing, style, ...props }: ScrollViewProps) => react.JSX.Element;
119
+ declare const ScrollViewInner: ({ chipProps, chipThreshold, children, contentContainerStyle: externalContentContainerStyle, footerFixed: footerFixedProp, gesture, headerFixed: headerFixedProp, horizontal, keyboardAware, onChipPress, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight, refreshing, style, ...props }: ScrollViewProps) => react.JSX.Element;
120
120
  declare const ScrollView: typeof ScrollViewInner;
121
121
 
122
122
  type ScrollViewContextType = {
package/dist/index.js CHANGED
@@ -52,7 +52,7 @@ module.exports = __toCommonJS(index_exports);
52
52
 
53
53
  // src/CustomList.tsx
54
54
  var import_react6 = require("react");
55
- var import_react_native4 = require("react-native");
55
+ var import_react_native6 = require("react-native");
56
56
  var import_react_native_gesture_handler = require("react-native-gesture-handler");
57
57
  var import_react_native_reanimated6 = require("react-native-reanimated");
58
58
 
@@ -122,6 +122,12 @@ var ScrollViewContext = (0, import_react.createContext)({
122
122
  // src/useScrollInit.ts
123
123
  var import_react2 = require("react");
124
124
  var import_react_native_reanimated2 = require("react-native-reanimated");
125
+
126
+ // src/internal/insetMode.ts
127
+ var import_react_native3 = require("react-native");
128
+ var usesContentInset = import_react_native3.Platform.OS === "ios";
129
+
130
+ // src/useScrollInit.ts
125
131
  var REMOUNT_SYNC_MAX_ATTEMPTS = 8;
126
132
  var REMOUNT_SYNC_TOLERANCE = 1;
127
133
  var REMOUNT_SYNC_FALLBACK_MS = 500;
@@ -130,8 +136,9 @@ var SNAP_VERIFY_TOLERANCE = 1;
130
136
  var SNAP_VERIFY_MAX_ATTEMPTS = 5;
131
137
  var SNAP_VERIFY_INITIAL_DELAY_MS = 400;
132
138
  var SNAP_VERIFY_RETRY_DELAY_MS = 150;
133
- function useScrollInit({ listHeaderComponent, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight, scrollTo }) {
139
+ function useScrollInit({ listHeaderComponent, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight: pullSearchHeightProp, scrollTo }) {
134
140
  const { headerHeight, scrollPosition, setProgressing } = (0, import_react2.useContext)(ScrollViewContext);
141
+ const pullSearchHeight = usesContentInset ? pullSearchHeightProp : void 0;
135
142
  const hasPullSearch = pullSearchHeight !== void 0;
136
143
  const [phase, setPhase] = (0, import_react2.useState)(hasPullSearch && !headerHeight ? "measuring" : "ready");
137
144
  const [refreshing, setRefreshing] = (0, import_react2.useState)(false);
@@ -173,7 +180,7 @@ function useScrollInit({ listHeaderComponent, onMomentumScrollEnd: externalMomen
173
180
  },
174
181
  []
175
182
  );
176
- const [remountTarget] = (0, import_react2.useState)(() => headerHeight !== null ? -headerHeight + (pullSearchHeight ?? 0) : null);
183
+ const [remountTarget] = (0, import_react2.useState)(() => usesContentInset && headerHeight !== null ? -headerHeight + (pullSearchHeight ?? 0) : null);
177
184
  const remountSyncTarget = (0, import_react_native_reanimated2.useSharedValue)(remountTarget);
178
185
  const [scrollReady, setScrollReady] = (0, import_react2.useState)(remountTarget === null);
179
186
  const remountAttempts = (0, import_react2.useRef)(0);
@@ -218,7 +225,7 @@ function useScrollInit({ listHeaderComponent, onMomentumScrollEnd: externalMomen
218
225
  headerInitialized.current = true;
219
226
  const target = remountTarget ?? -headerHeight;
220
227
  scrollPosition.value = target;
221
- scrollTo(target, false);
228
+ scrollTo(usesContentInset ? target : 0, false);
222
229
  if (remountTarget === null) return;
223
230
  let framesLeft = REMOUNT_PRIME_FRAMES;
224
231
  let handle = null;
@@ -325,9 +332,10 @@ function useScrollHandler({ capturedGeneration, chipHidden, chipThreshold = 100,
325
332
  chipHidden.value = x < chipThreshold ? 1 : 0;
326
333
  return;
327
334
  }
328
- const delta = y - scrollPosition.value;
329
- scrollPosition.value = y;
330
- chipHidden.value = y < chipThreshold ? 1 : 0;
335
+ const yn = usesContentInset ? y : y - headerHeightShared.value;
336
+ const delta = yn - scrollPosition.value;
337
+ scrollPosition.value = yn;
338
+ chipHidden.value = yn < chipThreshold ? 1 : 0;
331
339
  const maxScroll = contentHeight - layoutHeight;
332
340
  if (maxScroll > 0 && y >= maxScroll) {
333
341
  fromBottomBounce.value = true;
@@ -338,18 +346,18 @@ function useScrollHandler({ capturedGeneration, chipHidden, chipThreshold = 100,
338
346
  const snapHeader = snapBackHeaderShared.value && !headerFixed;
339
347
  const snapFooter = snapBackFooterShared.value && !footerFixed;
340
348
  if (snapHeader || snapFooter) {
341
- if (y <= -headerHeightShared.value) {
349
+ if (yn <= -headerHeightShared.value) {
342
350
  snapUpAccum.value = 0;
343
351
  if (snapHeader) headerOffset.value = 0;
344
352
  if (snapFooter) footerOffset.value = 0;
345
353
  } else if (delta > 0) {
346
354
  snapUpAccum.value = 0;
347
- if (y >= -headerHeightShared.value + pullSearchHeightShared.value) {
355
+ if (yn >= -headerHeightShared.value + pullSearchHeightShared.value) {
348
356
  if (snapHeader) headerOffset.value = Math.max(-headerHeightShared.value, Math.min(0, headerOffset.value - delta));
349
357
  if (snapFooter) footerOffset.value = Math.max(0, Math.min(footerHeightShared.value, footerOffset.value + delta));
350
358
  }
351
359
  } else if (delta < 0 && !fromBottomBounce.value) {
352
- if (y >= -headerHeightShared.value + pullSearchHeightShared.value) snapUpAccum.value -= delta;
360
+ if (yn >= -headerHeightShared.value + pullSearchHeightShared.value) snapUpAccum.value -= delta;
353
361
  if (snapUpAccum.value >= 10) {
354
362
  snapUpAccum.value = 999;
355
363
  if (snapHeader) headerOffset.value = (0, import_react_native_reanimated3.withTiming)(0, { duration: 200 });
@@ -383,15 +391,16 @@ function useScrollHandler({ capturedGeneration, chipHidden, chipThreshold = 100,
383
391
 
384
392
  // src/internal/useScrollList.ts
385
393
  var import_react5 = require("react");
386
- var import_react_native3 = require("react-native");
394
+ var import_react_native5 = require("react-native");
387
395
  var import_react_native_reanimated5 = require("react-native-reanimated");
388
396
  var import_react_native_safe_area_context = require("react-native-safe-area-context");
389
397
 
390
398
  // src/useKeyboardInset.ts
391
399
  var import_react4 = require("react");
400
+ var import_react_native4 = require("react-native");
392
401
  var import_react_native_keyboard_controller = require("react-native-keyboard-controller");
393
402
  var import_react_native_reanimated4 = require("react-native-reanimated");
394
- var useKeyboardInset = () => {
403
+ var useKeyboardInsetNative = () => {
395
404
  const [keyboardHeight, setKeyboardHeight] = (0, import_react4.useState)(0);
396
405
  (0, import_react_native_keyboard_controller.useKeyboardHandler)(
397
406
  {
@@ -408,8 +417,12 @@ var useKeyboardInset = () => {
408
417
  );
409
418
  return keyboardHeight;
410
419
  };
420
+ var useKeyboardInsetWeb = () => 0;
421
+ var useKeyboardInset = import_react_native4.Platform.OS === "web" ? useKeyboardInsetWeb : useKeyboardInsetNative;
411
422
 
412
423
  // src/internal/useScrollList.ts
424
+ var ZERO_INSET = { bottom: 0, top: 0 };
425
+ var ZERO_OFFSET = { x: 0, y: 0 };
413
426
  var CHIP_SLIDE = 48;
414
427
  function useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, isHorizontal, keyboardAware, pullSearchHeight, style } = {}) {
415
428
  const insets = (0, import_react_native_safe_area_context.useSafeAreaInsets)();
@@ -418,12 +431,15 @@ function useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedP
418
431
  const headerFixed = isHorizontal ? true : headerFixedProp ?? contextHeaderFixed;
419
432
  const footerFixed = isHorizontal ? true : footerFixedProp ?? contextFooterFixed;
420
433
  (0, import_react5.useEffect)(() => {
421
- pullSearchHeightShared.value = pullSearchHeight ?? 0;
434
+ pullSearchHeightShared.value = usesContentInset ? pullSearchHeight ?? 0 : 0;
422
435
  }, [pullSearchHeight, pullSearchHeightShared]);
423
- const containerStyle = (0, import_react5.useMemo)(() => headerHeight === null ? [{ flex: 1, opacity: 0 }, style] : [import_react_native3.StyleSheet.absoluteFill, style], [headerHeight, style]);
424
- const contentInset = (0, import_react5.useMemo)(() => ({ bottom: (footerFixed ? footerHeight || insets.bottom : insets.bottom) + (keyboardAware ? keyboardHeight : 0), top: headerHeight ?? 0 }), [footerFixed, footerHeight, insets.bottom, headerHeight, keyboardAware, keyboardHeight]);
436
+ const containerStyle = (0, import_react5.useMemo)(() => headerHeight === null ? [{ flex: 1, opacity: 0 }, style] : [import_react_native5.StyleSheet.absoluteFill, style], [headerHeight, style]);
437
+ const insetGeometry = (0, import_react5.useMemo)(() => ({ bottom: (footerFixed ? footerHeight || insets.bottom : insets.bottom) + (keyboardAware ? keyboardHeight : 0), top: headerHeight ?? 0 }), [footerFixed, footerHeight, insets.bottom, headerHeight, keyboardAware, keyboardHeight]);
438
+ const contentInset = usesContentInset ? insetGeometry : ZERO_INSET;
439
+ const contentPadding = (0, import_react5.useMemo)(() => usesContentInset || isHorizontal ? null : { paddingBottom: insetGeometry.bottom, paddingTop: insetGeometry.top }, [insetGeometry, isHorizontal]);
425
440
  const [startedWithHeader] = (0, import_react5.useState)(headerHeight !== null);
426
- const contentOffset = (0, import_react5.useMemo)(() => ({ x: 0, y: headerHeight !== null ? -headerHeight + (startedWithHeader && !isHorizontal && pullSearchHeight ? pullSearchHeight : 0) : 0 }), [headerHeight, isHorizontal, pullSearchHeight, startedWithHeader]);
441
+ const insetContentOffset = (0, import_react5.useMemo)(() => ({ x: 0, y: headerHeight !== null ? -headerHeight + (startedWithHeader && !isHorizontal && pullSearchHeight ? pullSearchHeight : 0) : 0 }), [headerHeight, isHorizontal, pullSearchHeight, startedWithHeader]);
442
+ const contentOffset = usesContentInset ? insetContentOffset : ZERO_OFFSET;
427
443
  const chipHidden = (0, import_react_native_reanimated5.useSharedValue)(1);
428
444
  const chipStyle = (0, import_react_native_reanimated5.useAnimatedStyle)(() => {
429
445
  const pointerEvents = chipHidden.value ? "none" : "box-none";
@@ -445,14 +461,14 @@ function useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedP
445
461
  transform: [{ translateY: chipHidden.value ? (0, import_react_native_reanimated5.withTiming)(-CHIP_SLIDE) : (0, import_react_native_reanimated5.withTiming)(0) }]
446
462
  };
447
463
  }, [isHorizontal, headerFixed, headerHeight, insets.top]);
448
- return { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed, headerHeight, insets, isHorizontal: isHorizontal ?? false };
464
+ return { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, contentPadding, footerFixed, headerFixed, headerHeight, insets, isHorizontal: isHorizontal ?? false };
449
465
  }
450
466
 
451
467
  // src/CustomList.tsx
452
468
  var import_jsx_runtime3 = require("react/jsx-runtime");
453
469
  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 }) => {
454
470
  const { ListHeaderComponent: listHeaderComponent, contentContainerStyle: externalContentContainerStyle, ...restProps } = props;
455
- const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, keyboardAware, pullSearchHeight, style });
471
+ const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, contentPadding, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, keyboardAware, pullSearchHeight, style });
456
472
  const { listGeneration, onListUnmount } = (0, import_react6.useContext)(ScrollViewContext);
457
473
  const capturedGeneration = (0, import_react_native_reanimated6.useSharedValue)(-1);
458
474
  (0, import_react6.useLayoutEffect)(() => {
@@ -501,7 +517,7 @@ var CustomListInner = ({ chipProps, chipThreshold, component: List, renderFilter
501
517
  renderFilters
502
518
  ] });
503
519
  }, [pullSearchHeader, renderFilters]);
504
- const contentContainerStyle = (0, import_react6.useMemo)(() => [{ minHeight: import_react_native4.Dimensions.get("window").height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle], [contentInset.bottom, contentInset.top, externalContentContainerStyle, pullSearchMinHeight]);
520
+ const contentContainerStyle = (0, import_react6.useMemo)(() => [{ minHeight: import_react_native6.Dimensions.get("window").height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle, contentPadding], [contentInset.bottom, contentInset.top, contentPadding, externalContentContainerStyle, pullSearchMinHeight]);
505
521
  const onScroll = useScrollHandler({ capturedGeneration, chipHidden, chipThreshold, footerFixed, headerFixed, listGeneration, onRemountSyncRetry, onRemountSynced, remountSyncTarget });
506
522
  const handleScrollToTop = (0, import_react6.useCallback)(() => {
507
523
  const offset = pullSearchHeight ? -contentInset.top + pullSearchHeight : -contentInset.top;
@@ -512,9 +528,9 @@ var CustomListInner = ({ chipProps, chipThreshold, component: List, renderFilter
512
528
  const combinedGesture = (0, import_react6.useMemo)(() => gesture !== void 0 ? import_react_native_gesture_handler.Gesture.Simultaneous(gesture, nativeGesture) : nativeGesture, [gesture, nativeGesture]);
513
529
  const detectorGesture = gesture !== void 0 ? combinedGesture : void 0;
514
530
  const TypedList = List;
515
- const content = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: containerStyle, children: [
531
+ const content = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native6.View, { style: containerStyle, children: [
516
532
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TypedList, { ...restProps, contentContainerStyle, contentInset, contentOffset, ListHeaderComponent: activeListHeader, onMomentumScrollEnd: handleMomentumScrollEnd, onScroll, onScrollBeginDrag: handleScrollBeginDrag, onScrollEndDrag: handleScrollEndDrag, ref: handleRef, refreshControl, scrollEventThrottle: 16 }),
517
- hiddenHeader && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.View, { pointerEvents: "none", style: styles2.measureContainer, children: hiddenHeader }),
533
+ hiddenHeader && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native6.View, { pointerEvents: "none", style: styles2.measureContainer, children: hiddenHeader }),
518
534
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ScrollViewChip, { chipProps, onChipPress, onPress: handleScrollToTop, style: chipStyle })
519
535
  ] });
520
536
  return detectorGesture ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_gesture_handler.GestureDetector, { gesture: detectorGesture, children: content }) : content;
@@ -524,13 +540,13 @@ var CustomList = (0, import_react6.memo)(CustomListInner);
524
540
 
525
541
  // src/FlatList.tsx
526
542
  var import_react9 = require("react");
527
- var import_react_native6 = require("react-native");
543
+ var import_react_native8 = require("react-native");
528
544
  var import_react_native_gesture_handler2 = require("react-native-gesture-handler");
529
545
 
530
546
  // src/internal/HorizontalDots.tsx
531
547
  var import_auto_paper2 = require("@rific/auto-paper");
532
548
  var import_react7 = require("react");
533
- var import_react_native5 = require("react-native");
549
+ var import_react_native7 = require("react-native");
534
550
  var import_react_native_paper = require("react-native-paper");
535
551
  var import_react_native_reanimated7 = __toESM(require("react-native-reanimated"));
536
552
  var import_react_native_safe_area_context2 = require("react-native-safe-area-context");
@@ -555,11 +571,11 @@ var Dot = ({ activeColor, index, inactiveColor, pageWidth, scrollPosition }) =>
555
571
  const backgroundColor = (0, import_react_native_reanimated7.interpolateColor)(t, [0, 1], [activeColor, inactiveColor]);
556
572
  return { backgroundColor, borderRadius: size / 2, height: size, width: size };
557
573
  });
558
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native5.View, { style: styles3.dotContainer, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_reanimated7.default.View, { style: circleStyle }) });
574
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native7.View, { style: styles3.dotContainer, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_reanimated7.default.View, { style: circleStyle }) });
559
575
  };
560
576
  var HorizontalDots = ({ total }) => {
561
577
  const { blur, footerHeightShared, scrollPosition } = (0, import_react7.useContext)(ScrollViewContext);
562
- const { width } = (0, import_react_native5.useWindowDimensions)();
578
+ const { width } = (0, import_react_native7.useWindowDimensions)();
563
579
  const insets = (0, import_react_native_safe_area_context2.useSafeAreaInsets)();
564
580
  const theme = (0, import_react_native_paper.useTheme)();
565
581
  const insetsBottom = insets.bottom;
@@ -574,12 +590,12 @@ var HorizontalDots = ({ total }) => {
574
590
  const offset = Math.max(0, Math.min((page - (CLIP_COUNT - 1) / 2) * DOT_STEP, maxOffset));
575
591
  return { transform: [{ translateX: -offset }] };
576
592
  });
577
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_reanimated7.default.View, { pointerEvents: "none", style: [styles3.pillContainer, pillContainerStyle], children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native5.View, { style: styles3.pill, children: [
578
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_auto_paper2.BlurView, { blur, style: import_react_native5.StyleSheet.absoluteFill }),
579
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native5.View, { style: styles3.dotsClip, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_reanimated7.default.View, { style: [styles3.dotsRow, rowStyle], children: Array.from({ length: total }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dot, { activeColor, index: i, inactiveColor, pageWidth: width, scrollPosition }, i)) }) })
593
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_reanimated7.default.View, { pointerEvents: "none", style: [styles3.pillContainer, pillContainerStyle], children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native7.View, { style: styles3.pill, children: [
594
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_auto_paper2.BlurView, { blur, style: import_react_native7.StyleSheet.absoluteFill }),
595
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native7.View, { style: styles3.dotsClip, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_reanimated7.default.View, { style: [styles3.dotsRow, rowStyle], children: Array.from({ length: total }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Dot, { activeColor, index: i, inactiveColor, pageWidth: width, scrollPosition }, i)) }) })
580
596
  ] }) });
581
597
  };
582
- var styles3 = import_react_native5.StyleSheet.create({
598
+ var styles3 = import_react_native7.StyleSheet.create({
583
599
  dotContainer: { alignItems: "center", height: DOT_FULL, justifyContent: "center", width: DOT_FULL },
584
600
  dotsClip: { overflow: "hidden", width: CLIP_WIDTH },
585
601
  dotsRow: { flexDirection: "row", gap: DOT_GAP },
@@ -632,9 +648,10 @@ function useScrollHandlerJS({ capturedGeneration, chipHidden, chipThreshold = 10
632
648
  chipHidden.value = x < chipThreshold ? 1 : 0;
633
649
  return;
634
650
  }
635
- const delta = y - scrollPosition.value;
636
- scrollPosition.value = y;
637
- chipHidden.value = y < chipThreshold ? 1 : 0;
651
+ const yn = usesContentInset ? y : y - headerHeightShared.value;
652
+ const delta = yn - scrollPosition.value;
653
+ scrollPosition.value = yn;
654
+ chipHidden.value = yn < chipThreshold ? 1 : 0;
638
655
  const maxScroll = contentHeight - layoutHeight;
639
656
  if (maxScroll > 0 && y >= maxScroll) {
640
657
  fromBottomBounce.current = true;
@@ -645,18 +662,18 @@ function useScrollHandlerJS({ capturedGeneration, chipHidden, chipThreshold = 10
645
662
  const snapHeader = snapBackHeaderShared.value && !headerFixed;
646
663
  const snapFooter = snapBackFooterShared.value && !footerFixed;
647
664
  if (snapHeader || snapFooter) {
648
- if (y <= -headerHeightShared.value) {
665
+ if (yn <= -headerHeightShared.value) {
649
666
  snapUpAccum.current = 0;
650
667
  if (snapHeader) headerOffset.value = 0;
651
668
  if (snapFooter) footerOffset.value = 0;
652
669
  } else if (delta > 0) {
653
670
  snapUpAccum.current = 0;
654
- if (y >= -headerHeightShared.value + pullSearchHeightShared.value) {
671
+ if (yn >= -headerHeightShared.value + pullSearchHeightShared.value) {
655
672
  if (snapHeader) headerOffset.value = Math.max(-headerHeightShared.value, Math.min(0, headerOffset.value - delta));
656
673
  if (snapFooter) footerOffset.value = Math.max(0, Math.min(footerHeightShared.value, footerOffset.value + delta));
657
674
  }
658
675
  } else if (delta < 0 && !fromBottomBounce.current) {
659
- if (y >= -headerHeightShared.value + pullSearchHeightShared.value) snapUpAccum.current -= delta;
676
+ if (yn >= -headerHeightShared.value + pullSearchHeightShared.value) snapUpAccum.current -= delta;
660
677
  if (snapUpAccum.current >= 10) {
661
678
  snapUpAccum.current = 999;
662
679
  if (snapHeader) headerOffset.value = (0, import_react_native_reanimated8.withTiming)(0, { duration: 200 });
@@ -720,11 +737,11 @@ var FlatListInner = ({ chipProps, chipThreshold, columnWrapperStyle, contentCont
720
737
  (info) => {
721
738
  if (cols <= 1) return renderItem ? renderItem(info) : null;
722
739
  const row = info.item;
723
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native6.View, { style: [styles4.row, columnWrapperStyle], children: row.map((item, kk) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react9.Fragment, { children: renderItem ? renderItem({ index: info.index * cols + kk, item, separators: info.separators }) : null }, kk)) });
740
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native8.View, { style: [styles4.row, columnWrapperStyle], children: row.map((item, kk) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react9.Fragment, { children: renderItem ? renderItem({ index: info.index * cols + kk, item, separators: info.separators }) : null }, kk)) });
724
741
  },
725
742
  [cols, columnWrapperStyle, renderItem]
726
743
  );
727
- const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, isHorizontal, keyboardAware, pullSearchHeight, style });
744
+ const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, contentPadding, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, isHorizontal, keyboardAware, pullSearchHeight, style });
728
745
  const { jsListGeneration, onJsListUnmount } = (0, import_react9.useContext)(ScrollViewContext);
729
746
  const capturedGeneration = (0, import_react9.useRef)(jsListGeneration.current);
730
747
  (0, import_react9.useLayoutEffect)(() => {
@@ -759,7 +776,7 @@ var FlatListInner = ({ chipProps, chipThreshold, columnWrapperStyle, contentCont
759
776
  renderFilters
760
777
  ] });
761
778
  }, [pullSearchHeader, renderFilters]);
762
- const contentContainerStyle = (0, import_react9.useMemo)(() => isHorizontal ? externalContentContainerStyle : [{ minHeight: import_react_native6.Dimensions.get("window").height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle], [isHorizontal, contentInset.bottom, contentInset.top, externalContentContainerStyle, pullSearchMinHeight]);
779
+ const contentContainerStyle = (0, import_react9.useMemo)(() => isHorizontal ? externalContentContainerStyle : [{ minHeight: import_react_native8.Dimensions.get("window").height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle, contentPadding], [isHorizontal, contentInset.bottom, contentInset.top, contentPadding, externalContentContainerStyle, pullSearchMinHeight]);
763
780
  const scrollHandler = useScrollHandlerJS({ capturedGeneration, chipHidden, chipThreshold, footerFixed, headerFixed, isHorizontal, jsListGeneration, remountTarget, scrollTo });
764
781
  const onScrollBeginDrag = (0, import_react9.useCallback)(
765
782
  (e) => {
@@ -800,9 +817,9 @@ var FlatListInner = ({ chipProps, chipThreshold, columnWrapperStyle, contentCont
800
817
  const nativeGesture = (0, import_react9.useMemo)(() => import_react_native_gesture_handler2.Gesture.Native(), []);
801
818
  const combinedGesture = (0, import_react9.useMemo)(() => gesture !== void 0 ? import_react_native_gesture_handler2.Gesture.Simultaneous(gesture, nativeGesture) : nativeGesture, [gesture, nativeGesture]);
802
819
  const detectorGesture = gesture !== void 0 ? combinedGesture : void 0;
803
- const content = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native6.View, { style: containerStyle, children: [
820
+ const content = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native8.View, { style: containerStyle, children: [
804
821
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
805
- import_react_native6.FlatList,
822
+ import_react_native8.FlatList,
806
823
  {
807
824
  ...props,
808
825
  contentContainerStyle,
@@ -835,7 +852,7 @@ var FlatListInner = ({ chipProps, chipThreshold, columnWrapperStyle, contentCont
835
852
  windowSize
836
853
  }
837
854
  ),
838
- hiddenHeader && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native6.View, { pointerEvents: "none", style: styles4.measureContainer, children: hiddenHeader }),
855
+ hiddenHeader && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native8.View, { pointerEvents: "none", style: styles4.measureContainer, children: hiddenHeader }),
839
856
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ScrollViewChip, { chipProps, isHorizontal, onChipPress, onPress: handleScrollToTop, style: chipStyle }),
840
857
  showDots && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(HorizontalDots, { total: data?.length ?? 0 })
841
858
  ] });
@@ -850,7 +867,7 @@ var FlatList = (0, import_react9.memo)(FlatListInner);
850
867
 
851
868
  // src/PullSearch.tsx
852
869
  var import_react10 = require("react");
853
- var import_react_native7 = require("react-native");
870
+ var import_react_native9 = require("react-native");
854
871
  var import_react_native_paper2 = require("react-native-paper");
855
872
  var import_react_native_reanimated9 = __toESM(require("react-native-reanimated"));
856
873
  var import_jsx_runtime6 = require("react/jsx-runtime");
@@ -913,7 +930,7 @@ var PullSearch = (0, import_react10.forwardRef)(function PullSearch2({ debounce,
913
930
  }, [barHeight, headerHeight]);
914
931
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_reanimated9.default.View, { onLayout: handleLayout, style: [styles5.container, animatedStyle], children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper2.Searchbar, { autoCorrect: false, onChangeText: (text) => setValue((text || "").trimStart()), onClearIconPress: handleClear, placeholder, placeholderTextColor: theme.colors.onSurfaceVariant, ref: inputRef, spellCheck: false, style: styles5.input, value }) });
915
932
  });
916
- var styles5 = import_react_native7.StyleSheet.create({
933
+ var styles5 = import_react_native9.StyleSheet.create({
917
934
  container: { paddingVertical: 8 },
918
935
  input: { margin: 0 }
919
936
  });
@@ -948,14 +965,14 @@ var scrollViewReducer = (state = defaultScrollViewSettings, action) => {
948
965
 
949
966
  // src/ScrollView.tsx
950
967
  var import_react12 = require("react");
951
- var import_react_native8 = require("react-native");
968
+ var import_react_native10 = require("react-native");
952
969
  var import_react_native_gesture_handler3 = require("react-native-gesture-handler");
953
970
  var import_react_native_reanimated10 = __toESM(require("react-native-reanimated"));
954
971
  var import_jsx_runtime7 = require("react/jsx-runtime");
955
- var ScrollViewInner = ({ chipProps, chipThreshold, children, footerFixed: footerFixedProp, gesture, headerFixed: headerFixedProp, horizontal, keyboardAware, onChipPress, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight, refreshing, style, ...props }) => {
972
+ var ScrollViewInner = ({ chipProps, chipThreshold, children, contentContainerStyle: externalContentContainerStyle, footerFixed: footerFixedProp, gesture, headerFixed: headerFixedProp, horizontal, keyboardAware, onChipPress, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight, refreshing, style, ...props }) => {
956
973
  const scrollView = (0, import_react12.useRef)(null);
957
974
  const isHorizontal = horizontal === true;
958
- const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, isHorizontal, keyboardAware, pullSearchHeight, style });
975
+ const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, contentPadding, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, isHorizontal, keyboardAware, pullSearchHeight, style });
959
976
  const { listGeneration, onListUnmount } = (0, import_react12.useContext)(ScrollViewContext);
960
977
  const capturedGeneration = (0, import_react_native_reanimated10.useSharedValue)(-1);
961
978
  (0, import_react12.useLayoutEffect)(() => {
@@ -989,11 +1006,12 @@ var ScrollViewInner = ({ chipProps, chipThreshold, children, footerFixed: footer
989
1006
  const nativeGesture = (0, import_react12.useMemo)(() => import_react_native_gesture_handler3.Gesture.Native(), []);
990
1007
  const combinedGesture = (0, import_react12.useMemo)(() => gesture !== void 0 ? import_react_native_gesture_handler3.Gesture.Simultaneous(gesture, nativeGesture) : nativeGesture, [gesture, nativeGesture]);
991
1008
  const detectorGesture = gesture !== void 0 ? combinedGesture : void 0;
992
- const content = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: containerStyle, children: [
1009
+ const content = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native10.View, { style: containerStyle, children: [
993
1010
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
994
1011
  import_react_native_reanimated10.default.ScrollView,
995
1012
  {
996
1013
  ...props,
1014
+ contentContainerStyle: contentPadding ? [externalContentContainerStyle, contentPadding] : externalContentContainerStyle,
997
1015
  contentInset,
998
1016
  contentOffset,
999
1017
  onMomentumScrollEnd: handleMomentumScrollEnd,
@@ -1018,7 +1036,7 @@ var ScrollView = (0, import_react12.memo)(ScrollViewInner);
1018
1036
  // src/ScrollViewFooter.tsx
1019
1037
  var import_auto_paper3 = require("@rific/auto-paper");
1020
1038
  var import_react13 = require("react");
1021
- var import_react_native9 = require("react-native");
1039
+ var import_react_native11 = require("react-native");
1022
1040
  var import_react_native_reanimated11 = __toESM(require("react-native-reanimated"));
1023
1041
  var import_react_native_safe_area_context3 = require("react-native-safe-area-context");
1024
1042
  var import_jsx_runtime8 = require("react/jsx-runtime");
@@ -1046,11 +1064,11 @@ var ScrollViewFooter = ({ children, style }) => {
1046
1064
  return { transform: [{ translateY: Math.min(effective, footerHeight) }] };
1047
1065
  }, [footerHeight, footerFixed]);
1048
1066
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native_reanimated11.default.View, { onLayout: handleLayout, pointerEvents: "box-none", style: [styles6.footer, footerStyle], children: [
1049
- footerHeight > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_auto_paper3.BlurView, { blur, style: import_react_native9.StyleSheet.absoluteFill }),
1050
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native9.View, { style: [styles6.row, { paddingBottom: insets.bottom }, style], children })
1067
+ footerHeight > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_auto_paper3.BlurView, { blur, style: import_react_native11.StyleSheet.absoluteFill }),
1068
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native11.View, { style: [styles6.row, { paddingBottom: insets.bottom }, style], children })
1051
1069
  ] });
1052
1070
  };
1053
- var styles6 = import_react_native9.StyleSheet.create({
1071
+ var styles6 = import_react_native11.StyleSheet.create({
1054
1072
  footer: { bottom: 0, left: 0, position: "absolute", right: 0, zIndex: 2 },
1055
1073
  row: { alignItems: "center", flexDirection: "row", zIndex: 1 }
1056
1074
  });
@@ -1058,12 +1076,12 @@ var styles6 = import_react_native9.StyleSheet.create({
1058
1076
  // src/ScrollViewHeader.tsx
1059
1077
  var import_auto_paper4 = require("@rific/auto-paper");
1060
1078
  var import_react14 = require("react");
1061
- var import_react_native10 = require("react-native");
1079
+ var import_react_native12 = require("react-native");
1062
1080
  var import_react_native_paper3 = require("react-native-paper");
1063
1081
  var import_react_native_reanimated12 = __toESM(require("react-native-reanimated"));
1064
1082
  var import_react_native_safe_area_context4 = require("react-native-safe-area-context");
1065
1083
  var import_jsx_runtime9 = require("react/jsx-runtime");
1066
- var ActionBg = ({ blur, style }) => blur ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native10.View, { style: [import_react_native10.StyleSheet.absoluteFill, styles7.actionBg, style], children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_auto_paper4.BlurView, { blur: true, style: import_react_native10.StyleSheet.absoluteFill }) }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native10.View, { style: [import_react_native10.StyleSheet.absoluteFill, styles7.actionBg, style], pointerEvents: "none", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_paper3.Surface, { style: import_react_native10.StyleSheet.absoluteFill, elevation: 1, children: null }) });
1084
+ var ActionBg = ({ blur, style }) => blur ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native12.View, { style: [import_react_native12.StyleSheet.absoluteFill, styles7.actionBg, style], children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_auto_paper4.BlurView, { blur: true, style: import_react_native12.StyleSheet.absoluteFill }) }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native12.View, { style: [import_react_native12.StyleSheet.absoluteFill, styles7.actionBg, style], pointerEvents: "none", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_paper3.Surface, { style: import_react_native12.StyleSheet.absoluteFill, elevation: 1, children: null }) });
1067
1085
  var ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, backActionFixed, caption, centerContent, children, iconSize, style, title, topInset = true, trailingAction, trailingActionFixed = true }) => {
1068
1086
  const { blur, headerHeight, headerFixed, headerOffset, progress, progressing, pullSearchHeightShared, scrollPosition, setHeaderHeight, snapBackHeaderShared } = (0, import_react14.useContext)(ScrollViewContext);
1069
1087
  const { settings } = (0, import_react14.useContext)(ScrollViewSettingsContext);
@@ -1108,15 +1126,15 @@ var ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, backActionFi
1108
1126
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1109
1127
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_reanimated12.default.View, { pointerEvents: "none", style: [styles7.blur, blurStyle], children: headerHeight !== null && headerHeight > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_auto_paper4.BlurView, { blur, style: [styles7.blurInner, { height: headerHeight }] }) }),
1110
1128
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native_reanimated12.default.View, { onLayout: handleLayout, pointerEvents: "box-none", style: [headerHeight === null ? styles7.headerInit : styles7.header, translateStyle], children: [
1111
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native10.View, { style: [{ paddingTop: top }, style], children: children ?? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native10.View, { style: [styles7.content, { minHeight: contentMinHeight }], children: [
1112
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native10.View, { style: styles7.side }),
1113
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native10.View, { style: styles7.spacer }),
1114
- (centerContent || title || caption) && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native10.View, { style: [import_react_native10.StyleSheet.absoluteFill, styles7.titleContainer], pointerEvents: centerContent ? "box-none" : "none", children: centerContent ?? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1115
- title && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native10.Text, { numberOfLines: 1, style: [styles7.title, { color: theme.colors.onSurface }], children: title }),
1116
- caption && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native10.Text, { numberOfLines: 1, style: [styles7.caption, { color: theme.colors.onSurfaceVariant }], children: caption })
1129
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native12.View, { style: [{ paddingTop: top }, style], children: children ?? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native12.View, { style: [styles7.content, { minHeight: contentMinHeight }], children: [
1130
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native12.View, { style: styles7.side }),
1131
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native12.View, { style: styles7.spacer }),
1132
+ (centerContent || title || caption) && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native12.View, { style: [import_react_native12.StyleSheet.absoluteFill, styles7.titleContainer], pointerEvents: centerContent ? "box-none" : "none", children: centerContent ?? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1133
+ title && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native12.Text, { numberOfLines: 1, style: [styles7.title, { color: theme.colors.onSurface }], children: title }),
1134
+ caption && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native12.Text, { numberOfLines: 1, style: [styles7.caption, { color: theme.colors.onSurfaceVariant }], children: caption })
1117
1135
  ] }) })
1118
1136
  ] }) }),
1119
- !blur && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native10.View, { style: [styles7.divider, { backgroundColor: theme.colors.outlineVariant }] })
1137
+ !blur && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native12.View, { style: [styles7.divider, { backgroundColor: theme.colors.outlineVariant }] })
1120
1138
  ] }),
1121
1139
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_reanimated12.default.View, { pointerEvents: "none", style: [styles7.progress, progressStyle], children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_paper3.ProgressBar, { indeterminate: progress === null, visible: progressing, progress: progress ?? void 0 }) }),
1122
1140
  backAction && (effectiveBackActionFixed ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native_reanimated12.default.View, { style: leadingStyle, children: [
@@ -1126,7 +1144,7 @@ var ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, backActionFi
1126
1144
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ActionBg, { blur, style: actionStyle }),
1127
1145
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_paper3.Appbar.BackAction, { onPress: backAction, size: iconSize ?? actionSize / 2, style: buttonStyle })
1128
1146
  ] })),
1129
- trailingAction && (trailingActionFixed ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native10.View, { style: trailingStyle, children: [
1147
+ trailingAction && (trailingActionFixed ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native12.View, { style: trailingStyle, children: [
1130
1148
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ActionBg, { blur, style: actionStyle }),
1131
1149
  trailingAction
1132
1150
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native_reanimated12.default.View, { style: [trailingStyle, translateStyle], children: [
@@ -1135,9 +1153,9 @@ var ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, backActionFi
1135
1153
  ] }))
1136
1154
  ] });
1137
1155
  };
1138
- var styles7 = import_react_native10.StyleSheet.create({
1156
+ var styles7 = import_react_native12.StyleSheet.create({
1139
1157
  actionBg: { borderRadius: 100, overflow: "hidden" },
1140
- divider: { bottom: 0, height: import_react_native10.StyleSheet.hairlineWidth, left: 0, position: "absolute", right: 0 },
1158
+ divider: { bottom: 0, height: import_react_native12.StyleSheet.hairlineWidth, left: 0, position: "absolute", right: 0 },
1141
1159
  caption: { fontSize: 12, fontWeight: "400", textAlign: "center" },
1142
1160
  content: { alignItems: "center", flexDirection: "row", width: "100%" },
1143
1161
  blur: { left: 0, overflow: "hidden", position: "absolute", right: 0, top: 0, zIndex: 2 },
@@ -1154,7 +1172,7 @@ var styles7 = import_react_native10.StyleSheet.create({
1154
1172
  // src/ScrollViewProvider.tsx
1155
1173
  var import_auto_paper5 = require("@rific/auto-paper");
1156
1174
  var import_react15 = require("react");
1157
- var import_react_native11 = require("react-native");
1175
+ var import_react_native13 = require("react-native");
1158
1176
  var import_react_native_reanimated13 = require("react-native-reanimated");
1159
1177
  var import_jsx_runtime10 = require("react/jsx-runtime");
1160
1178
  var ScrollViewProvider = ({ blur, children, fixed = false, footerFixed, headerFixed, snapBack, snapBackFooter, snapBackHeader, tabBarHeight = 60 }) => {
@@ -1208,7 +1226,7 @@ var ScrollViewProvider = ({ blur, children, fixed = false, footerFixed, headerFi
1208
1226
  },
1209
1227
  [footerHeightShared]
1210
1228
  );
1211
- const scrollHeight = (0, import_react15.useMemo)(() => import_react_native11.Dimensions.get("window").height - (headerHeight ?? 0) - footerHeight, [headerHeight, footerHeight]);
1229
+ const scrollHeight = (0, import_react15.useMemo)(() => import_react_native13.Dimensions.get("window").height - (headerHeight ?? 0) - footerHeight, [headerHeight, footerHeight]);
1212
1230
  const effectiveHeaderFixed = fixed || (headerFixed ?? settings.headerFixed);
1213
1231
  const effectiveFooterFixed = fixed || (footerFixed ?? settings.footerFixed);
1214
1232
  const value = (0, import_react15.useMemo)(() => ({ 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]);
@@ -1239,7 +1257,7 @@ var ScrollViewSettingsProvider = ({ children, onChange, initialValue }) => {
1239
1257
 
1240
1258
  // src/SectionList.tsx
1241
1259
  var import_react18 = require("react");
1242
- var import_react_native12 = require("react-native");
1260
+ var import_react_native14 = require("react-native");
1243
1261
  var import_react_native_gesture_handler4 = require("react-native-gesture-handler");
1244
1262
  var import_react_native_reanimated15 = __toESM(require("react-native-reanimated"));
1245
1263
 
@@ -1354,11 +1372,11 @@ function useStickyHeaders(headerFixed, enabled) {
1354
1372
 
1355
1373
  // src/SectionList.tsx
1356
1374
  var import_jsx_runtime12 = require("react/jsx-runtime");
1357
- var AnimatedSectionList = import_react_native_reanimated15.default.createAnimatedComponent(import_react_native12.SectionList);
1375
+ var AnimatedSectionList = import_react_native_reanimated15.default.createAnimatedComponent(import_react_native14.SectionList);
1358
1376
  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 }) => {
1359
1377
  const scrollView = (0, import_react18.useRef)(null);
1360
1378
  const { stickySectionHeadersEnabled = true, renderSectionHeader, ...passThroughProps } = props;
1361
- const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed, headerHeight } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, keyboardAware, pullSearchHeight, style });
1379
+ const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, contentPadding, footerFixed, headerFixed, headerHeight } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, keyboardAware, pullSearchHeight, style });
1362
1380
  const { listGeneration, onListUnmount } = (0, import_react18.useContext)(ScrollViewContext);
1363
1381
  const capturedGeneration = (0, import_react_native_reanimated15.useSharedValue)(-1);
1364
1382
  (0, import_react18.useLayoutEffect)(() => {
@@ -1379,7 +1397,7 @@ var SectionListInner = ({ chipProps, chipThreshold, contentContainerStyle: exter
1379
1397
  return (info) => {
1380
1398
  const idx = sections.findIndex((s) => s === info.section);
1381
1399
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1382
- import_react_native12.View,
1400
+ import_react_native14.View,
1383
1401
  {
1384
1402
  ref: (el) => {
1385
1403
  sectionViewRefs.current.set(idx, el);
@@ -1435,7 +1453,7 @@ var SectionListInner = ({ chipProps, chipThreshold, contentContainerStyle: exter
1435
1453
  });
1436
1454
  return () => cancelAnimationFrame(handle);
1437
1455
  }, [activeListHeader, usesCustomSticky, measureHeader]);
1438
- const contentContainerStyle = (0, import_react18.useMemo)(() => [{ minHeight: import_react_native12.Dimensions.get("window").height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle], [contentInset.bottom, contentInset.top, externalContentContainerStyle, pullSearchMinHeight]);
1456
+ const contentContainerStyle = (0, import_react18.useMemo)(() => [{ minHeight: import_react_native14.Dimensions.get("window").height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle, contentPadding], [contentInset.bottom, contentInset.top, contentPadding, externalContentContainerStyle, pullSearchMinHeight]);
1439
1457
  const handleScroll = useScrollHandler({ capturedGeneration, chipHidden, chipThreshold, footerFixed, headerFixed, listGeneration });
1440
1458
  const handleScrollToTop = (0, import_react18.useCallback)(() => {
1441
1459
  const offset = pullSearchHeight ? -contentInset.top + pullSearchHeight : -contentInset.top;
@@ -1451,7 +1469,7 @@ var SectionListInner = ({ chipProps, chipThreshold, contentContainerStyle: exter
1451
1469
  const nativeGesture = (0, import_react18.useMemo)(() => import_react_native_gesture_handler4.Gesture.Native(), []);
1452
1470
  const combinedGesture = (0, import_react18.useMemo)(() => gesture !== void 0 ? import_react_native_gesture_handler4.Gesture.Simultaneous(gesture, nativeGesture) : nativeGesture, [gesture, nativeGesture]);
1453
1471
  const detectorGesture = gesture !== void 0 ? combinedGesture : void 0;
1454
- const content = /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native12.View, { style: containerStyle, children: [
1472
+ const content = /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native14.View, { style: containerStyle, children: [
1455
1473
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1456
1474
  AnimatedSectionList,
1457
1475
  {
@@ -1482,7 +1500,7 @@ var SectionListInner = ({ chipProps, chipThreshold, contentContainerStyle: exter
1482
1500
  },
1483
1501
  headerHeight !== null && headerHeight > 0 ? 1 : 0
1484
1502
  ),
1485
- hiddenHeader && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.View, { pointerEvents: "none", style: styles8.measureContainer, children: hiddenHeader }),
1503
+ hiddenHeader && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native14.View, { pointerEvents: "none", style: styles8.measureContainer, children: hiddenHeader }),
1486
1504
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ScrollViewChip, { chipProps, onChipPress, onPress: handleScrollToTop, style: chipStyle }),
1487
1505
  usesCustomSticky && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native_reanimated15.default.View, { pointerEvents: "none", style: [styles8.stickyClip, clipStyle], children: activeIndex >= 0 && activeIndex < sections.length && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native_reanimated15.default.View, { style: [styles8.stickyOverlay, overlayStyle], children: renderSectionHeader({ section: sections[activeIndex] }) }) })
1488
1506
  ] });
package/dist/index.mjs CHANGED
@@ -70,6 +70,12 @@ var ScrollViewContext = createContext({
70
70
  // src/useScrollInit.ts
71
71
  import { createElement, useCallback, useContext, useEffect, useLayoutEffect, useRef, useState } from "react";
72
72
  import { useSharedValue } from "react-native-reanimated";
73
+
74
+ // src/internal/insetMode.ts
75
+ import { Platform } from "react-native";
76
+ var usesContentInset = Platform.OS === "ios";
77
+
78
+ // src/useScrollInit.ts
73
79
  var REMOUNT_SYNC_MAX_ATTEMPTS = 8;
74
80
  var REMOUNT_SYNC_TOLERANCE = 1;
75
81
  var REMOUNT_SYNC_FALLBACK_MS = 500;
@@ -78,8 +84,9 @@ var SNAP_VERIFY_TOLERANCE = 1;
78
84
  var SNAP_VERIFY_MAX_ATTEMPTS = 5;
79
85
  var SNAP_VERIFY_INITIAL_DELAY_MS = 400;
80
86
  var SNAP_VERIFY_RETRY_DELAY_MS = 150;
81
- function useScrollInit({ listHeaderComponent, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight, scrollTo }) {
87
+ function useScrollInit({ listHeaderComponent, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight: pullSearchHeightProp, scrollTo }) {
82
88
  const { headerHeight, scrollPosition, setProgressing } = useContext(ScrollViewContext);
89
+ const pullSearchHeight = usesContentInset ? pullSearchHeightProp : void 0;
83
90
  const hasPullSearch = pullSearchHeight !== void 0;
84
91
  const [phase, setPhase] = useState(hasPullSearch && !headerHeight ? "measuring" : "ready");
85
92
  const [refreshing, setRefreshing] = useState(false);
@@ -121,7 +128,7 @@ function useScrollInit({ listHeaderComponent, onMomentumScrollEnd: externalMomen
121
128
  },
122
129
  []
123
130
  );
124
- const [remountTarget] = useState(() => headerHeight !== null ? -headerHeight + (pullSearchHeight ?? 0) : null);
131
+ const [remountTarget] = useState(() => usesContentInset && headerHeight !== null ? -headerHeight + (pullSearchHeight ?? 0) : null);
125
132
  const remountSyncTarget = useSharedValue(remountTarget);
126
133
  const [scrollReady, setScrollReady] = useState(remountTarget === null);
127
134
  const remountAttempts = useRef(0);
@@ -166,7 +173,7 @@ function useScrollInit({ listHeaderComponent, onMomentumScrollEnd: externalMomen
166
173
  headerInitialized.current = true;
167
174
  const target = remountTarget ?? -headerHeight;
168
175
  scrollPosition.value = target;
169
- scrollTo(target, false);
176
+ scrollTo(usesContentInset ? target : 0, false);
170
177
  if (remountTarget === null) return;
171
178
  let framesLeft = REMOUNT_PRIME_FRAMES;
172
179
  let handle = null;
@@ -273,9 +280,10 @@ function useScrollHandler({ capturedGeneration, chipHidden, chipThreshold = 100,
273
280
  chipHidden.value = x < chipThreshold ? 1 : 0;
274
281
  return;
275
282
  }
276
- const delta = y - scrollPosition.value;
277
- scrollPosition.value = y;
278
- chipHidden.value = y < chipThreshold ? 1 : 0;
283
+ const yn = usesContentInset ? y : y - headerHeightShared.value;
284
+ const delta = yn - scrollPosition.value;
285
+ scrollPosition.value = yn;
286
+ chipHidden.value = yn < chipThreshold ? 1 : 0;
279
287
  const maxScroll = contentHeight - layoutHeight;
280
288
  if (maxScroll > 0 && y >= maxScroll) {
281
289
  fromBottomBounce.value = true;
@@ -286,18 +294,18 @@ function useScrollHandler({ capturedGeneration, chipHidden, chipThreshold = 100,
286
294
  const snapHeader = snapBackHeaderShared.value && !headerFixed;
287
295
  const snapFooter = snapBackFooterShared.value && !footerFixed;
288
296
  if (snapHeader || snapFooter) {
289
- if (y <= -headerHeightShared.value) {
297
+ if (yn <= -headerHeightShared.value) {
290
298
  snapUpAccum.value = 0;
291
299
  if (snapHeader) headerOffset.value = 0;
292
300
  if (snapFooter) footerOffset.value = 0;
293
301
  } else if (delta > 0) {
294
302
  snapUpAccum.value = 0;
295
- if (y >= -headerHeightShared.value + pullSearchHeightShared.value) {
303
+ if (yn >= -headerHeightShared.value + pullSearchHeightShared.value) {
296
304
  if (snapHeader) headerOffset.value = Math.max(-headerHeightShared.value, Math.min(0, headerOffset.value - delta));
297
305
  if (snapFooter) footerOffset.value = Math.max(0, Math.min(footerHeightShared.value, footerOffset.value + delta));
298
306
  }
299
307
  } else if (delta < 0 && !fromBottomBounce.value) {
300
- if (y >= -headerHeightShared.value + pullSearchHeightShared.value) snapUpAccum.value -= delta;
308
+ if (yn >= -headerHeightShared.value + pullSearchHeightShared.value) snapUpAccum.value -= delta;
301
309
  if (snapUpAccum.value >= 10) {
302
310
  snapUpAccum.value = 999;
303
311
  if (snapHeader) headerOffset.value = withTiming(0, { duration: 200 });
@@ -337,9 +345,10 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
337
345
 
338
346
  // src/useKeyboardInset.ts
339
347
  import { useState as useState2 } from "react";
348
+ import { Platform as Platform2 } from "react-native";
340
349
  import { useKeyboardHandler } from "react-native-keyboard-controller";
341
350
  import { runOnJS as runOnJS2 } from "react-native-reanimated";
342
- var useKeyboardInset = () => {
351
+ var useKeyboardInsetNative = () => {
343
352
  const [keyboardHeight, setKeyboardHeight] = useState2(0);
344
353
  useKeyboardHandler(
345
354
  {
@@ -356,8 +365,12 @@ var useKeyboardInset = () => {
356
365
  );
357
366
  return keyboardHeight;
358
367
  };
368
+ var useKeyboardInsetWeb = () => 0;
369
+ var useKeyboardInset = Platform2.OS === "web" ? useKeyboardInsetWeb : useKeyboardInsetNative;
359
370
 
360
371
  // src/internal/useScrollList.ts
372
+ var ZERO_INSET = { bottom: 0, top: 0 };
373
+ var ZERO_OFFSET = { x: 0, y: 0 };
361
374
  var CHIP_SLIDE = 48;
362
375
  function useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, isHorizontal, keyboardAware, pullSearchHeight, style } = {}) {
363
376
  const insets = useSafeAreaInsets();
@@ -366,12 +379,15 @@ function useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedP
366
379
  const headerFixed = isHorizontal ? true : headerFixedProp ?? contextHeaderFixed;
367
380
  const footerFixed = isHorizontal ? true : footerFixedProp ?? contextFooterFixed;
368
381
  useEffect2(() => {
369
- pullSearchHeightShared.value = pullSearchHeight ?? 0;
382
+ pullSearchHeightShared.value = usesContentInset ? pullSearchHeight ?? 0 : 0;
370
383
  }, [pullSearchHeight, pullSearchHeightShared]);
371
384
  const containerStyle = useMemo(() => headerHeight === null ? [{ flex: 1, opacity: 0 }, style] : [StyleSheet2.absoluteFill, style], [headerHeight, style]);
372
- const contentInset = useMemo(() => ({ bottom: (footerFixed ? footerHeight || insets.bottom : insets.bottom) + (keyboardAware ? keyboardHeight : 0), top: headerHeight ?? 0 }), [footerFixed, footerHeight, insets.bottom, headerHeight, keyboardAware, keyboardHeight]);
385
+ const insetGeometry = useMemo(() => ({ bottom: (footerFixed ? footerHeight || insets.bottom : insets.bottom) + (keyboardAware ? keyboardHeight : 0), top: headerHeight ?? 0 }), [footerFixed, footerHeight, insets.bottom, headerHeight, keyboardAware, keyboardHeight]);
386
+ const contentInset = usesContentInset ? insetGeometry : ZERO_INSET;
387
+ const contentPadding = useMemo(() => usesContentInset || isHorizontal ? null : { paddingBottom: insetGeometry.bottom, paddingTop: insetGeometry.top }, [insetGeometry, isHorizontal]);
373
388
  const [startedWithHeader] = useState3(headerHeight !== null);
374
- const contentOffset = useMemo(() => ({ x: 0, y: headerHeight !== null ? -headerHeight + (startedWithHeader && !isHorizontal && pullSearchHeight ? pullSearchHeight : 0) : 0 }), [headerHeight, isHorizontal, pullSearchHeight, startedWithHeader]);
389
+ const insetContentOffset = useMemo(() => ({ x: 0, y: headerHeight !== null ? -headerHeight + (startedWithHeader && !isHorizontal && pullSearchHeight ? pullSearchHeight : 0) : 0 }), [headerHeight, isHorizontal, pullSearchHeight, startedWithHeader]);
390
+ const contentOffset = usesContentInset ? insetContentOffset : ZERO_OFFSET;
375
391
  const chipHidden = useSharedValue3(1);
376
392
  const chipStyle = useAnimatedStyle(() => {
377
393
  const pointerEvents = chipHidden.value ? "none" : "box-none";
@@ -393,14 +409,14 @@ function useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedP
393
409
  transform: [{ translateY: chipHidden.value ? withTiming2(-CHIP_SLIDE) : withTiming2(0) }]
394
410
  };
395
411
  }, [isHorizontal, headerFixed, headerHeight, insets.top]);
396
- return { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed, headerHeight, insets, isHorizontal: isHorizontal ?? false };
412
+ return { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, contentPadding, footerFixed, headerFixed, headerHeight, insets, isHorizontal: isHorizontal ?? false };
397
413
  }
398
414
 
399
415
  // src/CustomList.tsx
400
416
  import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
401
417
  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 }) => {
402
418
  const { ListHeaderComponent: listHeaderComponent, contentContainerStyle: externalContentContainerStyle, ...restProps } = props;
403
- const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, keyboardAware, pullSearchHeight, style });
419
+ const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, contentPadding, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, keyboardAware, pullSearchHeight, style });
404
420
  const { listGeneration, onListUnmount } = useContext4(ScrollViewContext);
405
421
  const capturedGeneration = useSharedValue4(-1);
406
422
  useLayoutEffect2(() => {
@@ -449,7 +465,7 @@ var CustomListInner = ({ chipProps, chipThreshold, component: List, renderFilter
449
465
  renderFilters
450
466
  ] });
451
467
  }, [pullSearchHeader, renderFilters]);
452
- const contentContainerStyle = useMemo2(() => [{ minHeight: Dimensions.get("window").height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle], [contentInset.bottom, contentInset.top, externalContentContainerStyle, pullSearchMinHeight]);
468
+ const contentContainerStyle = useMemo2(() => [{ minHeight: Dimensions.get("window").height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle, contentPadding], [contentInset.bottom, contentInset.top, contentPadding, externalContentContainerStyle, pullSearchMinHeight]);
453
469
  const onScroll = useScrollHandler({ capturedGeneration, chipHidden, chipThreshold, footerFixed, headerFixed, listGeneration, onRemountSyncRetry, onRemountSynced, remountSyncTarget });
454
470
  const handleScrollToTop = useCallback2(() => {
455
471
  const offset = pullSearchHeight ? -contentInset.top + pullSearchHeight : -contentInset.top;
@@ -580,9 +596,10 @@ function useScrollHandlerJS({ capturedGeneration, chipHidden, chipThreshold = 10
580
596
  chipHidden.value = x < chipThreshold ? 1 : 0;
581
597
  return;
582
598
  }
583
- const delta = y - scrollPosition.value;
584
- scrollPosition.value = y;
585
- chipHidden.value = y < chipThreshold ? 1 : 0;
599
+ const yn = usesContentInset ? y : y - headerHeightShared.value;
600
+ const delta = yn - scrollPosition.value;
601
+ scrollPosition.value = yn;
602
+ chipHidden.value = yn < chipThreshold ? 1 : 0;
586
603
  const maxScroll = contentHeight - layoutHeight;
587
604
  if (maxScroll > 0 && y >= maxScroll) {
588
605
  fromBottomBounce.current = true;
@@ -593,18 +610,18 @@ function useScrollHandlerJS({ capturedGeneration, chipHidden, chipThreshold = 10
593
610
  const snapHeader = snapBackHeaderShared.value && !headerFixed;
594
611
  const snapFooter = snapBackFooterShared.value && !footerFixed;
595
612
  if (snapHeader || snapFooter) {
596
- if (y <= -headerHeightShared.value) {
613
+ if (yn <= -headerHeightShared.value) {
597
614
  snapUpAccum.current = 0;
598
615
  if (snapHeader) headerOffset.value = 0;
599
616
  if (snapFooter) footerOffset.value = 0;
600
617
  } else if (delta > 0) {
601
618
  snapUpAccum.current = 0;
602
- if (y >= -headerHeightShared.value + pullSearchHeightShared.value) {
619
+ if (yn >= -headerHeightShared.value + pullSearchHeightShared.value) {
603
620
  if (snapHeader) headerOffset.value = Math.max(-headerHeightShared.value, Math.min(0, headerOffset.value - delta));
604
621
  if (snapFooter) footerOffset.value = Math.max(0, Math.min(footerHeightShared.value, footerOffset.value + delta));
605
622
  }
606
623
  } else if (delta < 0 && !fromBottomBounce.current) {
607
- if (y >= -headerHeightShared.value + pullSearchHeightShared.value) snapUpAccum.current -= delta;
624
+ if (yn >= -headerHeightShared.value + pullSearchHeightShared.value) snapUpAccum.current -= delta;
608
625
  if (snapUpAccum.current >= 10) {
609
626
  snapUpAccum.current = 999;
610
627
  if (snapHeader) headerOffset.value = withTiming3(0, { duration: 200 });
@@ -672,7 +689,7 @@ var FlatListInner = ({ chipProps, chipThreshold, columnWrapperStyle, contentCont
672
689
  },
673
690
  [cols, columnWrapperStyle, renderItem]
674
691
  );
675
- const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, isHorizontal, keyboardAware, pullSearchHeight, style });
692
+ const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, contentPadding, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, isHorizontal, keyboardAware, pullSearchHeight, style });
676
693
  const { jsListGeneration, onJsListUnmount } = useContext7(ScrollViewContext);
677
694
  const capturedGeneration = useRef4(jsListGeneration.current);
678
695
  useLayoutEffect3(() => {
@@ -707,7 +724,7 @@ var FlatListInner = ({ chipProps, chipThreshold, columnWrapperStyle, contentCont
707
724
  renderFilters
708
725
  ] });
709
726
  }, [pullSearchHeader, renderFilters]);
710
- const contentContainerStyle = useMemo3(() => isHorizontal ? externalContentContainerStyle : [{ minHeight: Dimensions2.get("window").height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle], [isHorizontal, contentInset.bottom, contentInset.top, externalContentContainerStyle, pullSearchMinHeight]);
727
+ const contentContainerStyle = useMemo3(() => isHorizontal ? externalContentContainerStyle : [{ minHeight: Dimensions2.get("window").height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle, contentPadding], [isHorizontal, contentInset.bottom, contentInset.top, contentPadding, externalContentContainerStyle, pullSearchMinHeight]);
711
728
  const scrollHandler = useScrollHandlerJS({ capturedGeneration, chipHidden, chipThreshold, footerFixed, headerFixed, isHorizontal, jsListGeneration, remountTarget, scrollTo });
712
729
  const onScrollBeginDrag = useCallback4(
713
730
  (e) => {
@@ -900,10 +917,10 @@ import { View as View4 } from "react-native";
900
917
  import { Gesture as Gesture3, GestureDetector as GestureDetector3 } from "react-native-gesture-handler";
901
918
  import Animated4, { runOnUI as runOnUI2, useSharedValue as useSharedValue5 } from "react-native-reanimated";
902
919
  import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
903
- var ScrollViewInner = ({ chipProps, chipThreshold, children, footerFixed: footerFixedProp, gesture, headerFixed: headerFixedProp, horizontal, keyboardAware, onChipPress, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight, refreshing, style, ...props }) => {
920
+ var ScrollViewInner = ({ chipProps, chipThreshold, children, contentContainerStyle: externalContentContainerStyle, footerFixed: footerFixedProp, gesture, headerFixed: headerFixedProp, horizontal, keyboardAware, onChipPress, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight, refreshing, style, ...props }) => {
904
921
  const scrollView = useRef6(null);
905
922
  const isHorizontal = horizontal === true;
906
- const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, isHorizontal, keyboardAware, pullSearchHeight, style });
923
+ const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, contentPadding, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, isHorizontal, keyboardAware, pullSearchHeight, style });
907
924
  const { listGeneration, onListUnmount } = useContext9(ScrollViewContext);
908
925
  const capturedGeneration = useSharedValue5(-1);
909
926
  useLayoutEffect4(() => {
@@ -942,6 +959,7 @@ var ScrollViewInner = ({ chipProps, chipThreshold, children, footerFixed: footer
942
959
  Animated4.ScrollView,
943
960
  {
944
961
  ...props,
962
+ contentContainerStyle: contentPadding ? [externalContentContainerStyle, contentPadding] : externalContentContainerStyle,
945
963
  contentInset,
946
964
  contentOffset,
947
965
  onMomentumScrollEnd: handleMomentumScrollEnd,
@@ -1306,7 +1324,7 @@ var AnimatedSectionList = Animated7.createAnimatedComponent(RNSectionList);
1306
1324
  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 }) => {
1307
1325
  const scrollView = useRef9(null);
1308
1326
  const { stickySectionHeadersEnabled = true, renderSectionHeader, ...passThroughProps } = props;
1309
- const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed, headerHeight } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, keyboardAware, pullSearchHeight, style });
1327
+ const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, contentPadding, footerFixed, headerFixed, headerHeight } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, keyboardAware, pullSearchHeight, style });
1310
1328
  const { listGeneration, onListUnmount } = useContext14(ScrollViewContext);
1311
1329
  const capturedGeneration = useSharedValue8(-1);
1312
1330
  useLayoutEffect6(() => {
@@ -1383,7 +1401,7 @@ var SectionListInner = ({ chipProps, chipThreshold, contentContainerStyle: exter
1383
1401
  });
1384
1402
  return () => cancelAnimationFrame(handle);
1385
1403
  }, [activeListHeader, usesCustomSticky, measureHeader]);
1386
- const contentContainerStyle = useMemo7(() => [{ minHeight: Dimensions4.get("window").height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle], [contentInset.bottom, contentInset.top, externalContentContainerStyle, pullSearchMinHeight]);
1404
+ const contentContainerStyle = useMemo7(() => [{ minHeight: Dimensions4.get("window").height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle, contentPadding], [contentInset.bottom, contentInset.top, contentPadding, externalContentContainerStyle, pullSearchMinHeight]);
1387
1405
  const handleScroll = useScrollHandler({ capturedGeneration, chipHidden, chipThreshold, footerFixed, headerFixed, listGeneration });
1388
1406
  const handleScrollToTop = useCallback10(() => {
1389
1407
  const offset = pullSearchHeight ? -contentInset.top + pullSearchHeight : -contentInset.top;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rific/scroll-view",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Blur styled scroll system for React Native. Floating headers/footers, keyboard awareness, and scroll-away chrome",
5
5
  "keywords": [
6
6
  "react-native",
@@ -27,7 +27,7 @@
27
27
  "type": "commonjs",
28
28
  "exports": {
29
29
  ".": {
30
- "react-native": "./dist/index.js",
30
+ "react-native": "./src/index.ts",
31
31
  "types": "./dist/index.d.ts",
32
32
  "import": "./dist/index.mjs",
33
33
  "require": "./dist/index.js"
@@ -35,7 +35,7 @@ const CustomListInner = <P extends object>({ chipProps, chipThreshold, component
35
35
  // Intercept ListHeaderComponent so useScrollInit can apply the same 2-phase measurement as FlatList.
36
36
  const { ListHeaderComponent: listHeaderComponent, contentContainerStyle: externalContentContainerStyle, ...restProps } = props as Record<string, unknown>
37
37
 
38
- const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, keyboardAware, pullSearchHeight, style })
38
+ const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, contentPadding, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, keyboardAware, pullSearchHeight, style })
39
39
  const { listGeneration, onListUnmount } = useContext(ScrollViewContext)
40
40
  // -1 never matches a real generation (starts at 0, only increments) — the runOnUI call below
41
41
  // corrects it before any scroll event could observe the placeholder.
@@ -106,7 +106,7 @@ const CustomListInner = <P extends object>({ chipProps, chipThreshold, component
106
106
  )
107
107
  }, [pullSearchHeader, renderFilters])
108
108
 
109
- const contentContainerStyle = useMemo(() => [{ minHeight: Dimensions.get('window').height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle], [contentInset.bottom, contentInset.top, externalContentContainerStyle, pullSearchMinHeight])
109
+ const contentContainerStyle = useMemo(() => [{ minHeight: Dimensions.get('window').height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle, contentPadding], [contentInset.bottom, contentInset.top, contentPadding, externalContentContainerStyle, pullSearchMinHeight])
110
110
 
111
111
  const onScroll = useScrollHandler({ capturedGeneration, chipHidden, chipThreshold, footerFixed, headerFixed, listGeneration, onRemountSyncRetry, onRemountSynced, remountSyncTarget })
112
112
 
package/src/FlatList.tsx CHANGED
@@ -78,7 +78,7 @@ const FlatListInner = <T,>({ chipProps, chipThreshold, columnWrapperStyle, conte
78
78
  [cols, columnWrapperStyle, renderItem]
79
79
  )
80
80
 
81
- const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, isHorizontal, keyboardAware, pullSearchHeight, style })
81
+ const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, contentPadding, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, isHorizontal, keyboardAware, pullSearchHeight, style })
82
82
  const { jsListGeneration, onJsListUnmount } = useContext(ScrollViewContext)
83
83
  // Plain ref, not a SharedValue: see the comment on ScrollViewContextType.jsListGeneration for why.
84
84
  const capturedGeneration = useRef(jsListGeneration.current)
@@ -120,7 +120,7 @@ const FlatListInner = <T,>({ chipProps, chipThreshold, columnWrapperStyle, conte
120
120
  )
121
121
  }, [pullSearchHeader, renderFilters])
122
122
 
123
- const contentContainerStyle = useMemo(() => (isHorizontal ? externalContentContainerStyle : [{ minHeight: Dimensions.get('window').height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle]), [isHorizontal, contentInset.bottom, contentInset.top, externalContentContainerStyle, pullSearchMinHeight])
123
+ const contentContainerStyle = useMemo(() => (isHorizontal ? externalContentContainerStyle : [{ minHeight: Dimensions.get('window').height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle, contentPadding]), [isHorizontal, contentInset.bottom, contentInset.top, contentPadding, externalContentContainerStyle, pullSearchMinHeight])
124
124
 
125
125
  const scrollHandler = useScrollHandlerJS({ capturedGeneration, chipHidden, chipThreshold, footerFixed, headerFixed, isHorizontal, jsListGeneration, remountTarget, scrollTo })
126
126
 
@@ -26,11 +26,11 @@ export type ScrollViewProps = RNScrollViewProps & {
26
26
  refreshing?: boolean
27
27
  }
28
28
 
29
- const ScrollViewInner = ({ chipProps, chipThreshold, children, footerFixed: footerFixedProp, gesture, headerFixed: headerFixedProp, horizontal, keyboardAware, onChipPress, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight, refreshing, style, ...props }: ScrollViewProps) => {
29
+ const ScrollViewInner = ({ chipProps, chipThreshold, children, contentContainerStyle: externalContentContainerStyle, footerFixed: footerFixedProp, gesture, headerFixed: headerFixedProp, horizontal, keyboardAware, onChipPress, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight, refreshing, style, ...props }: ScrollViewProps) => {
30
30
  const scrollView = useRef<RNScrollView>(null)
31
31
  const isHorizontal = horizontal === true
32
32
 
33
- const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, isHorizontal, keyboardAware, pullSearchHeight, style })
33
+ const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, contentPadding, footerFixed, headerFixed } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, isHorizontal, keyboardAware, pullSearchHeight, style })
34
34
  const { listGeneration, onListUnmount } = useContext(ScrollViewContext)
35
35
  // -1 never matches a real generation (starts at 0, only increments) — the runOnUI call below
36
36
  // corrects it before any scroll event could observe the placeholder.
@@ -88,6 +88,7 @@ const ScrollViewInner = ({ chipProps, chipThreshold, children, footerFixed: foot
88
88
  <View style={containerStyle}>
89
89
  <Animated.ScrollView
90
90
  {...props}
91
+ contentContainerStyle={contentPadding ? [externalContentContainerStyle, contentPadding] : externalContentContainerStyle}
91
92
  contentInset={contentInset}
92
93
  contentOffset={contentOffset}
93
94
  onMomentumScrollEnd={handleMomentumScrollEnd}
@@ -34,7 +34,7 @@ const SectionListInner = <ItemT, SectionT extends { data: ReadonlyArray<ItemT> }
34
34
 
35
35
  const { stickySectionHeadersEnabled = true, renderSectionHeader, ...passThroughProps } = props as RNSectionListProps<ItemT, SectionT>
36
36
 
37
- const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed, headerHeight } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, keyboardAware, pullSearchHeight, style })
37
+ const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, contentPadding, footerFixed, headerFixed, headerHeight } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, keyboardAware, pullSearchHeight, style })
38
38
  const { listGeneration, onListUnmount } = useContext(ScrollViewContext)
39
39
  // -1 never matches a real generation (starts at 0, only increments) — the runOnUI call below
40
40
  // corrects it before any scroll event could observe the placeholder.
@@ -144,7 +144,7 @@ const SectionListInner = <ItemT, SectionT extends { data: ReadonlyArray<ItemT> }
144
144
  return () => cancelAnimationFrame(handle)
145
145
  }, [activeListHeader, usesCustomSticky, measureHeader])
146
146
 
147
- const contentContainerStyle = useMemo(() => [{ minHeight: Dimensions.get('window').height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle], [contentInset.bottom, contentInset.top, externalContentContainerStyle, pullSearchMinHeight])
147
+ const contentContainerStyle = useMemo(() => [{ minHeight: Dimensions.get('window').height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle, contentPadding], [contentInset.bottom, contentInset.top, contentPadding, externalContentContainerStyle, pullSearchMinHeight])
148
148
 
149
149
  const handleScroll = useScrollHandler({ capturedGeneration, chipHidden, chipThreshold, footerFixed, headerFixed, listGeneration })
150
150
 
@@ -0,0 +1,8 @@
1
+ import { Platform } from 'react-native'
2
+
3
+ // iOS positions content under the translucent bars with contentInset/contentOffset. Android and
4
+ // react-native-web silently ignore both, so on those platforms the same geometry is emulated with
5
+ // content-container padding, and the scroll handlers normalize offsets back into inset space
6
+ // (rest position = -headerHeight) so every downstream calculation keeps one coordinate system.
7
+ // Pull-search relies on iOS overscroll physics and stays disabled outside inset mode.
8
+ export const usesContentInset = Platform.OS === 'ios'
@@ -4,6 +4,7 @@ import { runOnJS, useAnimatedScrollHandler, useSharedValue, withTiming } from 'r
4
4
 
5
5
  import { ScrollViewContext } from '../ScrollViewContext'
6
6
  import { REMOUNT_SYNC_TOLERANCE } from '../useScrollInit'
7
+ import { usesContentInset } from './insetMode'
7
8
 
8
9
  type UseScrollHandlerOptions = {
9
10
  capturedGeneration: SharedValue<number>
@@ -43,9 +44,12 @@ export function useScrollHandler({ capturedGeneration, chipHidden, chipThreshold
43
44
  chipHidden.value = x < chipThreshold ? 1 : 0
44
45
  return
45
46
  }
46
- const delta = y - scrollPosition.value
47
- scrollPosition.value = y
48
- chipHidden.value = y < chipThreshold ? 1 : 0
47
+ // Normalize into inset space (rest = -headerHeight) on platforms that emulate the bars
48
+ // with content padding; bounce detection below stays in the raw space contentSize lives in.
49
+ const yn = usesContentInset ? y : y - headerHeightShared.value
50
+ const delta = yn - scrollPosition.value
51
+ scrollPosition.value = yn
52
+ chipHidden.value = yn < chipThreshold ? 1 : 0
49
53
  const maxScroll = contentHeight - layoutHeight
50
54
  if (maxScroll > 0 && y >= maxScroll) {
51
55
  fromBottomBounce.value = true
@@ -56,18 +60,18 @@ export function useScrollHandler({ capturedGeneration, chipHidden, chipThreshold
56
60
  const snapHeader = snapBackHeaderShared.value && !headerFixed
57
61
  const snapFooter = snapBackFooterShared.value && !footerFixed
58
62
  if (snapHeader || snapFooter) {
59
- if (y <= -headerHeightShared.value) {
63
+ if (yn <= -headerHeightShared.value) {
60
64
  snapUpAccum.value = 0
61
65
  if (snapHeader) headerOffset.value = 0
62
66
  if (snapFooter) footerOffset.value = 0
63
67
  } else if (delta > 0) {
64
68
  snapUpAccum.value = 0
65
- if (y >= -headerHeightShared.value + pullSearchHeightShared.value) {
69
+ if (yn >= -headerHeightShared.value + pullSearchHeightShared.value) {
66
70
  if (snapHeader) headerOffset.value = Math.max(-headerHeightShared.value, Math.min(0, headerOffset.value - delta))
67
71
  if (snapFooter) footerOffset.value = Math.max(0, Math.min(footerHeightShared.value, footerOffset.value + delta))
68
72
  }
69
73
  } else if (delta < 0 && !fromBottomBounce.value) {
70
- if (y >= -headerHeightShared.value + pullSearchHeightShared.value) snapUpAccum.value -= delta
74
+ if (yn >= -headerHeightShared.value + pullSearchHeightShared.value) snapUpAccum.value -= delta
71
75
  if (snapUpAccum.value >= 10) {
72
76
  snapUpAccum.value = 999
73
77
  if (snapHeader) headerOffset.value = withTiming(0, { duration: 200 })
@@ -4,6 +4,7 @@ import type { SharedValue } from 'react-native-reanimated'
4
4
  import { withTiming } from 'react-native-reanimated'
5
5
 
6
6
  import { ScrollViewContext } from '../ScrollViewContext'
7
+ import { usesContentInset } from './insetMode'
7
8
 
8
9
  const REMOUNT_RETRY_TOLERANCE = 1
9
10
  const REMOUNT_RETRY_MAX_ATTEMPTS = 8
@@ -73,9 +74,12 @@ export function useScrollHandlerJS({ capturedGeneration, chipHidden, chipThresho
73
74
  chipHidden.value = x < chipThreshold ? 1 : 0
74
75
  return
75
76
  }
76
- const delta = y - scrollPosition.value
77
- scrollPosition.value = y
78
- chipHidden.value = y < chipThreshold ? 1 : 0
77
+ // Same inset-space normalization as the worklet handler: rest = -headerHeight everywhere,
78
+ // raw space only for bounce detection against contentSize.
79
+ const yn = usesContentInset ? y : y - headerHeightShared.value
80
+ const delta = yn - scrollPosition.value
81
+ scrollPosition.value = yn
82
+ chipHidden.value = yn < chipThreshold ? 1 : 0
79
83
  const maxScroll = contentHeight - layoutHeight
80
84
  if (maxScroll > 0 && y >= maxScroll) {
81
85
  fromBottomBounce.current = true
@@ -86,18 +90,18 @@ export function useScrollHandlerJS({ capturedGeneration, chipHidden, chipThresho
86
90
  const snapHeader = snapBackHeaderShared.value && !headerFixed
87
91
  const snapFooter = snapBackFooterShared.value && !footerFixed
88
92
  if (snapHeader || snapFooter) {
89
- if (y <= -headerHeightShared.value) {
93
+ if (yn <= -headerHeightShared.value) {
90
94
  snapUpAccum.current = 0
91
95
  if (snapHeader) headerOffset.value = 0
92
96
  if (snapFooter) footerOffset.value = 0
93
97
  } else if (delta > 0) {
94
98
  snapUpAccum.current = 0
95
- if (y >= -headerHeightShared.value + pullSearchHeightShared.value) {
99
+ if (yn >= -headerHeightShared.value + pullSearchHeightShared.value) {
96
100
  if (snapHeader) headerOffset.value = Math.max(-headerHeightShared.value, Math.min(0, headerOffset.value - delta))
97
101
  if (snapFooter) footerOffset.value = Math.max(0, Math.min(footerHeightShared.value, footerOffset.value + delta))
98
102
  }
99
103
  } else if (delta < 0 && !fromBottomBounce.current) {
100
- if (y >= -headerHeightShared.value + pullSearchHeightShared.value) snapUpAccum.current -= delta
104
+ if (yn >= -headerHeightShared.value + pullSearchHeightShared.value) snapUpAccum.current -= delta
101
105
  if (snapUpAccum.current >= 10) {
102
106
  snapUpAccum.current = 999
103
107
  if (snapHeader) headerOffset.value = withTiming(0, { duration: 200 })
@@ -5,6 +5,10 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'
5
5
 
6
6
  import { ScrollViewContext } from '../ScrollViewContext'
7
7
  import { useKeyboardInset } from '../useKeyboardInset'
8
+ import { usesContentInset } from './insetMode'
9
+
10
+ const ZERO_INSET = { bottom: 0, top: 0 }
11
+ const ZERO_OFFSET = { x: 0, y: 0 }
8
12
 
9
13
  const CHIP_SLIDE = 48
10
14
 
@@ -26,7 +30,7 @@ export function useScrollList({ footerFixed: footerFixedProp, headerFixed: heade
26
30
  const footerFixed = isHorizontal ? true : (footerFixedProp ?? contextFooterFixed)
27
31
 
28
32
  useEffect(() => {
29
- pullSearchHeightShared.value = pullSearchHeight ?? 0
33
+ pullSearchHeightShared.value = usesContentInset ? (pullSearchHeight ?? 0) : 0
30
34
  }, [pullSearchHeight, pullSearchHeightShared])
31
35
 
32
36
  // First render (headerHeight===0): flex:1, hidden until header measures so the key=0→1
@@ -34,14 +38,20 @@ export function useScrollList({ footerFixed: footerFixedProp, headerFixed: heade
34
38
  // After header measures: absoluteFill with correct contentOffset — then reveal.
35
39
  const containerStyle = useMemo(() => (headerHeight === null ? [{ flex: 1, opacity: 0 }, style] : [StyleSheet.absoluteFill, style]), [headerHeight, style])
36
40
 
37
- const contentInset = useMemo(() => ({ bottom: (footerFixed ? footerHeight || insets.bottom : insets.bottom) + (keyboardAware ? keyboardHeight : 0), top: headerHeight ?? 0 }), [footerFixed, footerHeight, insets.bottom, headerHeight, keyboardAware, keyboardHeight])
41
+ const insetGeometry = useMemo(() => ({ bottom: (footerFixed ? footerHeight || insets.bottom : insets.bottom) + (keyboardAware ? keyboardHeight : 0), top: headerHeight ?? 0 }), [footerFixed, footerHeight, insets.bottom, headerHeight, keyboardAware, keyboardHeight])
42
+ // Outside inset mode the same geometry is applied as content padding instead; contentInset is
43
+ // zeroed (not omitted) so consumers' offset math — scroll-to-top targets, minHeight — stays
44
+ // correct in the raw 0-based coordinate space those platforms actually scroll in.
45
+ const contentInset = usesContentInset ? insetGeometry : ZERO_INSET
46
+ const contentPadding = useMemo(() => (usesContentInset || isHorizontal ? null : { paddingBottom: insetGeometry.bottom, paddingTop: insetGeometry.top }), [insetGeometry, isHorizontal])
38
47
 
39
48
  // Use hideY offset only when this component mounted with headerHeight already known (mode switch).
40
49
  // On initial load, headerHeight is null at mount so we start at showY and let useScrollInit
41
50
  // call scrollTo after the phase transition. On mode switch, headerHeight is already set so we
42
51
  // can position the FlatList at hideY atomically via contentOffset — no scrollTo needed.
43
52
  const [startedWithHeader] = useState(headerHeight !== null)
44
- const contentOffset = useMemo(() => ({ x: 0, y: headerHeight !== null ? -headerHeight + (startedWithHeader && !isHorizontal && pullSearchHeight ? pullSearchHeight : 0) : 0 }), [headerHeight, isHorizontal, pullSearchHeight, startedWithHeader])
53
+ const insetContentOffset = useMemo(() => ({ x: 0, y: headerHeight !== null ? -headerHeight + (startedWithHeader && !isHorizontal && pullSearchHeight ? pullSearchHeight : 0) : 0 }), [headerHeight, isHorizontal, pullSearchHeight, startedWithHeader])
54
+ const contentOffset = usesContentInset ? insetContentOffset : ZERO_OFFSET
45
55
 
46
56
  const chipHidden = useSharedValue(1)
47
57
  const chipStyle = useAnimatedStyle(() => {
@@ -65,5 +75,5 @@ export function useScrollList({ footerFixed: footerFixedProp, headerFixed: heade
65
75
  }
66
76
  }, [isHorizontal, headerFixed, headerHeight, insets.top])
67
77
 
68
- return { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, footerFixed, headerFixed, headerHeight, insets, isHorizontal: isHorizontal ?? false }
78
+ return { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, contentPadding, footerFixed, headerFixed, headerHeight, insets, isHorizontal: isHorizontal ?? false }
69
79
  }
@@ -1,8 +1,9 @@
1
1
  import { useState } from 'react'
2
+ import { Platform } from 'react-native'
2
3
  import { useKeyboardHandler } from 'react-native-keyboard-controller'
3
4
  import { runOnJS } from 'react-native-reanimated'
4
5
 
5
- export const useKeyboardInset = () => {
6
+ const useKeyboardInsetNative = () => {
6
7
  const [keyboardHeight, setKeyboardHeight] = useState(0)
7
8
  useKeyboardHandler(
8
9
  {
@@ -19,3 +20,9 @@ export const useKeyboardInset = () => {
19
20
  )
20
21
  return keyboardHeight
21
22
  }
23
+
24
+ const useKeyboardInsetWeb = () => 0
25
+
26
+ // react-native-keyboard-controller has no web implementation — its handler hook must never be
27
+ // called there. Platform is constant for the life of the bundle, so this select is hook-safe.
28
+ export const useKeyboardInset = Platform.OS === 'web' ? useKeyboardInsetWeb : useKeyboardInsetNative
@@ -2,6 +2,7 @@ import { type ComponentType, createElement, type ReactElement, useCallback, useC
2
2
  import type { NativeScrollEvent, NativeSyntheticEvent } from 'react-native'
3
3
  import { type SharedValue, useSharedValue } from 'react-native-reanimated'
4
4
 
5
+ import { usesContentInset } from './internal/insetMode'
5
6
  import { ScrollViewContext } from './ScrollViewContext'
6
7
 
7
8
  const REMOUNT_SYNC_MAX_ATTEMPTS = 8
@@ -41,8 +42,10 @@ export type UseScrollInitResult = {
41
42
  scrollReady: boolean
42
43
  }
43
44
 
44
- export function useScrollInit({ listHeaderComponent, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight, scrollTo }: UseScrollInitOptions): UseScrollInitResult {
45
+ export function useScrollInit({ listHeaderComponent, onMomentumScrollEnd: externalMomentumScrollEnd, onRefresh, onScrollBeginDrag: externalScrollBeginDrag, onScrollEndDrag: externalScrollEndDrag, pullSearchHeight: pullSearchHeightProp, scrollTo }: UseScrollInitOptions): UseScrollInitResult {
45
46
  const { headerHeight, scrollPosition, setProgressing } = useContext(ScrollViewContext)
47
+ // Pull-search rides iOS overscroll physics; outside inset mode it never engages.
48
+ const pullSearchHeight = usesContentInset ? pullSearchHeightProp : undefined
46
49
  const hasPullSearch = pullSearchHeight !== undefined
47
50
  // Start 'ready' immediately if headerHeight is already known (e.g. FlatList remount on mode
48
51
  // switch). Only enter 'measuring' on the very first mount when headerHeight hasn't been set yet.
@@ -115,7 +118,9 @@ export function useScrollInit({ listHeaderComponent, onMomentumScrollEnd: extern
115
118
  // for the first render), so the list stays hidden until a confirmed onScroll event reports the
116
119
  // intended resting position, re-issuing the imperative scrollTo in between as needed. This
117
120
  // replaces trying to guess how many spurious events to skip.
118
- const [remountTarget] = useState<number | null>(() => (headerHeight !== null ? -headerHeight + (pullSearchHeight ?? 0) : null))
121
+ // Remount sync exists for Fabric/iOS contentOffset unreliability; padding platforms mount at a
122
+ // plain 0 offset and need none of it.
123
+ const [remountTarget] = useState<number | null>(() => (usesContentInset && headerHeight !== null ? -headerHeight + (pullSearchHeight ?? 0) : null))
119
124
  const remountSyncTarget = useSharedValue<number | null>(remountTarget)
120
125
  const [scrollReady, setScrollReady] = useState(remountTarget === null)
121
126
  const remountAttempts = useRef(0)
@@ -195,7 +200,8 @@ export function useScrollInit({ listHeaderComponent, onMomentumScrollEnd: extern
195
200
  headerInitialized.current = true
196
201
  const target = remountTarget ?? -headerHeight
197
202
  scrollPosition.value = target
198
- scrollTo(target, false)
203
+ // Imperative scroll targets are raw-space: inset mode rests at -headerHeight, padding mode at 0.
204
+ scrollTo(usesContentInset ? target : 0, false)
199
205
  if (remountTarget === null) return
200
206
  let framesLeft = REMOUNT_PRIME_FRAMES
201
207
  let handle: number | null = null