@momo-kits/foundation 0.161.2-test.1 → 0.162.2-sp.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.
@@ -37,10 +37,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
37
37
  const action = useRef<undefined | string>(undefined);
38
38
  const insets = useSafeAreaInsets();
39
39
  const heightHeader = useHeaderHeight();
40
- // AI-GENERATED START: iOS caps at 21pt (home indicator), Android uses full nav bar height
41
- const bottomInset = Platform.OS === 'ios' ? Math.min(insets.bottom, 21) : insets.bottom;
42
- const keyboardOffset = heightHeader - bottomInset;
43
- // AI-GENERATED END
40
+ const keyboardOffset = heightHeader - Math.min(insets.bottom, 21);
44
41
 
45
42
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
46
43
 
@@ -314,7 +311,7 @@ const BottomSheet: React.FC<BottomSheetParams> = props => {
314
311
  {useBottomInset && (
315
312
  <View
316
313
  style={{
317
- height: bottomInset,
314
+ height: Math.min(insets.bottom, 21),
318
315
  backgroundColor: backgroundColor,
319
316
  }}
320
317
  />
@@ -296,9 +296,7 @@ const BottomTab: React.FC<BottomTabProps> = ({
296
296
  activeTintColor={theme.colors.primary}
297
297
  style={[
298
298
  {
299
- // AI-GENERATED START: iOS caps at 21pt (home indicator), Android uses full nav bar height
300
- height: 64 + (Platform.OS === 'ios' ? Math.min(insets.bottom, 21) : insets.bottom),
301
- // AI-GENERATED END
299
+ height: 64 + Math.min(insets.bottom, 21),
302
300
  },
303
301
  ]}
304
302
  />
@@ -8,6 +8,7 @@ import { type HeaderAnimatedProps } from '../types';
8
8
  import React from 'react';
9
9
  import { Image } from '../../Image';
10
10
  import { Colors } from '../../Consts';
11
+ import { useNormalizedSharedValue } from './useAnimatedCompat';
11
12
 
12
13
  const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
13
14
  animatedValue,
@@ -15,23 +16,19 @@ const HeaderAnimated: React.FC<HeaderAnimatedProps> = ({
15
16
  useScale = true,
16
17
  children,
17
18
  }) => {
19
+ const sharedValue = useNormalizedSharedValue(animatedValue);
18
20
  const animatedStyle = useAnimatedStyle(() => {
19
21
  const scale = useScale
20
- ? interpolate(
21
- animatedValue.value,
22
- [-300, 0, 300],
23
- [4, 1, 1],
24
- Extrapolation.CLAMP,
25
- )
22
+ ? interpolate(sharedValue.value, [-300, 0, 300], [4, 1, 1], Extrapolation.CLAMP)
26
23
  : 1;
27
24
  const opacity = interpolate(
28
- animatedValue.value,
25
+ sharedValue.value,
29
26
  [0, 150, 300],
30
27
  [1, 0.5, 0],
31
28
  Extrapolation.CLAMP,
32
29
  );
33
30
  const translateY = interpolate(
34
- animatedValue.value,
31
+ sharedValue.value,
35
32
  [-300, 0],
36
33
  [-80, -2],
37
34
  Extrapolation.CLAMP,
@@ -1,12 +1,13 @@
1
1
  import LinearGradient from 'react-native-linear-gradient';
2
2
  import { StyleSheet, View } from 'react-native';
3
3
  import React, { useContext } from 'react';
4
- import Animated, { useSharedValue } from 'react-native-reanimated';
4
+ import Animated from 'react-native-reanimated';
5
5
  import { HeaderBackgroundProps } from '../types';
6
6
  import { ApplicationContext, MiniAppContext } from '../../Context';
7
7
  import { Colors, Styles } from '../../Consts';
8
8
  import { Image } from '../../Image';
9
9
  import BackgroundImageView from './BackgroundImageView';
10
+ import { useNormalizedSharedValue } from './useAnimatedCompat';
10
11
 
11
12
  const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
12
13
  animatedValue,
@@ -19,8 +20,7 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
19
20
  const context = useContext<any>(MiniAppContext);
20
21
  const gradientColor = customGradientColor ?? theme.colors.gradient;
21
22
  const headerImage = headerBackground ?? theme.assets?.headerBackground;
22
- const fallback = useSharedValue(0);
23
- const sv = animatedValue ?? fallback;
23
+ const sharedValue = useNormalizedSharedValue(animatedValue);
24
24
 
25
25
  const showBaseLineDebug = context?.features?.showBaseLineDebug ?? false;
26
26
 
@@ -29,7 +29,7 @@ const HeaderBackground: React.FC<HeaderBackgroundProps> = ({
29
29
  <BackgroundImageView
30
30
  useShadowHeader={useShadowHeader}
31
31
  heightHeader={'100%'}
32
- animatedValue={sv}
32
+ animatedValue={sharedValue}
33
33
  headerBackground={headerImage}
34
34
  />
35
35
  <View style={styles.gradientContainer}>
@@ -5,8 +5,6 @@ import Animated, {
5
5
  Extrapolation,
6
6
  interpolate,
7
7
  useAnimatedStyle,
8
- useSharedValue,
9
- type SharedValue,
10
8
  } from 'react-native-reanimated';
11
9
  import { ApplicationContext, MiniAppContext } from '../../Context';
12
10
  import { HeaderType } from '../../Layout/types';
@@ -16,6 +14,10 @@ import { Colors, Radius, Spacing } from '../../Consts';
16
14
  import { Image } from '../../Image';
17
15
  import { SearchHeaderProps } from '../types';
18
16
  import BackgroundImageView from './BackgroundImageView';
17
+ import {
18
+ AnimatedCompatValue,
19
+ useNormalizedSharedValue,
20
+ } from './useAnimatedCompat';
19
21
 
20
22
  const SCREEN_PADDING = 12;
21
23
  const BACK_WIDTH = 28;
@@ -24,7 +26,7 @@ const { width: screenWidth } = Dimensions.get('window');
24
26
 
25
27
  const HeaderExtendHeader: React.FC<{
26
28
  headerType?: HeaderType;
27
- animatedValue: SharedValue<number>;
29
+ animatedValue: AnimatedCompatValue;
28
30
  heightHeader: number;
29
31
  headerRightWidth: number;
30
32
  inputSearchProps?: SearchHeaderProps;
@@ -46,8 +48,7 @@ const HeaderExtendHeader: React.FC<{
46
48
  }) => {
47
49
  const { theme } = useContext(ApplicationContext);
48
50
  const context = useContext<any>(MiniAppContext);
49
- const fallback = useSharedValue(0);
50
- const sv = animatedValue ?? fallback;
51
+ const sharedValue = useNormalizedSharedValue(animatedValue);
51
52
  const gradientColor = customGradientColor ?? theme.colors.gradient;
52
53
  const headerBackground = customBackground ?? theme.assets?.headerBackground;
53
54
  const leftPosition = inputSearchProps?.leftPosition || BACK_WIDTH + 20;
@@ -61,7 +62,7 @@ const HeaderExtendHeader: React.FC<{
61
62
 
62
63
  const heightStyle = useAnimatedStyle(() => ({
63
64
  height: interpolate(
64
- sv.value,
65
+ sharedValue.value,
65
66
  [0, 100],
66
67
  [heightHeader + 52, heightHeader],
67
68
  Extrapolation.CLAMP,
@@ -69,14 +70,14 @@ const HeaderExtendHeader: React.FC<{
69
70
  }));
70
71
 
71
72
  const gradientOpacityStyle = useAnimatedStyle(() => ({
72
- opacity: interpolate(sv.value, [0, 52], [1, 0], Extrapolation.CLAMP),
73
+ opacity: interpolate(sharedValue.value, [0, 52], [1, 0], Extrapolation.CLAMP),
73
74
  }));
74
75
 
75
76
  const searchTranslateStyle = useAnimatedStyle(() => ({
76
77
  transform: [
77
78
  {
78
79
  translateX: interpolate(
79
- sv.value,
80
+ sharedValue.value,
80
81
  [0, 100],
81
82
  [SCREEN_PADDING, leftPosition],
82
83
  Extrapolation.CLAMP,
@@ -84,7 +85,7 @@ const HeaderExtendHeader: React.FC<{
84
85
  },
85
86
  ],
86
87
  width: interpolate(
87
- sv.value,
88
+ sharedValue.value,
88
89
  [0, 100],
89
90
  [
90
91
  screenWidth - SCREEN_PADDING * 2,
@@ -95,7 +96,7 @@ const HeaderExtendHeader: React.FC<{
95
96
  }));
96
97
 
97
98
  const searchBackgroundStyle = useAnimatedStyle(() => {
98
- const t = Math.min(Math.max(sv.value / 100, 0), 1);
99
+ const t = Math.min(Math.max(sharedValue.value / 100, 0), 1);
99
100
  return { backgroundColor: t === 0
100
101
  ? theme.colors.background.surface
101
102
  : theme.colors.background.default };
@@ -103,7 +104,7 @@ const HeaderExtendHeader: React.FC<{
103
104
 
104
105
  const extendedHeightStyle = useAnimatedStyle(() => ({
105
106
  height: interpolate(
106
- sv.value,
107
+ sharedValue.value,
107
108
  [0, 100],
108
109
  [heightHeader + 52, heightHeader],
109
110
  Extrapolation.CLAMP,
@@ -117,7 +118,7 @@ const HeaderExtendHeader: React.FC<{
117
118
  <BackgroundImageView
118
119
  useShadowHeader={useShadowHeader}
119
120
  heightHeader={heightHeader}
120
- animatedValue={sv}
121
+ animatedValue={sharedValue}
121
122
  headerBackground={headerBackground}
122
123
  />
123
124
  <Animated.View
@@ -185,7 +186,7 @@ const HeaderExtendHeader: React.FC<{
185
186
  <BackgroundImageView
186
187
  useShadowHeader={useShadowHeader}
187
188
  heightHeader={heightHeader}
188
- animatedValue={sv}
189
+ animatedValue={sharedValue}
189
190
  headerBackground={headerBackground}
190
191
  />
191
192
  {!!gradientColor && (
@@ -9,7 +9,6 @@ import Animated, {
9
9
  Extrapolation,
10
10
  interpolate,
11
11
  useAnimatedStyle,
12
- type SharedValue,
13
12
  } from 'react-native-reanimated';
14
13
  import { ApplicationContext, MiniAppContext } from '../../Context';
15
14
  import { exportFontFamily, Text, useScaleSize } from '../../Text';
@@ -22,6 +21,10 @@ import {
22
21
  import { Image } from '../../Image';
23
22
  import { Icon } from '../../Icon';
24
23
  import { Skeleton } from '../../Skeleton';
24
+ import {
25
+ AnimatedCompatValue,
26
+ useNormalizedSharedValue,
27
+ } from './useAnimatedCompat';
25
28
 
26
29
  type HeaderTitleInterpolate = {
27
30
  inputRange: number[];
@@ -29,7 +32,7 @@ type HeaderTitleInterpolate = {
29
32
  };
30
33
 
31
34
  type HeaderTitleExtraProps = {
32
- animatedValue?: SharedValue<number>;
35
+ animatedValue?: AnimatedCompatValue;
33
36
  interpolate?: HeaderTitleInterpolate;
34
37
  tintColor?: string;
35
38
  children?: React.ReactNode;
@@ -47,15 +50,16 @@ const HeaderTitle: React.FC<HeaderTitleExtraProps & { [key: string]: any }> = pr
47
50
  inputRange: [0, 200],
48
51
  outputRange: [0, 1],
49
52
  };
50
- const animatedValue = props.animatedValue;
53
+ const hasAnimated = props.animatedValue != null;
54
+ const sharedValue = useNormalizedSharedValue(props.animatedValue);
51
55
 
52
56
  const animatedStyle = useAnimatedStyle(() => {
53
- if (!animatedValue) {
57
+ if (!hasAnimated) {
54
58
  return { opacity: 1 };
55
59
  }
56
60
  return {
57
61
  opacity: interpolate(
58
- animatedValue.value,
62
+ sharedValue.value,
59
63
  interpolateConfig.inputRange,
60
64
  interpolateConfig.outputRange,
61
65
  Extrapolation.CLAMP,
@@ -6,14 +6,12 @@ import {
6
6
  TouchableOpacity,
7
7
  View,
8
8
  } from 'react-native';
9
- import Animated, {
10
- useAnimatedStyle,
11
- useSharedValue,
12
- } from 'react-native-reanimated';
9
+ import Animated, { useAnimatedStyle } from 'react-native-reanimated';
13
10
  import React, { useContext } from 'react';
14
11
  import { SearchHeaderProps } from '../types';
15
12
  import { ApplicationContext, MiniAppContext } from '../../Context';
16
13
  import { Text } from '../../Text';
14
+ import { useNormalizedSharedValue } from './useAnimatedCompat';
17
15
 
18
16
  const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
19
17
  (
@@ -31,13 +29,12 @@ const SearchHeader = React.forwardRef<InputRef, SearchHeaderProps>(
31
29
  const BACK_WIDTH = 28;
32
30
  const { width: screenWidth } = Dimensions.get('window');
33
31
 
34
- const fallback = useSharedValue(0);
35
- const sv = animatedValue ?? fallback;
32
+ const sharedValue = useNormalizedSharedValue(animatedValue);
36
33
  const leftPosition = props?.leftPosition ?? BACK_WIDTH + 20;
37
34
  const headerRightWidth = props?.headerRightWidth ?? 73;
38
35
 
39
36
  const backgroundStyle = useAnimatedStyle(() => {
40
- const t = Math.min(Math.max(sv.value / 100, 0), 1);
37
+ const t = Math.min(Math.max(sharedValue.value / 100, 0), 1);
41
38
  return {
42
39
  backgroundColor: t === 0
43
40
  ? theme.colors.background.surface
@@ -0,0 +1,63 @@
1
+ import { useEffect } from 'react';
2
+ import { Animated } from 'react-native';
3
+ import { useSharedValue, type SharedValue } from 'react-native-reanimated';
4
+
5
+ /**
6
+ * Union of the two animation runtimes a consumer might hand to a foundation
7
+ * component. react-native `Animated.Value` (legacy) and reanimated
8
+ * `SharedValue` are different objects and cannot be used interchangeably, so
9
+ * foundation components normalize whatever they receive into a `SharedValue`
10
+ * via {@link useNormalizedSharedValue} before rendering with reanimated.
11
+ */
12
+ export type AnimatedCompatValue = Animated.Value | SharedValue<number>;
13
+
14
+ /**
15
+ * A react-native `Animated.Value` exposes `setValue`/`interpolate` methods; a
16
+ * reanimated `SharedValue` does not.
17
+ */
18
+ export const isRNAnimatedValue = (v: unknown): v is Animated.Value =>
19
+ !!v &&
20
+ typeof (v as Animated.Value).setValue === 'function' &&
21
+ typeof (v as Animated.Value).interpolate === 'function';
22
+
23
+ /**
24
+ * Heuristic: a reanimated `SharedValue` carries a `value` accessor but none of
25
+ * the `Animated.Value` methods.
26
+ */
27
+ export const isSharedValue = (v: unknown): v is SharedValue<number> =>
28
+ !!v && !isRNAnimatedValue(v) && 'value' in (v as object);
29
+
30
+ /**
31
+ * Normalize an incoming animated value (either runtime, or nothing) into a
32
+ * reanimated `SharedValue<number>` that foundation components can drive with
33
+ * `useAnimatedStyle`/`interpolate`.
34
+ *
35
+ * - SharedValue → returned as-is (zero overhead, shares the same UI-thread cell)
36
+ * - Animated.Value → bridged: a JS-thread `addListener` mirrors its value into
37
+ * an internal SharedValue. NOTE: when the source `Animated.Value` is driven
38
+ * by `Animated.event(..., { useNativeDriver: true })`, `addListener` does NOT
39
+ * fire per frame, so the bridge stays flat. For the Screen-driven case the
40
+ * bridge is fed from the scroll `listener` instead (see Screen.tsx); this
41
+ * hook's listener is the safety net for standalone / JS-driven usage.
42
+ * - undefined → a fresh internal SharedValue stuck at 0
43
+ */
44
+ export function useNormalizedSharedValue(
45
+ input?: AnimatedCompatValue,
46
+ ): SharedValue<number> {
47
+ const fallback = useSharedValue(0);
48
+
49
+ useEffect(() => {
50
+ if (!isRNAnimatedValue(input)) {
51
+ return;
52
+ }
53
+ const id = input.addListener(({ value }) => {
54
+ fallback.value = value;
55
+ });
56
+ return () => input.removeListener(id);
57
+ }, [input, fallback]);
58
+
59
+ if (isSharedValue(input)) {
60
+ return input;
61
+ }
62
+ return fallback;
63
+ }
@@ -2,6 +2,7 @@ import { EventArg } from '@react-navigation/core';
2
2
  import { StackNavigationOptions } from '@react-navigation/stack';
3
3
  import React, { ReactNode } from 'react';
4
4
  import {
5
+ Animated,
5
6
  TouchableOpacityProps,
6
7
  ViewProps,
7
8
  ViewStyle,
@@ -258,7 +259,7 @@ export interface HeaderBackProps extends NavigationButtonProps {
258
259
  }
259
260
 
260
261
  export type HeaderBackgroundProps = {
261
- animatedValue?: SharedValue<number>;
262
+ animatedValue?: Animated.Value | SharedValue<number>;
262
263
  useGradient?: boolean;
263
264
  useShadowHeader?: boolean;
264
265
  backgroundColor?: string;
@@ -298,7 +299,7 @@ export type TitleJourneyProps = {
298
299
  };
299
300
 
300
301
  export interface HeaderAnimatedProps extends ViewProps {
301
- animatedValue: SharedValue<number>;
302
+ animatedValue: Animated.Value | SharedValue<number>;
302
303
  image: string;
303
304
  useScale?: boolean;
304
305
  }
@@ -342,7 +343,7 @@ export type AnimatedHeader = {
342
343
 
343
344
  export interface SearchHeaderProps extends InputSearchProps {
344
345
  ref?: React.RefObject<InputRef>;
345
- animatedValue?: SharedValue<number>;
346
+ animatedValue?: Animated.Value | SharedValue<number>;
346
347
  headerRightWidth?: 0 | 74 | 110 | number;
347
348
  leftPosition?: 12 | 48 | number;
348
349
  renderButtons?: () => ReactNode;
@@ -11,11 +11,14 @@ import Animated, {
11
11
  useSharedValue,
12
12
  withTiming,
13
13
  runOnJS,
14
- type SharedValue,
15
14
  } from 'react-native-reanimated';
16
15
  import { ApplicationContext } from '../Context';
17
16
  import { Icon } from '../Icon';
18
17
  import { useScaleSize } from '../Text';
18
+ import {
19
+ AnimatedCompatValue,
20
+ useNormalizedSharedValue,
21
+ } from '../Application/Components/useAnimatedCompat';
19
22
 
20
23
  export interface FloatingButtonProps {
21
24
  label?: string;
@@ -24,7 +27,7 @@ export interface FloatingButtonProps {
24
27
  icon?: string;
25
28
  iconColor?: string;
26
29
  size?: 'small' | 'large';
27
- animatedValue?: SharedValue<number>;
30
+ animatedValue?: AnimatedCompatValue;
28
31
  bottom?: number;
29
32
  renderComponent?: () => React.ReactNode;
30
33
  }
@@ -41,6 +44,8 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
41
44
  bottom = 12,
42
45
  }: FloatingButtonProps) => {
43
46
  const { theme } = useContext(ApplicationContext);
47
+ const enabled = animatedValue != null;
48
+ const sharedValue = useNormalizedSharedValue(animatedValue);
44
49
  const scaledFontSize = useScaleSize(16);
45
50
  const scaledLineHeight = useScaleSize(22);
46
51
  const maxWidth = useSharedValue(0);
@@ -57,10 +62,10 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
57
62
  }, [label, opacityAnimated]);
58
63
 
59
64
  useAnimatedReaction(
60
- () => animatedValue?.value ?? 0,
65
+ () => sharedValue.value,
61
66
  (value) => {
62
67
  'worklet';
63
- if (!label || !animatedValue) return;
68
+ if (!label || !enabled) return;
64
69
  if (value !== lastOffset.value && value > 0) {
65
70
  const direction = value > lastOffset.value ? 'down' : 'up';
66
71
  lastOffset.value = value;
@@ -88,7 +93,7 @@ export const FloatingButton: React.FC<FloatingButtonProps> = ({
88
93
  }
89
94
  }
90
95
  },
91
- [label, animatedValue, minWidth],
96
+ [label, enabled, minWidth],
92
97
  );
93
98
 
94
99
  const containerStyle = useAnimatedStyle(() => ({
package/Layout/Screen.tsx CHANGED
@@ -12,6 +12,7 @@ import React, {
12
12
  useRef,
13
13
  } from 'react';
14
14
  import {
15
+ Animated,
15
16
  KeyboardAvoidingView,
16
17
  NativeScrollEvent,
17
18
  NativeSyntheticEvent,
@@ -24,12 +25,9 @@ import {
24
25
  View,
25
26
  ViewProps,
26
27
  } from 'react-native';
27
- import Animated, {
28
- runOnJS,
29
- useAnimatedScrollHandler,
28
+ import {
30
29
  useSharedValue,
31
30
  withTiming,
32
- type SharedValue,
33
31
  } from 'react-native-reanimated';
34
32
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
35
33
  import { ApplicationContext, ScreenContext } from '../Context';
@@ -142,9 +140,10 @@ export interface ScreenProps extends ViewProps {
142
140
  inputSearchRef?: Ref<InputRef>;
143
141
 
144
142
  /**
145
- * Optional. Animated value for header.
143
+ * Optional. React Native animated value for header.
144
+ * Internally, Screen mirrors it into a reanimated shared value.
146
145
  */
147
- animatedValue?: SharedValue<number>;
146
+ animatedValue?: Animated.Value;
148
147
 
149
148
  /**
150
149
  * Optional. If `true`, use shadow header.
@@ -201,23 +200,49 @@ const Screen = forwardRef(
201
200
  const screen: any = useContext(ScreenContext);
202
201
  const insets = useSafeAreaInsets();
203
202
  const heightHeader = useHeaderHeight();
204
- const internalAnimatedValue = useSharedValue(0);
205
- const animatedValue = customAnimatedValue ?? internalAnimatedValue;
203
+
204
+ /**
205
+ * Dual animation source:
206
+ * - `rnValue` is a react-native `Animated.Value` driven natively by the
207
+ * scroll event. It is what legacy consumers receive (`animatedValue`).
208
+ * - `sharedValue` is a reanimated `SharedValue` consumed by every internal
209
+ * foundation component (and offered to modern consumers). It is kept in
210
+ * sync from the scroll `listener` (see below), which fires on the JS
211
+ * thread even when the scroll uses the native driver.
212
+ * If a legacy value is driven outside of Screen, its listener also
213
+ * mirrors JS-driven updates into the shared value.
214
+ * Consumers pass only `Animated.Value`; the reanimated shared value stays
215
+ * an internal implementation detail.
216
+ */
217
+ const rnValue = useRef<Animated.Value>(
218
+ customAnimatedValue ?? new Animated.Value(0),
219
+ );
220
+ const internalShared = useSharedValue(0);
221
+ const sharedValue = internalShared;
222
+
223
+ useEffect(() => {
224
+ if (!customAnimatedValue) {
225
+ return;
226
+ }
227
+
228
+ const listenerId = customAnimatedValue.addListener(({ value }) => {
229
+ internalShared.value = value;
230
+ });
231
+
232
+ return () => {
233
+ customAnimatedValue.removeListener(listenerId);
234
+ };
235
+ }, [customAnimatedValue, internalShared]);
236
+
206
237
  const currentTint = useRef<string | undefined>(undefined);
207
238
  const isTab = navigation?.instance?.getState?.()?.type === 'tab';
208
239
 
209
- let handleScroll: any;
240
+ let handleScroll;
210
241
  let Component: any = View;
211
242
 
212
- // AI-GENERATED START
213
- // On iOS the home indicator is ~34pt but visual safe area needs only ~21pt cap.
214
- // On Android 15+ edge-to-edge the nav bar can be 48dp — no cap needed.
215
- const bottomInset = Platform.OS === 'ios' ? Math.min(insets.bottom, 21) : insets.bottom;
216
- // AI-GENERATED END
217
-
218
- let keyboardOffset = heightHeader - bottomInset;
243
+ let keyboardOffset = heightHeader - Math.min(insets.bottom, 21);
219
244
  if (headerType === 'extended' || animatedHeader || inputSearchProps) {
220
- keyboardOffset = -bottomInset;
245
+ keyboardOffset = -Math.min(insets.bottom, 21);
221
246
  }
222
247
 
223
248
  /**
@@ -260,7 +285,7 @@ const Screen = forwardRef(
260
285
  inputRange: [0, 50],
261
286
  outputRange: [1, 0],
262
287
  }}
263
- animatedValue={animatedValue}
288
+ animatedValue={sharedValue}
264
289
  />
265
290
  ),
266
291
  };
@@ -275,7 +300,7 @@ const Screen = forwardRef(
275
300
  headerBackground: (props: any) => (
276
301
  <HeaderBackground
277
302
  {...props}
278
- animatedValue={animatedValue}
303
+ animatedValue={sharedValue}
279
304
  useShadowHeader={useShadowHeader}
280
305
  headerBackground={headerBackground}
281
306
  gradientColor={gradientColor}
@@ -295,7 +320,7 @@ const Screen = forwardRef(
295
320
  inputRange: [0, 50],
296
321
  outputRange: [1, 0],
297
322
  }}
298
- animatedValue={animatedValue}
323
+ animatedValue={sharedValue}
299
324
  />
300
325
  ),
301
326
  };
@@ -309,6 +334,7 @@ const Screen = forwardRef(
309
334
  headerBackground,
310
335
  inputSearchProps,
311
336
  navigation?.instance,
337
+ sharedValue,
312
338
  useShadowHeader,
313
339
  ],
314
340
  );
@@ -330,7 +356,7 @@ const Screen = forwardRef(
330
356
  headerBackground: (props: any) => (
331
357
  <HeaderBackground
332
358
  {...props}
333
- animatedValue={animatedValue}
359
+ animatedValue={sharedValue}
334
360
  useGradient={false}
335
361
  useShadowHeader={useShadowHeader}
336
362
  headerBackground={headerBackground}
@@ -351,7 +377,13 @@ const Screen = forwardRef(
351
377
 
352
378
  navigation?.instance?.setOptions(options);
353
379
  },
354
- [gradientColor, headerBackground, navigation?.instance, useShadowHeader],
380
+ [
381
+ gradientColor,
382
+ headerBackground,
383
+ navigation?.instance,
384
+ sharedValue,
385
+ useShadowHeader,
386
+ ],
355
387
  );
356
388
 
357
389
  /**
@@ -369,13 +401,13 @@ const Screen = forwardRef(
369
401
  inputRange: [0, 50],
370
402
  outputRange: [1, 0],
371
403
  }}
372
- animatedValue={animatedValue}
404
+ animatedValue={sharedValue}
373
405
  />
374
406
  ),
375
407
  };
376
408
 
377
409
  navigation?.instance?.setOptions(options);
378
- }, [navigation?.instance]);
410
+ }, [navigation?.instance, sharedValue]);
379
411
 
380
412
  /**
381
413
  * export search header
@@ -398,7 +430,7 @@ const Screen = forwardRef(
398
430
  headerLeft: (props: any) =>
399
431
  params?.hiddenBack ? null : <HeaderLeft {...props} />,
400
432
  headerTitle: () => (
401
- <SearchHeader {...params} animatedValue={animatedValue} />
433
+ <SearchHeader {...params} animatedValue={sharedValue} />
402
434
  ),
403
435
  };
404
436
 
@@ -449,51 +481,47 @@ const Screen = forwardRef(
449
481
  });
450
482
  });
451
483
 
452
- const onTintColorChange = useCallback(
453
- (offsetY: number) => {
454
- if (!animatedHeader) return;
455
- let color = animatedHeader?.headerTintColor ?? Colors.black_17;
456
- if (offsetY > 50) {
457
- color = Colors.black_17;
458
- }
459
- if (color !== currentTint.current) {
460
- currentTint.current = color;
461
- navigation?.setOptions({
462
- headerTintColor: color,
463
- });
464
- let barStyle: StatusBarStyle = 'dark-content';
465
- if (currentTint.current === Colors.black_01) {
466
- barStyle = 'light-content';
467
- }
468
- StatusBar.setBarStyle(barStyle, true);
469
- }
470
- },
471
- [animatedHeader, navigation],
472
- );
473
-
474
- const emitOnScroll = useCallback(
475
- (offsetY: number) => {
476
- scrollViewProps?.onScroll?.({
477
- nativeEvent: { contentOffset: { x: 0, y: offsetY } },
478
- } as NativeSyntheticEvent<NativeScrollEvent>);
479
- },
480
- [scrollViewProps],
481
- );
482
-
483
- const scrollHandler = useAnimatedScrollHandler({
484
- onScroll: (event) => {
485
- animatedValue.value = event.contentOffset.y;
486
- runOnJS(emitOnScroll)(event.contentOffset.y);
487
- runOnJS(onTintColorChange)(event.contentOffset.y);
488
- },
489
- });
490
-
491
484
  /**
492
485
  * animated when use scroll && animated value
493
486
  */
494
487
  if (scrollable) {
495
488
  Component = Animated.ScrollView;
496
- handleScroll = scrollHandler;
489
+ handleScroll = Animated.event(
490
+ [
491
+ {
492
+ nativeEvent: {
493
+ contentOffset: { y: rnValue.current as Animated.Value },
494
+ },
495
+ },
496
+ ],
497
+ {
498
+ useNativeDriver: true,
499
+ listener: (e: NativeSyntheticEvent<NativeScrollEvent>) => {
500
+ const offsetY = e.nativeEvent.contentOffset.y;
501
+ // keep the reanimated source in sync for the internal components.
502
+ sharedValue.value = offsetY;
503
+ scrollViewProps?.onScroll?.(e);
504
+ if (animatedHeader) {
505
+ let color = animatedHeader?.headerTintColor ?? Colors.black_17;
506
+ if (offsetY > 50) {
507
+ color = Colors.black_17;
508
+ }
509
+ if (color !== currentTint.current) {
510
+ currentTint.current = color;
511
+ navigation?.setOptions({
512
+ headerTintColor: color,
513
+ });
514
+
515
+ let barStyle: StatusBarStyle = 'dark-content';
516
+ if (currentTint.current === Colors.black_01) {
517
+ barStyle = 'light-content';
518
+ }
519
+ StatusBar.setBarStyle(barStyle, true);
520
+ }
521
+ }
522
+ },
523
+ },
524
+ );
497
525
  }
498
526
 
499
527
  /**
@@ -503,7 +531,12 @@ const Screen = forwardRef(
503
531
  const handleScrollEnd = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
504
532
  const offsetY = e.nativeEvent.contentOffset.y;
505
533
  if (inputSearchProps && offsetY < 100 && offsetY > 0) {
506
- animatedValue.value = withTiming(0, { duration: 300 });
534
+ Animated.timing(rnValue.current, {
535
+ toValue: 0,
536
+ useNativeDriver: true,
537
+ duration: 300,
538
+ }).start();
539
+ sharedValue.value = withTiming(0, { duration: 300 });
507
540
  ref?.scrollTo?.({ y: 0, animated: true });
508
541
  }
509
542
  scrollViewProps?.onScrollEndDrag?.(e);
@@ -519,7 +552,10 @@ const Screen = forwardRef(
519
552
  style={[styles.screenBanner, { maxHeight: 210 + layoutOffset }]}
520
553
  >
521
554
  {animatedHeader?.component({
522
- animatedValue: animatedValue,
555
+ // legacy consumers read `animatedValue` (Animated.Value),
556
+ // modern consumers read `sharedValue` (SharedValue).
557
+ animatedValue: rnValue.current,
558
+ sharedValue: sharedValue,
523
559
  })}
524
560
  </View>
525
561
  );
@@ -586,7 +622,7 @@ const Screen = forwardRef(
586
622
  headerType={headerType}
587
623
  heightHeader={heightHeader}
588
624
  headerRightWidth={headerRightWidth}
589
- animatedValue={animatedValue}
625
+ animatedValue={sharedValue}
590
626
  inputSearchProps={inputSearchProps}
591
627
  navigation={navigation}
592
628
  inputSearchRef={inputSearchRef}
@@ -624,9 +660,9 @@ const Screen = forwardRef(
624
660
  <View>
625
661
  <FloatingButton
626
662
  {...floatingButtonProps}
627
- animatedValue={animatedValue}
663
+ animatedValue={sharedValue}
628
664
  bottom={
629
- Footer || isTab ? 12 : bottomInset + Spacing.S
665
+ Footer || isTab ? 12 : Math.min(insets.bottom, 21) + Spacing.S
630
666
  }
631
667
  />
632
668
  </View>
@@ -637,7 +673,7 @@ const Screen = forwardRef(
637
673
  style={[
638
674
  styles.shadow,
639
675
  {
640
- paddingBottom: bottomInset + Spacing.S,
676
+ paddingBottom: Math.min(insets.bottom, 21) + Spacing.S,
641
677
  backgroundColor: theme.colors.background.surface,
642
678
  },
643
679
  ]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/foundation",
3
- "version": "0.161.2-test.1",
3
+ "version": "0.162.2-sp.1",
4
4
  "description": "React Native Component Kits",
5
5
  "main": "index.ts",
6
6
  "scripts": {},