about summary refs log tree commit diff
path: root/src/view/com/pager/Pager.web.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/pager/Pager.web.tsx')
-rw-r--r--src/view/com/pager/Pager.web.tsx51
1 files changed, 35 insertions, 16 deletions
diff --git a/src/view/com/pager/Pager.web.tsx b/src/view/com/pager/Pager.web.tsx
index 3b5e9164a..dde799e42 100644
--- a/src/view/com/pager/Pager.web.tsx
+++ b/src/view/com/pager/Pager.web.tsx
@@ -1,10 +1,12 @@
 import React from 'react'
+import {flushSync} from 'react-dom'
 import {View} from 'react-native'
 import {s} from 'lib/styles'
 
 export interface RenderTabBarFnProps {
   selectedPage: number
   onSelect?: (index: number) => void
+  tabBarAnchor?: JSX.Element
 }
 export type RenderTabBarFn = (props: RenderTabBarFnProps) => JSX.Element
 
@@ -27,6 +29,8 @@ export const Pager = React.forwardRef(function PagerImpl(
   ref,
 ) {
   const [selectedPage, setSelectedPage] = React.useState(initialPage)
+  const scrollYs = React.useRef<Array<number | null>>([])
+  const anchorRef = React.useRef(null)
 
   React.useImperativeHandle(ref, () => ({
     setPage: (index: number) => setSelectedPage(index),
@@ -34,11 +38,36 @@ export const Pager = React.forwardRef(function PagerImpl(
 
   const onTabBarSelect = React.useCallback(
     (index: number) => {
-      setSelectedPage(index)
-      onPageSelected?.(index)
-      onPageSelecting?.(index)
+      const scrollY = window.scrollY
+      // We want to determine if the tabbar is already "sticking" at the top (in which
+      // case we should preserve and restore scroll), or if it is somewhere below in the
+      // viewport (in which case a scroll jump would be jarring). We determine this by
+      // measuring where the "anchor" element is (which we place just above the tabbar).
+      let anchorTop = anchorRef.current
+        ? (anchorRef.current as Element).getBoundingClientRect().top
+        : -scrollY // If there's no anchor, treat the top of the page as one.
+      const isSticking = anchorTop <= 5 // This would be 0 if browser scrollTo() was reliable.
+
+      if (isSticking) {
+        scrollYs.current[selectedPage] = window.scrollY
+      } else {
+        scrollYs.current[selectedPage] = null
+      }
+      flushSync(() => {
+        setSelectedPage(index)
+        onPageSelected?.(index)
+        onPageSelecting?.(index)
+      })
+      if (isSticking) {
+        const restoredScrollY = scrollYs.current[index]
+        if (restoredScrollY != null) {
+          window.scrollTo(0, restoredScrollY)
+        } else {
+          window.scrollTo(0, scrollY + anchorTop)
+        }
+      }
     },
-    [setSelectedPage, onPageSelected, onPageSelecting],
+    [selectedPage, setSelectedPage, onPageSelected, onPageSelecting],
   )
 
   return (
@@ -46,21 +75,11 @@ export const Pager = React.forwardRef(function PagerImpl(
       {tabBarPosition === 'top' &&
         renderTabBar({
           selectedPage,
+          tabBarAnchor: <View ref={anchorRef} />,
           onSelect: onTabBarSelect,
         })}
       {React.Children.map(children, (child, i) => (
-        <View
-          style={
-            selectedPage === i
-              ? s.flex1
-              : {
-                  position: 'absolute',
-                  pointerEvents: 'none',
-                  // @ts-ignore web-only
-                  visibility: 'hidden',
-                }
-          }
-          key={`page-${i}`}>
+        <View style={selectedPage === i ? s.flex1 : s.hidden} key={`page-${i}`}>
           {child}
         </View>
       ))}