react-native-header-motion 0.4.0 → 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +400 -335
  2. package/lib/module/components/Bridge.js +16 -0
  3. package/lib/module/components/Bridge.js.map +1 -0
  4. package/lib/module/components/FlatList.js +5 -62
  5. package/lib/module/components/FlatList.js.map +1 -1
  6. package/lib/module/components/Header.js +71 -13
  7. package/lib/module/components/Header.js.map +1 -1
  8. package/lib/module/components/HeaderDynamic.js +34 -0
  9. package/lib/module/components/HeaderDynamic.js.map +1 -0
  10. package/lib/module/components/HeaderMotion.js +59 -23
  11. package/lib/module/components/HeaderMotion.js.map +1 -1
  12. package/lib/module/components/HeaderPanBoundary.js +54 -0
  13. package/lib/module/components/HeaderPanBoundary.js.map +1 -0
  14. package/lib/module/components/NavigationBridge.js +20 -0
  15. package/lib/module/components/NavigationBridge.js.map +1 -0
  16. package/lib/module/components/ScrollManager.js +7 -5
  17. package/lib/module/components/ScrollManager.js.map +1 -1
  18. package/lib/module/components/ScrollView.js +6 -47
  19. package/lib/module/components/ScrollView.js.map +1 -1
  20. package/lib/module/components/createHeaderMotionScrollable.js +136 -0
  21. package/lib/module/components/createHeaderMotionScrollable.js.map +1 -0
  22. package/lib/module/components/index.js +3 -1
  23. package/lib/module/components/index.js.map +1 -1
  24. package/lib/module/context.js +8 -1
  25. package/lib/module/context.js.map +1 -1
  26. package/lib/module/hooks/index.js +1 -0
  27. package/lib/module/hooks/index.js.map +1 -1
  28. package/lib/module/hooks/useActiveScrollId.js +7 -6
  29. package/lib/module/hooks/useActiveScrollId.js.map +1 -1
  30. package/lib/module/hooks/useHeaderMotionBridge.js +14 -0
  31. package/lib/module/hooks/useHeaderMotionBridge.js.map +1 -0
  32. package/lib/module/hooks/useMotionProgress.js +10 -36
  33. package/lib/module/hooks/useMotionProgress.js.map +1 -1
  34. package/lib/module/hooks/useMotionProgress.test.js +56 -0
  35. package/lib/module/hooks/useMotionProgress.test.js.map +1 -0
  36. package/lib/module/hooks/useScrollManager.js +219 -109
  37. package/lib/module/hooks/useScrollManager.js.map +1 -1
  38. package/lib/module/index.js +21 -18
  39. package/lib/module/index.js.map +1 -1
  40. package/lib/module/utils/defaults.js +2 -1
  41. package/lib/module/utils/defaults.js.map +1 -1
  42. package/lib/module/utils/header.js +24 -0
  43. package/lib/module/utils/header.js.map +1 -0
  44. package/lib/module/utils/headerOffsetStyle.js +31 -0
  45. package/lib/module/utils/headerOffsetStyle.js.map +1 -0
  46. package/lib/module/utils/index.js +3 -0
  47. package/lib/module/utils/index.js.map +1 -1
  48. package/lib/module/utils/refreshControl.js +93 -0
  49. package/lib/module/utils/refreshControl.js.map +1 -0
  50. package/lib/module/utils/values.js +36 -0
  51. package/lib/module/utils/values.js.map +1 -1
  52. package/lib/typescript/src/components/Bridge.d.ts +19 -0
  53. package/lib/typescript/src/components/Bridge.d.ts.map +1 -0
  54. package/lib/typescript/src/components/FlatList.d.ts +7 -15
  55. package/lib/typescript/src/components/FlatList.d.ts.map +1 -1
  56. package/lib/typescript/src/components/Header.d.ts +73 -12
  57. package/lib/typescript/src/components/Header.d.ts.map +1 -1
  58. package/lib/typescript/src/components/HeaderDynamic.d.ts +11 -0
  59. package/lib/typescript/src/components/HeaderDynamic.d.ts.map +1 -0
  60. package/lib/typescript/src/components/HeaderMotion.d.ts +37 -18
  61. package/lib/typescript/src/components/HeaderMotion.d.ts.map +1 -1
  62. package/lib/typescript/src/components/HeaderPanBoundary.d.ts +11 -0
  63. package/lib/typescript/src/components/HeaderPanBoundary.d.ts.map +1 -0
  64. package/lib/typescript/src/components/NavigationBridge.d.ts +19 -0
  65. package/lib/typescript/src/components/NavigationBridge.d.ts.map +1 -0
  66. package/lib/typescript/src/components/ScrollManager.d.ts +18 -25
  67. package/lib/typescript/src/components/ScrollManager.d.ts.map +1 -1
  68. package/lib/typescript/src/components/ScrollView.d.ts +7 -14
  69. package/lib/typescript/src/components/ScrollView.d.ts.map +1 -1
  70. package/lib/typescript/src/components/createHeaderMotionScrollable.d.ts +86 -0
  71. package/lib/typescript/src/components/createHeaderMotionScrollable.d.ts.map +1 -0
  72. package/lib/typescript/src/components/index.d.ts +3 -1
  73. package/lib/typescript/src/components/index.d.ts.map +1 -1
  74. package/lib/typescript/src/context.d.ts +3 -13
  75. package/lib/typescript/src/context.d.ts.map +1 -1
  76. package/lib/typescript/src/hooks/index.d.ts +1 -0
  77. package/lib/typescript/src/hooks/index.d.ts.map +1 -1
  78. package/lib/typescript/src/hooks/useActiveScrollId.d.ts +7 -6
  79. package/lib/typescript/src/hooks/useActiveScrollId.d.ts.map +1 -1
  80. package/lib/typescript/src/hooks/useHeaderMotionBridge.d.ts +10 -0
  81. package/lib/typescript/src/hooks/useHeaderMotionBridge.d.ts.map +1 -0
  82. package/lib/typescript/src/hooks/useMotionProgress.d.ts +8 -25
  83. package/lib/typescript/src/hooks/useMotionProgress.d.ts.map +1 -1
  84. package/lib/typescript/src/hooks/useMotionProgress.test.d.ts +2 -0
  85. package/lib/typescript/src/hooks/useMotionProgress.test.d.ts.map +1 -0
  86. package/lib/typescript/src/hooks/useScrollManager.d.ts +63 -31
  87. package/lib/typescript/src/hooks/useScrollManager.d.ts.map +1 -1
  88. package/lib/typescript/src/index.d.ts +56 -26
  89. package/lib/typescript/src/index.d.ts.map +1 -1
  90. package/lib/typescript/src/types.d.ts +63 -15
  91. package/lib/typescript/src/types.d.ts.map +1 -1
  92. package/lib/typescript/src/utils/defaults.d.ts +3 -2
  93. package/lib/typescript/src/utils/defaults.d.ts.map +1 -1
  94. package/lib/typescript/src/utils/header.d.ts +10 -0
  95. package/lib/typescript/src/utils/header.d.ts.map +1 -0
  96. package/lib/typescript/src/utils/headerOffsetStyle.d.ts +19 -0
  97. package/lib/typescript/src/utils/headerOffsetStyle.d.ts.map +1 -0
  98. package/lib/typescript/src/utils/index.d.ts +3 -0
  99. package/lib/typescript/src/utils/index.d.ts.map +1 -1
  100. package/lib/typescript/src/utils/refreshControl.d.ts +150 -0
  101. package/lib/typescript/src/utils/refreshControl.d.ts.map +1 -0
  102. package/lib/typescript/src/utils/values.d.ts +4 -1
  103. package/lib/typescript/src/utils/values.d.ts.map +1 -1
  104. package/package.json +13 -5
  105. package/src/components/Bridge.tsx +29 -0
  106. package/src/components/FlatList.tsx +18 -84
  107. package/src/components/Header.tsx +159 -23
  108. package/src/components/HeaderDynamic.tsx +45 -0
  109. package/src/components/HeaderMotion.tsx +114 -41
  110. package/src/components/HeaderPanBoundary.tsx +92 -0
  111. package/src/components/NavigationBridge.tsx +30 -0
  112. package/src/components/ScrollManager.tsx +38 -43
  113. package/src/components/ScrollView.tsx +16 -68
  114. package/src/components/createHeaderMotionScrollable.tsx +438 -0
  115. package/src/components/index.ts +3 -1
  116. package/src/context.ts +12 -18
  117. package/src/hooks/index.ts +1 -0
  118. package/src/hooks/useActiveScrollId.ts +7 -6
  119. package/src/hooks/useHeaderMotionBridge.ts +15 -0
  120. package/src/hooks/useMotionProgress.test.ts +67 -0
  121. package/src/hooks/useMotionProgress.ts +12 -37
  122. package/src/hooks/useScrollManager.ts +310 -129
  123. package/src/index.ts +82 -36
  124. package/src/types.ts +85 -25
  125. package/src/utils/defaults.ts +7 -1
  126. package/src/utils/header.tsx +52 -0
  127. package/src/utils/headerOffsetStyle.ts +40 -0
  128. package/src/utils/index.ts +3 -0
  129. package/src/utils/refreshControl.tsx +118 -0
  130. package/src/utils/values.ts +57 -1
  131. package/lib/module/components/HeaderBase.js +0 -59
  132. package/lib/module/components/HeaderBase.js.map +0 -1
  133. package/lib/module/hooks/refreshControl.js +0 -31
  134. package/lib/module/hooks/refreshControl.js.map +0 -1
  135. package/lib/typescript/src/components/HeaderBase.d.ts +0 -34
  136. package/lib/typescript/src/components/HeaderBase.d.ts.map +0 -1
  137. package/lib/typescript/src/hooks/refreshControl.d.ts +0 -13
  138. package/lib/typescript/src/hooks/refreshControl.d.ts.map +0 -1
  139. package/src/components/HeaderBase.tsx +0 -51
  140. package/src/hooks/refreshControl.ts +0 -55
