Fix `IntersectionObserver` `rootMargin` in web `List` implementation, add `onStartReached` (#3866)

* add `onStartReached` to web list

* fix `rootMargin`
zio/stable
Hailey 2024-05-05 04:24:01 -07:00 committed by GitHub
parent 6b615f3720
commit 594b40c3ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 30 additions and 8 deletions

View File

@ -1,11 +1,12 @@
import React, {isValidElement, memo, useRef, startTransition} from 'react' import React, {isValidElement, memo, startTransition, useRef} from 'react'
import {FlatListProps, StyleSheet, View, ViewProps} from 'react-native' import {FlatListProps, StyleSheet, View, ViewProps} from 'react-native'
import {addStyle} from 'lib/styles'
import {batchedUpdates} from '#/lib/batchedUpdates'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {useScrollHandlers} from '#/lib/ScrollContext'
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 {useScrollHandlers} from '#/lib/ScrollContext' import {addStyle} from 'lib/styles'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {batchedUpdates} from '#/lib/batchedUpdates'
export type ListMethods = any // TODO: Better types. export type ListMethods = any // TODO: Better types.
export type ListProps<ItemT> = Omit< export type ListProps<ItemT> = Omit<
@ -32,6 +33,8 @@ function ListImpl<ItemT>(
headerOffset, headerOffset,
keyExtractor, keyExtractor,
refreshing: _unsupportedRefreshing, refreshing: _unsupportedRefreshing,
onStartReached,
onStartReachedThreshold = 0,
onEndReached, onEndReached,
onEndReachedThreshold = 0, onEndReachedThreshold = 0,
onRefresh: _unsupportedOnRefresh, onRefresh: _unsupportedOnRefresh,
@ -148,6 +151,17 @@ function ListImpl<ItemT>(
} }
} }
// --- onStartReached ---
const onHeadVisibilityChange = useNonReactiveCallback(
(isHeadVisible: boolean) => {
if (isHeadVisible) {
onStartReached?.({
distanceFromStart: onStartReachedThreshold || 0,
})
}
},
)
// --- onEndReached --- // --- onEndReached ---
const onTailVisibilityChange = useNonReactiveCallback( const onTailVisibilityChange = useNonReactiveCallback(
(isTailVisible: boolean) => { (isTailVisible: boolean) => {
@ -181,6 +195,12 @@ function ListImpl<ItemT>(
onVisibleChange={handleAboveTheFoldVisibleChange} onVisibleChange={handleAboveTheFoldVisibleChange}
style={[styles.aboveTheFoldDetector, {height: headerOffset}]} style={[styles.aboveTheFoldDetector, {height: headerOffset}]}
/> />
{onStartReached && (
<Visibility
onVisibleChange={onHeadVisibilityChange}
topMargin={(onStartReachedThreshold ?? 0) * 100 + '%'}
/>
)}
{header} {header}
{(data as Array<ItemT>).map((item, index) => ( {(data as Array<ItemT>).map((item, index) => (
<Row<ItemT> <Row<ItemT>
@ -193,8 +213,8 @@ function ListImpl<ItemT>(
))} ))}
{onEndReached && ( {onEndReached && (
<Visibility <Visibility
topMargin={(onEndReachedThreshold ?? 0) * 100 + '%'}
onVisibleChange={onTailVisibilityChange} onVisibleChange={onTailVisibilityChange}
bottomMargin={(onEndReachedThreshold ?? 0) * 100 + '%'}
/> />
)} )}
{footer} {footer}
@ -256,10 +276,12 @@ Row = React.memo(Row)
let Visibility = ({ let Visibility = ({
topMargin = '0px', topMargin = '0px',
bottomMargin = '0px',
onVisibleChange, onVisibleChange,
style, style,
}: { }: {
topMargin?: string topMargin?: string
bottomMargin?: string
onVisibleChange: (isVisible: boolean) => void onVisibleChange: (isVisible: boolean) => void
style?: ViewProps['style'] style?: ViewProps['style']
}): React.ReactNode => { }): React.ReactNode => {
@ -281,14 +303,14 @@ let Visibility = ({
React.useEffect(() => { React.useEffect(() => {
const observer = new IntersectionObserver(handleIntersection, { const observer = new IntersectionObserver(handleIntersection, {
rootMargin: `${topMargin} 0px 0px 0px`, rootMargin: `${topMargin} 0px ${bottomMargin} 0px`,
}) })
const tail: Element | null = tailRef.current! const tail: Element | null = tailRef.current!
observer.observe(tail) observer.observe(tail)
return () => { return () => {
observer.unobserve(tail) observer.unobserve(tail)
} }
}, [handleIntersection, topMargin]) }, [bottomMargin, handleIntersection, topMargin])
return ( return (
<View ref={tailRef} style={addStyle(styles.visibilityDetector, style)} /> <View ref={tailRef} style={addStyle(styles.visibilityDetector, style)} />