@r0b0t3d/react-native-collapsible 1.1.0 → 1.2.0-alpha.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.
Files changed (109) hide show
  1. package/lib/commonjs/components/CollapsibleContainer.js +49 -4
  2. package/lib/commonjs/components/CollapsibleContainer.js.map +1 -1
  3. package/lib/commonjs/components/{AnimatedTopView.js → header/AnimatedTopView.js} +0 -0
  4. package/lib/commonjs/components/{AnimatedTopView.js.map → header/AnimatedTopView.js.map} +0 -0
  5. package/lib/commonjs/components/{CollapsibleHeaderContainer.js → header/CollapsibleHeaderContainer.js} +9 -8
  6. package/lib/commonjs/components/header/CollapsibleHeaderContainer.js.map +1 -0
  7. package/lib/commonjs/components/{StickyView.js → header/StickyView.js} +4 -4
  8. package/lib/commonjs/components/header/StickyView.js.map +1 -0
  9. package/lib/commonjs/components/pullToRefresh/PullToRefreshContainer.js +75 -0
  10. package/lib/commonjs/components/pullToRefresh/PullToRefreshContainer.js.map +1 -0
  11. package/lib/commonjs/components/pullToRefresh/PullToRefreshProvider.js +35 -0
  12. package/lib/commonjs/components/pullToRefresh/PullToRefreshProvider.js.map +1 -0
  13. package/lib/commonjs/components/pullToRefresh/RefreshControl.js +73 -0
  14. package/lib/commonjs/components/pullToRefresh/RefreshControl.js.map +1 -0
  15. package/lib/commonjs/components/pullToRefresh/usePullToRefreshContext.js +24 -0
  16. package/lib/commonjs/components/pullToRefresh/usePullToRefreshContext.js.map +1 -0
  17. package/lib/commonjs/components/pullToRefresh/utils.js +59 -0
  18. package/lib/commonjs/components/pullToRefresh/utils.js.map +1 -0
  19. package/lib/commonjs/components/{CollapsibleFlatList.js → scrollable/CollapsibleFlatList.js} +28 -27
  20. package/lib/commonjs/components/scrollable/CollapsibleFlatList.js.map +1 -0
  21. package/lib/commonjs/components/{CollapsibleScrollView.js → scrollable/CollapsibleScrollView.js} +11 -12
  22. package/lib/commonjs/components/scrollable/CollapsibleScrollView.js.map +1 -0
  23. package/lib/commonjs/{hooks → components/scrollable}/useAnimatedScroll.js +3 -3
  24. package/lib/commonjs/{hooks → components/scrollable}/useAnimatedScroll.js.map +1 -1
  25. package/lib/commonjs/hooks/useInternalCollapsibleContext.js +1 -1
  26. package/lib/commonjs/hooks/useInternalCollapsibleContext.js.map +1 -1
  27. package/lib/commonjs/hooks/useKeyboardShowEvent.js +29 -0
  28. package/lib/commonjs/hooks/useKeyboardShowEvent.js.map +1 -0
  29. package/lib/commonjs/index.js +21 -12
  30. package/lib/commonjs/index.js.map +1 -1
  31. package/lib/commonjs/{hooks/withCollapsibleContext.js → withCollapsibleContext.js} +10 -6
  32. package/lib/commonjs/withCollapsibleContext.js.map +1 -0
  33. package/lib/module/components/CollapsibleContainer.js +46 -5
  34. package/lib/module/components/CollapsibleContainer.js.map +1 -1
  35. package/lib/module/components/{AnimatedTopView.js → header/AnimatedTopView.js} +0 -0
  36. package/lib/module/components/{AnimatedTopView.js.map → header/AnimatedTopView.js.map} +0 -0
  37. package/lib/module/components/{CollapsibleHeaderContainer.js → header/CollapsibleHeaderContainer.js} +7 -6
  38. package/lib/module/components/header/CollapsibleHeaderContainer.js.map +1 -0
  39. package/lib/module/components/{StickyView.js → header/StickyView.js} +2 -2
  40. package/lib/module/components/header/StickyView.js.map +1 -0
  41. package/lib/module/components/pullToRefresh/PullToRefreshContainer.js +56 -0
  42. package/lib/module/components/pullToRefresh/PullToRefreshContainer.js.map +1 -0
  43. package/lib/module/components/pullToRefresh/PullToRefreshProvider.js +21 -0
  44. package/lib/module/components/pullToRefresh/PullToRefreshProvider.js.map +1 -0
  45. package/lib/module/components/pullToRefresh/RefreshControl.js +55 -0
  46. package/lib/module/components/pullToRefresh/RefreshControl.js.map +1 -0
  47. package/lib/module/components/pullToRefresh/usePullToRefreshContext.js +13 -0
  48. package/lib/module/components/pullToRefresh/usePullToRefreshContext.js.map +1 -0
  49. package/lib/module/components/pullToRefresh/utils.js +42 -0
  50. package/lib/module/components/pullToRefresh/utils.js.map +1 -0
  51. package/lib/module/components/{CollapsibleFlatList.js → scrollable/CollapsibleFlatList.js} +28 -27
  52. package/lib/module/components/scrollable/CollapsibleFlatList.js.map +1 -0
  53. package/lib/module/components/{CollapsibleScrollView.js → scrollable/CollapsibleScrollView.js} +12 -12
  54. package/lib/module/components/scrollable/CollapsibleScrollView.js.map +1 -0
  55. package/lib/module/{hooks → components/scrollable}/useAnimatedScroll.js +2 -2
  56. package/lib/module/{hooks → components/scrollable}/useAnimatedScroll.js.map +1 -1
  57. package/lib/module/hooks/useInternalCollapsibleContext.js +1 -1
  58. package/lib/module/hooks/useInternalCollapsibleContext.js.map +1 -1
  59. package/lib/module/hooks/useKeyboardShowEvent.js +19 -0
  60. package/lib/module/hooks/useKeyboardShowEvent.js.map +1 -0
  61. package/lib/module/index.js +6 -5
  62. package/lib/module/index.js.map +1 -1
  63. package/lib/module/{hooks/withCollapsibleContext.js → withCollapsibleContext.js} +7 -6
  64. package/lib/module/withCollapsibleContext.js.map +1 -0
  65. package/lib/typescript/components/CollapsibleContainer.d.ts +4 -2
  66. package/lib/typescript/components/{AnimatedTopView.d.ts → header/AnimatedTopView.d.ts} +0 -0
  67. package/lib/typescript/components/{CollapsibleHeaderContainer.d.ts → header/CollapsibleHeaderContainer.d.ts} +0 -0
  68. package/lib/typescript/components/{StickyView.d.ts → header/StickyView.d.ts} +0 -0
  69. package/lib/typescript/components/pullToRefresh/PullToRefreshContainer.d.ts +8 -0
  70. package/lib/typescript/components/pullToRefresh/PullToRefreshProvider.d.ts +6 -0
  71. package/lib/typescript/components/pullToRefresh/RefreshControl.d.ts +9 -0
  72. package/lib/typescript/components/pullToRefresh/usePullToRefreshContext.d.ts +4 -0
  73. package/lib/typescript/components/pullToRefresh/utils.d.ts +20 -0
  74. package/lib/typescript/components/{CollapsibleFlatList.d.ts → scrollable/CollapsibleFlatList.d.ts} +1 -1
  75. package/lib/typescript/components/{CollapsibleScrollView.d.ts → scrollable/CollapsibleScrollView.d.ts} +1 -1
  76. package/lib/typescript/{hooks → components/scrollable}/useAnimatedScroll.d.ts +0 -0
  77. package/lib/typescript/hooks/useInternalCollapsibleContext.d.ts +1 -1
  78. package/lib/typescript/hooks/useKeyboardShowEvent.d.ts +1 -0
  79. package/lib/typescript/index.d.ts +6 -5
  80. package/lib/typescript/types.d.ts +6 -0
  81. package/lib/typescript/{hooks/withCollapsibleContext.d.ts → withCollapsibleContext.d.ts} +0 -0
  82. package/package.json +4 -2
  83. package/src/components/CollapsibleContainer.tsx +64 -11
  84. package/src/components/{AnimatedTopView.tsx → header/AnimatedTopView.tsx} +0 -0
  85. package/src/components/{CollapsibleHeaderContainer.tsx → header/CollapsibleHeaderContainer.tsx} +6 -4
  86. package/src/components/{StickyView.tsx → header/StickyView.tsx} +2 -2
  87. package/src/components/pullToRefresh/PullToRefreshContainer.tsx +65 -0
  88. package/src/components/pullToRefresh/PullToRefreshProvider.tsx +27 -0
  89. package/src/components/pullToRefresh/RefreshControl.tsx +80 -0
  90. package/src/components/pullToRefresh/usePullToRefreshContext.ts +13 -0
  91. package/src/components/pullToRefresh/utils.ts +49 -0
  92. package/src/components/{CollapsibleFlatList.tsx → scrollable/CollapsibleFlatList.tsx} +27 -24
  93. package/src/components/{CollapsibleScrollView.tsx → scrollable/CollapsibleScrollView.tsx} +11 -12
  94. package/src/{hooks → components/scrollable}/useAnimatedScroll.ts +2 -2
  95. package/src/hooks/useInternalCollapsibleContext.ts +1 -1
  96. package/src/hooks/useKeyboardShowEvent.ts +22 -0
  97. package/src/index.tsx +6 -5
  98. package/src/types.ts +7 -0
  99. package/src/{hooks/withCollapsibleContext.tsx → withCollapsibleContext.tsx} +10 -7
  100. package/lib/commonjs/components/CollapsibleFlatList.js.map +0 -1
  101. package/lib/commonjs/components/CollapsibleHeaderContainer.js.map +0 -1
  102. package/lib/commonjs/components/CollapsibleScrollView.js.map +0 -1
  103. package/lib/commonjs/components/StickyView.js.map +0 -1
  104. package/lib/commonjs/hooks/withCollapsibleContext.js.map +0 -1
  105. package/lib/module/components/CollapsibleFlatList.js.map +0 -1
  106. package/lib/module/components/CollapsibleHeaderContainer.js.map +0 -1
  107. package/lib/module/components/CollapsibleScrollView.js.map +0 -1
  108. package/lib/module/components/StickyView.js.map +0 -1
  109. package/lib/module/hooks/withCollapsibleContext.js.map +0 -1