package/src/index.ts CHANGED
@@ -1,76 +1,122 @@
1
1
  import {
2
- AnimatedHeaderBase,
3
- HeaderBase,
2
+ createHeaderMotionScrollable,
3
+ Bridge,
4
4
  HeaderMotionContextProvider,
5
- HeaderMotionFlatList,
6
- HeaderMotionHeader,
7
- HeaderMotionScrollManager,
8
- HeaderMotionScrollView,
5
+ FlatList,
6
+ Header,
7
+ NavigationBridge,
8
+ ScrollManager,
9
+ ScrollView,
10
+ type CreateHeaderMotionScrollableOptions,
11
+ type HeaderProps,
12
+ type HeaderMotionBridgeProps,
9
13
  type HeaderMotionFlatListProps,
10
- type HeaderMotionHeaderProps,
14
+ type HeaderMotionNavigationBridgeProps,
11
15
  type HeaderMotionProps,
12
16
  type HeaderMotionScrollManagerProps,
17
+ type HeaderMotionScrollableOwnProps,
13
18
  type HeaderMotionScrollViewProps,
14
19
  } from './components';
15
- import type { ReactElement } from 'react';
20
+ import type { HeaderDynamicProps } from './types';
16
21
 
17
- /**
18
- * Compound component type for HeaderMotion.
19
- * Provides the main context provider and sub-components for building collapsible headers.
20
- */
21
- type HeaderMotionComponent = {
22
- /** Main context provider component */
23
- <T extends string>(props: HeaderMotionProps<T>): ReactElement;
24
- /** Component for providing motion progress properties to animated headers.
25
- * Use to pass props to the header components in React Navigation / Expo Router, which cannot access HeaderMotion's context and `useMotionProgress` otherwise.
22
+ type HeaderMotionCompound = typeof HeaderMotionContextProvider & {
23
+ /**
24
+ * Header container that measures the total header height and can optionally
25
+ * make the header surface pannable.
26
+ *
27
+ * Use `HeaderMotion.Header.Dynamic` inside it to mark the part of the header
28
+ * that should define the collapse distance.
29
+ */
30
+ Header: typeof Header;
31
+ /**
32
+ * Captures the current HeaderMotion context and exposes it through a render
33
+ * function so it can be forwarded across a React tree boundary.
34
+ *
35
+ * This is primarily useful for navigation-rendered headers.
36
+ */
37
+ Bridge: typeof Bridge;
38
+ /**
39
+ * Re-provides a previously captured HeaderMotion context value in another
40
+ * subtree.
41
+ *
42
+ * This is primarily useful for navigation libraries that render headers
43
+ * outside the screen subtree where `HeaderMotion` lives.
44
+ */
45
+ NavigationBridge: typeof NavigationBridge;
46
+ /**
47
+ * Render-prop wrapper for managing a custom scrollable.
48
+ *
49
+ * Prefer `createHeaderMotionScrollable()` for most custom integrations. Use
50
+ * `ScrollManager` only when the factory approach is not flexible enough.
26
51
  */
27
- Header: typeof HeaderMotionHeader;
28
- /** Component for custom scroll implementations */
29
- ScrollManager: typeof HeaderMotionScrollManager;
30
- /** Animated ScrollView component with header motion integration */
31
- ScrollView: typeof HeaderMotionScrollView;
32
- /** Animated FlatList component with header motion integration */
33
- FlatList: typeof HeaderMotionFlatList;
52
+ ScrollManager: typeof ScrollManager;
53
+ /**
54
+ * Pre-wired `Animated.ScrollView` that participates in HeaderMotion's scroll
55
+ * tracking and header offsetting.
56
+ */
57
+ ScrollView: typeof ScrollView;
58
+ /**
59
+ * Pre-wired `Animated.FlatList` that participates in HeaderMotion's scroll
60
+ * tracking and header offsetting.
61
+ */
62
+ FlatList: typeof FlatList;
34
63
  };
