about summary refs log tree commit diff
path: root/src/view/com/util/List.tsx
diff options
context:
space:
mode:
authorSamuel Newman <mozzius@protonmail.com>2024-11-26 15:55:27 +0000
committerGitHub <noreply@github.com>2024-11-26 15:55:27 +0000
commitce1b04b9250d25ec781c7e1ee046ea6358a81a83 (patch)
tree952bc4c73345de774157dd6f9269493dcdaa31be /src/view/com/util/List.tsx
parent56a88098d2ef8e271c7d63cb7d73cc220a787658 (diff)
downloadvoidsky-ce1b04b9250d25ec781c7e1ee046ea6358a81a83.tar.zst
Fix `<List>` types (#6756)
* fix List and ScrollView types

* add comment

* rm omitting ref
Diffstat (limited to 'src/view/com/util/List.tsx')
-rw-r--r--src/view/com/util/List.tsx240
1 files changed, 126 insertions, 114 deletions
diff --git a/src/view/com/util/List.tsx b/src/view/com/util/List.tsx
index fa93ec5e6..8176d0b43 100644
--- a/src/view/com/util/List.tsx
+++ b/src/view/com/util/List.tsx
@@ -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)
-        }
-      }
+    function handleScrolledDownChange(didScrollDown: boolean) {
+      onScrolledDownChange?.(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)
-    },
-  })
+    // 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 [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)
+        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)
+        }
       },
-      {
-        itemVisiblePercentThreshold: 40,
-        minimumViewTime: 0.5e3,
+      // 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)
       },
-    ]
-  }, [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}
-      />
-    )
-  }
+    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 contentOffset
-  if (headerOffset != null) {
-    style = addStyle(style, {
-      paddingTop: headerOffset,
-    })
-    contentOffset = {x: 0, y: headerOffset * -1}
-  }
+    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}
+        />
+      )
+    }
 
-  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}
-    />
-  )
-}
+    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'
 
-export const List = memo(React.forwardRef(ListImpl)) as <ItemT>(
-  props: ListProps<ItemT> & {ref?: React.Ref<ListMethods>},
-) => React.ReactElement
+List = memo(List)
+export {List}