fullcalendar
Version:
FullCalendar Vanilla JS package for rendering a calendar
305 lines (298 loc) • 17.1 kB
JavaScript
import { jsx, jsxs, Fragment } from 'preact/jsx-runtime';
import { B as BaseComponent, S as Scroller, w as watchHeight, s as setRef, o as afterSize, f as ViewContainer, g as generateClassName, c as getIsHeightAuto, v as getTableHeaderSticky, x as getFooterScrollbarSticky, y as getScrollerSyncerClass } from './66c53e04.js';
import { e as RefMap, i as computeTopFromDate, g as DayGridRows, R as Ruler, j as computeColWidth, f as DayGridHeaderRow, h as dayMicroWidth } from './515d1f26.js';
import { j as joinClassNames } from './69261bb4.js';
import { createRef, createElement } from 'preact/compat';
import { c as classNames } from './4a45af02.js';
import { J as computeViewBorderless } from './2add5508.js';
/*
TODO: kill this class in favor of DayGridHeaderRows?
*/
class DayGridHeader extends BaseComponent {
render() {
const { props } = this;
const { headerTiers } = props;
return (jsx("div", { role: 'rowgroup', className: joinClassNames(props.className, classNames.flexCol, props.width == null && classNames.liquid), style: {
width: props.width,
}, children: headerTiers.map((rowConfig, i) => (createElement(DayGridHeaderRow, { ...rowConfig, key: i, role: 'row', borderBottom: i < headerTiers.length - 1, colWidth: props.colWidth, viewportWidth: props.viewportWidth, cellIsNarrow: props.cellIsNarrow, cellIsMicro: props.cellIsMicro, rowLevel: headerTiers.length - i - 1 }))) }));
}
}
class DayGridLayoutNormal extends BaseComponent {
constructor() {
super(...arguments);
this.state = {};
this.handleScroller = (scroller) => {
setRef(this.props.scrollerRef, scroller);
};
this.handleTotalWidth = (totalWidth) => {
if (this._isUnmounting)
return;
this.setState({ totalWidth });
};
this.handleClientWidth = (clientWidth) => {
if (this._isUnmounting)
return;
this.setState({ clientWidth });
};
}
render() {
const { props, state, context } = this;
const { options } = context;
const { borderlessX, borderlessTop, borderlessBottom } = computeViewBorderless(options);
const { totalWidth, clientWidth } = state;
let endScrollbarWidth = (totalWidth != null && clientWidth != null)
? totalWidth - clientWidth
: undefined;
// HACK when clientWidth does NOT include body-border, compared to totalWidth
if (endScrollbarWidth < 3) {
endScrollbarWidth = 0;
}
const verticalScrollbars = !props.forPrint && !getIsHeightAuto(options);
const tableHeaderSticky = !props.forPrint && getTableHeaderSticky(options);
const colCount = props.cellRows[0].length;
const cellWidth = clientWidth != null ? clientWidth / colCount : undefined;
const cellIsMicro = cellWidth != null && cellWidth <= dayMicroWidth;
const cellIsNarrow = cellIsMicro || (cellWidth != null && cellWidth <= options.dayNarrowWidth);
return (jsxs(Fragment, { children: [options.dayHeaders && (jsxs("div", { className: joinClassNames(generateClassName(options.tableHeaderClass, {
isSticky: tableHeaderSticky,
borderlessX,
borderlessTop,
borderlessBottom,
multiMonthColumns: 0,
}), classNames.printHeader, // either flexCol or table-header-group
tableHeaderSticky && classNames.tableHeaderSticky), children: [jsxs("div", { className: classNames.flexRow, children: [jsx(DayGridHeader, { headerTiers: props.headerTiers, cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro }), Boolean(endScrollbarWidth) && (jsx("div", { className: joinClassNames(generateClassName(options.fillerClass, { inTableHeader: true }), classNames.borderOnlyS), style: { minWidth: endScrollbarWidth } }))] }), jsx("div", { className: generateClassName(options.dayHeaderDividerClass, {
isSticky: tableHeaderSticky,
multiMonthColumns: 0,
options: { allDaySlot: Boolean(options.allDaySlot) },
}) })] })), jsx(Scroller, { vertical: verticalScrollbars, className: joinClassNames(generateClassName(options.tableBodyClass, {
borderlessX,
borderlessTop,
borderlessBottom,
multiMonthColumns: 0,
}),
// HACK for Safari. Can't do break-inside:avoid with flexbox items, likely b/c it's not standard:
// https://stackoverflow.com/a/60256345
!props.forPrint && classNames.flexCol, verticalScrollbars && classNames.liquid), ref: this.handleScroller, clientWidthRef: this.handleClientWidth, children: jsx(DayGridRows, { dateProfile: props.dateProfile, todayRange: props.todayRange, cellRows: props.cellRows, forPrint: props.forPrint, isHitComboAllowed: props.isHitComboAllowed, className: classNames.grow, dayMaxEvents: props.forPrint ? undefined : options.dayMaxEvents, dayMaxEventRows: options.dayMaxEventRows,
// content
fgEventSegs: props.fgEventSegs, bgEventSegs: props.bgEventSegs, businessHourSegs: props.businessHourSegs, dateSelectionSegs: props.dateSelectionSegs, eventDrag: props.eventDrag, eventResize: props.eventResize, eventSelection: props.eventSelection,
// dimensions
visibleWidth: totalWidth, cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro,
// refs
rowHeightRefMap: props.rowHeightRefMap }) }), jsx(Ruler, { widthRef: this.handleTotalWidth })] }));
}
componentDidMount() {
this._isUnmounting = false;
}
componentWillUnmount() {
this._isUnmounting = true;
}
}
class FooterScrollbar extends BaseComponent {
constructor() {
super(...arguments);
this.rootElRef = createRef();
}
render() {
const { props } = this;
// NOTE: we need a wrapper around the Scroller because if scrollbars appear/hide,
// the outer dimensions change, but the inner dimensions do not. The Scroller's
// dimension-watching, when used in ponyfill-mode, can't fire on border-box change, so we
// workaround it by monitoring dimensions of a wrapper instead
return (jsx("div", { ref: this.rootElRef, className: joinClassNames(classNames.footerScrollbar, props.isSticky && classNames.footerScrollbarSticky), children: jsx(Scroller, { horizontal: true, ref: props.scrollerRef, children: jsx("div", { style: { minWidth: props.canvasWidth } }) }) }));
}
componentDidMount() {
this._isUnmounting = false;
this.disconnectHeight = watchHeight(this.rootElRef.current, (height) => {
if (this._isUnmounting)
return;
setRef(this.props.scrollbarWidthRef, height);
});
}
componentWillUnmount() {
this._isUnmounting = true;
this.disconnectHeight();
setRef(this.props.scrollbarWidthRef, null);
}
}
class DayGridLayoutPannable extends BaseComponent {
constructor() {
super(...arguments);
this.state = {};
this.headerScrollerRef = createRef();
this.bodyScrollerRef = createRef();
this.footerScrollerRef = createRef();
// Sizing
// -----------------------------------------------------------------------------------------------
this.handleTotalWidth = (totalWidth) => {
if (this._isUnmounting)
return;
this.setState({ totalWidth });
};
this.handleClientWidth = (clientWidth) => {
if (this._isUnmounting)
return;
this.setState({ clientWidth });
};
}
render() {
const { props, state, context } = this;
const { options } = context;
const { borderlessX, borderlessTop, borderlessBottom } = computeViewBorderless(options);
const { totalWidth, clientWidth } = state;
const endScrollbarWidth = (totalWidth != null && clientWidth != null)
? totalWidth - clientWidth
: undefined;
const verticalScrollbars = !props.forPrint && !getIsHeightAuto(options);
const tableHeaderSticky = !props.forPrint && getTableHeaderSticky(options);
const footerScrollbarSticky = !props.forPrint && getFooterScrollbarSticky(options);
const colCount = props.cellRows[0].length;
const [canvasWidth, colWidth] = computeColWidth(colCount, props.dayMinWidth, clientWidth);
const cellIsMicro = colWidth != null && colWidth <= dayMicroWidth;
const cellIsNarrow = cellIsMicro || (colWidth != null && colWidth <= options.dayNarrowWidth);
return (jsxs(Fragment, { children: [options.dayHeaders && (jsxs("div", { className: joinClassNames(generateClassName(options.tableHeaderClass, {
isSticky: tableHeaderSticky,
borderlessX,
borderlessTop,
borderlessBottom,
multiMonthColumns: 0,
}), classNames.printHeader, // either flexCol or table-header-group
tableHeaderSticky && classNames.tableHeaderSticky), children: [jsxs(Scroller, { horizontal: true, hideScrollbars: true, className: classNames.flexRow, ref: this.headerScrollerRef, children: [jsx(DayGridHeader, { headerTiers: props.headerTiers, colWidth: colWidth, viewportWidth: clientWidth, width: canvasWidth, cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro }), Boolean(endScrollbarWidth) && (jsx("div", { className: joinClassNames(generateClassName(options.fillerClass, { inTableHeader: true }), classNames.borderOnlyS), style: { minWidth: endScrollbarWidth } }))] }), jsx("div", { className: generateClassName(options.dayHeaderDividerClass, {
isSticky: tableHeaderSticky,
multiMonthColumns: 0,
options: { allDaySlot: Boolean(options.allDaySlot) },
}) })] })), jsx(Scroller, { vertical: verticalScrollbars, horizontal: true, hideScrollbars: footerScrollbarSticky ||
props.forPrint // prevents blank space in print-view on Safari
, className: joinClassNames(generateClassName(options.tableBodyClass, {
borderlessX,
borderlessTop,
borderlessBottom,
multiMonthColumns: 0,
}),
// HACK for Safari. Can't do break-inside:avoid with flexbox items, likely b/c it's not standard:
// https://stackoverflow.com/a/60256345
!props.forPrint && classNames.flexCol, verticalScrollbars && classNames.liquid), ref: this.bodyScrollerRef, clientWidthRef: this.handleClientWidth, children: jsx(DayGridRows, { dateProfile: props.dateProfile, todayRange: props.todayRange, cellRows: props.cellRows, forPrint: props.forPrint, isHitComboAllowed: props.isHitComboAllowed, className: classNames.grow, dayMaxEvents: props.forPrint ? undefined : options.dayMaxEvents, dayMaxEventRows: options.dayMaxEventRows,
// content
fgEventSegs: props.fgEventSegs, bgEventSegs: props.bgEventSegs, businessHourSegs: props.businessHourSegs, dateSelectionSegs: props.dateSelectionSegs, eventDrag: props.eventDrag, eventResize: props.eventResize, eventSelection: props.eventSelection,
// dimensions
colWidth: colWidth, width: canvasWidth, visibleWidth: totalWidth, cellIsNarrow: cellIsNarrow, cellIsMicro: cellIsMicro,
// refs
rowHeightRefMap: props.rowHeightRefMap }) }), Boolean(footerScrollbarSticky) && (jsx(FooterScrollbar, { isSticky: true, canvasWidth: canvasWidth, scrollerRef: this.footerScrollerRef })), jsx(Ruler, { widthRef: this.handleTotalWidth })] }));
}
// Lifecycle
// -----------------------------------------------------------------------------------------------
componentDidMount() {
this._isUnmounting = false;
// scroller
const ScrollerSyncer = getScrollerSyncerClass(this.context.pluginHooks);
this.syncedScroller = new ScrollerSyncer(true); // horizontal=true
setRef(this.props.scrollerRef, this.syncedScroller);
this.updateSyncedScroller();
}
componentDidUpdate() {
// scroller
this.updateSyncedScroller();
}
componentWillUnmount() {
this._isUnmounting = true;
// scroller
this.syncedScroller.destroy();
}
// Scrolling
// -----------------------------------------------------------------------------------------------
updateSyncedScroller() {
this.syncedScroller.handleChildren([
this.headerScrollerRef.current,
this.bodyScrollerRef.current,
this.footerScrollerRef.current,
]);
}
}
class DayGridLayout extends BaseComponent {
constructor() {
super(...arguments);
// ref
this.scrollerRef = createRef();
this.rowHeightRefMap = new RefMap(() => {
afterSize(this.updateScrollY);
});
this.scrollDate = null;
this.updateScrollY = () => {
if (this._isUnmounting)
return;
const rowHeightMap = this.rowHeightRefMap.current;
const scroller = this.scrollerRef.current;
// Since updateScrollY is called by rowHeightRefMap, could be called with null during cleanup,
// and the scroller might not exist
if (scroller && this.scrollDate) {
let scrollTop = computeTopFromDate(this.scrollDate, this.props.cellRows, rowHeightMap);
if (scrollTop != null) {
if (scrollTop) {
scrollTop++; // clear top border
}
scroller.scrollTo({ y: scrollTop });
}
}
};
this.handleScrollEnd = (isDevice) => {
if (isDevice) {
this.scrollDate = null;
}
};
}
render() {
const { props, context } = this;
const { options } = context;
const { borderlessX, borderlessTop, borderlessBottom } = computeViewBorderless(options);
const businessHourSegs = props.forPrint ? [] : props.businessHourSegs;
const dateSelectionSegs = props.forPrint ? [] : props.dateSelectionSegs;
const eventDrag = props.forPrint ? null : props.eventDrag;
const eventResize = props.forPrint ? null : props.eventResize;
const commonLayoutProps = {
...props,
businessHourSegs,
dateSelectionSegs,
eventDrag,
eventResize,
scrollerRef: this.scrollerRef,
rowHeightRefMap: this.rowHeightRefMap,
};
return (jsx(ViewContainer, { viewSpec: context.viewSpec, attrs: {
role: 'grid',
'aria-rowcount': props.headerTiers.length + props.cellRows.length,
'aria-colcount': props.cellRows[0].length,
'aria-labelledby': props.labelId,
'aria-label': props.labelStr,
}, className: joinClassNames(props.className, classNames.printRoot, // either flexCol or table
generateClassName(options.tableClass, {
borderlessX,
borderlessTop,
borderlessBottom,
multiMonthColumns: 0,
})), children: options.dayMinWidth ? (jsx(DayGridLayoutPannable, { ...commonLayoutProps, dayMinWidth: options.dayMinWidth })) : (jsx(DayGridLayoutNormal, { ...commonLayoutProps })) }));
}
// Lifecycle
// -----------------------------------------------------------------------------------------------
componentDidMount() {
this._isUnmounting = false;
this.resetScroll();
this.scrollerRef.current.addScrollEndListener(this.handleScrollEnd);
}
componentDidUpdate(prevProps) {
if (prevProps.dateProfile !== this.props.dateProfile && this.context.options.scrollTimeReset) {
this.resetScroll();
}
}
componentWillUnmount() {
this._isUnmounting = true;
this.scrollerRef.current.removeScrollEndListener(this.handleScrollEnd);
}
// Scrolling
// -----------------------------------------------------------------------------------------------
resetScroll() {
this.scrollDate = this.props.dateProfile.currentDate;
this.updateScrollY();
const scroller = this.scrollerRef.current;
scroller.scrollTo({ x: 0 });
}
}
export { DayGridLayout as D, FooterScrollbar as F };