import { EventApi } from '../api/EventApi' ; import { Seg } from '../component/DateComponent' ; import { DateRange } from '../datelib/date-range' ; import { addDays , DateMarker } from '../datelib/marker' ; import { DateProfile } from '../DateProfileGenerator' ; import { Dictionary } from '../options' ; import { elementClosest , getUniqueDomId } from '../util/dom-manip' ; import { formatWithOrdinals } from '../util/misc' ; import { ComponentChildren , createElement , createRef , Fragment , Ref , RefObject , VNode } from '../vdom' ; import { BaseComponent } from '../vdom-util' ; import { ViewApi } from '../ViewApi' ; import { ViewContext , ViewContextType } from '../ViewContext' ; import { MorePopover } from './MorePopover' ; import { MountArg , RenderHook } from './render-hook' ; export type MoreLinkChildren = ( rootElRef : Ref < any > , classNames : string [ ] , innerElRef : Ref < any > , innerContent : ComponentChildren , handleClick : ( ev : MouseEvent ) => void , title : string , isExpanded : boolean , popoverId : string ) => ComponentChildren ; export interface MoreLinkRootProps { dateProfile : DateProfile ; todayRange : DateRange ; allDayDate : DateMarker | null ; moreCnt : number ; allSegs : Seg [ ] ; hiddenSegs : Seg [ ] ; extraDateSpan ? : Dictionary ; alignmentElRef : RefObject < HTMLElement > ; alignGridTop ? : boolean ; topAlignmentElRef ? : RefObject < HTMLElement > ; defaultContent ? : ( hookProps : MoreLinkContentArg ) => ComponentChildren ; popoverContent : ( ) => VNode ; children : MoreLinkChildren ; } export interface MoreLinkContentArg { num : number ; text : string ; shortText : string ; view : ViewApi ; } export type MoreLinkMountArg = MountArg < MoreLinkContentArg > ; interface MoreLinkRootState { isPopoverOpen : boolean ; popoverId : string ; } export class MoreLinkRoot extends BaseComponent < MoreLinkRootProps , MoreLinkRootState > { private linkElRef = createRef < HTMLElement > ( ) ; private parentEl : HTMLElement ; state = { isPopoverOpen : false , popoverId : getUniqueDomId ( ) , } ; render ( ) { let { props , state } = this ; return ( < ViewContextType . Consumer > { ( context ) => { let { viewApi , options , calendarApi } = context ; let { moreLinkText } = options ; let { moreCnt } = props ; let range = computeRange ( props ) ; let text = typeof moreLinkText === 'function' ? moreLinkText . call ( calendarApi , moreCnt ) : `+${ moreCnt } ${ moreLinkText }` ; let title = formatWithOrdinals ( options . moreLinkHint , [ moreCnt ] , text ) ; let hookProps = { num : moreCnt , shortText : `+${ moreCnt }` , text , view : viewApi , } ; return ( < Fragment > { Boolean ( props . moreCnt ) && ( < RenderHook < MoreLinkContentArg > elRef = { this . linkElRef } hookProps = { hookProps } classNames = { options . moreLinkClassNames } content = { options . moreLinkContent } defaultContent = { props . defaultContent || renderMoreLinkInner } didMount = { options . moreLinkDidMount } willUnmount = { options . moreLinkWillUnmount } > { ( rootElRef , customClassNames , innerElRef , innerContent ) => props . children ( rootElRef , [ 'fc-more-link' ] . concat ( customClassNames ) , innerElRef , innerContent , this . handleClick , title , state . isPopoverOpen , state . isPopoverOpen ? state . popoverId : '' ) } < /RenderHook>)} { state . isPopoverOpen && ( < MorePopover id = { state . popoverId } startDate = { range . start } endDate = { range . end } dateProfile = { props . dateProfile } todayRange = { props . todayRange } extraDateSpan = { props . extraDateSpan } parentEl = { this . parentEl } alignmentEl = { props . alignmentElRef . current } alignGridTop = { props . alignGridTop } onClose = { this . handlePopoverClose } > { props . popoverContent ( ) } < /MorePopover>)} < /Fragment>); } } < /ViewContextType.Consumer>); } componentDidMount ( ) { this . updateParentEl ( ) ; } componentDidUpdate ( ) { this . updateParentEl ( ) ; } updateParentEl ( ) { if ( this . linkElRef . current ) { this . parentEl = elementClosest ( this . linkElRef . current , '.fc-view-harness' ) ; } } handleClick = ( ev ) => { let { props , context } = this ; let { moreLinkClick } = context . options ; let date = computeRange ( props ) . start ; function buildPublicSeg ( seg ) { let { def , instance , range } = seg . eventRange ; return { event : new EventApi ( context , def , instance ) , start : context . dateEnv . toDate ( range . start ) , end : context . dateEnv . toDate ( range . end ) , isStart : seg . isStart , isEnd : seg . isEnd , } ; } if ( typeof moreLinkClick === 'function' ) { moreLinkClick = moreLinkClick ( { date , allDay : Boolean ( props . allDayDate ) , allSegs : props . allSegs . map ( buildPublicSeg ) , hiddenSegs : props . hiddenSegs . map ( buildPublicSeg ) , jsEvent : ev , view : context . viewApi , } ) as string | undefined ; } if ( ! moreLinkClick || moreLinkClick === 'popover' ) { this . setState ( { isPopoverOpen : true } ) ; } else if ( typeof moreLinkClick === 'string' ) { context . calendarApi . zoomTo ( date , moreLinkClick ) ; } } ; handlePopoverClose = ( ) => { this . setState ( { isPopoverOpen : false } ) ; } ; } function renderMoreLinkInner ( props ) { return props . text ; } function computeRange ( props ) { if ( props . allDayDate ) { return { start : props . allDayDate , end : addDays ( props . allDayDate , 1 ) , } ; } let { hiddenSegs } = props ; return { start : computeEarliestSegStart ( hiddenSegs ) , end : computeLatestSegEnd ( hiddenSegs ) , } ; } export function computeEarliestSegStart ( segs ) { return segs . reduce ( pickEarliestStart ) . eventRange . range . start ; } function pickEarliestStart ( seg0 , seg1 ) { return seg0 . eventRange . range . start < seg1 . eventRange . range . start ? seg0 : seg1 ; } function computeLatestSegEnd ( segs ) { return segs . reduce ( pickLatestEnd ) . eventRange . range . end ; } function pickLatestEnd ( seg0 , seg1 ) { return seg0 . eventRange . range . end > seg1 . eventRange . range . end ? seg0 : seg1 ; }