@@ -0,0 +1,4 @@
1
+ /// <reference types="react" />
2
+ import type { PullToRefreshContextType } from '../../types';
3
+ export declare const PullToRefreshContext: import("react").Context<PullToRefreshContextType>;
4
+ export default function usePullToRefreshContext(): PullToRefreshContextType;
@@ -0,0 +1,20 @@
1
+ export declare const springConfig: (velocity: number) => {
2
+ stiffness: number;
3
+ damping: number;
4
+ mass: number;
5
+ overshootClamping: boolean;
6
+ restDisplacementThreshold: number;
7
+ restSpeedThreshold: number;
8
+ velocity: number;
9
+ };
10
+ export declare function clamp(value: number, lowerbound: number, upperbound: number): number;
11
+ /**
12
+ * calculates rubber value
13
+ *
14
+ * @param x distance from the edge
15
+ * @param dim dimension, either width or height
16
+ * @param coeff constant value, UIScrollView uses 0.55
17
+ * @returns rubber = (1.0 – (1.0 / ((x * coeff / dim) + 1.0))) * dim
18
+ */
19
+ export declare const rubberBandClamp: (x: number, dim: number, coeff: number) => number;
20
+ export declare const rubberClamp: (y: number, topBound: number, bottomBound: number, coeff?: number) => number;
@@ -1,5 +1,5 @@
1
1
  import { FlatListProps } from 'react-native';
