@fullcalendar/timeline
Advanced tools
| 'use strict'; | ||
| Object.defineProperty(exports, '__esModule', { value: true }); | ||
| var core = require('@fullcalendar/core'); | ||
| var premiumCommonPlugin = require('@fullcalendar/premium-common'); | ||
| var internalCommon = require('./internal.cjs'); | ||
| var internal = require('@fullcalendar/core/internal'); | ||
| require('@fullcalendar/core/internal-classnames'); | ||
| require('@fullcalendar/core/preact'); | ||
| require('@fullcalendar/scrollgrid/internal'); | ||
| function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } | ||
| var premiumCommonPlugin__default = /*#__PURE__*/_interopDefaultLegacy(premiumCommonPlugin); | ||
| const OPTION_REFINERS = { | ||
| timelineTopClass: internal.identity, | ||
| timelineBottomClass: internal.identity, | ||
| }; | ||
| var index = core.createPlugin({ | ||
| name: '@fullcalendar/timeline', | ||
| premiumReleaseDate: '2025-12-20', | ||
| deps: [premiumCommonPlugin__default["default"]], | ||
| initialView: 'timelineDay', | ||
| optionRefiners: OPTION_REFINERS, | ||
| views: { | ||
| timeline: { | ||
| component: internalCommon.TimelineView, | ||
| usesMinMaxTime: true, | ||
| eventResizableFromStart: true, // how is this consumed for TimelineView tho? | ||
| }, | ||
| timelineDay: { | ||
| type: 'timeline', | ||
| duration: { days: 1 }, | ||
| }, | ||
| timelineWeek: { | ||
| type: 'timeline', | ||
| duration: { weeks: 1 }, | ||
| }, | ||
| timelineMonth: { | ||
| type: 'timeline', | ||
| duration: { months: 1 }, | ||
| }, | ||
| timelineYear: { | ||
| type: 'timeline', | ||
| duration: { years: 1 }, | ||
| }, | ||
| }, | ||
| }); | ||
| exports["default"] = index; |
+1224
| 'use strict'; | ||
| Object.defineProperty(exports, '__esModule', { value: true }); | ||
| var core = require('@fullcalendar/core'); | ||
| var internal = require('@fullcalendar/core/internal'); | ||
| var classNames = require('@fullcalendar/core/internal-classnames'); | ||
| var preact = require('@fullcalendar/core/preact'); | ||
| var internal$1 = require('@fullcalendar/scrollgrid/internal'); | ||
| function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } | ||
| var classNames__default = /*#__PURE__*/_interopDefaultLegacy(classNames); | ||
| const MIN_AUTO_LABELS = 18; // more than `12` months but less that `24` hours | ||
| const MAX_AUTO_SLOTS_PER_LABEL = 6; // allows 6 10-min slots in an hour | ||
| const MAX_AUTO_CELLS = 200; // allows 4-days to have a :30 slot duration | ||
| internal.config.MAX_TIMELINE_SLOTS = 1000; | ||
| // potential nice values for slot-duration and interval-duration | ||
| const STOCK_SUB_DURATIONS = [ | ||
| { years: 1 }, | ||
| { months: 1 }, | ||
| { days: 1 }, | ||
| { hours: 1 }, | ||
| { minutes: 30 }, | ||
| { minutes: 15 }, | ||
| { minutes: 10 }, | ||
| { minutes: 5 }, | ||
| { minutes: 1 }, | ||
| { seconds: 30 }, | ||
| { seconds: 15 }, | ||
| { seconds: 10 }, | ||
| { seconds: 5 }, | ||
| { seconds: 1 }, | ||
| { milliseconds: 500 }, | ||
| { milliseconds: 100 }, | ||
| { milliseconds: 10 }, | ||
| { milliseconds: 1 }, | ||
| ]; | ||
| function buildTimelineDateProfile(dateProfile, dateEnv, allOptions, dateProfileGenerator) { | ||
| let tDateProfile = { | ||
| labelInterval: allOptions.slotHeaderInterval, | ||
| slotDuration: allOptions.slotDuration, | ||
| }; | ||
| validateLabelAndSlot(tDateProfile, dateProfile, dateEnv); // validate after computed grid duration | ||
| ensureLabelInterval(tDateProfile, dateProfile, dateEnv); | ||
| ensureSlotDuration(tDateProfile, dateProfile, dateEnv); | ||
| let input = allOptions.slotHeaderFormat; | ||
| let rawFormats = Array.isArray(input) ? input : | ||
| (input != null) ? [input] : | ||
| computeHeaderFormats(tDateProfile, dateProfile, dateEnv, allOptions); | ||
| tDateProfile.headerFormats = rawFormats.map((rawFormat) => internal.createFormatter(rawFormat)); | ||
| tDateProfile.isTimeScale = Boolean(tDateProfile.slotDuration.milliseconds); | ||
| let largeUnit = null; | ||
| if (!tDateProfile.isTimeScale) { | ||
| const slotUnit = internal.greatestDurationDenominator(tDateProfile.slotDuration).unit; | ||
| if (/year|month|week/.test(slotUnit)) { | ||
| largeUnit = slotUnit; | ||
| } | ||
| } | ||
| tDateProfile.largeUnit = largeUnit; | ||
| /* | ||
| console.log('label interval =', timelineView.labelInterval.humanize()) | ||
| console.log('slot duration =', timelineView.slotDuration.humanize()) | ||
| console.log('header formats =', timelineView.headerFormats) | ||
| console.log('isTimeScale', timelineView.isTimeScale) | ||
| console.log('largeUnit', timelineView.largeUnit) | ||
| */ | ||
| let rawSnapDuration = allOptions.snapDuration; | ||
| let snapDuration; | ||
| let snapsPerSlot; | ||
| if (rawSnapDuration) { | ||
| snapDuration = internal.createDuration(rawSnapDuration); | ||
| snapsPerSlot = internal.wholeDivideDurations(tDateProfile.slotDuration, snapDuration); | ||
| // ^ TODO: warning if not whole? | ||
| } | ||
| if (snapsPerSlot == null) { | ||
| snapDuration = tDateProfile.slotDuration; | ||
| snapsPerSlot = 1; | ||
| } | ||
| tDateProfile.snapDuration = snapDuration; | ||
| tDateProfile.snapsPerSlot = snapsPerSlot; | ||
| // more... | ||
| let timeWindowMs = internal.asRoughMs(dateProfile.slotMaxTime) - internal.asRoughMs(dateProfile.slotMinTime); | ||
| // TODO: why not use normalizeRange!? | ||
| let normalizedStart = normalizeDate(dateProfile.renderRange.start, tDateProfile, dateEnv); | ||
| let normalizedEnd = normalizeDate(dateProfile.renderRange.end, tDateProfile, dateEnv); | ||
| // apply slotMinTime/slotMaxTime | ||
| // TODO: View should be responsible. | ||
| if (tDateProfile.isTimeScale) { | ||
| normalizedStart = dateEnv.add(normalizedStart, dateProfile.slotMinTime); | ||
| normalizedEnd = dateEnv.add(internal.addDays(normalizedEnd, -1), dateProfile.slotMaxTime); | ||
| } | ||
| tDateProfile.timeWindowMs = timeWindowMs; | ||
| tDateProfile.normalizedRange = { start: normalizedStart, end: normalizedEnd }; | ||
| let slotDates = []; | ||
| let slotDatesMajor = []; | ||
| let date = normalizedStart; | ||
| let majorUnit = internal.computeMajorUnit(dateProfile, dateEnv); | ||
| while (date < normalizedEnd) { | ||
| if (isValidDate(date, tDateProfile, dateProfile, dateProfileGenerator)) { | ||
| slotDates.push(date); | ||
| slotDatesMajor.push(internal.isMajorUnit(date, majorUnit, dateEnv)); | ||
| } | ||
| date = dateEnv.add(date, tDateProfile.slotDuration); | ||
| } | ||
| tDateProfile.slotDates = slotDates; | ||
| tDateProfile.slotDatesMajor = slotDatesMajor; | ||
| // more... | ||
| let snapIndex = -1; | ||
| let snapDiff = 0; // index of the diff :( | ||
| const snapDiffToIndex = []; | ||
| const snapIndexToDiff = []; | ||
| date = normalizedStart; | ||
| while (date < normalizedEnd) { | ||
| if (isValidDate(date, tDateProfile, dateProfile, dateProfileGenerator)) { | ||
| snapIndex += 1; | ||
| snapDiffToIndex.push(snapIndex); | ||
| snapIndexToDiff.push(snapDiff); | ||
| } | ||
| else { | ||
| snapDiffToIndex.push(snapIndex + 0.5); | ||
| } | ||
| date = dateEnv.add(date, tDateProfile.snapDuration); | ||
| snapDiff += 1; | ||
| } | ||
| tDateProfile.snapDiffToIndex = snapDiffToIndex; | ||
| tDateProfile.snapIndexToDiff = snapIndexToDiff; | ||
| tDateProfile.snapCnt = snapIndex + 1; // is always one behind | ||
| tDateProfile.slotCnt = tDateProfile.snapCnt / tDateProfile.snapsPerSlot; | ||
| // more... | ||
| tDateProfile.cellRows = buildCellRows(tDateProfile, dateEnv, majorUnit); | ||
| tDateProfile.slotsPerLabel = internal.wholeDivideDurations(tDateProfile.labelInterval, tDateProfile.slotDuration); | ||
| return tDateProfile; | ||
| } | ||
| /* | ||
| snaps to appropriate unit | ||
| */ | ||
| function normalizeDate(date, tDateProfile, dateEnv) { | ||
| let normalDate = date; | ||
| if (!tDateProfile.isTimeScale) { | ||
| normalDate = internal.startOfDay(normalDate); | ||
| if (tDateProfile.largeUnit) { | ||
| normalDate = dateEnv.startOf(normalDate, tDateProfile.largeUnit); | ||
| } | ||
| } | ||
| return normalDate; | ||
| } | ||
| /* | ||
| snaps to appropriate unit | ||
| */ | ||
| function normalizeRange(range, tDateProfile, dateEnv) { | ||
| if (!tDateProfile.isTimeScale) { | ||
| range = internal.computeVisibleDayRange(range); | ||
| if (tDateProfile.largeUnit) { | ||
| let dayRange = range; // preserve original result | ||
| range = { | ||
| start: dateEnv.startOf(range.start, tDateProfile.largeUnit), | ||
| end: dateEnv.startOf(range.end, tDateProfile.largeUnit), | ||
| }; | ||
| // if date is partially through the interval, or is in the same interval as the start, | ||
| // make the exclusive end be the *next* interval | ||
| if (range.end.valueOf() !== dayRange.end.valueOf() || range.end <= range.start) { | ||
| range = { | ||
| start: range.start, | ||
| end: dateEnv.add(range.end, tDateProfile.slotDuration), | ||
| }; | ||
| } | ||
| } | ||
| } | ||
| return range; | ||
| } | ||
| function isValidDate(date, tDateProfile, dateProfile, dateProfileGenerator) { | ||
| if (dateProfileGenerator.isHiddenDay(date)) { | ||
| return false; | ||
| } | ||
| if (tDateProfile.isTimeScale) { | ||
| // determine if the time is within slotMinTime/slotMaxTime, which may have wacky values | ||
| let day = internal.startOfDay(date); | ||
| let timeMs = date.valueOf() - day.valueOf(); | ||
| let ms = timeMs - internal.asRoughMs(dateProfile.slotMinTime); // milliseconds since slotMinTime | ||
| ms = ((ms % 86400000) + 86400000) % 86400000; // make negative values wrap to 24hr clock | ||
| return ms < tDateProfile.timeWindowMs; // before the slotMaxTime? | ||
| } | ||
| return true; | ||
| } | ||
| function validateLabelAndSlot(tDateProfile, dateProfile, dateEnv) { | ||
| const { currentRange } = dateProfile; | ||
| // make sure labelInterval doesn't exceed the max number of cells | ||
| if (tDateProfile.labelInterval) { | ||
| const labelCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, tDateProfile.labelInterval); | ||
| if (labelCnt > internal.config.MAX_TIMELINE_SLOTS) { | ||
| console.warn('slotHeaderInterval results in too many cells'); | ||
| tDateProfile.labelInterval = null; | ||
| } | ||
| } | ||
| // make sure slotDuration doesn't exceed the maximum number of cells | ||
| if (tDateProfile.slotDuration) { | ||
| const slotCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, tDateProfile.slotDuration); | ||
| if (slotCnt > internal.config.MAX_TIMELINE_SLOTS) { | ||
| console.warn('slotDuration results in too many cells'); | ||
| tDateProfile.slotDuration = null; | ||
| } | ||
| } | ||
| // make sure labelInterval is a multiple of slotDuration | ||
| if (tDateProfile.labelInterval && tDateProfile.slotDuration) { | ||
| const slotsPerLabel = internal.wholeDivideDurations(tDateProfile.labelInterval, tDateProfile.slotDuration); | ||
| if (slotsPerLabel === null || slotsPerLabel < 1) { | ||
| console.warn('slotHeaderInterval must be a multiple of slotDuration'); | ||
| tDateProfile.slotDuration = null; | ||
| } | ||
| } | ||
| } | ||
| function ensureLabelInterval(tDateProfile, dateProfile, dateEnv) { | ||
| const { currentRange } = dateProfile; | ||
| let { labelInterval } = tDateProfile; | ||
| if (!labelInterval) { | ||
| // compute based off the slot duration | ||
| // find the largest label interval with an acceptable slots-per-label | ||
| let input; | ||
| if (tDateProfile.slotDuration) { | ||
| for (input of STOCK_SUB_DURATIONS) { | ||
| const tryLabelInterval = internal.createDuration(input); | ||
| const slotsPerLabel = internal.wholeDivideDurations(tryLabelInterval, tDateProfile.slotDuration); | ||
| if (slotsPerLabel !== null && slotsPerLabel <= MAX_AUTO_SLOTS_PER_LABEL) { | ||
| labelInterval = tryLabelInterval; | ||
| break; | ||
| } | ||
| } | ||
| // use the slot duration as a last resort | ||
| if (!labelInterval) { | ||
| labelInterval = tDateProfile.slotDuration; | ||
| } | ||
| // compute based off the view's duration | ||
| // find the largest label interval that yields the minimum number of labels | ||
| } | ||
| else { | ||
| for (input of STOCK_SUB_DURATIONS) { | ||
| labelInterval = internal.createDuration(input); | ||
| const labelCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, labelInterval); | ||
| if (labelCnt >= MIN_AUTO_LABELS) { | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| tDateProfile.labelInterval = labelInterval; | ||
| } | ||
| return labelInterval; | ||
| } | ||
| function ensureSlotDuration(tDateProfile, dateProfile, dateEnv) { | ||
| const { currentRange } = dateProfile; | ||
| let { slotDuration } = tDateProfile; | ||
| if (!slotDuration) { | ||
| const labelInterval = ensureLabelInterval(tDateProfile, dateProfile, dateEnv); // will compute if necessary | ||
| // compute based off the label interval | ||
| // find the largest slot duration that is different from labelInterval, but still acceptable | ||
| for (let input of STOCK_SUB_DURATIONS) { | ||
| const trySlotDuration = internal.createDuration(input); | ||
| const slotsPerLabel = internal.wholeDivideDurations(labelInterval, trySlotDuration); | ||
| if (slotsPerLabel !== null && slotsPerLabel > 1 && slotsPerLabel <= MAX_AUTO_SLOTS_PER_LABEL) { | ||
| slotDuration = trySlotDuration; | ||
| break; | ||
| } | ||
| } | ||
| // only allow the value if it won't exceed the view's # of slots limit | ||
| if (slotDuration) { | ||
| const slotCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, slotDuration); | ||
| if (slotCnt > MAX_AUTO_CELLS) { | ||
| slotDuration = null; | ||
| } | ||
| } | ||
| // use the label interval as a last resort | ||
| if (!slotDuration) { | ||
| slotDuration = labelInterval; | ||
| } | ||
| tDateProfile.slotDuration = slotDuration; | ||
| } | ||
| return slotDuration; | ||
| } | ||
| function computeHeaderFormats(tDateProfile, dateProfile, dateEnv, allOptions) { | ||
| let format1; | ||
| let format2; | ||
| const { labelInterval } = tDateProfile; | ||
| const { currentRange } = dateProfile; | ||
| let unit = internal.greatestDurationDenominator(labelInterval).unit; | ||
| const weekNumbersVisible = allOptions.weekNumbers; | ||
| let format0 = (format1 = (format2 = null)); | ||
| // NOTE: weekNumber computation function wont work | ||
| if ((unit === 'week') && !weekNumbersVisible) { | ||
| unit = 'day'; | ||
| } | ||
| switch (unit) { | ||
| case 'year': | ||
| format0 = { year: 'numeric' }; // '2015' | ||
| break; | ||
| case 'month': | ||
| if (dateEnv.diffWholeYears(currentRange.start, currentRange.end) > 1) { | ||
| format0 = { year: 'numeric' }; // '2015' | ||
| } | ||
| format1 = { month: 'short' }; // 'Jan' | ||
| break; | ||
| case 'week': | ||
| if (dateEnv.diffWholeYears(currentRange.start, currentRange.end) > 1) { | ||
| format0 = { year: 'numeric' }; // '2015' | ||
| } | ||
| format1 = { week: 'narrow' }; // 'Wk4' | ||
| break; | ||
| case 'day': | ||
| if (dateEnv.diffWholeYears(currentRange.start, currentRange.end) > 1) { | ||
| format0 = { year: 'numeric', month: 'long' }; // 'January 2014' | ||
| } | ||
| else if (dateEnv.diffWholeMonths(currentRange.start, currentRange.end) > 1) { | ||
| format0 = { month: 'long' }; // 'January' | ||
| } | ||
| if (weekNumbersVisible) { | ||
| format1 = { week: 'short' }; // 'Wk 4' | ||
| } | ||
| format2 = { weekday: 'narrow', day: 'numeric' }; // 'Su 9' | ||
| break; | ||
| case 'hour': | ||
| if (weekNumbersVisible) { | ||
| format0 = { week: 'short' }; // 'Wk 4' | ||
| } | ||
| if (internal.diffWholeDays(currentRange.start, currentRange.end) > 1) { | ||
| format1 = { weekday: 'short', day: 'numeric', month: 'numeric', omitCommas: true }; // Sat 4/7 | ||
| } | ||
| format2 = { | ||
| hour: 'numeric', | ||
| minute: '2-digit', | ||
| omitZeroMinute: true, | ||
| meridiem: 'short', | ||
| }; | ||
| break; | ||
| case 'minute': | ||
| // sufficiently large number of different minute cells? | ||
| if ((internal.asRoughMinutes(labelInterval) / 60) >= MAX_AUTO_SLOTS_PER_LABEL) { | ||
| format0 = { | ||
| hour: 'numeric', | ||
| meridiem: 'short', | ||
| }; | ||
| format1 = (params) => (':' + internal.padStart(params.date.minute, 2) // ':30' | ||
| ); | ||
| } | ||
| else { | ||
| format0 = { | ||
| hour: 'numeric', | ||
| minute: 'numeric', | ||
| meridiem: 'short', | ||
| }; | ||
| } | ||
| break; | ||
| case 'second': | ||
| // sufficiently large number of different second cells? | ||
| if ((internal.asRoughSeconds(labelInterval) / 60) >= MAX_AUTO_SLOTS_PER_LABEL) { | ||
| format0 = { hour: 'numeric', minute: '2-digit', meridiem: 'lowercase' }; // '8:30 PM' | ||
| format1 = (params) => (':' + internal.padStart(params.date.second, 2) // ':30' | ||
| ); | ||
| } | ||
| else { | ||
| format0 = { hour: 'numeric', minute: '2-digit', second: '2-digit', meridiem: 'lowercase' }; // '8:30:45 PM' | ||
| } | ||
| break; | ||
| case 'millisecond': | ||
| format0 = { hour: 'numeric', minute: '2-digit', second: '2-digit', meridiem: 'lowercase' }; // '8:30:45 PM' | ||
| format1 = (params) => ('.' + internal.padStart(params.millisecond, 3)); | ||
| break; | ||
| } | ||
| return [].concat(format0 || [], format1 || [], format2 || []); | ||
| } | ||
| function buildCellRows(tDateProfile, dateEnv, majorUnit) { | ||
| let slotDates = tDateProfile.slotDates; | ||
| let formats = tDateProfile.headerFormats; | ||
| let cellRows = formats.map(() => []); // indexed by row,col | ||
| let slotAsDays = internal.asCleanDays(tDateProfile.slotDuration); | ||
| let guessedSlotUnit = slotAsDays === 7 ? 'week' : | ||
| slotAsDays === 1 ? 'day' : | ||
| null; | ||
| // specifically for navclicks | ||
| let rowUnitsFromFormats = formats.map((format) => (format.getSmallestUnit ? format.getSmallestUnit() : null)); | ||
| // builds cellRows and slotCells | ||
| for (let i = 0; i < slotDates.length; i += 1) { | ||
| let date = slotDates[i]; | ||
| for (let row = 0; row < formats.length; row += 1) { | ||
| let format = formats[row]; | ||
| let rowCells = cellRows[row]; | ||
| let leadingCell = rowCells[rowCells.length - 1]; | ||
| let isLastRow = row === formats.length - 1; | ||
| let isSuperRow = formats.length > 1 && !isLastRow; // more than one row and not the last | ||
| let isMajor = internal.isMajorUnit(date, majorUnit, dateEnv); | ||
| let newCell = null; | ||
| let rowUnit = rowUnitsFromFormats[row] || (isLastRow ? guessedSlotUnit : null); | ||
| if (isSuperRow) { | ||
| let [text] = dateEnv.format(date, format); | ||
| if (!leadingCell || (leadingCell.text !== text)) { | ||
| newCell = buildCellObject(date, isMajor, text, rowUnit); | ||
| } | ||
| else { | ||
| leadingCell.colspan += 1; | ||
| } | ||
| } | ||
| else if (!leadingCell || | ||
| internal.isInt(dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.labelInterval))) { | ||
| let [text] = dateEnv.format(date, format); | ||
| newCell = buildCellObject(date, isMajor, text, rowUnit); | ||
| } | ||
| else { | ||
| leadingCell.colspan += 1; | ||
| } | ||
| if (newCell) { | ||
| rowCells.push(newCell); | ||
| } | ||
| } | ||
| } | ||
| return cellRows; | ||
| } | ||
| function buildCellObject(date, isMajor, text, rowUnit) { | ||
| return { date, isMajor, text, rowUnit, colspan: 1 }; // colspan mutated later | ||
| } | ||
| class TimelineSlatCell extends internal.BaseComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // memo | ||
| this.getDateMeta = internal.memoize(internal.getDateMeta); | ||
| } | ||
| render() { | ||
| let { props, context } = this; | ||
| let { dateEnv, options } = context; | ||
| let { date, tDateProfile, isMajor } = props; | ||
| let dateMeta = this.getDateMeta(props.date, dateEnv, props.dateProfile, props.todayRange, props.nowDate); | ||
| let isMinor = tDateProfile.isTimeScale && | ||
| !internal.isInt(dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, props.date, tDateProfile.labelInterval)); | ||
| let renderProps = Object.assign(Object.assign({}, dateMeta), { isMajor, | ||
| isMinor, view: context.viewApi }); | ||
| return (preact.createElement(internal.ContentContainer, { tag: "div", className: core.joinClassNames(classNames__default["default"].tight, classNames__default["default"].alignStart, // shrinks width of InnerContent | ||
| props.borderStart ? classNames__default["default"].borderOnlyS : classNames__default["default"].borderNone, classNames__default["default"].internalTimelineSlot), attrs: Object.assign({ 'data-date': dateEnv.formatIso(date, { | ||
| omitTimeZoneOffset: true, | ||
| omitTime: !tDateProfile.isTimeScale, | ||
| }) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: { | ||
| width: props.width, | ||
| }, renderProps: renderProps, generatorName: undefined, classNameGenerator: options.slotLaneClass, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount })); | ||
| } | ||
| } | ||
| class TimelineSlats extends internal.BaseComponent { | ||
| render() { | ||
| let { props } = this; | ||
| let { tDateProfile, slotWidth } = props; | ||
| let { slotDates, slotDatesMajor } = tDateProfile; | ||
| return (preact.createElement("div", { "aria-hidden": true, className: core.joinClassNames(classNames__default["default"].flexRow, classNames__default["default"].fill), style: { height: props.height } }, slotDates.map((slotDate, i) => { | ||
| let key = slotDate.toISOString(); | ||
| return (preact.createElement(TimelineSlatCell, { key: key, date: slotDate, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isMajor: slotDatesMajor[i], borderStart: Boolean(i), | ||
| // dimensions | ||
| width: slotWidth })); | ||
| }))); | ||
| } | ||
| } | ||
| class TimelineHeaderCell extends internal.BaseComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // memo | ||
| this.getDateMeta = internal.memoize(internal.getDateMeta); | ||
| // ref | ||
| this.innerWrapperElRef = preact.createRef(); | ||
| } | ||
| render() { | ||
| let { props, state, context } = this; | ||
| let { dateEnv, options } = context; | ||
| let { cell, dateProfile, tDateProfile } = props; | ||
| // the cell.rowUnit is f'd | ||
| // giving 'month' for a 3-day view | ||
| // workaround: to infer day, do NOT time | ||
| let dateMeta = this.getDateMeta(cell.date, dateEnv, dateProfile, props.todayRange, props.nowDate); | ||
| let hasNavLink = options.navLinks && !dateMeta.isDisabled && (cell.rowUnit && cell.rowUnit !== 'time'); | ||
| let isTime = tDateProfile.isTimeScale && !props.rowLevel; // HACK: faulty way of determining this | ||
| let renderProps = Object.assign(Object.assign({}, dateMeta), { level: props.rowLevel, isMajor: cell.isMajor, isMinor: false, isNarrow: false, isTime, | ||
| hasNavLink, text: cell.text, isFirst: props.isFirst, view: context.viewApi }); | ||
| const { slotHeaderAlign } = options; | ||
| const align = this.align = | ||
| typeof slotHeaderAlign === 'function' | ||
| ? slotHeaderAlign({ level: props.rowLevel, isTime }) | ||
| : slotHeaderAlign; | ||
| const isSticky = this.isSticky = | ||
| props.rowLevel && options.slotHeaderSticky !== false; | ||
| let edgeCoord; | ||
| if (isSticky) { | ||
| if (align === 'center') { | ||
| if (state.innerWidth != null) { | ||
| edgeCoord = `calc(50% - ${state.innerWidth / 2}px)`; | ||
| } | ||
| } | ||
| else { | ||
| edgeCoord = (typeof options.slotHeaderSticky === 'number' || | ||
| typeof options.slotHeaderSticky === 'string') ? options.slotHeaderSticky : 0; | ||
| } | ||
| } | ||
| return (preact.createElement(internal.ContentContainer, { tag: "div", className: internal.joinArrayishClassNames(classNames__default["default"].tight, classNames__default["default"].flexCol, props.isFirst ? classNames__default["default"].borderNone : classNames__default["default"].borderOnlyS, align === 'center' ? classNames__default["default"].alignCenter : | ||
| align === 'end' ? classNames__default["default"].alignEnd : | ||
| classNames__default["default"].alignStart, classNames__default["default"].internalTimelineSlot), attrs: Object.assign({ 'data-date': dateEnv.formatIso(cell.date, { | ||
| omitTime: !tDateProfile.isTimeScale, | ||
| omitTimeZoneOffset: true, | ||
| }) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: { | ||
| width: props.slotWidth != null | ||
| ? props.slotWidth * cell.colspan | ||
| : undefined, | ||
| }, renderProps: renderProps, generatorName: "slotHeaderContent", customGenerator: options.slotHeaderContent, defaultGenerator: renderInnerContent, classNameGenerator: options.slotHeaderClass, didMount: options.slotHeaderDidMount, willUnmount: options.slotHeaderWillUnmount }, (InnerContent) => (preact.createElement("div", { ref: this.innerWrapperElRef, className: core.joinClassNames(classNames__default["default"].flexCol, classNames__default["default"].rigid, isSticky && classNames__default["default"].sticky), style: { | ||
| left: edgeCoord, | ||
| right: edgeCoord, | ||
| } }, | ||
| preact.createElement(InnerContent, { tag: 'div', attrs: hasNavLink | ||
| // not tabbable because parent is aria-hidden | ||
| ? internal.buildNavLinkAttrs(context, cell.date, cell.rowUnit, undefined, /* isTabbable = */ false) | ||
| : {} // don't bother with aria-hidden because parent already hidden | ||
| , className: internal.generateClassName(options.slotHeaderInnerClass, renderProps) }))))); | ||
| } | ||
| componentDidMount() { | ||
| const { props } = this; | ||
| const innerWrapperEl = this.innerWrapperElRef.current; // TODO: make dynamic with useEffect | ||
| this.disconnectSize = internal.watchSize(innerWrapperEl, (width, height) => { | ||
| internal.setRef(props.innerWidthRef, width); | ||
| internal.setRef(props.innerHeightRef, height); | ||
| if (this.align === 'center' && this.isSticky) { | ||
| this.setState({ innerWidth: width }); | ||
| } | ||
| }); | ||
| } | ||
| componentWillUnmount() { | ||
| const { props } = this; | ||
| this.disconnectSize(); | ||
| internal.setRef(props.innerWidthRef, null); | ||
| internal.setRef(props.innerHeightRef, null); | ||
| } | ||
| } | ||
| // Utils | ||
| // ------------------------------------------------------------------------------------------------- | ||
| function renderInnerContent(renderProps) { | ||
| return renderProps.text; | ||
| } | ||
| class TimelineHeaderRow extends internal.BaseComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // refs | ||
| this.innerWidthRefMap = new internal.RefMap(() => { | ||
| internal.afterSize(this.handleInnerWidths); | ||
| }); | ||
| this.innerHeightRefMap = new internal.RefMap(() => { | ||
| internal.afterSize(this.handleInnerHeights); | ||
| }); | ||
| this.handleInnerWidths = () => { | ||
| const innerWidthMap = this.innerWidthRefMap.current; | ||
| let max = 0; | ||
| for (const innerWidth of innerWidthMap.values()) { | ||
| max = Math.max(max, innerWidth); | ||
| } | ||
| // TODO: ensure not equal? | ||
| internal.setRef(this.props.innerWidthRef, max); | ||
| }; | ||
| this.handleInnerHeights = () => { | ||
| const innerHeightMap = this.innerHeightRefMap.current; | ||
| let max = 0; | ||
| for (const innerHeight of innerHeightMap.values()) { | ||
| max = Math.max(max, innerHeight); | ||
| } | ||
| // TODO: ensure not equal? | ||
| internal.setRef(this.props.innerHeighRef, max); | ||
| this.setState({ innerHeight: max }); | ||
| }; | ||
| } | ||
| render() { | ||
| const { props, innerWidthRefMap, innerHeightRefMap, state, context } = this; | ||
| const { options } = context; | ||
| return (preact.createElement("div", { className: internal.joinArrayishClassNames(options.slotHeaderRowClass, classNames__default["default"].flexRow, classNames__default["default"].grow, props.rowLevel // not the last row? | ||
| ? classNames__default["default"].borderOnlyB | ||
| : classNames__default["default"].borderNone), style: { | ||
| // we assign height because we allow cells to have distorted heights for visual effect | ||
| // but we still want to keep the overall extrenal mass | ||
| height: state.innerHeight, | ||
| } }, props.cells.map((cell, cellI) => { | ||
| // TODO: make this part of the cell obj? | ||
| // TODO: rowUnit seems wrong sometimes. says 'month' when it should be day | ||
| // TODO: rowUnit is relevant to whole row. put it on a row object, not the cells | ||
| // TODO: use rowUnit to key the Row itself? | ||
| const key = cell.rowUnit + ':' + cell.date.toISOString(); | ||
| return (preact.createElement(TimelineHeaderCell, { key: key, cell: cell, rowLevel: props.rowLevel, dateProfile: props.dateProfile, tDateProfile: props.tDateProfile, todayRange: props.todayRange, nowDate: props.nowDate, isFirst: cellI === 0, | ||
| // refs | ||
| innerWidthRef: innerWidthRefMap.createRef(key), innerHeightRef: innerHeightRefMap.createRef(key), | ||
| // dimensions | ||
| slotWidth: props.slotWidth })); | ||
| }))); | ||
| } | ||
| componentWillUnmount() { | ||
| internal.setRef(this.props.innerWidthRef, null); | ||
| internal.setRef(this.props.innerHeighRef, null); | ||
| } | ||
| } | ||
| // Timeline-specific | ||
| // ------------------------------------------------------------------------------------------------- | ||
| const MIN_SLOT_WIDTH = 30; // for real | ||
| /* | ||
| TODO: DRY with computeSlatHeight? | ||
| */ | ||
| function computeSlotWidth(slatCnt, slatsPerLabel, slatMinWidth, labelInnerWidth, viewportWidth) { | ||
| if (labelInnerWidth == null || viewportWidth == null) { | ||
| return [undefined, undefined, false]; | ||
| } | ||
| slatMinWidth = Math.max(slatMinWidth || 0, (labelInnerWidth + 1) / slatsPerLabel, MIN_SLOT_WIDTH); | ||
| const slatTryWidth = viewportWidth / slatCnt; | ||
| let slotLiquid; | ||
| let slatWidth; | ||
| if (slatTryWidth >= slatMinWidth) { | ||
| slotLiquid = true; | ||
| slatWidth = slatTryWidth; | ||
| } | ||
| else { | ||
| slotLiquid = false; | ||
| slatWidth = Math.max(slatMinWidth, slatTryWidth); | ||
| } | ||
| return [slatWidth * slatCnt, slatWidth, slotLiquid]; | ||
| } | ||
| function timeToCoord(// pixels | ||
| time, dateEnv, dateProfile, tDateProfile, slowWidth) { | ||
| let date = dateEnv.add(dateProfile.activeRange.start, time); | ||
| if (!tDateProfile.isTimeScale) { | ||
| date = internal.startOfDay(date); | ||
| } | ||
| return dateToCoord(date, dateEnv, tDateProfile, slowWidth); | ||
| } | ||
| function dateToCoord(// pixels | ||
| date, dateEnv, tDateProfile, slotWidth) { | ||
| let snapCoverage = computeDateSnapCoverage$1(date, tDateProfile, dateEnv); | ||
| let slotCoverage = snapCoverage / tDateProfile.snapsPerSlot; | ||
| return slotCoverage * slotWidth; | ||
| } | ||
| /* | ||
| returned value is between 0 and the number of snaps | ||
| */ | ||
| function computeDateSnapCoverage$1(date, tDateProfile, dateEnv) { | ||
| let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration); | ||
| if (snapDiff < 0) { | ||
| return 0; | ||
| } | ||
| if (snapDiff >= tDateProfile.snapDiffToIndex.length) { | ||
| return tDateProfile.snapCnt; | ||
| } | ||
| let snapDiffInt = Math.floor(snapDiff); | ||
| let snapCoverage = tDateProfile.snapDiffToIndex[snapDiffInt]; | ||
| if (internal.isInt(snapCoverage)) { // not an in-between value | ||
| snapCoverage += snapDiff - snapDiffInt; // add the remainder | ||
| } | ||
| else { | ||
| // a fractional value, meaning the date is not visible | ||
| // always round up in this case. works for start AND end dates in a range. | ||
| snapCoverage = Math.ceil(snapCoverage); | ||
| } | ||
| return snapCoverage; | ||
| } | ||
| class TimelineNowIndicatorLine extends internal.BaseComponent { | ||
| render() { | ||
| const { props, context } = this; | ||
| const xStyle = props.slotWidth == null | ||
| ? {} | ||
| : { | ||
| insetInlineStart: dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth) | ||
| }; | ||
| return (preact.createElement("div", { className: classNames__default["default"].fill, style: { | ||
| zIndex: 2, | ||
| pointerEvents: 'none', // TODO: className | ||
| } }, | ||
| preact.createElement(internal.NowIndicatorLineContainer, { className: core.joinClassNames(classNames__default["default"].fillY, classNames__default["default"].noMarginY, classNames__default["default"].borderlessY), style: xStyle, date: props.nowDate }), | ||
| preact.createElement("div", { className: core.joinClassNames(classNames__default["default"].flexCol, // better for negative margins | ||
| classNames__default["default"].fillY), style: xStyle }, | ||
| preact.createElement("div", { | ||
| // stickiness on NowIndicatorDot misbehaves b/c of negative marginss | ||
| className: classNames__default["default"].stickyT }, | ||
| preact.createElement(internal.NowIndicatorDot, null))))); | ||
| } | ||
| } | ||
| /* | ||
| TODO: DRY with other NowIndicator components | ||
| */ | ||
| class TimelineNowIndicatorArrow extends internal.BaseComponent { | ||
| render() { | ||
| const { props, context } = this; | ||
| const xStyle = props.slotWidth == null | ||
| ? {} | ||
| : { | ||
| insetInlineStart: dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth) | ||
| }; | ||
| return (preact.createElement("div", { | ||
| // crop any overflow that the arrow/line might cause | ||
| // TODO: just do this on the entire canvas within the scroller | ||
| className: core.joinClassNames(classNames__default["default"].fill, classNames__default["default"].crop), style: { | ||
| zIndex: 2, | ||
| pointerEvents: 'none', // TODO: className | ||
| } }, | ||
| preact.createElement(internal.NowIndicatorHeaderContainer, { className: classNames__default["default"].abs, style: xStyle, date: props.nowDate }))); | ||
| } | ||
| } | ||
| function getTimelineSlotEl(parentEl, index) { | ||
| return parentEl.querySelectorAll(`.${classNames__default["default"].internalTimelineSlot}`)[index]; | ||
| } | ||
| /* | ||
| TODO: rename this file! | ||
| */ | ||
| // returned value is between 0 and the number of snaps | ||
| function computeDateSnapCoverage(date, tDateProfile, dateEnv) { | ||
| let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration); | ||
| if (snapDiff < 0) { | ||
| return 0; | ||
| } | ||
| if (snapDiff >= tDateProfile.snapDiffToIndex.length) { | ||
| return tDateProfile.snapCnt; | ||
| } | ||
| let snapDiffInt = Math.floor(snapDiff); | ||
| let snapCoverage = tDateProfile.snapDiffToIndex[snapDiffInt]; | ||
| if (internal.isInt(snapCoverage)) { // not an in-between value | ||
| snapCoverage += snapDiff - snapDiffInt; // add the remainder | ||
| } | ||
| else { | ||
| // a fractional value, meaning the date is not visible | ||
| // always round up in this case. works for start AND end dates in a range. | ||
| snapCoverage = Math.ceil(snapCoverage); | ||
| } | ||
| return snapCoverage; | ||
| } | ||
| class TimelineLaneSlicer extends internal.Slicer { | ||
| sliceRange(origRange, dateProfile, dateProfileGenerator, tDateProfile, dateEnv) { | ||
| let normalRange = normalizeRange(origRange, tDateProfile, dateEnv); | ||
| let segs = []; | ||
| // protect against when the span is entirely in an invalid date region | ||
| if (computeDateSnapCoverage(normalRange.start, tDateProfile, dateEnv) | ||
| < computeDateSnapCoverage(normalRange.end, tDateProfile, dateEnv)) { | ||
| // intersect the footprint's range with the grid's range | ||
| let slicedRange = internal.intersectRanges(normalRange, tDateProfile.normalizedRange); | ||
| if (slicedRange) { | ||
| segs.push({ | ||
| startDate: slicedRange.start, | ||
| endDate: slicedRange.end, | ||
| isStart: slicedRange.start.valueOf() === normalRange.start.valueOf() | ||
| && isValidDate(slicedRange.start, tDateProfile, dateProfile, dateProfileGenerator), | ||
| isEnd: slicedRange.end.valueOf() === normalRange.end.valueOf() | ||
| && isValidDate(internal.addMs(slicedRange.end, -1), tDateProfile, dateProfile, dateProfileGenerator), | ||
| }); | ||
| } | ||
| } | ||
| return segs; | ||
| } | ||
| } | ||
| const DEFAULT_TIME_FORMAT = internal.createFormatter({ | ||
| hour: 'numeric', | ||
| minute: '2-digit', | ||
| omitZeroMinute: true, | ||
| meridiem: 'narrow', | ||
| }); | ||
| class TimelineEvent extends internal.BaseComponent { | ||
| render() { | ||
| let { props } = this; | ||
| return (preact.createElement(internal.StandardEvent, Object.assign({}, props, { display: 'row', defaultTimeFormat: DEFAULT_TIME_FORMAT, defaultDisplayEventTime: !props.isTimeScale }))); | ||
| } | ||
| } | ||
| class TimelineLaneMoreLink extends internal.BaseComponent { | ||
| render() { | ||
| let { props } = this; | ||
| let { hiddenSegs, resourceId } = props; | ||
| let dateSpanProps = resourceId ? { resourceId } : {}; | ||
| return (preact.createElement(internal.MoreLinkContainer, { display: 'row', allDayDate: null, segs: hiddenSegs, hiddenSegs: hiddenSegs, dateProfile: props.dateProfile, todayRange: props.todayRange, dateSpanProps: dateSpanProps, isNarrow: false, isMicro: false, popoverContent: () => (preact.createElement(preact.Fragment, null, hiddenSegs.map((seg) => { | ||
| let { eventRange } = seg; | ||
| let { instanceId } = eventRange.instance; | ||
| let isDragging = Boolean(props.eventDrag && props.eventDrag.affectedInstances[instanceId]); | ||
| let isResizing = Boolean(props.eventResize && props.eventResize.affectedInstances[instanceId]); | ||
| let isInvisible = isDragging || isResizing; | ||
| return (preact.createElement("div", { key: instanceId, style: { visibility: isInvisible ? 'hidden' : undefined } }, | ||
| preact.createElement(TimelineEvent, Object.assign({ isTimeScale: props.isTimeScale, eventRange: eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isDragging: isDragging, isResizing: isResizing, isMirror: false, isSelected: instanceId === props.eventSelection }, internal.getEventRangeMeta(eventRange, props.todayRange, props.nowDate))))); | ||
| }))) })); | ||
| } | ||
| } | ||
| function computeManySegHorizontals(segs, segMinWidth, dateEnv, tDateProfile, slotWidth) { | ||
| const res = {}; | ||
| for (const seg of segs) { | ||
| res[internal.getEventKey(seg)] = computeSegHorizontals(seg, segMinWidth, dateEnv, tDateProfile, slotWidth); | ||
| } | ||
| return res; | ||
| } | ||
| function computeSegHorizontals(seg, segMinWidth, dateEnv, tDateProfile, slotWidth) { | ||
| const startCoord = dateToCoord(seg.startDate, dateEnv, tDateProfile, slotWidth); | ||
| const endCoord = dateToCoord(seg.endDate, dateEnv, tDateProfile, slotWidth); | ||
| let size = endCoord - startCoord; | ||
| if (segMinWidth) { | ||
| size = Math.max(size, segMinWidth); | ||
| } | ||
| return { start: startCoord, size }; | ||
| } | ||
| function computeFgSegPlacements(// mostly horizontals | ||
| segs, segHorizontals, // TODO: use Map | ||
| segHeights, // keyed by instanceId | ||
| hiddenGroupHeights, strictOrder, maxDepth) { | ||
| const segRanges = []; | ||
| // isn't it true that there will either be ALL hcoords or NONE? can optimize | ||
| for (const seg of segs) { | ||
| const hcoords = segHorizontals[internal.getEventKey(seg)]; | ||
| if (hcoords) { | ||
| segRanges.push(Object.assign(Object.assign({}, seg), { start: hcoords.start, end: hcoords.start + hcoords.size })); | ||
| } | ||
| } | ||
| const hierarchy = new internal.SegHierarchy(segRanges, (seg) => segHeights.get(internal.getEventKey(seg)), strictOrder, undefined, // maxCoord | ||
| maxDepth); | ||
| const segTops = new Map(); | ||
| hierarchy.traverseSegs((seg, segTop) => { | ||
| segTops.set(internal.getEventKey(seg), segTop); | ||
| }); | ||
| const { hiddenSegs } = hierarchy; | ||
| let totalHeight = 0; | ||
| for (const segRange of segRanges) { | ||
| const segKey = internal.getEventKey(segRange); | ||
| const segHeight = segHeights.get(segKey); | ||
| const segTop = segTops.get(segKey); | ||
| if (segHeight != null) { | ||
| if (segTop != null) { | ||
| totalHeight = Math.max(totalHeight, segTop + segHeight); | ||
| } | ||
| } | ||
| } | ||
| const hiddenGroups = internal.groupIntersectingSegs(hiddenSegs); | ||
| const hiddenGroupTops = new Map(); | ||
| // HACK for hiddenGroup findInsertion() call | ||
| hierarchy.strictOrder = true; | ||
| for (const hiddenGroup of hiddenGroups) { | ||
| const { levelCoord: top } = hierarchy.findInsertion(hiddenGroup, 0); | ||
| const hiddenGroupHeight = hiddenGroupHeights.get(hiddenGroup.key) || 0; | ||
| hiddenGroupTops.set(hiddenGroup.key, top); | ||
| totalHeight = Math.max(totalHeight, top + hiddenGroupHeight); | ||
| } | ||
| return [ | ||
| segTops, | ||
| hiddenGroups, | ||
| hiddenGroupTops, | ||
| totalHeight, | ||
| ]; | ||
| } | ||
| /* | ||
| TODO: make DRY with other Event Harnesses | ||
| */ | ||
| class TimelineEventHarness extends preact.Component { | ||
| constructor() { | ||
| super(...arguments); | ||
| // ref | ||
| this.rootElRef = preact.createRef(); | ||
| } | ||
| render() { | ||
| const { props } = this; | ||
| return (preact.createElement("div", { className: classNames__default["default"].abs, style: props.style, ref: this.rootElRef }, props.children)); | ||
| } | ||
| componentDidMount() { | ||
| const rootEl = this.rootElRef.current; // TODO: make dynamic with useEffect | ||
| this.disconnectHeight = internal.watchHeight(rootEl, (height) => { | ||
| internal.setRef(this.props.heightRef, height); | ||
| }); | ||
| } | ||
| componentWillUnmount() { | ||
| this.disconnectHeight(); | ||
| internal.setRef(this.props.heightRef, null); | ||
| } | ||
| } | ||
| class TimelineFg extends internal.BaseComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // memo | ||
| this.sortEventSegs = internal.memoize(internal.sortEventSegs); | ||
| // refs | ||
| this.segHeightRefMap = new internal.RefMap(() => { | ||
| internal.afterSize(this.handleSegHeights); | ||
| }); | ||
| this.moreLinkHeightRefMap = new internal.RefMap(() => { | ||
| internal.afterSize(this.handleMoreLinkHeights); | ||
| }); | ||
| this.handleMoreLinkHeights = () => { | ||
| this.setState({ moreLinkHeightRev: this.moreLinkHeightRefMap.rev }); // will trigger rerender | ||
| }; | ||
| this.handleSegHeights = () => { | ||
| this.setState({ segHeightRev: this.segHeightRefMap.rev }); // will trigger rerender | ||
| }; | ||
| } | ||
| /* | ||
| TODO: lots of memoization needed here! | ||
| */ | ||
| render() { | ||
| let { props, context, segHeightRefMap, moreLinkHeightRefMap } = this; | ||
| let { options } = context; | ||
| let { tDateProfile } = props; | ||
| let mirrorSegs = (props.eventDrag ? props.eventDrag.segs : null) || | ||
| (props.eventResize ? props.eventResize.segs : null) || | ||
| []; | ||
| let fgSegs = this.sortEventSegs(props.fgEventSegs, options.eventOrder); | ||
| let fgSegHorizontals = props.slotWidth != null | ||
| ? computeManySegHorizontals(fgSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth) | ||
| : {}; | ||
| let [fgSegTops, hiddenGroups, hiddenGroupTops, totalHeight] = computeFgSegPlacements(fgSegs, fgSegHorizontals, segHeightRefMap.current, moreLinkHeightRefMap.current, options.eventOrderStrict, options.eventMaxStack); | ||
| this.totalHeight = totalHeight; | ||
| return (preact.createElement("div", { className: core.joinClassNames(classNames__default["default"].rel, classNames__default["default"].noShrink), style: { | ||
| height: totalHeight, | ||
| } }, | ||
| this.renderFgSegs(fgSegs, fgSegHorizontals, fgSegTops, hiddenGroups, hiddenGroupTops, | ||
| /* isMirror = */ false), | ||
| this.renderFgSegs(mirrorSegs, props.slotWidth // TODO: memoize | ||
| ? computeManySegHorizontals(mirrorSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth) | ||
| : {}, fgSegTops, | ||
| /* hiddenGroups = */ [], | ||
| /* hiddenGroupTops = */ new Map(), | ||
| /* isMirror = */ true))); | ||
| } | ||
| renderFgSegs(segs, segHorizontals, segTops, hiddenGroups, hiddenGroupTops, isMirror) { | ||
| let { props, segHeightRefMap, moreLinkHeightRefMap } = this; | ||
| return (preact.createElement(preact.Fragment, null, | ||
| segs.map((seg) => { | ||
| const { eventRange } = seg; | ||
| const { instanceId } = eventRange.instance; | ||
| const segTop = segTops.get(instanceId); | ||
| const segHorizontalMaybe = segHorizontals[instanceId]; | ||
| const segHorizontal = segHorizontalMaybe || {}; | ||
| const isDragging = Boolean(props.eventDrag && props.eventDrag.affectedInstances[instanceId]); | ||
| const isResizing = Boolean(props.eventResize && props.eventResize.affectedInstances[instanceId]); | ||
| const isInvisible = !isMirror && (isDragging || isResizing || !segHorizontalMaybe || segTop == null); | ||
| return (preact.createElement(TimelineEventHarness, { key: instanceId, style: { | ||
| visibility: isInvisible ? 'hidden' : undefined, | ||
| zIndex: 1, | ||
| top: segTop || 0, | ||
| insetInlineStart: segHorizontal.start, | ||
| width: segHorizontal.size, | ||
| }, heightRef: isMirror ? undefined : segHeightRefMap.createRef(instanceId) }, | ||
| preact.createElement(TimelineEvent, Object.assign({ isTimeScale: props.tDateProfile.isTimeScale, eventRange: eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isDragging: isDragging, isResizing: isResizing, isMirror: isMirror, isSelected: instanceId === props.eventSelection }, internal.getEventRangeMeta(eventRange, props.todayRange, props.nowDate))))); | ||
| }), | ||
| hiddenGroups.map((hiddenGroup) => (preact.createElement(TimelineEventHarness, { key: hiddenGroup.key, style: { | ||
| top: hiddenGroupTops.get(hiddenGroup.key) || 0, | ||
| insetInlineStart: hiddenGroup.start, | ||
| width: hiddenGroup.end - hiddenGroup.start, | ||
| }, heightRef: moreLinkHeightRefMap.createRef(hiddenGroup.key) }, | ||
| preact.createElement(TimelineLaneMoreLink, { hiddenSegs: hiddenGroup.segs, dateProfile: props.dateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isTimeScale: props.tDateProfile.isTimeScale, eventDrag: props.eventDrag, eventResize: props.eventResize, eventSelection: props.eventSelection, resourceId: props.resourceId })))))); | ||
| } | ||
| /* | ||
| componentDidMount(): void { | ||
| // might want to do firedTotalHeight, but won't be ready on first render | ||
| } | ||
| */ | ||
| componentDidUpdate() { | ||
| if (this.totalHeight !== this.firedTotalHeight) { | ||
| this.firedTotalHeight = this.totalHeight; | ||
| internal.setRef(this.props.heightRef, this.totalHeight); | ||
| } | ||
| } | ||
| componentWillUnmount() { | ||
| internal.setRef(this.props.heightRef, null); | ||
| } | ||
| } | ||
| class TimelineBg extends internal.BaseComponent { | ||
| render() { | ||
| let { props } = this; | ||
| let highlightSeg = [].concat(props.eventResizeSegs || [], props.dateSelectionSegs); | ||
| return (preact.createElement(preact.Fragment, null, | ||
| this.renderSegs(props.businessHourSegs || [], 'non-business'), | ||
| this.renderSegs(props.bgEventSegs || [], 'bg-event'), | ||
| this.renderSegs(highlightSeg, 'highlight'))); | ||
| } | ||
| renderSegs(segs, fillType) { | ||
| let { tDateProfile, todayRange, nowDate, slotWidth } = this.props; | ||
| let { dateEnv, options } = this.context; | ||
| return (preact.createElement(preact.Fragment, null, segs.map((seg) => { | ||
| let hStyle = {}; | ||
| if (slotWidth != null) { | ||
| let segHorizontal = computeSegHorizontals(seg, undefined, dateEnv, tDateProfile, slotWidth); | ||
| hStyle = { insetInlineStart: segHorizontal.start, width: segHorizontal.size }; | ||
| } | ||
| return (preact.createElement("div", { key: internal.buildEventRangeKey(seg.eventRange), className: classNames__default["default"].fillY, style: hStyle }, fillType === 'bg-event' ? (preact.createElement(internal.BgEvent, Object.assign({ eventRange: seg.eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isVertical: false }, internal.getEventRangeMeta(seg.eventRange, todayRange, nowDate)))) : (internal.renderFill(fillType, options)))); | ||
| }))); | ||
| } | ||
| } | ||
| class TimelineView extends internal.DateComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // memoized | ||
| this.buildTimelineDateProfile = internal.memoize(buildTimelineDateProfile); | ||
| this.computeSlotWidth = internal.memoize(computeSlotWidth); | ||
| // refs | ||
| this.headerScrollerRef = preact.createRef(); | ||
| this.bodyScrollerRef = preact.createRef(); | ||
| this.footerScrollerRef = preact.createRef(); | ||
| this.headerRowInnerWidthMap = new internal.RefMap(() => { | ||
| internal.afterSize(this.handleSlotInnerWidths); | ||
| }); | ||
| this.scrollTime = null; | ||
| this.slicer = new TimelineLaneSlicer(); | ||
| // Sizing | ||
| // ----------------------------------------------------------------------------------------------- | ||
| this.handleSlotInnerWidths = () => { | ||
| const headerSlotInnerWidth = this.headerRowInnerWidthMap.current.get(this.tDateProfile.cellRows.length - 1); | ||
| if (headerSlotInnerWidth != null && headerSlotInnerWidth !== this.state.slotInnerWidth) { | ||
| this.setState({ slotInnerWidth: headerSlotInnerWidth }); | ||
| } | ||
| }; | ||
| this.handleTotalWidth = (totalWidth) => { | ||
| this.setState({ | ||
| totalWidth, | ||
| }); | ||
| }; | ||
| this.handleClientWidth = (clientWidth) => { | ||
| this.setState({ | ||
| clientWidth, | ||
| }); | ||
| }; | ||
| this.handleTimeScrollRequest = (scrollTime) => { | ||
| this.scrollTime = scrollTime; | ||
| this.applyTimeScroll(); | ||
| }; | ||
| this.handleTimeScrollEnd = (isUser) => { | ||
| if (isUser) { | ||
| this.scrollTime = null; | ||
| } | ||
| }; | ||
| // Hit System | ||
| // ----------------------------------------------------------------------------------------------- | ||
| this.handeBodyEl = (el) => { | ||
| this.bodyEl = el; | ||
| if (el) { | ||
| this.context.registerInteractiveComponent(this, { el }); | ||
| } | ||
| else { | ||
| this.context.unregisterInteractiveComponent(this); | ||
| } | ||
| }; | ||
| } | ||
| render() { | ||
| const { props, state, context } = this; | ||
| const { options } = context; | ||
| const { totalWidth, clientWidth } = state; | ||
| const endScrollbarWidth = (totalWidth != null && clientWidth != null) | ||
| ? totalWidth - clientWidth | ||
| : undefined; | ||
| /* date */ | ||
| const tDateProfile = this.tDateProfile = this.buildTimelineDateProfile(props.dateProfile, context.dateEnv, options, context.dateProfileGenerator); | ||
| const { cellRows } = tDateProfile; | ||
| const timerUnit = internal.greatestDurationDenominator(tDateProfile.slotDuration).unit; | ||
| /* table settings */ | ||
| const verticalScrolling = !props.forPrint && !internal.getIsHeightAuto(options); | ||
| const stickyHeaderDates = !props.forPrint && internal.getStickyHeaderDates(options); | ||
| const stickyFooterScrollbar = !props.forPrint && internal.getStickyFooterScrollbar(options); | ||
| /* table positions */ | ||
| const [canvasWidth, slotWidth] = this.computeSlotWidth(tDateProfile.slotCnt, tDateProfile.slotsPerLabel, options.slotMinWidth, state.slotInnerWidth, // is ACTUALLY the label width. rename? | ||
| clientWidth); | ||
| this.slotWidth = slotWidth; | ||
| /* sliced */ | ||
| let slicedProps = this.slicer.sliceProps(props, props.dateProfile, tDateProfile.isTimeScale ? null : options.nextDayThreshold, context, // wish we didn't have to pass in the rest of the args... | ||
| props.dateProfile, context.dateProfileGenerator, tDateProfile, context.dateEnv); | ||
| return (preact.createElement(internal.NowTimer, { unit: timerUnit }, (nowDate, todayRange) => { | ||
| const enableNowIndicator = // TODO: DRY | ||
| options.nowIndicator && | ||
| slotWidth != null && | ||
| internal.rangeContainsMarker(props.dateProfile.currentRange, nowDate); | ||
| return (preact.createElement(internal.ViewContainer, { viewSpec: context.viewSpec, className: internal.joinArrayishClassNames( | ||
| // HACK for Safari print-mode, where noScrollbars won't take effect for | ||
| // the below Scrollers if they have liquid flex height | ||
| !props.forPrint && classNames__default["default"].flexCol, props.className, options.tableClass, classNames__default["default"].isolate), borderlessX: props.borderlessX, borderlessTop: props.borderlessTop, borderlessBottom: props.borderlessBottom, noEdgeEffects: props.noEdgeEffects }, | ||
| preact.createElement("div", { className: core.joinClassNames(internal.generateClassName(options.tableHeaderClass, { | ||
| isSticky: stickyHeaderDates, | ||
| }), props.borderlessX && classNames__default["default"].borderlessX, classNames__default["default"].flexCol, stickyHeaderDates && classNames__default["default"].tableHeaderSticky), style: { | ||
| zIndex: 1, | ||
| } }, | ||
| preact.createElement(internal.Scroller, { horizontal: true, hideScrollbars: true, className: classNames__default["default"].flexRow, ref: this.headerScrollerRef }, | ||
| preact.createElement("div", { | ||
| // TODO: DRY | ||
| className: core.joinClassNames(classNames__default["default"].rel, // origin for now-indicator | ||
| canvasWidth == null && classNames__default["default"].liquid), style: { width: canvasWidth } }, | ||
| cellRows.map((cells, rowIndex) => { | ||
| const rowLevel = cellRows.length - rowIndex - 1; | ||
| return (preact.createElement(TimelineHeaderRow, { key: rowIndex, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, rowLevel: rowLevel, cells: cells, slotWidth: slotWidth, innerWidthRef: this.headerRowInnerWidthMap.createRef(rowIndex) })); | ||
| }), | ||
| enableNowIndicator && (preact.createElement(TimelineNowIndicatorArrow, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth }))), | ||
| Boolean(endScrollbarWidth) && (preact.createElement("div", { className: internal.joinArrayishClassNames(internal.generateClassName(options.fillerClass, { isHeader: true }), classNames__default["default"].borderOnlyS), style: { minWidth: endScrollbarWidth } }))), | ||
| preact.createElement("div", { className: internal.generateClassName(options.slotHeaderDividerClass, { | ||
| isHeader: true, | ||
| options: { dayMinWidth: options.dayMinWidth }, | ||
| }) })), | ||
| preact.createElement(internal.Scroller, { vertical: verticalScrolling, horizontal: true, hideScrollbars: stickyFooterScrollbar || | ||
| props.forPrint // prevents blank space in print-view on Safari | ||
| , className: internal.joinArrayishClassNames(options.tableBodyClass, props.borderlessX && classNames__default["default"].borderlessX, stickyHeaderDates && classNames__default["default"].borderlessTop, (stickyHeaderDates || props.noEdgeEffects) && classNames__default["default"].noEdgeEffects, classNames__default["default"].flexCol, verticalScrolling && classNames__default["default"].liquid), style: { | ||
| zIndex: 0, | ||
| }, ref: this.bodyScrollerRef, clientWidthRef: this.handleClientWidth }, | ||
| preact.createElement("div", { "aria-label": options.eventsHint, className: core.joinClassNames(classNames__default["default"].rel, // for canvas origin? | ||
| classNames__default["default"].grow), style: { width: canvasWidth }, ref: this.handeBodyEl }, | ||
| preact.createElement(TimelineSlats, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, | ||
| // dimensions | ||
| slotWidth: slotWidth }), | ||
| preact.createElement(TimelineBg, { tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, | ||
| // content | ||
| bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventResizeSegs: slicedProps.eventResize ? slicedProps.eventResize.segs : null, | ||
| // dimensions | ||
| slotWidth: slotWidth }), | ||
| preact.createElement("div", { className: internal.joinArrayishClassNames(options.timelineTopClass) }), | ||
| preact.createElement(TimelineFg, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, | ||
| // content | ||
| fgEventSegs: slicedProps.fgEventSegs, eventDrag: slicedProps.eventDrag, eventResize: slicedProps.eventResize, eventSelection: slicedProps.eventSelection, | ||
| // dimensions | ||
| slotWidth: slotWidth }), | ||
| preact.createElement("div", { className: internal.joinArrayishClassNames(options.timelineBottomClass) }), | ||
| enableNowIndicator && (preact.createElement(TimelineNowIndicatorLine, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth })))), | ||
| Boolean(stickyFooterScrollbar) && (preact.createElement(internal.FooterScrollbar, { isSticky: true, canvasWidth: canvasWidth, scrollerRef: this.footerScrollerRef })), | ||
| preact.createElement(internal.Ruler, { widthRef: this.handleTotalWidth }))); | ||
| })); | ||
| } | ||
| // Lifecycle | ||
| // ----------------------------------------------------------------------------------------------- | ||
| componentDidMount() { | ||
| this.syncedScroller = new internal$1.ScrollerSyncer(true); // horizontal=true | ||
| this.updateSyncedScroller(); | ||
| this.resetScroll(); | ||
| this.context.emitter.on('_timeScrollRequest', this.handleTimeScrollRequest); | ||
| this.syncedScroller.addScrollEndListener(this.handleTimeScrollEnd); | ||
| } | ||
| componentDidUpdate(prevProps) { | ||
| this.updateSyncedScroller(); | ||
| if (prevProps.dateProfile !== this.props.dateProfile && this.context.options.scrollTimeReset) { | ||
| this.resetScroll(); | ||
| } | ||
| else { | ||
| // TODO: inefficient to update so often | ||
| this.applyTimeScroll(); | ||
| } | ||
| } | ||
| componentWillUnmount() { | ||
| this.syncedScroller.destroy(); | ||
| this.context.emitter.off('_timeScrollRequest', this.handleTimeScrollRequest); | ||
| this.syncedScroller.removeScrollEndListener(this.handleTimeScrollEnd); | ||
| } | ||
| // Scrolling | ||
| // ----------------------------------------------------------------------------------------------- | ||
| updateSyncedScroller() { | ||
| this.syncedScroller.handleChildren([ | ||
| this.headerScrollerRef.current, | ||
| this.bodyScrollerRef.current, | ||
| this.footerScrollerRef.current | ||
| ]); | ||
| } | ||
| resetScroll() { | ||
| this.handleTimeScrollRequest(this.context.options.scrollTime); | ||
| } | ||
| applyTimeScroll() { | ||
| const { props, context, tDateProfile, scrollTime, slotWidth } = this; | ||
| if (scrollTime != null && slotWidth != null) { | ||
| let x = timeToCoord(scrollTime, context.dateEnv, props.dateProfile, tDateProfile, slotWidth); | ||
| if (x) { | ||
| x += 1; // overcome border. TODO: DRY this up | ||
| } | ||
| this.syncedScroller.scrollTo({ x }); | ||
| } | ||
| } | ||
| queryHit(isRtl, positionLeft, positionTop, elWidth, elHeight) { | ||
| const { props, context, tDateProfile, slotWidth } = this; | ||
| const { dateEnv } = context; | ||
| if (slotWidth) { | ||
| const x = isRtl ? elWidth - positionLeft : positionLeft; | ||
| const slatIndex = Math.floor(x / slotWidth); | ||
| const slatX = slatIndex * slotWidth; | ||
| const partial = (x - slatX) / slotWidth; // floating point number between 0 and 1 | ||
| const localSnapIndex = Math.floor(partial * tDateProfile.snapsPerSlot); // the snap # relative to start of slat | ||
| let startDate = dateEnv.add(tDateProfile.slotDates[slatIndex], internal.multiplyDuration(tDateProfile.snapDuration, localSnapIndex)); | ||
| let endDate = dateEnv.add(startDate, tDateProfile.snapDuration); | ||
| // TODO: generalize this coord stuff to TimeGrid? | ||
| let snapWidth = slotWidth / tDateProfile.snapsPerSlot; | ||
| let startCoord = slatIndex * slotWidth + (snapWidth * localSnapIndex); | ||
| let endCoord = startCoord + snapWidth; | ||
| let left, right; | ||
| if (isRtl) { | ||
| left = elWidth - endCoord; | ||
| right = elWidth - startCoord; | ||
| } | ||
| else { | ||
| left = startCoord; | ||
| right = endCoord; | ||
| } | ||
| return { | ||
| dateProfile: props.dateProfile, | ||
| dateSpan: { | ||
| range: { start: startDate, end: endDate }, | ||
| allDay: !tDateProfile.isTimeScale, | ||
| }, | ||
| rect: { | ||
| left, | ||
| right, | ||
| top: 0, | ||
| bottom: elHeight, | ||
| }, | ||
| getDayEl: () => getTimelineSlotEl(this.bodyEl, slatIndex), | ||
| layer: 0, | ||
| }; | ||
| } | ||
| return null; | ||
| } | ||
| } | ||
| exports.TimelineBg = TimelineBg; | ||
| exports.TimelineFg = TimelineFg; | ||
| exports.TimelineHeaderRow = TimelineHeaderRow; | ||
| exports.TimelineLaneSlicer = TimelineLaneSlicer; | ||
| exports.TimelineNowIndicatorArrow = TimelineNowIndicatorArrow; | ||
| exports.TimelineNowIndicatorLine = TimelineNowIndicatorLine; | ||
| exports.TimelineSlats = TimelineSlats; | ||
| exports.TimelineView = TimelineView; | ||
| exports.buildTimelineDateProfile = buildTimelineDateProfile; | ||
| exports.computeSlotWidth = computeSlotWidth; | ||
| exports.getTimelineSlotEl = getTimelineSlotEl; | ||
| exports.timeToCoord = timeToCoord; |
| import { PluginDef } from '@fullcalendar/core'; | ||
| import '@fullcalendar/premium-common'; | ||
| import '@fullcalendar/scrollgrid'; | ||
| import { RawOptionsFromRefiners, RefinedOptionsFromRefiners, Identity } from '@fullcalendar/core/internal'; | ||
| declare const OPTION_REFINERS: { | ||
| timelineTopClass: Identity<string>; | ||
| timelineBottomClass: Identity<string>; | ||
| }; | ||
| type TimelineOptionRefiners = typeof OPTION_REFINERS; | ||
| type TimelineOptions = RawOptionsFromRefiners<TimelineOptionRefiners>; | ||
| type TimelineOptionsRefined = RefinedOptionsFromRefiners<TimelineOptionRefiners>; | ||
| declare module '@fullcalendar/core/internal' { | ||
| interface BaseOptions extends TimelineOptions { | ||
| } | ||
| interface BaseOptionsRefined extends TimelineOptionsRefined { | ||
| } | ||
| } | ||
| //# sourceMappingURL=ambient.d.ts.map | ||
| declare const _default: PluginDef; | ||
| //# sourceMappingURL=index.d.ts.map | ||
| export { TimelineOptions, _default as default }; |
+45
| import { createPlugin } from '@fullcalendar/core'; | ||
| import premiumCommonPlugin from '@fullcalendar/premium-common'; | ||
| import { TimelineView } from './internal.js'; | ||
| import { identity } from '@fullcalendar/core/internal'; | ||
| import '@fullcalendar/core/internal-classnames'; | ||
| import '@fullcalendar/core/preact'; | ||
| import '@fullcalendar/scrollgrid/internal'; | ||
| const OPTION_REFINERS = { | ||
| timelineTopClass: identity, | ||
| timelineBottomClass: identity, | ||
| }; | ||
| var index = createPlugin({ | ||
| name: '@fullcalendar/timeline', | ||
| premiumReleaseDate: '2025-12-20', | ||
| deps: [premiumCommonPlugin], | ||
| initialView: 'timelineDay', | ||
| optionRefiners: OPTION_REFINERS, | ||
| views: { | ||
| timeline: { | ||
| component: TimelineView, | ||
| usesMinMaxTime: true, | ||
| eventResizableFromStart: true, // how is this consumed for TimelineView tho? | ||
| }, | ||
| timelineDay: { | ||
| type: 'timeline', | ||
| duration: { days: 1 }, | ||
| }, | ||
| timelineWeek: { | ||
| type: 'timeline', | ||
| duration: { weeks: 1 }, | ||
| }, | ||
| timelineMonth: { | ||
| type: 'timeline', | ||
| duration: { months: 1 }, | ||
| }, | ||
| timelineYear: { | ||
| type: 'timeline', | ||
| duration: { years: 1 }, | ||
| }, | ||
| }, | ||
| }); | ||
| export { index as default }; |
| import { DateComponent, ViewProps, Hit, DateRange, DateMarker, DateProfile, DateEnv, BaseOptionsRefined, DateProfileGenerator, Slicer, CoordRange, BaseComponent, EventRangeProps, CoordSpan, SegGroup, EventSegUiInteractionState } from '@fullcalendar/core/internal'; | ||
| import { createElement, Ref } from '@fullcalendar/core/preact'; | ||
| import { Duration } from '@fullcalendar/core'; | ||
| interface TimelineViewState { | ||
| totalWidth?: number; | ||
| clientWidth?: number; | ||
| slotInnerWidth?: number; | ||
| } | ||
| declare class TimelineView extends DateComponent<ViewProps, TimelineViewState> { | ||
| private buildTimelineDateProfile; | ||
| private computeSlotWidth; | ||
| private headerScrollerRef; | ||
| private bodyScrollerRef; | ||
| private footerScrollerRef; | ||
| private tDateProfile?; | ||
| private bodyEl?; | ||
| private slotWidth?; | ||
| private headerRowInnerWidthMap; | ||
| private syncedScroller; | ||
| private scrollTime; | ||
| private slicer; | ||
| render(): createElement.JSX.Element; | ||
| componentDidMount(): void; | ||
| componentDidUpdate(prevProps: ViewProps): void; | ||
| componentWillUnmount(): void; | ||
| handleSlotInnerWidths: () => void; | ||
| handleTotalWidth: (totalWidth: number) => void; | ||
| handleClientWidth: (clientWidth: number) => void; | ||
| private updateSyncedScroller; | ||
| private resetScroll; | ||
| private handleTimeScrollRequest; | ||
| private handleTimeScrollEnd; | ||
| private applyTimeScroll; | ||
| handeBodyEl: (el: HTMLElement | null) => void; | ||
| queryHit(isRtl: boolean, positionLeft: number, positionTop: number, elWidth: number, elHeight: number): Hit; | ||
| } | ||
| interface TimelineDateProfile { | ||
| labelInterval: Duration; | ||
| slotDuration: Duration; | ||
| slotsPerLabel: number; | ||
| headerFormats: any; | ||
| isTimeScale: boolean; | ||
| largeUnit: string; | ||
| snapDuration: Duration; | ||
| snapsPerSlot: number; | ||
| normalizedRange: DateRange; | ||
| timeWindowMs: number; | ||
| slotDates: DateMarker[]; | ||
| slotDatesMajor: boolean[]; | ||
| snapDiffToIndex: number[]; | ||
| snapIndexToDiff: number[]; | ||
| snapCnt: number; | ||
| slotCnt: number; | ||
| cellRows: TimelineHeaderCellData[][]; | ||
| } | ||
| interface TimelineHeaderCellData { | ||
| date: DateMarker; | ||
| isMajor: boolean; | ||
| text: string; | ||
| rowUnit: string; | ||
| colspan: number; | ||
| } | ||
| declare function buildTimelineDateProfile(dateProfile: DateProfile, dateEnv: DateEnv, allOptions: BaseOptionsRefined, dateProfileGenerator: DateProfileGenerator): TimelineDateProfile; | ||
| interface TimelineRange { | ||
| startDate: DateMarker; | ||
| endDate: DateMarker; | ||
| isStart: boolean; | ||
| isEnd: boolean; | ||
| } | ||
| type TimelineCoordRange = TimelineRange & CoordRange; | ||
| declare class TimelineLaneSlicer extends Slicer<TimelineRange, [ | ||
| DateProfile, | ||
| DateProfileGenerator, | ||
| TimelineDateProfile, | ||
| DateEnv | ||
| ]> { | ||
| sliceRange(origRange: DateRange, dateProfile: DateProfile, dateProfileGenerator: DateProfileGenerator, tDateProfile: TimelineDateProfile, dateEnv: DateEnv): TimelineRange[]; | ||
| } | ||
| interface TimelineFgProps { | ||
| dateProfile: DateProfile; | ||
| tDateProfile: TimelineDateProfile; | ||
| nowDate: DateMarker; | ||
| todayRange: DateRange; | ||
| fgEventSegs: (TimelineRange & EventRangeProps)[]; | ||
| eventDrag: EventSegUiInteractionState<TimelineRange> | null; | ||
| eventResize: EventSegUiInteractionState<TimelineRange> | null; | ||
| eventSelection: string; | ||
| resourceId?: string; | ||
| slotWidth: number | undefined; | ||
| heightRef?: Ref<number>; | ||
| } | ||
| interface TimelineFgState { | ||
| segHeightRev?: string; | ||
| moreLinkHeightRev?: string; | ||
| } | ||
| declare class TimelineFg extends BaseComponent<TimelineFgProps, TimelineFgState> { | ||
| private sortEventSegs; | ||
| private segHeightRefMap; | ||
| private moreLinkHeightRefMap; | ||
| private totalHeight?; | ||
| private firedTotalHeight?; | ||
| render(): createElement.JSX.Element; | ||
| renderFgSegs(segs: (TimelineRange & EventRangeProps)[], segHorizontals: { | ||
| [instanceId: string]: CoordSpan; | ||
| }, segTops: Map<string, number>, hiddenGroups: SegGroup<TimelineCoordRange>[], hiddenGroupTops: Map<string, number>, isMirror: boolean): createElement.JSX.Element; | ||
| private handleMoreLinkHeights; | ||
| private handleSegHeights; | ||
| componentDidUpdate(): void; | ||
| componentWillUnmount(): void; | ||
| } | ||
| interface TimelineBgProps { | ||
| tDateProfile: TimelineDateProfile; | ||
| nowDate: DateMarker; | ||
| todayRange: DateRange; | ||
| bgEventSegs: (TimelineRange & EventRangeProps)[] | null; | ||
| businessHourSegs: (TimelineRange & EventRangeProps)[] | null; | ||
| dateSelectionSegs: (TimelineRange & EventRangeProps)[]; | ||
| eventResizeSegs: (TimelineRange & EventRangeProps)[] | null; | ||
| slotWidth: number | undefined; | ||
| } | ||
| declare class TimelineBg extends BaseComponent<TimelineBgProps> { | ||
| render(): createElement.JSX.Element; | ||
| renderSegs(segs: (TimelineRange & EventRangeProps)[], fillType: string): createElement.JSX.Element; | ||
| } | ||
| interface TimelineSlatsProps { | ||
| dateProfile: DateProfile; | ||
| tDateProfile: TimelineDateProfile; | ||
| nowDate: DateMarker; | ||
| todayRange: DateRange; | ||
| height?: number; | ||
| slotWidth: number | undefined; | ||
| } | ||
| declare class TimelineSlats extends BaseComponent<TimelineSlatsProps> { | ||
| render(): createElement.JSX.Element; | ||
| } | ||
| declare function computeSlotWidth(slatCnt: number, slatsPerLabel: number, slatMinWidth: number | undefined, labelInnerWidth: number | undefined, viewportWidth: number | undefined): [ | ||
| canvasWidth: number | undefined, | ||
| slatWidth: number | undefined, | ||
| slotLiquid: boolean | ||
| ]; | ||
| declare function timeToCoord(// pixels | ||
| time: Duration, dateEnv: DateEnv, dateProfile: DateProfile, tDateProfile: TimelineDateProfile, slowWidth: number): number; | ||
| interface TimelineHeaderRowProps { | ||
| dateProfile: DateProfile; | ||
| tDateProfile: TimelineDateProfile; | ||
| nowDate: DateMarker; | ||
| todayRange: DateRange; | ||
| rowLevel: number; | ||
| cells: TimelineHeaderCellData[]; | ||
| innerHeighRef?: Ref<number>; | ||
| innerWidthRef?: Ref<number>; | ||
| slotWidth: number | undefined; | ||
| } | ||
| interface TimelineHeaderRowState { | ||
| innerHeight?: number; | ||
| } | ||
| declare class TimelineHeaderRow extends BaseComponent<TimelineHeaderRowProps, TimelineHeaderRowState> { | ||
| private innerWidthRefMap; | ||
| private innerHeightRefMap; | ||
| render(): createElement.JSX.Element; | ||
| handleInnerWidths: () => void; | ||
| handleInnerHeights: () => void; | ||
| componentWillUnmount(): void; | ||
| } | ||
| interface TimelineNowIndicatorArrowProps { | ||
| tDateProfile: TimelineDateProfile; | ||
| nowDate: DateMarker; | ||
| slotWidth: number | undefined; | ||
| } | ||
| declare class TimelineNowIndicatorArrow extends BaseComponent<TimelineNowIndicatorArrowProps> { | ||
| render(): createElement.JSX.Element; | ||
| } | ||
| interface TimelineNowIndicatorLineProps { | ||
| tDateProfile: TimelineDateProfile; | ||
| nowDate: DateMarker; | ||
| slotWidth: number | undefined; | ||
| } | ||
| declare class TimelineNowIndicatorLine extends BaseComponent<TimelineNowIndicatorLineProps> { | ||
| render(): createElement.JSX.Element; | ||
| } | ||
| declare function getTimelineSlotEl(parentEl: HTMLElement, index: number): HTMLElement; | ||
| export { TimelineBg, TimelineDateProfile, TimelineFg, TimelineHeaderRow, TimelineLaneSlicer, TimelineNowIndicatorArrow, TimelineNowIndicatorLine, TimelineRange, TimelineSlats, TimelineView, buildTimelineDateProfile, computeSlotWidth, getTimelineSlotEl, timeToCoord }; |
+1205
| import { joinClassNames } from '@fullcalendar/core'; | ||
| import { config, createFormatter, greatestDurationDenominator, createDuration, wholeDivideDurations, asRoughMs, addDays, computeMajorUnit, isMajorUnit, startOfDay, asRoughSeconds, asRoughMinutes, diffWholeDays, asCleanDays, isInt, computeVisibleDayRange, padStart, BaseComponent, memoize, getDateMeta, ContentContainer, joinArrayishClassNames, buildNavLinkAttrs, generateClassName, watchSize, setRef, RefMap, afterSize, NowIndicatorLineContainer, NowIndicatorDot, NowIndicatorHeaderContainer, Slicer, intersectRanges, addMs, StandardEvent, MoreLinkContainer, getEventRangeMeta, getEventKey, SegHierarchy, groupIntersectingSegs, watchHeight, sortEventSegs, buildEventRangeKey, BgEvent, renderFill, DateComponent, getIsHeightAuto, getStickyHeaderDates, getStickyFooterScrollbar, NowTimer, rangeContainsMarker, ViewContainer, Scroller, FooterScrollbar, Ruler, multiplyDuration } from '@fullcalendar/core/internal'; | ||
| import classNames from '@fullcalendar/core/internal-classnames'; | ||
| import { createElement, createRef, Fragment, Component } from '@fullcalendar/core/preact'; | ||
| import { ScrollerSyncer } from '@fullcalendar/scrollgrid/internal'; | ||
| const MIN_AUTO_LABELS = 18; // more than `12` months but less that `24` hours | ||
| const MAX_AUTO_SLOTS_PER_LABEL = 6; // allows 6 10-min slots in an hour | ||
| const MAX_AUTO_CELLS = 200; // allows 4-days to have a :30 slot duration | ||
| config.MAX_TIMELINE_SLOTS = 1000; | ||
| // potential nice values for slot-duration and interval-duration | ||
| const STOCK_SUB_DURATIONS = [ | ||
| { years: 1 }, | ||
| { months: 1 }, | ||
| { days: 1 }, | ||
| { hours: 1 }, | ||
| { minutes: 30 }, | ||
| { minutes: 15 }, | ||
| { minutes: 10 }, | ||
| { minutes: 5 }, | ||
| { minutes: 1 }, | ||
| { seconds: 30 }, | ||
| { seconds: 15 }, | ||
| { seconds: 10 }, | ||
| { seconds: 5 }, | ||
| { seconds: 1 }, | ||
| { milliseconds: 500 }, | ||
| { milliseconds: 100 }, | ||
| { milliseconds: 10 }, | ||
| { milliseconds: 1 }, | ||
| ]; | ||
| function buildTimelineDateProfile(dateProfile, dateEnv, allOptions, dateProfileGenerator) { | ||
| let tDateProfile = { | ||
| labelInterval: allOptions.slotHeaderInterval, | ||
| slotDuration: allOptions.slotDuration, | ||
| }; | ||
| validateLabelAndSlot(tDateProfile, dateProfile, dateEnv); // validate after computed grid duration | ||
| ensureLabelInterval(tDateProfile, dateProfile, dateEnv); | ||
| ensureSlotDuration(tDateProfile, dateProfile, dateEnv); | ||
| let input = allOptions.slotHeaderFormat; | ||
| let rawFormats = Array.isArray(input) ? input : | ||
| (input != null) ? [input] : | ||
| computeHeaderFormats(tDateProfile, dateProfile, dateEnv, allOptions); | ||
| tDateProfile.headerFormats = rawFormats.map((rawFormat) => createFormatter(rawFormat)); | ||
| tDateProfile.isTimeScale = Boolean(tDateProfile.slotDuration.milliseconds); | ||
| let largeUnit = null; | ||
| if (!tDateProfile.isTimeScale) { | ||
| const slotUnit = greatestDurationDenominator(tDateProfile.slotDuration).unit; | ||
| if (/year|month|week/.test(slotUnit)) { | ||
| largeUnit = slotUnit; | ||
| } | ||
| } | ||
| tDateProfile.largeUnit = largeUnit; | ||
| /* | ||
| console.log('label interval =', timelineView.labelInterval.humanize()) | ||
| console.log('slot duration =', timelineView.slotDuration.humanize()) | ||
| console.log('header formats =', timelineView.headerFormats) | ||
| console.log('isTimeScale', timelineView.isTimeScale) | ||
| console.log('largeUnit', timelineView.largeUnit) | ||
| */ | ||
| let rawSnapDuration = allOptions.snapDuration; | ||
| let snapDuration; | ||
| let snapsPerSlot; | ||
| if (rawSnapDuration) { | ||
| snapDuration = createDuration(rawSnapDuration); | ||
| snapsPerSlot = wholeDivideDurations(tDateProfile.slotDuration, snapDuration); | ||
| // ^ TODO: warning if not whole? | ||
| } | ||
| if (snapsPerSlot == null) { | ||
| snapDuration = tDateProfile.slotDuration; | ||
| snapsPerSlot = 1; | ||
| } | ||
| tDateProfile.snapDuration = snapDuration; | ||
| tDateProfile.snapsPerSlot = snapsPerSlot; | ||
| // more... | ||
| let timeWindowMs = asRoughMs(dateProfile.slotMaxTime) - asRoughMs(dateProfile.slotMinTime); | ||
| // TODO: why not use normalizeRange!? | ||
| let normalizedStart = normalizeDate(dateProfile.renderRange.start, tDateProfile, dateEnv); | ||
| let normalizedEnd = normalizeDate(dateProfile.renderRange.end, tDateProfile, dateEnv); | ||
| // apply slotMinTime/slotMaxTime | ||
| // TODO: View should be responsible. | ||
| if (tDateProfile.isTimeScale) { | ||
| normalizedStart = dateEnv.add(normalizedStart, dateProfile.slotMinTime); | ||
| normalizedEnd = dateEnv.add(addDays(normalizedEnd, -1), dateProfile.slotMaxTime); | ||
| } | ||
| tDateProfile.timeWindowMs = timeWindowMs; | ||
| tDateProfile.normalizedRange = { start: normalizedStart, end: normalizedEnd }; | ||
| let slotDates = []; | ||
| let slotDatesMajor = []; | ||
| let date = normalizedStart; | ||
| let majorUnit = computeMajorUnit(dateProfile, dateEnv); | ||
| while (date < normalizedEnd) { | ||
| if (isValidDate(date, tDateProfile, dateProfile, dateProfileGenerator)) { | ||
| slotDates.push(date); | ||
| slotDatesMajor.push(isMajorUnit(date, majorUnit, dateEnv)); | ||
| } | ||
| date = dateEnv.add(date, tDateProfile.slotDuration); | ||
| } | ||
| tDateProfile.slotDates = slotDates; | ||
| tDateProfile.slotDatesMajor = slotDatesMajor; | ||
| // more... | ||
| let snapIndex = -1; | ||
| let snapDiff = 0; // index of the diff :( | ||
| const snapDiffToIndex = []; | ||
| const snapIndexToDiff = []; | ||
| date = normalizedStart; | ||
| while (date < normalizedEnd) { | ||
| if (isValidDate(date, tDateProfile, dateProfile, dateProfileGenerator)) { | ||
| snapIndex += 1; | ||
| snapDiffToIndex.push(snapIndex); | ||
| snapIndexToDiff.push(snapDiff); | ||
| } | ||
| else { | ||
| snapDiffToIndex.push(snapIndex + 0.5); | ||
| } | ||
| date = dateEnv.add(date, tDateProfile.snapDuration); | ||
| snapDiff += 1; | ||
| } | ||
| tDateProfile.snapDiffToIndex = snapDiffToIndex; | ||
| tDateProfile.snapIndexToDiff = snapIndexToDiff; | ||
| tDateProfile.snapCnt = snapIndex + 1; // is always one behind | ||
| tDateProfile.slotCnt = tDateProfile.snapCnt / tDateProfile.snapsPerSlot; | ||
| // more... | ||
| tDateProfile.cellRows = buildCellRows(tDateProfile, dateEnv, majorUnit); | ||
| tDateProfile.slotsPerLabel = wholeDivideDurations(tDateProfile.labelInterval, tDateProfile.slotDuration); | ||
| return tDateProfile; | ||
| } | ||
| /* | ||
| snaps to appropriate unit | ||
| */ | ||
| function normalizeDate(date, tDateProfile, dateEnv) { | ||
| let normalDate = date; | ||
| if (!tDateProfile.isTimeScale) { | ||
| normalDate = startOfDay(normalDate); | ||
| if (tDateProfile.largeUnit) { | ||
| normalDate = dateEnv.startOf(normalDate, tDateProfile.largeUnit); | ||
| } | ||
| } | ||
| return normalDate; | ||
| } | ||
| /* | ||
| snaps to appropriate unit | ||
| */ | ||
| function normalizeRange(range, tDateProfile, dateEnv) { | ||
| if (!tDateProfile.isTimeScale) { | ||
| range = computeVisibleDayRange(range); | ||
| if (tDateProfile.largeUnit) { | ||
| let dayRange = range; // preserve original result | ||
| range = { | ||
| start: dateEnv.startOf(range.start, tDateProfile.largeUnit), | ||
| end: dateEnv.startOf(range.end, tDateProfile.largeUnit), | ||
| }; | ||
| // if date is partially through the interval, or is in the same interval as the start, | ||
| // make the exclusive end be the *next* interval | ||
| if (range.end.valueOf() !== dayRange.end.valueOf() || range.end <= range.start) { | ||
| range = { | ||
| start: range.start, | ||
| end: dateEnv.add(range.end, tDateProfile.slotDuration), | ||
| }; | ||
| } | ||
| } | ||
| } | ||
| return range; | ||
| } | ||
| function isValidDate(date, tDateProfile, dateProfile, dateProfileGenerator) { | ||
| if (dateProfileGenerator.isHiddenDay(date)) { | ||
| return false; | ||
| } | ||
| if (tDateProfile.isTimeScale) { | ||
| // determine if the time is within slotMinTime/slotMaxTime, which may have wacky values | ||
| let day = startOfDay(date); | ||
| let timeMs = date.valueOf() - day.valueOf(); | ||
| let ms = timeMs - asRoughMs(dateProfile.slotMinTime); // milliseconds since slotMinTime | ||
| ms = ((ms % 86400000) + 86400000) % 86400000; // make negative values wrap to 24hr clock | ||
| return ms < tDateProfile.timeWindowMs; // before the slotMaxTime? | ||
| } | ||
| return true; | ||
| } | ||
| function validateLabelAndSlot(tDateProfile, dateProfile, dateEnv) { | ||
| const { currentRange } = dateProfile; | ||
| // make sure labelInterval doesn't exceed the max number of cells | ||
| if (tDateProfile.labelInterval) { | ||
| const labelCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, tDateProfile.labelInterval); | ||
| if (labelCnt > config.MAX_TIMELINE_SLOTS) { | ||
| console.warn('slotHeaderInterval results in too many cells'); | ||
| tDateProfile.labelInterval = null; | ||
| } | ||
| } | ||
| // make sure slotDuration doesn't exceed the maximum number of cells | ||
| if (tDateProfile.slotDuration) { | ||
| const slotCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, tDateProfile.slotDuration); | ||
| if (slotCnt > config.MAX_TIMELINE_SLOTS) { | ||
| console.warn('slotDuration results in too many cells'); | ||
| tDateProfile.slotDuration = null; | ||
| } | ||
| } | ||
| // make sure labelInterval is a multiple of slotDuration | ||
| if (tDateProfile.labelInterval && tDateProfile.slotDuration) { | ||
| const slotsPerLabel = wholeDivideDurations(tDateProfile.labelInterval, tDateProfile.slotDuration); | ||
| if (slotsPerLabel === null || slotsPerLabel < 1) { | ||
| console.warn('slotHeaderInterval must be a multiple of slotDuration'); | ||
| tDateProfile.slotDuration = null; | ||
| } | ||
| } | ||
| } | ||
| function ensureLabelInterval(tDateProfile, dateProfile, dateEnv) { | ||
| const { currentRange } = dateProfile; | ||
| let { labelInterval } = tDateProfile; | ||
| if (!labelInterval) { | ||
| // compute based off the slot duration | ||
| // find the largest label interval with an acceptable slots-per-label | ||
| let input; | ||
| if (tDateProfile.slotDuration) { | ||
| for (input of STOCK_SUB_DURATIONS) { | ||
| const tryLabelInterval = createDuration(input); | ||
| const slotsPerLabel = wholeDivideDurations(tryLabelInterval, tDateProfile.slotDuration); | ||
| if (slotsPerLabel !== null && slotsPerLabel <= MAX_AUTO_SLOTS_PER_LABEL) { | ||
| labelInterval = tryLabelInterval; | ||
| break; | ||
| } | ||
| } | ||
| // use the slot duration as a last resort | ||
| if (!labelInterval) { | ||
| labelInterval = tDateProfile.slotDuration; | ||
| } | ||
| // compute based off the view's duration | ||
| // find the largest label interval that yields the minimum number of labels | ||
| } | ||
| else { | ||
| for (input of STOCK_SUB_DURATIONS) { | ||
| labelInterval = createDuration(input); | ||
| const labelCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, labelInterval); | ||
| if (labelCnt >= MIN_AUTO_LABELS) { | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| tDateProfile.labelInterval = labelInterval; | ||
| } | ||
| return labelInterval; | ||
| } | ||
| function ensureSlotDuration(tDateProfile, dateProfile, dateEnv) { | ||
| const { currentRange } = dateProfile; | ||
| let { slotDuration } = tDateProfile; | ||
| if (!slotDuration) { | ||
| const labelInterval = ensureLabelInterval(tDateProfile, dateProfile, dateEnv); // will compute if necessary | ||
| // compute based off the label interval | ||
| // find the largest slot duration that is different from labelInterval, but still acceptable | ||
| for (let input of STOCK_SUB_DURATIONS) { | ||
| const trySlotDuration = createDuration(input); | ||
| const slotsPerLabel = wholeDivideDurations(labelInterval, trySlotDuration); | ||
| if (slotsPerLabel !== null && slotsPerLabel > 1 && slotsPerLabel <= MAX_AUTO_SLOTS_PER_LABEL) { | ||
| slotDuration = trySlotDuration; | ||
| break; | ||
| } | ||
| } | ||
| // only allow the value if it won't exceed the view's # of slots limit | ||
| if (slotDuration) { | ||
| const slotCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, slotDuration); | ||
| if (slotCnt > MAX_AUTO_CELLS) { | ||
| slotDuration = null; | ||
| } | ||
| } | ||
| // use the label interval as a last resort | ||
| if (!slotDuration) { | ||
| slotDuration = labelInterval; | ||
| } | ||
| tDateProfile.slotDuration = slotDuration; | ||
| } | ||
| return slotDuration; | ||
| } | ||
| function computeHeaderFormats(tDateProfile, dateProfile, dateEnv, allOptions) { | ||
| let format1; | ||
| let format2; | ||
| const { labelInterval } = tDateProfile; | ||
| const { currentRange } = dateProfile; | ||
| let unit = greatestDurationDenominator(labelInterval).unit; | ||
| const weekNumbersVisible = allOptions.weekNumbers; | ||
| let format0 = (format1 = (format2 = null)); | ||
| // NOTE: weekNumber computation function wont work | ||
| if ((unit === 'week') && !weekNumbersVisible) { | ||
| unit = 'day'; | ||
| } | ||
| switch (unit) { | ||
| case 'year': | ||
| format0 = { year: 'numeric' }; // '2015' | ||
| break; | ||
| case 'month': | ||
| if (dateEnv.diffWholeYears(currentRange.start, currentRange.end) > 1) { | ||
| format0 = { year: 'numeric' }; // '2015' | ||
| } | ||
| format1 = { month: 'short' }; // 'Jan' | ||
| break; | ||
| case 'week': | ||
| if (dateEnv.diffWholeYears(currentRange.start, currentRange.end) > 1) { | ||
| format0 = { year: 'numeric' }; // '2015' | ||
| } | ||
| format1 = { week: 'narrow' }; // 'Wk4' | ||
| break; | ||
| case 'day': | ||
| if (dateEnv.diffWholeYears(currentRange.start, currentRange.end) > 1) { | ||
| format0 = { year: 'numeric', month: 'long' }; // 'January 2014' | ||
| } | ||
| else if (dateEnv.diffWholeMonths(currentRange.start, currentRange.end) > 1) { | ||
| format0 = { month: 'long' }; // 'January' | ||
| } | ||
| if (weekNumbersVisible) { | ||
| format1 = { week: 'short' }; // 'Wk 4' | ||
| } | ||
| format2 = { weekday: 'narrow', day: 'numeric' }; // 'Su 9' | ||
| break; | ||
| case 'hour': | ||
| if (weekNumbersVisible) { | ||
| format0 = { week: 'short' }; // 'Wk 4' | ||
| } | ||
| if (diffWholeDays(currentRange.start, currentRange.end) > 1) { | ||
| format1 = { weekday: 'short', day: 'numeric', month: 'numeric', omitCommas: true }; // Sat 4/7 | ||
| } | ||
| format2 = { | ||
| hour: 'numeric', | ||
| minute: '2-digit', | ||
| omitZeroMinute: true, | ||
| meridiem: 'short', | ||
| }; | ||
| break; | ||
| case 'minute': | ||
| // sufficiently large number of different minute cells? | ||
| if ((asRoughMinutes(labelInterval) / 60) >= MAX_AUTO_SLOTS_PER_LABEL) { | ||
| format0 = { | ||
| hour: 'numeric', | ||
| meridiem: 'short', | ||
| }; | ||
| format1 = (params) => (':' + padStart(params.date.minute, 2) // ':30' | ||
| ); | ||
| } | ||
| else { | ||
| format0 = { | ||
| hour: 'numeric', | ||
| minute: 'numeric', | ||
| meridiem: 'short', | ||
| }; | ||
| } | ||
| break; | ||
| case 'second': | ||
| // sufficiently large number of different second cells? | ||
| if ((asRoughSeconds(labelInterval) / 60) >= MAX_AUTO_SLOTS_PER_LABEL) { | ||
| format0 = { hour: 'numeric', minute: '2-digit', meridiem: 'lowercase' }; // '8:30 PM' | ||
| format1 = (params) => (':' + padStart(params.date.second, 2) // ':30' | ||
| ); | ||
| } | ||
| else { | ||
| format0 = { hour: 'numeric', minute: '2-digit', second: '2-digit', meridiem: 'lowercase' }; // '8:30:45 PM' | ||
| } | ||
| break; | ||
| case 'millisecond': | ||
| format0 = { hour: 'numeric', minute: '2-digit', second: '2-digit', meridiem: 'lowercase' }; // '8:30:45 PM' | ||
| format1 = (params) => ('.' + padStart(params.millisecond, 3)); | ||
| break; | ||
| } | ||
| return [].concat(format0 || [], format1 || [], format2 || []); | ||
| } | ||
| function buildCellRows(tDateProfile, dateEnv, majorUnit) { | ||
| let slotDates = tDateProfile.slotDates; | ||
| let formats = tDateProfile.headerFormats; | ||
| let cellRows = formats.map(() => []); // indexed by row,col | ||
| let slotAsDays = asCleanDays(tDateProfile.slotDuration); | ||
| let guessedSlotUnit = slotAsDays === 7 ? 'week' : | ||
| slotAsDays === 1 ? 'day' : | ||
| null; | ||
| // specifically for navclicks | ||
| let rowUnitsFromFormats = formats.map((format) => (format.getSmallestUnit ? format.getSmallestUnit() : null)); | ||
| // builds cellRows and slotCells | ||
| for (let i = 0; i < slotDates.length; i += 1) { | ||
| let date = slotDates[i]; | ||
| for (let row = 0; row < formats.length; row += 1) { | ||
| let format = formats[row]; | ||
| let rowCells = cellRows[row]; | ||
| let leadingCell = rowCells[rowCells.length - 1]; | ||
| let isLastRow = row === formats.length - 1; | ||
| let isSuperRow = formats.length > 1 && !isLastRow; // more than one row and not the last | ||
| let isMajor = isMajorUnit(date, majorUnit, dateEnv); | ||
| let newCell = null; | ||
| let rowUnit = rowUnitsFromFormats[row] || (isLastRow ? guessedSlotUnit : null); | ||
| if (isSuperRow) { | ||
| let [text] = dateEnv.format(date, format); | ||
| if (!leadingCell || (leadingCell.text !== text)) { | ||
| newCell = buildCellObject(date, isMajor, text, rowUnit); | ||
| } | ||
| else { | ||
| leadingCell.colspan += 1; | ||
| } | ||
| } | ||
| else if (!leadingCell || | ||
| isInt(dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.labelInterval))) { | ||
| let [text] = dateEnv.format(date, format); | ||
| newCell = buildCellObject(date, isMajor, text, rowUnit); | ||
| } | ||
| else { | ||
| leadingCell.colspan += 1; | ||
| } | ||
| if (newCell) { | ||
| rowCells.push(newCell); | ||
| } | ||
| } | ||
| } | ||
| return cellRows; | ||
| } | ||
| function buildCellObject(date, isMajor, text, rowUnit) { | ||
| return { date, isMajor, text, rowUnit, colspan: 1 }; // colspan mutated later | ||
| } | ||
| class TimelineSlatCell extends BaseComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // memo | ||
| this.getDateMeta = memoize(getDateMeta); | ||
| } | ||
| render() { | ||
| let { props, context } = this; | ||
| let { dateEnv, options } = context; | ||
| let { date, tDateProfile, isMajor } = props; | ||
| let dateMeta = this.getDateMeta(props.date, dateEnv, props.dateProfile, props.todayRange, props.nowDate); | ||
| let isMinor = tDateProfile.isTimeScale && | ||
| !isInt(dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, props.date, tDateProfile.labelInterval)); | ||
| let renderProps = Object.assign(Object.assign({}, dateMeta), { isMajor, | ||
| isMinor, view: context.viewApi }); | ||
| return (createElement(ContentContainer, { tag: "div", className: joinClassNames(classNames.tight, classNames.alignStart, // shrinks width of InnerContent | ||
| props.borderStart ? classNames.borderOnlyS : classNames.borderNone, classNames.internalTimelineSlot), attrs: Object.assign({ 'data-date': dateEnv.formatIso(date, { | ||
| omitTimeZoneOffset: true, | ||
| omitTime: !tDateProfile.isTimeScale, | ||
| }) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: { | ||
| width: props.width, | ||
| }, renderProps: renderProps, generatorName: undefined, classNameGenerator: options.slotLaneClass, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount })); | ||
| } | ||
| } | ||
| class TimelineSlats extends BaseComponent { | ||
| render() { | ||
| let { props } = this; | ||
| let { tDateProfile, slotWidth } = props; | ||
| let { slotDates, slotDatesMajor } = tDateProfile; | ||
| return (createElement("div", { "aria-hidden": true, className: joinClassNames(classNames.flexRow, classNames.fill), style: { height: props.height } }, slotDates.map((slotDate, i) => { | ||
| let key = slotDate.toISOString(); | ||
| return (createElement(TimelineSlatCell, { key: key, date: slotDate, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isMajor: slotDatesMajor[i], borderStart: Boolean(i), | ||
| // dimensions | ||
| width: slotWidth })); | ||
| }))); | ||
| } | ||
| } | ||
| class TimelineHeaderCell extends BaseComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // memo | ||
| this.getDateMeta = memoize(getDateMeta); | ||
| // ref | ||
| this.innerWrapperElRef = createRef(); | ||
| } | ||
| render() { | ||
| let { props, state, context } = this; | ||
| let { dateEnv, options } = context; | ||
| let { cell, dateProfile, tDateProfile } = props; | ||
| // the cell.rowUnit is f'd | ||
| // giving 'month' for a 3-day view | ||
| // workaround: to infer day, do NOT time | ||
| let dateMeta = this.getDateMeta(cell.date, dateEnv, dateProfile, props.todayRange, props.nowDate); | ||
| let hasNavLink = options.navLinks && !dateMeta.isDisabled && (cell.rowUnit && cell.rowUnit !== 'time'); | ||
| let isTime = tDateProfile.isTimeScale && !props.rowLevel; // HACK: faulty way of determining this | ||
| let renderProps = Object.assign(Object.assign({}, dateMeta), { level: props.rowLevel, isMajor: cell.isMajor, isMinor: false, isNarrow: false, isTime, | ||
| hasNavLink, text: cell.text, isFirst: props.isFirst, view: context.viewApi }); | ||
| const { slotHeaderAlign } = options; | ||
| const align = this.align = | ||
| typeof slotHeaderAlign === 'function' | ||
| ? slotHeaderAlign({ level: props.rowLevel, isTime }) | ||
| : slotHeaderAlign; | ||
| const isSticky = this.isSticky = | ||
| props.rowLevel && options.slotHeaderSticky !== false; | ||
| let edgeCoord; | ||
| if (isSticky) { | ||
| if (align === 'center') { | ||
| if (state.innerWidth != null) { | ||
| edgeCoord = `calc(50% - ${state.innerWidth / 2}px)`; | ||
| } | ||
| } | ||
| else { | ||
| edgeCoord = (typeof options.slotHeaderSticky === 'number' || | ||
| typeof options.slotHeaderSticky === 'string') ? options.slotHeaderSticky : 0; | ||
| } | ||
| } | ||
| return (createElement(ContentContainer, { tag: "div", className: joinArrayishClassNames(classNames.tight, classNames.flexCol, props.isFirst ? classNames.borderNone : classNames.borderOnlyS, align === 'center' ? classNames.alignCenter : | ||
| align === 'end' ? classNames.alignEnd : | ||
| classNames.alignStart, classNames.internalTimelineSlot), attrs: Object.assign({ 'data-date': dateEnv.formatIso(cell.date, { | ||
| omitTime: !tDateProfile.isTimeScale, | ||
| omitTimeZoneOffset: true, | ||
| }) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: { | ||
| width: props.slotWidth != null | ||
| ? props.slotWidth * cell.colspan | ||
| : undefined, | ||
| }, renderProps: renderProps, generatorName: "slotHeaderContent", customGenerator: options.slotHeaderContent, defaultGenerator: renderInnerContent, classNameGenerator: options.slotHeaderClass, didMount: options.slotHeaderDidMount, willUnmount: options.slotHeaderWillUnmount }, (InnerContent) => (createElement("div", { ref: this.innerWrapperElRef, className: joinClassNames(classNames.flexCol, classNames.rigid, isSticky && classNames.sticky), style: { | ||
| left: edgeCoord, | ||
| right: edgeCoord, | ||
| } }, | ||
| createElement(InnerContent, { tag: 'div', attrs: hasNavLink | ||
| // not tabbable because parent is aria-hidden | ||
| ? buildNavLinkAttrs(context, cell.date, cell.rowUnit, undefined, /* isTabbable = */ false) | ||
| : {} // don't bother with aria-hidden because parent already hidden | ||
| , className: generateClassName(options.slotHeaderInnerClass, renderProps) }))))); | ||
| } | ||
| componentDidMount() { | ||
| const { props } = this; | ||
| const innerWrapperEl = this.innerWrapperElRef.current; // TODO: make dynamic with useEffect | ||
| this.disconnectSize = watchSize(innerWrapperEl, (width, height) => { | ||
| setRef(props.innerWidthRef, width); | ||
| setRef(props.innerHeightRef, height); | ||
| if (this.align === 'center' && this.isSticky) { | ||
| this.setState({ innerWidth: width }); | ||
| } | ||
| }); | ||
| } | ||
| componentWillUnmount() { | ||
| const { props } = this; | ||
| this.disconnectSize(); | ||
| setRef(props.innerWidthRef, null); | ||
| setRef(props.innerHeightRef, null); | ||
| } | ||
| } | ||
| // Utils | ||
| // ------------------------------------------------------------------------------------------------- | ||
| function renderInnerContent(renderProps) { | ||
| return renderProps.text; | ||
| } | ||
| class TimelineHeaderRow extends BaseComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // refs | ||
| this.innerWidthRefMap = new RefMap(() => { | ||
| afterSize(this.handleInnerWidths); | ||
| }); | ||
| this.innerHeightRefMap = new RefMap(() => { | ||
| afterSize(this.handleInnerHeights); | ||
| }); | ||
| this.handleInnerWidths = () => { | ||
| const innerWidthMap = this.innerWidthRefMap.current; | ||
| let max = 0; | ||
| for (const innerWidth of innerWidthMap.values()) { | ||
| max = Math.max(max, innerWidth); | ||
| } | ||
| // TODO: ensure not equal? | ||
| setRef(this.props.innerWidthRef, max); | ||
| }; | ||
| this.handleInnerHeights = () => { | ||
| const innerHeightMap = this.innerHeightRefMap.current; | ||
| let max = 0; | ||
| for (const innerHeight of innerHeightMap.values()) { | ||
| max = Math.max(max, innerHeight); | ||
| } | ||
| // TODO: ensure not equal? | ||
| setRef(this.props.innerHeighRef, max); | ||
| this.setState({ innerHeight: max }); | ||
| }; | ||
| } | ||
| render() { | ||
| const { props, innerWidthRefMap, innerHeightRefMap, state, context } = this; | ||
| const { options } = context; | ||
| return (createElement("div", { className: joinArrayishClassNames(options.slotHeaderRowClass, classNames.flexRow, classNames.grow, props.rowLevel // not the last row? | ||
| ? classNames.borderOnlyB | ||
| : classNames.borderNone), style: { | ||
| // we assign height because we allow cells to have distorted heights for visual effect | ||
| // but we still want to keep the overall extrenal mass | ||
| height: state.innerHeight, | ||
| } }, props.cells.map((cell, cellI) => { | ||
| // TODO: make this part of the cell obj? | ||
| // TODO: rowUnit seems wrong sometimes. says 'month' when it should be day | ||
| // TODO: rowUnit is relevant to whole row. put it on a row object, not the cells | ||
| // TODO: use rowUnit to key the Row itself? | ||
| const key = cell.rowUnit + ':' + cell.date.toISOString(); | ||
| return (createElement(TimelineHeaderCell, { key: key, cell: cell, rowLevel: props.rowLevel, dateProfile: props.dateProfile, tDateProfile: props.tDateProfile, todayRange: props.todayRange, nowDate: props.nowDate, isFirst: cellI === 0, | ||
| // refs | ||
| innerWidthRef: innerWidthRefMap.createRef(key), innerHeightRef: innerHeightRefMap.createRef(key), | ||
| // dimensions | ||
| slotWidth: props.slotWidth })); | ||
| }))); | ||
| } | ||
| componentWillUnmount() { | ||
| setRef(this.props.innerWidthRef, null); | ||
| setRef(this.props.innerHeighRef, null); | ||
| } | ||
| } | ||
| // Timeline-specific | ||
| // ------------------------------------------------------------------------------------------------- | ||
| const MIN_SLOT_WIDTH = 30; // for real | ||
| /* | ||
| TODO: DRY with computeSlatHeight? | ||
| */ | ||
| function computeSlotWidth(slatCnt, slatsPerLabel, slatMinWidth, labelInnerWidth, viewportWidth) { | ||
| if (labelInnerWidth == null || viewportWidth == null) { | ||
| return [undefined, undefined, false]; | ||
| } | ||
| slatMinWidth = Math.max(slatMinWidth || 0, (labelInnerWidth + 1) / slatsPerLabel, MIN_SLOT_WIDTH); | ||
| const slatTryWidth = viewportWidth / slatCnt; | ||
| let slotLiquid; | ||
| let slatWidth; | ||
| if (slatTryWidth >= slatMinWidth) { | ||
| slotLiquid = true; | ||
| slatWidth = slatTryWidth; | ||
| } | ||
| else { | ||
| slotLiquid = false; | ||
| slatWidth = Math.max(slatMinWidth, slatTryWidth); | ||
| } | ||
| return [slatWidth * slatCnt, slatWidth, slotLiquid]; | ||
| } | ||
| function timeToCoord(// pixels | ||
| time, dateEnv, dateProfile, tDateProfile, slowWidth) { | ||
| let date = dateEnv.add(dateProfile.activeRange.start, time); | ||
| if (!tDateProfile.isTimeScale) { | ||
| date = startOfDay(date); | ||
| } | ||
| return dateToCoord(date, dateEnv, tDateProfile, slowWidth); | ||
| } | ||
| function dateToCoord(// pixels | ||
| date, dateEnv, tDateProfile, slotWidth) { | ||
| let snapCoverage = computeDateSnapCoverage$1(date, tDateProfile, dateEnv); | ||
| let slotCoverage = snapCoverage / tDateProfile.snapsPerSlot; | ||
| return slotCoverage * slotWidth; | ||
| } | ||
| /* | ||
| returned value is between 0 and the number of snaps | ||
| */ | ||
| function computeDateSnapCoverage$1(date, tDateProfile, dateEnv) { | ||
| let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration); | ||
| if (snapDiff < 0) { | ||
| return 0; | ||
| } | ||
| if (snapDiff >= tDateProfile.snapDiffToIndex.length) { | ||
| return tDateProfile.snapCnt; | ||
| } | ||
| let snapDiffInt = Math.floor(snapDiff); | ||
| let snapCoverage = tDateProfile.snapDiffToIndex[snapDiffInt]; | ||
| if (isInt(snapCoverage)) { // not an in-between value | ||
| snapCoverage += snapDiff - snapDiffInt; // add the remainder | ||
| } | ||
| else { | ||
| // a fractional value, meaning the date is not visible | ||
| // always round up in this case. works for start AND end dates in a range. | ||
| snapCoverage = Math.ceil(snapCoverage); | ||
| } | ||
| return snapCoverage; | ||
| } | ||
| class TimelineNowIndicatorLine extends BaseComponent { | ||
| render() { | ||
| const { props, context } = this; | ||
| const xStyle = props.slotWidth == null | ||
| ? {} | ||
| : { | ||
| insetInlineStart: dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth) | ||
| }; | ||
| return (createElement("div", { className: classNames.fill, style: { | ||
| zIndex: 2, | ||
| pointerEvents: 'none', // TODO: className | ||
| } }, | ||
| createElement(NowIndicatorLineContainer, { className: joinClassNames(classNames.fillY, classNames.noMarginY, classNames.borderlessY), style: xStyle, date: props.nowDate }), | ||
| createElement("div", { className: joinClassNames(classNames.flexCol, // better for negative margins | ||
| classNames.fillY), style: xStyle }, | ||
| createElement("div", { | ||
| // stickiness on NowIndicatorDot misbehaves b/c of negative marginss | ||
| className: classNames.stickyT }, | ||
| createElement(NowIndicatorDot, null))))); | ||
| } | ||
| } | ||
| /* | ||
| TODO: DRY with other NowIndicator components | ||
| */ | ||
| class TimelineNowIndicatorArrow extends BaseComponent { | ||
| render() { | ||
| const { props, context } = this; | ||
| const xStyle = props.slotWidth == null | ||
| ? {} | ||
| : { | ||
| insetInlineStart: dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth) | ||
| }; | ||
| return (createElement("div", { | ||
| // crop any overflow that the arrow/line might cause | ||
| // TODO: just do this on the entire canvas within the scroller | ||
| className: joinClassNames(classNames.fill, classNames.crop), style: { | ||
| zIndex: 2, | ||
| pointerEvents: 'none', // TODO: className | ||
| } }, | ||
| createElement(NowIndicatorHeaderContainer, { className: classNames.abs, style: xStyle, date: props.nowDate }))); | ||
| } | ||
| } | ||
| function getTimelineSlotEl(parentEl, index) { | ||
| return parentEl.querySelectorAll(`.${classNames.internalTimelineSlot}`)[index]; | ||
| } | ||
| /* | ||
| TODO: rename this file! | ||
| */ | ||
| // returned value is between 0 and the number of snaps | ||
| function computeDateSnapCoverage(date, tDateProfile, dateEnv) { | ||
| let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration); | ||
| if (snapDiff < 0) { | ||
| return 0; | ||
| } | ||
| if (snapDiff >= tDateProfile.snapDiffToIndex.length) { | ||
| return tDateProfile.snapCnt; | ||
| } | ||
| let snapDiffInt = Math.floor(snapDiff); | ||
| let snapCoverage = tDateProfile.snapDiffToIndex[snapDiffInt]; | ||
| if (isInt(snapCoverage)) { // not an in-between value | ||
| snapCoverage += snapDiff - snapDiffInt; // add the remainder | ||
| } | ||
| else { | ||
| // a fractional value, meaning the date is not visible | ||
| // always round up in this case. works for start AND end dates in a range. | ||
| snapCoverage = Math.ceil(snapCoverage); | ||
| } | ||
| return snapCoverage; | ||
| } | ||
| class TimelineLaneSlicer extends Slicer { | ||
| sliceRange(origRange, dateProfile, dateProfileGenerator, tDateProfile, dateEnv) { | ||
| let normalRange = normalizeRange(origRange, tDateProfile, dateEnv); | ||
| let segs = []; | ||
| // protect against when the span is entirely in an invalid date region | ||
| if (computeDateSnapCoverage(normalRange.start, tDateProfile, dateEnv) | ||
| < computeDateSnapCoverage(normalRange.end, tDateProfile, dateEnv)) { | ||
| // intersect the footprint's range with the grid's range | ||
| let slicedRange = intersectRanges(normalRange, tDateProfile.normalizedRange); | ||
| if (slicedRange) { | ||
| segs.push({ | ||
| startDate: slicedRange.start, | ||
| endDate: slicedRange.end, | ||
| isStart: slicedRange.start.valueOf() === normalRange.start.valueOf() | ||
| && isValidDate(slicedRange.start, tDateProfile, dateProfile, dateProfileGenerator), | ||
| isEnd: slicedRange.end.valueOf() === normalRange.end.valueOf() | ||
| && isValidDate(addMs(slicedRange.end, -1), tDateProfile, dateProfile, dateProfileGenerator), | ||
| }); | ||
| } | ||
| } | ||
| return segs; | ||
| } | ||
| } | ||
| const DEFAULT_TIME_FORMAT = createFormatter({ | ||
| hour: 'numeric', | ||
| minute: '2-digit', | ||
| omitZeroMinute: true, | ||
| meridiem: 'narrow', | ||
| }); | ||
| class TimelineEvent extends BaseComponent { | ||
| render() { | ||
| let { props } = this; | ||
| return (createElement(StandardEvent, Object.assign({}, props, { display: 'row', defaultTimeFormat: DEFAULT_TIME_FORMAT, defaultDisplayEventTime: !props.isTimeScale }))); | ||
| } | ||
| } | ||
| class TimelineLaneMoreLink extends BaseComponent { | ||
| render() { | ||
| let { props } = this; | ||
| let { hiddenSegs, resourceId } = props; | ||
| let dateSpanProps = resourceId ? { resourceId } : {}; | ||
| return (createElement(MoreLinkContainer, { display: 'row', allDayDate: null, segs: hiddenSegs, hiddenSegs: hiddenSegs, dateProfile: props.dateProfile, todayRange: props.todayRange, dateSpanProps: dateSpanProps, isNarrow: false, isMicro: false, popoverContent: () => (createElement(Fragment, null, hiddenSegs.map((seg) => { | ||
| let { eventRange } = seg; | ||
| let { instanceId } = eventRange.instance; | ||
| let isDragging = Boolean(props.eventDrag && props.eventDrag.affectedInstances[instanceId]); | ||
| let isResizing = Boolean(props.eventResize && props.eventResize.affectedInstances[instanceId]); | ||
| let isInvisible = isDragging || isResizing; | ||
| return (createElement("div", { key: instanceId, style: { visibility: isInvisible ? 'hidden' : undefined } }, | ||
| createElement(TimelineEvent, Object.assign({ isTimeScale: props.isTimeScale, eventRange: eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isDragging: isDragging, isResizing: isResizing, isMirror: false, isSelected: instanceId === props.eventSelection }, getEventRangeMeta(eventRange, props.todayRange, props.nowDate))))); | ||
| }))) })); | ||
| } | ||
| } | ||
| function computeManySegHorizontals(segs, segMinWidth, dateEnv, tDateProfile, slotWidth) { | ||
| const res = {}; | ||
| for (const seg of segs) { | ||
| res[getEventKey(seg)] = computeSegHorizontals(seg, segMinWidth, dateEnv, tDateProfile, slotWidth); | ||
| } | ||
| return res; | ||
| } | ||
| function computeSegHorizontals(seg, segMinWidth, dateEnv, tDateProfile, slotWidth) { | ||
| const startCoord = dateToCoord(seg.startDate, dateEnv, tDateProfile, slotWidth); | ||
| const endCoord = dateToCoord(seg.endDate, dateEnv, tDateProfile, slotWidth); | ||
| let size = endCoord - startCoord; | ||
| if (segMinWidth) { | ||
| size = Math.max(size, segMinWidth); | ||
| } | ||
| return { start: startCoord, size }; | ||
| } | ||
| function computeFgSegPlacements(// mostly horizontals | ||
| segs, segHorizontals, // TODO: use Map | ||
| segHeights, // keyed by instanceId | ||
| hiddenGroupHeights, strictOrder, maxDepth) { | ||
| const segRanges = []; | ||
| // isn't it true that there will either be ALL hcoords or NONE? can optimize | ||
| for (const seg of segs) { | ||
| const hcoords = segHorizontals[getEventKey(seg)]; | ||
| if (hcoords) { | ||
| segRanges.push(Object.assign(Object.assign({}, seg), { start: hcoords.start, end: hcoords.start + hcoords.size })); | ||
| } | ||
| } | ||
| const hierarchy = new SegHierarchy(segRanges, (seg) => segHeights.get(getEventKey(seg)), strictOrder, undefined, // maxCoord | ||
| maxDepth); | ||
| const segTops = new Map(); | ||
| hierarchy.traverseSegs((seg, segTop) => { | ||
| segTops.set(getEventKey(seg), segTop); | ||
| }); | ||
| const { hiddenSegs } = hierarchy; | ||
| let totalHeight = 0; | ||
| for (const segRange of segRanges) { | ||
| const segKey = getEventKey(segRange); | ||
| const segHeight = segHeights.get(segKey); | ||
| const segTop = segTops.get(segKey); | ||
| if (segHeight != null) { | ||
| if (segTop != null) { | ||
| totalHeight = Math.max(totalHeight, segTop + segHeight); | ||
| } | ||
| } | ||
| } | ||
| const hiddenGroups = groupIntersectingSegs(hiddenSegs); | ||
| const hiddenGroupTops = new Map(); | ||
| // HACK for hiddenGroup findInsertion() call | ||
| hierarchy.strictOrder = true; | ||
| for (const hiddenGroup of hiddenGroups) { | ||
| const { levelCoord: top } = hierarchy.findInsertion(hiddenGroup, 0); | ||
| const hiddenGroupHeight = hiddenGroupHeights.get(hiddenGroup.key) || 0; | ||
| hiddenGroupTops.set(hiddenGroup.key, top); | ||
| totalHeight = Math.max(totalHeight, top + hiddenGroupHeight); | ||
| } | ||
| return [ | ||
| segTops, | ||
| hiddenGroups, | ||
| hiddenGroupTops, | ||
| totalHeight, | ||
| ]; | ||
| } | ||
| /* | ||
| TODO: make DRY with other Event Harnesses | ||
| */ | ||
| class TimelineEventHarness extends Component { | ||
| constructor() { | ||
| super(...arguments); | ||
| // ref | ||
| this.rootElRef = createRef(); | ||
| } | ||
| render() { | ||
| const { props } = this; | ||
| return (createElement("div", { className: classNames.abs, style: props.style, ref: this.rootElRef }, props.children)); | ||
| } | ||
| componentDidMount() { | ||
| const rootEl = this.rootElRef.current; // TODO: make dynamic with useEffect | ||
| this.disconnectHeight = watchHeight(rootEl, (height) => { | ||
| setRef(this.props.heightRef, height); | ||
| }); | ||
| } | ||
| componentWillUnmount() { | ||
| this.disconnectHeight(); | ||
| setRef(this.props.heightRef, null); | ||
| } | ||
| } | ||
| class TimelineFg extends BaseComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // memo | ||
| this.sortEventSegs = memoize(sortEventSegs); | ||
| // refs | ||
| this.segHeightRefMap = new RefMap(() => { | ||
| afterSize(this.handleSegHeights); | ||
| }); | ||
| this.moreLinkHeightRefMap = new RefMap(() => { | ||
| afterSize(this.handleMoreLinkHeights); | ||
| }); | ||
| this.handleMoreLinkHeights = () => { | ||
| this.setState({ moreLinkHeightRev: this.moreLinkHeightRefMap.rev }); // will trigger rerender | ||
| }; | ||
| this.handleSegHeights = () => { | ||
| this.setState({ segHeightRev: this.segHeightRefMap.rev }); // will trigger rerender | ||
| }; | ||
| } | ||
| /* | ||
| TODO: lots of memoization needed here! | ||
| */ | ||
| render() { | ||
| let { props, context, segHeightRefMap, moreLinkHeightRefMap } = this; | ||
| let { options } = context; | ||
| let { tDateProfile } = props; | ||
| let mirrorSegs = (props.eventDrag ? props.eventDrag.segs : null) || | ||
| (props.eventResize ? props.eventResize.segs : null) || | ||
| []; | ||
| let fgSegs = this.sortEventSegs(props.fgEventSegs, options.eventOrder); | ||
| let fgSegHorizontals = props.slotWidth != null | ||
| ? computeManySegHorizontals(fgSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth) | ||
| : {}; | ||
| let [fgSegTops, hiddenGroups, hiddenGroupTops, totalHeight] = computeFgSegPlacements(fgSegs, fgSegHorizontals, segHeightRefMap.current, moreLinkHeightRefMap.current, options.eventOrderStrict, options.eventMaxStack); | ||
| this.totalHeight = totalHeight; | ||
| return (createElement("div", { className: joinClassNames(classNames.rel, classNames.noShrink), style: { | ||
| height: totalHeight, | ||
| } }, | ||
| this.renderFgSegs(fgSegs, fgSegHorizontals, fgSegTops, hiddenGroups, hiddenGroupTops, | ||
| /* isMirror = */ false), | ||
| this.renderFgSegs(mirrorSegs, props.slotWidth // TODO: memoize | ||
| ? computeManySegHorizontals(mirrorSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth) | ||
| : {}, fgSegTops, | ||
| /* hiddenGroups = */ [], | ||
| /* hiddenGroupTops = */ new Map(), | ||
| /* isMirror = */ true))); | ||
| } | ||
| renderFgSegs(segs, segHorizontals, segTops, hiddenGroups, hiddenGroupTops, isMirror) { | ||
| let { props, segHeightRefMap, moreLinkHeightRefMap } = this; | ||
| return (createElement(Fragment, null, | ||
| segs.map((seg) => { | ||
| const { eventRange } = seg; | ||
| const { instanceId } = eventRange.instance; | ||
| const segTop = segTops.get(instanceId); | ||
| const segHorizontalMaybe = segHorizontals[instanceId]; | ||
| const segHorizontal = segHorizontalMaybe || {}; | ||
| const isDragging = Boolean(props.eventDrag && props.eventDrag.affectedInstances[instanceId]); | ||
| const isResizing = Boolean(props.eventResize && props.eventResize.affectedInstances[instanceId]); | ||
| const isInvisible = !isMirror && (isDragging || isResizing || !segHorizontalMaybe || segTop == null); | ||
| return (createElement(TimelineEventHarness, { key: instanceId, style: { | ||
| visibility: isInvisible ? 'hidden' : undefined, | ||
| zIndex: 1, | ||
| top: segTop || 0, | ||
| insetInlineStart: segHorizontal.start, | ||
| width: segHorizontal.size, | ||
| }, heightRef: isMirror ? undefined : segHeightRefMap.createRef(instanceId) }, | ||
| createElement(TimelineEvent, Object.assign({ isTimeScale: props.tDateProfile.isTimeScale, eventRange: eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isDragging: isDragging, isResizing: isResizing, isMirror: isMirror, isSelected: instanceId === props.eventSelection }, getEventRangeMeta(eventRange, props.todayRange, props.nowDate))))); | ||
| }), | ||
| hiddenGroups.map((hiddenGroup) => (createElement(TimelineEventHarness, { key: hiddenGroup.key, style: { | ||
| top: hiddenGroupTops.get(hiddenGroup.key) || 0, | ||
| insetInlineStart: hiddenGroup.start, | ||
| width: hiddenGroup.end - hiddenGroup.start, | ||
| }, heightRef: moreLinkHeightRefMap.createRef(hiddenGroup.key) }, | ||
| createElement(TimelineLaneMoreLink, { hiddenSegs: hiddenGroup.segs, dateProfile: props.dateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isTimeScale: props.tDateProfile.isTimeScale, eventDrag: props.eventDrag, eventResize: props.eventResize, eventSelection: props.eventSelection, resourceId: props.resourceId })))))); | ||
| } | ||
| /* | ||
| componentDidMount(): void { | ||
| // might want to do firedTotalHeight, but won't be ready on first render | ||
| } | ||
| */ | ||
| componentDidUpdate() { | ||
| if (this.totalHeight !== this.firedTotalHeight) { | ||
| this.firedTotalHeight = this.totalHeight; | ||
| setRef(this.props.heightRef, this.totalHeight); | ||
| } | ||
| } | ||
| componentWillUnmount() { | ||
| setRef(this.props.heightRef, null); | ||
| } | ||
| } | ||
| class TimelineBg extends BaseComponent { | ||
| render() { | ||
| let { props } = this; | ||
| let highlightSeg = [].concat(props.eventResizeSegs || [], props.dateSelectionSegs); | ||
| return (createElement(Fragment, null, | ||
| this.renderSegs(props.businessHourSegs || [], 'non-business'), | ||
| this.renderSegs(props.bgEventSegs || [], 'bg-event'), | ||
| this.renderSegs(highlightSeg, 'highlight'))); | ||
| } | ||
| renderSegs(segs, fillType) { | ||
| let { tDateProfile, todayRange, nowDate, slotWidth } = this.props; | ||
| let { dateEnv, options } = this.context; | ||
| return (createElement(Fragment, null, segs.map((seg) => { | ||
| let hStyle = {}; | ||
| if (slotWidth != null) { | ||
| let segHorizontal = computeSegHorizontals(seg, undefined, dateEnv, tDateProfile, slotWidth); | ||
| hStyle = { insetInlineStart: segHorizontal.start, width: segHorizontal.size }; | ||
| } | ||
| return (createElement("div", { key: buildEventRangeKey(seg.eventRange), className: classNames.fillY, style: hStyle }, fillType === 'bg-event' ? (createElement(BgEvent, Object.assign({ eventRange: seg.eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isVertical: false }, getEventRangeMeta(seg.eventRange, todayRange, nowDate)))) : (renderFill(fillType, options)))); | ||
| }))); | ||
| } | ||
| } | ||
| class TimelineView extends DateComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // memoized | ||
| this.buildTimelineDateProfile = memoize(buildTimelineDateProfile); | ||
| this.computeSlotWidth = memoize(computeSlotWidth); | ||
| // refs | ||
| this.headerScrollerRef = createRef(); | ||
| this.bodyScrollerRef = createRef(); | ||
| this.footerScrollerRef = createRef(); | ||
| this.headerRowInnerWidthMap = new RefMap(() => { | ||
| afterSize(this.handleSlotInnerWidths); | ||
| }); | ||
| this.scrollTime = null; | ||
| this.slicer = new TimelineLaneSlicer(); | ||
| // Sizing | ||
| // ----------------------------------------------------------------------------------------------- | ||
| this.handleSlotInnerWidths = () => { | ||
| const headerSlotInnerWidth = this.headerRowInnerWidthMap.current.get(this.tDateProfile.cellRows.length - 1); | ||
| if (headerSlotInnerWidth != null && headerSlotInnerWidth !== this.state.slotInnerWidth) { | ||
| this.setState({ slotInnerWidth: headerSlotInnerWidth }); | ||
| } | ||
| }; | ||
| this.handleTotalWidth = (totalWidth) => { | ||
| this.setState({ | ||
| totalWidth, | ||
| }); | ||
| }; | ||
| this.handleClientWidth = (clientWidth) => { | ||
| this.setState({ | ||
| clientWidth, | ||
| }); | ||
| }; | ||
| this.handleTimeScrollRequest = (scrollTime) => { | ||
| this.scrollTime = scrollTime; | ||
| this.applyTimeScroll(); | ||
| }; | ||
| this.handleTimeScrollEnd = (isUser) => { | ||
| if (isUser) { | ||
| this.scrollTime = null; | ||
| } | ||
| }; | ||
| // Hit System | ||
| // ----------------------------------------------------------------------------------------------- | ||
| this.handeBodyEl = (el) => { | ||
| this.bodyEl = el; | ||
| if (el) { | ||
| this.context.registerInteractiveComponent(this, { el }); | ||
| } | ||
| else { | ||
| this.context.unregisterInteractiveComponent(this); | ||
| } | ||
| }; | ||
| } | ||
| render() { | ||
| const { props, state, context } = this; | ||
| const { options } = context; | ||
| const { totalWidth, clientWidth } = state; | ||
| const endScrollbarWidth = (totalWidth != null && clientWidth != null) | ||
| ? totalWidth - clientWidth | ||
| : undefined; | ||
| /* date */ | ||
| const tDateProfile = this.tDateProfile = this.buildTimelineDateProfile(props.dateProfile, context.dateEnv, options, context.dateProfileGenerator); | ||
| const { cellRows } = tDateProfile; | ||
| const timerUnit = greatestDurationDenominator(tDateProfile.slotDuration).unit; | ||
| /* table settings */ | ||
| const verticalScrolling = !props.forPrint && !getIsHeightAuto(options); | ||
| const stickyHeaderDates = !props.forPrint && getStickyHeaderDates(options); | ||
| const stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(options); | ||
| /* table positions */ | ||
| const [canvasWidth, slotWidth] = this.computeSlotWidth(tDateProfile.slotCnt, tDateProfile.slotsPerLabel, options.slotMinWidth, state.slotInnerWidth, // is ACTUALLY the label width. rename? | ||
| clientWidth); | ||
| this.slotWidth = slotWidth; | ||
| /* sliced */ | ||
| let slicedProps = this.slicer.sliceProps(props, props.dateProfile, tDateProfile.isTimeScale ? null : options.nextDayThreshold, context, // wish we didn't have to pass in the rest of the args... | ||
| props.dateProfile, context.dateProfileGenerator, tDateProfile, context.dateEnv); | ||
| return (createElement(NowTimer, { unit: timerUnit }, (nowDate, todayRange) => { | ||
| const enableNowIndicator = // TODO: DRY | ||
| options.nowIndicator && | ||
| slotWidth != null && | ||
| rangeContainsMarker(props.dateProfile.currentRange, nowDate); | ||
| return (createElement(ViewContainer, { viewSpec: context.viewSpec, className: joinArrayishClassNames( | ||
| // HACK for Safari print-mode, where noScrollbars won't take effect for | ||
| // the below Scrollers if they have liquid flex height | ||
| !props.forPrint && classNames.flexCol, props.className, options.tableClass, classNames.isolate), borderlessX: props.borderlessX, borderlessTop: props.borderlessTop, borderlessBottom: props.borderlessBottom, noEdgeEffects: props.noEdgeEffects }, | ||
| createElement("div", { className: joinClassNames(generateClassName(options.tableHeaderClass, { | ||
| isSticky: stickyHeaderDates, | ||
| }), props.borderlessX && classNames.borderlessX, classNames.flexCol, stickyHeaderDates && classNames.tableHeaderSticky), style: { | ||
| zIndex: 1, | ||
| } }, | ||
| createElement(Scroller, { horizontal: true, hideScrollbars: true, className: classNames.flexRow, ref: this.headerScrollerRef }, | ||
| createElement("div", { | ||
| // TODO: DRY | ||
| className: joinClassNames(classNames.rel, // origin for now-indicator | ||
| canvasWidth == null && classNames.liquid), style: { width: canvasWidth } }, | ||
| cellRows.map((cells, rowIndex) => { | ||
| const rowLevel = cellRows.length - rowIndex - 1; | ||
| return (createElement(TimelineHeaderRow, { key: rowIndex, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, rowLevel: rowLevel, cells: cells, slotWidth: slotWidth, innerWidthRef: this.headerRowInnerWidthMap.createRef(rowIndex) })); | ||
| }), | ||
| enableNowIndicator && (createElement(TimelineNowIndicatorArrow, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth }))), | ||
| Boolean(endScrollbarWidth) && (createElement("div", { className: joinArrayishClassNames(generateClassName(options.fillerClass, { isHeader: true }), classNames.borderOnlyS), style: { minWidth: endScrollbarWidth } }))), | ||
| createElement("div", { className: generateClassName(options.slotHeaderDividerClass, { | ||
| isHeader: true, | ||
| options: { dayMinWidth: options.dayMinWidth }, | ||
| }) })), | ||
| createElement(Scroller, { vertical: verticalScrolling, horizontal: true, hideScrollbars: stickyFooterScrollbar || | ||
| props.forPrint // prevents blank space in print-view on Safari | ||
| , className: joinArrayishClassNames(options.tableBodyClass, props.borderlessX && classNames.borderlessX, stickyHeaderDates && classNames.borderlessTop, (stickyHeaderDates || props.noEdgeEffects) && classNames.noEdgeEffects, classNames.flexCol, verticalScrolling && classNames.liquid), style: { | ||
| zIndex: 0, | ||
| }, ref: this.bodyScrollerRef, clientWidthRef: this.handleClientWidth }, | ||
| createElement("div", { "aria-label": options.eventsHint, className: joinClassNames(classNames.rel, // for canvas origin? | ||
| classNames.grow), style: { width: canvasWidth }, ref: this.handeBodyEl }, | ||
| createElement(TimelineSlats, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, | ||
| // dimensions | ||
| slotWidth: slotWidth }), | ||
| createElement(TimelineBg, { tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, | ||
| // content | ||
| bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventResizeSegs: slicedProps.eventResize ? slicedProps.eventResize.segs : null, | ||
| // dimensions | ||
| slotWidth: slotWidth }), | ||
| createElement("div", { className: joinArrayishClassNames(options.timelineTopClass) }), | ||
| createElement(TimelineFg, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, | ||
| // content | ||
| fgEventSegs: slicedProps.fgEventSegs, eventDrag: slicedProps.eventDrag, eventResize: slicedProps.eventResize, eventSelection: slicedProps.eventSelection, | ||
| // dimensions | ||
| slotWidth: slotWidth }), | ||
| createElement("div", { className: joinArrayishClassNames(options.timelineBottomClass) }), | ||
| enableNowIndicator && (createElement(TimelineNowIndicatorLine, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth })))), | ||
| Boolean(stickyFooterScrollbar) && (createElement(FooterScrollbar, { isSticky: true, canvasWidth: canvasWidth, scrollerRef: this.footerScrollerRef })), | ||
| createElement(Ruler, { widthRef: this.handleTotalWidth }))); | ||
| })); | ||
| } | ||
| // Lifecycle | ||
| // ----------------------------------------------------------------------------------------------- | ||
| componentDidMount() { | ||
| this.syncedScroller = new ScrollerSyncer(true); // horizontal=true | ||
| this.updateSyncedScroller(); | ||
| this.resetScroll(); | ||
| this.context.emitter.on('_timeScrollRequest', this.handleTimeScrollRequest); | ||
| this.syncedScroller.addScrollEndListener(this.handleTimeScrollEnd); | ||
| } | ||
| componentDidUpdate(prevProps) { | ||
| this.updateSyncedScroller(); | ||
| if (prevProps.dateProfile !== this.props.dateProfile && this.context.options.scrollTimeReset) { | ||
| this.resetScroll(); | ||
| } | ||
| else { | ||
| // TODO: inefficient to update so often | ||
| this.applyTimeScroll(); | ||
| } | ||
| } | ||
| componentWillUnmount() { | ||
| this.syncedScroller.destroy(); | ||
| this.context.emitter.off('_timeScrollRequest', this.handleTimeScrollRequest); | ||
| this.syncedScroller.removeScrollEndListener(this.handleTimeScrollEnd); | ||
| } | ||
| // Scrolling | ||
| // ----------------------------------------------------------------------------------------------- | ||
| updateSyncedScroller() { | ||
| this.syncedScroller.handleChildren([ | ||
| this.headerScrollerRef.current, | ||
| this.bodyScrollerRef.current, | ||
| this.footerScrollerRef.current | ||
| ]); | ||
| } | ||
| resetScroll() { | ||
| this.handleTimeScrollRequest(this.context.options.scrollTime); | ||
| } | ||
| applyTimeScroll() { | ||
| const { props, context, tDateProfile, scrollTime, slotWidth } = this; | ||
| if (scrollTime != null && slotWidth != null) { | ||
| let x = timeToCoord(scrollTime, context.dateEnv, props.dateProfile, tDateProfile, slotWidth); | ||
| if (x) { | ||
| x += 1; // overcome border. TODO: DRY this up | ||
| } | ||
| this.syncedScroller.scrollTo({ x }); | ||
| } | ||
| } | ||
| queryHit(isRtl, positionLeft, positionTop, elWidth, elHeight) { | ||
| const { props, context, tDateProfile, slotWidth } = this; | ||
| const { dateEnv } = context; | ||
| if (slotWidth) { | ||
| const x = isRtl ? elWidth - positionLeft : positionLeft; | ||
| const slatIndex = Math.floor(x / slotWidth); | ||
| const slatX = slatIndex * slotWidth; | ||
| const partial = (x - slatX) / slotWidth; // floating point number between 0 and 1 | ||
| const localSnapIndex = Math.floor(partial * tDateProfile.snapsPerSlot); // the snap # relative to start of slat | ||
| let startDate = dateEnv.add(tDateProfile.slotDates[slatIndex], multiplyDuration(tDateProfile.snapDuration, localSnapIndex)); | ||
| let endDate = dateEnv.add(startDate, tDateProfile.snapDuration); | ||
| // TODO: generalize this coord stuff to TimeGrid? | ||
| let snapWidth = slotWidth / tDateProfile.snapsPerSlot; | ||
| let startCoord = slatIndex * slotWidth + (snapWidth * localSnapIndex); | ||
| let endCoord = startCoord + snapWidth; | ||
| let left, right; | ||
| if (isRtl) { | ||
| left = elWidth - endCoord; | ||
| right = elWidth - startCoord; | ||
| } | ||
| else { | ||
| left = startCoord; | ||
| right = endCoord; | ||
| } | ||
| return { | ||
| dateProfile: props.dateProfile, | ||
| dateSpan: { | ||
| range: { start: startDate, end: endDate }, | ||
| allDay: !tDateProfile.isTimeScale, | ||
| }, | ||
| rect: { | ||
| left, | ||
| right, | ||
| top: 0, | ||
| bottom: elHeight, | ||
| }, | ||
| getDayEl: () => getTimelineSlotEl(this.bodyEl, slatIndex), | ||
| layer: 0, | ||
| }; | ||
| } | ||
| return null; | ||
| } | ||
| } | ||
| export { TimelineBg, TimelineFg, TimelineHeaderRow, TimelineLaneSlicer, TimelineNowIndicatorArrow, TimelineNowIndicatorLine, TimelineSlats, TimelineView, buildTimelineDateProfile, computeSlotWidth, getTimelineSlotEl, timeToCoord }; |
Sorry, the diff of this file is too big to display
| /*! | ||
| FullCalendar Timeline Plugin v7.0.0-beta.5 | ||
| Docs & License: https://fullcalendar.io/docs/timeline-view-no-resources | ||
| (c) 2025 Adam Shaw | ||
| */ | ||
| FullCalendar.Timeline=function(e,t,n,i,a,s,r){"use strict";function l(e){return e&&e.__esModule?e:{default:e}}var o=l(n),d=l(a);i.config.MAX_TIMELINE_SLOTS=1e3;const c=[{years:1},{months:1},{days:1},{hours:1},{minutes:30},{minutes:15},{minutes:10},{minutes:5},{minutes:1},{seconds:30},{seconds:15},{seconds:10},{seconds:5},{seconds:1},{milliseconds:500},{milliseconds:100},{milliseconds:10},{milliseconds:1}];function u(e,t,n,a){let s={labelInterval:n.slotHeaderInterval,slotDuration:n.slotDuration};!function(e,t,n){const{currentRange:a}=t;if(e.labelInterval){n.countDurationsBetween(a.start,a.end,e.labelInterval)>i.config.MAX_TIMELINE_SLOTS&&(console.warn("slotHeaderInterval results in too many cells"),e.labelInterval=null)}if(e.slotDuration){n.countDurationsBetween(a.start,a.end,e.slotDuration)>i.config.MAX_TIMELINE_SLOTS&&(console.warn("slotDuration results in too many cells"),e.slotDuration=null)}if(e.labelInterval&&e.slotDuration){const t=i.wholeDivideDurations(e.labelInterval,e.slotDuration);(null===t||t<1)&&(console.warn("slotHeaderInterval must be a multiple of slotDuration"),e.slotDuration=null)}}(s,e,t),f(s,e,t),function(e,t,n){const{currentRange:a}=t;let{slotDuration:s}=e;if(!s){const r=f(e,t,n);for(let e of c){const t=i.createDuration(e),n=i.wholeDivideDurations(r,t);if(null!==n&&n>1&&n<=6){s=t;break}}if(s){n.countDurationsBetween(a.start,a.end,s)>200&&(s=null)}s||(s=r),e.slotDuration=s}}(s,e,t);let r=n.slotHeaderFormat,l=Array.isArray(r)?r:null!=r?[r]:function(e,t,n,a){let s,r;const{labelInterval:l}=e,{currentRange:o}=t;let d=i.greatestDurationDenominator(l).unit;const c=a.weekNumbers;let u=s=r=null;"week"!==d||c||(d="day");switch(d){case"year":u={year:"numeric"};break;case"month":n.diffWholeYears(o.start,o.end)>1&&(u={year:"numeric"}),s={month:"short"};break;case"week":n.diffWholeYears(o.start,o.end)>1&&(u={year:"numeric"}),s={week:"narrow"};break;case"day":n.diffWholeYears(o.start,o.end)>1?u={year:"numeric",month:"long"}:n.diffWholeMonths(o.start,o.end)>1&&(u={month:"long"}),c&&(s={week:"short"}),r={weekday:"narrow",day:"numeric"};break;case"hour":c&&(u={week:"short"}),i.diffWholeDays(o.start,o.end)>1&&(s={weekday:"short",day:"numeric",month:"numeric",omitCommas:!0}),r={hour:"numeric",minute:"2-digit",omitZeroMinute:!0,meridiem:"short"};break;case"minute":i.asRoughMinutes(l)/60>=6?(u={hour:"numeric",meridiem:"short"},s=e=>":"+i.padStart(e.date.minute,2)):u={hour:"numeric",minute:"numeric",meridiem:"short"};break;case"second":i.asRoughSeconds(l)/60>=6?(u={hour:"numeric",minute:"2-digit",meridiem:"lowercase"},s=e=>":"+i.padStart(e.date.second,2)):u={hour:"numeric",minute:"2-digit",second:"2-digit",meridiem:"lowercase"};break;case"millisecond":u={hour:"numeric",minute:"2-digit",second:"2-digit",meridiem:"lowercase"},s=e=>"."+i.padStart(e.millisecond,3)}return[].concat(u||[],s||[],r||[])}(s,e,t,n);s.headerFormats=l.map(e=>i.createFormatter(e)),s.isTimeScale=Boolean(s.slotDuration.milliseconds);let o=null;if(!s.isTimeScale){const e=i.greatestDurationDenominator(s.slotDuration).unit;/year|month|week/.test(e)&&(o=e)}s.largeUnit=o;let d,u,p=n.snapDuration;p&&(d=i.createDuration(p),u=i.wholeDivideDurations(s.slotDuration,d)),null==u&&(d=s.slotDuration,u=1),s.snapDuration=d,s.snapsPerSlot=u;let S=i.asRoughMs(e.slotMaxTime)-i.asRoughMs(e.slotMinTime),v=h(e.renderRange.start,s,t),D=h(e.renderRange.end,s,t);s.isTimeScale&&(v=t.add(v,e.slotMinTime),D=t.add(i.addDays(D,-1),e.slotMaxTime)),s.timeWindowMs=S,s.normalizedRange={start:v,end:D};let R=[],y=[],w=v,E=i.computeMajorUnit(e,t);for(;w<D;)m(w,s,e,a)&&(R.push(w),y.push(i.isMajorUnit(w,E,t))),w=t.add(w,s.slotDuration);s.slotDates=R,s.slotDatesMajor=y;let M=-1,b=0;const T=[],C=[];for(w=v;w<D;)m(w,s,e,a)?(M+=1,T.push(M),C.push(b)):T.push(M+.5),w=t.add(w,s.snapDuration),b+=1;return s.snapDiffToIndex=T,s.snapIndexToDiff=C,s.snapCnt=M+1,s.slotCnt=s.snapCnt/s.snapsPerSlot,s.cellRows=function(e,t,n){let a=e.slotDates,s=e.headerFormats,r=s.map(()=>[]),l=i.asCleanDays(e.slotDuration),o=7===l?"week":1===l?"day":null,d=s.map(e=>e.getSmallestUnit?e.getSmallestUnit():null);for(let l=0;l<a.length;l+=1){let c=a[l];for(let a=0;a<s.length;a+=1){let l=s[a],u=r[a],h=u[u.length-1],m=a===s.length-1,f=s.length>1&&!m,p=i.isMajorUnit(c,n,t),S=null,v=d[a]||(m?o:null);if(f){let[e]=t.format(c,l);h&&h.text===e?h.colspan+=1:S=g(c,p,e,v)}else if(!h||i.isInt(t.countDurationsBetween(e.normalizedRange.start,c,e.labelInterval))){let[e]=t.format(c,l);S=g(c,p,e,v)}else h.colspan+=1;S&&u.push(S)}}return r}(s,t,E),s.slotsPerLabel=i.wholeDivideDurations(s.labelInterval,s.slotDuration),s}function h(e,t,n){let a=e;return t.isTimeScale||(a=i.startOfDay(a),t.largeUnit&&(a=n.startOf(a,t.largeUnit))),a}function m(e,t,n,a){if(a.isHiddenDay(e))return!1;if(t.isTimeScale){let a=i.startOfDay(e),s=e.valueOf()-a.valueOf()-i.asRoughMs(n.slotMinTime);return s=(s%864e5+864e5)%864e5,s<t.timeWindowMs}return!0}function f(e,t,n){const{currentRange:a}=t;let{labelInterval:s}=e;if(!s){let t;if(e.slotDuration){for(t of c){const n=i.createDuration(t),a=i.wholeDivideDurations(n,e.slotDuration);if(null!==a&&a<=6){s=n;break}}s||(s=e.slotDuration)}else for(t of c){s=i.createDuration(t);if(n.countDurationsBetween(a.start,a.end,s)>=18)break}e.labelInterval=s}return s}function g(e,t,n,i){return{date:e,isMajor:t,text:n,rowUnit:i,colspan:1}}class p extends i.BaseComponent{constructor(){super(...arguments),this.getDateMeta=i.memoize(i.getDateMeta)}render(){let{props:e,context:n}=this,{dateEnv:a,options:r}=n,{date:l,tDateProfile:o,isMajor:c}=e,u=this.getDateMeta(e.date,a,e.dateProfile,e.todayRange,e.nowDate),h=o.isTimeScale&&!i.isInt(a.countDurationsBetween(o.normalizedRange.start,e.date,o.labelInterval)),m=Object.assign(Object.assign({},u),{isMajor:c,isMinor:h,view:n.viewApi});return s.createElement(i.ContentContainer,{tag:"div",className:t.joinClassNames(d.default.tight,d.default.alignStart,e.borderStart?d.default.borderOnlyS:d.default.borderNone,d.default.internalTimelineSlot),attrs:Object.assign({"data-date":a.formatIso(l,{omitTimeZoneOffset:!0,omitTime:!o.isTimeScale})},u.isToday?{"aria-current":"date"}:{}),style:{width:e.width},renderProps:m,generatorName:void 0,classNameGenerator:r.slotLaneClass,didMount:r.slotLaneDidMount,willUnmount:r.slotLaneWillUnmount})}}class S extends i.BaseComponent{render(){let{props:e}=this,{tDateProfile:n,slotWidth:i}=e,{slotDates:a,slotDatesMajor:r}=n;return s.createElement("div",{"aria-hidden":!0,className:t.joinClassNames(d.default.flexRow,d.default.fill),style:{height:e.height}},a.map((t,a)=>{let l=t.toISOString();return s.createElement(p,{key:l,date:t,dateProfile:e.dateProfile,tDateProfile:n,nowDate:e.nowDate,todayRange:e.todayRange,isMajor:r[a],borderStart:Boolean(a),width:i})}))}}class v extends i.BaseComponent{constructor(){super(...arguments),this.getDateMeta=i.memoize(i.getDateMeta),this.innerWrapperElRef=s.createRef()}render(){let{props:e,state:n,context:a}=this,{dateEnv:r,options:l}=a,{cell:o,dateProfile:c,tDateProfile:u}=e,h=this.getDateMeta(o.date,r,c,e.todayRange,e.nowDate),m=l.navLinks&&!h.isDisabled&&o.rowUnit&&"time"!==o.rowUnit,f=u.isTimeScale&&!e.rowLevel,g=Object.assign(Object.assign({},h),{level:e.rowLevel,isMajor:o.isMajor,isMinor:!1,isNarrow:!1,isTime:f,hasNavLink:m,text:o.text,isFirst:e.isFirst,view:a.viewApi});const{slotHeaderAlign:p}=l,S=this.align="function"==typeof p?p({level:e.rowLevel,isTime:f}):p,v=this.isSticky=e.rowLevel&&!1!==l.slotHeaderSticky;let R;return v&&("center"===S?null!=n.innerWidth&&(R=`calc(50% - ${n.innerWidth/2}px)`):R="number"==typeof l.slotHeaderSticky||"string"==typeof l.slotHeaderSticky?l.slotHeaderSticky:0),s.createElement(i.ContentContainer,{tag:"div",className:i.joinArrayishClassNames(d.default.tight,d.default.flexCol,e.isFirst?d.default.borderNone:d.default.borderOnlyS,"center"===S?d.default.alignCenter:"end"===S?d.default.alignEnd:d.default.alignStart,d.default.internalTimelineSlot),attrs:Object.assign({"data-date":r.formatIso(o.date,{omitTime:!u.isTimeScale,omitTimeZoneOffset:!0})},h.isToday?{"aria-current":"date"}:{}),style:{width:null!=e.slotWidth?e.slotWidth*o.colspan:void 0},renderProps:g,generatorName:"slotHeaderContent",customGenerator:l.slotHeaderContent,defaultGenerator:D,classNameGenerator:l.slotHeaderClass,didMount:l.slotHeaderDidMount,willUnmount:l.slotHeaderWillUnmount},e=>s.createElement("div",{ref:this.innerWrapperElRef,className:t.joinClassNames(d.default.flexCol,d.default.rigid,v&&d.default.sticky),style:{left:R,right:R}},s.createElement(e,{tag:"div",attrs:m?i.buildNavLinkAttrs(a,o.date,o.rowUnit,void 0,!1):{},className:i.generateClassName(l.slotHeaderInnerClass,g)})))}componentDidMount(){const{props:e}=this,t=this.innerWrapperElRef.current;this.disconnectSize=i.watchSize(t,(t,n)=>{i.setRef(e.innerWidthRef,t),i.setRef(e.innerHeightRef,n),"center"===this.align&&this.isSticky&&this.setState({innerWidth:t})})}componentWillUnmount(){const{props:e}=this;this.disconnectSize(),i.setRef(e.innerWidthRef,null),i.setRef(e.innerHeightRef,null)}}function D(e){return e.text}class R extends i.BaseComponent{constructor(){super(...arguments),this.innerWidthRefMap=new i.RefMap(()=>{i.afterSize(this.handleInnerWidths)}),this.innerHeightRefMap=new i.RefMap(()=>{i.afterSize(this.handleInnerHeights)}),this.handleInnerWidths=()=>{const e=this.innerWidthRefMap.current;let t=0;for(const n of e.values())t=Math.max(t,n);i.setRef(this.props.innerWidthRef,t)},this.handleInnerHeights=()=>{const e=this.innerHeightRefMap.current;let t=0;for(const n of e.values())t=Math.max(t,n);i.setRef(this.props.innerHeighRef,t),this.setState({innerHeight:t})}}render(){const{props:e,innerWidthRefMap:t,innerHeightRefMap:n,state:a,context:r}=this,{options:l}=r;return s.createElement("div",{className:i.joinArrayishClassNames(l.slotHeaderRowClass,d.default.flexRow,d.default.grow,e.rowLevel?d.default.borderOnlyB:d.default.borderNone),style:{height:a.innerHeight}},e.cells.map((i,a)=>{const r=i.rowUnit+":"+i.date.toISOString();return s.createElement(v,{key:r,cell:i,rowLevel:e.rowLevel,dateProfile:e.dateProfile,tDateProfile:e.tDateProfile,todayRange:e.todayRange,nowDate:e.nowDate,isFirst:0===a,innerWidthRef:t.createRef(r),innerHeightRef:n.createRef(r),slotWidth:e.slotWidth})}))}componentWillUnmount(){i.setRef(this.props.innerWidthRef,null),i.setRef(this.props.innerHeighRef,null)}}function y(e,t,n,i,a){if(null==i||null==a)return[void 0,void 0,!1];const s=a/e;let r,l;return s>=(n=Math.max(n||0,(i+1)/t,30))?(r=!0,l=s):(r=!1,l=Math.max(n,s)),[l*e,l,r]}function w(e,t,n,a,s){let r=t.add(n.activeRange.start,e);return a.isTimeScale||(r=i.startOfDay(r)),E(r,t,a,s)}function E(e,t,n,a){return function(e,t,n){let a=n.countDurationsBetween(t.normalizedRange.start,e,t.snapDuration);if(a<0)return 0;if(a>=t.snapDiffToIndex.length)return t.snapCnt;let s=Math.floor(a),r=t.snapDiffToIndex[s];i.isInt(r)?r+=a-s:r=Math.ceil(r);return r}(e,n,t)/n.snapsPerSlot*a}class M extends i.BaseComponent{render(){const{props:e,context:n}=this,a=null==e.slotWidth?{}:{insetInlineStart:E(e.nowDate,n.dateEnv,e.tDateProfile,e.slotWidth)};return s.createElement("div",{className:d.default.fill,style:{zIndex:2,pointerEvents:"none"}},s.createElement(i.NowIndicatorLineContainer,{className:t.joinClassNames(d.default.fillY,d.default.noMarginY,d.default.borderlessY),style:a,date:e.nowDate}),s.createElement("div",{className:t.joinClassNames(d.default.flexCol,d.default.fillY),style:a},s.createElement("div",{className:d.default.stickyT},s.createElement(i.NowIndicatorDot,null))))}}class b extends i.BaseComponent{render(){const{props:e,context:n}=this,a=null==e.slotWidth?{}:{insetInlineStart:E(e.nowDate,n.dateEnv,e.tDateProfile,e.slotWidth)};return s.createElement("div",{className:t.joinClassNames(d.default.fill,d.default.crop),style:{zIndex:2,pointerEvents:"none"}},s.createElement(i.NowIndicatorHeaderContainer,{className:d.default.abs,style:a,date:e.nowDate}))}}function T(e,t){return e.querySelectorAll("."+d.default.internalTimelineSlot)[t]}function C(e,t,n){let a=n.countDurationsBetween(t.normalizedRange.start,e,t.snapDuration);if(a<0)return 0;if(a>=t.snapDiffToIndex.length)return t.snapCnt;let s=Math.floor(a),r=t.snapDiffToIndex[s];return i.isInt(r)?r+=a-s:r=Math.ceil(r),r}class I extends i.Slicer{sliceRange(e,t,n,a,s){let r=function(e,t,n){if(!t.isTimeScale&&(e=i.computeVisibleDayRange(e),t.largeUnit)){let i=e;((e={start:n.startOf(e.start,t.largeUnit),end:n.startOf(e.end,t.largeUnit)}).end.valueOf()!==i.end.valueOf()||e.end<=e.start)&&(e={start:e.start,end:n.add(e.end,t.slotDuration)})}return e}(e,a,s),l=[];if(C(r.start,a,s)<C(r.end,a,s)){let e=i.intersectRanges(r,a.normalizedRange);e&&l.push({startDate:e.start,endDate:e.end,isStart:e.start.valueOf()===r.start.valueOf()&&m(e.start,a,t,n),isEnd:e.end.valueOf()===r.end.valueOf()&&m(i.addMs(e.end,-1),a,t,n)})}return l}}const W=i.createFormatter({hour:"numeric",minute:"2-digit",omitZeroMinute:!0,meridiem:"narrow"});class P extends i.BaseComponent{render(){let{props:e}=this;return s.createElement(i.StandardEvent,Object.assign({},e,{display:"row",defaultTimeFormat:W,defaultDisplayEventTime:!e.isTimeScale}))}}class N extends i.BaseComponent{render(){let{props:e}=this,{hiddenSegs:t,resourceId:n}=e,a=n?{resourceId:n}:{};return s.createElement(i.MoreLinkContainer,{display:"row",allDayDate:null,segs:t,hiddenSegs:t,dateProfile:e.dateProfile,todayRange:e.todayRange,dateSpanProps:a,isNarrow:!1,isMicro:!1,popoverContent:()=>s.createElement(s.Fragment,null,t.map(t=>{let{eventRange:n}=t,{instanceId:a}=n.instance,r=Boolean(e.eventDrag&&e.eventDrag.affectedInstances[a]),l=Boolean(e.eventResize&&e.eventResize.affectedInstances[a]),o=r||l;return s.createElement("div",{key:a,style:{visibility:o?"hidden":void 0}},s.createElement(P,Object.assign({isTimeScale:e.isTimeScale,eventRange:n,isStart:t.isStart,isEnd:t.isEnd,isDragging:r,isResizing:l,isMirror:!1,isSelected:a===e.eventSelection},i.getEventRangeMeta(n,e.todayRange,e.nowDate))))}))})}}function x(e,t,n,a,s){const r={};for(const l of e)r[i.getEventKey(l)]=H(l,t,n,a,s);return r}function H(e,t,n,i,a){const s=E(e.startDate,n,i,a);let r=E(e.endDate,n,i,a)-s;return t&&(r=Math.max(r,t)),{start:s,size:r}}class k extends s.Component{constructor(){super(...arguments),this.rootElRef=s.createRef()}render(){const{props:e}=this;return s.createElement("div",{className:d.default.abs,style:e.style,ref:this.rootElRef},e.children)}componentDidMount(){const e=this.rootElRef.current;this.disconnectHeight=i.watchHeight(e,e=>{i.setRef(this.props.heightRef,e)})}componentWillUnmount(){this.disconnectHeight(),i.setRef(this.props.heightRef,null)}}class z extends i.BaseComponent{constructor(){super(...arguments),this.sortEventSegs=i.memoize(i.sortEventSegs),this.segHeightRefMap=new i.RefMap(()=>{i.afterSize(this.handleSegHeights)}),this.moreLinkHeightRefMap=new i.RefMap(()=>{i.afterSize(this.handleMoreLinkHeights)}),this.handleMoreLinkHeights=()=>{this.setState({moreLinkHeightRev:this.moreLinkHeightRefMap.rev})},this.handleSegHeights=()=>{this.setState({segHeightRev:this.segHeightRefMap.rev})}}render(){let{props:e,context:n,segHeightRefMap:a,moreLinkHeightRefMap:r}=this,{options:l}=n,{tDateProfile:o}=e,c=(e.eventDrag?e.eventDrag.segs:null)||(e.eventResize?e.eventResize.segs:null)||[],u=this.sortEventSegs(e.fgEventSegs,l.eventOrder),h=null!=e.slotWidth?x(u,l.eventMinWidth,n.dateEnv,o,e.slotWidth):{},[m,f,g,p]=function(e,t,n,a,s,r){const l=[];for(const n of e){const e=t[i.getEventKey(n)];e&&l.push(Object.assign(Object.assign({},n),{start:e.start,end:e.start+e.size}))}const o=new i.SegHierarchy(l,e=>n.get(i.getEventKey(e)),s,void 0,r),d=new Map;o.traverseSegs((e,t)=>{d.set(i.getEventKey(e),t)});const{hiddenSegs:c}=o;let u=0;for(const e of l){const t=i.getEventKey(e),a=n.get(t),s=d.get(t);null!=a&&null!=s&&(u=Math.max(u,s+a))}const h=i.groupIntersectingSegs(c),m=new Map;o.strictOrder=!0;for(const e of h){const{levelCoord:t}=o.findInsertion(e,0),n=a.get(e.key)||0;m.set(e.key,t),u=Math.max(u,t+n)}return[d,h,m,u]}(u,h,a.current,r.current,l.eventOrderStrict,l.eventMaxStack);return this.totalHeight=p,s.createElement("div",{className:t.joinClassNames(d.default.rel,d.default.noShrink),style:{height:p}},this.renderFgSegs(u,h,m,f,g,!1),this.renderFgSegs(c,e.slotWidth?x(c,l.eventMinWidth,n.dateEnv,o,e.slotWidth):{},m,[],new Map,!0))}renderFgSegs(e,t,n,a,r,l){let{props:o,segHeightRefMap:d,moreLinkHeightRefMap:c}=this;return s.createElement(s.Fragment,null,e.map(e=>{const{eventRange:a}=e,{instanceId:r}=a.instance,c=n.get(r),u=t[r],h=u||{},m=Boolean(o.eventDrag&&o.eventDrag.affectedInstances[r]),f=Boolean(o.eventResize&&o.eventResize.affectedInstances[r]),g=!l&&(m||f||!u||null==c);return s.createElement(k,{key:r,style:{visibility:g?"hidden":void 0,zIndex:1,top:c||0,insetInlineStart:h.start,width:h.size},heightRef:l?void 0:d.createRef(r)},s.createElement(P,Object.assign({isTimeScale:o.tDateProfile.isTimeScale,eventRange:a,isStart:e.isStart,isEnd:e.isEnd,isDragging:m,isResizing:f,isMirror:l,isSelected:r===o.eventSelection},i.getEventRangeMeta(a,o.todayRange,o.nowDate))))}),a.map(e=>s.createElement(k,{key:e.key,style:{top:r.get(e.key)||0,insetInlineStart:e.start,width:e.end-e.start},heightRef:c.createRef(e.key)},s.createElement(N,{hiddenSegs:e.segs,dateProfile:o.dateProfile,nowDate:o.nowDate,todayRange:o.todayRange,isTimeScale:o.tDateProfile.isTimeScale,eventDrag:o.eventDrag,eventResize:o.eventResize,eventSelection:o.eventSelection,resourceId:o.resourceId}))))}componentDidUpdate(){this.totalHeight!==this.firedTotalHeight&&(this.firedTotalHeight=this.totalHeight,i.setRef(this.props.heightRef,this.totalHeight))}componentWillUnmount(){i.setRef(this.props.heightRef,null)}}class j extends i.BaseComponent{render(){let{props:e}=this,t=[].concat(e.eventResizeSegs||[],e.dateSelectionSegs);return s.createElement(s.Fragment,null,this.renderSegs(e.businessHourSegs||[],"non-business"),this.renderSegs(e.bgEventSegs||[],"bg-event"),this.renderSegs(t,"highlight"))}renderSegs(e,t){let{tDateProfile:n,todayRange:a,nowDate:r,slotWidth:l}=this.props,{dateEnv:o,options:c}=this.context;return s.createElement(s.Fragment,null,e.map(e=>{let u={};if(null!=l){let t=H(e,void 0,o,n,l);u={insetInlineStart:t.start,width:t.size}}return s.createElement("div",{key:i.buildEventRangeKey(e.eventRange),className:d.default.fillY,style:u},"bg-event"===t?s.createElement(i.BgEvent,Object.assign({eventRange:e.eventRange,isStart:e.isStart,isEnd:e.isEnd,isVertical:!1},i.getEventRangeMeta(e.eventRange,a,r))):i.renderFill(t,c))}))}}class O extends i.DateComponent{constructor(){super(...arguments),this.buildTimelineDateProfile=i.memoize(u),this.computeSlotWidth=i.memoize(y),this.headerScrollerRef=s.createRef(),this.bodyScrollerRef=s.createRef(),this.footerScrollerRef=s.createRef(),this.headerRowInnerWidthMap=new i.RefMap(()=>{i.afterSize(this.handleSlotInnerWidths)}),this.scrollTime=null,this.slicer=new I,this.handleSlotInnerWidths=()=>{const e=this.headerRowInnerWidthMap.current.get(this.tDateProfile.cellRows.length-1);null!=e&&e!==this.state.slotInnerWidth&&this.setState({slotInnerWidth:e})},this.handleTotalWidth=e=>{this.setState({totalWidth:e})},this.handleClientWidth=e=>{this.setState({clientWidth:e})},this.handleTimeScrollRequest=e=>{this.scrollTime=e,this.applyTimeScroll()},this.handleTimeScrollEnd=e=>{e&&(this.scrollTime=null)},this.handeBodyEl=e=>{this.bodyEl=e,e?this.context.registerInteractiveComponent(this,{el:e}):this.context.unregisterInteractiveComponent(this)}}render(){const{props:e,state:n,context:a}=this,{options:r}=a,{totalWidth:l,clientWidth:o}=n,c=null!=l&&null!=o?l-o:void 0,u=this.tDateProfile=this.buildTimelineDateProfile(e.dateProfile,a.dateEnv,r,a.dateProfileGenerator),{cellRows:h}=u,m=i.greatestDurationDenominator(u.slotDuration).unit,f=!e.forPrint&&!i.getIsHeightAuto(r),g=!e.forPrint&&i.getStickyHeaderDates(r),p=!e.forPrint&&i.getStickyFooterScrollbar(r),[v,D]=this.computeSlotWidth(u.slotCnt,u.slotsPerLabel,r.slotMinWidth,n.slotInnerWidth,o);this.slotWidth=D;let y=this.slicer.sliceProps(e,e.dateProfile,u.isTimeScale?null:r.nextDayThreshold,a,e.dateProfile,a.dateProfileGenerator,u,a.dateEnv);return s.createElement(i.NowTimer,{unit:m},(n,l)=>{const o=r.nowIndicator&&null!=D&&i.rangeContainsMarker(e.dateProfile.currentRange,n);return s.createElement(i.ViewContainer,{viewSpec:a.viewSpec,className:i.joinArrayishClassNames(!e.forPrint&&d.default.flexCol,e.className,r.tableClass,d.default.isolate),borderlessX:e.borderlessX,borderlessTop:e.borderlessTop,borderlessBottom:e.borderlessBottom,noEdgeEffects:e.noEdgeEffects},s.createElement("div",{className:t.joinClassNames(i.generateClassName(r.tableHeaderClass,{isSticky:g}),e.borderlessX&&d.default.borderlessX,d.default.flexCol,g&&d.default.tableHeaderSticky),style:{zIndex:1}},s.createElement(i.Scroller,{horizontal:!0,hideScrollbars:!0,className:d.default.flexRow,ref:this.headerScrollerRef},s.createElement("div",{className:t.joinClassNames(d.default.rel,null==v&&d.default.liquid),style:{width:v}},h.map((t,i)=>{const a=h.length-i-1;return s.createElement(R,{key:i,dateProfile:e.dateProfile,tDateProfile:u,nowDate:n,todayRange:l,rowLevel:a,cells:t,slotWidth:D,innerWidthRef:this.headerRowInnerWidthMap.createRef(i)})}),o&&s.createElement(b,{tDateProfile:u,nowDate:n,slotWidth:D})),Boolean(c)&&s.createElement("div",{className:i.joinArrayishClassNames(i.generateClassName(r.fillerClass,{isHeader:!0}),d.default.borderOnlyS),style:{minWidth:c}})),s.createElement("div",{className:i.generateClassName(r.slotHeaderDividerClass,{isHeader:!0,options:{dayMinWidth:r.dayMinWidth}})})),s.createElement(i.Scroller,{vertical:f,horizontal:!0,hideScrollbars:p||e.forPrint,className:i.joinArrayishClassNames(r.tableBodyClass,e.borderlessX&&d.default.borderlessX,g&&d.default.borderlessTop,(g||e.noEdgeEffects)&&d.default.noEdgeEffects,d.default.flexCol,f&&d.default.liquid),style:{zIndex:0},ref:this.bodyScrollerRef,clientWidthRef:this.handleClientWidth},s.createElement("div",{"aria-label":r.eventsHint,className:t.joinClassNames(d.default.rel,d.default.grow),style:{width:v},ref:this.handeBodyEl},s.createElement(S,{dateProfile:e.dateProfile,tDateProfile:u,nowDate:n,todayRange:l,slotWidth:D}),s.createElement(j,{tDateProfile:u,nowDate:n,todayRange:l,bgEventSegs:y.bgEventSegs,businessHourSegs:y.businessHourSegs,dateSelectionSegs:y.dateSelectionSegs,eventResizeSegs:y.eventResize?y.eventResize.segs:null,slotWidth:D}),s.createElement("div",{className:i.joinArrayishClassNames(r.timelineTopClass)}),s.createElement(z,{dateProfile:e.dateProfile,tDateProfile:u,nowDate:n,todayRange:l,fgEventSegs:y.fgEventSegs,eventDrag:y.eventDrag,eventResize:y.eventResize,eventSelection:y.eventSelection,slotWidth:D}),s.createElement("div",{className:i.joinArrayishClassNames(r.timelineBottomClass)}),o&&s.createElement(M,{tDateProfile:u,nowDate:n,slotWidth:D}))),Boolean(p)&&s.createElement(i.FooterScrollbar,{isSticky:!0,canvasWidth:v,scrollerRef:this.footerScrollerRef}),s.createElement(i.Ruler,{widthRef:this.handleTotalWidth}))})}componentDidMount(){this.syncedScroller=new r.ScrollerSyncer(!0),this.updateSyncedScroller(),this.resetScroll(),this.context.emitter.on("_timeScrollRequest",this.handleTimeScrollRequest),this.syncedScroller.addScrollEndListener(this.handleTimeScrollEnd)}componentDidUpdate(e){this.updateSyncedScroller(),e.dateProfile!==this.props.dateProfile&&this.context.options.scrollTimeReset?this.resetScroll():this.applyTimeScroll()}componentWillUnmount(){this.syncedScroller.destroy(),this.context.emitter.off("_timeScrollRequest",this.handleTimeScrollRequest),this.syncedScroller.removeScrollEndListener(this.handleTimeScrollEnd)}updateSyncedScroller(){this.syncedScroller.handleChildren([this.headerScrollerRef.current,this.bodyScrollerRef.current,this.footerScrollerRef.current])}resetScroll(){this.handleTimeScrollRequest(this.context.options.scrollTime)}applyTimeScroll(){const{props:e,context:t,tDateProfile:n,scrollTime:i,slotWidth:a}=this;if(null!=i&&null!=a){let s=w(i,t.dateEnv,e.dateProfile,n,a);s&&(s+=1),this.syncedScroller.scrollTo({x:s})}}queryHit(e,t,n,a,s){const{props:r,context:l,tDateProfile:o,slotWidth:d}=this,{dateEnv:c}=l;if(d){const n=e?a-t:t,l=Math.floor(n/d),u=(n-l*d)/d,h=Math.floor(u*o.snapsPerSlot);let m,f,g=c.add(o.slotDates[l],i.multiplyDuration(o.snapDuration,h)),p=c.add(g,o.snapDuration),S=d/o.snapsPerSlot,v=l*d+S*h,D=v+S;return e?(m=a-D,f=a-v):(m=v,f=D),{dateProfile:r.dateProfile,dateSpan:{range:{start:g,end:p},allDay:!o.isTimeScale},rect:{left:m,right:f,top:0,bottom:s},getDayEl:()=>T(this.bodyEl,l),layer:0}}return null}}const B={timelineTopClass:i.identity,timelineBottomClass:i.identity};var L=t.createPlugin({name:"@fullcalendar/timeline",premiumReleaseDate:"2025-12-20",deps:[o.default],initialView:"timelineDay",optionRefiners:B,views:{timeline:{component:O,usesMinMaxTime:!0,eventResizableFromStart:!0},timelineDay:{type:"timeline",duration:{days:1}},timelineWeek:{type:"timeline",duration:{weeks:1}},timelineMonth:{type:"timeline",duration:{months:1}},timelineYear:{type:"timeline",duration:{years:1}}}}),F={__proto__:null,TimelineView:O,TimelineFg:z,TimelineBg:j,TimelineSlats:S,buildTimelineDateProfile:u,computeSlotWidth:y,timeToCoord:w,TimelineLaneSlicer:I,TimelineHeaderRow:R,TimelineNowIndicatorArrow:b,TimelineNowIndicatorLine:M,getTimelineSlotEl:T};return t.globalPlugins.push(L),e.Internal=F,e.default=L,Object.defineProperty(e,"__esModule",{value:!0}),e}({},FullCalendar,FullCalendar.PremiumCommon,FullCalendar.Internal,FullCalendar.InternalClassNames,FullCalendar.Preact,FullCalendar.ScrollGrid.Internal); |
+2
-2
@@ -16,4 +16,4 @@ | ||
| - GPLv3 License | ||
| - AGPLv3 License | ||
| (intended for open-source projects) | ||
| http://www.gnu.org/licenses/gpl-3.0.en.html | ||
| https://www.gnu.org/licenses/agpl-3.0.en.html |
+24
-21
| { | ||
| "name": "@fullcalendar/timeline", | ||
| "version": "7.0.0-beta.4", | ||
| "version": "7.0.0-beta.5", | ||
| "title": "FullCalendar Timeline Plugin", | ||
@@ -18,7 +18,7 @@ "description": "Display events on a horizontal time axis (without resources)", | ||
| "dependencies": { | ||
| "@fullcalendar/premium-common": "7.0.0-beta.4", | ||
| "@fullcalendar/scrollgrid": "7.0.0-beta.4" | ||
| "@fullcalendar/premium-common": "7.0.0-beta.5", | ||
| "@fullcalendar/scrollgrid": "7.0.0-beta.5" | ||
| }, | ||
| "peerDependencies": { | ||
| "@fullcalendar/core": "7.0.0-beta.4" | ||
| "@fullcalendar/core": "7.0.0-beta.5" | ||
| }, | ||
@@ -38,26 +38,29 @@ "type": "module", | ||
| }, | ||
| "copyright": "2024 Adam Shaw", | ||
| "types": "./index.d.ts", | ||
| "main": "./index.cjs", | ||
| "module": "./index.js", | ||
| "unpkg": "./index.global.min.js", | ||
| "jsdelivr": "./index.global.min.js", | ||
| "copyright": "2025 Adam Shaw", | ||
| "types": "./esm/index.d.ts", | ||
| "module": "./esm/index.js", | ||
| "main": "./cjs/index.cjs", | ||
| "unpkg": "./global.min.js", | ||
| "jsdelivr": "./global.min.js", | ||
| "exports": { | ||
| "./package.json": "./package.json", | ||
| "./index.cjs": "./index.cjs", | ||
| "./index.js": "./index.js", | ||
| ".": { | ||
| "types": "./index.d.ts", | ||
| "require": "./index.cjs", | ||
| "import": "./index.js" | ||
| "import": { | ||
| "types": "./esm/index.d.ts", | ||
| "default": "./esm/index.js" | ||
| }, | ||
| "require": "./cjs/index.cjs" | ||
| }, | ||
| "./internal.cjs": "./internal.cjs", | ||
| "./internal.js": "./internal.js", | ||
| "./internal": { | ||
| "types": "./internal.d.ts", | ||
| "require": "./internal.cjs", | ||
| "import": "./internal.js" | ||
| "import": { | ||
| "types": "./esm/internal.d.ts", | ||
| "default": "./esm/internal.js" | ||
| }, | ||
| "require": "./cjs/internal.cjs" | ||
| } | ||
| }, | ||
| "sideEffects": false | ||
| "sideEffects": [ | ||
| "./global.js", | ||
| "./global.min.js" | ||
| ] | ||
| } |
-46
| 'use strict'; | ||
| Object.defineProperty(exports, '__esModule', { value: true }); | ||
| var index_cjs = require('@fullcalendar/core/index.cjs'); | ||
| var premiumCommonPlugin = require('@fullcalendar/premium-common/index.cjs'); | ||
| var internalCommon = require('./internal.cjs'); | ||
| require('@fullcalendar/core/internal.cjs'); | ||
| require('@fullcalendar/core/preact.cjs'); | ||
| require('@fullcalendar/scrollgrid/internal.cjs'); | ||
| function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } | ||
| var premiumCommonPlugin__default = /*#__PURE__*/_interopDefaultLegacy(premiumCommonPlugin); | ||
| var index = index_cjs.createPlugin({ | ||
| name: '@fullcalendar/timeline', | ||
| premiumReleaseDate: '2025-01-09', | ||
| deps: [premiumCommonPlugin__default["default"]], | ||
| initialView: 'timelineDay', | ||
| views: { | ||
| timeline: { | ||
| component: internalCommon.TimelineView, | ||
| usesMinMaxTime: true, | ||
| eventResizableFromStart: true, // how is this consumed for TimelineView tho? | ||
| }, | ||
| timelineDay: { | ||
| type: 'timeline', | ||
| duration: { days: 1 }, | ||
| }, | ||
| timelineWeek: { | ||
| type: 'timeline', | ||
| duration: { weeks: 1 }, | ||
| }, | ||
| timelineMonth: { | ||
| type: 'timeline', | ||
| duration: { months: 1 }, | ||
| }, | ||
| timelineYear: { | ||
| type: 'timeline', | ||
| duration: { years: 1 }, | ||
| }, | ||
| }, | ||
| }); | ||
| exports["default"] = index; |
| import { PluginDef } from '@fullcalendar/core'; | ||
| import '@fullcalendar/premium-common'; | ||
| import '@fullcalendar/scrollgrid'; | ||
| declare const _default: PluginDef; | ||
| //# sourceMappingURL=index.d.ts.map | ||
| export { _default as default }; |
Sorry, the diff of this file is too big to display
| /*! | ||
| FullCalendar Timeline Plugin v7.0.0-beta.4 | ||
| Docs & License: https://fullcalendar.io/docs/timeline-view-no-resources | ||
| (c) 2024 Adam Shaw | ||
| */ | ||
| FullCalendar.Timeline=function(e,t,n,i,r,s){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}var l=a(n);i.config.MAX_TIMELINE_SLOTS=1e3;const o=[{years:1},{months:1},{days:1},{hours:1},{minutes:30},{minutes:15},{minutes:10},{minutes:5},{minutes:1},{seconds:30},{seconds:15},{seconds:10},{seconds:5},{seconds:1},{milliseconds:500},{milliseconds:100},{milliseconds:10},{milliseconds:1}];function c(e,t,n,r){let s={labelInterval:n.slotLabelInterval,slotDuration:n.slotDuration};!function(e,t,n){const{currentRange:r}=t;if(e.labelInterval){n.countDurationsBetween(r.start,r.end,e.labelInterval)>i.config.MAX_TIMELINE_SLOTS&&(console.warn("slotLabelInterval results in too many cells"),e.labelInterval=null)}if(e.slotDuration){n.countDurationsBetween(r.start,r.end,e.slotDuration)>i.config.MAX_TIMELINE_SLOTS&&(console.warn("slotDuration results in too many cells"),e.slotDuration=null)}if(e.labelInterval&&e.slotDuration){const t=i.wholeDivideDurations(e.labelInterval,e.slotDuration);(null===t||t<1)&&(console.warn("slotLabelInterval must be a multiple of slotDuration"),e.slotDuration=null)}}(s,e,t),h(s,e,t),function(e,t,n){const{currentRange:r}=t;let{slotDuration:s}=e;if(!s){const a=h(e,t,n);for(let e of o){const t=i.createDuration(e),n=i.wholeDivideDurations(a,t);if(null!==n&&n>1&&n<=6){s=t;break}}if(s){n.countDurationsBetween(r.start,r.end,s)>200&&(s=null)}s||(s=a),e.slotDuration=s}}(s,e,t);let a=n.slotLabelFormat,l=Array.isArray(a)?a:null!=a?[a]:function(e,t,n,r){let s,a;const{labelInterval:l}=e;let o=i.greatestDurationDenominator(l).unit;const c=r.weekNumbers;let d=s=a=null;"week"!==o||c||(o="day");switch(o){case"year":d={year:"numeric"};break;case"month":m("years",t,n)>1&&(d={year:"numeric"}),s={month:"short"};break;case"week":m("years",t,n)>1&&(d={year:"numeric"}),s={week:"narrow"};break;case"day":m("years",t,n)>1?d={year:"numeric",month:"long"}:m("months",t,n)>1&&(d={month:"long"}),c&&(s={week:"short"}),a={weekday:"narrow",day:"numeric"};break;case"hour":c&&(d={week:"short"}),m("days",t,n)>1&&(s={weekday:"short",day:"numeric",month:"numeric",omitCommas:!0}),a={hour:"numeric",minute:"2-digit",omitZeroMinute:!0,meridiem:"short"};break;case"minute":i.asRoughMinutes(l)/60>=6?(d={hour:"numeric",meridiem:"short"},s=e=>":"+i.padStart(e.date.minute,2)):d={hour:"numeric",minute:"numeric",meridiem:"short"};break;case"second":i.asRoughSeconds(l)/60>=6?(d={hour:"numeric",minute:"2-digit",meridiem:"lowercase"},s=e=>":"+i.padStart(e.date.second,2)):d={hour:"numeric",minute:"2-digit",second:"2-digit",meridiem:"lowercase"};break;case"millisecond":d={hour:"numeric",minute:"2-digit",second:"2-digit",meridiem:"lowercase"},s=e=>"."+i.padStart(e.millisecond,3)}return[].concat(d||[],s||[],a||[])}(s,e,t,n);s.headerFormats=l.map(e=>i.createFormatter(e)),s.isTimeScale=Boolean(s.slotDuration.milliseconds);let c=null;if(!s.isTimeScale){const e=i.greatestDurationDenominator(s.slotDuration).unit;/year|month|week/.test(e)&&(c=e)}s.largeUnit=c,s.emphasizeWeeks=1===i.asCleanDays(s.slotDuration)&&m("weeks",e,t)>=2&&!n.businessHours;let g,p,v=n.snapDuration;v&&(g=i.createDuration(v),p=i.wholeDivideDurations(s.slotDuration,g)),null==p&&(g=s.slotDuration,p=1),s.snapDuration=g,s.snapsPerSlot=p;let S=i.asRoughMs(e.slotMaxTime)-i.asRoughMs(e.slotMinTime),R=d(e.renderRange.start,s,t),D=d(e.renderRange.end,s,t);s.isTimeScale&&(R=t.add(R,e.slotMinTime),D=t.add(i.addDays(D,-1),e.slotMaxTime)),s.timeWindowMs=S,s.normalizedRange={start:R,end:D};let y=[],w=R;for(;w<D;)f(w,s,e,r)&&y.push(w),w=t.add(w,s.slotDuration);s.slotDates=y;let b=-1,E=0;const x=[],W=[];for(w=R;w<D;)f(w,s,e,r)?(b+=1,x.push(b),W.push(E)):x.push(b+.5),w=t.add(w,s.snapDuration),E+=1;return s.snapDiffToIndex=x,s.snapIndexToDiff=W,s.snapCnt=b+1,s.slotCnt=s.snapCnt/s.snapsPerSlot,s.isWeekStarts=function(e,t){let{slotDates:n,emphasizeWeeks:i}=e,r=null,s=[];for(let e of n){let n=t.computeWeekNumber(e),a=i&&null!==r&&r!==n;r=n,s.push(a)}return s}(s,t),s.cellRows=function(e,t){let n=e.slotDates,r=e.headerFormats,s=r.map(()=>[]),a=i.asCleanDays(e.slotDuration),l=7===a?"week":1===a?"day":null,o=r.map(e=>e.getLargestUnit?e.getLargestUnit():null);for(let a=0;a<n.length;a+=1){let c=n[a],d=e.isWeekStarts[a];for(let n=0;n<r.length;n+=1){let a=r[n],f=s[n],h=f[f.length-1],m=n===r.length-1,g=r.length>1&&!m,p=null,v=o[n]||(m?l:null);if(g){let e=t.format(c,a);h&&h.text===e?h.colspan+=1:p=u(c,e,v)}else if(!h||i.isInt(t.countDurationsBetween(e.normalizedRange.start,c,e.labelInterval))){let e=t.format(c,a);p=u(c,e,v)}else h.colspan+=1;p&&(p.weekStart=d,f.push(p))}}return s}(s,t),s.slotsPerLabel=i.wholeDivideDurations(s.labelInterval,s.slotDuration),s}function d(e,t,n){let r=e;return t.isTimeScale||(r=i.startOfDay(r),t.largeUnit&&(r=n.startOf(r,t.largeUnit))),r}function f(e,t,n,r){if(r.isHiddenDay(e))return!1;if(t.isTimeScale){let r=i.startOfDay(e),s=e.valueOf()-r.valueOf()-i.asRoughMs(n.slotMinTime);return s=(s%864e5+864e5)%864e5,s<t.timeWindowMs}return!0}function h(e,t,n){const{currentRange:r}=t;let{labelInterval:s}=e;if(!s){let t;if(e.slotDuration){for(t of o){const n=i.createDuration(t),r=i.wholeDivideDurations(n,e.slotDuration);if(null!==r&&r<=6){s=n;break}}s||(s=e.slotDuration)}else for(t of o){s=i.createDuration(t);if(n.countDurationsBetween(r.start,r.end,s)>=18)break}e.labelInterval=s}return s}function m(e,t,n){let r=t.currentRange,s=null;return"years"===e?s=n.diffWholeYears(r.start,r.end):"months"===e||"weeks"===e?s=n.diffWholeMonths(r.start,r.end):"days"===e&&(s=i.diffWholeDays(r.start,r.end)),s||0}function u(e,t,n){return{date:e,text:t,rowUnit:n,colspan:1,isWeekStart:!1}}class g extends i.BaseComponent{constructor(){super(...arguments),this.innerElRef=r.createRef()}render(){let{props:e,context:t}=this,{dateEnv:n,options:s}=t,{date:a,tDateProfile:l,isEm:o}=e,c=i.getDateMeta(e.date,e.todayRange,e.nowDate,e.dateProfile),d=Object.assign(Object.assign({date:n.toDate(e.date)},c),{view:t.viewApi});return r.createElement(i.ContentContainer,{tag:"div",className:i.joinClassNames("fc-timeline-slot",o&&"fc-timeline-slot-em",l.isTimeScale&&(i.isInt(n.countDurationsBetween(l.normalizedRange.start,e.date,l.labelInterval))?"fc-timeline-slot-major":"fc-timeline-slot-minor"),"fc-timeline-slot-lane fc-cell fc-flex-col fc-align-start",e.borderStart&&"fc-border-s",e.isDay?i.getDayClassName(c):i.getSlotClassName(c)),attrs:Object.assign({"data-date":n.formatIso(a,{omitTimeZoneOffset:!0,omitTime:!l.isTimeScale})},c.isToday?{"aria-current":"date"}:{}),style:{width:e.width},renderProps:d,generatorName:"slotLaneContent",customGenerator:s.slotLaneContent,classNameGenerator:s.slotLaneClassNames,didMount:s.slotLaneDidMount,willUnmount:s.slotLaneWillUnmount},e=>r.createElement(e,{tag:"div",className:"fc-cell-inner",elRef:this.innerElRef}))}componentDidMount(){const e=this.innerElRef.current;this.disconnectInnerWidth=i.watchWidth(e,e=>{i.setRef(this.props.innerWidthRef,e)})}componentWillUnmount(){this.disconnectInnerWidth(),i.setRef(this.props.innerWidthRef,null)}}class p extends i.BaseComponent{constructor(){super(...arguments),this.innerWidthRefMap=new i.RefMap(()=>{i.afterSize(this.handleInnerWidths)}),this.handleInnerWidths=()=>{const e=this.innerWidthRefMap.current;let t=0;for(const n of e.values())t=Math.max(t,n);i.setRef(this.props.innerWidthRef,t)}}render(){let{props:e,innerWidthRefMap:t}=this,{tDateProfile:n,slotWidth:i}=e,{slotDates:s,isWeekStarts:a}=n,l=!n.isTimeScale&&!n.largeUnit;return r.createElement("div",{"aria-hidden":!0,className:"fc-timeline-slots fc-fill fc-flex-row",style:{height:e.height}},s.map((s,o)=>{let c=s.toISOString();return r.createElement(g,{key:c,date:s,dateProfile:e.dateProfile,tDateProfile:n,nowDate:e.nowDate,todayRange:e.todayRange,isEm:a[o],isDay:l,borderStart:Boolean(o),innerWidthRef:t.createRef(c),width:i})}))}}function v(e,t,n){let r=n.countDurationsBetween(t.normalizedRange.start,e,t.snapDuration);if(r<0)return 0;if(r>=t.snapDiffToIndex.length)return t.snapCnt;let s=Math.floor(r),a=t.snapDiffToIndex[s];return i.isInt(a)?a+=r-s:a=Math.ceil(a),a}function S(e,t){return e?t?{right:e.start,width:e.size}:{left:e.start,width:e.size}:{}}function R(e,t){return t?{right:e}:{left:e}}function D(e,t,n,i,r){if(null==i||null==r)return[void 0,void 0,!1];const s=r/e;let a,l;return s>=(n=Math.max(n||0,(i+1)/t,30))?(a=!0,l=s):(a=!1,l=Math.max(n,s)),[l*e,l,a]}function y(e,t,n,r,s){let a=t.add(n.activeRange.start,e);return r.isTimeScale||(a=i.startOfDay(a)),w(a,t,r,s)}function w(e,t,n,r){return function(e,t,n){let r=n.countDurationsBetween(t.normalizedRange.start,e,t.snapDuration);if(r<0)return 0;if(r>=t.snapDiffToIndex.length)return t.snapCnt;let s=Math.floor(r),a=t.snapDiffToIndex[s];i.isInt(a)?a+=r-s:a=Math.ceil(a);return a}(e,n,t)/n.snapsPerSlot*r}function b(e,t,n,r,s){const a={};for(const l of e)a[i.getEventKey(l)]=E(l,t,n,r,s);return a}function E(e,t,n,i,r){const s=w(e.startDate,n,i,r);let a=w(e.endDate,n,i,r)-s;return t&&(a=Math.max(a,t)),{start:s,size:a}}class x extends i.BaseComponent{render(){let{props:e}=this,t=[].concat(e.eventResizeSegs,e.dateSelectionSegs);return r.createElement(r.Fragment,null,this.renderSegs(e.businessHourSegs||[],"non-business"),this.renderSegs(e.bgEventSegs||[],"bg-event"),this.renderSegs(t,"highlight"))}renderSegs(e,t){let{tDateProfile:n,todayRange:s,nowDate:a,slotWidth:l}=this.props,{dateEnv:o,isRtl:c}=this.context;return r.createElement(r.Fragment,null,e.map(e=>{let d;if(null!=l){d=S(E(e,void 0,o,n,l),c)}return r.createElement("div",{key:i.buildEventRangeKey(e.eventRange),className:"fc-fill-y",style:d},"bg-event"===t?r.createElement(i.BgEvent,Object.assign({eventRange:e.eventRange,isStart:e.isStart,isEnd:e.isEnd},i.getEventRangeMeta(e.eventRange,s,a))):i.renderFill(t))}))}}class W extends i.Slicer{sliceRange(e,t,n,r,s){let a=function(e,t,n){if(!t.isTimeScale&&(e=i.computeVisibleDayRange(e),t.largeUnit)){let i=e;((e={start:n.startOf(e.start,t.largeUnit),end:n.startOf(e.end,t.largeUnit)}).end.valueOf()!==i.end.valueOf()||e.end<=e.start)&&(e={start:e.start,end:n.add(e.end,t.slotDuration)})}return e}(e,r,s),l=[];if(v(a.start,r,s)<v(a.end,r,s)){let e=i.intersectRanges(a,r.normalizedRange);e&&l.push({startDate:e.start,endDate:e.end,isStart:e.start.valueOf()===a.start.valueOf()&&f(e.start,r,t,n),isEnd:e.end.valueOf()===a.end.valueOf()&&f(i.addMs(e.end,-1),r,t,n)})}return l}}const M=i.createFormatter({hour:"numeric",minute:"2-digit",omitZeroMinute:!0,meridiem:"narrow"});class T extends i.BaseComponent{render(){let{props:e,context:t}=this,{options:n}=t;return r.createElement(i.StandardEvent,Object.assign({},e,{className:i.joinClassNames("fc-timeline-event",!1===n.eventOverlap&&"fc-timeline-event-spacious","fc-h-event"),defaultTimeFormat:M,defaultDisplayEventTime:!e.isTimeScale}))}}class I extends i.BaseComponent{render(){let{props:e}=this,{hiddenSegs:t,resourceId:n,forcedInvisibleMap:s}=e,a=n?{resourceId:n}:{};return r.createElement(i.MoreLinkContainer,{className:"fc-timeline-more-link",allDayDate:null,segs:t,hiddenSegs:t,dateProfile:e.dateProfile,todayRange:e.todayRange,dateSpanProps:a,popoverContent:()=>r.createElement(r.Fragment,null,t.map(t=>{let{eventRange:n}=t,a=n.instance.instanceId;return r.createElement("div",{key:a,style:{visibility:s[a]?"hidden":""}},r.createElement(T,Object.assign({isTimeScale:e.isTimeScale,eventRange:n,isStart:t.isStart,isEnd:t.isEnd,isDragging:!1,isResizing:!1,isDateSelecting:!1,isSelected:a===e.eventSelection},i.getEventRangeMeta(n,e.todayRange,e.nowDate))))}))},e=>r.createElement(e,{tag:"div",className:"fc-timeline-more-link-inner fc-sticky-s"}))}}class k extends r.Component{constructor(){super(...arguments),this.rootElRef=r.createRef()}render(){const{props:e}=this;return r.createElement("div",{className:"fc-abs",style:e.style,ref:this.rootElRef},e.children)}componentDidMount(){const e=this.rootElRef.current;this.disconnectHeight=i.watchHeight(e,e=>{i.setRef(this.props.heightRef,e)})}componentWillUnmount(){this.disconnectHeight(),i.setRef(this.props.heightRef,null)}}class P extends i.BaseComponent{constructor(){super(...arguments),this.sortEventSegs=i.memoize(i.sortEventSegs),this.segHeightRefMap=new i.RefMap(()=>{i.afterSize(this.handleSegHeights)}),this.moreLinkHeightRefMap=new i.RefMap(()=>{i.afterSize(this.handleMoreLinkHeights)}),this.slicer=new W,this.handleMoreLinkHeights=()=>{this.setState({moreLinkHeightRev:this.moreLinkHeightRefMap.rev})},this.handleSegHeights=()=>{this.setState({segHeightRev:this.segHeightRefMap.rev})}}render(){let{props:e,context:t,segHeightRefMap:n,moreLinkHeightRefMap:s}=this,{options:a}=t,{dateProfile:l,tDateProfile:o}=e,c=this.slicer.sliceProps(e,l,o.isTimeScale?null:e.nextDayThreshold,t,l,t.dateProfileGenerator,o,t.dateEnv),d=(c.eventDrag?c.eventDrag.segs:null)||(c.eventResize?c.eventResize.segs:null)||[],f=this.sortEventSegs(c.fgEventSegs,a.eventOrder),h=null!=e.slotWidth?b(f,a.eventMinWidth,t.dateEnv,o,e.slotWidth):{},[m,u,g,p]=function(e,t,n,r,s,a){const l=[];for(const n of e){const e=t[i.getEventKey(n)];e&&l.push(Object.assign(Object.assign({},n),{start:e.start,end:e.start+e.size}))}const o=new i.SegHierarchy(l,e=>n.get(i.getEventKey(e)),s,void 0,a),c=new Map;o.traverseSegs((e,t)=>{c.set(i.getEventKey(e),t)});const{hiddenSegs:d}=o;let f=0;for(const e of l){const t=i.getEventKey(e),r=n.get(t),s=c.get(t);null!=r&&null!=s&&(f=Math.max(f,s+r))}const h=i.groupIntersectingSegs(d),m=new Map;o.strictOrder=!0;for(const e of h){const{levelCoord:t}=o.findInsertion(e,0),n=r.get(e.key)||0;m.set(e.key,t),f=Math.max(f,t+n)}return[c,h,m,f]}(f,h,n.current,s.current,a.eventOrderStrict,a.eventMaxStack),v=(c.eventDrag?c.eventDrag.affectedInstances:null)||(c.eventResize?c.eventResize.affectedInstances:null)||{};return r.createElement(r.Fragment,null,r.createElement(x,{tDateProfile:o,nowDate:e.nowDate,todayRange:e.todayRange,bgEventSegs:c.bgEventSegs,businessHourSegs:c.businessHourSegs,dateSelectionSegs:c.dateSelectionSegs,eventResizeSegs:c.eventResize?c.eventResize.segs:[],slotWidth:e.slotWidth}),r.createElement("div",{className:i.joinClassNames("fc-timeline-events",!1===a.eventOverlap?"fc-timeline-events-overlap-disabled":"fc-timeline-events-overlap-enabled","fc-content-box"),style:{height:p}},this.renderFgSegs(f,h,m,v,u,g,!1,!1,!1),this.renderFgSegs(d,e.slotWidth?b(d,a.eventMinWidth,t.dateEnv,o,e.slotWidth):{},m,{},[],new Map,Boolean(c.eventDrag),Boolean(c.eventResize),!1)))}renderFgSegs(e,t,n,s,a,l,o,c,d){let{props:f,context:h,segHeightRefMap:m,moreLinkHeightRefMap:u}=this,g=o||c||d;return r.createElement(r.Fragment,null,e.map(e=>{const{eventRange:a}=e,{instanceId:l}=a.instance,u=n.get(l),p=t[l],v=g||p&&null!=u&&!s[l];return r.createElement(k,{key:l,style:Object.assign({visibility:v?"":"hidden",top:u||0},S(p,h.isRtl)),heightRef:g?void 0:m.createRef(l)},r.createElement(T,Object.assign({isTimeScale:f.tDateProfile.isTimeScale,eventRange:a,isStart:e.isStart,isEnd:e.isEnd,isDragging:o,isResizing:c,isDateSelecting:d,isSelected:l===f.eventSelection},i.getEventRangeMeta(a,f.todayRange,f.nowDate))))}),a.map(e=>r.createElement(k,{key:e.key,style:Object.assign({top:l.get(e.key)||0},S({start:e.start,size:e.end-e.start},h.isRtl)),heightRef:u.createRef(e.key)},r.createElement(I,{hiddenSegs:e.segs,dateProfile:f.dateProfile,nowDate:f.nowDate,todayRange:f.todayRange,isTimeScale:f.tDateProfile.isTimeScale,eventSelection:f.eventSelection,resourceId:f.resourceId,forcedInvisibleMap:s}))))}}class C extends i.BaseComponent{constructor(){super(...arguments),this.refineRenderProps=i.memoizeObjArg(N),this.innerElRef=r.createRef()}render(){let{props:e,context:t}=this,{dateEnv:n,options:s}=t,{cell:a,dateProfile:l,tDateProfile:o}=e,c=i.getDateMeta(a.date,e.todayRange,e.nowDate,l),d=this.refineRenderProps({level:e.rowLevel,dateMarker:a.date,text:a.text,dateEnv:t.dateEnv,viewApi:t.viewApi}),f=!c.isDisabled&&a.rowUnit&&"time"!==a.rowUnit;return r.createElement(i.ContentContainer,{tag:"div",className:i.joinClassNames("fc-timeline-slot-label fc-timeline-slot",a.isWeekStart&&"fc-timeline-slot-em","fc-header-cell fc-cell fc-flex-col fc-justify-center",e.borderStart&&"fc-border-s",e.isCentered?"fc-align-center":"fc-align-start","time"===a.rowUnit?i.getSlotClassName(c):i.getDayClassName(c)),attrs:Object.assign({"data-date":n.formatIso(a.date,{omitTime:!o.isTimeScale,omitTimeZoneOffset:!0})},c.isToday?{"aria-current":"date"}:{}),style:{width:null!=e.slotWidth?e.slotWidth*a.colspan:void 0},renderProps:d,generatorName:"slotLabelContent",customGenerator:s.slotLabelContent,defaultGenerator:z,classNameGenerator:s.slotLabelClassNames,didMount:s.slotLabelDidMount,willUnmount:s.slotLabelWillUnmount},n=>r.createElement(n,{tag:"div",attrs:f?i.buildNavLinkAttrs(t,a.date,a.rowUnit,void 0,!1):{},className:i.joinClassNames("fc-cell-inner fc-padding-md",e.isSticky&&"fc-sticky-s"),elRef:this.innerElRef}))}componentDidMount(){const{props:e}=this,t=this.innerElRef.current;this.detachSize=i.watchSize(t,(n,r)=>{i.setRef(e.innerWidthRef,n),i.setRef(e.innerHeightRef,r),t.style.left=t.style.right=e.isCentered&&e.isSticky?`calc(50% - ${n/2}px)`:""})}componentWillUnmount(){const{props:e}=this;this.detachSize(),i.setRef(e.innerWidthRef,null),i.setRef(e.innerHeightRef,null)}}function z(e){return e.text}function N(e){return{level:e.level,date:e.dateEnv.toDate(e.dateMarker),view:e.viewApi,text:e.text}}class L extends i.BaseComponent{constructor(){super(...arguments),this.innerWidthRefMap=new i.RefMap(()=>{i.afterSize(this.handleInnerWidths)}),this.innerHeightRefMap=new i.RefMap(()=>{i.afterSize(this.handleInnerHeights)}),this.handleInnerWidths=()=>{const e=this.innerWidthRefMap.current;let t=0;for(const n of e.values())t=Math.max(t,n);i.setRef(this.props.innerWidthRef,t)},this.handleInnerHeights=()=>{const e=this.innerHeightRefMap.current;let t=0;for(const n of e.values())t=Math.max(t,n);i.setRef(this.props.innerHeighRef,t)}}render(){const{props:e,innerWidthRefMap:t,innerHeightRefMap:n}=this,s=!(e.tDateProfile.isTimeScale&&e.isLastRow),a=!e.isLastRow;return r.createElement("div",{className:i.joinClassNames("fc-flex-row fc-grow",!e.isLastRow&&"fc-border-b")},e.cells.map((i,l)=>{const o=i.rowUnit+":"+i.date.toISOString();return r.createElement(C,{key:o,cell:i,rowLevel:e.rowLevel,dateProfile:e.dateProfile,tDateProfile:e.tDateProfile,todayRange:e.todayRange,nowDate:e.nowDate,isCentered:s,isSticky:a,borderStart:Boolean(l),innerWidthRef:t.createRef(o),innerHeightRef:n.createRef(o),slotWidth:e.slotWidth})}))}componentWillUnmount(){i.setRef(this.props.innerWidthRef,null),i.setRef(this.props.innerHeighRef,null)}}class H extends i.BaseComponent{render(){const{props:e,context:t}=this;return r.createElement("div",{className:"fc-timeline-now-indicator-container"},r.createElement(i.NowIndicatorContainer,{className:"fc-timeline-now-indicator-line",style:null!=e.slotWidth?R(w(e.nowDate,t.dateEnv,e.tDateProfile,e.slotWidth),t.isRtl):{},isAxis:!1,date:e.nowDate}))}}class O extends i.BaseComponent{render(){const{props:e,context:t}=this;return r.createElement("div",{className:"fc-timeline-now-indicator-container"},r.createElement(i.NowIndicatorContainer,{className:"fc-timeline-now-indicator-arrow",style:null!=e.slotWidth?R(w(e.nowDate,t.dateEnv,e.tDateProfile,e.slotWidth),t.isRtl):{},isAxis:!0,date:e.nowDate}))}}class B extends i.DateComponent{constructor(){super(...arguments),this.buildTimelineDateProfile=i.memoize(c),this.computeSlotWidth=i.memoize(D),this.headerScrollerRef=r.createRef(),this.bodyScrollerRef=r.createRef(),this.footerScrollerRef=r.createRef(),this.headerRowInnerWidthMap=new i.RefMap(()=>{i.afterSize(this.handleSlotInnerWidths)}),this.scrollTime=null,this.handleBodySlotInnerWidth=e=>{this.bodySlotInnerWidth=e,i.afterSize(this.handleSlotInnerWidths)},this.handleSlotInnerWidths=()=>{const{state:e}=this,t=Math.max(this.headerRowInnerWidthMap.current.get(this.tDateProfile.cellRows.length-1)||0,this.bodySlotInnerWidth);e.slotInnerWidth!==t&&this.setState({slotInnerWidth:t})},this.handleClientWidth=e=>{this.setState({clientWidth:e})},this.handleEndScrollbarWidth=e=>{this.setState({endScrollbarWidth:e})},this.handleTimeScrollRequest=e=>{this.scrollTime=e,this.applyTimeScroll()},this.handleTimeScrollEnd=({isUser:e})=>{e&&(this.scrollTime=null)},this.handeBodyEl=e=>{this.bodyEl=e,e?this.context.registerInteractiveComponent(this,{el:e}):this.context.unregisterInteractiveComponent(this)}}render(){const{props:e,state:t,context:n}=this,{options:s}=n,a=this.tDateProfile=this.buildTimelineDateProfile(e.dateProfile,n.dateEnv,s,n.dateProfileGenerator),{cellRows:l}=a,o=i.greatestDurationDenominator(a.slotDuration).unit,c=!e.forPrint&&!i.getIsHeightAuto(s),d=!e.forPrint&&i.getStickyHeaderDates(s),f=!e.forPrint&&i.getStickyFooterScrollbar(s),[h,m]=this.computeSlotWidth(a.slotCnt,a.slotsPerLabel,s.slotMinWidth,t.slotInnerWidth,t.clientWidth);return this.slotWidth=m,r.createElement(i.NowTimer,{unit:o},(o,u)=>{const g=s.nowIndicator&&null!=m&&i.rangeContainsMarker(e.dateProfile.currentRange,o);return r.createElement(i.ViewContainer,{viewSpec:n.viewSpec,className:i.joinClassNames("fc-timeline fc-border",!e.forPrint&&"fc-flex-col")},r.createElement(i.Scroller,{horizontal:!0,hideScrollbars:!0,className:i.joinClassNames("fc-timeline-header fc-flex-row fc-border-b",d&&"fc-table-header-sticky"),ref:this.headerScrollerRef},r.createElement("div",{className:i.joinClassNames("fc-rel",null==h&&"fc-liquid"),style:{width:h}},l.map((t,n)=>{const i=n===l.length-1;return r.createElement(L,{key:n,dateProfile:e.dateProfile,tDateProfile:a,nowDate:o,todayRange:u,rowLevel:n,isLastRow:i,cells:t,slotWidth:m,innerWidthRef:this.headerRowInnerWidthMap.createRef(n)})}),g&&r.createElement(O,{tDateProfile:a,nowDate:o,slotWidth:m})),Boolean(t.endScrollbarWidth)&&r.createElement("div",{className:"fc-border-s fc-filler",style:{minWidth:t.endScrollbarWidth}})),r.createElement(i.Scroller,{vertical:c,horizontal:!0,hideScrollbars:e.forPrint,className:i.joinClassNames("fc-timeline-body fc-flex-col",c&&"fc-liquid"),ref:this.bodyScrollerRef,clientWidthRef:this.handleClientWidth,endScrollbarWidthRef:this.handleEndScrollbarWidth},r.createElement("div",{"aria-label":s.eventsHint,className:"fc-rel fc-grow",style:{width:h},ref:this.handeBodyEl},r.createElement(p,{dateProfile:e.dateProfile,tDateProfile:a,nowDate:o,todayRange:u,innerWidthRef:this.handleBodySlotInnerWidth,slotWidth:m}),r.createElement(P,{dateProfile:e.dateProfile,tDateProfile:a,nowDate:o,todayRange:u,nextDayThreshold:s.nextDayThreshold,eventStore:e.eventStore,eventUiBases:e.eventUiBases,businessHours:e.businessHours,dateSelection:e.dateSelection,eventDrag:e.eventDrag,eventResize:e.eventResize,eventSelection:e.eventSelection,slotWidth:m}),g&&r.createElement(H,{tDateProfile:a,nowDate:o,slotWidth:m}))),f&&r.createElement(i.Scroller,{ref:this.footerScrollerRef,horizontal:!0},r.createElement("div",{style:{width:h}})))})}componentDidMount(){this.syncedScroller=new s.ScrollerSyncer(!0),this.updateSyncedScroller(),this.resetScroll(),this.context.emitter.on("_timeScrollRequest",this.handleTimeScrollRequest),this.syncedScroller.addScrollEndListener(this.handleTimeScrollEnd)}componentDidUpdate(e){this.updateSyncedScroller(),e.dateProfile!==this.props.dateProfile&&this.context.options.scrollTimeReset?this.resetScroll():this.applyTimeScroll()}componentWillUnmount(){this.syncedScroller.destroy(),this.context.emitter.off("_timeScrollRequest",this.handleTimeScrollRequest),this.syncedScroller.removeScrollEndListener(this.handleTimeScrollEnd)}updateSyncedScroller(){this.syncedScroller.handleChildren([this.headerScrollerRef.current,this.bodyScrollerRef.current,this.footerScrollerRef.current])}resetScroll(){this.handleTimeScrollRequest(this.context.options.scrollTime)}applyTimeScroll(){const{props:e,context:t,tDateProfile:n,scrollTime:i,slotWidth:r}=this;if(null!=i&&null!=r){let s=y(i,t.dateEnv,e.dateProfile,n,r);s&&(s+=1),this.syncedScroller.scrollTo({x:s})}}queryHit(e,t,n,r){const{props:s,context:a,tDateProfile:l,slotWidth:o}=this,{dateEnv:c}=a;if(o){const t=a.isRtl?n-e:e,d=Math.floor(t/o),f=(t-d*o)/o,h=Math.floor(f*l.snapsPerSlot);let m,u,g=c.add(l.slotDates[d],i.multiplyDuration(l.snapDuration,h)),p=c.add(g,l.snapDuration),v=o/l.snapsPerSlot,S=d*o+v*h,R=S+v;return a.isRtl?(m=n-R,u=n-S):(m=S,u=R),{dateProfile:s.dateProfile,dateSpan:{range:{start:g,end:p},allDay:!l.isTimeScale},rect:{left:m,right:u,top:0,bottom:r},dayEl:this.bodyEl.querySelectorAll(".fc-timeline-slot")[d],layer:0}}return null}}i.injectStyles('.fc-timeline-slots{z-index:1}.fc-timeline-events{position:relative;z-index:2}.fc-timeline-slot-minor{border-style:dotted}.fc-timeline-events-overlap-enabled{padding-bottom:10px}.fc-timeline-event{border-radius:0;font-size:var(--fc-small-font-size);margin-bottom:1px}.fc-direction-ltr .fc-timeline-event.fc-event-end{margin-right:1px}.fc-direction-rtl .fc-timeline-event.fc-event-end{margin-left:1px}.fc-timeline-event .fc-event-inner{align-items:center;display:flex;flex-direction:row;padding:2px 1px}.fc-timeline-event-spacious .fc-event-inner{padding-bottom:5px;padding-top:5px}.fc-timeline-event .fc-event-time{font-weight:700}.fc-timeline-event .fc-event-time,.fc-timeline-event .fc-event-title{padding:0 2px}.fc-timeline-event:not(.fc-event-end) .fc-event-inner:after,.fc-timeline-event:not(.fc-event-start) .fc-event-inner:before{border-color:transparent #000;border-style:solid;border-width:5px;content:"";flex-grow:0;flex-shrink:0;height:0;margin:0 1px;opacity:.5;width:0}.fc-direction-ltr .fc-timeline-event:not(.fc-event-start) .fc-event-inner:before,.fc-direction-rtl .fc-timeline-event:not(.fc-event-end) .fc-event-inner:after{border-left:0}.fc-direction-ltr .fc-timeline-event:not(.fc-event-end) .fc-event-inner:after,.fc-direction-rtl .fc-timeline-event:not(.fc-event-start) .fc-event-inner:before{border-right:0}.fc-timeline-more-link{align-items:flex-start;background:var(--fc-more-link-bg-color);color:var(--fc-more-link-text-color);cursor:pointer;display:flex;flex-direction:column;font-size:var(--fc-small-font-size);padding:1px}.fc-direction-ltr .fc-timeline-more-link{margin-right:1px}.fc-direction-rtl .fc-timeline-more-link{margin-left:1px}.fc-timeline-more-link-inner{padding:2px}.fc-timeline-now-indicator-container{bottom:0;left:0;overflow:hidden;pointer-events:none;position:absolute;right:0;top:0;z-index:4}.fc-timeline-now-indicator-arrow{border-bottom-style:solid;border-bottom-width:0;border-color:var(--fc-now-indicator-color);border-left:5px solid transparent;border-right:5px solid transparent;border-top-style:solid;border-top-width:6px;height:0;margin:0 -5px;position:absolute;top:0;width:0}.fc-timeline-now-indicator-line{border-left:1px solid var(--fc-now-indicator-color);bottom:0;position:absolute;top:0}');var U=t.createPlugin({name:"@fullcalendar/timeline",premiumReleaseDate:"2025-01-09",deps:[l.default],initialView:"timelineDay",views:{timeline:{component:B,usesMinMaxTime:!0,eventResizableFromStart:!0},timelineDay:{type:"timeline",duration:{days:1}},timelineWeek:{type:"timeline",duration:{weeks:1}},timelineMonth:{type:"timeline",duration:{months:1}},timelineYear:{type:"timeline",duration:{years:1}}}}),j={__proto__:null,TimelineView:B,TimelineLane:P,TimelineLaneBg:x,TimelineSlats:p,buildTimelineDateProfile:c,createVerticalStyle:function(e){if(e)return{top:e.start,height:e.size}},createHorizontalStyle:function(e,t){if(e)return{[t?"right":"left"]:e.start,width:e.size}},computeSlotWidth:D,timeToCoord:y,TimelineLaneSlicer:W,TimelineHeaderRow:L,TimelineNowIndicatorArrow:O,TimelineNowIndicatorLine:H};return t.globalPlugins.push(U),e.Internal=j,e.default=U,Object.defineProperty(e,"__esModule",{value:!0}),e}({},FullCalendar,FullCalendar.PremiumCommon,FullCalendar.Internal,FullCalendar.Preact,FullCalendar.ScrollGrid.Internal); |
-38
| import { createPlugin } from '@fullcalendar/core/index.js'; | ||
| import premiumCommonPlugin from '@fullcalendar/premium-common/index.js'; | ||
| import { TimelineView } from './internal.js'; | ||
| import '@fullcalendar/core/internal.js'; | ||
| import '@fullcalendar/core/preact.js'; | ||
| import '@fullcalendar/scrollgrid/internal.js'; | ||
| var index = createPlugin({ | ||
| name: '@fullcalendar/timeline', | ||
| premiumReleaseDate: '2025-01-09', | ||
| deps: [premiumCommonPlugin], | ||
| initialView: 'timelineDay', | ||
| views: { | ||
| timeline: { | ||
| component: TimelineView, | ||
| usesMinMaxTime: true, | ||
| eventResizableFromStart: true, // how is this consumed for TimelineView tho? | ||
| }, | ||
| timelineDay: { | ||
| type: 'timeline', | ||
| duration: { days: 1 }, | ||
| }, | ||
| timelineWeek: { | ||
| type: 'timeline', | ||
| duration: { weeks: 1 }, | ||
| }, | ||
| timelineMonth: { | ||
| type: 'timeline', | ||
| duration: { months: 1 }, | ||
| }, | ||
| timelineYear: { | ||
| type: 'timeline', | ||
| duration: { years: 1 }, | ||
| }, | ||
| }, | ||
| }); | ||
| export { index as default }; |
-1258
| 'use strict'; | ||
| Object.defineProperty(exports, '__esModule', { value: true }); | ||
| var internal_cjs = require('@fullcalendar/core/internal.cjs'); | ||
| var preact_cjs = require('@fullcalendar/core/preact.cjs'); | ||
| var internal_cjs$1 = require('@fullcalendar/scrollgrid/internal.cjs'); | ||
| const MIN_AUTO_LABELS = 18; // more than `12` months but less that `24` hours | ||
| const MAX_AUTO_SLOTS_PER_LABEL = 6; // allows 6 10-min slots in an hour | ||
| const MAX_AUTO_CELLS = 200; // allows 4-days to have a :30 slot duration | ||
| internal_cjs.config.MAX_TIMELINE_SLOTS = 1000; | ||
| // potential nice values for slot-duration and interval-duration | ||
| const STOCK_SUB_DURATIONS = [ | ||
| { years: 1 }, | ||
| { months: 1 }, | ||
| { days: 1 }, | ||
| { hours: 1 }, | ||
| { minutes: 30 }, | ||
| { minutes: 15 }, | ||
| { minutes: 10 }, | ||
| { minutes: 5 }, | ||
| { minutes: 1 }, | ||
| { seconds: 30 }, | ||
| { seconds: 15 }, | ||
| { seconds: 10 }, | ||
| { seconds: 5 }, | ||
| { seconds: 1 }, | ||
| { milliseconds: 500 }, | ||
| { milliseconds: 100 }, | ||
| { milliseconds: 10 }, | ||
| { milliseconds: 1 }, | ||
| ]; | ||
| function buildTimelineDateProfile(dateProfile, dateEnv, allOptions, dateProfileGenerator) { | ||
| let tDateProfile = { | ||
| labelInterval: allOptions.slotLabelInterval, | ||
| slotDuration: allOptions.slotDuration, | ||
| }; | ||
| validateLabelAndSlot(tDateProfile, dateProfile, dateEnv); // validate after computed grid duration | ||
| ensureLabelInterval(tDateProfile, dateProfile, dateEnv); | ||
| ensureSlotDuration(tDateProfile, dateProfile, dateEnv); | ||
| let input = allOptions.slotLabelFormat; | ||
| let rawFormats = Array.isArray(input) ? input : | ||
| (input != null) ? [input] : | ||
| computeHeaderFormats(tDateProfile, dateProfile, dateEnv, allOptions); | ||
| tDateProfile.headerFormats = rawFormats.map((rawFormat) => internal_cjs.createFormatter(rawFormat)); | ||
| tDateProfile.isTimeScale = Boolean(tDateProfile.slotDuration.milliseconds); | ||
| let largeUnit = null; | ||
| if (!tDateProfile.isTimeScale) { | ||
| const slotUnit = internal_cjs.greatestDurationDenominator(tDateProfile.slotDuration).unit; | ||
| if (/year|month|week/.test(slotUnit)) { | ||
| largeUnit = slotUnit; | ||
| } | ||
| } | ||
| tDateProfile.largeUnit = largeUnit; | ||
| tDateProfile.emphasizeWeeks = | ||
| internal_cjs.asCleanDays(tDateProfile.slotDuration) === 1 && | ||
| currentRangeAs('weeks', dateProfile, dateEnv) >= 2 && | ||
| !allOptions.businessHours; | ||
| /* | ||
| console.log('label interval =', timelineView.labelInterval.humanize()) | ||
| console.log('slot duration =', timelineView.slotDuration.humanize()) | ||
| console.log('header formats =', timelineView.headerFormats) | ||
| console.log('isTimeScale', timelineView.isTimeScale) | ||
| console.log('largeUnit', timelineView.largeUnit) | ||
| */ | ||
| let rawSnapDuration = allOptions.snapDuration; | ||
| let snapDuration; | ||
| let snapsPerSlot; | ||
| if (rawSnapDuration) { | ||
| snapDuration = internal_cjs.createDuration(rawSnapDuration); | ||
| snapsPerSlot = internal_cjs.wholeDivideDurations(tDateProfile.slotDuration, snapDuration); | ||
| // ^ TODO: warning if not whole? | ||
| } | ||
| if (snapsPerSlot == null) { | ||
| snapDuration = tDateProfile.slotDuration; | ||
| snapsPerSlot = 1; | ||
| } | ||
| tDateProfile.snapDuration = snapDuration; | ||
| tDateProfile.snapsPerSlot = snapsPerSlot; | ||
| // more... | ||
| let timeWindowMs = internal_cjs.asRoughMs(dateProfile.slotMaxTime) - internal_cjs.asRoughMs(dateProfile.slotMinTime); | ||
| // TODO: why not use normalizeRange!? | ||
| let normalizedStart = normalizeDate(dateProfile.renderRange.start, tDateProfile, dateEnv); | ||
| let normalizedEnd = normalizeDate(dateProfile.renderRange.end, tDateProfile, dateEnv); | ||
| // apply slotMinTime/slotMaxTime | ||
| // TODO: View should be responsible. | ||
| if (tDateProfile.isTimeScale) { | ||
| normalizedStart = dateEnv.add(normalizedStart, dateProfile.slotMinTime); | ||
| normalizedEnd = dateEnv.add(internal_cjs.addDays(normalizedEnd, -1), dateProfile.slotMaxTime); | ||
| } | ||
| tDateProfile.timeWindowMs = timeWindowMs; | ||
| tDateProfile.normalizedRange = { start: normalizedStart, end: normalizedEnd }; | ||
| let slotDates = []; | ||
| let date = normalizedStart; | ||
| while (date < normalizedEnd) { | ||
| if (isValidDate(date, tDateProfile, dateProfile, dateProfileGenerator)) { | ||
| slotDates.push(date); | ||
| } | ||
| date = dateEnv.add(date, tDateProfile.slotDuration); | ||
| } | ||
| tDateProfile.slotDates = slotDates; | ||
| // more... | ||
| let snapIndex = -1; | ||
| let snapDiff = 0; // index of the diff :( | ||
| const snapDiffToIndex = []; | ||
| const snapIndexToDiff = []; | ||
| date = normalizedStart; | ||
| while (date < normalizedEnd) { | ||
| if (isValidDate(date, tDateProfile, dateProfile, dateProfileGenerator)) { | ||
| snapIndex += 1; | ||
| snapDiffToIndex.push(snapIndex); | ||
| snapIndexToDiff.push(snapDiff); | ||
| } | ||
| else { | ||
| snapDiffToIndex.push(snapIndex + 0.5); | ||
| } | ||
| date = dateEnv.add(date, tDateProfile.snapDuration); | ||
| snapDiff += 1; | ||
| } | ||
| tDateProfile.snapDiffToIndex = snapDiffToIndex; | ||
| tDateProfile.snapIndexToDiff = snapIndexToDiff; | ||
| tDateProfile.snapCnt = snapIndex + 1; // is always one behind | ||
| tDateProfile.slotCnt = tDateProfile.snapCnt / tDateProfile.snapsPerSlot; | ||
| // more... | ||
| tDateProfile.isWeekStarts = buildIsWeekStarts(tDateProfile, dateEnv); | ||
| tDateProfile.cellRows = buildCellRows(tDateProfile, dateEnv); | ||
| tDateProfile.slotsPerLabel = internal_cjs.wholeDivideDurations(tDateProfile.labelInterval, tDateProfile.slotDuration); | ||
| return tDateProfile; | ||
| } | ||
| /* | ||
| snaps to appropriate unit | ||
| */ | ||
| function normalizeDate(date, tDateProfile, dateEnv) { | ||
| let normalDate = date; | ||
| if (!tDateProfile.isTimeScale) { | ||
| normalDate = internal_cjs.startOfDay(normalDate); | ||
| if (tDateProfile.largeUnit) { | ||
| normalDate = dateEnv.startOf(normalDate, tDateProfile.largeUnit); | ||
| } | ||
| } | ||
| return normalDate; | ||
| } | ||
| /* | ||
| snaps to appropriate unit | ||
| */ | ||
| function normalizeRange(range, tDateProfile, dateEnv) { | ||
| if (!tDateProfile.isTimeScale) { | ||
| range = internal_cjs.computeVisibleDayRange(range); | ||
| if (tDateProfile.largeUnit) { | ||
| let dayRange = range; // preserve original result | ||
| range = { | ||
| start: dateEnv.startOf(range.start, tDateProfile.largeUnit), | ||
| end: dateEnv.startOf(range.end, tDateProfile.largeUnit), | ||
| }; | ||
| // if date is partially through the interval, or is in the same interval as the start, | ||
| // make the exclusive end be the *next* interval | ||
| if (range.end.valueOf() !== dayRange.end.valueOf() || range.end <= range.start) { | ||
| range = { | ||
| start: range.start, | ||
| end: dateEnv.add(range.end, tDateProfile.slotDuration), | ||
| }; | ||
| } | ||
| } | ||
| } | ||
| return range; | ||
| } | ||
| function isValidDate(date, tDateProfile, dateProfile, dateProfileGenerator) { | ||
| if (dateProfileGenerator.isHiddenDay(date)) { | ||
| return false; | ||
| } | ||
| if (tDateProfile.isTimeScale) { | ||
| // determine if the time is within slotMinTime/slotMaxTime, which may have wacky values | ||
| let day = internal_cjs.startOfDay(date); | ||
| let timeMs = date.valueOf() - day.valueOf(); | ||
| let ms = timeMs - internal_cjs.asRoughMs(dateProfile.slotMinTime); // milliseconds since slotMinTime | ||
| ms = ((ms % 86400000) + 86400000) % 86400000; // make negative values wrap to 24hr clock | ||
| return ms < tDateProfile.timeWindowMs; // before the slotMaxTime? | ||
| } | ||
| return true; | ||
| } | ||
| function validateLabelAndSlot(tDateProfile, dateProfile, dateEnv) { | ||
| const { currentRange } = dateProfile; | ||
| // make sure labelInterval doesn't exceed the max number of cells | ||
| if (tDateProfile.labelInterval) { | ||
| const labelCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, tDateProfile.labelInterval); | ||
| if (labelCnt > internal_cjs.config.MAX_TIMELINE_SLOTS) { | ||
| console.warn('slotLabelInterval results in too many cells'); | ||
| tDateProfile.labelInterval = null; | ||
| } | ||
| } | ||
| // make sure slotDuration doesn't exceed the maximum number of cells | ||
| if (tDateProfile.slotDuration) { | ||
| const slotCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, tDateProfile.slotDuration); | ||
| if (slotCnt > internal_cjs.config.MAX_TIMELINE_SLOTS) { | ||
| console.warn('slotDuration results in too many cells'); | ||
| tDateProfile.slotDuration = null; | ||
| } | ||
| } | ||
| // make sure labelInterval is a multiple of slotDuration | ||
| if (tDateProfile.labelInterval && tDateProfile.slotDuration) { | ||
| const slotsPerLabel = internal_cjs.wholeDivideDurations(tDateProfile.labelInterval, tDateProfile.slotDuration); | ||
| if (slotsPerLabel === null || slotsPerLabel < 1) { | ||
| console.warn('slotLabelInterval must be a multiple of slotDuration'); | ||
| tDateProfile.slotDuration = null; | ||
| } | ||
| } | ||
| } | ||
| function ensureLabelInterval(tDateProfile, dateProfile, dateEnv) { | ||
| const { currentRange } = dateProfile; | ||
| let { labelInterval } = tDateProfile; | ||
| if (!labelInterval) { | ||
| // compute based off the slot duration | ||
| // find the largest label interval with an acceptable slots-per-label | ||
| let input; | ||
| if (tDateProfile.slotDuration) { | ||
| for (input of STOCK_SUB_DURATIONS) { | ||
| const tryLabelInterval = internal_cjs.createDuration(input); | ||
| const slotsPerLabel = internal_cjs.wholeDivideDurations(tryLabelInterval, tDateProfile.slotDuration); | ||
| if (slotsPerLabel !== null && slotsPerLabel <= MAX_AUTO_SLOTS_PER_LABEL) { | ||
| labelInterval = tryLabelInterval; | ||
| break; | ||
| } | ||
| } | ||
| // use the slot duration as a last resort | ||
| if (!labelInterval) { | ||
| labelInterval = tDateProfile.slotDuration; | ||
| } | ||
| // compute based off the view's duration | ||
| // find the largest label interval that yields the minimum number of labels | ||
| } | ||
| else { | ||
| for (input of STOCK_SUB_DURATIONS) { | ||
| labelInterval = internal_cjs.createDuration(input); | ||
| const labelCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, labelInterval); | ||
| if (labelCnt >= MIN_AUTO_LABELS) { | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| tDateProfile.labelInterval = labelInterval; | ||
| } | ||
| return labelInterval; | ||
| } | ||
| function ensureSlotDuration(tDateProfile, dateProfile, dateEnv) { | ||
| const { currentRange } = dateProfile; | ||
| let { slotDuration } = tDateProfile; | ||
| if (!slotDuration) { | ||
| const labelInterval = ensureLabelInterval(tDateProfile, dateProfile, dateEnv); // will compute if necessary | ||
| // compute based off the label interval | ||
| // find the largest slot duration that is different from labelInterval, but still acceptable | ||
| for (let input of STOCK_SUB_DURATIONS) { | ||
| const trySlotDuration = internal_cjs.createDuration(input); | ||
| const slotsPerLabel = internal_cjs.wholeDivideDurations(labelInterval, trySlotDuration); | ||
| if (slotsPerLabel !== null && slotsPerLabel > 1 && slotsPerLabel <= MAX_AUTO_SLOTS_PER_LABEL) { | ||
| slotDuration = trySlotDuration; | ||
| break; | ||
| } | ||
| } | ||
| // only allow the value if it won't exceed the view's # of slots limit | ||
| if (slotDuration) { | ||
| const slotCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, slotDuration); | ||
| if (slotCnt > MAX_AUTO_CELLS) { | ||
| slotDuration = null; | ||
| } | ||
| } | ||
| // use the label interval as a last resort | ||
| if (!slotDuration) { | ||
| slotDuration = labelInterval; | ||
| } | ||
| tDateProfile.slotDuration = slotDuration; | ||
| } | ||
| return slotDuration; | ||
| } | ||
| function computeHeaderFormats(tDateProfile, dateProfile, dateEnv, allOptions) { | ||
| let format1; | ||
| let format2; | ||
| const { labelInterval } = tDateProfile; | ||
| let unit = internal_cjs.greatestDurationDenominator(labelInterval).unit; | ||
| const weekNumbersVisible = allOptions.weekNumbers; | ||
| let format0 = (format1 = (format2 = null)); | ||
| // NOTE: weekNumber computation function wont work | ||
| if ((unit === 'week') && !weekNumbersVisible) { | ||
| unit = 'day'; | ||
| } | ||
| switch (unit) { | ||
| case 'year': | ||
| format0 = { year: 'numeric' }; // '2015' | ||
| break; | ||
| case 'month': | ||
| if (currentRangeAs('years', dateProfile, dateEnv) > 1) { | ||
| format0 = { year: 'numeric' }; // '2015' | ||
| } | ||
| format1 = { month: 'short' }; // 'Jan' | ||
| break; | ||
| case 'week': | ||
| if (currentRangeAs('years', dateProfile, dateEnv) > 1) { | ||
| format0 = { year: 'numeric' }; // '2015' | ||
| } | ||
| format1 = { week: 'narrow' }; // 'Wk4' | ||
| break; | ||
| case 'day': | ||
| if (currentRangeAs('years', dateProfile, dateEnv) > 1) { | ||
| format0 = { year: 'numeric', month: 'long' }; // 'January 2014' | ||
| } | ||
| else if (currentRangeAs('months', dateProfile, dateEnv) > 1) { | ||
| format0 = { month: 'long' }; // 'January' | ||
| } | ||
| if (weekNumbersVisible) { | ||
| format1 = { week: 'short' }; // 'Wk 4' | ||
| } | ||
| format2 = { weekday: 'narrow', day: 'numeric' }; // 'Su 9' | ||
| break; | ||
| case 'hour': | ||
| if (weekNumbersVisible) { | ||
| format0 = { week: 'short' }; // 'Wk 4' | ||
| } | ||
| if (currentRangeAs('days', dateProfile, dateEnv) > 1) { | ||
| format1 = { weekday: 'short', day: 'numeric', month: 'numeric', omitCommas: true }; // Sat 4/7 | ||
| } | ||
| format2 = { | ||
| hour: 'numeric', | ||
| minute: '2-digit', | ||
| omitZeroMinute: true, | ||
| meridiem: 'short', | ||
| }; | ||
| break; | ||
| case 'minute': | ||
| // sufficiently large number of different minute cells? | ||
| if ((internal_cjs.asRoughMinutes(labelInterval) / 60) >= MAX_AUTO_SLOTS_PER_LABEL) { | ||
| format0 = { | ||
| hour: 'numeric', | ||
| meridiem: 'short', | ||
| }; | ||
| format1 = (params) => (':' + internal_cjs.padStart(params.date.minute, 2) // ':30' | ||
| ); | ||
| } | ||
| else { | ||
| format0 = { | ||
| hour: 'numeric', | ||
| minute: 'numeric', | ||
| meridiem: 'short', | ||
| }; | ||
| } | ||
| break; | ||
| case 'second': | ||
| // sufficiently large number of different second cells? | ||
| if ((internal_cjs.asRoughSeconds(labelInterval) / 60) >= MAX_AUTO_SLOTS_PER_LABEL) { | ||
| format0 = { hour: 'numeric', minute: '2-digit', meridiem: 'lowercase' }; // '8:30 PM' | ||
| format1 = (params) => (':' + internal_cjs.padStart(params.date.second, 2) // ':30' | ||
| ); | ||
| } | ||
| else { | ||
| format0 = { hour: 'numeric', minute: '2-digit', second: '2-digit', meridiem: 'lowercase' }; // '8:30:45 PM' | ||
| } | ||
| break; | ||
| case 'millisecond': | ||
| format0 = { hour: 'numeric', minute: '2-digit', second: '2-digit', meridiem: 'lowercase' }; // '8:30:45 PM' | ||
| format1 = (params) => ('.' + internal_cjs.padStart(params.millisecond, 3)); | ||
| break; | ||
| } | ||
| return [].concat(format0 || [], format1 || [], format2 || []); | ||
| } | ||
| // Compute the number of the give units in the "current" range. | ||
| // Won't go more precise than days. | ||
| // Will return `0` if there's not a clean whole interval. | ||
| function currentRangeAs(unit, dateProfile, dateEnv) { | ||
| let range = dateProfile.currentRange; | ||
| let res = null; | ||
| if (unit === 'years') { | ||
| res = dateEnv.diffWholeYears(range.start, range.end); | ||
| } | ||
| else if (unit === 'months') { | ||
| res = dateEnv.diffWholeMonths(range.start, range.end); | ||
| } | ||
| else if (unit === 'weeks') { | ||
| res = dateEnv.diffWholeMonths(range.start, range.end); | ||
| } | ||
| else if (unit === 'days') { | ||
| res = internal_cjs.diffWholeDays(range.start, range.end); | ||
| } | ||
| return res || 0; | ||
| } | ||
| function buildIsWeekStarts(tDateProfile, dateEnv) { | ||
| let { slotDates, emphasizeWeeks } = tDateProfile; | ||
| let prevWeekNumber = null; | ||
| let isWeekStarts = []; | ||
| for (let slotDate of slotDates) { | ||
| let weekNumber = dateEnv.computeWeekNumber(slotDate); | ||
| let isWeekStart = emphasizeWeeks && (prevWeekNumber !== null) && (prevWeekNumber !== weekNumber); | ||
| prevWeekNumber = weekNumber; | ||
| isWeekStarts.push(isWeekStart); | ||
| } | ||
| return isWeekStarts; | ||
| } | ||
| function buildCellRows(tDateProfile, dateEnv) { | ||
| let slotDates = tDateProfile.slotDates; | ||
| let formats = tDateProfile.headerFormats; | ||
| let cellRows = formats.map(() => []); // indexed by row,col | ||
| let slotAsDays = internal_cjs.asCleanDays(tDateProfile.slotDuration); | ||
| let guessedSlotUnit = slotAsDays === 7 ? 'week' : | ||
| slotAsDays === 1 ? 'day' : | ||
| null; | ||
| // specifically for navclicks | ||
| let rowUnitsFromFormats = formats.map((format) => (format.getLargestUnit ? format.getLargestUnit() : null)); | ||
| // builds cellRows and slotCells | ||
| for (let i = 0; i < slotDates.length; i += 1) { | ||
| let date = slotDates[i]; | ||
| let isWeekStart = tDateProfile.isWeekStarts[i]; | ||
| for (let row = 0; row < formats.length; row += 1) { | ||
| let format = formats[row]; | ||
| let rowCells = cellRows[row]; | ||
| let leadingCell = rowCells[rowCells.length - 1]; | ||
| let isLastRow = row === formats.length - 1; | ||
| let isSuperRow = formats.length > 1 && !isLastRow; // more than one row and not the last | ||
| let newCell = null; | ||
| let rowUnit = rowUnitsFromFormats[row] || (isLastRow ? guessedSlotUnit : null); | ||
| if (isSuperRow) { | ||
| let text = dateEnv.format(date, format); | ||
| if (!leadingCell || (leadingCell.text !== text)) { | ||
| newCell = buildCellObject(date, text, rowUnit); | ||
| } | ||
| else { | ||
| leadingCell.colspan += 1; | ||
| } | ||
| } | ||
| else if (!leadingCell || | ||
| internal_cjs.isInt(dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.labelInterval))) { | ||
| let text = dateEnv.format(date, format); | ||
| newCell = buildCellObject(date, text, rowUnit); | ||
| } | ||
| else { | ||
| leadingCell.colspan += 1; | ||
| } | ||
| if (newCell) { | ||
| newCell.weekStart = isWeekStart; | ||
| rowCells.push(newCell); | ||
| } | ||
| } | ||
| } | ||
| return cellRows; | ||
| } | ||
| function buildCellObject(date, text, rowUnit) { | ||
| return { date, text, rowUnit, colspan: 1, isWeekStart: false }; | ||
| } | ||
| class TimelineSlatCell extends internal_cjs.BaseComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // ref | ||
| this.innerElRef = preact_cjs.createRef(); | ||
| } | ||
| render() { | ||
| let { props, context } = this; | ||
| let { dateEnv, options } = context; | ||
| let { date, tDateProfile, isEm } = props; | ||
| let dateMeta = internal_cjs.getDateMeta(props.date, props.todayRange, props.nowDate, props.dateProfile); | ||
| let renderProps = Object.assign(Object.assign({ date: dateEnv.toDate(props.date) }, dateMeta), { view: context.viewApi }); | ||
| return (preact_cjs.createElement(internal_cjs.ContentContainer, { tag: "div", | ||
| // fc-align-start shrinks width of InnerContent | ||
| // TODO: document this semantic className fc-timeline-slot-em | ||
| className: internal_cjs.joinClassNames('fc-timeline-slot', isEm && 'fc-timeline-slot-em', tDateProfile.isTimeScale && (internal_cjs.isInt(dateEnv.countDurationsBetween(// best to do this here? | ||
| tDateProfile.normalizedRange.start, props.date, tDateProfile.labelInterval)) ? | ||
| 'fc-timeline-slot-major' : | ||
| 'fc-timeline-slot-minor'), 'fc-timeline-slot-lane fc-cell fc-flex-col fc-align-start', props.borderStart && 'fc-border-s', props.isDay ? | ||
| internal_cjs.getDayClassName(dateMeta) : | ||
| internal_cjs.getSlotClassName(dateMeta)), attrs: Object.assign({ 'data-date': dateEnv.formatIso(date, { | ||
| omitTimeZoneOffset: true, | ||
| omitTime: !tDateProfile.isTimeScale, | ||
| }) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: { | ||
| width: props.width, | ||
| }, renderProps: renderProps, generatorName: "slotLaneContent", customGenerator: options.slotLaneContent, classNameGenerator: options.slotLaneClassNames, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount }, (InnerContent) => (preact_cjs.createElement(InnerContent, { tag: "div", className: 'fc-cell-inner', elRef: this.innerElRef })))); | ||
| } | ||
| componentDidMount() { | ||
| const innerEl = this.innerElRef.current; | ||
| this.disconnectInnerWidth = internal_cjs.watchWidth(innerEl, (width) => { | ||
| internal_cjs.setRef(this.props.innerWidthRef, width); | ||
| }); | ||
| } | ||
| componentWillUnmount() { | ||
| this.disconnectInnerWidth(); | ||
| internal_cjs.setRef(this.props.innerWidthRef, null); | ||
| } | ||
| } | ||
| class TimelineSlats extends internal_cjs.BaseComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| this.innerWidthRefMap = new internal_cjs.RefMap(() => { | ||
| internal_cjs.afterSize(this.handleInnerWidths); | ||
| }); | ||
| this.handleInnerWidths = () => { | ||
| const innerWidthMap = this.innerWidthRefMap.current; | ||
| let max = 0; | ||
| for (const innerWidth of innerWidthMap.values()) { | ||
| max = Math.max(max, innerWidth); | ||
| } | ||
| // TODO: check to see if changed before firing ref!? YES. do in other places too | ||
| internal_cjs.setRef(this.props.innerWidthRef, max); | ||
| }; | ||
| } | ||
| render() { | ||
| let { props, innerWidthRefMap } = this; | ||
| let { tDateProfile, slotWidth } = props; | ||
| let { slotDates, isWeekStarts } = tDateProfile; | ||
| let isDay = !tDateProfile.isTimeScale && !tDateProfile.largeUnit; | ||
| return (preact_cjs.createElement("div", { "aria-hidden": true, className: "fc-timeline-slots fc-fill fc-flex-row", style: { height: props.height } }, slotDates.map((slotDate, i) => { | ||
| let key = slotDate.toISOString(); | ||
| return (preact_cjs.createElement(TimelineSlatCell, { key: key, date: slotDate, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isEm: isWeekStarts[i], isDay: isDay, borderStart: Boolean(i), | ||
| // ref | ||
| innerWidthRef: innerWidthRefMap.createRef(key), | ||
| // dimensions | ||
| width: slotWidth })); | ||
| }))); | ||
| } | ||
| } | ||
| /* | ||
| TODO: rename this file! | ||
| */ | ||
| // returned value is between 0 and the number of snaps | ||
| function computeDateSnapCoverage$1(date, tDateProfile, dateEnv) { | ||
| let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration); | ||
| if (snapDiff < 0) { | ||
| return 0; | ||
| } | ||
| if (snapDiff >= tDateProfile.snapDiffToIndex.length) { | ||
| return tDateProfile.snapCnt; | ||
| } | ||
| let snapDiffInt = Math.floor(snapDiff); | ||
| let snapCoverage = tDateProfile.snapDiffToIndex[snapDiffInt]; | ||
| if (internal_cjs.isInt(snapCoverage)) { // not an in-between value | ||
| snapCoverage += snapDiff - snapDiffInt; // add the remainder | ||
| } | ||
| else { | ||
| // a fractional value, meaning the date is not visible | ||
| // always round up in this case. works for start AND end dates in a range. | ||
| snapCoverage = Math.ceil(snapCoverage); | ||
| } | ||
| return snapCoverage; | ||
| } | ||
| /* | ||
| TODO: DRY up with elsewhere? | ||
| */ | ||
| function horizontalsToCss(hcoord, isRtl) { | ||
| if (!hcoord) { | ||
| return {}; | ||
| } | ||
| if (isRtl) { | ||
| return { right: hcoord.start, width: hcoord.size }; | ||
| } | ||
| else { | ||
| return { left: hcoord.start, width: hcoord.size }; | ||
| } | ||
| } | ||
| function horizontalCoordToCss(start, isRtl) { | ||
| if (isRtl) { | ||
| return { right: start }; | ||
| } | ||
| else { | ||
| return { left: start }; | ||
| } | ||
| } | ||
| function createVerticalStyle(props) { | ||
| if (props) { | ||
| return { | ||
| top: props.start, | ||
| height: props.size, | ||
| }; | ||
| } | ||
| } | ||
| function createHorizontalStyle(// TODO: DRY up? | ||
| props, isRtl) { | ||
| if (props) { | ||
| return { | ||
| [isRtl ? 'right' : 'left']: props.start, | ||
| width: props.size, | ||
| }; | ||
| } | ||
| } | ||
| // Timeline-specific | ||
| // ------------------------------------------------------------------------------------------------- | ||
| const MIN_SLOT_WIDTH = 30; // for real | ||
| /* | ||
| TODO: DRY with computeSlatHeight? | ||
| */ | ||
| function computeSlotWidth(slatCnt, slatsPerLabel, slatMinWidth, labelInnerWidth, viewportWidth) { | ||
| if (labelInnerWidth == null || viewportWidth == null) { | ||
| return [undefined, undefined, false]; | ||
| } | ||
| slatMinWidth = Math.max(slatMinWidth || 0, (labelInnerWidth + 1) / slatsPerLabel, MIN_SLOT_WIDTH); | ||
| const slatTryWidth = viewportWidth / slatCnt; | ||
| let slatLiquid; | ||
| let slatWidth; | ||
| if (slatTryWidth >= slatMinWidth) { | ||
| slatLiquid = true; | ||
| slatWidth = slatTryWidth; | ||
| } | ||
| else { | ||
| slatLiquid = false; | ||
| slatWidth = Math.max(slatMinWidth, slatTryWidth); | ||
| } | ||
| return [slatWidth * slatCnt, slatWidth, slatLiquid]; | ||
| } | ||
| function timeToCoord(// pixels | ||
| time, dateEnv, dateProfile, tDateProfile, slowWidth) { | ||
| let date = dateEnv.add(dateProfile.activeRange.start, time); | ||
| if (!tDateProfile.isTimeScale) { | ||
| date = internal_cjs.startOfDay(date); | ||
| } | ||
| return dateToCoord(date, dateEnv, tDateProfile, slowWidth); | ||
| } | ||
| function dateToCoord(// pixels | ||
| date, dateEnv, tDateProfile, slotWidth) { | ||
| let snapCoverage = computeDateSnapCoverage(date, tDateProfile, dateEnv); | ||
| let slotCoverage = snapCoverage / tDateProfile.snapsPerSlot; | ||
| return slotCoverage * slotWidth; | ||
| } | ||
| /* | ||
| returned value is between 0 and the number of snaps | ||
| */ | ||
| function computeDateSnapCoverage(date, tDateProfile, dateEnv) { | ||
| let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration); | ||
| if (snapDiff < 0) { | ||
| return 0; | ||
| } | ||
| if (snapDiff >= tDateProfile.snapDiffToIndex.length) { | ||
| return tDateProfile.snapCnt; | ||
| } | ||
| let snapDiffInt = Math.floor(snapDiff); | ||
| let snapCoverage = tDateProfile.snapDiffToIndex[snapDiffInt]; | ||
| if (internal_cjs.isInt(snapCoverage)) { // not an in-between value | ||
| snapCoverage += snapDiff - snapDiffInt; // add the remainder | ||
| } | ||
| else { | ||
| // a fractional value, meaning the date is not visible | ||
| // always round up in this case. works for start AND end dates in a range. | ||
| snapCoverage = Math.ceil(snapCoverage); | ||
| } | ||
| return snapCoverage; | ||
| } | ||
| function computeManySegHorizontals(segs, segMinWidth, dateEnv, tDateProfile, slotWidth) { | ||
| const res = {}; | ||
| for (const seg of segs) { | ||
| res[internal_cjs.getEventKey(seg)] = computeSegHorizontals(seg, segMinWidth, dateEnv, tDateProfile, slotWidth); | ||
| } | ||
| return res; | ||
| } | ||
| function computeSegHorizontals(seg, segMinWidth, dateEnv, tDateProfile, slotWidth) { | ||
| const startCoord = dateToCoord(seg.startDate, dateEnv, tDateProfile, slotWidth); | ||
| const endCoord = dateToCoord(seg.endDate, dateEnv, tDateProfile, slotWidth); | ||
| let size = endCoord - startCoord; | ||
| if (segMinWidth) { | ||
| size = Math.max(size, segMinWidth); | ||
| } | ||
| return { start: startCoord, size }; | ||
| } | ||
| function computeFgSegPlacements(// mostly horizontals | ||
| segs, segHorizontals, // TODO: use Map | ||
| segHeights, // keyed by instanceId | ||
| hiddenGroupHeights, strictOrder, maxDepth) { | ||
| const segRanges = []; | ||
| // isn't it true that there will either be ALL hcoords or NONE? can optimize | ||
| for (const seg of segs) { | ||
| const hcoords = segHorizontals[internal_cjs.getEventKey(seg)]; | ||
| if (hcoords) { | ||
| segRanges.push(Object.assign(Object.assign({}, seg), { start: hcoords.start, end: hcoords.start + hcoords.size })); | ||
| } | ||
| } | ||
| const hierarchy = new internal_cjs.SegHierarchy(segRanges, (seg) => segHeights.get(internal_cjs.getEventKey(seg)), strictOrder, undefined, // maxCoord | ||
| maxDepth); | ||
| const segTops = new Map(); | ||
| hierarchy.traverseSegs((seg, segTop) => { | ||
| segTops.set(internal_cjs.getEventKey(seg), segTop); | ||
| }); | ||
| const { hiddenSegs } = hierarchy; | ||
| let totalHeight = 0; | ||
| for (const segRange of segRanges) { | ||
| const segKey = internal_cjs.getEventKey(segRange); | ||
| const segHeight = segHeights.get(segKey); | ||
| const segTop = segTops.get(segKey); | ||
| if (segHeight != null) { | ||
| if (segTop != null) { | ||
| totalHeight = Math.max(totalHeight, segTop + segHeight); | ||
| } | ||
| } | ||
| } | ||
| const hiddenGroups = internal_cjs.groupIntersectingSegs(hiddenSegs); | ||
| const hiddenGroupTops = new Map(); | ||
| // HACK for hiddenGroup findInsertion() call | ||
| hierarchy.strictOrder = true; | ||
| for (const hiddenGroup of hiddenGroups) { | ||
| const { levelCoord: top } = hierarchy.findInsertion(hiddenGroup, 0); | ||
| const hiddenGroupHeight = hiddenGroupHeights.get(hiddenGroup.key) || 0; | ||
| hiddenGroupTops.set(hiddenGroup.key, top); | ||
| totalHeight = Math.max(totalHeight, top + hiddenGroupHeight); | ||
| } | ||
| return [ | ||
| segTops, | ||
| hiddenGroups, | ||
| hiddenGroupTops, | ||
| totalHeight, | ||
| ]; | ||
| } | ||
| class TimelineLaneBg extends internal_cjs.BaseComponent { | ||
| render() { | ||
| let { props } = this; | ||
| let highlightSeg = [].concat(props.eventResizeSegs, props.dateSelectionSegs); | ||
| return (preact_cjs.createElement(preact_cjs.Fragment, null, | ||
| this.renderSegs(props.businessHourSegs || [], 'non-business'), | ||
| this.renderSegs(props.bgEventSegs || [], 'bg-event'), | ||
| this.renderSegs(highlightSeg, 'highlight'))); | ||
| } | ||
| renderSegs(segs, fillType) { | ||
| let { tDateProfile, todayRange, nowDate, slotWidth } = this.props; | ||
| let { dateEnv, isRtl } = this.context; | ||
| return (preact_cjs.createElement(preact_cjs.Fragment, null, segs.map((seg) => { | ||
| let hStyle; // TODO | ||
| if (slotWidth != null) { | ||
| let segHorizontal = computeSegHorizontals(seg, undefined, dateEnv, tDateProfile, slotWidth); | ||
| hStyle = horizontalsToCss(segHorizontal, isRtl); | ||
| } | ||
| return (preact_cjs.createElement("div", { key: internal_cjs.buildEventRangeKey(seg.eventRange), className: "fc-fill-y", style: hStyle }, fillType === 'bg-event' ? | ||
| preact_cjs.createElement(internal_cjs.BgEvent, Object.assign({ eventRange: seg.eventRange, isStart: seg.isStart, isEnd: seg.isEnd }, internal_cjs.getEventRangeMeta(seg.eventRange, todayRange, nowDate))) : (internal_cjs.renderFill(fillType)))); | ||
| }))); | ||
| } | ||
| } | ||
| class TimelineLaneSlicer extends internal_cjs.Slicer { | ||
| sliceRange(origRange, dateProfile, dateProfileGenerator, tDateProfile, dateEnv) { | ||
| let normalRange = normalizeRange(origRange, tDateProfile, dateEnv); | ||
| let segs = []; | ||
| // protect against when the span is entirely in an invalid date region | ||
| if (computeDateSnapCoverage$1(normalRange.start, tDateProfile, dateEnv) | ||
| < computeDateSnapCoverage$1(normalRange.end, tDateProfile, dateEnv)) { | ||
| // intersect the footprint's range with the grid's range | ||
| let slicedRange = internal_cjs.intersectRanges(normalRange, tDateProfile.normalizedRange); | ||
| if (slicedRange) { | ||
| segs.push({ | ||
| startDate: slicedRange.start, | ||
| endDate: slicedRange.end, | ||
| isStart: slicedRange.start.valueOf() === normalRange.start.valueOf() | ||
| && isValidDate(slicedRange.start, tDateProfile, dateProfile, dateProfileGenerator), | ||
| isEnd: slicedRange.end.valueOf() === normalRange.end.valueOf() | ||
| && isValidDate(internal_cjs.addMs(slicedRange.end, -1), tDateProfile, dateProfile, dateProfileGenerator), | ||
| }); | ||
| } | ||
| } | ||
| return segs; | ||
| } | ||
| } | ||
| const DEFAULT_TIME_FORMAT = internal_cjs.createFormatter({ | ||
| hour: 'numeric', | ||
| minute: '2-digit', | ||
| omitZeroMinute: true, | ||
| meridiem: 'narrow', | ||
| }); | ||
| class TimelineEvent extends internal_cjs.BaseComponent { | ||
| render() { | ||
| let { props, context } = this; | ||
| let { options } = context; | ||
| return (preact_cjs.createElement(internal_cjs.StandardEvent, Object.assign({}, props, { className: internal_cjs.joinClassNames('fc-timeline-event', options.eventOverlap === false // TODO: fix bad default | ||
| && 'fc-timeline-event-spacious', 'fc-h-event'), defaultTimeFormat: DEFAULT_TIME_FORMAT, defaultDisplayEventTime: !props.isTimeScale }))); | ||
| } | ||
| } | ||
| class TimelineLaneMoreLink extends internal_cjs.BaseComponent { | ||
| render() { | ||
| let { props } = this; | ||
| let { hiddenSegs, resourceId, forcedInvisibleMap } = props; | ||
| let dateSpanProps = resourceId ? { resourceId } : {}; | ||
| return (preact_cjs.createElement(internal_cjs.MoreLinkContainer, { className: 'fc-timeline-more-link', allDayDate: null, segs: hiddenSegs, hiddenSegs: hiddenSegs, dateProfile: props.dateProfile, todayRange: props.todayRange, dateSpanProps: dateSpanProps, popoverContent: () => (preact_cjs.createElement(preact_cjs.Fragment, null, hiddenSegs.map((seg) => { | ||
| let { eventRange } = seg; | ||
| let instanceId = eventRange.instance.instanceId; | ||
| return (preact_cjs.createElement("div", { key: instanceId, style: { visibility: forcedInvisibleMap[instanceId] ? 'hidden' : '' } }, | ||
| preact_cjs.createElement(TimelineEvent, Object.assign({ isTimeScale: props.isTimeScale, eventRange: eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === props.eventSelection }, internal_cjs.getEventRangeMeta(eventRange, props.todayRange, props.nowDate))))); | ||
| }))) }, (InnerContent) => (preact_cjs.createElement(InnerContent, { tag: "div", className: 'fc-timeline-more-link-inner fc-sticky-s' })))); | ||
| } | ||
| } | ||
| /* | ||
| TODO: make DRY with other Event Harnesses | ||
| */ | ||
| class TimelineEventHarness extends preact_cjs.Component { | ||
| constructor() { | ||
| super(...arguments); | ||
| // ref | ||
| this.rootElRef = preact_cjs.createRef(); | ||
| } | ||
| render() { | ||
| const { props } = this; | ||
| return (preact_cjs.createElement("div", { className: "fc-abs", style: props.style, ref: this.rootElRef }, props.children)); | ||
| } | ||
| componentDidMount() { | ||
| const rootEl = this.rootElRef.current; // TODO: make dynamic with useEffect | ||
| this.disconnectHeight = internal_cjs.watchHeight(rootEl, (height) => { | ||
| internal_cjs.setRef(this.props.heightRef, height); | ||
| }); | ||
| } | ||
| componentWillUnmount() { | ||
| this.disconnectHeight(); | ||
| internal_cjs.setRef(this.props.heightRef, null); | ||
| } | ||
| } | ||
| /* | ||
| TODO: split TimelineLaneBg and TimelineLaneFg? | ||
| */ | ||
| class TimelineLane extends internal_cjs.BaseComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // memo | ||
| this.sortEventSegs = internal_cjs.memoize(internal_cjs.sortEventSegs); | ||
| // refs | ||
| this.segHeightRefMap = new internal_cjs.RefMap(() => { | ||
| internal_cjs.afterSize(this.handleSegHeights); | ||
| }); | ||
| this.moreLinkHeightRefMap = new internal_cjs.RefMap(() => { | ||
| internal_cjs.afterSize(this.handleMoreLinkHeights); | ||
| }); | ||
| // internal | ||
| this.slicer = new TimelineLaneSlicer(); | ||
| this.handleMoreLinkHeights = () => { | ||
| this.setState({ moreLinkHeightRev: this.moreLinkHeightRefMap.rev }); // will trigger rerender | ||
| }; | ||
| this.handleSegHeights = () => { | ||
| this.setState({ segHeightRev: this.segHeightRefMap.rev }); // will trigger rerender | ||
| }; | ||
| } | ||
| /* | ||
| TODO: lots of memoization needed here! | ||
| */ | ||
| render() { | ||
| let { props, context, segHeightRefMap, moreLinkHeightRefMap } = this; | ||
| let { options } = context; | ||
| let { dateProfile, tDateProfile } = props; | ||
| let slicedProps = this.slicer.sliceProps(props, dateProfile, tDateProfile.isTimeScale ? null : props.nextDayThreshold, context, // wish we didn't have to pass in the rest of the args... | ||
| dateProfile, context.dateProfileGenerator, tDateProfile, context.dateEnv); | ||
| let mirrorSegs = (slicedProps.eventDrag ? slicedProps.eventDrag.segs : null) || | ||
| (slicedProps.eventResize ? slicedProps.eventResize.segs : null) || | ||
| []; | ||
| let fgSegs = this.sortEventSegs(slicedProps.fgEventSegs, options.eventOrder); | ||
| let fgSegHorizontals = props.slotWidth != null | ||
| ? computeManySegHorizontals(fgSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth) | ||
| : {}; | ||
| let [fgSegTops, hiddenGroups, hiddenGroupTops, totalHeight] = computeFgSegPlacements(fgSegs, fgSegHorizontals, segHeightRefMap.current, moreLinkHeightRefMap.current, options.eventOrderStrict, options.eventMaxStack); | ||
| let forcedInvisibleMap = // TODO: more convenient/DRY | ||
| (slicedProps.eventDrag ? slicedProps.eventDrag.affectedInstances : null) || | ||
| (slicedProps.eventResize ? slicedProps.eventResize.affectedInstances : null) || | ||
| {}; | ||
| return (preact_cjs.createElement(preact_cjs.Fragment, null, | ||
| preact_cjs.createElement(TimelineLaneBg, { tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange, | ||
| // content | ||
| bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventResizeSegs: slicedProps.eventResize ? slicedProps.eventResize.segs : [] /* bad new empty array? */, | ||
| // dimensions | ||
| slotWidth: props.slotWidth }), | ||
| preact_cjs.createElement("div", { className: internal_cjs.joinClassNames('fc-timeline-events', options.eventOverlap === false // TODO: fix bad default | ||
| ? 'fc-timeline-events-overlap-disabled' | ||
| : 'fc-timeline-events-overlap-enabled', 'fc-content-box'), style: { height: totalHeight } }, | ||
| this.renderFgSegs(fgSegs, fgSegHorizontals, fgSegTops, forcedInvisibleMap, hiddenGroups, hiddenGroupTops, false, // isDragging | ||
| false, // isResizing | ||
| false), | ||
| this.renderFgSegs(mirrorSegs, props.slotWidth // TODO: memoize | ||
| ? computeManySegHorizontals(mirrorSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth) | ||
| : {}, fgSegTops, {}, // forcedInvisibleMap | ||
| [], // hiddenGroups | ||
| new Map(), // hiddenGroupTops | ||
| Boolean(slicedProps.eventDrag), Boolean(slicedProps.eventResize), false)))); | ||
| } | ||
| renderFgSegs(segs, segHorizontals, segTops, forcedInvisibleMap, hiddenGroups, hiddenGroupTops, isDragging, isResizing, isDateSelecting) { | ||
| let { props, context, segHeightRefMap, moreLinkHeightRefMap } = this; | ||
| let isMirror = isDragging || isResizing || isDateSelecting; | ||
| return (preact_cjs.createElement(preact_cjs.Fragment, null, | ||
| segs.map((seg) => { | ||
| const { eventRange } = seg; | ||
| const { instanceId } = eventRange.instance; | ||
| const segTop = segTops.get(instanceId); | ||
| const segHorizontal = segHorizontals[instanceId]; | ||
| const isVisible = isMirror || | ||
| (segHorizontal && segTop != null && !forcedInvisibleMap[instanceId]); | ||
| return (preact_cjs.createElement(TimelineEventHarness, { key: instanceId, style: Object.assign({ visibility: isVisible ? '' : 'hidden', top: segTop || 0 }, horizontalsToCss(segHorizontal, context.isRtl)), heightRef: isMirror ? undefined : segHeightRefMap.createRef(instanceId) }, | ||
| preact_cjs.createElement(TimelineEvent, Object.assign({ isTimeScale: props.tDateProfile.isTimeScale, eventRange: eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === props.eventSelection /* TODO: bad for mirror? */ }, internal_cjs.getEventRangeMeta(eventRange, props.todayRange, props.nowDate))))); | ||
| }), | ||
| hiddenGroups.map((hiddenGroup) => (preact_cjs.createElement(TimelineEventHarness, { key: hiddenGroup.key, style: Object.assign({ top: hiddenGroupTops.get(hiddenGroup.key) || 0 }, horizontalsToCss({ | ||
| start: hiddenGroup.start, | ||
| size: hiddenGroup.end - hiddenGroup.start | ||
| }, context.isRtl)), heightRef: moreLinkHeightRefMap.createRef(hiddenGroup.key) }, | ||
| preact_cjs.createElement(TimelineLaneMoreLink, { hiddenSegs: hiddenGroup.segs, dateProfile: props.dateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isTimeScale: props.tDateProfile.isTimeScale, eventSelection: props.eventSelection, resourceId: props.resourceId, forcedInvisibleMap: forcedInvisibleMap })))))); | ||
| } | ||
| } | ||
| class TimelineHeaderCell extends internal_cjs.BaseComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // memo | ||
| this.refineRenderProps = internal_cjs.memoizeObjArg(refineRenderProps); | ||
| // ref | ||
| this.innerElRef = preact_cjs.createRef(); | ||
| } | ||
| render() { | ||
| let { props, context } = this; | ||
| let { dateEnv, options } = context; | ||
| let { cell, dateProfile, tDateProfile } = props; | ||
| // the cell.rowUnit is f'd | ||
| // giving 'month' for a 3-day view | ||
| // workaround: to infer day, do NOT time | ||
| let dateMeta = internal_cjs.getDateMeta(cell.date, props.todayRange, props.nowDate, dateProfile); | ||
| let renderProps = this.refineRenderProps({ | ||
| level: props.rowLevel, | ||
| dateMarker: cell.date, | ||
| text: cell.text, | ||
| dateEnv: context.dateEnv, | ||
| viewApi: context.viewApi, | ||
| }); | ||
| let isNavLink = !dateMeta.isDisabled && (cell.rowUnit && cell.rowUnit !== 'time'); | ||
| return (preact_cjs.createElement(internal_cjs.ContentContainer, { tag: "div", className: internal_cjs.joinClassNames('fc-timeline-slot-label fc-timeline-slot', cell.isWeekStart && 'fc-timeline-slot-em', // TODO: document this semantic className | ||
| 'fc-header-cell fc-cell fc-flex-col fc-justify-center', props.borderStart && 'fc-border-s', props.isCentered ? 'fc-align-center' : 'fc-align-start', | ||
| // TODO: so slot classnames for week/month/bigger. see note above about rowUnit | ||
| cell.rowUnit === 'time' ? | ||
| internal_cjs.getSlotClassName(dateMeta) : | ||
| internal_cjs.getDayClassName(dateMeta)), attrs: Object.assign({ 'data-date': dateEnv.formatIso(cell.date, { | ||
| omitTime: !tDateProfile.isTimeScale, | ||
| omitTimeZoneOffset: true, | ||
| }) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: { | ||
| width: props.slotWidth != null | ||
| ? props.slotWidth * cell.colspan | ||
| : undefined, | ||
| }, renderProps: renderProps, generatorName: "slotLabelContent", customGenerator: options.slotLabelContent, defaultGenerator: renderInnerContent, classNameGenerator: options.slotLabelClassNames, didMount: options.slotLabelDidMount, willUnmount: options.slotLabelWillUnmount }, (InnerContent) => (preact_cjs.createElement(InnerContent, { tag: 'div', attrs: isNavLink | ||
| // not tabbable because parent is aria-hidden | ||
| ? internal_cjs.buildNavLinkAttrs(context, cell.date, cell.rowUnit, undefined, /* isTabbable = */ false) | ||
| : {} // don't bother with aria-hidden because parent already hidden | ||
| , className: internal_cjs.joinClassNames('fc-cell-inner fc-padding-md', props.isSticky && 'fc-sticky-s'), elRef: this.innerElRef })))); | ||
| } | ||
| componentDidMount() { | ||
| const { props } = this; | ||
| const innerEl = this.innerElRef.current; // TODO: make dynamic with useEffect | ||
| this.detachSize = internal_cjs.watchSize(innerEl, (width, height) => { | ||
| internal_cjs.setRef(props.innerWidthRef, width); | ||
| internal_cjs.setRef(props.innerHeightRef, height); | ||
| // HACK for sticky-centering | ||
| innerEl.style.left = innerEl.style.right = | ||
| (props.isCentered && props.isSticky) | ||
| ? `calc(50% - ${width / 2}px)` | ||
| : ''; | ||
| }); | ||
| } | ||
| componentWillUnmount() { | ||
| const { props } = this; | ||
| this.detachSize(); | ||
| internal_cjs.setRef(props.innerWidthRef, null); | ||
| internal_cjs.setRef(props.innerHeightRef, null); | ||
| } | ||
| } | ||
| // Utils | ||
| // ------------------------------------------------------------------------------------------------- | ||
| function renderInnerContent(renderProps) { | ||
| return renderProps.text; | ||
| } | ||
| function refineRenderProps(input) { | ||
| return { | ||
| level: input.level, | ||
| date: input.dateEnv.toDate(input.dateMarker), | ||
| view: input.viewApi, | ||
| text: input.text, | ||
| }; | ||
| } | ||
| class TimelineHeaderRow extends internal_cjs.BaseComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // refs | ||
| this.innerWidthRefMap = new internal_cjs.RefMap(() => { | ||
| internal_cjs.afterSize(this.handleInnerWidths); | ||
| }); | ||
| this.innerHeightRefMap = new internal_cjs.RefMap(() => { | ||
| internal_cjs.afterSize(this.handleInnerHeights); | ||
| }); | ||
| this.handleInnerWidths = () => { | ||
| const innerWidthMap = this.innerWidthRefMap.current; | ||
| let max = 0; | ||
| for (const innerWidth of innerWidthMap.values()) { | ||
| max = Math.max(max, innerWidth); | ||
| } | ||
| // TODO: ensure not equal? | ||
| internal_cjs.setRef(this.props.innerWidthRef, max); | ||
| }; | ||
| this.handleInnerHeights = () => { | ||
| const innerHeightMap = this.innerHeightRefMap.current; | ||
| let max = 0; | ||
| for (const innerHeight of innerHeightMap.values()) { | ||
| max = Math.max(max, innerHeight); | ||
| } | ||
| // TODO: ensure not equal? | ||
| internal_cjs.setRef(this.props.innerHeighRef, max); | ||
| }; | ||
| } | ||
| render() { | ||
| const { props, innerWidthRefMap, innerHeightRefMap } = this; | ||
| const isCentered = !(props.tDateProfile.isTimeScale && props.isLastRow); | ||
| const isSticky = !props.isLastRow; | ||
| return (preact_cjs.createElement("div", { className: internal_cjs.joinClassNames('fc-flex-row fc-grow', // TODO: move fc-grow to parent? | ||
| !props.isLastRow && 'fc-border-b') }, props.cells.map((cell, cellI) => { | ||
| // TODO: make this part of the cell obj? | ||
| // TODO: rowUnit seems wrong sometimes. says 'month' when it should be day | ||
| // TODO: rowUnit is relevant to whole row. put it on a row object, not the cells | ||
| // TODO: use rowUnit to key the Row itself? | ||
| const key = cell.rowUnit + ':' + cell.date.toISOString(); | ||
| return (preact_cjs.createElement(TimelineHeaderCell, { key: key, cell: cell, rowLevel: props.rowLevel, dateProfile: props.dateProfile, tDateProfile: props.tDateProfile, todayRange: props.todayRange, nowDate: props.nowDate, isCentered: isCentered, isSticky: isSticky, borderStart: Boolean(cellI), | ||
| // refs | ||
| innerWidthRef: innerWidthRefMap.createRef(key), innerHeightRef: innerHeightRefMap.createRef(key), | ||
| // dimensions | ||
| slotWidth: props.slotWidth })); | ||
| }))); | ||
| } | ||
| componentWillUnmount() { | ||
| internal_cjs.setRef(this.props.innerWidthRef, null); | ||
| internal_cjs.setRef(this.props.innerHeighRef, null); | ||
| } | ||
| } | ||
| class TimelineNowIndicatorLine extends internal_cjs.BaseComponent { | ||
| render() { | ||
| const { props, context } = this; | ||
| return (preact_cjs.createElement("div", { className: "fc-timeline-now-indicator-container" }, | ||
| preact_cjs.createElement(internal_cjs.NowIndicatorContainer // TODO: make separate component? | ||
| , { className: 'fc-timeline-now-indicator-line', style: props.slotWidth != null | ||
| ? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl) | ||
| : {}, isAxis: false, date: props.nowDate }))); | ||
| } | ||
| } | ||
| class TimelineNowIndicatorArrow extends internal_cjs.BaseComponent { | ||
| render() { | ||
| const { props, context } = this; | ||
| return (preact_cjs.createElement("div", { className: "fc-timeline-now-indicator-container" }, | ||
| preact_cjs.createElement(internal_cjs.NowIndicatorContainer, { className: 'fc-timeline-now-indicator-arrow', style: props.slotWidth != null | ||
| ? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl) | ||
| : {}, isAxis: true, date: props.nowDate }))); | ||
| } | ||
| } | ||
| class TimelineView extends internal_cjs.DateComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // memoized | ||
| this.buildTimelineDateProfile = internal_cjs.memoize(buildTimelineDateProfile); | ||
| this.computeSlotWidth = internal_cjs.memoize(computeSlotWidth); | ||
| // refs | ||
| this.headerScrollerRef = preact_cjs.createRef(); | ||
| this.bodyScrollerRef = preact_cjs.createRef(); | ||
| this.footerScrollerRef = preact_cjs.createRef(); | ||
| this.headerRowInnerWidthMap = new internal_cjs.RefMap(() => { | ||
| internal_cjs.afterSize(this.handleSlotInnerWidths); | ||
| }); | ||
| this.scrollTime = null; | ||
| // Sizing | ||
| // ----------------------------------------------------------------------------------------------- | ||
| this.handleBodySlotInnerWidth = (innerWidth) => { | ||
| this.bodySlotInnerWidth = innerWidth; | ||
| internal_cjs.afterSize(this.handleSlotInnerWidths); | ||
| }; | ||
| this.handleSlotInnerWidths = () => { | ||
| const { state } = this; | ||
| const slotInnerWidth = Math.max(this.headerRowInnerWidthMap.current.get(this.tDateProfile.cellRows.length - 1) || 0, this.bodySlotInnerWidth); | ||
| if (state.slotInnerWidth !== slotInnerWidth) { | ||
| this.setState({ slotInnerWidth }); | ||
| } | ||
| }; | ||
| this.handleClientWidth = (clientWidth) => { | ||
| this.setState({ | ||
| clientWidth, | ||
| }); | ||
| }; | ||
| this.handleEndScrollbarWidth = (endScrollbarWidth) => { | ||
| this.setState({ | ||
| endScrollbarWidth | ||
| }); | ||
| }; | ||
| this.handleTimeScrollRequest = (scrollTime) => { | ||
| this.scrollTime = scrollTime; | ||
| this.applyTimeScroll(); | ||
| }; | ||
| this.handleTimeScrollEnd = ({ isUser }) => { | ||
| if (isUser) { | ||
| this.scrollTime = null; | ||
| } | ||
| }; | ||
| // Hit System | ||
| // ----------------------------------------------------------------------------------------------- | ||
| this.handeBodyEl = (el) => { | ||
| this.bodyEl = el; | ||
| if (el) { | ||
| this.context.registerInteractiveComponent(this, { el }); | ||
| } | ||
| else { | ||
| this.context.unregisterInteractiveComponent(this); | ||
| } | ||
| }; | ||
| } | ||
| render() { | ||
| const { props, state, context } = this; | ||
| const { options } = context; | ||
| /* date */ | ||
| const tDateProfile = this.tDateProfile = this.buildTimelineDateProfile(props.dateProfile, context.dateEnv, options, context.dateProfileGenerator); | ||
| const { cellRows } = tDateProfile; | ||
| const timerUnit = internal_cjs.greatestDurationDenominator(tDateProfile.slotDuration).unit; | ||
| /* table settings */ | ||
| const verticalScrolling = !props.forPrint && !internal_cjs.getIsHeightAuto(options); | ||
| const stickyHeaderDates = !props.forPrint && internal_cjs.getStickyHeaderDates(options); | ||
| const stickyFooterScrollbar = !props.forPrint && internal_cjs.getStickyFooterScrollbar(options); | ||
| /* table positions */ | ||
| const [canvasWidth, slotWidth] = this.computeSlotWidth(tDateProfile.slotCnt, tDateProfile.slotsPerLabel, options.slotMinWidth, state.slotInnerWidth, // is ACTUALLY the label width. rename? | ||
| state.clientWidth); | ||
| this.slotWidth = slotWidth; | ||
| return (preact_cjs.createElement(internal_cjs.NowTimer, { unit: timerUnit }, (nowDate, todayRange) => { | ||
| const enableNowIndicator = // TODO: DRY | ||
| options.nowIndicator && | ||
| slotWidth != null && | ||
| internal_cjs.rangeContainsMarker(props.dateProfile.currentRange, nowDate); | ||
| return (preact_cjs.createElement(internal_cjs.ViewContainer, { viewSpec: context.viewSpec, className: internal_cjs.joinClassNames('fc-timeline fc-border', | ||
| // HACK for Safari print-mode, where fc-scroller-no-bars won't take effect for | ||
| // the below Scrollers if they have liquid flex height | ||
| !props.forPrint && 'fc-flex-col') }, | ||
| preact_cjs.createElement(internal_cjs.Scroller, { horizontal: true, hideScrollbars: true, className: internal_cjs.joinClassNames('fc-timeline-header fc-flex-row fc-border-b', stickyHeaderDates && 'fc-table-header-sticky'), ref: this.headerScrollerRef }, | ||
| preact_cjs.createElement("div", { | ||
| // TODO: DRY | ||
| className: internal_cjs.joinClassNames('fc-rel', // origin for now-indicator | ||
| canvasWidth == null && 'fc-liquid'), style: { width: canvasWidth } }, | ||
| cellRows.map((cells, rowLevel) => { | ||
| const isLast = rowLevel === cellRows.length - 1; | ||
| return (preact_cjs.createElement(TimelineHeaderRow, { key: rowLevel, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, rowLevel: rowLevel, isLastRow: isLast, cells: cells, slotWidth: slotWidth, innerWidthRef: this.headerRowInnerWidthMap.createRef(rowLevel) })); | ||
| }), | ||
| enableNowIndicator && (preact_cjs.createElement(TimelineNowIndicatorArrow, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth }))), | ||
| Boolean(state.endScrollbarWidth) && (preact_cjs.createElement("div", { className: 'fc-border-s fc-filler', style: { minWidth: state.endScrollbarWidth } }))), | ||
| preact_cjs.createElement(internal_cjs.Scroller, { vertical: verticalScrolling, horizontal: true, hideScrollbars: props.forPrint, className: internal_cjs.joinClassNames('fc-timeline-body fc-flex-col', verticalScrolling && 'fc-liquid'), ref: this.bodyScrollerRef, clientWidthRef: this.handleClientWidth, endScrollbarWidthRef: this.handleEndScrollbarWidth }, | ||
| preact_cjs.createElement("div", { "aria-label": options.eventsHint, className: "fc-rel fc-grow", style: { width: canvasWidth }, ref: this.handeBodyEl }, | ||
| preact_cjs.createElement(TimelineSlats, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, | ||
| // ref | ||
| innerWidthRef: this.handleBodySlotInnerWidth, | ||
| // dimensions | ||
| slotWidth: slotWidth }), | ||
| preact_cjs.createElement(TimelineLane, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, nextDayThreshold: options.nextDayThreshold, eventStore: props.eventStore, eventUiBases: props.eventUiBases, businessHours: props.businessHours, dateSelection: props.dateSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, eventSelection: props.eventSelection, slotWidth: slotWidth }), | ||
| enableNowIndicator && (preact_cjs.createElement(TimelineNowIndicatorLine, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth })))), | ||
| stickyFooterScrollbar && (preact_cjs.createElement(internal_cjs.Scroller, { ref: this.footerScrollerRef, horizontal: true }, | ||
| preact_cjs.createElement("div", { style: { width: canvasWidth } }))))); | ||
| })); | ||
| } | ||
| // Lifecycle | ||
| // ----------------------------------------------------------------------------------------------- | ||
| componentDidMount() { | ||
| this.syncedScroller = new internal_cjs$1.ScrollerSyncer(true); // horizontal=true | ||
| this.updateSyncedScroller(); | ||
| this.resetScroll(); | ||
| this.context.emitter.on('_timeScrollRequest', this.handleTimeScrollRequest); | ||
| this.syncedScroller.addScrollEndListener(this.handleTimeScrollEnd); | ||
| } | ||
| componentDidUpdate(prevProps) { | ||
| this.updateSyncedScroller(); | ||
| if (prevProps.dateProfile !== this.props.dateProfile && this.context.options.scrollTimeReset) { | ||
| this.resetScroll(); | ||
| } | ||
| else { | ||
| // TODO: inefficient to update so often | ||
| this.applyTimeScroll(); | ||
| } | ||
| } | ||
| componentWillUnmount() { | ||
| this.syncedScroller.destroy(); | ||
| this.context.emitter.off('_timeScrollRequest', this.handleTimeScrollRequest); | ||
| this.syncedScroller.removeScrollEndListener(this.handleTimeScrollEnd); | ||
| } | ||
| // Scrolling | ||
| // ----------------------------------------------------------------------------------------------- | ||
| updateSyncedScroller() { | ||
| this.syncedScroller.handleChildren([ | ||
| this.headerScrollerRef.current, | ||
| this.bodyScrollerRef.current, | ||
| this.footerScrollerRef.current | ||
| ]); | ||
| } | ||
| resetScroll() { | ||
| this.handleTimeScrollRequest(this.context.options.scrollTime); | ||
| } | ||
| applyTimeScroll() { | ||
| const { props, context, tDateProfile, scrollTime, slotWidth } = this; | ||
| if (scrollTime != null && slotWidth != null) { | ||
| let x = timeToCoord(scrollTime, context.dateEnv, props.dateProfile, tDateProfile, slotWidth); | ||
| if (x) { | ||
| x += 1; // overcome border. TODO: DRY this up | ||
| } | ||
| this.syncedScroller.scrollTo({ x }); | ||
| } | ||
| } | ||
| queryHit(positionLeft, positionTop, elWidth, elHeight) { | ||
| const { props, context, tDateProfile, slotWidth } = this; | ||
| const { dateEnv } = context; | ||
| if (slotWidth) { | ||
| const x = context.isRtl ? elWidth - positionLeft : positionLeft; | ||
| const slatIndex = Math.floor(x / slotWidth); | ||
| const slatX = slatIndex * slotWidth; | ||
| const partial = (x - slatX) / slotWidth; // floating point number between 0 and 1 | ||
| const localSnapIndex = Math.floor(partial * tDateProfile.snapsPerSlot); // the snap # relative to start of slat | ||
| let startDate = dateEnv.add(tDateProfile.slotDates[slatIndex], internal_cjs.multiplyDuration(tDateProfile.snapDuration, localSnapIndex)); | ||
| let endDate = dateEnv.add(startDate, tDateProfile.snapDuration); | ||
| // TODO: generalize this coord stuff to TimeGrid? | ||
| let snapWidth = slotWidth / tDateProfile.snapsPerSlot; | ||
| let startCoord = slatIndex * slotWidth + (snapWidth * localSnapIndex); | ||
| let endCoord = startCoord + snapWidth; | ||
| let left, right; | ||
| if (context.isRtl) { | ||
| left = elWidth - endCoord; | ||
| right = elWidth - startCoord; | ||
| } | ||
| else { | ||
| left = startCoord; | ||
| right = endCoord; | ||
| } | ||
| return { | ||
| dateProfile: props.dateProfile, | ||
| dateSpan: { | ||
| range: { start: startDate, end: endDate }, | ||
| allDay: !tDateProfile.isTimeScale, | ||
| }, | ||
| rect: { | ||
| left, | ||
| right, | ||
| top: 0, | ||
| bottom: elHeight, | ||
| }, | ||
| // HACK. TODO: This is expensive to do every hit-query | ||
| dayEl: this.bodyEl.querySelectorAll('.fc-timeline-slot')[slatIndex], | ||
| layer: 0, | ||
| }; | ||
| } | ||
| return null; | ||
| } | ||
| } | ||
| var css_248z = ".fc-timeline-slots{z-index:1}.fc-timeline-events{position:relative;z-index:2}.fc-timeline-slot-minor{border-style:dotted}.fc-timeline-events-overlap-enabled{padding-bottom:10px}.fc-timeline-event{border-radius:0;font-size:var(--fc-small-font-size);margin-bottom:1px}.fc-direction-ltr .fc-timeline-event.fc-event-end{margin-right:1px}.fc-direction-rtl .fc-timeline-event.fc-event-end{margin-left:1px}.fc-timeline-event .fc-event-inner{align-items:center;display:flex;flex-direction:row;padding:2px 1px}.fc-timeline-event-spacious .fc-event-inner{padding-bottom:5px;padding-top:5px}.fc-timeline-event .fc-event-time{font-weight:700}.fc-timeline-event .fc-event-time,.fc-timeline-event .fc-event-title{padding:0 2px}.fc-timeline-event:not(.fc-event-end) .fc-event-inner:after,.fc-timeline-event:not(.fc-event-start) .fc-event-inner:before{border-color:transparent #000;border-style:solid;border-width:5px;content:\"\";flex-grow:0;flex-shrink:0;height:0;margin:0 1px;opacity:.5;width:0}.fc-direction-ltr .fc-timeline-event:not(.fc-event-start) .fc-event-inner:before,.fc-direction-rtl .fc-timeline-event:not(.fc-event-end) .fc-event-inner:after{border-left:0}.fc-direction-ltr .fc-timeline-event:not(.fc-event-end) .fc-event-inner:after,.fc-direction-rtl .fc-timeline-event:not(.fc-event-start) .fc-event-inner:before{border-right:0}.fc-timeline-more-link{align-items:flex-start;background:var(--fc-more-link-bg-color);color:var(--fc-more-link-text-color);cursor:pointer;display:flex;flex-direction:column;font-size:var(--fc-small-font-size);padding:1px}.fc-direction-ltr .fc-timeline-more-link{margin-right:1px}.fc-direction-rtl .fc-timeline-more-link{margin-left:1px}.fc-timeline-more-link-inner{padding:2px}.fc-timeline-now-indicator-container{bottom:0;left:0;overflow:hidden;pointer-events:none;position:absolute;right:0;top:0;z-index:4}.fc-timeline-now-indicator-arrow{border-bottom-style:solid;border-bottom-width:0;border-color:var(--fc-now-indicator-color);border-left:5px solid transparent;border-right:5px solid transparent;border-top-style:solid;border-top-width:6px;height:0;margin:0 -5px;position:absolute;top:0;width:0}.fc-timeline-now-indicator-line{border-left:1px solid var(--fc-now-indicator-color);bottom:0;position:absolute;top:0}"; | ||
| internal_cjs.injectStyles(css_248z); | ||
| exports.TimelineHeaderRow = TimelineHeaderRow; | ||
| exports.TimelineLane = TimelineLane; | ||
| exports.TimelineLaneBg = TimelineLaneBg; | ||
| exports.TimelineLaneSlicer = TimelineLaneSlicer; | ||
| exports.TimelineNowIndicatorArrow = TimelineNowIndicatorArrow; | ||
| exports.TimelineNowIndicatorLine = TimelineNowIndicatorLine; | ||
| exports.TimelineSlats = TimelineSlats; | ||
| exports.TimelineView = TimelineView; | ||
| exports.buildTimelineDateProfile = buildTimelineDateProfile; | ||
| exports.computeSlotWidth = computeSlotWidth; | ||
| exports.createHorizontalStyle = createHorizontalStyle; | ||
| exports.createVerticalStyle = createVerticalStyle; | ||
| exports.timeToCoord = timeToCoord; |
-209
| import { DateComponent, ViewProps, Hit, DateRange, DateMarker, DateProfile, DateEnv, BaseOptionsRefined, DateProfileGenerator, Slicer, CoordRange, EventStore, EventUiHash, DateSpan, EventInteractionState, BaseComponent, EventRangeProps, CoordSpan, SegGroup } from '@fullcalendar/core/internal'; | ||
| import { createElement, Ref } from '@fullcalendar/core/preact'; | ||
| import { Duration } from '@fullcalendar/core'; | ||
| interface TimelineViewState { | ||
| clientWidth?: number; | ||
| endScrollbarWidth?: number; | ||
| slotInnerWidth?: number; | ||
| } | ||
| declare class TimelineView extends DateComponent<ViewProps, TimelineViewState> { | ||
| private buildTimelineDateProfile; | ||
| private computeSlotWidth; | ||
| private headerScrollerRef; | ||
| private bodyScrollerRef; | ||
| private footerScrollerRef; | ||
| private tDateProfile?; | ||
| private bodyEl?; | ||
| private slotWidth?; | ||
| private headerRowInnerWidthMap; | ||
| private bodySlotInnerWidth?; | ||
| private syncedScroller; | ||
| private scrollTime; | ||
| render(): createElement.JSX.Element; | ||
| componentDidMount(): void; | ||
| componentDidUpdate(prevProps: ViewProps): void; | ||
| componentWillUnmount(): void; | ||
| handleBodySlotInnerWidth: (innerWidth: number) => void; | ||
| handleSlotInnerWidths: () => void; | ||
| handleClientWidth: (clientWidth: number) => void; | ||
| handleEndScrollbarWidth: (endScrollbarWidth: number) => void; | ||
| private updateSyncedScroller; | ||
| private resetScroll; | ||
| private handleTimeScrollRequest; | ||
| private handleTimeScrollEnd; | ||
| private applyTimeScroll; | ||
| handeBodyEl: (el: HTMLElement | null) => void; | ||
| queryHit(positionLeft: number, positionTop: number, elWidth: number, elHeight: number): Hit; | ||
| } | ||
| interface TimelineDateProfile { | ||
| labelInterval: Duration; | ||
| slotDuration: Duration; | ||
| slotsPerLabel: number; | ||
| headerFormats: any; | ||
| isTimeScale: boolean; | ||
| largeUnit: string; | ||
| emphasizeWeeks: boolean; | ||
| snapDuration: Duration; | ||
| snapsPerSlot: number; | ||
| normalizedRange: DateRange; | ||
| timeWindowMs: number; | ||
| slotDates: DateMarker[]; | ||
| isWeekStarts: boolean[]; | ||
| snapDiffToIndex: number[]; | ||
| snapIndexToDiff: number[]; | ||
| snapCnt: number; | ||
| slotCnt: number; | ||
| cellRows: TimelineHeaderCellData[][]; | ||
| } | ||
| interface TimelineHeaderCellData { | ||
| date: DateMarker; | ||
| text: string; | ||
| rowUnit: string; | ||
| colspan: number; | ||
| isWeekStart: boolean; | ||
| } | ||
| declare function buildTimelineDateProfile(dateProfile: DateProfile, dateEnv: DateEnv, allOptions: BaseOptionsRefined, dateProfileGenerator: DateProfileGenerator): TimelineDateProfile; | ||
| interface TimelineRange { | ||
| startDate: DateMarker; | ||
| endDate: DateMarker; | ||
| isStart: boolean; | ||
| isEnd: boolean; | ||
| } | ||
| type TimelineCoordRange = TimelineRange & CoordRange; | ||
| declare class TimelineLaneSlicer extends Slicer<TimelineRange, [ | ||
| DateProfile, | ||
| DateProfileGenerator, | ||
| TimelineDateProfile, | ||
| DateEnv | ||
| ]> { | ||
| sliceRange(origRange: DateRange, dateProfile: DateProfile, dateProfileGenerator: DateProfileGenerator, tDateProfile: TimelineDateProfile, dateEnv: DateEnv): TimelineRange[]; | ||
| } | ||
| interface TimelineLaneProps { | ||
| dateProfile: DateProfile; | ||
| tDateProfile: TimelineDateProfile; | ||
| nowDate: DateMarker; | ||
| todayRange: DateRange; | ||
| nextDayThreshold: Duration; | ||
| eventStore: EventStore | null; | ||
| eventUiBases: EventUiHash; | ||
| businessHours: EventStore | null; | ||
| dateSelection: DateSpan | null; | ||
| eventDrag: EventInteractionState | null; | ||
| eventResize: EventInteractionState | null; | ||
| eventSelection: string; | ||
| resourceId?: string; | ||
| slotWidth: number | undefined; | ||
| } | ||
| interface TimelineLaneState { | ||
| segHeightRev?: string; | ||
| moreLinkHeightRev?: string; | ||
| } | ||
| declare class TimelineLane extends BaseComponent<TimelineLaneProps, TimelineLaneState> { | ||
| private sortEventSegs; | ||
| private segHeightRefMap; | ||
| private moreLinkHeightRefMap; | ||
| private slicer; | ||
| render(): createElement.JSX.Element; | ||
| renderFgSegs(segs: (TimelineRange & EventRangeProps)[], segHorizontals: { | ||
| [instanceId: string]: CoordSpan; | ||
| }, segTops: Map<string, number>, forcedInvisibleMap: { | ||
| [instanceId: string]: any; | ||
| }, hiddenGroups: SegGroup<TimelineCoordRange>[], hiddenGroupTops: Map<string, number>, isDragging: boolean, isResizing: boolean, isDateSelecting: boolean): createElement.JSX.Element; | ||
| private handleMoreLinkHeights; | ||
| private handleSegHeights; | ||
| } | ||
| interface TimelineLaneBgProps { | ||
| tDateProfile: TimelineDateProfile; | ||
| nowDate: DateMarker; | ||
| todayRange: DateRange; | ||
| bgEventSegs: (TimelineRange & EventRangeProps)[] | null; | ||
| businessHourSegs: (TimelineRange & EventRangeProps)[] | null; | ||
| dateSelectionSegs: (TimelineRange & EventRangeProps)[]; | ||
| eventResizeSegs: (TimelineRange & EventRangeProps)[]; | ||
| slotWidth: number | undefined; | ||
| } | ||
| declare class TimelineLaneBg extends BaseComponent<TimelineLaneBgProps> { | ||
| render(): createElement.JSX.Element; | ||
| renderSegs(segs: (TimelineRange & EventRangeProps)[], fillType: string): createElement.JSX.Element; | ||
| } | ||
| interface TimelineSlatsProps { | ||
| dateProfile: DateProfile; | ||
| tDateProfile: TimelineDateProfile; | ||
| nowDate: DateMarker; | ||
| todayRange: DateRange; | ||
| height?: number; | ||
| slotWidth: number | undefined; | ||
| innerWidthRef?: Ref<number>; | ||
| } | ||
| declare class TimelineSlats extends BaseComponent<TimelineSlatsProps> { | ||
| private innerWidthRefMap; | ||
| render(): createElement.JSX.Element; | ||
| handleInnerWidths: () => void; | ||
| } | ||
| declare function createVerticalStyle(props: CoordSpan | undefined): { | ||
| top: number; | ||
| height: number; | ||
| } | undefined; | ||
| declare function createHorizontalStyle(// TODO: DRY up? | ||
| props: CoordSpan | undefined, isRtl: boolean): { | ||
| left: number; | ||
| width: number; | ||
| } | { | ||
| right: number; | ||
| width: number; | ||
| } | undefined; | ||
| declare function computeSlotWidth(slatCnt: number, slatsPerLabel: number, slatMinWidth: number | undefined, labelInnerWidth: number | undefined, viewportWidth: number | undefined): [ | ||
| canvasWidth: number | undefined, | ||
| slatWidth: number | undefined, | ||
| slatLiquid: boolean | ||
| ]; | ||
| declare function timeToCoord(// pixels | ||
| time: Duration, dateEnv: DateEnv, dateProfile: DateProfile, tDateProfile: TimelineDateProfile, slowWidth: number): number; | ||
| interface TimelineHeaderRowProps { | ||
| dateProfile: DateProfile; | ||
| tDateProfile: TimelineDateProfile; | ||
| nowDate: DateMarker; | ||
| todayRange: DateRange; | ||
| rowLevel: number; | ||
| isLastRow: boolean; | ||
| cells: TimelineHeaderCellData[]; | ||
| innerHeighRef?: Ref<number>; | ||
| innerWidthRef?: Ref<number>; | ||
| slotWidth: number | undefined; | ||
| } | ||
| declare class TimelineHeaderRow extends BaseComponent<TimelineHeaderRowProps> { | ||
| private innerWidthRefMap; | ||
| private innerHeightRefMap; | ||
| render(): createElement.JSX.Element; | ||
| handleInnerWidths: () => void; | ||
| handleInnerHeights: () => void; | ||
| componentWillUnmount(): void; | ||
| } | ||
| interface TimelineNowIndicatorArrowProps { | ||
| tDateProfile: TimelineDateProfile; | ||
| nowDate: DateMarker; | ||
| slotWidth: number | undefined; | ||
| } | ||
| declare class TimelineNowIndicatorArrow extends BaseComponent<TimelineNowIndicatorArrowProps> { | ||
| render(): createElement.JSX.Element; | ||
| } | ||
| interface TimelineNowIndicatorLineProps { | ||
| tDateProfile: TimelineDateProfile; | ||
| nowDate: DateMarker; | ||
| slotWidth: number | undefined; | ||
| } | ||
| declare class TimelineNowIndicatorLine extends BaseComponent<TimelineNowIndicatorLineProps> { | ||
| render(): createElement.JSX.Element; | ||
| } | ||
| export { TimelineDateProfile, TimelineHeaderRow, TimelineLane, TimelineLaneBg, TimelineLaneProps, TimelineLaneSlicer, TimelineNowIndicatorArrow, TimelineNowIndicatorLine, TimelineRange, TimelineSlats, TimelineView, buildTimelineDateProfile, computeSlotWidth, createHorizontalStyle, createVerticalStyle, timeToCoord }; |
-1242
| import { config, createFormatter, greatestDurationDenominator, asCleanDays, createDuration, wholeDivideDurations, asRoughMs, addDays, startOfDay, asRoughSeconds, asRoughMinutes, diffWholeDays, isInt, computeVisibleDayRange, padStart, BaseComponent, getDateMeta, ContentContainer, joinClassNames, getDayClassName, getSlotClassName, watchWidth, setRef, RefMap, afterSize, getEventKey, SegHierarchy, groupIntersectingSegs, buildEventRangeKey, BgEvent, getEventRangeMeta, renderFill, Slicer, intersectRanges, addMs, StandardEvent, MoreLinkContainer, watchHeight, memoize, sortEventSegs, memoizeObjArg, buildNavLinkAttrs, watchSize, NowIndicatorContainer, DateComponent, getIsHeightAuto, getStickyHeaderDates, getStickyFooterScrollbar, NowTimer, rangeContainsMarker, ViewContainer, Scroller, multiplyDuration, injectStyles } from '@fullcalendar/core/internal.js'; | ||
| import { createRef, createElement, Fragment, Component } from '@fullcalendar/core/preact.js'; | ||
| import { ScrollerSyncer } from '@fullcalendar/scrollgrid/internal.js'; | ||
| const MIN_AUTO_LABELS = 18; // more than `12` months but less that `24` hours | ||
| const MAX_AUTO_SLOTS_PER_LABEL = 6; // allows 6 10-min slots in an hour | ||
| const MAX_AUTO_CELLS = 200; // allows 4-days to have a :30 slot duration | ||
| config.MAX_TIMELINE_SLOTS = 1000; | ||
| // potential nice values for slot-duration and interval-duration | ||
| const STOCK_SUB_DURATIONS = [ | ||
| { years: 1 }, | ||
| { months: 1 }, | ||
| { days: 1 }, | ||
| { hours: 1 }, | ||
| { minutes: 30 }, | ||
| { minutes: 15 }, | ||
| { minutes: 10 }, | ||
| { minutes: 5 }, | ||
| { minutes: 1 }, | ||
| { seconds: 30 }, | ||
| { seconds: 15 }, | ||
| { seconds: 10 }, | ||
| { seconds: 5 }, | ||
| { seconds: 1 }, | ||
| { milliseconds: 500 }, | ||
| { milliseconds: 100 }, | ||
| { milliseconds: 10 }, | ||
| { milliseconds: 1 }, | ||
| ]; | ||
| function buildTimelineDateProfile(dateProfile, dateEnv, allOptions, dateProfileGenerator) { | ||
| let tDateProfile = { | ||
| labelInterval: allOptions.slotLabelInterval, | ||
| slotDuration: allOptions.slotDuration, | ||
| }; | ||
| validateLabelAndSlot(tDateProfile, dateProfile, dateEnv); // validate after computed grid duration | ||
| ensureLabelInterval(tDateProfile, dateProfile, dateEnv); | ||
| ensureSlotDuration(tDateProfile, dateProfile, dateEnv); | ||
| let input = allOptions.slotLabelFormat; | ||
| let rawFormats = Array.isArray(input) ? input : | ||
| (input != null) ? [input] : | ||
| computeHeaderFormats(tDateProfile, dateProfile, dateEnv, allOptions); | ||
| tDateProfile.headerFormats = rawFormats.map((rawFormat) => createFormatter(rawFormat)); | ||
| tDateProfile.isTimeScale = Boolean(tDateProfile.slotDuration.milliseconds); | ||
| let largeUnit = null; | ||
| if (!tDateProfile.isTimeScale) { | ||
| const slotUnit = greatestDurationDenominator(tDateProfile.slotDuration).unit; | ||
| if (/year|month|week/.test(slotUnit)) { | ||
| largeUnit = slotUnit; | ||
| } | ||
| } | ||
| tDateProfile.largeUnit = largeUnit; | ||
| tDateProfile.emphasizeWeeks = | ||
| asCleanDays(tDateProfile.slotDuration) === 1 && | ||
| currentRangeAs('weeks', dateProfile, dateEnv) >= 2 && | ||
| !allOptions.businessHours; | ||
| /* | ||
| console.log('label interval =', timelineView.labelInterval.humanize()) | ||
| console.log('slot duration =', timelineView.slotDuration.humanize()) | ||
| console.log('header formats =', timelineView.headerFormats) | ||
| console.log('isTimeScale', timelineView.isTimeScale) | ||
| console.log('largeUnit', timelineView.largeUnit) | ||
| */ | ||
| let rawSnapDuration = allOptions.snapDuration; | ||
| let snapDuration; | ||
| let snapsPerSlot; | ||
| if (rawSnapDuration) { | ||
| snapDuration = createDuration(rawSnapDuration); | ||
| snapsPerSlot = wholeDivideDurations(tDateProfile.slotDuration, snapDuration); | ||
| // ^ TODO: warning if not whole? | ||
| } | ||
| if (snapsPerSlot == null) { | ||
| snapDuration = tDateProfile.slotDuration; | ||
| snapsPerSlot = 1; | ||
| } | ||
| tDateProfile.snapDuration = snapDuration; | ||
| tDateProfile.snapsPerSlot = snapsPerSlot; | ||
| // more... | ||
| let timeWindowMs = asRoughMs(dateProfile.slotMaxTime) - asRoughMs(dateProfile.slotMinTime); | ||
| // TODO: why not use normalizeRange!? | ||
| let normalizedStart = normalizeDate(dateProfile.renderRange.start, tDateProfile, dateEnv); | ||
| let normalizedEnd = normalizeDate(dateProfile.renderRange.end, tDateProfile, dateEnv); | ||
| // apply slotMinTime/slotMaxTime | ||
| // TODO: View should be responsible. | ||
| if (tDateProfile.isTimeScale) { | ||
| normalizedStart = dateEnv.add(normalizedStart, dateProfile.slotMinTime); | ||
| normalizedEnd = dateEnv.add(addDays(normalizedEnd, -1), dateProfile.slotMaxTime); | ||
| } | ||
| tDateProfile.timeWindowMs = timeWindowMs; | ||
| tDateProfile.normalizedRange = { start: normalizedStart, end: normalizedEnd }; | ||
| let slotDates = []; | ||
| let date = normalizedStart; | ||
| while (date < normalizedEnd) { | ||
| if (isValidDate(date, tDateProfile, dateProfile, dateProfileGenerator)) { | ||
| slotDates.push(date); | ||
| } | ||
| date = dateEnv.add(date, tDateProfile.slotDuration); | ||
| } | ||
| tDateProfile.slotDates = slotDates; | ||
| // more... | ||
| let snapIndex = -1; | ||
| let snapDiff = 0; // index of the diff :( | ||
| const snapDiffToIndex = []; | ||
| const snapIndexToDiff = []; | ||
| date = normalizedStart; | ||
| while (date < normalizedEnd) { | ||
| if (isValidDate(date, tDateProfile, dateProfile, dateProfileGenerator)) { | ||
| snapIndex += 1; | ||
| snapDiffToIndex.push(snapIndex); | ||
| snapIndexToDiff.push(snapDiff); | ||
| } | ||
| else { | ||
| snapDiffToIndex.push(snapIndex + 0.5); | ||
| } | ||
| date = dateEnv.add(date, tDateProfile.snapDuration); | ||
| snapDiff += 1; | ||
| } | ||
| tDateProfile.snapDiffToIndex = snapDiffToIndex; | ||
| tDateProfile.snapIndexToDiff = snapIndexToDiff; | ||
| tDateProfile.snapCnt = snapIndex + 1; // is always one behind | ||
| tDateProfile.slotCnt = tDateProfile.snapCnt / tDateProfile.snapsPerSlot; | ||
| // more... | ||
| tDateProfile.isWeekStarts = buildIsWeekStarts(tDateProfile, dateEnv); | ||
| tDateProfile.cellRows = buildCellRows(tDateProfile, dateEnv); | ||
| tDateProfile.slotsPerLabel = wholeDivideDurations(tDateProfile.labelInterval, tDateProfile.slotDuration); | ||
| return tDateProfile; | ||
| } | ||
| /* | ||
| snaps to appropriate unit | ||
| */ | ||
| function normalizeDate(date, tDateProfile, dateEnv) { | ||
| let normalDate = date; | ||
| if (!tDateProfile.isTimeScale) { | ||
| normalDate = startOfDay(normalDate); | ||
| if (tDateProfile.largeUnit) { | ||
| normalDate = dateEnv.startOf(normalDate, tDateProfile.largeUnit); | ||
| } | ||
| } | ||
| return normalDate; | ||
| } | ||
| /* | ||
| snaps to appropriate unit | ||
| */ | ||
| function normalizeRange(range, tDateProfile, dateEnv) { | ||
| if (!tDateProfile.isTimeScale) { | ||
| range = computeVisibleDayRange(range); | ||
| if (tDateProfile.largeUnit) { | ||
| let dayRange = range; // preserve original result | ||
| range = { | ||
| start: dateEnv.startOf(range.start, tDateProfile.largeUnit), | ||
| end: dateEnv.startOf(range.end, tDateProfile.largeUnit), | ||
| }; | ||
| // if date is partially through the interval, or is in the same interval as the start, | ||
| // make the exclusive end be the *next* interval | ||
| if (range.end.valueOf() !== dayRange.end.valueOf() || range.end <= range.start) { | ||
| range = { | ||
| start: range.start, | ||
| end: dateEnv.add(range.end, tDateProfile.slotDuration), | ||
| }; | ||
| } | ||
| } | ||
| } | ||
| return range; | ||
| } | ||
| function isValidDate(date, tDateProfile, dateProfile, dateProfileGenerator) { | ||
| if (dateProfileGenerator.isHiddenDay(date)) { | ||
| return false; | ||
| } | ||
| if (tDateProfile.isTimeScale) { | ||
| // determine if the time is within slotMinTime/slotMaxTime, which may have wacky values | ||
| let day = startOfDay(date); | ||
| let timeMs = date.valueOf() - day.valueOf(); | ||
| let ms = timeMs - asRoughMs(dateProfile.slotMinTime); // milliseconds since slotMinTime | ||
| ms = ((ms % 86400000) + 86400000) % 86400000; // make negative values wrap to 24hr clock | ||
| return ms < tDateProfile.timeWindowMs; // before the slotMaxTime? | ||
| } | ||
| return true; | ||
| } | ||
| function validateLabelAndSlot(tDateProfile, dateProfile, dateEnv) { | ||
| const { currentRange } = dateProfile; | ||
| // make sure labelInterval doesn't exceed the max number of cells | ||
| if (tDateProfile.labelInterval) { | ||
| const labelCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, tDateProfile.labelInterval); | ||
| if (labelCnt > config.MAX_TIMELINE_SLOTS) { | ||
| console.warn('slotLabelInterval results in too many cells'); | ||
| tDateProfile.labelInterval = null; | ||
| } | ||
| } | ||
| // make sure slotDuration doesn't exceed the maximum number of cells | ||
| if (tDateProfile.slotDuration) { | ||
| const slotCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, tDateProfile.slotDuration); | ||
| if (slotCnt > config.MAX_TIMELINE_SLOTS) { | ||
| console.warn('slotDuration results in too many cells'); | ||
| tDateProfile.slotDuration = null; | ||
| } | ||
| } | ||
| // make sure labelInterval is a multiple of slotDuration | ||
| if (tDateProfile.labelInterval && tDateProfile.slotDuration) { | ||
| const slotsPerLabel = wholeDivideDurations(tDateProfile.labelInterval, tDateProfile.slotDuration); | ||
| if (slotsPerLabel === null || slotsPerLabel < 1) { | ||
| console.warn('slotLabelInterval must be a multiple of slotDuration'); | ||
| tDateProfile.slotDuration = null; | ||
| } | ||
| } | ||
| } | ||
| function ensureLabelInterval(tDateProfile, dateProfile, dateEnv) { | ||
| const { currentRange } = dateProfile; | ||
| let { labelInterval } = tDateProfile; | ||
| if (!labelInterval) { | ||
| // compute based off the slot duration | ||
| // find the largest label interval with an acceptable slots-per-label | ||
| let input; | ||
| if (tDateProfile.slotDuration) { | ||
| for (input of STOCK_SUB_DURATIONS) { | ||
| const tryLabelInterval = createDuration(input); | ||
| const slotsPerLabel = wholeDivideDurations(tryLabelInterval, tDateProfile.slotDuration); | ||
| if (slotsPerLabel !== null && slotsPerLabel <= MAX_AUTO_SLOTS_PER_LABEL) { | ||
| labelInterval = tryLabelInterval; | ||
| break; | ||
| } | ||
| } | ||
| // use the slot duration as a last resort | ||
| if (!labelInterval) { | ||
| labelInterval = tDateProfile.slotDuration; | ||
| } | ||
| // compute based off the view's duration | ||
| // find the largest label interval that yields the minimum number of labels | ||
| } | ||
| else { | ||
| for (input of STOCK_SUB_DURATIONS) { | ||
| labelInterval = createDuration(input); | ||
| const labelCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, labelInterval); | ||
| if (labelCnt >= MIN_AUTO_LABELS) { | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| tDateProfile.labelInterval = labelInterval; | ||
| } | ||
| return labelInterval; | ||
| } | ||
| function ensureSlotDuration(tDateProfile, dateProfile, dateEnv) { | ||
| const { currentRange } = dateProfile; | ||
| let { slotDuration } = tDateProfile; | ||
| if (!slotDuration) { | ||
| const labelInterval = ensureLabelInterval(tDateProfile, dateProfile, dateEnv); // will compute if necessary | ||
| // compute based off the label interval | ||
| // find the largest slot duration that is different from labelInterval, but still acceptable | ||
| for (let input of STOCK_SUB_DURATIONS) { | ||
| const trySlotDuration = createDuration(input); | ||
| const slotsPerLabel = wholeDivideDurations(labelInterval, trySlotDuration); | ||
| if (slotsPerLabel !== null && slotsPerLabel > 1 && slotsPerLabel <= MAX_AUTO_SLOTS_PER_LABEL) { | ||
| slotDuration = trySlotDuration; | ||
| break; | ||
| } | ||
| } | ||
| // only allow the value if it won't exceed the view's # of slots limit | ||
| if (slotDuration) { | ||
| const slotCnt = dateEnv.countDurationsBetween(currentRange.start, currentRange.end, slotDuration); | ||
| if (slotCnt > MAX_AUTO_CELLS) { | ||
| slotDuration = null; | ||
| } | ||
| } | ||
| // use the label interval as a last resort | ||
| if (!slotDuration) { | ||
| slotDuration = labelInterval; | ||
| } | ||
| tDateProfile.slotDuration = slotDuration; | ||
| } | ||
| return slotDuration; | ||
| } | ||
| function computeHeaderFormats(tDateProfile, dateProfile, dateEnv, allOptions) { | ||
| let format1; | ||
| let format2; | ||
| const { labelInterval } = tDateProfile; | ||
| let unit = greatestDurationDenominator(labelInterval).unit; | ||
| const weekNumbersVisible = allOptions.weekNumbers; | ||
| let format0 = (format1 = (format2 = null)); | ||
| // NOTE: weekNumber computation function wont work | ||
| if ((unit === 'week') && !weekNumbersVisible) { | ||
| unit = 'day'; | ||
| } | ||
| switch (unit) { | ||
| case 'year': | ||
| format0 = { year: 'numeric' }; // '2015' | ||
| break; | ||
| case 'month': | ||
| if (currentRangeAs('years', dateProfile, dateEnv) > 1) { | ||
| format0 = { year: 'numeric' }; // '2015' | ||
| } | ||
| format1 = { month: 'short' }; // 'Jan' | ||
| break; | ||
| case 'week': | ||
| if (currentRangeAs('years', dateProfile, dateEnv) > 1) { | ||
| format0 = { year: 'numeric' }; // '2015' | ||
| } | ||
| format1 = { week: 'narrow' }; // 'Wk4' | ||
| break; | ||
| case 'day': | ||
| if (currentRangeAs('years', dateProfile, dateEnv) > 1) { | ||
| format0 = { year: 'numeric', month: 'long' }; // 'January 2014' | ||
| } | ||
| else if (currentRangeAs('months', dateProfile, dateEnv) > 1) { | ||
| format0 = { month: 'long' }; // 'January' | ||
| } | ||
| if (weekNumbersVisible) { | ||
| format1 = { week: 'short' }; // 'Wk 4' | ||
| } | ||
| format2 = { weekday: 'narrow', day: 'numeric' }; // 'Su 9' | ||
| break; | ||
| case 'hour': | ||
| if (weekNumbersVisible) { | ||
| format0 = { week: 'short' }; // 'Wk 4' | ||
| } | ||
| if (currentRangeAs('days', dateProfile, dateEnv) > 1) { | ||
| format1 = { weekday: 'short', day: 'numeric', month: 'numeric', omitCommas: true }; // Sat 4/7 | ||
| } | ||
| format2 = { | ||
| hour: 'numeric', | ||
| minute: '2-digit', | ||
| omitZeroMinute: true, | ||
| meridiem: 'short', | ||
| }; | ||
| break; | ||
| case 'minute': | ||
| // sufficiently large number of different minute cells? | ||
| if ((asRoughMinutes(labelInterval) / 60) >= MAX_AUTO_SLOTS_PER_LABEL) { | ||
| format0 = { | ||
| hour: 'numeric', | ||
| meridiem: 'short', | ||
| }; | ||
| format1 = (params) => (':' + padStart(params.date.minute, 2) // ':30' | ||
| ); | ||
| } | ||
| else { | ||
| format0 = { | ||
| hour: 'numeric', | ||
| minute: 'numeric', | ||
| meridiem: 'short', | ||
| }; | ||
| } | ||
| break; | ||
| case 'second': | ||
| // sufficiently large number of different second cells? | ||
| if ((asRoughSeconds(labelInterval) / 60) >= MAX_AUTO_SLOTS_PER_LABEL) { | ||
| format0 = { hour: 'numeric', minute: '2-digit', meridiem: 'lowercase' }; // '8:30 PM' | ||
| format1 = (params) => (':' + padStart(params.date.second, 2) // ':30' | ||
| ); | ||
| } | ||
| else { | ||
| format0 = { hour: 'numeric', minute: '2-digit', second: '2-digit', meridiem: 'lowercase' }; // '8:30:45 PM' | ||
| } | ||
| break; | ||
| case 'millisecond': | ||
| format0 = { hour: 'numeric', minute: '2-digit', second: '2-digit', meridiem: 'lowercase' }; // '8:30:45 PM' | ||
| format1 = (params) => ('.' + padStart(params.millisecond, 3)); | ||
| break; | ||
| } | ||
| return [].concat(format0 || [], format1 || [], format2 || []); | ||
| } | ||
| // Compute the number of the give units in the "current" range. | ||
| // Won't go more precise than days. | ||
| // Will return `0` if there's not a clean whole interval. | ||
| function currentRangeAs(unit, dateProfile, dateEnv) { | ||
| let range = dateProfile.currentRange; | ||
| let res = null; | ||
| if (unit === 'years') { | ||
| res = dateEnv.diffWholeYears(range.start, range.end); | ||
| } | ||
| else if (unit === 'months') { | ||
| res = dateEnv.diffWholeMonths(range.start, range.end); | ||
| } | ||
| else if (unit === 'weeks') { | ||
| res = dateEnv.diffWholeMonths(range.start, range.end); | ||
| } | ||
| else if (unit === 'days') { | ||
| res = diffWholeDays(range.start, range.end); | ||
| } | ||
| return res || 0; | ||
| } | ||
| function buildIsWeekStarts(tDateProfile, dateEnv) { | ||
| let { slotDates, emphasizeWeeks } = tDateProfile; | ||
| let prevWeekNumber = null; | ||
| let isWeekStarts = []; | ||
| for (let slotDate of slotDates) { | ||
| let weekNumber = dateEnv.computeWeekNumber(slotDate); | ||
| let isWeekStart = emphasizeWeeks && (prevWeekNumber !== null) && (prevWeekNumber !== weekNumber); | ||
| prevWeekNumber = weekNumber; | ||
| isWeekStarts.push(isWeekStart); | ||
| } | ||
| return isWeekStarts; | ||
| } | ||
| function buildCellRows(tDateProfile, dateEnv) { | ||
| let slotDates = tDateProfile.slotDates; | ||
| let formats = tDateProfile.headerFormats; | ||
| let cellRows = formats.map(() => []); // indexed by row,col | ||
| let slotAsDays = asCleanDays(tDateProfile.slotDuration); | ||
| let guessedSlotUnit = slotAsDays === 7 ? 'week' : | ||
| slotAsDays === 1 ? 'day' : | ||
| null; | ||
| // specifically for navclicks | ||
| let rowUnitsFromFormats = formats.map((format) => (format.getLargestUnit ? format.getLargestUnit() : null)); | ||
| // builds cellRows and slotCells | ||
| for (let i = 0; i < slotDates.length; i += 1) { | ||
| let date = slotDates[i]; | ||
| let isWeekStart = tDateProfile.isWeekStarts[i]; | ||
| for (let row = 0; row < formats.length; row += 1) { | ||
| let format = formats[row]; | ||
| let rowCells = cellRows[row]; | ||
| let leadingCell = rowCells[rowCells.length - 1]; | ||
| let isLastRow = row === formats.length - 1; | ||
| let isSuperRow = formats.length > 1 && !isLastRow; // more than one row and not the last | ||
| let newCell = null; | ||
| let rowUnit = rowUnitsFromFormats[row] || (isLastRow ? guessedSlotUnit : null); | ||
| if (isSuperRow) { | ||
| let text = dateEnv.format(date, format); | ||
| if (!leadingCell || (leadingCell.text !== text)) { | ||
| newCell = buildCellObject(date, text, rowUnit); | ||
| } | ||
| else { | ||
| leadingCell.colspan += 1; | ||
| } | ||
| } | ||
| else if (!leadingCell || | ||
| isInt(dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.labelInterval))) { | ||
| let text = dateEnv.format(date, format); | ||
| newCell = buildCellObject(date, text, rowUnit); | ||
| } | ||
| else { | ||
| leadingCell.colspan += 1; | ||
| } | ||
| if (newCell) { | ||
| newCell.weekStart = isWeekStart; | ||
| rowCells.push(newCell); | ||
| } | ||
| } | ||
| } | ||
| return cellRows; | ||
| } | ||
| function buildCellObject(date, text, rowUnit) { | ||
| return { date, text, rowUnit, colspan: 1, isWeekStart: false }; | ||
| } | ||
| class TimelineSlatCell extends BaseComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // ref | ||
| this.innerElRef = createRef(); | ||
| } | ||
| render() { | ||
| let { props, context } = this; | ||
| let { dateEnv, options } = context; | ||
| let { date, tDateProfile, isEm } = props; | ||
| let dateMeta = getDateMeta(props.date, props.todayRange, props.nowDate, props.dateProfile); | ||
| let renderProps = Object.assign(Object.assign({ date: dateEnv.toDate(props.date) }, dateMeta), { view: context.viewApi }); | ||
| return (createElement(ContentContainer, { tag: "div", | ||
| // fc-align-start shrinks width of InnerContent | ||
| // TODO: document this semantic className fc-timeline-slot-em | ||
| className: joinClassNames('fc-timeline-slot', isEm && 'fc-timeline-slot-em', tDateProfile.isTimeScale && (isInt(dateEnv.countDurationsBetween(// best to do this here? | ||
| tDateProfile.normalizedRange.start, props.date, tDateProfile.labelInterval)) ? | ||
| 'fc-timeline-slot-major' : | ||
| 'fc-timeline-slot-minor'), 'fc-timeline-slot-lane fc-cell fc-flex-col fc-align-start', props.borderStart && 'fc-border-s', props.isDay ? | ||
| getDayClassName(dateMeta) : | ||
| getSlotClassName(dateMeta)), attrs: Object.assign({ 'data-date': dateEnv.formatIso(date, { | ||
| omitTimeZoneOffset: true, | ||
| omitTime: !tDateProfile.isTimeScale, | ||
| }) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: { | ||
| width: props.width, | ||
| }, renderProps: renderProps, generatorName: "slotLaneContent", customGenerator: options.slotLaneContent, classNameGenerator: options.slotLaneClassNames, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount }, (InnerContent) => (createElement(InnerContent, { tag: "div", className: 'fc-cell-inner', elRef: this.innerElRef })))); | ||
| } | ||
| componentDidMount() { | ||
| const innerEl = this.innerElRef.current; | ||
| this.disconnectInnerWidth = watchWidth(innerEl, (width) => { | ||
| setRef(this.props.innerWidthRef, width); | ||
| }); | ||
| } | ||
| componentWillUnmount() { | ||
| this.disconnectInnerWidth(); | ||
| setRef(this.props.innerWidthRef, null); | ||
| } | ||
| } | ||
| class TimelineSlats extends BaseComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| this.innerWidthRefMap = new RefMap(() => { | ||
| afterSize(this.handleInnerWidths); | ||
| }); | ||
| this.handleInnerWidths = () => { | ||
| const innerWidthMap = this.innerWidthRefMap.current; | ||
| let max = 0; | ||
| for (const innerWidth of innerWidthMap.values()) { | ||
| max = Math.max(max, innerWidth); | ||
| } | ||
| // TODO: check to see if changed before firing ref!? YES. do in other places too | ||
| setRef(this.props.innerWidthRef, max); | ||
| }; | ||
| } | ||
| render() { | ||
| let { props, innerWidthRefMap } = this; | ||
| let { tDateProfile, slotWidth } = props; | ||
| let { slotDates, isWeekStarts } = tDateProfile; | ||
| let isDay = !tDateProfile.isTimeScale && !tDateProfile.largeUnit; | ||
| return (createElement("div", { "aria-hidden": true, className: "fc-timeline-slots fc-fill fc-flex-row", style: { height: props.height } }, slotDates.map((slotDate, i) => { | ||
| let key = slotDate.toISOString(); | ||
| return (createElement(TimelineSlatCell, { key: key, date: slotDate, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isEm: isWeekStarts[i], isDay: isDay, borderStart: Boolean(i), | ||
| // ref | ||
| innerWidthRef: innerWidthRefMap.createRef(key), | ||
| // dimensions | ||
| width: slotWidth })); | ||
| }))); | ||
| } | ||
| } | ||
| /* | ||
| TODO: rename this file! | ||
| */ | ||
| // returned value is between 0 and the number of snaps | ||
| function computeDateSnapCoverage$1(date, tDateProfile, dateEnv) { | ||
| let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration); | ||
| if (snapDiff < 0) { | ||
| return 0; | ||
| } | ||
| if (snapDiff >= tDateProfile.snapDiffToIndex.length) { | ||
| return tDateProfile.snapCnt; | ||
| } | ||
| let snapDiffInt = Math.floor(snapDiff); | ||
| let snapCoverage = tDateProfile.snapDiffToIndex[snapDiffInt]; | ||
| if (isInt(snapCoverage)) { // not an in-between value | ||
| snapCoverage += snapDiff - snapDiffInt; // add the remainder | ||
| } | ||
| else { | ||
| // a fractional value, meaning the date is not visible | ||
| // always round up in this case. works for start AND end dates in a range. | ||
| snapCoverage = Math.ceil(snapCoverage); | ||
| } | ||
| return snapCoverage; | ||
| } | ||
| /* | ||
| TODO: DRY up with elsewhere? | ||
| */ | ||
| function horizontalsToCss(hcoord, isRtl) { | ||
| if (!hcoord) { | ||
| return {}; | ||
| } | ||
| if (isRtl) { | ||
| return { right: hcoord.start, width: hcoord.size }; | ||
| } | ||
| else { | ||
| return { left: hcoord.start, width: hcoord.size }; | ||
| } | ||
| } | ||
| function horizontalCoordToCss(start, isRtl) { | ||
| if (isRtl) { | ||
| return { right: start }; | ||
| } | ||
| else { | ||
| return { left: start }; | ||
| } | ||
| } | ||
| function createVerticalStyle(props) { | ||
| if (props) { | ||
| return { | ||
| top: props.start, | ||
| height: props.size, | ||
| }; | ||
| } | ||
| } | ||
| function createHorizontalStyle(// TODO: DRY up? | ||
| props, isRtl) { | ||
| if (props) { | ||
| return { | ||
| [isRtl ? 'right' : 'left']: props.start, | ||
| width: props.size, | ||
| }; | ||
| } | ||
| } | ||
| // Timeline-specific | ||
| // ------------------------------------------------------------------------------------------------- | ||
| const MIN_SLOT_WIDTH = 30; // for real | ||
| /* | ||
| TODO: DRY with computeSlatHeight? | ||
| */ | ||
| function computeSlotWidth(slatCnt, slatsPerLabel, slatMinWidth, labelInnerWidth, viewportWidth) { | ||
| if (labelInnerWidth == null || viewportWidth == null) { | ||
| return [undefined, undefined, false]; | ||
| } | ||
| slatMinWidth = Math.max(slatMinWidth || 0, (labelInnerWidth + 1) / slatsPerLabel, MIN_SLOT_WIDTH); | ||
| const slatTryWidth = viewportWidth / slatCnt; | ||
| let slatLiquid; | ||
| let slatWidth; | ||
| if (slatTryWidth >= slatMinWidth) { | ||
| slatLiquid = true; | ||
| slatWidth = slatTryWidth; | ||
| } | ||
| else { | ||
| slatLiquid = false; | ||
| slatWidth = Math.max(slatMinWidth, slatTryWidth); | ||
| } | ||
| return [slatWidth * slatCnt, slatWidth, slatLiquid]; | ||
| } | ||
| function timeToCoord(// pixels | ||
| time, dateEnv, dateProfile, tDateProfile, slowWidth) { | ||
| let date = dateEnv.add(dateProfile.activeRange.start, time); | ||
| if (!tDateProfile.isTimeScale) { | ||
| date = startOfDay(date); | ||
| } | ||
| return dateToCoord(date, dateEnv, tDateProfile, slowWidth); | ||
| } | ||
| function dateToCoord(// pixels | ||
| date, dateEnv, tDateProfile, slotWidth) { | ||
| let snapCoverage = computeDateSnapCoverage(date, tDateProfile, dateEnv); | ||
| let slotCoverage = snapCoverage / tDateProfile.snapsPerSlot; | ||
| return slotCoverage * slotWidth; | ||
| } | ||
| /* | ||
| returned value is between 0 and the number of snaps | ||
| */ | ||
| function computeDateSnapCoverage(date, tDateProfile, dateEnv) { | ||
| let snapDiff = dateEnv.countDurationsBetween(tDateProfile.normalizedRange.start, date, tDateProfile.snapDuration); | ||
| if (snapDiff < 0) { | ||
| return 0; | ||
| } | ||
| if (snapDiff >= tDateProfile.snapDiffToIndex.length) { | ||
| return tDateProfile.snapCnt; | ||
| } | ||
| let snapDiffInt = Math.floor(snapDiff); | ||
| let snapCoverage = tDateProfile.snapDiffToIndex[snapDiffInt]; | ||
| if (isInt(snapCoverage)) { // not an in-between value | ||
| snapCoverage += snapDiff - snapDiffInt; // add the remainder | ||
| } | ||
| else { | ||
| // a fractional value, meaning the date is not visible | ||
| // always round up in this case. works for start AND end dates in a range. | ||
| snapCoverage = Math.ceil(snapCoverage); | ||
| } | ||
| return snapCoverage; | ||
| } | ||
| function computeManySegHorizontals(segs, segMinWidth, dateEnv, tDateProfile, slotWidth) { | ||
| const res = {}; | ||
| for (const seg of segs) { | ||
| res[getEventKey(seg)] = computeSegHorizontals(seg, segMinWidth, dateEnv, tDateProfile, slotWidth); | ||
| } | ||
| return res; | ||
| } | ||
| function computeSegHorizontals(seg, segMinWidth, dateEnv, tDateProfile, slotWidth) { | ||
| const startCoord = dateToCoord(seg.startDate, dateEnv, tDateProfile, slotWidth); | ||
| const endCoord = dateToCoord(seg.endDate, dateEnv, tDateProfile, slotWidth); | ||
| let size = endCoord - startCoord; | ||
| if (segMinWidth) { | ||
| size = Math.max(size, segMinWidth); | ||
| } | ||
| return { start: startCoord, size }; | ||
| } | ||
| function computeFgSegPlacements(// mostly horizontals | ||
| segs, segHorizontals, // TODO: use Map | ||
| segHeights, // keyed by instanceId | ||
| hiddenGroupHeights, strictOrder, maxDepth) { | ||
| const segRanges = []; | ||
| // isn't it true that there will either be ALL hcoords or NONE? can optimize | ||
| for (const seg of segs) { | ||
| const hcoords = segHorizontals[getEventKey(seg)]; | ||
| if (hcoords) { | ||
| segRanges.push(Object.assign(Object.assign({}, seg), { start: hcoords.start, end: hcoords.start + hcoords.size })); | ||
| } | ||
| } | ||
| const hierarchy = new SegHierarchy(segRanges, (seg) => segHeights.get(getEventKey(seg)), strictOrder, undefined, // maxCoord | ||
| maxDepth); | ||
| const segTops = new Map(); | ||
| hierarchy.traverseSegs((seg, segTop) => { | ||
| segTops.set(getEventKey(seg), segTop); | ||
| }); | ||
| const { hiddenSegs } = hierarchy; | ||
| let totalHeight = 0; | ||
| for (const segRange of segRanges) { | ||
| const segKey = getEventKey(segRange); | ||
| const segHeight = segHeights.get(segKey); | ||
| const segTop = segTops.get(segKey); | ||
| if (segHeight != null) { | ||
| if (segTop != null) { | ||
| totalHeight = Math.max(totalHeight, segTop + segHeight); | ||
| } | ||
| } | ||
| } | ||
| const hiddenGroups = groupIntersectingSegs(hiddenSegs); | ||
| const hiddenGroupTops = new Map(); | ||
| // HACK for hiddenGroup findInsertion() call | ||
| hierarchy.strictOrder = true; | ||
| for (const hiddenGroup of hiddenGroups) { | ||
| const { levelCoord: top } = hierarchy.findInsertion(hiddenGroup, 0); | ||
| const hiddenGroupHeight = hiddenGroupHeights.get(hiddenGroup.key) || 0; | ||
| hiddenGroupTops.set(hiddenGroup.key, top); | ||
| totalHeight = Math.max(totalHeight, top + hiddenGroupHeight); | ||
| } | ||
| return [ | ||
| segTops, | ||
| hiddenGroups, | ||
| hiddenGroupTops, | ||
| totalHeight, | ||
| ]; | ||
| } | ||
| class TimelineLaneBg extends BaseComponent { | ||
| render() { | ||
| let { props } = this; | ||
| let highlightSeg = [].concat(props.eventResizeSegs, props.dateSelectionSegs); | ||
| return (createElement(Fragment, null, | ||
| this.renderSegs(props.businessHourSegs || [], 'non-business'), | ||
| this.renderSegs(props.bgEventSegs || [], 'bg-event'), | ||
| this.renderSegs(highlightSeg, 'highlight'))); | ||
| } | ||
| renderSegs(segs, fillType) { | ||
| let { tDateProfile, todayRange, nowDate, slotWidth } = this.props; | ||
| let { dateEnv, isRtl } = this.context; | ||
| return (createElement(Fragment, null, segs.map((seg) => { | ||
| let hStyle; // TODO | ||
| if (slotWidth != null) { | ||
| let segHorizontal = computeSegHorizontals(seg, undefined, dateEnv, tDateProfile, slotWidth); | ||
| hStyle = horizontalsToCss(segHorizontal, isRtl); | ||
| } | ||
| return (createElement("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-fill-y", style: hStyle }, fillType === 'bg-event' ? | ||
| createElement(BgEvent, Object.assign({ eventRange: seg.eventRange, isStart: seg.isStart, isEnd: seg.isEnd }, getEventRangeMeta(seg.eventRange, todayRange, nowDate))) : (renderFill(fillType)))); | ||
| }))); | ||
| } | ||
| } | ||
| class TimelineLaneSlicer extends Slicer { | ||
| sliceRange(origRange, dateProfile, dateProfileGenerator, tDateProfile, dateEnv) { | ||
| let normalRange = normalizeRange(origRange, tDateProfile, dateEnv); | ||
| let segs = []; | ||
| // protect against when the span is entirely in an invalid date region | ||
| if (computeDateSnapCoverage$1(normalRange.start, tDateProfile, dateEnv) | ||
| < computeDateSnapCoverage$1(normalRange.end, tDateProfile, dateEnv)) { | ||
| // intersect the footprint's range with the grid's range | ||
| let slicedRange = intersectRanges(normalRange, tDateProfile.normalizedRange); | ||
| if (slicedRange) { | ||
| segs.push({ | ||
| startDate: slicedRange.start, | ||
| endDate: slicedRange.end, | ||
| isStart: slicedRange.start.valueOf() === normalRange.start.valueOf() | ||
| && isValidDate(slicedRange.start, tDateProfile, dateProfile, dateProfileGenerator), | ||
| isEnd: slicedRange.end.valueOf() === normalRange.end.valueOf() | ||
| && isValidDate(addMs(slicedRange.end, -1), tDateProfile, dateProfile, dateProfileGenerator), | ||
| }); | ||
| } | ||
| } | ||
| return segs; | ||
| } | ||
| } | ||
| const DEFAULT_TIME_FORMAT = createFormatter({ | ||
| hour: 'numeric', | ||
| minute: '2-digit', | ||
| omitZeroMinute: true, | ||
| meridiem: 'narrow', | ||
| }); | ||
| class TimelineEvent extends BaseComponent { | ||
| render() { | ||
| let { props, context } = this; | ||
| let { options } = context; | ||
| return (createElement(StandardEvent, Object.assign({}, props, { className: joinClassNames('fc-timeline-event', options.eventOverlap === false // TODO: fix bad default | ||
| && 'fc-timeline-event-spacious', 'fc-h-event'), defaultTimeFormat: DEFAULT_TIME_FORMAT, defaultDisplayEventTime: !props.isTimeScale }))); | ||
| } | ||
| } | ||
| class TimelineLaneMoreLink extends BaseComponent { | ||
| render() { | ||
| let { props } = this; | ||
| let { hiddenSegs, resourceId, forcedInvisibleMap } = props; | ||
| let dateSpanProps = resourceId ? { resourceId } : {}; | ||
| return (createElement(MoreLinkContainer, { className: 'fc-timeline-more-link', allDayDate: null, segs: hiddenSegs, hiddenSegs: hiddenSegs, dateProfile: props.dateProfile, todayRange: props.todayRange, dateSpanProps: dateSpanProps, popoverContent: () => (createElement(Fragment, null, hiddenSegs.map((seg) => { | ||
| let { eventRange } = seg; | ||
| let instanceId = eventRange.instance.instanceId; | ||
| return (createElement("div", { key: instanceId, style: { visibility: forcedInvisibleMap[instanceId] ? 'hidden' : '' } }, | ||
| createElement(TimelineEvent, Object.assign({ isTimeScale: props.isTimeScale, eventRange: eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === props.eventSelection }, getEventRangeMeta(eventRange, props.todayRange, props.nowDate))))); | ||
| }))) }, (InnerContent) => (createElement(InnerContent, { tag: "div", className: 'fc-timeline-more-link-inner fc-sticky-s' })))); | ||
| } | ||
| } | ||
| /* | ||
| TODO: make DRY with other Event Harnesses | ||
| */ | ||
| class TimelineEventHarness extends Component { | ||
| constructor() { | ||
| super(...arguments); | ||
| // ref | ||
| this.rootElRef = createRef(); | ||
| } | ||
| render() { | ||
| const { props } = this; | ||
| return (createElement("div", { className: "fc-abs", style: props.style, ref: this.rootElRef }, props.children)); | ||
| } | ||
| componentDidMount() { | ||
| const rootEl = this.rootElRef.current; // TODO: make dynamic with useEffect | ||
| this.disconnectHeight = watchHeight(rootEl, (height) => { | ||
| setRef(this.props.heightRef, height); | ||
| }); | ||
| } | ||
| componentWillUnmount() { | ||
| this.disconnectHeight(); | ||
| setRef(this.props.heightRef, null); | ||
| } | ||
| } | ||
| /* | ||
| TODO: split TimelineLaneBg and TimelineLaneFg? | ||
| */ | ||
| class TimelineLane extends BaseComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // memo | ||
| this.sortEventSegs = memoize(sortEventSegs); | ||
| // refs | ||
| this.segHeightRefMap = new RefMap(() => { | ||
| afterSize(this.handleSegHeights); | ||
| }); | ||
| this.moreLinkHeightRefMap = new RefMap(() => { | ||
| afterSize(this.handleMoreLinkHeights); | ||
| }); | ||
| // internal | ||
| this.slicer = new TimelineLaneSlicer(); | ||
| this.handleMoreLinkHeights = () => { | ||
| this.setState({ moreLinkHeightRev: this.moreLinkHeightRefMap.rev }); // will trigger rerender | ||
| }; | ||
| this.handleSegHeights = () => { | ||
| this.setState({ segHeightRev: this.segHeightRefMap.rev }); // will trigger rerender | ||
| }; | ||
| } | ||
| /* | ||
| TODO: lots of memoization needed here! | ||
| */ | ||
| render() { | ||
| let { props, context, segHeightRefMap, moreLinkHeightRefMap } = this; | ||
| let { options } = context; | ||
| let { dateProfile, tDateProfile } = props; | ||
| let slicedProps = this.slicer.sliceProps(props, dateProfile, tDateProfile.isTimeScale ? null : props.nextDayThreshold, context, // wish we didn't have to pass in the rest of the args... | ||
| dateProfile, context.dateProfileGenerator, tDateProfile, context.dateEnv); | ||
| let mirrorSegs = (slicedProps.eventDrag ? slicedProps.eventDrag.segs : null) || | ||
| (slicedProps.eventResize ? slicedProps.eventResize.segs : null) || | ||
| []; | ||
| let fgSegs = this.sortEventSegs(slicedProps.fgEventSegs, options.eventOrder); | ||
| let fgSegHorizontals = props.slotWidth != null | ||
| ? computeManySegHorizontals(fgSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth) | ||
| : {}; | ||
| let [fgSegTops, hiddenGroups, hiddenGroupTops, totalHeight] = computeFgSegPlacements(fgSegs, fgSegHorizontals, segHeightRefMap.current, moreLinkHeightRefMap.current, options.eventOrderStrict, options.eventMaxStack); | ||
| let forcedInvisibleMap = // TODO: more convenient/DRY | ||
| (slicedProps.eventDrag ? slicedProps.eventDrag.affectedInstances : null) || | ||
| (slicedProps.eventResize ? slicedProps.eventResize.affectedInstances : null) || | ||
| {}; | ||
| return (createElement(Fragment, null, | ||
| createElement(TimelineLaneBg, { tDateProfile: tDateProfile, nowDate: props.nowDate, todayRange: props.todayRange, | ||
| // content | ||
| bgEventSegs: slicedProps.bgEventSegs, businessHourSegs: slicedProps.businessHourSegs, dateSelectionSegs: slicedProps.dateSelectionSegs, eventResizeSegs: slicedProps.eventResize ? slicedProps.eventResize.segs : [] /* bad new empty array? */, | ||
| // dimensions | ||
| slotWidth: props.slotWidth }), | ||
| createElement("div", { className: joinClassNames('fc-timeline-events', options.eventOverlap === false // TODO: fix bad default | ||
| ? 'fc-timeline-events-overlap-disabled' | ||
| : 'fc-timeline-events-overlap-enabled', 'fc-content-box'), style: { height: totalHeight } }, | ||
| this.renderFgSegs(fgSegs, fgSegHorizontals, fgSegTops, forcedInvisibleMap, hiddenGroups, hiddenGroupTops, false, // isDragging | ||
| false, // isResizing | ||
| false), | ||
| this.renderFgSegs(mirrorSegs, props.slotWidth // TODO: memoize | ||
| ? computeManySegHorizontals(mirrorSegs, options.eventMinWidth, context.dateEnv, tDateProfile, props.slotWidth) | ||
| : {}, fgSegTops, {}, // forcedInvisibleMap | ||
| [], // hiddenGroups | ||
| new Map(), // hiddenGroupTops | ||
| Boolean(slicedProps.eventDrag), Boolean(slicedProps.eventResize), false)))); | ||
| } | ||
| renderFgSegs(segs, segHorizontals, segTops, forcedInvisibleMap, hiddenGroups, hiddenGroupTops, isDragging, isResizing, isDateSelecting) { | ||
| let { props, context, segHeightRefMap, moreLinkHeightRefMap } = this; | ||
| let isMirror = isDragging || isResizing || isDateSelecting; | ||
| return (createElement(Fragment, null, | ||
| segs.map((seg) => { | ||
| const { eventRange } = seg; | ||
| const { instanceId } = eventRange.instance; | ||
| const segTop = segTops.get(instanceId); | ||
| const segHorizontal = segHorizontals[instanceId]; | ||
| const isVisible = isMirror || | ||
| (segHorizontal && segTop != null && !forcedInvisibleMap[instanceId]); | ||
| return (createElement(TimelineEventHarness, { key: instanceId, style: Object.assign({ visibility: isVisible ? '' : 'hidden', top: segTop || 0 }, horizontalsToCss(segHorizontal, context.isRtl)), heightRef: isMirror ? undefined : segHeightRefMap.createRef(instanceId) }, | ||
| createElement(TimelineEvent, Object.assign({ isTimeScale: props.tDateProfile.isTimeScale, eventRange: eventRange, isStart: seg.isStart, isEnd: seg.isEnd, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === props.eventSelection /* TODO: bad for mirror? */ }, getEventRangeMeta(eventRange, props.todayRange, props.nowDate))))); | ||
| }), | ||
| hiddenGroups.map((hiddenGroup) => (createElement(TimelineEventHarness, { key: hiddenGroup.key, style: Object.assign({ top: hiddenGroupTops.get(hiddenGroup.key) || 0 }, horizontalsToCss({ | ||
| start: hiddenGroup.start, | ||
| size: hiddenGroup.end - hiddenGroup.start | ||
| }, context.isRtl)), heightRef: moreLinkHeightRefMap.createRef(hiddenGroup.key) }, | ||
| createElement(TimelineLaneMoreLink, { hiddenSegs: hiddenGroup.segs, dateProfile: props.dateProfile, nowDate: props.nowDate, todayRange: props.todayRange, isTimeScale: props.tDateProfile.isTimeScale, eventSelection: props.eventSelection, resourceId: props.resourceId, forcedInvisibleMap: forcedInvisibleMap })))))); | ||
| } | ||
| } | ||
| class TimelineHeaderCell extends BaseComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // memo | ||
| this.refineRenderProps = memoizeObjArg(refineRenderProps); | ||
| // ref | ||
| this.innerElRef = createRef(); | ||
| } | ||
| render() { | ||
| let { props, context } = this; | ||
| let { dateEnv, options } = context; | ||
| let { cell, dateProfile, tDateProfile } = props; | ||
| // the cell.rowUnit is f'd | ||
| // giving 'month' for a 3-day view | ||
| // workaround: to infer day, do NOT time | ||
| let dateMeta = getDateMeta(cell.date, props.todayRange, props.nowDate, dateProfile); | ||
| let renderProps = this.refineRenderProps({ | ||
| level: props.rowLevel, | ||
| dateMarker: cell.date, | ||
| text: cell.text, | ||
| dateEnv: context.dateEnv, | ||
| viewApi: context.viewApi, | ||
| }); | ||
| let isNavLink = !dateMeta.isDisabled && (cell.rowUnit && cell.rowUnit !== 'time'); | ||
| return (createElement(ContentContainer, { tag: "div", className: joinClassNames('fc-timeline-slot-label fc-timeline-slot', cell.isWeekStart && 'fc-timeline-slot-em', // TODO: document this semantic className | ||
| 'fc-header-cell fc-cell fc-flex-col fc-justify-center', props.borderStart && 'fc-border-s', props.isCentered ? 'fc-align-center' : 'fc-align-start', | ||
| // TODO: so slot classnames for week/month/bigger. see note above about rowUnit | ||
| cell.rowUnit === 'time' ? | ||
| getSlotClassName(dateMeta) : | ||
| getDayClassName(dateMeta)), attrs: Object.assign({ 'data-date': dateEnv.formatIso(cell.date, { | ||
| omitTime: !tDateProfile.isTimeScale, | ||
| omitTimeZoneOffset: true, | ||
| }) }, (dateMeta.isToday ? { 'aria-current': 'date' } : {})), style: { | ||
| width: props.slotWidth != null | ||
| ? props.slotWidth * cell.colspan | ||
| : undefined, | ||
| }, renderProps: renderProps, generatorName: "slotLabelContent", customGenerator: options.slotLabelContent, defaultGenerator: renderInnerContent, classNameGenerator: options.slotLabelClassNames, didMount: options.slotLabelDidMount, willUnmount: options.slotLabelWillUnmount }, (InnerContent) => (createElement(InnerContent, { tag: 'div', attrs: isNavLink | ||
| // not tabbable because parent is aria-hidden | ||
| ? buildNavLinkAttrs(context, cell.date, cell.rowUnit, undefined, /* isTabbable = */ false) | ||
| : {} // don't bother with aria-hidden because parent already hidden | ||
| , className: joinClassNames('fc-cell-inner fc-padding-md', props.isSticky && 'fc-sticky-s'), elRef: this.innerElRef })))); | ||
| } | ||
| componentDidMount() { | ||
| const { props } = this; | ||
| const innerEl = this.innerElRef.current; // TODO: make dynamic with useEffect | ||
| this.detachSize = watchSize(innerEl, (width, height) => { | ||
| setRef(props.innerWidthRef, width); | ||
| setRef(props.innerHeightRef, height); | ||
| // HACK for sticky-centering | ||
| innerEl.style.left = innerEl.style.right = | ||
| (props.isCentered && props.isSticky) | ||
| ? `calc(50% - ${width / 2}px)` | ||
| : ''; | ||
| }); | ||
| } | ||
| componentWillUnmount() { | ||
| const { props } = this; | ||
| this.detachSize(); | ||
| setRef(props.innerWidthRef, null); | ||
| setRef(props.innerHeightRef, null); | ||
| } | ||
| } | ||
| // Utils | ||
| // ------------------------------------------------------------------------------------------------- | ||
| function renderInnerContent(renderProps) { | ||
| return renderProps.text; | ||
| } | ||
| function refineRenderProps(input) { | ||
| return { | ||
| level: input.level, | ||
| date: input.dateEnv.toDate(input.dateMarker), | ||
| view: input.viewApi, | ||
| text: input.text, | ||
| }; | ||
| } | ||
| class TimelineHeaderRow extends BaseComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // refs | ||
| this.innerWidthRefMap = new RefMap(() => { | ||
| afterSize(this.handleInnerWidths); | ||
| }); | ||
| this.innerHeightRefMap = new RefMap(() => { | ||
| afterSize(this.handleInnerHeights); | ||
| }); | ||
| this.handleInnerWidths = () => { | ||
| const innerWidthMap = this.innerWidthRefMap.current; | ||
| let max = 0; | ||
| for (const innerWidth of innerWidthMap.values()) { | ||
| max = Math.max(max, innerWidth); | ||
| } | ||
| // TODO: ensure not equal? | ||
| setRef(this.props.innerWidthRef, max); | ||
| }; | ||
| this.handleInnerHeights = () => { | ||
| const innerHeightMap = this.innerHeightRefMap.current; | ||
| let max = 0; | ||
| for (const innerHeight of innerHeightMap.values()) { | ||
| max = Math.max(max, innerHeight); | ||
| } | ||
| // TODO: ensure not equal? | ||
| setRef(this.props.innerHeighRef, max); | ||
| }; | ||
| } | ||
| render() { | ||
| const { props, innerWidthRefMap, innerHeightRefMap } = this; | ||
| const isCentered = !(props.tDateProfile.isTimeScale && props.isLastRow); | ||
| const isSticky = !props.isLastRow; | ||
| return (createElement("div", { className: joinClassNames('fc-flex-row fc-grow', // TODO: move fc-grow to parent? | ||
| !props.isLastRow && 'fc-border-b') }, props.cells.map((cell, cellI) => { | ||
| // TODO: make this part of the cell obj? | ||
| // TODO: rowUnit seems wrong sometimes. says 'month' when it should be day | ||
| // TODO: rowUnit is relevant to whole row. put it on a row object, not the cells | ||
| // TODO: use rowUnit to key the Row itself? | ||
| const key = cell.rowUnit + ':' + cell.date.toISOString(); | ||
| return (createElement(TimelineHeaderCell, { key: key, cell: cell, rowLevel: props.rowLevel, dateProfile: props.dateProfile, tDateProfile: props.tDateProfile, todayRange: props.todayRange, nowDate: props.nowDate, isCentered: isCentered, isSticky: isSticky, borderStart: Boolean(cellI), | ||
| // refs | ||
| innerWidthRef: innerWidthRefMap.createRef(key), innerHeightRef: innerHeightRefMap.createRef(key), | ||
| // dimensions | ||
| slotWidth: props.slotWidth })); | ||
| }))); | ||
| } | ||
| componentWillUnmount() { | ||
| setRef(this.props.innerWidthRef, null); | ||
| setRef(this.props.innerHeighRef, null); | ||
| } | ||
| } | ||
| class TimelineNowIndicatorLine extends BaseComponent { | ||
| render() { | ||
| const { props, context } = this; | ||
| return (createElement("div", { className: "fc-timeline-now-indicator-container" }, | ||
| createElement(NowIndicatorContainer // TODO: make separate component? | ||
| , { className: 'fc-timeline-now-indicator-line', style: props.slotWidth != null | ||
| ? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl) | ||
| : {}, isAxis: false, date: props.nowDate }))); | ||
| } | ||
| } | ||
| class TimelineNowIndicatorArrow extends BaseComponent { | ||
| render() { | ||
| const { props, context } = this; | ||
| return (createElement("div", { className: "fc-timeline-now-indicator-container" }, | ||
| createElement(NowIndicatorContainer, { className: 'fc-timeline-now-indicator-arrow', style: props.slotWidth != null | ||
| ? horizontalCoordToCss(dateToCoord(props.nowDate, context.dateEnv, props.tDateProfile, props.slotWidth), context.isRtl) | ||
| : {}, isAxis: true, date: props.nowDate }))); | ||
| } | ||
| } | ||
| class TimelineView extends DateComponent { | ||
| constructor() { | ||
| super(...arguments); | ||
| // memoized | ||
| this.buildTimelineDateProfile = memoize(buildTimelineDateProfile); | ||
| this.computeSlotWidth = memoize(computeSlotWidth); | ||
| // refs | ||
| this.headerScrollerRef = createRef(); | ||
| this.bodyScrollerRef = createRef(); | ||
| this.footerScrollerRef = createRef(); | ||
| this.headerRowInnerWidthMap = new RefMap(() => { | ||
| afterSize(this.handleSlotInnerWidths); | ||
| }); | ||
| this.scrollTime = null; | ||
| // Sizing | ||
| // ----------------------------------------------------------------------------------------------- | ||
| this.handleBodySlotInnerWidth = (innerWidth) => { | ||
| this.bodySlotInnerWidth = innerWidth; | ||
| afterSize(this.handleSlotInnerWidths); | ||
| }; | ||
| this.handleSlotInnerWidths = () => { | ||
| const { state } = this; | ||
| const slotInnerWidth = Math.max(this.headerRowInnerWidthMap.current.get(this.tDateProfile.cellRows.length - 1) || 0, this.bodySlotInnerWidth); | ||
| if (state.slotInnerWidth !== slotInnerWidth) { | ||
| this.setState({ slotInnerWidth }); | ||
| } | ||
| }; | ||
| this.handleClientWidth = (clientWidth) => { | ||
| this.setState({ | ||
| clientWidth, | ||
| }); | ||
| }; | ||
| this.handleEndScrollbarWidth = (endScrollbarWidth) => { | ||
| this.setState({ | ||
| endScrollbarWidth | ||
| }); | ||
| }; | ||
| this.handleTimeScrollRequest = (scrollTime) => { | ||
| this.scrollTime = scrollTime; | ||
| this.applyTimeScroll(); | ||
| }; | ||
| this.handleTimeScrollEnd = ({ isUser }) => { | ||
| if (isUser) { | ||
| this.scrollTime = null; | ||
| } | ||
| }; | ||
| // Hit System | ||
| // ----------------------------------------------------------------------------------------------- | ||
| this.handeBodyEl = (el) => { | ||
| this.bodyEl = el; | ||
| if (el) { | ||
| this.context.registerInteractiveComponent(this, { el }); | ||
| } | ||
| else { | ||
| this.context.unregisterInteractiveComponent(this); | ||
| } | ||
| }; | ||
| } | ||
| render() { | ||
| const { props, state, context } = this; | ||
| const { options } = context; | ||
| /* date */ | ||
| const tDateProfile = this.tDateProfile = this.buildTimelineDateProfile(props.dateProfile, context.dateEnv, options, context.dateProfileGenerator); | ||
| const { cellRows } = tDateProfile; | ||
| const timerUnit = greatestDurationDenominator(tDateProfile.slotDuration).unit; | ||
| /* table settings */ | ||
| const verticalScrolling = !props.forPrint && !getIsHeightAuto(options); | ||
| const stickyHeaderDates = !props.forPrint && getStickyHeaderDates(options); | ||
| const stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(options); | ||
| /* table positions */ | ||
| const [canvasWidth, slotWidth] = this.computeSlotWidth(tDateProfile.slotCnt, tDateProfile.slotsPerLabel, options.slotMinWidth, state.slotInnerWidth, // is ACTUALLY the label width. rename? | ||
| state.clientWidth); | ||
| this.slotWidth = slotWidth; | ||
| return (createElement(NowTimer, { unit: timerUnit }, (nowDate, todayRange) => { | ||
| const enableNowIndicator = // TODO: DRY | ||
| options.nowIndicator && | ||
| slotWidth != null && | ||
| rangeContainsMarker(props.dateProfile.currentRange, nowDate); | ||
| return (createElement(ViewContainer, { viewSpec: context.viewSpec, className: joinClassNames('fc-timeline fc-border', | ||
| // HACK for Safari print-mode, where fc-scroller-no-bars won't take effect for | ||
| // the below Scrollers if they have liquid flex height | ||
| !props.forPrint && 'fc-flex-col') }, | ||
| createElement(Scroller, { horizontal: true, hideScrollbars: true, className: joinClassNames('fc-timeline-header fc-flex-row fc-border-b', stickyHeaderDates && 'fc-table-header-sticky'), ref: this.headerScrollerRef }, | ||
| createElement("div", { | ||
| // TODO: DRY | ||
| className: joinClassNames('fc-rel', // origin for now-indicator | ||
| canvasWidth == null && 'fc-liquid'), style: { width: canvasWidth } }, | ||
| cellRows.map((cells, rowLevel) => { | ||
| const isLast = rowLevel === cellRows.length - 1; | ||
| return (createElement(TimelineHeaderRow, { key: rowLevel, dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, rowLevel: rowLevel, isLastRow: isLast, cells: cells, slotWidth: slotWidth, innerWidthRef: this.headerRowInnerWidthMap.createRef(rowLevel) })); | ||
| }), | ||
| enableNowIndicator && (createElement(TimelineNowIndicatorArrow, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth }))), | ||
| Boolean(state.endScrollbarWidth) && (createElement("div", { className: 'fc-border-s fc-filler', style: { minWidth: state.endScrollbarWidth } }))), | ||
| createElement(Scroller, { vertical: verticalScrolling, horizontal: true, hideScrollbars: props.forPrint, className: joinClassNames('fc-timeline-body fc-flex-col', verticalScrolling && 'fc-liquid'), ref: this.bodyScrollerRef, clientWidthRef: this.handleClientWidth, endScrollbarWidthRef: this.handleEndScrollbarWidth }, | ||
| createElement("div", { "aria-label": options.eventsHint, className: "fc-rel fc-grow", style: { width: canvasWidth }, ref: this.handeBodyEl }, | ||
| createElement(TimelineSlats, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, | ||
| // ref | ||
| innerWidthRef: this.handleBodySlotInnerWidth, | ||
| // dimensions | ||
| slotWidth: slotWidth }), | ||
| createElement(TimelineLane, { dateProfile: props.dateProfile, tDateProfile: tDateProfile, nowDate: nowDate, todayRange: todayRange, nextDayThreshold: options.nextDayThreshold, eventStore: props.eventStore, eventUiBases: props.eventUiBases, businessHours: props.businessHours, dateSelection: props.dateSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, eventSelection: props.eventSelection, slotWidth: slotWidth }), | ||
| enableNowIndicator && (createElement(TimelineNowIndicatorLine, { tDateProfile: tDateProfile, nowDate: nowDate, slotWidth: slotWidth })))), | ||
| stickyFooterScrollbar && (createElement(Scroller, { ref: this.footerScrollerRef, horizontal: true }, | ||
| createElement("div", { style: { width: canvasWidth } }))))); | ||
| })); | ||
| } | ||
| // Lifecycle | ||
| // ----------------------------------------------------------------------------------------------- | ||
| componentDidMount() { | ||
| this.syncedScroller = new ScrollerSyncer(true); // horizontal=true | ||
| this.updateSyncedScroller(); | ||
| this.resetScroll(); | ||
| this.context.emitter.on('_timeScrollRequest', this.handleTimeScrollRequest); | ||
| this.syncedScroller.addScrollEndListener(this.handleTimeScrollEnd); | ||
| } | ||
| componentDidUpdate(prevProps) { | ||
| this.updateSyncedScroller(); | ||
| if (prevProps.dateProfile !== this.props.dateProfile && this.context.options.scrollTimeReset) { | ||
| this.resetScroll(); | ||
| } | ||
| else { | ||
| // TODO: inefficient to update so often | ||
| this.applyTimeScroll(); | ||
| } | ||
| } | ||
| componentWillUnmount() { | ||
| this.syncedScroller.destroy(); | ||
| this.context.emitter.off('_timeScrollRequest', this.handleTimeScrollRequest); | ||
| this.syncedScroller.removeScrollEndListener(this.handleTimeScrollEnd); | ||
| } | ||
| // Scrolling | ||
| // ----------------------------------------------------------------------------------------------- | ||
| updateSyncedScroller() { | ||
| this.syncedScroller.handleChildren([ | ||
| this.headerScrollerRef.current, | ||
| this.bodyScrollerRef.current, | ||
| this.footerScrollerRef.current | ||
| ]); | ||
| } | ||
| resetScroll() { | ||
| this.handleTimeScrollRequest(this.context.options.scrollTime); | ||
| } | ||
| applyTimeScroll() { | ||
| const { props, context, tDateProfile, scrollTime, slotWidth } = this; | ||
| if (scrollTime != null && slotWidth != null) { | ||
| let x = timeToCoord(scrollTime, context.dateEnv, props.dateProfile, tDateProfile, slotWidth); | ||
| if (x) { | ||
| x += 1; // overcome border. TODO: DRY this up | ||
| } | ||
| this.syncedScroller.scrollTo({ x }); | ||
| } | ||
| } | ||
| queryHit(positionLeft, positionTop, elWidth, elHeight) { | ||
| const { props, context, tDateProfile, slotWidth } = this; | ||
| const { dateEnv } = context; | ||
| if (slotWidth) { | ||
| const x = context.isRtl ? elWidth - positionLeft : positionLeft; | ||
| const slatIndex = Math.floor(x / slotWidth); | ||
| const slatX = slatIndex * slotWidth; | ||
| const partial = (x - slatX) / slotWidth; // floating point number between 0 and 1 | ||
| const localSnapIndex = Math.floor(partial * tDateProfile.snapsPerSlot); // the snap # relative to start of slat | ||
| let startDate = dateEnv.add(tDateProfile.slotDates[slatIndex], multiplyDuration(tDateProfile.snapDuration, localSnapIndex)); | ||
| let endDate = dateEnv.add(startDate, tDateProfile.snapDuration); | ||
| // TODO: generalize this coord stuff to TimeGrid? | ||
| let snapWidth = slotWidth / tDateProfile.snapsPerSlot; | ||
| let startCoord = slatIndex * slotWidth + (snapWidth * localSnapIndex); | ||
| let endCoord = startCoord + snapWidth; | ||
| let left, right; | ||
| if (context.isRtl) { | ||
| left = elWidth - endCoord; | ||
| right = elWidth - startCoord; | ||
| } | ||
| else { | ||
| left = startCoord; | ||
| right = endCoord; | ||
| } | ||
| return { | ||
| dateProfile: props.dateProfile, | ||
| dateSpan: { | ||
| range: { start: startDate, end: endDate }, | ||
| allDay: !tDateProfile.isTimeScale, | ||
| }, | ||
| rect: { | ||
| left, | ||
| right, | ||
| top: 0, | ||
| bottom: elHeight, | ||
| }, | ||
| // HACK. TODO: This is expensive to do every hit-query | ||
| dayEl: this.bodyEl.querySelectorAll('.fc-timeline-slot')[slatIndex], | ||
| layer: 0, | ||
| }; | ||
| } | ||
| return null; | ||
| } | ||
| } | ||
| var css_248z = ".fc-timeline-slots{z-index:1}.fc-timeline-events{position:relative;z-index:2}.fc-timeline-slot-minor{border-style:dotted}.fc-timeline-events-overlap-enabled{padding-bottom:10px}.fc-timeline-event{border-radius:0;font-size:var(--fc-small-font-size);margin-bottom:1px}.fc-direction-ltr .fc-timeline-event.fc-event-end{margin-right:1px}.fc-direction-rtl .fc-timeline-event.fc-event-end{margin-left:1px}.fc-timeline-event .fc-event-inner{align-items:center;display:flex;flex-direction:row;padding:2px 1px}.fc-timeline-event-spacious .fc-event-inner{padding-bottom:5px;padding-top:5px}.fc-timeline-event .fc-event-time{font-weight:700}.fc-timeline-event .fc-event-time,.fc-timeline-event .fc-event-title{padding:0 2px}.fc-timeline-event:not(.fc-event-end) .fc-event-inner:after,.fc-timeline-event:not(.fc-event-start) .fc-event-inner:before{border-color:transparent #000;border-style:solid;border-width:5px;content:\"\";flex-grow:0;flex-shrink:0;height:0;margin:0 1px;opacity:.5;width:0}.fc-direction-ltr .fc-timeline-event:not(.fc-event-start) .fc-event-inner:before,.fc-direction-rtl .fc-timeline-event:not(.fc-event-end) .fc-event-inner:after{border-left:0}.fc-direction-ltr .fc-timeline-event:not(.fc-event-end) .fc-event-inner:after,.fc-direction-rtl .fc-timeline-event:not(.fc-event-start) .fc-event-inner:before{border-right:0}.fc-timeline-more-link{align-items:flex-start;background:var(--fc-more-link-bg-color);color:var(--fc-more-link-text-color);cursor:pointer;display:flex;flex-direction:column;font-size:var(--fc-small-font-size);padding:1px}.fc-direction-ltr .fc-timeline-more-link{margin-right:1px}.fc-direction-rtl .fc-timeline-more-link{margin-left:1px}.fc-timeline-more-link-inner{padding:2px}.fc-timeline-now-indicator-container{bottom:0;left:0;overflow:hidden;pointer-events:none;position:absolute;right:0;top:0;z-index:4}.fc-timeline-now-indicator-arrow{border-bottom-style:solid;border-bottom-width:0;border-color:var(--fc-now-indicator-color);border-left:5px solid transparent;border-right:5px solid transparent;border-top-style:solid;border-top-width:6px;height:0;margin:0 -5px;position:absolute;top:0;width:0}.fc-timeline-now-indicator-line{border-left:1px solid var(--fc-now-indicator-color);bottom:0;position:absolute;top:0}"; | ||
| injectStyles(css_248z); | ||
| export { TimelineHeaderRow, TimelineLane, TimelineLaneBg, TimelineLaneSlicer, TimelineNowIndicatorArrow, TimelineNowIndicatorLine, TimelineSlats, TimelineView, buildTimelineDateProfile, computeSlotWidth, createHorizontalStyle, createVerticalStyle, timeToCoord }; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
0
-100%228284
-3.03%3959
-3.04%1
Infinity%+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed