@r0b0t3d/react-native-collapsible 1.0.1 → 1.2.0-alpha.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 (124) hide show
  1. package/lib/commonjs/components/CollapsibleContainer.js +4 -2
  2. package/lib/commonjs/components/CollapsibleContainer.js.map +1 -1
  3. package/lib/commonjs/components/CollapsibleView.js +4 -3
  4. package/lib/commonjs/components/CollapsibleView.js.map +1 -1
  5. package/lib/commonjs/components/{AnimatedTopView.js → header/AnimatedTopView.js} +0 -0
  6. package/lib/commonjs/components/{AnimatedTopView.js.map → header/AnimatedTopView.js.map} +0 -0
  7. package/lib/commonjs/components/{CollapsibleHeaderContainer.js → header/CollapsibleHeaderContainer.js} +9 -8
  8. package/lib/commonjs/components/header/CollapsibleHeaderContainer.js.map +1 -0
  9. package/lib/commonjs/components/{StickyView.js → header/StickyView.js} +18 -31
  10. package/lib/commonjs/components/header/StickyView.js.map +1 -0
  11. package/lib/commonjs/components/pullToRefresh/PullToRefreshContainer.js +75 -0
  12. package/lib/commonjs/components/pullToRefresh/PullToRefreshContainer.js.map +1 -0
  13. package/lib/commonjs/components/pullToRefresh/PullToRefreshProvider.js +35 -0
  14. package/lib/commonjs/components/pullToRefresh/PullToRefreshProvider.js.map +1 -0
  15. package/lib/commonjs/components/pullToRefresh/RefreshControl.js +73 -0
  16. package/lib/commonjs/components/pullToRefresh/RefreshControl.js.map +1 -0
  17. package/lib/commonjs/components/pullToRefresh/usePullToRefreshContext.js +24 -0
  18. package/lib/commonjs/components/pullToRefresh/usePullToRefreshContext.js.map +1 -0
  19. package/lib/commonjs/components/pullToRefresh/utils.js +59 -0
  20. package/lib/commonjs/components/pullToRefresh/utils.js.map +1 -0
  21. package/lib/commonjs/components/{CollapsibleFlatList.js → scrollable/CollapsibleFlatList.js} +36 -31
  22. package/lib/commonjs/components/scrollable/CollapsibleFlatList.js.map +1 -0
  23. package/lib/commonjs/components/{CollapsibleScrollView.js → scrollable/CollapsibleScrollView.js} +6 -6
  24. package/lib/commonjs/components/scrollable/CollapsibleScrollView.js.map +1 -0
  25. package/lib/commonjs/{hooks → components/scrollable}/useAnimatedScroll.js +7 -7
  26. package/lib/commonjs/components/scrollable/useAnimatedScroll.js.map +1 -0
  27. package/lib/commonjs/hooks/useInternalCollapsibleContext.js +1 -1
  28. package/lib/commonjs/hooks/useInternalCollapsibleContext.js.map +1 -1
  29. package/lib/commonjs/index.js +21 -12
  30. package/lib/commonjs/index.js.map +1 -1
  31. package/lib/commonjs/{types.js → types.d.js} +1 -1
  32. package/lib/commonjs/{types.js.map → types.d.js.map} +0 -0
  33. package/lib/commonjs/utils/debounce.js +20 -0
  34. package/lib/commonjs/utils/debounce.js.map +1 -0
  35. package/lib/commonjs/{hooks/withCollapsibleContext.js → withCollapsibleContext.js} +95 -64
  36. package/lib/commonjs/withCollapsibleContext.js.map +1 -0
  37. package/lib/module/components/CollapsibleContainer.js +1 -1
  38. package/lib/module/components/CollapsibleContainer.js.map +1 -1
  39. package/lib/module/components/CollapsibleView.js +4 -3
  40. package/lib/module/components/CollapsibleView.js.map +1 -1
  41. package/lib/module/components/{AnimatedTopView.js → header/AnimatedTopView.js} +0 -0
  42. package/lib/module/components/{AnimatedTopView.js.map → header/AnimatedTopView.js.map} +0 -0
  43. package/lib/module/components/{CollapsibleHeaderContainer.js → header/CollapsibleHeaderContainer.js} +7 -6
  44. package/lib/module/components/header/CollapsibleHeaderContainer.js.map +1 -0
  45. package/lib/module/components/{StickyView.js → header/StickyView.js} +17 -30
  46. package/lib/module/components/header/StickyView.js.map +1 -0
  47. package/lib/module/components/pullToRefresh/PullToRefreshContainer.js +56 -0
  48. package/lib/module/components/pullToRefresh/PullToRefreshContainer.js.map +1 -0
  49. package/lib/module/components/pullToRefresh/PullToRefreshProvider.js +21 -0
  50. package/lib/module/components/pullToRefresh/PullToRefreshProvider.js.map +1 -0
  51. package/lib/module/components/pullToRefresh/RefreshControl.js +55 -0
  52. package/lib/module/components/pullToRefresh/RefreshControl.js.map +1 -0
  53. package/lib/module/components/pullToRefresh/usePullToRefreshContext.js +13 -0
  54. package/lib/module/components/pullToRefresh/usePullToRefreshContext.js.map +1 -0
  55. package/lib/module/components/pullToRefresh/utils.js +42 -0
  56. package/lib/module/components/pullToRefresh/utils.js.map +1 -0
  57. package/lib/module/components/{CollapsibleFlatList.js → scrollable/CollapsibleFlatList.js} +36 -32
  58. package/lib/module/components/scrollable/CollapsibleFlatList.js.map +1 -0
  59. package/lib/module/components/{CollapsibleScrollView.js → scrollable/CollapsibleScrollView.js} +5 -5
  60. package/lib/module/components/scrollable/CollapsibleScrollView.js.map +1 -0
  61. package/lib/module/{hooks → components/scrollable}/useAnimatedScroll.js +6 -6
  62. package/lib/module/components/scrollable/useAnimatedScroll.js.map +1 -0
  63. package/lib/module/hooks/useInternalCollapsibleContext.js +1 -1
  64. package/lib/module/hooks/useInternalCollapsibleContext.js.map +1 -1
  65. package/lib/module/index.js +6 -5
  66. package/lib/module/index.js.map +1 -1
  67. package/lib/module/types.d.js +2 -0
  68. package/lib/module/{types.js.map → types.d.js.map} +0 -0
  69. package/lib/module/utils/debounce.js +13 -0
  70. package/lib/module/utils/debounce.js.map +1 -0
  71. package/lib/module/withCollapsibleContext.js +163 -0
  72. package/lib/module/withCollapsibleContext.js.map +1 -0
  73. package/lib/typescript/components/CollapsibleView.d.ts +1 -1
  74. package/lib/typescript/components/{AnimatedTopView.d.ts → header/AnimatedTopView.d.ts} +0 -0
  75. package/lib/typescript/components/{CollapsibleHeaderContainer.d.ts → header/CollapsibleHeaderContainer.d.ts} +0 -0
  76. package/lib/typescript/components/{StickyView.d.ts → header/StickyView.d.ts} +0 -0
  77. package/lib/typescript/components/pullToRefresh/PullToRefreshContainer.d.ts +8 -0
  78. package/lib/typescript/components/pullToRefresh/PullToRefreshProvider.d.ts +6 -0
  79. package/lib/typescript/components/pullToRefresh/RefreshControl.d.ts +9 -0
  80. package/lib/typescript/components/pullToRefresh/usePullToRefreshContext.d.ts +4 -0
  81. package/lib/typescript/components/pullToRefresh/utils.d.ts +20 -0
  82. package/lib/typescript/components/{CollapsibleFlatList.d.ts → scrollable/CollapsibleFlatList.d.ts} +1 -1
  83. package/lib/typescript/components/{CollapsibleScrollView.d.ts → scrollable/CollapsibleScrollView.d.ts} +1 -1
  84. package/lib/typescript/{hooks → components/scrollable}/useAnimatedScroll.d.ts +0 -0
  85. package/lib/typescript/hooks/useInternalCollapsibleContext.d.ts +1 -1
  86. package/lib/typescript/index.d.ts +6 -5
  87. package/lib/typescript/utils/debounce.d.ts +1 -0
  88. package/lib/typescript/{hooks/withCollapsibleContext.d.ts → withCollapsibleContext.d.ts} +0 -0
  89. package/package.json +4 -2
  90. package/src/components/CollapsibleContainer.tsx +1 -1
  91. package/src/components/CollapsibleView.tsx +4 -3
  92. package/src/components/{AnimatedTopView.tsx → header/AnimatedTopView.tsx} +0 -0
  93. package/src/components/{CollapsibleHeaderContainer.tsx → header/CollapsibleHeaderContainer.tsx} +6 -4
  94. package/src/components/{StickyView.tsx → header/StickyView.tsx} +15 -22
  95. package/src/components/pullToRefresh/PullToRefreshContainer.tsx +65 -0
  96. package/src/components/pullToRefresh/PullToRefreshProvider.tsx +27 -0
  97. package/src/components/pullToRefresh/RefreshControl.tsx +80 -0
  98. package/src/components/pullToRefresh/usePullToRefreshContext.ts +13 -0
  99. package/src/components/pullToRefresh/utils.ts +49 -0
  100. package/src/components/scrollable/CollapsibleFlatList.tsx +135 -0
  101. package/src/components/{CollapsibleScrollView.tsx → scrollable/CollapsibleScrollView.tsx} +6 -6
  102. package/src/{hooks → components/scrollable}/useAnimatedScroll.ts +8 -8
  103. package/src/hooks/useInternalCollapsibleContext.ts +1 -1
  104. package/src/index.tsx +6 -5
  105. package/src/{types.ts → types.d.ts} +14 -3
  106. package/src/utils/debounce.ts +10 -0
  107. package/src/withCollapsibleContext.tsx +201 -0
  108. package/lib/commonjs/components/CollapsibleFlatList.js.map +0 -1
  109. package/lib/commonjs/components/CollapsibleHeaderContainer.js.map +0 -1
  110. package/lib/commonjs/components/CollapsibleScrollView.js.map +0 -1
  111. package/lib/commonjs/components/StickyView.js.map +0 -1
  112. package/lib/commonjs/hooks/useAnimatedScroll.js.map +0 -1
  113. package/lib/commonjs/hooks/withCollapsibleContext.js.map +0 -1
  114. package/lib/module/components/CollapsibleFlatList.js.map +0 -1
  115. package/lib/module/components/CollapsibleHeaderContainer.js.map +0 -1
  116. package/lib/module/components/CollapsibleScrollView.js.map +0 -1
  117. package/lib/module/components/StickyView.js.map +0 -1
  118. package/lib/module/hooks/useAnimatedScroll.js.map +0 -1
  119. package/lib/module/hooks/withCollapsibleContext.js +0 -136
  120. package/lib/module/hooks/withCollapsibleContext.js.map +0 -1
  121. package/lib/module/types.js +0 -2
  122. package/lib/typescript/types.d.ts +0 -33
  123. package/src/components/CollapsibleFlatList.tsx +0 -119
  124. package/src/hooks/withCollapsibleContext.tsx +0 -164
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@r0b0t3d/react-native-collapsible",
3
- "version": "1.0.1",
3
+ "version": "1.2.0-alpha.0",
4
4
  "description": "Fully customizable collapsible views",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -64,6 +64,7 @@
