Scroll restore for tabs
This commit is contained in:
@@ -20,6 +20,7 @@ export const Pager = React.forwardRef(function PagerImpl(
|
|||||||
children,
|
children,
|
||||||
tabBarPosition = 'top',
|
tabBarPosition = 'top',
|
||||||
initialPage = 0,
|
initialPage = 0,
|
||||||
|
headerOnlyHeight = 0,
|
||||||
renderTabBar,
|
renderTabBar,
|
||||||
onPageSelected,
|
onPageSelected,
|
||||||
onPageSelecting,
|
onPageSelecting,
|
||||||
@@ -27,6 +28,7 @@ export const Pager = React.forwardRef(function PagerImpl(
|
|||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const [selectedPage, setSelectedPage] = React.useState(initialPage)
|
const [selectedPage, setSelectedPage] = React.useState(initialPage)
|
||||||
|
const scrollYs = React.useRef([])
|
||||||
|
|
||||||
React.useImperativeHandle(ref, () => ({
|
React.useImperativeHandle(ref, () => ({
|
||||||
setPage: (index: number) => setSelectedPage(index),
|
setPage: (index: number) => setSelectedPage(index),
|
||||||
@@ -34,11 +36,25 @@ export const Pager = React.forwardRef(function PagerImpl(
|
|||||||
|
|
||||||
const onTabBarSelect = React.useCallback(
|
const onTabBarSelect = React.useCallback(
|
||||||
(index: number) => {
|
(index: number) => {
|
||||||
|
const scrollY = window.scrollY
|
||||||
|
scrollYs.current[selectedPage] = scrollY
|
||||||
setSelectedPage(index)
|
setSelectedPage(index)
|
||||||
onPageSelected?.(index)
|
onPageSelected?.(index)
|
||||||
onPageSelecting?.(index)
|
onPageSelecting?.(index)
|
||||||
|
if (scrollY >= headerOnlyHeight) {
|
||||||
|
window.scrollTo(
|
||||||
|
0,
|
||||||
|
Math.max(headerOnlyHeight, scrollYs.current[index] ?? 0),
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[setSelectedPage, onPageSelected, onPageSelecting],
|
[
|
||||||
|
selectedPage,
|
||||||
|
setSelectedPage,
|
||||||
|
onPageSelected,
|
||||||
|
onPageSelecting,
|
||||||
|
headerOnlyHeight,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -39,6 +39,17 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
|||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const [currentPage, setCurrentPage] = React.useState(0)
|
const [currentPage, setCurrentPage] = React.useState(0)
|
||||||
|
const [headerOnlyHeight, setHeaderOnlyHeight] = React.useState(0)
|
||||||
|
|
||||||
|
const onHeaderOnlyLayout = React.useCallback(
|
||||||
|
(evt: LayoutChangeEvent) => {
|
||||||
|
const height = evt.nativeEvent.layout.height
|
||||||
|
if (height > 0) {
|
||||||
|
setHeaderOnlyHeight(Math.round(height))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setHeaderOnlyHeight],
|
||||||
|
)
|
||||||
|
|
||||||
const renderTabBar = React.useCallback(
|
const renderTabBar = React.useCallback(
|
||||||
(props: RenderTabBarFnProps) => {
|
(props: RenderTabBarFnProps) => {
|
||||||
@@ -48,12 +59,20 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
|||||||
renderHeader={renderHeader}
|
renderHeader={renderHeader}
|
||||||
currentPage={currentPage}
|
currentPage={currentPage}
|
||||||
onCurrentPageSelected={onCurrentPageSelected}
|
onCurrentPageSelected={onCurrentPageSelected}
|
||||||
|
onHeaderOnlyLayout={onHeaderOnlyLayout}
|
||||||
onSelect={props.onSelect}
|
onSelect={props.onSelect}
|
||||||
testID={testID}
|
testID={testID}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[items, renderHeader, currentPage, onCurrentPageSelected, testID],
|
[
|
||||||
|
items,
|
||||||
|
renderHeader,
|
||||||
|
currentPage,
|
||||||
|
onCurrentPageSelected,
|
||||||
|
onHeaderOnlyLayout,
|
||||||
|
testID,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPageSelectedInner = React.useCallback(
|
const onPageSelectedInner = React.useCallback(
|
||||||
@@ -76,6 +95,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
|
|||||||
onPageSelected={onPageSelectedInner}
|
onPageSelected={onPageSelectedInner}
|
||||||
onPageSelecting={onPageSelecting}
|
onPageSelecting={onPageSelecting}
|
||||||
renderTabBar={renderTabBar}
|
renderTabBar={renderTabBar}
|
||||||
|
headerOnlyHeight={headerOnlyHeight}
|
||||||
tabBarPosition="top">
|
tabBarPosition="top">
|
||||||
{toArray(children)
|
{toArray(children)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
@@ -97,6 +117,7 @@ let PagerTabBar = ({
|
|||||||
testID,
|
testID,
|
||||||
renderHeader,
|
renderHeader,
|
||||||
onCurrentPageSelected,
|
onCurrentPageSelected,
|
||||||
|
onHeaderOnlyLayout,
|
||||||
onSelect,
|
onSelect,
|
||||||
}: {
|
}: {
|
||||||
currentPage: number
|
currentPage: number
|
||||||
@@ -110,7 +131,9 @@ let PagerTabBar = ({
|
|||||||
const {isMobile} = useWebMediaQueries()
|
const {isMobile} = useWebMediaQueries()
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View style={[!isMobile && styles.headerContainerDesktop, pal.border]}>
|
<View
|
||||||
|
style={[!isMobile && styles.headerContainerDesktop, pal.border]}
|
||||||
|
onLayout={onHeaderOnlyLayout}>
|
||||||
{renderHeader?.()}
|
{renderHeader?.()}
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, {useCallback} from 'react'
|
import React, {useCallback, useEffect} from 'react'
|
||||||
|
import EventEmitter from 'eventemitter3'
|
||||||
import {ScrollProvider} from '#/lib/ScrollContext'
|
import {ScrollProvider} from '#/lib/ScrollContext'
|
||||||
import {NativeScrollEvent} from 'react-native'
|
import {NativeScrollEvent} from 'react-native'
|
||||||
import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell'
|
import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell'
|
||||||
@@ -18,6 +19,15 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
|||||||
const startDragOffset = useSharedValue<number | null>(null)
|
const startDragOffset = useSharedValue<number | null>(null)
|
||||||
const startMode = useSharedValue<number | null>(null)
|
const startMode = useSharedValue<number | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isWeb) {
|
||||||
|
return listenToForcedWindowScroll(() => {
|
||||||
|
startDragOffset.value = null
|
||||||
|
startMode.value = null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const onBeginDrag = useCallback(
|
const onBeginDrag = useCallback(
|
||||||
(e: NativeScrollEvent) => {
|
(e: NativeScrollEvent) => {
|
||||||
'worklet'
|
'worklet'
|
||||||
@@ -95,3 +105,26 @@ export function MainScrollProvider({children}: {children: React.ReactNode}) {
|
|||||||
</ScrollProvider>
|
</ScrollProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const emitter = new EventEmitter()
|
||||||
|
|
||||||
|
if (isWeb) {
|
||||||
|
const originalScroll = window.scroll
|
||||||
|
window.scroll = function () {
|
||||||
|
emitter.emit('forced-scroll')
|
||||||
|
return originalScroll.apply(this, arguments as any)
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalScrollTo = window.scrollTo
|
||||||
|
window.scrollTo = function () {
|
||||||
|
emitter.emit('forced-scroll')
|
||||||
|
return originalScrollTo.apply(this, arguments as any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listenToForcedWindowScroll(listener: () => void) {
|
||||||
|
emitter.addListener('forced-scroll', listener)
|
||||||
|
return () => {
|
||||||
|
emitter.removeListener('forced-scroll', listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user