@rific/scroll-view 0.8.3 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -38,6 +38,9 @@ import { runOnJS, useAnimatedScrollHandler, useSharedValue as useSharedValue2, w
38
38
  import { createContext } from "react";
39
39
  var ScrollViewContext = createContext({
40
40
  blur: true,
41
+ chromeHosted: false,
42
+ chromeOverhang: 0,
43
+ chromeWritable: null,
41
44
  footerAboveKeyboard: false,
42
45
  footerHeight: null,
43
46
  footerHeightShared: null,
@@ -68,6 +71,7 @@ var ScrollViewContext = createContext({
68
71
  },
69
72
  snapBackFooterShared: null,
70
73
  snapBackHeaderShared: null,
74
+ stackHeightShared: null,
71
75
  tabBarHeight: 0
72
76
  });
73
77
 
@@ -259,9 +263,20 @@ function useScrollInit({ listHeaderComponent, onMomentumScrollEnd: externalMomen
259
263
  return { activeListHeader, handleMomentumScrollEnd, handleScrollBeginDrag, handleScrollEndDrag, hiddenHeader, onRemountSyncRetry, onRemountSynced, pullSearchMinHeight: pullSearchHeight ?? 0, remountSyncTarget, remountTarget, scrollReady };
260
264
  }
261
265
 
266
+ // src/internal/chrome.ts
267
+ var CHROME_SLACK = 2;
268
+ var chromeTravel = (maxScroll, headerHeight) => {
269
+ "worklet";
270
+ return usesContentInset ? maxScroll + headerHeight : maxScroll;
271
+ };
272
+ var chromeSettleTarget = (offset, stackHeight) => {
273
+ "worklet";
274
+ return offset > stackHeight / 2 ? stackHeight : 0;
275
+ };
276
+
262
277
  // src/internal/useScrollHandler.ts
263
278
  function useScrollHandler({ capturedGeneration, chipHidden, chipThreshold = 100, footerFixed, headerFixed, isHorizontal, listGeneration, onRemountSyncRetry, onRemountSynced, remountSyncTarget }) {
264
- const { footerHeightShared, footerOffset, headerHeightShared, headerOffset, pullSearchHeightShared, scrollPosition, snapBackFooterShared, snapBackHeaderShared } = useContext2(ScrollViewContext);
279
+ const { chromeHosted, chromeWritable, footerOffset, headerHeightShared, headerOffset, pullSearchHeightShared, scrollPosition, snapBackFooterShared, snapBackHeaderShared, stackHeightShared } = useContext2(ScrollViewContext);
265
280
  const snapUpAccum = useSharedValue2(0);
266
281
  const fromBottomBounce = useSharedValue2(false);
267
282
  return useAnimatedScrollHandler(
@@ -296,7 +311,9 @@ function useScrollHandler({ capturedGeneration, chipHidden, chipThreshold = 100,
296
311
  fromBottomBounce.value = false;
297
312
  }
298
313
  const snapHeader = snapBackHeaderShared.value && !headerFixed;
299
- const snapFooter = snapBackFooterShared.value && !footerFixed;
314
+ const hideable = !chromeHosted || chromeTravel(maxScroll, headerHeightShared.value) >= stackHeightShared.value;
315
+ if (chromeHosted && !hideable && chromeWritable.value && footerOffset.value !== 0) footerOffset.value = 0;
316
+ const snapFooter = snapBackFooterShared.value && !footerFixed && chromeWritable.value && hideable;
300
317
  if (snapHeader || snapFooter) {
301
318
  if (yn <= -headerHeightShared.value) {
302
319
  snapUpAccum.value = 0;
@@ -306,7 +323,7 @@ function useScrollHandler({ capturedGeneration, chipHidden, chipThreshold = 100,
306
323
  snapUpAccum.value = 0;
307
324
  if (yn >= -headerHeightShared.value + pullSearchHeightShared.value) {
308
325
  if (snapHeader) headerOffset.value = Math.max(-headerHeightShared.value, Math.min(0, headerOffset.value - delta));
309
- if (snapFooter) footerOffset.value = Math.max(0, Math.min(footerHeightShared.value, footerOffset.value + delta));
326
+ if (snapFooter) footerOffset.value = Math.max(0, Math.min(stackHeightShared.value, footerOffset.value + delta));
310
327
  }
311
328
  } else if (delta < 0 && !fromBottomBounce.value) {
312
329
  if (yn >= -headerHeightShared.value + pullSearchHeightShared.value) snapUpAccum.value -= delta;
@@ -323,21 +340,29 @@ function useScrollHandler({ capturedGeneration, chipHidden, chipThreshold = 100,
323
340
  fromBottomBounce.value = false;
324
341
  snapUpAccum.value = 0;
325
342
  },
326
- onEndDrag: ({ contentOffset: { y }, contentSize: { height: contentHeight }, layoutMeasurement: { height: layoutHeight } }) => {
343
+ onEndDrag: ({ contentOffset: { y }, contentSize: { height: contentHeight }, layoutMeasurement: { height: layoutHeight }, velocity }) => {
327
344
  "worklet";
328
345
  if (isHorizontal) return;
346
+ const revealing = snapUpAccum.value >= 10;
329
347
  if (y >= contentHeight - layoutHeight - 10) {
330
348
  fromBottomBounce.value = true;
331
349
  snapUpAccum.value = 0;
332
350
  }
351
+ if (chromeHosted && !footerFixed && chromeWritable.value && (velocity === void 0 || Math.abs(velocity.y) < 0.1)) {
352
+ footerOffset.value = withTiming(revealing ? 0 : chromeSettleTarget(footerOffset.value, stackHeightShared.value), { duration: 200 });
353
+ }
333
354
  },
334
355
  onMomentumEnd: () => {
335
356
  "worklet";
357
+ const revealing = snapUpAccum.value >= 10;
336
358
  fromBottomBounce.value = false;
337
359
  snapUpAccum.value = 0;
360
+ if (chromeHosted && !isHorizontal && !footerFixed && chromeWritable.value) {
361
+ footerOffset.value = withTiming(revealing ? 0 : chromeSettleTarget(footerOffset.value, stackHeightShared.value), { duration: 200 });
362
+ }
338
363
  }
339
364
  },
340
- [capturedGeneration, headerFixed, footerFixed, isHorizontal, chipThreshold, listGeneration, onRemountSyncRetry, onRemountSynced, remountSyncTarget]
365
+ [capturedGeneration, chromeHosted, headerFixed, footerFixed, isHorizontal, chipThreshold, listGeneration, onRemountSyncRetry, onRemountSynced, remountSyncTarget]
341
366
  );
342
367
  }
343
368
 
@@ -379,18 +404,20 @@ var CHIP_SLIDE = 48;
379
404
  function useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, hideUntilMeasured, isHorizontal, keyboardAware, pullSearchHeight, style } = {}) {
380
405
  const insets = useSafeAreaInsets();
381
406
  const keyboardHeight = useKeyboardInset();
382
- const { footerAboveKeyboard, footerHeight, footerFixed: contextFooterFixed, headerHeight, headerHeightShared, headerFixed: contextHeaderFixed, headerOffset, pullSearchHeightShared, scrollPosition, snapBackHeaderShared, tabBarHeight } = useContext3(ScrollViewContext);
407
+ const { chromeHosted, chromeOverhang, footerAboveKeyboard, footerHeight, footerFixed: contextFooterFixed, headerHeight, headerHeightShared, headerFixed: contextHeaderFixed, headerOffset, pullSearchHeightShared, scrollPosition, snapBackHeaderShared, tabBarHeight } = useContext3(ScrollViewContext);
383
408
  const headerFixed = isHorizontal ? true : headerFixedProp ?? contextHeaderFixed;
384
- const footerFixed = isHorizontal ? true : footerFixedProp ?? contextFooterFixed;
409
+ const footerFixed = isHorizontal ? true : chromeHosted ? contextFooterFixed : footerFixedProp ?? contextFooterFixed;
385
410
  useEffect2(() => {
386
411
  pullSearchHeightShared.value = usesContentInset ? pullSearchHeight ?? 0 : 0;
387
412
  }, [pullSearchHeight, pullSearchHeightShared]);
388
413
  const containerStyle = useMemo(() => headerHeight === null ? [{ flex: 1, opacity: hideUntilMeasured ? 0 : 1 }, style] : [StyleSheet2.absoluteFill, style], [headerHeight, hideUntilMeasured, style]);
389
414
  const insetGeometry = useMemo(() => {
390
- const footerReserve = footerFixed ? footerHeight || insets.bottom : insets.bottom;
391
- const bottom = (!keyboardAware || footerFixed && footerAboveKeyboard ? footerReserve : Math.max(footerReserve, keyboardHeight)) + tabBarHeight;
415
+ const baseReserve = chromeHosted ? chromeOverhang : insets.bottom;
416
+ const footerReserve = chromeHosted ? Math.max(footerHeight ?? 0, baseReserve) : footerFixed ? footerHeight || baseReserve : baseReserve;
417
+ const barReserve = chromeHosted && footerFixed && footerAboveKeyboard ? Math.max(tabBarHeight - keyboardHeight, 0) : tabBarHeight;
418
+ const bottom = (!keyboardAware || footerFixed && footerAboveKeyboard ? footerReserve : Math.max(footerReserve, keyboardHeight)) + barReserve;
392
419
  return { bottom, top: headerHeight ?? 0 };
393
- }, [footerAboveKeyboard, footerFixed, footerHeight, insets.bottom, tabBarHeight, headerHeight, keyboardAware, keyboardHeight]);
420
+ }, [chromeHosted, chromeOverhang, footerAboveKeyboard, footerFixed, footerHeight, insets.bottom, tabBarHeight, headerHeight, keyboardAware, keyboardHeight]);
394
421
  const contentInset = usesContentInset ? insetGeometry : ZERO_INSET;
395
422
  const contentPadding = useMemo(() => usesContentInset || isHorizontal ? null : { paddingBottom: insetGeometry.bottom, paddingTop: insetGeometry.top }, [insetGeometry, isHorizontal]);
396
423
  const [startedWithHeader] = useState3(headerHeight !== null);
@@ -579,7 +606,7 @@ import { withTiming as withTiming3 } from "react-native-reanimated";
579
606
  var REMOUNT_RETRY_TOLERANCE = 1;
580
607
  var REMOUNT_RETRY_MAX_ATTEMPTS = 8;
581
608
  function useScrollHandlerJS({ capturedGeneration, chipHidden, chipThreshold = 100, footerFixed, headerFixed, isHorizontal, jsListGeneration, remountTarget = null, scrollTo }) {
582
- const { footerHeightShared, footerOffset, headerHeightShared, headerOffset, pullSearchHeightShared, scrollPosition, snapBackFooterShared, snapBackHeaderShared } = useContext6(ScrollViewContext);
609
+ const { chromeHosted, chromeWritable, footerOffset, headerHeightShared, headerOffset, pullSearchHeightShared, scrollPosition, snapBackFooterShared, snapBackHeaderShared, stackHeightShared } = useContext6(ScrollViewContext);
583
610
  const snapUpAccum = useRef3(0);
584
611
  const fromBottomBounce = useRef3(false);
585
612
  const pendingRemountTarget = useRef3(remountTarget);
@@ -623,7 +650,9 @@ function useScrollHandlerJS({ capturedGeneration, chipHidden, chipThreshold = 10
623
650
  fromBottomBounce.current = false;
624
651
  }
625
652
  const snapHeader = snapBackHeaderShared.value && !headerFixed;
626
- const snapFooter = snapBackFooterShared.value && !footerFixed;
653
+ const hideable = !chromeHosted || chromeTravel(maxScroll, headerHeightShared.value) >= stackHeightShared.value;
654
+ if (chromeHosted && !hideable && chromeWritable.value && footerOffset.value !== 0) footerOffset.value = 0;
655
+ const snapFooter = snapBackFooterShared.value && !footerFixed && chromeWritable.value && hideable;
627
656
  if (snapHeader || snapFooter) {
628
657
  if (yn <= -headerHeightShared.value) {
629
658
  snapUpAccum.current = 0;
@@ -633,7 +662,7 @@ function useScrollHandlerJS({ capturedGeneration, chipHidden, chipThreshold = 10
633
662
  snapUpAccum.current = 0;
634
663
  if (yn >= -headerHeightShared.value + pullSearchHeightShared.value) {
635
664
  if (snapHeader) headerOffset.value = Math.max(-headerHeightShared.value, Math.min(0, headerOffset.value - delta));
636
- if (snapFooter) footerOffset.value = Math.max(0, Math.min(footerHeightShared.value, footerOffset.value + delta));
665
+ if (snapFooter) footerOffset.value = Math.max(0, Math.min(stackHeightShared.value, footerOffset.value + delta));
637
666
  }
638
667
  } else if (delta < 0 && !fromBottomBounce.current) {
639
668
  if (yn >= -headerHeightShared.value + pullSearchHeightShared.value) snapUpAccum.current -= delta;
@@ -645,7 +674,7 @@ function useScrollHandlerJS({ capturedGeneration, chipHidden, chipThreshold = 10
645
674
  }
646
675
  }
647
676
  },
648
- [capturedGeneration, chipHidden, chipThreshold, footerFixed, footerHeightShared, footerOffset, headerFixed, headerHeightShared, headerOffset, isHorizontal, jsListGeneration, pullSearchHeightShared, scrollPosition, scrollTo, snapBackFooterShared, snapBackHeaderShared]
677
+ [capturedGeneration, chipHidden, chipThreshold, chromeHosted, chromeWritable, footerFixed, footerOffset, headerFixed, headerHeightShared, headerOffset, isHorizontal, jsListGeneration, pullSearchHeightShared, scrollPosition, scrollTo, snapBackFooterShared, snapBackHeaderShared, stackHeightShared]
649
678
  );
650
679
  const onScrollBeginDrag = useCallback3(() => {
651
680
  fromBottomBounce.current = false;
@@ -653,25 +682,37 @@ function useScrollHandlerJS({ capturedGeneration, chipHidden, chipThreshold = 10
653
682
  }, []);
654
683
  const onMomentumScrollBegin = useCallback3(() => {
655
684
  }, []);
685
+ const settle = useCallback3(
686
+ (revealing) => {
687
+ if (!chromeHosted || footerFixed || !chromeWritable.value) return;
688
+ footerOffset.value = withTiming3(revealing ? 0 : chromeSettleTarget(footerOffset.value, stackHeightShared.value), { duration: 200 });
689
+ },
690
+ [chromeHosted, chromeWritable, footerFixed, footerOffset, stackHeightShared]
691
+ );
656
692
  const onScrollEndDrag = useCallback3(
657
693
  (event) => {
658
694
  if (isHorizontal) return;
659
695
  const {
660
696
  contentOffset: { y },
661
697
  contentSize: { height: contentHeight },
662
- layoutMeasurement: { height: layoutHeight }
698
+ layoutMeasurement: { height: layoutHeight },
699
+ velocity
663
700
  } = event.nativeEvent;
701
+ const revealing = snapUpAccum.current >= 10;
664
702
  if (y >= contentHeight - layoutHeight - 10) {
665
703
  fromBottomBounce.current = true;
666
704
  snapUpAccum.current = 0;
667
705
  }
706
+ if (velocity === void 0 || Math.abs(velocity.y) < 0.1) settle(revealing);
668
707
  },
669
- [isHorizontal]
708
+ [isHorizontal, settle]
670
709
  );
671
710
  const onMomentumScrollEnd = useCallback3(() => {
711
+ const revealing = snapUpAccum.current >= 10;
672
712
  fromBottomBounce.current = false;
673
713
  snapUpAccum.current = 0;
674
- }, []);
714
+ if (!isHorizontal) settle(revealing);
715
+ }, [isHorizontal, settle]);
675
716
  return { onMomentumScrollBegin, onMomentumScrollEnd, onScroll, onScrollBeginDrag, onScrollEndDrag };
676
717
  }
677
718
 
@@ -859,7 +900,7 @@ var PullSearch = forwardRef(function PullSearch2({ debounce, onChangeText, onHei
859
900
  (isHidden, wasHidden) => {
860
901
  if (isHidden && !wasHidden) runOnJS3(blurInput)();
861
902
  },
862
- // scrollPosition has to be listed too, not just read — see useStickyHeaders' useAnimatedReaction
903
+ // scrollPosition has to be listed too, not just read - see useStickyHeaders' useAnimatedReaction
863
904
  // for why: on web, without reanimated's Babel plugin active, an unlisted SharedValue mutation
864
905
  // never re-triggers this reaction (confirmed empirically elsewhere in this package).
865
906
  [barHeight, headerHeight, blurInput, scrollPosition]
@@ -1000,22 +1041,56 @@ var ScrollViewInner = ({ chipProps, chipThreshold, children, contentContainerSty
1000
1041
  };
1001
1042
  var ScrollView = memo3(ScrollViewInner);
1002
1043
 
1044
+ // src/ScrollViewChromeProvider.tsx
1045
+ import { useCallback as useCallback7, useEffect as useEffect4, useMemo as useMemo5, useState as useState5 } from "react";
1046
+ import { useSharedValue as useSharedValue6, withTiming as withTiming4 } from "react-native-reanimated";
1047
+
1048
+ // src/ScrollViewChromeContext.ts
1049
+ import { createContext as createContext2 } from "react";
1050
+ var ScrollViewChromeHostContext = createContext2(null);
1051
+ var ScrollViewChromeContext = createContext2(null);
1052
+
1053
+ // src/ScrollViewChromeProvider.tsx
1054
+ import { jsx as jsx8 } from "react/jsx-runtime";
1055
+ var REVEAL_DURATION = 200;
1056
+ var alwaysActive = () => true;
1057
+ var ScrollViewChromeProvider = ({ children, footprint, overhang = 0, snapBack = true, useIsActive = alwaysActive }) => {
1058
+ const offset = useSharedValue6(0);
1059
+ const [claims, setClaims] = useState5([]);
1060
+ const [pins, setPins] = useState5(0);
1061
+ const activeId = claims.length > 0 ? claims[claims.length - 1] : null;
1062
+ const pinned = pins > 0;
1063
+ const claim = useCallback7((id) => setClaims((current) => [...current.filter((c) => c !== id), id]), []);
1064
+ const release = useCallback7((id) => setClaims((current) => current.filter((c) => c !== id)), []);
1065
+ const addPin = useCallback7(() => setPins((current) => current + 1), []);
1066
+ const removePin = useCallback7(() => setPins((current) => Math.max(0, current - 1)), []);
1067
+ const reveal = useCallback7(() => {
1068
+ offset.value = withTiming4(0, { duration: REVEAL_DURATION });
1069
+ }, [offset]);
1070
+ useEffect4(() => {
1071
+ reveal();
1072
+ }, [activeId, pinned, reveal]);
1073
+ const host = useMemo5(() => ({ activeId, claim, footprint, offset, overhang, pinned, release, reveal, snapBack, useIsActive }), [activeId, claim, footprint, offset, overhang, pinned, release, reveal, snapBack, useIsActive]);
1074
+ const chrome = useMemo5(() => ({ addPin, footprint, offset, overhang, removePin, reveal }), [addPin, footprint, offset, overhang, removePin, reveal]);
1075
+ return /* @__PURE__ */ jsx8(ScrollViewChromeContext.Provider, { value: chrome, children: /* @__PURE__ */ jsx8(ScrollViewChromeHostContext.Provider, { value: host, children }) });
1076
+ };
1077
+
1003
1078
  // src/ScrollViewFooter.tsx
1004
1079
  import { BlurView as BlurView2 } from "@rific/auto-paper";
1005
- import { useContext as useContext10, useEffect as useEffect4, useRef as useRef7 } from "react";
1006
- import { StyleSheet as StyleSheet5, View as View5 } from "react-native";
1080
+ import { useContext as useContext10, useEffect as useEffect5, useLayoutEffect as useLayoutEffect5, useRef as useRef7 } from "react";
1081
+ import { Platform as Platform5, StyleSheet as StyleSheet5, View as View5 } from "react-native";
1007
1082
  import Animated5, { useAnimatedStyle as useAnimatedStyle4 } from "react-native-reanimated";
1008
1083
  import { useSafeAreaInsets as useSafeAreaInsets3 } from "react-native-safe-area-context";
1009
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1084
+ import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
1010
1085
  var ScrollViewFooter = ({ children, style }) => {
1011
- const { blur, footerAboveKeyboard, footerHeight, footerFixed, footerOffset, headerHeightShared, pullSearchHeightShared, scrollPosition, setFooterHeight, snapBackFooterShared, tabBarHeight } = useContext10(ScrollViewContext);
1086
+ const { blur, chromeHosted, chromeOverhang, footerAboveKeyboard, footerHeight, footerFixed, footerOffset, headerHeightShared, pullSearchHeightShared, scrollPosition, setFooterHeight, snapBackFooterShared, stackHeightShared, tabBarHeight } = useContext10(ScrollViewContext);
1012
1087
  const insets = useSafeAreaInsets3();
1013
1088
  const keyboardHeight = useKeyboardInset();
1014
1089
  const setFooterHeightRef = useRef7(setFooterHeight);
1015
- useEffect4(() => {
1090
+ useEffect5(() => {
1016
1091
  setFooterHeightRef.current = setFooterHeight;
1017
1092
  });
1018
- useEffect4(
1093
+ useEffect5(
1019
1094
  () => () => {
1020
1095
  setFooterHeightRef.current(null);
1021
1096
  },
@@ -1029,23 +1104,33 @@ var ScrollViewFooter = ({ children, style }) => {
1029
1104
  if (footerHeight !== height) setFooterHeight(height);
1030
1105
  };
1031
1106
  const footerStyle = useAnimatedStyle4(() => {
1107
+ if (chromeHosted) return { transform: [{ translateY: Math.min(Math.max(footerOffset.value, 0), stackHeightShared.value) }] };
1032
1108
  if (footerFixed) return { transform: [{ translateY: 0 }] };
1033
1109
  if (snapBackFooterShared.value) return { transform: [{ translateY: footerOffset.value }] };
1034
1110
  const effective = scrollPosition.value + headerHeightShared.value - pullSearchHeightShared.value;
1035
1111
  if (effective <= 0) return { transform: [{ translateY: 0 }] };
1036
1112
  return { transform: [{ translateY: Math.min(effective, footerHeight ?? 0) }] };
1037
- }, [footerHeight, footerFixed, headerHeightShared, pullSearchHeightShared, scrollPosition, snapBackFooterShared, footerOffset]);
1113
+ }, [chromeHosted, footerHeight, footerFixed, headerHeightShared, pullSearchHeightShared, scrollPosition, snapBackFooterShared, footerOffset, stackHeightShared]);
1038
1114
  const containerPaddingBottom = footerFixed && footerAboveKeyboard ? keyboardHeight : 0;
1039
- const rowPaddingBottom = footerFixed && footerAboveKeyboard ? Math.max(insets.bottom - keyboardHeight, 0) : insets.bottom;
1115
+ const rowRef = useRef7(null);
1116
+ useLayoutEffect5(() => {
1117
+ if (Platform5.OS !== "web" || footerHeight) return;
1118
+ const height = rowRef.current?.offsetHeight;
1119
+ if (typeof height !== "number" || height <= 0) return;
1120
+ setFooterHeight(height + containerPaddingBottom);
1121
+ }, []);
1122
+ const bottomPadding = chromeHosted ? chromeOverhang : insets.bottom;
1123
+ const rowPaddingBottom = footerFixed && footerAboveKeyboard ? Math.max(bottomPadding - keyboardHeight, 0) : bottomPadding;
1124
+ const footerBottom = chromeHosted && footerFixed && footerAboveKeyboard ? Math.max(tabBarHeight - keyboardHeight, 0) : tabBarHeight;
1040
1125
  const { paddingBottom: consumerPaddingBottom, ...restStyle } = style ?? {};
1041
1126
  return (
1042
1127
  // bottom: tabBarHeight (not the styles.footer default of 0) - a consuming app's own persistent
1043
1128
  // tab bar (a fixture outside this package's header/footer concepts entirely, same as
1044
1129
  // useScrollList's content-inset reservation) sits below this at the true screen bottom, so this
1045
1130
  // bar has to clear it rather than rendering flush against 0 and overlapping/hiding behind it.
1046
- /* @__PURE__ */ jsxs5(Animated5.View, { onLayout: handleLayout, pointerEvents: "box-none", style: [styles6.footer, { bottom: tabBarHeight, paddingBottom: containerPaddingBottom }, footerStyle], children: [
1047
- (footerHeight ?? 0) > 0 && /* @__PURE__ */ jsx8(BlurView2, { blur, style: StyleSheet5.absoluteFill }),
1048
- /* @__PURE__ */ jsx8(View5, { style: [styles6.row, restStyle, { paddingBottom: rowPaddingBottom + (typeof consumerPaddingBottom === "number" ? consumerPaddingBottom : 0) }], children })
1131
+ /* @__PURE__ */ jsxs5(Animated5.View, { onLayout: handleLayout, pointerEvents: "box-none", style: [styles6.footer, { bottom: footerBottom, paddingBottom: containerPaddingBottom }, footerStyle], children: [
1132
+ (footerHeight ?? 0) > 0 && /* @__PURE__ */ jsx9(BlurView2, { blur, style: StyleSheet5.absoluteFill }),
1133
+ /* @__PURE__ */ jsx9(View5, { ref: rowRef, style: [styles6.row, restStyle, { paddingBottom: rowPaddingBottom + (typeof consumerPaddingBottom === "number" ? consumerPaddingBottom : 0) }], children })
1049
1134
  ] })
1050
1135
  );
1051
1136
  };
@@ -1056,13 +1141,13 @@ var styles6 = StyleSheet5.create({
1056
1141
 
1057
1142
  // src/ScrollViewHeader.tsx
1058
1143
  import { BlurView as BlurView3 } from "@rific/auto-paper";
1059
- import { useContext as useContext11 } from "react";
1060
- import { StyleSheet as StyleSheet6, Text, View as View6 } from "react-native";
1144
+ import { useContext as useContext11, useLayoutEffect as useLayoutEffect6, useRef as useRef8 } from "react";
1145
+ import { Platform as Platform6, StyleSheet as StyleSheet6, Text, View as View6 } from "react-native";
1061
1146
  import { Appbar, ProgressBar, Surface, useTheme as useTheme4 } from "react-native-paper";
1062
- import Animated6, { useAnimatedStyle as useAnimatedStyle5, useSharedValue as useSharedValue6 } from "react-native-reanimated";
1147
+ import Animated6, { useAnimatedStyle as useAnimatedStyle5, useSharedValue as useSharedValue7 } from "react-native-reanimated";
1063
1148
  import { useSafeAreaInsets as useSafeAreaInsets4 } from "react-native-safe-area-context";
1064
- import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1065
- var ActionBg = ({ blur, style }) => blur ? /* @__PURE__ */ jsx9(View6, { style: [StyleSheet6.absoluteFill, styles7.actionBg, style], children: /* @__PURE__ */ jsx9(BlurView3, { blur: true, style: StyleSheet6.absoluteFill }) }) : /* @__PURE__ */ jsx9(View6, { style: [StyleSheet6.absoluteFill, styles7.actionBg, style], pointerEvents: "none", children: /* @__PURE__ */ jsx9(Surface, { style: StyleSheet6.absoluteFill, elevation: 1, children: null }) });
1149
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
1150
+ var ActionBg = ({ blur, style }) => blur ? /* @__PURE__ */ jsx10(View6, { style: [StyleSheet6.absoluteFill, styles7.actionBg, style], children: /* @__PURE__ */ jsx10(BlurView3, { blur: true, style: StyleSheet6.absoluteFill }) }) : /* @__PURE__ */ jsx10(View6, { style: [StyleSheet6.absoluteFill, styles7.actionBg, style], pointerEvents: "none", children: /* @__PURE__ */ jsx10(Surface, { style: StyleSheet6.absoluteFill, elevation: 1, children: null }) });
1066
1151
  var ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, backActionAccessibilityLabel, backActionFixed, caption, centerContent, children, iconSize, style, title, topInset = true, trailingAction, trailingActionFixed = true }) => {
1067
1152
  const { blur, headerHeight, headerFixed, headerHeightShared, headerOffset, progress, progressing, pullSearchHeightShared, scrollPosition, setHeaderHeight, snapBackHeaderShared } = useContext11(ScrollViewContext);
1068
1153
  const { settings } = useContext11(ScrollViewSettingsContext);
@@ -1070,7 +1155,7 @@ var ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, backActionAc
1070
1155
  const insets = useSafeAreaInsets4();
1071
1156
  const theme = useTheme4();
1072
1157
  const top = topInset ? insets.top : 0;
1073
- const measuredShared = useSharedValue6(false);
1158
+ const measuredShared = useSharedValue7(false);
1074
1159
  const handleLayout = ({
1075
1160
  nativeEvent: {
1076
1161
  layout: { height }
@@ -1079,6 +1164,14 @@ var ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, backActionAc
1079
1164
  measuredShared.value = true;
1080
1165
  if (headerHeight !== height) setHeaderHeight(height);
1081
1166
  };
1167
+ const contentRef = useRef8(null);
1168
+ useLayoutEffect6(() => {
1169
+ if (Platform6.OS !== "web" || headerHeight) return;
1170
+ const height = contentRef.current?.offsetHeight;
1171
+ if (typeof height !== "number" || height <= 0) return;
1172
+ measuredShared.value = true;
1173
+ setHeaderHeight(height);
1174
+ }, []);
1082
1175
  const actionMargin = 4;
1083
1176
  const contentMinHeight = actionSize + 2 * actionMargin;
1084
1177
  const unmeasuredHeight = top + contentMinHeight;
@@ -1108,31 +1201,31 @@ var ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, backActionAc
1108
1201
  const leadingStyle = { alignItems: "center", height: actionSize, justifyContent: "center", left: actionMargin, position: "absolute", top: actionTop, width: actionSize, zIndex: 3, ...actionShadow };
1109
1202
  const trailingStyle = { alignItems: "center", height: actionSize, justifyContent: "center", position: "absolute", right: actionMargin, top: actionTop, width: actionSize, zIndex: 3, ...actionShadow };
1110
1203
  return /* @__PURE__ */ jsxs6(Fragment4, { children: [
1111
- /* @__PURE__ */ jsx9(Animated6.View, { pointerEvents: "none", style: [styles7.blur, blurStyle], children: headerHeight !== 0 && /* @__PURE__ */ jsx9(BlurView3, { blur, style: [styles7.blurInner, { height: headerHeight ?? unmeasuredHeight }] }) }),
1204
+ /* @__PURE__ */ jsx10(Animated6.View, { pointerEvents: "none", style: [styles7.blur, blurStyle], children: headerHeight !== 0 && /* @__PURE__ */ jsx10(BlurView3, { blur, style: [styles7.blurInner, { height: headerHeight ?? unmeasuredHeight }] }) }),
1112
1205
  /* @__PURE__ */ jsxs6(Animated6.View, { onLayout: handleLayout, pointerEvents: "box-none", style: [headerHeight === null ? styles7.headerInit : styles7.header, translateStyle], children: [
1113
- /* @__PURE__ */ jsx9(View6, { style: [{ paddingTop: top }, style], children: children ?? /* @__PURE__ */ jsxs6(View6, { style: [styles7.content, { minHeight: contentMinHeight }], children: [
1114
- /* @__PURE__ */ jsx9(View6, { style: styles7.side }),
1115
- /* @__PURE__ */ jsx9(View6, { style: styles7.spacer }),
1116
- (centerContent || title || caption) && /* @__PURE__ */ jsx9(View6, { style: [StyleSheet6.absoluteFill, styles7.titleContainer], pointerEvents: centerContent ? "box-none" : "none", children: centerContent ?? /* @__PURE__ */ jsxs6(Fragment4, { children: [
1117
- title && /* @__PURE__ */ jsx9(Text, { numberOfLines: 1, style: [styles7.title, { color: theme.colors.onSurface }], children: title }),
1118
- caption && /* @__PURE__ */ jsx9(Text, { numberOfLines: 1, style: [styles7.caption, { color: theme.colors.onSurfaceVariant }], children: caption })
1206
+ /* @__PURE__ */ jsx10(View6, { ref: contentRef, style: [{ paddingTop: top }, style], children: children ?? /* @__PURE__ */ jsxs6(View6, { style: [styles7.content, { minHeight: contentMinHeight }], children: [
1207
+ /* @__PURE__ */ jsx10(View6, { style: styles7.side }),
1208
+ /* @__PURE__ */ jsx10(View6, { style: styles7.spacer }),
1209
+ (centerContent || title || caption) && /* @__PURE__ */ jsx10(View6, { style: [StyleSheet6.absoluteFill, styles7.titleContainer], pointerEvents: centerContent ? "box-none" : "none", children: centerContent ?? /* @__PURE__ */ jsxs6(Fragment4, { children: [
1210
+ title && /* @__PURE__ */ jsx10(Text, { numberOfLines: 1, style: [styles7.title, { color: theme.colors.onSurface }], children: title }),
1211
+ caption && /* @__PURE__ */ jsx10(Text, { numberOfLines: 1, style: [styles7.caption, { color: theme.colors.onSurfaceVariant }], children: caption })
1119
1212
  ] }) })
1120
1213
  ] }) }),
1121
- !blur && /* @__PURE__ */ jsx9(View6, { style: [styles7.divider, { backgroundColor: theme.colors.outlineVariant }] })
1214
+ !blur && /* @__PURE__ */ jsx10(View6, { style: [styles7.divider, { backgroundColor: theme.colors.outlineVariant }] })
1122
1215
  ] }),
1123
- /* @__PURE__ */ jsx9(Animated6.View, { pointerEvents: "none", style: [styles7.progress, progressStyle], children: /* @__PURE__ */ jsx9(ProgressBar, { indeterminate: progress === null, visible: progressing, progress: progress ?? void 0 }) }),
1216
+ /* @__PURE__ */ jsx10(Animated6.View, { pointerEvents: "none", style: [styles7.progress, progressStyle], children: /* @__PURE__ */ jsx10(ProgressBar, { indeterminate: progress === null, visible: progressing, progress: progress ?? void 0 }) }),
1124
1217
  backAction && (effectiveBackActionFixed ? /* @__PURE__ */ jsxs6(Animated6.View, { style: leadingStyle, children: [
1125
- /* @__PURE__ */ jsx9(ActionBg, { blur, style: actionStyle }),
1126
- typeof backAction === "function" ? /* @__PURE__ */ jsx9(Appbar.BackAction, { accessibilityLabel: backActionAccessibilityLabel, onPress: backAction, size: iconSize ?? actionSize / 2, style: buttonStyle }) : backAction
1218
+ /* @__PURE__ */ jsx10(ActionBg, { blur, style: actionStyle }),
1219
+ typeof backAction === "function" ? /* @__PURE__ */ jsx10(Appbar.BackAction, { accessibilityLabel: backActionAccessibilityLabel, onPress: backAction, size: iconSize ?? actionSize / 2, style: buttonStyle }) : backAction
1127
1220
  ] }) : /* @__PURE__ */ jsxs6(Animated6.View, { style: [leadingStyle, translateStyle], children: [
1128
- /* @__PURE__ */ jsx9(ActionBg, { blur, style: actionStyle }),
1129
- typeof backAction === "function" ? /* @__PURE__ */ jsx9(Appbar.BackAction, { accessibilityLabel: backActionAccessibilityLabel, onPress: backAction, size: iconSize ?? actionSize / 2, style: buttonStyle }) : backAction
1221
+ /* @__PURE__ */ jsx10(ActionBg, { blur, style: actionStyle }),
1222
+ typeof backAction === "function" ? /* @__PURE__ */ jsx10(Appbar.BackAction, { accessibilityLabel: backActionAccessibilityLabel, onPress: backAction, size: iconSize ?? actionSize / 2, style: buttonStyle }) : backAction
1130
1223
  ] })),
1131
1224
  trailingAction && (trailingActionFixed ? /* @__PURE__ */ jsxs6(View6, { style: trailingStyle, children: [
1132
- /* @__PURE__ */ jsx9(ActionBg, { blur, style: actionStyle }),
1225
+ /* @__PURE__ */ jsx10(ActionBg, { blur, style: actionStyle }),
1133
1226
  trailingAction
1134
1227
  ] }) : /* @__PURE__ */ jsxs6(Animated6.View, { style: [trailingStyle, translateStyle], children: [
1135
- /* @__PURE__ */ jsx9(ActionBg, { blur, style: actionStyle }),
1228
+ /* @__PURE__ */ jsx10(ActionBg, { blur, style: actionStyle }),
1136
1229
  trailingAction
1137
1230
  ] }))
1138
1231
  ] });
@@ -1155,57 +1248,101 @@ var styles7 = StyleSheet6.create({
1155
1248
 
1156
1249
  // src/ScrollViewProvider.tsx
1157
1250
  import { useBlur } from "@rific/auto-paper";
1158
- import { useCallback as useCallback7, useContext as useContext12, useEffect as useEffect5, useMemo as useMemo5, useRef as useRef8, useState as useState5 } from "react";
1159
- import { Dimensions as Dimensions3, Platform as Platform5 } from "react-native";
1160
- import { useSharedValue as useSharedValue7 } from "react-native-reanimated";
1161
- import { jsx as jsx10 } from "react/jsx-runtime";
1162
- var ScrollViewProvider = ({ blur, children, fixed = false, footerAboveKeyboard = false, footerFixed, headerFixed, snapBack, snapBackFooter, snapBackHeader, tabBarHeight = 0 }) => {
1251
+ import { useCallback as useCallback8, useContext as useContext12, useEffect as useEffect7, useLayoutEffect as useLayoutEffect7, useMemo as useMemo6, useRef as useRef9, useState as useState6 } from "react";
1252
+ import { Dimensions as Dimensions3, Platform as Platform7 } from "react-native";
1253
+ import { useSharedValue as useSharedValue8 } from "react-native-reanimated";
1254
+
1255
+ // src/internal/ChromeRegistrar.tsx
1256
+ import { useEffect as useEffect6, useId } from "react";
1257
+ var ChromeRegistrar = ({ footerFixed, host, writable }) => {
1258
+ const id = useId();
1259
+ const active = host.useIsActive();
1260
+ const { activeId, claim, pinned, release, reveal } = host;
1261
+ useEffect6(() => {
1262
+ if (!active) return void 0;
1263
+ claim(id);
1264
+ return () => release(id);
1265
+ }, [active, claim, id, release]);
1266
+ const owner = activeId === id;
1267
+ useEffect6(() => {
1268
+ writable.value = owner && !pinned;
1269
+ }, [owner, pinned, writable]);
1270
+ useEffect6(() => {
1271
+ if (owner && footerFixed) reveal();
1272
+ }, [footerFixed, owner, reveal]);
1273
+ return null;
1274
+ };
1275
+
1276
+ // src/ScrollViewProvider.tsx
1277
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
1278
+ var ScrollViewProvider = ({ blur, chrome, children, fixed = false, footerAboveKeyboard = false, footerFixed, headerFixed, snapBack, snapBackFooter, snapBackHeader, tabBarHeight = 0 }) => {
1163
1279
  const { settings } = useContext12(ScrollViewSettingsContext);
1280
+ const enclosingHost = useContext12(ScrollViewChromeHostContext);
1281
+ const chromeHost = chrome === false ? null : enclosingHost;
1282
+ const chromeHosted = chromeHost !== null;
1283
+ const chromeFootprint = chromeHost?.footprint ?? 0;
1284
+ const chromeOverhang = chromeHost?.overhang ?? 0;
1285
+ const chromeSnapBack = chromeHost?.snapBack ?? false;
1164
1286
  const effectiveBlur = useBlur(blur);
1165
1287
  const effectiveSnapBack = snapBack ?? settings.snapBack;
1166
1288
  const effectiveSnapBackHeader = snapBackHeader ?? settings.snapBackHeader;
1167
1289
  const effectiveSnapBackFooter = snapBackFooter ?? settings.snapBackFooter;
1168
- const [headerHeight, setHeaderHeightState] = useState5(null);
1169
- const [footerHeight, setFooterHeightState] = useState5(null);
1170
- const [progress, setProgress] = useState5(null);
1171
- const [progressing, setProgressing] = useState5(false);
1172
- const scrollPosition = useSharedValue7(0);
1173
- const listGeneration = useSharedValue7(0);
1174
- const jsListGeneration = useRef8(0);
1175
- const headerHeightShared = useSharedValue7(0);
1176
- const footerHeightShared = useSharedValue7(0);
1177
- const headerOffset = useSharedValue7(0);
1178
- const footerOffset = useSharedValue7(0);
1179
- const pullSearchHeightShared = useSharedValue7(0);
1180
- const snapBackHeaderShared = useSharedValue7(false);
1181
- const snapBackFooterShared = useSharedValue7(false);
1182
- useEffect5(() => {
1290
+ const [headerHeight, setHeaderHeightState] = useState6(null);
1291
+ const [footerHeight, setFooterHeightState] = useState6(null);
1292
+ const [progress, setProgress] = useState6(null);
1293
+ const [progressing, setProgressing] = useState6(false);
1294
+ const scrollPosition = useSharedValue8(0);
1295
+ const listGeneration = useSharedValue8(0);
1296
+ const jsListGeneration = useRef9(0);
1297
+ const headerHeightShared = useSharedValue8(0);
1298
+ const footerHeightShared = useSharedValue8(0);
1299
+ const headerOffset = useSharedValue8(0);
1300
+ const ownFooterOffset = useSharedValue8(0);
1301
+ const ownStackHeight = useSharedValue8(0);
1302
+ const footerOffset = chromeHost ? chromeHost.offset : ownFooterOffset;
1303
+ const chromeWritable = useSharedValue8(!chromeHosted);
1304
+ useEffect7(() => {
1305
+ if (!chromeHosted) chromeWritable.value = true;
1306
+ }, [chromeHosted, chromeWritable]);
1307
+ const revealChrome = chromeHost?.reveal;
1308
+ const stackHeightShared = chromeHosted ? ownStackHeight : footerHeightShared;
1309
+ const pullSearchHeightShared = useSharedValue8(0);
1310
+ const snapBackHeaderShared = useSharedValue8(false);
1311
+ const snapBackFooterShared = useSharedValue8(false);
1312
+ useEffect7(() => {
1183
1313
  snapBackHeaderShared.value = effectiveSnapBackHeader ?? effectiveSnapBack;
1184
- snapBackFooterShared.value = effectiveSnapBackFooter ?? effectiveSnapBack;
1185
- }, [effectiveSnapBack, effectiveSnapBackFooter, effectiveSnapBackHeader, snapBackFooterShared, snapBackHeaderShared]);
1186
- const setHeaderHeight = useCallback7(
1187
- (h) => {
1188
- if (h === 0 && headerHeight) return;
1189
- setHeaderHeightState(h);
1190
- headerHeightShared.value = h ?? 0;
1191
- },
1192
- [headerHeight, headerHeightShared]
1193
- );
1194
- const onListUnmount = useCallback7(() => {
1314
+ snapBackFooterShared.value = chromeHosted ? chromeSnapBack : effectiveSnapBackFooter ?? effectiveSnapBack;
1315
+ }, [chromeHosted, chromeSnapBack, effectiveSnapBack, effectiveSnapBackFooter, effectiveSnapBackHeader, snapBackFooterShared, snapBackHeaderShared]);
1316
+ useEffect7(() => {
1317
+ if (!chromeHosted) return;
1318
+ ownStackHeight.value = chromeFootprint + Math.max(footerHeight ?? 0, chromeOverhang) + CHROME_SLACK;
1319
+ }, [chromeFootprint, chromeHosted, chromeOverhang, footerHeight, ownStackHeight]);
1320
+ const headerHeightRef = useRef9(null);
1321
+ const footerHeightRef = useRef9(null);
1322
+ const setHeaderHeight = useCallback8((h) => {
1323
+ if (h === 0 && headerHeightRef.current) return;
1324
+ headerHeightRef.current = h;
1325
+ setHeaderHeightState(h);
1326
+ }, []);
1327
+ useLayoutEffect7(() => {
1328
+ headerHeightShared.value = headerHeight ?? 0;
1329
+ }, [headerHeight, headerHeightShared]);
1330
+ const onListUnmount = useCallback8(() => {
1195
1331
  listGeneration.value += 1;
1196
- }, [listGeneration]);
1197
- const onJsListUnmount = useCallback7(() => {
1332
+ if (revealChrome && chromeWritable.value) revealChrome();
1333
+ }, [chromeWritable, listGeneration, revealChrome]);
1334
+ const onJsListUnmount = useCallback8(() => {
1198
1335
  jsListGeneration.current += 1;
1199
1336
  }, []);
1200
- useEffect5(() => {
1337
+ useEffect7(() => {
1201
1338
  if (!__DEV__ || headerHeight !== null) return;
1202
1339
  const timeout = setTimeout(() => {
1203
- console.warn("[scroll-view] No ScrollViewHeader ever measured inside this ScrollViewProvider \u2014 scroll content stays hidden (opacity: 0) until one does. Render a <ScrollViewHeader> (it can be empty / zero height) inside this provider.");
1340
+ console.warn("[scroll-view] No ScrollViewHeader ever measured inside this ScrollViewProvider: scroll content stays hidden (opacity: 0) until one does. Render a <ScrollViewHeader> (it can be empty / zero height) inside this provider.");
1204
1341
  }, 3e3);
1205
1342
  return () => clearTimeout(timeout);
1206
1343
  }, [headerHeight]);
1207
- useEffect5(() => {
1208
- if (Platform5.OS !== "web" || headerHeight !== null) return;
1344
+ useEffect7(() => {
1345
+ if (Platform7.OS !== "web" || headerHeight !== null) return;
1209
1346
  let raf2 = 0;
1210
1347
  const raf1 = requestAnimationFrame(() => {
1211
1348
  raf2 = requestAnimationFrame(() => setHeaderHeight(0));
@@ -1215,46 +1352,54 @@ var ScrollViewProvider = ({ blur, children, fixed = false, footerAboveKeyboard =
1215
1352
  cancelAnimationFrame(raf2);
1216
1353
  };
1217
1354
  }, [headerHeight, setHeaderHeight]);
1218
- const setFooterHeight = useCallback7(
1355
+ const setFooterHeight = useCallback8(
1219
1356
  (h) => {
1220
- if (h === 0 && footerHeight) return;
1357
+ if (h === 0 && footerHeightRef.current) return;
1358
+ footerHeightRef.current = h;
1221
1359
  setFooterHeightState(h);
1222
1360
  footerHeightShared.value = h ?? 0;
1223
1361
  },
1224
- [footerHeight, footerHeightShared]
1362
+ [footerHeightShared]
1225
1363
  );
1226
- const scrollHeight = useMemo5(() => Dimensions3.get("window").height - (headerHeight ?? 0) - (footerHeight ?? 0), [headerHeight, footerHeight]);
1364
+ const scrollHeight = useMemo6(() => Dimensions3.get("window").height - (headerHeight ?? 0) - (footerHeight ?? 0), [headerHeight, footerHeight]);
1227
1365
  const effectiveHeaderFixed = fixed || (headerFixed ?? settings.headerFixed);
1228
1366
  const effectiveFooterFixed = fixed || (footerFixed ?? settings.footerFixed);
1229
- const value = useMemo5(() => ({ blur: effectiveBlur, footerAboveKeyboard, 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, footerAboveKeyboard, effectiveFooterFixed, effectiveHeaderFixed, footerHeight, footerHeightShared, footerOffset, headerHeight, headerHeightShared, headerOffset, jsListGeneration, listGeneration, onJsListUnmount, onListUnmount, progress, pullSearchHeightShared, progressing, scrollHeight, scrollPosition, setFooterHeight, setHeaderHeight, snapBackFooterShared, snapBackHeaderShared, tabBarHeight]);
1230
- return /* @__PURE__ */ jsx10(ScrollViewContext.Provider, { value, children });
1367
+ const effectiveTabBarHeight = chromeHosted ? chromeFootprint : tabBarHeight;
1368
+ const value = useMemo6(
1369
+ () => ({ blur: effectiveBlur, chromeHosted, chromeOverhang, chromeWritable, footerAboveKeyboard, 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, stackHeightShared, tabBarHeight: effectiveTabBarHeight }),
1370
+ [effectiveBlur, chromeHosted, chromeOverhang, chromeWritable, footerAboveKeyboard, effectiveFooterFixed, effectiveHeaderFixed, footerHeight, footerHeightShared, footerOffset, headerHeight, headerHeightShared, headerOffset, jsListGeneration, listGeneration, onJsListUnmount, onListUnmount, progress, pullSearchHeightShared, progressing, scrollHeight, scrollPosition, setFooterHeight, setHeaderHeight, snapBackFooterShared, snapBackHeaderShared, stackHeightShared, effectiveTabBarHeight]
1371
+ );
1372
+ return /* @__PURE__ */ jsx11(ScrollViewContext.Provider, { value, children: /* @__PURE__ */ jsxs7(ScrollViewChromeHostContext.Provider, { value: null, children: [
1373
+ children,
1374
+ chromeHost ? /* @__PURE__ */ jsx11(ChromeRegistrar, { footerFixed: effectiveFooterFixed, host: chromeHost, writable: chromeWritable }) : null
1375
+ ] }) });
1231
1376
  };
1232
1377
 
1233
1378
  // src/ScrollViewSettingsProvider.tsx
1234
1379
  var ScrollViewSettingsProvider = scrollViewSettingsProvider;
1235
1380
 
1236
1381
  // src/SectionList.tsx
1237
- import { memo as memo4, useCallback as useCallback9, useContext as useContext14, useEffect as useEffect6, useLayoutEffect as useLayoutEffect6, useMemo as useMemo6, useRef as useRef10 } from "react";
1238
- import { Dimensions as Dimensions4, Platform as Platform6, SectionList as RNSectionList, View as View7 } from "react-native";
1382
+ import { memo as memo4, useCallback as useCallback10, useContext as useContext14, useEffect as useEffect8, useLayoutEffect as useLayoutEffect9, useMemo as useMemo7, useRef as useRef11 } from "react";
1383
+ import { Dimensions as Dimensions4, Platform as Platform8, SectionList as RNSectionList, View as View7 } from "react-native";
1239
1384
  import { Gesture as Gesture4, GestureDetector as GestureDetector4 } from "react-native-gesture-handler";
1240
- import Animated7, { runOnUI as runOnUI3, useSharedValue as useSharedValue9 } from "react-native-reanimated";
1385
+ import Animated7, { runOnUI as runOnUI3, useSharedValue as useSharedValue10 } from "react-native-reanimated";
1241
1386
 
1242
1387
  // src/internal/useStickyHeaders.ts
1243
- import { useCallback as useCallback8, useContext as useContext13, useLayoutEffect as useLayoutEffect5, useRef as useRef9, useState as useState6 } from "react";
1244
- import { runOnJS as runOnJS4, useAnimatedReaction as useAnimatedReaction2, useAnimatedStyle as useAnimatedStyle6, useSharedValue as useSharedValue8 } from "react-native-reanimated";
1388
+ import { useCallback as useCallback9, useContext as useContext13, useLayoutEffect as useLayoutEffect8, useRef as useRef10, useState as useState7 } from "react";
1389
+ import { runOnJS as runOnJS4, useAnimatedReaction as useAnimatedReaction2, useAnimatedStyle as useAnimatedStyle6, useSharedValue as useSharedValue9 } from "react-native-reanimated";
1245
1390
  import { useSafeAreaInsets as useSafeAreaInsets5 } from "react-native-safe-area-context";
1246
1391
  function useStickyHeaders(headerFixed, enabled) {
1247
1392
  const { headerHeightShared, headerOffset, pullSearchHeightShared, scrollPosition, snapBackHeaderShared } = useContext13(ScrollViewContext);
1248
1393
  const insetsTop = useSafeAreaInsets5().top;
1249
- const sectionYPositions = useSharedValue8([]);
1250
- const sectionHeights = useSharedValue8([]);
1251
- const [activeIndex, setActiveIndex] = useState6(-1);
1252
- const contentIndexShared = useSharedValue8(-1);
1253
- useLayoutEffect5(() => {
1394
+ const sectionYPositions = useSharedValue9([]);
1395
+ const sectionHeights = useSharedValue9([]);
1396
+ const [activeIndex, setActiveIndex] = useState7(-1);
1397
+ const contentIndexShared = useSharedValue9(-1);
1398
+ useLayoutEffect8(() => {
1254
1399
  contentIndexShared.value = activeIndex;
1255
1400
  }, [activeIndex, contentIndexShared]);
1256
- const positionsCache = useRef9([]);
1257
- const heightsCache = useRef9([]);
1401
+ const positionsCache = useRef10([]);
1402
+ const heightsCache = useRef10([]);
1258
1403
  useAnimatedReaction2(
1259
1404
  () => {
1260
1405
  if (!enabled) return -1;
@@ -1285,7 +1430,7 @@ function useStickyHeaders(headerFixed, enabled) {
1285
1430
  // Same web-only reanimated gap as useAnimatedStyle elsewhere in this file: this "prepare"
1286
1431
  // function's own SharedValue reads (sectionYPositions, headerHeightShared, scrollPosition,
1287
1432
  // snapBackHeaderShared, headerOffset, pullSearchHeightShared) need to be listed explicitly too
1288
- // — confirmed empirically that without them, useAnimatedReaction re-evaluates once on mount and
1433
+ // - confirmed empirically that without them, useAnimatedReaction re-evaluates once on mount and
1289
1434
  // then never again on web, permanently freezing activeIndex/the active sticky section.
1290
1435
  [enabled, headerFixed, insetsTop, sectionYPositions, headerHeightShared, scrollPosition, snapBackHeaderShared, headerOffset, pullSearchHeightShared]
1291
1436
  );
@@ -1331,7 +1476,7 @@ function useStickyHeaders(headerFixed, enabled) {
1331
1476
  }
1332
1477
  return { opacity: 1, top: 0, transform: [{ translateY }] };
1333
1478
  }, [enabled, headerFixed, insetsTop, contentIndexShared, sectionYPositions, headerHeightShared, scrollPosition, snapBackHeaderShared, headerOffset, pullSearchHeightShared, sectionHeights]);
1334
- const measureHeader = useCallback8(
1479
+ const measureHeader = useCallback9(
1335
1480
  (index, pageY, height) => {
1336
1481
  const contentY = pageY + scrollPosition.value;
1337
1482
  positionsCache.current[index] = contentY;
@@ -1341,7 +1486,7 @@ function useStickyHeaders(headerFixed, enabled) {
1341
1486
  },
1342
1487
  [scrollPosition, sectionHeights, sectionYPositions]
1343
1488
  );
1344
- const resetPositions = useCallback8(
1489
+ const resetPositions = useCallback9(
1345
1490
  (count) => {
1346
1491
  positionsCache.current = Array.from({ length: count }, () => -1);
1347
1492
  sectionYPositions.value = positionsCache.current.slice();
@@ -1354,17 +1499,17 @@ function useStickyHeaders(headerFixed, enabled) {
1354
1499
  }
1355
1500
 
1356
1501
  // src/SectionList.tsx
1357
- import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
1502
+ import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
1358
1503
  var AnimatedSectionList = Animated7.createAnimatedComponent(RNSectionList);
1359
1504
  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 }) => {
1360
- const scrollView = useRef10(null);
1505
+ const scrollView = useRef11(null);
1361
1506
  const { stickySectionHeadersEnabled = true, renderSectionHeader, ...passThroughProps } = props;
1362
1507
  const { chipHidden, chipStyle, containerStyle, contentInset, contentOffset, contentPadding, footerFixed, headerFixed, headerHeight } = useScrollList({ footerFixed: footerFixedProp, headerFixed: headerFixedProp, hideUntilMeasured: true, keyboardAware, pullSearchHeight, style });
1363
1508
  const { listGeneration, onListUnmount } = useContext14(ScrollViewContext);
1364
- const capturedGeneration = useSharedValue9(-1);
1365
- useLayoutEffect6(() => {
1509
+ const capturedGeneration = useSharedValue10(-1);
1510
+ useLayoutEffect9(() => {
1366
1511
  const generation = listGeneration.value;
1367
- if (Platform6.OS === "web") {
1512
+ if (Platform8.OS === "web") {
1368
1513
  capturedGeneration.value = generation;
1369
1514
  return;
1370
1515
  }
@@ -1375,15 +1520,15 @@ var SectionListInner = ({ chipProps, chipThreshold, contentContainerStyle: exter
1375
1520
  }, [capturedGeneration, listGeneration]);
1376
1521
  const usesCustomSticky = stickySectionHeadersEnabled && renderSectionHeader != null;
1377
1522
  const { activeIndex, clipStyle, measureHeader, overlayStyle, resetPositions } = useStickyHeaders(headerFixed, usesCustomSticky);
1378
- useLayoutEffect6(() => {
1523
+ useLayoutEffect9(() => {
1379
1524
  if (usesCustomSticky) resetPositions(sections.length);
1380
1525
  }, [sections, usesCustomSticky, resetPositions, headerHeight]);
1381
- const sectionViewRefs = useRef10(/* @__PURE__ */ new Map());
1382
- const wrappedRenderSectionHeader = useMemo6(() => {
1526
+ const sectionViewRefs = useRef11(/* @__PURE__ */ new Map());
1527
+ const wrappedRenderSectionHeader = useMemo7(() => {
1383
1528
  if (!usesCustomSticky) return renderSectionHeader ?? void 0;
1384
1529
  return (info) => {
1385
1530
  const idx = sections.findIndex((s) => s === info.section);
1386
- return /* @__PURE__ */ jsx11(
1531
+ return /* @__PURE__ */ jsx12(
1387
1532
  View7,
1388
1533
  {
1389
1534
  ref: (el) => {
@@ -1400,8 +1545,8 @@ var SectionListInner = ({ chipProps, chipThreshold, contentContainerStyle: exter
1400
1545
  );
1401
1546
  };
1402
1547
  }, [usesCustomSticky, renderSectionHeader, sections, measureHeader]);
1403
- useLayoutEffect6(() => () => onListUnmount(), [onListUnmount]);
1404
- const scrollTo = useCallback9((offset, animated) => {
1548
+ useLayoutEffect9(() => () => onListUnmount(), [onListUnmount]);
1549
+ const scrollTo = useCallback10((offset, animated) => {
1405
1550
  scrollView.current?.getScrollResponder()?.scrollTo({ y: offset, animated });
1406
1551
  }, []);
1407
1552
  const {
@@ -1419,16 +1564,16 @@ var SectionListInner = ({ chipProps, chipThreshold, contentContainerStyle: exter
1419
1564
  pullSearchHeight,
1420
1565
  scrollTo
1421
1566
  });
1422
- const activeListHeader = useMemo6(() => {
1567
+ const activeListHeader = useMemo7(() => {
1423
1568
  if (!renderFilters) return pullSearchHeader;
1424
1569
  const Inner = pullSearchHeader;
1425
- return () => /* @__PURE__ */ jsxs7(Fragment5, { children: [
1426
- Inner ? typeof Inner === "function" ? /* @__PURE__ */ jsx11(Inner, {}) : Inner : null,
1570
+ return () => /* @__PURE__ */ jsxs8(Fragment5, { children: [
1571
+ Inner ? typeof Inner === "function" ? /* @__PURE__ */ jsx12(Inner, {}) : Inner : null,
1427
1572
  renderFilters
1428
1573
  ] });
1429
1574
  }, [pullSearchHeader, renderFilters]);
1430
- const pullSearchSettled = useRef10(false);
1431
- useEffect6(() => {
1575
+ const pullSearchSettled = useRef11(false);
1576
+ useEffect8(() => {
1432
1577
  if (!usesCustomSticky || activeListHeader == null || pullSearchSettled.current) return;
1433
1578
  pullSearchSettled.current = true;
1434
1579
  const handle = requestAnimationFrame(() => {
@@ -1440,24 +1585,24 @@ var SectionListInner = ({ chipProps, chipThreshold, contentContainerStyle: exter
1440
1585
  });
1441
1586
  return () => cancelAnimationFrame(handle);
1442
1587
  }, [activeListHeader, usesCustomSticky, measureHeader]);
1443
- const contentContainerStyle = useMemo6(() => [{ minHeight: Dimensions4.get("window").height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle, contentPadding], [contentInset.bottom, contentInset.top, contentPadding, externalContentContainerStyle, pullSearchMinHeight]);
1588
+ const contentContainerStyle = useMemo7(() => [{ minHeight: Dimensions4.get("window").height - contentInset.top - contentInset.bottom + pullSearchMinHeight }, externalContentContainerStyle, contentPadding], [contentInset.bottom, contentInset.top, contentPadding, externalContentContainerStyle, pullSearchMinHeight]);
1444
1589
  const handleScroll = useScrollHandler({ capturedGeneration, chipHidden, chipThreshold, footerFixed, headerFixed, listGeneration });
1445
- const handleScrollToTop = useCallback9(() => {
1590
+ const handleScrollToTop = useCallback10(() => {
1446
1591
  const offset = pullSearchHeight ? -contentInset.top + pullSearchHeight : -contentInset.top;
1447
1592
  scrollView.current?.getScrollResponder()?.scrollTo({ y: offset, animated: true });
1448
1593
  }, [contentInset.top, pullSearchHeight]);
1449
- const handleContentSizeChange = useCallback9(
1594
+ const handleContentSizeChange = useCallback10(
1450
1595
  (w, h) => {
1451
1596
  if (onContentSizeChange) onContentSizeChange(w, h);
1452
1597
  },
1453
1598
  [onContentSizeChange]
1454
1599
  );
1455
- const refreshControl = useMemo6(() => onRefresh ? /* @__PURE__ */ jsx11(RefreshControl, { onRefresh, refreshing: refreshing ?? false }) : /* @__PURE__ */ jsx11(RefreshControl, {}), [onRefresh, refreshing]);
1456
- const nativeGesture = useMemo6(() => Gesture4.Native(), []);
1457
- const combinedGesture = useMemo6(() => gesture !== void 0 ? Gesture4.Simultaneous(gesture, nativeGesture) : nativeGesture, [gesture, nativeGesture]);
1600
+ const refreshControl = useMemo7(() => onRefresh ? /* @__PURE__ */ jsx12(RefreshControl, { onRefresh, refreshing: refreshing ?? false }) : /* @__PURE__ */ jsx12(RefreshControl, {}), [onRefresh, refreshing]);
1601
+ const nativeGesture = useMemo7(() => Gesture4.Native(), []);
1602
+ const combinedGesture = useMemo7(() => gesture !== void 0 ? Gesture4.Simultaneous(gesture, nativeGesture) : nativeGesture, [gesture, nativeGesture]);
1458
1603
  const detectorGesture = gesture !== void 0 ? combinedGesture : void 0;
1459
- const content = /* @__PURE__ */ jsxs7(View7, { style: containerStyle, children: [
1460
- /* @__PURE__ */ jsx11(
1604
+ const content = /* @__PURE__ */ jsxs8(View7, { style: containerStyle, children: [
1605
+ /* @__PURE__ */ jsx12(
1461
1606
  AnimatedSectionList,
1462
1607
  {
1463
1608
  ...passThroughProps,
@@ -1487,11 +1632,11 @@ var SectionListInner = ({ chipProps, chipThreshold, contentContainerStyle: exter
1487
1632
  },
1488
1633
  headerHeight !== null && headerHeight > 0 ? 1 : 0
1489
1634
  ),
1490
- hiddenHeader && /* @__PURE__ */ jsx11(View7, { pointerEvents: "none", style: styles8.measureContainer, children: hiddenHeader }),
1491
- /* @__PURE__ */ jsx11(ScrollViewChip, { chipProps, onChipPress, onPress: handleScrollToTop, style: chipStyle }),
1492
- usesCustomSticky && /* @__PURE__ */ jsx11(Animated7.View, { pointerEvents: "none", style: [styles8.stickyClip, clipStyle], children: activeIndex >= 0 && activeIndex < sections.length && /* @__PURE__ */ jsx11(Animated7.View, { style: [styles8.stickyOverlay, overlayStyle], children: renderSectionHeader({ section: sections[activeIndex] }) }) })
1635
+ hiddenHeader && /* @__PURE__ */ jsx12(View7, { pointerEvents: "none", style: styles8.measureContainer, children: hiddenHeader }),
1636
+ /* @__PURE__ */ jsx12(ScrollViewChip, { chipProps, onChipPress, onPress: handleScrollToTop, style: chipStyle }),
1637
+ usesCustomSticky && /* @__PURE__ */ jsx12(Animated7.View, { pointerEvents: "none", style: [styles8.stickyClip, clipStyle], children: activeIndex >= 0 && activeIndex < sections.length && /* @__PURE__ */ jsx12(Animated7.View, { style: [styles8.stickyOverlay, overlayStyle], children: renderSectionHeader({ section: sections[activeIndex] }) }) })
1493
1638
  ] });
1494
- return detectorGesture ? /* @__PURE__ */ jsx11(GestureDetector4, { gesture: detectorGesture, children: content }) : content;
1639
+ return detectorGesture ? /* @__PURE__ */ jsx12(GestureDetector4, { gesture: detectorGesture, children: content }) : content;
1495
1640
  };
1496
1641
  var styles8 = {
1497
1642
  measureContainer: { left: 0, opacity: 0, position: "absolute", right: 0, top: -9999 },
@@ -1507,6 +1652,32 @@ var useScrollView = () => {
1507
1652
  return { progress, progressing, scrollHeight, scrollPosition, setProgress, setProgressing };
1508
1653
  };
1509
1654
 
1655
+ // src/useScrollViewChrome.ts
1656
+ import { useContext as useContext16, useEffect as useEffect9 } from "react";
1657
+ import { useAnimatedStyle as useAnimatedStyle7, useSharedValue as useSharedValue11 } from "react-native-reanimated";
1658
+ var noop = () => {
1659
+ };
1660
+ var useScrollViewChromeStyle = () => {
1661
+ const chrome = useContext16(ScrollViewChromeContext);
1662
+ const fallback = useSharedValue11(0);
1663
+ const offset = chrome?.offset ?? fallback;
1664
+ return useAnimatedStyle7(() => ({ transform: [{ translateY: offset.value }] }), [offset]);
1665
+ };
1666
+ var useScrollViewChromePin = (active) => {
1667
+ const chrome = useContext16(ScrollViewChromeContext);
1668
+ const addPin = chrome?.addPin;
1669
+ const removePin = chrome?.removePin;
1670
+ useEffect9(() => {
1671
+ if (!active || !addPin || !removePin) return void 0;
1672
+ addPin();
1673
+ return removePin;
1674
+ }, [active, addPin, removePin]);
1675
+ };
1676
+ var useScrollViewChromeReveal = () => {
1677
+ const chrome = useContext16(ScrollViewChromeContext);
1678
+ return chrome?.reveal ?? noop;
1679
+ };
1680
+
1510
1681
  // src/useScrollViewSettings.ts
1511
1682
  var useScrollViewSettings = scrollViewUseSettings;
1512
1683
  export {
@@ -1514,6 +1685,7 @@ export {
1514
1685
  FlatList,
1515
1686
  PullSearch,
1516
1687
  ScrollView,
1688
+ ScrollViewChromeProvider,
1517
1689
  ScrollViewContext,
1518
1690
  ScrollViewFooter,
1519
1691
  ScrollViewHeader,
@@ -1526,5 +1698,8 @@ export {
1526
1698
  scrollViewReducer,
1527
1699
  useKeyboardInset,
1528
1700
  useScrollView,
1701
+ useScrollViewChromePin,
1702
+ useScrollViewChromeReveal,
1703
+ useScrollViewChromeStyle,
1529
1704
  useScrollViewSettings
1530
1705
  };