diff --git a/src/components/FlatList/index.ts b/src/components/FlatList/index.ios.ts similarity index 100% rename from src/components/FlatList/index.ts rename to src/components/FlatList/index.ios.ts diff --git a/src/components/FlatList/MVCPFlatList.js b/src/components/FlatList/index.tsx similarity index 71% rename from src/components/FlatList/MVCPFlatList.js rename to src/components/FlatList/index.tsx index 656a0ed7f00e..9f42e9597c79 100644 --- a/src/components/FlatList/MVCPFlatList.js +++ b/src/components/FlatList/index.tsx @@ -1,10 +1,11 @@ /* eslint-disable es/no-optional-chaining, es/no-nullish-coalescing-operators, react/prop-types */ -import PropTypes from 'prop-types'; -import React from 'react'; +import type {ForwardedRef, MutableRefObject} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import type {FlatListProps, NativeScrollEvent, NativeSyntheticEvent} from 'react-native'; import {FlatList} from 'react-native'; -function mergeRefs(...args) { - return function forwardRef(node) { +function mergeRefs(...args: Array | ForwardedRef | null>) { + return function forwardRef(node: FlatList) { args.forEach((ref) => { if (ref == null) { return; @@ -23,41 +24,46 @@ function mergeRefs(...args) { }; } -function useMergeRefs(...args) { - return React.useMemo( +function useMergeRefs(...args: Array | ForwardedRef | null>) { + return useMemo( () => mergeRefs(...args), // eslint-disable-next-line [...args], ); } -const MVCPFlatList = React.forwardRef(({maintainVisibleContentPosition, horizontal, onScroll, ...props}, forwardedRef) => { +function getScrollableNode(flatList: FlatList | null): HTMLElement | undefined { + return flatList?.getScrollableNode() as HTMLElement | undefined; +} + +function MVCPFlatList({maintainVisibleContentPosition, horizontal = false, onScroll, ...props}: FlatListProps, ref: ForwardedRef) { const {minIndexForVisible: mvcpMinIndexForVisible, autoscrollToTopThreshold: mvcpAutoscrollToTopThreshold} = maintainVisibleContentPosition ?? {}; - const scrollRef = React.useRef(null); - const prevFirstVisibleOffsetRef = React.useRef(null); - const firstVisibleViewRef = React.useRef(null); - const mutationObserverRef = React.useRef(null); - const lastScrollOffsetRef = React.useRef(0); - const isListRenderedRef = React.useRef(false); - - const getScrollOffset = React.useCallback(() => { - if (scrollRef.current == null) { + const scrollRef = useRef(null); + const prevFirstVisibleOffsetRef = useRef(0); + const firstVisibleViewRef = useRef(null); + const mutationObserverRef = useRef(null); + const lastScrollOffsetRef = useRef(0); + const isListRenderedRef = useRef(false); + + const getScrollOffset = useCallback((): number => { + if (!scrollRef.current) { return 0; } - return horizontal ? scrollRef.current?.getScrollableNode()?.scrollLeft : scrollRef.current?.getScrollableNode()?.scrollTop; + return horizontal ? getScrollableNode(scrollRef.current)?.scrollLeft ?? 0 : getScrollableNode(scrollRef.current)?.scrollTop ?? 0; }, [horizontal]); - const getContentView = React.useCallback(() => scrollRef.current?.getScrollableNode()?.childNodes[0], []); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + const getContentView = useCallback(() => getScrollableNode(scrollRef.current)?.childNodes[0], []); - const scrollToOffset = React.useCallback( - (offset, animated) => { + const scrollToOffset = useCallback( + (offset: number, animated: boolean) => { const behavior = animated ? 'smooth' : 'instant'; - scrollRef.current?.getScrollableNode()?.scroll(horizontal ? {left: offset, behavior} : {top: offset, behavior}); + getScrollableNode(scrollRef.current)?.scroll(horizontal ? {left: offset, behavior} : {top: offset, behavior}); }, [horizontal], ); - const prepareForMaintainVisibleContentPosition = React.useCallback(() => { + const prepareForMaintainVisibleContentPosition = useCallback(() => { if (mvcpMinIndexForVisible == null) { return; } @@ -72,7 +78,7 @@ const MVCPFlatList = React.forwardRef(({maintainVisibleContentPosition, horizont const contentViewLength = contentView.childNodes.length; for (let i = mvcpMinIndexForVisible; i < contentViewLength; i++) { - const subview = contentView.childNodes[i]; + const subview = contentView.childNodes[i] as HTMLElement; const subviewOffset = horizontal ? subview.offsetLeft : subview.offsetTop; if (subviewOffset > scrollOffset) { prevFirstVisibleOffsetRef.current = subviewOffset; @@ -82,7 +88,7 @@ const MVCPFlatList = React.forwardRef(({maintainVisibleContentPosition, horizont } }, [getContentView, getScrollOffset, mvcpMinIndexForVisible, horizontal]); - const adjustForMaintainVisibleContentPosition = React.useCallback(() => { + const adjustForMaintainVisibleContentPosition = useCallback(() => { if (mvcpMinIndexForVisible == null) { return; } @@ -105,7 +111,7 @@ const MVCPFlatList = React.forwardRef(({maintainVisibleContentPosition, horizont } }, [getScrollOffset, scrollToOffset, mvcpMinIndexForVisible, mvcpAutoscrollToTopThreshold, horizontal]); - const setupMutationObserver = React.useCallback(() => { + const setupMutationObserver = useCallback(() => { const contentView = getContentView(); if (contentView == null) { return; @@ -139,7 +145,7 @@ const MVCPFlatList = React.forwardRef(({maintainVisibleContentPosition, horizont mutationObserverRef.current = mutationObserver; }, [adjustForMaintainVisibleContentPosition, prepareForMaintainVisibleContentPosition, getContentView, getScrollOffset, scrollToOffset]); - React.useEffect(() => { + useEffect(() => { if (!isListRenderedRef.current) { return; } @@ -149,10 +155,10 @@ const MVCPFlatList = React.forwardRef(({maintainVisibleContentPosition, horizont }); }, [prepareForMaintainVisibleContentPosition, setupMutationObserver]); - const setMergedRef = useMergeRefs(scrollRef, forwardedRef); + const setMergedRef = useMergeRefs(scrollRef, ref); - const onRef = React.useCallback( - (newRef) => { + const onRef = useCallback( + (newRef: FlatList) => { // Make sure to only call refs and re-attach listeners if the node changed. if (newRef == null || newRef === scrollRef.current) { return; @@ -165,18 +171,18 @@ const MVCPFlatList = React.forwardRef(({maintainVisibleContentPosition, horizont [prepareForMaintainVisibleContentPosition, setMergedRef, setupMutationObserver], ); - React.useEffect(() => { + useEffect(() => { const mutationObserver = mutationObserverRef.current; return () => { mutationObserver?.disconnect(); }; }, []); - const onScrollInternal = React.useCallback( - (ev) => { + const onScrollInternal = useCallback( + (event: NativeSyntheticEvent) => { prepareForMaintainVisibleContentPosition(); - onScroll?.(ev); + onScroll?.(event); }, [prepareForMaintainVisibleContentPosition, onScroll], ); @@ -196,20 +202,8 @@ const MVCPFlatList = React.forwardRef(({maintainVisibleContentPosition, horizont }} /> ); -}); +} MVCPFlatList.displayName = 'MVCPFlatList'; -MVCPFlatList.propTypes = { - maintainVisibleContentPosition: PropTypes.shape({ - minIndexForVisible: PropTypes.number.isRequired, - autoscrollToTopThreshold: PropTypes.number, - }), - horizontal: PropTypes.bool, -}; - -MVCPFlatList.defaultProps = { - maintainVisibleContentPosition: null, - horizontal: false, -}; - -export default MVCPFlatList; + +export default React.forwardRef(MVCPFlatList); diff --git a/src/components/FlatList/index.web.js b/src/components/FlatList/index.web.js deleted file mode 100644 index 7299776db9bc..000000000000 --- a/src/components/FlatList/index.web.js +++ /dev/null @@ -1,3 +0,0 @@ -import MVCPFlatList from './MVCPFlatList'; - -export default MVCPFlatList; diff --git a/src/components/InvertedFlatList/BaseInvertedFlatList/index.tsx b/src/components/InvertedFlatList/BaseInvertedFlatList/index.tsx index 9ee465369be1..54f6748986f4 100644 --- a/src/components/InvertedFlatList/BaseInvertedFlatList/index.tsx +++ b/src/components/InvertedFlatList/BaseInvertedFlatList/index.tsx @@ -1,6 +1,6 @@ import type {ForwardedRef} from 'react'; import React, {forwardRef, useMemo} from 'react'; -import type {FlatListProps, ScrollViewProps} from 'react-native'; +import type {FlatListProps, FlatList as RNFlatList, ScrollViewProps} from 'react-native'; import FlatList from '@components/FlatList'; type BaseInvertedFlatListProps = FlatListProps & { @@ -9,7 +9,7 @@ type BaseInvertedFlatListProps = FlatListProps & { const AUTOSCROLL_TO_TOP_THRESHOLD = 128; -function BaseInvertedFlatList(props: BaseInvertedFlatListProps, ref: ForwardedRef) { +function BaseInvertedFlatList(props: BaseInvertedFlatListProps, ref: ForwardedRef) { const {shouldEnableAutoScrollToTopThreshold, ...rest} = props; const maintainVisibleContentPosition = useMemo(() => {