UNPKG

@fullcalendar/web-component

Version:
361 lines (352 loc) 19.8 kB
import { jsx, jsxs, Fragment } from 'preact/jsx-runtime'; import { B as BaseComponent, f as afterSize, h as ViewContainer, g as generateClassName, s as setRef, d as getIsHeightAuto, j as getTableHeaderSticky, S as Scroller, k as getFooterScrollbarSticky, l as getScrollerSyncerClass, w as watchHeight, a as memoize, n as NowTimer } from './fa7e597f.js'; import { R as RefMap, c as computeTopFromDate, D as DayGridRows, a as Ruler, b as computeColWidth, d as DayGridHeaderRow, e as dayMicroWidth, f as DayTableSlicer, T as TableDateProfileGenerator, g as buildDayTableModel, h as buildDateRowConfigs, i as createDayHeaderFormatter } from './bd0d9fb1.js'; import { j as joinClassNames } from './423b7bc6.js'; import { createRef, createElement } from 'preact/compat'; import { I as computeViewBorderless, F as classNames } from './69b11357.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 }); } } class DayGridView extends BaseComponent { constructor() { super(...arguments); // memo this.buildDayTableModel = memoize(buildDayTableModel); this.buildDateRowConfigs = memoize(buildDateRowConfigs); this.createDayHeaderFormatter = memoize(createDayHeaderFormatter); // internal this.slicer = new DayTableSlicer(); } render() { const { props, context } = this; const { dateProfile } = props; const { options, dateEnv } = context; const dayTableModel = this.buildDayTableModel(dateProfile, context.dateProfileGenerator, dateEnv); const datesRepDistinctDays = dayTableModel.rowCount === 1; const dayHeaderFormat = this.createDayHeaderFormatter(context.options.dayHeaderFormat, datesRepDistinctDays, dayTableModel.colCount); const slicedProps = this.slicer.sliceProps(props, dateProfile, options.nextDayThreshold, context, dayTableModel); return (jsx(NowTimer, { unit: "day", children: (nowDate, todayRange) => { const headerTiers = this.buildDateRowConfigs(dayTableModel.headerDates, datesRepDistinctDays, dateProfile, todayRange, dayHeaderFormat, context); return (jsx(DayGridLayout, { labelId: props.labelId, labelStr: props.labelStr, dateProfile: dateProfile, todayRange: todayRange, cellRows: dayTableModel.cellRows, forPrint: props.forPrint, className: props.className, // header content headerTiers: headerTiers, // body content fgEventSegs: slicedProps.fgEventSegs, bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventDrag: slicedProps.eventDrag, eventResize: slicedProps.eventResize, eventSelection: slicedProps.eventSelection })); } })); } } var dayGridPlugin = { name: 'daygrid', initialView: 'dayGridMonth', views: { dayGrid: { component: DayGridView, dateProfileGeneratorClass: TableDateProfileGenerator, }, dayGridDay: { type: 'dayGrid', duration: { days: 1 }, }, dayGridWeek: { type: 'dayGrid', duration: { weeks: 1 }, }, dayGridMonth: { type: 'dayGrid', duration: { months: 1 }, fixedWeekCount: true, }, dayGridYear: { type: 'dayGrid', duration: { years: 1 }, }, }, }; export { FooterScrollbar as F, dayGridPlugin as d };