35
64
 
36
65
  /**
37
66
  * Main HeaderMotion component.
38
- * A compound component that provides context for collapsible header animations.
67
+ * Root provider and compound entrypoint for the library.
68
+ *
69
+ * It tracks header measurements, derives the shared `progress` value, and
70
+ * exposes the pre-wired subcomponents used to connect headers and scrollables.
39
71
  *
40
72
  * @example
41
73
  * ```tsx
42
74
  * <HeaderMotion>
43
- * <HeaderMotion.Header>
44
- * {(headerProps) => (
75
+ * <HeaderMotion.Bridge>
76
+ * {(value) => (
45
77
  * <Stack.Screen
46
78
  * options={{
47
79
  * header: () => (
48
- * <MyAnimatedHeader {...headerProps} />
80
+ * <HeaderMotion.NavigationBridge value={value}>
81
+ * <MyAnimatedHeader />
82
+ * </HeaderMotion.NavigationBridge>
49
83
  * ),
50
84
  * }}
51
85
  * />
52
86
  * )}
53
- * </HeaderMotion.Header>
87
+ * </HeaderMotion.Bridge>
54
88
  * <HeaderMotion.ScrollView>
55
89
  * <MyScrollableContent />
56
90
  * </HeaderMotion.ScrollView>
57
91
  * </HeaderMotion>
58
92
  * ```
59
93
  */