64
64
  "react": "16.13.1",
65
65
  "react-native": "0.63.4",
66
66
  "react-native-builder-bob": "^0.18.0",
67
+ "react-native-gesture-handler": "^1.10.3",
67
68
  "react-native-reanimated": "^2.2.0",
68
69
  "release-it": "^14.11.5",
69
70
  "typescript": "^4.1.3"
@@ -71,7 +72,8 @@
71
72
  "peerDependencies": {
72
73
  "react": "*",
73
74
  "react-native": "*",
74
- "react-native-reanimated": "^2.2.0"
75
+ "react-native-gesture-handler": "*",
76
+ "react-native-reanimated": ">=2.2.0"
75
77
  },
76
78
  "jest": {
77
79
  "preset": "react-native",
@@ -1,6 +1,6 @@
1
1
  import React, { useCallback } from 'react';
2
2
  import { LayoutChangeEvent, StyleSheet, View, ViewProps } from 'react-native';
3
- import { useInternalCollapsibleContext } from '../hooks/useInternalCollapsibleContext';
3
+ import useInternalCollapsibleContext from '../hooks/useInternalCollapsibleContext';
4
4
 
5
5
  type Props = Omit<ViewProps, 'ref' | 'onLayout'> & {
6
6
  children: Element;
@@ -63,7 +63,7 @@ export default function CollapsibleView({
63
63
  }, []);
64
64
 
65
65
  const handleLayout = useCallback((event: LayoutChangeEvent) => {
66
- if (event.nativeEvent.layout.height > 0) {
66
+ if (event.nativeEvent.layout.height >= 0) {
67
67
  actualHeight.value = event.nativeEvent.layout.height;
68
68
  }
69
69
  }, []);
@@ -142,7 +142,7 @@ export function CollapsibleHeaderText({
142
142
  iconInitialAngle = 0,
143
143
  children,
144
144
  }: {
145
- title: string;
145
+ title: string | Element;
146
146
  style?: StyleProp<ViewStyle>;
147
147
  titleStyle?: StyleProp<TextStyle>;
148
148
  icon?: ReactNode;
@@ -165,7 +165,7 @@ export function CollapsibleHeaderText({
165
165
  }, [iconInitialAngle]);
166
166
 
167
167
  return (
168
- <TouchableOpacity activeOpacity={0.9} onPress={onToggle} style={[style]}>
168
+ <TouchableOpacity activeOpacity={0.9} onPress={onToggle} style={style}>
169
169
  <View style={styles.headerContainer}>
170
170
  <Text style={[styles.headerTitle, titleStyle]}>{title}</Text>
171
171
  {icon && <Animated.View style={iconStyle}>{icon}</Animated.View>}
@@ -182,6 +182,7 @@ const styles = StyleSheet.create({
182
182
  content: {},
183
183
  headerContainer: {
184
184
  flexDirection: 'row',
185
+ alignItems: 'center',
185
186
  },
186
187
  headerTitle: {
187
188
  flex: 1,
@@ -1,8 +1,9 @@
1
1
  /* eslint-disable react-hooks/exhaustive-deps */
2
- import { useInternalCollapsibleContext } from '../hooks/useInternalCollapsibleContext';
2
+ import useInternalCollapsibleContext from '../../hooks/useInternalCollapsibleContext';
3
3
  import React, { ReactNode, useCallback, useEffect, useMemo } from 'react';
4
4
  import {
5
5
  LayoutChangeEvent,
6
+ Platform,
6
7
  StyleProp,
7
8
  StyleSheet,
8
9
  View,
@@ -15,7 +16,7 @@ import Animated, {
15
16
  useSharedValue,
16
17
  withTiming,
17
18
  } from 'react-native-reanimated';
18
- import useCollapsibleContext from '../hooks/useCollapsibleContext';
19
+ import useCollapsibleContext from '../../hooks/useCollapsibleContext';
19
20
 
20
21
  type Props = {
21
22
  children: ReactNode;
@@ -78,14 +79,14 @@ export default function CollapsibleHeaderContainer({
78
79
 
79
80
  return (
80
81
  <Animated.View
81
- style={[styles.container, headerStyle, internalStyle]}
82
+ style={[headerStyle, internalStyle]}
82
83
  pointerEvents="box-none"
83
84
  >
84
85
  <View
85
86
  key={contentKey}
86
87
  onLayout={handleHeaderLayout}
87
88
  pointerEvents="box-none"
88
- style={containerStyle}
89
+ style={[styles.container, containerStyle]}
89
90
  >
90
91
  {children}
91
92
  </View>
@@ -96,5 +97,6 @@ export default function CollapsibleHeaderContainer({
96
97
  const styles = StyleSheet.create({
97
98
  container: {
98
99
  backgroundColor: 'white',
100
+ marginTop: Platform.OS === 'android' ? -1 : 0,
99
101
  },
100
102
  });
@@ -1,13 +1,13 @@
1
1
  /* eslint-disable react-hooks/exhaustive-deps */
2
- import { useInternalCollapsibleContext } from '../hooks/useInternalCollapsibleContext';
3
2
  import React, { useCallback, useEffect, useMemo, useRef } from 'react';
4
3
  import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native';
5
- import useCollapsibleContext from '../hooks/useCollapsibleContext';
4
+ import useCollapsibleContext from '../../hooks/useCollapsibleContext';
5
+ import useInternalCollapsibleContext from '../../hooks/useInternalCollapsibleContext';
6
6
  import Animated, {
7
7
  Extrapolate,
8
8
  interpolate,
9
9
  useAnimatedStyle,
10
- useSharedValue,
10
+ useDerivedValue,
11
11
  } from 'react-native-reanimated';
12
12
 
13
13
  type Props = {
@@ -20,46 +20,39 @@ let stickyKey = 0;
20
20
  export default function StickyView({ children, style }: Props) {
21
21
  const key = useMemo(() => `sticky_${stickyKey++}`, []);
22
22
  const viewRef = useRef<View>(null);
23
- const { containerRef, handleStickyViewLayout, stickyViewTops } =
23
+ const { handleStickyViewLayout, stickyViewTops, stickyViewPositions } =
24
24
  useInternalCollapsibleContext();
25
25
  const { scrollY } = useCollapsibleContext();
26
- const layoutValues = useSharedValue({ top: 0, height: 0 });
27
26
 
28
27
  useEffect(() => {
29
28
  return () => handleStickyViewLayout(key, undefined);
30
29
  }, []);
31
30
 
32
31
  const handleLayout = useCallback(() => {
33
- if (viewRef.current && containerRef.current) {
34
- viewRef.current.measureLayout(
35
- // @ts-ignore
36
- containerRef.current,
37
- (left, top, width, height) => {
38
- handleStickyViewLayout(key, { left, top, width, height });
39
- layoutValues.value = { top, height };
40
- },
41
- () => {}
42
- );
43
- }
44
- }, [handleStickyViewLayout]);
32
+ handleStickyViewLayout(key, viewRef);
33
+ }, [handleStickyViewLayout, key]);
45
34
 
46
- const animatedStyle = useAnimatedStyle(() => {
35
+ const translateY = useDerivedValue(() => {
47
36
  const top = stickyViewTops.value[key] || 0;
48
- const inputMid = layoutValues.value.top - top;
49
- const translateY = interpolate(
37
+ const layoutValues = stickyViewPositions.value[key] || { top: 0 };
38
+ const inputMid = layoutValues.top - top;
39
+ return interpolate(
50
40
  scrollY.value,
51
41
  [0, inputMid, inputMid + 100000],
52
42
  [0, 0, 100000],
53
43
  Extrapolate.CLAMP
54
44
  );
45
+ }, []);
46
+
47
+ const animatedStyle = useAnimatedStyle(() => {
55
48
  return {
56
49
  transform: [
57
50
  {
58
- translateY: translateY,
51
+ translateY: translateY.value,
59
52
  },
60
53
  ],
61
54
  };
62
- }, [stickyViewTops, layoutValues, scrollY]);
55
+ }, []);
63
56
 
64
57
  return (
65
58
  <Animated.View
@@ -0,0 +1,65 @@
1
+ import {
2
+ NativeViewGestureHandler,
3
+ PanGestureHandler,
4
+ } from 'react-native-gesture-handler';
5
+ import React, { useRef } from 'react';
6
+ import Animated, {
7
+ useAnimatedGestureHandler,
8
+ withTiming,
9
+ } from 'react-native-reanimated';
10
+ import usePullToRefreshContext from './usePullToRefreshContext';
11
+ import { StyleSheet } from 'react-native';
12
+ import { rubberClamp } from './utils';
13
+
14
+ type Props = {
15
+ children: React.ReactNode;
16
+ scrollY: Animated.SharedValue<number>;
17
+ };
18
+
19
+ export default function PullToRefreshContainer({ children, scrollY }: Props) {
20
+ const scrollRef = useRef();
21
+ const panRef = useRef();
22
+ const { refreshValue, internalRefreshing, internalHeight } =
23
+ usePullToRefreshContext();
24
+
25
+ const gestureHandler = useAnimatedGestureHandler({
26
+ onStart: (_, ctx: any) => {
27
+ ctx.startY = internalRefreshing.value ? refreshValue.value : 0;
28
+ },
29
+ onActive: (event, ctx: any) => {
30
+ if (scrollY.value <= 1) {
31
+ const tranY = event.translationY + ctx.startY;
32
+ const clampedValue = rubberClamp(tranY, 0, internalHeight.value);
33
+ refreshValue.value = clampedValue;
34
+ if (clampedValue > internalHeight.value) {
35
+ internalRefreshing.value = true;
36
+ }
37
+ } else {
38
+ refreshValue.value = 0;
39
+ }
40
+ },
41
+ onEnd: () => {
42
+ if (refreshValue.value > 0) {
43
+ const value = internalRefreshing.value ? internalHeight.value : 0;
44
+ refreshValue.value = withTiming(value);
45
+ }
46
+ },
47
+ });
48
+
49
+ return (
50
+ <PanGestureHandler
51
+ ref={panRef}
52
+ simultaneousHandlers={scrollRef}
53
+ onGestureEvent={gestureHandler}
54
+ shouldCancelWhenOutside={false}
55
+ enableTrackpadTwoFingerGesture
56
+ maxPointers={1}
57
+ >
58
+ <Animated.View style={StyleSheet.absoluteFill}>
59
+ <NativeViewGestureHandler ref={scrollRef} simultaneousHandlers={panRef}>
60
+ {children}
61
+ </NativeViewGestureHandler>
62
+ </Animated.View>
63
+ </PanGestureHandler>
64
+ );
65
+ }
@@ -0,0 +1,27 @@
1
+ import React, { useMemo } from 'react';
2
+ import { useSharedValue } from 'react-native-reanimated';
3
+ import { PullToRefreshContext } from './usePullToRefreshContext';
4
+
5
+ type Props = {
6
+ children: React.ReactNode;
7
+ };
8
+
9
+ export default function PullToRefreshProvider({ children }: Props) {
10
+ const refreshValue = useSharedValue(0);
11
+ const internalRefreshing = useSharedValue(false);
12
+ const internalHeight = useSharedValue(0);
13
+
14
+ const context = useMemo(() => {
15
+ return {
16
+ refreshValue: refreshValue,
17
+ internalRefreshing,
18
+ internalHeight,
19
+ };
20
+ }, [refreshValue, internalRefreshing, internalHeight]);
21
+
22
+ return (
23
+ <PullToRefreshContext.Provider value={context}>
24
+ {children}
25
+ </PullToRefreshContext.Provider>
26
+ );
27
+ }
@@ -0,0 +1,80 @@
1
+ /* eslint-disable react-hooks/exhaustive-deps */
2
+ import React, { useCallback, useEffect } from 'react';
3
+ import { StyleSheet } from 'react-native';
4
+ import Animated, {
5
+ runOnJS,
6
+ useAnimatedProps,
7
+ useAnimatedReaction,
8
+ useAnimatedStyle,
9
+ withTiming,
10
+ } from 'react-native-reanimated';
11
+ import usePullToRefreshContext from './usePullToRefreshContext';
12
+
13
+ type Props = {
14
+ height?: number;
15
+ refreshing: boolean;
16
+ onRefresh: () => void;
17
+ renderAnimation: (animatedProps: any) => React.ReactNode;
18
+ };
19
+
20
+ export default function RefreshControl({
21
+ height = 100,
22
+ refreshing,
23
+ onRefresh,
24
+ renderAnimation,
25
+ }: Props) {
26
+ const { refreshValue, internalRefreshing, internalHeight } =
27
+ usePullToRefreshContext();
28
+
29
+ useEffect(() => {
30
+ internalHeight.value = height;
31
+ }, [height]);
32
+
33
+ useEffect(() => {
34
+ internalRefreshing.value = refreshing;
35
+ }, [refreshing]);
36
+
37
+ const animatedStyle = useAnimatedStyle(() => {
38
+ return {
39
+ height: refreshValue.value,
40
+ };
41
+ }, []);
42
+
43
+ const handleRefresh = useCallback(() => {
44
+ console.log('refresh');
45
+ onRefresh();
46
+ }, [onRefresh]);
47
+
48
+ useAnimatedReaction(
49
+ () => internalRefreshing.value,
50
+ (result, prev) => {
51
+ if (result !== prev) {
52
+ if (result) {
53
+ runOnJS(handleRefresh)();
54
+ } else {
55
+ refreshValue.value = withTiming(0);
56
+ }
57
+ }
58
+ }
59
+ );
60
+
61
+ const animatedProps = useAnimatedProps(() => {
62
+ return {
63
+ progress: internalRefreshing.value
64
+ ? undefined
65
+ : Math.min(refreshValue.value / height, 1),
66
+ };
67
+ }, [height]);
68
+
69
+ return (
70
+ <Animated.View style={[styles.container, animatedStyle]}>
71
+ {renderAnimation(animatedProps)}
72
+ </Animated.View>
73
+ );
74
+ }
75
+
76
+ const styles = StyleSheet.create({
77
+ container: {
78
+ overflow: 'hidden',
79
+ },
80
+ });
@@ -0,0 +1,13 @@
1
+ import { createContext, useContext } from 'react';
2
+ import type { PullToRefreshContextType } from '../../types';
3
+
4
+ // @ts-ignore
5
+ export const PullToRefreshContext = createContext<PullToRefreshContextType>({});
6
+
7
+ export default function usePullToRefreshContext() {
8
+ const ctx = useContext(PullToRefreshContext);
9
+ if (!ctx) {
10
+ throw new Error('Component should be wrapped with withCollapsibleContext');
11
+ }
12
+ return ctx;
13
+ }
@@ -0,0 +1,49 @@
1
+ export const springConfig = (velocity: number) => {
2
+ 'worklet';
3
+
4
+ return {
5
+ stiffness: 1000,
6
+ damping: 500,
7
+ mass: 3,
8
+ overshootClamping: true,
9
+ restDisplacementThreshold: 0.01,
10
+ restSpeedThreshold: 0.01,
11
+ velocity,
12
+ };
13
+ };
14
+
15
+ export function clamp(value: number, lowerbound: number, upperbound: number) {
16
+ 'worklet';
17
+
18
+ return Math.min(Math.max(value, lowerbound), upperbound);
19
+ }
20
+
21
+ /**
22
+ * calculates rubber value
23
+ *
24
+ * @param x distance from the edge
25
+ * @param dim dimension, either width or height
26
+ * @param coeff constant value, UIScrollView uses 0.55
27
+ * @returns rubber = (1.0 – (1.0 / ((x * coeff / dim) + 1.0))) * dim
28
+ */
29
+ export const rubberBandClamp = (x: number, dim: number, coeff: number) => {
30
+ 'worklet';
31
+
32
+ return (1.0 - 1.0 / ((x * coeff) / dim + 1.0)) * dim;
33
+ };
34
+
35
+ export const rubberClamp = (
36
+ y: number,
37
+ topBound: number,
38
+ bottomBound: number,
39
+ coeff = 0.55
40
+ ) => {
41
+ 'worklet';
42
+
43
+ const clampedY = clamp(y, topBound, bottomBound);
44
+ const diff = Math.abs(y - clampedY);
45
+ const sign = clampedY > y ? -1 : 1;
46
+ const dimension = bottomBound - topBound;
47
+
48
+ return clampedY + sign * rubberBandClamp(diff, dimension, coeff);
49
+ };
@@ -0,0 +1,135 @@
1
+ import React, {
2
+ useCallback,
3
+ useEffect,
4
+ useMemo,
5
+ useRef,
6
+ useState,
7
+ } from 'react';
8
+ import { FlatListProps, View, StyleSheet, FlatList } from 'react-native';
9
+ import Animated, {
10
+ runOnJS,
11
+ useAnimatedReaction,
12
+ } from 'react-native-reanimated';
13
+ import AnimatedTopView from '../header/AnimatedTopView';
14
+ import useAnimatedScroll from './useAnimatedScroll';
15
+ import useCollapsibleContext from '../../hooks/useCollapsibleContext';
16
+ import useInternalCollapsibleContext from '../../hooks/useInternalCollapsibleContext';
17
+ import type { CollapsibleProps } from '../../types';
18
+ import PullToRefreshContainer from '../pullToRefresh/PullToRefreshContainer';
19
+
20
+ const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
21
+
22
+ type Props<Data> = Omit<FlatListProps<Data>, 'scrollEnabled'> &
23
+ CollapsibleProps;
24
+
25
+ export default function CollapsibleFlatList<Data>({
26
+ headerSnappable = true,
27
+ ...props
28
+ }: Props<Data>) {
29
+ const { headerHeight, scrollY } = useCollapsibleContext();
30
+ const { contentMinHeight } = useInternalCollapsibleContext();
31
+ const scrollRef = useRef<any>(null);
32
+ const mounted = useRef(true);
33
+ const contentHeight = useRef(0);
34
+
35
+ useEffect(() => {
36
+ return () => {
37
+ mounted.current = false;
38
+ };
39
+ }, []);
40
+
41
+ const scrollTo = useCallback(
42
+ (yValue: number, animated = true) => {
43
+ scrollRef.current?.scrollToOffset({
44
+ offset: yValue,
45
+ animated,
46
+ });
47
+ },
48
+ [scrollRef]
49
+ );
50
+
51
+ const handleInternalContentHeight = useCallback((value: number) => {
52
+ if (mounted.current) {
53
+ setInternalContentMinHeight(value);
54
+ }
55
+ }, []);
56
+
57
+ const { scrollHandler } = useAnimatedScroll({
58
+ headerSnappable,
59
+ scrollTo,
60
+ });
61
+
62
+ const [internalContentMinHeight, setInternalContentMinHeight] = useState(
63
+ contentMinHeight.value
64
+ );
65
+
66
+ useAnimatedReaction(
67
+ () => {
68
+ return contentMinHeight.value;
69
+ },
70
+ (result, previous) => {
71
+ if (result !== previous) {
72
+ if (
73
+ contentHeight.current < contentMinHeight.value &&
74
+ internalContentMinHeight !== contentMinHeight.value
75
+ ) {
76
+ runOnJS(handleInternalContentHeight)(contentMinHeight.value);
77
+ }
78
+ }
79
+ }
80
+ );
81
+
82
+ const contentContainerStyle = useMemo(
83
+ () => [
84
+ styles.contentContainer,
85
+ { minHeight: internalContentMinHeight },
86
+ props.contentContainerStyle,
87
+ ],
88
+ [props.contentContainerStyle, internalContentMinHeight]
89
+ );
90
+
91
+ const handleContentSizeChange = useCallback((_, height) => {
92
+ contentHeight.current = height;
93
+ }, []);
94
+
95
+ const renderListHeader = () => (
96
+ <View>
97
+ <AnimatedTopView height={headerHeight} />
98
+ {props.ListHeaderComponent}
99
+ </View>
100
+ );
101
+
102
+ return (
103
+ <PullToRefreshContainer scrollY={scrollY}>
104
+ {/* @ts-ignore */}
105
+ <AnimatedFlatList
106
+ ref={scrollRef}
107
+ bounces={false}
108
+ keyboardDismissMode="on-drag"
109
+ keyboardShouldPersistTaps="handled"
110
+ scrollEventThrottle={1}
111
+ {...props}
112
+ style={[styles.container, props.style]}
113
+ contentContainerStyle={contentContainerStyle}
114
+ onScroll={scrollHandler}
115
+ ListHeaderComponent={renderListHeader()}
116
+ onContentSizeChange={handleContentSizeChange}
117
+ />
118
+ </PullToRefreshContainer>
119
+ );
120
+ }
121
+
122
+ const styles = StyleSheet.create({
123
+ container: {
124
+ ...StyleSheet.absoluteFillObject,
125
+ },
126
+ contentContainer: {
127
+ flexGrow: 1,
128
+ },
129
+ topView: {
130
+ position: 'absolute',
131
+ top: 0,
132
+ left: 0,
133
+ right: 0,
134
+ },
135
+ });
@@ -1,11 +1,11 @@
1
- import AnimatedTopView from './AnimatedTopView';
2
- import useAnimatedScroll from '../hooks/useAnimatedScroll';
1
+ import AnimatedTopView from '../header/AnimatedTopView';
2
+ import useAnimatedScroll from './useAnimatedScroll';
3
3
  import React, { ReactNode, useCallback, useMemo, useRef } from 'react';
4
4
  import { ScrollViewProps, StyleSheet } from 'react-native';
5
5
  import Animated, { useAnimatedStyle } from 'react-native-reanimated';
6
- import type { CollapsibleProps } from '../types';
7
- import useCollapsibleContext from '../hooks/useCollapsibleContext';
8
- import { useInternalCollapsibleContext } from '../hooks/useInternalCollapsibleContext';
6
+ import type { CollapsibleProps } from '../../types';
7
+ import useCollapsibleContext from '../../hooks/useCollapsibleContext';
8
+ import useInternalCollapsibleContext from '../../hooks/useInternalCollapsibleContext';
9
9
 
10
10
  type Props = ScrollViewProps &
11
11
  CollapsibleProps & {
@@ -52,7 +52,7 @@ export default function CollapsibleScrollView({
52
52
  onScroll={scrollHandler}
53
53
  keyboardDismissMode="on-drag"
54
54
  keyboardShouldPersistTaps="handled"
55
- scrollEventThrottle={16}
55
+ scrollEventThrottle={1}
56
56
  >
57
57
  <Animated.View style={animatedStyle}>
58
58
  <AnimatedTopView height={headerHeight} />
@@ -6,8 +6,8 @@ import {
6
6
  useAnimatedScrollHandler,
7
7
  useSharedValue,
8
8
  } from 'react-native-reanimated';
9
- import useCollapsibleContext from './useCollapsibleContext';
10
- import { useInternalCollapsibleContext } from './useInternalCollapsibleContext';
9
+ import useCollapsibleContext from '../../hooks/useCollapsibleContext';
10
+ import useInternalCollapsibleContext from '../../hooks/useInternalCollapsibleContext';
11
11
 
12
12
  const { height: wHeight } = Dimensions.get('window');
13
13
 
@@ -33,9 +33,9 @@ export default function useAnimatedScroll({
33
33
 
34
34
  const collapse = useCallback(() => {
35
35
  scrollTo(
36
- Math.min(fixedHeaderHeight.current || 0, firstStickyViewY.current || 0)
36
+ Math.min(fixedHeaderHeight.value || 0, firstStickyViewY.value || 0)
37
37
  );
38
- }, [scrollTo, fixedHeaderHeight.current, firstStickyViewY.current]);
38
+ }, [scrollTo]);
39
39
 
40
40
  const expand = useCallback(() => scrollTo(0), [scrollTo]);
41
41
 
@@ -58,9 +58,9 @@ export default function useAnimatedScroll({
58
58
  onEndDrag: () => {
59
59
  if (!headerSnappable) return;
60
60
  const maxY =
61
- firstStickyViewY.current && firstStickyViewY.current > 0
62
- ? firstStickyViewY.current
63
- : fixedHeaderHeight.current || 0;
61
+ firstStickyViewY.value && firstStickyViewY.value > 0
62
+ ? firstStickyViewY.value
63
+ : fixedHeaderHeight.value || 0;
64
64
 
65
65
  if (scrollY.value < maxY) {
66
66
  const delta = Math.abs(scrollY.value - maxY);
@@ -74,7 +74,7 @@ export default function useAnimatedScroll({
74
74
  }
75
75
  },
76
76
  },
77
- [firstStickyViewY.current, fixedHeaderHeight.current]
77
+ [scrollTo]
78
78
  );
79
79
 
80
80
  return {
@@ -5,7 +5,7 @@ export const InternalCollapsibleContext =
5
5
  // @ts-ignore
6
6
  createContext<CollapsibleContextInternalType>();
7
7
 
8
- export function useInternalCollapsibleContext() {
8
+ export default function useInternalCollapsibleContext() {
9
9
  const ctx = useContext(InternalCollapsibleContext);
10
10
  if (!ctx) {
11
11
  throw new Error('Component should be wrapped with withCollapsibleContext');
package/src/index.tsx CHANGED
@@ -1,10 +1,11 @@
1
- export { default as withCollapsibleContext } from './hooks/withCollapsibleContext';
1
+ export { default as withCollapsibleContext } from './withCollapsibleContext';
2
2
  export { default as useCollapsibleContext } from './hooks/useCollapsibleContext';
3
3
 
4
4
  export { default as CollapsibleContainer } from './components/CollapsibleContainer';
5
- export { default as CollapsibleFlatList } from './components/CollapsibleFlatList';
6
- export { default as CollapsibleScrollView } from './components/CollapsibleScrollView';
7
- export { default as CollapsibleHeaderContainer } from './components/CollapsibleHeaderContainer';
5
+ export { default as CollapsibleFlatList } from './components/scrollable/CollapsibleFlatList';
6
+ export { default as CollapsibleScrollView } from './components/scrollable/CollapsibleScrollView';
7
+ export { default as CollapsibleHeaderContainer } from './components/header/CollapsibleHeaderContainer';
8
+ export { default as StickyView } from './components/header/StickyView';
9
+ export { default as RefreshControl } from './components/pullToRefresh/RefreshControl';
8
10
  export { default as CollapsibleView } from './components/CollapsibleView';
9
- export { default as StickyView } from './components/StickyView';
10
11
  export * from './components/CollapsibleView';