1 | {"version":3,"sources":["useScrollToTop.tsx"],"names":["getScrollableNode","ref","current","getScrollResponder","getNode","useScrollToTop","navigation","route","React","useEffect","getState","type","getParent","unsubscribe","addListener","e","isFocused","isFirst","routes","key","requestAnimationFrame","scrollable","defaultPrevented","scrollToTop","scrollTo","y","animated","scrollToOffset","offset","scrollResponderScrollTo"],"mappings":";;;;;;;AAAA;;AACA;;;;;;AAeA,SAASA,iBAAT,CAA2BC,GAA3B,EAAoE;AAClE,MAAIA,GAAG,CAACC,OAAJ,IAAe,IAAnB,EAAyB;AACvB,WAAO,IAAP;AACD;;AAED,MACE,iBAAiBD,GAAG,CAACC,OAArB,IACA,cAAcD,GAAG,CAACC,OADlB,IAEA,oBAAoBD,GAAG,CAACC,OAFxB,IAGA,6BAA6BD,GAAG,CAACC,OAJnC,EAKE;AACA;AACA,WAAOD,GAAG,CAACC,OAAX;AACD,GARD,MAQO,IAAI,wBAAwBD,GAAG,CAACC,OAAhC,EAAyC;AAC9C;AACA;AACA,WAAOD,GAAG,CAACC,OAAJ,CAAYC,kBAAZ,EAAP;AACD,GAJM,MAIA,IAAI,aAAaF,GAAG,CAACC,OAArB,EAA8B;AACnC;AACA;AACA;AACA;AACA,WAAOD,GAAG,CAACC,OAAJ,CAAYE,OAAZ,EAAP;AACD,GANM,MAMA;AACL,WAAOH,GAAG,CAACC,OAAX;AACD;AACF;;AAEc,SAASG,cAAT,CACbJ,GADa,EAEb;AACA,QAAMK,UAAU,GAAG,0BAAnB;AACA,QAAMC,KAAK,GAAG,qBAAd;AAEAC,EAAAA,KAAK,CAACC,SAAN,CAAgB,MAAM;AACpB,QAAIP,OAAO,GAAGI,UAAd,CADoB,CAGpB;AACA;;AACA,WAAOJ,OAAO,IAAIA,OAAO,CAACQ,QAAR,GAAmBC,IAAnB,KAA4B,KAA9C,EAAqD;AACnDT,MAAAA,OAAO,GAAGA,OAAO,CAACU,SAAR,EAAV;AACD;;AAED,QAAI,CAACV,OAAL,EAAc;AACZ;AACD;;AAED,UAAMW,WAAW,GAAGX,OAAO,CAACY,WAAR,EAClB;AACA;AACA;AACA,cAJkB,EAKjBC,CAAD,IAAmC;AACjC;AACA,YAAMC,SAAS,GAAGV,UAAU,CAACU,SAAX,EAAlB,CAFiC,CAIjC;AACA;;AACA,YAAMC,OAAO,GACXX,UAAU,KAAKJ,OAAf,IACAI,UAAU,CAACI,QAAX,GAAsBQ,MAAtB,CAA6B,CAA7B,EAAgCC,GAAhC,KAAwCZ,KAAK,CAACY,GAFhD,CANiC,CAUjC;AACA;;AACAC,MAAAA,qBAAqB,CAAC,MAAM;AAC1B,cAAMC,UAAU,GAAGrB,iBAAiB,CAACC,GAAD,CAApC;;AAEA,YAAIe,SAAS,IAAIC,OAAb,IAAwBI,UAAxB,IAAsC,CAACN,CAAC,CAACO,gBAA7C,EAA+D;AAC7D,cAAI,iBAAiBD,UAArB,EAAiC;AAC/BA,YAAAA,UAAU,CAACE,WAAX;AACD,WAFD,MAEO,IAAI,cAAcF,UAAlB,EAA8B;AACnCA,YAAAA,UAAU,CAACG,QAAX,CAAoB;AAAEC,cAAAA,CAAC,EAAE,CAAL;AAAQC,cAAAA,QAAQ,EAAE;AAAlB,aAApB;AACD,WAFM,MAEA,IAAI,oBAAoBL,UAAxB,EAAoC;AACzCA,YAAAA,UAAU,CAACM,cAAX,CAA0B;AAAEC,cAAAA,MAAM,EAAE,CAAV;AAAaF,cAAAA,QAAQ,EAAE;AAAvB,aAA1B;AACD,WAFM,MAEA,IAAI,6BAA6BL,UAAjC,EAA6C;AAClDA,YAAAA,UAAU,CAACQ,uBAAX,CAAmC;AAAEJ,cAAAA,CAAC,EAAE,CAAL;AAAQC,cAAAA,QAAQ,EAAE;AAAlB,aAAnC;AACD;AACF;AACF,OAdoB,CAArB;AAeD,KAhCiB,CAApB;AAmCA,WAAOb,WAAP;AACD,GAjDD,EAiDG,CAACP,UAAD,EAAaL,GAAb,EAAkBM,KAAK,CAACY,GAAxB,CAjDH;AAkDD","sourcesContent":["import { EventArg, useNavigation, useRoute } from '@react-navigation/core';\nimport * as React from 'react';\n\ntype ScrollOptions = { y?: number; animated?: boolean };\n\ntype ScrollableView =\n | { scrollToTop(): void }\n | { scrollTo(options: ScrollOptions): void }\n | { scrollToOffset(options: { offset?: number; animated?: boolean }): void }\n | { scrollResponderScrollTo(options: ScrollOptions): void };\n\ntype ScrollableWrapper =\n | { getScrollResponder(): React.ReactNode }\n | { getNode(): ScrollableView }\n | ScrollableView;\n\nfunction getScrollableNode(ref: React.RefObject<ScrollableWrapper>) {\n if (ref.current == null) {\n return null;\n }\n\n if (\n 'scrollToTop' in ref.current ||\n 'scrollTo' in ref.current ||\n 'scrollToOffset' in ref.current ||\n 'scrollResponderScrollTo' in ref.current\n ) {\n // This is already a scrollable node.\n return ref.current;\n } else if ('getScrollResponder' in ref.current) {\n // If the view is a wrapper like FlatList, SectionList etc.\n // We need to use `getScrollResponder` to get access to the scroll responder\n return ref.current.getScrollResponder();\n } else if ('getNode' in ref.current) {\n // When a `ScrollView` is wraped in `Animated.createAnimatedComponent`\n // we need to use `getNode` to get the ref to the actual scrollview.\n // Note that `getNode` is deprecated in newer versions of react-native\n // this is why we check if we already have a scrollable node above.\n return ref.current.getNode();\n } else {\n return ref.current;\n }\n}\n\nexport default function useScrollToTop(\n ref: React.RefObject<ScrollableWrapper>\n) {\n const navigation = useNavigation();\n const route = useRoute();\n\n React.useEffect(() => {\n let current = navigation;\n\n // The screen might be inside another navigator such as stack nested in tabs\n // We need to find the closest tab navigator and add the listener there\n while (current && current.getState().type !== 'tab') {\n current = current.getParent();\n }\n\n if (!current) {\n return;\n }\n\n const unsubscribe = current.addListener(\n // We don't wanna import tab types here to avoid extra deps\n // in addition, there are multiple tab implementations\n // @ts-expect-error\n 'tabPress',\n (e: EventArg<'tabPress', true>) => {\n // We should scroll to top only when the screen is focused\n const isFocused = navigation.isFocused();\n\n // In a nested stack navigator, tab press resets the stack to first screen\n // So we should scroll to top only when we are on first screen\n const isFirst =\n navigation === current ||\n navigation.getState().routes[0].key === route.key;\n\n // Run the operation in the next frame so we're sure all listeners have been run\n // This is necessary to know if preventDefault() has been called\n requestAnimationFrame(() => {\n const scrollable = getScrollableNode(ref) as ScrollableWrapper;\n\n if (isFocused && isFirst && scrollable && !e.defaultPrevented) {\n if ('scrollToTop' in scrollable) {\n scrollable.scrollToTop();\n } else if ('scrollTo' in scrollable) {\n scrollable.scrollTo({ y: 0, animated: true });\n } else if ('scrollToOffset' in scrollable) {\n scrollable.scrollToOffset({ offset: 0, animated: true });\n } else if ('scrollResponderScrollTo' in scrollable) {\n scrollable.scrollResponderScrollTo({ y: 0, animated: true });\n }\n }\n });\n }\n );\n\n return unsubscribe;\n }, [navigation, ref, route.key]);\n}\n"]} |