60
- const HeaderMotion = HeaderMotionContextProvider as HeaderMotionComponent;
61
- HeaderMotion.Header = HeaderMotionHeader;
62
- HeaderMotion.ScrollManager = HeaderMotionScrollManager;
63
- HeaderMotion.ScrollView = HeaderMotionScrollView;
64
- HeaderMotion.FlatList = HeaderMotionFlatList;
94
+ const HeaderMotion: HeaderMotionCompound = Object.assign(
95
+ HeaderMotionContextProvider,
96
+ {
97
+ Header,
98
+ Bridge,
99
+ NavigationBridge,
100
+ ScrollManager,
101
+ ScrollView,
102
+ FlatList,
103
+ }
104
+ );
65
105
 
66
106
  export default HeaderMotion;
67
107
  export * from './hooks';
68
108
  export type * from './types';
69
- export { AnimatedHeaderBase, HeaderBase };
109
+ export { createHeaderMotionScrollable };
110
+ export { Bridge, Header, NavigationBridge };
70
111
  export type {
112
+ CreateHeaderMotionScrollableOptions,
113
+ HeaderDynamicProps,
71
114
  HeaderMotionFlatListProps,
72
- HeaderMotionHeaderProps,
115
+ HeaderMotionBridgeProps,
116
+ HeaderMotionNavigationBridgeProps,
73
117
  HeaderMotionProps,
74
118
  HeaderMotionScrollManagerProps,
119
+ HeaderMotionScrollableOwnProps,
75
120
  HeaderMotionScrollViewProps,
121
+ HeaderProps,
76
122
  };
package/src/types.ts CHANGED
@@ -1,13 +1,58 @@
1
1
  import type { ReactElement } from 'react';
2
2
  import type {
3
3
  LayoutChangeEvent,
4
- RefreshControlProps,
5
4
  ScrollViewProps,
5
+ ViewProps,
6
6
  } from 'react-native';
7
- import type { AnimatedRef, SharedValue } from 'react-native-reanimated';
7
+ import type {
8
+ AnimatedProps,
9
+ AnimatedRef,
10
+ SharedValue,
11
+ } from 'react-native-reanimated';
8
12
  import { DEFAULT_SCROLL_ID } from './utils/defaults';
13
+ import type { InstanceOrElement } from 'react-native-reanimated/lib/typescript/commonTypes';
14
+ import type {
15
+ GestureStateChangeEvent,
16
+ PanGestureHandlerEventPayload,
17
+ } from 'react-native-gesture-handler';
18
+ import type { WithDecayConfig } from 'react-native-reanimated';
9
19
 
10
20
  export type Progress = SharedValue<number>;
21
+ export type HeaderMotionOffsetStrategy =
22
+ | 'padding'
23
+ | 'margin'
24
+ | 'top'
25
+ | 'translate'
26
+ | 'none';
27
+
28
+ export interface HeaderMotionOffsetProps {
29
+ /**
30
+ * How the scrollable content should be pushed below the measured header.
31
+ *
32
+ * `padding` is the safest default for most screens. `margin`, `top`, and
33
+ * `translate` can be useful when the scrollable or its children need a
34
+ * different layout behavior.
35
+ *
36
+ * `top` and `translate` add bottom compensation so the end of the content
37
+ * remains reachable.
38
+ *
39
+ * @default 'padding'
40
+ */
41
+ headerOffsetStrategy?: HeaderMotionOffsetStrategy;
42
+ /**
43
+ * Adds a minimum content height so scrollables with short content can still collapse the
44
+ * header completely.
45
+ *
46
+ * **Experimental: this relies on extra layout measurement and may still be
47
+ * refined.**
48
+ *
49
+ * Enable this when some screens do not have enough content to naturally
50
+ * scroll through the full collapse distance.
51
+ *
52
+ * @default false
53
+ */
54
+ ensureScrollableContentMinHeight?: boolean;
55
+ }
11
56
 
12
57
  export type ProgressThreshold =
