Fix <List> types (#6756)
* fix List and ScrollView types * add comment * rm omitting ref
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react'
|
||||
import {FlatList, ScrollView, StyleSheet, View} from 'react-native'
|
||||
import {ScrollView, StyleSheet, View} from 'react-native'
|
||||
import {useAnimatedRef} from 'react-native-reanimated'
|
||||
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
@@ -11,7 +11,7 @@ import {TabBar} from './TabBar'
|
||||
export interface PagerWithHeaderChildParams {
|
||||
headerHeight: number
|
||||
isFocused: boolean
|
||||
scrollElRef: React.MutableRefObject<FlatList<any> | ScrollView | null>
|
||||
scrollElRef: React.MutableRefObject<ListMethods | ScrollView | null>
|
||||
}
|
||||
|
||||
export interface PagerWithHeaderProps {
|
||||
|
||||
+129
-117
@@ -1,6 +1,10 @@
|
||||
import React, {memo} from 'react'
|
||||
import {FlatListProps, RefreshControl, ViewToken} from 'react-native'
|
||||
import {runOnJS, useSharedValue} from 'react-native-reanimated'
|
||||
import {RefreshControl, ViewToken} from 'react-native'
|
||||
import {
|
||||
FlatListPropsWithLayout,
|
||||
runOnJS,
|
||||
useSharedValue,
|
||||
} from 'react-native-reanimated'
|
||||
import {updateActiveVideoViewAsync} from '@haileyok/bluesky-video'
|
||||
|
||||
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
|
||||
@@ -13,8 +17,8 @@ import {useTheme} from '#/alf'
|
||||
import {FlatList_INTERNAL} from './Views'
|
||||
|
||||
export type ListMethods = FlatList_INTERNAL
|
||||
export type ListProps<ItemT> = Omit<
|
||||
FlatListProps<ItemT>,
|
||||
export type ListProps<ItemT = any> = Omit<
|
||||
FlatListPropsWithLayout<ItemT>,
|
||||
| 'onMomentumScrollBegin' // Use ScrollContext instead.
|
||||
| 'onMomentumScrollEnd' // Use ScrollContext instead.
|
||||
| 'onScroll' // Use ScrollContext instead.
|
||||
@@ -22,6 +26,7 @@ export type ListProps<ItemT> = Omit<
|
||||
| 'onScrollEndDrag' // Use ScrollContext instead.
|
||||
| 'refreshControl' // Pass refreshing and/or onRefresh instead.
|
||||
| 'contentOffset' // Pass headerOffset instead.
|
||||
| 'progressViewOffset' // Can't be an animated value
|
||||
> & {
|
||||
onScrolledDownChange?: (isScrolledDown: boolean) => void
|
||||
headerOffset?: number
|
||||
@@ -32,130 +37,137 @@ export type ListProps<ItemT> = Omit<
|
||||
// Web only prop to contain the scroll to the container rather than the window
|
||||
disableFullWindowScroll?: boolean
|
||||
sideBorders?: boolean
|
||||
progressViewOffset?: number
|
||||
}
|
||||
export type ListRef = React.MutableRefObject<FlatList_INTERNAL | null>
|
||||
|
||||
const SCROLLED_DOWN_LIMIT = 200
|
||||
|
||||
function ListImpl<ItemT>(
|
||||
{
|
||||
onScrolledDownChange,
|
||||
refreshing,
|
||||
onRefresh,
|
||||
onItemSeen,
|
||||
headerOffset,
|
||||
style,
|
||||
progressViewOffset,
|
||||
...props
|
||||
}: ListProps<ItemT>,
|
||||
ref: React.Ref<ListMethods>,
|
||||
) {
|
||||
const isScrolledDown = useSharedValue(false)
|
||||
const t = useTheme()
|
||||
const dedupe = useDedupe(400)
|
||||
const {activeLightbox} = useLightbox()
|
||||
|
||||
function handleScrolledDownChange(didScrollDown: boolean) {
|
||||
onScrolledDownChange?.(didScrollDown)
|
||||
}
|
||||
|
||||
// Intentionally destructured outside the main thread closure.
|
||||
// See https://github.com/bluesky-social/social-app/pull/4108.
|
||||
const {
|
||||
onBeginDrag: onBeginDragFromContext,
|
||||
onEndDrag: onEndDragFromContext,
|
||||
onScroll: onScrollFromContext,
|
||||
onMomentumEnd: onMomentumEndFromContext,
|
||||
} = useScrollHandlers()
|
||||
const scrollHandler = useAnimatedScrollHandler({
|
||||
onBeginDrag(e, ctx) {
|
||||
onBeginDragFromContext?.(e, ctx)
|
||||
let List = React.forwardRef<ListMethods, ListProps>(
|
||||
(
|
||||
{
|
||||
onScrolledDownChange,
|
||||
refreshing,
|
||||
onRefresh,
|
||||
onItemSeen,
|
||||
headerOffset,
|
||||
style,
|
||||
progressViewOffset,
|
||||
...props
|
||||
},
|
||||
onEndDrag(e, ctx) {
|
||||
runOnJS(updateActiveVideoViewAsync)()
|
||||
onEndDragFromContext?.(e, ctx)
|
||||
},
|
||||
onScroll(e, ctx) {
|
||||
onScrollFromContext?.(e, ctx)
|
||||
ref,
|
||||
): React.ReactElement => {
|
||||
const isScrolledDown = useSharedValue(false)
|
||||
const t = useTheme()
|
||||
const dedupe = useDedupe(400)
|
||||
const {activeLightbox} = useLightbox()
|
||||
|
||||
const didScrollDown = e.contentOffset.y > SCROLLED_DOWN_LIMIT
|
||||
if (isScrolledDown.get() !== didScrollDown) {
|
||||
isScrolledDown.set(didScrollDown)
|
||||
if (onScrolledDownChange != null) {
|
||||
runOnJS(handleScrolledDownChange)(didScrollDown)
|
||||
}
|
||||
}
|
||||
|
||||
if (isIOS) {
|
||||
runOnJS(dedupe)(updateActiveVideoViewAsync)
|
||||
}
|
||||
},
|
||||
// Note: adding onMomentumBegin here makes simulator scroll
|
||||
// lag on Android. So either don't add it, or figure out why.
|
||||
onMomentumEnd(e, ctx) {
|
||||
runOnJS(updateActiveVideoViewAsync)()
|
||||
onMomentumEndFromContext?.(e, ctx)
|
||||
},
|
||||
})
|
||||
|
||||
const [onViewableItemsChanged, viewabilityConfig] = React.useMemo(() => {
|
||||
if (!onItemSeen) {
|
||||
return [undefined, undefined]
|
||||
function handleScrolledDownChange(didScrollDown: boolean) {
|
||||
onScrolledDownChange?.(didScrollDown)
|
||||
}
|
||||
return [
|
||||
(info: {viewableItems: Array<ViewToken>; changed: Array<ViewToken>}) => {
|
||||
for (const item of info.changed) {
|
||||
if (item.isViewable) {
|
||||
onItemSeen(item.item)
|
||||
|
||||
// Intentionally destructured outside the main thread closure.
|
||||
// See https://github.com/bluesky-social/social-app/pull/4108.
|
||||
const {
|
||||
onBeginDrag: onBeginDragFromContext,
|
||||
onEndDrag: onEndDragFromContext,
|
||||
onScroll: onScrollFromContext,
|
||||
onMomentumEnd: onMomentumEndFromContext,
|
||||
} = useScrollHandlers()
|
||||
const scrollHandler = useAnimatedScrollHandler({
|
||||
onBeginDrag(e, ctx) {
|
||||
onBeginDragFromContext?.(e, ctx)
|
||||
},
|
||||
onEndDrag(e, ctx) {
|
||||
runOnJS(updateActiveVideoViewAsync)()
|
||||
onEndDragFromContext?.(e, ctx)
|
||||
},
|
||||
onScroll(e, ctx) {
|
||||
onScrollFromContext?.(e, ctx)
|
||||
|
||||
const didScrollDown = e.contentOffset.y > SCROLLED_DOWN_LIMIT
|
||||
if (isScrolledDown.get() !== didScrollDown) {
|
||||
isScrolledDown.set(didScrollDown)
|
||||
if (onScrolledDownChange != null) {
|
||||
runOnJS(handleScrolledDownChange)(didScrollDown)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
itemVisiblePercentThreshold: 40,
|
||||
minimumViewTime: 0.5e3,
|
||||
},
|
||||
]
|
||||
}, [onItemSeen])
|
||||
|
||||
let refreshControl
|
||||
if (refreshing !== undefined || onRefresh !== undefined) {
|
||||
refreshControl = (
|
||||
<RefreshControl
|
||||
refreshing={refreshing ?? false}
|
||||
onRefresh={onRefresh}
|
||||
tintColor={t.atoms.text.color}
|
||||
titleColor={t.atoms.text.color}
|
||||
progressViewOffset={progressViewOffset ?? headerOffset}
|
||||
if (isIOS) {
|
||||
runOnJS(dedupe)(updateActiveVideoViewAsync)
|
||||
}
|
||||
},
|
||||
// Note: adding onMomentumBegin here makes simulator scroll
|
||||
// lag on Android. So either don't add it, or figure out why.
|
||||
onMomentumEnd(e, ctx) {
|
||||
runOnJS(updateActiveVideoViewAsync)()
|
||||
onMomentumEndFromContext?.(e, ctx)
|
||||
},
|
||||
})
|
||||
|
||||
const [onViewableItemsChanged, viewabilityConfig] = React.useMemo(() => {
|
||||
if (!onItemSeen) {
|
||||
return [undefined, undefined]
|
||||
}
|
||||
return [
|
||||
(info: {
|
||||
viewableItems: Array<ViewToken>
|
||||
changed: Array<ViewToken>
|
||||
}) => {
|
||||
for (const item of info.changed) {
|
||||
if (item.isViewable) {
|
||||
onItemSeen(item.item)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
itemVisiblePercentThreshold: 40,
|
||||
minimumViewTime: 0.5e3,
|
||||
},
|
||||
]
|
||||
}, [onItemSeen])
|
||||
|
||||
let refreshControl
|
||||
if (refreshing !== undefined || onRefresh !== undefined) {
|
||||
refreshControl = (
|
||||
<RefreshControl
|
||||
refreshing={refreshing ?? false}
|
||||
onRefresh={onRefresh}
|
||||
tintColor={t.atoms.text.color}
|
||||
titleColor={t.atoms.text.color}
|
||||
progressViewOffset={progressViewOffset ?? headerOffset}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
let contentOffset
|
||||
if (headerOffset != null) {
|
||||
style = addStyle(style, {
|
||||
paddingTop: headerOffset,
|
||||
})
|
||||
contentOffset = {x: 0, y: headerOffset * -1}
|
||||
}
|
||||
|
||||
return (
|
||||
<FlatList_INTERNAL
|
||||
{...props}
|
||||
scrollIndicatorInsets={{right: 1}}
|
||||
contentOffset={contentOffset}
|
||||
refreshControl={refreshControl}
|
||||
onScroll={scrollHandler}
|
||||
scrollsToTop={!activeLightbox}
|
||||
scrollEventThrottle={1}
|
||||
onViewableItemsChanged={onViewableItemsChanged}
|
||||
viewabilityConfig={viewabilityConfig}
|
||||
showsVerticalScrollIndicator={!isAndroid}
|
||||
style={style}
|
||||
// @ts-expect-error FlatList_INTERNAL ref type is wrong -sfn
|
||||
ref={ref}
|
||||
/>
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
List.displayName = 'List'
|
||||
|
||||
let contentOffset
|
||||
if (headerOffset != null) {
|
||||
style = addStyle(style, {
|
||||
paddingTop: headerOffset,
|
||||
})
|
||||
contentOffset = {x: 0, y: headerOffset * -1}
|
||||
}
|
||||
|
||||
return (
|
||||
<FlatList_INTERNAL
|
||||
{...props}
|
||||
scrollIndicatorInsets={{right: 1}}
|
||||
contentOffset={contentOffset}
|
||||
refreshControl={refreshControl}
|
||||
onScroll={scrollHandler}
|
||||
scrollsToTop={!activeLightbox}
|
||||
scrollEventThrottle={1}
|
||||
onViewableItemsChanged={onViewableItemsChanged}
|
||||
viewabilityConfig={viewabilityConfig}
|
||||
showsVerticalScrollIndicator={!isAndroid}
|
||||
style={style}
|
||||
ref={ref}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const List = memo(React.forwardRef(ListImpl)) as <ItemT>(
|
||||
props: ListProps<ItemT> & {ref?: React.Ref<ListMethods>},
|
||||
) => React.ReactElement
|
||||
List = memo(List)
|
||||
export {List}
|
||||
|
||||
@@ -113,6 +113,7 @@ export const ViewSelector = React.forwardRef<
|
||||
)
|
||||
return (
|
||||
<FlatList_INTERNAL
|
||||
// @ts-expect-error FlatList_INTERNAL ref type is wrong -sfn
|
||||
ref={flatListRef}
|
||||
data={data}
|
||||
keyExtractor={keyExtractor}
|
||||
|
||||
Vendored
-19
@@ -1,19 +0,0 @@
|
||||
import React from 'react'
|
||||
import {ViewProps} from 'react-native'
|
||||
export {FlatList as FlatList_INTERNAL, ScrollView} from 'react-native'
|
||||
export function CenteredView({
|
||||
style,
|
||||
sideBorders,
|
||||
...props
|
||||
}: React.PropsWithChildren<
|
||||
ViewProps & {
|
||||
/**
|
||||
* @platform web
|
||||
*/
|
||||
sideBorders?: boolean
|
||||
/**
|
||||
* @platform web
|
||||
*/
|
||||
topBorder?: boolean
|
||||
}
|
||||
>)
|
||||
@@ -1,7 +0,0 @@
|
||||
import {View} from 'react-native'
|
||||
import Animated from 'react-native-reanimated'
|
||||
|
||||
// If you explode these into functions, don't forget to forwardRef!
|
||||
export const FlatList_INTERNAL = Animated.FlatList
|
||||
export const CenteredView = View
|
||||
export const ScrollView = Animated.ScrollView
|
||||
@@ -0,0 +1,28 @@
|
||||
import {forwardRef} from 'react'
|
||||
import {FlatListComponent} from 'react-native'
|
||||
import {View, ViewProps} from 'react-native'
|
||||
import Animated from 'react-native-reanimated'
|
||||
import {FlatListPropsWithLayout} from 'react-native-reanimated'
|
||||
|
||||
// If you explode these into functions, don't forget to forwardRef!
|
||||
|
||||
/**
|
||||
* Avoid using `FlatList_INTERNAL` and use `List` where possible.
|
||||
* The types are a bit wrong on `FlatList_INTERNAL`
|
||||
*/
|
||||
export const FlatList_INTERNAL = Animated.FlatList
|
||||
export type FlatList_INTERNAL<ItemT = any> = Omit<
|
||||
FlatListComponent<ItemT, FlatListPropsWithLayout<ItemT>>,
|
||||
'CellRendererComponent'
|
||||
>
|
||||
export const ScrollView = Animated.ScrollView
|
||||
export type ScrollView = typeof Animated.ScrollView
|
||||
|
||||
export const CenteredView = forwardRef<
|
||||
View,
|
||||
React.PropsWithChildren<
|
||||
ViewProps & {sideBorders?: boolean; topBorder?: boolean}
|
||||
>
|
||||
>(function CenteredView(props, ref) {
|
||||
return <View ref={ref} {...props} />
|
||||
})
|
||||
Reference in New Issue
Block a user