import { CssDimValue, AllDayContentArg } from '@fullcalendar/core' import { diffDays, SimpleScrollGridSection, SimpleScrollGrid, ChunkContentCallbackArgs, ScrollGridSectionConfig, buildNavLinkAttrs, ViewContainer, WeekNumberContainer, DateComponent, ViewProps, renderScrollShim, getStickyHeaderDates, getStickyFooterScrollbar, createFormatter, NowTimer, DateMarker, NowIndicatorContainer, ContentContainer, } from '@fullcalendar/core/internal' import { createElement, createRef, VNode, RefObject, ComponentChild, } from '@fullcalendar/core/preact' import { AllDaySplitter } from './AllDaySplitter.js' import { TimeSlatMeta } from './time-slat-meta.js' import { TimeColsSlatsCoords } from './TimeColsSlatsCoords.js' import { TimeBodyAxis } from './TimeBodyAxis.js' const DEFAULT_WEEK_NUM_FORMAT = createFormatter({ week: 'short' }) const AUTO_ALL_DAY_MAX_EVENT_ROWS = 5 /* An abstract class for all timegrid-related views. Displays one more columns with time slots running vertically. ----------------------------------------------------------------------------------------------------------------------*/ // Is a manager for the TimeCols subcomponent and possibly the DayGrid subcomponent (if allDaySlot is on). // Responsible for managing width/height. interface TimeColsViewState { slatCoords: TimeColsSlatsCoords | null } export abstract class TimeColsView extends DateComponent { protected allDaySplitter = new AllDaySplitter() // for use by subclasses protected headerElRef: RefObject = createRef() private rootElRef: RefObject = createRef() private scrollerElRef: RefObject = createRef() state = { slatCoords: null, } // rendering // ---------------------------------------------------------------------------------------------------- renderSimpleLayout( headerRowContent: VNode | null, allDayContent: ((contentArg: ChunkContentCallbackArgs) => VNode) | null, timeContent: ((contentArg: ChunkContentCallbackArgs) => VNode) | null, ) { let { context, props } = this let sections: SimpleScrollGridSection[] = [] let stickyHeaderDates = getStickyHeaderDates(context.options) if (headerRowContent) { sections.push({ type: 'header', key: 'header', isSticky: stickyHeaderDates, chunk: { elRef: this.headerElRef, tableClassName: 'fc-col-header', rowContent: headerRowContent, }, }) } if (allDayContent) { sections.push({ type: 'body', key: 'all-day', chunk: { content: allDayContent }, }) sections.push({ type: 'body', key: 'all-day-divider', outerContent: ( // TODO: rename to cellContent so don't need to define ? ), }) } sections.push({ type: 'body', key: 'body', liquid: true, expandRows: Boolean(context.options.expandRows), chunk: { scrollerElRef: this.scrollerElRef, content: timeContent, }, }) return ( ) } renderHScrollLayout( headerRowContent: VNode | null, allDayContent: ((contentArg: ChunkContentCallbackArgs) => VNode) | null, timeContent: ((contentArg: ChunkContentCallbackArgs) => VNode) | null, colCnt: number, dayMinWidth: number, slatMetas: TimeSlatMeta[], slatCoords: TimeColsSlatsCoords | null, // yuck ) { let ScrollGrid = this.context.pluginHooks.scrollGridImpl if (!ScrollGrid) { throw new Error('No ScrollGrid implementation') } let { context, props } = this let stickyHeaderDates = !props.forPrint && getStickyHeaderDates(context.options) let stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(context.options) let sections: ScrollGridSectionConfig[] = [] if (headerRowContent) { sections.push({ type: 'header', key: 'header', isSticky: stickyHeaderDates, syncRowHeights: true, chunks: [ { key: 'axis', rowContent: (arg: ChunkContentCallbackArgs) => ( {this.renderHeadAxis('day', arg.rowSyncHeights[0])} ), }, { key: 'cols', elRef: this.headerElRef, tableClassName: 'fc-col-header', rowContent: headerRowContent, }, ], }) } if (allDayContent) { sections.push({ type: 'body', key: 'all-day', syncRowHeights: true, chunks: [ { key: 'axis', rowContent: (contentArg: ChunkContentCallbackArgs) => ( {this.renderTableRowAxis(contentArg.rowSyncHeights[0])} ), }, { key: 'cols', content: allDayContent, }, ], }) sections.push({ key: 'all-day-divider', type: 'body', outerContent: ( // TODO: rename to cellContent so don't need to define ? ), }) } let isNowIndicator = context.options.nowIndicator sections.push({ type: 'body', key: 'body', liquid: true, expandRows: Boolean(context.options.expandRows), chunks: [ { key: 'axis', content: (arg) => ( // TODO: make this now-indicator arrow more DRY with TimeColsContent
{arg.tableColGroupNode}
{(nowDate: DateMarker) => { let nowIndicatorTop = isNowIndicator && slatCoords && slatCoords.safeComputeTop(nowDate) // might return void if (typeof nowIndicatorTop === 'number') { return ( ) } return null }}
), }, { key: 'cols', scrollerElRef: this.scrollerElRef, content: timeContent, }, ], }) if (stickyFooterScrollbar) { sections.push({ key: 'footer', type: 'footer', isSticky: true, chunks: [ { key: 'axis', content: renderScrollShim, }, { key: 'cols', content: renderScrollShim, }, ], }) } return ( ) } handleScrollTopRequest = (scrollTop: number) => { let scrollerEl = this.scrollerElRef.current if (scrollerEl) { // TODO: not sure how this could ever be null. weirdness with the reducer scrollerEl.scrollTop = scrollTop } } /* Dimensions ------------------------------------------------------------------------------------------------------------------*/ getAllDayMaxEventProps() { let { dayMaxEvents, dayMaxEventRows } = this.context.options if (dayMaxEvents === true || dayMaxEventRows === true) { // is auto? dayMaxEvents = undefined dayMaxEventRows = AUTO_ALL_DAY_MAX_EVENT_ROWS // make sure "auto" goes to a real number } return { dayMaxEvents, dayMaxEventRows } } /* Header Render Methods ------------------------------------------------------------------------------------------------------------------*/ renderHeadAxis = (rowKey: 'day' | string, frameHeight: CssDimValue = '') => { let { options } = this.context let { dateProfile } = this.props let range = dateProfile.renderRange let dayCnt = diffDays(range.start, range.end) // only do in day views (to avoid doing in week views that dont need it) let navLinkAttrs = (dayCnt === 1) ? buildNavLinkAttrs(this.context, range.start, 'week') : {} if (options.weekNumbers && rowKey === 'day') { return ( {(InnerContent) => (
)}
) } return (
) } /* Table Component Render Methods ------------------------------------------------------------------------------------------------------------------*/ // only a one-way height sync. we don't send the axis inner-content height to the DayGrid, // but DayGrid still needs to have classNames on inner elements in order to measure. renderTableRowAxis = (rowHeight?: number) => { let { options, viewApi } = this.context let renderProps: AllDayContentArg = { text: options.allDayText, view: viewApi, } return ( // TODO: make reusable hook. used in list view too {(InnerContent) => (
)}
) } handleSlatCoords = (slatCoords: TimeColsSlatsCoords) => { this.setState({ slatCoords }) } } function renderAllDayInner(renderProps: AllDayContentArg): ComponentChild { return renderProps.text }