13
58
  | number
@@ -29,44 +74,59 @@ export type ScrollValues = Record<string, ScrollValue> & {
29
74
  [key in typeof DEFAULT_SCROLL_ID]?: ScrollValue;
30
75
  };
31
76
 
32
- export type WithCollapsibleHeaderProps<
33
- T extends Record<string, unknown> = Record<string, unknown>
34
- > = T & MotionProgress;
77
+ export interface MotionProgress {
78
+ progress: Progress;
79
+ progressThreshold: SharedValue<number>;
80
+ }
81
+
82
+ export type HeaderPanDecayEvent =
83
+ GestureStateChangeEvent<PanGestureHandlerEventPayload>;
84
+
85
+ export type HeaderPanDecayConfig =
86
+ | WithDecayConfig
87
+ | ((event: HeaderPanDecayEvent) => WithDecayConfig);
35
88
 
36
- export type WithCollapsiblePagedHeaderProps<
37
- Tab extends string = string,
38
- T extends Record<string, unknown> = Record<string, unknown>
39
- > = WithCollapsibleHeaderProps<T> & {
40
- onTabChange: (newTab: Tab) => void;
41
- activeTab: Tab;
89
+ export type HeaderAsChildProps = {
90
+ asChild: true;
91
+ children: ReactElement;
42
92
  };
43
93
 
44
- export interface MotionProgress {
45
- progress: Progress;
46
- progressThreshold: number;
94
+ export type HeaderDefaultProps = AnimatedProps<ViewProps> & {
95
+ asChild?: false;
96
+ };
97
+
98
+ export type HeaderDynamicProps = HeaderDefaultProps | HeaderAsChildProps;
99
+
100
+ export interface HeaderMotionBridgeValue extends MotionProgress {
47
101
  measureTotalHeight: MeasureAnimatedHeaderAndSet;
48
102
  measureDynamic: MeasureAnimatedHeaderAndSet;
103
+ headerPanMomentumOffset: SharedValue<number | null>;
104
+ scrollValues: SharedValue<ScrollValues>;
105
+ activeScrollId: SharedValue<string> | undefined;
106
+ scrollToRef: React.RefObject<ScrollTo | null>;
107
+ originalHeaderHeight: number;
49
108
  }
50
109
 
51
110
  export interface ScrollManagerHeaderMotionContext {
52
111
  originalHeaderHeight: number;
53
- minHeightContentContainerStyle:
54
- | {}
55
- | {
56
- minHeight: number;
57
- };
112
+ contentContainerMinHeight?: number;
58
113
  }
59
114
 
60
- export interface ScrollManagerConfig {
61
- scrollableProps: Required<
62
- Pick<ScrollViewProps, 'onScroll' | 'scrollEventThrottle'>
63
- > & {
64
- refreshControl?: ReactElement<RefreshControlProps>;
65
- ref: AnimatedRef<any>; // TODO: better typing
115
+ export interface ScrollManagerConfig<TRef extends InstanceOrElement = any> {
116
+ scrollableProps: Pick<ScrollViewProps, 'onScroll' | 'onLayout'> & {
117
+ refreshControl?: ReactElement;
118
+ ref: AnimatedRef<TRef>;
66
119
  };
67
120
  headerMotionContext: ScrollManagerHeaderMotionContext;
68
121
  }
69
122
 
123
+ export type ScrollTo = (y: number, options?: ScrollToOptions) => void;
124
+
70
125
  export type ScrollHandlerContext = {
71
126
  lastOffset: number | undefined;
72
127
  };
128
+
129
+ interface ScrollToOptions {
130
+ isValueDelta?: boolean;
131
+ animated?: boolean;
132
+ }
@@ -1,15 +1,21 @@
1
- import type { MeasureAnimatedHeader, ProgressThreshold } from '../types';
1
+ import type {
2
+ HeaderMotionOffsetStrategy,
3
+ MeasureAnimatedHeader,
4
+ ProgressThreshold,
5
+ } from '../types';
2
6
 
3
7
  const DEFAULT_PROGRESS_THRESHOLD: ProgressThreshold = (measuredHeaderValue) =>
4
8
  measuredHeaderValue;
5
9
  const DEFAULT_MEASURE_DYNAMIC: MeasureAnimatedHeader = (e) =>
6
10
  e.nativeEvent.layout.height;
11
+ const DEFAULT_HEADER_OFFSET_STRATEGY: HeaderMotionOffsetStrategy = 'padding';
7
12
 
8
13
  // Symbol doesn't work?
9
14
  // const DEFAULT_SCROLL_ID = Symbol("HEADER_MOTION_DEFAULT_SCROLL_ID");
10
15
  const DEFAULT_SCROLL_ID = '__HEADER_MOTION_DEFAULT_SCROLL_ID__';
11
16
 
12
17
  export {
18
+ DEFAULT_HEADER_OFFSET_STRATEGY,
13
19
  DEFAULT_MEASURE_DYNAMIC,
14
20
  DEFAULT_PROGRESS_THRESHOLD,
15
21
  DEFAULT_SCROLL_ID,
@@ -0,0 +1,52 @@
1
+ import {
2
+ Fragment,
3
+ cloneElement,
4
+ isValidElement,
5
+ type ReactElement,
6
+ } from 'react';
7
+ import type { ViewProps } from 'react-native';
8
+
9
+ export type SlottableElementProps = {
10
+ onLayout?: ViewProps['onLayout'];
11
+ };
12
+
13
+ export type SlottableElement = ReactElement<SlottableElementProps>;
14
+
15
+ export function composeOnLayoutHandlers(
16
+ userHandler: ViewProps['onLayout'],
17
+ internalHandler: ViewProps['onLayout']
18
+ ) {
19
+ return (e: Parameters<NonNullable<ViewProps['onLayout']>>[0]) => {
20
+ internalHandler?.(e);
21
+ userHandler?.(e);
22
+ };
23
+ }
24
+
25
+ export function resolveSlottableChild(
26
+ componentName: string,
27
+ child: ReactElement
28
+ ) {
29
+ if (!isValidElement(child) || child.type === Fragment) {
30
+ throw new Error(
31
+ `${componentName} with \`asChild\` expects a single valid React element child that accepts \`onLayout\`.`
32
+ );
33
+ }
34
+
35
+ return child as SlottableElement;
36
+ }
37
+
38
+ export function cloneWithOnLayout(
39
+ child: SlottableElement,
40
+ onLayout: ViewProps['onLayout'],
41
+ componentName: string
42
+ ) {
43
+ if (!isValidElement(child)) {
44
+ throw new Error(
45
+ `${componentName} with \`asChild\` expects a valid React element child.`
46
+ );
47
+ }
48
+
49
+ return cloneElement(child, {
50
+ onLayout: composeOnLayoutHandlers(child.props.onLayout, onLayout),
51
+ });
52
+ }
@@ -0,0 +1,40 @@
1
+ import type { SharedValue } from 'react-native-reanimated';
2
+ import type { HeaderMotionOffsetStrategy } from '../types';
3
+ import { DEFAULT_HEADER_OFFSET_STRATEGY } from './defaults';
4
+
5
+ type HeaderOffsetValue = number | SharedValue<number>;
6
+
7
+ type HeaderOffsetStyle =
8
+ | undefined
9
+ | { paddingTop: HeaderOffsetValue }
10
+ | { marginTop: HeaderOffsetValue }
11
+ | { top: HeaderOffsetValue; paddingBottom: HeaderOffsetValue }
12
+ | {
13
+ transform: [{ translateY: HeaderOffsetValue }];
14
+ paddingBottom: HeaderOffsetValue;
15
+ };
16
+
17
+ export function resolveHeaderOffsetStyle(
18
+ originalHeaderHeight: HeaderOffsetValue,
19
+ headerOffsetStrategy: HeaderMotionOffsetStrategy = DEFAULT_HEADER_OFFSET_STRATEGY
20
+ ): HeaderOffsetStyle {
21
+ switch (headerOffsetStrategy) {
22
+ case 'none':
23
+ return undefined;
24
+ case 'margin':
25
+ return { marginTop: originalHeaderHeight };
26
+ case 'top':
27
+ return {
28
+ top: originalHeaderHeight,
29
+ paddingBottom: originalHeaderHeight,
30
+ };
31
+ case 'translate':
32
+ return {
33
+ transform: [{ translateY: originalHeaderHeight }],
34
+ paddingBottom: originalHeaderHeight,
35
+ };
36
+ case 'padding':
37
+ default:
38
+ return { paddingTop: originalHeaderHeight };
39
+ }
40
+ }
@@ -1,2 +1,5 @@
1
1
  export * from './defaults';
2
+ export * from './header';
3
+ export * from './headerOffsetStyle';
2
4
  export * from './values';
5
+ export * from './refreshControl';
@@ -0,0 +1,118 @@
1
+ import {
2
+ cloneElement,
3
+ createElement,
4
+ isValidElement,
5
+ type ReactElement,
6
+ } from 'react';
7
+ import { RefreshControl, type RefreshControlProps } from 'react-native';
8
+ import type { SharedValue } from 'react-native-reanimated';
9
+ import Animated, { useAnimatedProps } from 'react-native-reanimated';
10
+
11
+ // FIXME: Types are a mess here
12
+
13
+ const AnimatedRefreshControl = Animated.createAnimatedComponent(RefreshControl);
14
+
15
+ type MaybeShared<T> = T | SharedValue<T | undefined>;
16
+
17
+ export interface ResolveRefreshControlOptions {
18
+ refreshControl?: MaybeShared<ReactElement<RefreshControlProps>>;
19
+ refreshing?: MaybeShared<boolean>;
20
+ onRefresh?: MaybeShared<() => void>;
21
+ progressViewOffset: MaybeShared<number> | SharedValue<number>;
22
+ }
23
+
24
+ export function resolveRefreshControl({
25
+ refreshControl,
26
+ refreshing,
27
+ onRefresh,
28
+ progressViewOffset,
29
+ }: ResolveRefreshControlOptions):
30
+ | ReactElement<
31
+ RefreshControlProps | React.ComponentProps<typeof AnimatedRefreshControl>
32
+ >
33
+ | undefined {
34
+ if (!refreshControl) {
35
+ return createRefreshControlWithOffset({
36
+ refreshing,
37
+ onRefresh,
38
+ progressViewOffset,
39
+ });
40
+ }
41
+
42
+ return isValidElement<RefreshControlProps>(refreshControl)
43
+ ? injectProgressViewOffset(refreshControl, progressViewOffset)
44
+ : undefined;
45
+ }
46
+
47
+ function createRefreshControlWithOffset({
48
+ refreshing,
49
+ onRefresh,
50
+ progressViewOffset,
51
+ }: Omit<ResolveRefreshControlOptions, 'refreshControl'>) {
52
+ if (!onRefresh) {
53
+ return undefined;
54
+ }
55
+
56
+ return createElement(ResolvedRefreshControl, {
57
+ refreshing: (refreshing as boolean) ?? false,
58
+ onRefresh: onRefresh as () => void,
59
+ progressViewOffset: progressViewOffset as number,
60
+ }) as ReactElement<RefreshControlProps>;
61
+ }
62
+
63
+ function injectProgressViewOffset(
64
+ refreshControl: ReactElement<RefreshControlProps>,
65
+ progressViewOffset: MaybeShared<number> | SharedValue<number>
66
+ ) {
67
+ const offset = refreshControl.props.progressViewOffset ?? progressViewOffset;
68
+
69
+ if (!isSharedValue<number>(offset)) {
70
+ return cloneElement(refreshControl, {
71
+ progressViewOffset: offset as number,
72
+ });
73
+ }
74
+
75
+ return createElement(ResolvedRefreshControl, {
76
+ ...refreshControl.props,
77
+ progressViewOffset: progressViewOffset as number,
78
+ });
79
+ }
80
+
81
+ function ResolvedRefreshControl({
82
+ refreshing,
83
+ onRefresh,
84
+ progressViewOffset,
85
+ ...props
86
+ }: React.ComponentProps<typeof AnimatedRefreshControl>) {
87
+ const animatedProps = useAnimatedProps<RefreshControlProps>(() => {
88
+ return {
89
+ ...(isSharedValue(refreshing) ? { refreshing: refreshing.value } : {}),
90
+ ...(isSharedValue(onRefresh) ? { onRefresh: onRefresh.value } : {}),
91
+ ...(isSharedValue(progressViewOffset)
92
+ ? { progressViewOffset: progressViewOffset.value }
93
+ : {}),
94
+ };
95
+ });
96
+
97
+ const nonAnimatedProps = {
98
+ ...(isSharedValue(refreshing) ? {} : { refreshing }),
99
+ ...(isSharedValue(onRefresh) ? {} : { onRefresh }),
100
+ ...(isSharedValue(progressViewOffset) ? {} : { progressViewOffset }),
101
+ };
102
+
103
+ return (
104
+ <AnimatedRefreshControl
105
+ {...props}
106
+ {...nonAnimatedProps}
107
+ refreshing={nonAnimatedProps.refreshing as boolean}
108
+ animatedProps={animatedProps}
109
+ />
110
+ );
111
+ }
112
+
113
+ function isSharedValue<T>(
114
+ value: MaybeShared<T> | SharedValue<T>
115
+ ): value is SharedValue<T> {
116
+ 'worklet';
117
+ return typeof value === 'object' && value !== null && 'value' in value;
118
+ }
@@ -1,6 +1,62 @@
1
- import type { ScrollValue } from '../types';
1
+ import type { SharedValue } from 'react-native-reanimated';
2
+ import type { ScrollValue, ScrollValues } from '../types';
3
+ import { DEFAULT_SCROLL_ID } from './defaults';
2
4
 
3
5
  export function getInitialScrollValue(): ScrollValue {
4
6
  'worklet';
5
7
  return { min: 0, current: 0 };
6
8
  }
9
+
10
+ export function ensureScrollValueRegistered(
11
+ scrollValues: SharedValue<ScrollValues>,
12
+ id: string
13
+ ): ScrollValues {
14
+ 'worklet';
15
+
16
+ const values = scrollValues.get();
17
+ if (values[id]) {
18
+ return values;
19
+ }
20
+
21
+ scrollValues.modify((value) => {
22
+ if (!value[id]) {
23
+ (value as ScrollValues)[id] = getInitialScrollValue();
24
+ }
25
+
26
+ return value;
27
+ });
28
+
29
+ return scrollValues.get();
30
+ }
31
+
32
+ export function warnIfMissingActiveScrollId(
33
+ scrollValues: ScrollValues,
34
+ id: string,
35
+ activeScrollId: string | undefined
36
+ ): void {
37
+ 'worklet';
38
+
39
+ if (!__DEV__ || activeScrollId || id === DEFAULT_SCROLL_ID) {
40
+ return;
41
+ }
42
+
43
+ let nonDefaultCount = 0;
44
+ let nonDefaultIds = '';
45
+
46
+ for (const key in scrollValues) {
47
+ if (key === DEFAULT_SCROLL_ID) {
48
+ continue;
49
+ }
50
+
51
+ nonDefaultCount += 1;
52
+ nonDefaultIds = nonDefaultIds ? `${nonDefaultIds}, ${key}` : key;
53
+ }
54
+
55
+ if (nonDefaultCount < 1) {
56
+ return;
57
+ }
58
+
59
+ console.warn(
60
+ `[react-native-header-motion] Explicit scrollIds (${nonDefaultIds}) are registered but no activeScrollId was provided. Pass useActiveScrollId(...).sv to <HeaderMotion activeScrollId={...}> to keep header motion deterministic.`
61
+ );
62
+ }
@@ -1,59 +0,0 @@
1
- "use strict";
2
-
3
- import { StyleSheet, View } from 'react-native';
4
- import Animated from 'react-native-reanimated';
5
- import { jsx as _jsx } from "react/jsx-runtime";
6
- /**
7
- * Base header component with absolute positioning.
8
- * Provides a foundation for building headers that need to be positioned absolutely.
9
- *
10
- * @example
11
- * ```tsx
12
- * <HeaderBase
13
- * onLayout={measureTotalHeight}
14
- * >
15
- * ...
16
- * </HeaderBase>
17
- * ```
18
- */
19
- export function HeaderBase({
20
- style,
21
- ...rest
22
- }) {
23
- return /*#__PURE__*/_jsx(View, {
24
- style: [style, styles.container],
25
- ...rest
26
- });
27
- }
28
-
29
- /**
30
- * Animated version of HeaderBase using Reanimated's Animated.View.
31
- * Use this when you need to animate the header based on scroll progress.
32
- *
33
- * @example
34
- * ```tsx
35
- * <AnimatedHeaderBase
36
- * onLayout={measureTotalHeight}
37
- * style={[{ paddingTop: insets.top }, animatedStyle]}
38
- * >
39
- * ...
40
- * </AnimatedHeaderBase>
41
- * ```
42
- */
43
- export function AnimatedHeaderBase({
44
- style,
45
- ...rest
46
- }) {
47
- return /*#__PURE__*/_jsx(Animated.View, {
48
- style: [style, styles.container],
49
- ...rest
50
- });
51
- }
52
- const styles = StyleSheet.create({
53
- container: {
54
- position: 'absolute',
55
- left: 0,
56
- right: 0
57
- }
58
- });
59
- //# sourceMappingURL=HeaderBase.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["StyleSheet","View","Animated","jsx","_jsx","HeaderBase","style","rest","styles","container","AnimatedHeaderBase","create","position","left","right"],"sourceRoot":"../../../src","sources":["components/HeaderBase.tsx"],"mappings":";;AAAA,SAASA,UAAU,EAAEC,IAAI,QAAwB,cAAc;AAC/D,OAAOC,QAAQ,MAA8B,yBAAyB;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAKvE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,UAAUA,CAAC;EAAEC,KAAK;EAAE,GAAGC;AAAsB,CAAC,EAAE;EAC9D,oBAAOH,IAAA,CAACH,IAAI;IAACK,KAAK,EAAE,CAACA,KAAK,EAAEE,MAAM,CAACC,SAAS,CAAE;IAAA,GAAKF;EAAI,CAAG,CAAC;AAC7D;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASG,kBAAkBA,CAAC;EACjCJ,KAAK;EACL,GAAGC;AACoB,CAAC,EAAE;EAC1B,oBAAOH,IAAA,CAACF,QAAQ,CAACD,IAAI;IAACK,KAAK,EAAE,CAACA,KAAK,EAAEE,MAAM,CAACC,SAAS,CAAE;IAAA,GAAKF;EAAI,CAAG,CAAC;AACtE;AAEA,MAAMC,MAAM,GAAGR,UAAU,CAACW,MAAM,CAAC;EAC/BF,SAAS,EAAE;IACTG,QAAQ,EAAE,UAAU;IACpBC,IAAI,EAAE,CAAC;IACPC,KAAK,EAAE;EACT;AACF,CAAC,CAAC","ignoreList":[]}