2
- import type { CollapsibleProps } from '../types';
2
+ import type { CollapsibleProps } from '../../types';
3
3
  declare type Props<Data> = Omit<FlatListProps<Data>, 'scrollEnabled'> & CollapsibleProps;
4
4
  export default function CollapsibleFlatList<Data>({ headerSnappable, ...props }: Props<Data>): JSX.Element;
5
5
  export {};
@@ -1,6 +1,6 @@
1
1
  import { ReactNode } from 'react';
2
2
  import { ScrollViewProps } from 'react-native';
3
- import type { CollapsibleProps } from '../types';
3
+ import type { CollapsibleProps } from '../../types';
4
4
  declare type Props = ScrollViewProps & CollapsibleProps & {
5
5
  children?: ReactNode;
6
6
  };
@@ -1,4 +1,4 @@
1
1
  /// <reference types="react" />
2
2
  import type { CollapsibleContextInternalType } from '../types';
3
3
  export declare const InternalCollapsibleContext: import("react").Context<CollapsibleContextInternalType>;
4
- export declare function useInternalCollapsibleContext(): CollapsibleContextInternalType;
4
+ export default function useInternalCollapsibleContext(): CollapsibleContextInternalType;
@@ -0,0 +1 @@
1
+ export default function useKeyboardShowEvent(callback: () => void): void;
@@ -1,9 +1,10 @@
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
  export { default as CollapsibleContainer } from './components/CollapsibleContainer';
