This commit is contained in:
Dan Abramov
2023-12-23 01:40:01 +00:00
parent 90b0a69c0e
commit d83a9aceab
4 changed files with 53 additions and 33 deletions
+1 -1
View File
@@ -6,7 +6,6 @@ import {TabBar} from './TabBar'
import {usePalette} from '#/lib/hooks/usePalette' import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {ListMethods} from '../util/List' import {ListMethods} from '../util/List'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
export interface PagerWithHeaderChildParams { export interface PagerWithHeaderChildParams {
headerHeight: number headerHeight: number
@@ -108,6 +107,7 @@ let PagerTabBar = ({
renderHeader?: () => JSX.Element renderHeader?: () => JSX.Element
onCurrentPageSelected?: (index: number) => void onCurrentPageSelected?: (index: number) => void
onSelect?: (index: number) => void onSelect?: (index: number) => void
tabBarAnchor?: JSX.Element
}): React.ReactNode => { }): React.ReactNode => {
const pal = usePalette('default') const pal = usePalette('default')
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
+7 -3
View File
@@ -197,7 +197,7 @@ function PostThreadLoaded({
// wait for loading to finish // wait for loading to finish
if (thread.type === 'post' && !!thread.parent) { if (thread.type === 'post' && !!thread.parent) {
function onMeasure(pageY) { function onMeasure(pageY: number) {
let spinnerHeight = 0 let spinnerHeight = 0
if (isDesktop) { if (isDesktop) {
spinnerHeight = 40 spinnerHeight = 40
@@ -216,8 +216,12 @@ function PostThreadLoaded({
}, },
) )
} else { } else {
const pageY = highlightedPostRef.current.getBoundingClientRect().top // Measure synchronously to avoid a layout jump.
onMeasure(pageY) const domNode = highlightedPostRef.current
if (domNode) {
const pageY = (domNode as any as Element).getBoundingClientRect().top
onMeasure(pageY)
}
} }
needsScrollAdjustment.current = false needsScrollAdjustment.current = false
} }
+1 -1
View File
@@ -86,9 +86,9 @@ function ListImpl<ItemT>(
return ( return (
<FlatList_INTERNAL <FlatList_INTERNAL
{...props} {...props}
scrollIndicatorInsets={{right: 1}}
contentOffset={contentOffset} contentOffset={contentOffset}
refreshControl={refreshControl} refreshControl={refreshControl}
scrollIndicatorInsets={{right: 1}}
onScroll={scrollHandler} onScroll={scrollHandler}
scrollEventThrottle={1} scrollEventThrottle={1}
style={style} style={style}
+44 -28
View File
@@ -7,7 +7,7 @@ import {useScrollHandlers} from '#/lib/ScrollContext'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {batchedUpdates} from '#/lib/batchedUpdates' import {batchedUpdates} from '#/lib/batchedUpdates'
export type ListMethods = FlatList_INTERNAL export type ListMethods = any // TODO: Better types.
export type ListProps<ItemT> = Omit< export type ListProps<ItemT> = Omit<
FlatListProps<ItemT>, FlatListProps<ItemT>,
| 'onScroll' // Use ScrollContext instead. | 'onScroll' // Use ScrollContext instead.
@@ -18,8 +18,9 @@ export type ListProps<ItemT> = Omit<
headerOffset?: number headerOffset?: number
refreshing?: boolean refreshing?: boolean
onRefresh?: () => void onRefresh?: () => void
desktopFixedHeight: any // TODO: Better types.
} }
export type ListRef = React.MutableRefObject<FlatList_INTERNAL | null> export type ListRef = React.MutableRefObject<any | null> // TODO: Better types.
function ListImpl<ItemT>( function ListImpl<ItemT>(
{ {
@@ -27,7 +28,7 @@ function ListImpl<ItemT>(
ListFooterComponent, ListFooterComponent,
contentContainerStyle, contentContainerStyle,
data, data,
desktopFixedHeight, // TODO desktopFixedHeight,
headerOffset, headerOffset,
keyExtractor, keyExtractor,
refreshing: _unsupportedRefreshing, refreshing: _unsupportedRefreshing,
@@ -53,7 +54,7 @@ function ListImpl<ItemT>(
) )
} }
let header: ListProps<ItemT>['ListHeaderComponent'] = null let header: JSX.Element | null = null
if (ListHeaderComponent != null) { if (ListHeaderComponent != null) {
if (typeof ListHeaderComponent === 'object') { if (typeof ListHeaderComponent === 'object') {
header = ListHeaderComponent header = ListHeaderComponent
@@ -63,7 +64,7 @@ function ListImpl<ItemT>(
} }
} }
let footer: ListProps<ItemT>['ListHeaderComponent'] = null let footer: JSX.Element | null = null
if (ListFooterComponent != null) { if (ListFooterComponent != null) {
if (typeof ListFooterComponent === 'object') { if (typeof ListFooterComponent === 'object') {
footer = ListFooterComponent footer = ListFooterComponent
@@ -100,11 +101,15 @@ function ListImpl<ItemT>(
behavior: animated ? 'smooth' : 'instant', behavior: animated ? 'smooth' : 'instant',
}) })
}, },
} as any), // TODO: Types. } as any), // TODO: Better types.
[], [],
) )
const [isVisible, setIsVisible] = React.useState(false) // --- onContentSizeChange ---
const containerRef = useRef(null)
useResizeObserver(containerRef, onContentSizeChange)
// --- onScroll ---
const handleScroll = useNonReactiveCallback(() => { const handleScroll = useNonReactiveCallback(() => {
contextScrollHandlers.onScroll?.( contextScrollHandlers.onScroll?.(
{ {
@@ -112,21 +117,22 @@ function ListImpl<ItemT>(
x: window.scrollX, x: window.scrollX,
y: window.scrollY, y: window.scrollY,
}, },
// TODO } as any, // TODO: Better types.
}, null as any,
{
/* TODO */
},
) )
}) })
const [isParentTreeVisible, setIsParentTreeVisible] = React.useState(false)
React.useEffect(() => { React.useEffect(() => {
if (!isVisible) { if (!isParentTreeVisible) {
// Prevents hidden tabs from firing scroll events.
// Only one list is expected to be firing these at a time.
return return
} }
window.addEventListener('scroll', handleScroll) window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll) return () => window.removeEventListener('scroll', handleScroll)
}, [isVisible, handleScroll]) }, [isParentTreeVisible, handleScroll])
// --- onScrolledDownChange ---
const isScrolledDown = useRef(false) const isScrolledDown = useRef(false)
function handleAboveTheFoldVisibleChange(isAboveTheFold: boolean) { function handleAboveTheFoldVisibleChange(isAboveTheFold: boolean) {
const didScrollDown = !isAboveTheFold const didScrollDown = !isAboveTheFold
@@ -138,13 +144,21 @@ function ListImpl<ItemT>(
} }
} }
const containerRef = useRef(null) // --- onEndReached ---
useResizeObserver(containerRef, onContentSizeChange) const onTailVisibilityChange = useNonReactiveCallback(
(isTailVisible: boolean) => {
if (isTailVisible) {
onEndReached?.({
distanceFromEnd: onEndReachedThreshold || 0,
})
}
},
)
return ( return (
<View {...props} style={style} ref={nativeRef}> <View {...props} style={style} ref={nativeRef}>
<Visibility <Visibility
onVisibleChange={setIsVisible} onVisibleChange={setIsParentTreeVisible}
style={styles.parentTreeVisibilityDetector} style={styles.parentTreeVisibilityDetector}
/> />
<View <View
@@ -172,13 +186,7 @@ function ListImpl<ItemT>(
{onEndReached && ( {onEndReached && (
<Visibility <Visibility
topMargin={(onEndReachedThreshold ?? 0) * 100 + '%'} topMargin={(onEndReachedThreshold ?? 0) * 100 + '%'}
onVisibleChange={isVisible => { onVisibleChange={onTailVisibilityChange}
if (isVisible) {
onEndReached?.({
distanceFromEnd: onEndReachedThreshold || 0,
})
}
}}
/> />
)} )}
{footer} {footer}
@@ -187,17 +195,25 @@ function ListImpl<ItemT>(
) )
} }
function useResizeObserver(ref, onResize) { function useResizeObserver(
ref: React.RefObject<Element>,
onResize: undefined | ((w: number, h: number) => void),
) {
const handleResize = useNonReactiveCallback(onResize ?? (() => {})) const handleResize = useNonReactiveCallback(onResize ?? (() => {}))
const isActive = !!onResize const isActive = !!onResize
React.useEffect(() => { React.useEffect(() => {
if (!isActive) { if (!isActive) {
return return
} }
const resizeObserver = new ResizeObserver(() => { const resizeObserver = new ResizeObserver(entries => {
handleResize() batchedUpdates(() => {
for (let entry of entries) {
const rect = entry.contentRect
handleResize(rect.width, rect.height)
}
})
}) })
const node = ref.current const node = ref.current!
resizeObserver.observe(node) resizeObserver.observe(node)
return () => { return () => {
resizeObserver.unobserve(node) resizeObserver.unobserve(node)