aboutsummaryrefslogtreecommitdiff
path: root/public/js/fullcalendar/packages/core/index.global.js
diff options
context:
space:
mode:
Diffstat (limited to 'public/js/fullcalendar/packages/core/index.global.js')
-rw-r--r--public/js/fullcalendar/packages/core/index.global.js9929
1 files changed, 9929 insertions, 0 deletions
diff --git a/public/js/fullcalendar/packages/core/index.global.js b/public/js/fullcalendar/packages/core/index.global.js
new file mode 100644
index 0000000..5c22940
--- /dev/null
+++ b/public/js/fullcalendar/packages/core/index.global.js
@@ -0,0 +1,9929 @@
1/*!
2FullCalendar Core v6.1.17
3Docs & License: https://fullcalendar.io
4(c) 2024 Adam Shaw
5*/
6var FullCalendar = (function (exports) {
7 'use strict';
8
9 const styleTexts = [];
10 const styleEls = new Map();
11 function injectStyles(styleText) {
12 styleTexts.push(styleText);
13 styleEls.forEach((styleEl) => {
14 appendStylesTo(styleEl, styleText);
15 });
16 }
17 function ensureElHasStyles(el) {
18 if (el.isConnected && // sometimes true if SSR system simulates DOM
19 el.getRootNode // sometimes undefined if SSR system simulates DOM
20 ) {
21 registerStylesRoot(el.getRootNode());
22 }
23 }
24 function registerStylesRoot(rootNode) {
25 let styleEl = styleEls.get(rootNode);
26 if (!styleEl || !styleEl.isConnected) {
27 styleEl = rootNode.querySelector('style[data-fullcalendar]');
28 if (!styleEl) {
29 styleEl = document.createElement('style');
30 styleEl.setAttribute('data-fullcalendar', '');
31 const nonce = getNonceValue();
32 if (nonce) {
33 styleEl.nonce = nonce;
34 }
35 const parentEl = rootNode === document ? document.head : rootNode;
36 const insertBefore = rootNode === document
37 ? parentEl.querySelector('script,link[rel=stylesheet],link[as=style],style')
38 : parentEl.firstChild;
39 parentEl.insertBefore(styleEl, insertBefore);
40 }
41 styleEls.set(rootNode, styleEl);
42 hydrateStylesRoot(styleEl);
43 }
44 }
45 function hydrateStylesRoot(styleEl) {
46 for (const styleText of styleTexts) {
47 appendStylesTo(styleEl, styleText);
48 }
49 }
50 function appendStylesTo(styleEl, styleText) {
51 const { sheet } = styleEl;
52 const ruleCnt = sheet.cssRules.length;
53 styleText.split('}').forEach((styleStr, i) => {
54 styleStr = styleStr.trim();
55 if (styleStr) {
56 sheet.insertRule(styleStr + '}', ruleCnt + i);
57 }
58 });
59 }
60 // nonce
61 // -------------------------------------------------------------------------------------------------
62 let queriedNonceValue;
63 function getNonceValue() {
64 if (queriedNonceValue === undefined) {
65 queriedNonceValue = queryNonceValue();
66 }
67 return queriedNonceValue;
68 }
69 /*
70 TODO: discourage meta tag and instead put nonce attribute on placeholder <style> tag
71 */
72 function queryNonceValue() {
73 const metaWithNonce = document.querySelector('meta[name="csp-nonce"]');
74 if (metaWithNonce && metaWithNonce.hasAttribute('content')) {
75 return metaWithNonce.getAttribute('content');
76 }
77 const elWithNonce = document.querySelector('script[nonce]');
78 if (elWithNonce) {
79 return elWithNonce.nonce || '';
80 }
81 return '';
82 }
83 // main
84 // -------------------------------------------------------------------------------------------------
85 if (typeof document !== 'undefined') {
86 registerStylesRoot(document);
87 }
88
89 var css_248z = ":root{--fc-small-font-size:.85em;--fc-page-bg-color:#fff;--fc-neutral-bg-color:hsla(0,0%,82%,.3);--fc-neutral-text-color:grey;--fc-border-color:#ddd;--fc-button-text-color:#fff;--fc-button-bg-color:#2c3e50;--fc-button-border-color:#2c3e50;--fc-button-hover-bg-color:#1e2b37;--fc-button-hover-border-color:#1a252f;--fc-button-active-bg-color:#1a252f;--fc-button-active-border-color:#151e27;--fc-event-bg-color:#3788d8;--fc-event-border-color:#3788d8;--fc-event-text-color:#fff;--fc-event-selected-overlay-color:rgba(0,0,0,.25);--fc-more-link-bg-color:#d0d0d0;--fc-more-link-text-color:inherit;--fc-event-resizer-thickness:8px;--fc-event-resizer-dot-total-width:8px;--fc-event-resizer-dot-border-width:1px;--fc-non-business-color:hsla(0,0%,84%,.3);--fc-bg-event-color:#8fdf82;--fc-bg-event-opacity:0.3;--fc-highlight-color:rgba(188,232,241,.3);--fc-today-bg-color:rgba(255,220,40,.15);--fc-now-indicator-color:red}.fc-not-allowed,.fc-not-allowed .fc-event{cursor:not-allowed}.fc{display:flex;flex-direction:column;font-size:1em}.fc,.fc *,.fc :after,.fc :before{box-sizing:border-box}.fc table{border-collapse:collapse;border-spacing:0;font-size:1em}.fc th{text-align:center}.fc td,.fc th{padding:0;vertical-align:top}.fc a[data-navlink]{cursor:pointer}.fc a[data-navlink]:hover{text-decoration:underline}.fc-direction-ltr{direction:ltr;text-align:left}.fc-direction-rtl{direction:rtl;text-align:right}.fc-theme-standard td,.fc-theme-standard th{border:1px solid var(--fc-border-color)}.fc-liquid-hack td,.fc-liquid-hack th{position:relative}@font-face{font-family:fcicons;font-style:normal;font-weight:400;src:url(\"data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg8SBfAAAAC8AAAAYGNtYXAXVtKNAAABHAAAAFRnYXNwAAAAEAAAAXAAAAAIZ2x5ZgYydxIAAAF4AAAFNGhlYWQUJ7cIAAAGrAAAADZoaGVhB20DzAAABuQAAAAkaG10eCIABhQAAAcIAAAALGxvY2ED4AU6AAAHNAAAABhtYXhwAA8AjAAAB0wAAAAgbmFtZXsr690AAAdsAAABhnBvc3QAAwAAAAAI9AAAACAAAwPAAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADpBgPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQAOAAAAAoACAACAAIAAQAg6Qb//f//AAAAAAAg6QD//f//AAH/4xcEAAMAAQAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAWIAjQKeAskAEwAAJSc3NjQnJiIHAQYUFwEWMjc2NCcCnuLiDQ0MJAz/AA0NAQAMJAwNDcni4gwjDQwM/wANIwz/AA0NDCMNAAAAAQFiAI0CngLJABMAACUBNjQnASYiBwYUHwEHBhQXFjI3AZ4BAA0N/wAMJAwNDeLiDQ0MJAyNAQAMIw0BAAwMDSMM4uINIwwNDQAAAAIA4gC3Ax4CngATACcAACUnNzY0JyYiDwEGFB8BFjI3NjQnISc3NjQnJiIPAQYUHwEWMjc2NCcB87e3DQ0MIw3VDQ3VDSMMDQ0BK7e3DQ0MJAzVDQ3VDCQMDQ3zuLcMJAwNDdUNIwzWDAwNIwy4twwkDA0N1Q0jDNYMDA0jDAAAAgDiALcDHgKeABMAJwAAJTc2NC8BJiIHBhQfAQcGFBcWMjchNzY0LwEmIgcGFB8BBwYUFxYyNwJJ1Q0N1Q0jDA0Nt7cNDQwjDf7V1Q0N1QwkDA0Nt7cNDQwkDLfWDCMN1Q0NDCQMt7gMIw0MDNYMIw3VDQ0MJAy3uAwjDQwMAAADAFUAAAOrA1UAMwBoAHcAABMiBgcOAQcOAQcOARURFBYXHgEXHgEXHgEzITI2Nz4BNz4BNz4BNRE0JicuAScuAScuASMFITIWFx4BFx4BFx4BFREUBgcOAQcOAQcOASMhIiYnLgEnLgEnLgE1ETQ2Nz4BNz4BNz4BMxMhMjY1NCYjISIGFRQWM9UNGAwLFQkJDgUFBQUFBQ4JCRULDBgNAlYNGAwLFQkJDgUFBQUFBQ4JCRULDBgN/aoCVgQIBAQHAwMFAQIBAQIBBQMDBwQECAT9qgQIBAQHAwMFAQIBAQIBBQMDBwQECASAAVYRGRkR/qoRGRkRA1UFBAUOCQkVDAsZDf2rDRkLDBUJCA4FBQUFBQUOCQgVDAsZDQJVDRkLDBUJCQ4FBAVVAgECBQMCBwQECAX9qwQJAwQHAwMFAQICAgIBBQMDBwQDCQQCVQUIBAQHAgMFAgEC/oAZEhEZGRESGQAAAAADAFUAAAOrA1UAMwBoAIkAABMiBgcOAQcOAQcOARURFBYXHgEXHgEXHgEzITI2Nz4BNz4BNz4BNRE0JicuAScuAScuASMFITIWFx4BFx4BFx4BFREUBgcOAQcOAQcOASMhIiYnLgEnLgEnLgE1ETQ2Nz4BNz4BNz4BMxMzFRQWMzI2PQEzMjY1NCYrATU0JiMiBh0BIyIGFRQWM9UNGAwLFQkJDgUFBQUFBQ4JCRULDBgNAlYNGAwLFQkJDgUFBQUFBQ4JCRULDBgN/aoCVgQIBAQHAwMFAQIBAQIBBQMDBwQECAT9qgQIBAQHAwMFAQIBAQIBBQMDBwQECASAgBkSEhmAERkZEYAZEhIZgBEZGREDVQUEBQ4JCRUMCxkN/asNGQsMFQkIDgUFBQUFBQ4JCBUMCxkNAlUNGQsMFQkJDgUEBVUCAQIFAwIHBAQIBf2rBAkDBAcDAwUBAgICAgEFAwMHBAMJBAJVBQgEBAcCAwUCAQL+gIASGRkSgBkSERmAEhkZEoAZERIZAAABAOIAjQMeAskAIAAAExcHBhQXFjI/ARcWMjc2NC8BNzY0JyYiDwEnJiIHBhQX4uLiDQ0MJAzi4gwkDA0N4uINDQwkDOLiDCQMDQ0CjeLiDSMMDQ3h4Q0NDCMN4uIMIw0MDOLiDAwNIwwAAAABAAAAAQAAa5n0y18PPPUACwQAAAAAANivOVsAAAAA2K85WwAAAAADqwNVAAAACAACAAAAAAAAAAEAAAPA/8AAAAQAAAAAAAOrAAEAAAAAAAAAAAAAAAAAAAALBAAAAAAAAAAAAAAAAgAAAAQAAWIEAAFiBAAA4gQAAOIEAABVBAAAVQQAAOIAAAAAAAoAFAAeAEQAagCqAOoBngJkApoAAQAAAAsAigADAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA4ArgABAAAAAAABAAcAAAABAAAAAAACAAcAYAABAAAAAAADAAcANgABAAAAAAAEAAcAdQABAAAAAAAFAAsAFQABAAAAAAAGAAcASwABAAAAAAAKABoAigADAAEECQABAA4ABwADAAEECQACAA4AZwADAAEECQADAA4APQADAAEECQAEAA4AfAADAAEECQAFABYAIAADAAEECQAGAA4AUgADAAEECQAKADQApGZjaWNvbnMAZgBjAGkAYwBvAG4Ac1ZlcnNpb24gMS4wAFYAZQByAHMAaQBvAG4AIAAxAC4AMGZjaWNvbnMAZgBjAGkAYwBvAG4Ac2ZjaWNvbnMAZgBjAGkAYwBvAG4Ac1JlZ3VsYXIAUgBlAGcAdQBsAGEAcmZjaWNvbnMAZgBjAGkAYwBvAG4Ac0ZvbnQgZ2VuZXJhdGVkIGJ5IEljb01vb24uAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\") format(\"truetype\")}.fc-icon{speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:inline-block;font-family:fcicons!important;font-style:normal;font-variant:normal;font-weight:400;height:1em;line-height:1;text-align:center;text-transform:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:1em}.fc-icon-chevron-left:before{content:\"\\e900\"}.fc-icon-chevron-right:before{content:\"\\e901\"}.fc-icon-chevrons-left:before{content:\"\\e902\"}.fc-icon-chevrons-right:before{content:\"\\e903\"}.fc-icon-minus-square:before{content:\"\\e904\"}.fc-icon-plus-square:before{content:\"\\e905\"}.fc-icon-x:before{content:\"\\e906\"}.fc .fc-button{border-radius:0;font-family:inherit;font-size:inherit;line-height:inherit;margin:0;overflow:visible;text-transform:none}.fc .fc-button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}.fc .fc-button{-webkit-appearance:button}.fc .fc-button:not(:disabled){cursor:pointer}.fc .fc-button{background-color:transparent;border:1px solid transparent;border-radius:.25em;display:inline-block;font-size:1em;font-weight:400;line-height:1.5;padding:.4em .65em;text-align:center;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle}.fc .fc-button:hover{text-decoration:none}.fc .fc-button:focus{box-shadow:0 0 0 .2rem rgba(44,62,80,.25);outline:0}.fc .fc-button:disabled{opacity:.65}.fc .fc-button-primary{background-color:var(--fc-button-bg-color);border-color:var(--fc-button-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:hover{background-color:var(--fc-button-hover-bg-color);border-color:var(--fc-button-hover-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:disabled{background-color:var(--fc-button-bg-color);border-color:var(--fc-button-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:focus{box-shadow:0 0 0 .2rem rgba(76,91,106,.5)}.fc .fc-button-primary:not(:disabled).fc-button-active,.fc .fc-button-primary:not(:disabled):active{background-color:var(--fc-button-active-bg-color);border-color:var(--fc-button-active-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:not(:disabled).fc-button-active:focus,.fc .fc-button-primary:not(:disabled):active:focus{box-shadow:0 0 0 .2rem rgba(76,91,106,.5)}.fc .fc-button .fc-icon{font-size:1.5em;vertical-align:middle}.fc .fc-button-group{display:inline-flex;position:relative;vertical-align:middle}.fc .fc-button-group>.fc-button{flex:1 1 auto;position:relative}.fc .fc-button-group>.fc-button.fc-button-active,.fc .fc-button-group>.fc-button:active,.fc .fc-button-group>.fc-button:focus,.fc .fc-button-group>.fc-button:hover{z-index:1}.fc-direction-ltr .fc-button-group>.fc-button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-1px}.fc-direction-ltr .fc-button-group>.fc-button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.fc-direction-rtl .fc-button-group>.fc-button:not(:first-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.fc-direction-rtl .fc-button-group>.fc-button:not(:last-child){border-bottom-left-radius:0;border-top-left-radius:0}.fc .fc-toolbar{align-items:center;display:flex;justify-content:space-between}.fc .fc-toolbar.fc-header-toolbar{margin-bottom:1.5em}.fc .fc-toolbar.fc-footer-toolbar{margin-top:1.5em}.fc .fc-toolbar-title{font-size:1.75em;margin:0}.fc-direction-ltr .fc-toolbar>*>:not(:first-child){margin-left:.75em}.fc-direction-rtl .fc-toolbar>*>:not(:first-child){margin-right:.75em}.fc-direction-rtl .fc-toolbar-ltr{flex-direction:row-reverse}.fc .fc-scroller{-webkit-overflow-scrolling:touch;position:relative}.fc .fc-scroller-liquid{height:100%}.fc .fc-scroller-liquid-absolute{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-scroller-harness{direction:ltr;overflow:hidden;position:relative}.fc .fc-scroller-harness-liquid{height:100%}.fc-direction-rtl .fc-scroller-harness>.fc-scroller{direction:rtl}.fc-theme-standard .fc-scrollgrid{border:1px solid var(--fc-border-color)}.fc .fc-scrollgrid,.fc .fc-scrollgrid table{table-layout:fixed;width:100%}.fc .fc-scrollgrid table{border-left-style:hidden;border-right-style:hidden;border-top-style:hidden}.fc .fc-scrollgrid{border-bottom-width:0;border-collapse:separate;border-right-width:0}.fc .fc-scrollgrid-liquid{height:100%}.fc .fc-scrollgrid-section,.fc .fc-scrollgrid-section table,.fc .fc-scrollgrid-section>td{height:1px}.fc .fc-scrollgrid-section-liquid>td{height:100%}.fc .fc-scrollgrid-section>*{border-left-width:0;border-top-width:0}.fc .fc-scrollgrid-section-footer>*,.fc .fc-scrollgrid-section-header>*{border-bottom-width:0}.fc .fc-scrollgrid-section-body table,.fc .fc-scrollgrid-section-footer table{border-bottom-style:hidden}.fc .fc-scrollgrid-section-sticky>*{background:var(--fc-page-bg-color);position:sticky;z-index:3}.fc .fc-scrollgrid-section-header.fc-scrollgrid-section-sticky>*{top:0}.fc .fc-scrollgrid-section-footer.fc-scrollgrid-section-sticky>*{bottom:0}.fc .fc-scrollgrid-sticky-shim{height:1px;margin-bottom:-1px}.fc-sticky{position:sticky}.fc .fc-view-harness{flex-grow:1;position:relative}.fc .fc-view-harness-active>.fc-view{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-col-header-cell-cushion{display:inline-block;padding:2px 4px}.fc .fc-bg-event,.fc .fc-highlight,.fc .fc-non-business{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-non-business{background:var(--fc-non-business-color)}.fc .fc-bg-event{background:var(--fc-bg-event-color);opacity:var(--fc-bg-event-opacity)}.fc .fc-bg-event .fc-event-title{font-size:var(--fc-small-font-size);font-style:italic;margin:.5em}.fc .fc-highlight{background:var(--fc-highlight-color)}.fc .fc-cell-shaded,.fc .fc-day-disabled{background:var(--fc-neutral-bg-color)}a.fc-event,a.fc-event:hover{text-decoration:none}.fc-event.fc-event-draggable,.fc-event[href]{cursor:pointer}.fc-event .fc-event-main{position:relative;z-index:2}.fc-event-dragging:not(.fc-event-selected){opacity:.75}.fc-event-dragging.fc-event-selected{box-shadow:0 2px 7px rgba(0,0,0,.3)}.fc-event .fc-event-resizer{display:none;position:absolute;z-index:4}.fc-event-selected .fc-event-resizer,.fc-event:hover .fc-event-resizer{display:block}.fc-event-selected .fc-event-resizer{background:var(--fc-page-bg-color);border-color:inherit;border-radius:calc(var(--fc-event-resizer-dot-total-width)/2);border-style:solid;border-width:var(--fc-event-resizer-dot-border-width);height:var(--fc-event-resizer-dot-total-width);width:var(--fc-event-resizer-dot-total-width)}.fc-event-selected .fc-event-resizer:before{bottom:-20px;content:\"\";left:-20px;position:absolute;right:-20px;top:-20px}.fc-event-selected,.fc-event:focus{box-shadow:0 2px 5px rgba(0,0,0,.2)}.fc-event-selected:before,.fc-event:focus:before{bottom:0;content:\"\";left:0;position:absolute;right:0;top:0;z-index:3}.fc-event-selected:after,.fc-event:focus:after{background:var(--fc-event-selected-overlay-color);bottom:-1px;content:\"\";left:-1px;position:absolute;right:-1px;top:-1px;z-index:1}.fc-h-event{background-color:var(--fc-event-bg-color);border:1px solid var(--fc-event-border-color);display:block}.fc-h-event .fc-event-main{color:var(--fc-event-text-color)}.fc-h-event .fc-event-main-frame{display:flex}.fc-h-event .fc-event-time{max-width:100%;overflow:hidden}.fc-h-event .fc-event-title-container{flex-grow:1;flex-shrink:1;min-width:0}.fc-h-event .fc-event-title{display:inline-block;left:0;max-width:100%;overflow:hidden;right:0;vertical-align:top}.fc-h-event.fc-event-selected:before{bottom:-10px;top:-10px}.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-start),.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-end){border-bottom-left-radius:0;border-left-width:0;border-top-left-radius:0}.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-end),.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-start){border-bottom-right-radius:0;border-right-width:0;border-top-right-radius:0}.fc-h-event:not(.fc-event-selected) .fc-event-resizer{bottom:0;top:0;width:var(--fc-event-resizer-thickness)}.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start,.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end{cursor:w-resize;left:calc(var(--fc-event-resizer-thickness)*-.5)}.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end,.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start{cursor:e-resize;right:calc(var(--fc-event-resizer-thickness)*-.5)}.fc-h-event.fc-event-selected .fc-event-resizer{margin-top:calc(var(--fc-event-resizer-dot-total-width)*-.5);top:50%}.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-start,.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-end{left:calc(var(--fc-event-resizer-dot-total-width)*-.5)}.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-end,.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-start{right:calc(var(--fc-event-resizer-dot-total-width)*-.5)}.fc .fc-popover{box-shadow:0 2px 6px rgba(0,0,0,.15);position:absolute;z-index:9999}.fc .fc-popover-header{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding:3px 4px}.fc .fc-popover-title{margin:0 2px}.fc .fc-popover-close{cursor:pointer;font-size:1.1em;opacity:.65}.fc-theme-standard .fc-popover{background:var(--fc-page-bg-color);border:1px solid var(--fc-border-color)}.fc-theme-standard .fc-popover-header{background:var(--fc-neutral-bg-color)}";
90 injectStyles(css_248z);
91
92 function removeElement(el) {
93 if (el.parentNode) {
94 el.parentNode.removeChild(el);
95 }
96 }
97 // Querying
98 // ----------------------------------------------------------------------------------------------------------------
99 function elementClosest(el, selector) {
100 if (el.closest) {
101 return el.closest(selector);
102 // really bad fallback for IE
103 // from https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
104 }
105 if (!document.documentElement.contains(el)) {
106 return null;
107 }
108 do {
109 if (elementMatches(el, selector)) {
110 return el;
111 }
112 el = (el.parentElement || el.parentNode);
113 } while (el !== null && el.nodeType === 1);
114 return null;
115 }
116 function elementMatches(el, selector) {
117 let method = el.matches || el.matchesSelector || el.msMatchesSelector;
118 return method.call(el, selector);
119 }
120 // accepts multiple subject els
121 // returns a real array. good for methods like forEach
122 // TODO: accept the document
123 function findElements(container, selector) {
124 let containers = container instanceof HTMLElement ? [container] : container;
125 let allMatches = [];
126 for (let i = 0; i < containers.length; i += 1) {
127 let matches = containers[i].querySelectorAll(selector);
128 for (let j = 0; j < matches.length; j += 1) {
129 allMatches.push(matches[j]);
130 }
131 }
132 return allMatches;
133 }
134 // accepts multiple subject els
135 // only queries direct child elements // TODO: rename to findDirectChildren!
136 function findDirectChildren(parent, selector) {
137 let parents = parent instanceof HTMLElement ? [parent] : parent;
138 let allMatches = [];
139 for (let i = 0; i < parents.length; i += 1) {
140 let childNodes = parents[i].children; // only ever elements
141 for (let j = 0; j < childNodes.length; j += 1) {
142 let childNode = childNodes[j];
143 if (!selector || elementMatches(childNode, selector)) {
144 allMatches.push(childNode);
145 }
146 }
147 }
148 return allMatches;
149 }
150 // Style
151 // ----------------------------------------------------------------------------------------------------------------
152 const PIXEL_PROP_RE = /(top|left|right|bottom|width|height)$/i;
153 function applyStyle(el, props) {
154 for (let propName in props) {
155 applyStyleProp(el, propName, props[propName]);
156 }
157 }
158 function applyStyleProp(el, name, val) {
159 if (val == null) {
160 el.style[name] = '';
161 }
162 else if (typeof val === 'number' && PIXEL_PROP_RE.test(name)) {
163 el.style[name] = `${val}px`;
164 }
165 else {
166 el.style[name] = val;
167 }
168 }
169 // Event Handling
170 // ----------------------------------------------------------------------------------------------------------------
171 // if intercepting bubbled events at the document/window/body level,
172 // and want to see originating element (the 'target'), use this util instead
173 // of `ev.target` because it goes within web-component boundaries.
174 function getEventTargetViaRoot(ev) {
175 var _a, _b;
176 return (_b = (_a = ev.composedPath) === null || _a === void 0 ? void 0 : _a.call(ev)[0]) !== null && _b !== void 0 ? _b : ev.target;
177 }
178 // Unique ID for DOM attribute
179 let guid$1 = 0;
180 function getUniqueDomId() {
181 guid$1 += 1;
182 return 'fc-dom-' + guid$1;
183 }
184
185 // Stops a mouse/touch event from doing it's native browser action
186 function preventDefault(ev) {
187 ev.preventDefault();
188 }
189 // Event Delegation
190 // ----------------------------------------------------------------------------------------------------------------
191 function buildDelegationHandler(selector, handler) {
192 return (ev) => {
193 let matchedChild = elementClosest(ev.target, selector);
194 if (matchedChild) {
195 handler.call(matchedChild, ev, matchedChild);
196 }
197 };
198 }
199 function listenBySelector(container, eventType, selector, handler) {
200 let attachedHandler = buildDelegationHandler(selector, handler);
201 container.addEventListener(eventType, attachedHandler);
202 return () => {
203 container.removeEventListener(eventType, attachedHandler);
204 };
205 }
206 function listenToHoverBySelector(container, selector, onMouseEnter, onMouseLeave) {
207 let currentMatchedChild;
208 return listenBySelector(container, 'mouseover', selector, (mouseOverEv, matchedChild) => {
209 if (matchedChild !== currentMatchedChild) {
210 currentMatchedChild = matchedChild;
211 onMouseEnter(mouseOverEv, matchedChild);
212 let realOnMouseLeave = (mouseLeaveEv) => {
213 currentMatchedChild = null;
214 onMouseLeave(mouseLeaveEv, matchedChild);
215 matchedChild.removeEventListener('mouseleave', realOnMouseLeave);
216 };
217 // listen to the next mouseleave, and then unattach
218 matchedChild.addEventListener('mouseleave', realOnMouseLeave);
219 }
220 });
221 }
222 // Animation
223 // ----------------------------------------------------------------------------------------------------------------
224 const transitionEventNames = [
225 'webkitTransitionEnd',
226 'otransitionend',
227 'oTransitionEnd',
228 'msTransitionEnd',
229 'transitionend',
230 ];
231 // triggered only when the next single subsequent transition finishes
232 function whenTransitionDone(el, callback) {
233 let realCallback = (ev) => {
234 callback(ev);
235 transitionEventNames.forEach((eventName) => {
236 el.removeEventListener(eventName, realCallback);
237 });
238 };
239 transitionEventNames.forEach((eventName) => {
240 el.addEventListener(eventName, realCallback); // cross-browser way to determine when the transition finishes
241 });
242 }
243 // ARIA workarounds
244 // ----------------------------------------------------------------------------------------------------------------
245 function createAriaClickAttrs(handler) {
246 return Object.assign({ onClick: handler }, createAriaKeyboardAttrs(handler));
247 }
248 function createAriaKeyboardAttrs(handler) {
249 return {
250 tabIndex: 0,
251 onKeyDown(ev) {
252 if (ev.key === 'Enter' || ev.key === ' ') {
253 handler(ev);
254 ev.preventDefault(); // if space, don't scroll down page
255 }
256 },
257 };
258 }
259
260 let guidNumber = 0;
261 function guid() {
262 guidNumber += 1;
263 return String(guidNumber);
264 }
265 /* FullCalendar-specific DOM Utilities
266 ----------------------------------------------------------------------------------------------------------------------*/
267 // Make the mouse cursor express that an event is not allowed in the current area
268 function disableCursor() {
269 document.body.classList.add('fc-not-allowed');
270 }
271 // Returns the mouse cursor to its original look
272 function enableCursor() {
273 document.body.classList.remove('fc-not-allowed');
274 }
275 /* Selection
276 ----------------------------------------------------------------------------------------------------------------------*/
277 function preventSelection(el) {
278 el.style.userSelect = 'none';
279 el.style.webkitUserSelect = 'none';
280 el.addEventListener('selectstart', preventDefault);
281 }
282 function allowSelection(el) {
283 el.style.userSelect = '';
284 el.style.webkitUserSelect = '';
285 el.removeEventListener('selectstart', preventDefault);
286 }
287 /* Context Menu
288 ----------------------------------------------------------------------------------------------------------------------*/
289 function preventContextMenu(el) {
290 el.addEventListener('contextmenu', preventDefault);
291 }
292 function allowContextMenu(el) {
293 el.removeEventListener('contextmenu', preventDefault);
294 }
295 function parseFieldSpecs(input) {
296 let specs = [];
297 let tokens = [];
298 let i;
299 let token;
300 if (typeof input === 'string') {
301 tokens = input.split(/\s*,\s*/);
302 }
303 else if (typeof input === 'function') {
304 tokens = [input];
305 }
306 else if (Array.isArray(input)) {
307 tokens = input;
308 }
309 for (i = 0; i < tokens.length; i += 1) {
310 token = tokens[i];
311 if (typeof token === 'string') {
312 specs.push(token.charAt(0) === '-' ?
313 { field: token.substring(1), order: -1 } :
314 { field: token, order: 1 });
315 }
316 else if (typeof token === 'function') {
317 specs.push({ func: token });
318 }
319 }
320 return specs;
321 }
322 function compareByFieldSpecs(obj0, obj1, fieldSpecs) {
323 let i;
324 let cmp;
325 for (i = 0; i < fieldSpecs.length; i += 1) {
326 cmp = compareByFieldSpec(obj0, obj1, fieldSpecs[i]);
327 if (cmp) {
328 return cmp;
329 }
330 }
331 return 0;
332 }
333 function compareByFieldSpec(obj0, obj1, fieldSpec) {
334 if (fieldSpec.func) {
335 return fieldSpec.func(obj0, obj1);
336 }
337 return flexibleCompare(obj0[fieldSpec.field], obj1[fieldSpec.field])
338 * (fieldSpec.order || 1);
339 }
340 function flexibleCompare(a, b) {
341 if (!a && !b) {
342 return 0;
343 }
344 if (b == null) {
345 return -1;
346 }
347 if (a == null) {
348 return 1;
349 }
350 if (typeof a === 'string' || typeof b === 'string') {
351 return String(a).localeCompare(String(b));
352 }
353 return a - b;
354 }
355 /* String Utilities
356 ----------------------------------------------------------------------------------------------------------------------*/
357 function padStart(val, len) {
358 let s = String(val);
359 return '000'.substr(0, len - s.length) + s;
360 }
361 function formatWithOrdinals(formatter, args, fallbackText) {
362 if (typeof formatter === 'function') {
363 return formatter(...args);
364 }
365 if (typeof formatter === 'string') { // non-blank string
366 return args.reduce((str, arg, index) => (str.replace('$' + index, arg || '')), formatter);
367 }
368 return fallbackText;
369 }
370 /* Number Utilities
371 ----------------------------------------------------------------------------------------------------------------------*/
372 function compareNumbers(a, b) {
373 return a - b;
374 }
375 function isInt(n) {
376 return n % 1 === 0;
377 }
378 /* FC-specific DOM dimension stuff
379 ----------------------------------------------------------------------------------------------------------------------*/
380 function computeSmallestCellWidth(cellEl) {
381 let allWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-frame');
382 let contentWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-cushion');
383 if (!allWidthEl) {
384 throw new Error('needs fc-scrollgrid-shrink-frame className'); // TODO: use const
385 }
386 if (!contentWidthEl) {
387 throw new Error('needs fc-scrollgrid-shrink-cushion className');
388 }
389 return cellEl.getBoundingClientRect().width - allWidthEl.getBoundingClientRect().width + // the cell padding+border
390 contentWidthEl.getBoundingClientRect().width;
391 }
392
393 const INTERNAL_UNITS = ['years', 'months', 'days', 'milliseconds'];
394 const PARSE_RE = /^(-?)(?:(\d+)\.)?(\d+):(\d\d)(?::(\d\d)(?:\.(\d\d\d))?)?/;
395 // Parsing and Creation
396 function createDuration(input, unit) {
397 if (typeof input === 'string') {
398 return parseString(input);
399 }
400 if (typeof input === 'object' && input) { // non-null object
401 return parseObject(input);
402 }
403 if (typeof input === 'number') {
404 return parseObject({ [unit || 'milliseconds']: input });
405 }
406 return null;
407 }
408 function parseString(s) {
409 let m = PARSE_RE.exec(s);
410 if (m) {
411 let sign = m[1] ? -1 : 1;
412 return {
413 years: 0,
414 months: 0,
415 days: sign * (m[2] ? parseInt(m[2], 10) : 0),
416 milliseconds: sign * ((m[3] ? parseInt(m[3], 10) : 0) * 60 * 60 * 1000 + // hours
417 (m[4] ? parseInt(m[4], 10) : 0) * 60 * 1000 + // minutes
418 (m[5] ? parseInt(m[5], 10) : 0) * 1000 + // seconds
419 (m[6] ? parseInt(m[6], 10) : 0) // ms
420 ),
421 };
422 }
423 return null;
424 }
425 function parseObject(obj) {
426 let duration = {
427 years: obj.years || obj.year || 0,
428 months: obj.months || obj.month || 0,
429 days: obj.days || obj.day || 0,
430 milliseconds: (obj.hours || obj.hour || 0) * 60 * 60 * 1000 + // hours
431 (obj.minutes || obj.minute || 0) * 60 * 1000 + // minutes
432 (obj.seconds || obj.second || 0) * 1000 + // seconds
433 (obj.milliseconds || obj.millisecond || obj.ms || 0), // ms
434 };
435 let weeks = obj.weeks || obj.week;
436 if (weeks) {
437 duration.days += weeks * 7;
438 duration.specifiedWeeks = true;
439 }
440 return duration;
441 }
442 // Equality
443 function durationsEqual(d0, d1) {
444 return d0.years === d1.years &&
445 d0.months === d1.months &&
446 d0.days === d1.days &&
447 d0.milliseconds === d1.milliseconds;
448 }
449 function asCleanDays(dur) {
450 if (!dur.years && !dur.months && !dur.milliseconds) {
451 return dur.days;
452 }
453 return 0;
454 }
455 // Simple Math
456 function addDurations(d0, d1) {
457 return {
458 years: d0.years + d1.years,
459 months: d0.months + d1.months,
460 days: d0.days + d1.days,
461 milliseconds: d0.milliseconds + d1.milliseconds,
462 };
463 }
464 function subtractDurations(d1, d0) {
465 return {
466 years: d1.years - d0.years,
467 months: d1.months - d0.months,
468 days: d1.days - d0.days,
469 milliseconds: d1.milliseconds - d0.milliseconds,
470 };
471 }
472 function multiplyDuration(d, n) {
473 return {
474 years: d.years * n,
475 months: d.months * n,
476 days: d.days * n,
477 milliseconds: d.milliseconds * n,
478 };
479 }
480 // Conversions
481 // "Rough" because they are based on average-case Gregorian months/years
482 function asRoughYears(dur) {
483 return asRoughDays(dur) / 365;
484 }
485 function asRoughMonths(dur) {
486 return asRoughDays(dur) / 30;
487 }
488 function asRoughDays(dur) {
489 return asRoughMs(dur) / 864e5;
490 }
491 function asRoughMinutes(dur) {
492 return asRoughMs(dur) / (1000 * 60);
493 }
494 function asRoughSeconds(dur) {
495 return asRoughMs(dur) / 1000;
496 }
497 function asRoughMs(dur) {
498 return dur.years * (365 * 864e5) +
499 dur.months * (30 * 864e5) +
500 dur.days * 864e5 +
501 dur.milliseconds;
502 }
503 // Advanced Math
504 function wholeDivideDurations(numerator, denominator) {
505 let res = null;
506 for (let i = 0; i < INTERNAL_UNITS.length; i += 1) {
507 let unit = INTERNAL_UNITS[i];
508 if (denominator[unit]) {
509 let localRes = numerator[unit] / denominator[unit];
510 if (!isInt(localRes) || (res !== null && res !== localRes)) {
511 return null;
512 }
513 res = localRes;
514 }
515 else if (numerator[unit]) {
516 // needs to divide by something but can't!
517 return null;
518 }
519 }
520 return res;
521 }
522 function greatestDurationDenominator(dur) {
523 let ms = dur.milliseconds;
524 if (ms) {
525 if (ms % 1000 !== 0) {
526 return { unit: 'millisecond', value: ms };
527 }
528 if (ms % (1000 * 60) !== 0) {
529 return { unit: 'second', value: ms / 1000 };
530 }
531 if (ms % (1000 * 60 * 60) !== 0) {
532 return { unit: 'minute', value: ms / (1000 * 60) };
533 }
534 if (ms) {
535 return { unit: 'hour', value: ms / (1000 * 60 * 60) };
536 }
537 }
538 if (dur.days) {
539 if (dur.specifiedWeeks && dur.days % 7 === 0) {
540 return { unit: 'week', value: dur.days / 7 };
541 }
542 return { unit: 'day', value: dur.days };
543 }
544 if (dur.months) {
545 return { unit: 'month', value: dur.months };
546 }
547 if (dur.years) {
548 return { unit: 'year', value: dur.years };
549 }
550 return { unit: 'millisecond', value: 0 };
551 }
552
553 const { hasOwnProperty } = Object.prototype;
554 // Merges an array of objects into a single object.
555 // The second argument allows for an array of property names who's object values will be merged together.
556 function mergeProps(propObjs, complexPropsMap) {
557 let dest = {};
558 if (complexPropsMap) {
559 for (let name in complexPropsMap) {
560 if (complexPropsMap[name] === isMaybeObjectsEqual) { // implies that it's object-mergeable
561 let complexObjs = [];
562 // collect the trailing object values, stopping when a non-object is discovered
563 for (let i = propObjs.length - 1; i >= 0; i -= 1) {
564 let val = propObjs[i][name];
565 if (typeof val === 'object' && val) { // non-null object
566 complexObjs.unshift(val);
567 }
568 else if (val !== undefined) {
569 dest[name] = val; // if there were no objects, this value will be used
570 break;
571 }
572 }
573 // if the trailing values were objects, use the merged value
574 if (complexObjs.length) {
575 dest[name] = mergeProps(complexObjs);
576 }
577 }
578 }
579 }
580 // copy values into the destination, going from last to first
581 for (let i = propObjs.length - 1; i >= 0; i -= 1) {
582 let props = propObjs[i];
583 for (let name in props) {
584 if (!(name in dest)) { // if already assigned by previous props or complex props, don't reassign
585 dest[name] = props[name];
586 }
587 }
588 }
589 return dest;
590 }
591 function filterHash(hash, func) {
592 let filtered = {};
593 for (let key in hash) {
594 if (func(hash[key], key)) {
595 filtered[key] = hash[key];
596 }
597 }
598 return filtered;
599 }
600 function mapHash(hash, func) {
601 let newHash = {};
602 for (let key in hash) {
603 newHash[key] = func(hash[key], key);
604 }
605 return newHash;
606 }
607 function arrayToHash(a) {
608 let hash = {};
609 for (let item of a) {
610 hash[item] = true;
611 }
612 return hash;
613 }
614 // TODO: reassess browser support
615 // https://caniuse.com/?search=object.values
616 function hashValuesToArray(obj) {
617 let a = [];
618 for (let key in obj) {
619 a.push(obj[key]);
620 }
621 return a;
622 }
623 function isPropsEqual(obj0, obj1) {
624 if (obj0 === obj1) {
625 return true;
626 }
627 for (let key in obj0) {
628 if (hasOwnProperty.call(obj0, key)) {
629 if (!(key in obj1)) {
630 return false;
631 }
632 }
633 }
634 for (let key in obj1) {
635 if (hasOwnProperty.call(obj1, key)) {
636 if (obj0[key] !== obj1[key]) {
637 return false;
638 }
639 }
640 }
641 return true;
642 }
643 const HANDLER_RE = /^on[A-Z]/;
644 function isNonHandlerPropsEqual(obj0, obj1) {
645 const keys = getUnequalProps(obj0, obj1);
646 for (let key of keys) {
647 if (!HANDLER_RE.test(key)) {
648 return false;
649 }
650 }
651 return true;
652 }
653 function getUnequalProps(obj0, obj1) {
654 let keys = [];
655 for (let key in obj0) {
656 if (hasOwnProperty.call(obj0, key)) {
657 if (!(key in obj1)) {
658 keys.push(key);
659 }
660 }
661 }
662 for (let key in obj1) {
663 if (hasOwnProperty.call(obj1, key)) {
664 if (obj0[key] !== obj1[key]) {
665 keys.push(key);
666 }
667 }
668 }
669 return keys;
670 }
671 function compareObjs(oldProps, newProps, equalityFuncs = {}) {
672 if (oldProps === newProps) {
673 return true;
674 }
675 for (let key in newProps) {
676 if (key in oldProps && isObjValsEqual(oldProps[key], newProps[key], equalityFuncs[key])) ;
677 else {
678 return false;
679 }
680 }
681 // check for props that were omitted in the new
682 for (let key in oldProps) {
683 if (!(key in newProps)) {
684 return false;
685 }
686 }
687 return true;
688 }
689 /*
690 assumed "true" equality for handler names like "onReceiveSomething"
691 */
692 function isObjValsEqual(val0, val1, comparator) {
693 if (val0 === val1 || comparator === true) {
694 return true;
695 }
696 if (comparator) {
697 return comparator(val0, val1);
698 }
699 return false;
700 }
701 function collectFromHash(hash, startIndex = 0, endIndex, step = 1) {
702 let res = [];
703 if (endIndex == null) {
704 endIndex = Object.keys(hash).length;
705 }
706 for (let i = startIndex; i < endIndex; i += step) {
707 let val = hash[i];
708 if (val !== undefined) { // will disregard undefined for sparse arrays
709 res.push(val);
710 }
711 }
712 return res;
713 }
714
715 // TODO: new util arrayify?
716 function removeExact(array, exactVal) {
717 let removeCnt = 0;
718 let i = 0;
719 while (i < array.length) {
720 if (array[i] === exactVal) {
721 array.splice(i, 1);
722 removeCnt += 1;
723 }
724 else {
725 i += 1;
726 }
727 }
728 return removeCnt;
729 }
730 function isArraysEqual(a0, a1, equalityFunc) {
731 if (a0 === a1) {
732 return true;
733 }
734 let len = a0.length;
735 let i;
736 if (len !== a1.length) { // not array? or not same length?
737 return false;
738 }
739 for (i = 0; i < len; i += 1) {
740 if (!(equalityFunc ? equalityFunc(a0[i], a1[i]) : a0[i] === a1[i])) {
741 return false;
742 }
743 }
744 return true;
745 }
746
747 const DAY_IDS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
748 // Adding
749 function addWeeks(m, n) {
750 let a = dateToUtcArray(m);
751 a[2] += n * 7;
752 return arrayToUtcDate(a);
753 }
754 function addDays(m, n) {
755 let a = dateToUtcArray(m);
756 a[2] += n;
757 return arrayToUtcDate(a);
758 }
759 function addMs(m, n) {
760 let a = dateToUtcArray(m);
761 a[6] += n;
762 return arrayToUtcDate(a);
763 }
764 // Diffing (all return floats)
765 // TODO: why not use ranges?
766 function diffWeeks(m0, m1) {
767 return diffDays(m0, m1) / 7;
768 }
769 function diffDays(m0, m1) {
770 return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60 * 24);
771 }
772 function diffHours(m0, m1) {
773 return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60);
774 }
775 function diffMinutes(m0, m1) {
776 return (m1.valueOf() - m0.valueOf()) / (1000 * 60);
777 }
778 function diffSeconds(m0, m1) {
779 return (m1.valueOf() - m0.valueOf()) / 1000;
780 }
781 function diffDayAndTime(m0, m1) {
782 let m0day = startOfDay(m0);
783 let m1day = startOfDay(m1);
784 return {
785 years: 0,
786 months: 0,
787 days: Math.round(diffDays(m0day, m1day)),
788 milliseconds: (m1.valueOf() - m1day.valueOf()) - (m0.valueOf() - m0day.valueOf()),
789 };
790 }
791 // Diffing Whole Units
792 function diffWholeWeeks(m0, m1) {
793 let d = diffWholeDays(m0, m1);
794 if (d !== null && d % 7 === 0) {
795 return d / 7;
796 }
797 return null;
798 }
799 function diffWholeDays(m0, m1) {
800 if (timeAsMs(m0) === timeAsMs(m1)) {
801 return Math.round(diffDays(m0, m1));
802 }
803 return null;
804 }
805 // Start-Of
806 function startOfDay(m) {
807 return arrayToUtcDate([
808 m.getUTCFullYear(),
809 m.getUTCMonth(),
810 m.getUTCDate(),
811 ]);
812 }
813 function startOfHour(m) {
814 return arrayToUtcDate([
815 m.getUTCFullYear(),
816 m.getUTCMonth(),
817 m.getUTCDate(),
818 m.getUTCHours(),
819 ]);
820 }
821 function startOfMinute(m) {
822 return arrayToUtcDate([
823 m.getUTCFullYear(),
824 m.getUTCMonth(),
825 m.getUTCDate(),
826 m.getUTCHours(),
827 m.getUTCMinutes(),
828 ]);
829 }
830 function startOfSecond(m) {
831 return arrayToUtcDate([
832 m.getUTCFullYear(),
833 m.getUTCMonth(),
834 m.getUTCDate(),
835 m.getUTCHours(),
836 m.getUTCMinutes(),
837 m.getUTCSeconds(),
838 ]);
839 }
840 // Week Computation
841 function weekOfYear(marker, dow, doy) {
842 let y = marker.getUTCFullYear();
843 let w = weekOfGivenYear(marker, y, dow, doy);
844 if (w < 1) {
845 return weekOfGivenYear(marker, y - 1, dow, doy);
846 }
847 let nextW = weekOfGivenYear(marker, y + 1, dow, doy);
848 if (nextW >= 1) {
849 return Math.min(w, nextW);
850 }
851 return w;
852 }
853 function weekOfGivenYear(marker, year, dow, doy) {
854 let firstWeekStart = arrayToUtcDate([year, 0, 1 + firstWeekOffset(year, dow, doy)]);
855 let dayStart = startOfDay(marker);
856 let days = Math.round(diffDays(firstWeekStart, dayStart));
857 return Math.floor(days / 7) + 1; // zero-indexed
858 }
859 // start-of-first-week - start-of-year
860 function firstWeekOffset(year, dow, doy) {
861 // first-week day -- which january is always in the first week (4 for iso, 1 for other)
862 let fwd = 7 + dow - doy;
863 // first-week day local weekday -- which local weekday is fwd
864 let fwdlw = (7 + arrayToUtcDate([year, 0, fwd]).getUTCDay() - dow) % 7;
865 return -fwdlw + fwd - 1;
866 }
867 // Array Conversion
868 function dateToLocalArray(date) {
869 return [
870 date.getFullYear(),
871 date.getMonth(),
872 date.getDate(),
873 date.getHours(),
874 date.getMinutes(),
875 date.getSeconds(),
876 date.getMilliseconds(),
877 ];
878 }
879 function arrayToLocalDate(a) {
880 return new Date(a[0], a[1] || 0, a[2] == null ? 1 : a[2], // day of month
881 a[3] || 0, a[4] || 0, a[5] || 0);
882 }
883 function dateToUtcArray(date) {
884 return [
885 date.getUTCFullYear(),
886 date.getUTCMonth(),
887 date.getUTCDate(),
888 date.getUTCHours(),
889 date.getUTCMinutes(),
890 date.getUTCSeconds(),
891 date.getUTCMilliseconds(),
892 ];
893 }
894 function arrayToUtcDate(a) {
895 // according to web standards (and Safari), a month index is required.
896 // massage if only given a year.
897 if (a.length === 1) {
898 a = a.concat([0]);
899 }
900 return new Date(Date.UTC(...a));
901 }
902 // Other Utils
903 function isValidDate(m) {
904 return !isNaN(m.valueOf());
905 }
906 function timeAsMs(m) {
907 return m.getUTCHours() * 1000 * 60 * 60 +
908 m.getUTCMinutes() * 1000 * 60 +
909 m.getUTCSeconds() * 1000 +
910 m.getUTCMilliseconds();
911 }
912
913 // timeZoneOffset is in minutes
914 function buildIsoString(marker, timeZoneOffset, stripZeroTime = false) {
915 let s = marker.toISOString();
916 s = s.replace('.000', '');
917 if (stripZeroTime) {
918 s = s.replace('T00:00:00Z', '');
919 }
920 if (s.length > 10) { // time part wasn't stripped, can add timezone info
921 if (timeZoneOffset == null) {
922 s = s.replace('Z', '');
923 }
924 else if (timeZoneOffset !== 0) {
925 s = s.replace('Z', formatTimeZoneOffset(timeZoneOffset, true));
926 }
927 // otherwise, its UTC-0 and we want to keep the Z
928 }
929 return s;
930 }
931 // formats the date, but with no time part
932 // TODO: somehow merge with buildIsoString and stripZeroTime
933 // TODO: rename. omit "string"
934 function formatDayString(marker) {
935 return marker.toISOString().replace(/T.*$/, '');
936 }
937 function formatIsoMonthStr(marker) {
938 return marker.toISOString().match(/^\d{4}-\d{2}/)[0];
939 }
940 // TODO: use Date::toISOString and use everything after the T?
941 function formatIsoTimeString(marker) {
942 return padStart(marker.getUTCHours(), 2) + ':' +
943 padStart(marker.getUTCMinutes(), 2) + ':' +
944 padStart(marker.getUTCSeconds(), 2);
945 }
946 function formatTimeZoneOffset(minutes, doIso = false) {
947 let sign = minutes < 0 ? '-' : '+';
948 let abs = Math.abs(minutes);
949 let hours = Math.floor(abs / 60);
950 let mins = Math.round(abs % 60);
951 if (doIso) {
952 return `${sign + padStart(hours, 2)}:${padStart(mins, 2)}`;
953 }
954 return `GMT${sign}${hours}${mins ? `:${padStart(mins, 2)}` : ''}`;
955 }
956
957 function memoize(workerFunc, resEquality, teardownFunc) {
958 let currentArgs;
959 let currentRes;
960 return function (...newArgs) {
961 if (!currentArgs) {
962 currentRes = workerFunc.apply(this, newArgs);
963 }
964 else if (!isArraysEqual(currentArgs, newArgs)) {
965 if (teardownFunc) {
966 teardownFunc(currentRes);
967 }
968 let res = workerFunc.apply(this, newArgs);
969 if (!resEquality || !resEquality(res, currentRes)) {
970 currentRes = res;
971 }
972 }
973 currentArgs = newArgs;
974 return currentRes;
975 };
976 }
977 function memoizeObjArg(workerFunc, resEquality, teardownFunc) {
978 let currentArg;
979 let currentRes;
980 return (newArg) => {
981 if (!currentArg) {
982 currentRes = workerFunc.call(this, newArg);
983 }
984 else if (!isPropsEqual(currentArg, newArg)) {
985 if (teardownFunc) {
986 teardownFunc(currentRes);
987 }
988 let res = workerFunc.call(this, newArg);
989 if (!resEquality || !resEquality(res, currentRes)) {
990 currentRes = res;
991 }
992 }
993 currentArg = newArg;
994 return currentRes;
995 };
996 }
997 function memoizeArraylike(// used at all?
998 workerFunc, resEquality, teardownFunc) {
999 let currentArgSets = [];
1000 let currentResults = [];
1001 return (newArgSets) => {
1002 let currentLen = currentArgSets.length;
1003 let newLen = newArgSets.length;
1004 let i = 0;
1005 for (; i < currentLen; i += 1) {
1006 if (!newArgSets[i]) { // one of the old sets no longer exists
1007 if (teardownFunc) {
1008 teardownFunc(currentResults[i]);
1009 }
1010 }
1011 else if (!isArraysEqual(currentArgSets[i], newArgSets[i])) {
1012 if (teardownFunc) {
1013 teardownFunc(currentResults[i]);
1014 }
1015 let res = workerFunc.apply(this, newArgSets[i]);
1016 if (!resEquality || !resEquality(res, currentResults[i])) {
1017 currentResults[i] = res;
1018 }
1019 }
1020 }
1021 for (; i < newLen; i += 1) {
1022 currentResults[i] = workerFunc.apply(this, newArgSets[i]);
1023 }
1024 currentArgSets = newArgSets;
1025 currentResults.splice(newLen); // remove excess
1026 return currentResults;
1027 };
1028 }
1029 function memoizeHashlike(workerFunc, resEquality, teardownFunc) {
1030 let currentArgHash = {};
1031 let currentResHash = {};
1032 return (newArgHash) => {
1033 let newResHash = {};
1034 for (let key in newArgHash) {
1035 if (!currentResHash[key]) {
1036 newResHash[key] = workerFunc.apply(this, newArgHash[key]);
1037 }
1038 else if (!isArraysEqual(currentArgHash[key], newArgHash[key])) {
1039 if (teardownFunc) {
1040 teardownFunc(currentResHash[key]);
1041 }
1042 let res = workerFunc.apply(this, newArgHash[key]);
1043 newResHash[key] = (resEquality && resEquality(res, currentResHash[key]))
1044 ? currentResHash[key]
1045 : res;
1046 }
1047 else {
1048 newResHash[key] = currentResHash[key];
1049 }
1050 }
1051 currentArgHash = newArgHash;
1052 currentResHash = newResHash;
1053 return newResHash;
1054 };
1055 }
1056
1057 const EXTENDED_SETTINGS_AND_SEVERITIES = {
1058 week: 3,
1059 separator: 9,
1060 omitZeroMinute: 9,
1061 meridiem: 9,
1062 omitCommas: 9,
1063 };
1064 const STANDARD_DATE_PROP_SEVERITIES = {
1065 timeZoneName: 7,
1066 era: 6,
1067 year: 5,
1068 month: 4,
1069 day: 2,
1070 weekday: 2,
1071 hour: 1,
1072 minute: 1,
1073 second: 1,
1074 };
1075 const MERIDIEM_RE = /\s*([ap])\.?m\.?/i; // eats up leading spaces too
1076 const COMMA_RE = /,/g; // we need re for globalness
1077 const MULTI_SPACE_RE = /\s+/g;
1078 const LTR_RE = /\u200e/g; // control character
1079 const UTC_RE = /UTC|GMT/;
1080 class NativeFormatter {
1081 constructor(formatSettings) {
1082 let standardDateProps = {};
1083 let extendedSettings = {};
1084 let smallestUnitNum = 9; // the smallest unit in the formatter (9 is a sentinel, beyond max)
1085 for (let name in formatSettings) {
1086 if (name in EXTENDED_SETTINGS_AND_SEVERITIES) {
1087 extendedSettings[name] = formatSettings[name];
1088 const severity = EXTENDED_SETTINGS_AND_SEVERITIES[name];
1089 if (severity < 9) {
1090 smallestUnitNum = Math.min(EXTENDED_SETTINGS_AND_SEVERITIES[name], smallestUnitNum);
1091 }
1092 }
1093 else {
1094 standardDateProps[name] = formatSettings[name];
1095 if (name in STANDARD_DATE_PROP_SEVERITIES) { // TODO: what about hour12? no severity
1096 smallestUnitNum = Math.min(STANDARD_DATE_PROP_SEVERITIES[name], smallestUnitNum);
1097 }
1098 }
1099 }
1100 this.standardDateProps = standardDateProps;
1101 this.extendedSettings = extendedSettings;
1102 this.smallestUnitNum = smallestUnitNum;
1103 this.buildFormattingFunc = memoize(buildFormattingFunc);
1104 }
1105 format(date, context) {
1106 return this.buildFormattingFunc(this.standardDateProps, this.extendedSettings, context)(date);
1107 }
1108 formatRange(start, end, context, betterDefaultSeparator) {
1109 let { standardDateProps, extendedSettings } = this;
1110 let diffSeverity = computeMarkerDiffSeverity(start.marker, end.marker, context.calendarSystem);
1111 if (!diffSeverity) {
1112 return this.format(start, context);
1113 }
1114 let biggestUnitForPartial = diffSeverity;
1115 if (biggestUnitForPartial > 1 && // the two dates are different in a way that's larger scale than time
1116 (standardDateProps.year === 'numeric' || standardDateProps.year === '2-digit') &&
1117 (standardDateProps.month === 'numeric' || standardDateProps.month === '2-digit') &&
1118 (standardDateProps.day === 'numeric' || standardDateProps.day === '2-digit')) {
1119 biggestUnitForPartial = 1; // make it look like the dates are only different in terms of time
1120 }
1121 let full0 = this.format(start, context);
1122 let full1 = this.format(end, context);
1123 if (full0 === full1) {
1124 return full0;
1125 }
1126 let partialDateProps = computePartialFormattingOptions(standardDateProps, biggestUnitForPartial);
1127 let partialFormattingFunc = buildFormattingFunc(partialDateProps, extendedSettings, context);
1128 let partial0 = partialFormattingFunc(start);
1129 let partial1 = partialFormattingFunc(end);
1130 let insertion = findCommonInsertion(full0, partial0, full1, partial1);
1131 let separator = extendedSettings.separator || betterDefaultSeparator || context.defaultSeparator || '';
1132 if (insertion) {
1133 return insertion.before + partial0 + separator + partial1 + insertion.after;
1134 }
1135 return full0 + separator + full1;
1136 }
1137 getSmallestUnit() {
1138 switch (this.smallestUnitNum) {
1139 case 7:
1140 case 6:
1141 case 5:
1142 return 'year';
1143 case 4:
1144 return 'month';
1145 case 3:
1146 return 'week';
1147 case 2:
1148 return 'day';
1149 default:
1150 return 'time'; // really?
1151 }
1152 }
1153 }
1154 function buildFormattingFunc(standardDateProps, extendedSettings, context) {
1155 let standardDatePropCnt = Object.keys(standardDateProps).length;
1156 if (standardDatePropCnt === 1 && standardDateProps.timeZoneName === 'short') {
1157 return (date) => (formatTimeZoneOffset(date.timeZoneOffset));
1158 }
1159 if (standardDatePropCnt === 0 && extendedSettings.week) {
1160 return (date) => (formatWeekNumber(context.computeWeekNumber(date.marker), context.weekText, context.weekTextLong, context.locale, extendedSettings.week));
1161 }
1162 return buildNativeFormattingFunc(standardDateProps, extendedSettings, context);
1163 }
1164 function buildNativeFormattingFunc(standardDateProps, extendedSettings, context) {
1165 standardDateProps = Object.assign({}, standardDateProps); // copy
1166 extendedSettings = Object.assign({}, extendedSettings); // copy
1167 sanitizeSettings(standardDateProps, extendedSettings);
1168 standardDateProps.timeZone = 'UTC'; // we leverage the only guaranteed timeZone for our UTC markers
1169 let normalFormat = new Intl.DateTimeFormat(context.locale.codes, standardDateProps);
1170 let zeroFormat; // needed?
1171 if (extendedSettings.omitZeroMinute) {
1172 let zeroProps = Object.assign({}, standardDateProps);
1173 delete zeroProps.minute; // seconds and ms were already considered in sanitizeSettings
1174 zeroFormat = new Intl.DateTimeFormat(context.locale.codes, zeroProps);
1175 }
1176 return (date) => {
1177 let { marker } = date;
1178 let format;
1179 if (zeroFormat && !marker.getUTCMinutes()) {
1180 format = zeroFormat;
1181 }
1182 else {
1183 format = normalFormat;
1184 }
1185 let s = format.format(marker);
1186 return postProcess(s, date, standardDateProps, extendedSettings, context);
1187 };
1188 }
1189 function sanitizeSettings(standardDateProps, extendedSettings) {
1190 // deal with a browser inconsistency where formatting the timezone
1191 // requires that the hour/minute be present.
1192 if (standardDateProps.timeZoneName) {
1193 if (!standardDateProps.hour) {
1194 standardDateProps.hour = '2-digit';
1195 }
1196 if (!standardDateProps.minute) {
1197 standardDateProps.minute = '2-digit';
1198 }
1199 }
1200 // only support short timezone names
1201 if (standardDateProps.timeZoneName === 'long') {
1202 standardDateProps.timeZoneName = 'short';
1203 }
1204 // if requesting to display seconds, MUST display minutes
1205 if (extendedSettings.omitZeroMinute && (standardDateProps.second || standardDateProps.millisecond)) {
1206 delete extendedSettings.omitZeroMinute;
1207 }
1208 }
1209 function postProcess(s, date, standardDateProps, extendedSettings, context) {
1210 s = s.replace(LTR_RE, ''); // remove left-to-right control chars. do first. good for other regexes
1211 if (standardDateProps.timeZoneName === 'short') {
1212 s = injectTzoStr(s, (context.timeZone === 'UTC' || date.timeZoneOffset == null) ?
1213 'UTC' : // important to normalize for IE, which does "GMT"
1214 formatTimeZoneOffset(date.timeZoneOffset));
1215 }
1216 if (extendedSettings.omitCommas) {
1217 s = s.replace(COMMA_RE, '').trim();
1218 }
1219 if (extendedSettings.omitZeroMinute) {
1220 s = s.replace(':00', ''); // zeroFormat doesn't always achieve this
1221 }
1222 // ^ do anything that might create adjacent spaces before this point,
1223 // because MERIDIEM_RE likes to eat up loading spaces
1224 if (extendedSettings.meridiem === false) {
1225 s = s.replace(MERIDIEM_RE, '').trim();
1226 }
1227 else if (extendedSettings.meridiem === 'narrow') { // a/p
1228 s = s.replace(MERIDIEM_RE, (m0, m1) => m1.toLocaleLowerCase());
1229 }
1230 else if (extendedSettings.meridiem === 'short') { // am/pm
1231 s = s.replace(MERIDIEM_RE, (m0, m1) => `${m1.toLocaleLowerCase()}m`);
1232 }
1233 else if (extendedSettings.meridiem === 'lowercase') { // other meridiem transformers already converted to lowercase
1234 s = s.replace(MERIDIEM_RE, (m0) => m0.toLocaleLowerCase());
1235 }
1236 s = s.replace(MULTI_SPACE_RE, ' ');
1237 s = s.trim();
1238 return s;
1239 }
1240 function injectTzoStr(s, tzoStr) {
1241 let replaced = false;
1242 s = s.replace(UTC_RE, () => {
1243 replaced = true;
1244 return tzoStr;
1245 });
1246 // IE11 doesn't include UTC/GMT in the original string, so append to end
1247 if (!replaced) {
1248 s += ` ${tzoStr}`;
1249 }
1250 return s;
1251 }
1252 function formatWeekNumber(num, weekText, weekTextLong, locale, display) {
1253 let parts = [];
1254 if (display === 'long') {
1255 parts.push(weekTextLong);
1256 }
1257 else if (display === 'short' || display === 'narrow') {
1258 parts.push(weekText);
1259 }
1260 if (display === 'long' || display === 'short') {
1261 parts.push(' ');
1262 }
1263 parts.push(locale.simpleNumberFormat.format(num));
1264 if (locale.options.direction === 'rtl') { // TODO: use control characters instead?
1265 parts.reverse();
1266 }
1267 return parts.join('');
1268 }
1269 // Range Formatting Utils
1270 // 0 = exactly the same
1271 // 1 = different by time
1272 // and bigger
1273 function computeMarkerDiffSeverity(d0, d1, ca) {
1274 if (ca.getMarkerYear(d0) !== ca.getMarkerYear(d1)) {
1275 return 5;
1276 }
1277 if (ca.getMarkerMonth(d0) !== ca.getMarkerMonth(d1)) {
1278 return 4;
1279 }
1280 if (ca.getMarkerDay(d0) !== ca.getMarkerDay(d1)) {
1281 return 2;
1282 }
1283 if (timeAsMs(d0) !== timeAsMs(d1)) {
1284 return 1;
1285 }
1286 return 0;
1287 }
1288 function computePartialFormattingOptions(options, biggestUnit) {
1289 let partialOptions = {};
1290 for (let name in options) {
1291 if (!(name in STANDARD_DATE_PROP_SEVERITIES) || // not a date part prop (like timeZone)
1292 STANDARD_DATE_PROP_SEVERITIES[name] <= biggestUnit) {
1293 partialOptions[name] = options[name];
1294 }
1295 }
1296 return partialOptions;
1297 }
1298 function findCommonInsertion(full0, partial0, full1, partial1) {
1299 let i0 = 0;
1300 while (i0 < full0.length) {
1301 let found0 = full0.indexOf(partial0, i0);
1302 if (found0 === -1) {
1303 break;
1304 }
1305 let before0 = full0.substr(0, found0);
1306 i0 = found0 + partial0.length;
1307 let after0 = full0.substr(i0);
1308 let i1 = 0;
1309 while (i1 < full1.length) {
1310 let found1 = full1.indexOf(partial1, i1);
1311 if (found1 === -1) {
1312 break;
1313 }
1314 let before1 = full1.substr(0, found1);
1315 i1 = found1 + partial1.length;
1316 let after1 = full1.substr(i1);
1317 if (before0 === before1 && after0 === after1) {
1318 return {
1319 before: before0,
1320 after: after0,
1321 };
1322 }
1323 }
1324 }
1325 return null;
1326 }
1327
1328 function expandZonedMarker(dateInfo, calendarSystem) {
1329 let a = calendarSystem.markerToArray(dateInfo.marker);
1330 return {
1331 marker: dateInfo.marker,
1332 timeZoneOffset: dateInfo.timeZoneOffset,
1333 array: a,
1334 year: a[0],
1335 month: a[1],
1336 day: a[2],
1337 hour: a[3],
1338 minute: a[4],
1339 second: a[5],
1340 millisecond: a[6],
1341 };
1342 }
1343
1344 function createVerboseFormattingArg(start, end, context, betterDefaultSeparator) {
1345 let startInfo = expandZonedMarker(start, context.calendarSystem);
1346 let endInfo = end ? expandZonedMarker(end, context.calendarSystem) : null;
1347 return {
1348 date: startInfo,
1349 start: startInfo,
1350 end: endInfo,
1351 timeZone: context.timeZone,
1352 localeCodes: context.locale.codes,
1353 defaultSeparator: betterDefaultSeparator || context.defaultSeparator,
1354 };
1355 }
1356
1357 /*
1358 TODO: fix the terminology of "formatter" vs "formatting func"
1359 */
1360 /*
1361 At the time of instantiation, this object does not know which cmd-formatting system it will use.
1362 It receives this at the time of formatting, as a setting.
1363 */
1364 class CmdFormatter {
1365 constructor(cmdStr) {
1366 this.cmdStr = cmdStr;
1367 }
1368 format(date, context, betterDefaultSeparator) {
1369 return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(date, null, context, betterDefaultSeparator));
1370 }
1371 formatRange(start, end, context, betterDefaultSeparator) {
1372 return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(start, end, context, betterDefaultSeparator));
1373 }
1374 }
1375
1376 class FuncFormatter {
1377 constructor(func) {
1378 this.func = func;
1379 }
1380 format(date, context, betterDefaultSeparator) {
1381 return this.func(createVerboseFormattingArg(date, null, context, betterDefaultSeparator));
1382 }
1383 formatRange(start, end, context, betterDefaultSeparator) {
1384 return this.func(createVerboseFormattingArg(start, end, context, betterDefaultSeparator));
1385 }
1386 }
1387
1388 function createFormatter(input) {
1389 if (typeof input === 'object' && input) { // non-null object
1390 return new NativeFormatter(input);
1391 }
1392 if (typeof input === 'string') {
1393 return new CmdFormatter(input);
1394 }
1395 if (typeof input === 'function') {
1396 return new FuncFormatter(input);
1397 }
1398 return null;
1399 }
1400
1401 // base options
1402 // ------------
1403 const BASE_OPTION_REFINERS = {
1404 navLinkDayClick: identity,
1405 navLinkWeekClick: identity,
1406 duration: createDuration,
1407 bootstrapFontAwesome: identity,
1408 buttonIcons: identity,
1409 customButtons: identity,
1410 defaultAllDayEventDuration: createDuration,
1411 defaultTimedEventDuration: createDuration,
1412 nextDayThreshold: createDuration,
1413 scrollTime: createDuration,
1414 scrollTimeReset: Boolean,
1415 slotMinTime: createDuration,
1416 slotMaxTime: createDuration,
1417 dayPopoverFormat: createFormatter,
1418 slotDuration: createDuration,
1419 snapDuration: createDuration,
1420 headerToolbar: identity,
1421 footerToolbar: identity,
1422 defaultRangeSeparator: String,
1423 titleRangeSeparator: String,
1424 forceEventDuration: Boolean,
1425 dayHeaders: Boolean,
1426 dayHeaderFormat: createFormatter,
1427 dayHeaderClassNames: identity,
1428 dayHeaderContent: identity,
1429 dayHeaderDidMount: identity,
1430 dayHeaderWillUnmount: identity,
1431 dayCellClassNames: identity,
1432 dayCellContent: identity,
1433 dayCellDidMount: identity,
1434 dayCellWillUnmount: identity,
1435 initialView: String,
1436 aspectRatio: Number,
1437 weekends: Boolean,
1438 weekNumberCalculation: identity,
1439 weekNumbers: Boolean,
1440 weekNumberClassNames: identity,
1441 weekNumberContent: identity,
1442 weekNumberDidMount: identity,
1443 weekNumberWillUnmount: identity,
1444 editable: Boolean,
1445 viewClassNames: identity,
1446 viewDidMount: identity,
1447 viewWillUnmount: identity,
1448 nowIndicator: Boolean,
1449 nowIndicatorClassNames: identity,
1450 nowIndicatorContent: identity,
1451 nowIndicatorDidMount: identity,
1452 nowIndicatorWillUnmount: identity,
1453 showNonCurrentDates: Boolean,
1454 lazyFetching: Boolean,
1455 startParam: String,
1456 endParam: String,
1457 timeZoneParam: String,
1458 timeZone: String,
1459 locales: identity,
1460 locale: identity,
1461 themeSystem: String,
1462 dragRevertDuration: Number,
1463 dragScroll: Boolean,
1464 allDayMaintainDuration: Boolean,
1465 unselectAuto: Boolean,
1466 dropAccept: identity,
1467 eventOrder: parseFieldSpecs,
1468 eventOrderStrict: Boolean,
1469 handleWindowResize: Boolean,
1470 windowResizeDelay: Number,
1471 longPressDelay: Number,
1472 eventDragMinDistance: Number,
1473 expandRows: Boolean,
1474 height: identity,
1475 contentHeight: identity,
1476 direction: String,
1477 weekNumberFormat: createFormatter,
1478 eventResizableFromStart: Boolean,
1479 displayEventTime: Boolean,
1480 displayEventEnd: Boolean,
1481 weekText: String,
1482 weekTextLong: String,
1483 progressiveEventRendering: Boolean,
1484 businessHours: identity,
1485 initialDate: identity,
1486 now: identity,
1487 eventDataTransform: identity,
1488 stickyHeaderDates: identity,
1489 stickyFooterScrollbar: identity,
1490 viewHeight: identity,
1491 defaultAllDay: Boolean,
1492 eventSourceFailure: identity,
1493 eventSourceSuccess: identity,
1494 eventDisplay: String,
1495 eventStartEditable: Boolean,
1496 eventDurationEditable: Boolean,
1497 eventOverlap: identity,
1498 eventConstraint: identity,
1499 eventAllow: identity,
1500 eventBackgroundColor: String,
1501 eventBorderColor: String,
1502 eventTextColor: String,
1503 eventColor: String,
1504 eventClassNames: identity,
1505 eventContent: identity,
1506 eventDidMount: identity,
1507 eventWillUnmount: identity,
1508 selectConstraint: identity,
1509 selectOverlap: identity,
1510 selectAllow: identity,
1511 droppable: Boolean,
1512 unselectCancel: String,
1513 slotLabelFormat: identity,
1514 slotLaneClassNames: identity,
1515 slotLaneContent: identity,
1516 slotLaneDidMount: identity,
1517 slotLaneWillUnmount: identity,
1518 slotLabelClassNames: identity,
1519 slotLabelContent: identity,
1520 slotLabelDidMount: identity,
1521 slotLabelWillUnmount: identity,
1522 dayMaxEvents: identity,
1523 dayMaxEventRows: identity,
1524 dayMinWidth: Number,
1525 slotLabelInterval: createDuration,
1526 allDayText: String,
1527 allDayClassNames: identity,
1528 allDayContent: identity,
1529 allDayDidMount: identity,
1530 allDayWillUnmount: identity,
1531 slotMinWidth: Number,
1532 navLinks: Boolean,
1533 eventTimeFormat: createFormatter,
1534 rerenderDelay: Number,
1535 moreLinkText: identity,
1536 moreLinkHint: identity,
1537 selectMinDistance: Number,
1538 selectable: Boolean,
1539 selectLongPressDelay: Number,
1540 eventLongPressDelay: Number,
1541 selectMirror: Boolean,
1542 eventMaxStack: Number,
1543 eventMinHeight: Number,
1544 eventMinWidth: Number,
1545 eventShortHeight: Number,
1546 slotEventOverlap: Boolean,
1547 plugins: identity,
1548 firstDay: Number,
1549 dayCount: Number,
1550 dateAlignment: String,
1551 dateIncrement: createDuration,
1552 hiddenDays: identity,
1553 fixedWeekCount: Boolean,
1554 validRange: identity,
1555 visibleRange: identity,
1556 titleFormat: identity,
1557 eventInteractive: Boolean,
1558 // only used by list-view, but languages define the value, so we need it in base options
1559 noEventsText: String,
1560 viewHint: identity,
1561 navLinkHint: identity,
1562 closeHint: String,
1563 timeHint: String,
1564 eventHint: String,
1565 moreLinkClick: identity,
1566 moreLinkClassNames: identity,
1567 moreLinkContent: identity,
1568 moreLinkDidMount: identity,
1569 moreLinkWillUnmount: identity,
1570 monthStartFormat: createFormatter,
1571 // for connectors
1572 // (can't be part of plugin system b/c must be provided at runtime)
1573 handleCustomRendering: identity,
1574 customRenderingMetaMap: identity,
1575 customRenderingReplaces: Boolean,
1576 };
1577 // do NOT give a type here. need `typeof BASE_OPTION_DEFAULTS` to give real results.
1578 // raw values.
1579 const BASE_OPTION_DEFAULTS = {
1580 eventDisplay: 'auto',
1581 defaultRangeSeparator: ' - ',
1582 titleRangeSeparator: ' \u2013 ',
1583 defaultTimedEventDuration: '01:00:00',
1584 defaultAllDayEventDuration: { day: 1 },
1585 forceEventDuration: false,
1586 nextDayThreshold: '00:00:00',
1587 dayHeaders: true,
1588 initialView: '',
1589 aspectRatio: 1.35,
1590 headerToolbar: {
1591 start: 'title',
1592 center: '',
1593 end: 'today prev,next',
1594 },
1595 weekends: true,
1596 weekNumbers: false,
1597 weekNumberCalculation: 'local',
1598 editable: false,
1599 nowIndicator: false,
1600 scrollTime: '06:00:00',
1601 scrollTimeReset: true,
1602 slotMinTime: '00:00:00',
1603 slotMaxTime: '24:00:00',
1604 showNonCurrentDates: true,
1605 lazyFetching: true,
1606 startParam: 'start',
1607 endParam: 'end',
1608 timeZoneParam: 'timeZone',
1609 timeZone: 'local',
1610 locales: [],
1611 locale: '',
1612 themeSystem: 'standard',
1613 dragRevertDuration: 500,
1614 dragScroll: true,
1615 allDayMaintainDuration: false,
1616 unselectAuto: true,
1617 dropAccept: '*',
1618 eventOrder: 'start,-duration,allDay,title',
1619 dayPopoverFormat: { month: 'long', day: 'numeric', year: 'numeric' },
1620 handleWindowResize: true,
1621 windowResizeDelay: 100,
1622 longPressDelay: 1000,
1623 eventDragMinDistance: 5,
1624 expandRows: false,
1625 navLinks: false,
1626 selectable: false,
1627 eventMinHeight: 15,
1628 eventMinWidth: 30,
1629 eventShortHeight: 30,
1630 monthStartFormat: { month: 'long', day: 'numeric' },
1631 };
1632 // calendar listeners
1633 // ------------------
1634 const CALENDAR_LISTENER_REFINERS = {
1635 datesSet: identity,
1636 eventsSet: identity,
1637 eventAdd: identity,
1638 eventChange: identity,
1639 eventRemove: identity,
1640 windowResize: identity,
1641 eventClick: identity,
1642 eventMouseEnter: identity,
1643 eventMouseLeave: identity,
1644 select: identity,
1645 unselect: identity,
1646 loading: identity,
1647 // internal
1648 _unmount: identity,
1649 _beforeprint: identity,
1650 _afterprint: identity,
1651 _noEventDrop: identity,
1652 _noEventResize: identity,
1653 _resize: identity,
1654 _scrollRequest: identity,
1655 };
1656 // calendar-specific options
1657 // -------------------------
1658 const CALENDAR_OPTION_REFINERS = {
1659 buttonText: identity,
1660 buttonHints: identity,
1661 views: identity,
1662 plugins: identity,
1663 initialEvents: identity,
1664 events: identity,
1665 eventSources: identity,
1666 };
1667 const COMPLEX_OPTION_COMPARATORS = {
1668 headerToolbar: isMaybeObjectsEqual,
1669 footerToolbar: isMaybeObjectsEqual,
1670 buttonText: isMaybeObjectsEqual,
1671 buttonHints: isMaybeObjectsEqual,
1672 buttonIcons: isMaybeObjectsEqual,
1673 dateIncrement: isMaybeObjectsEqual,
1674 plugins: isMaybeArraysEqual,
1675 events: isMaybeArraysEqual,
1676 eventSources: isMaybeArraysEqual,
1677 ['resources']: isMaybeArraysEqual,
1678 };
1679 function isMaybeObjectsEqual(a, b) {
1680 if (typeof a === 'object' && typeof b === 'object' && a && b) { // both non-null objects
1681 return isPropsEqual(a, b);
1682 }
1683 return a === b;
1684 }
1685 function isMaybeArraysEqual(a, b) {
1686 if (Array.isArray(a) && Array.isArray(b)) {
1687 return isArraysEqual(a, b);
1688 }
1689 return a === b;
1690 }
1691 // view-specific options
1692 // ---------------------
1693 const VIEW_OPTION_REFINERS = {
1694 type: String,
1695 component: identity,
1696 buttonText: String,
1697 buttonTextKey: String,
1698 dateProfileGeneratorClass: identity,
1699 usesMinMaxTime: Boolean,
1700 classNames: identity,
1701 content: identity,
1702 didMount: identity,
1703 willUnmount: identity,
1704 };
1705 // util funcs
1706 // ----------------------------------------------------------------------------------------------------
1707 function mergeRawOptions(optionSets) {
1708 return mergeProps(optionSets, COMPLEX_OPTION_COMPARATORS);
1709 }
1710 function refineProps(input, refiners) {
1711 let refined = {};
1712 let extra = {};
1713 for (let propName in refiners) {
1714 if (propName in input) {
1715 refined[propName] = refiners[propName](input[propName]);
1716 }
1717 }
1718 for (let propName in input) {
1719 if (!(propName in refiners)) {
1720 extra[propName] = input[propName];
1721 }
1722 }
1723 return { refined, extra };
1724 }
1725 function identity(raw) {
1726 return raw;
1727 }
1728
1729 function createEventInstance(defId, range, forcedStartTzo, forcedEndTzo) {
1730 return {
1731 instanceId: guid(),
1732 defId,
1733 range,
1734 forcedStartTzo: forcedStartTzo == null ? null : forcedStartTzo,
1735 forcedEndTzo: forcedEndTzo == null ? null : forcedEndTzo,
1736 };
1737 }
1738
1739 function parseRecurring(refined, defaultAllDay, dateEnv, recurringTypes) {
1740 for (let i = 0; i < recurringTypes.length; i += 1) {
1741 let parsed = recurringTypes[i].parse(refined, dateEnv);
1742 if (parsed) {
1743 let { allDay } = refined;
1744 if (allDay == null) {
1745 allDay = defaultAllDay;
1746 if (allDay == null) {
1747 allDay = parsed.allDayGuess;
1748 if (allDay == null) {
1749 allDay = false;
1750 }
1751 }
1752 }
1753 return {
1754 allDay,
1755 duration: parsed.duration,
1756 typeData: parsed.typeData,
1757 typeId: i,
1758 };
1759 }
1760 }
1761 return null;
1762 }
1763 function expandRecurring(eventStore, framingRange, context) {
1764 let { dateEnv, pluginHooks, options } = context;
1765 let { defs, instances } = eventStore;
1766 // remove existing recurring instances
1767 // TODO: bad. always expand events as a second step
1768 instances = filterHash(instances, (instance) => !defs[instance.defId].recurringDef);
1769 for (let defId in defs) {
1770 let def = defs[defId];
1771 if (def.recurringDef) {
1772 let { duration } = def.recurringDef;
1773 if (!duration) {
1774 duration = def.allDay ?
1775 options.defaultAllDayEventDuration :
1776 options.defaultTimedEventDuration;
1777 }
1778 let starts = expandRecurringRanges(def, duration, framingRange, dateEnv, pluginHooks.recurringTypes);
1779 for (let start of starts) {
1780 let instance = createEventInstance(defId, {
1781 start,
1782 end: dateEnv.add(start, duration),
1783 });
1784 instances[instance.instanceId] = instance;
1785 }
1786 }
1787 }
1788 return { defs, instances };
1789 }
1790 /*
1791 Event MUST have a recurringDef
1792 */
1793 function expandRecurringRanges(eventDef, duration, framingRange, dateEnv, recurringTypes) {
1794 let typeDef = recurringTypes[eventDef.recurringDef.typeId];
1795 let markers = typeDef.expand(eventDef.recurringDef.typeData, {
1796 start: dateEnv.subtract(framingRange.start, duration),
1797 end: framingRange.end,
1798 }, dateEnv);
1799 // the recurrence plugins don't guarantee that all-day events are start-of-day, so we have to
1800 if (eventDef.allDay) {
1801 markers = markers.map(startOfDay);
1802 }
1803 return markers;
1804 }
1805
1806 function parseEvents(rawEvents, eventSource, context, allowOpenRange, defIdMap, instanceIdMap) {
1807 let eventStore = createEmptyEventStore();
1808 let eventRefiners = buildEventRefiners(context);
1809 for (let rawEvent of rawEvents) {
1810 let tuple = parseEvent(rawEvent, eventSource, context, allowOpenRange, eventRefiners, defIdMap, instanceIdMap);
1811 if (tuple) {
1812 eventTupleToStore(tuple, eventStore);
1813 }
1814 }
1815 return eventStore;
1816 }
1817 function eventTupleToStore(tuple, eventStore = createEmptyEventStore()) {
1818 eventStore.defs[tuple.def.defId] = tuple.def;
1819 if (tuple.instance) {
1820 eventStore.instances[tuple.instance.instanceId] = tuple.instance;
1821 }
1822 return eventStore;
1823 }
1824 // retrieves events that have the same groupId as the instance specified by `instanceId`
1825 // or they are the same as the instance.
1826 // why might instanceId not be in the store? an event from another calendar?
1827 function getRelevantEvents(eventStore, instanceId) {
1828 let instance = eventStore.instances[instanceId];
1829 if (instance) {
1830 let def = eventStore.defs[instance.defId];
1831 // get events/instances with same group
1832 let newStore = filterEventStoreDefs(eventStore, (lookDef) => isEventDefsGrouped(def, lookDef));
1833 // add the original
1834 // TODO: wish we could use eventTupleToStore or something like it
1835 newStore.defs[def.defId] = def;
1836 newStore.instances[instance.instanceId] = instance;
1837 return newStore;
1838 }
1839 return createEmptyEventStore();
1840 }
1841 function isEventDefsGrouped(def0, def1) {
1842 return Boolean(def0.groupId && def0.groupId === def1.groupId);
1843 }
1844 function createEmptyEventStore() {
1845 return { defs: {}, instances: {} };
1846 }
1847 function mergeEventStores(store0, store1) {
1848 return {
1849 defs: Object.assign(Object.assign({}, store0.defs), store1.defs),
1850 instances: Object.assign(Object.assign({}, store0.instances), store1.instances),
1851 };
1852 }
1853 function filterEventStoreDefs(eventStore, filterFunc) {
1854 let defs = filterHash(eventStore.defs, filterFunc);
1855 let instances = filterHash(eventStore.instances, (instance) => (defs[instance.defId] // still exists?
1856 ));
1857 return { defs, instances };
1858 }
1859 function excludeSubEventStore(master, sub) {
1860 let { defs, instances } = master;
1861 let filteredDefs = {};
1862 let filteredInstances = {};
1863 for (let defId in defs) {
1864 if (!sub.defs[defId]) { // not explicitly excluded
1865 filteredDefs[defId] = defs[defId];
1866 }
1867 }
1868 for (let instanceId in instances) {
1869 if (!sub.instances[instanceId] && // not explicitly excluded
1870 filteredDefs[instances[instanceId].defId] // def wasn't filtered away
1871 ) {
1872 filteredInstances[instanceId] = instances[instanceId];
1873 }
1874 }
1875 return {
1876 defs: filteredDefs,
1877 instances: filteredInstances,
1878 };
1879 }
1880
1881 function normalizeConstraint(input, context) {
1882 if (Array.isArray(input)) {
1883 return parseEvents(input, null, context, true); // allowOpenRange=true
1884 }
1885 if (typeof input === 'object' && input) { // non-null object
1886 return parseEvents([input], null, context, true); // allowOpenRange=true
1887 }
1888 if (input != null) {
1889 return String(input);
1890 }
1891 return null;
1892 }
1893
1894 function parseClassNames(raw) {
1895 if (Array.isArray(raw)) {
1896 return raw;
1897 }
1898 if (typeof raw === 'string') {
1899 return raw.split(/\s+/);
1900 }
1901 return [];
1902 }
1903
1904 // TODO: better called "EventSettings" or "EventConfig"
1905 // TODO: move this file into structs
1906 // TODO: separate constraint/overlap/allow, because selection uses only that, not other props
1907 const EVENT_UI_REFINERS = {
1908 display: String,
1909 editable: Boolean,
1910 startEditable: Boolean,
1911 durationEditable: Boolean,
1912 constraint: identity,
1913 overlap: identity,
1914 allow: identity,
1915 className: parseClassNames,
1916 classNames: parseClassNames,
1917 color: String,
1918 backgroundColor: String,
1919 borderColor: String,
1920 textColor: String,
1921 };
1922 const EMPTY_EVENT_UI = {
1923 display: null,
1924 startEditable: null,
1925 durationEditable: null,
1926 constraints: [],
1927 overlap: null,
1928 allows: [],
1929 backgroundColor: '',
1930 borderColor: '',
1931 textColor: '',
1932 classNames: [],
1933 };
1934 function createEventUi(refined, context) {
1935 let constraint = normalizeConstraint(refined.constraint, context);
1936 return {
1937 display: refined.display || null,
1938 startEditable: refined.startEditable != null ? refined.startEditable : refined.editable,
1939 durationEditable: refined.durationEditable != null ? refined.durationEditable : refined.editable,
1940 constraints: constraint != null ? [constraint] : [],
1941 overlap: refined.overlap != null ? refined.overlap : null,
1942 allows: refined.allow != null ? [refined.allow] : [],
1943 backgroundColor: refined.backgroundColor || refined.color || '',
1944 borderColor: refined.borderColor || refined.color || '',
1945 textColor: refined.textColor || '',
1946 classNames: (refined.className || []).concat(refined.classNames || []), // join singular and plural
1947 };
1948 }
1949 // TODO: prevent against problems with <2 args!
1950 function combineEventUis(uis) {
1951 return uis.reduce(combineTwoEventUis, EMPTY_EVENT_UI);
1952 }
1953 function combineTwoEventUis(item0, item1) {
1954 return {
1955 display: item1.display != null ? item1.display : item0.display,
1956 startEditable: item1.startEditable != null ? item1.startEditable : item0.startEditable,
1957 durationEditable: item1.durationEditable != null ? item1.durationEditable : item0.durationEditable,
1958 constraints: item0.constraints.concat(item1.constraints),
1959 overlap: typeof item1.overlap === 'boolean' ? item1.overlap : item0.overlap,
1960 allows: item0.allows.concat(item1.allows),
1961 backgroundColor: item1.backgroundColor || item0.backgroundColor,
1962 borderColor: item1.borderColor || item0.borderColor,
1963 textColor: item1.textColor || item0.textColor,
1964 classNames: item0.classNames.concat(item1.classNames),
1965 };
1966 }
1967
1968 const EVENT_NON_DATE_REFINERS = {
1969 id: String,
1970 groupId: String,
1971 title: String,
1972 url: String,
1973 interactive: Boolean,
1974 };
1975 const EVENT_DATE_REFINERS = {
1976 start: identity,
1977 end: identity,
1978 date: identity,
1979 allDay: Boolean,
1980 };
1981 const EVENT_REFINERS = Object.assign(Object.assign(Object.assign({}, EVENT_NON_DATE_REFINERS), EVENT_DATE_REFINERS), { extendedProps: identity });
1982 function parseEvent(raw, eventSource, context, allowOpenRange, refiners = buildEventRefiners(context), defIdMap, instanceIdMap) {
1983 let { refined, extra } = refineEventDef(raw, context, refiners);
1984 let defaultAllDay = computeIsDefaultAllDay(eventSource, context);
1985 let recurringRes = parseRecurring(refined, defaultAllDay, context.dateEnv, context.pluginHooks.recurringTypes);
1986 if (recurringRes) {
1987 let def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', recurringRes.allDay, Boolean(recurringRes.duration), context, defIdMap);
1988 def.recurringDef = {
1989 typeId: recurringRes.typeId,
1990 typeData: recurringRes.typeData,
1991 duration: recurringRes.duration,
1992 };
1993 return { def, instance: null };
1994 }
1995 let singleRes = parseSingle(refined, defaultAllDay, context, allowOpenRange);
1996 if (singleRes) {
1997 let def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', singleRes.allDay, singleRes.hasEnd, context, defIdMap);
1998 let instance = createEventInstance(def.defId, singleRes.range, singleRes.forcedStartTzo, singleRes.forcedEndTzo);
1999 if (instanceIdMap && def.publicId && instanceIdMap[def.publicId]) {
2000 instance.instanceId = instanceIdMap[def.publicId];
2001 }
2002 return { def, instance };
2003 }
2004 return null;
2005 }
2006 function refineEventDef(raw, context, refiners = buildEventRefiners(context)) {
2007 return refineProps(raw, refiners);
2008 }
2009 function buildEventRefiners(context) {
2010 return Object.assign(Object.assign(Object.assign({}, EVENT_UI_REFINERS), EVENT_REFINERS), context.pluginHooks.eventRefiners);
2011 }
2012 /*
2013 Will NOT populate extendedProps with the leftover properties.
2014 Will NOT populate date-related props.
2015 */
2016 function parseEventDef(refined, extra, sourceId, allDay, hasEnd, context, defIdMap) {
2017 let def = {
2018 title: refined.title || '',
2019 groupId: refined.groupId || '',
2020 publicId: refined.id || '',
2021 url: refined.url || '',
2022 recurringDef: null,
2023 defId: ((defIdMap && refined.id) ? defIdMap[refined.id] : '') || guid(),
2024 sourceId,
2025 allDay,
2026 hasEnd,
2027 interactive: refined.interactive,
2028 ui: createEventUi(refined, context),
2029 extendedProps: Object.assign(Object.assign({}, (refined.extendedProps || {})), extra),
2030 };
2031 for (let memberAdder of context.pluginHooks.eventDefMemberAdders) {
2032 Object.assign(def, memberAdder(refined));
2033 }
2034 // help out EventImpl from having user modify props
2035 Object.freeze(def.ui.classNames);
2036 Object.freeze(def.extendedProps);
2037 return def;
2038 }
2039 function parseSingle(refined, defaultAllDay, context, allowOpenRange) {
2040 let { allDay } = refined;
2041 let startMeta;
2042 let startMarker = null;
2043 let hasEnd = false;
2044 let endMeta;
2045 let endMarker = null;
2046 let startInput = refined.start != null ? refined.start : refined.date;
2047 startMeta = context.dateEnv.createMarkerMeta(startInput);
2048 if (startMeta) {
2049 startMarker = startMeta.marker;
2050 }
2051 else if (!allowOpenRange) {
2052 return null;
2053 }
2054 if (refined.end != null) {
2055 endMeta = context.dateEnv.createMarkerMeta(refined.end);
2056 }
2057 if (allDay == null) {
2058 if (defaultAllDay != null) {
2059 allDay = defaultAllDay;
2060 }
2061 else {
2062 // fall back to the date props LAST
2063 allDay = (!startMeta || startMeta.isTimeUnspecified) &&
2064 (!endMeta || endMeta.isTimeUnspecified);
2065 }
2066 }
2067 if (allDay && startMarker) {
2068 startMarker = startOfDay(startMarker);
2069 }
2070 if (endMeta) {
2071 endMarker = endMeta.marker;
2072 if (allDay) {
2073 endMarker = startOfDay(endMarker);
2074 }
2075 if (startMarker && endMarker <= startMarker) {
2076 endMarker = null;
2077 }
2078 }
2079 if (endMarker) {
2080 hasEnd = true;
2081 }
2082 else if (!allowOpenRange) {
2083 hasEnd = context.options.forceEventDuration || false;
2084 endMarker = context.dateEnv.add(startMarker, allDay ?
2085 context.options.defaultAllDayEventDuration :
2086 context.options.defaultTimedEventDuration);
2087 }
2088 return {
2089 allDay,
2090 hasEnd,
2091 range: { start: startMarker, end: endMarker },
2092 forcedStartTzo: startMeta ? startMeta.forcedTzo : null,
2093 forcedEndTzo: endMeta ? endMeta.forcedTzo : null,
2094 };
2095 }
2096 function computeIsDefaultAllDay(eventSource, context) {
2097 let res = null;
2098 if (eventSource) {
2099 res = eventSource.defaultAllDay;
2100 }
2101 if (res == null) {
2102 res = context.options.defaultAllDay;
2103 }
2104 return res;
2105 }
2106
2107 const DEF_DEFAULTS = {
2108 startTime: '09:00',
2109 endTime: '17:00',
2110 daysOfWeek: [1, 2, 3, 4, 5],
2111 display: 'inverse-background',
2112 classNames: 'fc-non-business',
2113 groupId: '_businessHours', // so multiple defs get grouped
2114 };
2115 /*
2116 TODO: pass around as EventDefHash!!!
2117 */
2118 function parseBusinessHours(input, context) {
2119 return parseEvents(refineInputs(input), null, context);
2120 }
2121 function refineInputs(input) {
2122 let rawDefs;
2123 if (input === true) {
2124 rawDefs = [{}]; // will get DEF_DEFAULTS verbatim
2125 }
2126 else if (Array.isArray(input)) {
2127 // if specifying an array, every sub-definition NEEDS a day-of-week
2128 rawDefs = input.filter((rawDef) => rawDef.daysOfWeek);
2129 }
2130 else if (typeof input === 'object' && input) { // non-null object
2131 rawDefs = [input];
2132 }
2133 else { // is probably false
2134 rawDefs = [];
2135 }
2136 rawDefs = rawDefs.map((rawDef) => (Object.assign(Object.assign({}, DEF_DEFAULTS), rawDef)));
2137 return rawDefs;
2138 }
2139
2140 /* Date stuff that doesn't belong in datelib core
2141 ----------------------------------------------------------------------------------------------------------------------*/
2142 // given a timed range, computes an all-day range that has the same exact duration,
2143 // but whose start time is aligned with the start of the day.
2144 function computeAlignedDayRange(timedRange) {
2145 let dayCnt = Math.floor(diffDays(timedRange.start, timedRange.end)) || 1;
2146 let start = startOfDay(timedRange.start);
2147 let end = addDays(start, dayCnt);
2148 return { start, end };
2149 }
2150 // given a timed range, computes an all-day range based on how for the end date bleeds into the next day
2151 // TODO: give nextDayThreshold a default arg
2152 function computeVisibleDayRange(timedRange, nextDayThreshold = createDuration(0)) {
2153 let startDay = null;
2154 let endDay = null;
2155 if (timedRange.end) {
2156 endDay = startOfDay(timedRange.end);
2157 let endTimeMS = timedRange.end.valueOf() - endDay.valueOf(); // # of milliseconds into `endDay`
2158 // If the end time is actually inclusively part of the next day and is equal to or
2159 // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`.
2160 // Otherwise, leaving it as inclusive will cause it to exclude `endDay`.
2161 if (endTimeMS && endTimeMS >= asRoughMs(nextDayThreshold)) {
2162 endDay = addDays(endDay, 1);
2163 }
2164 }
2165 if (timedRange.start) {
2166 startDay = startOfDay(timedRange.start); // the beginning of the day the range starts
2167 // If end is within `startDay` but not past nextDayThreshold, assign the default duration of one day.
2168 if (endDay && endDay <= startDay) {
2169 endDay = addDays(startDay, 1);
2170 }
2171 }
2172 return { start: startDay, end: endDay };
2173 }
2174 // spans from one day into another?
2175 function isMultiDayRange(range) {
2176 let visibleRange = computeVisibleDayRange(range);
2177 return diffDays(visibleRange.start, visibleRange.end) > 1;
2178 }
2179 function diffDates(date0, date1, dateEnv, largeUnit) {
2180 if (largeUnit === 'year') {
2181 return createDuration(dateEnv.diffWholeYears(date0, date1), 'year');
2182 }
2183 if (largeUnit === 'month') {
2184 return createDuration(dateEnv.diffWholeMonths(date0, date1), 'month');
2185 }
2186 return diffDayAndTime(date0, date1); // returns a duration
2187 }
2188
2189 function pointInsideRect(point, rect) {
2190 return point.left >= rect.left &&
2191 point.left < rect.right &&
2192 point.top >= rect.top &&
2193 point.top < rect.bottom;
2194 }
2195 // Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false
2196 function intersectRects(rect1, rect2) {
2197 let res = {
2198 left: Math.max(rect1.left, rect2.left),
2199 right: Math.min(rect1.right, rect2.right),
2200 top: Math.max(rect1.top, rect2.top),
2201 bottom: Math.min(rect1.bottom, rect2.bottom),
2202 };
2203 if (res.left < res.right && res.top < res.bottom) {
2204 return res;
2205 }
2206 return false;
2207 }
2208 function translateRect(rect, deltaX, deltaY) {
2209 return {
2210 left: rect.left + deltaX,
2211 right: rect.right + deltaX,
2212 top: rect.top + deltaY,
2213 bottom: rect.bottom + deltaY,
2214 };
2215 }
2216 // Returns a new point that will have been moved to reside within the given rectangle
2217 function constrainPoint(point, rect) {
2218 return {
2219 left: Math.min(Math.max(point.left, rect.left), rect.right),
2220 top: Math.min(Math.max(point.top, rect.top), rect.bottom),
2221 };
2222 }
2223 // Returns a point that is the center of the given rectangle
2224 function getRectCenter(rect) {
2225 return {
2226 left: (rect.left + rect.right) / 2,
2227 top: (rect.top + rect.bottom) / 2,
2228 };
2229 }
2230 // Subtracts point2's coordinates from point1's coordinates, returning a delta
2231 function diffPoints(point1, point2) {
2232 return {
2233 left: point1.left - point2.left,
2234 top: point1.top - point2.top,
2235 };
2236 }
2237
2238 let canVGrowWithinCell;
2239 function getCanVGrowWithinCell() {
2240 if (canVGrowWithinCell == null) {
2241 canVGrowWithinCell = computeCanVGrowWithinCell();
2242 }
2243 return canVGrowWithinCell;
2244 }
2245 function computeCanVGrowWithinCell() {
2246 // for SSR, because this function is call immediately at top-level
2247 // TODO: just make this logic execute top-level, immediately, instead of doing lazily
2248 if (typeof document === 'undefined') {
2249 return true;
2250 }
2251 let el = document.createElement('div');
2252 el.style.position = 'absolute';
2253 el.style.top = '0px';
2254 el.style.left = '0px';
2255 el.innerHTML = '<table><tr><td><div></div></td></tr></table>';
2256 el.querySelector('table').style.height = '100px';
2257 el.querySelector('div').style.height = '100%';
2258 document.body.appendChild(el);
2259 let div = el.querySelector('div');
2260 let possible = div.offsetHeight > 0;
2261 document.body.removeChild(el);
2262 return possible;
2263 }
2264
2265 const EMPTY_EVENT_STORE = createEmptyEventStore(); // for purecomponents. TODO: keep elsewhere
2266 class Splitter {
2267 constructor() {
2268 this.getKeysForEventDefs = memoize(this._getKeysForEventDefs);
2269 this.splitDateSelection = memoize(this._splitDateSpan);
2270 this.splitEventStore = memoize(this._splitEventStore);
2271 this.splitIndividualUi = memoize(this._splitIndividualUi);
2272 this.splitEventDrag = memoize(this._splitInteraction);
2273 this.splitEventResize = memoize(this._splitInteraction);
2274 this.eventUiBuilders = {}; // TODO: typescript protection
2275 }
2276 splitProps(props) {
2277 let keyInfos = this.getKeyInfo(props);
2278 let defKeys = this.getKeysForEventDefs(props.eventStore);
2279 let dateSelections = this.splitDateSelection(props.dateSelection);
2280 let individualUi = this.splitIndividualUi(props.eventUiBases, defKeys); // the individual *bases*
2281 let eventStores = this.splitEventStore(props.eventStore, defKeys);
2282 let eventDrags = this.splitEventDrag(props.eventDrag);
2283 let eventResizes = this.splitEventResize(props.eventResize);
2284 let splitProps = {};
2285 this.eventUiBuilders = mapHash(keyInfos, (info, key) => this.eventUiBuilders[key] || memoize(buildEventUiForKey));
2286 for (let key in keyInfos) {
2287 let keyInfo = keyInfos[key];
2288 let eventStore = eventStores[key] || EMPTY_EVENT_STORE;
2289 let buildEventUi = this.eventUiBuilders[key];
2290 splitProps[key] = {
2291 businessHours: keyInfo.businessHours || props.businessHours,
2292 dateSelection: dateSelections[key] || null,
2293 eventStore,
2294 eventUiBases: buildEventUi(props.eventUiBases[''], keyInfo.ui, individualUi[key]),
2295 eventSelection: eventStore.instances[props.eventSelection] ? props.eventSelection : '',
2296 eventDrag: eventDrags[key] || null,
2297 eventResize: eventResizes[key] || null,
2298 };
2299 }
2300 return splitProps;
2301 }
2302 _splitDateSpan(dateSpan) {
2303 let dateSpans = {};
2304 if (dateSpan) {
2305 let keys = this.getKeysForDateSpan(dateSpan);
2306 for (let key of keys) {
2307 dateSpans[key] = dateSpan;
2308 }
2309 }
2310 return dateSpans;
2311 }
2312 _getKeysForEventDefs(eventStore) {
2313 return mapHash(eventStore.defs, (eventDef) => this.getKeysForEventDef(eventDef));
2314 }
2315 _splitEventStore(eventStore, defKeys) {
2316 let { defs, instances } = eventStore;
2317 let splitStores = {};
2318 for (let defId in defs) {
2319 for (let key of defKeys[defId]) {
2320 if (!splitStores[key]) {
2321 splitStores[key] = createEmptyEventStore();
2322 }
2323 splitStores[key].defs[defId] = defs[defId];
2324 }
2325 }
2326 for (let instanceId in instances) {
2327 let instance = instances[instanceId];
2328 for (let key of defKeys[instance.defId]) {
2329 if (splitStores[key]) { // must have already been created
2330 splitStores[key].instances[instanceId] = instance;
2331 }
2332 }
2333 }
2334 return splitStores;
2335 }
2336 _splitIndividualUi(eventUiBases, defKeys) {
2337 let splitHashes = {};
2338 for (let defId in eventUiBases) {
2339 if (defId) { // not the '' key
2340 for (let key of defKeys[defId]) {
2341 if (!splitHashes[key]) {
2342 splitHashes[key] = {};
2343 }
2344 splitHashes[key][defId] = eventUiBases[defId];
2345 }
2346 }
2347 }
2348 return splitHashes;
2349 }
2350 _splitInteraction(interaction) {
2351 let splitStates = {};
2352 if (interaction) {
2353 let affectedStores = this._splitEventStore(interaction.affectedEvents, this._getKeysForEventDefs(interaction.affectedEvents));
2354 // can't rely on defKeys because event data is mutated
2355 let mutatedKeysByDefId = this._getKeysForEventDefs(interaction.mutatedEvents);
2356 let mutatedStores = this._splitEventStore(interaction.mutatedEvents, mutatedKeysByDefId);
2357 let populate = (key) => {
2358 if (!splitStates[key]) {
2359 splitStates[key] = {
2360 affectedEvents: affectedStores[key] || EMPTY_EVENT_STORE,
2361 mutatedEvents: mutatedStores[key] || EMPTY_EVENT_STORE,
2362 isEvent: interaction.isEvent,
2363 };
2364 }
2365 };
2366 for (let key in affectedStores) {
2367 populate(key);
2368 }
2369 for (let key in mutatedStores) {
2370 populate(key);
2371 }
2372 }
2373 return splitStates;
2374 }
2375 }
2376 function buildEventUiForKey(allUi, eventUiForKey, individualUi) {
2377 let baseParts = [];
2378 if (allUi) {
2379 baseParts.push(allUi);
2380 }
2381 if (eventUiForKey) {
2382 baseParts.push(eventUiForKey);
2383 }
2384 let stuff = {
2385 '': combineEventUis(baseParts),
2386 };
2387 if (individualUi) {
2388 Object.assign(stuff, individualUi);
2389 }
2390 return stuff;
2391 }
2392
2393 function parseRange(input, dateEnv) {
2394 let start = null;
2395 let end = null;
2396 if (input.start) {
2397 start = dateEnv.createMarker(input.start);
2398 }
2399 if (input.end) {
2400 end = dateEnv.createMarker(input.end);
2401 }
2402 if (!start && !end) {
2403 return null;
2404 }
2405 if (start && end && end < start) {
2406 return null;
2407 }
2408 return { start, end };
2409 }
2410 // SIDE-EFFECT: will mutate ranges.
2411 // Will return a new array result.
2412 function invertRanges(ranges, constraintRange) {
2413 let invertedRanges = [];
2414 let { start } = constraintRange; // the end of the previous range. the start of the new range
2415 let i;
2416 let dateRange;
2417 // ranges need to be in order. required for our date-walking algorithm
2418 ranges.sort(compareRanges);
2419 for (i = 0; i < ranges.length; i += 1) {
2420 dateRange = ranges[i];
2421 // add the span of time before the event (if there is any)
2422 if (dateRange.start > start) { // compare millisecond time (skip any ambig logic)
2423 invertedRanges.push({ start, end: dateRange.start });
2424 }
2425 if (dateRange.end > start) {
2426 start = dateRange.end;
2427 }
2428 }
2429 // add the span of time after the last event (if there is any)
2430 if (start < constraintRange.end) { // compare millisecond time (skip any ambig logic)
2431 invertedRanges.push({ start, end: constraintRange.end });
2432 }
2433 return invertedRanges;
2434 }
2435 function compareRanges(range0, range1) {
2436 return range0.start.valueOf() - range1.start.valueOf(); // earlier ranges go first
2437 }
2438 function intersectRanges(range0, range1) {
2439 let { start, end } = range0;
2440 let newRange = null;
2441 if (range1.start !== null) {
2442 if (start === null) {
2443 start = range1.start;
2444 }
2445 else {
2446 start = new Date(Math.max(start.valueOf(), range1.start.valueOf()));
2447 }
2448 }
2449 if (range1.end != null) {
2450 if (end === null) {
2451 end = range1.end;
2452 }
2453 else {
2454 end = new Date(Math.min(end.valueOf(), range1.end.valueOf()));
2455 }
2456 }
2457 if (start === null || end === null || start < end) {
2458 newRange = { start, end };
2459 }
2460 return newRange;
2461 }
2462 function rangesEqual(range0, range1) {
2463 return (range0.start === null ? null : range0.start.valueOf()) === (range1.start === null ? null : range1.start.valueOf()) &&
2464 (range0.end === null ? null : range0.end.valueOf()) === (range1.end === null ? null : range1.end.valueOf());
2465 }
2466 function rangesIntersect(range0, range1) {
2467 return (range0.end === null || range1.start === null || range0.end > range1.start) &&
2468 (range0.start === null || range1.end === null || range0.start < range1.end);
2469 }
2470 function rangeContainsRange(outerRange, innerRange) {
2471 return (outerRange.start === null || (innerRange.start !== null && innerRange.start >= outerRange.start)) &&
2472 (outerRange.end === null || (innerRange.end !== null && innerRange.end <= outerRange.end));
2473 }
2474 function rangeContainsMarker(range, date) {
2475 return (range.start === null || date >= range.start) &&
2476 (range.end === null || date < range.end);
2477 }
2478 // If the given date is not within the given range, move it inside.
2479 // (If it's past the end, make it one millisecond before the end).
2480 function constrainMarkerToRange(date, range) {
2481 if (range.start != null && date < range.start) {
2482 return range.start;
2483 }
2484 if (range.end != null && date >= range.end) {
2485 return new Date(range.end.valueOf() - 1);
2486 }
2487 return date;
2488 }
2489
2490 function getDateMeta(date, todayRange, nowDate, dateProfile) {
2491 return {
2492 dow: date.getUTCDay(),
2493 isDisabled: Boolean(dateProfile && (!dateProfile.activeRange || !rangeContainsMarker(dateProfile.activeRange, date))),
2494 isOther: Boolean(dateProfile && !rangeContainsMarker(dateProfile.currentRange, date)),
2495 isToday: Boolean(todayRange && rangeContainsMarker(todayRange, date)),
2496 isPast: Boolean(nowDate ? (date < nowDate) : todayRange ? (date < todayRange.start) : false),
2497 isFuture: Boolean(nowDate ? (date > nowDate) : todayRange ? (date >= todayRange.end) : false),
2498 };
2499 }
2500 function getDayClassNames(meta, theme) {
2501 let classNames = [
2502 'fc-day',
2503 `fc-day-${DAY_IDS[meta.dow]}`,
2504 ];
2505 if (meta.isDisabled) {
2506 classNames.push('fc-day-disabled');
2507 }
2508 else {
2509 if (meta.isToday) {
2510 classNames.push('fc-day-today');
2511 classNames.push(theme.getClass('today'));
2512 }
2513 if (meta.isPast) {
2514 classNames.push('fc-day-past');
2515 }
2516 if (meta.isFuture) {
2517 classNames.push('fc-day-future');
2518 }
2519 if (meta.isOther) {
2520 classNames.push('fc-day-other');
2521 }
2522 }
2523 return classNames;
2524 }
2525 function getSlotClassNames(meta, theme) {
2526 let classNames = [
2527 'fc-slot',
2528 `fc-slot-${DAY_IDS[meta.dow]}`,
2529 ];
2530 if (meta.isDisabled) {
2531 classNames.push('fc-slot-disabled');
2532 }
2533 else {
2534 if (meta.isToday) {
2535 classNames.push('fc-slot-today');
2536 classNames.push(theme.getClass('today'));
2537 }
2538 if (meta.isPast) {
2539 classNames.push('fc-slot-past');
2540 }
2541 if (meta.isFuture) {
2542 classNames.push('fc-slot-future');
2543 }
2544 }
2545 return classNames;
2546 }
2547
2548 const DAY_FORMAT = createFormatter({ year: 'numeric', month: 'long', day: 'numeric' });
2549 const WEEK_FORMAT = createFormatter({ week: 'long' });
2550 function buildNavLinkAttrs(context, dateMarker, viewType = 'day', isTabbable = true) {
2551 const { dateEnv, options, calendarApi } = context;
2552 let dateStr = dateEnv.format(dateMarker, viewType === 'week' ? WEEK_FORMAT : DAY_FORMAT);
2553 if (options.navLinks) {
2554 let zonedDate = dateEnv.toDate(dateMarker);
2555 const handleInteraction = (ev) => {
2556 let customAction = viewType === 'day' ? options.navLinkDayClick :
2557 viewType === 'week' ? options.navLinkWeekClick : null;
2558 if (typeof customAction === 'function') {
2559 customAction.call(calendarApi, dateEnv.toDate(dateMarker), ev);
2560 }
2561 else {
2562 if (typeof customAction === 'string') {
2563 viewType = customAction;
2564 }
2565 calendarApi.zoomTo(dateMarker, viewType);
2566 }
2567 };
2568 return Object.assign({ title: formatWithOrdinals(options.navLinkHint, [dateStr, zonedDate], dateStr), 'data-navlink': '' }, (isTabbable
2569 ? createAriaClickAttrs(handleInteraction)
2570 : { onClick: handleInteraction }));
2571 }
2572 return { 'aria-label': dateStr };
2573 }
2574
2575 let _isRtlScrollbarOnLeft = null;
2576 function getIsRtlScrollbarOnLeft() {
2577 if (_isRtlScrollbarOnLeft === null) {
2578 _isRtlScrollbarOnLeft = computeIsRtlScrollbarOnLeft();
2579 }
2580 return _isRtlScrollbarOnLeft;
2581 }
2582 function computeIsRtlScrollbarOnLeft() {
2583 let outerEl = document.createElement('div');
2584 applyStyle(outerEl, {
2585 position: 'absolute',
2586 top: -1000,
2587 left: 0,
2588 border: 0,
2589 padding: 0,
2590 overflow: 'scroll',
2591 direction: 'rtl',
2592 });
2593 outerEl.innerHTML = '<div></div>';
2594 document.body.appendChild(outerEl);
2595 let innerEl = outerEl.firstChild;
2596 let res = innerEl.getBoundingClientRect().left > outerEl.getBoundingClientRect().left;
2597 removeElement(outerEl);
2598 return res;
2599 }
2600
2601 let _scrollbarWidths;
2602 function getScrollbarWidths() {
2603 if (!_scrollbarWidths) {
2604 _scrollbarWidths = computeScrollbarWidths();
2605 }
2606 return _scrollbarWidths;
2607 }
2608 function computeScrollbarWidths() {
2609 let el = document.createElement('div');
2610 el.style.overflow = 'scroll';
2611 el.style.position = 'absolute';
2612 el.style.top = '-9999px';
2613 el.style.left = '-9999px';
2614 document.body.appendChild(el);
2615 let res = computeScrollbarWidthsForEl(el);
2616 document.body.removeChild(el);
2617 return res;
2618 }
2619 // WARNING: will include border
2620 function computeScrollbarWidthsForEl(el) {
2621 return {
2622 x: el.offsetHeight - el.clientHeight,
2623 y: el.offsetWidth - el.clientWidth,
2624 };
2625 }
2626
2627 function computeEdges(el, getPadding = false) {
2628 let computedStyle = window.getComputedStyle(el);
2629 let borderLeft = parseInt(computedStyle.borderLeftWidth, 10) || 0;
2630 let borderRight = parseInt(computedStyle.borderRightWidth, 10) || 0;
2631 let borderTop = parseInt(computedStyle.borderTopWidth, 10) || 0;
2632 let borderBottom = parseInt(computedStyle.borderBottomWidth, 10) || 0;
2633 let badScrollbarWidths = computeScrollbarWidthsForEl(el); // includes border!
2634 let scrollbarLeftRight = badScrollbarWidths.y - borderLeft - borderRight;
2635 let scrollbarBottom = badScrollbarWidths.x - borderTop - borderBottom;
2636 let res = {
2637 borderLeft,
2638 borderRight,
2639 borderTop,
2640 borderBottom,
2641 scrollbarBottom,
2642 scrollbarLeft: 0,
2643 scrollbarRight: 0,
2644 };
2645 if (getIsRtlScrollbarOnLeft() && computedStyle.direction === 'rtl') { // is the scrollbar on the left side?
2646 res.scrollbarLeft = scrollbarLeftRight;
2647 }
2648 else {
2649 res.scrollbarRight = scrollbarLeftRight;
2650 }
2651 if (getPadding) {
2652 res.paddingLeft = parseInt(computedStyle.paddingLeft, 10) || 0;
2653 res.paddingRight = parseInt(computedStyle.paddingRight, 10) || 0;
2654 res.paddingTop = parseInt(computedStyle.paddingTop, 10) || 0;
2655 res.paddingBottom = parseInt(computedStyle.paddingBottom, 10) || 0;
2656 }
2657 return res;
2658 }
2659 function computeInnerRect(el, goWithinPadding = false, doFromWindowViewport) {
2660 let outerRect = doFromWindowViewport ? el.getBoundingClientRect() : computeRect(el);
2661 let edges = computeEdges(el, goWithinPadding);
2662 let res = {
2663 left: outerRect.left + edges.borderLeft + edges.scrollbarLeft,
2664 right: outerRect.right - edges.borderRight - edges.scrollbarRight,
2665 top: outerRect.top + edges.borderTop,
2666 bottom: outerRect.bottom - edges.borderBottom - edges.scrollbarBottom,
2667 };
2668 if (goWithinPadding) {
2669 res.left += edges.paddingLeft;
2670 res.right -= edges.paddingRight;
2671 res.top += edges.paddingTop;
2672 res.bottom -= edges.paddingBottom;
2673 }
2674 return res;
2675 }
2676 function computeRect(el) {
2677 let rect = el.getBoundingClientRect();
2678 return {
2679 left: rect.left + window.scrollX,
2680 top: rect.top + window.scrollY,
2681 right: rect.right + window.scrollX,
2682 bottom: rect.bottom + window.scrollY,
2683 };
2684 }
2685 function computeClippedClientRect(el) {
2686 let clippingParents = getClippingParents(el);
2687 let rect = el.getBoundingClientRect();
2688 for (let clippingParent of clippingParents) {
2689 let intersection = intersectRects(rect, clippingParent.getBoundingClientRect());
2690 if (intersection) {
2691 rect = intersection;
2692 }
2693 else {
2694 return null;
2695 }
2696 }
2697 return rect;
2698 }
2699 // does not return window
2700 function getClippingParents(el) {
2701 let parents = [];
2702 while (el instanceof HTMLElement) { // will stop when gets to document or null
2703 let computedStyle = window.getComputedStyle(el);
2704 if (computedStyle.position === 'fixed') {
2705 break;
2706 }
2707 if ((/(auto|scroll)/).test(computedStyle.overflow + computedStyle.overflowY + computedStyle.overflowX)) {
2708 parents.push(el);
2709 }
2710 el = el.parentNode;
2711 }
2712 return parents;
2713 }
2714
2715 /*
2716 given a function that resolves a result asynchronously.
2717 the function can either call passed-in success and failure callbacks,
2718 or it can return a promise.
2719 if you need to pass additional params to func, bind them first.
2720 */
2721 function unpromisify(func, normalizedSuccessCallback, normalizedFailureCallback) {
2722 // guard against success/failure callbacks being called more than once
2723 // and guard against a promise AND callback being used together.
2724 let isResolved = false;
2725 let wrappedSuccess = function (res) {
2726 if (!isResolved) {
2727 isResolved = true;
2728 normalizedSuccessCallback(res);
2729 }
2730 };
2731 let wrappedFailure = function (error) {
2732 if (!isResolved) {
2733 isResolved = true;
2734 normalizedFailureCallback(error);
2735 }
2736 };
2737 let res = func(wrappedSuccess, wrappedFailure);
2738 if (res && typeof res.then === 'function') {
2739 res.then(wrappedSuccess, wrappedFailure);
2740 }
2741 }
2742
2743 class Emitter {
2744 constructor() {
2745 this.handlers = {};
2746 this.thisContext = null;
2747 }
2748 setThisContext(thisContext) {
2749 this.thisContext = thisContext;
2750 }
2751 setOptions(options) {
2752 this.options = options;
2753 }
2754 on(type, handler) {
2755 addToHash(this.handlers, type, handler);
2756 }
2757 off(type, handler) {
2758 removeFromHash(this.handlers, type, handler);
2759 }
2760 trigger(type, ...args) {
2761 let attachedHandlers = this.handlers[type] || [];
2762 let optionHandler = this.options && this.options[type];
2763 let handlers = [].concat(optionHandler || [], attachedHandlers);
2764 for (let handler of handlers) {
2765 handler.apply(this.thisContext, args);
2766 }
2767 }
2768 hasHandlers(type) {
2769 return Boolean((this.handlers[type] && this.handlers[type].length) ||
2770 (this.options && this.options[type]));
2771 }
2772 }
2773 function addToHash(hash, type, handler) {
2774 (hash[type] || (hash[type] = []))
2775 .push(handler);
2776 }
2777 function removeFromHash(hash, type, handler) {
2778 if (handler) {
2779 if (hash[type]) {
2780 hash[type] = hash[type].filter((func) => func !== handler);
2781 }
2782 }
2783 else {
2784 delete hash[type]; // remove all handler funcs for this type
2785 }
2786 }
2787
2788 /*
2789 Records offset information for a set of elements, relative to an origin element.
2790 Can record the left/right OR the top/bottom OR both.
2791 Provides methods for querying the cache by position.
2792 */
2793 class PositionCache {
2794 constructor(originEl, els, isHorizontal, isVertical) {
2795 this.els = els;
2796 let originClientRect = this.originClientRect = originEl.getBoundingClientRect(); // relative to viewport top-left
2797 if (isHorizontal) {
2798 this.buildElHorizontals(originClientRect.left);
2799 }
2800 if (isVertical) {
2801 this.buildElVerticals(originClientRect.top);
2802 }
2803 }
2804 // Populates the left/right internal coordinate arrays
2805 buildElHorizontals(originClientLeft) {
2806 let lefts = [];
2807 let rights = [];
2808 for (let el of this.els) {
2809 let rect = el.getBoundingClientRect();
2810 lefts.push(rect.left - originClientLeft);
2811 rights.push(rect.right - originClientLeft);
2812 }
2813 this.lefts = lefts;
2814 this.rights = rights;
2815 }
2816 // Populates the top/bottom internal coordinate arrays
2817 buildElVerticals(originClientTop) {
2818 let tops = [];
2819 let bottoms = [];
2820 for (let el of this.els) {
2821 let rect = el.getBoundingClientRect();
2822 tops.push(rect.top - originClientTop);
2823 bottoms.push(rect.bottom - originClientTop);
2824 }
2825 this.tops = tops;
2826 this.bottoms = bottoms;
2827 }
2828 // Given a left offset (from document left), returns the index of the el that it horizontally intersects.
2829 // If no intersection is made, returns undefined.
2830 leftToIndex(leftPosition) {
2831 let { lefts, rights } = this;
2832 let len = lefts.length;
2833 let i;
2834 for (i = 0; i < len; i += 1) {
2835 if (leftPosition >= lefts[i] && leftPosition < rights[i]) {
2836 return i;
2837 }
2838 }
2839 return undefined; // TODO: better
2840 }
2841 // Given a top offset (from document top), returns the index of the el that it vertically intersects.
2842 // If no intersection is made, returns undefined.
2843 topToIndex(topPosition) {
2844 let { tops, bottoms } = this;
2845 let len = tops.length;
2846 let i;
2847 for (i = 0; i < len; i += 1) {
2848 if (topPosition >= tops[i] && topPosition < bottoms[i]) {
2849 return i;
2850 }
2851 }
2852 return undefined; // TODO: better
2853 }
2854 // Gets the width of the element at the given index
2855 getWidth(leftIndex) {
2856 return this.rights[leftIndex] - this.lefts[leftIndex];
2857 }
2858 // Gets the height of the element at the given index
2859 getHeight(topIndex) {
2860 return this.bottoms[topIndex] - this.tops[topIndex];
2861 }
2862 similarTo(otherCache) {
2863 return similarNumArrays(this.tops || [], otherCache.tops || []) &&
2864 similarNumArrays(this.bottoms || [], otherCache.bottoms || []) &&
2865 similarNumArrays(this.lefts || [], otherCache.lefts || []) &&
2866 similarNumArrays(this.rights || [], otherCache.rights || []);
2867 }
2868 }
2869 function similarNumArrays(a, b) {
2870 const len = a.length;
2871 if (len !== b.length) {
2872 return false;
2873 }
2874 for (let i = 0; i < len; i++) {
2875 if (Math.round(a[i]) !== Math.round(b[i])) {
2876 return false;
2877 }
2878 }
2879 return true;
2880 }
2881
2882 /* eslint max-classes-per-file: "off" */
2883 /*
2884 An object for getting/setting scroll-related information for an element.
2885 Internally, this is done very differently for window versus DOM element,
2886 so this object serves as a common interface.
2887 */
2888 class ScrollController {
2889 getMaxScrollTop() {
2890 return this.getScrollHeight() - this.getClientHeight();
2891 }
2892 getMaxScrollLeft() {
2893 return this.getScrollWidth() - this.getClientWidth();
2894 }
2895 canScrollVertically() {
2896 return this.getMaxScrollTop() > 0;
2897 }
2898 canScrollHorizontally() {
2899 return this.getMaxScrollLeft() > 0;
2900 }
2901 canScrollUp() {
2902 return this.getScrollTop() > 0;
2903 }
2904 canScrollDown() {
2905 return this.getScrollTop() < this.getMaxScrollTop();
2906 }
2907 canScrollLeft() {
2908 return this.getScrollLeft() > 0;
2909 }
2910 canScrollRight() {
2911 return this.getScrollLeft() < this.getMaxScrollLeft();
2912 }
2913 }
2914 class ElementScrollController extends ScrollController {
2915 constructor(el) {
2916 super();
2917 this.el = el;
2918 }
2919 getScrollTop() {
2920 return this.el.scrollTop;
2921 }
2922 getScrollLeft() {
2923 return this.el.scrollLeft;
2924 }
2925 setScrollTop(top) {
2926 this.el.scrollTop = top;
2927 }
2928 setScrollLeft(left) {
2929 this.el.scrollLeft = left;
2930 }
2931 getScrollWidth() {
2932 return this.el.scrollWidth;
2933 }
2934 getScrollHeight() {
2935 return this.el.scrollHeight;
2936 }
2937 getClientHeight() {
2938 return this.el.clientHeight;
2939 }
2940 getClientWidth() {
2941 return this.el.clientWidth;
2942 }
2943 }
2944 class WindowScrollController extends ScrollController {
2945 getScrollTop() {
2946 return window.scrollY;
2947 }
2948 getScrollLeft() {
2949 return window.scrollX;
2950 }
2951 setScrollTop(n) {
2952 window.scroll(window.scrollX, n);
2953 }
2954 setScrollLeft(n) {
2955 window.scroll(n, window.scrollY);
2956 }
2957 getScrollWidth() {
2958 return document.documentElement.scrollWidth;
2959 }
2960 getScrollHeight() {
2961 return document.documentElement.scrollHeight;
2962 }
2963 getClientHeight() {
2964 return document.documentElement.clientHeight;
2965 }
2966 getClientWidth() {
2967 return document.documentElement.clientWidth;
2968 }
2969 }
2970
2971 class Theme {
2972 constructor(calendarOptions) {
2973 if (this.iconOverrideOption) {
2974 this.setIconOverride(calendarOptions[this.iconOverrideOption]);
2975 }
2976 }
2977 setIconOverride(iconOverrideHash) {
2978 let iconClassesCopy;
2979 let buttonName;
2980 if (typeof iconOverrideHash === 'object' && iconOverrideHash) { // non-null object
2981 iconClassesCopy = Object.assign({}, this.iconClasses);
2982 for (buttonName in iconOverrideHash) {
2983 iconClassesCopy[buttonName] = this.applyIconOverridePrefix(iconOverrideHash[buttonName]);
2984 }
2985 this.iconClasses = iconClassesCopy;
2986 }
2987 else if (iconOverrideHash === false) {
2988 this.iconClasses = {};
2989 }
2990 }
2991 applyIconOverridePrefix(className) {
2992 let prefix = this.iconOverridePrefix;
2993 if (prefix && className.indexOf(prefix) !== 0) { // if not already present
2994 className = prefix + className;
2995 }
2996 return className;
2997 }
2998 getClass(key) {
2999 return this.classes[key] || '';
3000 }
3001 getIconClass(buttonName, isRtl) {
3002 let className;
3003 if (isRtl && this.rtlIconClasses) {
3004 className = this.rtlIconClasses[buttonName] || this.iconClasses[buttonName];
3005 }
3006 else {
3007 className = this.iconClasses[buttonName];
3008 }
3009 if (className) {
3010 return `${this.baseIconClass} ${className}`;
3011 }
3012 return '';
3013 }
3014 getCustomButtonIconClass(customButtonProps) {
3015 let className;
3016 if (this.iconOverrideCustomButtonOption) {
3017 className = customButtonProps[this.iconOverrideCustomButtonOption];
3018 if (className) {
3019 return `${this.baseIconClass} ${this.applyIconOverridePrefix(className)}`;
3020 }
3021 }
3022 return '';
3023 }
3024 }
3025 Theme.prototype.classes = {};
3026 Theme.prototype.iconClasses = {};
3027 Theme.prototype.baseIconClass = '';
3028 Theme.prototype.iconOverridePrefix = '';
3029
3030 var n,l$1,u$1,i$1,t,r$1,o,f$1,e$1,c$1={},s=[],a$1=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function h(n,l){for(var u in l)n[u]=l[u];return n}function v$1(n){var l=n.parentNode;l&&l.removeChild(n);}function y(l,u,i){var t,r,o,f={};for(o in u)"key"==o?t=u[o]:"ref"==o?r=u[o]:f[o]=u[o];if(arguments.length>2&&(f.children=arguments.length>3?n.call(arguments,2):i),"function"==typeof l&&null!=l.defaultProps)for(o in l.defaultProps)void 0===f[o]&&(f[o]=l.defaultProps[o]);return p(l,f,t,r,null)}function p(n,i,t,r,o){var f={type:n,props:i,key:t,ref:r,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==o?++u$1:o};return null==o&&null!=l$1.vnode&&l$1.vnode(f),f}function d(){return {current:null}}function _(n){return n.children}function k$1(n,l,u,i,t){var r;for(r in u)"children"===r||"key"===r||r in l||g$2(n,r,null,u[r],i);for(r in l)t&&"function"!=typeof l[r]||"children"===r||"key"===r||"value"===r||"checked"===r||u[r]===l[r]||g$2(n,r,l[r],u[r],i);}function b$1(n,l,u){"-"===l[0]?n.setProperty(l,null==u?"":u):n[l]=null==u?"":"number"!=typeof u||a$1.test(l)?u:u+"px";}function g$2(n,l,u,i,t){var r;n:if("style"===l)if("string"==typeof u)n.style.cssText=u;else {if("string"==typeof i&&(n.style.cssText=i=""),i)for(l in i)u&&l in u||b$1(n.style,l,"");if(u)for(l in u)i&&u[l]===i[l]||b$1(n.style,l,u[l]);}else if("o"===l[0]&&"n"===l[1])r=l!==(l=l.replace(/Capture$/,"")),l=l.toLowerCase()in n?l.toLowerCase().slice(2):l.slice(2),n.l||(n.l={}),n.l[l+r]=u,u?i||n.addEventListener(l,r?w$2:m$1,r):n.removeEventListener(l,r?w$2:m$1,r);else if("dangerouslySetInnerHTML"!==l){if(t)l=l.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if("width"!==l&&"height"!==l&&"href"!==l&&"list"!==l&&"form"!==l&&"tabIndex"!==l&&"download"!==l&&l in n)try{n[l]=null==u?"":u;break n}catch(n){}"function"==typeof u||(null==u||!1===u&&-1==l.indexOf("-")?n.removeAttribute(l):n.setAttribute(l,u));}}function m$1(n){t=!0;try{return this.l[n.type+!1](l$1.event?l$1.event(n):n)}finally{t=!1;}}function w$2(n){t=!0;try{return this.l[n.type+!0](l$1.event?l$1.event(n):n)}finally{t=!1;}}function x$1(n,l){this.props=n,this.context=l;}function A(n,l){if(null==l)return n.__?A(n.__,n.__.__k.indexOf(n)+1):null;for(var u;l<n.__k.length;l++)if(null!=(u=n.__k[l])&&null!=u.__e)return u.__e;return "function"==typeof n.type?A(n):null}function P$1(n){var l,u;if(null!=(n=n.__)&&null!=n.__c){for(n.__e=n.__c.base=null,l=0;l<n.__k.length;l++)if(null!=(u=n.__k[l])&&null!=u.__e){n.__e=n.__c.base=u.__e;break}return P$1(n)}}function C$1(n){t?setTimeout(n):f$1(n);}function T$1(n){(!n.__d&&(n.__d=!0)&&r$1.push(n)&&!$$1.__r++||o!==l$1.debounceRendering)&&((o=l$1.debounceRendering)||C$1)($$1);}function $$1(){var n,l,u,i,t,o,f,e;for(r$1.sort(function(n,l){return n.__v.__b-l.__v.__b});n=r$1.shift();)n.__d&&(l=r$1.length,i=void 0,t=void 0,f=(o=(u=n).__v).__e,(e=u.__P)&&(i=[],(t=h({},o)).__v=o.__v+1,M(e,o,t,u.__n,void 0!==e.ownerSVGElement,null!=o.__h?[f]:null,i,null==f?A(o):f,o.__h),N(i,o),o.__e!=f&&P$1(o)),r$1.length>l&&r$1.sort(function(n,l){return n.__v.__b-l.__v.__b}));$$1.__r=0;}function H$1(n,l,u,i,t,r,o,f,e,a){var h,v,y,d,k,b,g,m=i&&i.__k||s,w=m.length;for(u.__k=[],h=0;h<l.length;h++)if(null!=(d=u.__k[h]=null==(d=l[h])||"boolean"==typeof d?null:"string"==typeof d||"number"==typeof d||"bigint"==typeof d?p(null,d,null,null,d):Array.isArray(d)?p(_,{children:d},null,null,null):d.__b>0?p(d.type,d.props,d.key,d.ref?d.ref:null,d.__v):d)){if(d.__=u,d.__b=u.__b+1,null===(y=m[h])||y&&d.key==y.key&&d.type===y.type)m[h]=void 0;else for(v=0;v<w;v++){if((y=m[v])&&d.key==y.key&&d.type===y.type){m[v]=void 0;break}y=null;}M(n,d,y=y||c$1,t,r,o,f,e,a),k=d.__e,(v=d.ref)&&y.ref!=v&&(g||(g=[]),y.ref&&g.push(y.ref,null,d),g.push(v,d.__c||k,d)),null!=k?(null==b&&(b=k),"function"==typeof d.type&&d.__k===y.__k?d.__d=e=I$1(d,e,n):e=z$1(n,d,y,m,k,e),"function"==typeof u.type&&(u.__d=e)):e&&y.__e==e&&e.parentNode!=n&&(e=A(y));}for(u.__e=b,h=w;h--;)null!=m[h]&&("function"==typeof u.type&&null!=m[h].__e&&m[h].__e==u.__d&&(u.__d=L$1(i).nextSibling),q(m[h],m[h]));if(g)for(h=0;h<g.length;h++)S(g[h],g[++h],g[++h]);}function I$1(n,l,u){for(var i,t=n.__k,r=0;t&&r<t.length;r++)(i=t[r])&&(i.__=n,l="function"==typeof i.type?I$1(i,l,u):z$1(u,i,i,t,i.__e,l));return l}function j$2(n,l){return l=l||[],null==n||"boolean"==typeof n||(Array.isArray(n)?n.some(function(n){j$2(n,l);}):l.push(n)),l}function z$1(n,l,u,i,t,r){var o,f,e;if(void 0!==l.__d)o=l.__d,l.__d=void 0;else if(null==u||t!=r||null==t.parentNode)n:if(null==r||r.parentNode!==n)n.appendChild(t),o=null;else {for(f=r,e=0;(f=f.nextSibling)&&e<i.length;e+=1)if(f==t)break n;n.insertBefore(t,r),o=r;}return void 0!==o?o:t.nextSibling}function L$1(n){var l,u,i;if(null==n.type||"string"==typeof n.type)return n.__e;if(n.__k)for(l=n.__k.length-1;l>=0;l--)if((u=n.__k[l])&&(i=L$1(u)))return i;return null}function M(n,u,i,t,r,o,f,e,c){var s,a,v,y,p,d,k,b,g,m,w,A,P,C,T,$=u.type;if(void 0!==u.constructor)return null;null!=i.__h&&(c=i.__h,e=u.__e=i.__e,u.__h=null,o=[e]),(s=l$1.__b)&&s(u);try{n:if("function"==typeof $){if(b=u.props,g=(s=$.contextType)&&t[s.__c],m=s?g?g.props.value:s.__:t,i.__c?k=(a=u.__c=i.__c).__=a.__E:("prototype"in $&&$.prototype.render?u.__c=a=new $(b,m):(u.__c=a=new x$1(b,m),a.constructor=$,a.render=B$1),g&&g.sub(a),a.props=b,a.state||(a.state={}),a.context=m,a.__n=t,v=a.__d=!0,a.__h=[],a._sb=[]),null==a.__s&&(a.__s=a.state),null!=$.getDerivedStateFromProps&&(a.__s==a.state&&(a.__s=h({},a.__s)),h(a.__s,$.getDerivedStateFromProps(b,a.__s))),y=a.props,p=a.state,a.__v=u,v)null==$.getDerivedStateFromProps&&null!=a.componentWillMount&&a.componentWillMount(),null!=a.componentDidMount&&a.__h.push(a.componentDidMount);else {if(null==$.getDerivedStateFromProps&&b!==y&&null!=a.componentWillReceiveProps&&a.componentWillReceiveProps(b,m),!a.__e&&null!=a.shouldComponentUpdate&&!1===a.shouldComponentUpdate(b,a.__s,m)||u.__v===i.__v){for(u.__v!==i.__v&&(a.props=b,a.state=a.__s,a.__d=!1),u.__e=i.__e,u.__k=i.__k,u.__k.forEach(function(n){n&&(n.__=u);}),w=0;w<a._sb.length;w++)a.__h.push(a._sb[w]);a._sb=[],a.__h.length&&f.push(a);break n}null!=a.componentWillUpdate&&a.componentWillUpdate(b,a.__s,m),null!=a.componentDidUpdate&&a.__h.push(function(){a.componentDidUpdate(y,p,d);});}if(a.context=m,a.props=b,a.__P=n,A=l$1.__r,P=0,"prototype"in $&&$.prototype.render){for(a.state=a.__s,a.__d=!1,A&&A(u),s=a.render(a.props,a.state,a.context),C=0;C<a._sb.length;C++)a.__h.push(a._sb[C]);a._sb=[];}else do{a.__d=!1,A&&A(u),s=a.render(a.props,a.state,a.context),a.state=a.__s;}while(a.__d&&++P<25);a.state=a.__s,null!=a.getChildContext&&(t=h(h({},t),a.getChildContext())),v||null==a.getSnapshotBeforeUpdate||(d=a.getSnapshotBeforeUpdate(y,p)),T=null!=s&&s.type===_&&null==s.key?s.props.children:s,H$1(n,Array.isArray(T)?T:[T],u,i,t,r,o,f,e,c),a.base=u.__e,u.__h=null,a.__h.length&&f.push(a),k&&(a.__E=a.__=null),a.__e=!1;}else null==o&&u.__v===i.__v?(u.__k=i.__k,u.__e=i.__e):u.__e=O(i.__e,u,i,t,r,o,f,c);(s=l$1.diffed)&&s(u);}catch(n){u.__v=null,(c||null!=o)&&(u.__e=e,u.__h=!!c,o[o.indexOf(e)]=null),l$1.__e(n,u,i);}}function N(n,u){l$1.__c&&l$1.__c(u,n),n.some(function(u){try{n=u.__h,u.__h=[],n.some(function(n){n.call(u);});}catch(n){l$1.__e(n,u.__v);}});}function O(l,u,i,t,r,o,f,e){var s,a,h,y=i.props,p=u.props,d=u.type,_=0;if("svg"===d&&(r=!0),null!=o)for(;_<o.length;_++)if((s=o[_])&&"setAttribute"in s==!!d&&(d?s.localName===d:3===s.nodeType)){l=s,o[_]=null;break}if(null==l){if(null===d)return document.createTextNode(p);l=r?document.createElementNS("http://www.w3.org/2000/svg",d):document.createElement(d,p.is&&p),o=null,e=!1;}if(null===d)y===p||e&&l.data===p||(l.data=p);else {if(o=o&&n.call(l.childNodes),a=(y=i.props||c$1).dangerouslySetInnerHTML,h=p.dangerouslySetInnerHTML,!e){if(null!=o)for(y={},_=0;_<l.attributes.length;_++)y[l.attributes[_].name]=l.attributes[_].value;(h||a)&&(h&&(a&&h.__html==a.__html||h.__html===l.innerHTML)||(l.innerHTML=h&&h.__html||""));}if(k$1(l,p,y,r,e),h)u.__k=[];else if(_=u.props.children,H$1(l,Array.isArray(_)?_:[_],u,i,t,r&&"foreignObject"!==d,o,f,o?o[0]:i.__k&&A(i,0),e),null!=o)for(_=o.length;_--;)null!=o[_]&&v$1(o[_]);e||("value"in p&&void 0!==(_=p.value)&&(_!==l.value||"progress"===d&&!_||"option"===d&&_!==y.value)&&g$2(l,"value",_,y.value,!1),"checked"in p&&void 0!==(_=p.checked)&&_!==l.checked&&g$2(l,"checked",_,y.checked,!1));}return l}function S(n,u,i){try{"function"==typeof n?n(u):n.current=u;}catch(n){l$1.__e(n,i);}}function q(n,u,i){var t,r;if(l$1.unmount&&l$1.unmount(n),(t=n.ref)&&(t.current&&t.current!==n.__e||S(t,null,u)),null!=(t=n.__c)){if(t.componentWillUnmount)try{t.componentWillUnmount();}catch(n){l$1.__e(n,u);}t.base=t.__P=null,n.__c=void 0;}if(t=n.__k)for(r=0;r<t.length;r++)t[r]&&q(t[r],u,i||"function"!=typeof n.type);i||null==n.__e||v$1(n.__e),n.__=n.__e=n.__d=void 0;}function B$1(n,l,u){return this.constructor(n,u)}function D$1(u,i,t){var r,o,f;l$1.__&&l$1.__(u,i),o=(r="function"==typeof t)?null:t&&t.__k||i.__k,f=[],M(i,u=(!r&&t||i).__k=y(_,null,[u]),o||c$1,c$1,void 0!==i.ownerSVGElement,!r&&t?[t]:o?null:i.firstChild?n.call(i.childNodes):null,f,!r&&t?t:o?o.__e:i.firstChild,r),N(f,u);}function E(n,l){D$1(n,l,E);}function F$1(l,u,i){var t,r,o,f=h({},l.props);for(o in u)"key"==o?t=u[o]:"ref"==o?r=u[o]:f[o]=u[o];return arguments.length>2&&(f.children=arguments.length>3?n.call(arguments,2):i),p(l.type,f,t||l.key,r||l.ref,null)}function G$1(n,l){var u={__c:l="__cC"+e$1++,__:n,Consumer:function(n,l){return n.children(l)},Provider:function(n){var u,i;return this.getChildContext||(u=[],(i={})[l]=this,this.getChildContext=function(){return i},this.shouldComponentUpdate=function(n){this.props.value!==n.value&&u.some(function(n){n.__e=!0,T$1(n);});},this.sub=function(n){u.push(n);var l=n.componentWillUnmount;n.componentWillUnmount=function(){u.splice(u.indexOf(n),1),l&&l.call(n);};}),n.children}};return u.Provider.__=u.Consumer.contextType=u}n=s.slice,l$1={__e:function(n,l,u,i){for(var t,r,o;l=l.__;)if((t=l.__c)&&!t.__)try{if((r=t.constructor)&&null!=r.getDerivedStateFromError&&(t.setState(r.getDerivedStateFromError(n)),o=t.__d),null!=t.componentDidCatch&&(t.componentDidCatch(n,i||{}),o=t.__d),o)return t.__E=t}catch(l){n=l;}throw n}},u$1=0,i$1=function(n){return null!=n&&void 0===n.constructor},t=!1,x$1.prototype.setState=function(n,l){var u;u=null!=this.__s&&this.__s!==this.state?this.__s:this.__s=h({},this.state),"function"==typeof n&&(n=n(h({},u),this.props)),n&&h(u,n),null!=n&&this.__v&&(l&&this._sb.push(l),T$1(this));},x$1.prototype.forceUpdate=function(n){this.__v&&(this.__e=!0,n&&this.__h.push(n),T$1(this));},x$1.prototype.render=_,r$1=[],f$1="function"==typeof Promise?Promise.prototype.then.bind(Promise.resolve()):setTimeout,$$1.__r=0,e$1=0;
3031
3032 var r,u,i,f=[],c=[],e=l$1.__b,a=l$1.__r,v=l$1.diffed,l=l$1.__c,m=l$1.unmount;function b(){for(var t;t=f.shift();)if(t.__P&&t.__H)try{t.__H.__h.forEach(k),t.__H.__h.forEach(w$1),t.__H.__h=[];}catch(r){t.__H.__h=[],l$1.__e(r,t.__v);}}l$1.__b=function(n){r=null,e&&e(n);},l$1.__r=function(n){a&&a(n);var i=(r=n.__c).__H;i&&(u===r?(i.__h=[],r.__h=[],i.__.forEach(function(n){n.__N&&(n.__=n.__N),n.__V=c,n.__N=n.i=void 0;})):(i.__h.forEach(k),i.__h.forEach(w$1),i.__h=[])),u=r;},l$1.diffed=function(t){v&&v(t);var o=t.__c;o&&o.__H&&(o.__H.__h.length&&(1!==f.push(o)&&i===l$1.requestAnimationFrame||((i=l$1.requestAnimationFrame)||j$1)(b)),o.__H.__.forEach(function(n){n.i&&(n.__H=n.i),n.__V!==c&&(n.__=n.__V),n.i=void 0,n.__V=c;})),u=r=null;},l$1.__c=function(t,r){r.some(function(t){try{t.__h.forEach(k),t.__h=t.__h.filter(function(n){return !n.__||w$1(n)});}catch(u){r.some(function(n){n.__h&&(n.__h=[]);}),r=[],l$1.__e(u,t.__v);}}),l&&l(t,r);},l$1.unmount=function(t){m&&m(t);var r,u=t.__c;u&&u.__H&&(u.__H.__.forEach(function(n){try{k(n);}catch(n){r=n;}}),u.__H=void 0,r&&l$1.__e(r,u.__v));};var g$1="function"==typeof requestAnimationFrame;function j$1(n){var t,r=function(){clearTimeout(u),g$1&&cancelAnimationFrame(t),setTimeout(n);},u=setTimeout(r,100);g$1&&(t=requestAnimationFrame(r));}function k(n){var t=r,u=n.__c;"function"==typeof u&&(n.__c=void 0,u()),r=t;}function w$1(n){var t=r;n.__c=n.__(),r=t;}
3033
3034 function g(n,t){for(var e in t)n[e]=t[e];return n}function C(n,t){for(var e in n)if("__source"!==e&&!(e in t))return !0;for(var r in t)if("__source"!==r&&n[r]!==t[r])return !0;return !1}function w(n){this.props=n;}(w.prototype=new x$1).isPureReactComponent=!0,w.prototype.shouldComponentUpdate=function(n,t){return C(this.props,n)||C(this.state,t)};var x=l$1.__b;l$1.__b=function(n){n.type&&n.type.__f&&n.ref&&(n.props.ref=n.ref,n.ref=null),x&&x(n);};var T=l$1.__e;l$1.__e=function(n,t,e,r){if(n.then)for(var u,o=t;o=o.__;)if((u=o.__c)&&u.__c)return null==t.__e&&(t.__e=e.__e,t.__k=e.__k),u.__c(n,t);T(n,t,e,r);};var I=l$1.unmount;function L(n,t,e){return n&&(n.__c&&n.__c.__H&&(n.__c.__H.__.forEach(function(n){"function"==typeof n.__c&&n.__c();}),n.__c.__H=null),null!=(n=g({},n)).__c&&(n.__c.__P===e&&(n.__c.__P=t),n.__c=null),n.__k=n.__k&&n.__k.map(function(n){return L(n,t,e)})),n}function U(n,t,e){return n&&(n.__v=null,n.__k=n.__k&&n.__k.map(function(n){return U(n,t,e)}),n.__c&&n.__c.__P===t&&(n.__e&&e.insertBefore(n.__e,n.__d),n.__c.__e=!0,n.__c.__P=e)),n}function D(){this.__u=0,this.t=null,this.__b=null;}function F(n){var t=n.__.__c;return t&&t.__a&&t.__a(n)}function V(){this.u=null,this.o=null;}l$1.unmount=function(n){var t=n.__c;t&&t.__R&&t.__R(),t&&!0===n.__h&&(n.type=null),I&&I(n);},(D.prototype=new x$1).__c=function(n,t){var e=t.__c,r=this;null==r.t&&(r.t=[]),r.t.push(e);var u=F(r.__v),o=!1,i=function(){o||(o=!0,e.__R=null,u?u(l):l());};e.__R=i;var l=function(){if(!--r.__u){if(r.state.__a){var n=r.state.__a;r.__v.__k[0]=U(n,n.__c.__P,n.__c.__O);}var t;for(r.setState({__a:r.__b=null});t=r.t.pop();)t.forceUpdate();}},c=!0===t.__h;r.__u++||c||r.setState({__a:r.__b=r.__v.__k[0]}),n.then(i,i);},D.prototype.componentWillUnmount=function(){this.t=[];},D.prototype.render=function(n,e){if(this.__b){if(this.__v.__k){var r=document.createElement("div"),o=this.__v.__k[0].__c;this.__v.__k[0]=L(this.__b,r,o.__O=o.__P);}this.__b=null;}var i=e.__a&&y(_,null,n.fallback);return i&&(i.__h=null),[y(_,null,e.__a?null:n.children),i]};var W=function(n,t,e){if(++e[1]===e[0]&&n.o.delete(t),n.props.revealOrder&&("t"!==n.props.revealOrder[0]||!n.o.size))for(e=n.u;e;){for(;e.length>3;)e.pop()();if(e[1]<e[0])break;n.u=e=e[2];}};function P(n){return this.getChildContext=function(){return n.context},n.children}function $(n){var e=this,r=n.i;e.componentWillUnmount=function(){D$1(null,e.l),e.l=null,e.i=null;},e.i&&e.i!==r&&e.componentWillUnmount(),n.__v?(e.l||(e.i=r,e.l={nodeType:1,parentNode:r,childNodes:[],appendChild:function(n){this.childNodes.push(n),e.i.appendChild(n);},insertBefore:function(n,t){this.childNodes.push(n),e.i.appendChild(n);},removeChild:function(n){this.childNodes.splice(this.childNodes.indexOf(n)>>>1,1),e.i.removeChild(n);}}),D$1(y(P,{context:e.context},n.__v),e.l)):e.l&&e.componentWillUnmount();}function j(n,e){var r=y($,{__v:n,i:e});return r.containerInfo=e,r}(V.prototype=new x$1).__a=function(n){var t=this,e=F(t.__v),r=t.o.get(n);return r[0]++,function(u){var o=function(){t.props.revealOrder?(r.push(u),W(t,n,r)):u();};e?e(o):o();}},V.prototype.render=function(n){this.u=null,this.o=new Map;var t=j$2(n.children);n.revealOrder&&"b"===n.revealOrder[0]&&t.reverse();for(var e=t.length;e--;)this.o.set(t[e],this.u=[1,0,this.u]);return n.children},V.prototype.componentDidUpdate=V.prototype.componentDidMount=function(){var n=this;this.o.forEach(function(t,e){W(n,e,t);});};var z="undefined"!=typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103,B=/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/,H="undefined"!=typeof document,Z=function(n){return ("undefined"!=typeof Symbol&&"symbol"==typeof Symbol()?/fil|che|rad/i:/fil|che|ra/i).test(n)};x$1.prototype.isReactComponent={},["componentWillMount","componentWillReceiveProps","componentWillUpdate"].forEach(function(t){Object.defineProperty(x$1.prototype,t,{configurable:!0,get:function(){return this["UNSAFE_"+t]},set:function(n){Object.defineProperty(this,t,{configurable:!0,writable:!0,value:n});}});});var G=l$1.event;function J(){}function K(){return this.cancelBubble}function Q(){return this.defaultPrevented}l$1.event=function(n){return G&&(n=G(n)),n.persist=J,n.isPropagationStopped=K,n.isDefaultPrevented=Q,n.nativeEvent=n};var nn={configurable:!0,get:function(){return this.class}},tn=l$1.vnode;l$1.vnode=function(n){var t=n.type,e=n.props,u=e;if("string"==typeof t){var o=-1===t.indexOf("-");for(var i in u={},e){var l=e[i];H&&"children"===i&&"noscript"===t||"value"===i&&"defaultValue"in e&&null==l||("defaultValue"===i&&"value"in e&&null==e.value?i="value":"download"===i&&!0===l?l="":/ondoubleclick/i.test(i)?i="ondblclick":/^onchange(textarea|input)/i.test(i+t)&&!Z(e.type)?i="oninput":/^onfocus$/i.test(i)?i="onfocusin":/^onblur$/i.test(i)?i="onfocusout":/^on(Ani|Tra|Tou|BeforeInp|Compo)/.test(i)?i=i.toLowerCase():o&&B.test(i)?i=i.replace(/[A-Z0-9]/g,"-$&").toLowerCase():null===l&&(l=void 0),/^oninput$/i.test(i)&&(i=i.toLowerCase(),u[i]&&(i="oninputCapture")),u[i]=l);}"select"==t&&u.multiple&&Array.isArray(u.value)&&(u.value=j$2(e.children).forEach(function(n){n.props.selected=-1!=u.value.indexOf(n.props.value);})),"select"==t&&null!=u.defaultValue&&(u.value=j$2(e.children).forEach(function(n){n.props.selected=u.multiple?-1!=u.defaultValue.indexOf(n.props.value):u.defaultValue==n.props.value;})),n.props=u,e.class!=e.className&&(nn.enumerable="className"in e,null!=e.className&&(u.class=e.className),Object.defineProperty(u,"className",nn));}n.$$typeof=z,tn&&tn(n);};var en=l$1.__r;l$1.__r=function(n){en&&en(n),n.__c;};
3035
3036 /*
3037 NOTE: this can be a public API, especially createElement for hooks.
3038 See examples/typescript-scheduler/src/index.ts
3039 */
3040 function flushSync(runBeforeFlush) {
3041 runBeforeFlush();
3042 let oldDebounceRendering = l$1.debounceRendering; // orig
3043 let callbackQ = [];
3044 function execCallbackSync(callback) {
3045 callbackQ.push(callback);
3046 }
3047 l$1.debounceRendering = execCallbackSync;
3048 D$1(y(FakeComponent, {}), document.createElement('div'));
3049 while (callbackQ.length) {
3050 callbackQ.shift()();
3051 }
3052 l$1.debounceRendering = oldDebounceRendering;
3053 }
3054 class FakeComponent extends x$1 {
3055 render() { return y('div', {}); }
3056 componentDidMount() { this.setState({}); }
3057 }
3058 // TODO: use preact/compat instead?
3059 function createContext(defaultValue) {
3060 let ContextType = G$1(defaultValue);
3061 let origProvider = ContextType.Provider;
3062 ContextType.Provider = function () {
3063 let isNew = !this.getChildContext;
3064 let children = origProvider.apply(this, arguments); // eslint-disable-line prefer-rest-params
3065 if (isNew) {
3066 let subs = [];
3067 this.shouldComponentUpdate = (_props) => {
3068 if (this.props.value !== _props.value) {
3069 subs.forEach((c) => {
3070 c.context = _props.value;
3071 c.forceUpdate();
3072 });
3073 }
3074 };
3075 this.sub = (c) => {
3076 subs.push(c);
3077 let old = c.componentWillUnmount;
3078 c.componentWillUnmount = () => {
3079 subs.splice(subs.indexOf(c), 1);
3080 old && old.call(c);
3081 };
3082 };
3083 }
3084 return children;
3085 };
3086 return ContextType;
3087 }
3088
3089 var preact = {
3090 __proto__: null,
3091 flushSync: flushSync,
3092 createContext: createContext,
3093 createPortal: j,
3094 Component: x$1,
3095 Fragment: _,
3096 cloneElement: F$1,
3097 createElement: y,
3098 createRef: d,
3099 h: y,
3100 hydrate: E,
3101 get isValidElement () { return i$1; },
3102 get options () { return l$1; },
3103 render: D$1,
3104 toChildArray: j$2
3105 };
3106
3107 class ScrollResponder {
3108 constructor(execFunc, emitter, scrollTime, scrollTimeReset) {
3109 this.execFunc = execFunc;
3110 this.emitter = emitter;
3111 this.scrollTime = scrollTime;
3112 this.scrollTimeReset = scrollTimeReset;
3113 this.handleScrollRequest = (request) => {
3114 this.queuedRequest = Object.assign({}, this.queuedRequest || {}, request);
3115 this.drain();
3116 };
3117 emitter.on('_scrollRequest', this.handleScrollRequest);
3118 this.fireInitialScroll();
3119 }
3120 detach() {
3121 this.emitter.off('_scrollRequest', this.handleScrollRequest);
3122 }
3123 update(isDatesNew) {
3124 if (isDatesNew && this.scrollTimeReset) {
3125 this.fireInitialScroll(); // will drain
3126 }
3127 else {
3128 this.drain();
3129 }
3130 }
3131 fireInitialScroll() {
3132 this.handleScrollRequest({
3133 time: this.scrollTime,
3134 });
3135 }
3136 drain() {
3137 if (this.queuedRequest && this.execFunc(this.queuedRequest)) {
3138 this.queuedRequest = null;
3139 }
3140 }
3141 }
3142
3143 const ViewContextType = createContext({}); // for Components
3144 function buildViewContext(viewSpec, viewApi, viewOptions, dateProfileGenerator, dateEnv, nowManager, theme, pluginHooks, dispatch, getCurrentData, emitter, calendarApi, registerInteractiveComponent, unregisterInteractiveComponent) {
3145 return {
3146 dateEnv,
3147 nowManager,
3148 options: viewOptions,
3149 pluginHooks,
3150 emitter,
3151 dispatch,
3152 getCurrentData,
3153 calendarApi,
3154 viewSpec,
3155 viewApi,
3156 dateProfileGenerator,
3157 theme,
3158 isRtl: viewOptions.direction === 'rtl',
3159 addResizeHandler(handler) {
3160 emitter.on('_resize', handler);
3161 },
3162 removeResizeHandler(handler) {
3163 emitter.off('_resize', handler);
3164 },
3165 createScrollResponder(execFunc) {
3166 return new ScrollResponder(execFunc, emitter, createDuration(viewOptions.scrollTime), viewOptions.scrollTimeReset);
3167 },
3168 registerInteractiveComponent,
3169 unregisterInteractiveComponent,
3170 };
3171 }
3172
3173 /* eslint max-classes-per-file: off */
3174 class PureComponent extends x$1 {
3175 shouldComponentUpdate(nextProps, nextState) {
3176 if (this.debug) {
3177 // eslint-disable-next-line no-console
3178 console.log(getUnequalProps(nextProps, this.props), getUnequalProps(nextState, this.state));
3179 }
3180 return !compareObjs(this.props, nextProps, this.propEquality) ||
3181 !compareObjs(this.state, nextState, this.stateEquality);
3182 }
3183 // HACK for freakin' React StrictMode
3184 safeSetState(newState) {
3185 if (!compareObjs(this.state, Object.assign(Object.assign({}, this.state), newState), this.stateEquality)) {
3186 this.setState(newState);
3187 }
3188 }
3189 }
3190 PureComponent.addPropsEquality = addPropsEquality;
3191 PureComponent.addStateEquality = addStateEquality;
3192 PureComponent.contextType = ViewContextType;
3193 PureComponent.prototype.propEquality = {};
3194 PureComponent.prototype.stateEquality = {};
3195 class BaseComponent extends PureComponent {
3196 }
3197 BaseComponent.contextType = ViewContextType;
3198 function addPropsEquality(propEquality) {
3199 let hash = Object.create(this.prototype.propEquality);
3200 Object.assign(hash, propEquality);
3201 this.prototype.propEquality = hash;
3202 }
3203 function addStateEquality(stateEquality) {
3204 let hash = Object.create(this.prototype.stateEquality);
3205 Object.assign(hash, stateEquality);
3206 this.prototype.stateEquality = hash;
3207 }
3208 // use other one
3209 function setRef(ref, current) {
3210 if (typeof ref === 'function') {
3211 ref(current);
3212 }
3213 else if (ref) {
3214 // see https://github.com/facebook/react/issues/13029
3215 ref.current = current;
3216 }
3217 }
3218
3219 /*
3220 an INTERACTABLE date component
3221
3222 PURPOSES:
3223 - hook up to fg, fill, and mirror renderers
3224 - interface for dragging and hits
3225 */
3226 class DateComponent extends BaseComponent {
3227 constructor() {
3228 super(...arguments);
3229 this.uid = guid();
3230 }
3231 // Hit System
3232 // -----------------------------------------------------------------------------------------------------------------
3233 prepareHits() {
3234 }
3235 queryHit(positionLeft, positionTop, elWidth, elHeight) {
3236 return null; // this should be abstract
3237 }
3238 // Pointer Interaction Utils
3239 // -----------------------------------------------------------------------------------------------------------------
3240 isValidSegDownEl(el) {
3241 return !this.props.eventDrag && // HACK
3242 !this.props.eventResize && // HACK
3243 !elementClosest(el, '.fc-event-mirror');
3244 }
3245 isValidDateDownEl(el) {
3246 return !elementClosest(el, '.fc-event:not(.fc-bg-event)') &&
3247 !elementClosest(el, '.fc-more-link') && // a "more.." link
3248 !elementClosest(el, 'a[data-navlink]') && // a clickable nav link
3249 !elementClosest(el, '.fc-popover'); // hack
3250 }
3251 }
3252
3253 class DateProfileGenerator {
3254 constructor(props) {
3255 this.props = props;
3256 this.initHiddenDays();
3257 }
3258 /* Date Range Computation
3259 ------------------------------------------------------------------------------------------------------------------*/
3260 // Builds a structure with info about what the dates/ranges will be for the "prev" view.
3261 buildPrev(currentDateProfile, currentDate, forceToValid) {
3262 let { dateEnv } = this.props;
3263 let prevDate = dateEnv.subtract(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month
3264 currentDateProfile.dateIncrement);
3265 return this.build(prevDate, -1, forceToValid);
3266 }
3267 // Builds a structure with info about what the dates/ranges will be for the "next" view.
3268 buildNext(currentDateProfile, currentDate, forceToValid) {
3269 let { dateEnv } = this.props;
3270 let nextDate = dateEnv.add(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month
3271 currentDateProfile.dateIncrement);
3272 return this.build(nextDate, 1, forceToValid);
3273 }
3274 // Builds a structure holding dates/ranges for rendering around the given date.
3275 // Optional direction param indicates whether the date is being incremented/decremented
3276 // from its previous value. decremented = -1, incremented = 1 (default).
3277 build(currentDate, direction, forceToValid = true) {
3278 let { props } = this;
3279 let validRange;
3280 let currentInfo;
3281 let isRangeAllDay;
3282 let renderRange;
3283 let activeRange;
3284 let isValid;
3285 validRange = this.buildValidRange();
3286 validRange = this.trimHiddenDays(validRange);
3287 if (forceToValid) {
3288 currentDate = constrainMarkerToRange(currentDate, validRange);
3289 }
3290 currentInfo = this.buildCurrentRangeInfo(currentDate, direction);
3291 isRangeAllDay = /^(year|month|week|day)$/.test(currentInfo.unit);
3292 renderRange = this.buildRenderRange(this.trimHiddenDays(currentInfo.range), currentInfo.unit, isRangeAllDay);
3293 renderRange = this.trimHiddenDays(renderRange);
3294 activeRange = renderRange;
3295 if (!props.showNonCurrentDates) {
3296 activeRange = intersectRanges(activeRange, currentInfo.range);
3297 }
3298 activeRange = this.adjustActiveRange(activeRange);
3299 activeRange = intersectRanges(activeRange, validRange); // might return null
3300 // it's invalid if the originally requested date is not contained,
3301 // or if the range is completely outside of the valid range.
3302 isValid = rangesIntersect(currentInfo.range, validRange);
3303 // HACK: constrain to render-range so `currentDate` is more useful to view rendering
3304 if (!rangeContainsMarker(renderRange, currentDate)) {
3305 currentDate = renderRange.start;
3306 }
3307 return {
3308 currentDate,
3309 // constraint for where prev/next operations can go and where events can be dragged/resized to.
3310 // an object with optional start and end properties.
3311 validRange,
3312 // range the view is formally responsible for.
3313 // for example, a month view might have 1st-31st, excluding padded dates
3314 currentRange: currentInfo.range,
3315 // name of largest unit being displayed, like "month" or "week"
3316 currentRangeUnit: currentInfo.unit,
3317 isRangeAllDay,
3318 // dates that display events and accept drag-n-drop
3319 // will be `null` if no dates accept events
3320 activeRange,
3321 // date range with a rendered skeleton
3322 // includes not-active days that need some sort of DOM
3323 renderRange,
3324 // Duration object that denotes the first visible time of any given day
3325 slotMinTime: props.slotMinTime,
3326 // Duration object that denotes the exclusive visible end time of any given day
3327 slotMaxTime: props.slotMaxTime,
3328 isValid,
3329 // how far the current date will move for a prev/next operation
3330 dateIncrement: this.buildDateIncrement(currentInfo.duration),
3331 // pass a fallback (might be null) ^
3332 };
3333 }
3334 // Builds an object with optional start/end properties.
3335 // Indicates the minimum/maximum dates to display.
3336 // not responsible for trimming hidden days.
3337 buildValidRange() {
3338 let input = this.props.validRangeInput;
3339 let simpleInput = typeof input === 'function'
3340 ? input.call(this.props.calendarApi, this.props.dateEnv.toDate(this.props.nowManager.getDateMarker()))
3341 : input;
3342 return this.refineRange(simpleInput) ||
3343 { start: null, end: null }; // completely open-ended
3344 }
3345 // Builds a structure with info about the "current" range, the range that is
3346 // highlighted as being the current month for example.
3347 // See build() for a description of `direction`.
3348 // Guaranteed to have `range` and `unit` properties. `duration` is optional.
3349 buildCurrentRangeInfo(date, direction) {
3350 let { props } = this;
3351 let duration = null;
3352 let unit = null;
3353 let range = null;
3354 let dayCount;
3355 if (props.duration) {
3356 duration = props.duration;
3357 unit = props.durationUnit;
3358 range = this.buildRangeFromDuration(date, direction, duration, unit);
3359 }
3360 else if ((dayCount = this.props.dayCount)) {
3361 unit = 'day';
3362 range = this.buildRangeFromDayCount(date, direction, dayCount);
3363 }
3364 else if ((range = this.buildCustomVisibleRange(date))) {
3365 unit = props.dateEnv.greatestWholeUnit(range.start, range.end).unit;
3366 }
3367 else {
3368 duration = this.getFallbackDuration();
3369 unit = greatestDurationDenominator(duration).unit;
3370 range = this.buildRangeFromDuration(date, direction, duration, unit);
3371 }
3372 return { duration, unit, range };
3373 }
3374 getFallbackDuration() {
3375 return createDuration({ day: 1 });
3376 }
3377 // Returns a new activeRange to have time values (un-ambiguate)
3378 // slotMinTime or slotMaxTime causes the range to expand.
3379 adjustActiveRange(range) {
3380 let { dateEnv, usesMinMaxTime, slotMinTime, slotMaxTime } = this.props;
3381 let { start, end } = range;
3382 if (usesMinMaxTime) {
3383 // expand active range if slotMinTime is negative (why not when positive?)
3384 if (asRoughDays(slotMinTime) < 0) {
3385 start = startOfDay(start); // necessary?
3386 start = dateEnv.add(start, slotMinTime);
3387 }
3388 // expand active range if slotMaxTime is beyond one day (why not when negative?)
3389 if (asRoughDays(slotMaxTime) > 1) {
3390 end = startOfDay(end); // necessary?
3391 end = addDays(end, -1);
3392 end = dateEnv.add(end, slotMaxTime);
3393 }
3394 }
3395 return { start, end };
3396 }
3397 // Builds the "current" range when it is specified as an explicit duration.
3398 // `unit` is the already-computed greatestDurationDenominator unit of duration.
3399 buildRangeFromDuration(date, direction, duration, unit) {
3400 let { dateEnv, dateAlignment } = this.props;
3401 let start;
3402 let end;
3403 let res;
3404 // compute what the alignment should be
3405 if (!dateAlignment) {
3406 let { dateIncrement } = this.props;
3407 if (dateIncrement) {
3408 // use the smaller of the two units
3409 if (asRoughMs(dateIncrement) < asRoughMs(duration)) {
3410 dateAlignment = greatestDurationDenominator(dateIncrement).unit;
3411 }
3412 else {
3413 dateAlignment = unit;
3414 }
3415 }
3416 else {
3417 dateAlignment = unit;
3418 }
3419 }
3420 // if the view displays a single day or smaller
3421 if (asRoughDays(duration) <= 1) {
3422 if (this.isHiddenDay(start)) {
3423 start = this.skipHiddenDays(start, direction);
3424 start = startOfDay(start);
3425 }
3426 }
3427 function computeRes() {
3428 start = dateEnv.startOf(date, dateAlignment);
3429 end = dateEnv.add(start, duration);
3430 res = { start, end };
3431 }
3432 computeRes();
3433 // if range is completely enveloped by hidden days, go past the hidden days
3434 if (!this.trimHiddenDays(res)) {
3435 date = this.skipHiddenDays(date, direction);
3436 computeRes();
3437 }
3438 return res;
3439 }
3440 // Builds the "current" range when a dayCount is specified.
3441 buildRangeFromDayCount(date, direction, dayCount) {
3442 let { dateEnv, dateAlignment } = this.props;
3443 let runningCount = 0;
3444 let start = date;
3445 let end;
3446 if (dateAlignment) {
3447 start = dateEnv.startOf(start, dateAlignment);
3448 }
3449 start = startOfDay(start);
3450 start = this.skipHiddenDays(start, direction);
3451 end = start;
3452 do {
3453 end = addDays(end, 1);
3454 if (!this.isHiddenDay(end)) {
3455 runningCount += 1;
3456 }
3457 } while (runningCount < dayCount);
3458 return { start, end };
3459 }
3460 // Builds a normalized range object for the "visible" range,
3461 // which is a way to define the currentRange and activeRange at the same time.
3462 buildCustomVisibleRange(date) {
3463 let { props } = this;
3464 let input = props.visibleRangeInput;
3465 let simpleInput = typeof input === 'function'
3466 ? input.call(props.calendarApi, props.dateEnv.toDate(date))
3467 : input;
3468 let range = this.refineRange(simpleInput);
3469 if (range && (range.start == null || range.end == null)) {
3470 return null;
3471 }
3472 return range;
3473 }
3474 // Computes the range that will represent the element/cells for *rendering*,
3475 // but which may have voided days/times.
3476 // not responsible for trimming hidden days.
3477 buildRenderRange(currentRange, currentRangeUnit, isRangeAllDay) {
3478 return currentRange;
3479 }
3480 // Compute the duration value that should be added/substracted to the current date
3481 // when a prev/next operation happens.
3482 buildDateIncrement(fallback) {
3483 let { dateIncrement } = this.props;
3484 let customAlignment;
3485 if (dateIncrement) {
3486 return dateIncrement;
3487 }
3488 if ((customAlignment = this.props.dateAlignment)) {
3489 return createDuration(1, customAlignment);
3490 }
3491 if (fallback) {
3492 return fallback;
3493 }
3494 return createDuration({ days: 1 });
3495 }
3496 refineRange(rangeInput) {
3497 if (rangeInput) {
3498 let range = parseRange(rangeInput, this.props.dateEnv);
3499 if (range) {
3500 range = computeVisibleDayRange(range);
3501 }
3502 return range;
3503 }
3504 return null;
3505 }
3506 /* Hidden Days
3507 ------------------------------------------------------------------------------------------------------------------*/
3508 // Initializes internal variables related to calculating hidden days-of-week
3509 initHiddenDays() {
3510 let hiddenDays = this.props.hiddenDays || []; // array of day-of-week indices that are hidden
3511 let isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool)
3512 let dayCnt = 0;
3513 let i;
3514 if (this.props.weekends === false) {
3515 hiddenDays.push(0, 6); // 0=sunday, 6=saturday
3516 }
3517 for (i = 0; i < 7; i += 1) {
3518 if (!(isHiddenDayHash[i] = hiddenDays.indexOf(i) !== -1)) {
3519 dayCnt += 1;
3520 }
3521 }
3522 if (!dayCnt) {
3523 throw new Error('invalid hiddenDays'); // all days were hidden? bad.
3524 }
3525 this.isHiddenDayHash = isHiddenDayHash;
3526 }
3527 // Remove days from the beginning and end of the range that are computed as hidden.
3528 // If the whole range is trimmed off, returns null
3529 trimHiddenDays(range) {
3530 let { start, end } = range;
3531 if (start) {
3532 start = this.skipHiddenDays(start);
3533 }
3534 if (end) {
3535 end = this.skipHiddenDays(end, -1, true);
3536 }
3537 if (start == null || end == null || start < end) {
3538 return { start, end };
3539 }
3540 return null;
3541 }
3542 // Is the current day hidden?
3543 // `day` is a day-of-week index (0-6), or a Date (used for UTC)
3544 isHiddenDay(day) {
3545 if (day instanceof Date) {
3546 day = day.getUTCDay();
3547 }
3548 return this.isHiddenDayHash[day];
3549 }
3550 // Incrementing the current day until it is no longer a hidden day, returning a copy.
3551 // DOES NOT CONSIDER validRange!
3552 // If the initial value of `date` is not a hidden day, don't do anything.
3553 // Pass `isExclusive` as `true` if you are dealing with an end date.
3554 // `inc` defaults to `1` (increment one day forward each time)
3555 skipHiddenDays(date, inc = 1, isExclusive = false) {
3556 while (this.isHiddenDayHash[(date.getUTCDay() + (isExclusive ? inc : 0) + 7) % 7]) {
3557 date = addDays(date, inc);
3558 }
3559 return date;
3560 }
3561 }
3562
3563 function triggerDateSelect(selection, pev, context) {
3564 context.emitter.trigger('select', Object.assign(Object.assign({}, buildDateSpanApiWithContext(selection, context)), { jsEvent: pev ? pev.origEvent : null, view: context.viewApi || context.calendarApi.view }));
3565 }
3566 function triggerDateUnselect(pev, context) {
3567 context.emitter.trigger('unselect', {
3568 jsEvent: pev ? pev.origEvent : null,
3569 view: context.viewApi || context.calendarApi.view,
3570 });
3571 }
3572 function buildDateSpanApiWithContext(dateSpan, context) {
3573 let props = {};
3574 for (let transform of context.pluginHooks.dateSpanTransforms) {
3575 Object.assign(props, transform(dateSpan, context));
3576 }
3577 Object.assign(props, buildDateSpanApi(dateSpan, context.dateEnv));
3578 return props;
3579 }
3580 // Given an event's allDay status and start date, return what its fallback end date should be.
3581 // TODO: rename to computeDefaultEventEnd
3582 function getDefaultEventEnd(allDay, marker, context) {
3583 let { dateEnv, options } = context;
3584 let end = marker;
3585 if (allDay) {
3586 end = startOfDay(end);
3587 end = dateEnv.add(end, options.defaultAllDayEventDuration);
3588 }
3589 else {
3590 end = dateEnv.add(end, options.defaultTimedEventDuration);
3591 }
3592 return end;
3593 }
3594
3595 // applies the mutation to ALL defs/instances within the event store
3596 function applyMutationToEventStore(eventStore, eventConfigBase, mutation, context) {
3597 let eventConfigs = compileEventUis(eventStore.defs, eventConfigBase);
3598 let dest = createEmptyEventStore();
3599 for (let defId in eventStore.defs) {
3600 let def = eventStore.defs[defId];
3601 dest.defs[defId] = applyMutationToEventDef(def, eventConfigs[defId], mutation, context);
3602 }
3603 for (let instanceId in eventStore.instances) {
3604 let instance = eventStore.instances[instanceId];
3605 let def = dest.defs[instance.defId]; // important to grab the newly modified def
3606 dest.instances[instanceId] = applyMutationToEventInstance(instance, def, eventConfigs[instance.defId], mutation, context);
3607 }
3608 return dest;
3609 }
3610 function applyMutationToEventDef(eventDef, eventConfig, mutation, context) {
3611 let standardProps = mutation.standardProps || {};
3612 // if hasEnd has not been specified, guess a good value based on deltas.
3613 // if duration will change, there's no way the default duration will persist,
3614 // and thus, we need to mark the event as having a real end
3615 if (standardProps.hasEnd == null &&
3616 eventConfig.durationEditable &&
3617 (mutation.startDelta || mutation.endDelta)) {
3618 standardProps.hasEnd = true; // TODO: is this mutation okay?
3619 }
3620 let copy = Object.assign(Object.assign(Object.assign({}, eventDef), standardProps), { ui: Object.assign(Object.assign({}, eventDef.ui), standardProps.ui) });
3621 if (mutation.extendedProps) {
3622 copy.extendedProps = Object.assign(Object.assign({}, copy.extendedProps), mutation.extendedProps);
3623 }
3624 for (let applier of context.pluginHooks.eventDefMutationAppliers) {
3625 applier(copy, mutation, context);
3626 }
3627 if (!copy.hasEnd && context.options.forceEventDuration) {
3628 copy.hasEnd = true;
3629 }
3630 return copy;
3631 }
3632 function applyMutationToEventInstance(eventInstance, eventDef, // must first be modified by applyMutationToEventDef
3633 eventConfig, mutation, context) {
3634 let { dateEnv } = context;
3635 let forceAllDay = mutation.standardProps && mutation.standardProps.allDay === true;
3636 let clearEnd = mutation.standardProps && mutation.standardProps.hasEnd === false;
3637 let copy = Object.assign({}, eventInstance);
3638 if (forceAllDay) {
3639 copy.range = computeAlignedDayRange(copy.range);
3640 }
3641 if (mutation.datesDelta && eventConfig.startEditable) {
3642 copy.range = {
3643 start: dateEnv.add(copy.range.start, mutation.datesDelta),
3644 end: dateEnv.add(copy.range.end, mutation.datesDelta),
3645 };
3646 }
3647 if (mutation.startDelta && eventConfig.durationEditable) {
3648 copy.range = {
3649 start: dateEnv.add(copy.range.start, mutation.startDelta),
3650 end: copy.range.end,
3651 };
3652 }
3653 if (mutation.endDelta && eventConfig.durationEditable) {
3654 copy.range = {
3655 start: copy.range.start,
3656 end: dateEnv.add(copy.range.end, mutation.endDelta),
3657 };
3658 }
3659 if (clearEnd) {
3660 copy.range = {
3661 start: copy.range.start,
3662 end: getDefaultEventEnd(eventDef.allDay, copy.range.start, context),
3663 };
3664 }
3665 // in case event was all-day but the supplied deltas were not
3666 // better util for this?
3667 if (eventDef.allDay) {
3668 copy.range = {
3669 start: startOfDay(copy.range.start),
3670 end: startOfDay(copy.range.end),
3671 };
3672 }
3673 // handle invalid durations
3674 if (copy.range.end < copy.range.start) {
3675 copy.range.end = getDefaultEventEnd(eventDef.allDay, copy.range.start, context);
3676 }
3677 return copy;
3678 }
3679
3680 class EventSourceImpl {
3681 constructor(context, internalEventSource) {
3682 this.context = context;
3683 this.internalEventSource = internalEventSource;
3684 }
3685 remove() {
3686 this.context.dispatch({
3687 type: 'REMOVE_EVENT_SOURCE',
3688 sourceId: this.internalEventSource.sourceId,
3689 });
3690 }
3691 refetch() {
3692 this.context.dispatch({
3693 type: 'FETCH_EVENT_SOURCES',
3694 sourceIds: [this.internalEventSource.sourceId],
3695 isRefetch: true,
3696 });
3697 }
3698 get id() {
3699 return this.internalEventSource.publicId;
3700 }
3701 get url() {
3702 return this.internalEventSource.meta.url;
3703 }
3704 get format() {
3705 return this.internalEventSource.meta.format; // TODO: bad. not guaranteed
3706 }
3707 }
3708
3709 class EventImpl {
3710 // instance will be null if expressing a recurring event that has no current instances,
3711 // OR if trying to validate an incoming external event that has no dates assigned
3712 constructor(context, def, instance) {
3713 this._context = context;
3714 this._def = def;
3715 this._instance = instance || null;
3716 }
3717 /*
3718 TODO: make event struct more responsible for this
3719 */
3720 setProp(name, val) {
3721 if (name in EVENT_DATE_REFINERS) {
3722 console.warn('Could not set date-related prop \'name\'. Use one of the date-related methods instead.');
3723 // TODO: make proper aliasing system?
3724 }
3725 else if (name === 'id') {
3726 val = EVENT_NON_DATE_REFINERS[name](val);
3727 this.mutate({
3728 standardProps: { publicId: val }, // hardcoded internal name
3729 });
3730 }
3731 else if (name in EVENT_NON_DATE_REFINERS) {
3732 val = EVENT_NON_DATE_REFINERS[name](val);
3733 this.mutate({
3734 standardProps: { [name]: val },
3735 });
3736 }
3737 else if (name in EVENT_UI_REFINERS) {
3738 let ui = EVENT_UI_REFINERS[name](val);
3739 if (name === 'color') {
3740 ui = { backgroundColor: val, borderColor: val };
3741 }
3742 else if (name === 'editable') {
3743 ui = { startEditable: val, durationEditable: val };
3744 }
3745 else {
3746 ui = { [name]: val };
3747 }
3748 this.mutate({
3749 standardProps: { ui },
3750 });
3751 }
3752 else {
3753 console.warn(`Could not set prop '${name}'. Use setExtendedProp instead.`);
3754 }
3755 }
3756 setExtendedProp(name, val) {
3757 this.mutate({
3758 extendedProps: { [name]: val },
3759 });
3760 }
3761 setStart(startInput, options = {}) {
3762 let { dateEnv } = this._context;
3763 let start = dateEnv.createMarker(startInput);
3764 if (start && this._instance) { // TODO: warning if parsed bad
3765 let instanceRange = this._instance.range;
3766 let startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity); // what if parsed bad!?
3767 if (options.maintainDuration) {
3768 this.mutate({ datesDelta: startDelta });
3769 }
3770 else {
3771 this.mutate({ startDelta });
3772 }
3773 }
3774 }
3775 setEnd(endInput, options = {}) {
3776 let { dateEnv } = this._context;
3777 let end;
3778 if (endInput != null) {
3779 end = dateEnv.createMarker(endInput);
3780 if (!end) {
3781 return; // TODO: warning if parsed bad
3782 }
3783 }
3784 if (this._instance) {
3785 if (end) {
3786 let endDelta = diffDates(this._instance.range.end, end, dateEnv, options.granularity);
3787 this.mutate({ endDelta });
3788 }
3789 else {
3790 this.mutate({ standardProps: { hasEnd: false } });
3791 }
3792 }
3793 }
3794 setDates(startInput, endInput, options = {}) {
3795 let { dateEnv } = this._context;
3796 let standardProps = { allDay: options.allDay };
3797 let start = dateEnv.createMarker(startInput);
3798 let end;
3799 if (!start) {
3800 return; // TODO: warning if parsed bad
3801 }
3802 if (endInput != null) {
3803 end = dateEnv.createMarker(endInput);
3804 if (!end) { // TODO: warning if parsed bad
3805 return;
3806 }
3807 }
3808 if (this._instance) {
3809 let instanceRange = this._instance.range;
3810 // when computing the diff for an event being converted to all-day,
3811 // compute diff off of the all-day values the way event-mutation does.
3812 if (options.allDay === true) {
3813 instanceRange = computeAlignedDayRange(instanceRange);
3814 }
3815 let startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity);
3816 if (end) {
3817 let endDelta = diffDates(instanceRange.end, end, dateEnv, options.granularity);
3818 if (durationsEqual(startDelta, endDelta)) {
3819 this.mutate({ datesDelta: startDelta, standardProps });
3820 }
3821 else {
3822 this.mutate({ startDelta, endDelta, standardProps });
3823 }
3824 }
3825 else { // means "clear the end"
3826 standardProps.hasEnd = false;
3827 this.mutate({ datesDelta: startDelta, standardProps });
3828 }
3829 }
3830 }
3831 moveStart(deltaInput) {
3832 let delta = createDuration(deltaInput);
3833 if (delta) { // TODO: warning if parsed bad
3834 this.mutate({ startDelta: delta });
3835 }
3836 }
3837 moveEnd(deltaInput) {
3838 let delta = createDuration(deltaInput);
3839 if (delta) { // TODO: warning if parsed bad
3840 this.mutate({ endDelta: delta });
3841 }
3842 }
3843 moveDates(deltaInput) {
3844 let delta = createDuration(deltaInput);
3845 if (delta) { // TODO: warning if parsed bad
3846 this.mutate({ datesDelta: delta });
3847 }
3848 }
3849 setAllDay(allDay, options = {}) {
3850 let standardProps = { allDay };
3851 let { maintainDuration } = options;
3852 if (maintainDuration == null) {
3853 maintainDuration = this._context.options.allDayMaintainDuration;
3854 }
3855 if (this._def.allDay !== allDay) {
3856 standardProps.hasEnd = maintainDuration;
3857 }
3858 this.mutate({ standardProps });
3859 }
3860 formatRange(formatInput) {
3861 let { dateEnv } = this._context;
3862 let instance = this._instance;
3863 let formatter = createFormatter(formatInput);
3864 if (this._def.hasEnd) {
3865 return dateEnv.formatRange(instance.range.start, instance.range.end, formatter, {
3866 forcedStartTzo: instance.forcedStartTzo,
3867 forcedEndTzo: instance.forcedEndTzo,
3868 });
3869 }
3870 return dateEnv.format(instance.range.start, formatter, {
3871 forcedTzo: instance.forcedStartTzo,
3872 });
3873 }
3874 mutate(mutation) {
3875 let instance = this._instance;
3876 if (instance) {
3877 let def = this._def;
3878 let context = this._context;
3879 let { eventStore } = context.getCurrentData();
3880 let relevantEvents = getRelevantEvents(eventStore, instance.instanceId);
3881 let eventConfigBase = {
3882 '': {
3883 display: '',
3884 startEditable: true,
3885 durationEditable: true,
3886 constraints: [],
3887 overlap: null,
3888 allows: [],
3889 backgroundColor: '',
3890 borderColor: '',
3891 textColor: '',
3892 classNames: [],
3893 },
3894 };
3895 relevantEvents = applyMutationToEventStore(relevantEvents, eventConfigBase, mutation, context);
3896 let oldEvent = new EventImpl(context, def, instance); // snapshot
3897 this._def = relevantEvents.defs[def.defId];
3898 this._instance = relevantEvents.instances[instance.instanceId];
3899 context.dispatch({
3900 type: 'MERGE_EVENTS',
3901 eventStore: relevantEvents,
3902 });
3903 context.emitter.trigger('eventChange', {
3904 oldEvent,
3905 event: this,
3906 relatedEvents: buildEventApis(relevantEvents, context, instance),
3907 revert() {
3908 context.dispatch({
3909 type: 'RESET_EVENTS',
3910 eventStore, // the ORIGINAL store
3911 });
3912 },
3913 });
3914 }
3915 }
3916 remove() {
3917 let context = this._context;
3918 let asStore = eventApiToStore(this);
3919 context.dispatch({
3920 type: 'REMOVE_EVENTS',
3921 eventStore: asStore,
3922 });
3923 context.emitter.trigger('eventRemove', {
3924 event: this,
3925 relatedEvents: [],
3926 revert() {
3927 context.dispatch({
3928 type: 'MERGE_EVENTS',
3929 eventStore: asStore,
3930 });
3931 },
3932 });
3933 }
3934 get source() {
3935 let { sourceId } = this._def;
3936 if (sourceId) {
3937 return new EventSourceImpl(this._context, this._context.getCurrentData().eventSources[sourceId]);
3938 }
3939 return null;
3940 }
3941 get start() {
3942 return this._instance ?
3943 this._context.dateEnv.toDate(this._instance.range.start) :
3944 null;
3945 }
3946 get end() {
3947 return (this._instance && this._def.hasEnd) ?
3948 this._context.dateEnv.toDate(this._instance.range.end) :
3949 null;
3950 }
3951 get startStr() {
3952 let instance = this._instance;
3953 if (instance) {
3954 return this._context.dateEnv.formatIso(instance.range.start, {
3955 omitTime: this._def.allDay,
3956 forcedTzo: instance.forcedStartTzo,
3957 });
3958 }
3959 return '';
3960 }
3961 get endStr() {
3962 let instance = this._instance;
3963 if (instance && this._def.hasEnd) {
3964 return this._context.dateEnv.formatIso(instance.range.end, {
3965 omitTime: this._def.allDay,
3966 forcedTzo: instance.forcedEndTzo,
3967 });
3968 }
3969 return '';
3970 }
3971 // computable props that all access the def
3972 // TODO: find a TypeScript-compatible way to do this at scale
3973 get id() { return this._def.publicId; }
3974 get groupId() { return this._def.groupId; }
3975 get allDay() { return this._def.allDay; }
3976 get title() { return this._def.title; }
3977 get url() { return this._def.url; }
3978 get display() { return this._def.ui.display || 'auto'; } // bad. just normalize the type earlier
3979 get startEditable() { return this._def.ui.startEditable; }
3980 get durationEditable() { return this._def.ui.durationEditable; }
3981 get constraint() { return this._def.ui.constraints[0] || null; }
3982 get overlap() { return this._def.ui.overlap; }
3983 get allow() { return this._def.ui.allows[0] || null; }
3984 get backgroundColor() { return this._def.ui.backgroundColor; }
3985 get borderColor() { return this._def.ui.borderColor; }
3986 get textColor() { return this._def.ui.textColor; }
3987 // NOTE: user can't modify these because Object.freeze was called in event-def parsing
3988 get classNames() { return this._def.ui.classNames; }
3989 get extendedProps() { return this._def.extendedProps; }
3990 toPlainObject(settings = {}) {
3991 let def = this._def;
3992 let { ui } = def;
3993 let { startStr, endStr } = this;
3994 let res = {
3995 allDay: def.allDay,
3996 };
3997 if (def.title) {
3998 res.title = def.title;
3999 }
4000 if (startStr) {
4001 res.start = startStr;
4002 }
4003 if (endStr) {
4004 res.end = endStr;
4005 }
4006 if (def.publicId) {
4007 res.id = def.publicId;
4008 }
4009 if (def.groupId) {
4010 res.groupId = def.groupId;
4011 }
4012 if (def.url) {
4013 res.url = def.url;
4014 }
4015 if (ui.display && ui.display !== 'auto') {
4016 res.display = ui.display;
4017 }
4018 // TODO: what about recurring-event properties???
4019 // TODO: include startEditable/durationEditable/constraint/overlap/allow
4020 if (settings.collapseColor && ui.backgroundColor && ui.backgroundColor === ui.borderColor) {
4021 res.color = ui.backgroundColor;
4022 }
4023 else {
4024 if (ui.backgroundColor) {
4025 res.backgroundColor = ui.backgroundColor;
4026 }
4027 if (ui.borderColor) {
4028 res.borderColor = ui.borderColor;
4029 }
4030 }
4031 if (ui.textColor) {
4032 res.textColor = ui.textColor;
4033 }
4034 if (ui.classNames.length) {
4035 res.classNames = ui.classNames;
4036 }
4037 if (Object.keys(def.extendedProps).length) {
4038 if (settings.collapseExtendedProps) {
4039 Object.assign(res, def.extendedProps);
4040 }
4041 else {
4042 res.extendedProps = def.extendedProps;
4043 }
4044 }
4045 return res;
4046 }
4047 toJSON() {
4048 return this.toPlainObject();
4049 }
4050 }
4051 function eventApiToStore(eventApi) {
4052 let def = eventApi._def;
4053 let instance = eventApi._instance;
4054 return {
4055 defs: { [def.defId]: def },
4056 instances: instance
4057 ? { [instance.instanceId]: instance }
4058 : {},
4059 };
4060 }
4061 function buildEventApis(eventStore, context, excludeInstance) {
4062 let { defs, instances } = eventStore;
4063 let eventApis = [];
4064 let excludeInstanceId = excludeInstance ? excludeInstance.instanceId : '';
4065 for (let id in instances) {
4066 let instance = instances[id];
4067 let def = defs[instance.defId];
4068 if (instance.instanceId !== excludeInstanceId) {
4069 eventApis.push(new EventImpl(context, def, instance));
4070 }
4071 }
4072 return eventApis;
4073 }
4074
4075 /*
4076 Specifying nextDayThreshold signals that all-day ranges should be sliced.
4077 */
4078 function sliceEventStore(eventStore, eventUiBases, framingRange, nextDayThreshold) {
4079 let inverseBgByGroupId = {};
4080 let inverseBgByDefId = {};
4081 let defByGroupId = {};
4082 let bgRanges = [];
4083 let fgRanges = [];
4084 let eventUis = compileEventUis(eventStore.defs, eventUiBases);
4085 for (let defId in eventStore.defs) {
4086 let def = eventStore.defs[defId];
4087 let ui = eventUis[def.defId];
4088 if (ui.display === 'inverse-background') {
4089 if (def.groupId) {
4090 inverseBgByGroupId[def.groupId] = [];
4091 if (!defByGroupId[def.groupId]) {
4092 defByGroupId[def.groupId] = def;
4093 }
4094 }
4095 else {
4096 inverseBgByDefId[defId] = [];
4097 }
4098 }
4099 }
4100 for (let instanceId in eventStore.instances) {
4101 let instance = eventStore.instances[instanceId];
4102 let def = eventStore.defs[instance.defId];
4103 let ui = eventUis[def.defId];
4104 let origRange = instance.range;
4105 let normalRange = (!def.allDay && nextDayThreshold) ?
4106 computeVisibleDayRange(origRange, nextDayThreshold) :
4107 origRange;
4108 let slicedRange = intersectRanges(normalRange, framingRange);
4109 if (slicedRange) {
4110 if (ui.display === 'inverse-background') {
4111 if (def.groupId) {
4112 inverseBgByGroupId[def.groupId].push(slicedRange);
4113 }
4114 else {
4115 inverseBgByDefId[instance.defId].push(slicedRange);
4116 }
4117 }
4118 else if (ui.display !== 'none') {
4119 (ui.display === 'background' ? bgRanges : fgRanges).push({
4120 def,
4121 ui,
4122 instance,
4123 range: slicedRange,
4124 isStart: normalRange.start && normalRange.start.valueOf() === slicedRange.start.valueOf(),
4125 isEnd: normalRange.end && normalRange.end.valueOf() === slicedRange.end.valueOf(),
4126 });
4127 }
4128 }
4129 }
4130 for (let groupId in inverseBgByGroupId) { // BY GROUP
4131 let ranges = inverseBgByGroupId[groupId];
4132 let invertedRanges = invertRanges(ranges, framingRange);
4133 for (let invertedRange of invertedRanges) {
4134 let def = defByGroupId[groupId];
4135 let ui = eventUis[def.defId];
4136 bgRanges.push({
4137 def,
4138 ui,
4139 instance: null,
4140 range: invertedRange,
4141 isStart: false,
4142 isEnd: false,
4143 });
4144 }
4145 }
4146 for (let defId in inverseBgByDefId) {
4147 let ranges = inverseBgByDefId[defId];
4148 let invertedRanges = invertRanges(ranges, framingRange);
4149 for (let invertedRange of invertedRanges) {
4150 bgRanges.push({
4151 def: eventStore.defs[defId],
4152 ui: eventUis[defId],
4153 instance: null,
4154 range: invertedRange,
4155 isStart: false,
4156 isEnd: false,
4157 });
4158 }
4159 }
4160 return { bg: bgRanges, fg: fgRanges };
4161 }
4162 function hasBgRendering(def) {
4163 return def.ui.display === 'background' || def.ui.display === 'inverse-background';
4164 }
4165 function setElSeg(el, seg) {
4166 el.fcSeg = seg;
4167 }
4168 function getElSeg(el) {
4169 return el.fcSeg ||
4170 el.parentNode.fcSeg || // for the harness
4171 null;
4172 }
4173 // event ui computation
4174 function compileEventUis(eventDefs, eventUiBases) {
4175 return mapHash(eventDefs, (eventDef) => compileEventUi(eventDef, eventUiBases));
4176 }
4177 function compileEventUi(eventDef, eventUiBases) {
4178 let uis = [];
4179 if (eventUiBases['']) {
4180 uis.push(eventUiBases['']);
4181 }
4182 if (eventUiBases[eventDef.defId]) {
4183 uis.push(eventUiBases[eventDef.defId]);
4184 }
4185 uis.push(eventDef.ui);
4186 return combineEventUis(uis);
4187 }
4188 function sortEventSegs(segs, eventOrderSpecs) {
4189 let objs = segs.map(buildSegCompareObj);
4190 objs.sort((obj0, obj1) => compareByFieldSpecs(obj0, obj1, eventOrderSpecs));
4191 return objs.map((c) => c._seg);
4192 }
4193 // returns a object with all primitive props that can be compared
4194 function buildSegCompareObj(seg) {
4195 let { eventRange } = seg;
4196 let eventDef = eventRange.def;
4197 let range = eventRange.instance ? eventRange.instance.range : eventRange.range;
4198 let start = range.start ? range.start.valueOf() : 0; // TODO: better support for open-range events
4199 let end = range.end ? range.end.valueOf() : 0; // "
4200 return Object.assign(Object.assign(Object.assign({}, eventDef.extendedProps), eventDef), { id: eventDef.publicId, start,
4201 end, duration: end - start, allDay: Number(eventDef.allDay), _seg: seg });
4202 }
4203 function computeSegDraggable(seg, context) {
4204 let { pluginHooks } = context;
4205 let transformers = pluginHooks.isDraggableTransformers;
4206 let { def, ui } = seg.eventRange;
4207 let val = ui.startEditable;
4208 for (let transformer of transformers) {
4209 val = transformer(val, def, ui, context);
4210 }
4211 return val;
4212 }
4213 function computeSegStartResizable(seg, context) {
4214 return seg.isStart && seg.eventRange.ui.durationEditable && context.options.eventResizableFromStart;
4215 }
4216 function computeSegEndResizable(seg, context) {
4217 return seg.isEnd && seg.eventRange.ui.durationEditable;
4218 }
4219 function buildSegTimeText(seg, timeFormat, context, defaultDisplayEventTime, // defaults to true
4220 defaultDisplayEventEnd, // defaults to true
4221 startOverride, endOverride) {
4222 let { dateEnv, options } = context;
4223 let { displayEventTime, displayEventEnd } = options;
4224 let eventDef = seg.eventRange.def;
4225 let eventInstance = seg.eventRange.instance;
4226 if (displayEventTime == null) {
4227 displayEventTime = defaultDisplayEventTime !== false;
4228 }
4229 if (displayEventEnd == null) {
4230 displayEventEnd = defaultDisplayEventEnd !== false;
4231 }
4232 let wholeEventStart = eventInstance.range.start;
4233 let wholeEventEnd = eventInstance.range.end;
4234 let segStart = startOverride || seg.start || seg.eventRange.range.start;
4235 let segEnd = endOverride || seg.end || seg.eventRange.range.end;
4236 let isStartDay = startOfDay(wholeEventStart).valueOf() === startOfDay(segStart).valueOf();
4237 let isEndDay = startOfDay(addMs(wholeEventEnd, -1)).valueOf() === startOfDay(addMs(segEnd, -1)).valueOf();
4238 if (displayEventTime && !eventDef.allDay && (isStartDay || isEndDay)) {
4239 segStart = isStartDay ? wholeEventStart : segStart;
4240 segEnd = isEndDay ? wholeEventEnd : segEnd;
4241 if (displayEventEnd && eventDef.hasEnd) {
4242 return dateEnv.formatRange(segStart, segEnd, timeFormat, {
4243 forcedStartTzo: startOverride ? null : eventInstance.forcedStartTzo,
4244 forcedEndTzo: endOverride ? null : eventInstance.forcedEndTzo,
4245 });
4246 }
4247 return dateEnv.format(segStart, timeFormat, {
4248 forcedTzo: startOverride ? null : eventInstance.forcedStartTzo, // nooooo, same
4249 });
4250 }
4251 return '';
4252 }
4253 function getSegMeta(seg, todayRange, nowDate) {
4254 let segRange = seg.eventRange.range;
4255 return {
4256 isPast: segRange.end <= (nowDate || todayRange.start),
4257 isFuture: segRange.start >= (nowDate || todayRange.end),
4258 isToday: todayRange && rangeContainsMarker(todayRange, segRange.start),
4259 };
4260 }
4261 function getEventClassNames(props) {
4262 let classNames = ['fc-event'];
4263 if (props.isMirror) {
4264 classNames.push('fc-event-mirror');
4265 }
4266 if (props.isDraggable) {
4267 classNames.push('fc-event-draggable');
4268 }
4269 if (props.isStartResizable || props.isEndResizable) {
4270 classNames.push('fc-event-resizable');
4271 }
4272 if (props.isDragging) {
4273 classNames.push('fc-event-dragging');
4274 }
4275 if (props.isResizing) {
4276 classNames.push('fc-event-resizing');
4277 }
4278 if (props.isSelected) {
4279 classNames.push('fc-event-selected');
4280 }
4281 if (props.isStart) {
4282 classNames.push('fc-event-start');
4283 }
4284 if (props.isEnd) {
4285 classNames.push('fc-event-end');
4286 }
4287 if (props.isPast) {
4288 classNames.push('fc-event-past');
4289 }
4290 if (props.isToday) {
4291 classNames.push('fc-event-today');
4292 }
4293 if (props.isFuture) {
4294 classNames.push('fc-event-future');
4295 }
4296 return classNames;
4297 }
4298 function buildEventRangeKey(eventRange) {
4299 return eventRange.instance
4300 ? eventRange.instance.instanceId
4301 : `${eventRange.def.defId}:${eventRange.range.start.toISOString()}`;
4302 // inverse-background events don't have specific instances. TODO: better solution
4303 }
4304 function getSegAnchorAttrs(seg, context) {
4305 let { def, instance } = seg.eventRange;
4306 let { url } = def;
4307 if (url) {
4308 return { href: url };
4309 }
4310 let { emitter, options } = context;
4311 let { eventInteractive } = options;
4312 if (eventInteractive == null) {
4313 eventInteractive = def.interactive;
4314 if (eventInteractive == null) {
4315 eventInteractive = Boolean(emitter.hasHandlers('eventClick'));
4316 }
4317 }
4318 // mock what happens in EventClicking
4319 if (eventInteractive) {
4320 // only attach keyboard-related handlers because click handler is already done in EventClicking
4321 return createAriaKeyboardAttrs((ev) => {
4322 emitter.trigger('eventClick', {
4323 el: ev.target,
4324 event: new EventImpl(context, def, instance),
4325 jsEvent: ev,
4326 view: context.viewApi,
4327 });
4328 });
4329 }
4330 return {};
4331 }
4332
4333 const STANDARD_PROPS = {
4334 start: identity,
4335 end: identity,
4336 allDay: Boolean,
4337 };
4338 function parseDateSpan(raw, dateEnv, defaultDuration) {
4339 let span = parseOpenDateSpan(raw, dateEnv);
4340 let { range } = span;
4341 if (!range.start) {
4342 return null;
4343 }
4344 if (!range.end) {
4345 if (defaultDuration == null) {
4346 return null;
4347 }
4348 range.end = dateEnv.add(range.start, defaultDuration);
4349 }
4350 return span;
4351 }
4352 /*
4353 TODO: somehow combine with parseRange?
4354 Will return null if the start/end props were present but parsed invalidly.
4355 */
4356 function parseOpenDateSpan(raw, dateEnv) {
4357 let { refined: standardProps, extra } = refineProps(raw, STANDARD_PROPS);
4358 let startMeta = standardProps.start ? dateEnv.createMarkerMeta(standardProps.start) : null;
4359 let endMeta = standardProps.end ? dateEnv.createMarkerMeta(standardProps.end) : null;
4360 let { allDay } = standardProps;
4361 if (allDay == null) {
4362 allDay = (startMeta && startMeta.isTimeUnspecified) &&
4363 (!endMeta || endMeta.isTimeUnspecified);
4364 }
4365 return Object.assign({ range: {
4366 start: startMeta ? startMeta.marker : null,
4367 end: endMeta ? endMeta.marker : null,
4368 }, allDay }, extra);
4369 }
4370 function isDateSpansEqual(span0, span1) {
4371 return rangesEqual(span0.range, span1.range) &&
4372 span0.allDay === span1.allDay &&
4373 isSpanPropsEqual(span0, span1);
4374 }
4375 // the NON-DATE-RELATED props
4376 function isSpanPropsEqual(span0, span1) {
4377 for (let propName in span1) {
4378 if (propName !== 'range' && propName !== 'allDay') {
4379 if (span0[propName] !== span1[propName]) {
4380 return false;
4381 }
4382 }
4383 }
4384 // are there any props that span0 has that span1 DOESN'T have?
4385 // both have range/allDay, so no need to special-case.
4386 for (let propName in span0) {
4387 if (!(propName in span1)) {
4388 return false;
4389 }
4390 }
4391 return true;
4392 }
4393 function buildDateSpanApi(span, dateEnv) {
4394 return Object.assign(Object.assign({}, buildRangeApi(span.range, dateEnv, span.allDay)), { allDay: span.allDay });
4395 }
4396 function buildRangeApiWithTimeZone(range, dateEnv, omitTime) {
4397 return Object.assign(Object.assign({}, buildRangeApi(range, dateEnv, omitTime)), { timeZone: dateEnv.timeZone });
4398 }
4399 function buildRangeApi(range, dateEnv, omitTime) {
4400 return {
4401 start: dateEnv.toDate(range.start),
4402 end: dateEnv.toDate(range.end),
4403 startStr: dateEnv.formatIso(range.start, { omitTime }),
4404 endStr: dateEnv.formatIso(range.end, { omitTime }),
4405 };
4406 }
4407 function fabricateEventRange(dateSpan, eventUiBases, context) {
4408 let res = refineEventDef({ editable: false }, context);
4409 let def = parseEventDef(res.refined, res.extra, '', // sourceId
4410 dateSpan.allDay, true, // hasEnd
4411 context);
4412 return {
4413 def,
4414 ui: compileEventUi(def, eventUiBases),
4415 instance: createEventInstance(def.defId, dateSpan.range),
4416 range: dateSpan.range,
4417 isStart: true,
4418 isEnd: true,
4419 };
4420 }
4421
4422 let calendarSystemClassMap = {};
4423 function registerCalendarSystem(name, theClass) {
4424 calendarSystemClassMap[name] = theClass;
4425 }
4426 function createCalendarSystem(name) {
4427 return new calendarSystemClassMap[name]();
4428 }
4429 class GregorianCalendarSystem {
4430 getMarkerYear(d) {
4431 return d.getUTCFullYear();
4432 }
4433 getMarkerMonth(d) {
4434 return d.getUTCMonth();
4435 }
4436 getMarkerDay(d) {
4437 return d.getUTCDate();
4438 }
4439 arrayToMarker(arr) {
4440 return arrayToUtcDate(arr);
4441 }
4442 markerToArray(marker) {
4443 return dateToUtcArray(marker);
4444 }
4445 }
4446 registerCalendarSystem('gregory', GregorianCalendarSystem);
4447
4448 const ISO_RE = /^\s*(\d{4})(-?(\d{2})(-?(\d{2})([T ](\d{2}):?(\d{2})(:?(\d{2})(\.(\d+))?)?(Z|(([-+])(\d{2})(:?(\d{2}))?))?)?)?)?$/;
4449 function parse(str) {
4450 let m = ISO_RE.exec(str);
4451 if (m) {
4452 let marker = new Date(Date.UTC(Number(m[1]), m[3] ? Number(m[3]) - 1 : 0, Number(m[5] || 1), Number(m[7] || 0), Number(m[8] || 0), Number(m[10] || 0), m[12] ? Number(`0.${m[12]}`) * 1000 : 0));
4453 if (isValidDate(marker)) {
4454 let timeZoneOffset = null;
4455 if (m[13]) {
4456 timeZoneOffset = (m[15] === '-' ? -1 : 1) * (Number(m[16] || 0) * 60 +
4457 Number(m[18] || 0));
4458 }
4459 return {
4460 marker,
4461 isTimeUnspecified: !m[6],
4462 timeZoneOffset,
4463 };
4464 }
4465 }
4466 return null;
4467 }
4468
4469 class DateEnv {
4470 constructor(settings) {
4471 let timeZone = this.timeZone = settings.timeZone;
4472 let isNamedTimeZone = timeZone !== 'local' && timeZone !== 'UTC';
4473 if (settings.namedTimeZoneImpl && isNamedTimeZone) {
4474 this.namedTimeZoneImpl = new settings.namedTimeZoneImpl(timeZone);
4475 }
4476 this.canComputeOffset = Boolean(!isNamedTimeZone || this.namedTimeZoneImpl);
4477 this.calendarSystem = createCalendarSystem(settings.calendarSystem);
4478 this.locale = settings.locale;
4479 this.weekDow = settings.locale.week.dow;
4480 this.weekDoy = settings.locale.week.doy;
4481 if (settings.weekNumberCalculation === 'ISO') {
4482 this.weekDow = 1;
4483 this.weekDoy = 4;
4484 }
4485 if (typeof settings.firstDay === 'number') {
4486 this.weekDow = settings.firstDay;
4487 }
4488 if (typeof settings.weekNumberCalculation === 'function') {
4489 this.weekNumberFunc = settings.weekNumberCalculation;
4490 }
4491 this.weekText = settings.weekText != null ? settings.weekText : settings.locale.options.weekText;
4492 this.weekTextLong = (settings.weekTextLong != null ? settings.weekTextLong : settings.locale.options.weekTextLong) || this.weekText;
4493 this.cmdFormatter = settings.cmdFormatter;
4494 this.defaultSeparator = settings.defaultSeparator;
4495 }
4496 // Creating / Parsing
4497 createMarker(input) {
4498 let meta = this.createMarkerMeta(input);
4499 if (meta === null) {
4500 return null;
4501 }
4502 return meta.marker;
4503 }
4504 createNowMarker() {
4505 if (this.canComputeOffset) {
4506 return this.timestampToMarker(new Date().valueOf());
4507 }
4508 // if we can't compute the current date val for a timezone,
4509 // better to give the current local date vals than UTC
4510 return arrayToUtcDate(dateToLocalArray(new Date()));
4511 }
4512 createMarkerMeta(input) {
4513 if (typeof input === 'string') {
4514 return this.parse(input);
4515 }
4516 let marker = null;
4517 if (typeof input === 'number') {
4518 marker = this.timestampToMarker(input);
4519 }
4520 else if (input instanceof Date) {
4521 input = input.valueOf();
4522 if (!isNaN(input)) {
4523 marker = this.timestampToMarker(input);
4524 }
4525 }
4526 else if (Array.isArray(input)) {
4527 marker = arrayToUtcDate(input);
4528 }
4529 if (marker === null || !isValidDate(marker)) {
4530 return null;
4531 }
4532 return { marker, isTimeUnspecified: false, forcedTzo: null };
4533 }
4534 parse(s) {
4535 let parts = parse(s);
4536 if (parts === null) {
4537 return null;
4538 }
4539 let { marker } = parts;
4540 let forcedTzo = null;
4541 if (parts.timeZoneOffset !== null) {
4542 if (this.canComputeOffset) {
4543 marker = this.timestampToMarker(marker.valueOf() - parts.timeZoneOffset * 60 * 1000);
4544 }
4545 else {
4546 forcedTzo = parts.timeZoneOffset;
4547 }
4548 }
4549 return { marker, isTimeUnspecified: parts.isTimeUnspecified, forcedTzo };
4550 }
4551 // Accessors
4552 getYear(marker) {
4553 return this.calendarSystem.getMarkerYear(marker);
4554 }
4555 getMonth(marker) {
4556 return this.calendarSystem.getMarkerMonth(marker);
4557 }
4558 getDay(marker) {
4559 return this.calendarSystem.getMarkerDay(marker);
4560 }
4561 // Adding / Subtracting
4562 add(marker, dur) {
4563 let a = this.calendarSystem.markerToArray(marker);
4564 a[0] += dur.years;
4565 a[1] += dur.months;
4566 a[2] += dur.days;
4567 a[6] += dur.milliseconds;
4568 return this.calendarSystem.arrayToMarker(a);
4569 }
4570 subtract(marker, dur) {
4571 let a = this.calendarSystem.markerToArray(marker);
4572 a[0] -= dur.years;
4573 a[1] -= dur.months;
4574 a[2] -= dur.days;
4575 a[6] -= dur.milliseconds;
4576 return this.calendarSystem.arrayToMarker(a);
4577 }
4578 addYears(marker, n) {
4579 let a = this.calendarSystem.markerToArray(marker);
4580 a[0] += n;
4581 return this.calendarSystem.arrayToMarker(a);
4582 }
4583 addMonths(marker, n) {
4584 let a = this.calendarSystem.markerToArray(marker);
4585 a[1] += n;
4586 return this.calendarSystem.arrayToMarker(a);
4587 }
4588 // Diffing Whole Units
4589 diffWholeYears(m0, m1) {
4590 let { calendarSystem } = this;
4591 if (timeAsMs(m0) === timeAsMs(m1) &&
4592 calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1) &&
4593 calendarSystem.getMarkerMonth(m0) === calendarSystem.getMarkerMonth(m1)) {
4594 return calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0);
4595 }
4596 return null;
4597 }
4598 diffWholeMonths(m0, m1) {
4599 let { calendarSystem } = this;
4600 if (timeAsMs(m0) === timeAsMs(m1) &&
4601 calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1)) {
4602 return (calendarSystem.getMarkerMonth(m1) - calendarSystem.getMarkerMonth(m0)) +
4603 (calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0)) * 12;
4604 }
4605 return null;
4606 }
4607 // Range / Duration
4608 greatestWholeUnit(m0, m1) {
4609 let n = this.diffWholeYears(m0, m1);
4610 if (n !== null) {
4611 return { unit: 'year', value: n };
4612 }
4613 n = this.diffWholeMonths(m0, m1);
4614 if (n !== null) {
4615 return { unit: 'month', value: n };
4616 }
4617 n = diffWholeWeeks(m0, m1);
4618 if (n !== null) {
4619 return { unit: 'week', value: n };
4620 }
4621 n = diffWholeDays(m0, m1);
4622 if (n !== null) {
4623 return { unit: 'day', value: n };
4624 }
4625 n = diffHours(m0, m1);
4626 if (isInt(n)) {
4627 return { unit: 'hour', value: n };
4628 }
4629 n = diffMinutes(m0, m1);
4630 if (isInt(n)) {
4631 return { unit: 'minute', value: n };
4632 }
4633 n = diffSeconds(m0, m1);
4634 if (isInt(n)) {
4635 return { unit: 'second', value: n };
4636 }
4637 return { unit: 'millisecond', value: m1.valueOf() - m0.valueOf() };
4638 }
4639 countDurationsBetween(m0, m1, d) {
4640 // TODO: can use greatestWholeUnit
4641 let diff;
4642 if (d.years) {
4643 diff = this.diffWholeYears(m0, m1);
4644 if (diff !== null) {
4645 return diff / asRoughYears(d);
4646 }
4647 }
4648 if (d.months) {
4649 diff = this.diffWholeMonths(m0, m1);
4650 if (diff !== null) {
4651 return diff / asRoughMonths(d);
4652 }
4653 }
4654 if (d.days) {
4655 diff = diffWholeDays(m0, m1);
4656 if (diff !== null) {
4657 return diff / asRoughDays(d);
4658 }
4659 }
4660 return (m1.valueOf() - m0.valueOf()) / asRoughMs(d);
4661 }
4662 // Start-Of
4663 // these DON'T return zoned-dates. only UTC start-of dates
4664 startOf(m, unit) {
4665 if (unit === 'year') {
4666 return this.startOfYear(m);
4667 }
4668 if (unit === 'month') {
4669 return this.startOfMonth(m);
4670 }
4671 if (unit === 'week') {
4672 return this.startOfWeek(m);
4673 }
4674 if (unit === 'day') {
4675 return startOfDay(m);
4676 }
4677 if (unit === 'hour') {
4678 return startOfHour(m);
4679 }
4680 if (unit === 'minute') {
4681 return startOfMinute(m);
4682 }
4683 if (unit === 'second') {
4684 return startOfSecond(m);
4685 }
4686 return null;
4687 }
4688 startOfYear(m) {
4689 return this.calendarSystem.arrayToMarker([
4690 this.calendarSystem.getMarkerYear(m),
4691 ]);
4692 }
4693 startOfMonth(m) {
4694 return this.calendarSystem.arrayToMarker([
4695 this.calendarSystem.getMarkerYear(m),
4696 this.calendarSystem.getMarkerMonth(m),
4697 ]);
4698 }
4699 startOfWeek(m) {
4700 return this.calendarSystem.arrayToMarker([
4701 this.calendarSystem.getMarkerYear(m),
4702 this.calendarSystem.getMarkerMonth(m),
4703 m.getUTCDate() - ((m.getUTCDay() - this.weekDow + 7) % 7),
4704 ]);
4705 }
4706 // Week Number
4707 computeWeekNumber(marker) {
4708 if (this.weekNumberFunc) {
4709 return this.weekNumberFunc(this.toDate(marker));
4710 }
4711 return weekOfYear(marker, this.weekDow, this.weekDoy);
4712 }
4713 // TODO: choke on timeZoneName: long
4714 format(marker, formatter, dateOptions = {}) {
4715 return formatter.format({
4716 marker,
4717 timeZoneOffset: dateOptions.forcedTzo != null ?
4718 dateOptions.forcedTzo :
4719 this.offsetForMarker(marker),
4720 }, this);
4721 }
4722 formatRange(start, end, formatter, dateOptions = {}) {
4723 if (dateOptions.isEndExclusive) {
4724 end = addMs(end, -1);
4725 }
4726 return formatter.formatRange({
4727 marker: start,
4728 timeZoneOffset: dateOptions.forcedStartTzo != null ?
4729 dateOptions.forcedStartTzo :
4730 this.offsetForMarker(start),
4731 }, {
4732 marker: end,
4733 timeZoneOffset: dateOptions.forcedEndTzo != null ?
4734 dateOptions.forcedEndTzo :
4735 this.offsetForMarker(end),
4736 }, this, dateOptions.defaultSeparator);
4737 }
4738 /*
4739 DUMB: the omitTime arg is dumb. if we omit the time, we want to omit the timezone offset. and if we do that,
4740 might as well use buildIsoString or some other util directly
4741 */
4742 formatIso(marker, extraOptions = {}) {
4743 let timeZoneOffset = null;
4744 if (!extraOptions.omitTimeZoneOffset) {
4745 if (extraOptions.forcedTzo != null) {
4746 timeZoneOffset = extraOptions.forcedTzo;
4747 }
4748 else {
4749 timeZoneOffset = this.offsetForMarker(marker);
4750 }
4751 }
4752 return buildIsoString(marker, timeZoneOffset, extraOptions.omitTime);
4753 }
4754 // TimeZone
4755 timestampToMarker(ms) {
4756 if (this.timeZone === 'local') {
4757 return arrayToUtcDate(dateToLocalArray(new Date(ms)));
4758 }
4759 if (this.timeZone === 'UTC' || !this.namedTimeZoneImpl) {
4760 return new Date(ms);
4761 }
4762 return arrayToUtcDate(this.namedTimeZoneImpl.timestampToArray(ms));
4763 }
4764 offsetForMarker(m) {
4765 if (this.timeZone === 'local') {
4766 return -arrayToLocalDate(dateToUtcArray(m)).getTimezoneOffset(); // convert "inverse" offset to "normal" offset
4767 }
4768 if (this.timeZone === 'UTC') {
4769 return 0;
4770 }
4771 if (this.namedTimeZoneImpl) {
4772 return this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m));
4773 }
4774 return null;
4775 }
4776 // Conversion
4777 toDate(m, forcedTzo) {
4778 if (this.timeZone === 'local') {
4779 return arrayToLocalDate(dateToUtcArray(m));
4780 }
4781 if (this.timeZone === 'UTC') {
4782 return new Date(m.valueOf()); // make sure it's a copy
4783 }
4784 if (!this.namedTimeZoneImpl) {
4785 return new Date(m.valueOf() - (forcedTzo || 0));
4786 }
4787 return new Date(m.valueOf() -
4788 this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m)) * 1000 * 60);
4789 }
4790 }
4791
4792 class NamedTimeZoneImpl {
4793 constructor(timeZoneName) {
4794 this.timeZoneName = timeZoneName;
4795 }
4796 }
4797
4798 class SegHierarchy {
4799 constructor(getEntryThickness = (entry) => {
4800 // if no thickness known, assume 1 (if 0, so small it always fits)
4801 return entry.thickness || 1;
4802 }) {
4803 this.getEntryThickness = getEntryThickness;
4804 // settings
4805 this.strictOrder = false;
4806 this.allowReslicing = false;
4807 this.maxCoord = -1; // -1 means no max
4808 this.maxStackCnt = -1; // -1 means no max
4809 this.levelCoords = []; // ordered
4810 this.entriesByLevel = []; // parallel with levelCoords
4811 this.stackCnts = {}; // TODO: use better technique!?
4812 }
4813 addSegs(inputs) {
4814 let hiddenEntries = [];
4815 for (let input of inputs) {
4816 this.insertEntry(input, hiddenEntries);
4817 }
4818 return hiddenEntries;
4819 }
4820 insertEntry(entry, hiddenEntries) {
4821 let insertion = this.findInsertion(entry);
4822 if (this.isInsertionValid(insertion, entry)) {
4823 this.insertEntryAt(entry, insertion);
4824 }
4825 else {
4826 this.handleInvalidInsertion(insertion, entry, hiddenEntries);
4827 }
4828 }
4829 isInsertionValid(insertion, entry) {
4830 return (this.maxCoord === -1 || insertion.levelCoord + this.getEntryThickness(entry) <= this.maxCoord) &&
4831 (this.maxStackCnt === -1 || insertion.stackCnt < this.maxStackCnt);
4832 }
4833 handleInvalidInsertion(insertion, entry, hiddenEntries) {
4834 if (this.allowReslicing && insertion.touchingEntry) {
4835 const hiddenEntry = Object.assign(Object.assign({}, entry), { span: intersectSpans(entry.span, insertion.touchingEntry.span) });
4836 hiddenEntries.push(hiddenEntry);
4837 this.splitEntry(entry, insertion.touchingEntry, hiddenEntries);
4838 }
4839 else {
4840 hiddenEntries.push(entry);
4841 }
4842 }
4843 /*
4844 Does NOT add what hit the `barrier` into hiddenEntries. Should already be done.
4845 */
4846 splitEntry(entry, barrier, hiddenEntries) {
4847 let entrySpan = entry.span;
4848 let barrierSpan = barrier.span;
4849 if (entrySpan.start < barrierSpan.start) {
4850 this.insertEntry({
4851 index: entry.index,
4852 thickness: entry.thickness,
4853 span: { start: entrySpan.start, end: barrierSpan.start },
4854 }, hiddenEntries);
4855 }
4856 if (entrySpan.end > barrierSpan.end) {
4857 this.insertEntry({
4858 index: entry.index,
4859 thickness: entry.thickness,
4860 span: { start: barrierSpan.end, end: entrySpan.end },
4861 }, hiddenEntries);
4862 }
4863 }
4864 insertEntryAt(entry, insertion) {
4865 let { entriesByLevel, levelCoords } = this;
4866 if (insertion.lateral === -1) {
4867 // create a new level
4868 insertAt(levelCoords, insertion.level, insertion.levelCoord);
4869 insertAt(entriesByLevel, insertion.level, [entry]);
4870 }
4871 else {
4872 // insert into existing level
4873 insertAt(entriesByLevel[insertion.level], insertion.lateral, entry);
4874 }
4875 this.stackCnts[buildEntryKey(entry)] = insertion.stackCnt;
4876 }
4877 /*
4878 does not care about limits
4879 */
4880 findInsertion(newEntry) {
4881 let { levelCoords, entriesByLevel, strictOrder, stackCnts } = this;
4882 let levelCnt = levelCoords.length;
4883 let candidateCoord = 0;
4884 let touchingLevel = -1;
4885 let touchingLateral = -1;
4886 let touchingEntry = null;
4887 let stackCnt = 0;
4888 for (let trackingLevel = 0; trackingLevel < levelCnt; trackingLevel += 1) {
4889 const trackingCoord = levelCoords[trackingLevel];
4890 // if the current level is past the placed entry, we have found a good empty space and can stop.
4891 // if strictOrder, keep finding more lateral intersections.
4892 if (!strictOrder && trackingCoord >= candidateCoord + this.getEntryThickness(newEntry)) {
4893 break;
4894 }
4895 let trackingEntries = entriesByLevel[trackingLevel];
4896 let trackingEntry;
4897 let searchRes = binarySearch(trackingEntries, newEntry.span.start, getEntrySpanEnd); // find first entry after newEntry's end
4898 let lateralIndex = searchRes[0] + searchRes[1]; // if exact match (which doesn't collide), go to next one
4899 while ( // loop through entries that horizontally intersect
4900 (trackingEntry = trackingEntries[lateralIndex]) && // but not past the whole entry list
4901 trackingEntry.span.start < newEntry.span.end // and not entirely past newEntry
4902 ) {
4903 let trackingEntryBottom = trackingCoord + this.getEntryThickness(trackingEntry);
4904 // intersects into the top of the candidate?
4905 if (trackingEntryBottom > candidateCoord) {
4906 candidateCoord = trackingEntryBottom;
4907 touchingEntry = trackingEntry;
4908 touchingLevel = trackingLevel;
4909 touchingLateral = lateralIndex;
4910 }
4911 // butts up against top of candidate? (will happen if just intersected as well)
4912 if (trackingEntryBottom === candidateCoord) {
4913 // accumulate the highest possible stackCnt of the trackingEntries that butt up
4914 stackCnt = Math.max(stackCnt, stackCnts[buildEntryKey(trackingEntry)] + 1);
4915 }
4916 lateralIndex += 1;
4917 }
4918 }
4919 // the destination level will be after touchingEntry's level. find it
4920 let destLevel = 0;
4921 if (touchingEntry) {
4922 destLevel = touchingLevel + 1;
4923 while (destLevel < levelCnt && levelCoords[destLevel] < candidateCoord) {
4924 destLevel += 1;
4925 }
4926 }
4927 // if adding to an existing level, find where to insert
4928 let destLateral = -1;
4929 if (destLevel < levelCnt && levelCoords[destLevel] === candidateCoord) {
4930 destLateral = binarySearch(entriesByLevel[destLevel], newEntry.span.end, getEntrySpanEnd)[0];
4931 }
4932 return {
4933 touchingLevel,
4934 touchingLateral,
4935 touchingEntry,
4936 stackCnt,
4937 levelCoord: candidateCoord,
4938 level: destLevel,
4939 lateral: destLateral,
4940 };
4941 }
4942 // sorted by levelCoord (lowest to highest)
4943 toRects() {
4944 let { entriesByLevel, levelCoords } = this;
4945 let levelCnt = entriesByLevel.length;
4946 let rects = [];
4947 for (let level = 0; level < levelCnt; level += 1) {
4948 let entries = entriesByLevel[level];
4949 let levelCoord = levelCoords[level];
4950 for (let entry of entries) {
4951 rects.push(Object.assign(Object.assign({}, entry), { thickness: this.getEntryThickness(entry), levelCoord }));
4952 }
4953 }
4954 return rects;
4955 }
4956 }
4957 function getEntrySpanEnd(entry) {
4958 return entry.span.end;
4959 }
4960 function buildEntryKey(entry) {
4961 return entry.index + ':' + entry.span.start;
4962 }
4963 // returns groups with entries sorted by input order
4964 function groupIntersectingEntries(entries) {
4965 let merges = [];
4966 for (let entry of entries) {
4967 let filteredMerges = [];
4968 let hungryMerge = {
4969 span: entry.span,
4970 entries: [entry],
4971 };
4972 for (let merge of merges) {
4973 if (intersectSpans(merge.span, hungryMerge.span)) {
4974 hungryMerge = {
4975 entries: merge.entries.concat(hungryMerge.entries),
4976 span: joinSpans(merge.span, hungryMerge.span),
4977 };
4978 }
4979 else {
4980 filteredMerges.push(merge);
4981 }
4982 }
4983 filteredMerges.push(hungryMerge);
4984 merges = filteredMerges;
4985 }
4986 return merges;
4987 }
4988 function joinSpans(span0, span1) {
4989 return {
4990 start: Math.min(span0.start, span1.start),
4991 end: Math.max(span0.end, span1.end),
4992 };
4993 }
4994 function intersectSpans(span0, span1) {
4995 let start = Math.max(span0.start, span1.start);
4996 let end = Math.min(span0.end, span1.end);
4997 if (start < end) {
4998 return { start, end };
4999 }
5000 return null;
5001 }
5002 // general util
5003 // ---------------------------------------------------------------------------------------------------------------------
5004 function insertAt(arr, index, item) {
5005 arr.splice(index, 0, item);
5006 }
5007 function binarySearch(a, searchVal, getItemVal) {
5008 let startIndex = 0;
5009 let endIndex = a.length; // exclusive
5010 if (!endIndex || searchVal < getItemVal(a[startIndex])) { // no items OR before first item
5011 return [0, 0];
5012 }
5013 if (searchVal > getItemVal(a[endIndex - 1])) { // after last item
5014 return [endIndex, 0];
5015 }
5016 while (startIndex < endIndex) {
5017 let middleIndex = Math.floor(startIndex + (endIndex - startIndex) / 2);
5018 let middleVal = getItemVal(a[middleIndex]);
5019 if (searchVal < middleVal) {
5020 endIndex = middleIndex;
5021 }
5022 else if (searchVal > middleVal) {
5023 startIndex = middleIndex + 1;
5024 }
5025 else { // equal!
5026 return [middleIndex, 1];
5027 }
5028 }
5029 return [startIndex, 0];
5030 }
5031
5032 class Interaction {
5033 constructor(settings) {
5034 this.component = settings.component;
5035 this.isHitComboAllowed = settings.isHitComboAllowed || null;
5036 }
5037 destroy() {
5038 }
5039 }
5040 function parseInteractionSettings(component, input) {
5041 return {
5042 component,
5043 el: input.el,
5044 useEventCenter: input.useEventCenter != null ? input.useEventCenter : true,
5045 isHitComboAllowed: input.isHitComboAllowed || null,
5046 };
5047 }
5048 function interactionSettingsToStore(settings) {
5049 return {
5050 [settings.component.uid]: settings,
5051 };
5052 }
5053 // global state
5054 const interactionSettingsStore = {};
5055
5056 /*
5057 An abstraction for a dragging interaction originating on an event.
5058 Does higher-level things than PointerDragger, such as possibly:
5059 - a "mirror" that moves with the pointer
5060 - a minimum number of pixels or other criteria for a true drag to begin
5061
5062 subclasses must emit:
5063 - pointerdown
5064 - dragstart
5065 - dragmove
5066 - pointerup
5067 - dragend
5068 */
5069 class ElementDragging {
5070 constructor(el, selector) {
5071 this.emitter = new Emitter();
5072 }
5073 destroy() {
5074 }
5075 setMirrorIsVisible(bool) {
5076 // optional if subclass doesn't want to support a mirror
5077 }
5078 setMirrorNeedsRevert(bool) {
5079 // optional if subclass doesn't want to support a mirror
5080 }
5081 setAutoScrollEnabled(bool) {
5082 // optional
5083 }
5084 }
5085
5086 // TODO: get rid of this in favor of options system,
5087 // tho it's really easy to access this globally rather than pass thru options.
5088 const config = {};
5089
5090 /*
5091 Information about what will happen when an external element is dragged-and-dropped
5092 onto a calendar. Contains information for creating an event.
5093 */
5094 const DRAG_META_REFINERS = {
5095 startTime: createDuration,
5096 duration: createDuration,
5097 create: Boolean,
5098 sourceId: String,
5099 };
5100 function parseDragMeta(raw) {
5101 let { refined, extra } = refineProps(raw, DRAG_META_REFINERS);
5102 return {
5103 startTime: refined.startTime || null,
5104 duration: refined.duration || null,
5105 create: refined.create != null ? refined.create : true,
5106 sourceId: refined.sourceId,
5107 leftoverProps: extra,
5108 };
5109 }
5110
5111 class CalendarRoot extends BaseComponent {
5112 constructor() {
5113 super(...arguments);
5114 this.state = {
5115 forPrint: false,
5116 };
5117 this.handleBeforePrint = () => {
5118 flushSync(() => {
5119 this.setState({ forPrint: true });
5120 });
5121 };
5122 this.handleAfterPrint = () => {
5123 flushSync(() => {
5124 this.setState({ forPrint: false });
5125 });
5126 };
5127 }
5128 render() {
5129 let { props } = this;
5130 let { options } = props;
5131 let { forPrint } = this.state;
5132 let isHeightAuto = forPrint || options.height === 'auto' || options.contentHeight === 'auto';
5133 let height = (!isHeightAuto && options.height != null) ? options.height : '';
5134 let classNames = [
5135 'fc',
5136 forPrint ? 'fc-media-print' : 'fc-media-screen',
5137 `fc-direction-${options.direction}`,
5138 props.theme.getClass('root'),
5139 ];
5140 if (!getCanVGrowWithinCell()) {
5141 classNames.push('fc-liquid-hack');
5142 }
5143 return props.children(classNames, height, isHeightAuto, forPrint);
5144 }
5145 componentDidMount() {
5146 let { emitter } = this.props;
5147 emitter.on('_beforeprint', this.handleBeforePrint);
5148 emitter.on('_afterprint', this.handleAfterPrint);
5149 }
5150 componentWillUnmount() {
5151 let { emitter } = this.props;
5152 emitter.off('_beforeprint', this.handleBeforePrint);
5153 emitter.off('_afterprint', this.handleAfterPrint);
5154 }
5155 }
5156
5157 // Computes a default column header formatting string if `colFormat` is not explicitly defined
5158 function computeFallbackHeaderFormat(datesRepDistinctDays, dayCnt) {
5159 // if more than one week row, or if there are a lot of columns with not much space,
5160 // put just the day numbers will be in each cell
5161 if (!datesRepDistinctDays || dayCnt > 10) {
5162 return createFormatter({ weekday: 'short' }); // "Sat"
5163 }
5164 if (dayCnt > 1) {
5165 return createFormatter({ weekday: 'short', month: 'numeric', day: 'numeric', omitCommas: true }); // "Sat 11/12"
5166 }
5167 return createFormatter({ weekday: 'long' }); // "Saturday"
5168 }
5169
5170 const CLASS_NAME = 'fc-col-header-cell'; // do the cushion too? no
5171 function renderInner$1(renderProps) {
5172 return renderProps.text;
5173 }
5174
5175 class ContentInjector extends BaseComponent {
5176 constructor() {
5177 super(...arguments);
5178 this.id = guid();
5179 this.queuedDomNodes = [];
5180 this.currentDomNodes = [];
5181 this.handleEl = (el) => {
5182 const { options } = this.context;
5183 const { generatorName } = this.props;
5184 if (!options.customRenderingReplaces || !hasCustomRenderingHandler(generatorName, options)) {
5185 this.updateElRef(el);
5186 }
5187 };
5188 this.updateElRef = (el) => {
5189 if (this.props.elRef) {
5190 setRef(this.props.elRef, el);
5191 }
5192 };
5193 }
5194 render() {
5195 const { props, context } = this;
5196 const { options } = context;
5197 const { customGenerator, defaultGenerator, renderProps } = props;
5198 const attrs = buildElAttrs(props, [], this.handleEl);
5199 let useDefault = false;
5200 let innerContent;
5201 let queuedDomNodes = [];
5202 let currentGeneratorMeta;
5203 if (customGenerator != null) {
5204 const customGeneratorRes = typeof customGenerator === 'function' ?
5205 customGenerator(renderProps, y) :
5206 customGenerator;
5207 if (customGeneratorRes === true) {
5208 useDefault = true;
5209 }
5210 else {
5211 const isObject = customGeneratorRes && typeof customGeneratorRes === 'object'; // non-null
5212 if (isObject && ('html' in customGeneratorRes)) {
5213 attrs.dangerouslySetInnerHTML = { __html: customGeneratorRes.html };
5214 }
5215 else if (isObject && ('domNodes' in customGeneratorRes)) {
5216 queuedDomNodes = Array.prototype.slice.call(customGeneratorRes.domNodes);
5217 }
5218 else if (isObject
5219 ? i$1(customGeneratorRes) // vdom node
5220 : typeof customGeneratorRes !== 'function' // primitive value (like string or number)
5221 ) {
5222 // use in vdom
5223 innerContent = customGeneratorRes;
5224 }
5225 else {
5226 // an exotic object for handleCustomRendering
5227 currentGeneratorMeta = customGeneratorRes;
5228 }
5229 }
5230 }
5231 else {
5232 useDefault = !hasCustomRenderingHandler(props.generatorName, options);
5233 }
5234 if (useDefault && defaultGenerator) {
5235 innerContent = defaultGenerator(renderProps);
5236 }
5237 this.queuedDomNodes = queuedDomNodes;
5238 this.currentGeneratorMeta = currentGeneratorMeta;
5239 return y(props.elTag, attrs, innerContent);
5240 }
5241 componentDidMount() {
5242 this.applyQueueudDomNodes();
5243 this.triggerCustomRendering(true);
5244 }
5245 componentDidUpdate() {
5246 this.applyQueueudDomNodes();
5247 this.triggerCustomRendering(true);
5248 }
5249 componentWillUnmount() {
5250 this.triggerCustomRendering(false); // TODO: different API for removal?
5251 }
5252 triggerCustomRendering(isActive) {
5253 var _a;
5254 const { props, context } = this;
5255 const { handleCustomRendering, customRenderingMetaMap } = context.options;
5256 if (handleCustomRendering) {
5257 const generatorMeta = (_a = this.currentGeneratorMeta) !== null && _a !== void 0 ? _a : customRenderingMetaMap === null || customRenderingMetaMap === void 0 ? void 0 : customRenderingMetaMap[props.generatorName];
5258 if (generatorMeta) {
5259 handleCustomRendering(Object.assign(Object.assign({ id: this.id, isActive, containerEl: this.base, reportNewContainerEl: this.updateElRef, // front-end framework tells us about new container els
5260 generatorMeta }, props), { elClasses: (props.elClasses || []).filter(isTruthy) }));
5261 }
5262 }
5263 }
5264 applyQueueudDomNodes() {
5265 const { queuedDomNodes, currentDomNodes } = this;
5266 const el = this.base;
5267 if (!isArraysEqual(queuedDomNodes, currentDomNodes)) {
5268 currentDomNodes.forEach(removeElement);
5269 for (let newNode of queuedDomNodes) {
5270 el.appendChild(newNode);
5271 }
5272 this.currentDomNodes = queuedDomNodes;
5273 }
5274 }
5275 }
5276 ContentInjector.addPropsEquality({
5277 elClasses: isArraysEqual,
5278 elStyle: isPropsEqual,
5279 elAttrs: isNonHandlerPropsEqual,
5280 renderProps: isPropsEqual,
5281 });
5282 // Util
5283 /*
5284 Does UI-framework provide custom way of rendering that does not use Preact VDOM
5285 AND does the calendar's options define custom rendering?
5286 AKA. Should we NOT render the default content?
5287 */
5288 function hasCustomRenderingHandler(generatorName, options) {
5289 var _a;
5290 return Boolean(options.handleCustomRendering &&
5291 generatorName &&
5292 ((_a = options.customRenderingMetaMap) === null || _a === void 0 ? void 0 : _a[generatorName]));
5293 }
5294 function buildElAttrs(props, extraClassNames, elRef) {
5295 const attrs = Object.assign(Object.assign({}, props.elAttrs), { ref: elRef });
5296 if (props.elClasses || extraClassNames) {
5297 attrs.className = (props.elClasses || [])
5298 .concat(extraClassNames || [])
5299 .concat(attrs.className || [])
5300 .filter(Boolean)
5301 .join(' ');
5302 }
5303 if (props.elStyle) {
5304 attrs.style = props.elStyle;
5305 }
5306 return attrs;
5307 }
5308 function isTruthy(val) {
5309 return Boolean(val);
5310 }
5311
5312 const RenderId = createContext(0);
5313
5314 class ContentContainer extends x$1 {
5315 constructor() {
5316 super(...arguments);
5317 this.InnerContent = InnerContentInjector.bind(undefined, this);
5318 this.handleEl = (el) => {
5319 this.el = el;
5320 if (this.props.elRef) {
5321 setRef(this.props.elRef, el);
5322 if (el && this.didMountMisfire) {
5323 this.componentDidMount();
5324 }
5325 }
5326 };
5327 }
5328 render() {
5329 const { props } = this;
5330 const generatedClassNames = generateClassNames(props.classNameGenerator, props.renderProps);
5331 if (props.children) {
5332 const elAttrs = buildElAttrs(props, generatedClassNames, this.handleEl);
5333 const children = props.children(this.InnerContent, props.renderProps, elAttrs);
5334 if (props.elTag) {
5335 return y(props.elTag, elAttrs, children);
5336 }
5337 else {
5338 return children;
5339 }
5340 }
5341 else {
5342 return y((ContentInjector), Object.assign(Object.assign({}, props), { elRef: this.handleEl, elTag: props.elTag || 'div', elClasses: (props.elClasses || []).concat(generatedClassNames), renderId: this.context }));
5343 }
5344 }
5345 componentDidMount() {
5346 var _a, _b;
5347 if (this.el) {
5348 (_b = (_a = this.props).didMount) === null || _b === void 0 ? void 0 : _b.call(_a, Object.assign(Object.assign({}, this.props.renderProps), { el: this.el }));
5349 }
5350 else {
5351 this.didMountMisfire = true;
5352 }
5353 }
5354 componentWillUnmount() {
5355 var _a, _b;
5356 (_b = (_a = this.props).willUnmount) === null || _b === void 0 ? void 0 : _b.call(_a, Object.assign(Object.assign({}, this.props.renderProps), { el: this.el }));
5357 }
5358 }
5359 ContentContainer.contextType = RenderId;
5360 function InnerContentInjector(containerComponent, props) {
5361 const parentProps = containerComponent.props;
5362 return y((ContentInjector), Object.assign({ renderProps: parentProps.renderProps, generatorName: parentProps.generatorName, customGenerator: parentProps.customGenerator, defaultGenerator: parentProps.defaultGenerator, renderId: containerComponent.context }, props));
5363 }
5364 // Utils
5365 function generateClassNames(classNameGenerator, renderProps) {
5366 const classNames = typeof classNameGenerator === 'function' ?
5367 classNameGenerator(renderProps) :
5368 classNameGenerator || [];
5369 return typeof classNames === 'string' ? [classNames] : classNames;
5370 }
5371
5372 // BAD name for this class now. used in the Header
5373 class TableDateCell extends BaseComponent {
5374 render() {
5375 let { dateEnv, options, theme, viewApi } = this.context;
5376 let { props } = this;
5377 let { date, dateProfile } = props;
5378 let dayMeta = getDateMeta(date, props.todayRange, null, dateProfile);
5379 let classNames = [CLASS_NAME].concat(getDayClassNames(dayMeta, theme));
5380 let text = dateEnv.format(date, props.dayHeaderFormat);
5381 // if colCnt is 1, we are already in a day-view and don't need a navlink
5382 let navLinkAttrs = (!dayMeta.isDisabled && props.colCnt > 1)
5383 ? buildNavLinkAttrs(this.context, date)
5384 : {};
5385 let publicDate = dateEnv.toDate(date);
5386 // workaround for Luxon (and maybe moment) returning prior-days when start-of-day
5387 // in DST gap: https://github.com/fullcalendar/fullcalendar/issues/7633
5388 if (dateEnv.namedTimeZoneImpl) {
5389 publicDate = addMs(publicDate, 3600000); // add an hour
5390 }
5391 let renderProps = Object.assign(Object.assign(Object.assign({ date: publicDate, view: viewApi }, props.extraRenderProps), { text }), dayMeta);
5392 return (y(ContentContainer, { elTag: "th", elClasses: classNames, elAttrs: Object.assign({ role: 'columnheader', colSpan: props.colSpan, 'data-date': !dayMeta.isDisabled ? formatDayString(date) : undefined }, props.extraDataAttrs), renderProps: renderProps, generatorName: "dayHeaderContent", customGenerator: options.dayHeaderContent, defaultGenerator: renderInner$1, classNameGenerator: options.dayHeaderClassNames, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, (InnerContainer) => (y("div", { className: "fc-scrollgrid-sync-inner" }, !dayMeta.isDisabled && (y(InnerContainer, { elTag: "a", elAttrs: navLinkAttrs, elClasses: [
5393 'fc-col-header-cell-cushion',
5394 props.isSticky && 'fc-sticky',
5395 ] }))))));
5396 }
5397 }
5398
5399 const WEEKDAY_FORMAT = createFormatter({ weekday: 'long' });
5400 class TableDowCell extends BaseComponent {
5401 render() {
5402 let { props } = this;
5403 let { dateEnv, theme, viewApi, options } = this.context;
5404 let date = addDays(new Date(259200000), props.dow); // start with Sun, 04 Jan 1970 00:00:00 GMT
5405 let dateMeta = {
5406 dow: props.dow,
5407 isDisabled: false,
5408 isFuture: false,
5409 isPast: false,
5410 isToday: false,
5411 isOther: false,
5412 };
5413 let text = dateEnv.format(date, props.dayHeaderFormat);
5414 let renderProps = Object.assign(Object.assign(Object.assign(Object.assign({ // TODO: make this public?
5415 date }, dateMeta), { view: viewApi }), props.extraRenderProps), { text });
5416 return (y(ContentContainer, { elTag: "th", elClasses: [
5417 CLASS_NAME,
5418 ...getDayClassNames(dateMeta, theme),
5419 ...(props.extraClassNames || []),
5420 ], elAttrs: Object.assign({ role: 'columnheader', colSpan: props.colSpan }, props.extraDataAttrs), renderProps: renderProps, generatorName: "dayHeaderContent", customGenerator: options.dayHeaderContent, defaultGenerator: renderInner$1, classNameGenerator: options.dayHeaderClassNames, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, (InnerContent) => (y("div", { className: "fc-scrollgrid-sync-inner" },
5421 y(InnerContent, { elTag: "a", elClasses: [
5422 'fc-col-header-cell-cushion',
5423 props.isSticky && 'fc-sticky',
5424 ], elAttrs: {
5425 'aria-label': dateEnv.format(date, WEEKDAY_FORMAT),
5426 } })))));
5427 }
5428 }
5429
5430 class NowTimer extends x$1 {
5431 constructor(props, context) {
5432 super(props, context);
5433 this.handleRefresh = () => {
5434 let timing = this.computeTiming();
5435 if (timing.state.nowDate.valueOf() !== this.state.nowDate.valueOf()) {
5436 this.setState(timing.state);
5437 }
5438 this.clearTimeout();
5439 this.setTimeout(timing.waitMs);
5440 };
5441 this.handleVisibilityChange = () => {
5442 if (!document.hidden) {
5443 this.handleRefresh();
5444 }
5445 };
5446 this.state = this.computeTiming().state;
5447 }
5448 render() {
5449 let { props, state } = this;
5450 return props.children(state.nowDate, state.todayRange);
5451 }
5452 componentDidMount() {
5453 this.setTimeout();
5454 this.context.nowManager.addResetListener(this.handleRefresh);
5455 // fired tab becomes visible after being hidden
5456 document.addEventListener('visibilitychange', this.handleVisibilityChange);
5457 }
5458 componentDidUpdate(prevProps) {
5459 if (prevProps.unit !== this.props.unit) {
5460 this.clearTimeout();
5461 this.setTimeout();
5462 }
5463 }
5464 componentWillUnmount() {
5465 this.clearTimeout();
5466 this.context.nowManager.removeResetListener(this.handleRefresh);
5467 document.removeEventListener('visibilitychange', this.handleVisibilityChange);
5468 }
5469 computeTiming() {
5470 let { props, context } = this;
5471 let unroundedNow = context.nowManager.getDateMarker();
5472 let currentUnitStart = context.dateEnv.startOf(unroundedNow, props.unit);
5473 let nextUnitStart = context.dateEnv.add(currentUnitStart, createDuration(1, props.unit));
5474 let waitMs = nextUnitStart.valueOf() - unroundedNow.valueOf();
5475 // there is a max setTimeout ms value (https://stackoverflow.com/a/3468650/96342)
5476 // ensure no longer than a day
5477 waitMs = Math.min(1000 * 60 * 60 * 24, waitMs);
5478 return {
5479 state: { nowDate: currentUnitStart, todayRange: buildDayRange(currentUnitStart) },
5480 waitMs,
5481 };
5482 }
5483 setTimeout(waitMs = this.computeTiming().waitMs) {
5484 // NOTE: timeout could take longer than expected if tab sleeps,
5485 // which is why we listen to 'visibilitychange'
5486 this.timeoutId = setTimeout(() => {
5487 // NOTE: timeout could also return *earlier* than expected, and we need to wait 2 ms more
5488 // This is why use use same waitMs from computeTiming, so we don't skip an interval while
5489 // .setState() is executing
5490 const timing = this.computeTiming();
5491 this.setState(timing.state, () => {
5492 this.setTimeout(timing.waitMs);
5493 });
5494 }, waitMs);
5495 }
5496 clearTimeout() {
5497 if (this.timeoutId) {
5498 clearTimeout(this.timeoutId);
5499 }
5500 }
5501 }
5502 NowTimer.contextType = ViewContextType;
5503 function buildDayRange(date) {
5504 let start = startOfDay(date);
5505 let end = addDays(start, 1);
5506 return { start, end };
5507 }
5508
5509 class DayHeader extends BaseComponent {
5510 constructor() {
5511 super(...arguments);
5512 this.createDayHeaderFormatter = memoize(createDayHeaderFormatter);
5513 }
5514 render() {
5515 let { context } = this;
5516 let { dates, dateProfile, datesRepDistinctDays, renderIntro } = this.props;
5517 let dayHeaderFormat = this.createDayHeaderFormatter(context.options.dayHeaderFormat, datesRepDistinctDays, dates.length);
5518 return (y(NowTimer, { unit: "day" }, (nowDate, todayRange) => (y("tr", { role: "row" },
5519 renderIntro && renderIntro('day'),
5520 dates.map((date) => (datesRepDistinctDays ? (y(TableDateCell, { key: date.toISOString(), date: date, dateProfile: dateProfile, todayRange: todayRange, colCnt: dates.length, dayHeaderFormat: dayHeaderFormat })) : (y(TableDowCell, { key: date.getUTCDay(), dow: date.getUTCDay(), dayHeaderFormat: dayHeaderFormat }))))))));
5521 }
5522 }
5523 function createDayHeaderFormatter(explicitFormat, datesRepDistinctDays, dateCnt) {
5524 return explicitFormat || computeFallbackHeaderFormat(datesRepDistinctDays, dateCnt);
5525 }
5526
5527 class DaySeriesModel {
5528 constructor(range, dateProfileGenerator) {
5529 let date = range.start;
5530 let { end } = range;
5531 let indices = [];
5532 let dates = [];
5533 let dayIndex = -1;
5534 while (date < end) { // loop each day from start to end
5535 if (dateProfileGenerator.isHiddenDay(date)) {
5536 indices.push(dayIndex + 0.5); // mark that it's between indices
5537 }
5538 else {
5539 dayIndex += 1;
5540 indices.push(dayIndex);
5541 dates.push(date);
5542 }
5543 date = addDays(date, 1);
5544 }
5545 this.dates = dates;
5546 this.indices = indices;
5547 this.cnt = dates.length;
5548 }
5549 sliceRange(range) {
5550 let firstIndex = this.getDateDayIndex(range.start); // inclusive first index
5551 let lastIndex = this.getDateDayIndex(addDays(range.end, -1)); // inclusive last index
5552 let clippedFirstIndex = Math.max(0, firstIndex);
5553 let clippedLastIndex = Math.min(this.cnt - 1, lastIndex);
5554 // deal with in-between indices
5555 clippedFirstIndex = Math.ceil(clippedFirstIndex); // in-between starts round to next cell
5556 clippedLastIndex = Math.floor(clippedLastIndex); // in-between ends round to prev cell
5557 if (clippedFirstIndex <= clippedLastIndex) {
5558 return {
5559 firstIndex: clippedFirstIndex,
5560 lastIndex: clippedLastIndex,
5561 isStart: firstIndex === clippedFirstIndex,
5562 isEnd: lastIndex === clippedLastIndex,
5563 };
5564 }
5565 return null;
5566 }
5567 // Given a date, returns its chronolocial cell-index from the first cell of the grid.
5568 // If the date lies between cells (because of hiddenDays), returns a floating-point value between offsets.
5569 // If before the first offset, returns a negative number.
5570 // If after the last offset, returns an offset past the last cell offset.
5571 // Only works for *start* dates of cells. Will not work for exclusive end dates for cells.
5572 getDateDayIndex(date) {
5573 let { indices } = this;
5574 let dayOffset = Math.floor(diffDays(this.dates[0], date));
5575 if (dayOffset < 0) {
5576 return indices[0] - 1;
5577 }
5578 if (dayOffset >= indices.length) {
5579 return indices[indices.length - 1] + 1;
5580 }
5581 return indices[dayOffset];
5582 }
5583 }
5584
5585 class DayTableModel {
5586 constructor(daySeries, breakOnWeeks) {
5587 let { dates } = daySeries;
5588 let daysPerRow;
5589 let firstDay;
5590 let rowCnt;
5591 if (breakOnWeeks) {
5592 // count columns until the day-of-week repeats
5593 firstDay = dates[0].getUTCDay();
5594 for (daysPerRow = 1; daysPerRow < dates.length; daysPerRow += 1) {
5595 if (dates[daysPerRow].getUTCDay() === firstDay) {
5596 break;
5597 }
5598 }
5599 rowCnt = Math.ceil(dates.length / daysPerRow);
5600 }
5601 else {
5602 rowCnt = 1;
5603 daysPerRow = dates.length;
5604 }
5605 this.rowCnt = rowCnt;
5606 this.colCnt = daysPerRow;
5607 this.daySeries = daySeries;
5608 this.cells = this.buildCells();
5609 this.headerDates = this.buildHeaderDates();
5610 }
5611 buildCells() {
5612 let rows = [];
5613 for (let row = 0; row < this.rowCnt; row += 1) {
5614 let cells = [];
5615 for (let col = 0; col < this.colCnt; col += 1) {
5616 cells.push(this.buildCell(row, col));
5617 }
5618 rows.push(cells);
5619 }
5620 return rows;
5621 }
5622 buildCell(row, col) {
5623 let date = this.daySeries.dates[row * this.colCnt + col];
5624 return {
5625 key: date.toISOString(),
5626 date,
5627 };
5628 }
5629 buildHeaderDates() {
5630 let dates = [];
5631 for (let col = 0; col < this.colCnt; col += 1) {
5632 dates.push(this.cells[0][col].date);
5633 }
5634 return dates;
5635 }
5636 sliceRange(range) {
5637 let { colCnt } = this;
5638 let seriesSeg = this.daySeries.sliceRange(range);
5639 let segs = [];
5640 if (seriesSeg) {
5641 let { firstIndex, lastIndex } = seriesSeg;
5642 let index = firstIndex;
5643 while (index <= lastIndex) {
5644 let row = Math.floor(index / colCnt);
5645 let nextIndex = Math.min((row + 1) * colCnt, lastIndex + 1);
5646 segs.push({
5647 row,
5648 firstCol: index % colCnt,
5649 lastCol: (nextIndex - 1) % colCnt,
5650 isStart: seriesSeg.isStart && index === firstIndex,
5651 isEnd: seriesSeg.isEnd && (nextIndex - 1) === lastIndex,
5652 });
5653 index = nextIndex;
5654 }
5655 }
5656 return segs;
5657 }
5658 }
5659
5660 class Slicer {
5661 constructor() {
5662 this.sliceBusinessHours = memoize(this._sliceBusinessHours);
5663 this.sliceDateSelection = memoize(this._sliceDateSpan);
5664 this.sliceEventStore = memoize(this._sliceEventStore);
5665 this.sliceEventDrag = memoize(this._sliceInteraction);
5666 this.sliceEventResize = memoize(this._sliceInteraction);
5667 this.forceDayIfListItem = false; // hack
5668 }
5669 sliceProps(props, dateProfile, nextDayThreshold, context, ...extraArgs) {
5670 let { eventUiBases } = props;
5671 let eventSegs = this.sliceEventStore(props.eventStore, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs);
5672 return {
5673 dateSelectionSegs: this.sliceDateSelection(props.dateSelection, dateProfile, nextDayThreshold, eventUiBases, context, ...extraArgs),
5674 businessHourSegs: this.sliceBusinessHours(props.businessHours, dateProfile, nextDayThreshold, context, ...extraArgs),
5675 fgEventSegs: eventSegs.fg,
5676 bgEventSegs: eventSegs.bg,
5677 eventDrag: this.sliceEventDrag(props.eventDrag, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs),
5678 eventResize: this.sliceEventResize(props.eventResize, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs),
5679 eventSelection: props.eventSelection,
5680 }; // TODO: give interactionSegs?
5681 }
5682 sliceNowDate(// does not memoize
5683 date, dateProfile, nextDayThreshold, context, ...extraArgs) {
5684 return this._sliceDateSpan({ range: { start: date, end: addMs(date, 1) }, allDay: false }, // add 1 ms, protect against null range
5685 dateProfile, nextDayThreshold, {}, context, ...extraArgs);
5686 }
5687 _sliceBusinessHours(businessHours, dateProfile, nextDayThreshold, context, ...extraArgs) {
5688 if (!businessHours) {
5689 return [];
5690 }
5691 return this._sliceEventStore(expandRecurring(businessHours, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), context), {}, dateProfile, nextDayThreshold, ...extraArgs).bg;
5692 }
5693 _sliceEventStore(eventStore, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs) {
5694 if (eventStore) {
5695 let rangeRes = sliceEventStore(eventStore, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold);
5696 return {
5697 bg: this.sliceEventRanges(rangeRes.bg, extraArgs),
5698 fg: this.sliceEventRanges(rangeRes.fg, extraArgs),
5699 };
5700 }
5701 return { bg: [], fg: [] };
5702 }
5703 _sliceInteraction(interaction, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs) {
5704 if (!interaction) {
5705 return null;
5706 }
5707 let rangeRes = sliceEventStore(interaction.mutatedEvents, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold);
5708 return {
5709 segs: this.sliceEventRanges(rangeRes.fg, extraArgs),
5710 affectedInstances: interaction.affectedEvents.instances,
5711 isEvent: interaction.isEvent,
5712 };
5713 }
5714 _sliceDateSpan(dateSpan, dateProfile, nextDayThreshold, eventUiBases, context, ...extraArgs) {
5715 if (!dateSpan) {
5716 return [];
5717 }
5718 let activeRange = computeActiveRange(dateProfile, Boolean(nextDayThreshold));
5719 let activeDateSpanRange = intersectRanges(dateSpan.range, activeRange);
5720 if (activeDateSpanRange) {
5721 dateSpan = Object.assign(Object.assign({}, dateSpan), { range: activeDateSpanRange });
5722 let eventRange = fabricateEventRange(dateSpan, eventUiBases, context);
5723 let segs = this.sliceRange(dateSpan.range, ...extraArgs);
5724 for (let seg of segs) {
5725 seg.eventRange = eventRange;
5726 }
5727 return segs;
5728 }
5729 return [];
5730 }
5731 /*
5732 "complete" seg means it has component and eventRange
5733 */
5734 sliceEventRanges(eventRanges, extraArgs) {
5735 let segs = [];
5736 for (let eventRange of eventRanges) {
5737 segs.push(...this.sliceEventRange(eventRange, extraArgs));
5738 }
5739 return segs;
5740 }
5741 /*
5742 "complete" seg means it has component and eventRange
5743 */
5744 sliceEventRange(eventRange, extraArgs) {
5745 let dateRange = eventRange.range;
5746 // hack to make multi-day events that are being force-displayed as list-items to take up only one day
5747 if (this.forceDayIfListItem && eventRange.ui.display === 'list-item') {
5748 dateRange = {
5749 start: dateRange.start,
5750 end: addDays(dateRange.start, 1),
5751 };
5752 }
5753 let segs = this.sliceRange(dateRange, ...extraArgs);
5754 for (let seg of segs) {
5755 seg.eventRange = eventRange;
5756 seg.isStart = eventRange.isStart && seg.isStart;
5757 seg.isEnd = eventRange.isEnd && seg.isEnd;
5758 }
5759 return segs;
5760 }
5761 }
5762 /*
5763 for incorporating slotMinTime/slotMaxTime if appropriate
5764 TODO: should be part of DateProfile!
5765 TimelineDateProfile already does this btw
5766 */
5767 function computeActiveRange(dateProfile, isComponentAllDay) {
5768 let range = dateProfile.activeRange;
5769 if (isComponentAllDay) {
5770 return range;
5771 }
5772 return {
5773 start: addMs(range.start, dateProfile.slotMinTime.milliseconds),
5774 end: addMs(range.end, dateProfile.slotMaxTime.milliseconds - 864e5), // 864e5 = ms in a day
5775 };
5776 }
5777
5778 function reduceEventStore(eventStore, action, eventSources, dateProfile, context) {
5779 switch (action.type) {
5780 case 'RECEIVE_EVENTS': // raw
5781 return receiveRawEvents(eventStore, eventSources[action.sourceId], action.fetchId, action.fetchRange, action.rawEvents, context);
5782 case 'RESET_RAW_EVENTS':
5783 return resetRawEvents(eventStore, eventSources[action.sourceId], action.rawEvents, dateProfile.activeRange, context);
5784 case 'ADD_EVENTS': // already parsed, but not expanded
5785 return addEvent(eventStore, action.eventStore, // new ones
5786 dateProfile ? dateProfile.activeRange : null, context);
5787 case 'RESET_EVENTS':
5788 return action.eventStore;
5789 case 'MERGE_EVENTS': // already parsed and expanded
5790 return mergeEventStores(eventStore, action.eventStore);
5791 case 'PREV': // TODO: how do we track all actions that affect dateProfile :(
5792 case 'NEXT':
5793 case 'CHANGE_DATE':
5794 case 'CHANGE_VIEW_TYPE':
5795 if (dateProfile) {
5796 return expandRecurring(eventStore, dateProfile.activeRange, context);
5797 }
5798 return eventStore;
5799 case 'REMOVE_EVENTS':
5800 return excludeSubEventStore(eventStore, action.eventStore);
5801 case 'REMOVE_EVENT_SOURCE':
5802 return excludeEventsBySourceId(eventStore, action.sourceId);
5803 case 'REMOVE_ALL_EVENT_SOURCES':
5804 return filterEventStoreDefs(eventStore, (eventDef) => (!eventDef.sourceId // only keep events with no source id
5805 ));
5806 case 'REMOVE_ALL_EVENTS':
5807 return createEmptyEventStore();
5808 default:
5809 return eventStore;
5810 }
5811 }
5812 function receiveRawEvents(eventStore, eventSource, fetchId, fetchRange, rawEvents, context) {
5813 if (eventSource && // not already removed
5814 fetchId === eventSource.latestFetchId // TODO: wish this logic was always in event-sources
5815 ) {
5816 let subset = parseEvents(transformRawEvents(rawEvents, eventSource, context), eventSource, context);
5817 if (fetchRange) {
5818 subset = expandRecurring(subset, fetchRange, context);
5819 }
5820 return mergeEventStores(excludeEventsBySourceId(eventStore, eventSource.sourceId), subset);
5821 }
5822 return eventStore;
5823 }
5824 function resetRawEvents(existingEventStore, eventSource, rawEvents, activeRange, context) {
5825 const { defIdMap, instanceIdMap } = buildPublicIdMaps(existingEventStore);
5826 let newEventStore = parseEvents(transformRawEvents(rawEvents, eventSource, context), eventSource, context, false, defIdMap, instanceIdMap);
5827 return expandRecurring(newEventStore, activeRange, context);
5828 }
5829 function transformRawEvents(rawEvents, eventSource, context) {
5830 let calEachTransform = context.options.eventDataTransform;
5831 let sourceEachTransform = eventSource ? eventSource.eventDataTransform : null;
5832 if (sourceEachTransform) {
5833 rawEvents = transformEachRawEvent(rawEvents, sourceEachTransform);
5834 }
5835 if (calEachTransform) {
5836 rawEvents = transformEachRawEvent(rawEvents, calEachTransform);
5837 }
5838 return rawEvents;
5839 }
5840 function transformEachRawEvent(rawEvents, func) {
5841 let refinedEvents;
5842 if (!func) {
5843 refinedEvents = rawEvents;
5844 }
5845 else {
5846 refinedEvents = [];
5847 for (let rawEvent of rawEvents) {
5848 let refinedEvent = func(rawEvent);
5849 if (refinedEvent) {
5850 refinedEvents.push(refinedEvent);
5851 }
5852 else if (refinedEvent == null) {
5853 refinedEvents.push(rawEvent);
5854 } // if a different falsy value, do nothing
5855 }
5856 }
5857 return refinedEvents;
5858 }
5859 function addEvent(eventStore, subset, expandRange, context) {
5860 if (expandRange) {
5861 subset = expandRecurring(subset, expandRange, context);
5862 }
5863 return mergeEventStores(eventStore, subset);
5864 }
5865 function rezoneEventStoreDates(eventStore, oldDateEnv, newDateEnv) {
5866 let { defs } = eventStore;
5867 let instances = mapHash(eventStore.instances, (instance) => {
5868 let def = defs[instance.defId];
5869 if (def.allDay) {
5870 return instance; // isn't dependent on timezone
5871 }
5872 return Object.assign(Object.assign({}, instance), { range: {
5873 start: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.start, instance.forcedStartTzo)),
5874 end: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.end, instance.forcedEndTzo)),
5875 }, forcedStartTzo: newDateEnv.canComputeOffset ? null : instance.forcedStartTzo, forcedEndTzo: newDateEnv.canComputeOffset ? null : instance.forcedEndTzo });
5876 });
5877 return { defs, instances };
5878 }
5879 function excludeEventsBySourceId(eventStore, sourceId) {
5880 return filterEventStoreDefs(eventStore, (eventDef) => eventDef.sourceId !== sourceId);
5881 }
5882 // QUESTION: why not just return instances? do a general object-property-exclusion util
5883 function excludeInstances(eventStore, removals) {
5884 return {
5885 defs: eventStore.defs,
5886 instances: filterHash(eventStore.instances, (instance) => !removals[instance.instanceId]),
5887 };
5888 }
5889 function buildPublicIdMaps(eventStore) {
5890 const { defs, instances } = eventStore;
5891 const defIdMap = {};
5892 const instanceIdMap = {};
5893 for (let defId in defs) {
5894 const def = defs[defId];
5895 const { publicId } = def;
5896 if (publicId) {
5897 defIdMap[publicId] = defId;
5898 }
5899 }
5900 for (let instanceId in instances) {
5901 const instance = instances[instanceId];
5902 const def = defs[instance.defId];
5903 const { publicId } = def;
5904 if (publicId) {
5905 instanceIdMap[publicId] = instanceId;
5906 }
5907 }
5908 return { defIdMap, instanceIdMap };
5909 }
5910
5911 // high-level segmenting-aware tester functions
5912 // ------------------------------------------------------------------------------------------------------------------------
5913 function isInteractionValid(interaction, dateProfile, context) {
5914 let { instances } = interaction.mutatedEvents;
5915 for (let instanceId in instances) {
5916 if (!rangeContainsRange(dateProfile.validRange, instances[instanceId].range)) {
5917 return false;
5918 }
5919 }
5920 return isNewPropsValid({ eventDrag: interaction }, context); // HACK: the eventDrag props is used for ALL interactions
5921 }
5922 function isDateSelectionValid(dateSelection, dateProfile, context) {
5923 if (!rangeContainsRange(dateProfile.validRange, dateSelection.range)) {
5924 return false;
5925 }
5926 return isNewPropsValid({ dateSelection }, context);
5927 }
5928 function isNewPropsValid(newProps, context) {
5929 let calendarState = context.getCurrentData();
5930 let props = Object.assign({ businessHours: calendarState.businessHours, dateSelection: '', eventStore: calendarState.eventStore, eventUiBases: calendarState.eventUiBases, eventSelection: '', eventDrag: null, eventResize: null }, newProps);
5931 return (context.pluginHooks.isPropsValid || isPropsValid)(props, context);
5932 }
5933 function isPropsValid(state, context, dateSpanMeta = {}, filterConfig) {
5934 if (state.eventDrag && !isInteractionPropsValid(state, context, dateSpanMeta, filterConfig)) {
5935 return false;
5936 }
5937 if (state.dateSelection && !isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig)) {
5938 return false;
5939 }
5940 return true;
5941 }
5942 // Moving Event Validation
5943 // ------------------------------------------------------------------------------------------------------------------------
5944 function isInteractionPropsValid(state, context, dateSpanMeta, filterConfig) {
5945 let currentState = context.getCurrentData();
5946 let interaction = state.eventDrag; // HACK: the eventDrag props is used for ALL interactions
5947 let subjectEventStore = interaction.mutatedEvents;
5948 let subjectDefs = subjectEventStore.defs;
5949 let subjectInstances = subjectEventStore.instances;
5950 let subjectConfigs = compileEventUis(subjectDefs, interaction.isEvent ?
5951 state.eventUiBases :
5952 { '': currentState.selectionConfig });
5953 if (filterConfig) {
5954 subjectConfigs = mapHash(subjectConfigs, filterConfig);
5955 }
5956 // exclude the subject events. TODO: exclude defs too?
5957 let otherEventStore = excludeInstances(state.eventStore, interaction.affectedEvents.instances);
5958 let otherDefs = otherEventStore.defs;
5959 let otherInstances = otherEventStore.instances;
5960 let otherConfigs = compileEventUis(otherDefs, state.eventUiBases);
5961 for (let subjectInstanceId in subjectInstances) {
5962 let subjectInstance = subjectInstances[subjectInstanceId];
5963 let subjectRange = subjectInstance.range;
5964 let subjectConfig = subjectConfigs[subjectInstance.defId];
5965 let subjectDef = subjectDefs[subjectInstance.defId];
5966 // constraint
5967 if (!allConstraintsPass(subjectConfig.constraints, subjectRange, otherEventStore, state.businessHours, context)) {
5968 return false;
5969 }
5970 // overlap
5971 let { eventOverlap } = context.options;
5972 let eventOverlapFunc = typeof eventOverlap === 'function' ? eventOverlap : null;
5973 for (let otherInstanceId in otherInstances) {
5974 let otherInstance = otherInstances[otherInstanceId];
5975 // intersect! evaluate
5976 if (rangesIntersect(subjectRange, otherInstance.range)) {
5977 let otherOverlap = otherConfigs[otherInstance.defId].overlap;
5978 // consider the other event's overlap. only do this if the subject event is a "real" event
5979 if (otherOverlap === false && interaction.isEvent) {
5980 return false;
5981 }
5982 if (subjectConfig.overlap === false) {
5983 return false;
5984 }
5985 if (eventOverlapFunc && !eventOverlapFunc(new EventImpl(context, otherDefs[otherInstance.defId], otherInstance), // still event
5986 new EventImpl(context, subjectDef, subjectInstance))) {
5987 return false;
5988 }
5989 }
5990 }
5991 // allow (a function)
5992 let calendarEventStore = currentState.eventStore; // need global-to-calendar, not local to component (splittable)state
5993 for (let subjectAllow of subjectConfig.allows) {
5994 let subjectDateSpan = Object.assign(Object.assign({}, dateSpanMeta), { range: subjectInstance.range, allDay: subjectDef.allDay });
5995 let origDef = calendarEventStore.defs[subjectDef.defId];
5996 let origInstance = calendarEventStore.instances[subjectInstanceId];
5997 let eventApi;
5998 if (origDef) { // was previously in the calendar
5999 eventApi = new EventImpl(context, origDef, origInstance);
6000 }
6001 else { // was an external event
6002 eventApi = new EventImpl(context, subjectDef); // no instance, because had no dates
6003 }
6004 if (!subjectAllow(buildDateSpanApiWithContext(subjectDateSpan, context), eventApi)) {
6005 return false;
6006 }
6007 }
6008 }
6009 return true;
6010 }
6011 // Date Selection Validation
6012 // ------------------------------------------------------------------------------------------------------------------------
6013 function isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig) {
6014 let relevantEventStore = state.eventStore;
6015 let relevantDefs = relevantEventStore.defs;
6016 let relevantInstances = relevantEventStore.instances;
6017 let selection = state.dateSelection;
6018 let selectionRange = selection.range;
6019 let { selectionConfig } = context.getCurrentData();
6020 if (filterConfig) {
6021 selectionConfig = filterConfig(selectionConfig);
6022 }
6023 // constraint
6024 if (!allConstraintsPass(selectionConfig.constraints, selectionRange, relevantEventStore, state.businessHours, context)) {
6025 return false;
6026 }
6027 // overlap
6028 let { selectOverlap } = context.options;
6029 let selectOverlapFunc = typeof selectOverlap === 'function' ? selectOverlap : null;
6030 for (let relevantInstanceId in relevantInstances) {
6031 let relevantInstance = relevantInstances[relevantInstanceId];
6032 // intersect! evaluate
6033 if (rangesIntersect(selectionRange, relevantInstance.range)) {
6034 if (selectionConfig.overlap === false) {
6035 return false;
6036 }
6037 if (selectOverlapFunc && !selectOverlapFunc(new EventImpl(context, relevantDefs[relevantInstance.defId], relevantInstance), null)) {
6038 return false;
6039 }
6040 }
6041 }
6042 // allow (a function)
6043 for (let selectionAllow of selectionConfig.allows) {
6044 let fullDateSpan = Object.assign(Object.assign({}, dateSpanMeta), selection);
6045 if (!selectionAllow(buildDateSpanApiWithContext(fullDateSpan, context), null)) {
6046 return false;
6047 }
6048 }
6049 return true;
6050 }
6051 // Constraint Utils
6052 // ------------------------------------------------------------------------------------------------------------------------
6053 function allConstraintsPass(constraints, subjectRange, otherEventStore, businessHoursUnexpanded, context) {
6054 for (let constraint of constraints) {
6055 if (!anyRangesContainRange(constraintToRanges(constraint, subjectRange, otherEventStore, businessHoursUnexpanded, context), subjectRange)) {
6056 return false;
6057 }
6058 }
6059 return true;
6060 }
6061 function constraintToRanges(constraint, subjectRange, // for expanding a recurring constraint, or expanding business hours
6062 otherEventStore, // for if constraint is an even group ID
6063 businessHoursUnexpanded, // for if constraint is 'businessHours'
6064 context) {
6065 if (constraint === 'businessHours') {
6066 return eventStoreToRanges(expandRecurring(businessHoursUnexpanded, subjectRange, context));
6067 }
6068 if (typeof constraint === 'string') { // an group ID
6069 return eventStoreToRanges(filterEventStoreDefs(otherEventStore, (eventDef) => eventDef.groupId === constraint));
6070 }
6071 if (typeof constraint === 'object' && constraint) { // non-null object
6072 return eventStoreToRanges(expandRecurring(constraint, subjectRange, context));
6073 }
6074 return []; // if it's false
6075 }
6076 // TODO: move to event-store file?
6077 function eventStoreToRanges(eventStore) {
6078 let { instances } = eventStore;
6079 let ranges = [];
6080 for (let instanceId in instances) {
6081 ranges.push(instances[instanceId].range);
6082 }
6083 return ranges;
6084 }
6085 // TODO: move to geom file?
6086 function anyRangesContainRange(outerRanges, innerRange) {
6087 for (let outerRange of outerRanges) {
6088 if (rangeContainsRange(outerRange, innerRange)) {
6089 return true;
6090 }
6091 }
6092 return false;
6093 }
6094
6095 class JsonRequestError extends Error {
6096 constructor(message, response) {
6097 super(message);
6098 this.response = response;
6099 }
6100 }
6101 function requestJson(method, url, params) {
6102 method = method.toUpperCase();
6103 const fetchOptions = {
6104 method,
6105 };
6106 if (method === 'GET') {
6107 url += (url.indexOf('?') === -1 ? '?' : '&') +
6108 new URLSearchParams(params);
6109 }
6110 else {
6111 fetchOptions.body = new URLSearchParams(params);
6112 fetchOptions.headers = {
6113 'Content-Type': 'application/x-www-form-urlencoded',
6114 };
6115 }
6116 return fetch(url, fetchOptions).then((fetchRes) => {
6117 if (fetchRes.ok) {
6118 return fetchRes.json().then((parsedResponse) => {
6119 return [parsedResponse, fetchRes];
6120 }, () => {
6121 throw new JsonRequestError('Failure parsing JSON', fetchRes);
6122 });
6123 }
6124 else {
6125 throw new JsonRequestError('Request failed', fetchRes);
6126 }
6127 });
6128 }
6129
6130 class DelayedRunner {
6131 constructor(drainedOption) {
6132 this.drainedOption = drainedOption;
6133 this.isRunning = false;
6134 this.isDirty = false;
6135 this.pauseDepths = {};
6136 this.timeoutId = 0;
6137 }
6138 request(delay) {
6139 this.isDirty = true;
6140 if (!this.isPaused()) {
6141 this.clearTimeout();
6142 if (delay == null) {
6143 this.tryDrain();
6144 }
6145 else {
6146 this.timeoutId = setTimeout(// NOT OPTIMAL! TODO: look at debounce
6147 this.tryDrain.bind(this), delay);
6148 }
6149 }
6150 }
6151 pause(scope = '') {
6152 let { pauseDepths } = this;
6153 pauseDepths[scope] = (pauseDepths[scope] || 0) + 1;
6154 this.clearTimeout();
6155 }
6156 resume(scope = '', force) {
6157 let { pauseDepths } = this;
6158 if (scope in pauseDepths) {
6159 if (force) {
6160 delete pauseDepths[scope];
6161 }
6162 else {
6163 pauseDepths[scope] -= 1;
6164 let depth = pauseDepths[scope];
6165 if (depth <= 0) {
6166 delete pauseDepths[scope];
6167 }
6168 }
6169 this.tryDrain();
6170 }
6171 }
6172 isPaused() {
6173 return Object.keys(this.pauseDepths).length;
6174 }
6175 tryDrain() {
6176 if (!this.isRunning && !this.isPaused()) {
6177 this.isRunning = true;
6178 while (this.isDirty) {
6179 this.isDirty = false;
6180 this.drained(); // might set isDirty to true again
6181 }
6182 this.isRunning = false;
6183 }
6184 }
6185 clear() {
6186 this.clearTimeout();
6187 this.isDirty = false;
6188 this.pauseDepths = {};
6189 }
6190 clearTimeout() {
6191 if (this.timeoutId) {
6192 clearTimeout(this.timeoutId);
6193 this.timeoutId = 0;
6194 }
6195 }
6196 drained() {
6197 if (this.drainedOption) {
6198 this.drainedOption();
6199 }
6200 }
6201 }
6202
6203 const VISIBLE_HIDDEN_RE = /^(visible|hidden)$/;
6204 class Scroller extends BaseComponent {
6205 constructor() {
6206 super(...arguments);
6207 this.handleEl = (el) => {
6208 this.el = el;
6209 setRef(this.props.elRef, el);
6210 };
6211 }
6212 render() {
6213 let { props } = this;
6214 let { liquid, liquidIsAbsolute } = props;
6215 let isAbsolute = liquid && liquidIsAbsolute;
6216 let className = ['fc-scroller'];
6217 if (liquid) {
6218 if (liquidIsAbsolute) {
6219 className.push('fc-scroller-liquid-absolute');
6220 }
6221 else {
6222 className.push('fc-scroller-liquid');
6223 }
6224 }
6225 return (y("div", { ref: this.handleEl, className: className.join(' '), style: {
6226 overflowX: props.overflowX,
6227 overflowY: props.overflowY,
6228 left: (isAbsolute && -(props.overcomeLeft || 0)) || '',
6229 right: (isAbsolute && -(props.overcomeRight || 0)) || '',
6230 bottom: (isAbsolute && -(props.overcomeBottom || 0)) || '',
6231 marginLeft: (!isAbsolute && -(props.overcomeLeft || 0)) || '',
6232 marginRight: (!isAbsolute && -(props.overcomeRight || 0)) || '',
6233 marginBottom: (!isAbsolute && -(props.overcomeBottom || 0)) || '',
6234 maxHeight: props.maxHeight || '',
6235 } }, props.children));
6236 }
6237 needsXScrolling() {
6238 if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) {
6239 return false;
6240 }
6241 // testing scrollWidth>clientWidth is unreliable cross-browser when pixel heights aren't integers.
6242 // much more reliable to see if children are taller than the scroller, even tho doesn't account for
6243 // inner-child margins and absolute positioning
6244 let { el } = this;
6245 let realClientWidth = this.el.getBoundingClientRect().width - this.getYScrollbarWidth();
6246 let { children } = el;
6247 for (let i = 0; i < children.length; i += 1) {
6248 let childEl = children[i];
6249 if (childEl.getBoundingClientRect().width > realClientWidth) {
6250 return true;
6251 }
6252 }
6253 return false;
6254 }
6255 needsYScrolling() {
6256 if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) {
6257 return false;
6258 }
6259 // testing scrollHeight>clientHeight is unreliable cross-browser when pixel heights aren't integers.
6260 // much more reliable to see if children are taller than the scroller, even tho doesn't account for
6261 // inner-child margins and absolute positioning
6262 let { el } = this;
6263 let realClientHeight = this.el.getBoundingClientRect().height - this.getXScrollbarWidth();
6264 let { children } = el;
6265 for (let i = 0; i < children.length; i += 1) {
6266 let childEl = children[i];
6267 if (childEl.getBoundingClientRect().height > realClientHeight) {
6268 return true;
6269 }
6270 }
6271 return false;
6272 }
6273 getXScrollbarWidth() {
6274 if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) {
6275 return 0;
6276 }
6277 return this.el.offsetHeight - this.el.clientHeight; // only works because we guarantee no borders. TODO: add to CSS with important?
6278 }
6279 getYScrollbarWidth() {
6280 if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) {
6281 return 0;
6282 }
6283 return this.el.offsetWidth - this.el.clientWidth; // only works because we guarantee no borders. TODO: add to CSS with important?
6284 }
6285 }
6286
6287 /*
6288 TODO: somehow infer OtherArgs from masterCallback?
6289 TODO: infer RefType from masterCallback if provided
6290 */
6291 class RefMap {
6292 constructor(masterCallback) {
6293 this.masterCallback = masterCallback;
6294 this.currentMap = {};
6295 this.depths = {};
6296 this.callbackMap = {};
6297 this.handleValue = (val, key) => {
6298 let { depths, currentMap } = this;
6299 let removed = false;
6300 let added = false;
6301 if (val !== null) {
6302 // for bug... ACTUALLY: can probably do away with this now that callers don't share numeric indices anymore
6303 removed = (key in currentMap);
6304 currentMap[key] = val;
6305 depths[key] = (depths[key] || 0) + 1;
6306 added = true;
6307 }
6308 else {
6309 depths[key] -= 1;
6310 if (!depths[key]) {
6311 delete currentMap[key];
6312 delete this.callbackMap[key];
6313 removed = true;
6314 }
6315 }
6316 if (this.masterCallback) {
6317 if (removed) {
6318 this.masterCallback(null, String(key));
6319 }
6320 if (added) {
6321 this.masterCallback(val, String(key));
6322 }
6323 }
6324 };
6325 }
6326 createRef(key) {
6327 let refCallback = this.callbackMap[key];
6328 if (!refCallback) {
6329 refCallback = this.callbackMap[key] = (val) => {
6330 this.handleValue(val, String(key));
6331 };
6332 }
6333 return refCallback;
6334 }
6335 // TODO: check callers that don't care about order. should use getAll instead
6336 // NOTE: this method has become less valuable now that we are encouraged to map order by some other index
6337 // TODO: provide ONE array-export function, buildArray, which fails on non-numeric indexes. caller can manipulate and "collect"
6338 collect(startIndex, endIndex, step) {
6339 return collectFromHash(this.currentMap, startIndex, endIndex, step);
6340 }
6341 getAll() {
6342 return hashValuesToArray(this.currentMap);
6343 }
6344 }
6345
6346 function computeShrinkWidth(chunkEls) {
6347 let shrinkCells = findElements(chunkEls, '.fc-scrollgrid-shrink');
6348 let largestWidth = 0;
6349 for (let shrinkCell of shrinkCells) {
6350 largestWidth = Math.max(largestWidth, computeSmallestCellWidth(shrinkCell));
6351 }
6352 return Math.ceil(largestWidth); // <table> elements work best with integers. round up to ensure contents fits
6353 }
6354 function getSectionHasLiquidHeight(props, sectionConfig) {
6355 return props.liquid && sectionConfig.liquid; // does the section do liquid-height? (need to have whole scrollgrid liquid-height as well)
6356 }
6357 function getAllowYScrolling(props, sectionConfig) {
6358 return sectionConfig.maxHeight != null || // if its possible for the height to max out, we might need scrollbars
6359 getSectionHasLiquidHeight(props, sectionConfig); // if the section is liquid height, it might condense enough to require scrollbars
6360 }
6361 // TODO: ONLY use `arg`. force out internal function to use same API
6362 function renderChunkContent(sectionConfig, chunkConfig, arg, isHeader) {
6363 let { expandRows } = arg;
6364 let content = typeof chunkConfig.content === 'function' ?
6365 chunkConfig.content(arg) :
6366 y('table', {
6367 role: 'presentation',
6368 className: [
6369 chunkConfig.tableClassName,
6370 sectionConfig.syncRowHeights ? 'fc-scrollgrid-sync-table' : '',
6371 ].join(' '),
6372 style: {
6373 minWidth: arg.tableMinWidth,
6374 width: arg.clientWidth,
6375 height: expandRows ? arg.clientHeight : '', // css `height` on a <table> serves as a min-height
6376 },
6377 }, arg.tableColGroupNode, y(isHeader ? 'thead' : 'tbody', {
6378 role: 'presentation',
6379 }, typeof chunkConfig.rowContent === 'function'
6380 ? chunkConfig.rowContent(arg)
6381 : chunkConfig.rowContent));
6382 return content;
6383 }
6384 function isColPropsEqual(cols0, cols1) {
6385 return isArraysEqual(cols0, cols1, isPropsEqual);
6386 }
6387 function renderMicroColGroup(cols, shrinkWidth) {
6388 let colNodes = [];
6389 /*
6390 for ColProps with spans, it would have been great to make a single <col span="">
6391 HOWEVER, Chrome was getting messing up distributing the width to <td>/<th> elements with colspans.
6392 SOLUTION: making individual <col> elements makes Chrome behave.
6393 */
6394 for (let colProps of cols) {
6395 let span = colProps.span || 1;
6396 for (let i = 0; i < span; i += 1) {
6397 colNodes.push(y("col", { style: {
6398 width: colProps.width === 'shrink' ? sanitizeShrinkWidth(shrinkWidth) : (colProps.width || ''),
6399 minWidth: colProps.minWidth || '',
6400 } }));
6401 }
6402 }
6403 return y('colgroup', {}, ...colNodes);
6404 }
6405 function sanitizeShrinkWidth(shrinkWidth) {
6406 /* why 4? if we do 0, it will kill any border, which are needed for computeSmallestCellWidth
6407 4 accounts for 2 2-pixel borders. TODO: better solution? */
6408 return shrinkWidth == null ? 4 : shrinkWidth;
6409 }
6410 function hasShrinkWidth(cols) {
6411 for (let col of cols) {
6412 if (col.width === 'shrink') {
6413 return true;
6414 }
6415 }
6416 return false;
6417 }
6418 function getScrollGridClassNames(liquid, context) {
6419 let classNames = [
6420 'fc-scrollgrid',
6421 context.theme.getClass('table'),
6422 ];
6423 if (liquid) {
6424 classNames.push('fc-scrollgrid-liquid');
6425 }
6426 return classNames;
6427 }
6428 function getSectionClassNames(sectionConfig, wholeTableVGrow) {
6429 let classNames = [
6430 'fc-scrollgrid-section',
6431 `fc-scrollgrid-section-${sectionConfig.type}`,
6432 sectionConfig.className, // used?
6433 ];
6434 if (wholeTableVGrow && sectionConfig.liquid && sectionConfig.maxHeight == null) {
6435 classNames.push('fc-scrollgrid-section-liquid');
6436 }
6437 if (sectionConfig.isSticky) {
6438 classNames.push('fc-scrollgrid-section-sticky');
6439 }
6440 return classNames;
6441 }
6442 function renderScrollShim(arg) {
6443 return (y("div", { className: "fc-scrollgrid-sticky-shim", style: {
6444 width: arg.clientWidth,
6445 minWidth: arg.tableMinWidth,
6446 } }));
6447 }
6448 function getStickyHeaderDates(options) {
6449 let { stickyHeaderDates } = options;
6450 if (stickyHeaderDates == null || stickyHeaderDates === 'auto') {
6451 stickyHeaderDates = options.height === 'auto' || options.viewHeight === 'auto';
6452 }
6453 return stickyHeaderDates;
6454 }
6455 function getStickyFooterScrollbar(options) {
6456 let { stickyFooterScrollbar } = options;
6457 if (stickyFooterScrollbar == null || stickyFooterScrollbar === 'auto') {
6458 stickyFooterScrollbar = options.height === 'auto' || options.viewHeight === 'auto';
6459 }
6460 return stickyFooterScrollbar;
6461 }
6462
6463 class SimpleScrollGrid extends BaseComponent {
6464 constructor() {
6465 super(...arguments);
6466 this.processCols = memoize((a) => a, isColPropsEqual); // so we get same `cols` props every time
6467 // yucky to memoize VNodes, but much more efficient for consumers
6468 this.renderMicroColGroup = memoize(renderMicroColGroup);
6469 this.scrollerRefs = new RefMap();
6470 this.scrollerElRefs = new RefMap(this._handleScrollerEl.bind(this));
6471 this.state = {
6472 shrinkWidth: null,
6473 forceYScrollbars: false,
6474 scrollerClientWidths: {},
6475 scrollerClientHeights: {},
6476 };
6477 // TODO: can do a really simple print-view. dont need to join rows
6478 this.handleSizing = () => {
6479 this.safeSetState(Object.assign({ shrinkWidth: this.computeShrinkWidth() }, this.computeScrollerDims()));
6480 };
6481 }
6482 render() {
6483 let { props, state, context } = this;
6484 let sectionConfigs = props.sections || [];
6485 let cols = this.processCols(props.cols);
6486 let microColGroupNode = this.renderMicroColGroup(cols, state.shrinkWidth);
6487 let classNames = getScrollGridClassNames(props.liquid, context);
6488 if (props.collapsibleWidth) {
6489 classNames.push('fc-scrollgrid-collapsible');
6490 }
6491 // TODO: make DRY
6492 let configCnt = sectionConfigs.length;
6493 let configI = 0;
6494 let currentConfig;
6495 let headSectionNodes = [];
6496 let bodySectionNodes = [];
6497 let footSectionNodes = [];
6498 while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'header') {
6499 headSectionNodes.push(this.renderSection(currentConfig, microColGroupNode, true));
6500 configI += 1;
6501 }
6502 while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'body') {
6503 bodySectionNodes.push(this.renderSection(currentConfig, microColGroupNode, false));
6504 configI += 1;
6505 }
6506 while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'footer') {
6507 footSectionNodes.push(this.renderSection(currentConfig, microColGroupNode, true));
6508 configI += 1;
6509 }
6510 // firefox bug: when setting height on table and there is a thead or tfoot,
6511 // the necessary height:100% on the liquid-height body section forces the *whole* table to be taller. (bug #5524)
6512 // use getCanVGrowWithinCell as a way to detect table-stupid firefox.
6513 // if so, use a simpler dom structure, jam everything into a lone tbody.
6514 let isBuggy = !getCanVGrowWithinCell();
6515 const roleAttrs = { role: 'rowgroup' };
6516 return y('table', {
6517 role: 'grid',
6518 className: classNames.join(' '),
6519 style: { height: props.height },
6520 }, Boolean(!isBuggy && headSectionNodes.length) && y('thead', roleAttrs, ...headSectionNodes), Boolean(!isBuggy && bodySectionNodes.length) && y('tbody', roleAttrs, ...bodySectionNodes), Boolean(!isBuggy && footSectionNodes.length) && y('tfoot', roleAttrs, ...footSectionNodes), isBuggy && y('tbody', roleAttrs, ...headSectionNodes, ...bodySectionNodes, ...footSectionNodes));
6521 }
6522 renderSection(sectionConfig, microColGroupNode, isHeader) {
6523 if ('outerContent' in sectionConfig) {
6524 return (y(_, { key: sectionConfig.key }, sectionConfig.outerContent));
6525 }
6526 return (y("tr", { key: sectionConfig.key, role: "presentation", className: getSectionClassNames(sectionConfig, this.props.liquid).join(' ') }, this.renderChunkTd(sectionConfig, microColGroupNode, sectionConfig.chunk, isHeader)));
6527 }
6528 renderChunkTd(sectionConfig, microColGroupNode, chunkConfig, isHeader) {
6529 if ('outerContent' in chunkConfig) {
6530 return chunkConfig.outerContent;
6531 }
6532 let { props } = this;
6533 let { forceYScrollbars, scrollerClientWidths, scrollerClientHeights } = this.state;
6534 let needsYScrolling = getAllowYScrolling(props, sectionConfig); // TODO: do lazily. do in section config?
6535 let isLiquid = getSectionHasLiquidHeight(props, sectionConfig);
6536 // for `!props.liquid` - is WHOLE scrollgrid natural height?
6537 // TODO: do same thing in advanced scrollgrid? prolly not b/c always has horizontal scrollbars
6538 let overflowY = !props.liquid ? 'visible' :
6539 forceYScrollbars ? 'scroll' :
6540 !needsYScrolling ? 'hidden' :
6541 'auto';
6542 let sectionKey = sectionConfig.key;
6543 let content = renderChunkContent(sectionConfig, chunkConfig, {
6544 tableColGroupNode: microColGroupNode,
6545 tableMinWidth: '',
6546 clientWidth: (!props.collapsibleWidth && scrollerClientWidths[sectionKey] !== undefined) ? scrollerClientWidths[sectionKey] : null,
6547 clientHeight: scrollerClientHeights[sectionKey] !== undefined ? scrollerClientHeights[sectionKey] : null,
6548 expandRows: sectionConfig.expandRows,
6549 syncRowHeights: false,
6550 rowSyncHeights: [],
6551 reportRowHeightChange: () => { },
6552 }, isHeader);
6553 return y(isHeader ? 'th' : 'td', {
6554 ref: chunkConfig.elRef,
6555 role: 'presentation',
6556 }, y("div", { className: `fc-scroller-harness${isLiquid ? ' fc-scroller-harness-liquid' : ''}` },
6557 y(Scroller, { ref: this.scrollerRefs.createRef(sectionKey), elRef: this.scrollerElRefs.createRef(sectionKey), overflowY: overflowY, overflowX: !props.liquid ? 'visible' : 'hidden' /* natural height? */, maxHeight: sectionConfig.maxHeight, liquid: isLiquid, liquidIsAbsolute // because its within a harness
6558 : true }, content)));
6559 }
6560 _handleScrollerEl(scrollerEl, key) {
6561 let section = getSectionByKey(this.props.sections, key);
6562 if (section) {
6563 setRef(section.chunk.scrollerElRef, scrollerEl);
6564 }
6565 }
6566 componentDidMount() {
6567 this.handleSizing();
6568 this.context.addResizeHandler(this.handleSizing);
6569 }
6570 componentDidUpdate() {
6571 // TODO: need better solution when state contains non-sizing things
6572 this.handleSizing();
6573 }
6574 componentWillUnmount() {
6575 this.context.removeResizeHandler(this.handleSizing);
6576 }
6577 computeShrinkWidth() {
6578 return hasShrinkWidth(this.props.cols)
6579 ? computeShrinkWidth(this.scrollerElRefs.getAll())
6580 : 0;
6581 }
6582 computeScrollerDims() {
6583 let scrollbarWidth = getScrollbarWidths();
6584 let { scrollerRefs, scrollerElRefs } = this;
6585 let forceYScrollbars = false;
6586 let scrollerClientWidths = {};
6587 let scrollerClientHeights = {};
6588 for (let sectionKey in scrollerRefs.currentMap) {
6589 let scroller = scrollerRefs.currentMap[sectionKey];
6590 if (scroller && scroller.needsYScrolling()) {
6591 forceYScrollbars = true;
6592 break;
6593 }
6594 }
6595 for (let section of this.props.sections) {
6596 let sectionKey = section.key;
6597 let scrollerEl = scrollerElRefs.currentMap[sectionKey];
6598 if (scrollerEl) {
6599 let harnessEl = scrollerEl.parentNode; // TODO: weird way to get this. need harness b/c doesn't include table borders
6600 scrollerClientWidths[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().width - (forceYScrollbars
6601 ? scrollbarWidth.y // use global because scroller might not have scrollbars yet but will need them in future
6602 : 0));
6603 scrollerClientHeights[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().height);
6604 }
6605 }
6606 return { forceYScrollbars, scrollerClientWidths, scrollerClientHeights };
6607 }
6608 }
6609 SimpleScrollGrid.addStateEquality({
6610 scrollerClientWidths: isPropsEqual,
6611 scrollerClientHeights: isPropsEqual,
6612 });
6613 function getSectionByKey(sections, key) {
6614 for (let section of sections) {
6615 if (section.key === key) {
6616 return section;
6617 }
6618 }
6619 return null;
6620 }
6621
6622 class EventContainer extends BaseComponent {
6623 constructor() {
6624 super(...arguments);
6625 this.handleEl = (el) => {
6626 this.el = el;
6627 if (el) {
6628 setElSeg(el, this.props.seg);
6629 }
6630 };
6631 }
6632 render() {
6633 const { props, context } = this;
6634 const { options } = context;
6635 const { seg } = props;
6636 const { eventRange } = seg;
6637 const { ui } = eventRange;
6638 const renderProps = {
6639 event: new EventImpl(context, eventRange.def, eventRange.instance),
6640 view: context.viewApi,
6641 timeText: props.timeText,
6642 textColor: ui.textColor,
6643 backgroundColor: ui.backgroundColor,
6644 borderColor: ui.borderColor,
6645 isDraggable: !props.disableDragging && computeSegDraggable(seg, context),
6646 isStartResizable: !props.disableResizing && computeSegStartResizable(seg, context),
6647 isEndResizable: !props.disableResizing && computeSegEndResizable(seg),
6648 isMirror: Boolean(props.isDragging || props.isResizing || props.isDateSelecting),
6649 isStart: Boolean(seg.isStart),
6650 isEnd: Boolean(seg.isEnd),
6651 isPast: Boolean(props.isPast),
6652 isFuture: Boolean(props.isFuture),
6653 isToday: Boolean(props.isToday),
6654 isSelected: Boolean(props.isSelected),
6655 isDragging: Boolean(props.isDragging),
6656 isResizing: Boolean(props.isResizing),
6657 };
6658 return (y(ContentContainer, Object.assign({}, props /* contains children */, { elRef: this.handleEl, elClasses: [
6659 ...getEventClassNames(renderProps),
6660 ...seg.eventRange.ui.classNames,
6661 ...(props.elClasses || []),
6662 ], renderProps: renderProps, generatorName: "eventContent", customGenerator: options.eventContent, defaultGenerator: props.defaultGenerator, classNameGenerator: options.eventClassNames, didMount: options.eventDidMount, willUnmount: options.eventWillUnmount })));
6663 }
6664 componentDidUpdate(prevProps) {
6665 if (this.el && this.props.seg !== prevProps.seg) {
6666 setElSeg(this.el, this.props.seg);
6667 }
6668 }
6669 }
6670
6671 // should not be a purecomponent
6672 class StandardEvent extends BaseComponent {
6673 render() {
6674 let { props, context } = this;
6675 let { options } = context;
6676 let { seg } = props;
6677 let { ui } = seg.eventRange;
6678 let timeFormat = options.eventTimeFormat || props.defaultTimeFormat;
6679 let timeText = buildSegTimeText(seg, timeFormat, context, props.defaultDisplayEventTime, props.defaultDisplayEventEnd);
6680 return (y(EventContainer, Object.assign({}, props /* includes elRef */, { elTag: "a", elStyle: {
6681 borderColor: ui.borderColor,
6682 backgroundColor: ui.backgroundColor,
6683 }, elAttrs: getSegAnchorAttrs(seg, context), defaultGenerator: renderInnerContent$1, timeText: timeText }), (InnerContent, eventContentArg) => (y(_, null,
6684 y(InnerContent, { elTag: "div", elClasses: ['fc-event-main'], elStyle: { color: eventContentArg.textColor } }),
6685 Boolean(eventContentArg.isStartResizable) && (y("div", { className: "fc-event-resizer fc-event-resizer-start" })),
6686 Boolean(eventContentArg.isEndResizable) && (y("div", { className: "fc-event-resizer fc-event-resizer-end" }))))));
6687 }
6688 }
6689 function renderInnerContent$1(innerProps) {
6690 return (y("div", { className: "fc-event-main-frame" },
6691 innerProps.timeText && (y("div", { className: "fc-event-time" }, innerProps.timeText)),
6692 y("div", { className: "fc-event-title-container" },
6693 y("div", { className: "fc-event-title fc-sticky" }, innerProps.event.title || y(_, null, "\u00A0")))));
6694 }
6695
6696 const NowIndicatorContainer = (props) => (y(ViewContextType.Consumer, null, (context) => {
6697 let { options } = context;
6698 let renderProps = {
6699 isAxis: props.isAxis,
6700 date: context.dateEnv.toDate(props.date),
6701 view: context.viewApi,
6702 };
6703 return (y(ContentContainer, Object.assign({}, props /* includes children */, { elTag: props.elTag || 'div', renderProps: renderProps, generatorName: "nowIndicatorContent", customGenerator: options.nowIndicatorContent, classNameGenerator: options.nowIndicatorClassNames, didMount: options.nowIndicatorDidMount, willUnmount: options.nowIndicatorWillUnmount })));
6704 }));
6705
6706 const DAY_NUM_FORMAT = createFormatter({ day: 'numeric' });
6707 class DayCellContainer extends BaseComponent {
6708 constructor() {
6709 super(...arguments);
6710 this.refineRenderProps = memoizeObjArg(refineRenderProps);
6711 }
6712 render() {
6713 let { props, context } = this;
6714 let { options } = context;
6715 let renderProps = this.refineRenderProps({
6716 date: props.date,
6717 dateProfile: props.dateProfile,
6718 todayRange: props.todayRange,
6719 isMonthStart: props.isMonthStart || false,
6720 showDayNumber: props.showDayNumber,
6721 extraRenderProps: props.extraRenderProps,
6722 viewApi: context.viewApi,
6723 dateEnv: context.dateEnv,
6724 monthStartFormat: options.monthStartFormat,
6725 });
6726 return (y(ContentContainer, Object.assign({}, props /* includes children */, { elClasses: [
6727 ...getDayClassNames(renderProps, context.theme),
6728 ...(props.elClasses || []),
6729 ], elAttrs: Object.assign(Object.assign({}, props.elAttrs), (renderProps.isDisabled ? {} : { 'data-date': formatDayString(props.date) })), renderProps: renderProps, generatorName: "dayCellContent", customGenerator: options.dayCellContent, defaultGenerator: props.defaultGenerator, classNameGenerator:
6730 // don't use custom classNames if disabled
6731 renderProps.isDisabled ? undefined : options.dayCellClassNames, didMount: options.dayCellDidMount, willUnmount: options.dayCellWillUnmount })));
6732 }
6733 }
6734 function hasCustomDayCellContent(options) {
6735 return Boolean(options.dayCellContent || hasCustomRenderingHandler('dayCellContent', options));
6736 }
6737 function refineRenderProps(raw) {
6738 let { date, dateEnv, dateProfile, isMonthStart } = raw;
6739 let dayMeta = getDateMeta(date, raw.todayRange, null, dateProfile);
6740 let dayNumberText = raw.showDayNumber ? (dateEnv.format(date, isMonthStart ? raw.monthStartFormat : DAY_NUM_FORMAT)) : '';
6741 return Object.assign(Object.assign(Object.assign({ date: dateEnv.toDate(date), view: raw.viewApi }, dayMeta), { isMonthStart,
6742 dayNumberText }), raw.extraRenderProps);
6743 }
6744
6745 class BgEvent extends BaseComponent {
6746 render() {
6747 let { props } = this;
6748 let { seg } = props;
6749 return (y(EventContainer, { elTag: "div", elClasses: ['fc-bg-event'], elStyle: { backgroundColor: seg.eventRange.ui.backgroundColor }, defaultGenerator: renderInnerContent, seg: seg, timeText: "", isDragging: false, isResizing: false, isDateSelecting: false, isSelected: false, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday, disableDragging: true, disableResizing: true }));
6750 }
6751 }
6752 function renderInnerContent(props) {
6753 let { title } = props.event;
6754 return title && (y("div", { className: "fc-event-title" }, props.event.title));
6755 }
6756 function renderFill(fillType) {
6757 return (y("div", { className: `fc-${fillType}` }));
6758 }
6759
6760 const WeekNumberContainer = (props) => (y(ViewContextType.Consumer, null, (context) => {
6761 let { dateEnv, options } = context;
6762 let { date } = props;
6763 let format = options.weekNumberFormat || props.defaultFormat;
6764 let num = dateEnv.computeWeekNumber(date); // TODO: somehow use for formatting as well?
6765 let text = dateEnv.format(date, format);
6766 let renderProps = { num, text, date };
6767 return (y(ContentContainer // why isn't WeekNumberContentArg being auto-detected?
6768 , Object.assign({}, props /* includes children */, { renderProps: renderProps, generatorName: "weekNumberContent", customGenerator: options.weekNumberContent, defaultGenerator: renderInner, classNameGenerator: options.weekNumberClassNames, didMount: options.weekNumberDidMount, willUnmount: options.weekNumberWillUnmount })));
6769 }));
6770 function renderInner(innerProps) {
6771 return innerProps.text;
6772 }
6773
6774 const PADDING_FROM_VIEWPORT = 10;
6775 class Popover extends BaseComponent {
6776 constructor() {
6777 super(...arguments);
6778 this.state = {
6779 titleId: getUniqueDomId(),
6780 };
6781 this.handleRootEl = (el) => {
6782 this.rootEl = el;
6783 if (this.props.elRef) {
6784 setRef(this.props.elRef, el);
6785 }
6786 };
6787 // Triggered when the user clicks *anywhere* in the document, for the autoHide feature
6788 this.handleDocumentMouseDown = (ev) => {
6789 // only hide the popover if the click happened outside the popover
6790 const target = getEventTargetViaRoot(ev);
6791 if (!this.rootEl.contains(target)) {
6792 this.handleCloseClick();
6793 }
6794 };
6795 this.handleDocumentKeyDown = (ev) => {
6796 if (ev.key === 'Escape') {
6797 this.handleCloseClick();
6798 }
6799 };
6800 this.handleCloseClick = () => {
6801 let { onClose } = this.props;
6802 if (onClose) {
6803 onClose();
6804 }
6805 };
6806 }
6807 render() {
6808 let { theme, options } = this.context;
6809 let { props, state } = this;
6810 let classNames = [
6811 'fc-popover',
6812 theme.getClass('popover'),
6813 ].concat(props.extraClassNames || []);
6814 return j(y("div", Object.assign({}, props.extraAttrs, { id: props.id, className: classNames.join(' '), "aria-labelledby": state.titleId, ref: this.handleRootEl }),
6815 y("div", { className: 'fc-popover-header ' + theme.getClass('popoverHeader') },
6816 y("span", { className: "fc-popover-title", id: state.titleId }, props.title),
6817 y("span", { className: 'fc-popover-close ' + theme.getIconClass('close'), title: options.closeHint, onClick: this.handleCloseClick })),
6818 y("div", { className: 'fc-popover-body ' + theme.getClass('popoverContent') }, props.children)), props.parentEl);
6819 }
6820 componentDidMount() {
6821 document.addEventListener('mousedown', this.handleDocumentMouseDown);
6822 document.addEventListener('keydown', this.handleDocumentKeyDown);
6823 this.updateSize();
6824 }
6825 componentWillUnmount() {
6826 document.removeEventListener('mousedown', this.handleDocumentMouseDown);
6827 document.removeEventListener('keydown', this.handleDocumentKeyDown);
6828 }
6829 updateSize() {
6830 let { isRtl } = this.context;
6831 let { alignmentEl, alignGridTop } = this.props;
6832 let { rootEl } = this;
6833 let alignmentRect = computeClippedClientRect(alignmentEl);
6834 if (alignmentRect) {
6835 let popoverDims = rootEl.getBoundingClientRect();
6836 // position relative to viewport
6837 let popoverTop = alignGridTop
6838 ? elementClosest(alignmentEl, '.fc-scrollgrid').getBoundingClientRect().top
6839 : alignmentRect.top;
6840 let popoverLeft = isRtl ? alignmentRect.right - popoverDims.width : alignmentRect.left;
6841 // constrain
6842 popoverTop = Math.max(popoverTop, PADDING_FROM_VIEWPORT);
6843 popoverLeft = Math.min(popoverLeft, document.documentElement.clientWidth - PADDING_FROM_VIEWPORT - popoverDims.width);
6844 popoverLeft = Math.max(popoverLeft, PADDING_FROM_VIEWPORT);
6845 let origin = rootEl.offsetParent.getBoundingClientRect();
6846 applyStyle(rootEl, {
6847 top: popoverTop - origin.top,
6848 left: popoverLeft - origin.left,
6849 });
6850 }
6851 }
6852 }
6853
6854 class MorePopover extends DateComponent {
6855 constructor() {
6856 super(...arguments);
6857 this.handleRootEl = (rootEl) => {
6858 this.rootEl = rootEl;
6859 if (rootEl) {
6860 this.context.registerInteractiveComponent(this, {
6861 el: rootEl,
6862 useEventCenter: false,
6863 });
6864 }
6865 else {
6866 this.context.unregisterInteractiveComponent(this);
6867 }
6868 };
6869 }
6870 render() {
6871 let { options, dateEnv } = this.context;
6872 let { props } = this;
6873 let { startDate, todayRange, dateProfile } = props;
6874 let title = dateEnv.format(startDate, options.dayPopoverFormat);
6875 return (y(DayCellContainer, { elRef: this.handleRootEl, date: startDate, dateProfile: dateProfile, todayRange: todayRange }, (InnerContent, renderProps, elAttrs) => (y(Popover, { elRef: elAttrs.ref, id: props.id, title: title, extraClassNames: ['fc-more-popover'].concat(elAttrs.className || []), extraAttrs: elAttrs /* TODO: make these time-based when not whole-day? */, parentEl: props.parentEl, alignmentEl: props.alignmentEl, alignGridTop: props.alignGridTop, onClose: props.onClose },
6876 hasCustomDayCellContent(options) && (y(InnerContent, { elTag: "div", elClasses: ['fc-more-popover-misc'] })),
6877 props.children))));
6878 }
6879 queryHit(positionLeft, positionTop, elWidth, elHeight) {
6880 let { rootEl, props } = this;
6881 if (positionLeft >= 0 && positionLeft < elWidth &&
6882 positionTop >= 0 && positionTop < elHeight) {
6883 return {
6884 dateProfile: props.dateProfile,
6885 dateSpan: Object.assign({ allDay: !props.forceTimed, range: {
6886 start: props.startDate,
6887 end: props.endDate,
6888 } }, props.extraDateSpan),
6889 dayEl: rootEl,
6890 rect: {
6891 left: 0,
6892 top: 0,
6893 right: elWidth,
6894 bottom: elHeight,
6895 },
6896 layer: 1, // important when comparing with hits from other components
6897 };
6898 }
6899 return null;
6900 }
6901 }
6902
6903 class MoreLinkContainer extends BaseComponent {
6904 constructor() {
6905 super(...arguments);
6906 this.state = {
6907 isPopoverOpen: false,
6908 popoverId: getUniqueDomId(),
6909 };
6910 this.handleLinkEl = (linkEl) => {
6911 this.linkEl = linkEl;
6912 if (this.props.elRef) {
6913 setRef(this.props.elRef, linkEl);
6914 }
6915 };
6916 this.handleClick = (ev) => {
6917 let { props, context } = this;
6918 let { moreLinkClick } = context.options;
6919 let date = computeRange(props).start;
6920 function buildPublicSeg(seg) {
6921 let { def, instance, range } = seg.eventRange;
6922 return {
6923 event: new EventImpl(context, def, instance),
6924 start: context.dateEnv.toDate(range.start),
6925 end: context.dateEnv.toDate(range.end),
6926 isStart: seg.isStart,
6927 isEnd: seg.isEnd,
6928 };
6929 }
6930 if (typeof moreLinkClick === 'function') {
6931 moreLinkClick = moreLinkClick({
6932 date,
6933 allDay: Boolean(props.allDayDate),
6934 allSegs: props.allSegs.map(buildPublicSeg),
6935 hiddenSegs: props.hiddenSegs.map(buildPublicSeg),
6936 jsEvent: ev,
6937 view: context.viewApi,
6938 });
6939 }
6940 if (!moreLinkClick || moreLinkClick === 'popover') {
6941 this.setState({ isPopoverOpen: true });
6942 }
6943 else if (typeof moreLinkClick === 'string') { // a view name
6944 context.calendarApi.zoomTo(date, moreLinkClick);
6945 }
6946 };
6947 this.handlePopoverClose = () => {
6948 this.setState({ isPopoverOpen: false });
6949 };
6950 }
6951 render() {
6952 let { props, state } = this;
6953 return (y(ViewContextType.Consumer, null, (context) => {
6954 let { viewApi, options, calendarApi } = context;
6955 let { moreLinkText } = options;
6956 let { moreCnt } = props;
6957 let range = computeRange(props);
6958 let text = typeof moreLinkText === 'function' // TODO: eventually use formatWithOrdinals
6959 ? moreLinkText.call(calendarApi, moreCnt)
6960 : `+${moreCnt} ${moreLinkText}`;
6961 let hint = formatWithOrdinals(options.moreLinkHint, [moreCnt], text);
6962 let renderProps = {
6963 num: moreCnt,
6964 shortText: `+${moreCnt}`,
6965 text,
6966 view: viewApi,
6967 };
6968 return (y(_, null,
6969 Boolean(props.moreCnt) && (y(ContentContainer, { elTag: props.elTag || 'a', elRef: this.handleLinkEl, elClasses: [
6970 ...(props.elClasses || []),
6971 'fc-more-link',
6972 ], elStyle: props.elStyle, elAttrs: Object.assign(Object.assign(Object.assign({}, props.elAttrs), createAriaClickAttrs(this.handleClick)), { title: hint, 'aria-expanded': state.isPopoverOpen, 'aria-controls': state.isPopoverOpen ? state.popoverId : '' }), renderProps: renderProps, generatorName: "moreLinkContent", customGenerator: options.moreLinkContent, defaultGenerator: props.defaultGenerator || renderMoreLinkInner, classNameGenerator: options.moreLinkClassNames, didMount: options.moreLinkDidMount, willUnmount: options.moreLinkWillUnmount }, props.children)),
6973 state.isPopoverOpen && (y(MorePopover, { id: state.popoverId, startDate: range.start, endDate: range.end, dateProfile: props.dateProfile, todayRange: props.todayRange, extraDateSpan: props.extraDateSpan, parentEl: this.parentEl, alignmentEl: props.alignmentElRef ?
6974 props.alignmentElRef.current :
6975 this.linkEl, alignGridTop: props.alignGridTop, forceTimed: props.forceTimed, onClose: this.handlePopoverClose }, props.popoverContent()))));
6976 }));
6977 }
6978 componentDidMount() {
6979 this.updateParentEl();
6980 }
6981 componentDidUpdate() {
6982 this.updateParentEl();
6983 }
6984 updateParentEl() {
6985 if (this.linkEl) {
6986 this.parentEl = elementClosest(this.linkEl, '.fc-view-harness');
6987 }
6988 }
6989 }
6990 function renderMoreLinkInner(props) {
6991 return props.text;
6992 }
6993 function computeRange(props) {
6994 if (props.allDayDate) {
6995 return {
6996 start: props.allDayDate,
6997 end: addDays(props.allDayDate, 1),
6998 };
6999 }
7000 let { hiddenSegs } = props;
7001 return {
7002 start: computeEarliestSegStart(hiddenSegs),
7003 end: computeLatestSegEnd(hiddenSegs),
7004 };
7005 }
7006 function computeEarliestSegStart(segs) {
7007 return segs.reduce(pickEarliestStart).eventRange.range.start;
7008 }
7009 function pickEarliestStart(seg0, seg1) {
7010 return seg0.eventRange.range.start < seg1.eventRange.range.start ? seg0 : seg1;
7011 }
7012 function computeLatestSegEnd(segs) {
7013 return segs.reduce(pickLatestEnd).eventRange.range.end;
7014 }
7015 function pickLatestEnd(seg0, seg1) {
7016 return seg0.eventRange.range.end > seg1.eventRange.range.end ? seg0 : seg1;
7017 }
7018
7019 class ViewContainer extends BaseComponent {
7020 render() {
7021 let { props, context } = this;
7022 let { options } = context;
7023 let renderProps = { view: context.viewApi };
7024 return (y(ContentContainer, Object.assign({}, props, { elTag: props.elTag || 'div', elClasses: [
7025 ...buildViewClassNames(props.viewSpec),
7026 ...(props.elClasses || []),
7027 ], renderProps: renderProps, classNameGenerator: options.viewClassNames, generatorName: undefined, didMount: options.viewDidMount, willUnmount: options.viewWillUnmount }), () => props.children));
7028 }
7029 }
7030 function buildViewClassNames(viewSpec) {
7031 return [
7032 `fc-${viewSpec.type}-view`,
7033 'fc-view',
7034 ];
7035 }
7036
7037 const EVENT_SOURCE_REFINERS = {
7038 id: String,
7039 defaultAllDay: Boolean,
7040 url: String,
7041 format: String,
7042 events: identity,
7043 eventDataTransform: identity,
7044 // for any network-related sources
7045 success: identity,
7046 failure: identity,
7047 };
7048 function parseEventSource(raw, context, refiners = buildEventSourceRefiners(context)) {
7049 let rawObj;
7050 if (typeof raw === 'string') {
7051 rawObj = { url: raw };
7052 }
7053 else if (typeof raw === 'function' || Array.isArray(raw)) {
7054 rawObj = { events: raw };
7055 }
7056 else if (typeof raw === 'object' && raw) { // not null
7057 rawObj = raw;
7058 }
7059 if (rawObj) {
7060 let { refined, extra } = refineProps(rawObj, refiners);
7061 let metaRes = buildEventSourceMeta(refined, context);
7062 if (metaRes) {
7063 return {
7064 _raw: raw,
7065 isFetching: false,
7066 latestFetchId: '',
7067 fetchRange: null,
7068 defaultAllDay: refined.defaultAllDay,
7069 eventDataTransform: refined.eventDataTransform,
7070 success: refined.success,
7071 failure: refined.failure,
7072 publicId: refined.id || '',
7073 sourceId: guid(),
7074 sourceDefId: metaRes.sourceDefId,
7075 meta: metaRes.meta,
7076 ui: createEventUi(refined, context),
7077 extendedProps: extra,
7078 };
7079 }
7080 }
7081 return null;
7082 }
7083 function buildEventSourceRefiners(context) {
7084 return Object.assign(Object.assign(Object.assign({}, EVENT_UI_REFINERS), EVENT_SOURCE_REFINERS), context.pluginHooks.eventSourceRefiners);
7085 }
7086 function buildEventSourceMeta(raw, context) {
7087 let defs = context.pluginHooks.eventSourceDefs;
7088 for (let i = defs.length - 1; i >= 0; i -= 1) { // later-added plugins take precedence
7089 let def = defs[i];
7090 let meta = def.parseMeta(raw);
7091 if (meta) {
7092 return { sourceDefId: i, meta };
7093 }
7094 }
7095 return null;
7096 }
7097
7098 class CalendarImpl {
7099 getCurrentData() {
7100 return this.currentDataManager.getCurrentData();
7101 }
7102 dispatch(action) {
7103 this.currentDataManager.dispatch(action);
7104 }
7105 get view() { return this.getCurrentData().viewApi; }
7106 batchRendering(callback) {
7107 callback();
7108 }
7109 updateSize() {
7110 this.trigger('_resize', true);
7111 }
7112 // Options
7113 // -----------------------------------------------------------------------------------------------------------------
7114 setOption(name, val) {
7115 this.dispatch({
7116 type: 'SET_OPTION',
7117 optionName: name,
7118 rawOptionValue: val,
7119 });
7120 }
7121 getOption(name) {
7122 return this.currentDataManager.currentCalendarOptionsInput[name];
7123 }
7124 getAvailableLocaleCodes() {
7125 return Object.keys(this.getCurrentData().availableRawLocales);
7126 }
7127 // Trigger
7128 // -----------------------------------------------------------------------------------------------------------------
7129 on(handlerName, handler) {
7130 let { currentDataManager } = this;
7131 if (currentDataManager.currentCalendarOptionsRefiners[handlerName]) {
7132 currentDataManager.emitter.on(handlerName, handler);
7133 }
7134 else {
7135 console.warn(`Unknown listener name '${handlerName}'`);
7136 }
7137 }
7138 off(handlerName, handler) {
7139 this.currentDataManager.emitter.off(handlerName, handler);
7140 }
7141 // not meant for public use
7142 trigger(handlerName, ...args) {
7143 this.currentDataManager.emitter.trigger(handlerName, ...args);
7144 }
7145 // View
7146 // -----------------------------------------------------------------------------------------------------------------
7147 changeView(viewType, dateOrRange) {
7148 this.batchRendering(() => {
7149 this.unselect();
7150 if (dateOrRange) {
7151 if (dateOrRange.start && dateOrRange.end) { // a range
7152 this.dispatch({
7153 type: 'CHANGE_VIEW_TYPE',
7154 viewType,
7155 });
7156 this.dispatch({
7157 type: 'SET_OPTION',
7158 optionName: 'visibleRange',
7159 rawOptionValue: dateOrRange,
7160 });
7161 }
7162 else {
7163 let { dateEnv } = this.getCurrentData();
7164 this.dispatch({
7165 type: 'CHANGE_VIEW_TYPE',
7166 viewType,
7167 dateMarker: dateEnv.createMarker(dateOrRange),
7168 });
7169 }
7170 }
7171 else {
7172 this.dispatch({
7173 type: 'CHANGE_VIEW_TYPE',
7174 viewType,
7175 });
7176 }
7177 });
7178 }
7179 // Forces navigation to a view for the given date.
7180 // `viewType` can be a specific view name or a generic one like "week" or "day".
7181 // needs to change
7182 zoomTo(dateMarker, viewType) {
7183 let state = this.getCurrentData();
7184 let spec;
7185 viewType = viewType || 'day'; // day is default zoom
7186 spec = state.viewSpecs[viewType] || this.getUnitViewSpec(viewType);
7187 this.unselect();
7188 if (spec) {
7189 this.dispatch({
7190 type: 'CHANGE_VIEW_TYPE',
7191 viewType: spec.type,
7192 dateMarker,
7193 });
7194 }
7195 else {
7196 this.dispatch({
7197 type: 'CHANGE_DATE',
7198 dateMarker,
7199 });
7200 }
7201 }
7202 // Given a duration singular unit, like "week" or "day", finds a matching view spec.
7203 // Preference is given to views that have corresponding buttons.
7204 getUnitViewSpec(unit) {
7205 let { viewSpecs, toolbarConfig } = this.getCurrentData();
7206 let viewTypes = [].concat(toolbarConfig.header ? toolbarConfig.header.viewsWithButtons : [], toolbarConfig.footer ? toolbarConfig.footer.viewsWithButtons : []);
7207 let i;
7208 let spec;
7209 for (let viewType in viewSpecs) {
7210 viewTypes.push(viewType);
7211 }
7212 for (i = 0; i < viewTypes.length; i += 1) {
7213 spec = viewSpecs[viewTypes[i]];
7214 if (spec) {
7215 if (spec.singleUnit === unit) {
7216 return spec;
7217 }
7218 }
7219 }
7220 return null;
7221 }
7222 // Current Date
7223 // -----------------------------------------------------------------------------------------------------------------
7224 prev() {
7225 this.unselect();
7226 this.dispatch({ type: 'PREV' });
7227 }
7228 next() {
7229 this.unselect();
7230 this.dispatch({ type: 'NEXT' });
7231 }
7232 prevYear() {
7233 let state = this.getCurrentData();
7234 this.unselect();
7235 this.dispatch({
7236 type: 'CHANGE_DATE',
7237 dateMarker: state.dateEnv.addYears(state.currentDate, -1),
7238 });
7239 }
7240 nextYear() {
7241 let state = this.getCurrentData();
7242 this.unselect();
7243 this.dispatch({
7244 type: 'CHANGE_DATE',
7245 dateMarker: state.dateEnv.addYears(state.currentDate, 1),
7246 });
7247 }
7248 today() {
7249 let state = this.getCurrentData();
7250 this.unselect();
7251 this.dispatch({
7252 type: 'CHANGE_DATE',
7253 dateMarker: state.nowManager.getDateMarker(),
7254 });
7255 }
7256 gotoDate(zonedDateInput) {
7257 let state = this.getCurrentData();
7258 this.unselect();
7259 this.dispatch({
7260 type: 'CHANGE_DATE',
7261 dateMarker: state.dateEnv.createMarker(zonedDateInput),
7262 });
7263 }
7264 incrementDate(deltaInput) {
7265 let state = this.getCurrentData();
7266 let delta = createDuration(deltaInput);
7267 if (delta) { // else, warn about invalid input?
7268 this.unselect();
7269 this.dispatch({
7270 type: 'CHANGE_DATE',
7271 dateMarker: state.dateEnv.add(state.currentDate, delta),
7272 });
7273 }
7274 }
7275 getDate() {
7276 let state = this.getCurrentData();
7277 return state.dateEnv.toDate(state.currentDate);
7278 }
7279 // Date Formatting Utils
7280 // -----------------------------------------------------------------------------------------------------------------
7281 formatDate(d, formatter) {
7282 let { dateEnv } = this.getCurrentData();
7283 return dateEnv.format(dateEnv.createMarker(d), createFormatter(formatter));
7284 }
7285 // `settings` is for formatter AND isEndExclusive
7286 formatRange(d0, d1, settings) {
7287 let { dateEnv } = this.getCurrentData();
7288 return dateEnv.formatRange(dateEnv.createMarker(d0), dateEnv.createMarker(d1), createFormatter(settings), settings);
7289 }
7290 formatIso(d, omitTime) {
7291 let { dateEnv } = this.getCurrentData();
7292 return dateEnv.formatIso(dateEnv.createMarker(d), { omitTime });
7293 }
7294 // Date Selection / Event Selection / DayClick
7295 // -----------------------------------------------------------------------------------------------------------------
7296 select(dateOrObj, endDate) {
7297 let selectionInput;
7298 if (endDate == null) {
7299 if (dateOrObj.start != null) {
7300 selectionInput = dateOrObj;
7301 }
7302 else {
7303 selectionInput = {
7304 start: dateOrObj,
7305 end: null,
7306 };
7307 }
7308 }
7309 else {
7310 selectionInput = {
7311 start: dateOrObj,
7312 end: endDate,
7313 };
7314 }
7315 let state = this.getCurrentData();
7316 let selection = parseDateSpan(selectionInput, state.dateEnv, createDuration({ days: 1 }));
7317 if (selection) { // throw parse error otherwise?
7318 this.dispatch({ type: 'SELECT_DATES', selection });
7319 triggerDateSelect(selection, null, state);
7320 }
7321 }
7322 unselect(pev) {
7323 let state = this.getCurrentData();
7324 if (state.dateSelection) {
7325 this.dispatch({ type: 'UNSELECT_DATES' });
7326 triggerDateUnselect(pev, state);
7327 }
7328 }
7329 // Public Events API
7330 // -----------------------------------------------------------------------------------------------------------------
7331 addEvent(eventInput, sourceInput) {
7332 if (eventInput instanceof EventImpl) {
7333 let def = eventInput._def;
7334 let instance = eventInput._instance;
7335 let currentData = this.getCurrentData();
7336 // not already present? don't want to add an old snapshot
7337 if (!currentData.eventStore.defs[def.defId]) {
7338 this.dispatch({
7339 type: 'ADD_EVENTS',
7340 eventStore: eventTupleToStore({ def, instance }), // TODO: better util for two args?
7341 });
7342 this.triggerEventAdd(eventInput);
7343 }
7344 return eventInput;
7345 }
7346 let state = this.getCurrentData();
7347 let eventSource;
7348 if (sourceInput instanceof EventSourceImpl) {
7349 eventSource = sourceInput.internalEventSource;
7350 }
7351 else if (typeof sourceInput === 'boolean') {
7352 if (sourceInput) { // true. part of the first event source
7353 [eventSource] = hashValuesToArray(state.eventSources);
7354 }
7355 }
7356 else if (sourceInput != null) { // an ID. accepts a number too
7357 let sourceApi = this.getEventSourceById(sourceInput); // TODO: use an internal function
7358 if (!sourceApi) {
7359 console.warn(`Could not find an event source with ID "${sourceInput}"`); // TODO: test
7360 return null;
7361 }
7362 eventSource = sourceApi.internalEventSource;
7363 }
7364 let tuple = parseEvent(eventInput, eventSource, state, false);
7365 if (tuple) {
7366 let newEventApi = new EventImpl(state, tuple.def, tuple.def.recurringDef ? null : tuple.instance);
7367 this.dispatch({
7368 type: 'ADD_EVENTS',
7369 eventStore: eventTupleToStore(tuple),
7370 });
7371 this.triggerEventAdd(newEventApi);
7372 return newEventApi;
7373 }
7374 return null;
7375 }
7376 triggerEventAdd(eventApi) {
7377 let { emitter } = this.getCurrentData();
7378 emitter.trigger('eventAdd', {
7379 event: eventApi,
7380 relatedEvents: [],
7381 revert: () => {
7382 this.dispatch({
7383 type: 'REMOVE_EVENTS',
7384 eventStore: eventApiToStore(eventApi),
7385 });
7386 },
7387 });
7388 }
7389 // TODO: optimize
7390 getEventById(id) {
7391 let state = this.getCurrentData();
7392 let { defs, instances } = state.eventStore;
7393 id = String(id);
7394 for (let defId in defs) {
7395 let def = defs[defId];
7396 if (def.publicId === id) {
7397 if (def.recurringDef) {
7398 return new EventImpl(state, def, null);
7399 }
7400 for (let instanceId in instances) {
7401 let instance = instances[instanceId];
7402 if (instance.defId === def.defId) {
7403 return new EventImpl(state, def, instance);
7404 }
7405 }
7406 }
7407 }
7408 return null;
7409 }
7410 getEvents() {
7411 let currentData = this.getCurrentData();
7412 return buildEventApis(currentData.eventStore, currentData);
7413 }
7414 removeAllEvents() {
7415 this.dispatch({ type: 'REMOVE_ALL_EVENTS' });
7416 }
7417 // Public Event Sources API
7418 // -----------------------------------------------------------------------------------------------------------------
7419 getEventSources() {
7420 let state = this.getCurrentData();
7421 let sourceHash = state.eventSources;
7422 let sourceApis = [];
7423 for (let internalId in sourceHash) {
7424 sourceApis.push(new EventSourceImpl(state, sourceHash[internalId]));
7425 }
7426 return sourceApis;
7427 }
7428 getEventSourceById(id) {
7429 let state = this.getCurrentData();
7430 let sourceHash = state.eventSources;
7431 id = String(id);
7432 for (let sourceId in sourceHash) {
7433 if (sourceHash[sourceId].publicId === id) {
7434 return new EventSourceImpl(state, sourceHash[sourceId]);
7435 }
7436 }
7437 return null;
7438 }
7439 addEventSource(sourceInput) {
7440 let state = this.getCurrentData();
7441 if (sourceInput instanceof EventSourceImpl) {
7442 // not already present? don't want to add an old snapshot
7443 if (!state.eventSources[sourceInput.internalEventSource.sourceId]) {
7444 this.dispatch({
7445 type: 'ADD_EVENT_SOURCES',
7446 sources: [sourceInput.internalEventSource],
7447 });
7448 }
7449 return sourceInput;
7450 }
7451 let eventSource = parseEventSource(sourceInput, state);
7452 if (eventSource) { // TODO: error otherwise?
7453 this.dispatch({ type: 'ADD_EVENT_SOURCES', sources: [eventSource] });
7454 return new EventSourceImpl(state, eventSource);
7455 }
7456 return null;
7457 }
7458 removeAllEventSources() {
7459 this.dispatch({ type: 'REMOVE_ALL_EVENT_SOURCES' });
7460 }
7461 refetchEvents() {
7462 this.dispatch({ type: 'FETCH_EVENT_SOURCES', isRefetch: true });
7463 }
7464 // Scroll
7465 // -----------------------------------------------------------------------------------------------------------------
7466 scrollToTime(timeInput) {
7467 let time = createDuration(timeInput);
7468 if (time) {
7469 this.trigger('_scrollRequest', { time });
7470 }
7471 }
7472 }
7473
7474 class Store {
7475 constructor() {
7476 this.handlers = [];
7477 }
7478 set(value) {
7479 this.currentValue = value;
7480 for (let handler of this.handlers) {
7481 handler(value);
7482 }
7483 }
7484 subscribe(handler) {
7485 this.handlers.push(handler);
7486 if (this.currentValue !== undefined) {
7487 handler(this.currentValue);
7488 }
7489 }
7490 }
7491
7492 /*
7493 Subscribers will get a LIST of CustomRenderings
7494 */
7495 class CustomRenderingStore extends Store {
7496 constructor() {
7497 super(...arguments);
7498 this.map = new Map();
7499 }
7500 // for consistent order
7501 handle(customRendering) {
7502 const { map } = this;
7503 let updated = false;
7504 if (customRendering.isActive) {
7505 map.set(customRendering.id, customRendering);
7506 updated = true;
7507 }
7508 else if (map.has(customRendering.id)) {
7509 map.delete(customRendering.id);
7510 updated = true;
7511 }
7512 if (updated) {
7513 this.set(map);
7514 }
7515 }
7516 }
7517
7518 var internal = {
7519 __proto__: null,
7520 BASE_OPTION_DEFAULTS: BASE_OPTION_DEFAULTS,
7521 identity: identity,
7522 refineProps: refineProps,
7523 createEventInstance: createEventInstance,
7524 parseEventDef: parseEventDef,
7525 refineEventDef: refineEventDef,
7526 parseBusinessHours: parseBusinessHours,
7527 padStart: padStart,
7528 isInt: isInt,
7529 parseFieldSpecs: parseFieldSpecs,
7530 compareByFieldSpecs: compareByFieldSpecs,
7531 flexibleCompare: flexibleCompare,
7532 preventSelection: preventSelection,
7533 allowSelection: allowSelection,
7534 preventContextMenu: preventContextMenu,
7535 allowContextMenu: allowContextMenu,
7536 compareNumbers: compareNumbers,
7537 enableCursor: enableCursor,
7538 disableCursor: disableCursor,
7539 guid: guid,
7540 computeVisibleDayRange: computeVisibleDayRange,
7541 isMultiDayRange: isMultiDayRange,
7542 diffDates: diffDates,
7543 removeExact: removeExact,
7544 isArraysEqual: isArraysEqual,
7545 memoize: memoize,
7546 memoizeObjArg: memoizeObjArg,
7547 memoizeArraylike: memoizeArraylike,
7548 memoizeHashlike: memoizeHashlike,
7549 intersectRects: intersectRects,
7550 pointInsideRect: pointInsideRect,
7551 constrainPoint: constrainPoint,
7552 getRectCenter: getRectCenter,
7553 diffPoints: diffPoints,
7554 translateRect: translateRect,
7555 mapHash: mapHash,
7556 filterHash: filterHash,
7557 isPropsEqual: isPropsEqual,
7558 compareObjs: compareObjs,
7559 collectFromHash: collectFromHash,
7560 findElements: findElements,
7561 findDirectChildren: findDirectChildren,
7562 removeElement: removeElement,
7563 applyStyle: applyStyle,
7564 elementMatches: elementMatches,
7565 elementClosest: elementClosest,
7566 getEventTargetViaRoot: getEventTargetViaRoot,
7567 getUniqueDomId: getUniqueDomId,
7568 parseClassNames: parseClassNames,
7569 getCanVGrowWithinCell: getCanVGrowWithinCell,
7570 createEmptyEventStore: createEmptyEventStore,
7571 mergeEventStores: mergeEventStores,
7572 getRelevantEvents: getRelevantEvents,
7573 eventTupleToStore: eventTupleToStore,
7574 combineEventUis: combineEventUis,
7575 createEventUi: createEventUi,
7576 Splitter: Splitter,
7577 getDayClassNames: getDayClassNames,
7578 getDateMeta: getDateMeta,
7579 getSlotClassNames: getSlotClassNames,
7580 buildNavLinkAttrs: buildNavLinkAttrs,
7581 preventDefault: preventDefault,
7582 whenTransitionDone: whenTransitionDone,
7583 computeInnerRect: computeInnerRect,
7584 computeEdges: computeEdges,
7585 getClippingParents: getClippingParents,
7586 computeRect: computeRect,
7587 unpromisify: unpromisify,
7588 Emitter: Emitter,
7589 rangeContainsMarker: rangeContainsMarker,
7590 intersectRanges: intersectRanges,
7591 rangesEqual: rangesEqual,
7592 rangesIntersect: rangesIntersect,
7593 rangeContainsRange: rangeContainsRange,
7594 PositionCache: PositionCache,
7595 ScrollController: ScrollController,
7596 ElementScrollController: ElementScrollController,
7597 WindowScrollController: WindowScrollController,
7598 Theme: Theme,
7599 ViewContextType: ViewContextType,
7600 DateComponent: DateComponent,
7601 DateProfileGenerator: DateProfileGenerator,
7602 isDateSpansEqual: isDateSpansEqual,
7603 addDays: addDays,
7604 startOfDay: startOfDay,
7605 addMs: addMs,
7606 addWeeks: addWeeks,
7607 diffWeeks: diffWeeks,
7608 diffWholeWeeks: diffWholeWeeks,
7609 diffWholeDays: diffWholeDays,
7610 diffDayAndTime: diffDayAndTime,
7611 diffDays: diffDays,
7612 isValidDate: isValidDate,
7613 createDuration: createDuration,
7614 asCleanDays: asCleanDays,
7615 multiplyDuration: multiplyDuration,
7616 addDurations: addDurations,
7617 asRoughMinutes: asRoughMinutes,
7618 asRoughSeconds: asRoughSeconds,
7619 asRoughMs: asRoughMs,
7620 wholeDivideDurations: wholeDivideDurations,
7621 greatestDurationDenominator: greatestDurationDenominator,
7622 DateEnv: DateEnv,
7623 createFormatter: createFormatter,
7624 formatIsoTimeString: formatIsoTimeString,
7625 formatDayString: formatDayString,
7626 buildIsoString: buildIsoString,
7627 formatIsoMonthStr: formatIsoMonthStr,
7628 NamedTimeZoneImpl: NamedTimeZoneImpl,
7629 parseMarker: parse,
7630 SegHierarchy: SegHierarchy,
7631 buildEntryKey: buildEntryKey,
7632 getEntrySpanEnd: getEntrySpanEnd,
7633 binarySearch: binarySearch,
7634 groupIntersectingEntries: groupIntersectingEntries,
7635 intersectSpans: intersectSpans,
7636 Interaction: Interaction,
7637 interactionSettingsToStore: interactionSettingsToStore,
7638 interactionSettingsStore: interactionSettingsStore,
7639 ElementDragging: ElementDragging,
7640 config: config,
7641 parseDragMeta: parseDragMeta,
7642 CalendarRoot: CalendarRoot,
7643 DayHeader: DayHeader,
7644 computeFallbackHeaderFormat: computeFallbackHeaderFormat,
7645 TableDateCell: TableDateCell,
7646 TableDowCell: TableDowCell,
7647 DaySeriesModel: DaySeriesModel,
7648 sliceEventStore: sliceEventStore,
7649 hasBgRendering: hasBgRendering,
7650 getElSeg: getElSeg,
7651 buildSegTimeText: buildSegTimeText,
7652 sortEventSegs: sortEventSegs,
7653 getSegMeta: getSegMeta,
7654 buildEventRangeKey: buildEventRangeKey,
7655 getSegAnchorAttrs: getSegAnchorAttrs,
7656 DayTableModel: DayTableModel,
7657 Slicer: Slicer,
7658 applyMutationToEventStore: applyMutationToEventStore,
7659 isPropsValid: isPropsValid,
7660 isInteractionValid: isInteractionValid,
7661 isDateSelectionValid: isDateSelectionValid,
7662 requestJson: requestJson,
7663 BaseComponent: BaseComponent,
7664 setRef: setRef,
7665 DelayedRunner: DelayedRunner,
7666 SimpleScrollGrid: SimpleScrollGrid,
7667 hasShrinkWidth: hasShrinkWidth,
7668 renderMicroColGroup: renderMicroColGroup,
7669 getScrollGridClassNames: getScrollGridClassNames,
7670 getSectionClassNames: getSectionClassNames,
7671 getSectionHasLiquidHeight: getSectionHasLiquidHeight,
7672 getAllowYScrolling: getAllowYScrolling,
7673 renderChunkContent: renderChunkContent,
7674 computeShrinkWidth: computeShrinkWidth,
7675 sanitizeShrinkWidth: sanitizeShrinkWidth,
7676 isColPropsEqual: isColPropsEqual,
7677 renderScrollShim: renderScrollShim,
7678 getStickyFooterScrollbar: getStickyFooterScrollbar,
7679 getStickyHeaderDates: getStickyHeaderDates,
7680 Scroller: Scroller,
7681 getScrollbarWidths: getScrollbarWidths,
7682 RefMap: RefMap,
7683 getIsRtlScrollbarOnLeft: getIsRtlScrollbarOnLeft,
7684 NowTimer: NowTimer,
7685 ScrollResponder: ScrollResponder,
7686 StandardEvent: StandardEvent,
7687 NowIndicatorContainer: NowIndicatorContainer,
7688 DayCellContainer: DayCellContainer,
7689 hasCustomDayCellContent: hasCustomDayCellContent,
7690 EventContainer: EventContainer,
7691 renderFill: renderFill,
7692 BgEvent: BgEvent,
7693 WeekNumberContainer: WeekNumberContainer,
7694 MoreLinkContainer: MoreLinkContainer,
7695 computeEarliestSegStart: computeEarliestSegStart,
7696 ViewContainer: ViewContainer,
7697 triggerDateSelect: triggerDateSelect,
7698 getDefaultEventEnd: getDefaultEventEnd,
7699 injectStyles: injectStyles,
7700 CalendarImpl: CalendarImpl,
7701 EventImpl: EventImpl,
7702 buildEventApis: buildEventApis,
7703 buildElAttrs: buildElAttrs,
7704 ContentContainer: ContentContainer,
7705 CustomRenderingStore: CustomRenderingStore
7706 };
7707
7708 const globalLocales = [];
7709
7710 const MINIMAL_RAW_EN_LOCALE = {
7711 code: 'en',
7712 week: {
7713 dow: 0,
7714 doy: 4, // 4 days need to be within the year to be considered the first week
7715 },
7716 direction: 'ltr',
7717 buttonText: {
7718 prev: 'prev',
7719 next: 'next',
7720 prevYear: 'prev year',
7721 nextYear: 'next year',
7722 year: 'year',
7723 today: 'today',
7724 month: 'month',
7725 week: 'week',
7726 day: 'day',
7727 list: 'list',
7728 },
7729 weekText: 'W',
7730 weekTextLong: 'Week',
7731 closeHint: 'Close',
7732 timeHint: 'Time',
7733 eventHint: 'Event',
7734 allDayText: 'all-day',
7735 moreLinkText: 'more',
7736 noEventsText: 'No events to display',
7737 };
7738 const RAW_EN_LOCALE = Object.assign(Object.assign({}, MINIMAL_RAW_EN_LOCALE), {
7739 // Includes things we don't want other locales to inherit,
7740 // things that derive from other translatable strings.
7741 buttonHints: {
7742 prev: 'Previous $0',
7743 next: 'Next $0',
7744 today(buttonText, unit) {
7745 return (unit === 'day')
7746 ? 'Today'
7747 : `This ${buttonText}`;
7748 },
7749 }, viewHint: '$0 view', navLinkHint: 'Go to $0', moreLinkHint(eventCnt) {
7750 return `Show ${eventCnt} more event${eventCnt === 1 ? '' : 's'}`;
7751 } });
7752 function organizeRawLocales(explicitRawLocales) {
7753 let defaultCode = explicitRawLocales.length > 0 ? explicitRawLocales[0].code : 'en';
7754 let allRawLocales = globalLocales.concat(explicitRawLocales);
7755 let rawLocaleMap = {
7756 en: RAW_EN_LOCALE,
7757 };
7758 for (let rawLocale of allRawLocales) {
7759 rawLocaleMap[rawLocale.code] = rawLocale;
7760 }
7761 return {
7762 map: rawLocaleMap,
7763 defaultCode,
7764 };
7765 }
7766 function buildLocale(inputSingular, available) {
7767 if (typeof inputSingular === 'object' && !Array.isArray(inputSingular)) {
7768 return parseLocale(inputSingular.code, [inputSingular.code], inputSingular);
7769 }
7770 return queryLocale(inputSingular, available);
7771 }
7772 function queryLocale(codeArg, available) {
7773 let codes = [].concat(codeArg || []); // will convert to array
7774 let raw = queryRawLocale(codes, available) || RAW_EN_LOCALE;
7775 return parseLocale(codeArg, codes, raw);
7776 }
7777 function queryRawLocale(codes, available) {
7778 for (let i = 0; i < codes.length; i += 1) {
7779 let parts = codes[i].toLocaleLowerCase().split('-');
7780 for (let j = parts.length; j > 0; j -= 1) {
7781 let simpleId = parts.slice(0, j).join('-');
7782 if (available[simpleId]) {
7783 return available[simpleId];
7784 }
7785 }
7786 }
7787 return null;
7788 }
7789 function parseLocale(codeArg, codes, raw) {
7790 let merged = mergeProps([MINIMAL_RAW_EN_LOCALE, raw], ['buttonText']);
7791 delete merged.code; // don't want this part of the options
7792 let { week } = merged;
7793 delete merged.week;
7794 return {
7795 codeArg,
7796 codes,
7797 week,
7798 simpleNumberFormat: new Intl.NumberFormat(codeArg),
7799 options: merged,
7800 };
7801 }
7802
7803 // TODO: easier way to add new hooks? need to update a million things
7804 function createPlugin(input) {
7805 return {
7806 id: guid(),
7807 name: input.name,
7808 premiumReleaseDate: input.premiumReleaseDate ? new Date(input.premiumReleaseDate) : undefined,
7809 deps: input.deps || [],
7810 reducers: input.reducers || [],
7811 isLoadingFuncs: input.isLoadingFuncs || [],
7812 contextInit: [].concat(input.contextInit || []),
7813 eventRefiners: input.eventRefiners || {},
7814 eventDefMemberAdders: input.eventDefMemberAdders || [],
7815 eventSourceRefiners: input.eventSourceRefiners || {},
7816 isDraggableTransformers: input.isDraggableTransformers || [],
7817 eventDragMutationMassagers: input.eventDragMutationMassagers || [],
7818 eventDefMutationAppliers: input.eventDefMutationAppliers || [],
7819 dateSelectionTransformers: input.dateSelectionTransformers || [],
7820 datePointTransforms: input.datePointTransforms || [],
7821 dateSpanTransforms: input.dateSpanTransforms || [],
7822 views: input.views || {},
7823 viewPropsTransformers: input.viewPropsTransformers || [],
7824 isPropsValid: input.isPropsValid || null,
7825 externalDefTransforms: input.externalDefTransforms || [],
7826 viewContainerAppends: input.viewContainerAppends || [],
7827 eventDropTransformers: input.eventDropTransformers || [],
7828 componentInteractions: input.componentInteractions || [],
7829 calendarInteractions: input.calendarInteractions || [],
7830 themeClasses: input.themeClasses || {},
7831 eventSourceDefs: input.eventSourceDefs || [],
7832 cmdFormatter: input.cmdFormatter,
7833 recurringTypes: input.recurringTypes || [],
7834 namedTimeZonedImpl: input.namedTimeZonedImpl,
7835 initialView: input.initialView || '',
7836 elementDraggingImpl: input.elementDraggingImpl,
7837 optionChangeHandlers: input.optionChangeHandlers || {},
7838 scrollGridImpl: input.scrollGridImpl || null,
7839 listenerRefiners: input.listenerRefiners || {},
7840 optionRefiners: input.optionRefiners || {},
7841 propSetHandlers: input.propSetHandlers || {},
7842 };
7843 }
7844 function buildPluginHooks(pluginDefs, globalDefs) {
7845 let currentPluginIds = {};
7846 let hooks = {
7847 premiumReleaseDate: undefined,
7848 reducers: [],
7849 isLoadingFuncs: [],
7850 contextInit: [],
7851 eventRefiners: {},
7852 eventDefMemberAdders: [],
7853 eventSourceRefiners: {},
7854 isDraggableTransformers: [],
7855 eventDragMutationMassagers: [],
7856 eventDefMutationAppliers: [],
7857 dateSelectionTransformers: [],
7858 datePointTransforms: [],
7859 dateSpanTransforms: [],
7860 views: {},
7861 viewPropsTransformers: [],
7862 isPropsValid: null,
7863 externalDefTransforms: [],
7864 viewContainerAppends: [],
7865 eventDropTransformers: [],
7866 componentInteractions: [],
7867 calendarInteractions: [],
7868 themeClasses: {},
7869 eventSourceDefs: [],
7870 cmdFormatter: null,
7871 recurringTypes: [],
7872 namedTimeZonedImpl: null,
7873 initialView: '',
7874 elementDraggingImpl: null,
7875 optionChangeHandlers: {},
7876 scrollGridImpl: null,
7877 listenerRefiners: {},
7878 optionRefiners: {},
7879 propSetHandlers: {},
7880 };
7881 function addDefs(defs) {
7882 for (let def of defs) {
7883 const pluginName = def.name;
7884 const currentId = currentPluginIds[pluginName];
7885 if (currentId === undefined) {
7886 currentPluginIds[pluginName] = def.id;
7887 addDefs(def.deps);
7888 hooks = combineHooks(hooks, def);
7889 }
7890 else if (currentId !== def.id) {
7891 // different ID than the one already added
7892 console.warn(`Duplicate plugin '${pluginName}'`);
7893 }
7894 }
7895 }
7896 if (pluginDefs) {
7897 addDefs(pluginDefs);
7898 }
7899 addDefs(globalDefs);
7900 return hooks;
7901 }
7902 function buildBuildPluginHooks() {
7903 let currentOverrideDefs = [];
7904 let currentGlobalDefs = [];
7905 let currentHooks;
7906 return (overrideDefs, globalDefs) => {
7907 if (!currentHooks || !isArraysEqual(overrideDefs, currentOverrideDefs) || !isArraysEqual(globalDefs, currentGlobalDefs)) {
7908 currentHooks = buildPluginHooks(overrideDefs, globalDefs);
7909 }
7910 currentOverrideDefs = overrideDefs;
7911 currentGlobalDefs = globalDefs;
7912 return currentHooks;
7913 };
7914 }
7915 function combineHooks(hooks0, hooks1) {
7916 return {
7917 premiumReleaseDate: compareOptionalDates(hooks0.premiumReleaseDate, hooks1.premiumReleaseDate),
7918 reducers: hooks0.reducers.concat(hooks1.reducers),
7919 isLoadingFuncs: hooks0.isLoadingFuncs.concat(hooks1.isLoadingFuncs),
7920 contextInit: hooks0.contextInit.concat(hooks1.contextInit),
7921 eventRefiners: Object.assign(Object.assign({}, hooks0.eventRefiners), hooks1.eventRefiners),
7922 eventDefMemberAdders: hooks0.eventDefMemberAdders.concat(hooks1.eventDefMemberAdders),
7923 eventSourceRefiners: Object.assign(Object.assign({}, hooks0.eventSourceRefiners), hooks1.eventSourceRefiners),
7924 isDraggableTransformers: hooks0.isDraggableTransformers.concat(hooks1.isDraggableTransformers),
7925 eventDragMutationMassagers: hooks0.eventDragMutationMassagers.concat(hooks1.eventDragMutationMassagers),
7926 eventDefMutationAppliers: hooks0.eventDefMutationAppliers.concat(hooks1.eventDefMutationAppliers),
7927 dateSelectionTransformers: hooks0.dateSelectionTransformers.concat(hooks1.dateSelectionTransformers),
7928 datePointTransforms: hooks0.datePointTransforms.concat(hooks1.datePointTransforms),
7929 dateSpanTransforms: hooks0.dateSpanTransforms.concat(hooks1.dateSpanTransforms),
7930 views: Object.assign(Object.assign({}, hooks0.views), hooks1.views),
7931 viewPropsTransformers: hooks0.viewPropsTransformers.concat(hooks1.viewPropsTransformers),
7932 isPropsValid: hooks1.isPropsValid || hooks0.isPropsValid,
7933 externalDefTransforms: hooks0.externalDefTransforms.concat(hooks1.externalDefTransforms),
7934 viewContainerAppends: hooks0.viewContainerAppends.concat(hooks1.viewContainerAppends),
7935 eventDropTransformers: hooks0.eventDropTransformers.concat(hooks1.eventDropTransformers),
7936 calendarInteractions: hooks0.calendarInteractions.concat(hooks1.calendarInteractions),
7937 componentInteractions: hooks0.componentInteractions.concat(hooks1.componentInteractions),
7938 themeClasses: Object.assign(Object.assign({}, hooks0.themeClasses), hooks1.themeClasses),
7939 eventSourceDefs: hooks0.eventSourceDefs.concat(hooks1.eventSourceDefs),
7940 cmdFormatter: hooks1.cmdFormatter || hooks0.cmdFormatter,
7941 recurringTypes: hooks0.recurringTypes.concat(hooks1.recurringTypes),
7942 namedTimeZonedImpl: hooks1.namedTimeZonedImpl || hooks0.namedTimeZonedImpl,
7943 initialView: hooks0.initialView || hooks1.initialView,
7944 elementDraggingImpl: hooks0.elementDraggingImpl || hooks1.elementDraggingImpl,
7945 optionChangeHandlers: Object.assign(Object.assign({}, hooks0.optionChangeHandlers), hooks1.optionChangeHandlers),
7946 scrollGridImpl: hooks1.scrollGridImpl || hooks0.scrollGridImpl,
7947 listenerRefiners: Object.assign(Object.assign({}, hooks0.listenerRefiners), hooks1.listenerRefiners),
7948 optionRefiners: Object.assign(Object.assign({}, hooks0.optionRefiners), hooks1.optionRefiners),
7949 propSetHandlers: Object.assign(Object.assign({}, hooks0.propSetHandlers), hooks1.propSetHandlers),
7950 };
7951 }
7952 function compareOptionalDates(date0, date1) {
7953 if (date0 === undefined) {
7954 return date1;
7955 }
7956 if (date1 === undefined) {
7957 return date0;
7958 }
7959 return new Date(Math.max(date0.valueOf(), date1.valueOf()));
7960 }
7961
7962 class StandardTheme extends Theme {
7963 }
7964 StandardTheme.prototype.classes = {
7965 root: 'fc-theme-standard',
7966 tableCellShaded: 'fc-cell-shaded',
7967 buttonGroup: 'fc-button-group',
7968 button: 'fc-button fc-button-primary',
7969 buttonActive: 'fc-button-active',
7970 };
7971 StandardTheme.prototype.baseIconClass = 'fc-icon';
7972 StandardTheme.prototype.iconClasses = {
7973 close: 'fc-icon-x',
7974 prev: 'fc-icon-chevron-left',
7975 next: 'fc-icon-chevron-right',
7976 prevYear: 'fc-icon-chevrons-left',
7977 nextYear: 'fc-icon-chevrons-right',
7978 };
7979 StandardTheme.prototype.rtlIconClasses = {
7980 prev: 'fc-icon-chevron-right',
7981 next: 'fc-icon-chevron-left',
7982 prevYear: 'fc-icon-chevrons-right',
7983 nextYear: 'fc-icon-chevrons-left',
7984 };
7985 StandardTheme.prototype.iconOverrideOption = 'buttonIcons'; // TODO: make TS-friendly
7986 StandardTheme.prototype.iconOverrideCustomButtonOption = 'icon';
7987 StandardTheme.prototype.iconOverridePrefix = 'fc-icon-';
7988
7989 function compileViewDefs(defaultConfigs, overrideConfigs) {
7990 let hash = {};
7991 let viewType;
7992 for (viewType in defaultConfigs) {
7993 ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs);
7994 }
7995 for (viewType in overrideConfigs) {
7996 ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs);
7997 }
7998 return hash;
7999 }
8000 function ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs) {
8001 if (hash[viewType]) {
8002 return hash[viewType];
8003 }
8004 let viewDef = buildViewDef(viewType, hash, defaultConfigs, overrideConfigs);
8005 if (viewDef) {
8006 hash[viewType] = viewDef;
8007 }
8008 return viewDef;
8009 }
8010 function buildViewDef(viewType, hash, defaultConfigs, overrideConfigs) {
8011 let defaultConfig = defaultConfigs[viewType];
8012 let overrideConfig = overrideConfigs[viewType];
8013 let queryProp = (name) => ((defaultConfig && defaultConfig[name] !== null) ? defaultConfig[name] :
8014 ((overrideConfig && overrideConfig[name] !== null) ? overrideConfig[name] : null));
8015 let theComponent = queryProp('component');
8016 let superType = queryProp('superType');
8017 let superDef = null;
8018 if (superType) {
8019 if (superType === viewType) {
8020 throw new Error('Can\'t have a custom view type that references itself');
8021 }
8022 superDef = ensureViewDef(superType, hash, defaultConfigs, overrideConfigs);
8023 }
8024 if (!theComponent && superDef) {
8025 theComponent = superDef.component;
8026 }
8027 if (!theComponent) {
8028 return null; // don't throw a warning, might be settings for a single-unit view
8029 }
8030 return {
8031 type: viewType,
8032 component: theComponent,
8033 defaults: Object.assign(Object.assign({}, (superDef ? superDef.defaults : {})), (defaultConfig ? defaultConfig.rawOptions : {})),
8034 overrides: Object.assign(Object.assign({}, (superDef ? superDef.overrides : {})), (overrideConfig ? overrideConfig.rawOptions : {})),
8035 };
8036 }
8037
8038 function parseViewConfigs(inputs) {
8039 return mapHash(inputs, parseViewConfig);
8040 }
8041 function parseViewConfig(input) {
8042 let rawOptions = typeof input === 'function' ?
8043 { component: input } :
8044 input;
8045 let { component } = rawOptions;
8046 if (rawOptions.content) {
8047 // TODO: remove content/classNames/didMount/etc from options?
8048 component = createViewHookComponent(rawOptions);
8049 }
8050 else if (component && !(component.prototype instanceof BaseComponent)) {
8051 // WHY?: people were using `component` property for `content`
8052 // TODO: converge on one setting name
8053 component = createViewHookComponent(Object.assign(Object.assign({}, rawOptions), { content: component }));
8054 }
8055 return {
8056 superType: rawOptions.type,
8057 component: component,
8058 rawOptions, // includes type and component too :(
8059 };
8060 }
8061 function createViewHookComponent(options) {
8062 return (viewProps) => (y(ViewContextType.Consumer, null, (context) => (y(ContentContainer, { elTag: "div", elClasses: buildViewClassNames(context.viewSpec), renderProps: Object.assign(Object.assign({}, viewProps), { nextDayThreshold: context.options.nextDayThreshold }), generatorName: undefined, customGenerator: options.content, classNameGenerator: options.classNames, didMount: options.didMount, willUnmount: options.willUnmount }))));
8063 }
8064
8065 function buildViewSpecs(defaultInputs, optionOverrides, dynamicOptionOverrides, localeDefaults) {
8066 let defaultConfigs = parseViewConfigs(defaultInputs);
8067 let overrideConfigs = parseViewConfigs(optionOverrides.views);
8068 let viewDefs = compileViewDefs(defaultConfigs, overrideConfigs);
8069 return mapHash(viewDefs, (viewDef) => buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults));
8070 }
8071 function buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults) {
8072 let durationInput = viewDef.overrides.duration ||
8073 viewDef.defaults.duration ||
8074 dynamicOptionOverrides.duration ||
8075 optionOverrides.duration;
8076 let duration = null;
8077 let durationUnit = '';
8078 let singleUnit = '';
8079 let singleUnitOverrides = {};
8080 if (durationInput) {
8081 duration = createDurationCached(durationInput);
8082 if (duration) { // valid?
8083 let denom = greatestDurationDenominator(duration);
8084 durationUnit = denom.unit;
8085 if (denom.value === 1) {
8086 singleUnit = durationUnit;
8087 singleUnitOverrides = overrideConfigs[durationUnit] ? overrideConfigs[durationUnit].rawOptions : {};
8088 }
8089 }
8090 }
8091 let queryButtonText = (optionsSubset) => {
8092 let buttonTextMap = optionsSubset.buttonText || {};
8093 let buttonTextKey = viewDef.defaults.buttonTextKey;
8094 if (buttonTextKey != null && buttonTextMap[buttonTextKey] != null) {
8095 return buttonTextMap[buttonTextKey];
8096 }
8097 if (buttonTextMap[viewDef.type] != null) {
8098 return buttonTextMap[viewDef.type];
8099 }
8100 if (buttonTextMap[singleUnit] != null) {
8101 return buttonTextMap[singleUnit];
8102 }
8103 return null;
8104 };
8105 let queryButtonTitle = (optionsSubset) => {
8106 let buttonHints = optionsSubset.buttonHints || {};
8107 let buttonKey = viewDef.defaults.buttonTextKey; // use same key as text
8108 if (buttonKey != null && buttonHints[buttonKey] != null) {
8109 return buttonHints[buttonKey];
8110 }
8111 if (buttonHints[viewDef.type] != null) {
8112 return buttonHints[viewDef.type];
8113 }
8114 if (buttonHints[singleUnit] != null) {
8115 return buttonHints[singleUnit];
8116 }
8117 return null;
8118 };
8119 return {
8120 type: viewDef.type,
8121 component: viewDef.component,
8122 duration,
8123 durationUnit,
8124 singleUnit,
8125 optionDefaults: viewDef.defaults,
8126 optionOverrides: Object.assign(Object.assign({}, singleUnitOverrides), viewDef.overrides),
8127 buttonTextOverride: queryButtonText(dynamicOptionOverrides) ||
8128 queryButtonText(optionOverrides) || // constructor-specified buttonText lookup hash takes precedence
8129 viewDef.overrides.buttonText,
8130 buttonTextDefault: queryButtonText(localeDefaults) ||
8131 viewDef.defaults.buttonText ||
8132 queryButtonText(BASE_OPTION_DEFAULTS) ||
8133 viewDef.type,
8134 // not DRY
8135 buttonTitleOverride: queryButtonTitle(dynamicOptionOverrides) ||
8136 queryButtonTitle(optionOverrides) ||
8137 viewDef.overrides.buttonHint,
8138 buttonTitleDefault: queryButtonTitle(localeDefaults) ||
8139 viewDef.defaults.buttonHint ||
8140 queryButtonTitle(BASE_OPTION_DEFAULTS),
8141 // will eventually fall back to buttonText
8142 };
8143 }
8144 // hack to get memoization working
8145 let durationInputMap = {};
8146 function createDurationCached(durationInput) {
8147 let json = JSON.stringify(durationInput);
8148 let res = durationInputMap[json];
8149 if (res === undefined) {
8150 res = createDuration(durationInput);
8151 durationInputMap[json] = res;
8152 }
8153 return res;
8154 }
8155
8156 function reduceViewType(viewType, action) {
8157 switch (action.type) {
8158 case 'CHANGE_VIEW_TYPE':
8159 viewType = action.viewType;
8160 }
8161 return viewType;
8162 }
8163
8164 function reduceCurrentDate(currentDate, action) {
8165 switch (action.type) {
8166 case 'CHANGE_DATE':
8167 return action.dateMarker;
8168 default:
8169 return currentDate;
8170 }
8171 }
8172 // should be initialized once and stay constant
8173 // this will change too
8174 function getInitialDate(options, dateEnv, nowManager) {
8175 let initialDateInput = options.initialDate;
8176 // compute the initial ambig-timezone date
8177 if (initialDateInput != null) {
8178 return dateEnv.createMarker(initialDateInput);
8179 }
8180 return nowManager.getDateMarker();
8181 }
8182
8183 function reduceDynamicOptionOverrides(dynamicOptionOverrides, action) {
8184 switch (action.type) {
8185 case 'SET_OPTION':
8186 return Object.assign(Object.assign({}, dynamicOptionOverrides), { [action.optionName]: action.rawOptionValue });
8187 default:
8188 return dynamicOptionOverrides;
8189 }
8190 }
8191
8192 function reduceDateProfile(currentDateProfile, action, currentDate, dateProfileGenerator) {
8193 let dp;
8194 switch (action.type) {
8195 case 'CHANGE_VIEW_TYPE':
8196 return dateProfileGenerator.build(action.dateMarker || currentDate);
8197 case 'CHANGE_DATE':
8198 return dateProfileGenerator.build(action.dateMarker);
8199 case 'PREV':
8200 dp = dateProfileGenerator.buildPrev(currentDateProfile, currentDate);
8201 if (dp.isValid) {
8202 return dp;
8203 }
8204 break;
8205 case 'NEXT':
8206 dp = dateProfileGenerator.buildNext(currentDateProfile, currentDate);
8207 if (dp.isValid) {
8208 return dp;
8209 }
8210 break;
8211 }
8212 return currentDateProfile;
8213 }
8214
8215 function initEventSources(calendarOptions, dateProfile, context) {
8216 let activeRange = dateProfile ? dateProfile.activeRange : null;
8217 return addSources({}, parseInitialSources(calendarOptions, context), activeRange, context);
8218 }
8219 function reduceEventSources(eventSources, action, dateProfile, context) {
8220 let activeRange = dateProfile ? dateProfile.activeRange : null; // need this check?
8221 switch (action.type) {
8222 case 'ADD_EVENT_SOURCES': // already parsed
8223 return addSources(eventSources, action.sources, activeRange, context);
8224 case 'REMOVE_EVENT_SOURCE':
8225 return removeSource(eventSources, action.sourceId);
8226 case 'PREV': // TODO: how do we track all actions that affect dateProfile :(
8227 case 'NEXT':
8228 case 'CHANGE_DATE':
8229 case 'CHANGE_VIEW_TYPE':
8230 if (dateProfile) {
8231 return fetchDirtySources(eventSources, activeRange, context);
8232 }
8233 return eventSources;
8234 case 'FETCH_EVENT_SOURCES':
8235 return fetchSourcesByIds(eventSources, action.sourceIds ? // why no type?
8236 arrayToHash(action.sourceIds) :
8237 excludeStaticSources(eventSources, context), activeRange, action.isRefetch || false, context);
8238 case 'RECEIVE_EVENTS':
8239 case 'RECEIVE_EVENT_ERROR':
8240 return receiveResponse(eventSources, action.sourceId, action.fetchId, action.fetchRange);
8241 case 'REMOVE_ALL_EVENT_SOURCES':
8242 return {};
8243 default:
8244 return eventSources;
8245 }
8246 }
8247 function reduceEventSourcesNewTimeZone(eventSources, dateProfile, context) {
8248 let activeRange = dateProfile ? dateProfile.activeRange : null; // need this check?
8249 return fetchSourcesByIds(eventSources, excludeStaticSources(eventSources, context), activeRange, true, context);
8250 }
8251 function computeEventSourcesLoading(eventSources) {
8252 for (let sourceId in eventSources) {
8253 if (eventSources[sourceId].isFetching) {
8254 return true;
8255 }
8256 }
8257 return false;
8258 }
8259 function addSources(eventSourceHash, sources, fetchRange, context) {
8260 let hash = {};
8261 for (let source of sources) {
8262 hash[source.sourceId] = source;
8263 }
8264 if (fetchRange) {
8265 hash = fetchDirtySources(hash, fetchRange, context);
8266 }
8267 return Object.assign(Object.assign({}, eventSourceHash), hash);
8268 }
8269 function removeSource(eventSourceHash, sourceId) {
8270 return filterHash(eventSourceHash, (eventSource) => eventSource.sourceId !== sourceId);
8271 }
8272 function fetchDirtySources(sourceHash, fetchRange, context) {
8273 return fetchSourcesByIds(sourceHash, filterHash(sourceHash, (eventSource) => isSourceDirty(eventSource, fetchRange, context)), fetchRange, false, context);
8274 }
8275 function isSourceDirty(eventSource, fetchRange, context) {
8276 if (!doesSourceNeedRange(eventSource, context)) {
8277 return !eventSource.latestFetchId;
8278 }
8279 return !context.options.lazyFetching ||
8280 !eventSource.fetchRange ||
8281 eventSource.isFetching || // always cancel outdated in-progress fetches
8282 fetchRange.start < eventSource.fetchRange.start ||
8283 fetchRange.end > eventSource.fetchRange.end;
8284 }
8285 function fetchSourcesByIds(prevSources, sourceIdHash, fetchRange, isRefetch, context) {
8286 let nextSources = {};
8287 for (let sourceId in prevSources) {
8288 let source = prevSources[sourceId];
8289 if (sourceIdHash[sourceId]) {
8290 nextSources[sourceId] = fetchSource(source, fetchRange, isRefetch, context);
8291 }
8292 else {
8293 nextSources[sourceId] = source;
8294 }
8295 }
8296 return nextSources;
8297 }
8298 function fetchSource(eventSource, fetchRange, isRefetch, context) {
8299 let { options, calendarApi } = context;
8300 let sourceDef = context.pluginHooks.eventSourceDefs[eventSource.sourceDefId];
8301 let fetchId = guid();
8302 sourceDef.fetch({
8303 eventSource,
8304 range: fetchRange,
8305 isRefetch,
8306 context,
8307 }, (res) => {
8308 let { rawEvents } = res;
8309 if (options.eventSourceSuccess) {
8310 rawEvents = options.eventSourceSuccess.call(calendarApi, rawEvents, res.response) || rawEvents;
8311 }
8312 if (eventSource.success) {
8313 rawEvents = eventSource.success.call(calendarApi, rawEvents, res.response) || rawEvents;
8314 }
8315 context.dispatch({
8316 type: 'RECEIVE_EVENTS',
8317 sourceId: eventSource.sourceId,
8318 fetchId,
8319 fetchRange,
8320 rawEvents,
8321 });
8322 }, (error) => {
8323 let errorHandled = false;
8324 if (options.eventSourceFailure) {
8325 options.eventSourceFailure.call(calendarApi, error);
8326 errorHandled = true;
8327 }
8328 if (eventSource.failure) {
8329 eventSource.failure(error);
8330 errorHandled = true;
8331 }
8332 if (!errorHandled) {
8333 console.warn(error.message, error);
8334 }
8335 context.dispatch({
8336 type: 'RECEIVE_EVENT_ERROR',
8337 sourceId: eventSource.sourceId,
8338 fetchId,
8339 fetchRange,
8340 error,
8341 });
8342 });
8343 return Object.assign(Object.assign({}, eventSource), { isFetching: true, latestFetchId: fetchId });
8344 }
8345 function receiveResponse(sourceHash, sourceId, fetchId, fetchRange) {
8346 let eventSource = sourceHash[sourceId];
8347 if (eventSource && // not already removed
8348 fetchId === eventSource.latestFetchId) {
8349 return Object.assign(Object.assign({}, sourceHash), { [sourceId]: Object.assign(Object.assign({}, eventSource), { isFetching: false, fetchRange }) });
8350 }
8351 return sourceHash;
8352 }
8353 function excludeStaticSources(eventSources, context) {
8354 return filterHash(eventSources, (eventSource) => doesSourceNeedRange(eventSource, context));
8355 }
8356 function parseInitialSources(rawOptions, context) {
8357 let refiners = buildEventSourceRefiners(context);
8358 let rawSources = [].concat(rawOptions.eventSources || []);
8359 let sources = []; // parsed
8360 if (rawOptions.initialEvents) {
8361 rawSources.unshift(rawOptions.initialEvents);
8362 }
8363 if (rawOptions.events) {
8364 rawSources.unshift(rawOptions.events);
8365 }
8366 for (let rawSource of rawSources) {
8367 let source = parseEventSource(rawSource, context, refiners);
8368 if (source) {
8369 sources.push(source);
8370 }
8371 }
8372 return sources;
8373 }
8374 function doesSourceNeedRange(eventSource, context) {
8375 let defs = context.pluginHooks.eventSourceDefs;
8376 return !defs[eventSource.sourceDefId].ignoreRange;
8377 }
8378
8379 function reduceDateSelection(currentSelection, action) {
8380 switch (action.type) {
8381 case 'UNSELECT_DATES':
8382 return null;
8383 case 'SELECT_DATES':
8384 return action.selection;
8385 default:
8386 return currentSelection;
8387 }
8388 }
8389
8390 function reduceSelectedEvent(currentInstanceId, action) {
8391 switch (action.type) {
8392 case 'UNSELECT_EVENT':
8393 return '';
8394 case 'SELECT_EVENT':
8395 return action.eventInstanceId;
8396 default:
8397 return currentInstanceId;
8398 }
8399 }
8400
8401 function reduceEventDrag(currentDrag, action) {
8402 let newDrag;
8403 switch (action.type) {
8404 case 'UNSET_EVENT_DRAG':
8405 return null;
8406 case 'SET_EVENT_DRAG':
8407 newDrag = action.state;
8408 return {
8409 affectedEvents: newDrag.affectedEvents,
8410 mutatedEvents: newDrag.mutatedEvents,
8411 isEvent: newDrag.isEvent,
8412 };
8413 default:
8414 return currentDrag;
8415 }
8416 }
8417
8418 function reduceEventResize(currentResize, action) {
8419 let newResize;
8420 switch (action.type) {
8421 case 'UNSET_EVENT_RESIZE':
8422 return null;
8423 case 'SET_EVENT_RESIZE':
8424 newResize = action.state;
8425 return {
8426 affectedEvents: newResize.affectedEvents,
8427 mutatedEvents: newResize.mutatedEvents,
8428 isEvent: newResize.isEvent,
8429 };
8430 default:
8431 return currentResize;
8432 }
8433 }
8434
8435 function parseToolbars(calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) {
8436 let header = calendarOptions.headerToolbar ? parseToolbar(calendarOptions.headerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) : null;
8437 let footer = calendarOptions.footerToolbar ? parseToolbar(calendarOptions.footerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) : null;
8438 return { header, footer };
8439 }
8440 function parseToolbar(sectionStrHash, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) {
8441 let sectionWidgets = {};
8442 let viewsWithButtons = [];
8443 let hasTitle = false;
8444 for (let sectionName in sectionStrHash) {
8445 let sectionStr = sectionStrHash[sectionName];
8446 let sectionRes = parseSection(sectionStr, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi);
8447 sectionWidgets[sectionName] = sectionRes.widgets;
8448 viewsWithButtons.push(...sectionRes.viewsWithButtons);
8449 hasTitle = hasTitle || sectionRes.hasTitle;
8450 }
8451 return { sectionWidgets, viewsWithButtons, hasTitle };
8452 }
8453 /*
8454 BAD: querying icons and text here. should be done at render time
8455 */
8456 function parseSection(sectionStr, calendarOptions, // defaults+overrides, then refined
8457 calendarOptionOverrides, // overrides only!, unrefined :(
8458 theme, viewSpecs, calendarApi) {
8459 let isRtl = calendarOptions.direction === 'rtl';
8460 let calendarCustomButtons = calendarOptions.customButtons || {};
8461 let calendarButtonTextOverrides = calendarOptionOverrides.buttonText || {};
8462 let calendarButtonText = calendarOptions.buttonText || {};
8463 let calendarButtonHintOverrides = calendarOptionOverrides.buttonHints || {};
8464 let calendarButtonHints = calendarOptions.buttonHints || {};
8465 let sectionSubstrs = sectionStr ? sectionStr.split(' ') : [];
8466 let viewsWithButtons = [];
8467 let hasTitle = false;
8468 let widgets = sectionSubstrs.map((buttonGroupStr) => (buttonGroupStr.split(',').map((buttonName) => {
8469 if (buttonName === 'title') {
8470 hasTitle = true;
8471 return { buttonName };
8472 }
8473 let customButtonProps;
8474 let viewSpec;
8475 let buttonClick;
8476 let buttonIcon; // only one of these will be set
8477 let buttonText; // "
8478 let buttonHint;
8479 // ^ for the title="" attribute, for accessibility
8480 if ((customButtonProps = calendarCustomButtons[buttonName])) {
8481 buttonClick = (ev) => {
8482 if (customButtonProps.click) {
8483 customButtonProps.click.call(ev.target, ev, ev.target); // TODO: use Calendar this context?
8484 }
8485 };
8486 (buttonIcon = theme.getCustomButtonIconClass(customButtonProps)) ||
8487 (buttonIcon = theme.getIconClass(buttonName, isRtl)) ||
8488 (buttonText = customButtonProps.text);
8489 buttonHint = customButtonProps.hint || customButtonProps.text;
8490 }
8491 else if ((viewSpec = viewSpecs[buttonName])) {
8492 viewsWithButtons.push(buttonName);
8493 buttonClick = () => {
8494 calendarApi.changeView(buttonName);
8495 };
8496 (buttonText = viewSpec.buttonTextOverride) ||
8497 (buttonIcon = theme.getIconClass(buttonName, isRtl)) ||
8498 (buttonText = viewSpec.buttonTextDefault);
8499 let textFallback = viewSpec.buttonTextOverride ||
8500 viewSpec.buttonTextDefault;
8501 buttonHint = formatWithOrdinals(viewSpec.buttonTitleOverride ||
8502 viewSpec.buttonTitleDefault ||
8503 calendarOptions.viewHint, [textFallback, buttonName], // view-name = buttonName
8504 textFallback);
8505 }
8506 else if (calendarApi[buttonName]) { // a calendarApi method
8507 buttonClick = () => {
8508 calendarApi[buttonName]();
8509 };
8510 (buttonText = calendarButtonTextOverrides[buttonName]) ||
8511 (buttonIcon = theme.getIconClass(buttonName, isRtl)) ||
8512 (buttonText = calendarButtonText[buttonName]); // everything else is considered default
8513 if (buttonName === 'prevYear' || buttonName === 'nextYear') {
8514 let prevOrNext = buttonName === 'prevYear' ? 'prev' : 'next';
8515 buttonHint = formatWithOrdinals(calendarButtonHintOverrides[prevOrNext] ||
8516 calendarButtonHints[prevOrNext], [
8517 calendarButtonText.year || 'year',
8518 'year',
8519 ], calendarButtonText[buttonName]);
8520 }
8521 else {
8522 buttonHint = (navUnit) => formatWithOrdinals(calendarButtonHintOverrides[buttonName] ||
8523 calendarButtonHints[buttonName], [
8524 calendarButtonText[navUnit] || navUnit,
8525 navUnit,
8526 ], calendarButtonText[buttonName]);
8527 }
8528 }
8529 return { buttonName, buttonClick, buttonIcon, buttonText, buttonHint };
8530 })));
8531 return { widgets, viewsWithButtons, hasTitle };
8532 }
8533
8534 // always represents the current view. otherwise, it'd need to change value every time date changes
8535 class ViewImpl {
8536 constructor(type, getCurrentData, dateEnv) {
8537 this.type = type;
8538 this.getCurrentData = getCurrentData;
8539 this.dateEnv = dateEnv;
8540 }
8541 get calendar() {
8542 return this.getCurrentData().calendarApi;
8543 }
8544 get title() {
8545 return this.getCurrentData().viewTitle;
8546 }
8547 get activeStart() {
8548 return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.start);
8549 }
8550 get activeEnd() {
8551 return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.end);
8552 }
8553 get currentStart() {
8554 return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.start);
8555 }
8556 get currentEnd() {
8557 return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.end);
8558 }
8559 getOption(name) {
8560 return this.getCurrentData().options[name]; // are the view-specific options
8561 }
8562 }
8563
8564 let eventSourceDef$2 = {
8565 ignoreRange: true,
8566 parseMeta(refined) {
8567 if (Array.isArray(refined.events)) {
8568 return refined.events;
8569 }
8570 return null;
8571 },
8572 fetch(arg, successCallback) {
8573 successCallback({
8574 rawEvents: arg.eventSource.meta,
8575 });
8576 },
8577 };
8578 const arrayEventSourcePlugin = createPlugin({
8579 name: 'array-event-source',
8580 eventSourceDefs: [eventSourceDef$2],
8581 });
8582
8583 let eventSourceDef$1 = {
8584 parseMeta(refined) {
8585 if (typeof refined.events === 'function') {
8586 return refined.events;
8587 }
8588 return null;
8589 },
8590 fetch(arg, successCallback, errorCallback) {
8591 const { dateEnv } = arg.context;
8592 const func = arg.eventSource.meta;
8593 unpromisify(func.bind(null, buildRangeApiWithTimeZone(arg.range, dateEnv)), (rawEvents) => successCallback({ rawEvents }), errorCallback);
8594 },
8595 };
8596 const funcEventSourcePlugin = createPlugin({
8597 name: 'func-event-source',
8598 eventSourceDefs: [eventSourceDef$1],
8599 });
8600
8601 const JSON_FEED_EVENT_SOURCE_REFINERS = {
8602 method: String,
8603 extraParams: identity,
8604 startParam: String,
8605 endParam: String,
8606 timeZoneParam: String,
8607 };
8608
8609 let eventSourceDef = {
8610 parseMeta(refined) {
8611 if (refined.url && (refined.format === 'json' || !refined.format)) {
8612 return {
8613 url: refined.url,
8614 format: 'json',
8615 method: (refined.method || 'GET').toUpperCase(),
8616 extraParams: refined.extraParams,
8617 startParam: refined.startParam,
8618 endParam: refined.endParam,
8619 timeZoneParam: refined.timeZoneParam,
8620 };
8621 }
8622 return null;
8623 },
8624 fetch(arg, successCallback, errorCallback) {
8625 const { meta } = arg.eventSource;
8626 const requestParams = buildRequestParams(meta, arg.range, arg.context);
8627 requestJson(meta.method, meta.url, requestParams).then(([rawEvents, response]) => {
8628 successCallback({ rawEvents, response });
8629 }, errorCallback);
8630 },
8631 };
8632 const jsonFeedEventSourcePlugin = createPlugin({
8633 name: 'json-event-source',
8634 eventSourceRefiners: JSON_FEED_EVENT_SOURCE_REFINERS,
8635 eventSourceDefs: [eventSourceDef],
8636 });
8637 function buildRequestParams(meta, range, context) {
8638 let { dateEnv, options } = context;
8639 let startParam;
8640 let endParam;
8641 let timeZoneParam;
8642 let customRequestParams;
8643 let params = {};
8644 startParam = meta.startParam;
8645 if (startParam == null) {
8646 startParam = options.startParam;
8647 }
8648 endParam = meta.endParam;
8649 if (endParam == null) {
8650 endParam = options.endParam;
8651 }
8652 timeZoneParam = meta.timeZoneParam;
8653 if (timeZoneParam == null) {
8654 timeZoneParam = options.timeZoneParam;
8655 }
8656 // retrieve any outbound GET/POST data from the options
8657 if (typeof meta.extraParams === 'function') {
8658 // supplied as a function that returns a key/value object
8659 customRequestParams = meta.extraParams();
8660 }
8661 else {
8662 // probably supplied as a straight key/value object
8663 customRequestParams = meta.extraParams || {};
8664 }
8665 Object.assign(params, customRequestParams);
8666 params[startParam] = dateEnv.formatIso(range.start);
8667 params[endParam] = dateEnv.formatIso(range.end);
8668 if (dateEnv.timeZone !== 'local') {
8669 params[timeZoneParam] = dateEnv.timeZone;
8670 }
8671 return params;
8672 }
8673
8674 const SIMPLE_RECURRING_REFINERS = {
8675 daysOfWeek: identity,
8676 startTime: createDuration,
8677 endTime: createDuration,
8678 duration: createDuration,
8679 startRecur: identity,
8680 endRecur: identity,
8681 };
8682
8683 let recurring = {
8684 parse(refined, dateEnv) {
8685 if (refined.daysOfWeek || refined.startTime || refined.endTime || refined.startRecur || refined.endRecur) {
8686 let recurringData = {
8687 daysOfWeek: refined.daysOfWeek || null,
8688 startTime: refined.startTime || null,
8689 endTime: refined.endTime || null,
8690 startRecur: refined.startRecur ? dateEnv.createMarker(refined.startRecur) : null,
8691 endRecur: refined.endRecur ? dateEnv.createMarker(refined.endRecur) : null,
8692 dateEnv,
8693 };
8694 let duration;
8695 if (refined.duration) {
8696 duration = refined.duration;
8697 }
8698 if (!duration && refined.startTime && refined.endTime) {
8699 duration = subtractDurations(refined.endTime, refined.startTime);
8700 }
8701 return {
8702 allDayGuess: Boolean(!refined.startTime && !refined.endTime),
8703 duration,
8704 typeData: recurringData, // doesn't need endTime anymore but oh well
8705 };
8706 }
8707 return null;
8708 },
8709 expand(typeData, framingRange, dateEnv) {
8710 let clippedFramingRange = intersectRanges(framingRange, { start: typeData.startRecur, end: typeData.endRecur });
8711 if (clippedFramingRange) {
8712 return expandRanges(typeData.daysOfWeek, typeData.startTime, typeData.dateEnv, dateEnv, clippedFramingRange);
8713 }
8714 return [];
8715 },
8716 };
8717 const simpleRecurringEventsPlugin = createPlugin({
8718 name: 'simple-recurring-event',
8719 recurringTypes: [recurring],
8720 eventRefiners: SIMPLE_RECURRING_REFINERS,
8721 });
8722 function expandRanges(daysOfWeek, startTime, eventDateEnv, calendarDateEnv, framingRange) {
8723 let dowHash = daysOfWeek ? arrayToHash(daysOfWeek) : null;
8724 let dayMarker = startOfDay(framingRange.start);
8725 let endMarker = framingRange.end;
8726 let instanceStarts = [];
8727 while (dayMarker < endMarker) {
8728 let instanceStart;
8729 // if everyday, or this particular day-of-week
8730 if (!dowHash || dowHash[dayMarker.getUTCDay()]) {
8731 if (startTime) {
8732 instanceStart = calendarDateEnv.add(dayMarker, startTime);
8733 }
8734 else {
8735 instanceStart = dayMarker;
8736 }
8737 instanceStarts.push(calendarDateEnv.createMarker(eventDateEnv.toDate(instanceStart)));
8738 }
8739 dayMarker = addDays(dayMarker, 1);
8740 }
8741 return instanceStarts;
8742 }
8743
8744 const changeHandlerPlugin = createPlugin({
8745 name: 'change-handler',
8746 optionChangeHandlers: {
8747 events(events, context) {
8748 handleEventSources([events], context);
8749 },
8750 eventSources: handleEventSources,
8751 },
8752 });
8753 /*
8754 BUG: if `event` was supplied, all previously-given `eventSources` will be wiped out
8755 */
8756 function handleEventSources(inputs, context) {
8757 let unfoundSources = hashValuesToArray(context.getCurrentData().eventSources);
8758 if (unfoundSources.length === 1 &&
8759 inputs.length === 1 &&
8760 Array.isArray(unfoundSources[0]._raw) &&
8761 Array.isArray(inputs[0])) {
8762 context.dispatch({
8763 type: 'RESET_RAW_EVENTS',
8764 sourceId: unfoundSources[0].sourceId,
8765 rawEvents: inputs[0],
8766 });
8767 return;
8768 }
8769 let newInputs = [];
8770 for (let input of inputs) {
8771 let inputFound = false;
8772 for (let i = 0; i < unfoundSources.length; i += 1) {
8773 if (unfoundSources[i]._raw === input) {
8774 unfoundSources.splice(i, 1); // delete
8775 inputFound = true;
8776 break;
8777 }
8778 }
8779 if (!inputFound) {
8780 newInputs.push(input);
8781 }
8782 }
8783 for (let unfoundSource of unfoundSources) {
8784 context.dispatch({
8785 type: 'REMOVE_EVENT_SOURCE',
8786 sourceId: unfoundSource.sourceId,
8787 });
8788 }
8789 for (let newInput of newInputs) {
8790 context.calendarApi.addEventSource(newInput);
8791 }
8792 }
8793
8794 function handleDateProfile(dateProfile, context) {
8795 context.emitter.trigger('datesSet', Object.assign(Object.assign({}, buildRangeApiWithTimeZone(dateProfile.activeRange, context.dateEnv)), { view: context.viewApi }));
8796 }
8797
8798 function handleEventStore(eventStore, context) {
8799 let { emitter } = context;
8800 if (emitter.hasHandlers('eventsSet')) {
8801 emitter.trigger('eventsSet', buildEventApis(eventStore, context));
8802 }
8803 }
8804
8805 /*
8806 this array is exposed on the root namespace so that UMD plugins can add to it.
8807 see the rollup-bundles script.
8808 */
8809 const globalPlugins = [
8810 arrayEventSourcePlugin,
8811 funcEventSourcePlugin,
8812 jsonFeedEventSourcePlugin,
8813 simpleRecurringEventsPlugin,
8814 changeHandlerPlugin,
8815 createPlugin({
8816 name: 'misc',
8817 isLoadingFuncs: [
8818 (state) => computeEventSourcesLoading(state.eventSources),
8819 ],
8820 propSetHandlers: {
8821 dateProfile: handleDateProfile,
8822 eventStore: handleEventStore,
8823 },
8824 }),
8825 ];
8826
8827 class TaskRunner {
8828 constructor(runTaskOption, drainedOption) {
8829 this.runTaskOption = runTaskOption;
8830 this.drainedOption = drainedOption;
8831 this.queue = [];
8832 this.delayedRunner = new DelayedRunner(this.drain.bind(this));
8833 }
8834 request(task, delay) {
8835 this.queue.push(task);
8836 this.delayedRunner.request(delay);
8837 }
8838 pause(scope) {
8839 this.delayedRunner.pause(scope);
8840 }
8841 resume(scope, force) {
8842 this.delayedRunner.resume(scope, force);
8843 }
8844 drain() {
8845 let { queue } = this;
8846 while (queue.length) {
8847 let completedTasks = [];
8848 let task;
8849 while ((task = queue.shift())) {
8850 this.runTask(task);
8851 completedTasks.push(task);
8852 }
8853 this.drained(completedTasks);
8854 } // keep going, in case new tasks were added in the drained handler
8855 }
8856 runTask(task) {
8857 if (this.runTaskOption) {
8858 this.runTaskOption(task);
8859 }
8860 }
8861 drained(completedTasks) {
8862 if (this.drainedOption) {
8863 this.drainedOption(completedTasks);
8864 }
8865 }
8866 }
8867
8868 // Computes what the title at the top of the calendarApi should be for this view
8869 function buildTitle(dateProfile, viewOptions, dateEnv) {
8870 let range;
8871 // for views that span a large unit of time, show the proper interval, ignoring stray days before and after
8872 if (/^(year|month)$/.test(dateProfile.currentRangeUnit)) {
8873 range = dateProfile.currentRange;
8874 }
8875 else { // for day units or smaller, use the actual day range
8876 range = dateProfile.activeRange;
8877 }
8878 return dateEnv.formatRange(range.start, range.end, createFormatter(viewOptions.titleFormat || buildTitleFormat(dateProfile)), {
8879 isEndExclusive: dateProfile.isRangeAllDay,
8880 defaultSeparator: viewOptions.titleRangeSeparator,
8881 });
8882 }
8883 // Generates the format string that should be used to generate the title for the current date range.
8884 // Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`.
8885 function buildTitleFormat(dateProfile) {
8886 let { currentRangeUnit } = dateProfile;
8887 if (currentRangeUnit === 'year') {
8888 return { year: 'numeric' };
8889 }
8890 if (currentRangeUnit === 'month') {
8891 return { year: 'numeric', month: 'long' }; // like "September 2014"
8892 }
8893 let days = diffWholeDays(dateProfile.currentRange.start, dateProfile.currentRange.end);
8894 if (days !== null && days > 1) {
8895 // multi-day range. shorter, like "Sep 9 - 10 2014"
8896 return { year: 'numeric', month: 'short', day: 'numeric' };
8897 }
8898 // one day. longer, like "September 9 2014"
8899 return { year: 'numeric', month: 'long', day: 'numeric' };
8900 }
8901
8902 /*
8903 TODO: test switching timezones when NO timezone plugin
8904 */
8905 class CalendarNowManager {
8906 constructor() {
8907 this.resetListeners = new Set();
8908 }
8909 handleInput(dateEnv, // will change if timezone setup changed
8910 nowInput) {
8911 const oldDateEnv = this.dateEnv;
8912 if (dateEnv !== oldDateEnv) {
8913 if (typeof nowInput === 'function') {
8914 this.nowFn = nowInput;
8915 }
8916 else if (!oldDateEnv) { // first time?
8917 this.nowAnchorDate = dateEnv.toDate(nowInput
8918 ? dateEnv.createMarker(nowInput)
8919 : dateEnv.createNowMarker());
8920 this.nowAnchorQueried = Date.now();
8921 }
8922 this.dateEnv = dateEnv;
8923 // not first time? fire reset handlers
8924 if (oldDateEnv) {
8925 for (const resetListener of this.resetListeners.values()) {
8926 resetListener();
8927 }
8928 }
8929 }
8930 }
8931 getDateMarker() {
8932 return this.nowAnchorDate
8933 ? this.dateEnv.timestampToMarker(this.nowAnchorDate.valueOf() +
8934 (Date.now() - this.nowAnchorQueried))
8935 : this.dateEnv.createMarker(this.nowFn());
8936 }
8937 addResetListener(handler) {
8938 this.resetListeners.add(handler);
8939 }
8940 removeResetListener(handler) {
8941 this.resetListeners.delete(handler);
8942 }
8943 }
8944
8945 // in future refactor, do the redux-style function(state=initial) for initial-state
8946 // also, whatever is happening in constructor, have it happen in action queue too
8947 class CalendarDataManager {
8948 constructor(props) {
8949 this.computeCurrentViewData = memoize(this._computeCurrentViewData);
8950 this.organizeRawLocales = memoize(organizeRawLocales);
8951 this.buildLocale = memoize(buildLocale);
8952 this.buildPluginHooks = buildBuildPluginHooks();
8953 this.buildDateEnv = memoize(buildDateEnv$1);
8954 this.buildTheme = memoize(buildTheme);
8955 this.parseToolbars = memoize(parseToolbars);
8956 this.buildViewSpecs = memoize(buildViewSpecs);
8957 this.buildDateProfileGenerator = memoizeObjArg(buildDateProfileGenerator);
8958 this.buildViewApi = memoize(buildViewApi);
8959 this.buildViewUiProps = memoizeObjArg(buildViewUiProps);
8960 this.buildEventUiBySource = memoize(buildEventUiBySource, isPropsEqual);
8961 this.buildEventUiBases = memoize(buildEventUiBases);
8962 this.parseContextBusinessHours = memoizeObjArg(parseContextBusinessHours);
8963 this.buildTitle = memoize(buildTitle);
8964 this.nowManager = new CalendarNowManager();
8965 this.emitter = new Emitter();
8966 this.actionRunner = new TaskRunner(this._handleAction.bind(this), this.updateData.bind(this));
8967 this.currentCalendarOptionsInput = {};
8968 this.currentCalendarOptionsRefined = {};
8969 this.currentViewOptionsInput = {};
8970 this.currentViewOptionsRefined = {};
8971 this.currentCalendarOptionsRefiners = {};
8972 this.optionsForRefining = [];
8973 this.optionsForHandling = [];
8974 this.getCurrentData = () => this.data;
8975 this.dispatch = (action) => {
8976 this.actionRunner.request(action); // protects against recursive calls to _handleAction
8977 };
8978 this.props = props;
8979 this.actionRunner.pause();
8980 this.nowManager = new CalendarNowManager();
8981 let dynamicOptionOverrides = {};
8982 let optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi);
8983 let currentViewType = optionsData.calendarOptions.initialView || optionsData.pluginHooks.initialView;
8984 let currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides);
8985 // wire things up
8986 // TODO: not DRY
8987 props.calendarApi.currentDataManager = this;
8988 this.emitter.setThisContext(props.calendarApi);
8989 this.emitter.setOptions(currentViewData.options);
8990 let calendarContext = {
8991 nowManager: this.nowManager,
8992 dateEnv: optionsData.dateEnv,
8993 options: optionsData.calendarOptions,
8994 pluginHooks: optionsData.pluginHooks,
8995 calendarApi: props.calendarApi,
8996 dispatch: this.dispatch,
8997 emitter: this.emitter,
8998 getCurrentData: this.getCurrentData,
8999 };
9000 let currentDate = getInitialDate(optionsData.calendarOptions, optionsData.dateEnv, this.nowManager);
9001 let dateProfile = currentViewData.dateProfileGenerator.build(currentDate);
9002 if (!rangeContainsMarker(dateProfile.activeRange, currentDate)) {
9003 currentDate = dateProfile.currentRange.start;
9004 }
9005 // needs to be after setThisContext
9006 for (let callback of optionsData.pluginHooks.contextInit) {
9007 callback(calendarContext);
9008 }
9009 // NOT DRY
9010 let eventSources = initEventSources(optionsData.calendarOptions, dateProfile, calendarContext);
9011 let initialState = {
9012 dynamicOptionOverrides,
9013 currentViewType,
9014 currentDate,
9015 dateProfile,
9016 businessHours: this.parseContextBusinessHours(calendarContext),
9017 eventSources,
9018 eventUiBases: {},
9019 eventStore: createEmptyEventStore(),
9020 renderableEventStore: createEmptyEventStore(),
9021 dateSelection: null,
9022 eventSelection: '',
9023 eventDrag: null,
9024 eventResize: null,
9025 selectionConfig: this.buildViewUiProps(calendarContext).selectionConfig,
9026 };
9027 let contextAndState = Object.assign(Object.assign({}, calendarContext), initialState);
9028 for (let reducer of optionsData.pluginHooks.reducers) {
9029 Object.assign(initialState, reducer(null, null, contextAndState));
9030 }
9031 if (computeIsLoading(initialState, calendarContext)) {
9032 this.emitter.trigger('loading', true); // NOT DRY
9033 }
9034 this.state = initialState;
9035 this.updateData();
9036 this.actionRunner.resume();
9037 }
9038 resetOptions(optionOverrides, changedOptionNames) {
9039 let { props } = this;
9040 if (changedOptionNames === undefined) {
9041 props.optionOverrides = optionOverrides;
9042 }
9043 else {
9044 props.optionOverrides = Object.assign(Object.assign({}, (props.optionOverrides || {})), optionOverrides);
9045 this.optionsForRefining.push(...changedOptionNames);
9046 }
9047 if (changedOptionNames === undefined || changedOptionNames.length) {
9048 this.actionRunner.request({
9049 type: 'NOTHING',
9050 });
9051 }
9052 }
9053 _handleAction(action) {
9054 let { props, state, emitter } = this;
9055 let dynamicOptionOverrides = reduceDynamicOptionOverrides(state.dynamicOptionOverrides, action);
9056 let optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi);
9057 let currentViewType = reduceViewType(state.currentViewType, action);
9058 let currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides);
9059 // wire things up
9060 // TODO: not DRY
9061 props.calendarApi.currentDataManager = this;
9062 emitter.setThisContext(props.calendarApi);
9063 emitter.setOptions(currentViewData.options);
9064 let calendarContext = {
9065 nowManager: this.nowManager,
9066 dateEnv: optionsData.dateEnv,
9067 options: optionsData.calendarOptions,
9068 pluginHooks: optionsData.pluginHooks,
9069 calendarApi: props.calendarApi,
9070 dispatch: this.dispatch,
9071 emitter,
9072 getCurrentData: this.getCurrentData,
9073 };
9074 let { currentDate, dateProfile } = state;
9075 if (this.data && this.data.dateProfileGenerator !== currentViewData.dateProfileGenerator) { // hack
9076 dateProfile = currentViewData.dateProfileGenerator.build(currentDate);
9077 }
9078 currentDate = reduceCurrentDate(currentDate, action);
9079 dateProfile = reduceDateProfile(dateProfile, action, currentDate, currentViewData.dateProfileGenerator);
9080 if (action.type === 'PREV' || // TODO: move this logic into DateProfileGenerator
9081 action.type === 'NEXT' || // "
9082 !rangeContainsMarker(dateProfile.currentRange, currentDate)) {
9083 currentDate = dateProfile.currentRange.start;
9084 }
9085 let eventSources = reduceEventSources(state.eventSources, action, dateProfile, calendarContext);
9086 let eventStore = reduceEventStore(state.eventStore, action, eventSources, dateProfile, calendarContext);
9087 let isEventsLoading = computeEventSourcesLoading(eventSources); // BAD. also called in this func in computeIsLoading
9088 let renderableEventStore = (isEventsLoading && !currentViewData.options.progressiveEventRendering) ?
9089 (state.renderableEventStore || eventStore) : // try from previous state
9090 eventStore;
9091 let { eventUiSingleBase, selectionConfig } = this.buildViewUiProps(calendarContext); // will memoize obj
9092 let eventUiBySource = this.buildEventUiBySource(eventSources);
9093 let eventUiBases = this.buildEventUiBases(renderableEventStore.defs, eventUiSingleBase, eventUiBySource);
9094 let newState = {
9095 dynamicOptionOverrides,
9096 currentViewType,
9097 currentDate,
9098 dateProfile,
9099 eventSources,
9100 eventStore,
9101 renderableEventStore,
9102 selectionConfig,
9103 eventUiBases,
9104 businessHours: this.parseContextBusinessHours(calendarContext),
9105 dateSelection: reduceDateSelection(state.dateSelection, action),
9106 eventSelection: reduceSelectedEvent(state.eventSelection, action),
9107 eventDrag: reduceEventDrag(state.eventDrag, action),
9108 eventResize: reduceEventResize(state.eventResize, action),
9109 };
9110 let contextAndState = Object.assign(Object.assign({}, calendarContext), newState);
9111 for (let reducer of optionsData.pluginHooks.reducers) {
9112 Object.assign(newState, reducer(state, action, contextAndState)); // give the OLD state, for old value
9113 }
9114 let wasLoading = computeIsLoading(state, calendarContext);
9115 let isLoading = computeIsLoading(newState, calendarContext);
9116 // TODO: use propSetHandlers in plugin system
9117 if (!wasLoading && isLoading) {
9118 emitter.trigger('loading', true);
9119 }
9120 else if (wasLoading && !isLoading) {
9121 emitter.trigger('loading', false);
9122 }
9123 this.state = newState;
9124 if (props.onAction) {
9125 props.onAction(action);
9126 }
9127 }
9128 updateData() {
9129 let { props, state } = this;
9130 let oldData = this.data;
9131 let optionsData = this.computeOptionsData(props.optionOverrides, state.dynamicOptionOverrides, props.calendarApi);
9132 let currentViewData = this.computeCurrentViewData(state.currentViewType, optionsData, props.optionOverrides, state.dynamicOptionOverrides);
9133 let data = this.data = Object.assign(Object.assign(Object.assign({ nowManager: this.nowManager, viewTitle: this.buildTitle(state.dateProfile, currentViewData.options, optionsData.dateEnv), calendarApi: props.calendarApi, dispatch: this.dispatch, emitter: this.emitter, getCurrentData: this.getCurrentData }, optionsData), currentViewData), state);
9134 let changeHandlers = optionsData.pluginHooks.optionChangeHandlers;
9135 let oldCalendarOptions = oldData && oldData.calendarOptions;
9136 let newCalendarOptions = optionsData.calendarOptions;
9137 if (oldCalendarOptions && oldCalendarOptions !== newCalendarOptions) {
9138 if (oldCalendarOptions.timeZone !== newCalendarOptions.timeZone) {
9139 // hack
9140 state.eventSources = data.eventSources = reduceEventSourcesNewTimeZone(data.eventSources, state.dateProfile, data);
9141 state.eventStore = data.eventStore = rezoneEventStoreDates(data.eventStore, oldData.dateEnv, data.dateEnv);
9142 state.renderableEventStore = data.renderableEventStore = rezoneEventStoreDates(data.renderableEventStore, oldData.dateEnv, data.dateEnv);
9143 }
9144 for (let optionName in changeHandlers) {
9145 if (this.optionsForHandling.indexOf(optionName) !== -1 ||
9146 oldCalendarOptions[optionName] !== newCalendarOptions[optionName]) {
9147 changeHandlers[optionName](newCalendarOptions[optionName], data);
9148 }
9149 }
9150 }
9151 this.optionsForHandling = [];
9152 if (props.onData) {
9153 props.onData(data);
9154 }
9155 }
9156 computeOptionsData(optionOverrides, dynamicOptionOverrides, calendarApi) {
9157 // TODO: blacklist options that are handled by optionChangeHandlers
9158 if (!this.optionsForRefining.length &&
9159 optionOverrides === this.stableOptionOverrides &&
9160 dynamicOptionOverrides === this.stableDynamicOptionOverrides) {
9161 return this.stableCalendarOptionsData;
9162 }
9163 let { refinedOptions, pluginHooks, localeDefaults, availableLocaleData, extra, } = this.processRawCalendarOptions(optionOverrides, dynamicOptionOverrides);
9164 warnUnknownOptions(extra);
9165 let dateEnv = this.buildDateEnv(refinedOptions.timeZone, refinedOptions.locale, refinedOptions.weekNumberCalculation, refinedOptions.firstDay, refinedOptions.weekText, pluginHooks, availableLocaleData, refinedOptions.defaultRangeSeparator);
9166 let viewSpecs = this.buildViewSpecs(pluginHooks.views, this.stableOptionOverrides, this.stableDynamicOptionOverrides, localeDefaults);
9167 let theme = this.buildTheme(refinedOptions, pluginHooks);
9168 let toolbarConfig = this.parseToolbars(refinedOptions, this.stableOptionOverrides, theme, viewSpecs, calendarApi);
9169 return this.stableCalendarOptionsData = {
9170 calendarOptions: refinedOptions,
9171 pluginHooks,
9172 dateEnv,
9173 viewSpecs,
9174 theme,
9175 toolbarConfig,
9176 localeDefaults,
9177 availableRawLocales: availableLocaleData.map,
9178 };
9179 }
9180 // always called from behind a memoizer
9181 processRawCalendarOptions(optionOverrides, dynamicOptionOverrides) {
9182 let { locales, locale } = mergeRawOptions([
9183 BASE_OPTION_DEFAULTS,
9184 optionOverrides,
9185 dynamicOptionOverrides,
9186 ]);
9187 let availableLocaleData = this.organizeRawLocales(locales);
9188 let availableRawLocales = availableLocaleData.map;
9189 let localeDefaults = this.buildLocale(locale || availableLocaleData.defaultCode, availableRawLocales).options;
9190 let pluginHooks = this.buildPluginHooks(optionOverrides.plugins || [], globalPlugins);
9191 let refiners = this.currentCalendarOptionsRefiners = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, BASE_OPTION_REFINERS), CALENDAR_LISTENER_REFINERS), CALENDAR_OPTION_REFINERS), pluginHooks.listenerRefiners), pluginHooks.optionRefiners);
9192 let extra = {};
9193 let raw = mergeRawOptions([
9194 BASE_OPTION_DEFAULTS,
9195 localeDefaults,
9196 optionOverrides,
9197 dynamicOptionOverrides,
9198 ]);
9199 let refined = {};
9200 let currentRaw = this.currentCalendarOptionsInput;
9201 let currentRefined = this.currentCalendarOptionsRefined;
9202 let anyChanges = false;
9203 for (let optionName in raw) {
9204 if (this.optionsForRefining.indexOf(optionName) === -1 && (raw[optionName] === currentRaw[optionName] || (COMPLEX_OPTION_COMPARATORS[optionName] &&
9205 (optionName in currentRaw) &&
9206 COMPLEX_OPTION_COMPARATORS[optionName](currentRaw[optionName], raw[optionName])))) {
9207 refined[optionName] = currentRefined[optionName];
9208 }
9209 else if (refiners[optionName]) {
9210 refined[optionName] = refiners[optionName](raw[optionName]);
9211 anyChanges = true;
9212 }
9213 else {
9214 extra[optionName] = currentRaw[optionName];
9215 }
9216 }
9217 if (anyChanges) {
9218 this.currentCalendarOptionsInput = raw;
9219 this.currentCalendarOptionsRefined = refined;
9220 this.stableOptionOverrides = optionOverrides;
9221 this.stableDynamicOptionOverrides = dynamicOptionOverrides;
9222 }
9223 this.optionsForHandling.push(...this.optionsForRefining);
9224 this.optionsForRefining = [];
9225 return {
9226 rawOptions: this.currentCalendarOptionsInput,
9227 refinedOptions: this.currentCalendarOptionsRefined,
9228 pluginHooks,
9229 availableLocaleData,
9230 localeDefaults,
9231 extra,
9232 };
9233 }
9234 _computeCurrentViewData(viewType, optionsData, optionOverrides, dynamicOptionOverrides) {
9235 let viewSpec = optionsData.viewSpecs[viewType];
9236 if (!viewSpec) {
9237 throw new Error(`viewType "${viewType}" is not available. Please make sure you've loaded all neccessary plugins`);
9238 }
9239 let { refinedOptions, extra } = this.processRawViewOptions(viewSpec, optionsData.pluginHooks, optionsData.localeDefaults, optionOverrides, dynamicOptionOverrides);
9240 warnUnknownOptions(extra);
9241 this.nowManager.handleInput(optionsData.dateEnv, refinedOptions.now);
9242 let dateProfileGenerator = this.buildDateProfileGenerator({
9243 dateProfileGeneratorClass: viewSpec.optionDefaults.dateProfileGeneratorClass,
9244 nowManager: this.nowManager,
9245 duration: viewSpec.duration,
9246 durationUnit: viewSpec.durationUnit,
9247 usesMinMaxTime: viewSpec.optionDefaults.usesMinMaxTime,
9248 dateEnv: optionsData.dateEnv,
9249 calendarApi: this.props.calendarApi,
9250 slotMinTime: refinedOptions.slotMinTime,
9251 slotMaxTime: refinedOptions.slotMaxTime,
9252 showNonCurrentDates: refinedOptions.showNonCurrentDates,
9253 dayCount: refinedOptions.dayCount,
9254 dateAlignment: refinedOptions.dateAlignment,
9255 dateIncrement: refinedOptions.dateIncrement,
9256 hiddenDays: refinedOptions.hiddenDays,
9257 weekends: refinedOptions.weekends,
9258 validRangeInput: refinedOptions.validRange,
9259 visibleRangeInput: refinedOptions.visibleRange,
9260 fixedWeekCount: refinedOptions.fixedWeekCount,
9261 });
9262 let viewApi = this.buildViewApi(viewType, this.getCurrentData, optionsData.dateEnv);
9263 return { viewSpec, options: refinedOptions, dateProfileGenerator, viewApi };
9264 }
9265 processRawViewOptions(viewSpec, pluginHooks, localeDefaults, optionOverrides, dynamicOptionOverrides) {
9266 let raw = mergeRawOptions([
9267 BASE_OPTION_DEFAULTS,
9268 viewSpec.optionDefaults,
9269 localeDefaults,
9270 optionOverrides,
9271 viewSpec.optionOverrides,
9272 dynamicOptionOverrides,
9273 ]);
9274 let refiners = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, BASE_OPTION_REFINERS), CALENDAR_LISTENER_REFINERS), CALENDAR_OPTION_REFINERS), VIEW_OPTION_REFINERS), pluginHooks.listenerRefiners), pluginHooks.optionRefiners);
9275 let refined = {};
9276 let currentRaw = this.currentViewOptionsInput;
9277 let currentRefined = this.currentViewOptionsRefined;
9278 let anyChanges = false;
9279 let extra = {};
9280 for (let optionName in raw) {
9281 if (raw[optionName] === currentRaw[optionName] ||
9282 (COMPLEX_OPTION_COMPARATORS[optionName] &&
9283 COMPLEX_OPTION_COMPARATORS[optionName](raw[optionName], currentRaw[optionName]))) {
9284 refined[optionName] = currentRefined[optionName];
9285 }
9286 else {
9287 if (raw[optionName] === this.currentCalendarOptionsInput[optionName] ||
9288 (COMPLEX_OPTION_COMPARATORS[optionName] &&
9289 COMPLEX_OPTION_COMPARATORS[optionName](raw[optionName], this.currentCalendarOptionsInput[optionName]))) {
9290 if (optionName in this.currentCalendarOptionsRefined) { // might be an "extra" prop
9291 refined[optionName] = this.currentCalendarOptionsRefined[optionName];
9292 }
9293 }
9294 else if (refiners[optionName]) {
9295 refined[optionName] = refiners[optionName](raw[optionName]);
9296 }
9297 else {
9298 extra[optionName] = raw[optionName];
9299 }
9300 anyChanges = true;
9301 }
9302 }
9303 if (anyChanges) {
9304 this.currentViewOptionsInput = raw;
9305 this.currentViewOptionsRefined = refined;
9306 }
9307 return {
9308 rawOptions: this.currentViewOptionsInput,
9309 refinedOptions: this.currentViewOptionsRefined,
9310 extra,
9311 };
9312 }
9313 }
9314 function buildDateEnv$1(timeZone, explicitLocale, weekNumberCalculation, firstDay, weekText, pluginHooks, availableLocaleData, defaultSeparator) {
9315 let locale = buildLocale(explicitLocale || availableLocaleData.defaultCode, availableLocaleData.map);
9316 return new DateEnv({
9317 calendarSystem: 'gregory',
9318 timeZone,
9319 namedTimeZoneImpl: pluginHooks.namedTimeZonedImpl,
9320 locale,
9321 weekNumberCalculation,
9322 firstDay,
9323 weekText,
9324 cmdFormatter: pluginHooks.cmdFormatter,
9325 defaultSeparator,
9326 });
9327 }
9328 function buildTheme(options, pluginHooks) {
9329 let ThemeClass = pluginHooks.themeClasses[options.themeSystem] || StandardTheme;
9330 return new ThemeClass(options);
9331 }
9332 function buildDateProfileGenerator(props) {
9333 let DateProfileGeneratorClass = props.dateProfileGeneratorClass || DateProfileGenerator;
9334 return new DateProfileGeneratorClass(props);
9335 }
9336 function buildViewApi(type, getCurrentData, dateEnv) {
9337 return new ViewImpl(type, getCurrentData, dateEnv);
9338 }
9339 function buildEventUiBySource(eventSources) {
9340 return mapHash(eventSources, (eventSource) => eventSource.ui);
9341 }
9342 function buildEventUiBases(eventDefs, eventUiSingleBase, eventUiBySource) {
9343 let eventUiBases = { '': eventUiSingleBase };
9344 for (let defId in eventDefs) {
9345 let def = eventDefs[defId];
9346 if (def.sourceId && eventUiBySource[def.sourceId]) {
9347 eventUiBases[defId] = eventUiBySource[def.sourceId];
9348 }
9349 }
9350 return eventUiBases;
9351 }
9352 function buildViewUiProps(calendarContext) {
9353 let { options } = calendarContext;
9354 return {
9355 eventUiSingleBase: createEventUi({
9356 display: options.eventDisplay,
9357 editable: options.editable,
9358 startEditable: options.eventStartEditable,
9359 durationEditable: options.eventDurationEditable,
9360 constraint: options.eventConstraint,
9361 overlap: typeof options.eventOverlap === 'boolean' ? options.eventOverlap : undefined,
9362 allow: options.eventAllow,
9363 backgroundColor: options.eventBackgroundColor,
9364 borderColor: options.eventBorderColor,
9365 textColor: options.eventTextColor,
9366 color: options.eventColor,
9367 // classNames: options.eventClassNames // render hook will handle this
9368 }, calendarContext),
9369 selectionConfig: createEventUi({
9370 constraint: options.selectConstraint,
9371 overlap: typeof options.selectOverlap === 'boolean' ? options.selectOverlap : undefined,
9372 allow: options.selectAllow,
9373 }, calendarContext),
9374 };
9375 }
9376 function computeIsLoading(state, context) {
9377 for (let isLoadingFunc of context.pluginHooks.isLoadingFuncs) {
9378 if (isLoadingFunc(state)) {
9379 return true;
9380 }
9381 }
9382 return false;
9383 }
9384 function parseContextBusinessHours(calendarContext) {
9385 return parseBusinessHours(calendarContext.options.businessHours, calendarContext);
9386 }
9387 function warnUnknownOptions(options, viewName) {
9388 for (let optionName in options) {
9389 console.warn(`Unknown option '${optionName}'` +
9390 (viewName ? ` for view '${viewName}'` : ''));
9391 }
9392 }
9393
9394 class ToolbarSection extends BaseComponent {
9395 render() {
9396 let children = this.props.widgetGroups.map((widgetGroup) => this.renderWidgetGroup(widgetGroup));
9397 return y('div', { className: 'fc-toolbar-chunk' }, ...children);
9398 }
9399 renderWidgetGroup(widgetGroup) {
9400 let { props } = this;
9401 let { theme } = this.context;
9402 let children = [];
9403 let isOnlyButtons = true;
9404 for (let widget of widgetGroup) {
9405 let { buttonName, buttonClick, buttonText, buttonIcon, buttonHint } = widget;
9406 if (buttonName === 'title') {
9407 isOnlyButtons = false;
9408 children.push(y("h2", { className: "fc-toolbar-title", id: props.titleId }, props.title));
9409 }
9410 else {
9411 let isPressed = buttonName === props.activeButton;
9412 let isDisabled = (!props.isTodayEnabled && buttonName === 'today') ||
9413 (!props.isPrevEnabled && buttonName === 'prev') ||
9414 (!props.isNextEnabled && buttonName === 'next');
9415 let buttonClasses = [`fc-${buttonName}-button`, theme.getClass('button')];
9416 if (isPressed) {
9417 buttonClasses.push(theme.getClass('buttonActive'));
9418 }
9419 children.push(y("button", { type: "button", title: typeof buttonHint === 'function' ? buttonHint(props.navUnit) : buttonHint, disabled: isDisabled, "aria-pressed": isPressed, className: buttonClasses.join(' '), onClick: buttonClick }, buttonText || (buttonIcon ? y("span", { className: buttonIcon, role: "img" }) : '')));
9420 }
9421 }
9422 if (children.length > 1) {
9423 let groupClassName = (isOnlyButtons && theme.getClass('buttonGroup')) || '';
9424 return y('div', { className: groupClassName }, ...children);
9425 }
9426 return children[0];
9427 }
9428 }
9429
9430 class Toolbar extends BaseComponent {
9431 render() {
9432 let { model, extraClassName } = this.props;
9433 let forceLtr = false;
9434 let startContent;
9435 let endContent;
9436 let sectionWidgets = model.sectionWidgets;
9437 let centerContent = sectionWidgets.center;
9438 if (sectionWidgets.left) {
9439 forceLtr = true;
9440 startContent = sectionWidgets.left;
9441 }
9442 else {
9443 startContent = sectionWidgets.start;
9444 }
9445 if (sectionWidgets.right) {
9446 forceLtr = true;
9447 endContent = sectionWidgets.right;
9448 }
9449 else {
9450 endContent = sectionWidgets.end;
9451 }
9452 let classNames = [
9453 extraClassName || '',
9454 'fc-toolbar',
9455 forceLtr ? 'fc-toolbar-ltr' : '',
9456 ];
9457 return (y("div", { className: classNames.join(' ') },
9458 this.renderSection('start', startContent || []),
9459 this.renderSection('center', centerContent || []),
9460 this.renderSection('end', endContent || [])));
9461 }
9462 renderSection(key, widgetGroups) {
9463 let { props } = this;
9464 return (y(ToolbarSection, { key: key, widgetGroups: widgetGroups, title: props.title, navUnit: props.navUnit, activeButton: props.activeButton, isTodayEnabled: props.isTodayEnabled, isPrevEnabled: props.isPrevEnabled, isNextEnabled: props.isNextEnabled, titleId: props.titleId }));
9465 }
9466 }
9467
9468 class ViewHarness extends BaseComponent {
9469 constructor() {
9470 super(...arguments);
9471 this.state = {
9472 availableWidth: null,
9473 };
9474 this.handleEl = (el) => {
9475 this.el = el;
9476 setRef(this.props.elRef, el);
9477 this.updateAvailableWidth();
9478 };
9479 this.handleResize = () => {
9480 this.updateAvailableWidth();
9481 };
9482 }
9483 render() {
9484 let { props, state } = this;
9485 let { aspectRatio } = props;
9486 let classNames = [
9487 'fc-view-harness',
9488 (aspectRatio || props.liquid || props.height)
9489 ? 'fc-view-harness-active' // harness controls the height
9490 : 'fc-view-harness-passive', // let the view do the height
9491 ];
9492 let height = '';
9493 let paddingBottom = '';
9494 if (aspectRatio) {
9495 if (state.availableWidth !== null) {
9496 height = state.availableWidth / aspectRatio;
9497 }
9498 else {
9499 // while waiting to know availableWidth, we can't set height to *zero*
9500 // because will cause lots of unnecessary scrollbars within scrollgrid.
9501 // BETTER: don't start rendering ANYTHING yet until we know container width
9502 // NOTE: why not always use paddingBottom? Causes height oscillation (issue 5606)
9503 paddingBottom = `${(1 / aspectRatio) * 100}%`;
9504 }
9505 }
9506 else {
9507 height = props.height || '';
9508 }
9509 return (y("div", { "aria-labelledby": props.labeledById, ref: this.handleEl, className: classNames.join(' '), style: { height, paddingBottom } }, props.children));
9510 }
9511 componentDidMount() {
9512 this.context.addResizeHandler(this.handleResize);
9513 }
9514 componentWillUnmount() {
9515 this.context.removeResizeHandler(this.handleResize);
9516 }
9517 updateAvailableWidth() {
9518 if (this.el && // needed. but why?
9519 this.props.aspectRatio // aspectRatio is the only height setting that needs availableWidth
9520 ) {
9521 this.setState({ availableWidth: this.el.offsetWidth });
9522 }
9523 }
9524 }
9525
9526 /*
9527 Detects when the user clicks on an event within a DateComponent
9528 */
9529 class EventClicking extends Interaction {
9530 constructor(settings) {
9531 super(settings);
9532 this.handleSegClick = (ev, segEl) => {
9533 let { component } = this;
9534 let { context } = component;
9535 let seg = getElSeg(segEl);
9536 if (seg && // might be the <div> surrounding the more link
9537 component.isValidSegDownEl(ev.target)) {
9538 // our way to simulate a link click for elements that can't be <a> tags
9539 // grab before trigger fired in case trigger trashes DOM thru rerendering
9540 let hasUrlContainer = elementClosest(ev.target, '.fc-event-forced-url');
9541 let url = hasUrlContainer ? hasUrlContainer.querySelector('a[href]').href : '';
9542 context.emitter.trigger('eventClick', {
9543 el: segEl,
9544 event: new EventImpl(component.context, seg.eventRange.def, seg.eventRange.instance),
9545 jsEvent: ev,
9546 view: context.viewApi,
9547 });
9548 if (url && !ev.defaultPrevented) {
9549 window.location.href = url;
9550 }
9551 }
9552 };
9553 this.destroy = listenBySelector(settings.el, 'click', '.fc-event', // on both fg and bg events
9554 this.handleSegClick);
9555 }
9556 }
9557
9558 /*
9559 Triggers events and adds/removes core classNames when the user's pointer
9560 enters/leaves event-elements of a component.
9561 */
9562 class EventHovering extends Interaction {
9563 constructor(settings) {
9564 super(settings);
9565 // for simulating an eventMouseLeave when the event el is destroyed while mouse is over it
9566 this.handleEventElRemove = (el) => {
9567 if (el === this.currentSegEl) {
9568 this.handleSegLeave(null, this.currentSegEl);
9569 }
9570 };
9571 this.handleSegEnter = (ev, segEl) => {
9572 if (getElSeg(segEl)) { // TODO: better way to make sure not hovering over more+ link or its wrapper
9573 this.currentSegEl = segEl;
9574 this.triggerEvent('eventMouseEnter', ev, segEl);
9575 }
9576 };
9577 this.handleSegLeave = (ev, segEl) => {
9578 if (this.currentSegEl) {
9579 this.currentSegEl = null;
9580 this.triggerEvent('eventMouseLeave', ev, segEl);
9581 }
9582 };
9583 this.removeHoverListeners = listenToHoverBySelector(settings.el, '.fc-event', // on both fg and bg events
9584 this.handleSegEnter, this.handleSegLeave);
9585 }
9586 destroy() {
9587 this.removeHoverListeners();
9588 }
9589 triggerEvent(publicEvName, ev, segEl) {
9590 let { component } = this;
9591 let { context } = component;
9592 let seg = getElSeg(segEl);
9593 if (!ev || component.isValidSegDownEl(ev.target)) {
9594 context.emitter.trigger(publicEvName, {
9595 el: segEl,
9596 event: new EventImpl(context, seg.eventRange.def, seg.eventRange.instance),
9597 jsEvent: ev,
9598 view: context.viewApi,
9599 });
9600 }
9601 }
9602 }
9603
9604 class CalendarContent extends PureComponent {
9605 constructor() {
9606 super(...arguments);
9607 this.buildViewContext = memoize(buildViewContext);
9608 this.buildViewPropTransformers = memoize(buildViewPropTransformers);
9609 this.buildToolbarProps = memoize(buildToolbarProps);
9610 this.headerRef = d();
9611 this.footerRef = d();
9612 this.interactionsStore = {};
9613 // eslint-disable-next-line
9614 this.state = {
9615 viewLabelId: getUniqueDomId(),
9616 };
9617 // Component Registration
9618 // -----------------------------------------------------------------------------------------------------------------
9619 this.registerInteractiveComponent = (component, settingsInput) => {
9620 let settings = parseInteractionSettings(component, settingsInput);
9621 let DEFAULT_INTERACTIONS = [
9622 EventClicking,
9623 EventHovering,
9624 ];
9625 let interactionClasses = DEFAULT_INTERACTIONS.concat(this.props.pluginHooks.componentInteractions);
9626 let interactions = interactionClasses.map((TheInteractionClass) => new TheInteractionClass(settings));
9627 this.interactionsStore[component.uid] = interactions;
9628 interactionSettingsStore[component.uid] = settings;
9629 };
9630 this.unregisterInteractiveComponent = (component) => {
9631 let listeners = this.interactionsStore[component.uid];
9632 if (listeners) {
9633 for (let listener of listeners) {
9634 listener.destroy();
9635 }
9636 delete this.interactionsStore[component.uid];
9637 }
9638 delete interactionSettingsStore[component.uid];
9639 };
9640 // Resizing
9641 // -----------------------------------------------------------------------------------------------------------------
9642 this.resizeRunner = new DelayedRunner(() => {
9643 this.props.emitter.trigger('_resize', true); // should window resizes be considered "forced" ?
9644 this.props.emitter.trigger('windowResize', { view: this.props.viewApi });
9645 });
9646 this.handleWindowResize = (ev) => {
9647 let { options } = this.props;
9648 if (options.handleWindowResize &&
9649 ev.target === window // avoid jqui events
9650 ) {
9651 this.resizeRunner.request(options.windowResizeDelay);
9652 }
9653 };
9654 }
9655 /*
9656 renders INSIDE of an outer div
9657 */
9658 render() {
9659 let { props } = this;
9660 let { toolbarConfig, options } = props;
9661 let viewVGrow = false;
9662 let viewHeight = '';
9663 let viewAspectRatio;
9664 if (props.isHeightAuto || props.forPrint) {
9665 viewHeight = '';
9666 }
9667 else if (options.height != null) {
9668 viewVGrow = true;
9669 }
9670 else if (options.contentHeight != null) {
9671 viewHeight = options.contentHeight;
9672 }
9673 else {
9674 viewAspectRatio = Math.max(options.aspectRatio, 0.5); // prevent from getting too tall
9675 }
9676 let viewContext = this.buildViewContext(props.viewSpec, props.viewApi, props.options, props.dateProfileGenerator, props.dateEnv, props.nowManager, props.theme, props.pluginHooks, props.dispatch, props.getCurrentData, props.emitter, props.calendarApi, this.registerInteractiveComponent, this.unregisterInteractiveComponent);
9677 let viewLabelId = (toolbarConfig.header && toolbarConfig.header.hasTitle)
9678 ? this.state.viewLabelId
9679 : undefined;
9680 return (y(ViewContextType.Provider, { value: viewContext },
9681 y(NowTimer, { unit: "day" }, (nowDate) => {
9682 let toolbarProps = this.buildToolbarProps(props.viewSpec, props.dateProfile, props.dateProfileGenerator, props.currentDate, nowDate, props.viewTitle);
9683 return (y(_, null,
9684 toolbarConfig.header && (y(Toolbar, Object.assign({ ref: this.headerRef, extraClassName: "fc-header-toolbar", model: toolbarConfig.header, titleId: viewLabelId }, toolbarProps))),
9685 y(ViewHarness, { liquid: viewVGrow, height: viewHeight, aspectRatio: viewAspectRatio, labeledById: viewLabelId },
9686 this.renderView(props),
9687 this.buildAppendContent()),
9688 toolbarConfig.footer && (y(Toolbar, Object.assign({ ref: this.footerRef, extraClassName: "fc-footer-toolbar", model: toolbarConfig.footer, titleId: "" }, toolbarProps)))));
9689 })));
9690 }
9691 componentDidMount() {
9692 let { props } = this;
9693 this.calendarInteractions = props.pluginHooks.calendarInteractions
9694 .map((CalendarInteractionClass) => new CalendarInteractionClass(props));
9695 window.addEventListener('resize', this.handleWindowResize);
9696 let { propSetHandlers } = props.pluginHooks;
9697 for (let propName in propSetHandlers) {
9698 propSetHandlers[propName](props[propName], props);
9699 }
9700 }
9701 componentDidUpdate(prevProps) {
9702 let { props } = this;
9703 let { propSetHandlers } = props.pluginHooks;
9704 for (let propName in propSetHandlers) {
9705 if (props[propName] !== prevProps[propName]) {
9706 propSetHandlers[propName](props[propName], props);
9707 }
9708 }
9709 }
9710 componentWillUnmount() {
9711 window.removeEventListener('resize', this.handleWindowResize);
9712 this.resizeRunner.clear();
9713 for (let interaction of this.calendarInteractions) {
9714 interaction.destroy();
9715 }
9716 this.props.emitter.trigger('_unmount');
9717 }
9718 buildAppendContent() {
9719 let { props } = this;
9720 let children = props.pluginHooks.viewContainerAppends.map((buildAppendContent) => buildAppendContent(props));
9721 return y(_, {}, ...children);
9722 }
9723 renderView(props) {
9724 let { pluginHooks } = props;
9725 let { viewSpec } = props;
9726 let viewProps = {
9727 dateProfile: props.dateProfile,
9728 businessHours: props.businessHours,
9729 eventStore: props.renderableEventStore,
9730 eventUiBases: props.eventUiBases,
9731 dateSelection: props.dateSelection,
9732 eventSelection: props.eventSelection,
9733 eventDrag: props.eventDrag,
9734 eventResize: props.eventResize,
9735 isHeightAuto: props.isHeightAuto,
9736 forPrint: props.forPrint,
9737 };
9738 let transformers = this.buildViewPropTransformers(pluginHooks.viewPropsTransformers);
9739 for (let transformer of transformers) {
9740 Object.assign(viewProps, transformer.transform(viewProps, props));
9741 }
9742 let ViewComponent = viewSpec.component;
9743 return (y(ViewComponent, Object.assign({}, viewProps)));
9744 }
9745 }
9746 function buildToolbarProps(viewSpec, dateProfile, dateProfileGenerator, currentDate, now, title) {
9747 // don't force any date-profiles to valid date profiles (the `false`) so that we can tell if it's invalid
9748 let todayInfo = dateProfileGenerator.build(now, undefined, false); // TODO: need `undefined` or else INFINITE LOOP for some reason
9749 let prevInfo = dateProfileGenerator.buildPrev(dateProfile, currentDate, false);
9750 let nextInfo = dateProfileGenerator.buildNext(dateProfile, currentDate, false);
9751 return {
9752 title,
9753 activeButton: viewSpec.type,
9754 navUnit: viewSpec.singleUnit,
9755 isTodayEnabled: todayInfo.isValid && !rangeContainsMarker(dateProfile.currentRange, now),
9756 isPrevEnabled: prevInfo.isValid,
9757 isNextEnabled: nextInfo.isValid,
9758 };
9759 }
9760 // Plugin
9761 // -----------------------------------------------------------------------------------------------------------------
9762 function buildViewPropTransformers(theClasses) {
9763 return theClasses.map((TheClass) => new TheClass());
9764 }
9765
9766 class Calendar extends CalendarImpl {
9767 constructor(el, optionOverrides = {}) {
9768 super();
9769 this.isRendering = false;
9770 this.isRendered = false;
9771 this.currentClassNames = [];
9772 this.customContentRenderId = 0;
9773 this.handleAction = (action) => {
9774 // actions we know we want to render immediately
9775 switch (action.type) {
9776 case 'SET_EVENT_DRAG':
9777 case 'SET_EVENT_RESIZE':
9778 this.renderRunner.tryDrain();
9779 }
9780 };
9781 this.handleData = (data) => {
9782 this.currentData = data;
9783 this.renderRunner.request(data.calendarOptions.rerenderDelay);
9784 };
9785 this.handleRenderRequest = () => {
9786 if (this.isRendering) {
9787 this.isRendered = true;
9788 let { currentData } = this;
9789 flushSync(() => {
9790 D$1(y(CalendarRoot, { options: currentData.calendarOptions, theme: currentData.theme, emitter: currentData.emitter }, (classNames, height, isHeightAuto, forPrint) => {
9791 this.setClassNames(classNames);
9792 this.setHeight(height);
9793 return (y(RenderId.Provider, { value: this.customContentRenderId },
9794 y(CalendarContent, Object.assign({ isHeightAuto: isHeightAuto, forPrint: forPrint }, currentData))));
9795 }), this.el);
9796 });
9797 }
9798 else if (this.isRendered) {
9799 this.isRendered = false;
9800 D$1(null, this.el);
9801 this.setClassNames([]);
9802 this.setHeight('');
9803 }
9804 };
9805 ensureElHasStyles(el);
9806 this.el = el;
9807 this.renderRunner = new DelayedRunner(this.handleRenderRequest);
9808 new CalendarDataManager({
9809 optionOverrides,
9810 calendarApi: this,
9811 onAction: this.handleAction,
9812 onData: this.handleData,
9813 });
9814 }
9815 render() {
9816 let wasRendering = this.isRendering;
9817 if (!wasRendering) {
9818 this.isRendering = true;
9819 }
9820 else {
9821 this.customContentRenderId += 1;
9822 }
9823 this.renderRunner.request();
9824 if (wasRendering) {
9825 this.updateSize();
9826 }
9827 }
9828 destroy() {
9829 if (this.isRendering) {
9830 this.isRendering = false;
9831 this.renderRunner.request();
9832 }
9833 }
9834 updateSize() {
9835 flushSync(() => {
9836 super.updateSize();
9837 });
9838 }
9839 batchRendering(func) {
9840 this.renderRunner.pause('batchRendering');
9841 func();
9842 this.renderRunner.resume('batchRendering');
9843 }
9844 pauseRendering() {
9845 this.renderRunner.pause('pauseRendering');
9846 }
9847 resumeRendering() {
9848 this.renderRunner.resume('pauseRendering', true);
9849 }
9850 resetOptions(optionOverrides, changedOptionNames) {
9851 this.currentDataManager.resetOptions(optionOverrides, changedOptionNames);
9852 }
9853 setClassNames(classNames) {
9854 if (!isArraysEqual(classNames, this.currentClassNames)) {
9855 let { classList } = this.el;
9856 for (let className of this.currentClassNames) {
9857 classList.remove(className);
9858 }
9859 for (let className of classNames) {
9860 classList.add(className);
9861 }
9862 this.currentClassNames = classNames;
9863 }
9864 }
9865 setHeight(height) {
9866 applyStyleProp(this.el, 'height', height);
9867 }
9868 }
9869
9870 function formatDate(dateInput, options = {}) {
9871 let dateEnv = buildDateEnv(options);
9872 let formatter = createFormatter(options);
9873 let dateMeta = dateEnv.createMarkerMeta(dateInput);
9874 if (!dateMeta) { // TODO: warning?
9875 return '';
9876 }
9877 return dateEnv.format(dateMeta.marker, formatter, {
9878 forcedTzo: dateMeta.forcedTzo,
9879 });
9880 }
9881 function formatRange(startInput, endInput, options) {
9882 let dateEnv = buildDateEnv(typeof options === 'object' && options ? options : {}); // pass in if non-null object
9883 let formatter = createFormatter(options);
9884 let startMeta = dateEnv.createMarkerMeta(startInput);
9885 let endMeta = dateEnv.createMarkerMeta(endInput);
9886 if (!startMeta || !endMeta) { // TODO: warning?
9887 return '';
9888 }
9889 return dateEnv.formatRange(startMeta.marker, endMeta.marker, formatter, {
9890 forcedStartTzo: startMeta.forcedTzo,
9891 forcedEndTzo: endMeta.forcedTzo,
9892 isEndExclusive: options.isEndExclusive,
9893 defaultSeparator: BASE_OPTION_DEFAULTS.defaultRangeSeparator,
9894 });
9895 }
9896 // TODO: more DRY and optimized
9897 function buildDateEnv(settings) {
9898 let locale = buildLocale(settings.locale || 'en', organizeRawLocales([]).map); // TODO: don't hardcode 'en' everywhere
9899 return new DateEnv(Object.assign(Object.assign({ timeZone: BASE_OPTION_DEFAULTS.timeZone, calendarSystem: 'gregory' }, settings), { locale }));
9900 }
9901
9902 // HELPERS
9903 /*
9904 if nextDayThreshold is specified, slicing is done in an all-day fashion.
9905 you can get nextDayThreshold from context.nextDayThreshold
9906 */
9907 function sliceEvents(props, allDay) {
9908 return sliceEventStore(props.eventStore, props.eventUiBases, props.dateProfile.activeRange, allDay ? props.nextDayThreshold : null).fg;
9909 }
9910
9911 const version = '6.1.17';
9912
9913 exports.Calendar = Calendar;
9914 exports.Internal = internal;
9915 exports.JsonRequestError = JsonRequestError;
9916 exports.Preact = preact;
9917 exports.createPlugin = createPlugin;
9918 exports.formatDate = formatDate;
9919 exports.formatRange = formatRange;
9920 exports.globalLocales = globalLocales;
9921 exports.globalPlugins = globalPlugins;
9922 exports.sliceEvents = sliceEvents;
9923 exports.version = version;
9924
9925 Object.defineProperty(exports, '__esModule', { value: true });
9926
9927 return exports;
9928
9929})({});