1 | import { EventArg, useNavigation, useRoute } from '@react-navigation/core';
|
2 | import * as React from 'react';
|
3 |
|
4 | type ScrollOptions = { y?: number; animated?: boolean };
|
5 |
|
6 | type ScrollableView =
|
7 | | { scrollToTop(): void }
|
8 | | { scrollTo(options: ScrollOptions): void }
|
9 | | { scrollToOffset(options: { offset?: number; animated?: boolean }): void }
|
10 | | { scrollResponderScrollTo(options: ScrollOptions): void };
|
11 |
|
12 | type ScrollableWrapper =
|
13 | | { getScrollResponder(): React.ReactNode }
|
14 | | { getNode(): ScrollableView }
|
15 | | ScrollableView;
|
16 |
|
17 | function getScrollableNode(ref: React.RefObject<ScrollableWrapper>) {
|
18 | if (ref.current == null) {
|
19 | return null;
|
20 | }
|
21 |
|
22 | if (
|
23 | 'scrollToTop' in ref.current ||
|
24 | 'scrollTo' in ref.current ||
|
25 | 'scrollToOffset' in ref.current ||
|
26 | 'scrollResponderScrollTo' in ref.current
|
27 | ) {
|
28 |
|
29 | return ref.current;
|
30 | } else if ('getScrollResponder' in ref.current) {
|
31 |
|
32 |
|
33 | return ref.current.getScrollResponder();
|
34 | } else if ('getNode' in ref.current) {
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | return ref.current.getNode();
|
40 | } else {
|
41 | return ref.current;
|
42 | }
|
43 | }
|
44 |
|
45 | export default function useScrollToTop(
|
46 | ref: React.RefObject<ScrollableWrapper>
|
47 | ) {
|
48 | const navigation = useNavigation();
|
49 | const route = useRoute();
|
50 |
|
51 | React.useEffect(() => {
|
52 | let current = navigation;
|
53 |
|
54 |
|
55 |
|
56 | while (current && current.getState().type !== 'tab') {
|
57 | current = current.getParent();
|
58 | }
|
59 |
|
60 | if (!current) {
|
61 | return;
|
62 | }
|
63 |
|
64 | const unsubscribe = current.addListener(
|
65 |
|
66 |
|
67 |
|
68 | 'tabPress',
|
69 | (e: EventArg<'tabPress', true>) => {
|
70 |
|
71 | const isFocused = navigation.isFocused();
|
72 |
|
73 |
|
74 |
|
75 | const isFirst =
|
76 | navigation === current ||
|
77 | navigation.getState().routes[0].key === route.key;
|
78 |
|
79 |
|
80 |
|
81 | requestAnimationFrame(() => {
|
82 | const scrollable = getScrollableNode(ref) as ScrollableWrapper;
|
83 |
|
84 | if (isFocused && isFirst && scrollable && !e.defaultPrevented) {
|
85 | if ('scrollToTop' in scrollable) {
|
86 | scrollable.scrollToTop();
|
87 | } else if ('scrollTo' in scrollable) {
|
88 | scrollable.scrollTo({ y: 0, animated: true });
|
89 | } else if ('scrollToOffset' in scrollable) {
|
90 | scrollable.scrollToOffset({ offset: 0, animated: true });
|
91 | } else if ('scrollResponderScrollTo' in scrollable) {
|
92 | scrollable.scrollResponderScrollTo({ y: 0, animated: true });
|
93 | }
|
94 | }
|
95 | });
|
96 | }
|
97 | );
|
98 |
|
99 | return unsubscribe;
|
100 | }, [navigation, ref, route.key]);
|
101 | }
|