/***
 * DONE: Reduce layout processing on data insert
 * DONE: Add notify data set changed and notify data insert option in data source
 * DONE: Add on end reached callback
 * DONE: Make another class for render stack generator
 * DONE: Simplify rendering a loading footer
 * DONE: Anchor first visible index on any insert/delete data wise
 * DONE: Build Scroll to index
 * DONE: Give viewability callbacks
 * DONE: Add full render logic in cases like change of dimensions
 * DONE: Fix all proptypes
 * DONE: Add Initial render Index support
 * DONE: Add animated scroll to web scrollviewer
 * DONE: Animate list view transition, including add/remove
 * DONE: Implement sticky headers and footers
 * TODO: Destroy less frequently used items in recycle pool, this will help in case of too many types.
 * TODO: Make viewability callbacks configurable
 * TODO: Observe size changes on web to optimize for reflowability
 * TODO: Solve //TSI
 */ import debounce = require ( "lodash.debounce" ) ; import * as PropTypes from "prop-types" ; import * as React from "react" ; import { ObjectUtil , Default } from "ts-object-utils" ; import ContextProvider from "./dependencies/ContextProvider" ; import { BaseDataProvider } from "./dependencies/DataProvider" ; import { Dimension , BaseLayoutProvider } from "./dependencies/LayoutProvider" ; import CustomError from "./exceptions/CustomError" ; import RecyclerListViewExceptions from "./exceptions/RecyclerListViewExceptions" ; import { Point , Layout , LayoutManager } from "./layoutmanager/LayoutManager" ; import { Constants } from "./constants/Constants" ; import { Messages } from "./constants/Messages" ; import BaseScrollComponent from "./scrollcomponent/BaseScrollComponent" ; import BaseScrollView , { ScrollEvent , ScrollViewDefaultProps } from "./scrollcomponent/BaseScrollView" ; import { TOnItemStatusChanged , WindowCorrection } from "./ViewabilityTracker" ; import VirtualRenderer , { RenderStack , RenderStackItem , RenderStackParams } from "./VirtualRenderer" ; import ItemAnimator , { BaseItemAnimator } from "./ItemAnimator" ; import { DebugHandlers } from ".." ; import { ComponentCompat } from "../utils/ComponentCompat" ; import ScrollComponent from "../platform/reactnative/scrollcomponent/ScrollComponent" ; import ViewRenderer from "../platform/reactnative/viewrenderer/ViewRenderer" ; import { DefaultJSItemAnimator as DefaultItemAnimator } from "../platform/reactnative/itemanimators/defaultjsanimator/DefaultJSItemAnimator" ; import { Platform } from "react-native" ; const IS_WEB = ! Platform || Platform . OS === "web" ; /***
 * To use on web, start importing from recyclerlistview/web. To make it even easier specify an alias in you builder of choice.
 */ /***
 * This is the main component, please refer to samples to understand how to use.
 * For advanced usage check out prop descriptions below.
 * You also get common methods such as: scrollToIndex, scrollToItem, scrollToTop, scrollToEnd, scrollToOffset, getCurrentScrollOffset,
 * findApproxFirstVisibleIndex.
 * You'll need a ref to Recycler in order to call these
 * Needs to have bounded size in all cases other than window scrolling (web).
 *
 * NOTE: React Native implementation uses ScrollView internally which means you get all ScrollView features as well such as Pull To Refresh, paging enabled
 *       You can easily create a recycling image flip view using one paging enabled flag. Read about ScrollView features in official
 *       react native documentation.
 * NOTE: If you see blank space look at the renderAheadOffset prop and make sure your data provider has a good enough rowHasChanged method.
 *       Blanks are totally avoidable with this listview.
 * NOTE: Also works on web (experimental)
 * NOTE: For reflowability set canChangeSize to true (experimental)
 */ export interface OnRecreateParams { lastOffset ? : number ; } export interface RecyclerListViewProps { layoutProvider : BaseLayoutProvider ; dataProvider : BaseDataProvider ; rowRenderer : ( type : string | number , data : any , index : number , extendedState ? : object ) => JSX . Element | JSX . Element [ ] | null ; contextProvider ? : ContextProvider ; renderAheadOffset ? : number ; isHorizontal ? : boolean ; onScroll ? : ( rawEvent : ScrollEvent , offsetX : number , offsetY : number ) => void ; onRecreate ? : ( params : OnRecreateParams ) => void ; onEndReached ? : ( ) => void ; onEndReachedThreshold ? : number ; onVisibleIndexesChanged ? : TOnItemStatusChanged ; onVisibleIndicesChanged ? : TOnItemStatusChanged ; renderFooter ? : ( ) => JSX . Element | JSX . Element [ ] | null ; externalScrollView ? : { new ( props ) : BaseScrollView ; } ; layoutSize ? : Dimension ; initialOffset ? : number ; initialRenderIndex ? : number ; scrollThrottle ? : number ; canChangeSize ? : boolean ; useWindowScroll ? : boolean ; disableRecycling ? : boolean ; forceNonDeterministicRendering ? : boolean ; extendedState ? : object ; itemAnimator ? : ItemAnimator ; optimizeForInsertDeleteAnimations ? : boolean ; style ? : object | number ; debugHandlers ? : DebugHandlers ; renderContentContainer ? : ( props ? : object , children ? : React . ReactNode ) => React . ReactNode | null ; scrollViewProps ? : object ; applyWindowCorrection ? : ( offsetX : number , offsetY : number , windowCorrection : WindowCorrection ) => void ; onItemLayout ? : ( index : number ) => void ; } export interface RecyclerListViewState { renderStack : RenderStack ; internalSnapshot : Record < string , object > ; } export default class RecyclerListView < P extends RecyclerListViewProps , S extends RecyclerListViewState > extends ComponentCompat < P , S > { public static defaultProps = { canChangeSize : false , disableRecycling : false , initialOffset : 0 , initialRenderIndex : 0 , isHorizontal : false , onEndReachedThreshold : 0 , renderAheadOffset : IS_WEB ? 1000 : 250 , } ; public static propTypes = { } ; private refreshRequestDebouncer = debounce ( ( executable ) => { executable ( ) ; } ) ; private _virtualRenderer : VirtualRenderer ; private _onEndReachedCalled = false ; private _initComplete = false ; private _relayoutReqIndex : number = - 1 ; private _params : RenderStackParams = { initialOffset : 0 , initialRenderIndex : 0 , isHorizontal : false , itemCount : 0 , renderAheadOffset : 250 , } ; private _layout : Dimension = { height : 0 , width : 0 } ; private _pendingScrollToOffset : Point | null = null ; private _pendingRenderStack ? : RenderStack ; private _tempDim : Dimension = { height : 0 , width : 0 } ; private _initialOffset = 0 ; private _cachedLayouts ? : Layout [ ] ; private _scrollComponent : BaseScrollComponent | null = null ; private _windowCorrection : WindowCorrection ; private _defaultItemAnimator : ItemAnimator = new BaseItemAnimator ( ) ; constructor ( props , context ? ) { super ( props , context ) ; this . _virtualRenderer = new VirtualRenderer ( this . _renderStackWhenReady , ( offset ) => { this . _pendingScrollToOffset = offset ; } , ( index ) => { return this . props . dataProvider . getStableId ( index ) ; } , ! props . disableRecycling ) ; this . _windowCorrection = { startCorrection : 0 , endCorrection : 0 , windowShift : 0 , } ; this . _getContextFromContextProvider ( props ) ; if ( props . layoutSize ) { this . _layout . height = props . layoutSize . height ; this . _layout . width = props . layoutSize . width ; this . _initComplete = true ; this . _initTrackers ( props ) ; } else { this . state = { internalSnapshot : { } , renderStack : { } , } as S ; } } public componentWillReceivePropsCompat ( newProps ) { this . _assertDependencyPresence ( newProps ) ; this . _checkAndChangeLayouts ( newProps ) ; if ( ! newProps . onVisibleIndicesChanged ) { this . _virtualRenderer . removeVisibleItemsListener ( ) ; } if ( newProps . onVisibleIndexesChanged ) { throw new CustomError ( RecyclerListViewExceptions . usingOldVisibleIndexesChangedParam ) ; } if ( newProps . onVisibleIndicesChanged ) { this . _virtualRenderer . attachVisibleItemsListener ( newProps . onVisibleIndicesChanged ! ) ; } } public componentDidUpdate ( ) { this . _processInitialOffset ( ) ; this . _processOnEndReached ( ) ; this . _checkAndChangeLayouts ( this . props ) ; if ( this . props . dataProvider . getSize ( ) === 0 ) { console . warn ( Messages . WARN_NO_DATA ) ; } } public componentDidMount ( ) { if ( this . _initComplete ) { this . _processInitialOffset ( ) ; this . _processOnEndReached ( ) ; } } public componentWillUnmount ( ) { if ( this . props . contextProvider ) { const uniqueKey = this . props . contextProvider . getUniqueKey ( ) ; if ( uniqueKey ) { this . props . contextProvider . save ( uniqueKey + Constants . CONTEXT_PROVIDER_OFFSET_KEY_SUFFIX , this . getCurrentScrollOffset ( ) ) ; if ( this . props . forceNonDeterministicRendering ) { if ( this . _virtualRenderer ) { const layoutManager = this . _virtualRenderer . getLayoutManager ( ) ; if ( layoutManager ) { const layoutsToCache = layoutManager . getLayouts ( ) ; this . props . contextProvider . save ( uniqueKey + Constants . CONTEXT_PROVIDER_LAYOUT_KEY_SUFFIX , JSON . stringify ( { layoutArray : layoutsToCache } ) ) ; } } } } } } public scrollToIndex ( index , animate ? ) { const layoutManager = this . _virtualRenderer . getLayoutManager ( ) ; if ( layoutManager ) { const offsets = layoutManager . getOffsetForIndex ( index ) ; this . scrollToOffset ( offsets . x , offsets . y , animate ) ; } else { console . warn ( Messages . WARN_SCROLL_TO_INDEX ) ; } } /**
     * This API is almost similar to scrollToIndex, but differs when the view is already in viewport.
     * Instead of bringing the view to the top of the viewport, it will calculate the overflow of the @param index
     * and scroll to just bring the entire view to viewport.
     */ public bringToFocus ( index , animate ? ) { const listSize = this . getRenderedSize ( ) ; const itemLayout = this . getLayout ( index ) ; const currentScrollOffset = this . getCurrentScrollOffset ( ) ; const { isHorizontal } = this . props ; if ( itemLayout ) { const mainAxisLayoutDimen = isHorizontal ? itemLayout . width : itemLayout . height ; const mainAxisLayoutPos = isHorizontal ? itemLayout . x : itemLayout . y ; const mainAxisListDimen = isHorizontal ? listSize . width : listSize . height ; const screenEndPos = mainAxisListDimen + currentScrollOffset ; if ( mainAxisLayoutDimen > mainAxisListDimen || mainAxisLayoutPos < currentScrollOffset || mainAxisLayoutPos > screenEndPos ) { this . scrollToIndex ( index ) ; } else { const viewEndPos = mainAxisLayoutPos + mainAxisLayoutDimen ; if ( viewEndPos > screenEndPos ) { const offset = viewEndPos - screenEndPos ; this . scrollToOffset ( 0 , offset + currentScrollOffset , animate ) ; } } } } public scrollToItem ( data , animate ? ) { const count = this . props . dataProvider . getSize ( ) ; for ( let i = 0 ; i < count ; i ++ ) { if ( this . props . dataProvider . getDataForIndex ( i ) === data ) { this . scrollToIndex ( i , animate ) ; break ; } } } public getLayout ( index ) { const layoutManager = this . _virtualRenderer . getLayoutManager ( ) ; return layoutManager ? layoutManager . getLayouts ( ) [ index ] : undefined ; } public scrollToTop ( animate ? ) { this . scrollToOffset ( 0 , 0 , animate ) ; } public scrollToEnd ( animate ? ) { const lastIndex = this . props . dataProvider . getSize ( ) - 1 ; this . scrollToIndex ( lastIndex , animate ) ; } public scrollToOffset = ( x , y , animate = false ) : void => { if ( this . _scrollComponent ) { if ( this . props . isHorizontal ) { y = 0 ; } else { x = 0 ; } this . _scrollComponent . scrollTo ( x , y , animate ) ; } } ; public updateRenderAheadOffset ( renderAheadOffset ) { const viewabilityTracker = this . _virtualRenderer . getViewabilityTracker ( ) ; if ( viewabilityTracker ) { viewabilityTracker . updateRenderAheadOffset ( renderAheadOffset ) ; return true ; } return false ; } public getCurrentRenderAheadOffset ( ) { const viewabilityTracker = this . _virtualRenderer . getViewabilityTracker ( ) ; if ( viewabilityTracker ) { return viewabilityTracker . getCurrentRenderAheadOffset ( ) ; } return this . props . renderAheadOffset ! ; } public getCurrentScrollOffset ( ) { const viewabilityTracker = this . _virtualRenderer . getViewabilityTracker ( ) ; return viewabilityTracker ? viewabilityTracker . getLastActualOffset ( ) : 0 ; } public findApproxFirstVisibleIndex ( ) { const viewabilityTracker = this . _virtualRenderer . getViewabilityTracker ( ) ; return viewabilityTracker ? viewabilityTracker . findFirstLogicallyVisibleIndex ( ) : 0 ; } public getRenderedSize ( ) { return this . _layout ; } public getContentDimension ( ) { return this . _virtualRenderer . getLayoutDimension ( ) ; } public forceRerender ( ) { this . setState ( { internalSnapshot : { } , } ) ; } public renderCompat ( ) { return ( < ScrollComponent ref = { ( scrollComponent ) => this . _scrollComponent = scrollComponent as BaseScrollComponent | null } { ... this . props } { ... this . props . scrollViewProps } onScroll = { this . _onScroll } onSizeChanged = { this . _onSizeChanged } contentHeight = { this . _initComplete ? this . _virtualRenderer . getLayoutDimension ( ) . height : 0 } contentWidth = { this . _initComplete ? this . _virtualRenderer . getLayoutDimension ( ) . width : 0 } renderAheadOffset = { this . getCurrentRenderAheadOffset ( ) } > { this . _generateRenderStack ( ) } < /ScrollComponent>); } protected getVirtualRenderer ( ) { return this . _virtualRenderer ; } private _processInitialOffset ( ) { if ( this . _pendingScrollToOffset ) { setTimeout ( ( ) => { if ( this . _pendingScrollToOffset ) { const offset = this . _pendingScrollToOffset ; this . _pendingScrollToOffset = null ; if ( this . props . isHorizontal ) { offset . y = 0 ; } else { offset . x = 0 ; } this . scrollToOffset ( offset . x , offset . y , false ) ; if ( this . _pendingRenderStack ) { this . _renderStackWhenReady ( this . _pendingRenderStack ) ; this . _pendingRenderStack = undefined ; } } } , 0 ) ; } } private _getContextFromContextProvider ( props ) { if ( props . contextProvider ) { const uniqueKey = props . contextProvider . getUniqueKey ( ) ; if ( uniqueKey ) { const offset = props . contextProvider . get ( uniqueKey + Constants . CONTEXT_PROVIDER_OFFSET_KEY_SUFFIX ) ; if ( typeof offset === "number" && offset > 0 ) { this . _initialOffset = offset ; if ( props . onRecreate ) { props . onRecreate ( { lastOffset : this . _initialOffset } ) ; } props . contextProvider . remove ( uniqueKey + Constants . CONTEXT_PROVIDER_OFFSET_KEY_SUFFIX ) ; } if ( props . forceNonDeterministicRendering ) { const cachedLayouts = props . contextProvider . get ( uniqueKey + Constants . CONTEXT_PROVIDER_LAYOUT_KEY_SUFFIX ) as string ; if ( cachedLayouts && typeof cachedLayouts === "string" ) { this . _cachedLayouts = JSON . parse ( cachedLayouts ) . layoutArray ; props . contextProvider . remove ( uniqueKey + Constants . CONTEXT_PROVIDER_LAYOUT_KEY_SUFFIX ) ; } } } } } private _checkAndChangeLayouts ( newProps , forceFullRender ? ) { this . _params . isHorizontal = newProps . isHorizontal ; this . _params . itemCount = newProps . dataProvider . getSize ( ) ; this . _virtualRenderer . setParamsAndDimensions ( this . _params , this . _layout ) ; this . _virtualRenderer . setLayoutProvider ( newProps . layoutProvider ) ; if ( newProps . dataProvider . hasStableIds ( ) && this . props . dataProvider !== newProps . dataProvider && newProps . dataProvider . requiresDataChangeHandling ( ) ) { this . _virtualRenderer . handleDataSetChange ( newProps . dataProvider , this . props . optimizeForInsertDeleteAnimations ) ; } if ( this . props . layoutProvider !== newProps . layoutProvider || this . props . isHorizontal !== newProps . isHorizontal ) { this . _virtualRenderer . setLayoutManager ( newProps . layoutProvider . newLayoutManager ( this . _layout , newProps . isHorizontal ) ) ; if ( newProps . layoutProvider . shouldRefreshWithAnchoring ) { this . _virtualRenderer . refreshWithAnchor ( ) ; } else { this . _virtualRenderer . refresh ( ) ; } this . _refreshViewability ( ) ; } else if ( this . props . dataProvider !== newProps . dataProvider ) { if ( newProps . dataProvider . getSize ( ) > this . props . dataProvider . getSize ( ) ) { this . _onEndReachedCalled = false ; } const layoutManager = this . _virtualRenderer . getLayoutManager ( ) ; if ( layoutManager ) { layoutManager . relayoutFromIndex ( newProps . dataProvider . getFirstIndexToProcessInternal ( ) , newProps . dataProvider . getSize ( ) ) ; this . _virtualRenderer . refresh ( ) ; } } else if ( forceFullRender ) { const layoutManager = this . _virtualRenderer . getLayoutManager ( ) ; if ( layoutManager ) { const cachedLayouts = layoutManager . getLayouts ( ) ; this . _virtualRenderer . setLayoutManager ( newProps . layoutProvider . newLayoutManager ( this . _layout , newProps . isHorizontal , cachedLayouts ) ) ; this . _refreshViewability ( ) ; } } else if ( this . _relayoutReqIndex >= 0 ) { const layoutManager = this . _virtualRenderer . getLayoutManager ( ) ; if ( layoutManager ) { const dataProviderSize = newProps . dataProvider . getSize ( ) ; layoutManager . relayoutFromIndex ( Math . min ( Math . max ( dataProviderSize - 1 , 0 ) , this . _relayoutReqIndex ) , dataProviderSize ) ; this . _relayoutReqIndex = - 1 ; this . _refreshViewability ( ) ; } } } private _refreshViewability ( ) { this . _virtualRenderer . refresh ( ) ; this . _queueStateRefresh ( ) ; } private _queueStateRefresh ( ) { this . refreshRequestDebouncer ( ( ) => { this . setState ( ( prevState ) => { return prevState ; } ) ; } ) ; } private _onSizeChanged = ( layout ) : void => { if ( ! this . props . canChangeSize && this . props . layoutSize ) { return ; } const hasHeightChanged = this . _layout . height !== layout . height ; const hasWidthChanged = this . _layout . width !== layout . width ; this . _layout . height = layout . height ; this . _layout . width = layout . width ; if ( layout . height === 0 || layout . width === 0 ) { throw new CustomError ( RecyclerListViewExceptions . layoutException ) ; } if ( ! this . _initComplete ) { this . _initComplete = true ; this . _initTrackers ( this . props ) ; this . _processOnEndReached ( ) ; } else { if ( ( hasHeightChanged && hasWidthChanged ) || ( hasHeightChanged && this . props . isHorizontal ) || ( hasWidthChanged && ! this . props . isHorizontal ) ) { this . _checkAndChangeLayouts ( this . props , true ) ; } else { this . _refreshViewability ( ) ; } } } ; private _initStateIfRequired ( stack ? ) { if ( ! this . state ) { this . state = { internalSnapshot : { } , renderStack : stack , } as S ; return true ; } return false ; } private _renderStackWhenReady = ( stack ) : void => { if ( this . _pendingScrollToOffset ) { this . _pendingRenderStack = stack ; return ; } if ( ! this . _initStateIfRequired ( stack ) ) { this . setState ( ( ) => { return { renderStack : stack } ; } ) ; } } ; private _initTrackers ( props ) { this . _assertDependencyPresence ( props ) ; if ( props . onVisibleIndexesChanged ) { throw new CustomError ( RecyclerListViewExceptions . usingOldVisibleIndexesChangedParam ) ; } if ( props . onVisibleIndicesChanged ) { this . _virtualRenderer . attachVisibleItemsListener ( props . onVisibleIndicesChanged ! ) ; } this . _params = { initialOffset : this . _initialOffset ? this . _initialOffset : props . initialOffset , initialRenderIndex : props . initialRenderIndex , isHorizontal : props . isHorizontal , itemCount : props . dataProvider . getSize ( ) , renderAheadOffset : props . renderAheadOffset , } ; this . _virtualRenderer . setParamsAndDimensions ( this . _params , this . _layout ) ; const layoutManager = props . layoutProvider . newLayoutManager ( this . _layout , props . isHorizontal , this . _cachedLayouts ) ; this . _virtualRenderer . setLayoutManager ( layoutManager ) ; this . _virtualRenderer . setLayoutProvider ( props . layoutProvider ) ; this . _virtualRenderer . init ( ) ; const offset = this . _virtualRenderer . getInitialOffset ( ) ; const contentDimension = layoutManager . getContentDimension ( ) ; if ( ( offset . y > 0 && contentDimension . height > this . _layout . height ) || ( offset . x > 0 && contentDimension . width > this . _layout . width ) ) { this . _pendingScrollToOffset = offset ; if ( ! this . _initStateIfRequired ( ) ) { this . setState ( { } ) ; } } else { this . _virtualRenderer . startViewabilityTracker ( this . _getWindowCorrection ( offset . x , offset . y , props ) ) ; } } private _getWindowCorrection ( offsetX , offsetY , props ) { return ( props . applyWindowCorrection && props . applyWindowCorrection ( offsetX , offsetY , this . _windowCorrection ) ) || this . _windowCorrection ; } private _assertDependencyPresence ( props ) { if ( ! props . dataProvider || ! props . layoutProvider ) { throw new CustomError ( RecyclerListViewExceptions . unresolvedDependenciesException ) ; } } private _assertType ( type ) { if ( ! type && type !== 0 ) { throw new CustomError ( RecyclerListViewExceptions . itemTypeNullException ) ; } } private _dataHasChanged = ( row1 , row2 ) : boolean => { return this . props . dataProvider . rowHasChanged ( row1 , row2 ) ; } ; private _renderRowUsingMeta ( itemMeta ) { const dataSize = this . props . dataProvider . getSize ( ) ; const dataIndex = itemMeta . dataIndex ; if ( ! ObjectUtil . isNullOrUndefined ( dataIndex ) && dataIndex < dataSize ) { const itemRect = ( this . _virtualRenderer . getLayoutManager ( ) as LayoutManager ) . getLayouts ( ) [ dataIndex ] ; const data = this . props . dataProvider . getDataForIndex ( dataIndex ) ; const type = this . props . layoutProvider . getLayoutTypeForIndex ( dataIndex ) ; const key = this . _virtualRenderer . syncAndGetKey ( dataIndex ) ; const styleOverrides = ( this . _virtualRenderer . getLayoutManager ( ) as LayoutManager ) . getStyleOverridesForIndex ( dataIndex ) ; this . _assertType ( type ) ; if ( ! this . props . forceNonDeterministicRendering ) { this . _checkExpectedDimensionDiscrepancy ( itemRect , type , dataIndex ) ; } return ( < ViewRenderer key = { key } data = { data } dataHasChanged = { this . _dataHasChanged } x = { itemRect . x } y = { itemRect . y } layoutType = { type } index = { dataIndex } styleOverrides = { styleOverrides } layoutProvider = { this . props . layoutProvider } forceNonDeterministicRendering = { this . props . forceNonDeterministicRendering } isHorizontal = { this . props . isHorizontal } onSizeChanged = { this . _onViewContainerSizeChange } childRenderer = { this . props . rowRenderer } height = { itemRect . height } width = { itemRect . width } itemAnimator = { Default . value < ItemAnimator > ( this . props . itemAnimator , this . _defaultItemAnimator ) } extendedState = { this . props . extendedState } internalSnapshot = { this . state . internalSnapshot } onItemLayout = { this . props . onItemLayout } / > ) ; } return null ; } private _onViewContainerSizeChange = ( dim , index ) : void => { const layoutManager = this . _virtualRenderer . getLayoutManager ( ) as LayoutManager ; if ( this . props . debugHandlers && this . props . debugHandlers . resizeDebugHandler ) { const itemRect = layoutManager . getLayouts ( ) [ index ] ; this . props . debugHandlers . resizeDebugHandler . resizeDebug ( { width : itemRect . width , height : itemRect . height , } , dim , index ) ; } if ( layoutManager . overrideLayout ( index , dim ) ) { if ( this . _relayoutReqIndex === - 1 ) { this . _relayoutReqIndex = index ; } else { this . _relayoutReqIndex = Math . min ( this . _relayoutReqIndex , index ) ; } this . _queueStateRefresh ( ) ; } } ; private _checkExpectedDimensionDiscrepancy ( itemRect , type , index ) { if ( this . props . layoutProvider . checkDimensionDiscrepancy ( itemRect , type , index ) ) { if ( this . _relayoutReqIndex === - 1 ) { this . _relayoutReqIndex = index ; } else { this . _relayoutReqIndex = Math . min ( this . _relayoutReqIndex , index ) ; } } } private _generateRenderStack ( ) { const renderedItems = [ ] ; for ( const key in this . state . renderStack ) { if ( this . state . renderStack . hasOwnProperty ( key ) ) { renderedItems . push ( this . _renderRowUsingMeta ( this . state . renderStack [ key ] ) ) ; } } return renderedItems ; } private _onScroll = ( offsetX , offsetY , rawEvent ) : void => { this . _virtualRenderer . updateOffset ( offsetX , offsetY , true , this . _getWindowCorrection ( offsetX , offsetY , this . props ) ) ; if ( this . props . onScroll ) { this . props . onScroll ( rawEvent , offsetX , offsetY ) ; } this . _processOnEndReached ( ) ; } ; private _processOnEndReached ( ) { if ( this . props . onEndReached && this . _virtualRenderer ) { const layout = this . _virtualRenderer . getLayoutDimension ( ) ; const viewabilityTracker = this . _virtualRenderer . getViewabilityTracker ( ) ; if ( viewabilityTracker ) { const windowBound = this . props . isHorizontal ? layout . width - this . _layout . width : layout . height - this . _layout . height ; const lastOffset = viewabilityTracker ? viewabilityTracker . getLastOffset ( ) : 0 ; if ( windowBound - lastOffset <= Default . value < number > ( this . props . onEndReachedThreshold , 0 ) ) { if ( this . props . onEndReached && ! this . _onEndReachedCalled ) { this . _onEndReachedCalled = true ; this . props . onEndReached ( ) ; } } else { this . _onEndReachedCalled = false ; } } } } } RecyclerListView . propTypes = { layoutProvider : PropTypes . instanceOf ( BaseLayoutProvider ) . isRequired , dataProvider : PropTypes . instanceOf ( BaseDataProvider ) . isRequired , contextProvider : PropTypes . instanceOf ( ContextProvider ) , rowRenderer : PropTypes . func . isRequired , initialOffset : PropTypes . number , renderAheadOffset : PropTypes . number , isHorizontal : PropTypes . bool , onScroll : PropTypes . func , onRecreate : PropTypes . func , externalScrollView : PropTypes . func , onEndReached : PropTypes . func , onEndReachedThreshold : PropTypes . number , onVisibleIndexesChanged : PropTypes . func , onVisibleIndicesChanged : PropTypes . func , renderFooter : PropTypes . func , initialRenderIndex : PropTypes . number , layoutSize : PropTypes . object , scrollThrottle : PropTypes . number , canChangeSize : PropTypes . bool , useWindowScroll : PropTypes . bool , disableRecycling : PropTypes . bool , forceNonDeterministicRendering : PropTypes . bool , extendedState : PropTypes . object , itemAnimator : PropTypes . instanceOf ( BaseItemAnimator ) , renderContentContainer : PropTypes . func , optimizeForInsertDeleteAnimations : PropTypes . bool , style : PropTypes . oneOfType ( [ PropTypes . object , PropTypes . number , ] ) , scrollViewProps : PropTypes . object , applyWindowCorrection : PropTypes . func , onItemLayout : PropTypes . func , } ;