@rific/scroll-view 0.6.1 → 0.6.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
@@ -6,7 +6,7 @@ import { ChipProps as ChipProps$1 } from '@rific/auto-paper';
6
6
  import * as react_native_reanimated from 'react-native-reanimated';
7
7
  import { SharedValue } from 'react-native-reanimated';
8
8
 
9
- type ChipProps = Omit<ChipProps$1, 'compact' | 'icon' | 'onPress'> & {
9
+ type ChipProps = Omit<ChipProps$1, 'compact' | 'icon' | 'onPress' | 'children'> & {
10
10
  label?: ReactNode;
11
11
  };
12
12
 
@@ -122,7 +122,7 @@ declare const ScrollView: typeof ScrollViewInner;
122
122
  type ScrollViewContextType = {
123
123
  blur: boolean;
124
124
  footerAboveKeyboard: boolean;
125
- footerHeight: number;
125
+ footerHeight: number | null;
126
126
  footerHeightShared: SharedValue<number>;
127
127
  footerFixed: boolean;
128
128
  footerOffset: SharedValue<number>;
@@ -141,7 +141,7 @@ type ScrollViewContextType = {
141
141
  progressing: boolean;
142
142
  scrollHeight: number;
143
143
  scrollPosition: SharedValue<number>;
144
- setFooterHeight: (h: number) => void;
144
+ setFooterHeight: (h: number | null) => void;
145
145
  setHeaderHeight: (h: number | null) => void;
146
146
  setProgress: (p: number | null) => void;
147
147
  setProgressing: (b: boolean) => void;
@@ -158,7 +158,7 @@ declare const ScrollViewFooter: ({ children, style }: ScrollViewFooterProps) =>
158
158
  type ScrollViewHeaderProps = {
159
159
  actionSize?: number;
160
160
  actionStyle?: ViewStyle;
161
- backAction?: () => void;
161
+ backAction?: (() => void) | ReactNode;
162
162
  backActionAccessibilityLabel?: string;
163
163
  backActionFixed?: boolean;
164
164
  caption?: string;
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ import { ChipProps as ChipProps$1 } from '@rific/auto-paper';
6
6
  import * as react_native_reanimated from 'react-native-reanimated';
7
7
  import { SharedValue } from 'react-native-reanimated';
8
8
 
9
- type ChipProps = Omit<ChipProps$1, 'compact' | 'icon' | 'onPress'> & {
9
+ type ChipProps = Omit<ChipProps$1, 'compact' | 'icon' | 'onPress' | 'children'> & {
10
10
  label?: ReactNode;
11
11
  };
12
12
 
@@ -122,7 +122,7 @@ declare const ScrollView: typeof ScrollViewInner;
122
122
  type ScrollViewContextType = {
123
123
  blur: boolean;
124
124
  footerAboveKeyboard: boolean;
125
- footerHeight: number;
125
+ footerHeight: number | null;
126
126
  footerHeightShared: SharedValue<number>;
127
127
  footerFixed: boolean;
128
128
  footerOffset: SharedValue<number>;
@@ -141,7 +141,7 @@ type ScrollViewContextType = {
141
141
  progressing: boolean;
142
142
  scrollHeight: number;
143
143
  scrollPosition: SharedValue<number>;
144
- setFooterHeight: (h: number) => void;
144
+ setFooterHeight: (h: number | null) => void;
145
145
  setHeaderHeight: (h: number | null) => void;
146
146
  setProgress: (p: number | null) => void;
147
147
  setProgressing: (b: boolean) => void;
@@ -158,7 +158,7 @@ declare const ScrollViewFooter: ({ children, style }: ScrollViewFooterProps) =>
158
158
  type ScrollViewHeaderProps = {
159
159
  actionSize?: number;
160
160
  actionStyle?: ViewStyle;
161
- backAction?: () => void;
161
+ backAction?: (() => void) | ReactNode;
162
162
  backActionAccessibilityLabel?: string;
163
163
  backActionFixed?: boolean;
164
164
  caption?: string;
package/dist/index.js CHANGED
@@ -64,15 +64,18 @@ var RefreshControl = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(impo
64
64
  // src/internal/ScrollViewChip.tsx
65
65
  var import_auto_paper = require("@rific/auto-paper");
66
66
  var import_react_native2 = require("react-native");
67
+ var import_react_native_paper = require("react-native-paper");
67
68
  var import_react_native_reanimated = __toESM(require("react-native-reanimated"));
68
69
  var import_jsx_runtime2 = require("react/jsx-runtime");
69
70
  var ScrollViewChip = ({ chipProps, isHorizontal, onChipPress, onPress, style }) => {
70
- const { label, style: chipStyle, ...restChipProps } = chipProps ?? {};
71
+ const { colors } = (0, import_react_native_paper.useTheme)();
72
+ const { label, selectedColor, style: chipStyle, ...restChipProps } = chipProps ?? {};
71
73
  const handlePress = onChipPress ? () => {
72
74
  onPress();
73
75
  onChipPress();
74
76
  } : onPress;
75
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_reanimated.default.View, { style: [styles.chip, style], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_auto_paper.Chip, { compact: true, ...restChipProps, icon: isHorizontal ? "chevron-left" : "chevron-up", onPress: handlePress, style: [styles.chipInner, chipStyle], children: label ?? (isHorizontal ? "Start" : "Top") }) });
77
+ const iconColor = selectedColor ?? (restChipProps.mode === "outlined" ? colors.onSurfaceVariant : colors.onSecondaryContainer);
78
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_reanimated.default.View, { style: [styles.chip, style], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_auto_paper.Chip, { compact: true, selectedColor, ...restChipProps, avatar: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper.Icon, { source: isHorizontal ? "chevron-left" : "chevron-up", size: 18, color: iconColor }), onPress: handlePress, style: [styles.chipInner, chipStyle], children: label ?? (isHorizontal ? "Start" : "Top") }) });
76
79
  };
77
80
  var styles = import_react_native2.StyleSheet.create({
78
81
  chip: { alignItems: "center", left: 0, position: "absolute", right: 0, zIndex: 3 },
@@ -88,7 +91,7 @@ var import_react = require("react");
88
91
  var ScrollViewContext = (0, import_react.createContext)({
89
92
  blur: true,
90
93
  footerAboveKeyboard: false,
91
- footerHeight: 0,
94
+ footerHeight: null,
92
95
  footerHeightShared: null,
93
96
  footerFixed: false,
94
97
  footerOffset: null,
@@ -556,7 +559,7 @@ var import_react_native_gesture_handler2 = require("react-native-gesture-handler
556
559
  var import_auto_paper2 = require("@rific/auto-paper");
557
560
  var import_react7 = require("react");
558
561
  var import_react_native7 = require("react-native");
559
- var import_react_native_paper = require("react-native-paper");
562
+ var import_react_native_paper2 = require("react-native-paper");
560
563
  var import_react_native_reanimated7 = __toESM(require("react-native-reanimated"));
561
564
  var import_react_native_safe_area_context2 = require("react-native-safe-area-context");
562
565
  var import_jsx_runtime4 = require("react/jsx-runtime");
@@ -586,7 +589,7 @@ var HorizontalDots = ({ total }) => {
586
589
  const { blur, footerHeightShared, scrollPosition } = (0, import_react7.useContext)(ScrollViewContext);
587
590
  const { width } = (0, import_react_native7.useWindowDimensions)();
588
591
  const insets = (0, import_react_native_safe_area_context2.useSafeAreaInsets)();
589
- const theme = (0, import_react_native_paper.useTheme)();
592
+ const theme = (0, import_react_native_paper2.useTheme)();
590
593
  const insetsBottom = insets.bottom;
591
594
  const activeColor = theme.colors.primary;
592
595
  const inactiveColor = theme.colors.outlineVariant;
@@ -880,12 +883,12 @@ var FlatList = (0, import_react9.memo)(FlatListInner);
880
883
  // src/PullSearch.tsx
881
884
  var import_react10 = require("react");
882
885
  var import_react_native9 = require("react-native");
883
- var import_react_native_paper2 = require("react-native-paper");
886
+ var import_react_native_paper3 = require("react-native-paper");
884
887
  var import_react_native_reanimated9 = __toESM(require("react-native-reanimated"));
885
888
  var import_jsx_runtime6 = require("react/jsx-runtime");
886
889
  var PullSearch = (0, import_react10.forwardRef)(function PullSearch2({ debounce, onChangeText, onHeightChange, placeholder, value: propValue }, ref) {
887
890
  const { headerHeight, scrollPosition } = (0, import_react10.useContext)(ScrollViewContext);
888
- const theme = (0, import_react_native_paper2.useTheme)();
891
+ const theme = (0, import_react_native_paper3.useTheme)();
889
892
  const inputRef = (0, import_react10.useRef)(null);
890
893
  const [barHeight, setBarHeight] = (0, import_react10.useState)(0);
891
894
  const [value, setValue] = (0, import_react10.useState)(propValue ?? "");
@@ -943,7 +946,7 @@ var PullSearch = (0, import_react10.forwardRef)(function PullSearch2({ debounce,
943
946
  const hideY = -h + barHeight;
944
947
  return { opacity: (0, import_react_native_reanimated9.interpolate)(scrollPosition.value, [showY, hideY], [1, 0], import_react_native_reanimated9.Extrapolation.CLAMP) };
945
948
  }, [barHeight, headerHeight, scrollPosition]);
946
- 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 }) });
949
+ 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_paper3.Searchbar, { autoCorrect: false, onChangeText: (text) => setValue((text || "").trimStart()), onClearIconPress: handleClear, placeholder, placeholderTextColor: theme.colors.onSurfaceVariant, ref: inputRef, spellCheck: false, style: styles5.input, value }) });
947
950
  });
948
951
  var styles5 = import_react_native9.StyleSheet.create({
949
952
  container: { paddingVertical: 8 },
@@ -1058,14 +1061,20 @@ var import_react13 = require("react");
1058
1061
  var import_react_native11 = require("react-native");
1059
1062
  var import_react_native_reanimated11 = __toESM(require("react-native-reanimated"));
1060
1063
  var import_react_native_safe_area_context3 = require("react-native-safe-area-context");
1061
- var import_jsx_runtime8 = require("react/jsx-runtime");
1064
+ var import_jsx_runtime8 = (
1065
+ // bottom: tabBarHeight (not the styles.footer default of 0) - a consuming app's own persistent
1066
+ // tab bar (a fixture outside this package's header/footer concepts entirely, same as
1067
+ // useScrollList's content-inset reservation) sits below this at the true screen bottom, so this
1068
+ // bar has to clear it rather than rendering flush against 0 and overlapping/hiding behind it.
1069
+ require("react/jsx-runtime")
1070
+ );
1062
1071
  var ScrollViewFooter = ({ children, style }) => {
1063
- const { blur, footerAboveKeyboard, footerHeight, footerFixed, footerOffset, headerHeightShared, pullSearchHeightShared, scrollPosition, setFooterHeight, snapBackFooterShared } = (0, import_react13.useContext)(ScrollViewContext);
1072
+ const { blur, footerAboveKeyboard, footerHeight, footerFixed, footerOffset, headerHeightShared, pullSearchHeightShared, scrollPosition, setFooterHeight, snapBackFooterShared, tabBarHeight } = (0, import_react13.useContext)(ScrollViewContext);
1064
1073
  const insets = (0, import_react_native_safe_area_context3.useSafeAreaInsets)();
1065
1074
  const keyboardHeight = useKeyboardInset();
1066
1075
  (0, import_react13.useEffect)(
1067
1076
  () => () => {
1068
- setFooterHeight(0);
1077
+ setFooterHeight(null);
1069
1078
  },
1070
1079
  [setFooterHeight]
1071
1080
  );
@@ -1081,17 +1090,17 @@ var ScrollViewFooter = ({ children, style }) => {
1081
1090
  if (snapBackFooterShared.value) return { transform: [{ translateY: footerOffset.value }] };
1082
1091
  const effective = scrollPosition.value + headerHeightShared.value - pullSearchHeightShared.value;
1083
1092
  if (effective <= 0) return { transform: [{ translateY: 0 }] };
1084
- return { transform: [{ translateY: Math.min(effective, footerHeight) }] };
1093
+ return { transform: [{ translateY: Math.min(effective, footerHeight ?? 0) }] };
1085
1094
  }, [footerHeight, footerFixed, headerHeightShared, pullSearchHeightShared, scrollPosition, snapBackFooterShared, footerOffset]);
1086
1095
  const containerPaddingBottom = footerFixed && footerAboveKeyboard ? keyboardHeight : 0;
1087
1096
  const rowPaddingBottom = footerFixed && footerAboveKeyboard ? Math.max(insets.bottom - keyboardHeight, 0) : insets.bottom;
1088
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native_reanimated11.default.View, { onLayout: handleLayout, pointerEvents: "box-none", style: [styles6.footer, { paddingBottom: containerPaddingBottom }, footerStyle], children: [
1089
- footerHeight > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_auto_paper3.BlurView, { blur, style: import_react_native11.StyleSheet.absoluteFill }),
1097
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native_reanimated11.default.View, { onLayout: handleLayout, pointerEvents: "box-none", style: [styles6.footer, { bottom: tabBarHeight, paddingBottom: containerPaddingBottom }, footerStyle], children: [
1098
+ (footerHeight ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_auto_paper3.BlurView, { blur, style: import_react_native11.StyleSheet.absoluteFill }),
1090
1099
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native11.View, { style: [styles6.row, { paddingBottom: rowPaddingBottom }, style], children })
1091
1100
  ] });
1092
1101
  };
1093
1102
  var styles6 = import_react_native11.StyleSheet.create({
1094
- footer: { bottom: 0, left: 0, position: "absolute", right: 0, zIndex: 2 },
1103
+ footer: { left: 0, position: "absolute", right: 0, zIndex: 2 },
1095
1104
  row: { alignItems: "center", flexDirection: "row", zIndex: 1 }
1096
1105
  });
1097
1106
 
@@ -1099,17 +1108,17 @@ var styles6 = import_react_native11.StyleSheet.create({
1099
1108
  var import_auto_paper4 = require("@rific/auto-paper");
1100
1109
  var import_react14 = require("react");
1101
1110
  var import_react_native12 = require("react-native");
1102
- var import_react_native_paper3 = require("react-native-paper");
1111
+ var import_react_native_paper4 = require("react-native-paper");
1103
1112
  var import_react_native_reanimated12 = __toESM(require("react-native-reanimated"));
1104
1113
  var import_react_native_safe_area_context4 = require("react-native-safe-area-context");
1105
1114
  var import_jsx_runtime9 = require("react/jsx-runtime");
1106
- 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 }) });
1115
+ 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_paper4.Surface, { style: import_react_native12.StyleSheet.absoluteFill, elevation: 1, children: null }) });
1107
1116
  var ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, backActionAccessibilityLabel, backActionFixed, caption, centerContent, children, iconSize, style, title, topInset = true, trailingAction, trailingActionFixed = true }) => {
1108
1117
  const { blur, headerHeight, headerFixed, headerHeightShared, headerOffset, progress, progressing, pullSearchHeightShared, scrollPosition, setHeaderHeight, snapBackHeaderShared } = (0, import_react14.useContext)(ScrollViewContext);
1109
1118
  const { settings } = (0, import_react14.useContext)(ScrollViewSettingsContext);
1110
1119
  const effectiveBackActionFixed = backActionFixed ?? settings.backActionFixed;
1111
1120
  const insets = (0, import_react_native_safe_area_context4.useSafeAreaInsets)();
1112
- const theme = (0, import_react_native_paper3.useTheme)();
1121
+ const theme = (0, import_react_native_paper4.useTheme)();
1113
1122
  const top = topInset ? insets.top : 0;
1114
1123
  const measuredShared = (0, import_react_native_reanimated12.useSharedValue)(false);
1115
1124
  const handleLayout = ({
@@ -1161,13 +1170,13 @@ var ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, backActionAc
1161
1170
  ] }) }),
