import { VNode , createElement , Fragment } from '../vdom' ; import { BaseComponent , setRef } from '../vdom-util' ; import { Scroller , OverflowValue } from './Scroller' ; import { RefMap } from '../util/RefMap' ; import { ColProps , SectionConfig , renderMicroColGroup , computeShrinkWidth , getScrollGridClassNames , getSectionClassNames , getAllowYScrolling , renderChunkContent , getSectionHasLiquidHeight , ChunkConfig , hasShrinkWidth , CssDimValue , isColPropsEqual , } from './util' ; import { getCanVGrowWithinCell } from '../util/table-styling' ; import { memoize } from '../util/memoize' ; import { isPropsEqual } from '../util/object' ; import { getScrollbarWidths } from '../util/scrollbar-width' ; export interface SimpleScrollGridProps { cols : ColProps [ ] ; sections : SimpleScrollGridSection [ ] ; liquid : boolean ; collapsibleWidth : boolean ; height ? : CssDimValue ; } export interface SimpleScrollGridSection extends SectionConfig { key : string ; chunk ? : ChunkConfig ; } interface SimpleScrollGridState { shrinkWidth : number | null ; forceYScrollbars : boolean ; scrollerClientWidths : { [ key ] : number ; } ; scrollerClientHeights : { [ key ] : number ; } ; } export class SimpleScrollGrid extends BaseComponent < SimpleScrollGridProps , SimpleScrollGridState > { processCols = memoize ( ( a ) => a , isColPropsEqual ) ; renderMicroColGroup : typeof renderMicroColGroup = memoize ( renderMicroColGroup ) ; scrollerRefs = new RefMap < Scroller > ( ) ; scrollerElRefs = new RefMap < HTMLElement > ( this . _handleScrollerEl . bind ( this ) ) ; state : SimpleScrollGridState = { shrinkWidth : null , forceYScrollbars : false , scrollerClientWidths : { } , scrollerClientHeights : { } , } ; render ( ) { let { props , state , context } = this ; let sectionConfigs = props . sections || [ ] ; let cols = this . processCols ( props . cols ) ; let microColGroupNode = this . renderMicroColGroup ( cols , state . shrinkWidth ) ; let classNames = getScrollGridClassNames ( props . liquid , context ) ; if ( props . collapsibleWidth ) { classNames . push ( 'fc-scrollgrid-collapsible' ) ; } let configCnt = sectionConfigs . length ; let configI = 0 ; let currentConfig ; let headSectionNodes = [ ] ; let bodySectionNodes = [ ] ; let footSectionNodes = [ ] ; while ( configI < configCnt && ( currentConfig = sectionConfigs [ configI ] ) . type === 'header' ) { headSectionNodes . push ( this . renderSection ( currentConfig , microColGroupNode , true ) ) ; configI += 1 ; } while ( configI < configCnt && ( currentConfig = sectionConfigs [ configI ] ) . type === 'body' ) { bodySectionNodes . push ( this . renderSection ( currentConfig , microColGroupNode , false ) ) ; configI += 1 ; } while ( configI < configCnt && ( currentConfig = sectionConfigs [ configI ] ) . type === 'footer' ) { footSectionNodes . push ( this . renderSection ( currentConfig , microColGroupNode , true ) ) ; configI += 1 ; } let isBuggy = ! getCanVGrowWithinCell ( ) ; const roleAttrs = { role : 'rowgroup' } ; return createElement ( 'table' , { role : 'grid' , className : classNames . join ( ' ' ) , style : { height : props . height } , } , Boolean ( ! isBuggy && headSectionNodes . length ) && createElement ( 'thead' , roleAttrs , ... headSectionNodes ) , Boolean ( ! isBuggy && bodySectionNodes . length ) && createElement ( 'tbody' , roleAttrs , ... bodySectionNodes ) , Boolean ( ! isBuggy && footSectionNodes . length ) && createElement ( 'tfoot' , roleAttrs , ... footSectionNodes ) , isBuggy && createElement ( 'tbody' , roleAttrs , ... headSectionNodes , ... bodySectionNodes , ... footSectionNodes ) ) ; } renderSection ( sectionConfig , microColGroupNode , isHeader ) { if ( 'outerContent' in sectionConfig ) { return ( < Fragment key = { sectionConfig . key } > { sectionConfig . outerContent } < /Fragment>); } return ( < tr key = { sectionConfig . key } role = "presentation" className = { getSectionClassNames ( sectionConfig , this . props . liquid ) . join ( ' ' ) } > { this . renderChunkTd ( sectionConfig , microColGroupNode , sectionConfig . chunk , isHeader ) } < /tr>); } renderChunkTd ( sectionConfig , microColGroupNode , chunkConfig , isHeader ) { if ( 'outerContent' in chunkConfig ) { return chunkConfig . outerContent ; } let { props } = this ; let { forceYScrollbars , scrollerClientWidths , scrollerClientHeights } = this . state ; let needsYScrolling = getAllowYScrolling ( props , sectionConfig ) ; let isLiquid = getSectionHasLiquidHeight ( props , sectionConfig ) ; let overflowY = ! props . liquid ? 'visible' : forceYScrollbars ? 'scroll' : ! needsYScrolling ? 'hidden' : 'auto' ; let sectionKey = sectionConfig . key ; let content = renderChunkContent ( sectionConfig , chunkConfig , { tableColGroupNode : microColGroupNode , tableMinWidth : '' , clientWidth : ( ! props . collapsibleWidth && scrollerClientWidths [ sectionKey ] !== undefined ) ? scrollerClientWidths [ sectionKey ] : null , clientHeight : scrollerClientHeights [ sectionKey ] !== undefined ? scrollerClientHeights [ sectionKey ] : null , expandRows : sectionConfig . expandRows , syncRowHeights : false , rowSyncHeights : [ ] , reportRowHeightChange : ( ) => { } , } , isHeader ) ; return createElement ( isHeader ? 'th' : 'td' , { ref : chunkConfig . elRef as any , role : 'presentation' , } , < div className = { `fc-scroller-harness${ isLiquid ? ' fc-scroller-harness-liquid' : '' }` } > < Scroller ref = { this . scrollerRefs . createRef ( sectionKey ) } elRef = { this . scrollerElRefs . createRef ( sectionKey ) } overflowY = { overflowY } overflowX = { ! props . liquid ? 'visible' : 'hidden' } maxHeight = { sectionConfig . maxHeight } liquid = { isLiquid } liquidIsAbsolute > { content } < /Scroller> < /div>); } _handleScrollerEl ( scrollerEl , key ) { let section = getSectionByKey ( this . props . sections , key ) ; if ( section ) { setRef ( section . chunk . scrollerElRef , scrollerEl ) ; } } handleSizing = ( ) => { this . setState ( { shrinkWidth : this . computeShrinkWidth ( ) , ... this . computeScrollerDims ( ) , } ) ; } ; componentDidMount ( ) { this . handleSizing ( ) ; this . context . addResizeHandler ( this . handleSizing ) ; } componentDidUpdate ( ) { this . handleSizing ( ) ; } componentWillUnmount ( ) { this . context . removeResizeHandler ( this . handleSizing ) ; } computeShrinkWidth ( ) { return hasShrinkWidth ( this . props . cols ) ? computeShrinkWidth ( this . scrollerElRefs . getAll ( ) ) : 0 ; } computeScrollerDims ( ) { let scrollbarWidth = getScrollbarWidths ( ) ; let { scrollerRefs , scrollerElRefs } = this ; let forceYScrollbars = false ; let scrollerClientWidths = { } ; let scrollerClientHeights = { } ; for ( let sectionKey in scrollerRefs . currentMap ) { let scroller = scrollerRefs . currentMap [ sectionKey ] ; if ( scroller && scroller . needsYScrolling ( ) ) { forceYScrollbars = true ; break ; } } for ( let section of this . props . sections ) { let sectionKey = section . key ; let scrollerEl = scrollerElRefs . currentMap [ sectionKey ] ; if ( scrollerEl ) { let harnessEl = scrollerEl . parentNode as HTMLElement ; scrollerClientWidths [ sectionKey ] = Math . floor ( harnessEl . getBoundingClientRect ( ) . width - ( forceYScrollbars ? scrollbarWidth . y : 0 ) ) ; scrollerClientHeights [ sectionKey ] = Math . floor ( harnessEl . getBoundingClientRect ( ) . height ) ; } } return { forceYScrollbars , scrollerClientWidths , scrollerClientHeights } ; } } SimpleScrollGrid . addStateEquality ( { scrollerClientWidths : isPropsEqual , scrollerClientHeights : isPropsEqual , } ) ; function getSectionByKey ( sections , key ) { for ( let section of sections ) { if ( section . key === key ) { return section ; } } return null ; }