Fix `IntersectionObserver` `rootMargin` in web `List` implementation, add `onStartReached` (#3866)
* add `onStartReached` to web list * fix `rootMargin`zio/stable
parent
6b615f3720
commit
594b40c3ae
|
@ -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)} />
|
||||||
|
|
Loading…
Reference in New Issue