11/* eslint-disable no-underscore-dangle */
22import React , {
33 MutableRefObject ,
4+ useCallback ,
45 useEffect ,
56 useImperativeHandle ,
67 useRef ,
@@ -16,7 +17,7 @@ import {
1617} from 'react-native' ;
1718
1819import { Virtuoso , VirtuosoHandle , VirtuosoProps } from 'react-virtuoso' ;
19- import type { Props } from './types' ;
20+ import type { Props , WebFlatListProps } from './types' ;
2021
2122const styles = StyleSheet . create ( {
2223 indicatorContainer : {
@@ -35,10 +36,8 @@ const styles = StyleSheet.create({
3536
3637const waiter = ( ) => new Promise ( ( resolve ) => setTimeout ( resolve , 500 ) ) ;
3738const dampingFactor = 5 ;
39+ const pullToRefreshReleaseThreshold = 100 ;
3840
39- type WebFlatListProps < T > = Props < T > & {
40- onScroll : ( event : React . UIEvent < 'div' , UIEvent > ) => void ;
41- } ;
4241/**
4342 * Note:
4443 * - `onEndReached` and `onStartReached` must return a promise.
@@ -95,20 +94,39 @@ export const BidirectionalFlatList = (React.forwardRef(
9594 ) ;
9695 const [ onEndReachedInProgress , setOnEndReachedInProgress ] = useState ( false ) ;
9796 const [ vData , setVDate ] = useState ( data ) ;
98- const [ firstItemIndex , setFirstItemIndex ] = useState ( 10 ** 7 ) ;
97+ const firstItemIndex = useRef ( 0 ) ;
9998 const previousDataLength = useRef ( data ?. length || 0 ) ;
10099 const fadeAnimation = useRef ( new Animated . Value ( 0 ) ) . current ;
101100 const pan = useRef ( new Animated . ValueXY ( ) ) . current ;
102101 const lastYValue = useRef ( 0 ) ;
103102 const panResponderActive = useRef ( false ) ;
103+ const onRefreshRef = useRef ( onRefresh ) ;
104+ const simulateRefreshAction = useRef ( async ( ) => {
105+ if ( ! scrollerRef . current ) return ;
106+
107+ prependingItems . current = true ;
108+ await waiter ( ) ;
109+ await onRefreshRef . current ?.( ) ;
110+ scrollerRef . current . style . overflow = 'auto' ;
111+
112+ Animated . spring ( pan , {
113+ toValue : { x : 0 , y : 0 } ,
114+ useNativeDriver : true ,
115+ } ) . start ( ) ;
116+
117+ fadeAnimation . setValue ( 0 ) ;
118+ } ) ;
119+
104120 const resetPanHandlerAndScroller = ( ) => {
105121 if ( scrollerRef . current ) {
106122 scrollerRef . current . style . overflow = 'auto' ;
107123 }
108124 panResponderActive . current = false ;
109125 } ;
126+
110127 const startReached = async ( ) => {
111128 if (
129+ ! onStartReached ||
112130 prependingItems . current ||
113131 onStartReachedInProgress ||
114132 ( inverted && ! onEndReached ) ||
@@ -119,6 +137,7 @@ export const BidirectionalFlatList = (React.forwardRef(
119137
120138 prependingItems . current = true ;
121139 setOnStartReachedInProgress ( true ) ;
140+
122141 if ( inverted ) {
123142 await onEndReached ?.( ) ;
124143 } else {
@@ -129,7 +148,9 @@ export const BidirectionalFlatList = (React.forwardRef(
129148
130149 const endReached = async ( ) => {
131150 if (
151+ ! onEndReached ||
132152 onEndReachedInProgress ||
153+ appendingItems . current ||
133154 ( inverted && ! onStartReached ) ||
134155 ( ! inverted && ! onEndReached )
135156 ) {
@@ -179,15 +200,17 @@ export const BidirectionalFlatList = (React.forwardRef(
179200 }
180201
181202 panResponderActive . current = true ;
182- scrollerRef . current . style . overflow = 'hidden' ;
203+ // scrollerRef.current.style.overflow = 'hidden';
183204 pan . setValue ( {
184205 x : 0 ,
185206 y : lastYValue . current + gestureState . dy / dampingFactor ,
186207 } ) ;
187208
188209 if (
189- lastYValue . current + gestureState . dy / dampingFactor < 35 &&
190- lastYValue . current + gestureState . dy / dampingFactor > - 35
210+ lastYValue . current + gestureState . dy / dampingFactor <
211+ pullToRefreshReleaseThreshold &&
212+ lastYValue . current + gestureState . dy / dampingFactor >
213+ - pullToRefreshReleaseThreshold
191214 ) {
192215 fadeAnimation . setValue ( 0 ) ;
193216 } else {
@@ -200,7 +223,8 @@ export const BidirectionalFlatList = (React.forwardRef(
200223 resetPanHandlerAndScroller ( ) ;
201224
202225 if (
203- lastYValue . current + gestureState . dy > 35 &&
226+ lastYValue . current + gestureState . dy >
227+ pullToRefreshReleaseThreshold &&
204228 offsetFromTop . current <= 0
205229 ) {
206230 fadeAnimation . setValue ( 1 ) ;
@@ -221,52 +245,41 @@ export const BidirectionalFlatList = (React.forwardRef(
221245 } )
222246 ) . current ;
223247
224- const Footer = ( ) =>
225- ListFooterComponent ? (
226- < ListFooterComponent />
227- ) : onEndReachedInProgress ? (
228- FooterLoadingIndicator ? (
229- < FooterLoadingIndicator />
230- ) : showDefaultLoadingIndicators ? (
231- < View style = { styles . indicatorContainer } >
232- < ActivityIndicator color = { activityIndicatorColor } size = { 'small' } />
233- </ View >
234- ) : null
235- ) : null ;
236-
237248 const handleScroll : VirtuosoProps < unknown > [ 'onScroll' ] = ( e ) => {
238249 const targetElem = e . target as HTMLDivElement ;
239250 offsetFromBottom . current =
240251 targetElem . scrollHeight -
241252 ( targetElem . scrollTop + targetElem . clientHeight ) ;
242253 offsetFromTop . current = targetElem . scrollTop ;
243254
244- if (
245- inverted &&
246- ! appendingItems . current &&
247- ! prependingItems . current &&
248- startReached
249- ) {
250- if ( targetElem . scrollTop <= onEndReachedThreshold ) {
251- startReached ( ) ;
252- }
255+ if ( appendingItems . current || prependingItems . current ) {
256+ return ;
257+ }
253258
254- if ( offsetFromBottom . current <= onStartReachedThreshold && endReached ) {
255- endReached ( ) ;
256- }
257- } else {
258- if ( targetElem . scrollTop <= onStartReachedThreshold && endReached ) {
259- endReached ( ) ;
260- }
259+ if ( targetElem . scrollTop <= onStartReachedThreshold ) {
260+ startReached ( ) ;
261+ }
261262
262- if ( offsetFromBottom . current <= onEndReachedThreshold && startReached ) {
263- startReached ( ) ;
264- }
263+ if ( offsetFromBottom . current <= onEndReachedThreshold ) {
264+ endReached ( ) ;
265265 }
266266
267267 onScroll ?.( e ) ;
268268 } ;
269269
270+ const Footer = ( ) =>
271+ ListFooterComponent ? (
272+ < ListFooterComponent />
273+ ) : onEndReachedInProgress ? (
274+ FooterLoadingIndicator ? (
275+ < FooterLoadingIndicator />
276+ ) : showDefaultLoadingIndicators ? (
277+ < View style = { styles . indicatorContainer } >
278+ < ActivityIndicator color = { activityIndicatorColor } size = { 'small' } />
279+ </ View >
280+ ) : null
281+ ) : null ;
282+
270283 const Header = ( ) =>
271284 ListHeaderComponent ? (
272285 < ListHeaderComponent />
@@ -280,67 +293,60 @@ export const BidirectionalFlatList = (React.forwardRef(
280293 ) : null
281294 ) : null ;
282295
283- const itemContent = ( index : number ) => {
284- if ( ! renderItem ) {
285- console . warn ( 'Please specify renderItem prop' ) ;
286- return null ;
287- }
288-
289- if ( ! vData ) {
290- return null ;
291- }
292-
293- const indexInData = inverted
294- ? vData . length - 1 - ( index - firstItemIndex )
295- : index - firstItemIndex ;
296-
297- return (
298- < >
299- { /* @ts -ignore */ }
300- { renderItem ( { index, item : vData [ indexInData ] } ) }
301- { indexInData !== vData . length && ! ! ItemSeparatorComponent && (
302- < ItemSeparatorComponent />
303- ) }
304- </ >
305- ) ;
306- } ;
307-
308- const onRefreshRef = useRef ( onRefresh ) ;
309-
310- const simulateRefreshAction = useRef ( async ( ) => {
311- if ( ! scrollerRef . current || prependingItems . current ) return ;
296+ const itemContent = useCallback (
297+ ( index : number ) => {
298+ if ( ! renderItem ) {
299+ console . warn ( 'Please specify renderItem prop' ) ;
300+ return null ;
301+ }
312302
313- prependingItems . current = true ;
314- await waiter ( ) ;
315- await onRefreshRef . current ?.( ) ;
316- scrollerRef . current . style . overflow = 'auto' ;
303+ if ( ! vData ) {
304+ return null ;
305+ }
317306
318- Animated . spring ( pan , {
319- toValue : { x : 0 , y : 0 } ,
320- useNativeDriver : true ,
321- } ) . start ( ) ;
307+ const indexInData = inverted
308+ ? vData . length - 1 - ( index + Math . abs ( firstItemIndex . current ) )
309+ : index + Math . abs ( firstItemIndex . current ) ;
322310
323- fadeAnimation . setValue ( 0 ) ;
324- } ) ;
311+ return (
312+ < >
313+ { renderItem ( {
314+ index,
315+ item : vData [ indexInData ] ,
316+ separators : {
317+ highlight : ( ) => null ,
318+ unhighlight : ( ) => null ,
319+ updateProps : ( ) => null ,
320+ } ,
321+ } ) }
322+ { indexInData !== vData . length && ! ! ItemSeparatorComponent && (
323+ < ItemSeparatorComponent />
324+ ) }
325+ </ >
326+ ) ;
327+ } ,
328+ [ vData ?. length ]
329+ ) ;
325330
326331 useEffect ( ( ) => {
327332 const updateVirtuoso = ( ) => {
328333 if ( ! data ?. length ) {
329334 return ;
330335 }
331336
332- let nextFirstItemIndex = firstItemIndex ;
333- if ( prependingItems . current === true ) {
337+ let nextFirstItemIndex = firstItemIndex . current ;
338+ if ( prependingItems . current ) {
334339 nextFirstItemIndex =
335- firstItemIndex - ( data . length - previousDataLength . current ) ;
336- setFirstItemIndex ( nextFirstItemIndex ) ;
340+ firstItemIndex . current - ( data . length - previousDataLength . current ) ;
337341 }
338342
339- setVDate ( ( ) => data ) ;
340- prependingItems . current = false ;
341343 appendingItems . current = false ;
344+ prependingItems . current = false ;
342345 previousDataLength . current = data . length ;
343346
347+ firstItemIndex . current = nextFirstItemIndex ;
348+ setVDate ( ( ) => data ) ;
349+
344350 if (
345351 enableAutoscrollToTop &&
346352 offsetFromBottom . current < autoscrollToTopThreshold
@@ -355,18 +361,19 @@ export const BidirectionalFlatList = (React.forwardRef(
355361 } ;
356362
357363 updateVirtuoso ( ) ;
358- } , [ enableAutoscrollToTop , autoscrollToTopThreshold , data , firstItemIndex ] ) ;
364+ } , [ enableAutoscrollToTop , autoscrollToTopThreshold , data , setVDate ] ) ;
359365
360366 useImperativeHandle (
361367 ref ,
368+ // @ts -ignore
362369 ( ) => ( {
363370 flashScrollIndicators : ( ) => null ,
364371 getNativeScrollRef : ( ) => null ,
365372 getScrollableNode : ( ) => null ,
366373 getScrollResponder : ( ) => null ,
367374 recordInteraction : ( ) => null ,
368- // @ts -ignore
369- scrollToEnd : ( { animated } : { animated ?: boolean | null } ) => {
375+ scrollToEnd : ( params = { animated : true } ) => {
376+ const { animated } = params ;
370377 if ( ! vData ?. length ) return ;
371378
372379 if ( inverted ) {
@@ -432,7 +439,6 @@ export const BidirectionalFlatList = (React.forwardRef(
432439 } , [ onRefresh ] ) ;
433440
434441 if ( ! vData ?. length || vData ?. length === 0 ) {
435- // @ts -ignore
436442 return < ListEmptyComponent /> ;
437443 }
438444
@@ -443,8 +449,7 @@ export const BidirectionalFlatList = (React.forwardRef(
443449 Footer,
444450 Header,
445451 } }
446- endReached = { endReached }
447- firstItemIndex = { firstItemIndex }
452+ firstItemIndex = { firstItemIndex . current }
448453 initialTopMostItemIndex = {
449454 inverted
450455 ? vData . length - 1 - initialScrollIndex
@@ -456,7 +461,6 @@ export const BidirectionalFlatList = (React.forwardRef(
456461 ref = { virtuosoRef }
457462 // @ts -ignore
458463 scrollerRef = { ( ref ) => ( scrollerRef . current = ref ) }
459- startReached = { startReached }
460464 totalCount = { vData . length }
461465 />
462466 ) ;
@@ -488,8 +492,7 @@ export const BidirectionalFlatList = (React.forwardRef(
488492 Footer,
489493 Header,
490494 } }
491- endReached = { endReached }
492- firstItemIndex = { firstItemIndex }
495+ firstItemIndex = { firstItemIndex . current }
493496 initialTopMostItemIndex = {
494497 inverted
495498 ? vData . length - 1 - initialScrollIndex
@@ -499,9 +502,7 @@ export const BidirectionalFlatList = (React.forwardRef(
499502 onScroll = { handleScroll }
500503 overscan = { 300 }
501504 ref = { virtuosoRef }
502- // @ts -ignore
503- scrollerRef = { ( ref ) => ( scrollerRef . current = ref ) }
504- startReached = { startReached }
505+ scrollerRef = { ( ref ) => ( scrollerRef . current = ref as HTMLElement ) }
505506 totalCount = { vData . length }
506507 />
507508 </ Animated . View >
0 commit comments