4
- export { default as CollapsibleFlatList } from './components/CollapsibleFlatList';
5
- export { default as CollapsibleScrollView } from './components/CollapsibleScrollView';
6
- export { default as CollapsibleHeaderContainer } from './components/CollapsibleHeaderContainer';
4
+ export { default as CollapsibleFlatList } from './components/scrollable/CollapsibleFlatList';
5
+ export { default as CollapsibleScrollView } from './components/scrollable/CollapsibleScrollView';
6
+ export { default as CollapsibleHeaderContainer } from './components/header/CollapsibleHeaderContainer';
7
+ export { default as StickyView } from './components/header/StickyView';
8
+ export { default as RefreshControl } from './components/pullToRefresh/RefreshControl';
7
9
  export { default as CollapsibleView } from './components/CollapsibleView';
8
- export { default as StickyView } from './components/StickyView';
9
10
  export * from './components/CollapsibleView';
@@ -18,6 +18,7 @@ export declare type LayoutParams = {
18
18
  height: number;
19
19
  };
20
20
  export declare type CollapsibleContextInternalType = {
21
+ scrollViewRef: React.RefObject<any>;
21
22
  containerRef: React.RefObject<View>;
22
23
  contentMinHeight: Animated.SharedValue<number>;
23
24
  firstStickyViewY: Animated.SharedValue<number>;
@@ -32,3 +33,8 @@ export declare type CollapsibleContextInternalType = {
32
33
  export declare type CollapsibleProps = {
33
34
  headerSnappable?: boolean;
34
35
  };
36
+ export declare type PullToRefreshContextType = {
37
+ refreshValue: Animated.SharedValue<number>;
38
+ internalRefreshing: Animated.SharedValue<boolean>;
39
+ internalHeight: Animated.SharedValue<number>;
40
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@r0b0t3d/react-native-collapsible",
3
- "version": "1.1.0",
3
+ "version": "1.2.0-alpha.1",
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,32 +1,85 @@
1
- import React, { useCallback } from 'react';
2
- import { LayoutChangeEvent, StyleSheet, View, ViewProps } from 'react-native';
3
- import { useInternalCollapsibleContext } from '../hooks/useInternalCollapsibleContext';
1
+ import React, { useCallback, useRef } from 'react';
2
+ import {
3
+ KeyboardAvoidingView,
4
+ KeyboardAvoidingViewProps,
5
+ LayoutChangeEvent,
6
+ StyleSheet,
7
+ View,
8
+ ViewProps,
9
+ } from 'react-native';
10
+ import useKeyboardShowEvent from '../hooks/useKeyboardShowEvent';
11
+ import useInternalCollapsibleContext from '../hooks/useInternalCollapsibleContext';
12
+ import useCollapsibleContext from '../hooks/useCollapsibleContext';
4
13
 
5
14
  type Props = Omit<ViewProps, 'ref' | 'onLayout'> & {
6
15
  children: Element;
16
+ keyboardAvoidingViewProps?: KeyboardAvoidingViewProps;
17
+ textInputRefs?: any[];
7
18
  };
8
19
 
9
- export default function CollapsibleContainer({ children, ...props }: Props) {
20
+ export default function CollapsibleContainer({
21
+ children,
22
+ keyboardAvoidingViewProps,
23
+ textInputRefs = [],
24
+ ...props
25
+ }: Props) {
10
26
  const { containerRef, handleContainerHeight } =
11
27
  useInternalCollapsibleContext();
28
+ const { scrollY, scrollTo } = useCollapsibleContext();
29
+
30
+ const containerHeight = useRef(0);
31
+
32
+ useKeyboardShowEvent(() => {
33
+ textInputRefs.some((ref) => {
34
+ const isFocusedFunc = ref.current.isFocused;
35
+ const isFocused =
36
+ isFocusedFunc && typeof isFocusedFunc === 'function'
37
+ ? isFocusedFunc()
38
+ : isFocusedFunc;
39
+ if (isFocused) {
40
+ ref.current.measureLayout(
41
+ // @ts-ignore
42
+ containerRef.current,
43
+ (left: number, top: number, width: number, height: number) => {
44
+ console.log({ left, top, width, height });
45
+ if (top + height - scrollY.value > containerHeight.current) {
46
+ console.log('need to scroll');
47
+ const extraOffset =
48
+ keyboardAvoidingViewProps?.keyboardVerticalOffset ?? 20;
49
+ scrollTo(top + height + extraOffset - containerHeight.current);
50
+ }
51
+ },
52
+ () => {}
53
+ );
54
+ }
55
+ return isFocused;
56
+ });
57
+ });
12
58
 
13
59
  const handleContainerLayout = useCallback(
14
60
  (layout: LayoutChangeEvent) => {
15
61
  const height = layout.nativeEvent.layout.height;
62
+ containerHeight.current = height;
16
63
  handleContainerHeight(height);
17
64
  },
18
65
  [handleContainerHeight]
19
66
  );
20
67
 
21
68
  return (
22
- <View
23
- {...props}
24
- ref={containerRef}
25
- style={[styles.container, props.style]}
26
- onLayout={handleContainerLayout}
69
+ <KeyboardAvoidingView
70
+ style={styles.container}
71
+ behavior="padding"
72
+ {...keyboardAvoidingViewProps}
27
73
  >
28
- {children}
29
- </View>
74
+ <View
75
+ {...props}
76
+ ref={containerRef}
77
+ style={[styles.container, props.style]}
78
+ onLayout={handleContainerLayout}
79
+ >
80
+ {children}
81
+ </View>
82
+ </KeyboardAvoidingView>
30
83
  );
31
84
  }
32
85
 
@@ -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,8 +1,8 @@
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,
@@ -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
+ };
@@ -1,3 +1,4 @@
1
+ /* eslint-disable react-hooks/exhaustive-deps */
1
2
  import React, {
2
3
  useCallback,
3
4
  useEffect,
@@ -5,16 +6,17 @@ import React, {
5
6
  useRef,
6
7
  useState,
7
8
  } from 'react';
8
- import { FlatListProps, FlatList, View, StyleSheet } from 'react-native';
9
+ import { FlatListProps, View, StyleSheet, FlatList } from 'react-native';
9
10
  import Animated, {
10
11
  runOnJS,
11
12
  useAnimatedReaction,
12
13
  } from 'react-native-reanimated';
13
- import AnimatedTopView from './AnimatedTopView';
14
- import useAnimatedScroll from '../hooks/useAnimatedScroll';
15
- import useCollapsibleContext from '../hooks/useCollapsibleContext';
16
- import type { CollapsibleProps } from '../types';
17
- import { useInternalCollapsibleContext } from '../hooks/useInternalCollapsibleContext';
14
+ import AnimatedTopView from '../header/AnimatedTopView';
15
+ import useAnimatedScroll from './useAnimatedScroll';
16
+ import useCollapsibleContext from '../../hooks/useCollapsibleContext';
17
+ import useInternalCollapsibleContext from '../../hooks/useInternalCollapsibleContext';
18
+ import type { CollapsibleProps } from '../../types';
19
+ import PullToRefreshContainer from '../pullToRefresh/PullToRefreshContainer';
18
20
 
19
21
  const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
20
22
 
@@ -25,9 +27,8 @@ export default function CollapsibleFlatList<Data>({
25
27
  headerSnappable = true,
26
28
  ...props
27
29
  }: Props<Data>) {
28
- const scrollView = useRef<FlatList>(null);
29
- const { headerHeight } = useCollapsibleContext();
30
- const { contentMinHeight } = useInternalCollapsibleContext();
30
+ const { headerHeight, scrollY } = useCollapsibleContext();
31
+ const { contentMinHeight, scrollViewRef } = useInternalCollapsibleContext();
31
32
  const mounted = useRef(true);
32
33
  const contentHeight = useRef(0);
33
34
 
@@ -38,7 +39,7 @@ export default function CollapsibleFlatList<Data>({
38
39
  }, []);
39
40
 
40
41
  const scrollTo = useCallback((yValue: number, animated = true) => {
41
- scrollView.current?.scrollToOffset({
42
+ scrollViewRef.current?.scrollToOffset({
42
43
  offset: yValue,
43
44
  animated,
44
45
  });
@@ -96,20 +97,22 @@ export default function CollapsibleFlatList<Data>({
96
97
  );
97
98
 
98
99
  return (
99
- // @ts-ignore
100
- <AnimatedFlatList
101
- ref={scrollView}
102
- bounces={false}
103
- keyboardDismissMode="on-drag"
104
- keyboardShouldPersistTaps="handled"
105
- scrollEventThrottle={16}
106
- {...props}
107
- style={[styles.container, props.style]}
108
- contentContainerStyle={contentContainerStyle}
109
- onScroll={scrollHandler}
110
- ListHeaderComponent={renderListHeader()}
111
- onContentSizeChange={handleContentSizeChange}
112
- />
100
+ <PullToRefreshContainer scrollY={scrollY}>
101
+ {/* @ts-ignore */}
102
+ <AnimatedFlatList
103
+ ref={scrollViewRef}
104
+ bounces={false}
105
+ keyboardDismissMode="on-drag"
106
+ keyboardShouldPersistTaps="handled"
107
+ scrollEventThrottle={1}
108
+ {...props}
109
+ style={[styles.container, props.style]}
110
+ contentContainerStyle={contentContainerStyle}
111
+ onScroll={scrollHandler}
112
+ ListHeaderComponent={renderListHeader()}
113
+ onContentSizeChange={handleContentSizeChange}
114
+ />
115
+ </PullToRefreshContainer>
113
116
  );
114
117
  }
115
118
 
@@ -1,11 +1,12 @@
1
- import AnimatedTopView from './AnimatedTopView';
2
- import useAnimatedScroll from '../hooks/useAnimatedScroll';
3
- import React, { ReactNode, useCallback, useMemo, useRef } from 'react';
1
+ /* eslint-disable react-hooks/exhaustive-deps */
2
+ import AnimatedTopView from '../header/AnimatedTopView';
3
+ import useAnimatedScroll from './useAnimatedScroll';
4
+ import React, { ReactNode, useCallback, useMemo } from 'react';
4
5
  import { ScrollViewProps, StyleSheet } from 'react-native';
5
6
  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';
7
+ import type { CollapsibleProps } from '../../types';
8
+ import useCollapsibleContext from '../../hooks/useCollapsibleContext';
9
+ import useInternalCollapsibleContext from '../../hooks/useInternalCollapsibleContext';
9
10
 
10
11
  type Props = ScrollViewProps &
11
12
  CollapsibleProps & {
@@ -17,13 +18,11 @@ export default function CollapsibleScrollView({
17
18
  children,
18
19
  ...props
19
20
  }: Props) {
20
- const scrollView = useRef<Animated.ScrollView>(null);
21
- const { contentMinHeight } = useInternalCollapsibleContext();
21
+ const { contentMinHeight, scrollViewRef } = useInternalCollapsibleContext();
22
22
  const { headerHeight } = useCollapsibleContext();
23
23
 
24
24
  const scrollTo = useCallback((yValue: number, animated = true) => {
25
- // @ts-ignore
26
- scrollView.current?.scrollTo({ y: yValue, animated });
25
+ scrollViewRef.current?.scrollTo({ y: yValue, animated });
27
26
  }, []);
28
27
 
29
28
  const { scrollHandler } = useAnimatedScroll({
@@ -44,7 +43,7 @@ export default function CollapsibleScrollView({
44
43
 
45
44
  return (
46
45
  <Animated.ScrollView
47
- ref={scrollView}
46
+ ref={scrollViewRef}
48
47
  bounces={false}
49
48
  {...props}
50
49
  style={[styles.container, props.style]}
@@ -52,7 +51,7 @@ export default function CollapsibleScrollView({
52
51
  onScroll={scrollHandler}
53
52
  keyboardDismissMode="on-drag"
54
53
  keyboardShouldPersistTaps="handled"
55
- scrollEventThrottle={16}
54
+ scrollEventThrottle={1}
56
55
  >
57
56
  <Animated.View style={animatedStyle}>
58
57
  <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