UNPKG

@fullcalendar/core

Version:

Provides core functionality, including the Calendar class

1,450 lines (1,431 loc) 334 kB
/*! FullCalendar Core Package v4.4.0 Docs & License: https://fullcalendar.io/ (c) 2019 Adam Shaw */ // Creating // ---------------------------------------------------------------------------------------------------------------- var elementPropHash = { className: true, colSpan: true, rowSpan: true }; var containerTagHash = { '<tr': 'tbody', '<td': 'tr' }; function createElement(tagName, attrs, content) { var el = document.createElement(tagName); if (attrs) { for (var attrName in attrs) { if (attrName === 'style') { applyStyle(el, attrs[attrName]); } else if (elementPropHash[attrName]) { el[attrName] = attrs[attrName]; } else { el.setAttribute(attrName, attrs[attrName]); } } } if (typeof content === 'string') { el.innerHTML = content; // shortcut. no need to process HTML in any way } else if (content != null) { appendToElement(el, content); } return el; } function htmlToElement(html) { html = html.trim(); var container = document.createElement(computeContainerTag(html)); container.innerHTML = html; return container.firstChild; } function htmlToElements(html) { return Array.prototype.slice.call(htmlToNodeList(html)); } function htmlToNodeList(html) { html = html.trim(); var container = document.createElement(computeContainerTag(html)); container.innerHTML = html; return container.childNodes; } // assumes html already trimmed and tag names are lowercase function computeContainerTag(html) { return containerTagHash[html.substr(0, 3) // faster than using regex ] || 'div'; } function appendToElement(el, content) { var childNodes = normalizeContent(content); for (var i = 0; i < childNodes.length; i++) { el.appendChild(childNodes[i]); } } function prependToElement(parent, content) { var newEls = normalizeContent(content); var afterEl = parent.firstChild || null; // if no firstChild, will append to end, but that's okay, b/c there were no children for (var i = 0; i < newEls.length; i++) { parent.insertBefore(newEls[i], afterEl); } } function insertAfterElement(refEl, content) { var newEls = normalizeContent(content); var afterEl = refEl.nextSibling || null; for (var i = 0; i < newEls.length; i++) { refEl.parentNode.insertBefore(newEls[i], afterEl); } } function normalizeContent(content) { var els; if (typeof content === 'string') { els = htmlToElements(content); } else if (content instanceof Node) { els = [content]; } else { // Node[] or NodeList els = Array.prototype.slice.call(content); } return els; } function removeElement(el) { if (el.parentNode) { el.parentNode.removeChild(el); } } // Querying // ---------------------------------------------------------------------------------------------------------------- // from https://developer.mozilla.org/en-US/docs/Web/API/Element/closest var matchesMethod = Element.prototype.matches || Element.prototype.matchesSelector || Element.prototype.msMatchesSelector; var closestMethod = Element.prototype.closest || function (selector) { // polyfill var el = this; if (!document.documentElement.contains(el)) { return null; } do { if (elementMatches(el, selector)) { return el; } el = el.parentElement || el.parentNode; } while (el !== null && el.nodeType === 1); return null; }; function elementClosest(el, selector) { return closestMethod.call(el, selector); } function elementMatches(el, selector) { return matchesMethod.call(el, selector); } // accepts multiple subject els // returns a real array. good for methods like forEach function findElements(container, selector) { var containers = container instanceof HTMLElement ? [container] : container; var allMatches = []; for (var i = 0; i < containers.length; i++) { var matches = containers[i].querySelectorAll(selector); for (var j = 0; j < matches.length; j++) { allMatches.push(matches[j]); } } return allMatches; } // accepts multiple subject els // only queries direct child elements function findChildren(parent, selector) { var parents = parent instanceof HTMLElement ? [parent] : parent; var allMatches = []; for (var i = 0; i < parents.length; i++) { var childNodes = parents[i].children; // only ever elements for (var j = 0; j < childNodes.length; j++) { var childNode = childNodes[j]; if (!selector || elementMatches(childNode, selector)) { allMatches.push(childNode); } } } return allMatches; } // Attributes // ---------------------------------------------------------------------------------------------------------------- function forceClassName(el, className, bool) { if (bool) { el.classList.add(className); } else { el.classList.remove(className); } } // Style // ---------------------------------------------------------------------------------------------------------------- var PIXEL_PROP_RE = /(top|left|right|bottom|width|height)$/i; function applyStyle(el, props) { for (var propName in props) { applyStyleProp(el, propName, props[propName]); } } function applyStyleProp(el, name, val) { if (val == null) { el.style[name] = ''; } else if (typeof val === 'number' && PIXEL_PROP_RE.test(name)) { el.style[name] = val + 'px'; } else { el.style[name] = val; } } function pointInsideRect(point, rect) { return point.left >= rect.left && point.left < rect.right && point.top >= rect.top && point.top < rect.bottom; } // Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false function intersectRects(rect1, rect2) { var res = { left: Math.max(rect1.left, rect2.left), right: Math.min(rect1.right, rect2.right), top: Math.max(rect1.top, rect2.top), bottom: Math.min(rect1.bottom, rect2.bottom) }; if (res.left < res.right && res.top < res.bottom) { return res; } return false; } function translateRect(rect, deltaX, deltaY) { return { left: rect.left + deltaX, right: rect.right + deltaX, top: rect.top + deltaY, bottom: rect.bottom + deltaY }; } // Returns a new point that will have been moved to reside within the given rectangle function constrainPoint(point, rect) { return { left: Math.min(Math.max(point.left, rect.left), rect.right), top: Math.min(Math.max(point.top, rect.top), rect.bottom) }; } // Returns a point that is the center of the given rectangle function getRectCenter(rect) { return { left: (rect.left + rect.right) / 2, top: (rect.top + rect.bottom) / 2 }; } // Subtracts point2's coordinates from point1's coordinates, returning a delta function diffPoints(point1, point2) { return { left: point1.left - point2.left, top: point1.top - point2.top }; } // Logic for determining if, when the element is right-to-left, the scrollbar appears on the left side var isRtlScrollbarOnLeft = null; function getIsRtlScrollbarOnLeft() { if (isRtlScrollbarOnLeft === null) { isRtlScrollbarOnLeft = computeIsRtlScrollbarOnLeft(); } return isRtlScrollbarOnLeft; } function computeIsRtlScrollbarOnLeft() { var outerEl = createElement('div', { style: { position: 'absolute', top: -1000, left: 0, border: 0, padding: 0, overflow: 'scroll', direction: 'rtl' } }, '<div></div>'); document.body.appendChild(outerEl); var innerEl = outerEl.firstChild; var res = innerEl.getBoundingClientRect().left > outerEl.getBoundingClientRect().left; removeElement(outerEl); return res; } // The scrollbar width computations in computeEdges are sometimes flawed when it comes to // retina displays, rounding, and IE11. Massage them into a usable value. function sanitizeScrollbarWidth(width) { width = Math.max(0, width); // no negatives width = Math.round(width); return width; } function computeEdges(el, getPadding) { if (getPadding === void 0) { getPadding = false; } var computedStyle = window.getComputedStyle(el); var borderLeft = parseInt(computedStyle.borderLeftWidth, 10) || 0; var borderRight = parseInt(computedStyle.borderRightWidth, 10) || 0; var borderTop = parseInt(computedStyle.borderTopWidth, 10) || 0; var borderBottom = parseInt(computedStyle.borderBottomWidth, 10) || 0; // must use offset(Width|Height) because compatible with client(Width|Height) var scrollbarLeftRight = sanitizeScrollbarWidth(el.offsetWidth - el.clientWidth - borderLeft - borderRight); var scrollbarBottom = sanitizeScrollbarWidth(el.offsetHeight - el.clientHeight - borderTop - borderBottom); var res = { borderLeft: borderLeft, borderRight: borderRight, borderTop: borderTop, borderBottom: borderBottom, scrollbarBottom: scrollbarBottom, scrollbarLeft: 0, scrollbarRight: 0 }; if (getIsRtlScrollbarOnLeft() && computedStyle.direction === 'rtl') { // is the scrollbar on the left side? res.scrollbarLeft = scrollbarLeftRight; } else { res.scrollbarRight = scrollbarLeftRight; } if (getPadding) { res.paddingLeft = parseInt(computedStyle.paddingLeft, 10) || 0; res.paddingRight = parseInt(computedStyle.paddingRight, 10) || 0; res.paddingTop = parseInt(computedStyle.paddingTop, 10) || 0; res.paddingBottom = parseInt(computedStyle.paddingBottom, 10) || 0; } return res; } function computeInnerRect(el, goWithinPadding) { if (goWithinPadding === void 0) { goWithinPadding = false; } var outerRect = computeRect(el); var edges = computeEdges(el, goWithinPadding); var res = { left: outerRect.left + edges.borderLeft + edges.scrollbarLeft, right: outerRect.right - edges.borderRight - edges.scrollbarRight, top: outerRect.top + edges.borderTop, bottom: outerRect.bottom - edges.borderBottom - edges.scrollbarBottom }; if (goWithinPadding) { res.left += edges.paddingLeft; res.right -= edges.paddingRight; res.top += edges.paddingTop; res.bottom -= edges.paddingBottom; } return res; } function computeRect(el) { var rect = el.getBoundingClientRect(); return { left: rect.left + window.pageXOffset, top: rect.top + window.pageYOffset, right: rect.right + window.pageXOffset, bottom: rect.bottom + window.pageYOffset }; } function computeViewportRect() { return { left: window.pageXOffset, right: window.pageXOffset + document.documentElement.clientWidth, top: window.pageYOffset, bottom: window.pageYOffset + document.documentElement.clientHeight }; } function computeHeightAndMargins(el) { return el.getBoundingClientRect().height + computeVMargins(el); } function computeVMargins(el) { var computed = window.getComputedStyle(el); return parseInt(computed.marginTop, 10) + parseInt(computed.marginBottom, 10); } // does not return window function getClippingParents(el) { var parents = []; while (el instanceof HTMLElement) { // will stop when gets to document or null var computedStyle = window.getComputedStyle(el); if (computedStyle.position === 'fixed') { break; } if ((/(auto|scroll)/).test(computedStyle.overflow + computedStyle.overflowY + computedStyle.overflowX)) { parents.push(el); } el = el.parentNode; } return parents; } function computeClippingRect(el) { return getClippingParents(el) .map(function (el) { return computeInnerRect(el); }) .concat(computeViewportRect()) .reduce(function (rect0, rect1) { return intersectRects(rect0, rect1) || rect1; // should always intersect }); } // Stops a mouse/touch event from doing it's native browser action function preventDefault(ev) { ev.preventDefault(); } // Event Delegation // ---------------------------------------------------------------------------------------------------------------- function listenBySelector(container, eventType, selector, handler) { function realHandler(ev) { var matchedChild = elementClosest(ev.target, selector); if (matchedChild) { handler.call(matchedChild, ev, matchedChild); } } container.addEventListener(eventType, realHandler); return function () { container.removeEventListener(eventType, realHandler); }; } function listenToHoverBySelector(container, selector, onMouseEnter, onMouseLeave) { var currentMatchedChild; return listenBySelector(container, 'mouseover', selector, function (ev, matchedChild) { if (matchedChild !== currentMatchedChild) { currentMatchedChild = matchedChild; onMouseEnter(ev, matchedChild); var realOnMouseLeave_1 = function (ev) { currentMatchedChild = null; onMouseLeave(ev, matchedChild); matchedChild.removeEventListener('mouseleave', realOnMouseLeave_1); }; // listen to the next mouseleave, and then unattach matchedChild.addEventListener('mouseleave', realOnMouseLeave_1); } }); } // Animation // ---------------------------------------------------------------------------------------------------------------- var transitionEventNames = [ 'webkitTransitionEnd', 'otransitionend', 'oTransitionEnd', 'msTransitionEnd', 'transitionend' ]; // triggered only when the next single subsequent transition finishes function whenTransitionDone(el, callback) { var realCallback = function (ev) { callback(ev); transitionEventNames.forEach(function (eventName) { el.removeEventListener(eventName, realCallback); }); }; transitionEventNames.forEach(function (eventName) { el.addEventListener(eventName, realCallback); // cross-browser way to determine when the transition finishes }); } var DAY_IDS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']; // Adding function addWeeks(m, n) { var a = dateToUtcArray(m); a[2] += n * 7; return arrayToUtcDate(a); } function addDays(m, n) { var a = dateToUtcArray(m); a[2] += n; return arrayToUtcDate(a); } function addMs(m, n) { var a = dateToUtcArray(m); a[6] += n; return arrayToUtcDate(a); } // Diffing (all return floats) function diffWeeks(m0, m1) { return diffDays(m0, m1) / 7; } function diffDays(m0, m1) { return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60 * 24); } function diffHours(m0, m1) { return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60); } function diffMinutes(m0, m1) { return (m1.valueOf() - m0.valueOf()) / (1000 * 60); } function diffSeconds(m0, m1) { return (m1.valueOf() - m0.valueOf()) / 1000; } function diffDayAndTime(m0, m1) { var m0day = startOfDay(m0); var m1day = startOfDay(m1); return { years: 0, months: 0, days: Math.round(diffDays(m0day, m1day)), milliseconds: (m1.valueOf() - m1day.valueOf()) - (m0.valueOf() - m0day.valueOf()) }; } // Diffing Whole Units function diffWholeWeeks(m0, m1) { var d = diffWholeDays(m0, m1); if (d !== null && d % 7 === 0) { return d / 7; } return null; } function diffWholeDays(m0, m1) { if (timeAsMs(m0) === timeAsMs(m1)) { return Math.round(diffDays(m0, m1)); } return null; } // Start-Of function startOfDay(m) { return arrayToUtcDate([ m.getUTCFullYear(), m.getUTCMonth(), m.getUTCDate() ]); } function startOfHour(m) { return arrayToUtcDate([ m.getUTCFullYear(), m.getUTCMonth(), m.getUTCDate(), m.getUTCHours() ]); } function startOfMinute(m) { return arrayToUtcDate([ m.getUTCFullYear(), m.getUTCMonth(), m.getUTCDate(), m.getUTCHours(), m.getUTCMinutes() ]); } function startOfSecond(m) { return arrayToUtcDate([ m.getUTCFullYear(), m.getUTCMonth(), m.getUTCDate(), m.getUTCHours(), m.getUTCMinutes(), m.getUTCSeconds() ]); } // Week Computation function weekOfYear(marker, dow, doy) { var y = marker.getUTCFullYear(); var w = weekOfGivenYear(marker, y, dow, doy); if (w < 1) { return weekOfGivenYear(marker, y - 1, dow, doy); } var nextW = weekOfGivenYear(marker, y + 1, dow, doy); if (nextW >= 1) { return Math.min(w, nextW); } return w; } function weekOfGivenYear(marker, year, dow, doy) { var firstWeekStart = arrayToUtcDate([year, 0, 1 + firstWeekOffset(year, dow, doy)]); var dayStart = startOfDay(marker); var days = Math.round(diffDays(firstWeekStart, dayStart)); return Math.floor(days / 7) + 1; // zero-indexed } // start-of-first-week - start-of-year function firstWeekOffset(year, dow, doy) { // first-week day -- which january is always in the first week (4 for iso, 1 for other) var fwd = 7 + dow - doy; // first-week day local weekday -- which local weekday is fwd var fwdlw = (7 + arrayToUtcDate([year, 0, fwd]).getUTCDay() - dow) % 7; return -fwdlw + fwd - 1; } // Array Conversion function dateToLocalArray(date) { return [ date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds() ]; } function arrayToLocalDate(a) { return new Date(a[0], a[1] || 0, a[2] == null ? 1 : a[2], // day of month a[3] || 0, a[4] || 0, a[5] || 0); } function dateToUtcArray(date) { return [ date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds() ]; } function arrayToUtcDate(a) { // according to web standards (and Safari), a month index is required. // massage if only given a year. if (a.length === 1) { a = a.concat([0]); } return new Date(Date.UTC.apply(Date, a)); } // Other Utils function isValidDate(m) { return !isNaN(m.valueOf()); } function timeAsMs(m) { return m.getUTCHours() * 1000 * 60 * 60 + m.getUTCMinutes() * 1000 * 60 + m.getUTCSeconds() * 1000 + m.getUTCMilliseconds(); } var INTERNAL_UNITS = ['years', 'months', 'days', 'milliseconds']; var PARSE_RE = /^(-?)(?:(\d+)\.)?(\d+):(\d\d)(?::(\d\d)(?:\.(\d\d\d))?)?/; // Parsing and Creation function createDuration(input, unit) { var _a; if (typeof input === 'string') { return parseString(input); } else if (typeof input === 'object' && input) { // non-null object return normalizeObject(input); } else if (typeof input === 'number') { return normalizeObject((_a = {}, _a[unit || 'milliseconds'] = input, _a)); } else { return null; } } function parseString(s) { var m = PARSE_RE.exec(s); if (m) { var sign = m[1] ? -1 : 1; return { years: 0, months: 0, days: sign * (m[2] ? parseInt(m[2], 10) : 0), milliseconds: sign * ((m[3] ? parseInt(m[3], 10) : 0) * 60 * 60 * 1000 + // hours (m[4] ? parseInt(m[4], 10) : 0) * 60 * 1000 + // minutes (m[5] ? parseInt(m[5], 10) : 0) * 1000 + // seconds (m[6] ? parseInt(m[6], 10) : 0) // ms ) }; } return null; } function normalizeObject(obj) { return { years: obj.years || obj.year || 0, months: obj.months || obj.month || 0, days: (obj.days || obj.day || 0) + getWeeksFromInput(obj) * 7, milliseconds: (obj.hours || obj.hour || 0) * 60 * 60 * 1000 + // hours (obj.minutes || obj.minute || 0) * 60 * 1000 + // minutes (obj.seconds || obj.second || 0) * 1000 + // seconds (obj.milliseconds || obj.millisecond || obj.ms || 0) // ms }; } function getWeeksFromInput(obj) { return obj.weeks || obj.week || 0; } // Equality function durationsEqual(d0, d1) { return d0.years === d1.years && d0.months === d1.months && d0.days === d1.days && d0.milliseconds === d1.milliseconds; } function isSingleDay(dur) { return dur.years === 0 && dur.months === 0 && dur.days === 1 && dur.milliseconds === 0; } // Simple Math function addDurations(d0, d1) { return { years: d0.years + d1.years, months: d0.months + d1.months, days: d0.days + d1.days, milliseconds: d0.milliseconds + d1.milliseconds }; } function subtractDurations(d1, d0) { return { years: d1.years - d0.years, months: d1.months - d0.months, days: d1.days - d0.days, milliseconds: d1.milliseconds - d0.milliseconds }; } function multiplyDuration(d, n) { return { years: d.years * n, months: d.months * n, days: d.days * n, milliseconds: d.milliseconds * n }; } // Conversions // "Rough" because they are based on average-case Gregorian months/years function asRoughYears(dur) { return asRoughDays(dur) / 365; } function asRoughMonths(dur) { return asRoughDays(dur) / 30; } function asRoughDays(dur) { return asRoughMs(dur) / 864e5; } function asRoughMinutes(dur) { return asRoughMs(dur) / (1000 * 60); } function asRoughSeconds(dur) { return asRoughMs(dur) / 1000; } function asRoughMs(dur) { return dur.years * (365 * 864e5) + dur.months * (30 * 864e5) + dur.days * 864e5 + dur.milliseconds; } // Advanced Math function wholeDivideDurations(numerator, denominator) { var res = null; for (var i = 0; i < INTERNAL_UNITS.length; i++) { var unit = INTERNAL_UNITS[i]; if (denominator[unit]) { var localRes = numerator[unit] / denominator[unit]; if (!isInt(localRes) || (res !== null && res !== localRes)) { return null; } res = localRes; } else if (numerator[unit]) { // needs to divide by something but can't! return null; } } return res; } function greatestDurationDenominator(dur, dontReturnWeeks) { var ms = dur.milliseconds; if (ms) { if (ms % 1000 !== 0) { return { unit: 'millisecond', value: ms }; } if (ms % (1000 * 60) !== 0) { return { unit: 'second', value: ms / 1000 }; } if (ms % (1000 * 60 * 60) !== 0) { return { unit: 'minute', value: ms / (1000 * 60) }; } if (ms) { return { unit: 'hour', value: ms / (1000 * 60 * 60) }; } } if (dur.days) { if (!dontReturnWeeks && dur.days % 7 === 0) { return { unit: 'week', value: dur.days / 7 }; } return { unit: 'day', value: dur.days }; } if (dur.months) { return { unit: 'month', value: dur.months }; } if (dur.years) { return { unit: 'year', value: dur.years }; } return { unit: 'millisecond', value: 0 }; } /* FullCalendar-specific DOM Utilities ----------------------------------------------------------------------------------------------------------------------*/ // Given the scrollbar widths of some other container, create borders/margins on rowEls in order to match the left // and right space that was offset by the scrollbars. A 1-pixel border first, then margin beyond that. function compensateScroll(rowEl, scrollbarWidths) { if (scrollbarWidths.left) { applyStyle(rowEl, { borderLeftWidth: 1, marginLeft: scrollbarWidths.left - 1 }); } if (scrollbarWidths.right) { applyStyle(rowEl, { borderRightWidth: 1, marginRight: scrollbarWidths.right - 1 }); } } // Undoes compensateScroll and restores all borders/margins function uncompensateScroll(rowEl) { applyStyle(rowEl, { marginLeft: '', marginRight: '', borderLeftWidth: '', borderRightWidth: '' }); } // Make the mouse cursor express that an event is not allowed in the current area function disableCursor() { document.body.classList.add('fc-not-allowed'); } // Returns the mouse cursor to its original look function enableCursor() { document.body.classList.remove('fc-not-allowed'); } // Given a total available height to fill, have `els` (essentially child rows) expand to accomodate. // By default, all elements that are shorter than the recommended height are expanded uniformly, not considering // any other els that are already too tall. if `shouldRedistribute` is on, it considers these tall rows and // reduces the available height. function distributeHeight(els, availableHeight, shouldRedistribute) { // *FLOORING NOTE*: we floor in certain places because zoom can give inaccurate floating-point dimensions, // and it is better to be shorter than taller, to avoid creating unnecessary scrollbars. var minOffset1 = Math.floor(availableHeight / els.length); // for non-last element var minOffset2 = Math.floor(availableHeight - minOffset1 * (els.length - 1)); // for last element *FLOORING NOTE* var flexEls = []; // elements that are allowed to expand. array of DOM nodes var flexOffsets = []; // amount of vertical space it takes up var flexHeights = []; // actual css height var usedHeight = 0; undistributeHeight(els); // give all elements their natural height // find elements that are below the recommended height (expandable). // important to query for heights in a single first pass (to avoid reflow oscillation). els.forEach(function (el, i) { var minOffset = i === els.length - 1 ? minOffset2 : minOffset1; var naturalHeight = el.getBoundingClientRect().height; var naturalOffset = naturalHeight + computeVMargins(el); if (naturalOffset < minOffset) { flexEls.push(el); flexOffsets.push(naturalOffset); flexHeights.push(naturalHeight); } else { // this element stretches past recommended height (non-expandable). mark the space as occupied. usedHeight += naturalOffset; } }); // readjust the recommended height to only consider the height available to non-maxed-out rows. if (shouldRedistribute) { availableHeight -= usedHeight; minOffset1 = Math.floor(availableHeight / flexEls.length); minOffset2 = Math.floor(availableHeight - minOffset1 * (flexEls.length - 1)); // *FLOORING NOTE* } // assign heights to all expandable elements flexEls.forEach(function (el, i) { var minOffset = i === flexEls.length - 1 ? minOffset2 : minOffset1; var naturalOffset = flexOffsets[i]; var naturalHeight = flexHeights[i]; var newHeight = minOffset - (naturalOffset - naturalHeight); // subtract the margin/padding if (naturalOffset < minOffset) { // we check this again because redistribution might have changed things el.style.height = newHeight + 'px'; } }); } // Undoes distrubuteHeight, restoring all els to their natural height function undistributeHeight(els) { els.forEach(function (el) { el.style.height = ''; }); } // Given `els`, a set of <td> cells, find the cell with the largest natural width and set the widths of all the // cells to be that width. // PREREQUISITE: if you want a cell to take up width, it needs to have a single inner element w/ display:inline function matchCellWidths(els) { var maxInnerWidth = 0; els.forEach(function (el) { var innerEl = el.firstChild; // hopefully an element if (innerEl instanceof HTMLElement) { var innerWidth_1 = innerEl.getBoundingClientRect().width; if (innerWidth_1 > maxInnerWidth) { maxInnerWidth = innerWidth_1; } } }); maxInnerWidth++; // sometimes not accurate of width the text needs to stay on one line. insurance els.forEach(function (el) { el.style.width = maxInnerWidth + 'px'; }); return maxInnerWidth; } // Given one element that resides inside another, // Subtracts the height of the inner element from the outer element. function subtractInnerElHeight(outerEl, innerEl) { // effin' IE8/9/10/11 sometimes returns 0 for dimensions. this weird hack was the only thing that worked var reflowStyleProps = { position: 'relative', left: -1 // ensure reflow in case the el was already relative. negative is less likely to cause new scroll }; applyStyle(outerEl, reflowStyleProps); applyStyle(innerEl, reflowStyleProps); var diff = // grab the dimensions outerEl.getBoundingClientRect().height - innerEl.getBoundingClientRect().height; // undo hack var resetStyleProps = { position: '', left: '' }; applyStyle(outerEl, resetStyleProps); applyStyle(innerEl, resetStyleProps); return diff; } /* Selection ----------------------------------------------------------------------------------------------------------------------*/ function preventSelection(el) { el.classList.add('fc-unselectable'); el.addEventListener('selectstart', preventDefault); } function allowSelection(el) { el.classList.remove('fc-unselectable'); el.removeEventListener('selectstart', preventDefault); } /* Context Menu ----------------------------------------------------------------------------------------------------------------------*/ function preventContextMenu(el) { el.addEventListener('contextmenu', preventDefault); } function allowContextMenu(el) { el.removeEventListener('contextmenu', preventDefault); } /* Object Ordering by Field ----------------------------------------------------------------------------------------------------------------------*/ function parseFieldSpecs(input) { var specs = []; var tokens = []; var i; var token; if (typeof input === 'string') { tokens = input.split(/\s*,\s*/); } else if (typeof input === 'function') { tokens = [input]; } else if (Array.isArray(input)) { tokens = input; } for (i = 0; i < tokens.length; i++) { token = tokens[i]; if (typeof token === 'string') { specs.push(token.charAt(0) === '-' ? { field: token.substring(1), order: -1 } : { field: token, order: 1 }); } else if (typeof token === 'function') { specs.push({ func: token }); } } return specs; } function compareByFieldSpecs(obj0, obj1, fieldSpecs) { var i; var cmp; for (i = 0; i < fieldSpecs.length; i++) { cmp = compareByFieldSpec(obj0, obj1, fieldSpecs[i]); if (cmp) { return cmp; } } return 0; } function compareByFieldSpec(obj0, obj1, fieldSpec) { if (fieldSpec.func) { return fieldSpec.func(obj0, obj1); } return flexibleCompare(obj0[fieldSpec.field], obj1[fieldSpec.field]) * (fieldSpec.order || 1); } function flexibleCompare(a, b) { if (!a && !b) { return 0; } if (b == null) { return -1; } if (a == null) { return 1; } if (typeof a === 'string' || typeof b === 'string') { return String(a).localeCompare(String(b)); } return a - b; } /* String Utilities ----------------------------------------------------------------------------------------------------------------------*/ function capitaliseFirstLetter(str) { return str.charAt(0).toUpperCase() + str.slice(1); } function padStart(val, len) { var s = String(val); return '000'.substr(0, len - s.length) + s; } /* Number Utilities ----------------------------------------------------------------------------------------------------------------------*/ function compareNumbers(a, b) { return a - b; } function isInt(n) { return n % 1 === 0; } /* Weird Utilities ----------------------------------------------------------------------------------------------------------------------*/ function applyAll(functions, thisObj, args) { if (typeof functions === 'function') { // supplied a single function functions = [functions]; } if (functions) { var i = void 0; var ret = void 0; for (i = 0; i < functions.length; i++) { ret = functions[i].apply(thisObj, args) || ret; } return ret; } } function firstDefined() { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } for (var i = 0; i < args.length; i++) { if (args[i] !== undefined) { return args[i]; } } } // Returns a function, that, as long as it continues to be invoked, will not // be triggered. The function will be called after it stops being called for // N milliseconds. If `immediate` is passed, trigger the function on the // leading edge, instead of the trailing. // https://github.com/jashkenas/underscore/blob/1.6.0/underscore.js#L714 function debounce(func, wait) { var timeout; var args; var context; var timestamp; var result; var later = function () { var last = new Date().valueOf() - timestamp; if (last < wait) { timeout = setTimeout(later, wait - last); } else { timeout = null; result = func.apply(context, args); context = args = null; } }; return function () { context = this; args = arguments; timestamp = new Date().valueOf(); if (!timeout) { timeout = setTimeout(later, wait); } return result; }; } // Number and Boolean are only types that defaults or not computed for // TODO: write more comments function refineProps(rawProps, processors, defaults, leftoverProps) { if (defaults === void 0) { defaults = {}; } var refined = {}; for (var key in processors) { var processor = processors[key]; if (rawProps[key] !== undefined) { // found if (processor === Function) { refined[key] = typeof rawProps[key] === 'function' ? rawProps[key] : null; } else if (processor) { // a refining function? refined[key] = processor(rawProps[key]); } else { refined[key] = rawProps[key]; } } else if (defaults[key] !== undefined) { // there's an explicit default refined[key] = defaults[key]; } else { // must compute a default if (processor === String) { refined[key] = ''; // empty string is default for String } else if (!processor || processor === Number || processor === Boolean || processor === Function) { refined[key] = null; // assign null for other non-custom processor funcs } else { refined[key] = processor(null); // run the custom processor func } } } if (leftoverProps) { for (var key in rawProps) { if (processors[key] === undefined) { leftoverProps[key] = rawProps[key]; } } } return refined; } /* Date stuff that doesn't belong in datelib core ----------------------------------------------------------------------------------------------------------------------*/ // given a timed range, computes an all-day range that has the same exact duration, // but whose start time is aligned with the start of the day. function computeAlignedDayRange(timedRange) { var dayCnt = Math.floor(diffDays(timedRange.start, timedRange.end)) || 1; var start = startOfDay(timedRange.start); var end = addDays(start, dayCnt); return { start: start, end: end }; } // given a timed range, computes an all-day range based on how for the end date bleeds into the next day // TODO: give nextDayThreshold a default arg function computeVisibleDayRange(timedRange, nextDayThreshold) { if (nextDayThreshold === void 0) { nextDayThreshold = createDuration(0); } var startDay = null; var endDay = null; if (timedRange.end) { endDay = startOfDay(timedRange.end); var endTimeMS = timedRange.end.valueOf() - endDay.valueOf(); // # of milliseconds into `endDay` // If the end time is actually inclusively part of the next day and is equal to or // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`. // Otherwise, leaving it as inclusive will cause it to exclude `endDay`. if (endTimeMS && endTimeMS >= asRoughMs(nextDayThreshold)) { endDay = addDays(endDay, 1); } } if (timedRange.start) { startDay = startOfDay(timedRange.start); // the beginning of the day the range starts // If end is within `startDay` but not past nextDayThreshold, assign the default duration of one day. if (endDay && endDay <= startDay) { endDay = addDays(startDay, 1); } } return { start: startDay, end: endDay }; } // spans from one day into another? function isMultiDayRange(range) { var visibleRange = computeVisibleDayRange(range); return diffDays(visibleRange.start, visibleRange.end) > 1; } function diffDates(date0, date1, dateEnv, largeUnit) { if (largeUnit === 'year') { return createDuration(dateEnv.diffWholeYears(date0, date1), 'year'); } else if (largeUnit === 'month') { return createDuration(dateEnv.diffWholeMonths(date0, date1), 'month'); } else { return diffDayAndTime(date0, date1); // returns a duration } } /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ /* global Reflect, Promise */ var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function parseRecurring(eventInput, allDayDefault, dateEnv, recurringTypes, leftovers) { for (var i = 0; i < recurringTypes.length; i++) { var localLeftovers = {}; var parsed = recurringTypes[i].parse(eventInput, localLeftovers, dateEnv); if (parsed) { var allDay = localLeftovers.allDay; delete localLeftovers.allDay; // remove from leftovers if (allDay == null) { allDay = allDayDefault; if (allDay == null) { allDay = parsed.allDayGuess; if (allDay == null) { allDay = false; } } } __assign(leftovers, localLeftovers); return { allDay: allDay, duration: parsed.duration, typeData: parsed.typeData, typeId: i }; } } return null; } /* Event MUST have a recurringDef */ function expandRecurringRanges(eventDef, duration, framingRange, dateEnv, recurringTypes) { var typeDef = recurringTypes[eventDef.recurringDef.typeId]; var markers = typeDef.expand(eventDef.recurringDef.typeData, { start: dateEnv.subtract(framingRange.start, duration), end: framingRange.end }, dateEnv); // the recurrence plugins don't guarantee that all-day events are start-of-day, so we have to if (eventDef.allDay) { markers = markers.map(startOfDay); } return markers; } var hasOwnProperty = Object.prototype.hasOwnProperty; // Merges an array of objects into a single object. // The second argument allows for an array of property names who's object values will be merged together. function mergeProps(propObjs, complexProps) { var dest = {}; var i; var name; var complexObjs; var j; var val; var props; if (complexProps) { for (i = 0; i < complexProps.length; i++) { name = complexProps[i]; complexObjs = []; // collect the trailing object values, stopping when a non-object is discovered for (j = propObjs.length - 1; j >= 0; j--) { val = propObjs[j][name]; if (typeof val === 'object' && val) { // non-null object complexObjs.unshift(val); } else if (val !== undefined) { dest[name] = val; // if there were no objects, this value will be used break; } } // if the trailing values were objects, use the merged value if (complexObjs.length) { dest[name] = mergeProps(complexObjs); } } } // copy values into the destination, going from last to first for (i = propObjs.length - 1; i >= 0; i--) { props = propObjs[i]; for (name in props) { if (!(name in dest)) { // if already assigned by previous props or complex props, don't reassign dest[name] = props[name]; } } } return dest; } function filterHash(hash, func) { var filtered = {}; for (var key in hash) { if (func(hash[key], key)) { filtered[key] = hash[key]; } } return filtered; } function mapHash(hash, func) { var newHash = {}; for (var key in hash) { newHash[key] = func(hash[key], key); } return newHash; } function arrayToHash(a) { var hash = {}; for (var _i = 0, a_1 = a; _i < a_1.length; _i++) { var item = a_1[_i]; hash[item] = true; } return hash; } function hashValuesToArray(obj) { var a = []; for (var key in obj) { a.push(obj[key]); } return a; } function isPropsEqual(obj0, obj1) { for (var key in obj0) { if (hasOwnProperty.call(obj0, key)) { if (!(key in obj1)) { return false; } } } for (var key in obj1) { if (hasOwnProperty.call(obj1, key)) { if (obj0[key] !== obj1[key]) { return false; } } } return true; } function parseEvents(rawEvents, sourceId, calendar, allowOpenRange) { var eventStore = createEmptyEventStore(); for (var _i = 0, rawEvents_1 = rawEvents; _i < rawEvents_1.length; _i++) { var rawEvent = rawEvents_1[_i]; var tuple = parseEvent(rawEvent, sourceId, calendar, allowOpenRange); if (tuple) { eventTupleToStore(tuple, eventStore); } } return eventStore; } function eventTupleToStore(tuple, eventStore) { if (eventStore === void 0) { eventStore = createEmptyEventStore(); } eventStore.defs[tuple.def.defId] = tuple.def; if (tuple.instance) { eventStore.instances[tuple.instance.instanceId] = tuple.instance; } return eventStore; } function expandRecurring(eventStore, framingRange, calendar) { var dateEnv = calendar.dateEnv; var defs = eventStore.defs, instances = eventStore.instances; // remove existing recurring instances instances = filterHash(instances, function (instance) { return !defs[instance.defId].recurringDef; }); for (var defId in defs) { var def = defs[defId]; if (def.recurringDef) { var duration = def.recurringDef.duration; if (!duration) { duration = def.allDay ? calendar.defaultAllDayEventDuration : calendar.defaultTimedEventDuration; } var starts = expandRecurringRanges(def, duration, framingRange, calendar.dateEnv, calendar.pluginSystem.hooks.recurringTypes); for (var _i = 0, starts_1 = starts; _i < starts_1.length; _i++) { var start = starts_1[_i]; var instance = createEventInstance(defId, { start: start, end: dateEnv.add(start, duration) }); instances[instance.instanceId] = instance; } } } return { defs: defs, instances: instances }; } // retrieves events that have the same groupId as the instance specified by `instanceId` // or they are the same as the instance. // why might instanceId not be in the store? an event from another calendar? function getRelevantEvents(eventStore, instanceId) { var instance = eventStore.instances[instanceId]; if (instance) { var def_1 = eventStore.defs[instance.defId]; // get events/instances with same group var newStore = filterEventStoreDefs(eventStore, function (lookDef) { return isEventDefsGrouped(def_1, lookDef); }); // add the original // TODO: wish we could use eventTupleToStore or something like it newStore.defs[def_1.defId] = def_1; newStore.instances[instance.instanceId] = instance; return newStore; } return createEmptyEventStore(); } function isEventDefsGrouped(def0, def1) { return Boolean(def0.groupId && def0.groupId === def1.groupId); } function transformRawEvents(rawEvents, eventSource, calendar) { var calEachTransform = calendar.opt('eventDataTransform'); var sourceEachTransform = eventSource ? eventSource.eventDataTransform : null; if (sourceEachTransform) { rawEvents = transformEachRawEvent(rawEvents, sourceEachTransform); } if (calEachTransform) { rawEvents = transformEachRawEvent(rawEvents, calEachTransform); } return rawEvents; } function transformEachRawEvent(rawEvents, func) { var refinedEvents; if (!func) { refinedEvents = rawEvents; } else { refinedEvents = []; for (var _i = 0, rawEvents_2 = rawEvents; _i < rawEvents_2.length; _i++) { var rawEvent = rawEvents_2[_i]; var refinedEvent = func(rawEvent); if (refinedEvent) { refinedEvents.push(refinedEvent); } else if (refinedEvent == null) { refinedEvents.push(rawEvent); } // if a different falsy value, do nothing } } return refinedEvents; } function createEmptyEventStore() { return { defs: {}, instances: {} }; } function mergeEventStores(store0, store1) { return { defs: __assign({}, store0.defs, store1.defs), instances: __assign({}, store0.instances, store1.instances) }; } function filterEventStoreDefs(eventStore, filterFunc) { var defs = filterHash(eventStore.defs, filterFunc); var instances = filterHash(eventStore.instances, function (instance) { return defs[instance.defId]; // still exists? }); return { defs: defs, instances: instances }; } function parseRange(input, dateEnv) { var start = null; var end = null; if (input.start) { start = dateEnv.createMarker(input.start); } if (input.end) { end = dateEnv.createMarker(input.end); } if (!start && !end) { return null; } if (start && end && end < start) { return null; } return { start: start, end: end }; } // SIDE-EFFECT: will mutate ranges. // Will return a new array result. function invertRanges(ranges, constraintRange) { var invertedRanges = []; var start = constraintRange.start; // the end of the previous range. the start of the new range var i; var dateRange; // ranges need to be in order. required for our date-walking algorithm ranges.sort(compareRanges); for (i = 0; i < ranges.length; i++) { dateRange = ranges[i]; // add the span of time before the event (if there is any) if (dateRange.start > start) { // compare millisecond time (skip any ambig logic)