/**
 * Created by ananya.chandra on 14/09/18.
 */ import * as React from "react" ; import * as PropTypes from "prop-types" ; import { StyleProp , View , ViewStyle } from "react-native" ; import RecyclerListView , { RecyclerListViewState , RecyclerListViewProps } from "./RecyclerListView" ; import { ScrollEvent } from "./scrollcomponent/BaseScrollView" ; import StickyObject , { StickyObjectProps } from "./sticky/StickyObject" ; import StickyHeader from "./sticky/StickyHeader" ; import StickyFooter from "./sticky/StickyFooter" ; import CustomError from "./exceptions/CustomError" ; import RecyclerListViewExceptions from "./exceptions/RecyclerListViewExceptions" ; import { Layout } from "./layoutmanager/LayoutManager" ; import { BaseLayoutProvider , Dimension } from "./dependencies/LayoutProvider" ; import { BaseDataProvider } from "./dependencies/DataProvider" ; import { ReactElement } from "react" ; import { ComponentCompat } from "../utils/ComponentCompat" ; import { WindowCorrection } from "./ViewabilityTracker" ; export interface StickyContainerProps { children : RecyclerChild ; stickyHeaderIndices ? : number [ ] ; stickyFooterIndices ? : number [ ] ; overrideRowRenderer ? : ( type : string | number | undefined , data : any , index : number , extendedState ? : object ) => JSX . Element | JSX . Element [ ] | null ; applyWindowCorrection ? : ( offsetX : number , offsetY : number , winowCorrection : WindowCorrection ) => void ; renderStickyContainer ? : ( stickyContent : JSX . Element , index : number , extendedState ? : object ) => JSX . Element | null ; style ? : StyleProp < ViewStyle > ; alwaysStickyFooter ? : boolean ; } export interface RecyclerChild extends React . ReactElement < RecyclerListViewProps > { ref : ( recyclerRef : any ) => { } ; props : RecyclerListViewProps ; } export default class StickyContainer < P extends StickyContainerProps > extends ComponentCompat < P > { public static propTypes = { } ; private _recyclerRef : RecyclerListView < RecyclerListViewProps , RecyclerListViewState > | undefined = undefined ; private _dataProvider : BaseDataProvider ; private _layoutProvider : BaseLayoutProvider ; private _extendedState : object | undefined ; private _rowRenderer : ( ( type : string | number , data : any , index : number , extendedState ? : object ) => JSX . Element | JSX . Element [ ] | null ) ; private _stickyHeaderRef : StickyHeader < StickyObjectProps > | null = null ; private _stickyFooterRef : StickyFooter < StickyObjectProps > | null = null ; private _visibleIndicesAll : number [ ] = [ ] ; private _windowCorrection : WindowCorrection = { startCorrection : 0 , endCorrection : 0 , windowShift : 0 , } ; constructor ( props , context ? ) { super ( props , context ) ; this . _assertChildType ( ) ; const childProps = props . children . props ; this . _dataProvider = childProps . dataProvider ; this . _layoutProvider = childProps . layoutProvider ; this . _extendedState = childProps . extendedState ; this . _rowRenderer = childProps . rowRenderer ; this . _getWindowCorrection ( 0 , 0 , props ) ; } public componentWillReceivePropsCompat ( newProps ) { this . _initParams ( newProps ) ; } public renderCompat ( ) { this . _assertChildType ( ) ; const recycler = React . cloneElement ( this . props . children , { ... this . props . children . props , ref : this . _getRecyclerRef , onVisibleIndicesChanged : this . _onVisibleIndicesChanged , onScroll : this . _onScroll , applyWindowCorrection : this . _applyWindowCorrection , rowRenderer : this . _rlvRowRenderer , } ) ; return ( < View style = { this . props . style ? this . props . style : { flex : 1 } } > { recycler } { this . props . stickyHeaderIndices ? ( < StickyHeader ref = { ( stickyHeaderRef ) => this . _getStickyHeaderRef ( stickyHeaderRef ) } stickyIndices = { this . props . stickyHeaderIndices } getLayoutForIndex = { this . _getLayoutForIndex } getDataForIndex = { this . _getDataForIndex } getLayoutTypeForIndex = { this . _getLayoutTypeForIndex } getExtendedState = { this . _getExtendedState } getRLVRenderedSize = { this . _getRLVRenderedSize } getContentDimension = { this . _getContentDimension } getRowRenderer = { this . _getRowRenderer } overrideRowRenderer = { this . props . overrideRowRenderer } renderContainer = { this . props . renderStickyContainer } getWindowCorrection = { this . _getCurrentWindowCorrection } / > ) : null } { this . props . stickyFooterIndices ? ( < StickyFooter ref = { ( stickyFooterRef ) => this . _getStickyFooterRef ( stickyFooterRef ) } stickyIndices = { this . props . stickyFooterIndices } getLayoutForIndex = { this . _getLayoutForIndex } getDataForIndex = { this . _getDataForIndex } getLayoutTypeForIndex = { this . _getLayoutTypeForIndex } getExtendedState = { this . _getExtendedState } getRLVRenderedSize = { this . _getRLVRenderedSize } getContentDimension = { this . _getContentDimension } getRowRenderer = { this . _getRowRenderer } overrideRowRenderer = { this . props . overrideRowRenderer } renderContainer = { this . props . renderStickyContainer } getWindowCorrection = { this . _getCurrentWindowCorrection } alwaysStickBottom = { this . props . alwaysStickyFooter } / > ) : null } < /View>); } private _rlvRowRenderer = ( type , data , index , extendedState ? ) : JSX . Element | JSX . Element [ ] | null => { if ( this . props . alwaysStickyFooter ) { const rlvDimension = this . _getRLVRenderedSize ( ) ; const contentDimension = this . _getContentDimension ( ) ; let isScrollable = false ; if ( rlvDimension && contentDimension ) { isScrollable = contentDimension . height > rlvDimension . height ; } if ( ! isScrollable && this . props . stickyFooterIndices && index === this . props . stickyFooterIndices [ 0 ] ) { return null ; } } return this . _rowRenderer ( type , data , index , extendedState ) ; } ; private _getRecyclerRef = ( recycler ) => { this . _recyclerRef = recycler as ( RecyclerListView < RecyclerListViewProps , RecyclerListViewState > | undefined ) ; if ( this . props . children . ref ) { if ( typeof this . props . children . ref === "function" ) { ( this . props . children ) . ref ( recycler ) ; } else { throw new CustomError ( RecyclerListViewExceptions . refNotAsFunctionException ) ; } } } ; private _getCurrentWindowCorrection = ( ) : WindowCorrection => { return this . _windowCorrection ; } ; private _getStickyHeaderRef = ( stickyHeaderRef ) => { if ( this . _stickyHeaderRef !== stickyHeaderRef ) { this . _stickyHeaderRef = stickyHeaderRef as ( StickyHeader < StickyObjectProps > | null ) ; this . _callStickyObjectsOnVisibleIndicesChanged ( this . _visibleIndicesAll ) ; } } ; private _getStickyFooterRef = ( stickyFooterRef ) => { if ( this . _stickyFooterRef !== stickyFooterRef ) { this . _stickyFooterRef = stickyFooterRef as ( StickyFooter < StickyObjectProps > | null ) ; this . _callStickyObjectsOnVisibleIndicesChanged ( this . _visibleIndicesAll ) ; } } ; private _onVisibleIndicesChanged = ( all , now , notNow ) => { this . _visibleIndicesAll = all ; this . _callStickyObjectsOnVisibleIndicesChanged ( all ) ; if ( this . props . children && this . props . children . props && this . props . children . props . onVisibleIndicesChanged ) { this . props . children . props . onVisibleIndicesChanged ( all , now , notNow ) ; } } ; private _callStickyObjectsOnVisibleIndicesChanged = ( all ) => { if ( this . _stickyHeaderRef ) { this . _stickyHeaderRef . onVisibleIndicesChanged ( all ) ; } if ( this . _stickyFooterRef ) { this . _stickyFooterRef . onVisibleIndicesChanged ( all ) ; } } ; private _onScroll = ( rawEvent , offsetX , offsetY ) => { this . _getWindowCorrection ( offsetX , offsetY , this . props ) ; if ( this . _stickyHeaderRef ) { this . _stickyHeaderRef . onScroll ( offsetY ) ; } if ( this . _stickyFooterRef ) { this . _stickyFooterRef . onScroll ( offsetY ) ; } if ( this . props . children && this . props . children . props . onScroll ) { this . props . children . props . onScroll ( rawEvent , offsetX , offsetY ) ; } } ; private _getWindowCorrection ( offsetX , offsetY , props ) { return ( props . applyWindowCorrection && props . applyWindowCorrection ( offsetX , offsetY , this . _windowCorrection ) ) || this . _windowCorrection ; } private _assertChildType = ( ) : void => { if ( React . Children . count ( this . props . children ) !== 1 || ! this . _isChildRecyclerInstance ( ) ) { throw new CustomError ( RecyclerListViewExceptions . wrongStickyChildTypeException ) ; } } ; private _isChildRecyclerInstance = ( ) : boolean => { return ( this . props . children . props . dataProvider && this . props . children . props . rowRenderer && this . props . children . props . layoutProvider ) ; } ; private _getLayoutForIndex = ( index ) : Layout | undefined => { if ( this . _recyclerRef ) { return this . _recyclerRef . getLayout ( index ) ; } return undefined ; } ; private _getDataForIndex = ( index ) : any => { return this . _dataProvider . getDataForIndex ( index ) ; } ; private _getLayoutTypeForIndex = ( index ) : string | number => { return this . _layoutProvider . getLayoutTypeForIndex ( index ) ; } ; private _getExtendedState = ( ) : object | undefined => { return this . _extendedState ; } ; private _getRowRenderer = ( ) : ( ( type : string | number , data : any , index : number , extendedState ? : object ) => JSX . Element | JSX . Element [ ] | null ) => { return this . _rowRenderer ; } ; private _getRLVRenderedSize = ( ) : Dimension | undefined => { if ( this . _recyclerRef ) { return this . _recyclerRef . getRenderedSize ( ) ; } return undefined ; } ; private _getContentDimension = ( ) : Dimension | undefined => { if ( this . _recyclerRef ) { return this . _recyclerRef . getContentDimension ( ) ; } return undefined ; } ; private _applyWindowCorrection = ( offsetX , offsetY , windowCorrection ) : void => { if ( this . props . applyWindowCorrection ) { this . props . applyWindowCorrection ( offsetX , offsetY , windowCorrection ) ; } } ; private _initParams = ( props ) => { const childProps = props . children . props ; this . _dataProvider = childProps . dataProvider ; this . _layoutProvider = childProps . layoutProvider ; this . _extendedState = childProps . extendedState ; this . _rowRenderer = childProps . rowRenderer ; } ; } StickyContainer . propTypes = { children : PropTypes . element . isRequired , stickyHeaderIndices : PropTypes . arrayOf ( PropTypes . number ) , stickyFooterIndices : PropTypes . arrayOf ( PropTypes . number ) , overrideRowRenderer : PropTypes . func , style : PropTypes . object , renderStickyContainer : PropTypes . func , applyWindowCorrection : PropTypes . func , } ;