@fullcalendar/core
Version:
Provides core functionality, including the Calendar class
1,450 lines (1,431 loc) • 334 kB
JavaScript
/*!
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)