1162
1171
  !blur && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native12.View, { style: [styles7.divider, { backgroundColor: theme.colors.outlineVariant }] })
1163
1172
  ] }),
1164
- /* @__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 }) }),
1173
+ /* @__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_paper4.ProgressBar, { indeterminate: progress === null, visible: progressing, progress: progress ?? void 0 }) }),
1165
1174
  backAction && (effectiveBackActionFixed ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native_reanimated12.default.View, { style: leadingStyle, children: [
1166
1175
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ActionBg, { blur, style: actionStyle }),
1167
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_paper3.Appbar.BackAction, { accessibilityLabel: backActionAccessibilityLabel, onPress: backAction, size: iconSize ?? actionSize / 2, style: buttonStyle })
1176
+ typeof backAction === "function" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_paper4.Appbar.BackAction, { accessibilityLabel: backActionAccessibilityLabel, onPress: backAction, size: iconSize ?? actionSize / 2, style: buttonStyle }) : backAction
1168
1177
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native_reanimated12.default.View, { style: [leadingStyle, translateStyle], children: [
1169
1178
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ActionBg, { blur, style: actionStyle }),
1170
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_paper3.Appbar.BackAction, { accessibilityLabel: backActionAccessibilityLabel, onPress: backAction, size: iconSize ?? actionSize / 2, style: buttonStyle })
1179
+ typeof backAction === "function" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native_paper4.Appbar.BackAction, { accessibilityLabel: backActionAccessibilityLabel, onPress: backAction, size: iconSize ?? actionSize / 2, style: buttonStyle }) : backAction
1171
1180
  ] })),
1172
1181
  trailingAction && (trailingActionFixed ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native12.View, { style: trailingStyle, children: [
1173
1182
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ActionBg, { blur, style: actionStyle }),
@@ -1207,7 +1216,7 @@ var ScrollViewProvider = ({ blur, children, fixed = false, footerAboveKeyboard =
1207
1216
  const effectiveSnapBackHeader = snapBackHeader ?? settings.snapBackHeader;
1208
1217
  const effectiveSnapBackFooter = snapBackFooter ?? settings.snapBackFooter;
1209
1218
  const [headerHeight, setHeaderHeightState] = (0, import_react15.useState)(null);
1210
- const [footerHeight, setFooterHeightState] = (0, import_react15.useState)(0);
1219
+ const [footerHeight, setFooterHeightState] = (0, import_react15.useState)(null);
1211
1220
  const [progress, setProgress] = (0, import_react15.useState)(null);
1212
1221
  const [progressing, setProgressing] = (0, import_react15.useState)(false);
1213
1222
  const scrollPosition = (0, import_react_native_reanimated13.useSharedValue)(0);
@@ -1247,12 +1256,13 @@ var ScrollViewProvider = ({ blur, children, fixed = false, footerAboveKeyboard =
1247
1256
  }, [headerHeight]);
1248
1257
  const setFooterHeight = (0, import_react15.useCallback)(
1249
1258
  (h) => {
1259
+ if (h === 0 && footerHeight) return;
1250
1260
  setFooterHeightState(h);
1251
- footerHeightShared.value = h;
1261
+ footerHeightShared.value = h ?? 0;
1252
1262
  },
1253
- [footerHeightShared]
1263
+ [footerHeight, footerHeightShared]
1254
1264
  );
1255
- const scrollHeight = (0, import_react15.useMemo)(() => import_react_native13.Dimensions.get("window").height - (headerHeight ?? 0) - footerHeight, [headerHeight, footerHeight]);
1265
+ const scrollHeight = (0, import_react15.useMemo)(() => import_react_native13.Dimensions.get("window").height - (headerHeight ?? 0) - (footerHeight ?? 0), [headerHeight, footerHeight]);
1256
1266
  const effectiveHeaderFixed = fixed || (headerFixed ?? settings.headerFixed);
1257
1267
  const effectiveFooterFixed = fixed || (footerFixed ?? settings.footerFixed);
1258
1268
  const value = (0, import_react15.useMemo)(() => ({ 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]);
package/dist/index.mjs CHANGED
@@ -12,15 +12,18 @@ var RefreshControl = (props) => /* @__PURE__ */ jsx(RNRefreshControl, { refreshi
12
12
  // src/internal/ScrollViewChip.tsx
13
13
  import { Chip } from "@rific/auto-paper";
14
14
  import { StyleSheet } from "react-native";
15
+ import { Icon, useTheme } from "react-native-paper";
15
16
  import Animated from "react-native-reanimated";
16
17
  import { jsx as jsx2 } from "react/jsx-runtime";
17
18
  var ScrollViewChip = ({ chipProps, isHorizontal, onChipPress, onPress, style }) => {
18
- const { label, style: chipStyle, ...restChipProps } = chipProps ?? {};
19
+ const { colors } = useTheme();
20
+ const { label, selectedColor, style: chipStyle, ...restChipProps } = chipProps ?? {};
19
21
  const handlePress = onChipPress ? () => {
20
22
  onPress();
21
23
  onChipPress();
22
24
  } : onPress;
23
- return /* @__PURE__ */ jsx2(Animated.View, { style: [styles.chip, style], children: /* @__PURE__ */ jsx2(Chip, { compact: true, ...restChipProps, icon: isHorizontal ? "chevron-left" : "chevron-up", onPress: handlePress, style: [styles.chipInner, chipStyle], children: label ?? (isHorizontal ? "Start" : "Top") }) });
25
+ const iconColor = selectedColor ?? (restChipProps.mode === "outlined" ? colors.onSurfaceVariant : colors.onSecondaryContainer);
26
+ return /* @__PURE__ */ jsx2(Animated.View, { style: [styles.chip, style], children: /* @__PURE__ */ jsx2(Chip, { compact: true, selectedColor, ...restChipProps, avatar: /* @__PURE__ */ jsx2(Icon, { source: isHorizontal ? "chevron-left" : "chevron-up", size: 18, color: iconColor }), onPress: handlePress, style: [styles.chipInner, chipStyle], children: label ?? (isHorizontal ? "Start" : "Top") }) });
24
27
  };
25
28
  var styles = StyleSheet.create({
26
29
  chip: { alignItems: "center", left: 0, position: "absolute", right: 0, zIndex: 3 },
@@ -36,7 +39,7 @@ import { createContext } from "react";
36
39
  var ScrollViewContext = createContext({
37
40
  blur: true,
38
41
  footerAboveKeyboard: false,
39
- footerHeight: 0,
42
+ footerHeight: null,
40
43
  footerHeightShared: null,
41
44
  footerFixed: false,
42
45
  footerOffset: null,
@@ -504,7 +507,7 @@ import { Gesture as Gesture2, GestureDetector as GestureDetector2 } from "react-
504
507
  import { BlurView } from "@rific/auto-paper";
505
508
  import { useContext as useContext5 } from "react";
506
509
  import { StyleSheet as StyleSheet3, useWindowDimensions, View as View2 } from "react-native";
507
- import { useTheme } from "react-native-paper";
510
+ import { useTheme as useTheme2 } from "react-native-paper";
508
511
  import Animated2, { interpolateColor, useAnimatedStyle as useAnimatedStyle2 } from "react-native-reanimated";
509
512
  import { useSafeAreaInsets as useSafeAreaInsets2 } from "react-native-safe-area-context";
510
513
  import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
@@ -534,7 +537,7 @@ var HorizontalDots = ({ total }) => {
534
537
  const { blur, footerHeightShared, scrollPosition } = useContext5(ScrollViewContext);
535
538
  const { width } = useWindowDimensions();
536
539
  const insets = useSafeAreaInsets2();
537
- const theme = useTheme();
540
+ const theme = useTheme2();
538
541
  const insetsBottom = insets.bottom;
539
542
  const activeColor = theme.colors.primary;
540
543
  const inactiveColor = theme.colors.outlineVariant;
@@ -828,12 +831,12 @@ var FlatList = memo2(FlatListInner);
828
831
  // src/PullSearch.tsx
829
832
  import { forwardRef, useCallback as useCallback5, useContext as useContext8, useEffect as useEffect3, useImperativeHandle, useRef as useRef5, useState as useState4 } from "react";
830
833
  import { StyleSheet as StyleSheet4 } from "react-native";
831
- import { Searchbar, useTheme as useTheme2 } from "react-native-paper";
834
+ import { Searchbar, useTheme as useTheme3 } from "react-native-paper";
832
835
  import Animated3, { Extrapolation, interpolate, runOnJS as runOnJS3, useAnimatedReaction, useAnimatedStyle as useAnimatedStyle3 } from "react-native-reanimated";
833
836
  import { jsx as jsx6 } from "react/jsx-runtime";
834
837
  var PullSearch = forwardRef(function PullSearch2({ debounce, onChangeText, onHeightChange, placeholder, value: propValue }, ref) {
835
838
  const { headerHeight, scrollPosition } = useContext8(ScrollViewContext);
836
- const theme = useTheme2();
839
+ const theme = useTheme3();
837
840
  const inputRef = useRef5(null);
838
841
  const [barHeight, setBarHeight] = useState4(0);
839
842
  const [value, setValue] = useState4(propValue ?? "");
@@ -1008,12 +1011,12 @@ import Animated5, { useAnimatedStyle as useAnimatedStyle4 } from "react-native-r
1008
1011
  import { useSafeAreaInsets as useSafeAreaInsets3 } from "react-native-safe-area-context";
1009
1012
  import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1010
1013
  var ScrollViewFooter = ({ children, style }) => {
1011
- const { blur, footerAboveKeyboard, footerHeight, footerFixed, footerOffset, headerHeightShared, pullSearchHeightShared, scrollPosition, setFooterHeight, snapBackFooterShared } = useContext10(ScrollViewContext);
1014
+ const { blur, footerAboveKeyboard, footerHeight, footerFixed, footerOffset, headerHeightShared, pullSearchHeightShared, scrollPosition, setFooterHeight, snapBackFooterShared, tabBarHeight } = useContext10(ScrollViewContext);
1012
1015
  const insets = useSafeAreaInsets3();
1013
1016
  const keyboardHeight = useKeyboardInset();
1014
1017
  useEffect4(
1015
1018
  () => () => {
1016
- setFooterHeight(0);
1019
+ setFooterHeight(null);
1017
1020
  },
1018
1021
  [setFooterHeight]
1019
1022
  );
@@ -1029,17 +1032,23 @@ var ScrollViewFooter = ({ children, style }) => {
1029
1032
  if (snapBackFooterShared.value) return { transform: [{ translateY: footerOffset.value }] };
1030
1033
  const effective = scrollPosition.value + headerHeightShared.value - pullSearchHeightShared.value;
1031
1034
  if (effective <= 0) return { transform: [{ translateY: 0 }] };
1032
- return { transform: [{ translateY: Math.min(effective, footerHeight) }] };
1035
+ return { transform: [{ translateY: Math.min(effective, footerHeight ?? 0) }] };
1033
1036
  }, [footerHeight, footerFixed, headerHeightShared, pullSearchHeightShared, scrollPosition, snapBackFooterShared, footerOffset]);
1034
1037
  const containerPaddingBottom = footerFixed && footerAboveKeyboard ? keyboardHeight : 0;
1035
1038
  const rowPaddingBottom = footerFixed && footerAboveKeyboard ? Math.max(insets.bottom - keyboardHeight, 0) : insets.bottom;
1036
- return /* @__PURE__ */ jsxs5(Animated5.View, { onLayout: handleLayout, pointerEvents: "box-none", style: [styles6.footer, { paddingBottom: containerPaddingBottom }, footerStyle], children: [
1037
- footerHeight > 0 && /* @__PURE__ */ jsx8(BlurView2, { blur, style: StyleSheet5.absoluteFill }),
1038
- /* @__PURE__ */ jsx8(View5, { style: [styles6.row, { paddingBottom: rowPaddingBottom }, style], children })
1039
- ] });
1039
+ return (
1040
+ // bottom: tabBarHeight (not the styles.footer default of 0) - a consuming app's own persistent
1041
+ // tab bar (a fixture outside this package's header/footer concepts entirely, same as
1042
+ // useScrollList's content-inset reservation) sits below this at the true screen bottom, so this
1043
+ // bar has to clear it rather than rendering flush against 0 and overlapping/hiding behind it.
1044
+ /* @__PURE__ */ jsxs5(Animated5.View, { onLayout: handleLayout, pointerEvents: "box-none", style: [styles6.footer, { bottom: tabBarHeight, paddingBottom: containerPaddingBottom }, footerStyle], children: [
1045
+ (footerHeight ?? 0) > 0 && /* @__PURE__ */ jsx8(BlurView2, { blur, style: StyleSheet5.absoluteFill }),
1046
+ /* @__PURE__ */ jsx8(View5, { style: [styles6.row, { paddingBottom: rowPaddingBottom }, style], children })
1047
+ ] })
1048
+ );
1040
1049
  };
1041
1050
  var styles6 = StyleSheet5.create({
1042
- footer: { bottom: 0, left: 0, position: "absolute", right: 0, zIndex: 2 },
1051
+ footer: { left: 0, position: "absolute", right: 0, zIndex: 2 },
1043
1052
  row: { alignItems: "center", flexDirection: "row", zIndex: 1 }
1044
1053
  });
1045
1054
 
@@ -1047,7 +1056,7 @@ var styles6 = StyleSheet5.create({
1047
1056
  import { BlurView as BlurView3 } from "@rific/auto-paper";
1048
1057
  import { useContext as useContext11 } from "react";
1049
1058
  import { StyleSheet as StyleSheet6, Text, View as View6 } from "react-native";
1050
- import { Appbar, ProgressBar, Surface, useTheme as useTheme3 } from "react-native-paper";
1059
+ import { Appbar, ProgressBar, Surface, useTheme as useTheme4 } from "react-native-paper";
1051
1060
  import Animated6, { useAnimatedStyle as useAnimatedStyle5, useSharedValue as useSharedValue6 } from "react-native-reanimated";
1052
1061
  import { useSafeAreaInsets as useSafeAreaInsets4 } from "react-native-safe-area-context";
1053
1062
  import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
@@ -1057,7 +1066,7 @@ var ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, backActionAc
1057
1066
  const { settings } = useContext11(ScrollViewSettingsContext);
1058
1067
  const effectiveBackActionFixed = backActionFixed ?? settings.backActionFixed;
1059
1068
  const insets = useSafeAreaInsets4();
1060
- const theme = useTheme3();
1069
+ const theme = useTheme4();
1061
1070
  const top = topInset ? insets.top : 0;
1062
1071
  const measuredShared = useSharedValue6(false);
1063
1072
  const handleLayout = ({
@@ -1112,10 +1121,10 @@ var ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, backActionAc
1112
1121
  /* @__PURE__ */ jsx9(Animated6.View, { pointerEvents: "none", style: [styles7.progress, progressStyle], children: /* @__PURE__ */ jsx9(ProgressBar, { indeterminate: progress === null, visible: progressing, progress: progress ?? void 0 }) }),
1113
1122
  backAction && (effectiveBackActionFixed ? /* @__PURE__ */ jsxs6(Animated6.View, { style: leadingStyle, children: [
1114
1123
  /* @__PURE__ */ jsx9(ActionBg, { blur, style: actionStyle }),
1115
- /* @__PURE__ */ jsx9(Appbar.BackAction, { accessibilityLabel: backActionAccessibilityLabel, onPress: backAction, size: iconSize ?? actionSize / 2, style: buttonStyle })
1124
+ typeof backAction === "function" ? /* @__PURE__ */ jsx9(Appbar.BackAction, { accessibilityLabel: backActionAccessibilityLabel, onPress: backAction, size: iconSize ?? actionSize / 2, style: buttonStyle }) : backAction
1116
1125
  ] }) : /* @__PURE__ */ jsxs6(Animated6.View, { style: [leadingStyle, translateStyle], children: [
1117
1126
  /* @__PURE__ */ jsx9(ActionBg, { blur, style: actionStyle }),
1118
- /* @__PURE__ */ jsx9(Appbar.BackAction, { accessibilityLabel: backActionAccessibilityLabel, onPress: backAction, size: iconSize ?? actionSize / 2, style: buttonStyle })
1127
+ typeof backAction === "function" ? /* @__PURE__ */ jsx9(Appbar.BackAction, { accessibilityLabel: backActionAccessibilityLabel, onPress: backAction, size: iconSize ?? actionSize / 2, style: buttonStyle }) : backAction
1119
1128
  ] })),
1120
1129
  trailingAction && (trailingActionFixed ? /* @__PURE__ */ jsxs6(View6, { style: trailingStyle, children: [
1121
1130
  /* @__PURE__ */ jsx9(ActionBg, { blur, style: actionStyle }),
@@ -1155,7 +1164,7 @@ var ScrollViewProvider = ({ blur, children, fixed = false, footerAboveKeyboard =
1155
1164
  const effectiveSnapBackHeader = snapBackHeader ?? settings.snapBackHeader;
1156
1165
  const effectiveSnapBackFooter = snapBackFooter ?? settings.snapBackFooter;
1157
1166
  const [headerHeight, setHeaderHeightState] = useState5(null);
1158
- const [footerHeight, setFooterHeightState] = useState5(0);
1167
+ const [footerHeight, setFooterHeightState] = useState5(null);
1159
1168
  const [progress, setProgress] = useState5(null);
1160
1169
  const [progressing, setProgressing] = useState5(false);
1161
1170
  const scrollPosition = useSharedValue7(0);
@@ -1195,12 +1204,13 @@ var ScrollViewProvider = ({ blur, children, fixed = false, footerAboveKeyboard =
1195
1204
  }, [headerHeight]);
1196
1205
  const setFooterHeight = useCallback7(
1197
1206
  (h) => {
1207
+ if (h === 0 && footerHeight) return;
1198
1208
  setFooterHeightState(h);
1199
- footerHeightShared.value = h;
1209
+ footerHeightShared.value = h ?? 0;
1200
1210
  },
1201
- [footerHeightShared]
1211
+ [footerHeight, footerHeightShared]
1202
1212
  );
1203
- const scrollHeight = useMemo5(() => Dimensions3.get("window").height - (headerHeight ?? 0) - footerHeight, [headerHeight, footerHeight]);
1213
+ const scrollHeight = useMemo5(() => Dimensions3.get("window").height - (headerHeight ?? 0) - (footerHeight ?? 0), [headerHeight, footerHeight]);
1204
1214
  const effectiveHeaderFixed = fixed || (headerFixed ?? settings.headerFixed);
1205
1215
  const effectiveFooterFixed = fixed || (footerFixed ?? settings.footerFixed);
1206
1216
  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]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rific/scroll-view",
3
- "version": "0.6.1",
3
+ "version": "0.6.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",
@@ -4,7 +4,7 @@ import { SharedValue } from 'react-native-reanimated'
4
4
  export type ScrollViewContextType = {
5
5
  blur: boolean
6
6
  footerAboveKeyboard: boolean
7
- footerHeight: number
7
+ footerHeight: number | null
8
8
  footerHeightShared: SharedValue<number>
9
9
  footerFixed: boolean
10
10
  footerOffset: SharedValue<number>
@@ -28,7 +28,7 @@ export type ScrollViewContextType = {
28
28
  progressing: boolean
29
29
  scrollHeight: number
30
30
  scrollPosition: SharedValue<number>
31
- setFooterHeight: (h: number) => void
31
+ setFooterHeight: (h: number | null) => void
32
32
  setHeaderHeight: (h: number | null) => void
33
33
  setProgress: (p: number | null) => void
34
34
  setProgressing: (b: boolean) => void
@@ -38,7 +38,7 @@ export type ScrollViewContextType = {
38
38
  export const ScrollViewContext = createContext<ScrollViewContextType>({
39
39
  blur: true,
40
40
  footerAboveKeyboard: false,
41
- footerHeight: 0,
41
+ footerHeight: null,
42
42
  footerHeightShared: null as unknown as SharedValue<number>,
43
43
  footerFixed: false,
44
44
  footerOffset: null as unknown as SharedValue<number>,
@@ -13,12 +13,12 @@ export type ScrollViewFooterProps = {
13
13
  }
14
14
 
15
15
  export const ScrollViewFooter = ({ children, style }: ScrollViewFooterProps) => {
16
- const { blur, footerAboveKeyboard, footerHeight, footerFixed, footerOffset, headerHeightShared, pullSearchHeightShared, scrollPosition, setFooterHeight, snapBackFooterShared } = useContext(ScrollViewContext)
16
+ const { blur, footerAboveKeyboard, footerHeight, footerFixed, footerOffset, headerHeightShared, pullSearchHeightShared, scrollPosition, setFooterHeight, snapBackFooterShared, tabBarHeight } = useContext(ScrollViewContext)
17
17
  const insets = useSafeAreaInsets()
18
18
  const keyboardHeight = useKeyboardInset()
19
19
  useEffect(
20
20
  () => () => {
21
- setFooterHeight(0)
21
+ setFooterHeight(null)
22
22
  },
23
23
  [setFooterHeight]
24
24
  )
@@ -38,7 +38,7 @@ export const ScrollViewFooter = ({ children, style }: ScrollViewFooterProps) =>
38
38
  if (snapBackFooterShared.value) return { transform: [{ translateY: footerOffset.value }] }
39
39
  const effective = scrollPosition.value + headerHeightShared.value - pullSearchHeightShared.value
40
40
  if (effective <= 0) return { transform: [{ translateY: 0 }] }
41
- return { transform: [{ translateY: Math.min(effective, footerHeight) }] }
41
+ return { transform: [{ translateY: Math.min(effective, footerHeight ?? 0) }] }
42
42
  }, [footerHeight, footerFixed, headerHeightShared, pullSearchHeightShared, scrollPosition, snapBackFooterShared, footerOffset])
43
43
  // Floating above the keyboard is done by GROWING this container (via paddingBottom) rather than
44
44
  // translating it. Translating the whole bar left its bottom edge — and the BlurView filling it —
@@ -66,14 +66,18 @@ export const ScrollViewFooter = ({ children, style }: ScrollViewFooterProps) =>
66
66
  // of dropping past it and correcting back up.
67
67
  const rowPaddingBottom = footerFixed && footerAboveKeyboard ? Math.max(insets.bottom - keyboardHeight, 0) : insets.bottom
68
68
  return (
69
- <Animated.View onLayout={handleLayout} pointerEvents='box-none' style={[styles.footer, { paddingBottom: containerPaddingBottom }, footerStyle]}>
70
- {footerHeight > 0 && <BlurView blur={blur} style={StyleSheet.absoluteFill} />}
69
+ // bottom: tabBarHeight (not the styles.footer default of 0) - a consuming app's own persistent
70
+ // tab bar (a fixture outside this package's header/footer concepts entirely, same as
71
+ // useScrollList's content-inset reservation) sits below this at the true screen bottom, so this
72
+ // bar has to clear it rather than rendering flush against 0 and overlapping/hiding behind it.
73
+ <Animated.View onLayout={handleLayout} pointerEvents='box-none' style={[styles.footer, { bottom: tabBarHeight, paddingBottom: containerPaddingBottom }, footerStyle]}>
74
+ {(footerHeight ?? 0) > 0 && <BlurView blur={blur} style={StyleSheet.absoluteFill} />}
71
75
  <View style={[styles.row, { paddingBottom: rowPaddingBottom }, style]}>{children}</View>
72
76
  </Animated.View>
73
77
  )
74
78
  }
75
79
 
76
80
  const styles = StyleSheet.create({
77
- footer: { bottom: 0, left: 0, position: 'absolute', right: 0, zIndex: 2 },
81
+ footer: { left: 0, position: 'absolute', right: 0, zIndex: 2 },
78
82
  row: { alignItems: 'center', flexDirection: 'row', zIndex: 1 }
79
83
  })
@@ -11,7 +11,14 @@ import { ScrollViewSettingsContext } from './ScrollViewSettingsContext'
11
11
  export type ScrollViewHeaderProps = {
12
12
  actionSize?: number
13
13
  actionStyle?: ViewStyle
14
- backAction?: () => void
14
+ // A plain callback renders the default Appbar.BackAction (themed onSurface, sized/positioned by
15
+ // actionSize/iconSize below) — the common case, unchanged from before. Passing a ReactNode
16
+ // instead skips that default entirely and renders the given element in its place, same escape
17
+ // hatch trailingAction already gave the other corner: a caller that wants its own color, icon,
18
+ // or component altogether (a colored IconButton, say) isn't stuck fighting Appbar.BackAction's
19
+ // own fixed color/no-customization API to get it. backActionAccessibilityLabel/iconSize below
20
+ // only apply to the default-rendered case — a custom element carries its own.
21
+ backAction?: (() => void) | ReactNode
15
22
  backActionAccessibilityLabel?: string
16
23
  backActionFixed?: boolean
17
24
  caption?: string
@@ -147,12 +154,12 @@ export const ScrollViewHeader = ({ actionSize = 48, actionStyle, backAction, bac
147
154
  (effectiveBackActionFixed ? (
148
155
  <Animated.View style={leadingStyle}>
149
156
  <ActionBg blur={blur} style={actionStyle} />
150
- <Appbar.BackAction accessibilityLabel={backActionAccessibilityLabel} onPress={backAction} size={iconSize ?? actionSize / 2} style={buttonStyle} />
157
+ {typeof backAction === 'function' ? <Appbar.BackAction accessibilityLabel={backActionAccessibilityLabel} onPress={backAction} size={iconSize ?? actionSize / 2} style={buttonStyle} /> : backAction}
151
158
  </Animated.View>
152
159
  ) : (
153
160
  <Animated.View style={[leadingStyle, translateStyle]}>
154
161
  <ActionBg blur={blur} style={actionStyle} />
155
- <Appbar.BackAction accessibilityLabel={backActionAccessibilityLabel} onPress={backAction} size={iconSize ?? actionSize / 2} style={buttonStyle} />
162
+ {typeof backAction === 'function' ? <Appbar.BackAction accessibilityLabel={backActionAccessibilityLabel} onPress={backAction} size={iconSize ?? actionSize / 2} style={buttonStyle} /> : backAction}
156
163
  </Animated.View>
157
164
  ))}
158
165
  {trailingAction &&
@@ -26,7 +26,7 @@ export const ScrollViewProvider = ({ blur, children, fixed = false, footerAboveK
26
26
  const effectiveSnapBackHeader = snapBackHeader ?? settings.snapBackHeader
27
27
  const effectiveSnapBackFooter = snapBackFooter ?? settings.snapBackFooter
28
28
  const [headerHeight, setHeaderHeightState] = useState<number | null>(null)
29
- const [footerHeight, setFooterHeightState] = useState(0)
29
+ const [footerHeight, setFooterHeightState] = useState<number | null>(null)
30
30
  const [progress, setProgress] = useState<number | null>(null)
31
31
  const [progressing, setProgressing] = useState(false)
32
32
  const scrollPosition = useSharedValue(0)
@@ -71,13 +71,20 @@ export const ScrollViewProvider = ({ blur, children, fixed = false, footerAboveK
71
71
  return () => clearTimeout(timeout)
72
72
  }, [headerHeight])
73
73
  const setFooterHeight = useCallback(
74
- (h: number) => {
74
+ (h: number | null) => {
75
+ // Same race setHeaderHeight guards against, on the footer side: react-native-screens' web
76
+ // shim (and a footer's own unmount cleanup below, which now passes null instead of 0 for
77
+ // exactly this reason) can report/force a spurious 0 while a real footer is effectively
78
+ // still on screen (e.g. a wizard cycling through several <ScrollViewFooter> instances).
79
+ // Never let that clobber a known height — only an explicit null (genuinely no footer
80
+ // mounted) may reset it.
81
+ if (h === 0 && footerHeight) return
75
82
  setFooterHeightState(h)
76
- footerHeightShared.value = h
83
+ footerHeightShared.value = h ?? 0
77
84
  },
78
- [footerHeightShared]
85
+ [footerHeight, footerHeightShared]
79
86
  )
80
- const scrollHeight = useMemo(() => Dimensions.get('window').height - (headerHeight ?? 0) - footerHeight, [headerHeight, footerHeight])
87
+ const scrollHeight = useMemo(() => Dimensions.get('window').height - (headerHeight ?? 0) - (footerHeight ?? 0), [headerHeight, footerHeight])
81
88
  const effectiveHeaderFixed = fixed || (headerFixed ?? settings.headerFixed)
82
89
  const effectiveFooterFixed = fixed || (footerFixed ?? settings.footerFixed)
83
90
  const value = useMemo(() => ({ 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])
@@ -1,9 +1,16 @@
1
1
  import { Chip, type ChipProps as AutoPaperChipProps } from '@rific/auto-paper'
2
2
  import { type ReactNode } from 'react'
3
3
  import { StyleSheet, type ViewStyle } from 'react-native'
4
+ import { Icon, useTheme } from 'react-native-paper'
4
5
  import Animated, { type AnimatedStyle } from 'react-native-reanimated'
5
6
 
6
- export type ChipProps = Omit<AutoPaperChipProps, 'compact' | 'icon' | 'onPress'> & {
7
+ // 'children' omitted alongside the rest — this chip's own text always comes from `label` (or the
8
+ // isHorizontal-driven 'Start'/'Top' default below), passed as the real JSX children the underlying
9
+ // Chip renders with, which is what actually wins: React always takes the children an element is
10
+ // given between its tags over one merely passed as a "children" prop via spread. A `children` in
11
+ // chipProps would therefore type-check but silently do nothing at runtime — omitting it here means
12
+ // TypeScript catches that mismatch instead of a caller finding out the hard way.
13
+ export type ChipProps = Omit<AutoPaperChipProps, 'compact' | 'icon' | 'onPress' | 'children'> & {
7
14
  label?: ReactNode
8
15
  }
9
16
 
@@ -16,16 +23,27 @@ type Props = {
16
23
  }
17
24
 
18
25
  export const ScrollViewChip = ({ chipProps, isHorizontal, onChipPress, onPress, style }: Props) => {
19
- const { label, style: chipStyle, ...restChipProps } = (chipProps ?? {}) as Partial<ChipProps>
26
+ const { colors } = useTheme()
27
+ const { label, selectedColor, style: chipStyle, ...restChipProps } = (chipProps ?? {}) as Partial<ChipProps>
20
28
  const handlePress = onChipPress
21
29
  ? () => {
22
30
  onPress()
23
31
  onChipPress()
24
32
  }
25
33
  : onPress
34
+ // Rendered via `avatar`, not Chip's own `icon` prop: react-native-paper's MD3 Chip hardcodes
35
+ // `icon`'s color to theme.colors.primary and ignores `selectedColor` entirely (its own doc
36
+ // comment: "With theme version 3 selectedColor doesn't apply to the icon. If you want [a]
37
+ // custom color for the icon, render your own Icon component") — a chip whose own background
38
+ // IS theme.colors.primary (this library's callers all set that; see Hangman's own vivid-chip
39
+ // convention) renders that icon invisible against itself. `avatar` has no such override; it
40
+ // clones whatever element/color it's given. Falls back to the same default Paper itself would
41
+ // resolve for unselected/non-outlined text (onSecondaryContainer) or outlined text
42
+ // (onSurfaceVariant), so the icon still matches the label color when no selectedColor is set.
43
+ const iconColor = selectedColor ?? (restChipProps.mode === 'outlined' ? colors.onSurfaceVariant : colors.onSecondaryContainer)
26
44
  return (
27
45
  <Animated.View style={[styles.chip, style]}>
28
- <Chip compact {...restChipProps} icon={isHorizontal ? 'chevron-left' : 'chevron-up'} onPress={handlePress} style={[styles.chipInner, chipStyle]}>
46
+ <Chip compact selectedColor={selectedColor} {...restChipProps} avatar={<Icon source={isHorizontal ? 'chevron-left' : 'chevron-up'} size={18} color={iconColor} />} onPress={handlePress} style={[styles.chipInner, chipStyle]}>
29
47
  {label ?? (isHorizontal ? 'Start' : 'Top')}
30
48
  </Chip>
31
49
  </Animated.View>