UNPKG

297 kBJavaScriptView Raw
1import * as preact from 'preact';
2import { Component, createElement, isValidElement, Fragment } from 'preact';
3import { createPortal } from 'preact/compat';
4
5const styleTexts = [];
6const styleEls = new Map();
7function injectStyles(styleText) {
8 styleTexts.push(styleText);
9 styleEls.forEach((styleEl) => {
10 appendStylesTo(styleEl, styleText);
11 });
12}
13function ensureElHasStyles(el) {
14 if (el.isConnected && // sometimes true if SSR system simulates DOM
15 el.getRootNode // sometimes undefined if SSR system simulates DOM
16 ) {
17 registerStylesRoot(el.getRootNode());
18 }
19}
20function registerStylesRoot(rootNode) {
21 let styleEl = styleEls.get(rootNode);
22 if (!styleEl || !styleEl.isConnected) {
23 styleEl = rootNode.querySelector('style[data-fullcalendar]');
24 if (!styleEl) {
25 styleEl = document.createElement('style');
26 styleEl.setAttribute('data-fullcalendar', '');
27 const nonce = getNonceValue();
28 if (nonce) {
29 styleEl.nonce = nonce;
30 }
31 const parentEl = rootNode === document ? document.head : rootNode;
32 const insertBefore = rootNode === document
33 ? parentEl.querySelector('script,link[rel=stylesheet],link[as=style],style')
34 : parentEl.firstChild;
35 parentEl.insertBefore(styleEl, insertBefore);
36 }
37 styleEls.set(rootNode, styleEl);
38 hydrateStylesRoot(styleEl);
39 }
40}
41function hydrateStylesRoot(styleEl) {
42 for (const styleText of styleTexts) {
43 appendStylesTo(styleEl, styleText);
44 }
45}
46function appendStylesTo(styleEl, styleText) {
47 const { sheet } = styleEl;
48 const ruleCnt = sheet.cssRules.length;
49 styleText.split('}').forEach((styleStr, i) => {
50 styleStr = styleStr.trim();
51 if (styleStr) {
52 sheet.insertRule(styleStr + '}', ruleCnt + i);
53 }
54 });
55}
56// nonce
57// -------------------------------------------------------------------------------------------------
58let queriedNonceValue;
59function getNonceValue() {
60 if (queriedNonceValue === undefined) {
61 queriedNonceValue = queryNonceValue();
62 }
63 return queriedNonceValue;
64}
65/*
66TODO: discourage meta tag and instead put nonce attribute on placeholder <style> tag
67*/
68function queryNonceValue() {
69 const metaWithNonce = document.querySelector('meta[name="csp-nonce"]');
70 if (metaWithNonce && metaWithNonce.hasAttribute('content')) {
71 return metaWithNonce.getAttribute('content');
72 }
73 const elWithNonce = document.querySelector('script[nonce]');
74 if (elWithNonce) {
75 return elWithNonce.nonce || '';
76 }
77 return '';
78}
79// main
80// -------------------------------------------------------------------------------------------------
81if (typeof document !== 'undefined') {
82 registerStylesRoot(document);
83}
84
85var 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;-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;-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)}";
86injectStyles(css_248z);
87
88class DelayedRunner {
89 constructor(drainedOption) {
90 this.drainedOption = drainedOption;
91 this.isRunning = false;
92 this.isDirty = false;
93 this.pauseDepths = {};
94 this.timeoutId = 0;
95 }
96 request(delay) {
97 this.isDirty = true;
98 if (!this.isPaused()) {
99 this.clearTimeout();
100 if (delay == null) {
101 this.tryDrain();
102 }
103 else {
104 this.timeoutId = setTimeout(// NOT OPTIMAL! TODO: look at debounce
105 this.tryDrain.bind(this), delay);
106 }
107 }
108 }
109 pause(scope = '') {
110 let { pauseDepths } = this;
111 pauseDepths[scope] = (pauseDepths[scope] || 0) + 1;
112 this.clearTimeout();
113 }
114 resume(scope = '', force) {
115 let { pauseDepths } = this;
116 if (scope in pauseDepths) {
117 if (force) {
118 delete pauseDepths[scope];
119 }
120 else {
121 pauseDepths[scope] -= 1;
122 let depth = pauseDepths[scope];
123 if (depth <= 0) {
124 delete pauseDepths[scope];
125 }
126 }
127 this.tryDrain();
128 }
129 }
130 isPaused() {
131 return Object.keys(this.pauseDepths).length;
132 }
133 tryDrain() {
134 if (!this.isRunning && !this.isPaused()) {
135 this.isRunning = true;
136 while (this.isDirty) {
137 this.isDirty = false;
138 this.drained(); // might set isDirty to true again
139 }
140 this.isRunning = false;
141 }
142 }
143 clear() {
144 this.clearTimeout();
145 this.isDirty = false;
146 this.pauseDepths = {};
147 }
148 clearTimeout() {
149 if (this.timeoutId) {
150 clearTimeout(this.timeoutId);
151 this.timeoutId = 0;
152 }
153 }
154 drained() {
155 if (this.drainedOption) {
156 this.drainedOption();
157 }
158 }
159}
160
161function removeElement(el) {
162 if (el.parentNode) {
163 el.parentNode.removeChild(el);
164 }
165}
166// Querying
167// ----------------------------------------------------------------------------------------------------------------
168function elementClosest(el, selector) {
169 if (el.closest) {
170 return el.closest(selector);
171 // really bad fallback for IE
172 // from https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
173 }
174 if (!document.documentElement.contains(el)) {
175 return null;
176 }
177 do {
178 if (elementMatches(el, selector)) {
179 return el;
180 }
181 el = (el.parentElement || el.parentNode);
182 } while (el !== null && el.nodeType === 1);
183 return null;
184}
185function elementMatches(el, selector) {
186 let method = el.matches || el.matchesSelector || el.msMatchesSelector;
187 return method.call(el, selector);
188}
189// accepts multiple subject els
190// returns a real array. good for methods like forEach
191// TODO: accept the document
192function findElements(container, selector) {
193 let containers = container instanceof HTMLElement ? [container] : container;
194 let allMatches = [];
195 for (let i = 0; i < containers.length; i += 1) {
196 let matches = containers[i].querySelectorAll(selector);
197 for (let j = 0; j < matches.length; j += 1) {
198 allMatches.push(matches[j]);
199 }
200 }
201 return allMatches;
202}
203// accepts multiple subject els
204// only queries direct child elements // TODO: rename to findDirectChildren!
205function findDirectChildren(parent, selector) {
206 let parents = parent instanceof HTMLElement ? [parent] : parent;
207 let allMatches = [];
208 for (let i = 0; i < parents.length; i += 1) {
209 let childNodes = parents[i].children; // only ever elements
210 for (let j = 0; j < childNodes.length; j += 1) {
211 let childNode = childNodes[j];
212 if (!selector || elementMatches(childNode, selector)) {
213 allMatches.push(childNode);
214 }
215 }
216 }
217 return allMatches;
218}
219// Style
220// ----------------------------------------------------------------------------------------------------------------
221const PIXEL_PROP_RE = /(top|left|right|bottom|width|height)$/i;
222function applyStyle(el, props) {
223 for (let propName in props) {
224 applyStyleProp(el, propName, props[propName]);
225 }
226}
227function applyStyleProp(el, name, val) {
228 if (val == null) {
229 el.style[name] = '';
230 }
231 else if (typeof val === 'number' && PIXEL_PROP_RE.test(name)) {
232 el.style[name] = `${val}px`;
233 }
234 else {
235 el.style[name] = val;
236 }
237}
238// Event Handling
239// ----------------------------------------------------------------------------------------------------------------
240// if intercepting bubbled events at the document/window/body level,
241// and want to see originating element (the 'target'), use this util instead
242// of `ev.target` because it goes within web-component boundaries.
243function getEventTargetViaRoot(ev) {
244 var _a, _b;
245 return (_b = (_a = ev.composedPath) === null || _a === void 0 ? void 0 : _a.call(ev)[0]) !== null && _b !== void 0 ? _b : ev.target;
246}
247// Unique ID for DOM attribute
248let guid$1 = 0;
249function getUniqueDomId() {
250 guid$1 += 1;
251 return 'fc-dom-' + guid$1;
252}
253
254// Stops a mouse/touch event from doing it's native browser action
255function preventDefault(ev) {
256 ev.preventDefault();
257}
258// Event Delegation
259// ----------------------------------------------------------------------------------------------------------------
260function buildDelegationHandler(selector, handler) {
261 return (ev) => {
262 let matchedChild = elementClosest(ev.target, selector);
263 if (matchedChild) {
264 handler.call(matchedChild, ev, matchedChild);
265 }
266 };
267}
268function listenBySelector(container, eventType, selector, handler) {
269 let attachedHandler = buildDelegationHandler(selector, handler);
270 container.addEventListener(eventType, attachedHandler);
271 return () => {
272 container.removeEventListener(eventType, attachedHandler);
273 };
274}
275function listenToHoverBySelector(container, selector, onMouseEnter, onMouseLeave) {
276 let currentMatchedChild;
277 return listenBySelector(container, 'mouseover', selector, (mouseOverEv, matchedChild) => {
278 if (matchedChild !== currentMatchedChild) {
279 currentMatchedChild = matchedChild;
280 onMouseEnter(mouseOverEv, matchedChild);
281 let realOnMouseLeave = (mouseLeaveEv) => {
282 currentMatchedChild = null;
283 onMouseLeave(mouseLeaveEv, matchedChild);
284 matchedChild.removeEventListener('mouseleave', realOnMouseLeave);
285 };
286 // listen to the next mouseleave, and then unattach
287 matchedChild.addEventListener('mouseleave', realOnMouseLeave);
288 }
289 });
290}
291// Animation
292// ----------------------------------------------------------------------------------------------------------------
293const transitionEventNames = [
294 'webkitTransitionEnd',
295 'otransitionend',
296 'oTransitionEnd',
297 'msTransitionEnd',
298 'transitionend',
299];
300// triggered only when the next single subsequent transition finishes
301function whenTransitionDone(el, callback) {
302 let realCallback = (ev) => {
303 callback(ev);
304 transitionEventNames.forEach((eventName) => {
305 el.removeEventListener(eventName, realCallback);
306 });
307 };
308 transitionEventNames.forEach((eventName) => {
309 el.addEventListener(eventName, realCallback); // cross-browser way to determine when the transition finishes
310 });
311}
312// ARIA workarounds
313// ----------------------------------------------------------------------------------------------------------------
314function createAriaClickAttrs(handler) {
315 return Object.assign({ onClick: handler }, createAriaKeyboardAttrs(handler));
316}
317function createAriaKeyboardAttrs(handler) {
318 return {
319 tabIndex: 0,
320 onKeyDown(ev) {
321 if (ev.key === 'Enter' || ev.key === ' ') {
322 handler(ev);
323 ev.preventDefault(); // if space, don't scroll down page
324 }
325 },
326 };
327}
328
329let guidNumber = 0;
330function guid() {
331 guidNumber += 1;
332 return String(guidNumber);
333}
334/* FullCalendar-specific DOM Utilities
335----------------------------------------------------------------------------------------------------------------------*/
336// Make the mouse cursor express that an event is not allowed in the current area
337function disableCursor() {
338 document.body.classList.add('fc-not-allowed');
339}
340// Returns the mouse cursor to its original look
341function enableCursor() {
342 document.body.classList.remove('fc-not-allowed');
343}
344/* Selection
345----------------------------------------------------------------------------------------------------------------------*/
346function preventSelection(el) {
347 el.style.userSelect = 'none';
348 el.style.webkitUserSelect = 'none';
349 el.addEventListener('selectstart', preventDefault);
350}
351function allowSelection(el) {
352 el.style.userSelect = '';
353 el.style.webkitUserSelect = '';
354 el.removeEventListener('selectstart', preventDefault);
355}
356/* Context Menu
357----------------------------------------------------------------------------------------------------------------------*/
358function preventContextMenu(el) {
359 el.addEventListener('contextmenu', preventDefault);
360}
361function allowContextMenu(el) {
362 el.removeEventListener('contextmenu', preventDefault);
363}
364function parseFieldSpecs(input) {
365 let specs = [];
366 let tokens = [];
367 let i;
368 let token;
369 if (typeof input === 'string') {
370 tokens = input.split(/\s*,\s*/);
371 }
372 else if (typeof input === 'function') {
373 tokens = [input];
374 }
375 else if (Array.isArray(input)) {
376 tokens = input;
377 }
378 for (i = 0; i < tokens.length; i += 1) {
379 token = tokens[i];
380 if (typeof token === 'string') {
381 specs.push(token.charAt(0) === '-' ?
382 { field: token.substring(1), order: -1 } :
383 { field: token, order: 1 });
384 }
385 else if (typeof token === 'function') {
386 specs.push({ func: token });
387 }
388 }
389 return specs;
390}
391function compareByFieldSpecs(obj0, obj1, fieldSpecs) {
392 let i;
393 let cmp;
394 for (i = 0; i < fieldSpecs.length; i += 1) {
395 cmp = compareByFieldSpec(obj0, obj1, fieldSpecs[i]);
396 if (cmp) {
397 return cmp;
398 }
399 }
400 return 0;
401}
402function compareByFieldSpec(obj0, obj1, fieldSpec) {
403 if (fieldSpec.func) {
404 return fieldSpec.func(obj0, obj1);
405 }
406 return flexibleCompare(obj0[fieldSpec.field], obj1[fieldSpec.field])
407 * (fieldSpec.order || 1);
408}
409function flexibleCompare(a, b) {
410 if (!a && !b) {
411 return 0;
412 }
413 if (b == null) {
414 return -1;
415 }
416 if (a == null) {
417 return 1;
418 }
419 if (typeof a === 'string' || typeof b === 'string') {
420 return String(a).localeCompare(String(b));
421 }
422 return a - b;
423}
424/* String Utilities
425----------------------------------------------------------------------------------------------------------------------*/
426function padStart(val, len) {
427 let s = String(val);
428 return '000'.substr(0, len - s.length) + s;
429}
430function formatWithOrdinals(formatter, args, fallbackText) {
431 if (typeof formatter === 'function') {
432 return formatter(...args);
433 }
434 if (typeof formatter === 'string') { // non-blank string
435 return args.reduce((str, arg, index) => (str.replace('$' + index, arg || '')), formatter);
436 }
437 return fallbackText;
438}
439/* Number Utilities
440----------------------------------------------------------------------------------------------------------------------*/
441function compareNumbers(a, b) {
442 return a - b;
443}
444function isInt(n) {
445 return n % 1 === 0;
446}
447/* FC-specific DOM dimension stuff
448----------------------------------------------------------------------------------------------------------------------*/
449function computeSmallestCellWidth(cellEl) {
450 let allWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-frame');
451 let contentWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-cushion');
452 if (!allWidthEl) {
453 throw new Error('needs fc-scrollgrid-shrink-frame className'); // TODO: use const
454 }
455 if (!contentWidthEl) {
456 throw new Error('needs fc-scrollgrid-shrink-cushion className');
457 }
458 return cellEl.getBoundingClientRect().width - allWidthEl.getBoundingClientRect().width + // the cell padding+border
459 contentWidthEl.getBoundingClientRect().width;
460}
461
462const INTERNAL_UNITS = ['years', 'months', 'days', 'milliseconds'];
463const PARSE_RE = /^(-?)(?:(\d+)\.)?(\d+):(\d\d)(?::(\d\d)(?:\.(\d\d\d))?)?/;
464// Parsing and Creation
465function createDuration(input, unit) {
466 if (typeof input === 'string') {
467 return parseString(input);
468 }
469 if (typeof input === 'object' && input) { // non-null object
470 return parseObject(input);
471 }
472 if (typeof input === 'number') {
473 return parseObject({ [unit || 'milliseconds']: input });
474 }
475 return null;
476}
477function parseString(s) {
478 let m = PARSE_RE.exec(s);
479 if (m) {
480 let sign = m[1] ? -1 : 1;
481 return {
482 years: 0,
483 months: 0,
484 days: sign * (m[2] ? parseInt(m[2], 10) : 0),
485 milliseconds: sign * ((m[3] ? parseInt(m[3], 10) : 0) * 60 * 60 * 1000 + // hours
486 (m[4] ? parseInt(m[4], 10) : 0) * 60 * 1000 + // minutes
487 (m[5] ? parseInt(m[5], 10) : 0) * 1000 + // seconds
488 (m[6] ? parseInt(m[6], 10) : 0) // ms
489 ),
490 };
491 }
492 return null;
493}
494function parseObject(obj) {
495 let duration = {
496 years: obj.years || obj.year || 0,
497 months: obj.months || obj.month || 0,
498 days: obj.days || obj.day || 0,
499 milliseconds: (obj.hours || obj.hour || 0) * 60 * 60 * 1000 + // hours
500 (obj.minutes || obj.minute || 0) * 60 * 1000 + // minutes
501 (obj.seconds || obj.second || 0) * 1000 + // seconds
502 (obj.milliseconds || obj.millisecond || obj.ms || 0), // ms
503 };
504 let weeks = obj.weeks || obj.week;
505 if (weeks) {
506 duration.days += weeks * 7;
507 duration.specifiedWeeks = true;
508 }
509 return duration;
510}
511// Equality
512function durationsEqual(d0, d1) {
513 return d0.years === d1.years &&
514 d0.months === d1.months &&
515 d0.days === d1.days &&
516 d0.milliseconds === d1.milliseconds;
517}
518function asCleanDays(dur) {
519 if (!dur.years && !dur.months && !dur.milliseconds) {
520 return dur.days;
521 }
522 return 0;
523}
524// Simple Math
525function addDurations(d0, d1) {
526 return {
527 years: d0.years + d1.years,
528 months: d0.months + d1.months,
529 days: d0.days + d1.days,
530 milliseconds: d0.milliseconds + d1.milliseconds,
531 };
532}
533function subtractDurations(d1, d0) {
534 return {
535 years: d1.years - d0.years,
536 months: d1.months - d0.months,
537 days: d1.days - d0.days,
538 milliseconds: d1.milliseconds - d0.milliseconds,
539 };
540}
541function multiplyDuration(d, n) {
542 return {
543 years: d.years * n,
544 months: d.months * n,
545 days: d.days * n,
546 milliseconds: d.milliseconds * n,
547 };
548}
549// Conversions
550// "Rough" because they are based on average-case Gregorian months/years
551function asRoughYears(dur) {
552 return asRoughDays(dur) / 365;
553}
554function asRoughMonths(dur) {
555 return asRoughDays(dur) / 30;
556}
557function asRoughDays(dur) {
558 return asRoughMs(dur) / 864e5;
559}
560function asRoughMinutes(dur) {
561 return asRoughMs(dur) / (1000 * 60);
562}
563function asRoughSeconds(dur) {
564 return asRoughMs(dur) / 1000;
565}
566function asRoughMs(dur) {
567 return dur.years * (365 * 864e5) +
568 dur.months * (30 * 864e5) +
569 dur.days * 864e5 +
570 dur.milliseconds;
571}
572// Advanced Math
573function wholeDivideDurations(numerator, denominator) {
574 let res = null;
575 for (let i = 0; i < INTERNAL_UNITS.length; i += 1) {
576 let unit = INTERNAL_UNITS[i];
577 if (denominator[unit]) {
578 let localRes = numerator[unit] / denominator[unit];
579 if (!isInt(localRes) || (res !== null && res !== localRes)) {
580 return null;
581 }
582 res = localRes;
583 }
584 else if (numerator[unit]) {
585 // needs to divide by something but can't!
586 return null;
587 }
588 }
589 return res;
590}
591function greatestDurationDenominator(dur) {
592 let ms = dur.milliseconds;
593 if (ms) {
594 if (ms % 1000 !== 0) {
595 return { unit: 'millisecond', value: ms };
596 }
597 if (ms % (1000 * 60) !== 0) {
598 return { unit: 'second', value: ms / 1000 };
599 }
600 if (ms % (1000 * 60 * 60) !== 0) {
601 return { unit: 'minute', value: ms / (1000 * 60) };
602 }
603 if (ms) {
604 return { unit: 'hour', value: ms / (1000 * 60 * 60) };
605 }
606 }
607 if (dur.days) {
608 if (dur.specifiedWeeks && dur.days % 7 === 0) {
609 return { unit: 'week', value: dur.days / 7 };
610 }
611 return { unit: 'day', value: dur.days };
612 }
613 if (dur.months) {
614 return { unit: 'month', value: dur.months };
615 }
616 if (dur.years) {
617 return { unit: 'year', value: dur.years };
618 }
619 return { unit: 'millisecond', value: 0 };
620}
621
622// TODO: new util arrayify?
623function removeExact(array, exactVal) {
624 let removeCnt = 0;
625 let i = 0;
626 while (i < array.length) {
627 if (array[i] === exactVal) {
628 array.splice(i, 1);
629 removeCnt += 1;
630 }
631 else {
632 i += 1;
633 }
634 }
635 return removeCnt;
636}
637function isArraysEqual(a0, a1, equalityFunc) {
638 if (a0 === a1) {
639 return true;
640 }
641 let len = a0.length;
642 let i;
643 if (len !== a1.length) { // not array? or not same length?
644 return false;
645 }
646 for (i = 0; i < len; i += 1) {
647 if (!(equalityFunc ? equalityFunc(a0[i], a1[i]) : a0[i] === a1[i])) {
648 return false;
649 }
650 }
651 return true;
652}
653
654const DAY_IDS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
655// Adding
656function addWeeks(m, n) {
657 let a = dateToUtcArray(m);
658 a[2] += n * 7;
659 return arrayToUtcDate(a);
660}
661function addDays(m, n) {
662 let a = dateToUtcArray(m);
663 a[2] += n;
664 return arrayToUtcDate(a);
665}
666function addMs(m, n) {
667 let a = dateToUtcArray(m);
668 a[6] += n;
669 return arrayToUtcDate(a);
670}
671// Diffing (all return floats)
672// TODO: why not use ranges?
673function diffWeeks(m0, m1) {
674 return diffDays(m0, m1) / 7;
675}
676function diffDays(m0, m1) {
677 return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60 * 24);
678}
679function diffHours(m0, m1) {
680 return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60);
681}
682function diffMinutes(m0, m1) {
683 return (m1.valueOf() - m0.valueOf()) / (1000 * 60);
684}
685function diffSeconds(m0, m1) {
686 return (m1.valueOf() - m0.valueOf()) / 1000;
687}
688function diffDayAndTime(m0, m1) {
689 let m0day = startOfDay(m0);
690 let m1day = startOfDay(m1);
691 return {
692 years: 0,
693 months: 0,
694 days: Math.round(diffDays(m0day, m1day)),
695 milliseconds: (m1.valueOf() - m1day.valueOf()) - (m0.valueOf() - m0day.valueOf()),
696 };
697}
698// Diffing Whole Units
699function diffWholeWeeks(m0, m1) {
700 let d = diffWholeDays(m0, m1);
701 if (d !== null && d % 7 === 0) {
702 return d / 7;
703 }
704 return null;
705}
706function diffWholeDays(m0, m1) {
707 if (timeAsMs(m0) === timeAsMs(m1)) {
708 return Math.round(diffDays(m0, m1));
709 }
710 return null;
711}
712// Start-Of
713function startOfDay(m) {
714 return arrayToUtcDate([
715 m.getUTCFullYear(),
716 m.getUTCMonth(),
717 m.getUTCDate(),
718 ]);
719}
720function startOfHour(m) {
721 return arrayToUtcDate([
722 m.getUTCFullYear(),
723 m.getUTCMonth(),
724 m.getUTCDate(),
725 m.getUTCHours(),
726 ]);
727}
728function startOfMinute(m) {
729 return arrayToUtcDate([
730 m.getUTCFullYear(),
731 m.getUTCMonth(),
732 m.getUTCDate(),
733 m.getUTCHours(),
734 m.getUTCMinutes(),
735 ]);
736}
737function startOfSecond(m) {
738 return arrayToUtcDate([
739 m.getUTCFullYear(),
740 m.getUTCMonth(),
741 m.getUTCDate(),
742 m.getUTCHours(),
743 m.getUTCMinutes(),
744 m.getUTCSeconds(),
745 ]);
746}
747// Week Computation
748function weekOfYear(marker, dow, doy) {
749 let y = marker.getUTCFullYear();
750 let w = weekOfGivenYear(marker, y, dow, doy);
751 if (w < 1) {
752 return weekOfGivenYear(marker, y - 1, dow, doy);
753 }
754 let nextW = weekOfGivenYear(marker, y + 1, dow, doy);
755 if (nextW >= 1) {
756 return Math.min(w, nextW);
757 }
758 return w;
759}
760function weekOfGivenYear(marker, year, dow, doy) {
761 let firstWeekStart = arrayToUtcDate([year, 0, 1 + firstWeekOffset(year, dow, doy)]);
762 let dayStart = startOfDay(marker);
763 let days = Math.round(diffDays(firstWeekStart, dayStart));
764 return Math.floor(days / 7) + 1; // zero-indexed
765}
766// start-of-first-week - start-of-year
767function firstWeekOffset(year, dow, doy) {
768 // first-week day -- which january is always in the first week (4 for iso, 1 for other)
769 let fwd = 7 + dow - doy;
770 // first-week day local weekday -- which local weekday is fwd
771 let fwdlw = (7 + arrayToUtcDate([year, 0, fwd]).getUTCDay() - dow) % 7;
772 return -fwdlw + fwd - 1;
773}
774// Array Conversion
775function dateToLocalArray(date) {
776 return [
777 date.getFullYear(),
778 date.getMonth(),
779 date.getDate(),
780 date.getHours(),
781 date.getMinutes(),
782 date.getSeconds(),
783 date.getMilliseconds(),
784 ];
785}
786function arrayToLocalDate(a) {
787 return new Date(a[0], a[1] || 0, a[2] == null ? 1 : a[2], // day of month
788 a[3] || 0, a[4] || 0, a[5] || 0);
789}
790function dateToUtcArray(date) {
791 return [
792 date.getUTCFullYear(),
793 date.getUTCMonth(),
794 date.getUTCDate(),
795 date.getUTCHours(),
796 date.getUTCMinutes(),
797 date.getUTCSeconds(),
798 date.getUTCMilliseconds(),
799 ];
800}
801function arrayToUtcDate(a) {
802 // according to web standards (and Safari), a month index is required.
803 // massage if only given a year.
804 if (a.length === 1) {
805 a = a.concat([0]);
806 }
807 return new Date(Date.UTC(...a));
808}
809// Other Utils
810function isValidDate(m) {
811 return !isNaN(m.valueOf());
812}
813function timeAsMs(m) {
814 return m.getUTCHours() * 1000 * 60 * 60 +
815 m.getUTCMinutes() * 1000 * 60 +
816 m.getUTCSeconds() * 1000 +
817 m.getUTCMilliseconds();
818}
819
820// timeZoneOffset is in minutes
821function buildIsoString(marker, timeZoneOffset, stripZeroTime = false) {
822 let s = marker.toISOString();
823 s = s.replace('.000', '');
824 if (stripZeroTime) {
825 s = s.replace('T00:00:00Z', '');
826 }
827 if (s.length > 10) { // time part wasn't stripped, can add timezone info
828 if (timeZoneOffset == null) {
829 s = s.replace('Z', '');
830 }
831 else if (timeZoneOffset !== 0) {
832 s = s.replace('Z', formatTimeZoneOffset(timeZoneOffset, true));
833 }
834 // otherwise, its UTC-0 and we want to keep the Z
835 }
836 return s;
837}
838// formats the date, but with no time part
839// TODO: somehow merge with buildIsoString and stripZeroTime
840// TODO: rename. omit "string"
841function formatDayString(marker) {
842 return marker.toISOString().replace(/T.*$/, '');
843}
844function formatIsoMonthStr(marker) {
845 return marker.toISOString().match(/^\d{4}-\d{2}/)[0];
846}
847// TODO: use Date::toISOString and use everything after the T?
848function formatIsoTimeString(marker) {
849 return padStart(marker.getUTCHours(), 2) + ':' +
850 padStart(marker.getUTCMinutes(), 2) + ':' +
851 padStart(marker.getUTCSeconds(), 2);
852}
853function formatTimeZoneOffset(minutes, doIso = false) {
854 let sign = minutes < 0 ? '-' : '+';
855 let abs = Math.abs(minutes);
856 let hours = Math.floor(abs / 60);
857 let mins = Math.round(abs % 60);
858 if (doIso) {
859 return `${sign + padStart(hours, 2)}:${padStart(mins, 2)}`;
860 }
861 return `GMT${sign}${hours}${mins ? `:${padStart(mins, 2)}` : ''}`;
862}
863
864function memoize(workerFunc, resEquality, teardownFunc) {
865 let currentArgs;
866 let currentRes;
867 return function (...newArgs) {
868 if (!currentArgs) {
869 currentRes = workerFunc.apply(this, newArgs);
870 }
871 else if (!isArraysEqual(currentArgs, newArgs)) {
872 if (teardownFunc) {
873 teardownFunc(currentRes);
874 }
875 let res = workerFunc.apply(this, newArgs);
876 if (!resEquality || !resEquality(res, currentRes)) {
877 currentRes = res;
878 }
879 }
880 currentArgs = newArgs;
881 return currentRes;
882 };
883}
884function memoizeObjArg(workerFunc, resEquality, teardownFunc) {
885 let currentArg;
886 let currentRes;
887 return (newArg) => {
888 if (!currentArg) {
889 currentRes = workerFunc.call(this, newArg);
890 }
891 else if (!isPropsEqual(currentArg, newArg)) {
892 if (teardownFunc) {
893 teardownFunc(currentRes);
894 }
895 let res = workerFunc.call(this, newArg);
896 if (!resEquality || !resEquality(res, currentRes)) {
897 currentRes = res;
898 }
899 }
900 currentArg = newArg;
901 return currentRes;
902 };
903}
904function memoizeArraylike(// used at all?
905workerFunc, resEquality, teardownFunc) {
906 let currentArgSets = [];
907 let currentResults = [];
908 return (newArgSets) => {
909 let currentLen = currentArgSets.length;
910 let newLen = newArgSets.length;
911 let i = 0;
912 for (; i < currentLen; i += 1) {
913 if (!newArgSets[i]) { // one of the old sets no longer exists
914 if (teardownFunc) {
915 teardownFunc(currentResults[i]);
916 }
917 }
918 else if (!isArraysEqual(currentArgSets[i], newArgSets[i])) {
919 if (teardownFunc) {
920 teardownFunc(currentResults[i]);
921 }
922 let res = workerFunc.apply(this, newArgSets[i]);
923 if (!resEquality || !resEquality(res, currentResults[i])) {
924 currentResults[i] = res;
925 }
926 }
927 }
928 for (; i < newLen; i += 1) {
929 currentResults[i] = workerFunc.apply(this, newArgSets[i]);
930 }
931 currentArgSets = newArgSets;
932 currentResults.splice(newLen); // remove excess
933 return currentResults;
934 };
935}
936function memoizeHashlike(workerFunc, resEquality, teardownFunc) {
937 let currentArgHash = {};
938 let currentResHash = {};
939 return (newArgHash) => {
940 let newResHash = {};
941 for (let key in newArgHash) {
942 if (!currentResHash[key]) {
943 newResHash[key] = workerFunc.apply(this, newArgHash[key]);
944 }
945 else if (!isArraysEqual(currentArgHash[key], newArgHash[key])) {
946 if (teardownFunc) {
947 teardownFunc(currentResHash[key]);
948 }
949 let res = workerFunc.apply(this, newArgHash[key]);
950 newResHash[key] = (resEquality && resEquality(res, currentResHash[key]))
951 ? currentResHash[key]
952 : res;
953 }
954 else {
955 newResHash[key] = currentResHash[key];
956 }
957 }
958 currentArgHash = newArgHash;
959 currentResHash = newResHash;
960 return newResHash;
961 };
962}
963
964const EXTENDED_SETTINGS_AND_SEVERITIES = {
965 week: 3,
966 separator: 0,
967 omitZeroMinute: 0,
968 meridiem: 0,
969 omitCommas: 0,
970};
971const STANDARD_DATE_PROP_SEVERITIES = {
972 timeZoneName: 7,
973 era: 6,
974 year: 5,
975 month: 4,
976 day: 2,
977 weekday: 2,
978 hour: 1,
979 minute: 1,
980 second: 1,
981};
982const MERIDIEM_RE = /\s*([ap])\.?m\.?/i; // eats up leading spaces too
983const COMMA_RE = /,/g; // we need re for globalness
984const MULTI_SPACE_RE = /\s+/g;
985const LTR_RE = /\u200e/g; // control character
986const UTC_RE = /UTC|GMT/;
987class NativeFormatter {
988 constructor(formatSettings) {
989 let standardDateProps = {};
990 let extendedSettings = {};
991 let severity = 0;
992 for (let name in formatSettings) {
993 if (name in EXTENDED_SETTINGS_AND_SEVERITIES) {
994 extendedSettings[name] = formatSettings[name];
995 severity = Math.max(EXTENDED_SETTINGS_AND_SEVERITIES[name], severity);
996 }
997 else {
998 standardDateProps[name] = formatSettings[name];
999 if (name in STANDARD_DATE_PROP_SEVERITIES) { // TODO: what about hour12? no severity
1000 severity = Math.max(STANDARD_DATE_PROP_SEVERITIES[name], severity);
1001 }
1002 }
1003 }
1004 this.standardDateProps = standardDateProps;
1005 this.extendedSettings = extendedSettings;
1006 this.severity = severity;
1007 this.buildFormattingFunc = memoize(buildFormattingFunc);
1008 }
1009 format(date, context) {
1010 return this.buildFormattingFunc(this.standardDateProps, this.extendedSettings, context)(date);
1011 }
1012 formatRange(start, end, context, betterDefaultSeparator) {
1013 let { standardDateProps, extendedSettings } = this;
1014 let diffSeverity = computeMarkerDiffSeverity(start.marker, end.marker, context.calendarSystem);
1015 if (!diffSeverity) {
1016 return this.format(start, context);
1017 }
1018 let biggestUnitForPartial = diffSeverity;
1019 if (biggestUnitForPartial > 1 && // the two dates are different in a way that's larger scale than time
1020 (standardDateProps.year === 'numeric' || standardDateProps.year === '2-digit') &&
1021 (standardDateProps.month === 'numeric' || standardDateProps.month === '2-digit') &&
1022 (standardDateProps.day === 'numeric' || standardDateProps.day === '2-digit')) {
1023 biggestUnitForPartial = 1; // make it look like the dates are only different in terms of time
1024 }
1025 let full0 = this.format(start, context);
1026 let full1 = this.format(end, context);
1027 if (full0 === full1) {
1028 return full0;
1029 }
1030 let partialDateProps = computePartialFormattingOptions(standardDateProps, biggestUnitForPartial);
1031 let partialFormattingFunc = buildFormattingFunc(partialDateProps, extendedSettings, context);
1032 let partial0 = partialFormattingFunc(start);
1033 let partial1 = partialFormattingFunc(end);
1034 let insertion = findCommonInsertion(full0, partial0, full1, partial1);
1035 let separator = extendedSettings.separator || betterDefaultSeparator || context.defaultSeparator || '';
1036 if (insertion) {
1037 return insertion.before + partial0 + separator + partial1 + insertion.after;
1038 }
1039 return full0 + separator + full1;
1040 }
1041 getLargestUnit() {
1042 switch (this.severity) {
1043 case 7:
1044 case 6:
1045 case 5:
1046 return 'year';
1047 case 4:
1048 return 'month';
1049 case 3:
1050 return 'week';
1051 case 2:
1052 return 'day';
1053 default:
1054 return 'time'; // really?
1055 }
1056 }
1057}
1058function buildFormattingFunc(standardDateProps, extendedSettings, context) {
1059 let standardDatePropCnt = Object.keys(standardDateProps).length;
1060 if (standardDatePropCnt === 1 && standardDateProps.timeZoneName === 'short') {
1061 return (date) => (formatTimeZoneOffset(date.timeZoneOffset));
1062 }
1063 if (standardDatePropCnt === 0 && extendedSettings.week) {
1064 return (date) => (formatWeekNumber(context.computeWeekNumber(date.marker), context.weekText, context.weekTextLong, context.locale, extendedSettings.week));
1065 }
1066 return buildNativeFormattingFunc(standardDateProps, extendedSettings, context);
1067}
1068function buildNativeFormattingFunc(standardDateProps, extendedSettings, context) {
1069 standardDateProps = Object.assign({}, standardDateProps); // copy
1070 extendedSettings = Object.assign({}, extendedSettings); // copy
1071 sanitizeSettings(standardDateProps, extendedSettings);
1072 standardDateProps.timeZone = 'UTC'; // we leverage the only guaranteed timeZone for our UTC markers
1073 let normalFormat = new Intl.DateTimeFormat(context.locale.codes, standardDateProps);
1074 let zeroFormat; // needed?
1075 if (extendedSettings.omitZeroMinute) {
1076 let zeroProps = Object.assign({}, standardDateProps);
1077 delete zeroProps.minute; // seconds and ms were already considered in sanitizeSettings
1078 zeroFormat = new Intl.DateTimeFormat(context.locale.codes, zeroProps);
1079 }
1080 return (date) => {
1081 let { marker } = date;
1082 let format;
1083 if (zeroFormat && !marker.getUTCMinutes()) {
1084 format = zeroFormat;
1085 }
1086 else {
1087 format = normalFormat;
1088 }
1089 let s = format.format(marker);
1090 return postProcess(s, date, standardDateProps, extendedSettings, context);
1091 };
1092}
1093function sanitizeSettings(standardDateProps, extendedSettings) {
1094 // deal with a browser inconsistency where formatting the timezone
1095 // requires that the hour/minute be present.
1096 if (standardDateProps.timeZoneName) {
1097 if (!standardDateProps.hour) {
1098 standardDateProps.hour = '2-digit';
1099 }
1100 if (!standardDateProps.minute) {
1101 standardDateProps.minute = '2-digit';
1102 }
1103 }
1104 // only support short timezone names
1105 if (standardDateProps.timeZoneName === 'long') {
1106 standardDateProps.timeZoneName = 'short';
1107 }
1108 // if requesting to display seconds, MUST display minutes
1109 if (extendedSettings.omitZeroMinute && (standardDateProps.second || standardDateProps.millisecond)) {
1110 delete extendedSettings.omitZeroMinute;
1111 }
1112}
1113function postProcess(s, date, standardDateProps, extendedSettings, context) {
1114 s = s.replace(LTR_RE, ''); // remove left-to-right control chars. do first. good for other regexes
1115 if (standardDateProps.timeZoneName === 'short') {
1116 s = injectTzoStr(s, (context.timeZone === 'UTC' || date.timeZoneOffset == null) ?
1117 'UTC' : // important to normalize for IE, which does "GMT"
1118 formatTimeZoneOffset(date.timeZoneOffset));
1119 }
1120 if (extendedSettings.omitCommas) {
1121 s = s.replace(COMMA_RE, '').trim();
1122 }
1123 if (extendedSettings.omitZeroMinute) {
1124 s = s.replace(':00', ''); // zeroFormat doesn't always achieve this
1125 }
1126 // ^ do anything that might create adjacent spaces before this point,
1127 // because MERIDIEM_RE likes to eat up loading spaces
1128 if (extendedSettings.meridiem === false) {
1129 s = s.replace(MERIDIEM_RE, '').trim();
1130 }
1131 else if (extendedSettings.meridiem === 'narrow') { // a/p
1132 s = s.replace(MERIDIEM_RE, (m0, m1) => m1.toLocaleLowerCase());
1133 }
1134 else if (extendedSettings.meridiem === 'short') { // am/pm
1135 s = s.replace(MERIDIEM_RE, (m0, m1) => `${m1.toLocaleLowerCase()}m`);
1136 }
1137 else if (extendedSettings.meridiem === 'lowercase') { // other meridiem transformers already converted to lowercase
1138 s = s.replace(MERIDIEM_RE, (m0) => m0.toLocaleLowerCase());
1139 }
1140 s = s.replace(MULTI_SPACE_RE, ' ');
1141 s = s.trim();
1142 return s;
1143}
1144function injectTzoStr(s, tzoStr) {
1145 let replaced = false;
1146 s = s.replace(UTC_RE, () => {
1147 replaced = true;
1148 return tzoStr;
1149 });
1150 // IE11 doesn't include UTC/GMT in the original string, so append to end
1151 if (!replaced) {
1152 s += ` ${tzoStr}`;
1153 }
1154 return s;
1155}
1156function formatWeekNumber(num, weekText, weekTextLong, locale, display) {
1157 let parts = [];
1158 if (display === 'long') {
1159 parts.push(weekTextLong);
1160 }
1161 else if (display === 'short' || display === 'narrow') {
1162 parts.push(weekText);
1163 }
1164 if (display === 'long' || display === 'short') {
1165 parts.push(' ');
1166 }
1167 parts.push(locale.simpleNumberFormat.format(num));
1168 if (locale.options.direction === 'rtl') { // TODO: use control characters instead?
1169 parts.reverse();
1170 }
1171 return parts.join('');
1172}
1173// Range Formatting Utils
1174// 0 = exactly the same
1175// 1 = different by time
1176// and bigger
1177function computeMarkerDiffSeverity(d0, d1, ca) {
1178 if (ca.getMarkerYear(d0) !== ca.getMarkerYear(d1)) {
1179 return 5;
1180 }
1181 if (ca.getMarkerMonth(d0) !== ca.getMarkerMonth(d1)) {
1182 return 4;
1183 }
1184 if (ca.getMarkerDay(d0) !== ca.getMarkerDay(d1)) {
1185 return 2;
1186 }
1187 if (timeAsMs(d0) !== timeAsMs(d1)) {
1188 return 1;
1189 }
1190 return 0;
1191}
1192function computePartialFormattingOptions(options, biggestUnit) {
1193 let partialOptions = {};
1194 for (let name in options) {
1195 if (!(name in STANDARD_DATE_PROP_SEVERITIES) || // not a date part prop (like timeZone)
1196 STANDARD_DATE_PROP_SEVERITIES[name] <= biggestUnit) {
1197 partialOptions[name] = options[name];
1198 }
1199 }
1200 return partialOptions;
1201}
1202function findCommonInsertion(full0, partial0, full1, partial1) {
1203 let i0 = 0;
1204 while (i0 < full0.length) {
1205 let found0 = full0.indexOf(partial0, i0);
1206 if (found0 === -1) {
1207 break;
1208 }
1209 let before0 = full0.substr(0, found0);
1210 i0 = found0 + partial0.length;
1211 let after0 = full0.substr(i0);
1212 let i1 = 0;
1213 while (i1 < full1.length) {
1214 let found1 = full1.indexOf(partial1, i1);
1215 if (found1 === -1) {
1216 break;
1217 }
1218 let before1 = full1.substr(0, found1);
1219 i1 = found1 + partial1.length;
1220 let after1 = full1.substr(i1);
1221 if (before0 === before1 && after0 === after1) {
1222 return {
1223 before: before0,
1224 after: after0,
1225 };
1226 }
1227 }
1228 }
1229 return null;
1230}
1231
1232function expandZonedMarker(dateInfo, calendarSystem) {
1233 let a = calendarSystem.markerToArray(dateInfo.marker);
1234 return {
1235 marker: dateInfo.marker,
1236 timeZoneOffset: dateInfo.timeZoneOffset,
1237 array: a,
1238 year: a[0],
1239 month: a[1],
1240 day: a[2],
1241 hour: a[3],
1242 minute: a[4],
1243 second: a[5],
1244 millisecond: a[6],
1245 };
1246}
1247
1248function createVerboseFormattingArg(start, end, context, betterDefaultSeparator) {
1249 let startInfo = expandZonedMarker(start, context.calendarSystem);
1250 let endInfo = end ? expandZonedMarker(end, context.calendarSystem) : null;
1251 return {
1252 date: startInfo,
1253 start: startInfo,
1254 end: endInfo,
1255 timeZone: context.timeZone,
1256 localeCodes: context.locale.codes,
1257 defaultSeparator: betterDefaultSeparator || context.defaultSeparator,
1258 };
1259}
1260
1261/*
1262TODO: fix the terminology of "formatter" vs "formatting func"
1263*/
1264/*
1265At the time of instantiation, this object does not know which cmd-formatting system it will use.
1266It receives this at the time of formatting, as a setting.
1267*/
1268class CmdFormatter {
1269 constructor(cmdStr) {
1270 this.cmdStr = cmdStr;
1271 }
1272 format(date, context, betterDefaultSeparator) {
1273 return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(date, null, context, betterDefaultSeparator));
1274 }
1275 formatRange(start, end, context, betterDefaultSeparator) {
1276 return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(start, end, context, betterDefaultSeparator));
1277 }
1278}
1279
1280class FuncFormatter {
1281 constructor(func) {
1282 this.func = func;
1283 }
1284 format(date, context, betterDefaultSeparator) {
1285 return this.func(createVerboseFormattingArg(date, null, context, betterDefaultSeparator));
1286 }
1287 formatRange(start, end, context, betterDefaultSeparator) {
1288 return this.func(createVerboseFormattingArg(start, end, context, betterDefaultSeparator));
1289 }
1290}
1291
1292function createFormatter(input) {
1293 if (typeof input === 'object' && input) { // non-null object
1294 return new NativeFormatter(input);
1295 }
1296 if (typeof input === 'string') {
1297 return new CmdFormatter(input);
1298 }
1299 if (typeof input === 'function') {
1300 return new FuncFormatter(input);
1301 }
1302 return null;
1303}
1304
1305// base options
1306// ------------
1307const BASE_OPTION_REFINERS = {
1308 navLinkDayClick: identity,
1309 navLinkWeekClick: identity,
1310 duration: createDuration,
1311 bootstrapFontAwesome: identity,
1312 buttonIcons: identity,
1313 customButtons: identity,
1314 defaultAllDayEventDuration: createDuration,
1315 defaultTimedEventDuration: createDuration,
1316 nextDayThreshold: createDuration,
1317 scrollTime: createDuration,
1318 scrollTimeReset: Boolean,
1319 slotMinTime: createDuration,
1320 slotMaxTime: createDuration,
1321 dayPopoverFormat: createFormatter,
1322 slotDuration: createDuration,
1323 snapDuration: createDuration,
1324 headerToolbar: identity,
1325 footerToolbar: identity,
1326 defaultRangeSeparator: String,
1327 titleRangeSeparator: String,
1328 forceEventDuration: Boolean,
1329 dayHeaders: Boolean,
1330 dayHeaderFormat: createFormatter,
1331 dayHeaderClassNames: identity,
1332 dayHeaderContent: identity,
1333 dayHeaderDidMount: identity,
1334 dayHeaderWillUnmount: identity,
1335 dayCellClassNames: identity,
1336 dayCellContent: identity,
1337 dayCellDidMount: identity,
1338 dayCellWillUnmount: identity,
1339 initialView: String,
1340 aspectRatio: Number,
1341 weekends: Boolean,
1342 weekNumberCalculation: identity,
1343 weekNumbers: Boolean,
1344 weekNumberClassNames: identity,
1345 weekNumberContent: identity,
1346 weekNumberDidMount: identity,
1347 weekNumberWillUnmount: identity,
1348 editable: Boolean,
1349 viewClassNames: identity,
1350 viewDidMount: identity,
1351 viewWillUnmount: identity,
1352 nowIndicator: Boolean,
1353 nowIndicatorClassNames: identity,
1354 nowIndicatorContent: identity,
1355 nowIndicatorDidMount: identity,
1356 nowIndicatorWillUnmount: identity,
1357 showNonCurrentDates: Boolean,
1358 lazyFetching: Boolean,
1359 startParam: String,
1360 endParam: String,
1361 timeZoneParam: String,
1362 timeZone: String,
1363 locales: identity,
1364 locale: identity,
1365 themeSystem: String,
1366 dragRevertDuration: Number,
1367 dragScroll: Boolean,
1368 allDayMaintainDuration: Boolean,
1369 unselectAuto: Boolean,
1370 dropAccept: identity,
1371 eventOrder: parseFieldSpecs,
1372 eventOrderStrict: Boolean,
1373 handleWindowResize: Boolean,
1374 windowResizeDelay: Number,
1375 longPressDelay: Number,
1376 eventDragMinDistance: Number,
1377 expandRows: Boolean,
1378 height: identity,
1379 contentHeight: identity,
1380 direction: String,
1381 weekNumberFormat: createFormatter,
1382 eventResizableFromStart: Boolean,
1383 displayEventTime: Boolean,
1384 displayEventEnd: Boolean,
1385 weekText: String,
1386 weekTextLong: String,
1387 progressiveEventRendering: Boolean,
1388 businessHours: identity,
1389 initialDate: identity,
1390 now: identity,
1391 eventDataTransform: identity,
1392 stickyHeaderDates: identity,
1393 stickyFooterScrollbar: identity,
1394 viewHeight: identity,
1395 defaultAllDay: Boolean,
1396 eventSourceFailure: identity,
1397 eventSourceSuccess: identity,
1398 eventDisplay: String,
1399 eventStartEditable: Boolean,
1400 eventDurationEditable: Boolean,
1401 eventOverlap: identity,
1402 eventConstraint: identity,
1403 eventAllow: identity,
1404 eventBackgroundColor: String,
1405 eventBorderColor: String,
1406 eventTextColor: String,
1407 eventColor: String,
1408 eventClassNames: identity,
1409 eventContent: identity,
1410 eventDidMount: identity,
1411 eventWillUnmount: identity,
1412 selectConstraint: identity,
1413 selectOverlap: identity,
1414 selectAllow: identity,
1415 droppable: Boolean,
1416 unselectCancel: String,
1417 slotLabelFormat: identity,
1418 slotLaneClassNames: identity,
1419 slotLaneContent: identity,
1420 slotLaneDidMount: identity,
1421 slotLaneWillUnmount: identity,
1422 slotLabelClassNames: identity,
1423 slotLabelContent: identity,
1424 slotLabelDidMount: identity,
1425 slotLabelWillUnmount: identity,
1426 dayMaxEvents: identity,
1427 dayMaxEventRows: identity,
1428 dayMinWidth: Number,
1429 slotLabelInterval: createDuration,
1430 allDayText: String,
1431 allDayClassNames: identity,
1432 allDayContent: identity,
1433 allDayDidMount: identity,
1434 allDayWillUnmount: identity,
1435 slotMinWidth: Number,
1436 navLinks: Boolean,
1437 eventTimeFormat: createFormatter,
1438 rerenderDelay: Number,
1439 moreLinkText: identity,
1440 moreLinkHint: identity,
1441 selectMinDistance: Number,
1442 selectable: Boolean,
1443 selectLongPressDelay: Number,
1444 eventLongPressDelay: Number,
1445 selectMirror: Boolean,
1446 eventMaxStack: Number,
1447 eventMinHeight: Number,
1448 eventMinWidth: Number,
1449 eventShortHeight: Number,
1450 slotEventOverlap: Boolean,
1451 plugins: identity,
1452 firstDay: Number,
1453 dayCount: Number,
1454 dateAlignment: String,
1455 dateIncrement: createDuration,
1456 hiddenDays: identity,
1457 fixedWeekCount: Boolean,
1458 validRange: identity,
1459 visibleRange: identity,
1460 titleFormat: identity,
1461 eventInteractive: Boolean,
1462 // only used by list-view, but languages define the value, so we need it in base options
1463 noEventsText: String,
1464 viewHint: identity,
1465 navLinkHint: identity,
1466 closeHint: String,
1467 timeHint: String,
1468 eventHint: String,
1469 moreLinkClick: identity,
1470 moreLinkClassNames: identity,
1471 moreLinkContent: identity,
1472 moreLinkDidMount: identity,
1473 moreLinkWillUnmount: identity,
1474 monthStartFormat: createFormatter,
1475 // for connectors
1476 // (can't be part of plugin system b/c must be provided at runtime)
1477 handleCustomRendering: identity,
1478 customRenderingMetaMap: identity,
1479 customRenderingReplaces: Boolean,
1480};
1481// do NOT give a type here. need `typeof BASE_OPTION_DEFAULTS` to give real results.
1482// raw values.
1483const BASE_OPTION_DEFAULTS = {
1484 eventDisplay: 'auto',
1485 defaultRangeSeparator: ' - ',
1486 titleRangeSeparator: ' \u2013 ',
1487 defaultTimedEventDuration: '01:00:00',
1488 defaultAllDayEventDuration: { day: 1 },
1489 forceEventDuration: false,
1490 nextDayThreshold: '00:00:00',
1491 dayHeaders: true,
1492 initialView: '',
1493 aspectRatio: 1.35,
1494 headerToolbar: {
1495 start: 'title',
1496 center: '',
1497 end: 'today prev,next',
1498 },
1499 weekends: true,
1500 weekNumbers: false,
1501 weekNumberCalculation: 'local',
1502 editable: false,
1503 nowIndicator: false,
1504 scrollTime: '06:00:00',
1505 scrollTimeReset: true,
1506 slotMinTime: '00:00:00',
1507 slotMaxTime: '24:00:00',
1508 showNonCurrentDates: true,
1509 lazyFetching: true,
1510 startParam: 'start',
1511 endParam: 'end',
1512 timeZoneParam: 'timeZone',
1513 timeZone: 'local',
1514 locales: [],
1515 locale: '',
1516 themeSystem: 'standard',
1517 dragRevertDuration: 500,
1518 dragScroll: true,
1519 allDayMaintainDuration: false,
1520 unselectAuto: true,
1521 dropAccept: '*',
1522 eventOrder: 'start,-duration,allDay,title',
1523 dayPopoverFormat: { month: 'long', day: 'numeric', year: 'numeric' },
1524 handleWindowResize: true,
1525 windowResizeDelay: 100,
1526 longPressDelay: 1000,
1527 eventDragMinDistance: 5,
1528 expandRows: false,
1529 navLinks: false,
1530 selectable: false,
1531 eventMinHeight: 15,
1532 eventMinWidth: 30,
1533 eventShortHeight: 30,
1534 monthStartFormat: { month: 'long', day: 'numeric' },
1535};
1536// calendar listeners
1537// ------------------
1538const CALENDAR_LISTENER_REFINERS = {
1539 datesSet: identity,
1540 eventsSet: identity,
1541 eventAdd: identity,
1542 eventChange: identity,
1543 eventRemove: identity,
1544 windowResize: identity,
1545 eventClick: identity,
1546 eventMouseEnter: identity,
1547 eventMouseLeave: identity,
1548 select: identity,
1549 unselect: identity,
1550 loading: identity,
1551 // internal
1552 _unmount: identity,
1553 _beforeprint: identity,
1554 _afterprint: identity,
1555 _noEventDrop: identity,
1556 _noEventResize: identity,
1557 _resize: identity,
1558 _scrollRequest: identity,
1559};
1560// calendar-specific options
1561// -------------------------
1562const CALENDAR_OPTION_REFINERS = {
1563 buttonText: identity,
1564 buttonHints: identity,
1565 views: identity,
1566 plugins: identity,
1567 initialEvents: identity,
1568 events: identity,
1569 eventSources: identity,
1570};
1571const COMPLEX_OPTION_COMPARATORS = {
1572 headerToolbar: isMaybeObjectsEqual,
1573 footerToolbar: isMaybeObjectsEqual,
1574 buttonText: isMaybeObjectsEqual,
1575 buttonHints: isMaybeObjectsEqual,
1576 buttonIcons: isMaybeObjectsEqual,
1577 dateIncrement: isMaybeObjectsEqual,
1578 plugins: isMaybeArraysEqual,
1579 events: isMaybeArraysEqual,
1580 eventSources: isMaybeArraysEqual,
1581 ['resources']: isMaybeArraysEqual,
1582};
1583function isMaybeObjectsEqual(a, b) {
1584 if (typeof a === 'object' && typeof b === 'object' && a && b) { // both non-null objects
1585 return isPropsEqual(a, b);
1586 }
1587 return a === b;
1588}
1589function isMaybeArraysEqual(a, b) {
1590 if (Array.isArray(a) && Array.isArray(b)) {
1591 return isArraysEqual(a, b);
1592 }
1593 return a === b;
1594}
1595// view-specific options
1596// ---------------------
1597const VIEW_OPTION_REFINERS = {
1598 type: String,
1599 component: identity,
1600 buttonText: String,
1601 buttonTextKey: String,
1602 dateProfileGeneratorClass: identity,
1603 usesMinMaxTime: Boolean,
1604 classNames: identity,
1605 content: identity,
1606 didMount: identity,
1607 willUnmount: identity,
1608};
1609// util funcs
1610// ----------------------------------------------------------------------------------------------------
1611function mergeRawOptions(optionSets) {
1612 return mergeProps(optionSets, COMPLEX_OPTION_COMPARATORS);
1613}
1614function refineProps(input, refiners) {
1615 let refined = {};
1616 let extra = {};
1617 for (let propName in refiners) {
1618 if (propName in input) {
1619 refined[propName] = refiners[propName](input[propName]);
1620 }
1621 }
1622 for (let propName in input) {
1623 if (!(propName in refiners)) {
1624 extra[propName] = input[propName];
1625 }
1626 }
1627 return { refined, extra };
1628}
1629function identity(raw) {
1630 return raw;
1631}
1632
1633const { hasOwnProperty } = Object.prototype;
1634// Merges an array of objects into a single object.
1635// The second argument allows for an array of property names who's object values will be merged together.
1636function mergeProps(propObjs, complexPropsMap) {
1637 let dest = {};
1638 if (complexPropsMap) {
1639 for (let name in complexPropsMap) {
1640 if (complexPropsMap[name] === isMaybeObjectsEqual) { // implies that it's object-mergeable
1641 let complexObjs = [];
1642 // collect the trailing object values, stopping when a non-object is discovered
1643 for (let i = propObjs.length - 1; i >= 0; i -= 1) {
1644 let val = propObjs[i][name];
1645 if (typeof val === 'object' && val) { // non-null object
1646 complexObjs.unshift(val);
1647 }
1648 else if (val !== undefined) {
1649 dest[name] = val; // if there were no objects, this value will be used
1650 break;
1651 }
1652 }
1653 // if the trailing values were objects, use the merged value
1654 if (complexObjs.length) {
1655 dest[name] = mergeProps(complexObjs);
1656 }
1657 }
1658 }
1659 }
1660 // copy values into the destination, going from last to first
1661 for (let i = propObjs.length - 1; i >= 0; i -= 1) {
1662 let props = propObjs[i];
1663 for (let name in props) {
1664 if (!(name in dest)) { // if already assigned by previous props or complex props, don't reassign
1665 dest[name] = props[name];
1666 }
1667 }
1668 }
1669 return dest;
1670}
1671function filterHash(hash, func) {
1672 let filtered = {};
1673 for (let key in hash) {
1674 if (func(hash[key], key)) {
1675 filtered[key] = hash[key];
1676 }
1677 }
1678 return filtered;
1679}
1680function mapHash(hash, func) {
1681 let newHash = {};
1682 for (let key in hash) {
1683 newHash[key] = func(hash[key], key);
1684 }
1685 return newHash;
1686}
1687function arrayToHash(a) {
1688 let hash = {};
1689 for (let item of a) {
1690 hash[item] = true;
1691 }
1692 return hash;
1693}
1694// TODO: reassess browser support
1695// https://caniuse.com/?search=object.values
1696function hashValuesToArray(obj) {
1697 let a = [];
1698 for (let key in obj) {
1699 a.push(obj[key]);
1700 }
1701 return a;
1702}
1703function isPropsEqual(obj0, obj1) {
1704 if (obj0 === obj1) {
1705 return true;
1706 }
1707 for (let key in obj0) {
1708 if (hasOwnProperty.call(obj0, key)) {
1709 if (!(key in obj1)) {
1710 return false;
1711 }
1712 }
1713 }
1714 for (let key in obj1) {
1715 if (hasOwnProperty.call(obj1, key)) {
1716 if (obj0[key] !== obj1[key]) {
1717 return false;
1718 }
1719 }
1720 }
1721 return true;
1722}
1723const HANDLER_RE = /^on[A-Z]/;
1724function isNonHandlerPropsEqual(obj0, obj1) {
1725 const keys = getUnequalProps(obj0, obj1);
1726 for (let key of keys) {
1727 if (!HANDLER_RE.test(key)) {
1728 return false;
1729 }
1730 }
1731 return true;
1732}
1733function getUnequalProps(obj0, obj1) {
1734 let keys = [];
1735 for (let key in obj0) {
1736 if (hasOwnProperty.call(obj0, key)) {
1737 if (!(key in obj1)) {
1738 keys.push(key);
1739 }
1740 }
1741 }
1742 for (let key in obj1) {
1743 if (hasOwnProperty.call(obj1, key)) {
1744 if (obj0[key] !== obj1[key]) {
1745 keys.push(key);
1746 }
1747 }
1748 }
1749 return keys;
1750}
1751function compareObjs(oldProps, newProps, equalityFuncs = {}) {
1752 if (oldProps === newProps) {
1753 return true;
1754 }
1755 for (let key in newProps) {
1756 if (key in oldProps && isObjValsEqual(oldProps[key], newProps[key], equalityFuncs[key])) ;
1757 else {
1758 return false;
1759 }
1760 }
1761 // check for props that were omitted in the new
1762 for (let key in oldProps) {
1763 if (!(key in newProps)) {
1764 return false;
1765 }
1766 }
1767 return true;
1768}
1769/*
1770assumed "true" equality for handler names like "onReceiveSomething"
1771*/
1772function isObjValsEqual(val0, val1, comparator) {
1773 if (val0 === val1 || comparator === true) {
1774 return true;
1775 }
1776 if (comparator) {
1777 return comparator(val0, val1);
1778 }
1779 return false;
1780}
1781function collectFromHash(hash, startIndex = 0, endIndex, step = 1) {
1782 let res = [];
1783 if (endIndex == null) {
1784 endIndex = Object.keys(hash).length;
1785 }
1786 for (let i = startIndex; i < endIndex; i += step) {
1787 let val = hash[i];
1788 if (val !== undefined) { // will disregard undefined for sparse arrays
1789 res.push(val);
1790 }
1791 }
1792 return res;
1793}
1794
1795let calendarSystemClassMap = {};
1796function registerCalendarSystem(name, theClass) {
1797 calendarSystemClassMap[name] = theClass;
1798}
1799function createCalendarSystem(name) {
1800 return new calendarSystemClassMap[name]();
1801}
1802class GregorianCalendarSystem {
1803 getMarkerYear(d) {
1804 return d.getUTCFullYear();
1805 }
1806 getMarkerMonth(d) {
1807 return d.getUTCMonth();
1808 }
1809 getMarkerDay(d) {
1810 return d.getUTCDate();
1811 }
1812 arrayToMarker(arr) {
1813 return arrayToUtcDate(arr);
1814 }
1815 markerToArray(marker) {
1816 return dateToUtcArray(marker);
1817 }
1818}
1819registerCalendarSystem('gregory', GregorianCalendarSystem);
1820
1821const ISO_RE = /^\s*(\d{4})(-?(\d{2})(-?(\d{2})([T ](\d{2}):?(\d{2})(:?(\d{2})(\.(\d+))?)?(Z|(([-+])(\d{2})(:?(\d{2}))?))?)?)?)?$/;
1822function parse(str) {
1823 let m = ISO_RE.exec(str);
1824 if (m) {
1825 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));
1826 if (isValidDate(marker)) {
1827 let timeZoneOffset = null;
1828 if (m[13]) {
1829 timeZoneOffset = (m[15] === '-' ? -1 : 1) * (Number(m[16] || 0) * 60 +
1830 Number(m[18] || 0));
1831 }
1832 return {
1833 marker,
1834 isTimeUnspecified: !m[6],
1835 timeZoneOffset,
1836 };
1837 }
1838 }
1839 return null;
1840}
1841
1842class DateEnv {
1843 constructor(settings) {
1844 let timeZone = this.timeZone = settings.timeZone;
1845 let isNamedTimeZone = timeZone !== 'local' && timeZone !== 'UTC';
1846 if (settings.namedTimeZoneImpl && isNamedTimeZone) {
1847 this.namedTimeZoneImpl = new settings.namedTimeZoneImpl(timeZone);
1848 }
1849 this.canComputeOffset = Boolean(!isNamedTimeZone || this.namedTimeZoneImpl);
1850 this.calendarSystem = createCalendarSystem(settings.calendarSystem);
1851 this.locale = settings.locale;
1852 this.weekDow = settings.locale.week.dow;
1853 this.weekDoy = settings.locale.week.doy;
1854 if (settings.weekNumberCalculation === 'ISO') {
1855 this.weekDow = 1;
1856 this.weekDoy = 4;
1857 }
1858 if (typeof settings.firstDay === 'number') {
1859 this.weekDow = settings.firstDay;
1860 }
1861 if (typeof settings.weekNumberCalculation === 'function') {
1862 this.weekNumberFunc = settings.weekNumberCalculation;
1863 }
1864 this.weekText = settings.weekText != null ? settings.weekText : settings.locale.options.weekText;
1865 this.weekTextLong = (settings.weekTextLong != null ? settings.weekTextLong : settings.locale.options.weekTextLong) || this.weekText;
1866 this.cmdFormatter = settings.cmdFormatter;
1867 this.defaultSeparator = settings.defaultSeparator;
1868 }
1869 // Creating / Parsing
1870 createMarker(input) {
1871 let meta = this.createMarkerMeta(input);
1872 if (meta === null) {
1873 return null;
1874 }
1875 return meta.marker;
1876 }
1877 createNowMarker() {
1878 if (this.canComputeOffset) {
1879 return this.timestampToMarker(new Date().valueOf());
1880 }
1881 // if we can't compute the current date val for a timezone,
1882 // better to give the current local date vals than UTC
1883 return arrayToUtcDate(dateToLocalArray(new Date()));
1884 }
1885 createMarkerMeta(input) {
1886 if (typeof input === 'string') {
1887 return this.parse(input);
1888 }
1889 let marker = null;
1890 if (typeof input === 'number') {
1891 marker = this.timestampToMarker(input);
1892 }
1893 else if (input instanceof Date) {
1894 input = input.valueOf();
1895 if (!isNaN(input)) {
1896 marker = this.timestampToMarker(input);
1897 }
1898 }
1899 else if (Array.isArray(input)) {
1900 marker = arrayToUtcDate(input);
1901 }
1902 if (marker === null || !isValidDate(marker)) {
1903 return null;
1904 }
1905 return { marker, isTimeUnspecified: false, forcedTzo: null };
1906 }
1907 parse(s) {
1908 let parts = parse(s);
1909 if (parts === null) {
1910 return null;
1911 }
1912 let { marker } = parts;
1913 let forcedTzo = null;
1914 if (parts.timeZoneOffset !== null) {
1915 if (this.canComputeOffset) {
1916 marker = this.timestampToMarker(marker.valueOf() - parts.timeZoneOffset * 60 * 1000);
1917 }
1918 else {
1919 forcedTzo = parts.timeZoneOffset;
1920 }
1921 }
1922 return { marker, isTimeUnspecified: parts.isTimeUnspecified, forcedTzo };
1923 }
1924 // Accessors
1925 getYear(marker) {
1926 return this.calendarSystem.getMarkerYear(marker);
1927 }
1928 getMonth(marker) {
1929 return this.calendarSystem.getMarkerMonth(marker);
1930 }
1931 getDay(marker) {
1932 return this.calendarSystem.getMarkerDay(marker);
1933 }
1934 // Adding / Subtracting
1935 add(marker, dur) {
1936 let a = this.calendarSystem.markerToArray(marker);
1937 a[0] += dur.years;
1938 a[1] += dur.months;
1939 a[2] += dur.days;
1940 a[6] += dur.milliseconds;
1941 return this.calendarSystem.arrayToMarker(a);
1942 }
1943 subtract(marker, dur) {
1944 let a = this.calendarSystem.markerToArray(marker);
1945 a[0] -= dur.years;
1946 a[1] -= dur.months;
1947 a[2] -= dur.days;
1948 a[6] -= dur.milliseconds;
1949 return this.calendarSystem.arrayToMarker(a);
1950 }
1951 addYears(marker, n) {
1952 let a = this.calendarSystem.markerToArray(marker);
1953 a[0] += n;
1954 return this.calendarSystem.arrayToMarker(a);
1955 }
1956 addMonths(marker, n) {
1957 let a = this.calendarSystem.markerToArray(marker);
1958 a[1] += n;
1959 return this.calendarSystem.arrayToMarker(a);
1960 }
1961 // Diffing Whole Units
1962 diffWholeYears(m0, m1) {
1963 let { calendarSystem } = this;
1964 if (timeAsMs(m0) === timeAsMs(m1) &&
1965 calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1) &&
1966 calendarSystem.getMarkerMonth(m0) === calendarSystem.getMarkerMonth(m1)) {
1967 return calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0);
1968 }
1969 return null;
1970 }
1971 diffWholeMonths(m0, m1) {
1972 let { calendarSystem } = this;
1973 if (timeAsMs(m0) === timeAsMs(m1) &&
1974 calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1)) {
1975 return (calendarSystem.getMarkerMonth(m1) - calendarSystem.getMarkerMonth(m0)) +
1976 (calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0)) * 12;
1977 }
1978 return null;
1979 }
1980 // Range / Duration
1981 greatestWholeUnit(m0, m1) {
1982 let n = this.diffWholeYears(m0, m1);
1983 if (n !== null) {
1984 return { unit: 'year', value: n };
1985 }
1986 n = this.diffWholeMonths(m0, m1);
1987 if (n !== null) {
1988 return { unit: 'month', value: n };
1989 }
1990 n = diffWholeWeeks(m0, m1);
1991 if (n !== null) {
1992 return { unit: 'week', value: n };
1993 }
1994 n = diffWholeDays(m0, m1);
1995 if (n !== null) {
1996 return { unit: 'day', value: n };
1997 }
1998 n = diffHours(m0, m1);
1999 if (isInt(n)) {
2000 return { unit: 'hour', value: n };
2001 }
2002 n = diffMinutes(m0, m1);
2003 if (isInt(n)) {
2004 return { unit: 'minute', value: n };
2005 }
2006 n = diffSeconds(m0, m1);
2007 if (isInt(n)) {
2008 return { unit: 'second', value: n };
2009 }
2010 return { unit: 'millisecond', value: m1.valueOf() - m0.valueOf() };
2011 }
2012 countDurationsBetween(m0, m1, d) {
2013 // TODO: can use greatestWholeUnit
2014 let diff;
2015 if (d.years) {
2016 diff = this.diffWholeYears(m0, m1);
2017 if (diff !== null) {
2018 return diff / asRoughYears(d);
2019 }
2020 }
2021 if (d.months) {
2022 diff = this.diffWholeMonths(m0, m1);
2023 if (diff !== null) {
2024 return diff / asRoughMonths(d);
2025 }
2026 }
2027 if (d.days) {
2028 diff = diffWholeDays(m0, m1);
2029 if (diff !== null) {
2030 return diff / asRoughDays(d);
2031 }
2032 }
2033 return (m1.valueOf() - m0.valueOf()) / asRoughMs(d);
2034 }
2035 // Start-Of
2036 // these DON'T return zoned-dates. only UTC start-of dates
2037 startOf(m, unit) {
2038 if (unit === 'year') {
2039 return this.startOfYear(m);
2040 }
2041 if (unit === 'month') {
2042 return this.startOfMonth(m);
2043 }
2044 if (unit === 'week') {
2045 return this.startOfWeek(m);
2046 }
2047 if (unit === 'day') {
2048 return startOfDay(m);
2049 }
2050 if (unit === 'hour') {
2051 return startOfHour(m);
2052 }
2053 if (unit === 'minute') {
2054 return startOfMinute(m);
2055 }
2056 if (unit === 'second') {
2057 return startOfSecond(m);
2058 }
2059 return null;
2060 }
2061 startOfYear(m) {
2062 return this.calendarSystem.arrayToMarker([
2063 this.calendarSystem.getMarkerYear(m),
2064 ]);
2065 }
2066 startOfMonth(m) {
2067 return this.calendarSystem.arrayToMarker([
2068 this.calendarSystem.getMarkerYear(m),
2069 this.calendarSystem.getMarkerMonth(m),
2070 ]);
2071 }
2072 startOfWeek(m) {
2073 return this.calendarSystem.arrayToMarker([
2074 this.calendarSystem.getMarkerYear(m),
2075 this.calendarSystem.getMarkerMonth(m),
2076 m.getUTCDate() - ((m.getUTCDay() - this.weekDow + 7) % 7),
2077 ]);
2078 }
2079 // Week Number
2080 computeWeekNumber(marker) {
2081 if (this.weekNumberFunc) {
2082 return this.weekNumberFunc(this.toDate(marker));
2083 }
2084 return weekOfYear(marker, this.weekDow, this.weekDoy);
2085 }
2086 // TODO: choke on timeZoneName: long
2087 format(marker, formatter, dateOptions = {}) {
2088 return formatter.format({
2089 marker,
2090 timeZoneOffset: dateOptions.forcedTzo != null ?
2091 dateOptions.forcedTzo :
2092 this.offsetForMarker(marker),
2093 }, this);
2094 }
2095 formatRange(start, end, formatter, dateOptions = {}) {
2096 if (dateOptions.isEndExclusive) {
2097 end = addMs(end, -1);
2098 }
2099 return formatter.formatRange({
2100 marker: start,
2101 timeZoneOffset: dateOptions.forcedStartTzo != null ?
2102 dateOptions.forcedStartTzo :
2103 this.offsetForMarker(start),
2104 }, {
2105 marker: end,
2106 timeZoneOffset: dateOptions.forcedEndTzo != null ?
2107 dateOptions.forcedEndTzo :
2108 this.offsetForMarker(end),
2109 }, this, dateOptions.defaultSeparator);
2110 }
2111 /*
2112 DUMB: the omitTime arg is dumb. if we omit the time, we want to omit the timezone offset. and if we do that,
2113 might as well use buildIsoString or some other util directly
2114 */
2115 formatIso(marker, extraOptions = {}) {
2116 let timeZoneOffset = null;
2117 if (!extraOptions.omitTimeZoneOffset) {
2118 if (extraOptions.forcedTzo != null) {
2119 timeZoneOffset = extraOptions.forcedTzo;
2120 }
2121 else {
2122 timeZoneOffset = this.offsetForMarker(marker);
2123 }
2124 }
2125 return buildIsoString(marker, timeZoneOffset, extraOptions.omitTime);
2126 }
2127 // TimeZone
2128 timestampToMarker(ms) {
2129 if (this.timeZone === 'local') {
2130 return arrayToUtcDate(dateToLocalArray(new Date(ms)));
2131 }
2132 if (this.timeZone === 'UTC' || !this.namedTimeZoneImpl) {
2133 return new Date(ms);
2134 }
2135 return arrayToUtcDate(this.namedTimeZoneImpl.timestampToArray(ms));
2136 }
2137 offsetForMarker(m) {
2138 if (this.timeZone === 'local') {
2139 return -arrayToLocalDate(dateToUtcArray(m)).getTimezoneOffset(); // convert "inverse" offset to "normal" offset
2140 }
2141 if (this.timeZone === 'UTC') {
2142 return 0;
2143 }
2144 if (this.namedTimeZoneImpl) {
2145 return this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m));
2146 }
2147 return null;
2148 }
2149 // Conversion
2150 toDate(m, forcedTzo) {
2151 if (this.timeZone === 'local') {
2152 return arrayToLocalDate(dateToUtcArray(m));
2153 }
2154 if (this.timeZone === 'UTC') {
2155 return new Date(m.valueOf()); // make sure it's a copy
2156 }
2157 if (!this.namedTimeZoneImpl) {
2158 return new Date(m.valueOf() - (forcedTzo || 0));
2159 }
2160 return new Date(m.valueOf() -
2161 this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m)) * 1000 * 60);
2162 }
2163}
2164
2165class Theme {
2166 constructor(calendarOptions) {
2167 if (this.iconOverrideOption) {
2168 this.setIconOverride(calendarOptions[this.iconOverrideOption]);
2169 }
2170 }
2171 setIconOverride(iconOverrideHash) {
2172 let iconClassesCopy;
2173 let buttonName;
2174 if (typeof iconOverrideHash === 'object' && iconOverrideHash) { // non-null object
2175 iconClassesCopy = Object.assign({}, this.iconClasses);
2176 for (buttonName in iconOverrideHash) {
2177 iconClassesCopy[buttonName] = this.applyIconOverridePrefix(iconOverrideHash[buttonName]);
2178 }
2179 this.iconClasses = iconClassesCopy;
2180 }
2181 else if (iconOverrideHash === false) {
2182 this.iconClasses = {};
2183 }
2184 }
2185 applyIconOverridePrefix(className) {
2186 let prefix = this.iconOverridePrefix;
2187 if (prefix && className.indexOf(prefix) !== 0) { // if not already present
2188 className = prefix + className;
2189 }
2190 return className;
2191 }
2192 getClass(key) {
2193 return this.classes[key] || '';
2194 }
2195 getIconClass(buttonName, isRtl) {
2196 let className;
2197 if (isRtl && this.rtlIconClasses) {
2198 className = this.rtlIconClasses[buttonName] || this.iconClasses[buttonName];
2199 }
2200 else {
2201 className = this.iconClasses[buttonName];
2202 }
2203 if (className) {
2204 return `${this.baseIconClass} ${className}`;
2205 }
2206 return '';
2207 }
2208 getCustomButtonIconClass(customButtonProps) {
2209 let className;
2210 if (this.iconOverrideCustomButtonOption) {
2211 className = customButtonProps[this.iconOverrideCustomButtonOption];
2212 if (className) {
2213 return `${this.baseIconClass} ${this.applyIconOverridePrefix(className)}`;
2214 }
2215 }
2216 return '';
2217 }
2218}
2219Theme.prototype.classes = {};
2220Theme.prototype.iconClasses = {};
2221Theme.prototype.baseIconClass = '';
2222Theme.prototype.iconOverridePrefix = '';
2223
2224/*
2225NOTE: this can be a public API, especially createElement for hooks.
2226See examples/typescript-scheduler/src/index.ts
2227*/
2228function flushSync(runBeforeFlush) {
2229 runBeforeFlush();
2230 let oldDebounceRendering = preact.options.debounceRendering; // orig
2231 let callbackQ = [];
2232 function execCallbackSync(callback) {
2233 callbackQ.push(callback);
2234 }
2235 preact.options.debounceRendering = execCallbackSync;
2236 preact.render(preact.createElement(FakeComponent, {}), document.createElement('div'));
2237 while (callbackQ.length) {
2238 callbackQ.shift()();
2239 }
2240 preact.options.debounceRendering = oldDebounceRendering;
2241}
2242class FakeComponent extends preact.Component {
2243 render() { return preact.createElement('div', {}); }
2244 componentDidMount() { this.setState({}); }
2245}
2246// TODO: use preact/compat instead?
2247function createContext(defaultValue) {
2248 let ContextType = preact.createContext(defaultValue);
2249 let origProvider = ContextType.Provider;
2250 ContextType.Provider = function () {
2251 let isNew = !this.getChildContext;
2252 let children = origProvider.apply(this, arguments); // eslint-disable-line prefer-rest-params
2253 if (isNew) {
2254 let subs = [];
2255 this.shouldComponentUpdate = (_props) => {
2256 if (this.props.value !== _props.value) {
2257 subs.forEach((c) => {
2258 c.context = _props.value;
2259 c.forceUpdate();
2260 });
2261 }
2262 };
2263 this.sub = (c) => {
2264 subs.push(c);
2265 let old = c.componentWillUnmount;
2266 c.componentWillUnmount = () => {
2267 subs.splice(subs.indexOf(c), 1);
2268 old && old.call(c);
2269 };
2270 };
2271 }
2272 return children;
2273 };
2274 return ContextType;
2275}
2276
2277class ScrollResponder {
2278 constructor(execFunc, emitter, scrollTime, scrollTimeReset) {
2279 this.execFunc = execFunc;
2280 this.emitter = emitter;
2281 this.scrollTime = scrollTime;
2282 this.scrollTimeReset = scrollTimeReset;
2283 this.handleScrollRequest = (request) => {
2284 this.queuedRequest = Object.assign({}, this.queuedRequest || {}, request);
2285 this.drain();
2286 };
2287 emitter.on('_scrollRequest', this.handleScrollRequest);
2288 this.fireInitialScroll();
2289 }
2290 detach() {
2291 this.emitter.off('_scrollRequest', this.handleScrollRequest);
2292 }
2293 update(isDatesNew) {
2294 if (isDatesNew && this.scrollTimeReset) {
2295 this.fireInitialScroll(); // will drain
2296 }
2297 else {
2298 this.drain();
2299 }
2300 }
2301 fireInitialScroll() {
2302 this.handleScrollRequest({
2303 time: this.scrollTime,
2304 });
2305 }
2306 drain() {
2307 if (this.queuedRequest && this.execFunc(this.queuedRequest)) {
2308 this.queuedRequest = null;
2309 }
2310 }
2311}
2312
2313const ViewContextType = createContext({}); // for Components
2314function buildViewContext(viewSpec, viewApi, viewOptions, dateProfileGenerator, dateEnv, theme, pluginHooks, dispatch, getCurrentData, emitter, calendarApi, registerInteractiveComponent, unregisterInteractiveComponent) {
2315 return {
2316 dateEnv,
2317 options: viewOptions,
2318 pluginHooks,
2319 emitter,
2320 dispatch,
2321 getCurrentData,
2322 calendarApi,
2323 viewSpec,
2324 viewApi,
2325 dateProfileGenerator,
2326 theme,
2327 isRtl: viewOptions.direction === 'rtl',
2328 addResizeHandler(handler) {
2329 emitter.on('_resize', handler);
2330 },
2331 removeResizeHandler(handler) {
2332 emitter.off('_resize', handler);
2333 },
2334 createScrollResponder(execFunc) {
2335 return new ScrollResponder(execFunc, emitter, createDuration(viewOptions.scrollTime), viewOptions.scrollTimeReset);
2336 },
2337 registerInteractiveComponent,
2338 unregisterInteractiveComponent,
2339 };
2340}
2341
2342/* eslint max-classes-per-file: off */
2343class PureComponent extends Component {
2344 shouldComponentUpdate(nextProps, nextState) {
2345 if (this.debug) {
2346 // eslint-disable-next-line no-console
2347 console.log(getUnequalProps(nextProps, this.props), getUnequalProps(nextState, this.state));
2348 }
2349 return !compareObjs(this.props, nextProps, this.propEquality) ||
2350 !compareObjs(this.state, nextState, this.stateEquality);
2351 }
2352 // HACK for freakin' React StrictMode
2353 safeSetState(newState) {
2354 if (!compareObjs(this.state, Object.assign(Object.assign({}, this.state), newState), this.stateEquality)) {
2355 this.setState(newState);
2356 }
2357 }
2358}
2359PureComponent.addPropsEquality = addPropsEquality;
2360PureComponent.addStateEquality = addStateEquality;
2361PureComponent.contextType = ViewContextType;
2362PureComponent.prototype.propEquality = {};
2363PureComponent.prototype.stateEquality = {};
2364class BaseComponent extends PureComponent {
2365}
2366BaseComponent.contextType = ViewContextType;
2367function addPropsEquality(propEquality) {
2368 let hash = Object.create(this.prototype.propEquality);
2369 Object.assign(hash, propEquality);
2370 this.prototype.propEquality = hash;
2371}
2372function addStateEquality(stateEquality) {
2373 let hash = Object.create(this.prototype.stateEquality);
2374 Object.assign(hash, stateEquality);
2375 this.prototype.stateEquality = hash;
2376}
2377// use other one
2378function setRef(ref, current) {
2379 if (typeof ref === 'function') {
2380 ref(current);
2381 }
2382 else if (ref) {
2383 // see https://github.com/facebook/react/issues/13029
2384 ref.current = current;
2385 }
2386}
2387
2388class ContentInjector extends BaseComponent {
2389 constructor() {
2390 super(...arguments);
2391 this.id = guid();
2392 this.queuedDomNodes = [];
2393 this.currentDomNodes = [];
2394 this.handleEl = (el) => {
2395 const { options } = this.context;
2396 const { generatorName } = this.props;
2397 if (!options.customRenderingReplaces || !hasCustomRenderingHandler(generatorName, options)) {
2398 this.updateElRef(el);
2399 }
2400 };
2401 this.updateElRef = (el) => {
2402 if (this.props.elRef) {
2403 setRef(this.props.elRef, el);
2404 }
2405 };
2406 }
2407 render() {
2408 const { props, context } = this;
2409 const { options } = context;
2410 const { customGenerator, defaultGenerator, renderProps } = props;
2411 const attrs = buildElAttrs(props, [], this.handleEl);
2412 let useDefault = false;
2413 let innerContent;
2414 let queuedDomNodes = [];
2415 let currentGeneratorMeta;
2416 if (customGenerator != null) {
2417 const customGeneratorRes = typeof customGenerator === 'function' ?
2418 customGenerator(renderProps, createElement) :
2419 customGenerator;
2420 if (customGeneratorRes === true) {
2421 useDefault = true;
2422 }
2423 else {
2424 const isObject = customGeneratorRes && typeof customGeneratorRes === 'object'; // non-null
2425 if (isObject && ('html' in customGeneratorRes)) {
2426 attrs.dangerouslySetInnerHTML = { __html: customGeneratorRes.html };
2427 }
2428 else if (isObject && ('domNodes' in customGeneratorRes)) {
2429 queuedDomNodes = Array.prototype.slice.call(customGeneratorRes.domNodes);
2430 }
2431 else if (isObject
2432 ? isValidElement(customGeneratorRes) // vdom node
2433 : typeof customGeneratorRes !== 'function' // primitive value (like string or number)
2434 ) {
2435 // use in vdom
2436 innerContent = customGeneratorRes;
2437 }
2438 else {
2439 // an exotic object for handleCustomRendering
2440 currentGeneratorMeta = customGeneratorRes;
2441 }
2442 }
2443 }
2444 else {
2445 useDefault = !hasCustomRenderingHandler(props.generatorName, options);
2446 }
2447 if (useDefault && defaultGenerator) {
2448 innerContent = defaultGenerator(renderProps);
2449 }
2450 this.queuedDomNodes = queuedDomNodes;
2451 this.currentGeneratorMeta = currentGeneratorMeta;
2452 return createElement(props.elTag, attrs, innerContent);
2453 }
2454 componentDidMount() {
2455 this.applyQueueudDomNodes();
2456 this.triggerCustomRendering(true);
2457 }
2458 componentDidUpdate() {
2459 this.applyQueueudDomNodes();
2460 this.triggerCustomRendering(true);
2461 }
2462 componentWillUnmount() {
2463 this.triggerCustomRendering(false); // TODO: different API for removal?
2464 }
2465 triggerCustomRendering(isActive) {
2466 var _a;
2467 const { props, context } = this;
2468 const { handleCustomRendering, customRenderingMetaMap } = context.options;
2469 if (handleCustomRendering) {
2470 const generatorMeta = (_a = this.currentGeneratorMeta) !== null && _a !== void 0 ? _a : customRenderingMetaMap === null || customRenderingMetaMap === void 0 ? void 0 : customRenderingMetaMap[props.generatorName];
2471 if (generatorMeta) {
2472 handleCustomRendering(Object.assign(Object.assign({ id: this.id, isActive, containerEl: this.base, reportNewContainerEl: this.updateElRef, // front-end framework tells us about new container els
2473 generatorMeta }, props), { elClasses: (props.elClasses || []).filter(isTruthy) }));
2474 }
2475 }
2476 }
2477 applyQueueudDomNodes() {
2478 const { queuedDomNodes, currentDomNodes } = this;
2479 const el = this.base;
2480 if (!isArraysEqual(queuedDomNodes, currentDomNodes)) {
2481 currentDomNodes.forEach(removeElement);
2482 for (let newNode of queuedDomNodes) {
2483 el.appendChild(newNode);
2484 }
2485 this.currentDomNodes = queuedDomNodes;
2486 }
2487 }
2488}
2489ContentInjector.addPropsEquality({
2490 elClasses: isArraysEqual,
2491 elStyle: isPropsEqual,
2492 elAttrs: isNonHandlerPropsEqual,
2493 renderProps: isPropsEqual,
2494});
2495// Util
2496/*
2497Does UI-framework provide custom way of rendering that does not use Preact VDOM
2498AND does the calendar's options define custom rendering?
2499AKA. Should we NOT render the default content?
2500*/
2501function hasCustomRenderingHandler(generatorName, options) {
2502 var _a;
2503 return Boolean(options.handleCustomRendering &&
2504 generatorName &&
2505 ((_a = options.customRenderingMetaMap) === null || _a === void 0 ? void 0 : _a[generatorName]));
2506}
2507function buildElAttrs(props, extraClassNames, elRef) {
2508 const attrs = Object.assign(Object.assign({}, props.elAttrs), { ref: elRef });
2509 if (props.elClasses || extraClassNames) {
2510 attrs.className = (props.elClasses || [])
2511 .concat(extraClassNames || [])
2512 .concat(attrs.className || [])
2513 .filter(Boolean)
2514 .join(' ');
2515 }
2516 if (props.elStyle) {
2517 attrs.style = props.elStyle;
2518 }
2519 return attrs;
2520}
2521function isTruthy(val) {
2522 return Boolean(val);
2523}
2524
2525const RenderId = createContext(0);
2526
2527class ContentContainer extends Component {
2528 constructor() {
2529 super(...arguments);
2530 this.InnerContent = InnerContentInjector.bind(undefined, this);
2531 this.handleEl = (el) => {
2532 this.el = el;
2533 if (this.props.elRef) {
2534 setRef(this.props.elRef, el);
2535 if (el && this.didMountMisfire) {
2536 this.componentDidMount();
2537 }
2538 }
2539 };
2540 }
2541 render() {
2542 const { props } = this;
2543 const generatedClassNames = generateClassNames(props.classNameGenerator, props.renderProps);
2544 if (props.children) {
2545 const elAttrs = buildElAttrs(props, generatedClassNames, this.handleEl);
2546 const children = props.children(this.InnerContent, props.renderProps, elAttrs);
2547 if (props.elTag) {
2548 return createElement(props.elTag, elAttrs, children);
2549 }
2550 else {
2551 return children;
2552 }
2553 }
2554 else {
2555 return createElement((ContentInjector), Object.assign(Object.assign({}, props), { elRef: this.handleEl, elTag: props.elTag || 'div', elClasses: (props.elClasses || []).concat(generatedClassNames), renderId: this.context }));
2556 }
2557 }
2558 componentDidMount() {
2559 var _a, _b;
2560 if (this.el) {
2561 (_b = (_a = this.props).didMount) === null || _b === void 0 ? void 0 : _b.call(_a, Object.assign(Object.assign({}, this.props.renderProps), { el: this.el }));
2562 }
2563 else {
2564 this.didMountMisfire = true;
2565 }
2566 }
2567 componentWillUnmount() {
2568 var _a, _b;
2569 (_b = (_a = this.props).willUnmount) === null || _b === void 0 ? void 0 : _b.call(_a, Object.assign(Object.assign({}, this.props.renderProps), { el: this.el }));
2570 }
2571}
2572ContentContainer.contextType = RenderId;
2573function InnerContentInjector(containerComponent, props) {
2574 const parentProps = containerComponent.props;
2575 return createElement((ContentInjector), Object.assign({ renderProps: parentProps.renderProps, generatorName: parentProps.generatorName, customGenerator: parentProps.customGenerator, defaultGenerator: parentProps.defaultGenerator, renderId: containerComponent.context }, props));
2576}
2577// Utils
2578function generateClassNames(classNameGenerator, renderProps) {
2579 const classNames = typeof classNameGenerator === 'function' ?
2580 classNameGenerator(renderProps) :
2581 classNameGenerator || [];
2582 return typeof classNames === 'string' ? [classNames] : classNames;
2583}
2584
2585class ViewContainer extends BaseComponent {
2586 render() {
2587 let { props, context } = this;
2588 let { options } = context;
2589 let renderProps = { view: context.viewApi };
2590 return (createElement(ContentContainer, Object.assign({}, props, { elTag: props.elTag || 'div', elClasses: [
2591 ...buildViewClassNames(props.viewSpec),
2592 ...(props.elClasses || []),
2593 ], renderProps: renderProps, classNameGenerator: options.viewClassNames, generatorName: undefined, didMount: options.viewDidMount, willUnmount: options.viewWillUnmount }), () => props.children));
2594 }
2595}
2596function buildViewClassNames(viewSpec) {
2597 return [
2598 `fc-${viewSpec.type}-view`,
2599 'fc-view',
2600 ];
2601}
2602
2603function parseRange(input, dateEnv) {
2604 let start = null;
2605 let end = null;
2606 if (input.start) {
2607 start = dateEnv.createMarker(input.start);
2608 }
2609 if (input.end) {
2610 end = dateEnv.createMarker(input.end);
2611 }
2612 if (!start && !end) {
2613 return null;
2614 }
2615 if (start && end && end < start) {
2616 return null;
2617 }
2618 return { start, end };
2619}
2620// SIDE-EFFECT: will mutate ranges.
2621// Will return a new array result.
2622function invertRanges(ranges, constraintRange) {
2623 let invertedRanges = [];
2624 let { start } = constraintRange; // the end of the previous range. the start of the new range
2625 let i;
2626 let dateRange;
2627 // ranges need to be in order. required for our date-walking algorithm
2628 ranges.sort(compareRanges);
2629 for (i = 0; i < ranges.length; i += 1) {
2630 dateRange = ranges[i];
2631 // add the span of time before the event (if there is any)
2632 if (dateRange.start > start) { // compare millisecond time (skip any ambig logic)
2633 invertedRanges.push({ start, end: dateRange.start });
2634 }
2635 if (dateRange.end > start) {
2636 start = dateRange.end;
2637 }
2638 }
2639 // add the span of time after the last event (if there is any)
2640 if (start < constraintRange.end) { // compare millisecond time (skip any ambig logic)
2641 invertedRanges.push({ start, end: constraintRange.end });
2642 }
2643 return invertedRanges;
2644}
2645function compareRanges(range0, range1) {
2646 return range0.start.valueOf() - range1.start.valueOf(); // earlier ranges go first
2647}
2648function intersectRanges(range0, range1) {
2649 let { start, end } = range0;
2650 let newRange = null;
2651 if (range1.start !== null) {
2652 if (start === null) {
2653 start = range1.start;
2654 }
2655 else {
2656 start = new Date(Math.max(start.valueOf(), range1.start.valueOf()));
2657 }
2658 }
2659 if (range1.end != null) {
2660 if (end === null) {
2661 end = range1.end;
2662 }
2663 else {
2664 end = new Date(Math.min(end.valueOf(), range1.end.valueOf()));
2665 }
2666 }
2667 if (start === null || end === null || start < end) {
2668 newRange = { start, end };
2669 }
2670 return newRange;
2671}
2672function rangesEqual(range0, range1) {
2673 return (range0.start === null ? null : range0.start.valueOf()) === (range1.start === null ? null : range1.start.valueOf()) &&
2674 (range0.end === null ? null : range0.end.valueOf()) === (range1.end === null ? null : range1.end.valueOf());
2675}
2676function rangesIntersect(range0, range1) {
2677 return (range0.end === null || range1.start === null || range0.end > range1.start) &&
2678 (range0.start === null || range1.end === null || range0.start < range1.end);
2679}
2680function rangeContainsRange(outerRange, innerRange) {
2681 return (outerRange.start === null || (innerRange.start !== null && innerRange.start >= outerRange.start)) &&
2682 (outerRange.end === null || (innerRange.end !== null && innerRange.end <= outerRange.end));
2683}
2684function rangeContainsMarker(range, date) {
2685 return (range.start === null || date >= range.start) &&
2686 (range.end === null || date < range.end);
2687}
2688// If the given date is not within the given range, move it inside.
2689// (If it's past the end, make it one millisecond before the end).
2690function constrainMarkerToRange(date, range) {
2691 if (range.start != null && date < range.start) {
2692 return range.start;
2693 }
2694 if (range.end != null && date >= range.end) {
2695 return new Date(range.end.valueOf() - 1);
2696 }
2697 return date;
2698}
2699
2700/* Date stuff that doesn't belong in datelib core
2701----------------------------------------------------------------------------------------------------------------------*/
2702// given a timed range, computes an all-day range that has the same exact duration,
2703// but whose start time is aligned with the start of the day.
2704function computeAlignedDayRange(timedRange) {
2705 let dayCnt = Math.floor(diffDays(timedRange.start, timedRange.end)) || 1;
2706 let start = startOfDay(timedRange.start);
2707 let end = addDays(start, dayCnt);
2708 return { start, end };
2709}
2710// given a timed range, computes an all-day range based on how for the end date bleeds into the next day
2711// TODO: give nextDayThreshold a default arg
2712function computeVisibleDayRange(timedRange, nextDayThreshold = createDuration(0)) {
2713 let startDay = null;
2714 let endDay = null;
2715 if (timedRange.end) {
2716 endDay = startOfDay(timedRange.end);
2717 let endTimeMS = timedRange.end.valueOf() - endDay.valueOf(); // # of milliseconds into `endDay`
2718 // If the end time is actually inclusively part of the next day and is equal to or
2719 // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`.
2720 // Otherwise, leaving it as inclusive will cause it to exclude `endDay`.
2721 if (endTimeMS && endTimeMS >= asRoughMs(nextDayThreshold)) {
2722 endDay = addDays(endDay, 1);
2723 }
2724 }
2725 if (timedRange.start) {
2726 startDay = startOfDay(timedRange.start); // the beginning of the day the range starts
2727 // If end is within `startDay` but not past nextDayThreshold, assign the default duration of one day.
2728 if (endDay && endDay <= startDay) {
2729 endDay = addDays(startDay, 1);
2730 }
2731 }
2732 return { start: startDay, end: endDay };
2733}
2734// spans from one day into another?
2735function isMultiDayRange(range) {
2736 let visibleRange = computeVisibleDayRange(range);
2737 return diffDays(visibleRange.start, visibleRange.end) > 1;
2738}
2739function diffDates(date0, date1, dateEnv, largeUnit) {
2740 if (largeUnit === 'year') {
2741 return createDuration(dateEnv.diffWholeYears(date0, date1), 'year');
2742 }
2743 if (largeUnit === 'month') {
2744 return createDuration(dateEnv.diffWholeMonths(date0, date1), 'month');
2745 }
2746 return diffDayAndTime(date0, date1); // returns a duration
2747}
2748
2749function reduceCurrentDate(currentDate, action) {
2750 switch (action.type) {
2751 case 'CHANGE_DATE':
2752 return action.dateMarker;
2753 default:
2754 return currentDate;
2755 }
2756}
2757function getInitialDate(options, dateEnv) {
2758 let initialDateInput = options.initialDate;
2759 // compute the initial ambig-timezone date
2760 if (initialDateInput != null) {
2761 return dateEnv.createMarker(initialDateInput);
2762 }
2763 return getNow(options.now, dateEnv); // getNow already returns unzoned
2764}
2765function getNow(nowInput, dateEnv) {
2766 if (typeof nowInput === 'function') {
2767 nowInput = nowInput();
2768 }
2769 if (nowInput == null) {
2770 return dateEnv.createNowMarker();
2771 }
2772 return dateEnv.createMarker(nowInput);
2773}
2774
2775class DateProfileGenerator {
2776 constructor(props) {
2777 this.props = props;
2778 this.nowDate = getNow(props.nowInput, props.dateEnv);
2779 this.initHiddenDays();
2780 }
2781 /* Date Range Computation
2782 ------------------------------------------------------------------------------------------------------------------*/
2783 // Builds a structure with info about what the dates/ranges will be for the "prev" view.
2784 buildPrev(currentDateProfile, currentDate, forceToValid) {
2785 let { dateEnv } = this.props;
2786 let prevDate = dateEnv.subtract(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month
2787 currentDateProfile.dateIncrement);
2788 return this.build(prevDate, -1, forceToValid);
2789 }
2790 // Builds a structure with info about what the dates/ranges will be for the "next" view.
2791 buildNext(currentDateProfile, currentDate, forceToValid) {
2792 let { dateEnv } = this.props;
2793 let nextDate = dateEnv.add(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month
2794 currentDateProfile.dateIncrement);
2795 return this.build(nextDate, 1, forceToValid);
2796 }
2797 // Builds a structure holding dates/ranges for rendering around the given date.
2798 // Optional direction param indicates whether the date is being incremented/decremented
2799 // from its previous value. decremented = -1, incremented = 1 (default).
2800 build(currentDate, direction, forceToValid = true) {
2801 let { props } = this;
2802 let validRange;
2803 let currentInfo;
2804 let isRangeAllDay;
2805 let renderRange;
2806 let activeRange;
2807 let isValid;
2808 validRange = this.buildValidRange();
2809 validRange = this.trimHiddenDays(validRange);
2810 if (forceToValid) {
2811 currentDate = constrainMarkerToRange(currentDate, validRange);
2812 }
2813 currentInfo = this.buildCurrentRangeInfo(currentDate, direction);
2814 isRangeAllDay = /^(year|month|week|day)$/.test(currentInfo.unit);
2815 renderRange = this.buildRenderRange(this.trimHiddenDays(currentInfo.range), currentInfo.unit, isRangeAllDay);
2816 renderRange = this.trimHiddenDays(renderRange);
2817 activeRange = renderRange;
2818 if (!props.showNonCurrentDates) {
2819 activeRange = intersectRanges(activeRange, currentInfo.range);
2820 }
2821 activeRange = this.adjustActiveRange(activeRange);
2822 activeRange = intersectRanges(activeRange, validRange); // might return null
2823 // it's invalid if the originally requested date is not contained,
2824 // or if the range is completely outside of the valid range.
2825 isValid = rangesIntersect(currentInfo.range, validRange);
2826 // HACK: constrain to render-range so `currentDate` is more useful to view rendering
2827 if (!rangeContainsMarker(renderRange, currentDate)) {
2828 currentDate = renderRange.start;
2829 }
2830 return {
2831 currentDate,
2832 // constraint for where prev/next operations can go and where events can be dragged/resized to.
2833 // an object with optional start and end properties.
2834 validRange,
2835 // range the view is formally responsible for.
2836 // for example, a month view might have 1st-31st, excluding padded dates
2837 currentRange: currentInfo.range,
2838 // name of largest unit being displayed, like "month" or "week"
2839 currentRangeUnit: currentInfo.unit,
2840 isRangeAllDay,
2841 // dates that display events and accept drag-n-drop
2842 // will be `null` if no dates accept events
2843 activeRange,
2844 // date range with a rendered skeleton
2845 // includes not-active days that need some sort of DOM
2846 renderRange,
2847 // Duration object that denotes the first visible time of any given day
2848 slotMinTime: props.slotMinTime,
2849 // Duration object that denotes the exclusive visible end time of any given day
2850 slotMaxTime: props.slotMaxTime,
2851 isValid,
2852 // how far the current date will move for a prev/next operation
2853 dateIncrement: this.buildDateIncrement(currentInfo.duration),
2854 // pass a fallback (might be null) ^
2855 };
2856 }
2857 // Builds an object with optional start/end properties.
2858 // Indicates the minimum/maximum dates to display.
2859 // not responsible for trimming hidden days.
2860 buildValidRange() {
2861 let input = this.props.validRangeInput;
2862 let simpleInput = typeof input === 'function'
2863 ? input.call(this.props.calendarApi, this.nowDate)
2864 : input;
2865 return this.refineRange(simpleInput) ||
2866 { start: null, end: null }; // completely open-ended
2867 }
2868 // Builds a structure with info about the "current" range, the range that is
2869 // highlighted as being the current month for example.
2870 // See build() for a description of `direction`.
2871 // Guaranteed to have `range` and `unit` properties. `duration` is optional.
2872 buildCurrentRangeInfo(date, direction) {
2873 let { props } = this;
2874 let duration = null;
2875 let unit = null;
2876 let range = null;
2877 let dayCount;
2878 if (props.duration) {
2879 duration = props.duration;
2880 unit = props.durationUnit;
2881 range = this.buildRangeFromDuration(date, direction, duration, unit);
2882 }
2883 else if ((dayCount = this.props.dayCount)) {
2884 unit = 'day';
2885 range = this.buildRangeFromDayCount(date, direction, dayCount);
2886 }
2887 else if ((range = this.buildCustomVisibleRange(date))) {
2888 unit = props.dateEnv.greatestWholeUnit(range.start, range.end).unit;
2889 }
2890 else {
2891 duration = this.getFallbackDuration();
2892 unit = greatestDurationDenominator(duration).unit;
2893 range = this.buildRangeFromDuration(date, direction, duration, unit);
2894 }
2895 return { duration, unit, range };
2896 }
2897 getFallbackDuration() {
2898 return createDuration({ day: 1 });
2899 }
2900 // Returns a new activeRange to have time values (un-ambiguate)
2901 // slotMinTime or slotMaxTime causes the range to expand.
2902 adjustActiveRange(range) {
2903 let { dateEnv, usesMinMaxTime, slotMinTime, slotMaxTime } = this.props;
2904 let { start, end } = range;
2905 if (usesMinMaxTime) {
2906 // expand active range if slotMinTime is negative (why not when positive?)
2907 if (asRoughDays(slotMinTime) < 0) {
2908 start = startOfDay(start); // necessary?
2909 start = dateEnv.add(start, slotMinTime);
2910 }
2911 // expand active range if slotMaxTime is beyond one day (why not when negative?)
2912 if (asRoughDays(slotMaxTime) > 1) {
2913 end = startOfDay(end); // necessary?
2914 end = addDays(end, -1);
2915 end = dateEnv.add(end, slotMaxTime);
2916 }
2917 }
2918 return { start, end };
2919 }
2920 // Builds the "current" range when it is specified as an explicit duration.
2921 // `unit` is the already-computed greatestDurationDenominator unit of duration.
2922 buildRangeFromDuration(date, direction, duration, unit) {
2923 let { dateEnv, dateAlignment } = this.props;
2924 let start;
2925 let end;
2926 let res;
2927 // compute what the alignment should be
2928 if (!dateAlignment) {
2929 let { dateIncrement } = this.props;
2930 if (dateIncrement) {
2931 // use the smaller of the two units
2932 if (asRoughMs(dateIncrement) < asRoughMs(duration)) {
2933 dateAlignment = greatestDurationDenominator(dateIncrement).unit;
2934 }
2935 else {
2936 dateAlignment = unit;
2937 }
2938 }
2939 else {
2940 dateAlignment = unit;
2941 }
2942 }
2943 // if the view displays a single day or smaller
2944 if (asRoughDays(duration) <= 1) {
2945 if (this.isHiddenDay(start)) {
2946 start = this.skipHiddenDays(start, direction);
2947 start = startOfDay(start);
2948 }
2949 }
2950 function computeRes() {
2951 start = dateEnv.startOf(date, dateAlignment);
2952 end = dateEnv.add(start, duration);
2953 res = { start, end };
2954 }
2955 computeRes();
2956 // if range is completely enveloped by hidden days, go past the hidden days
2957 if (!this.trimHiddenDays(res)) {
2958 date = this.skipHiddenDays(date, direction);
2959 computeRes();
2960 }
2961 return res;
2962 }
2963 // Builds the "current" range when a dayCount is specified.
2964 buildRangeFromDayCount(date, direction, dayCount) {
2965 let { dateEnv, dateAlignment } = this.props;
2966 let runningCount = 0;
2967 let start = date;
2968 let end;
2969 if (dateAlignment) {
2970 start = dateEnv.startOf(start, dateAlignment);
2971 }
2972 start = startOfDay(start);
2973 start = this.skipHiddenDays(start, direction);
2974 end = start;
2975 do {
2976 end = addDays(end, 1);
2977 if (!this.isHiddenDay(end)) {
2978 runningCount += 1;
2979 }
2980 } while (runningCount < dayCount);
2981 return { start, end };
2982 }
2983 // Builds a normalized range object for the "visible" range,
2984 // which is a way to define the currentRange and activeRange at the same time.
2985 buildCustomVisibleRange(date) {
2986 let { props } = this;
2987 let input = props.visibleRangeInput;
2988 let simpleInput = typeof input === 'function'
2989 ? input.call(props.calendarApi, props.dateEnv.toDate(date))
2990 : input;
2991 let range = this.refineRange(simpleInput);
2992 if (range && (range.start == null || range.end == null)) {
2993 return null;
2994 }
2995 return range;
2996 }
2997 // Computes the range that will represent the element/cells for *rendering*,
2998 // but which may have voided days/times.
2999 // not responsible for trimming hidden days.
3000 buildRenderRange(currentRange, currentRangeUnit, isRangeAllDay) {
3001 return currentRange;
3002 }
3003 // Compute the duration value that should be added/substracted to the current date
3004 // when a prev/next operation happens.
3005 buildDateIncrement(fallback) {
3006 let { dateIncrement } = this.props;
3007 let customAlignment;
3008 if (dateIncrement) {
3009 return dateIncrement;
3010 }
3011 if ((customAlignment = this.props.dateAlignment)) {
3012 return createDuration(1, customAlignment);
3013 }
3014 if (fallback) {
3015 return fallback;
3016 }
3017 return createDuration({ days: 1 });
3018 }
3019 refineRange(rangeInput) {
3020 if (rangeInput) {
3021 let range = parseRange(rangeInput, this.props.dateEnv);
3022 if (range) {
3023 range = computeVisibleDayRange(range);
3024 }
3025 return range;
3026 }
3027 return null;
3028 }
3029 /* Hidden Days
3030 ------------------------------------------------------------------------------------------------------------------*/
3031 // Initializes internal variables related to calculating hidden days-of-week
3032 initHiddenDays() {
3033 let hiddenDays = this.props.hiddenDays || []; // array of day-of-week indices that are hidden
3034 let isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool)
3035 let dayCnt = 0;
3036 let i;
3037 if (this.props.weekends === false) {
3038 hiddenDays.push(0, 6); // 0=sunday, 6=saturday
3039 }
3040 for (i = 0; i < 7; i += 1) {
3041 if (!(isHiddenDayHash[i] = hiddenDays.indexOf(i) !== -1)) {
3042 dayCnt += 1;
3043 }
3044 }
3045 if (!dayCnt) {
3046 throw new Error('invalid hiddenDays'); // all days were hidden? bad.
3047 }
3048 this.isHiddenDayHash = isHiddenDayHash;
3049 }
3050 // Remove days from the beginning and end of the range that are computed as hidden.
3051 // If the whole range is trimmed off, returns null
3052 trimHiddenDays(range) {
3053 let { start, end } = range;
3054 if (start) {
3055 start = this.skipHiddenDays(start);
3056 }
3057 if (end) {
3058 end = this.skipHiddenDays(end, -1, true);
3059 }
3060 if (start == null || end == null || start < end) {
3061 return { start, end };
3062 }
3063 return null;
3064 }
3065 // Is the current day hidden?
3066 // `day` is a day-of-week index (0-6), or a Date (used for UTC)
3067 isHiddenDay(day) {
3068 if (day instanceof Date) {
3069 day = day.getUTCDay();
3070 }
3071 return this.isHiddenDayHash[day];
3072 }
3073 // Incrementing the current day until it is no longer a hidden day, returning a copy.
3074 // DOES NOT CONSIDER validRange!
3075 // If the initial value of `date` is not a hidden day, don't do anything.
3076 // Pass `isExclusive` as `true` if you are dealing with an end date.
3077 // `inc` defaults to `1` (increment one day forward each time)
3078 skipHiddenDays(date, inc = 1, isExclusive = false) {
3079 while (this.isHiddenDayHash[(date.getUTCDay() + (isExclusive ? inc : 0) + 7) % 7]) {
3080 date = addDays(date, inc);
3081 }
3082 return date;
3083 }
3084}
3085
3086function createEventInstance(defId, range, forcedStartTzo, forcedEndTzo) {
3087 return {
3088 instanceId: guid(),
3089 defId,
3090 range,
3091 forcedStartTzo: forcedStartTzo == null ? null : forcedStartTzo,
3092 forcedEndTzo: forcedEndTzo == null ? null : forcedEndTzo,
3093 };
3094}
3095
3096function parseRecurring(refined, defaultAllDay, dateEnv, recurringTypes) {
3097 for (let i = 0; i < recurringTypes.length; i += 1) {
3098 let parsed = recurringTypes[i].parse(refined, dateEnv);
3099 if (parsed) {
3100 let { allDay } = refined;
3101 if (allDay == null) {
3102 allDay = defaultAllDay;
3103 if (allDay == null) {
3104 allDay = parsed.allDayGuess;
3105 if (allDay == null) {
3106 allDay = false;
3107 }
3108 }
3109 }
3110 return {
3111 allDay,
3112 duration: parsed.duration,
3113 typeData: parsed.typeData,
3114 typeId: i,
3115 };
3116 }
3117 }
3118 return null;
3119}
3120function expandRecurring(eventStore, framingRange, context) {
3121 let { dateEnv, pluginHooks, options } = context;
3122 let { defs, instances } = eventStore;
3123 // remove existing recurring instances
3124 // TODO: bad. always expand events as a second step
3125 instances = filterHash(instances, (instance) => !defs[instance.defId].recurringDef);
3126 for (let defId in defs) {
3127 let def = defs[defId];
3128 if (def.recurringDef) {
3129 let { duration } = def.recurringDef;
3130 if (!duration) {
3131 duration = def.allDay ?
3132 options.defaultAllDayEventDuration :
3133 options.defaultTimedEventDuration;
3134 }
3135 let starts = expandRecurringRanges(def, duration, framingRange, dateEnv, pluginHooks.recurringTypes);
3136 for (let start of starts) {
3137 let instance = createEventInstance(defId, {
3138 start,
3139 end: dateEnv.add(start, duration),
3140 });
3141 instances[instance.instanceId] = instance;
3142 }
3143 }
3144 }
3145 return { defs, instances };
3146}
3147/*
3148Event MUST have a recurringDef
3149*/
3150function expandRecurringRanges(eventDef, duration, framingRange, dateEnv, recurringTypes) {
3151 let typeDef = recurringTypes[eventDef.recurringDef.typeId];
3152 let markers = typeDef.expand(eventDef.recurringDef.typeData, {
3153 start: dateEnv.subtract(framingRange.start, duration),
3154 end: framingRange.end,
3155 }, dateEnv);
3156 // the recurrence plugins don't guarantee that all-day events are start-of-day, so we have to
3157 if (eventDef.allDay) {
3158 markers = markers.map(startOfDay);
3159 }
3160 return markers;
3161}
3162
3163const EVENT_NON_DATE_REFINERS = {
3164 id: String,
3165 groupId: String,
3166 title: String,
3167 url: String,
3168 interactive: Boolean,
3169};
3170const EVENT_DATE_REFINERS = {
3171 start: identity,
3172 end: identity,
3173 date: identity,
3174 allDay: Boolean,
3175};
3176const EVENT_REFINERS = Object.assign(Object.assign(Object.assign({}, EVENT_NON_DATE_REFINERS), EVENT_DATE_REFINERS), { extendedProps: identity });
3177function parseEvent(raw, eventSource, context, allowOpenRange, refiners = buildEventRefiners(context), defIdMap, instanceIdMap) {
3178 let { refined, extra } = refineEventDef(raw, context, refiners);
3179 let defaultAllDay = computeIsDefaultAllDay(eventSource, context);
3180 let recurringRes = parseRecurring(refined, defaultAllDay, context.dateEnv, context.pluginHooks.recurringTypes);
3181 if (recurringRes) {
3182 let def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', recurringRes.allDay, Boolean(recurringRes.duration), context, defIdMap);
3183 def.recurringDef = {
3184 typeId: recurringRes.typeId,
3185 typeData: recurringRes.typeData,
3186 duration: recurringRes.duration,
3187 };
3188 return { def, instance: null };
3189 }
3190 let singleRes = parseSingle(refined, defaultAllDay, context, allowOpenRange);
3191 if (singleRes) {
3192 let def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', singleRes.allDay, singleRes.hasEnd, context, defIdMap);
3193 let instance = createEventInstance(def.defId, singleRes.range, singleRes.forcedStartTzo, singleRes.forcedEndTzo);
3194 if (instanceIdMap && def.publicId && instanceIdMap[def.publicId]) {
3195 instance.instanceId = instanceIdMap[def.publicId];
3196 }
3197 return { def, instance };
3198 }
3199 return null;
3200}
3201function refineEventDef(raw, context, refiners = buildEventRefiners(context)) {
3202 return refineProps(raw, refiners);
3203}
3204function buildEventRefiners(context) {
3205 return Object.assign(Object.assign(Object.assign({}, EVENT_UI_REFINERS), EVENT_REFINERS), context.pluginHooks.eventRefiners);
3206}
3207/*
3208Will NOT populate extendedProps with the leftover properties.
3209Will NOT populate date-related props.
3210*/
3211function parseEventDef(refined, extra, sourceId, allDay, hasEnd, context, defIdMap) {
3212 let def = {
3213 title: refined.title || '',
3214 groupId: refined.groupId || '',
3215 publicId: refined.id || '',
3216 url: refined.url || '',
3217 recurringDef: null,
3218 defId: ((defIdMap && refined.id) ? defIdMap[refined.id] : '') || guid(),
3219 sourceId,
3220 allDay,
3221 hasEnd,
3222 interactive: refined.interactive,
3223 ui: createEventUi(refined, context),
3224 extendedProps: Object.assign(Object.assign({}, (refined.extendedProps || {})), extra),
3225 };
3226 for (let memberAdder of context.pluginHooks.eventDefMemberAdders) {
3227 Object.assign(def, memberAdder(refined));
3228 }
3229 // help out EventImpl from having user modify props
3230 Object.freeze(def.ui.classNames);
3231 Object.freeze(def.extendedProps);
3232 return def;
3233}
3234function parseSingle(refined, defaultAllDay, context, allowOpenRange) {
3235 let { allDay } = refined;
3236 let startMeta;
3237 let startMarker = null;
3238 let hasEnd = false;
3239 let endMeta;
3240 let endMarker = null;
3241 let startInput = refined.start != null ? refined.start : refined.date;
3242 startMeta = context.dateEnv.createMarkerMeta(startInput);
3243 if (startMeta) {
3244 startMarker = startMeta.marker;
3245 }
3246 else if (!allowOpenRange) {
3247 return null;
3248 }
3249 if (refined.end != null) {
3250 endMeta = context.dateEnv.createMarkerMeta(refined.end);
3251 }
3252 if (allDay == null) {
3253 if (defaultAllDay != null) {
3254 allDay = defaultAllDay;
3255 }
3256 else {
3257 // fall back to the date props LAST
3258 allDay = (!startMeta || startMeta.isTimeUnspecified) &&
3259 (!endMeta || endMeta.isTimeUnspecified);
3260 }
3261 }
3262 if (allDay && startMarker) {
3263 startMarker = startOfDay(startMarker);
3264 }
3265 if (endMeta) {
3266 endMarker = endMeta.marker;
3267 if (allDay) {
3268 endMarker = startOfDay(endMarker);
3269 }
3270 if (startMarker && endMarker <= startMarker) {
3271 endMarker = null;
3272 }
3273 }
3274 if (endMarker) {
3275 hasEnd = true;
3276 }
3277 else if (!allowOpenRange) {
3278 hasEnd = context.options.forceEventDuration || false;
3279 endMarker = context.dateEnv.add(startMarker, allDay ?
3280 context.options.defaultAllDayEventDuration :
3281 context.options.defaultTimedEventDuration);
3282 }
3283 return {
3284 allDay,
3285 hasEnd,
3286 range: { start: startMarker, end: endMarker },
3287 forcedStartTzo: startMeta ? startMeta.forcedTzo : null,
3288 forcedEndTzo: endMeta ? endMeta.forcedTzo : null,
3289 };
3290}
3291function computeIsDefaultAllDay(eventSource, context) {
3292 let res = null;
3293 if (eventSource) {
3294 res = eventSource.defaultAllDay;
3295 }
3296 if (res == null) {
3297 res = context.options.defaultAllDay;
3298 }
3299 return res;
3300}
3301
3302function parseEvents(rawEvents, eventSource, context, allowOpenRange, defIdMap, instanceIdMap) {
3303 let eventStore = createEmptyEventStore();
3304 let eventRefiners = buildEventRefiners(context);
3305 for (let rawEvent of rawEvents) {
3306 let tuple = parseEvent(rawEvent, eventSource, context, allowOpenRange, eventRefiners, defIdMap, instanceIdMap);
3307 if (tuple) {
3308 eventTupleToStore(tuple, eventStore);
3309 }
3310 }
3311 return eventStore;
3312}
3313function eventTupleToStore(tuple, eventStore = createEmptyEventStore()) {
3314 eventStore.defs[tuple.def.defId] = tuple.def;
3315 if (tuple.instance) {
3316 eventStore.instances[tuple.instance.instanceId] = tuple.instance;
3317 }
3318 return eventStore;
3319}
3320// retrieves events that have the same groupId as the instance specified by `instanceId`
3321// or they are the same as the instance.
3322// why might instanceId not be in the store? an event from another calendar?
3323function getRelevantEvents(eventStore, instanceId) {
3324 let instance = eventStore.instances[instanceId];
3325 if (instance) {
3326 let def = eventStore.defs[instance.defId];
3327 // get events/instances with same group
3328 let newStore = filterEventStoreDefs(eventStore, (lookDef) => isEventDefsGrouped(def, lookDef));
3329 // add the original
3330 // TODO: wish we could use eventTupleToStore or something like it
3331 newStore.defs[def.defId] = def;
3332 newStore.instances[instance.instanceId] = instance;
3333 return newStore;
3334 }
3335 return createEmptyEventStore();
3336}
3337function isEventDefsGrouped(def0, def1) {
3338 return Boolean(def0.groupId && def0.groupId === def1.groupId);
3339}
3340function createEmptyEventStore() {
3341 return { defs: {}, instances: {} };
3342}
3343function mergeEventStores(store0, store1) {
3344 return {
3345 defs: Object.assign(Object.assign({}, store0.defs), store1.defs),
3346 instances: Object.assign(Object.assign({}, store0.instances), store1.instances),
3347 };
3348}
3349function filterEventStoreDefs(eventStore, filterFunc) {
3350 let defs = filterHash(eventStore.defs, filterFunc);
3351 let instances = filterHash(eventStore.instances, (instance) => (defs[instance.defId] // still exists?
3352 ));
3353 return { defs, instances };
3354}
3355function excludeSubEventStore(master, sub) {
3356 let { defs, instances } = master;
3357 let filteredDefs = {};
3358 let filteredInstances = {};
3359 for (let defId in defs) {
3360 if (!sub.defs[defId]) { // not explicitly excluded
3361 filteredDefs[defId] = defs[defId];
3362 }
3363 }
3364 for (let instanceId in instances) {
3365 if (!sub.instances[instanceId] && // not explicitly excluded
3366 filteredDefs[instances[instanceId].defId] // def wasn't filtered away
3367 ) {
3368 filteredInstances[instanceId] = instances[instanceId];
3369 }
3370 }
3371 return {
3372 defs: filteredDefs,
3373 instances: filteredInstances,
3374 };
3375}
3376
3377function normalizeConstraint(input, context) {
3378 if (Array.isArray(input)) {
3379 return parseEvents(input, null, context, true); // allowOpenRange=true
3380 }
3381 if (typeof input === 'object' && input) { // non-null object
3382 return parseEvents([input], null, context, true); // allowOpenRange=true
3383 }
3384 if (input != null) {
3385 return String(input);
3386 }
3387 return null;
3388}
3389
3390function parseClassNames(raw) {
3391 if (Array.isArray(raw)) {
3392 return raw;
3393 }
3394 if (typeof raw === 'string') {
3395 return raw.split(/\s+/);
3396 }
3397 return [];
3398}
3399
3400// TODO: better called "EventSettings" or "EventConfig"
3401// TODO: move this file into structs
3402// TODO: separate constraint/overlap/allow, because selection uses only that, not other props
3403const EVENT_UI_REFINERS = {
3404 display: String,
3405 editable: Boolean,
3406 startEditable: Boolean,
3407 durationEditable: Boolean,
3408 constraint: identity,
3409 overlap: identity,
3410 allow: identity,
3411 className: parseClassNames,
3412 classNames: parseClassNames,
3413 color: String,
3414 backgroundColor: String,
3415 borderColor: String,
3416 textColor: String,
3417};
3418const EMPTY_EVENT_UI = {
3419 display: null,
3420 startEditable: null,
3421 durationEditable: null,
3422 constraints: [],
3423 overlap: null,
3424 allows: [],
3425 backgroundColor: '',
3426 borderColor: '',
3427 textColor: '',
3428 classNames: [],
3429};
3430function createEventUi(refined, context) {
3431 let constraint = normalizeConstraint(refined.constraint, context);
3432 return {
3433 display: refined.display || null,
3434 startEditable: refined.startEditable != null ? refined.startEditable : refined.editable,
3435 durationEditable: refined.durationEditable != null ? refined.durationEditable : refined.editable,
3436 constraints: constraint != null ? [constraint] : [],
3437 overlap: refined.overlap != null ? refined.overlap : null,
3438 allows: refined.allow != null ? [refined.allow] : [],
3439 backgroundColor: refined.backgroundColor || refined.color || '',
3440 borderColor: refined.borderColor || refined.color || '',
3441 textColor: refined.textColor || '',
3442 classNames: (refined.className || []).concat(refined.classNames || []), // join singular and plural
3443 };
3444}
3445// TODO: prevent against problems with <2 args!
3446function combineEventUis(uis) {
3447 return uis.reduce(combineTwoEventUis, EMPTY_EVENT_UI);
3448}
3449function combineTwoEventUis(item0, item1) {
3450 return {
3451 display: item1.display != null ? item1.display : item0.display,
3452 startEditable: item1.startEditable != null ? item1.startEditable : item0.startEditable,
3453 durationEditable: item1.durationEditable != null ? item1.durationEditable : item0.durationEditable,
3454 constraints: item0.constraints.concat(item1.constraints),
3455 overlap: typeof item1.overlap === 'boolean' ? item1.overlap : item0.overlap,
3456 allows: item0.allows.concat(item1.allows),
3457 backgroundColor: item1.backgroundColor || item0.backgroundColor,
3458 borderColor: item1.borderColor || item0.borderColor,
3459 textColor: item1.textColor || item0.textColor,
3460 classNames: item0.classNames.concat(item1.classNames),
3461 };
3462}
3463
3464const EVENT_SOURCE_REFINERS = {
3465 id: String,
3466 defaultAllDay: Boolean,
3467 url: String,
3468 format: String,
3469 events: identity,
3470 eventDataTransform: identity,
3471 // for any network-related sources
3472 success: identity,
3473 failure: identity,
3474};
3475function parseEventSource(raw, context, refiners = buildEventSourceRefiners(context)) {
3476 let rawObj;
3477 if (typeof raw === 'string') {
3478 rawObj = { url: raw };
3479 }
3480 else if (typeof raw === 'function' || Array.isArray(raw)) {
3481 rawObj = { events: raw };
3482 }
3483 else if (typeof raw === 'object' && raw) { // not null
3484 rawObj = raw;
3485 }
3486 if (rawObj) {
3487 let { refined, extra } = refineProps(rawObj, refiners);
3488 let metaRes = buildEventSourceMeta(refined, context);
3489 if (metaRes) {
3490 return {
3491 _raw: raw,
3492 isFetching: false,
3493 latestFetchId: '',
3494 fetchRange: null,
3495 defaultAllDay: refined.defaultAllDay,
3496 eventDataTransform: refined.eventDataTransform,
3497 success: refined.success,
3498 failure: refined.failure,
3499 publicId: refined.id || '',
3500 sourceId: guid(),
3501 sourceDefId: metaRes.sourceDefId,
3502 meta: metaRes.meta,
3503 ui: createEventUi(refined, context),
3504 extendedProps: extra,
3505 };
3506 }
3507 }
3508 return null;
3509}
3510function buildEventSourceRefiners(context) {
3511 return Object.assign(Object.assign(Object.assign({}, EVENT_UI_REFINERS), EVENT_SOURCE_REFINERS), context.pluginHooks.eventSourceRefiners);
3512}
3513function buildEventSourceMeta(raw, context) {
3514 let defs = context.pluginHooks.eventSourceDefs;
3515 for (let i = defs.length - 1; i >= 0; i -= 1) { // later-added plugins take precedence
3516 let def = defs[i];
3517 let meta = def.parseMeta(raw);
3518 if (meta) {
3519 return { sourceDefId: i, meta };
3520 }
3521 }
3522 return null;
3523}
3524
3525function reduceEventStore(eventStore, action, eventSources, dateProfile, context) {
3526 switch (action.type) {
3527 case 'RECEIVE_EVENTS': // raw
3528 return receiveRawEvents(eventStore, eventSources[action.sourceId], action.fetchId, action.fetchRange, action.rawEvents, context);
3529 case 'RESET_RAW_EVENTS':
3530 return resetRawEvents(eventStore, eventSources[action.sourceId], action.rawEvents, dateProfile.activeRange, context);
3531 case 'ADD_EVENTS': // already parsed, but not expanded
3532 return addEvent(eventStore, action.eventStore, // new ones
3533 dateProfile ? dateProfile.activeRange : null, context);
3534 case 'RESET_EVENTS':
3535 return action.eventStore;
3536 case 'MERGE_EVENTS': // already parsed and expanded
3537 return mergeEventStores(eventStore, action.eventStore);
3538 case 'PREV': // TODO: how do we track all actions that affect dateProfile :(
3539 case 'NEXT':
3540 case 'CHANGE_DATE':
3541 case 'CHANGE_VIEW_TYPE':
3542 if (dateProfile) {
3543 return expandRecurring(eventStore, dateProfile.activeRange, context);
3544 }
3545 return eventStore;
3546 case 'REMOVE_EVENTS':
3547 return excludeSubEventStore(eventStore, action.eventStore);
3548 case 'REMOVE_EVENT_SOURCE':
3549 return excludeEventsBySourceId(eventStore, action.sourceId);
3550 case 'REMOVE_ALL_EVENT_SOURCES':
3551 return filterEventStoreDefs(eventStore, (eventDef) => (!eventDef.sourceId // only keep events with no source id
3552 ));
3553 case 'REMOVE_ALL_EVENTS':
3554 return createEmptyEventStore();
3555 default:
3556 return eventStore;
3557 }
3558}
3559function receiveRawEvents(eventStore, eventSource, fetchId, fetchRange, rawEvents, context) {
3560 if (eventSource && // not already removed
3561 fetchId === eventSource.latestFetchId // TODO: wish this logic was always in event-sources
3562 ) {
3563 let subset = parseEvents(transformRawEvents(rawEvents, eventSource, context), eventSource, context);
3564 if (fetchRange) {
3565 subset = expandRecurring(subset, fetchRange, context);
3566 }
3567 return mergeEventStores(excludeEventsBySourceId(eventStore, eventSource.sourceId), subset);
3568 }
3569 return eventStore;
3570}
3571function resetRawEvents(existingEventStore, eventSource, rawEvents, activeRange, context) {
3572 const { defIdMap, instanceIdMap } = buildPublicIdMaps(existingEventStore);
3573 let newEventStore = parseEvents(transformRawEvents(rawEvents, eventSource, context), eventSource, context, false, defIdMap, instanceIdMap);
3574 return expandRecurring(newEventStore, activeRange, context);
3575}
3576function transformRawEvents(rawEvents, eventSource, context) {
3577 let calEachTransform = context.options.eventDataTransform;
3578 let sourceEachTransform = eventSource ? eventSource.eventDataTransform : null;
3579 if (sourceEachTransform) {
3580 rawEvents = transformEachRawEvent(rawEvents, sourceEachTransform);
3581 }
3582 if (calEachTransform) {
3583 rawEvents = transformEachRawEvent(rawEvents, calEachTransform);
3584 }
3585 return rawEvents;
3586}
3587function transformEachRawEvent(rawEvents, func) {
3588 let refinedEvents;
3589 if (!func) {
3590 refinedEvents = rawEvents;
3591 }
3592 else {
3593 refinedEvents = [];
3594 for (let rawEvent of rawEvents) {
3595 let refinedEvent = func(rawEvent);
3596 if (refinedEvent) {
3597 refinedEvents.push(refinedEvent);
3598 }
3599 else if (refinedEvent == null) {
3600 refinedEvents.push(rawEvent);
3601 } // if a different falsy value, do nothing
3602 }
3603 }
3604 return refinedEvents;
3605}
3606function addEvent(eventStore, subset, expandRange, context) {
3607 if (expandRange) {
3608 subset = expandRecurring(subset, expandRange, context);
3609 }
3610 return mergeEventStores(eventStore, subset);
3611}
3612function rezoneEventStoreDates(eventStore, oldDateEnv, newDateEnv) {
3613 let { defs } = eventStore;
3614 let instances = mapHash(eventStore.instances, (instance) => {
3615 let def = defs[instance.defId];
3616 if (def.allDay) {
3617 return instance; // isn't dependent on timezone
3618 }
3619 return Object.assign(Object.assign({}, instance), { range: {
3620 start: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.start, instance.forcedStartTzo)),
3621 end: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.end, instance.forcedEndTzo)),
3622 }, forcedStartTzo: newDateEnv.canComputeOffset ? null : instance.forcedStartTzo, forcedEndTzo: newDateEnv.canComputeOffset ? null : instance.forcedEndTzo });
3623 });
3624 return { defs, instances };
3625}
3626function excludeEventsBySourceId(eventStore, sourceId) {
3627 return filterEventStoreDefs(eventStore, (eventDef) => eventDef.sourceId !== sourceId);
3628}
3629// QUESTION: why not just return instances? do a general object-property-exclusion util
3630function excludeInstances(eventStore, removals) {
3631 return {
3632 defs: eventStore.defs,
3633 instances: filterHash(eventStore.instances, (instance) => !removals[instance.instanceId]),
3634 };
3635}
3636function buildPublicIdMaps(eventStore) {
3637 const { defs, instances } = eventStore;
3638 const defIdMap = {};
3639 const instanceIdMap = {};
3640 for (let defId in defs) {
3641 const def = defs[defId];
3642 const { publicId } = def;
3643 if (publicId) {
3644 defIdMap[publicId] = defId;
3645 }
3646 }
3647 for (let instanceId in instances) {
3648 const instance = instances[instanceId];
3649 const def = defs[instance.defId];
3650 const { publicId } = def;
3651 if (publicId) {
3652 instanceIdMap[publicId] = instanceId;
3653 }
3654 }
3655 return { defIdMap, instanceIdMap };
3656}
3657
3658class Emitter {
3659 constructor() {
3660 this.handlers = {};
3661 this.thisContext = null;
3662 }
3663 setThisContext(thisContext) {
3664 this.thisContext = thisContext;
3665 }
3666 setOptions(options) {
3667 this.options = options;
3668 }
3669 on(type, handler) {
3670 addToHash(this.handlers, type, handler);
3671 }
3672 off(type, handler) {
3673 removeFromHash(this.handlers, type, handler);
3674 }
3675 trigger(type, ...args) {
3676 let attachedHandlers = this.handlers[type] || [];
3677 let optionHandler = this.options && this.options[type];
3678 let handlers = [].concat(optionHandler || [], attachedHandlers);
3679 for (let handler of handlers) {
3680 handler.apply(this.thisContext, args);
3681 }
3682 }
3683 hasHandlers(type) {
3684 return Boolean((this.handlers[type] && this.handlers[type].length) ||
3685 (this.options && this.options[type]));
3686 }
3687}
3688function addToHash(hash, type, handler) {
3689 (hash[type] || (hash[type] = []))
3690 .push(handler);
3691}
3692function removeFromHash(hash, type, handler) {
3693 if (handler) {
3694 if (hash[type]) {
3695 hash[type] = hash[type].filter((func) => func !== handler);
3696 }
3697 }
3698 else {
3699 delete hash[type]; // remove all handler funcs for this type
3700 }
3701}
3702
3703const DEF_DEFAULTS = {
3704 startTime: '09:00',
3705 endTime: '17:00',
3706 daysOfWeek: [1, 2, 3, 4, 5],
3707 display: 'inverse-background',
3708 classNames: 'fc-non-business',
3709 groupId: '_businessHours', // so multiple defs get grouped
3710};
3711/*
3712TODO: pass around as EventDefHash!!!
3713*/
3714function parseBusinessHours(input, context) {
3715 return parseEvents(refineInputs(input), null, context);
3716}
3717function refineInputs(input) {
3718 let rawDefs;
3719 if (input === true) {
3720 rawDefs = [{}]; // will get DEF_DEFAULTS verbatim
3721 }
3722 else if (Array.isArray(input)) {
3723 // if specifying an array, every sub-definition NEEDS a day-of-week
3724 rawDefs = input.filter((rawDef) => rawDef.daysOfWeek);
3725 }
3726 else if (typeof input === 'object' && input) { // non-null object
3727 rawDefs = [input];
3728 }
3729 else { // is probably false
3730 rawDefs = [];
3731 }
3732 rawDefs = rawDefs.map((rawDef) => (Object.assign(Object.assign({}, DEF_DEFAULTS), rawDef)));
3733 return rawDefs;
3734}
3735
3736function triggerDateSelect(selection, pev, context) {
3737 context.emitter.trigger('select', Object.assign(Object.assign({}, buildDateSpanApiWithContext(selection, context)), { jsEvent: pev ? pev.origEvent : null, view: context.viewApi || context.calendarApi.view }));
3738}
3739function triggerDateUnselect(pev, context) {
3740 context.emitter.trigger('unselect', {
3741 jsEvent: pev ? pev.origEvent : null,
3742 view: context.viewApi || context.calendarApi.view,
3743 });
3744}
3745function buildDateSpanApiWithContext(dateSpan, context) {
3746 let props = {};
3747 for (let transform of context.pluginHooks.dateSpanTransforms) {
3748 Object.assign(props, transform(dateSpan, context));
3749 }
3750 Object.assign(props, buildDateSpanApi(dateSpan, context.dateEnv));
3751 return props;
3752}
3753// Given an event's allDay status and start date, return what its fallback end date should be.
3754// TODO: rename to computeDefaultEventEnd
3755function getDefaultEventEnd(allDay, marker, context) {
3756 let { dateEnv, options } = context;
3757 let end = marker;
3758 if (allDay) {
3759 end = startOfDay(end);
3760 end = dateEnv.add(end, options.defaultAllDayEventDuration);
3761 }
3762 else {
3763 end = dateEnv.add(end, options.defaultTimedEventDuration);
3764 }
3765 return end;
3766}
3767
3768// applies the mutation to ALL defs/instances within the event store
3769function applyMutationToEventStore(eventStore, eventConfigBase, mutation, context) {
3770 let eventConfigs = compileEventUis(eventStore.defs, eventConfigBase);
3771 let dest = createEmptyEventStore();
3772 for (let defId in eventStore.defs) {
3773 let def = eventStore.defs[defId];
3774 dest.defs[defId] = applyMutationToEventDef(def, eventConfigs[defId], mutation, context);
3775 }
3776 for (let instanceId in eventStore.instances) {
3777 let instance = eventStore.instances[instanceId];
3778 let def = dest.defs[instance.defId]; // important to grab the newly modified def
3779 dest.instances[instanceId] = applyMutationToEventInstance(instance, def, eventConfigs[instance.defId], mutation, context);
3780 }
3781 return dest;
3782}
3783function applyMutationToEventDef(eventDef, eventConfig, mutation, context) {
3784 let standardProps = mutation.standardProps || {};
3785 // if hasEnd has not been specified, guess a good value based on deltas.
3786 // if duration will change, there's no way the default duration will persist,
3787 // and thus, we need to mark the event as having a real end
3788 if (standardProps.hasEnd == null &&
3789 eventConfig.durationEditable &&
3790 (mutation.startDelta || mutation.endDelta)) {
3791 standardProps.hasEnd = true; // TODO: is this mutation okay?
3792 }
3793 let copy = Object.assign(Object.assign(Object.assign({}, eventDef), standardProps), { ui: Object.assign(Object.assign({}, eventDef.ui), standardProps.ui) });
3794 if (mutation.extendedProps) {
3795 copy.extendedProps = Object.assign(Object.assign({}, copy.extendedProps), mutation.extendedProps);
3796 }
3797 for (let applier of context.pluginHooks.eventDefMutationAppliers) {
3798 applier(copy, mutation, context);
3799 }
3800 if (!copy.hasEnd && context.options.forceEventDuration) {
3801 copy.hasEnd = true;
3802 }
3803 return copy;
3804}
3805function applyMutationToEventInstance(eventInstance, eventDef, // must first be modified by applyMutationToEventDef
3806eventConfig, mutation, context) {
3807 let { dateEnv } = context;
3808 let forceAllDay = mutation.standardProps && mutation.standardProps.allDay === true;
3809 let clearEnd = mutation.standardProps && mutation.standardProps.hasEnd === false;
3810 let copy = Object.assign({}, eventInstance);
3811 if (forceAllDay) {
3812 copy.range = computeAlignedDayRange(copy.range);
3813 }
3814 if (mutation.datesDelta && eventConfig.startEditable) {
3815 copy.range = {
3816 start: dateEnv.add(copy.range.start, mutation.datesDelta),
3817 end: dateEnv.add(copy.range.end, mutation.datesDelta),
3818 };
3819 }
3820 if (mutation.startDelta && eventConfig.durationEditable) {
3821 copy.range = {
3822 start: dateEnv.add(copy.range.start, mutation.startDelta),
3823 end: copy.range.end,
3824 };
3825 }
3826 if (mutation.endDelta && eventConfig.durationEditable) {
3827 copy.range = {
3828 start: copy.range.start,
3829 end: dateEnv.add(copy.range.end, mutation.endDelta),
3830 };
3831 }
3832 if (clearEnd) {
3833 copy.range = {
3834 start: copy.range.start,
3835 end: getDefaultEventEnd(eventDef.allDay, copy.range.start, context),
3836 };
3837 }
3838 // in case event was all-day but the supplied deltas were not
3839 // better util for this?
3840 if (eventDef.allDay) {
3841 copy.range = {
3842 start: startOfDay(copy.range.start),
3843 end: startOfDay(copy.range.end),
3844 };
3845 }
3846 // handle invalid durations
3847 if (copy.range.end < copy.range.start) {
3848 copy.range.end = getDefaultEventEnd(eventDef.allDay, copy.range.start, context);
3849 }
3850 return copy;
3851}
3852
3853class EventSourceImpl {
3854 constructor(context, internalEventSource) {
3855 this.context = context;
3856 this.internalEventSource = internalEventSource;
3857 }
3858 remove() {
3859 this.context.dispatch({
3860 type: 'REMOVE_EVENT_SOURCE',
3861 sourceId: this.internalEventSource.sourceId,
3862 });
3863 }
3864 refetch() {
3865 this.context.dispatch({
3866 type: 'FETCH_EVENT_SOURCES',
3867 sourceIds: [this.internalEventSource.sourceId],
3868 isRefetch: true,
3869 });
3870 }
3871 get id() {
3872 return this.internalEventSource.publicId;
3873 }
3874 get url() {
3875 return this.internalEventSource.meta.url;
3876 }
3877 get format() {
3878 return this.internalEventSource.meta.format; // TODO: bad. not guaranteed
3879 }
3880}
3881
3882class EventImpl {
3883 // instance will be null if expressing a recurring event that has no current instances,
3884 // OR if trying to validate an incoming external event that has no dates assigned
3885 constructor(context, def, instance) {
3886 this._context = context;
3887 this._def = def;
3888 this._instance = instance || null;
3889 }
3890 /*
3891 TODO: make event struct more responsible for this
3892 */
3893 setProp(name, val) {
3894 if (name in EVENT_DATE_REFINERS) {
3895 console.warn('Could not set date-related prop \'name\'. Use one of the date-related methods instead.');
3896 // TODO: make proper aliasing system?
3897 }
3898 else if (name === 'id') {
3899 val = EVENT_NON_DATE_REFINERS[name](val);
3900 this.mutate({
3901 standardProps: { publicId: val }, // hardcoded internal name
3902 });
3903 }
3904 else if (name in EVENT_NON_DATE_REFINERS) {
3905 val = EVENT_NON_DATE_REFINERS[name](val);
3906 this.mutate({
3907 standardProps: { [name]: val },
3908 });
3909 }
3910 else if (name in EVENT_UI_REFINERS) {
3911 let ui = EVENT_UI_REFINERS[name](val);
3912 if (name === 'color') {
3913 ui = { backgroundColor: val, borderColor: val };
3914 }
3915 else if (name === 'editable') {
3916 ui = { startEditable: val, durationEditable: val };
3917 }
3918 else {
3919 ui = { [name]: val };
3920 }
3921 this.mutate({
3922 standardProps: { ui },
3923 });
3924 }
3925 else {
3926 console.warn(`Could not set prop '${name}'. Use setExtendedProp instead.`);
3927 }
3928 }
3929 setExtendedProp(name, val) {
3930 this.mutate({
3931 extendedProps: { [name]: val },
3932 });
3933 }
3934 setStart(startInput, options = {}) {
3935 let { dateEnv } = this._context;
3936 let start = dateEnv.createMarker(startInput);
3937 if (start && this._instance) { // TODO: warning if parsed bad
3938 let instanceRange = this._instance.range;
3939 let startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity); // what if parsed bad!?
3940 if (options.maintainDuration) {
3941 this.mutate({ datesDelta: startDelta });
3942 }
3943 else {
3944 this.mutate({ startDelta });
3945 }
3946 }
3947 }
3948 setEnd(endInput, options = {}) {
3949 let { dateEnv } = this._context;
3950 let end;
3951 if (endInput != null) {
3952 end = dateEnv.createMarker(endInput);
3953 if (!end) {
3954 return; // TODO: warning if parsed bad
3955 }
3956 }
3957 if (this._instance) {
3958 if (end) {
3959 let endDelta = diffDates(this._instance.range.end, end, dateEnv, options.granularity);
3960 this.mutate({ endDelta });
3961 }
3962 else {
3963 this.mutate({ standardProps: { hasEnd: false } });
3964 }
3965 }
3966 }
3967 setDates(startInput, endInput, options = {}) {
3968 let { dateEnv } = this._context;
3969 let standardProps = { allDay: options.allDay };
3970 let start = dateEnv.createMarker(startInput);
3971 let end;
3972 if (!start) {
3973 return; // TODO: warning if parsed bad
3974 }
3975 if (endInput != null) {
3976 end = dateEnv.createMarker(endInput);
3977 if (!end) { // TODO: warning if parsed bad
3978 return;
3979 }
3980 }
3981 if (this._instance) {
3982 let instanceRange = this._instance.range;
3983 // when computing the diff for an event being converted to all-day,
3984 // compute diff off of the all-day values the way event-mutation does.
3985 if (options.allDay === true) {
3986 instanceRange = computeAlignedDayRange(instanceRange);
3987 }
3988 let startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity);
3989 if (end) {
3990 let endDelta = diffDates(instanceRange.end, end, dateEnv, options.granularity);
3991 if (durationsEqual(startDelta, endDelta)) {
3992 this.mutate({ datesDelta: startDelta, standardProps });
3993 }
3994 else {
3995 this.mutate({ startDelta, endDelta, standardProps });
3996 }
3997 }
3998 else { // means "clear the end"
3999 standardProps.hasEnd = false;
4000 this.mutate({ datesDelta: startDelta, standardProps });
4001 }
4002 }
4003 }
4004 moveStart(deltaInput) {
4005 let delta = createDuration(deltaInput);
4006 if (delta) { // TODO: warning if parsed bad
4007 this.mutate({ startDelta: delta });
4008 }
4009 }
4010 moveEnd(deltaInput) {
4011 let delta = createDuration(deltaInput);
4012 if (delta) { // TODO: warning if parsed bad
4013 this.mutate({ endDelta: delta });
4014 }
4015 }
4016 moveDates(deltaInput) {
4017 let delta = createDuration(deltaInput);
4018 if (delta) { // TODO: warning if parsed bad
4019 this.mutate({ datesDelta: delta });
4020 }
4021 }
4022 setAllDay(allDay, options = {}) {
4023 let standardProps = { allDay };
4024 let { maintainDuration } = options;
4025 if (maintainDuration == null) {
4026 maintainDuration = this._context.options.allDayMaintainDuration;
4027 }
4028 if (this._def.allDay !== allDay) {
4029 standardProps.hasEnd = maintainDuration;
4030 }
4031 this.mutate({ standardProps });
4032 }
4033 formatRange(formatInput) {
4034 let { dateEnv } = this._context;
4035 let instance = this._instance;
4036 let formatter = createFormatter(formatInput);
4037 if (this._def.hasEnd) {
4038 return dateEnv.formatRange(instance.range.start, instance.range.end, formatter, {
4039 forcedStartTzo: instance.forcedStartTzo,
4040 forcedEndTzo: instance.forcedEndTzo,
4041 });
4042 }
4043 return dateEnv.format(instance.range.start, formatter, {
4044 forcedTzo: instance.forcedStartTzo,
4045 });
4046 }
4047 mutate(mutation) {
4048 let instance = this._instance;
4049 if (instance) {
4050 let def = this._def;
4051 let context = this._context;
4052 let { eventStore } = context.getCurrentData();
4053 let relevantEvents = getRelevantEvents(eventStore, instance.instanceId);
4054 let eventConfigBase = {
4055 '': {
4056 display: '',
4057 startEditable: true,
4058 durationEditable: true,
4059 constraints: [],
4060 overlap: null,
4061 allows: [],
4062 backgroundColor: '',
4063 borderColor: '',
4064 textColor: '',
4065 classNames: [],
4066 },
4067 };
4068 relevantEvents = applyMutationToEventStore(relevantEvents, eventConfigBase, mutation, context);
4069 let oldEvent = new EventImpl(context, def, instance); // snapshot
4070 this._def = relevantEvents.defs[def.defId];
4071 this._instance = relevantEvents.instances[instance.instanceId];
4072 context.dispatch({
4073 type: 'MERGE_EVENTS',
4074 eventStore: relevantEvents,
4075 });
4076 context.emitter.trigger('eventChange', {
4077 oldEvent,
4078 event: this,
4079 relatedEvents: buildEventApis(relevantEvents, context, instance),
4080 revert() {
4081 context.dispatch({
4082 type: 'RESET_EVENTS',
4083 eventStore, // the ORIGINAL store
4084 });
4085 },
4086 });
4087 }
4088 }
4089 remove() {
4090 let context = this._context;
4091 let asStore = eventApiToStore(this);
4092 context.dispatch({
4093 type: 'REMOVE_EVENTS',
4094 eventStore: asStore,
4095 });
4096 context.emitter.trigger('eventRemove', {
4097 event: this,
4098 relatedEvents: [],
4099 revert() {
4100 context.dispatch({
4101 type: 'MERGE_EVENTS',
4102 eventStore: asStore,
4103 });
4104 },
4105 });
4106 }
4107 get source() {
4108 let { sourceId } = this._def;
4109 if (sourceId) {
4110 return new EventSourceImpl(this._context, this._context.getCurrentData().eventSources[sourceId]);
4111 }
4112 return null;
4113 }
4114 get start() {
4115 return this._instance ?
4116 this._context.dateEnv.toDate(this._instance.range.start) :
4117 null;
4118 }
4119 get end() {
4120 return (this._instance && this._def.hasEnd) ?
4121 this._context.dateEnv.toDate(this._instance.range.end) :
4122 null;
4123 }
4124 get startStr() {
4125 let instance = this._instance;
4126 if (instance) {
4127 return this._context.dateEnv.formatIso(instance.range.start, {
4128 omitTime: this._def.allDay,
4129 forcedTzo: instance.forcedStartTzo,
4130 });
4131 }
4132 return '';
4133 }
4134 get endStr() {
4135 let instance = this._instance;
4136 if (instance && this._def.hasEnd) {
4137 return this._context.dateEnv.formatIso(instance.range.end, {
4138 omitTime: this._def.allDay,
4139 forcedTzo: instance.forcedEndTzo,
4140 });
4141 }
4142 return '';
4143 }
4144 // computable props that all access the def
4145 // TODO: find a TypeScript-compatible way to do this at scale
4146 get id() { return this._def.publicId; }
4147 get groupId() { return this._def.groupId; }
4148 get allDay() { return this._def.allDay; }
4149 get title() { return this._def.title; }
4150 get url() { return this._def.url; }
4151 get display() { return this._def.ui.display || 'auto'; } // bad. just normalize the type earlier
4152 get startEditable() { return this._def.ui.startEditable; }
4153 get durationEditable() { return this._def.ui.durationEditable; }
4154 get constraint() { return this._def.ui.constraints[0] || null; }
4155 get overlap() { return this._def.ui.overlap; }
4156 get allow() { return this._def.ui.allows[0] || null; }
4157 get backgroundColor() { return this._def.ui.backgroundColor; }
4158 get borderColor() { return this._def.ui.borderColor; }
4159 get textColor() { return this._def.ui.textColor; }
4160 // NOTE: user can't modify these because Object.freeze was called in event-def parsing
4161 get classNames() { return this._def.ui.classNames; }
4162 get extendedProps() { return this._def.extendedProps; }
4163 toPlainObject(settings = {}) {
4164 let def = this._def;
4165 let { ui } = def;
4166 let { startStr, endStr } = this;
4167 let res = {
4168 allDay: def.allDay,
4169 };
4170 if (def.title) {
4171 res.title = def.title;
4172 }
4173 if (startStr) {
4174 res.start = startStr;
4175 }
4176 if (endStr) {
4177 res.end = endStr;
4178 }
4179 if (def.publicId) {
4180 res.id = def.publicId;
4181 }
4182 if (def.groupId) {
4183 res.groupId = def.groupId;
4184 }
4185 if (def.url) {
4186 res.url = def.url;
4187 }
4188 if (ui.display && ui.display !== 'auto') {
4189 res.display = ui.display;
4190 }
4191 // TODO: what about recurring-event properties???
4192 // TODO: include startEditable/durationEditable/constraint/overlap/allow
4193 if (settings.collapseColor && ui.backgroundColor && ui.backgroundColor === ui.borderColor) {
4194 res.color = ui.backgroundColor;
4195 }
4196 else {
4197 if (ui.backgroundColor) {
4198 res.backgroundColor = ui.backgroundColor;
4199 }
4200 if (ui.borderColor) {
4201 res.borderColor = ui.borderColor;
4202 }
4203 }
4204 if (ui.textColor) {
4205 res.textColor = ui.textColor;
4206 }
4207 if (ui.classNames.length) {
4208 res.classNames = ui.classNames;
4209 }
4210 if (Object.keys(def.extendedProps).length) {
4211 if (settings.collapseExtendedProps) {
4212 Object.assign(res, def.extendedProps);
4213 }
4214 else {
4215 res.extendedProps = def.extendedProps;
4216 }
4217 }
4218 return res;
4219 }
4220 toJSON() {
4221 return this.toPlainObject();
4222 }
4223}
4224function eventApiToStore(eventApi) {
4225 let def = eventApi._def;
4226 let instance = eventApi._instance;
4227 return {
4228 defs: { [def.defId]: def },
4229 instances: instance
4230 ? { [instance.instanceId]: instance }
4231 : {},
4232 };
4233}
4234function buildEventApis(eventStore, context, excludeInstance) {
4235 let { defs, instances } = eventStore;
4236 let eventApis = [];
4237 let excludeInstanceId = excludeInstance ? excludeInstance.instanceId : '';
4238 for (let id in instances) {
4239 let instance = instances[id];
4240 let def = defs[instance.defId];
4241 if (instance.instanceId !== excludeInstanceId) {
4242 eventApis.push(new EventImpl(context, def, instance));
4243 }
4244 }
4245 return eventApis;
4246}
4247
4248/*
4249Specifying nextDayThreshold signals that all-day ranges should be sliced.
4250*/
4251function sliceEventStore(eventStore, eventUiBases, framingRange, nextDayThreshold) {
4252 let inverseBgByGroupId = {};
4253 let inverseBgByDefId = {};
4254 let defByGroupId = {};
4255 let bgRanges = [];
4256 let fgRanges = [];
4257 let eventUis = compileEventUis(eventStore.defs, eventUiBases);
4258 for (let defId in eventStore.defs) {
4259 let def = eventStore.defs[defId];
4260 let ui = eventUis[def.defId];
4261 if (ui.display === 'inverse-background') {
4262 if (def.groupId) {
4263 inverseBgByGroupId[def.groupId] = [];
4264 if (!defByGroupId[def.groupId]) {
4265 defByGroupId[def.groupId] = def;
4266 }
4267 }
4268 else {
4269 inverseBgByDefId[defId] = [];
4270 }
4271 }
4272 }
4273 for (let instanceId in eventStore.instances) {
4274 let instance = eventStore.instances[instanceId];
4275 let def = eventStore.defs[instance.defId];
4276 let ui = eventUis[def.defId];
4277 let origRange = instance.range;
4278 let normalRange = (!def.allDay && nextDayThreshold) ?
4279 computeVisibleDayRange(origRange, nextDayThreshold) :
4280 origRange;
4281 let slicedRange = intersectRanges(normalRange, framingRange);
4282 if (slicedRange) {
4283 if (ui.display === 'inverse-background') {
4284 if (def.groupId) {
4285 inverseBgByGroupId[def.groupId].push(slicedRange);
4286 }
4287 else {
4288 inverseBgByDefId[instance.defId].push(slicedRange);
4289 }
4290 }
4291 else if (ui.display !== 'none') {
4292 (ui.display === 'background' ? bgRanges : fgRanges).push({
4293 def,
4294 ui,
4295 instance,
4296 range: slicedRange,
4297 isStart: normalRange.start && normalRange.start.valueOf() === slicedRange.start.valueOf(),
4298 isEnd: normalRange.end && normalRange.end.valueOf() === slicedRange.end.valueOf(),
4299 });
4300 }
4301 }
4302 }
4303 for (let groupId in inverseBgByGroupId) { // BY GROUP
4304 let ranges = inverseBgByGroupId[groupId];
4305 let invertedRanges = invertRanges(ranges, framingRange);
4306 for (let invertedRange of invertedRanges) {
4307 let def = defByGroupId[groupId];
4308 let ui = eventUis[def.defId];
4309 bgRanges.push({
4310 def,
4311 ui,
4312 instance: null,
4313 range: invertedRange,
4314 isStart: false,
4315 isEnd: false,
4316 });
4317 }
4318 }
4319 for (let defId in inverseBgByDefId) {
4320 let ranges = inverseBgByDefId[defId];
4321 let invertedRanges = invertRanges(ranges, framingRange);
4322 for (let invertedRange of invertedRanges) {
4323 bgRanges.push({
4324 def: eventStore.defs[defId],
4325 ui: eventUis[defId],
4326 instance: null,
4327 range: invertedRange,
4328 isStart: false,
4329 isEnd: false,
4330 });
4331 }
4332 }
4333 return { bg: bgRanges, fg: fgRanges };
4334}
4335function hasBgRendering(def) {
4336 return def.ui.display === 'background' || def.ui.display === 'inverse-background';
4337}
4338function setElSeg(el, seg) {
4339 el.fcSeg = seg;
4340}
4341function getElSeg(el) {
4342 return el.fcSeg ||
4343 el.parentNode.fcSeg || // for the harness
4344 null;
4345}
4346// event ui computation
4347function compileEventUis(eventDefs, eventUiBases) {
4348 return mapHash(eventDefs, (eventDef) => compileEventUi(eventDef, eventUiBases));
4349}
4350function compileEventUi(eventDef, eventUiBases) {
4351 let uis = [];
4352 if (eventUiBases['']) {
4353 uis.push(eventUiBases['']);
4354 }
4355 if (eventUiBases[eventDef.defId]) {
4356 uis.push(eventUiBases[eventDef.defId]);
4357 }
4358 uis.push(eventDef.ui);
4359 return combineEventUis(uis);
4360}
4361function sortEventSegs(segs, eventOrderSpecs) {
4362 let objs = segs.map(buildSegCompareObj);
4363 objs.sort((obj0, obj1) => compareByFieldSpecs(obj0, obj1, eventOrderSpecs));
4364 return objs.map((c) => c._seg);
4365}
4366// returns a object with all primitive props that can be compared
4367function buildSegCompareObj(seg) {
4368 let { eventRange } = seg;
4369 let eventDef = eventRange.def;
4370 let range = eventRange.instance ? eventRange.instance.range : eventRange.range;
4371 let start = range.start ? range.start.valueOf() : 0; // TODO: better support for open-range events
4372 let end = range.end ? range.end.valueOf() : 0; // "
4373 return Object.assign(Object.assign(Object.assign({}, eventDef.extendedProps), eventDef), { id: eventDef.publicId, start,
4374 end, duration: end - start, allDay: Number(eventDef.allDay), _seg: seg });
4375}
4376function computeSegDraggable(seg, context) {
4377 let { pluginHooks } = context;
4378 let transformers = pluginHooks.isDraggableTransformers;
4379 let { def, ui } = seg.eventRange;
4380 let val = ui.startEditable;
4381 for (let transformer of transformers) {
4382 val = transformer(val, def, ui, context);
4383 }
4384 return val;
4385}
4386function computeSegStartResizable(seg, context) {
4387 return seg.isStart && seg.eventRange.ui.durationEditable && context.options.eventResizableFromStart;
4388}
4389function computeSegEndResizable(seg, context) {
4390 return seg.isEnd && seg.eventRange.ui.durationEditable;
4391}
4392function buildSegTimeText(seg, timeFormat, context, defaultDisplayEventTime, // defaults to true
4393defaultDisplayEventEnd, // defaults to true
4394startOverride, endOverride) {
4395 let { dateEnv, options } = context;
4396 let { displayEventTime, displayEventEnd } = options;
4397 let eventDef = seg.eventRange.def;
4398 let eventInstance = seg.eventRange.instance;
4399 if (displayEventTime == null) {
4400 displayEventTime = defaultDisplayEventTime !== false;
4401 }
4402 if (displayEventEnd == null) {
4403 displayEventEnd = defaultDisplayEventEnd !== false;
4404 }
4405 let wholeEventStart = eventInstance.range.start;
4406 let wholeEventEnd = eventInstance.range.end;
4407 let segStart = startOverride || seg.start || seg.eventRange.range.start;
4408 let segEnd = endOverride || seg.end || seg.eventRange.range.end;
4409 let isStartDay = startOfDay(wholeEventStart).valueOf() === startOfDay(segStart).valueOf();
4410 let isEndDay = startOfDay(addMs(wholeEventEnd, -1)).valueOf() === startOfDay(addMs(segEnd, -1)).valueOf();
4411 if (displayEventTime && !eventDef.allDay && (isStartDay || isEndDay)) {
4412 segStart = isStartDay ? wholeEventStart : segStart;
4413 segEnd = isEndDay ? wholeEventEnd : segEnd;
4414 if (displayEventEnd && eventDef.hasEnd) {
4415 return dateEnv.formatRange(segStart, segEnd, timeFormat, {
4416 forcedStartTzo: startOverride ? null : eventInstance.forcedStartTzo,
4417 forcedEndTzo: endOverride ? null : eventInstance.forcedEndTzo,
4418 });
4419 }
4420 return dateEnv.format(segStart, timeFormat, {
4421 forcedTzo: startOverride ? null : eventInstance.forcedStartTzo, // nooooo, same
4422 });
4423 }
4424 return '';
4425}
4426function getSegMeta(seg, todayRange, nowDate) {
4427 let segRange = seg.eventRange.range;
4428 return {
4429 isPast: segRange.end <= (nowDate || todayRange.start),
4430 isFuture: segRange.start >= (nowDate || todayRange.end),
4431 isToday: todayRange && rangeContainsMarker(todayRange, segRange.start),
4432 };
4433}
4434function getEventClassNames(props) {
4435 let classNames = ['fc-event'];
4436 if (props.isMirror) {
4437 classNames.push('fc-event-mirror');
4438 }
4439 if (props.isDraggable) {
4440 classNames.push('fc-event-draggable');
4441 }
4442 if (props.isStartResizable || props.isEndResizable) {
4443 classNames.push('fc-event-resizable');
4444 }
4445 if (props.isDragging) {
4446 classNames.push('fc-event-dragging');
4447 }
4448 if (props.isResizing) {
4449 classNames.push('fc-event-resizing');
4450 }
4451 if (props.isSelected) {
4452 classNames.push('fc-event-selected');
4453 }
4454 if (props.isStart) {
4455 classNames.push('fc-event-start');
4456 }
4457 if (props.isEnd) {
4458 classNames.push('fc-event-end');
4459 }
4460 if (props.isPast) {
4461 classNames.push('fc-event-past');
4462 }
4463 if (props.isToday) {
4464 classNames.push('fc-event-today');
4465 }
4466 if (props.isFuture) {
4467 classNames.push('fc-event-future');
4468 }
4469 return classNames;
4470}
4471function buildEventRangeKey(eventRange) {
4472 return eventRange.instance
4473 ? eventRange.instance.instanceId
4474 : `${eventRange.def.defId}:${eventRange.range.start.toISOString()}`;
4475 // inverse-background events don't have specific instances. TODO: better solution
4476}
4477function getSegAnchorAttrs(seg, context) {
4478 let { def, instance } = seg.eventRange;
4479 let { url } = def;
4480 if (url) {
4481 return { href: url };
4482 }
4483 let { emitter, options } = context;
4484 let { eventInteractive } = options;
4485 if (eventInteractive == null) {
4486 eventInteractive = def.interactive;
4487 if (eventInteractive == null) {
4488 eventInteractive = Boolean(emitter.hasHandlers('eventClick'));
4489 }
4490 }
4491 // mock what happens in EventClicking
4492 if (eventInteractive) {
4493 // only attach keyboard-related handlers because click handler is already done in EventClicking
4494 return createAriaKeyboardAttrs((ev) => {
4495 emitter.trigger('eventClick', {
4496 el: ev.target,
4497 event: new EventImpl(context, def, instance),
4498 jsEvent: ev,
4499 view: context.viewApi,
4500 });
4501 });
4502 }
4503 return {};
4504}
4505
4506const STANDARD_PROPS = {
4507 start: identity,
4508 end: identity,
4509 allDay: Boolean,
4510};
4511function parseDateSpan(raw, dateEnv, defaultDuration) {
4512 let span = parseOpenDateSpan(raw, dateEnv);
4513 let { range } = span;
4514 if (!range.start) {
4515 return null;
4516 }
4517 if (!range.end) {
4518 if (defaultDuration == null) {
4519 return null;
4520 }
4521 range.end = dateEnv.add(range.start, defaultDuration);
4522 }
4523 return span;
4524}
4525/*
4526TODO: somehow combine with parseRange?
4527Will return null if the start/end props were present but parsed invalidly.
4528*/
4529function parseOpenDateSpan(raw, dateEnv) {
4530 let { refined: standardProps, extra } = refineProps(raw, STANDARD_PROPS);
4531 let startMeta = standardProps.start ? dateEnv.createMarkerMeta(standardProps.start) : null;
4532 let endMeta = standardProps.end ? dateEnv.createMarkerMeta(standardProps.end) : null;
4533 let { allDay } = standardProps;
4534 if (allDay == null) {
4535 allDay = (startMeta && startMeta.isTimeUnspecified) &&
4536 (!endMeta || endMeta.isTimeUnspecified);
4537 }
4538 return Object.assign({ range: {
4539 start: startMeta ? startMeta.marker : null,
4540 end: endMeta ? endMeta.marker : null,
4541 }, allDay }, extra);
4542}
4543function isDateSpansEqual(span0, span1) {
4544 return rangesEqual(span0.range, span1.range) &&
4545 span0.allDay === span1.allDay &&
4546 isSpanPropsEqual(span0, span1);
4547}
4548// the NON-DATE-RELATED props
4549function isSpanPropsEqual(span0, span1) {
4550 for (let propName in span1) {
4551 if (propName !== 'range' && propName !== 'allDay') {
4552 if (span0[propName] !== span1[propName]) {
4553 return false;
4554 }
4555 }
4556 }
4557 // are there any props that span0 has that span1 DOESN'T have?
4558 // both have range/allDay, so no need to special-case.
4559 for (let propName in span0) {
4560 if (!(propName in span1)) {
4561 return false;
4562 }
4563 }
4564 return true;
4565}
4566function buildDateSpanApi(span, dateEnv) {
4567 return Object.assign(Object.assign({}, buildRangeApi(span.range, dateEnv, span.allDay)), { allDay: span.allDay });
4568}
4569function buildRangeApiWithTimeZone(range, dateEnv, omitTime) {
4570 return Object.assign(Object.assign({}, buildRangeApi(range, dateEnv, omitTime)), { timeZone: dateEnv.timeZone });
4571}
4572function buildRangeApi(range, dateEnv, omitTime) {
4573 return {
4574 start: dateEnv.toDate(range.start),
4575 end: dateEnv.toDate(range.end),
4576 startStr: dateEnv.formatIso(range.start, { omitTime }),
4577 endStr: dateEnv.formatIso(range.end, { omitTime }),
4578 };
4579}
4580function fabricateEventRange(dateSpan, eventUiBases, context) {
4581 let res = refineEventDef({ editable: false }, context);
4582 let def = parseEventDef(res.refined, res.extra, '', // sourceId
4583 dateSpan.allDay, true, // hasEnd
4584 context);
4585 return {
4586 def,
4587 ui: compileEventUi(def, eventUiBases),
4588 instance: createEventInstance(def.defId, dateSpan.range),
4589 range: dateSpan.range,
4590 isStart: true,
4591 isEnd: true,
4592 };
4593}
4594
4595/*
4596given a function that resolves a result asynchronously.
4597the function can either call passed-in success and failure callbacks,
4598or it can return a promise.
4599if you need to pass additional params to func, bind them first.
4600*/
4601function unpromisify(func, normalizedSuccessCallback, normalizedFailureCallback) {
4602 // guard against success/failure callbacks being called more than once
4603 // and guard against a promise AND callback being used together.
4604 let isResolved = false;
4605 let wrappedSuccess = function (res) {
4606 if (!isResolved) {
4607 isResolved = true;
4608 normalizedSuccessCallback(res);
4609 }
4610 };
4611 let wrappedFailure = function (error) {
4612 if (!isResolved) {
4613 isResolved = true;
4614 normalizedFailureCallback(error);
4615 }
4616 };
4617 let res = func(wrappedSuccess, wrappedFailure);
4618 if (res && typeof res.then === 'function') {
4619 res.then(wrappedSuccess, wrappedFailure);
4620 }
4621}
4622
4623class JsonRequestError extends Error {
4624 constructor(message, response) {
4625 super(message);
4626 this.response = response;
4627 }
4628}
4629function requestJson(method, url, params) {
4630 method = method.toUpperCase();
4631 const fetchOptions = {
4632 method,
4633 };
4634 if (method === 'GET') {
4635 url += (url.indexOf('?') === -1 ? '?' : '&') +
4636 new URLSearchParams(params);
4637 }
4638 else {
4639 fetchOptions.body = new URLSearchParams(params);
4640 fetchOptions.headers = {
4641 'Content-Type': 'application/x-www-form-urlencoded',
4642 };
4643 }
4644 return fetch(url, fetchOptions).then((fetchRes) => {
4645 if (fetchRes.ok) {
4646 return fetchRes.json().then((parsedResponse) => {
4647 return [parsedResponse, fetchRes];
4648 }, () => {
4649 throw new JsonRequestError('Failure parsing JSON', fetchRes);
4650 });
4651 }
4652 else {
4653 throw new JsonRequestError('Request failed', fetchRes);
4654 }
4655 });
4656}
4657
4658let canVGrowWithinCell;
4659function getCanVGrowWithinCell() {
4660 if (canVGrowWithinCell == null) {
4661 canVGrowWithinCell = computeCanVGrowWithinCell();
4662 }
4663 return canVGrowWithinCell;
4664}
4665function computeCanVGrowWithinCell() {
4666 // for SSR, because this function is call immediately at top-level
4667 // TODO: just make this logic execute top-level, immediately, instead of doing lazily
4668 if (typeof document === 'undefined') {
4669 return true;
4670 }
4671 let el = document.createElement('div');
4672 el.style.position = 'absolute';
4673 el.style.top = '0px';
4674 el.style.left = '0px';
4675 el.innerHTML = '<table><tr><td><div></div></td></tr></table>';
4676 el.querySelector('table').style.height = '100px';
4677 el.querySelector('div').style.height = '100%';
4678 document.body.appendChild(el);
4679 let div = el.querySelector('div');
4680 let possible = div.offsetHeight > 0;
4681 document.body.removeChild(el);
4682 return possible;
4683}
4684
4685class CalendarRoot extends BaseComponent {
4686 constructor() {
4687 super(...arguments);
4688 this.state = {
4689 forPrint: false,
4690 };
4691 this.handleBeforePrint = () => {
4692 flushSync(() => {
4693 this.setState({ forPrint: true });
4694 });
4695 };
4696 this.handleAfterPrint = () => {
4697 flushSync(() => {
4698 this.setState({ forPrint: false });
4699 });
4700 };
4701 }
4702 render() {
4703 let { props } = this;
4704 let { options } = props;
4705 let { forPrint } = this.state;
4706 let isHeightAuto = forPrint || options.height === 'auto' || options.contentHeight === 'auto';
4707 let height = (!isHeightAuto && options.height != null) ? options.height : '';
4708 let classNames = [
4709 'fc',
4710 forPrint ? 'fc-media-print' : 'fc-media-screen',
4711 `fc-direction-${options.direction}`,
4712 props.theme.getClass('root'),
4713 ];
4714 if (!getCanVGrowWithinCell()) {
4715 classNames.push('fc-liquid-hack');
4716 }
4717 return props.children(classNames, height, isHeightAuto, forPrint);
4718 }
4719 componentDidMount() {
4720 let { emitter } = this.props;
4721 emitter.on('_beforeprint', this.handleBeforePrint);
4722 emitter.on('_afterprint', this.handleAfterPrint);
4723 }
4724 componentWillUnmount() {
4725 let { emitter } = this.props;
4726 emitter.off('_beforeprint', this.handleBeforePrint);
4727 emitter.off('_afterprint', this.handleAfterPrint);
4728 }
4729}
4730
4731class Interaction {
4732 constructor(settings) {
4733 this.component = settings.component;
4734 this.isHitComboAllowed = settings.isHitComboAllowed || null;
4735 }
4736 destroy() {
4737 }
4738}
4739function parseInteractionSettings(component, input) {
4740 return {
4741 component,
4742 el: input.el,
4743 useEventCenter: input.useEventCenter != null ? input.useEventCenter : true,
4744 isHitComboAllowed: input.isHitComboAllowed || null,
4745 };
4746}
4747function interactionSettingsToStore(settings) {
4748 return {
4749 [settings.component.uid]: settings,
4750 };
4751}
4752// global state
4753const interactionSettingsStore = {};
4754
4755class CalendarImpl {
4756 getCurrentData() {
4757 return this.currentDataManager.getCurrentData();
4758 }
4759 dispatch(action) {
4760 this.currentDataManager.dispatch(action);
4761 }
4762 get view() { return this.getCurrentData().viewApi; }
4763 batchRendering(callback) {
4764 callback();
4765 }
4766 updateSize() {
4767 this.trigger('_resize', true);
4768 }
4769 // Options
4770 // -----------------------------------------------------------------------------------------------------------------
4771 setOption(name, val) {
4772 this.dispatch({
4773 type: 'SET_OPTION',
4774 optionName: name,
4775 rawOptionValue: val,
4776 });
4777 }
4778 getOption(name) {
4779 return this.currentDataManager.currentCalendarOptionsInput[name];
4780 }
4781 getAvailableLocaleCodes() {
4782 return Object.keys(this.getCurrentData().availableRawLocales);
4783 }
4784 // Trigger
4785 // -----------------------------------------------------------------------------------------------------------------
4786 on(handlerName, handler) {
4787 let { currentDataManager } = this;
4788 if (currentDataManager.currentCalendarOptionsRefiners[handlerName]) {
4789 currentDataManager.emitter.on(handlerName, handler);
4790 }
4791 else {
4792 console.warn(`Unknown listener name '${handlerName}'`);
4793 }
4794 }
4795 off(handlerName, handler) {
4796 this.currentDataManager.emitter.off(handlerName, handler);
4797 }
4798 // not meant for public use
4799 trigger(handlerName, ...args) {
4800 this.currentDataManager.emitter.trigger(handlerName, ...args);
4801 }
4802 // View
4803 // -----------------------------------------------------------------------------------------------------------------
4804 changeView(viewType, dateOrRange) {
4805 this.batchRendering(() => {
4806 this.unselect();
4807 if (dateOrRange) {
4808 if (dateOrRange.start && dateOrRange.end) { // a range
4809 this.dispatch({
4810 type: 'CHANGE_VIEW_TYPE',
4811 viewType,
4812 });
4813 this.dispatch({
4814 type: 'SET_OPTION',
4815 optionName: 'visibleRange',
4816 rawOptionValue: dateOrRange,
4817 });
4818 }
4819 else {
4820 let { dateEnv } = this.getCurrentData();
4821 this.dispatch({
4822 type: 'CHANGE_VIEW_TYPE',
4823 viewType,
4824 dateMarker: dateEnv.createMarker(dateOrRange),
4825 });
4826 }
4827 }
4828 else {
4829 this.dispatch({
4830 type: 'CHANGE_VIEW_TYPE',
4831 viewType,
4832 });
4833 }
4834 });
4835 }
4836 // Forces navigation to a view for the given date.
4837 // `viewType` can be a specific view name or a generic one like "week" or "day".
4838 // needs to change
4839 zoomTo(dateMarker, viewType) {
4840 let state = this.getCurrentData();
4841 let spec;
4842 viewType = viewType || 'day'; // day is default zoom
4843 spec = state.viewSpecs[viewType] || this.getUnitViewSpec(viewType);
4844 this.unselect();
4845 if (spec) {
4846 this.dispatch({
4847 type: 'CHANGE_VIEW_TYPE',
4848 viewType: spec.type,
4849 dateMarker,
4850 });
4851 }
4852 else {
4853 this.dispatch({
4854 type: 'CHANGE_DATE',
4855 dateMarker,
4856 });
4857 }
4858 }
4859 // Given a duration singular unit, like "week" or "day", finds a matching view spec.
4860 // Preference is given to views that have corresponding buttons.
4861 getUnitViewSpec(unit) {
4862 let { viewSpecs, toolbarConfig } = this.getCurrentData();
4863 let viewTypes = [].concat(toolbarConfig.header ? toolbarConfig.header.viewsWithButtons : [], toolbarConfig.footer ? toolbarConfig.footer.viewsWithButtons : []);
4864 let i;
4865 let spec;
4866 for (let viewType in viewSpecs) {
4867 viewTypes.push(viewType);
4868 }
4869 for (i = 0; i < viewTypes.length; i += 1) {
4870 spec = viewSpecs[viewTypes[i]];
4871 if (spec) {
4872 if (spec.singleUnit === unit) {
4873 return spec;
4874 }
4875 }
4876 }
4877 return null;
4878 }
4879 // Current Date
4880 // -----------------------------------------------------------------------------------------------------------------
4881 prev() {
4882 this.unselect();
4883 this.dispatch({ type: 'PREV' });
4884 }
4885 next() {
4886 this.unselect();
4887 this.dispatch({ type: 'NEXT' });
4888 }
4889 prevYear() {
4890 let state = this.getCurrentData();
4891 this.unselect();
4892 this.dispatch({
4893 type: 'CHANGE_DATE',
4894 dateMarker: state.dateEnv.addYears(state.currentDate, -1),
4895 });
4896 }
4897 nextYear() {
4898 let state = this.getCurrentData();
4899 this.unselect();
4900 this.dispatch({
4901 type: 'CHANGE_DATE',
4902 dateMarker: state.dateEnv.addYears(state.currentDate, 1),
4903 });
4904 }
4905 today() {
4906 let state = this.getCurrentData();
4907 this.unselect();
4908 this.dispatch({
4909 type: 'CHANGE_DATE',
4910 dateMarker: getNow(state.calendarOptions.now, state.dateEnv),
4911 });
4912 }
4913 gotoDate(zonedDateInput) {
4914 let state = this.getCurrentData();
4915 this.unselect();
4916 this.dispatch({
4917 type: 'CHANGE_DATE',
4918 dateMarker: state.dateEnv.createMarker(zonedDateInput),
4919 });
4920 }
4921 incrementDate(deltaInput) {
4922 let state = this.getCurrentData();
4923 let delta = createDuration(deltaInput);
4924 if (delta) { // else, warn about invalid input?
4925 this.unselect();
4926 this.dispatch({
4927 type: 'CHANGE_DATE',
4928 dateMarker: state.dateEnv.add(state.currentDate, delta),
4929 });
4930 }
4931 }
4932 getDate() {
4933 let state = this.getCurrentData();
4934 return state.dateEnv.toDate(state.currentDate);
4935 }
4936 // Date Formatting Utils
4937 // -----------------------------------------------------------------------------------------------------------------
4938 formatDate(d, formatter) {
4939 let { dateEnv } = this.getCurrentData();
4940 return dateEnv.format(dateEnv.createMarker(d), createFormatter(formatter));
4941 }
4942 // `settings` is for formatter AND isEndExclusive
4943 formatRange(d0, d1, settings) {
4944 let { dateEnv } = this.getCurrentData();
4945 return dateEnv.formatRange(dateEnv.createMarker(d0), dateEnv.createMarker(d1), createFormatter(settings), settings);
4946 }
4947 formatIso(d, omitTime) {
4948 let { dateEnv } = this.getCurrentData();
4949 return dateEnv.formatIso(dateEnv.createMarker(d), { omitTime });
4950 }
4951 // Date Selection / Event Selection / DayClick
4952 // -----------------------------------------------------------------------------------------------------------------
4953 select(dateOrObj, endDate) {
4954 let selectionInput;
4955 if (endDate == null) {
4956 if (dateOrObj.start != null) {
4957 selectionInput = dateOrObj;
4958 }
4959 else {
4960 selectionInput = {
4961 start: dateOrObj,
4962 end: null,
4963 };
4964 }
4965 }
4966 else {
4967 selectionInput = {
4968 start: dateOrObj,
4969 end: endDate,
4970 };
4971 }
4972 let state = this.getCurrentData();
4973 let selection = parseDateSpan(selectionInput, state.dateEnv, createDuration({ days: 1 }));
4974 if (selection) { // throw parse error otherwise?
4975 this.dispatch({ type: 'SELECT_DATES', selection });
4976 triggerDateSelect(selection, null, state);
4977 }
4978 }
4979 unselect(pev) {
4980 let state = this.getCurrentData();
4981 if (state.dateSelection) {
4982 this.dispatch({ type: 'UNSELECT_DATES' });
4983 triggerDateUnselect(pev, state);
4984 }
4985 }
4986 // Public Events API
4987 // -----------------------------------------------------------------------------------------------------------------
4988 addEvent(eventInput, sourceInput) {
4989 if (eventInput instanceof EventImpl) {
4990 let def = eventInput._def;
4991 let instance = eventInput._instance;
4992 let currentData = this.getCurrentData();
4993 // not already present? don't want to add an old snapshot
4994 if (!currentData.eventStore.defs[def.defId]) {
4995 this.dispatch({
4996 type: 'ADD_EVENTS',
4997 eventStore: eventTupleToStore({ def, instance }), // TODO: better util for two args?
4998 });
4999 this.triggerEventAdd(eventInput);
5000 }
5001 return eventInput;
5002 }
5003 let state = this.getCurrentData();
5004 let eventSource;
5005 if (sourceInput instanceof EventSourceImpl) {
5006 eventSource = sourceInput.internalEventSource;
5007 }
5008 else if (typeof sourceInput === 'boolean') {
5009 if (sourceInput) { // true. part of the first event source
5010 [eventSource] = hashValuesToArray(state.eventSources);
5011 }
5012 }
5013 else if (sourceInput != null) { // an ID. accepts a number too
5014 let sourceApi = this.getEventSourceById(sourceInput); // TODO: use an internal function
5015 if (!sourceApi) {
5016 console.warn(`Could not find an event source with ID "${sourceInput}"`); // TODO: test
5017 return null;
5018 }
5019 eventSource = sourceApi.internalEventSource;
5020 }
5021 let tuple = parseEvent(eventInput, eventSource, state, false);
5022 if (tuple) {
5023 let newEventApi = new EventImpl(state, tuple.def, tuple.def.recurringDef ? null : tuple.instance);
5024 this.dispatch({
5025 type: 'ADD_EVENTS',
5026 eventStore: eventTupleToStore(tuple),
5027 });
5028 this.triggerEventAdd(newEventApi);
5029 return newEventApi;
5030 }
5031 return null;
5032 }
5033 triggerEventAdd(eventApi) {
5034 let { emitter } = this.getCurrentData();
5035 emitter.trigger('eventAdd', {
5036 event: eventApi,
5037 relatedEvents: [],
5038 revert: () => {
5039 this.dispatch({
5040 type: 'REMOVE_EVENTS',
5041 eventStore: eventApiToStore(eventApi),
5042 });
5043 },
5044 });
5045 }
5046 // TODO: optimize
5047 getEventById(id) {
5048 let state = this.getCurrentData();
5049 let { defs, instances } = state.eventStore;
5050 id = String(id);
5051 for (let defId in defs) {
5052 let def = defs[defId];
5053 if (def.publicId === id) {
5054 if (def.recurringDef) {
5055 return new EventImpl(state, def, null);
5056 }
5057 for (let instanceId in instances) {
5058 let instance = instances[instanceId];
5059 if (instance.defId === def.defId) {
5060 return new EventImpl(state, def, instance);
5061 }
5062 }
5063 }
5064 }
5065 return null;
5066 }
5067 getEvents() {
5068 let currentData = this.getCurrentData();
5069 return buildEventApis(currentData.eventStore, currentData);
5070 }
5071 removeAllEvents() {
5072 this.dispatch({ type: 'REMOVE_ALL_EVENTS' });
5073 }
5074 // Public Event Sources API
5075 // -----------------------------------------------------------------------------------------------------------------
5076 getEventSources() {
5077 let state = this.getCurrentData();
5078 let sourceHash = state.eventSources;
5079 let sourceApis = [];
5080 for (let internalId in sourceHash) {
5081 sourceApis.push(new EventSourceImpl(state, sourceHash[internalId]));
5082 }
5083 return sourceApis;
5084 }
5085 getEventSourceById(id) {
5086 let state = this.getCurrentData();
5087 let sourceHash = state.eventSources;
5088 id = String(id);
5089 for (let sourceId in sourceHash) {
5090 if (sourceHash[sourceId].publicId === id) {
5091 return new EventSourceImpl(state, sourceHash[sourceId]);
5092 }
5093 }
5094 return null;
5095 }
5096 addEventSource(sourceInput) {
5097 let state = this.getCurrentData();
5098 if (sourceInput instanceof EventSourceImpl) {
5099 // not already present? don't want to add an old snapshot
5100 if (!state.eventSources[sourceInput.internalEventSource.sourceId]) {
5101 this.dispatch({
5102 type: 'ADD_EVENT_SOURCES',
5103 sources: [sourceInput.internalEventSource],
5104 });
5105 }
5106 return sourceInput;
5107 }
5108 let eventSource = parseEventSource(sourceInput, state);
5109 if (eventSource) { // TODO: error otherwise?
5110 this.dispatch({ type: 'ADD_EVENT_SOURCES', sources: [eventSource] });
5111 return new EventSourceImpl(state, eventSource);
5112 }
5113 return null;
5114 }
5115 removeAllEventSources() {
5116 this.dispatch({ type: 'REMOVE_ALL_EVENT_SOURCES' });
5117 }
5118 refetchEvents() {
5119 this.dispatch({ type: 'FETCH_EVENT_SOURCES', isRefetch: true });
5120 }
5121 // Scroll
5122 // -----------------------------------------------------------------------------------------------------------------
5123 scrollToTime(timeInput) {
5124 let time = createDuration(timeInput);
5125 if (time) {
5126 this.trigger('_scrollRequest', { time });
5127 }
5128 }
5129}
5130
5131function pointInsideRect(point, rect) {
5132 return point.left >= rect.left &&
5133 point.left < rect.right &&
5134 point.top >= rect.top &&
5135 point.top < rect.bottom;
5136}
5137// Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false
5138function intersectRects(rect1, rect2) {
5139 let res = {
5140 left: Math.max(rect1.left, rect2.left),
5141 right: Math.min(rect1.right, rect2.right),
5142 top: Math.max(rect1.top, rect2.top),
5143 bottom: Math.min(rect1.bottom, rect2.bottom),
5144 };
5145 if (res.left < res.right && res.top < res.bottom) {
5146 return res;
5147 }
5148 return false;
5149}
5150function translateRect(rect, deltaX, deltaY) {
5151 return {
5152 left: rect.left + deltaX,
5153 right: rect.right + deltaX,
5154 top: rect.top + deltaY,
5155 bottom: rect.bottom + deltaY,
5156 };
5157}
5158// Returns a new point that will have been moved to reside within the given rectangle
5159function constrainPoint(point, rect) {
5160 return {
5161 left: Math.min(Math.max(point.left, rect.left), rect.right),
5162 top: Math.min(Math.max(point.top, rect.top), rect.bottom),
5163 };
5164}
5165// Returns a point that is the center of the given rectangle
5166function getRectCenter(rect) {
5167 return {
5168 left: (rect.left + rect.right) / 2,
5169 top: (rect.top + rect.bottom) / 2,
5170 };
5171}
5172// Subtracts point2's coordinates from point1's coordinates, returning a delta
5173function diffPoints(point1, point2) {
5174 return {
5175 left: point1.left - point2.left,
5176 top: point1.top - point2.top,
5177 };
5178}
5179
5180const EMPTY_EVENT_STORE = createEmptyEventStore(); // for purecomponents. TODO: keep elsewhere
5181class Splitter {
5182 constructor() {
5183 this.getKeysForEventDefs = memoize(this._getKeysForEventDefs);
5184 this.splitDateSelection = memoize(this._splitDateSpan);
5185 this.splitEventStore = memoize(this._splitEventStore);
5186 this.splitIndividualUi = memoize(this._splitIndividualUi);
5187 this.splitEventDrag = memoize(this._splitInteraction);
5188 this.splitEventResize = memoize(this._splitInteraction);
5189 this.eventUiBuilders = {}; // TODO: typescript protection
5190 }
5191 splitProps(props) {
5192 let keyInfos = this.getKeyInfo(props);
5193 let defKeys = this.getKeysForEventDefs(props.eventStore);
5194 let dateSelections = this.splitDateSelection(props.dateSelection);
5195 let individualUi = this.splitIndividualUi(props.eventUiBases, defKeys); // the individual *bases*
5196 let eventStores = this.splitEventStore(props.eventStore, defKeys);
5197 let eventDrags = this.splitEventDrag(props.eventDrag);
5198 let eventResizes = this.splitEventResize(props.eventResize);
5199 let splitProps = {};
5200 this.eventUiBuilders = mapHash(keyInfos, (info, key) => this.eventUiBuilders[key] || memoize(buildEventUiForKey));
5201 for (let key in keyInfos) {
5202 let keyInfo = keyInfos[key];
5203 let eventStore = eventStores[key] || EMPTY_EVENT_STORE;
5204 let buildEventUi = this.eventUiBuilders[key];
5205 splitProps[key] = {
5206 businessHours: keyInfo.businessHours || props.businessHours,
5207 dateSelection: dateSelections[key] || null,
5208 eventStore,
5209 eventUiBases: buildEventUi(props.eventUiBases[''], keyInfo.ui, individualUi[key]),
5210 eventSelection: eventStore.instances[props.eventSelection] ? props.eventSelection : '',
5211 eventDrag: eventDrags[key] || null,
5212 eventResize: eventResizes[key] || null,
5213 };
5214 }
5215 return splitProps;
5216 }
5217 _splitDateSpan(dateSpan) {
5218 let dateSpans = {};
5219 if (dateSpan) {
5220 let keys = this.getKeysForDateSpan(dateSpan);
5221 for (let key of keys) {
5222 dateSpans[key] = dateSpan;
5223 }
5224 }
5225 return dateSpans;
5226 }
5227 _getKeysForEventDefs(eventStore) {
5228 return mapHash(eventStore.defs, (eventDef) => this.getKeysForEventDef(eventDef));
5229 }
5230 _splitEventStore(eventStore, defKeys) {
5231 let { defs, instances } = eventStore;
5232 let splitStores = {};
5233 for (let defId in defs) {
5234 for (let key of defKeys[defId]) {
5235 if (!splitStores[key]) {
5236 splitStores[key] = createEmptyEventStore();
5237 }
5238 splitStores[key].defs[defId] = defs[defId];
5239 }
5240 }
5241 for (let instanceId in instances) {
5242 let instance = instances[instanceId];
5243 for (let key of defKeys[instance.defId]) {
5244 if (splitStores[key]) { // must have already been created
5245 splitStores[key].instances[instanceId] = instance;
5246 }
5247 }
5248 }
5249 return splitStores;
5250 }
5251 _splitIndividualUi(eventUiBases, defKeys) {
5252 let splitHashes = {};
5253 for (let defId in eventUiBases) {
5254 if (defId) { // not the '' key
5255 for (let key of defKeys[defId]) {
5256 if (!splitHashes[key]) {
5257 splitHashes[key] = {};
5258 }
5259 splitHashes[key][defId] = eventUiBases[defId];
5260 }
5261 }
5262 }
5263 return splitHashes;
5264 }
5265 _splitInteraction(interaction) {
5266 let splitStates = {};
5267 if (interaction) {
5268 let affectedStores = this._splitEventStore(interaction.affectedEvents, this._getKeysForEventDefs(interaction.affectedEvents));
5269 // can't rely on defKeys because event data is mutated
5270 let mutatedKeysByDefId = this._getKeysForEventDefs(interaction.mutatedEvents);
5271 let mutatedStores = this._splitEventStore(interaction.mutatedEvents, mutatedKeysByDefId);
5272 let populate = (key) => {
5273 if (!splitStates[key]) {
5274 splitStates[key] = {
5275 affectedEvents: affectedStores[key] || EMPTY_EVENT_STORE,
5276 mutatedEvents: mutatedStores[key] || EMPTY_EVENT_STORE,
5277 isEvent: interaction.isEvent,
5278 };
5279 }
5280 };
5281 for (let key in affectedStores) {
5282 populate(key);
5283 }
5284 for (let key in mutatedStores) {
5285 populate(key);
5286 }
5287 }
5288 return splitStates;
5289 }
5290}
5291function buildEventUiForKey(allUi, eventUiForKey, individualUi) {
5292 let baseParts = [];
5293 if (allUi) {
5294 baseParts.push(allUi);
5295 }
5296 if (eventUiForKey) {
5297 baseParts.push(eventUiForKey);
5298 }
5299 let stuff = {
5300 '': combineEventUis(baseParts),
5301 };
5302 if (individualUi) {
5303 Object.assign(stuff, individualUi);
5304 }
5305 return stuff;
5306}
5307
5308function getDateMeta(date, todayRange, nowDate, dateProfile) {
5309 return {
5310 dow: date.getUTCDay(),
5311 isDisabled: Boolean(dateProfile && !rangeContainsMarker(dateProfile.activeRange, date)),
5312 isOther: Boolean(dateProfile && !rangeContainsMarker(dateProfile.currentRange, date)),
5313 isToday: Boolean(todayRange && rangeContainsMarker(todayRange, date)),
5314 isPast: Boolean(nowDate ? (date < nowDate) : todayRange ? (date < todayRange.start) : false),
5315 isFuture: Boolean(nowDate ? (date > nowDate) : todayRange ? (date >= todayRange.end) : false),
5316 };
5317}
5318function getDayClassNames(meta, theme) {
5319 let classNames = [
5320 'fc-day',
5321 `fc-day-${DAY_IDS[meta.dow]}`,
5322 ];
5323 if (meta.isDisabled) {
5324 classNames.push('fc-day-disabled');
5325 }
5326 else {
5327 if (meta.isToday) {
5328 classNames.push('fc-day-today');
5329 classNames.push(theme.getClass('today'));
5330 }
5331 if (meta.isPast) {
5332 classNames.push('fc-day-past');
5333 }
5334 if (meta.isFuture) {
5335 classNames.push('fc-day-future');
5336 }
5337 if (meta.isOther) {
5338 classNames.push('fc-day-other');
5339 }
5340 }
5341 return classNames;
5342}
5343function getSlotClassNames(meta, theme) {
5344 let classNames = [
5345 'fc-slot',
5346 `fc-slot-${DAY_IDS[meta.dow]}`,
5347 ];
5348 if (meta.isDisabled) {
5349 classNames.push('fc-slot-disabled');
5350 }
5351 else {
5352 if (meta.isToday) {
5353 classNames.push('fc-slot-today');
5354 classNames.push(theme.getClass('today'));
5355 }
5356 if (meta.isPast) {
5357 classNames.push('fc-slot-past');
5358 }
5359 if (meta.isFuture) {
5360 classNames.push('fc-slot-future');
5361 }
5362 }
5363 return classNames;
5364}
5365
5366const DAY_FORMAT = createFormatter({ year: 'numeric', month: 'long', day: 'numeric' });
5367const WEEK_FORMAT = createFormatter({ week: 'long' });
5368function buildNavLinkAttrs(context, dateMarker, viewType = 'day', isTabbable = true) {
5369 const { dateEnv, options, calendarApi } = context;
5370 let dateStr = dateEnv.format(dateMarker, viewType === 'week' ? WEEK_FORMAT : DAY_FORMAT);
5371 if (options.navLinks) {
5372 let zonedDate = dateEnv.toDate(dateMarker);
5373 const handleInteraction = (ev) => {
5374 let customAction = viewType === 'day' ? options.navLinkDayClick :
5375 viewType === 'week' ? options.navLinkWeekClick : null;
5376 if (typeof customAction === 'function') {
5377 customAction.call(calendarApi, dateEnv.toDate(dateMarker), ev);
5378 }
5379 else {
5380 if (typeof customAction === 'string') {
5381 viewType = customAction;
5382 }
5383 calendarApi.zoomTo(dateMarker, viewType);
5384 }
5385 };
5386 return Object.assign({ title: formatWithOrdinals(options.navLinkHint, [dateStr, zonedDate], dateStr), 'data-navlink': '' }, (isTabbable
5387 ? createAriaClickAttrs(handleInteraction)
5388 : { onClick: handleInteraction }));
5389 }
5390 return { 'aria-label': dateStr };
5391}
5392
5393let _isRtlScrollbarOnLeft = null;
5394function getIsRtlScrollbarOnLeft() {
5395 if (_isRtlScrollbarOnLeft === null) {
5396 _isRtlScrollbarOnLeft = computeIsRtlScrollbarOnLeft();
5397 }
5398 return _isRtlScrollbarOnLeft;
5399}
5400function computeIsRtlScrollbarOnLeft() {
5401 let outerEl = document.createElement('div');
5402 applyStyle(outerEl, {
5403 position: 'absolute',
5404 top: -1000,
5405 left: 0,
5406 border: 0,
5407 padding: 0,
5408 overflow: 'scroll',
5409 direction: 'rtl',
5410 });
5411 outerEl.innerHTML = '<div></div>';
5412 document.body.appendChild(outerEl);
5413 let innerEl = outerEl.firstChild;
5414 let res = innerEl.getBoundingClientRect().left > outerEl.getBoundingClientRect().left;
5415 removeElement(outerEl);
5416 return res;
5417}
5418
5419let _scrollbarWidths;
5420function getScrollbarWidths() {
5421 if (!_scrollbarWidths) {
5422 _scrollbarWidths = computeScrollbarWidths();
5423 }
5424 return _scrollbarWidths;
5425}
5426function computeScrollbarWidths() {
5427 let el = document.createElement('div');
5428 el.style.overflow = 'scroll';
5429 el.style.position = 'absolute';
5430 el.style.top = '-9999px';
5431 el.style.left = '-9999px';
5432 document.body.appendChild(el);
5433 let res = computeScrollbarWidthsForEl(el);
5434 document.body.removeChild(el);
5435 return res;
5436}
5437// WARNING: will include border
5438function computeScrollbarWidthsForEl(el) {
5439 return {
5440 x: el.offsetHeight - el.clientHeight,
5441 y: el.offsetWidth - el.clientWidth,
5442 };
5443}
5444
5445function computeEdges(el, getPadding = false) {
5446 let computedStyle = window.getComputedStyle(el);
5447 let borderLeft = parseInt(computedStyle.borderLeftWidth, 10) || 0;
5448 let borderRight = parseInt(computedStyle.borderRightWidth, 10) || 0;
5449 let borderTop = parseInt(computedStyle.borderTopWidth, 10) || 0;
5450 let borderBottom = parseInt(computedStyle.borderBottomWidth, 10) || 0;
5451 let badScrollbarWidths = computeScrollbarWidthsForEl(el); // includes border!
5452 let scrollbarLeftRight = badScrollbarWidths.y - borderLeft - borderRight;
5453 let scrollbarBottom = badScrollbarWidths.x - borderTop - borderBottom;
5454 let res = {
5455 borderLeft,
5456 borderRight,
5457 borderTop,
5458 borderBottom,
5459 scrollbarBottom,
5460 scrollbarLeft: 0,
5461 scrollbarRight: 0,
5462 };
5463 if (getIsRtlScrollbarOnLeft() && computedStyle.direction === 'rtl') { // is the scrollbar on the left side?
5464 res.scrollbarLeft = scrollbarLeftRight;
5465 }
5466 else {
5467 res.scrollbarRight = scrollbarLeftRight;
5468 }
5469 if (getPadding) {
5470 res.paddingLeft = parseInt(computedStyle.paddingLeft, 10) || 0;
5471 res.paddingRight = parseInt(computedStyle.paddingRight, 10) || 0;
5472 res.paddingTop = parseInt(computedStyle.paddingTop, 10) || 0;
5473 res.paddingBottom = parseInt(computedStyle.paddingBottom, 10) || 0;
5474 }
5475 return res;
5476}
5477function computeInnerRect(el, goWithinPadding = false, doFromWindowViewport) {
5478 let outerRect = doFromWindowViewport ? el.getBoundingClientRect() : computeRect(el);
5479 let edges = computeEdges(el, goWithinPadding);
5480 let res = {
5481 left: outerRect.left + edges.borderLeft + edges.scrollbarLeft,
5482 right: outerRect.right - edges.borderRight - edges.scrollbarRight,
5483 top: outerRect.top + edges.borderTop,
5484 bottom: outerRect.bottom - edges.borderBottom - edges.scrollbarBottom,
5485 };
5486 if (goWithinPadding) {
5487 res.left += edges.paddingLeft;
5488 res.right -= edges.paddingRight;
5489 res.top += edges.paddingTop;
5490 res.bottom -= edges.paddingBottom;
5491 }
5492 return res;
5493}
5494function computeRect(el) {
5495 let rect = el.getBoundingClientRect();
5496 return {
5497 left: rect.left + window.scrollX,
5498 top: rect.top + window.scrollY,
5499 right: rect.right + window.scrollX,
5500 bottom: rect.bottom + window.scrollY,
5501 };
5502}
5503function computeClippedClientRect(el) {
5504 let clippingParents = getClippingParents(el);
5505 let rect = el.getBoundingClientRect();
5506 for (let clippingParent of clippingParents) {
5507 let intersection = intersectRects(rect, clippingParent.getBoundingClientRect());
5508 if (intersection) {
5509 rect = intersection;
5510 }
5511 else {
5512 return null;
5513 }
5514 }
5515 return rect;
5516}
5517// does not return window
5518function getClippingParents(el) {
5519 let parents = [];
5520 while (el instanceof HTMLElement) { // will stop when gets to document or null
5521 let computedStyle = window.getComputedStyle(el);
5522 if (computedStyle.position === 'fixed') {
5523 break;
5524 }
5525 if ((/(auto|scroll)/).test(computedStyle.overflow + computedStyle.overflowY + computedStyle.overflowX)) {
5526 parents.push(el);
5527 }
5528 el = el.parentNode;
5529 }
5530 return parents;
5531}
5532
5533/*
5534Records offset information for a set of elements, relative to an origin element.
5535Can record the left/right OR the top/bottom OR both.
5536Provides methods for querying the cache by position.
5537*/
5538class PositionCache {
5539 constructor(originEl, els, isHorizontal, isVertical) {
5540 this.els = els;
5541 let originClientRect = this.originClientRect = originEl.getBoundingClientRect(); // relative to viewport top-left
5542 if (isHorizontal) {
5543 this.buildElHorizontals(originClientRect.left);
5544 }
5545 if (isVertical) {
5546 this.buildElVerticals(originClientRect.top);
5547 }
5548 }
5549 // Populates the left/right internal coordinate arrays
5550 buildElHorizontals(originClientLeft) {
5551 let lefts = [];
5552 let rights = [];
5553 for (let el of this.els) {
5554 let rect = el.getBoundingClientRect();
5555 lefts.push(rect.left - originClientLeft);
5556 rights.push(rect.right - originClientLeft);
5557 }
5558 this.lefts = lefts;
5559 this.rights = rights;
5560 }
5561 // Populates the top/bottom internal coordinate arrays
5562 buildElVerticals(originClientTop) {
5563 let tops = [];
5564 let bottoms = [];
5565 for (let el of this.els) {
5566 let rect = el.getBoundingClientRect();
5567 tops.push(rect.top - originClientTop);
5568 bottoms.push(rect.bottom - originClientTop);
5569 }
5570 this.tops = tops;
5571 this.bottoms = bottoms;
5572 }
5573 // Given a left offset (from document left), returns the index of the el that it horizontally intersects.
5574 // If no intersection is made, returns undefined.
5575 leftToIndex(leftPosition) {
5576 let { lefts, rights } = this;
5577 let len = lefts.length;
5578 let i;
5579 for (i = 0; i < len; i += 1) {
5580 if (leftPosition >= lefts[i] && leftPosition < rights[i]) {
5581 return i;
5582 }
5583 }
5584 return undefined; // TODO: better
5585 }
5586 // Given a top offset (from document top), returns the index of the el that it vertically intersects.
5587 // If no intersection is made, returns undefined.
5588 topToIndex(topPosition) {
5589 let { tops, bottoms } = this;
5590 let len = tops.length;
5591 let i;
5592 for (i = 0; i < len; i += 1) {
5593 if (topPosition >= tops[i] && topPosition < bottoms[i]) {
5594 return i;
5595 }
5596 }
5597 return undefined; // TODO: better
5598 }
5599 // Gets the width of the element at the given index
5600 getWidth(leftIndex) {
5601 return this.rights[leftIndex] - this.lefts[leftIndex];
5602 }
5603 // Gets the height of the element at the given index
5604 getHeight(topIndex) {
5605 return this.bottoms[topIndex] - this.tops[topIndex];
5606 }
5607 similarTo(otherCache) {
5608 return similarNumArrays(this.tops || [], otherCache.tops || []) &&
5609 similarNumArrays(this.bottoms || [], otherCache.bottoms || []) &&
5610 similarNumArrays(this.lefts || [], otherCache.lefts || []) &&
5611 similarNumArrays(this.rights || [], otherCache.rights || []);
5612 }
5613}
5614function similarNumArrays(a, b) {
5615 const len = a.length;
5616 if (len !== b.length) {
5617 return false;
5618 }
5619 for (let i = 0; i < len; i++) {
5620 if (Math.round(a[i]) !== Math.round(b[i])) {
5621 return false;
5622 }
5623 }
5624 return true;
5625}
5626
5627/* eslint max-classes-per-file: "off" */
5628/*
5629An object for getting/setting scroll-related information for an element.
5630Internally, this is done very differently for window versus DOM element,
5631so this object serves as a common interface.
5632*/
5633class ScrollController {
5634 getMaxScrollTop() {
5635 return this.getScrollHeight() - this.getClientHeight();
5636 }
5637 getMaxScrollLeft() {
5638 return this.getScrollWidth() - this.getClientWidth();
5639 }
5640 canScrollVertically() {
5641 return this.getMaxScrollTop() > 0;
5642 }
5643 canScrollHorizontally() {
5644 return this.getMaxScrollLeft() > 0;
5645 }
5646 canScrollUp() {
5647 return this.getScrollTop() > 0;
5648 }
5649 canScrollDown() {
5650 return this.getScrollTop() < this.getMaxScrollTop();
5651 }
5652 canScrollLeft() {
5653 return this.getScrollLeft() > 0;
5654 }
5655 canScrollRight() {
5656 return this.getScrollLeft() < this.getMaxScrollLeft();
5657 }
5658}
5659class ElementScrollController extends ScrollController {
5660 constructor(el) {
5661 super();
5662 this.el = el;
5663 }
5664 getScrollTop() {
5665 return this.el.scrollTop;
5666 }
5667 getScrollLeft() {
5668 return this.el.scrollLeft;
5669 }
5670 setScrollTop(top) {
5671 this.el.scrollTop = top;
5672 }
5673 setScrollLeft(left) {
5674 this.el.scrollLeft = left;
5675 }
5676 getScrollWidth() {
5677 return this.el.scrollWidth;
5678 }
5679 getScrollHeight() {
5680 return this.el.scrollHeight;
5681 }
5682 getClientHeight() {
5683 return this.el.clientHeight;
5684 }
5685 getClientWidth() {
5686 return this.el.clientWidth;
5687 }
5688}
5689class WindowScrollController extends ScrollController {
5690 getScrollTop() {
5691 return window.scrollY;
5692 }
5693 getScrollLeft() {
5694 return window.scrollX;
5695 }
5696 setScrollTop(n) {
5697 window.scroll(window.scrollX, n);
5698 }
5699 setScrollLeft(n) {
5700 window.scroll(n, window.scrollY);
5701 }
5702 getScrollWidth() {
5703 return document.documentElement.scrollWidth;
5704 }
5705 getScrollHeight() {
5706 return document.documentElement.scrollHeight;
5707 }
5708 getClientHeight() {
5709 return document.documentElement.clientHeight;
5710 }
5711 getClientWidth() {
5712 return document.documentElement.clientWidth;
5713 }
5714}
5715
5716/*
5717an INTERACTABLE date component
5718
5719PURPOSES:
5720- hook up to fg, fill, and mirror renderers
5721- interface for dragging and hits
5722*/
5723class DateComponent extends BaseComponent {
5724 constructor() {
5725 super(...arguments);
5726 this.uid = guid();
5727 }
5728 // Hit System
5729 // -----------------------------------------------------------------------------------------------------------------
5730 prepareHits() {
5731 }
5732 queryHit(positionLeft, positionTop, elWidth, elHeight) {
5733 return null; // this should be abstract
5734 }
5735 // Pointer Interaction Utils
5736 // -----------------------------------------------------------------------------------------------------------------
5737 isValidSegDownEl(el) {
5738 return !this.props.eventDrag && // HACK
5739 !this.props.eventResize && // HACK
5740 !elementClosest(el, '.fc-event-mirror');
5741 }
5742 isValidDateDownEl(el) {
5743 return !elementClosest(el, '.fc-event:not(.fc-bg-event)') &&
5744 !elementClosest(el, '.fc-more-link') && // a "more.." link
5745 !elementClosest(el, 'a[data-navlink]') && // a clickable nav link
5746 !elementClosest(el, '.fc-popover'); // hack
5747 }
5748}
5749
5750class NamedTimeZoneImpl {
5751 constructor(timeZoneName) {
5752 this.timeZoneName = timeZoneName;
5753 }
5754}
5755
5756class SegHierarchy {
5757 constructor(getEntryThickness = (entry) => {
5758 // if no thickness known, assume 1 (if 0, so small it always fits)
5759 return entry.thickness || 1;
5760 }) {
5761 this.getEntryThickness = getEntryThickness;
5762 // settings
5763 this.strictOrder = false;
5764 this.allowReslicing = false;
5765 this.maxCoord = -1; // -1 means no max
5766 this.maxStackCnt = -1; // -1 means no max
5767 this.levelCoords = []; // ordered
5768 this.entriesByLevel = []; // parallel with levelCoords
5769 this.stackCnts = {}; // TODO: use better technique!?
5770 }
5771 addSegs(inputs) {
5772 let hiddenEntries = [];
5773 for (let input of inputs) {
5774 this.insertEntry(input, hiddenEntries);
5775 }
5776 return hiddenEntries;
5777 }
5778 insertEntry(entry, hiddenEntries) {
5779 let insertion = this.findInsertion(entry);
5780 if (this.isInsertionValid(insertion, entry)) {
5781 this.insertEntryAt(entry, insertion);
5782 }
5783 else {
5784 this.handleInvalidInsertion(insertion, entry, hiddenEntries);
5785 }
5786 }
5787 isInsertionValid(insertion, entry) {
5788 return (this.maxCoord === -1 || insertion.levelCoord + this.getEntryThickness(entry) <= this.maxCoord) &&
5789 (this.maxStackCnt === -1 || insertion.stackCnt < this.maxStackCnt);
5790 }
5791 handleInvalidInsertion(insertion, entry, hiddenEntries) {
5792 if (this.allowReslicing && insertion.touchingEntry) {
5793 const hiddenEntry = Object.assign(Object.assign({}, entry), { span: intersectSpans(entry.span, insertion.touchingEntry.span) });
5794 hiddenEntries.push(hiddenEntry);
5795 this.splitEntry(entry, insertion.touchingEntry, hiddenEntries);
5796 }
5797 else {
5798 hiddenEntries.push(entry);
5799 }
5800 }
5801 /*
5802 Does NOT add what hit the `barrier` into hiddenEntries. Should already be done.
5803 */
5804 splitEntry(entry, barrier, hiddenEntries) {
5805 let entrySpan = entry.span;
5806 let barrierSpan = barrier.span;
5807 if (entrySpan.start < barrierSpan.start) {
5808 this.insertEntry({
5809 index: entry.index,
5810 thickness: entry.thickness,
5811 span: { start: entrySpan.start, end: barrierSpan.start },
5812 }, hiddenEntries);
5813 }
5814 if (entrySpan.end > barrierSpan.end) {
5815 this.insertEntry({
5816 index: entry.index,
5817 thickness: entry.thickness,
5818 span: { start: barrierSpan.end, end: entrySpan.end },
5819 }, hiddenEntries);
5820 }
5821 }
5822 insertEntryAt(entry, insertion) {
5823 let { entriesByLevel, levelCoords } = this;
5824 if (insertion.lateral === -1) {
5825 // create a new level
5826 insertAt(levelCoords, insertion.level, insertion.levelCoord);
5827 insertAt(entriesByLevel, insertion.level, [entry]);
5828 }
5829 else {
5830 // insert into existing level
5831 insertAt(entriesByLevel[insertion.level], insertion.lateral, entry);
5832 }
5833 this.stackCnts[buildEntryKey(entry)] = insertion.stackCnt;
5834 }
5835 /*
5836 does not care about limits
5837 */
5838 findInsertion(newEntry) {
5839 let { levelCoords, entriesByLevel, strictOrder, stackCnts } = this;
5840 let levelCnt = levelCoords.length;
5841 let candidateCoord = 0;
5842 let touchingLevel = -1;
5843 let touchingLateral = -1;
5844 let touchingEntry = null;
5845 let stackCnt = 0;
5846 for (let trackingLevel = 0; trackingLevel < levelCnt; trackingLevel += 1) {
5847 const trackingCoord = levelCoords[trackingLevel];
5848 // if the current level is past the placed entry, we have found a good empty space and can stop.
5849 // if strictOrder, keep finding more lateral intersections.
5850 if (!strictOrder && trackingCoord >= candidateCoord + this.getEntryThickness(newEntry)) {
5851 break;
5852 }
5853 let trackingEntries = entriesByLevel[trackingLevel];
5854 let trackingEntry;
5855 let searchRes = binarySearch(trackingEntries, newEntry.span.start, getEntrySpanEnd); // find first entry after newEntry's end
5856 let lateralIndex = searchRes[0] + searchRes[1]; // if exact match (which doesn't collide), go to next one
5857 while ( // loop through entries that horizontally intersect
5858 (trackingEntry = trackingEntries[lateralIndex]) && // but not past the whole entry list
5859 trackingEntry.span.start < newEntry.span.end // and not entirely past newEntry
5860 ) {
5861 let trackingEntryBottom = trackingCoord + this.getEntryThickness(trackingEntry);
5862 // intersects into the top of the candidate?
5863 if (trackingEntryBottom > candidateCoord) {
5864 candidateCoord = trackingEntryBottom;
5865 touchingEntry = trackingEntry;
5866 touchingLevel = trackingLevel;
5867 touchingLateral = lateralIndex;
5868 }
5869 // butts up against top of candidate? (will happen if just intersected as well)
5870 if (trackingEntryBottom === candidateCoord) {
5871 // accumulate the highest possible stackCnt of the trackingEntries that butt up
5872 stackCnt = Math.max(stackCnt, stackCnts[buildEntryKey(trackingEntry)] + 1);
5873 }
5874 lateralIndex += 1;
5875 }
5876 }
5877 // the destination level will be after touchingEntry's level. find it
5878 let destLevel = 0;
5879 if (touchingEntry) {
5880 destLevel = touchingLevel + 1;
5881 while (destLevel < levelCnt && levelCoords[destLevel] < candidateCoord) {
5882 destLevel += 1;
5883 }
5884 }
5885 // if adding to an existing level, find where to insert
5886 let destLateral = -1;
5887 if (destLevel < levelCnt && levelCoords[destLevel] === candidateCoord) {
5888 destLateral = binarySearch(entriesByLevel[destLevel], newEntry.span.end, getEntrySpanEnd)[0];
5889 }
5890 return {
5891 touchingLevel,
5892 touchingLateral,
5893 touchingEntry,
5894 stackCnt,
5895 levelCoord: candidateCoord,
5896 level: destLevel,
5897 lateral: destLateral,
5898 };
5899 }
5900 // sorted by levelCoord (lowest to highest)
5901 toRects() {
5902 let { entriesByLevel, levelCoords } = this;
5903 let levelCnt = entriesByLevel.length;
5904 let rects = [];
5905 for (let level = 0; level < levelCnt; level += 1) {
5906 let entries = entriesByLevel[level];
5907 let levelCoord = levelCoords[level];
5908 for (let entry of entries) {
5909 rects.push(Object.assign(Object.assign({}, entry), { thickness: this.getEntryThickness(entry), levelCoord }));
5910 }
5911 }
5912 return rects;
5913 }
5914}
5915function getEntrySpanEnd(entry) {
5916 return entry.span.end;
5917}
5918function buildEntryKey(entry) {
5919 return entry.index + ':' + entry.span.start;
5920}
5921// returns groups with entries sorted by input order
5922function groupIntersectingEntries(entries) {
5923 let merges = [];
5924 for (let entry of entries) {
5925 let filteredMerges = [];
5926 let hungryMerge = {
5927 span: entry.span,
5928 entries: [entry],
5929 };
5930 for (let merge of merges) {
5931 if (intersectSpans(merge.span, hungryMerge.span)) {
5932 hungryMerge = {
5933 entries: merge.entries.concat(hungryMerge.entries),
5934 span: joinSpans(merge.span, hungryMerge.span),
5935 };
5936 }
5937 else {
5938 filteredMerges.push(merge);
5939 }
5940 }
5941 filteredMerges.push(hungryMerge);
5942 merges = filteredMerges;
5943 }
5944 return merges;
5945}
5946function joinSpans(span0, span1) {
5947 return {
5948 start: Math.min(span0.start, span1.start),
5949 end: Math.max(span0.end, span1.end),
5950 };
5951}
5952function intersectSpans(span0, span1) {
5953 let start = Math.max(span0.start, span1.start);
5954 let end = Math.min(span0.end, span1.end);
5955 if (start < end) {
5956 return { start, end };
5957 }
5958 return null;
5959}
5960// general util
5961// ---------------------------------------------------------------------------------------------------------------------
5962function insertAt(arr, index, item) {
5963 arr.splice(index, 0, item);
5964}
5965function binarySearch(a, searchVal, getItemVal) {
5966 let startIndex = 0;
5967 let endIndex = a.length; // exclusive
5968 if (!endIndex || searchVal < getItemVal(a[startIndex])) { // no items OR before first item
5969 return [0, 0];
5970 }
5971 if (searchVal > getItemVal(a[endIndex - 1])) { // after last item
5972 return [endIndex, 0];
5973 }
5974 while (startIndex < endIndex) {
5975 let middleIndex = Math.floor(startIndex + (endIndex - startIndex) / 2);
5976 let middleVal = getItemVal(a[middleIndex]);
5977 if (searchVal < middleVal) {
5978 endIndex = middleIndex;
5979 }
5980 else if (searchVal > middleVal) {
5981 startIndex = middleIndex + 1;
5982 }
5983 else { // equal!
5984 return [middleIndex, 1];
5985 }
5986 }
5987 return [startIndex, 0];
5988}
5989
5990/*
5991An abstraction for a dragging interaction originating on an event.
5992Does higher-level things than PointerDragger, such as possibly:
5993- a "mirror" that moves with the pointer
5994- a minimum number of pixels or other criteria for a true drag to begin
5995
5996subclasses must emit:
5997- pointerdown
5998- dragstart
5999- dragmove
6000- pointerup
6001- dragend
6002*/
6003class ElementDragging {
6004 constructor(el, selector) {
6005 this.emitter = new Emitter();
6006 }
6007 destroy() {
6008 }
6009 setMirrorIsVisible(bool) {
6010 // optional if subclass doesn't want to support a mirror
6011 }
6012 setMirrorNeedsRevert(bool) {
6013 // optional if subclass doesn't want to support a mirror
6014 }
6015 setAutoScrollEnabled(bool) {
6016 // optional
6017 }
6018}
6019
6020// TODO: get rid of this in favor of options system,
6021// tho it's really easy to access this globally rather than pass thru options.
6022const config = {};
6023
6024/*
6025Information about what will happen when an external element is dragged-and-dropped
6026onto a calendar. Contains information for creating an event.
6027*/
6028const DRAG_META_REFINERS = {
6029 startTime: createDuration,
6030 duration: createDuration,
6031 create: Boolean,
6032 sourceId: String,
6033};
6034function parseDragMeta(raw) {
6035 let { refined, extra } = refineProps(raw, DRAG_META_REFINERS);
6036 return {
6037 startTime: refined.startTime || null,
6038 duration: refined.duration || null,
6039 create: refined.create != null ? refined.create : true,
6040 sourceId: refined.sourceId,
6041 leftoverProps: extra,
6042 };
6043}
6044
6045// Computes a default column header formatting string if `colFormat` is not explicitly defined
6046function computeFallbackHeaderFormat(datesRepDistinctDays, dayCnt) {
6047 // if more than one week row, or if there are a lot of columns with not much space,
6048 // put just the day numbers will be in each cell
6049 if (!datesRepDistinctDays || dayCnt > 10) {
6050 return createFormatter({ weekday: 'short' }); // "Sat"
6051 }
6052 if (dayCnt > 1) {
6053 return createFormatter({ weekday: 'short', month: 'numeric', day: 'numeric', omitCommas: true }); // "Sat 11/12"
6054 }
6055 return createFormatter({ weekday: 'long' }); // "Saturday"
6056}
6057
6058const CLASS_NAME = 'fc-col-header-cell'; // do the cushion too? no
6059function renderInner$1(renderProps) {
6060 return renderProps.text;
6061}
6062
6063// BAD name for this class now. used in the Header
6064class TableDateCell extends BaseComponent {
6065 render() {
6066 let { dateEnv, options, theme, viewApi } = this.context;
6067 let { props } = this;
6068 let { date, dateProfile } = props;
6069 let dayMeta = getDateMeta(date, props.todayRange, null, dateProfile);
6070 let classNames = [CLASS_NAME].concat(getDayClassNames(dayMeta, theme));
6071 let text = dateEnv.format(date, props.dayHeaderFormat);
6072 // if colCnt is 1, we are already in a day-view and don't need a navlink
6073 let navLinkAttrs = (!dayMeta.isDisabled && props.colCnt > 1)
6074 ? buildNavLinkAttrs(this.context, date)
6075 : {};
6076 let renderProps = Object.assign(Object.assign(Object.assign({ date: dateEnv.toDate(date), view: viewApi }, props.extraRenderProps), { text }), dayMeta);
6077 return (createElement(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) => (createElement("div", { className: "fc-scrollgrid-sync-inner" }, !dayMeta.isDisabled && (createElement(InnerContainer, { elTag: "a", elAttrs: navLinkAttrs, elClasses: [
6078 'fc-col-header-cell-cushion',
6079 props.isSticky && 'fc-sticky',
6080 ] }))))));
6081 }
6082}
6083
6084const WEEKDAY_FORMAT = createFormatter({ weekday: 'long' });
6085class TableDowCell extends BaseComponent {
6086 render() {
6087 let { props } = this;
6088 let { dateEnv, theme, viewApi, options } = this.context;
6089 let date = addDays(new Date(259200000), props.dow); // start with Sun, 04 Jan 1970 00:00:00 GMT
6090 let dateMeta = {
6091 dow: props.dow,
6092 isDisabled: false,
6093 isFuture: false,
6094 isPast: false,
6095 isToday: false,
6096 isOther: false,
6097 };
6098 let text = dateEnv.format(date, props.dayHeaderFormat);
6099 let renderProps = Object.assign(Object.assign(Object.assign(Object.assign({ // TODO: make this public?
6100 date }, dateMeta), { view: viewApi }), props.extraRenderProps), { text });
6101 return (createElement(ContentContainer, { elTag: "th", elClasses: [
6102 CLASS_NAME,
6103 ...getDayClassNames(dateMeta, theme),
6104 ...(props.extraClassNames || []),
6105 ], 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) => (createElement("div", { className: "fc-scrollgrid-sync-inner" },
6106 createElement(InnerContent, { elTag: "a", elClasses: [
6107 'fc-col-header-cell-cushion',
6108 props.isSticky && 'fc-sticky',
6109 ], elAttrs: {
6110 'aria-label': dateEnv.format(date, WEEKDAY_FORMAT),
6111 } })))));
6112 }
6113}
6114
6115class NowTimer extends Component {
6116 constructor(props, context) {
6117 super(props, context);
6118 this.initialNowDate = getNow(context.options.now, context.dateEnv);
6119 this.initialNowQueriedMs = new Date().valueOf();
6120 this.state = this.computeTiming().currentState;
6121 }
6122 render() {
6123 let { props, state } = this;
6124 return props.children(state.nowDate, state.todayRange);
6125 }
6126 componentDidMount() {
6127 this.setTimeout();
6128 }
6129 componentDidUpdate(prevProps) {
6130 if (prevProps.unit !== this.props.unit) {
6131 this.clearTimeout();
6132 this.setTimeout();
6133 }
6134 }
6135 componentWillUnmount() {
6136 this.clearTimeout();
6137 }
6138 computeTiming() {
6139 let { props, context } = this;
6140 let unroundedNow = addMs(this.initialNowDate, new Date().valueOf() - this.initialNowQueriedMs);
6141 let currentUnitStart = context.dateEnv.startOf(unroundedNow, props.unit);
6142 let nextUnitStart = context.dateEnv.add(currentUnitStart, createDuration(1, props.unit));
6143 let waitMs = nextUnitStart.valueOf() - unroundedNow.valueOf();
6144 // there is a max setTimeout ms value (https://stackoverflow.com/a/3468650/96342)
6145 // ensure no longer than a day
6146 waitMs = Math.min(1000 * 60 * 60 * 24, waitMs);
6147 return {
6148 currentState: { nowDate: currentUnitStart, todayRange: buildDayRange(currentUnitStart) },
6149 nextState: { nowDate: nextUnitStart, todayRange: buildDayRange(nextUnitStart) },
6150 waitMs,
6151 };
6152 }
6153 setTimeout() {
6154 let { nextState, waitMs } = this.computeTiming();
6155 this.timeoutId = setTimeout(() => {
6156 this.setState(nextState, () => {
6157 this.setTimeout();
6158 });
6159 }, waitMs);
6160 }
6161 clearTimeout() {
6162 if (this.timeoutId) {
6163 clearTimeout(this.timeoutId);
6164 }
6165 }
6166}
6167NowTimer.contextType = ViewContextType;
6168function buildDayRange(date) {
6169 let start = startOfDay(date);
6170 let end = addDays(start, 1);
6171 return { start, end };
6172}
6173
6174class DayHeader extends BaseComponent {
6175 constructor() {
6176 super(...arguments);
6177 this.createDayHeaderFormatter = memoize(createDayHeaderFormatter);
6178 }
6179 render() {
6180 let { context } = this;
6181 let { dates, dateProfile, datesRepDistinctDays, renderIntro } = this.props;
6182 let dayHeaderFormat = this.createDayHeaderFormatter(context.options.dayHeaderFormat, datesRepDistinctDays, dates.length);
6183 return (createElement(NowTimer, { unit: "day" }, (nowDate, todayRange) => (createElement("tr", { role: "row" },
6184 renderIntro && renderIntro('day'),
6185 dates.map((date) => (datesRepDistinctDays ? (createElement(TableDateCell, { key: date.toISOString(), date: date, dateProfile: dateProfile, todayRange: todayRange, colCnt: dates.length, dayHeaderFormat: dayHeaderFormat })) : (createElement(TableDowCell, { key: date.getUTCDay(), dow: date.getUTCDay(), dayHeaderFormat: dayHeaderFormat }))))))));
6186 }
6187}
6188function createDayHeaderFormatter(explicitFormat, datesRepDistinctDays, dateCnt) {
6189 return explicitFormat || computeFallbackHeaderFormat(datesRepDistinctDays, dateCnt);
6190}
6191
6192class DaySeriesModel {
6193 constructor(range, dateProfileGenerator) {
6194 let date = range.start;
6195 let { end } = range;
6196 let indices = [];
6197 let dates = [];
6198 let dayIndex = -1;
6199 while (date < end) { // loop each day from start to end
6200 if (dateProfileGenerator.isHiddenDay(date)) {
6201 indices.push(dayIndex + 0.5); // mark that it's between indices
6202 }
6203 else {
6204 dayIndex += 1;
6205 indices.push(dayIndex);
6206 dates.push(date);
6207 }
6208 date = addDays(date, 1);
6209 }
6210 this.dates = dates;
6211 this.indices = indices;
6212 this.cnt = dates.length;
6213 }
6214 sliceRange(range) {
6215 let firstIndex = this.getDateDayIndex(range.start); // inclusive first index
6216 let lastIndex = this.getDateDayIndex(addDays(range.end, -1)); // inclusive last index
6217 let clippedFirstIndex = Math.max(0, firstIndex);
6218 let clippedLastIndex = Math.min(this.cnt - 1, lastIndex);
6219 // deal with in-between indices
6220 clippedFirstIndex = Math.ceil(clippedFirstIndex); // in-between starts round to next cell
6221 clippedLastIndex = Math.floor(clippedLastIndex); // in-between ends round to prev cell
6222 if (clippedFirstIndex <= clippedLastIndex) {
6223 return {
6224 firstIndex: clippedFirstIndex,
6225 lastIndex: clippedLastIndex,
6226 isStart: firstIndex === clippedFirstIndex,
6227 isEnd: lastIndex === clippedLastIndex,
6228 };
6229 }
6230 return null;
6231 }
6232 // Given a date, returns its chronolocial cell-index from the first cell of the grid.
6233 // If the date lies between cells (because of hiddenDays), returns a floating-point value between offsets.
6234 // If before the first offset, returns a negative number.
6235 // If after the last offset, returns an offset past the last cell offset.
6236 // Only works for *start* dates of cells. Will not work for exclusive end dates for cells.
6237 getDateDayIndex(date) {
6238 let { indices } = this;
6239 let dayOffset = Math.floor(diffDays(this.dates[0], date));
6240 if (dayOffset < 0) {
6241 return indices[0] - 1;
6242 }
6243 if (dayOffset >= indices.length) {
6244 return indices[indices.length - 1] + 1;
6245 }
6246 return indices[dayOffset];
6247 }
6248}
6249
6250class DayTableModel {
6251 constructor(daySeries, breakOnWeeks) {
6252 let { dates } = daySeries;
6253 let daysPerRow;
6254 let firstDay;
6255 let rowCnt;
6256 if (breakOnWeeks) {
6257 // count columns until the day-of-week repeats
6258 firstDay = dates[0].getUTCDay();
6259 for (daysPerRow = 1; daysPerRow < dates.length; daysPerRow += 1) {
6260 if (dates[daysPerRow].getUTCDay() === firstDay) {
6261 break;
6262 }
6263 }
6264 rowCnt = Math.ceil(dates.length / daysPerRow);
6265 }
6266 else {
6267 rowCnt = 1;
6268 daysPerRow = dates.length;
6269 }
6270 this.rowCnt = rowCnt;
6271 this.colCnt = daysPerRow;
6272 this.daySeries = daySeries;
6273 this.cells = this.buildCells();
6274 this.headerDates = this.buildHeaderDates();
6275 }
6276 buildCells() {
6277 let rows = [];
6278 for (let row = 0; row < this.rowCnt; row += 1) {
6279 let cells = [];
6280 for (let col = 0; col < this.colCnt; col += 1) {
6281 cells.push(this.buildCell(row, col));
6282 }
6283 rows.push(cells);
6284 }
6285 return rows;
6286 }
6287 buildCell(row, col) {
6288 let date = this.daySeries.dates[row * this.colCnt + col];
6289 return {
6290 key: date.toISOString(),
6291 date,
6292 };
6293 }
6294 buildHeaderDates() {
6295 let dates = [];
6296 for (let col = 0; col < this.colCnt; col += 1) {
6297 dates.push(this.cells[0][col].date);
6298 }
6299 return dates;
6300 }
6301 sliceRange(range) {
6302 let { colCnt } = this;
6303 let seriesSeg = this.daySeries.sliceRange(range);
6304 let segs = [];
6305 if (seriesSeg) {
6306 let { firstIndex, lastIndex } = seriesSeg;
6307 let index = firstIndex;
6308 while (index <= lastIndex) {
6309 let row = Math.floor(index / colCnt);
6310 let nextIndex = Math.min((row + 1) * colCnt, lastIndex + 1);
6311 segs.push({
6312 row,
6313 firstCol: index % colCnt,
6314 lastCol: (nextIndex - 1) % colCnt,
6315 isStart: seriesSeg.isStart && index === firstIndex,
6316 isEnd: seriesSeg.isEnd && (nextIndex - 1) === lastIndex,
6317 });
6318 index = nextIndex;
6319 }
6320 }
6321 return segs;
6322 }
6323}
6324
6325class Slicer {
6326 constructor() {
6327 this.sliceBusinessHours = memoize(this._sliceBusinessHours);
6328 this.sliceDateSelection = memoize(this._sliceDateSpan);
6329 this.sliceEventStore = memoize(this._sliceEventStore);
6330 this.sliceEventDrag = memoize(this._sliceInteraction);
6331 this.sliceEventResize = memoize(this._sliceInteraction);
6332 this.forceDayIfListItem = false; // hack
6333 }
6334 sliceProps(props, dateProfile, nextDayThreshold, context, ...extraArgs) {
6335 let { eventUiBases } = props;
6336 let eventSegs = this.sliceEventStore(props.eventStore, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs);
6337 return {
6338 dateSelectionSegs: this.sliceDateSelection(props.dateSelection, dateProfile, nextDayThreshold, eventUiBases, context, ...extraArgs),
6339 businessHourSegs: this.sliceBusinessHours(props.businessHours, dateProfile, nextDayThreshold, context, ...extraArgs),
6340 fgEventSegs: eventSegs.fg,
6341 bgEventSegs: eventSegs.bg,
6342 eventDrag: this.sliceEventDrag(props.eventDrag, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs),
6343 eventResize: this.sliceEventResize(props.eventResize, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs),
6344 eventSelection: props.eventSelection,
6345 }; // TODO: give interactionSegs?
6346 }
6347 sliceNowDate(// does not memoize
6348 date, dateProfile, nextDayThreshold, context, ...extraArgs) {
6349 return this._sliceDateSpan({ range: { start: date, end: addMs(date, 1) }, allDay: false }, // add 1 ms, protect against null range
6350 dateProfile, nextDayThreshold, {}, context, ...extraArgs);
6351 }
6352 _sliceBusinessHours(businessHours, dateProfile, nextDayThreshold, context, ...extraArgs) {
6353 if (!businessHours) {
6354 return [];
6355 }
6356 return this._sliceEventStore(expandRecurring(businessHours, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), context), {}, dateProfile, nextDayThreshold, ...extraArgs).bg;
6357 }
6358 _sliceEventStore(eventStore, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs) {
6359 if (eventStore) {
6360 let rangeRes = sliceEventStore(eventStore, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold);
6361 return {
6362 bg: this.sliceEventRanges(rangeRes.bg, extraArgs),
6363 fg: this.sliceEventRanges(rangeRes.fg, extraArgs),
6364 };
6365 }
6366 return { bg: [], fg: [] };
6367 }
6368 _sliceInteraction(interaction, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs) {
6369 if (!interaction) {
6370 return null;
6371 }
6372 let rangeRes = sliceEventStore(interaction.mutatedEvents, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold);
6373 return {
6374 segs: this.sliceEventRanges(rangeRes.fg, extraArgs),
6375 affectedInstances: interaction.affectedEvents.instances,
6376 isEvent: interaction.isEvent,
6377 };
6378 }
6379 _sliceDateSpan(dateSpan, dateProfile, nextDayThreshold, eventUiBases, context, ...extraArgs) {
6380 if (!dateSpan) {
6381 return [];
6382 }
6383 let activeRange = computeActiveRange(dateProfile, Boolean(nextDayThreshold));
6384 let activeDateSpanRange = intersectRanges(dateSpan.range, activeRange);
6385 if (activeDateSpanRange) {
6386 dateSpan = Object.assign(Object.assign({}, dateSpan), { range: activeDateSpanRange });
6387 let eventRange = fabricateEventRange(dateSpan, eventUiBases, context);
6388 let segs = this.sliceRange(dateSpan.range, ...extraArgs);
6389 for (let seg of segs) {
6390 seg.eventRange = eventRange;
6391 }
6392 return segs;
6393 }
6394 return [];
6395 }
6396 /*
6397 "complete" seg means it has component and eventRange
6398 */
6399 sliceEventRanges(eventRanges, extraArgs) {
6400 let segs = [];
6401 for (let eventRange of eventRanges) {
6402 segs.push(...this.sliceEventRange(eventRange, extraArgs));
6403 }
6404 return segs;
6405 }
6406 /*
6407 "complete" seg means it has component and eventRange
6408 */
6409 sliceEventRange(eventRange, extraArgs) {
6410 let dateRange = eventRange.range;
6411 // hack to make multi-day events that are being force-displayed as list-items to take up only one day
6412 if (this.forceDayIfListItem && eventRange.ui.display === 'list-item') {
6413 dateRange = {
6414 start: dateRange.start,
6415 end: addDays(dateRange.start, 1),
6416 };
6417 }
6418 let segs = this.sliceRange(dateRange, ...extraArgs);
6419 for (let seg of segs) {
6420 seg.eventRange = eventRange;
6421 seg.isStart = eventRange.isStart && seg.isStart;
6422 seg.isEnd = eventRange.isEnd && seg.isEnd;
6423 }
6424 return segs;
6425 }
6426}
6427/*
6428for incorporating slotMinTime/slotMaxTime if appropriate
6429TODO: should be part of DateProfile!
6430TimelineDateProfile already does this btw
6431*/
6432function computeActiveRange(dateProfile, isComponentAllDay) {
6433 let range = dateProfile.activeRange;
6434 if (isComponentAllDay) {
6435 return range;
6436 }
6437 return {
6438 start: addMs(range.start, dateProfile.slotMinTime.milliseconds),
6439 end: addMs(range.end, dateProfile.slotMaxTime.milliseconds - 864e5), // 864e5 = ms in a day
6440 };
6441}
6442
6443// high-level segmenting-aware tester functions
6444// ------------------------------------------------------------------------------------------------------------------------
6445function isInteractionValid(interaction, dateProfile, context) {
6446 let { instances } = interaction.mutatedEvents;
6447 for (let instanceId in instances) {
6448 if (!rangeContainsRange(dateProfile.validRange, instances[instanceId].range)) {
6449 return false;
6450 }
6451 }
6452 return isNewPropsValid({ eventDrag: interaction }, context); // HACK: the eventDrag props is used for ALL interactions
6453}
6454function isDateSelectionValid(dateSelection, dateProfile, context) {
6455 if (!rangeContainsRange(dateProfile.validRange, dateSelection.range)) {
6456 return false;
6457 }
6458 return isNewPropsValid({ dateSelection }, context);
6459}
6460function isNewPropsValid(newProps, context) {
6461 let calendarState = context.getCurrentData();
6462 let props = Object.assign({ businessHours: calendarState.businessHours, dateSelection: '', eventStore: calendarState.eventStore, eventUiBases: calendarState.eventUiBases, eventSelection: '', eventDrag: null, eventResize: null }, newProps);
6463 return (context.pluginHooks.isPropsValid || isPropsValid)(props, context);
6464}
6465function isPropsValid(state, context, dateSpanMeta = {}, filterConfig) {
6466 if (state.eventDrag && !isInteractionPropsValid(state, context, dateSpanMeta, filterConfig)) {
6467 return false;
6468 }
6469 if (state.dateSelection && !isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig)) {
6470 return false;
6471 }
6472 return true;
6473}
6474// Moving Event Validation
6475// ------------------------------------------------------------------------------------------------------------------------
6476function isInteractionPropsValid(state, context, dateSpanMeta, filterConfig) {
6477 let currentState = context.getCurrentData();
6478 let interaction = state.eventDrag; // HACK: the eventDrag props is used for ALL interactions
6479 let subjectEventStore = interaction.mutatedEvents;
6480 let subjectDefs = subjectEventStore.defs;
6481 let subjectInstances = subjectEventStore.instances;
6482 let subjectConfigs = compileEventUis(subjectDefs, interaction.isEvent ?
6483 state.eventUiBases :
6484 { '': currentState.selectionConfig });
6485 if (filterConfig) {
6486 subjectConfigs = mapHash(subjectConfigs, filterConfig);
6487 }
6488 // exclude the subject events. TODO: exclude defs too?
6489 let otherEventStore = excludeInstances(state.eventStore, interaction.affectedEvents.instances);
6490 let otherDefs = otherEventStore.defs;
6491 let otherInstances = otherEventStore.instances;
6492 let otherConfigs = compileEventUis(otherDefs, state.eventUiBases);
6493 for (let subjectInstanceId in subjectInstances) {
6494 let subjectInstance = subjectInstances[subjectInstanceId];
6495 let subjectRange = subjectInstance.range;
6496 let subjectConfig = subjectConfigs[subjectInstance.defId];
6497 let subjectDef = subjectDefs[subjectInstance.defId];
6498 // constraint
6499 if (!allConstraintsPass(subjectConfig.constraints, subjectRange, otherEventStore, state.businessHours, context)) {
6500 return false;
6501 }
6502 // overlap
6503 let { eventOverlap } = context.options;
6504 let eventOverlapFunc = typeof eventOverlap === 'function' ? eventOverlap : null;
6505 for (let otherInstanceId in otherInstances) {
6506 let otherInstance = otherInstances[otherInstanceId];
6507 // intersect! evaluate
6508 if (rangesIntersect(subjectRange, otherInstance.range)) {
6509 let otherOverlap = otherConfigs[otherInstance.defId].overlap;
6510 // consider the other event's overlap. only do this if the subject event is a "real" event
6511 if (otherOverlap === false && interaction.isEvent) {
6512 return false;
6513 }
6514 if (subjectConfig.overlap === false) {
6515 return false;
6516 }
6517 if (eventOverlapFunc && !eventOverlapFunc(new EventImpl(context, otherDefs[otherInstance.defId], otherInstance), // still event
6518 new EventImpl(context, subjectDef, subjectInstance))) {
6519 return false;
6520 }
6521 }
6522 }
6523 // allow (a function)
6524 let calendarEventStore = currentState.eventStore; // need global-to-calendar, not local to component (splittable)state
6525 for (let subjectAllow of subjectConfig.allows) {
6526 let subjectDateSpan = Object.assign(Object.assign({}, dateSpanMeta), { range: subjectInstance.range, allDay: subjectDef.allDay });
6527 let origDef = calendarEventStore.defs[subjectDef.defId];
6528 let origInstance = calendarEventStore.instances[subjectInstanceId];
6529 let eventApi;
6530 if (origDef) { // was previously in the calendar
6531 eventApi = new EventImpl(context, origDef, origInstance);
6532 }
6533 else { // was an external event
6534 eventApi = new EventImpl(context, subjectDef); // no instance, because had no dates
6535 }
6536 if (!subjectAllow(buildDateSpanApiWithContext(subjectDateSpan, context), eventApi)) {
6537 return false;
6538 }
6539 }
6540 }
6541 return true;
6542}
6543// Date Selection Validation
6544// ------------------------------------------------------------------------------------------------------------------------
6545function isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig) {
6546 let relevantEventStore = state.eventStore;
6547 let relevantDefs = relevantEventStore.defs;
6548 let relevantInstances = relevantEventStore.instances;
6549 let selection = state.dateSelection;
6550 let selectionRange = selection.range;
6551 let { selectionConfig } = context.getCurrentData();
6552 if (filterConfig) {
6553 selectionConfig = filterConfig(selectionConfig);
6554 }
6555 // constraint
6556 if (!allConstraintsPass(selectionConfig.constraints, selectionRange, relevantEventStore, state.businessHours, context)) {
6557 return false;
6558 }
6559 // overlap
6560 let { selectOverlap } = context.options;
6561 let selectOverlapFunc = typeof selectOverlap === 'function' ? selectOverlap : null;
6562 for (let relevantInstanceId in relevantInstances) {
6563 let relevantInstance = relevantInstances[relevantInstanceId];
6564 // intersect! evaluate
6565 if (rangesIntersect(selectionRange, relevantInstance.range)) {
6566 if (selectionConfig.overlap === false) {
6567 return false;
6568 }
6569 if (selectOverlapFunc && !selectOverlapFunc(new EventImpl(context, relevantDefs[relevantInstance.defId], relevantInstance), null)) {
6570 return false;
6571 }
6572 }
6573 }
6574 // allow (a function)
6575 for (let selectionAllow of selectionConfig.allows) {
6576 let fullDateSpan = Object.assign(Object.assign({}, dateSpanMeta), selection);
6577 if (!selectionAllow(buildDateSpanApiWithContext(fullDateSpan, context), null)) {
6578 return false;
6579 }
6580 }
6581 return true;
6582}
6583// Constraint Utils
6584// ------------------------------------------------------------------------------------------------------------------------
6585function allConstraintsPass(constraints, subjectRange, otherEventStore, businessHoursUnexpanded, context) {
6586 for (let constraint of constraints) {
6587 if (!anyRangesContainRange(constraintToRanges(constraint, subjectRange, otherEventStore, businessHoursUnexpanded, context), subjectRange)) {
6588 return false;
6589 }
6590 }
6591 return true;
6592}
6593function constraintToRanges(constraint, subjectRange, // for expanding a recurring constraint, or expanding business hours
6594otherEventStore, // for if constraint is an even group ID
6595businessHoursUnexpanded, // for if constraint is 'businessHours'
6596context) {
6597 if (constraint === 'businessHours') {
6598 return eventStoreToRanges(expandRecurring(businessHoursUnexpanded, subjectRange, context));
6599 }
6600 if (typeof constraint === 'string') { // an group ID
6601 return eventStoreToRanges(filterEventStoreDefs(otherEventStore, (eventDef) => eventDef.groupId === constraint));
6602 }
6603 if (typeof constraint === 'object' && constraint) { // non-null object
6604 return eventStoreToRanges(expandRecurring(constraint, subjectRange, context));
6605 }
6606 return []; // if it's false
6607}
6608// TODO: move to event-store file?
6609function eventStoreToRanges(eventStore) {
6610 let { instances } = eventStore;
6611 let ranges = [];
6612 for (let instanceId in instances) {
6613 ranges.push(instances[instanceId].range);
6614 }
6615 return ranges;
6616}
6617// TODO: move to geom file?
6618function anyRangesContainRange(outerRanges, innerRange) {
6619 for (let outerRange of outerRanges) {
6620 if (rangeContainsRange(outerRange, innerRange)) {
6621 return true;
6622 }
6623 }
6624 return false;
6625}
6626
6627const VISIBLE_HIDDEN_RE = /^(visible|hidden)$/;
6628class Scroller extends BaseComponent {
6629 constructor() {
6630 super(...arguments);
6631 this.handleEl = (el) => {
6632 this.el = el;
6633 setRef(this.props.elRef, el);
6634 };
6635 }
6636 render() {
6637 let { props } = this;
6638 let { liquid, liquidIsAbsolute } = props;
6639 let isAbsolute = liquid && liquidIsAbsolute;
6640 let className = ['fc-scroller'];
6641 if (liquid) {
6642 if (liquidIsAbsolute) {
6643 className.push('fc-scroller-liquid-absolute');
6644 }
6645 else {
6646 className.push('fc-scroller-liquid');
6647 }
6648 }
6649 return (createElement("div", { ref: this.handleEl, className: className.join(' '), style: {
6650 overflowX: props.overflowX,
6651 overflowY: props.overflowY,
6652 left: (isAbsolute && -(props.overcomeLeft || 0)) || '',
6653 right: (isAbsolute && -(props.overcomeRight || 0)) || '',
6654 bottom: (isAbsolute && -(props.overcomeBottom || 0)) || '',
6655 marginLeft: (!isAbsolute && -(props.overcomeLeft || 0)) || '',
6656 marginRight: (!isAbsolute && -(props.overcomeRight || 0)) || '',
6657 marginBottom: (!isAbsolute && -(props.overcomeBottom || 0)) || '',
6658 maxHeight: props.maxHeight || '',
6659 } }, props.children));
6660 }
6661 needsXScrolling() {
6662 if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) {
6663 return false;
6664 }
6665 // testing scrollWidth>clientWidth is unreliable cross-browser when pixel heights aren't integers.
6666 // much more reliable to see if children are taller than the scroller, even tho doesn't account for
6667 // inner-child margins and absolute positioning
6668 let { el } = this;
6669 let realClientWidth = this.el.getBoundingClientRect().width - this.getYScrollbarWidth();
6670 let { children } = el;
6671 for (let i = 0; i < children.length; i += 1) {
6672 let childEl = children[i];
6673 if (childEl.getBoundingClientRect().width > realClientWidth) {
6674 return true;
6675 }
6676 }
6677 return false;
6678 }
6679 needsYScrolling() {
6680 if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) {
6681 return false;
6682 }
6683 // testing scrollHeight>clientHeight is unreliable cross-browser when pixel heights aren't integers.
6684 // much more reliable to see if children are taller than the scroller, even tho doesn't account for
6685 // inner-child margins and absolute positioning
6686 let { el } = this;
6687 let realClientHeight = this.el.getBoundingClientRect().height - this.getXScrollbarWidth();
6688 let { children } = el;
6689 for (let i = 0; i < children.length; i += 1) {
6690 let childEl = children[i];
6691 if (childEl.getBoundingClientRect().height > realClientHeight) {
6692 return true;
6693 }
6694 }
6695 return false;
6696 }
6697 getXScrollbarWidth() {
6698 if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) {
6699 return 0;
6700 }
6701 return this.el.offsetHeight - this.el.clientHeight; // only works because we guarantee no borders. TODO: add to CSS with important?
6702 }
6703 getYScrollbarWidth() {
6704 if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) {
6705 return 0;
6706 }
6707 return this.el.offsetWidth - this.el.clientWidth; // only works because we guarantee no borders. TODO: add to CSS with important?
6708 }
6709}
6710
6711/*
6712TODO: somehow infer OtherArgs from masterCallback?
6713TODO: infer RefType from masterCallback if provided
6714*/
6715class RefMap {
6716 constructor(masterCallback) {
6717 this.masterCallback = masterCallback;
6718 this.currentMap = {};
6719 this.depths = {};
6720 this.callbackMap = {};
6721 this.handleValue = (val, key) => {
6722 let { depths, currentMap } = this;
6723 let removed = false;
6724 let added = false;
6725 if (val !== null) {
6726 // for bug... ACTUALLY: can probably do away with this now that callers don't share numeric indices anymore
6727 removed = (key in currentMap);
6728 currentMap[key] = val;
6729 depths[key] = (depths[key] || 0) + 1;
6730 added = true;
6731 }
6732 else {
6733 depths[key] -= 1;
6734 if (!depths[key]) {
6735 delete currentMap[key];
6736 delete this.callbackMap[key];
6737 removed = true;
6738 }
6739 }
6740 if (this.masterCallback) {
6741 if (removed) {
6742 this.masterCallback(null, String(key));
6743 }
6744 if (added) {
6745 this.masterCallback(val, String(key));
6746 }
6747 }
6748 };
6749 }
6750 createRef(key) {
6751 let refCallback = this.callbackMap[key];
6752 if (!refCallback) {
6753 refCallback = this.callbackMap[key] = (val) => {
6754 this.handleValue(val, String(key));
6755 };
6756 }
6757 return refCallback;
6758 }
6759 // TODO: check callers that don't care about order. should use getAll instead
6760 // NOTE: this method has become less valuable now that we are encouraged to map order by some other index
6761 // TODO: provide ONE array-export function, buildArray, which fails on non-numeric indexes. caller can manipulate and "collect"
6762 collect(startIndex, endIndex, step) {
6763 return collectFromHash(this.currentMap, startIndex, endIndex, step);
6764 }
6765 getAll() {
6766 return hashValuesToArray(this.currentMap);
6767 }
6768}
6769
6770function computeShrinkWidth(chunkEls) {
6771 let shrinkCells = findElements(chunkEls, '.fc-scrollgrid-shrink');
6772 let largestWidth = 0;
6773 for (let shrinkCell of shrinkCells) {
6774 largestWidth = Math.max(largestWidth, computeSmallestCellWidth(shrinkCell));
6775 }
6776 return Math.ceil(largestWidth); // <table> elements work best with integers. round up to ensure contents fits
6777}
6778function getSectionHasLiquidHeight(props, sectionConfig) {
6779 return props.liquid && sectionConfig.liquid; // does the section do liquid-height? (need to have whole scrollgrid liquid-height as well)
6780}
6781function getAllowYScrolling(props, sectionConfig) {
6782 return sectionConfig.maxHeight != null || // if its possible for the height to max out, we might need scrollbars
6783 getSectionHasLiquidHeight(props, sectionConfig); // if the section is liquid height, it might condense enough to require scrollbars
6784}
6785// TODO: ONLY use `arg`. force out internal function to use same API
6786function renderChunkContent(sectionConfig, chunkConfig, arg, isHeader) {
6787 let { expandRows } = arg;
6788 let content = typeof chunkConfig.content === 'function' ?
6789 chunkConfig.content(arg) :
6790 createElement('table', {
6791 role: 'presentation',
6792 className: [
6793 chunkConfig.tableClassName,
6794 sectionConfig.syncRowHeights ? 'fc-scrollgrid-sync-table' : '',
6795 ].join(' '),
6796 style: {
6797 minWidth: arg.tableMinWidth,
6798 width: arg.clientWidth,
6799 height: expandRows ? arg.clientHeight : '', // css `height` on a <table> serves as a min-height
6800 },
6801 }, arg.tableColGroupNode, createElement(isHeader ? 'thead' : 'tbody', {
6802 role: 'presentation',
6803 }, typeof chunkConfig.rowContent === 'function'
6804 ? chunkConfig.rowContent(arg)
6805 : chunkConfig.rowContent));
6806 return content;
6807}
6808function isColPropsEqual(cols0, cols1) {
6809 return isArraysEqual(cols0, cols1, isPropsEqual);
6810}
6811function renderMicroColGroup(cols, shrinkWidth) {
6812 let colNodes = [];
6813 /*
6814 for ColProps with spans, it would have been great to make a single <col span="">
6815 HOWEVER, Chrome was getting messing up distributing the width to <td>/<th> elements with colspans.
6816 SOLUTION: making individual <col> elements makes Chrome behave.
6817 */
6818 for (let colProps of cols) {
6819 let span = colProps.span || 1;
6820 for (let i = 0; i < span; i += 1) {
6821 colNodes.push(createElement("col", { style: {
6822 width: colProps.width === 'shrink' ? sanitizeShrinkWidth(shrinkWidth) : (colProps.width || ''),
6823 minWidth: colProps.minWidth || '',
6824 } }));
6825 }
6826 }
6827 return createElement('colgroup', {}, ...colNodes);
6828}
6829function sanitizeShrinkWidth(shrinkWidth) {
6830 /* why 4? if we do 0, it will kill any border, which are needed for computeSmallestCellWidth
6831 4 accounts for 2 2-pixel borders. TODO: better solution? */
6832 return shrinkWidth == null ? 4 : shrinkWidth;
6833}
6834function hasShrinkWidth(cols) {
6835 for (let col of cols) {
6836 if (col.width === 'shrink') {
6837 return true;
6838 }
6839 }
6840 return false;
6841}
6842function getScrollGridClassNames(liquid, context) {
6843 let classNames = [
6844 'fc-scrollgrid',
6845 context.theme.getClass('table'),
6846 ];
6847 if (liquid) {
6848 classNames.push('fc-scrollgrid-liquid');
6849 }
6850 return classNames;
6851}
6852function getSectionClassNames(sectionConfig, wholeTableVGrow) {
6853 let classNames = [
6854 'fc-scrollgrid-section',
6855 `fc-scrollgrid-section-${sectionConfig.type}`,
6856 sectionConfig.className, // used?
6857 ];
6858 if (wholeTableVGrow && sectionConfig.liquid && sectionConfig.maxHeight == null) {
6859 classNames.push('fc-scrollgrid-section-liquid');
6860 }
6861 if (sectionConfig.isSticky) {
6862 classNames.push('fc-scrollgrid-section-sticky');
6863 }
6864 return classNames;
6865}
6866function renderScrollShim(arg) {
6867 return (createElement("div", { className: "fc-scrollgrid-sticky-shim", style: {
6868 width: arg.clientWidth,
6869 minWidth: arg.tableMinWidth,
6870 } }));
6871}
6872function getStickyHeaderDates(options) {
6873 let { stickyHeaderDates } = options;
6874 if (stickyHeaderDates == null || stickyHeaderDates === 'auto') {
6875 stickyHeaderDates = options.height === 'auto' || options.viewHeight === 'auto';
6876 }
6877 return stickyHeaderDates;
6878}
6879function getStickyFooterScrollbar(options) {
6880 let { stickyFooterScrollbar } = options;
6881 if (stickyFooterScrollbar == null || stickyFooterScrollbar === 'auto') {
6882 stickyFooterScrollbar = options.height === 'auto' || options.viewHeight === 'auto';
6883 }
6884 return stickyFooterScrollbar;
6885}
6886
6887class SimpleScrollGrid extends BaseComponent {
6888 constructor() {
6889 super(...arguments);
6890 this.processCols = memoize((a) => a, isColPropsEqual); // so we get same `cols` props every time
6891 // yucky to memoize VNodes, but much more efficient for consumers
6892 this.renderMicroColGroup = memoize(renderMicroColGroup);
6893 this.scrollerRefs = new RefMap();
6894 this.scrollerElRefs = new RefMap(this._handleScrollerEl.bind(this));
6895 this.state = {
6896 shrinkWidth: null,
6897 forceYScrollbars: false,
6898 scrollerClientWidths: {},
6899 scrollerClientHeights: {},
6900 };
6901 // TODO: can do a really simple print-view. dont need to join rows
6902 this.handleSizing = () => {
6903 this.safeSetState(Object.assign({ shrinkWidth: this.computeShrinkWidth() }, this.computeScrollerDims()));
6904 };
6905 }
6906 render() {
6907 let { props, state, context } = this;
6908 let sectionConfigs = props.sections || [];
6909 let cols = this.processCols(props.cols);
6910 let microColGroupNode = this.renderMicroColGroup(cols, state.shrinkWidth);
6911 let classNames = getScrollGridClassNames(props.liquid, context);
6912 if (props.collapsibleWidth) {
6913 classNames.push('fc-scrollgrid-collapsible');
6914 }
6915 // TODO: make DRY
6916 let configCnt = sectionConfigs.length;
6917 let configI = 0;
6918 let currentConfig;
6919 let headSectionNodes = [];
6920 let bodySectionNodes = [];
6921 let footSectionNodes = [];
6922 while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'header') {
6923 headSectionNodes.push(this.renderSection(currentConfig, microColGroupNode, true));
6924 configI += 1;
6925 }
6926 while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'body') {
6927 bodySectionNodes.push(this.renderSection(currentConfig, microColGroupNode, false));
6928 configI += 1;
6929 }
6930 while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'footer') {
6931 footSectionNodes.push(this.renderSection(currentConfig, microColGroupNode, true));
6932 configI += 1;
6933 }
6934 // firefox bug: when setting height on table and there is a thead or tfoot,
6935 // the necessary height:100% on the liquid-height body section forces the *whole* table to be taller. (bug #5524)
6936 // use getCanVGrowWithinCell as a way to detect table-stupid firefox.
6937 // if so, use a simpler dom structure, jam everything into a lone tbody.
6938 let isBuggy = !getCanVGrowWithinCell();
6939 const roleAttrs = { role: 'rowgroup' };
6940 return createElement('table', {
6941 role: 'grid',
6942 className: classNames.join(' '),
6943 style: { height: props.height },
6944 }, Boolean(!isBuggy && headSectionNodes.length) && createElement('thead', roleAttrs, ...headSectionNodes), Boolean(!isBuggy && bodySectionNodes.length) && createElement('tbody', roleAttrs, ...bodySectionNodes), Boolean(!isBuggy && footSectionNodes.length) && createElement('tfoot', roleAttrs, ...footSectionNodes), isBuggy && createElement('tbody', roleAttrs, ...headSectionNodes, ...bodySectionNodes, ...footSectionNodes));
6945 }
6946 renderSection(sectionConfig, microColGroupNode, isHeader) {
6947 if ('outerContent' in sectionConfig) {
6948 return (createElement(Fragment, { key: sectionConfig.key }, sectionConfig.outerContent));
6949 }
6950 return (createElement("tr", { key: sectionConfig.key, role: "presentation", className: getSectionClassNames(sectionConfig, this.props.liquid).join(' ') }, this.renderChunkTd(sectionConfig, microColGroupNode, sectionConfig.chunk, isHeader)));
6951 }
6952 renderChunkTd(sectionConfig, microColGroupNode, chunkConfig, isHeader) {
6953 if ('outerContent' in chunkConfig) {
6954 return chunkConfig.outerContent;
6955 }
6956 let { props } = this;
6957 let { forceYScrollbars, scrollerClientWidths, scrollerClientHeights } = this.state;
6958 let needsYScrolling = getAllowYScrolling(props, sectionConfig); // TODO: do lazily. do in section config?
6959 let isLiquid = getSectionHasLiquidHeight(props, sectionConfig);
6960 // for `!props.liquid` - is WHOLE scrollgrid natural height?
6961 // TODO: do same thing in advanced scrollgrid? prolly not b/c always has horizontal scrollbars
6962 let overflowY = !props.liquid ? 'visible' :
6963 forceYScrollbars ? 'scroll' :
6964 !needsYScrolling ? 'hidden' :
6965 'auto';
6966 let sectionKey = sectionConfig.key;
6967 let content = renderChunkContent(sectionConfig, chunkConfig, {
6968 tableColGroupNode: microColGroupNode,
6969 tableMinWidth: '',
6970 clientWidth: (!props.collapsibleWidth && scrollerClientWidths[sectionKey] !== undefined) ? scrollerClientWidths[sectionKey] : null,
6971 clientHeight: scrollerClientHeights[sectionKey] !== undefined ? scrollerClientHeights[sectionKey] : null,
6972 expandRows: sectionConfig.expandRows,
6973 syncRowHeights: false,
6974 rowSyncHeights: [],
6975 reportRowHeightChange: () => { },
6976 }, isHeader);
6977 return createElement(isHeader ? 'th' : 'td', {
6978 ref: chunkConfig.elRef,
6979 role: 'presentation',
6980 }, createElement("div", { className: `fc-scroller-harness${isLiquid ? ' fc-scroller-harness-liquid' : ''}` },
6981 createElement(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
6982 : true }, content)));
6983 }
6984 _handleScrollerEl(scrollerEl, key) {
6985 let section = getSectionByKey(this.props.sections, key);
6986 if (section) {
6987 setRef(section.chunk.scrollerElRef, scrollerEl);
6988 }
6989 }
6990 componentDidMount() {
6991 this.handleSizing();
6992 this.context.addResizeHandler(this.handleSizing);
6993 }
6994 componentDidUpdate() {
6995 // TODO: need better solution when state contains non-sizing things
6996 this.handleSizing();
6997 }
6998 componentWillUnmount() {
6999 this.context.removeResizeHandler(this.handleSizing);
7000 }
7001 computeShrinkWidth() {
7002 return hasShrinkWidth(this.props.cols)
7003 ? computeShrinkWidth(this.scrollerElRefs.getAll())
7004 : 0;
7005 }
7006 computeScrollerDims() {
7007 let scrollbarWidth = getScrollbarWidths();
7008 let { scrollerRefs, scrollerElRefs } = this;
7009 let forceYScrollbars = false;
7010 let scrollerClientWidths = {};
7011 let scrollerClientHeights = {};
7012 for (let sectionKey in scrollerRefs.currentMap) {
7013 let scroller = scrollerRefs.currentMap[sectionKey];
7014 if (scroller && scroller.needsYScrolling()) {
7015 forceYScrollbars = true;
7016 break;
7017 }
7018 }
7019 for (let section of this.props.sections) {
7020 let sectionKey = section.key;
7021 let scrollerEl = scrollerElRefs.currentMap[sectionKey];
7022 if (scrollerEl) {
7023 let harnessEl = scrollerEl.parentNode; // TODO: weird way to get this. need harness b/c doesn't include table borders
7024 scrollerClientWidths[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().width - (forceYScrollbars
7025 ? scrollbarWidth.y // use global because scroller might not have scrollbars yet but will need them in future
7026 : 0));
7027 scrollerClientHeights[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().height);
7028 }
7029 }
7030 return { forceYScrollbars, scrollerClientWidths, scrollerClientHeights };
7031 }
7032}
7033SimpleScrollGrid.addStateEquality({
7034 scrollerClientWidths: isPropsEqual,
7035 scrollerClientHeights: isPropsEqual,
7036});
7037function getSectionByKey(sections, key) {
7038 for (let section of sections) {
7039 if (section.key === key) {
7040 return section;
7041 }
7042 }
7043 return null;
7044}
7045
7046class EventContainer extends BaseComponent {
7047 constructor() {
7048 super(...arguments);
7049 this.handleEl = (el) => {
7050 this.el = el;
7051 if (el) {
7052 setElSeg(el, this.props.seg);
7053 }
7054 };
7055 }
7056 render() {
7057 const { props, context } = this;
7058 const { options } = context;
7059 const { seg } = props;
7060 const { eventRange } = seg;
7061 const { ui } = eventRange;
7062 const renderProps = {
7063 event: new EventImpl(context, eventRange.def, eventRange.instance),
7064 view: context.viewApi,
7065 timeText: props.timeText,
7066 textColor: ui.textColor,
7067 backgroundColor: ui.backgroundColor,
7068 borderColor: ui.borderColor,
7069 isDraggable: !props.disableDragging && computeSegDraggable(seg, context),
7070 isStartResizable: !props.disableResizing && computeSegStartResizable(seg, context),
7071 isEndResizable: !props.disableResizing && computeSegEndResizable(seg),
7072 isMirror: Boolean(props.isDragging || props.isResizing || props.isDateSelecting),
7073 isStart: Boolean(seg.isStart),
7074 isEnd: Boolean(seg.isEnd),
7075 isPast: Boolean(props.isPast),
7076 isFuture: Boolean(props.isFuture),
7077 isToday: Boolean(props.isToday),
7078 isSelected: Boolean(props.isSelected),
7079 isDragging: Boolean(props.isDragging),
7080 isResizing: Boolean(props.isResizing),
7081 };
7082 return (createElement(ContentContainer, Object.assign({}, props /* contains children */, { elRef: this.handleEl, elClasses: [
7083 ...getEventClassNames(renderProps),
7084 ...seg.eventRange.ui.classNames,
7085 ...(props.elClasses || []),
7086 ], renderProps: renderProps, generatorName: "eventContent", customGenerator: options.eventContent, defaultGenerator: props.defaultGenerator, classNameGenerator: options.eventClassNames, didMount: options.eventDidMount, willUnmount: options.eventWillUnmount })));
7087 }
7088 componentDidUpdate(prevProps) {
7089 if (this.el && this.props.seg !== prevProps.seg) {
7090 setElSeg(this.el, this.props.seg);
7091 }
7092 }
7093}
7094
7095// should not be a purecomponent
7096class StandardEvent extends BaseComponent {
7097 render() {
7098 let { props, context } = this;
7099 let { options } = context;
7100 let { seg } = props;
7101 let { ui } = seg.eventRange;
7102 let timeFormat = options.eventTimeFormat || props.defaultTimeFormat;
7103 let timeText = buildSegTimeText(seg, timeFormat, context, props.defaultDisplayEventTime, props.defaultDisplayEventEnd);
7104 return (createElement(EventContainer, Object.assign({}, props /* includes elRef */, { elTag: "a", elStyle: {
7105 borderColor: ui.borderColor,
7106 backgroundColor: ui.backgroundColor,
7107 }, elAttrs: getSegAnchorAttrs(seg, context), defaultGenerator: renderInnerContent$1, timeText: timeText }), (InnerContent, eventContentArg) => (createElement(Fragment, null,
7108 createElement(InnerContent, { elTag: "div", elClasses: ['fc-event-main'], elStyle: { color: eventContentArg.textColor } }),
7109 Boolean(eventContentArg.isStartResizable) && (createElement("div", { className: "fc-event-resizer fc-event-resizer-start" })),
7110 Boolean(eventContentArg.isEndResizable) && (createElement("div", { className: "fc-event-resizer fc-event-resizer-end" }))))));
7111 }
7112}
7113function renderInnerContent$1(innerProps) {
7114 return (createElement("div", { className: "fc-event-main-frame" },
7115 innerProps.timeText && (createElement("div", { className: "fc-event-time" }, innerProps.timeText)),
7116 createElement("div", { className: "fc-event-title-container" },
7117 createElement("div", { className: "fc-event-title fc-sticky" }, innerProps.event.title || createElement(Fragment, null, "\u00A0")))));
7118}
7119
7120const NowIndicatorContainer = (props) => (createElement(ViewContextType.Consumer, null, (context) => {
7121 let { options } = context;
7122 let renderProps = {
7123 isAxis: props.isAxis,
7124 date: context.dateEnv.toDate(props.date),
7125 view: context.viewApi,
7126 };
7127 return (createElement(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 })));
7128}));
7129
7130const DAY_NUM_FORMAT = createFormatter({ day: 'numeric' });
7131class DayCellContainer extends BaseComponent {
7132 constructor() {
7133 super(...arguments);
7134 this.refineRenderProps = memoizeObjArg(refineRenderProps);
7135 }
7136 render() {
7137 let { props, context } = this;
7138 let { options } = context;
7139 let renderProps = this.refineRenderProps({
7140 date: props.date,
7141 dateProfile: props.dateProfile,
7142 todayRange: props.todayRange,
7143 isMonthStart: props.isMonthStart || false,
7144 showDayNumber: props.showDayNumber,
7145 extraRenderProps: props.extraRenderProps,
7146 viewApi: context.viewApi,
7147 dateEnv: context.dateEnv,
7148 monthStartFormat: options.monthStartFormat,
7149 });
7150 return (createElement(ContentContainer, Object.assign({}, props /* includes children */, { elClasses: [
7151 ...getDayClassNames(renderProps, context.theme),
7152 ...(props.elClasses || []),
7153 ], 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:
7154 // don't use custom classNames if disabled
7155 renderProps.isDisabled ? undefined : options.dayCellClassNames, didMount: options.dayCellDidMount, willUnmount: options.dayCellWillUnmount })));
7156 }
7157}
7158function hasCustomDayCellContent(options) {
7159 return Boolean(options.dayCellContent || hasCustomRenderingHandler('dayCellContent', options));
7160}
7161function refineRenderProps(raw) {
7162 let { date, dateEnv, dateProfile, isMonthStart } = raw;
7163 let dayMeta = getDateMeta(date, raw.todayRange, null, dateProfile);
7164 let dayNumberText = raw.showDayNumber ? (dateEnv.format(date, isMonthStart ? raw.monthStartFormat : DAY_NUM_FORMAT)) : '';
7165 return Object.assign(Object.assign(Object.assign({ date: dateEnv.toDate(date), view: raw.viewApi }, dayMeta), { isMonthStart,
7166 dayNumberText }), raw.extraRenderProps);
7167}
7168
7169class BgEvent extends BaseComponent {
7170 render() {
7171 let { props } = this;
7172 let { seg } = props;
7173 return (createElement(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 }));
7174 }
7175}
7176function renderInnerContent(props) {
7177 let { title } = props.event;
7178 return title && (createElement("div", { className: "fc-event-title" }, props.event.title));
7179}
7180function renderFill(fillType) {
7181 return (createElement("div", { className: `fc-${fillType}` }));
7182}
7183
7184const WeekNumberContainer = (props) => (createElement(ViewContextType.Consumer, null, (context) => {
7185 let { dateEnv, options } = context;
7186 let { date } = props;
7187 let format = options.weekNumberFormat || props.defaultFormat;
7188 let num = dateEnv.computeWeekNumber(date); // TODO: somehow use for formatting as well?
7189 let text = dateEnv.format(date, format);
7190 let renderProps = { num, text, date };
7191 return (createElement(ContentContainer // why isn't WeekNumberContentArg being auto-detected?
7192 , Object.assign({}, props /* includes children */, { renderProps: renderProps, generatorName: "weekNumberContent", customGenerator: options.weekNumberContent, defaultGenerator: renderInner, classNameGenerator: options.weekNumberClassNames, didMount: options.weekNumberDidMount, willUnmount: options.weekNumberWillUnmount })));
7193}));
7194function renderInner(innerProps) {
7195 return innerProps.text;
7196}
7197
7198const PADDING_FROM_VIEWPORT = 10;
7199class Popover extends BaseComponent {
7200 constructor() {
7201 super(...arguments);
7202 this.state = {
7203 titleId: getUniqueDomId(),
7204 };
7205 this.handleRootEl = (el) => {
7206 this.rootEl = el;
7207 if (this.props.elRef) {
7208 setRef(this.props.elRef, el);
7209 }
7210 };
7211 // Triggered when the user clicks *anywhere* in the document, for the autoHide feature
7212 this.handleDocumentMouseDown = (ev) => {
7213 // only hide the popover if the click happened outside the popover
7214 const target = getEventTargetViaRoot(ev);
7215 if (!this.rootEl.contains(target)) {
7216 this.handleCloseClick();
7217 }
7218 };
7219 this.handleDocumentKeyDown = (ev) => {
7220 if (ev.key === 'Escape') {
7221 this.handleCloseClick();
7222 }
7223 };
7224 this.handleCloseClick = () => {
7225 let { onClose } = this.props;
7226 if (onClose) {
7227 onClose();
7228 }
7229 };
7230 }
7231 render() {
7232 let { theme, options } = this.context;
7233 let { props, state } = this;
7234 let classNames = [
7235 'fc-popover',
7236 theme.getClass('popover'),
7237 ].concat(props.extraClassNames || []);
7238 return createPortal(createElement("div", Object.assign({}, props.extraAttrs, { id: props.id, className: classNames.join(' '), "aria-labelledby": state.titleId, ref: this.handleRootEl }),
7239 createElement("div", { className: 'fc-popover-header ' + theme.getClass('popoverHeader') },
7240 createElement("span", { className: "fc-popover-title", id: state.titleId }, props.title),
7241 createElement("span", { className: 'fc-popover-close ' + theme.getIconClass('close'), title: options.closeHint, onClick: this.handleCloseClick })),
7242 createElement("div", { className: 'fc-popover-body ' + theme.getClass('popoverContent') }, props.children)), props.parentEl);
7243 }
7244 componentDidMount() {
7245 document.addEventListener('mousedown', this.handleDocumentMouseDown);
7246 document.addEventListener('keydown', this.handleDocumentKeyDown);
7247 this.updateSize();
7248 }
7249 componentWillUnmount() {
7250 document.removeEventListener('mousedown', this.handleDocumentMouseDown);
7251 document.removeEventListener('keydown', this.handleDocumentKeyDown);
7252 }
7253 updateSize() {
7254 let { isRtl } = this.context;
7255 let { alignmentEl, alignGridTop } = this.props;
7256 let { rootEl } = this;
7257 let alignmentRect = computeClippedClientRect(alignmentEl);
7258 if (alignmentRect) {
7259 let popoverDims = rootEl.getBoundingClientRect();
7260 // position relative to viewport
7261 let popoverTop = alignGridTop
7262 ? elementClosest(alignmentEl, '.fc-scrollgrid').getBoundingClientRect().top
7263 : alignmentRect.top;
7264 let popoverLeft = isRtl ? alignmentRect.right - popoverDims.width : alignmentRect.left;
7265 // constrain
7266 popoverTop = Math.max(popoverTop, PADDING_FROM_VIEWPORT);
7267 popoverLeft = Math.min(popoverLeft, document.documentElement.clientWidth - PADDING_FROM_VIEWPORT - popoverDims.width);
7268 popoverLeft = Math.max(popoverLeft, PADDING_FROM_VIEWPORT);
7269 let origin = rootEl.offsetParent.getBoundingClientRect();
7270 applyStyle(rootEl, {
7271 top: popoverTop - origin.top,
7272 left: popoverLeft - origin.left,
7273 });
7274 }
7275 }
7276}
7277
7278class MorePopover extends DateComponent {
7279 constructor() {
7280 super(...arguments);
7281 this.handleRootEl = (rootEl) => {
7282 this.rootEl = rootEl;
7283 if (rootEl) {
7284 this.context.registerInteractiveComponent(this, {
7285 el: rootEl,
7286 useEventCenter: false,
7287 });
7288 }
7289 else {
7290 this.context.unregisterInteractiveComponent(this);
7291 }
7292 };
7293 }
7294 render() {
7295 let { options, dateEnv } = this.context;
7296 let { props } = this;
7297 let { startDate, todayRange, dateProfile } = props;
7298 let title = dateEnv.format(startDate, options.dayPopoverFormat);
7299 return (createElement(DayCellContainer, { elRef: this.handleRootEl, date: startDate, dateProfile: dateProfile, todayRange: todayRange }, (InnerContent, renderProps, elAttrs) => (createElement(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 },
7300 hasCustomDayCellContent(options) && (createElement(InnerContent, { elTag: "div", elClasses: ['fc-more-popover-misc'] })),
7301 props.children))));
7302 }
7303 queryHit(positionLeft, positionTop, elWidth, elHeight) {
7304 let { rootEl, props } = this;
7305 if (positionLeft >= 0 && positionLeft < elWidth &&
7306 positionTop >= 0 && positionTop < elHeight) {
7307 return {
7308 dateProfile: props.dateProfile,
7309 dateSpan: Object.assign({ allDay: !props.forceTimed, range: {
7310 start: props.startDate,
7311 end: props.endDate,
7312 } }, props.extraDateSpan),
7313 dayEl: rootEl,
7314 rect: {
7315 left: 0,
7316 top: 0,
7317 right: elWidth,
7318 bottom: elHeight,
7319 },
7320 layer: 1, // important when comparing with hits from other components
7321 };
7322 }
7323 return null;
7324 }
7325}
7326
7327class MoreLinkContainer extends BaseComponent {
7328 constructor() {
7329 super(...arguments);
7330 this.state = {
7331 isPopoverOpen: false,
7332 popoverId: getUniqueDomId(),
7333 };
7334 this.handleLinkEl = (linkEl) => {
7335 this.linkEl = linkEl;
7336 if (this.props.elRef) {
7337 setRef(this.props.elRef, linkEl);
7338 }
7339 };
7340 this.handleClick = (ev) => {
7341 let { props, context } = this;
7342 let { moreLinkClick } = context.options;
7343 let date = computeRange(props).start;
7344 function buildPublicSeg(seg) {
7345 let { def, instance, range } = seg.eventRange;
7346 return {
7347 event: new EventImpl(context, def, instance),
7348 start: context.dateEnv.toDate(range.start),
7349 end: context.dateEnv.toDate(range.end),
7350 isStart: seg.isStart,
7351 isEnd: seg.isEnd,
7352 };
7353 }
7354 if (typeof moreLinkClick === 'function') {
7355 moreLinkClick = moreLinkClick({
7356 date,
7357 allDay: Boolean(props.allDayDate),
7358 allSegs: props.allSegs.map(buildPublicSeg),
7359 hiddenSegs: props.hiddenSegs.map(buildPublicSeg),
7360 jsEvent: ev,
7361 view: context.viewApi,
7362 });
7363 }
7364 if (!moreLinkClick || moreLinkClick === 'popover') {
7365 this.setState({ isPopoverOpen: true });
7366 }
7367 else if (typeof moreLinkClick === 'string') { // a view name
7368 context.calendarApi.zoomTo(date, moreLinkClick);
7369 }
7370 };
7371 this.handlePopoverClose = () => {
7372 this.setState({ isPopoverOpen: false });
7373 };
7374 }
7375 render() {
7376 let { props, state } = this;
7377 return (createElement(ViewContextType.Consumer, null, (context) => {
7378 let { viewApi, options, calendarApi } = context;
7379 let { moreLinkText } = options;
7380 let { moreCnt } = props;
7381 let range = computeRange(props);
7382 let text = typeof moreLinkText === 'function' // TODO: eventually use formatWithOrdinals
7383 ? moreLinkText.call(calendarApi, moreCnt)
7384 : `+${moreCnt} ${moreLinkText}`;
7385 let hint = formatWithOrdinals(options.moreLinkHint, [moreCnt], text);
7386 let renderProps = {
7387 num: moreCnt,
7388 shortText: `+${moreCnt}`,
7389 text,
7390 view: viewApi,
7391 };
7392 return (createElement(Fragment, null,
7393 Boolean(props.moreCnt) && (createElement(ContentContainer, { elTag: props.elTag || 'a', elRef: this.handleLinkEl, elClasses: [
7394 ...(props.elClasses || []),
7395 'fc-more-link',
7396 ], 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)),
7397 state.isPopoverOpen && (createElement(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 ?
7398 props.alignmentElRef.current :
7399 this.linkEl, alignGridTop: props.alignGridTop, forceTimed: props.forceTimed, onClose: this.handlePopoverClose }, props.popoverContent()))));
7400 }));
7401 }
7402 componentDidMount() {
7403 this.updateParentEl();
7404 }
7405 componentDidUpdate() {
7406 this.updateParentEl();
7407 }
7408 updateParentEl() {
7409 if (this.linkEl) {
7410 this.parentEl = elementClosest(this.linkEl, '.fc-view-harness');
7411 }
7412 }
7413}
7414function renderMoreLinkInner(props) {
7415 return props.text;
7416}
7417function computeRange(props) {
7418 if (props.allDayDate) {
7419 return {
7420 start: props.allDayDate,
7421 end: addDays(props.allDayDate, 1),
7422 };
7423 }
7424 let { hiddenSegs } = props;
7425 return {
7426 start: computeEarliestSegStart(hiddenSegs),
7427 end: computeLatestSegEnd(hiddenSegs),
7428 };
7429}
7430function computeEarliestSegStart(segs) {
7431 return segs.reduce(pickEarliestStart).eventRange.range.start;
7432}
7433function pickEarliestStart(seg0, seg1) {
7434 return seg0.eventRange.range.start < seg1.eventRange.range.start ? seg0 : seg1;
7435}
7436function computeLatestSegEnd(segs) {
7437 return segs.reduce(pickLatestEnd).eventRange.range.end;
7438}
7439function pickLatestEnd(seg0, seg1) {
7440 return seg0.eventRange.range.end > seg1.eventRange.range.end ? seg0 : seg1;
7441}
7442
7443class Store {
7444 constructor() {
7445 this.handlers = [];
7446 }
7447 set(value) {
7448 this.currentValue = value;
7449 for (let handler of this.handlers) {
7450 handler(value);
7451 }
7452 }
7453 subscribe(handler) {
7454 this.handlers.push(handler);
7455 if (this.currentValue !== undefined) {
7456 handler(this.currentValue);
7457 }
7458 }
7459}
7460
7461/*
7462Subscribers will get a LIST of CustomRenderings
7463*/
7464class CustomRenderingStore extends Store {
7465 constructor() {
7466 super(...arguments);
7467 this.map = new Map();
7468 }
7469 // for consistent order
7470 handle(customRendering) {
7471 const { map } = this;
7472 let updated = false;
7473 if (customRendering.isActive) {
7474 map.set(customRendering.id, customRendering);
7475 updated = true;
7476 }
7477 else if (map.has(customRendering.id)) {
7478 map.delete(customRendering.id);
7479 updated = true;
7480 }
7481 if (updated) {
7482 this.set(map);
7483 }
7484 }
7485}
7486
7487export { elementClosest as $, memoizeObjArg as A, BaseComponent as B, ContentContainer as C, DelayedRunner as D, isPropsEqual as E, Emitter as F, getInitialDate as G, rangeContainsMarker as H, createEmptyEventStore as I, reduceCurrentDate as J, reduceEventStore as K, rezoneEventStoreDates as L, mergeRawOptions as M, BASE_OPTION_REFINERS as N, CALENDAR_LISTENER_REFINERS as O, CALENDAR_OPTION_REFINERS as P, COMPLEX_OPTION_COMPARATORS as Q, VIEW_OPTION_REFINERS as R, DateEnv as S, Theme as T, DateProfileGenerator as U, ViewContextType as V, createEventUi as W, parseBusinessHours as X, setRef as Y, Interaction as Z, getElSeg as _, mapHash as a, getSlotClassNames as a$, EventImpl as a0, listenBySelector as a1, listenToHoverBySelector as a2, PureComponent as a3, buildViewContext as a4, getUniqueDomId as a5, parseInteractionSettings as a6, interactionSettingsStore as a7, getNow as a8, CalendarImpl as a9, diffDates as aA, removeExact as aB, memoizeArraylike as aC, memoizeHashlike as aD, intersectRects as aE, pointInsideRect as aF, constrainPoint as aG, getRectCenter as aH, diffPoints as aI, translateRect as aJ, compareObjs as aK, collectFromHash as aL, findElements as aM, findDirectChildren as aN, removeElement as aO, applyStyle as aP, elementMatches as aQ, getEventTargetViaRoot as aR, parseClassNames as aS, getCanVGrowWithinCell as aT, mergeEventStores as aU, getRelevantEvents as aV, eventTupleToStore as aW, combineEventUis as aX, Splitter as aY, getDayClassNames as aZ, getDateMeta as a_, flushSync as aa, CalendarRoot as ab, RenderId as ac, ensureElHasStyles as ad, applyStyleProp as ae, sliceEventStore as af, JsonRequestError as ag, createContext as ah, refineProps as ai, createEventInstance as aj, parseEventDef as ak, refineEventDef as al, padStart as am, isInt as an, parseFieldSpecs as ao, compareByFieldSpecs as ap, flexibleCompare as aq, preventSelection as ar, allowSelection as as, preventContextMenu as at, allowContextMenu as au, compareNumbers as av, enableCursor as aw, disableCursor as ax, computeVisibleDayRange as ay, isMultiDayRange as az, buildViewClassNames as b, SimpleScrollGrid as b$, buildNavLinkAttrs as b0, preventDefault as b1, whenTransitionDone as b2, computeInnerRect as b3, computeEdges as b4, getClippingParents as b5, computeRect as b6, rangesEqual as b7, rangesIntersect as b8, rangeContainsRange as b9, SegHierarchy as bA, buildEntryKey as bB, getEntrySpanEnd as bC, binarySearch as bD, groupIntersectingEntries as bE, intersectSpans as bF, interactionSettingsToStore as bG, ElementDragging as bH, config as bI, parseDragMeta as bJ, DayHeader as bK, computeFallbackHeaderFormat as bL, TableDateCell as bM, TableDowCell as bN, DaySeriesModel as bO, hasBgRendering as bP, buildSegTimeText as bQ, sortEventSegs as bR, getSegMeta as bS, buildEventRangeKey as bT, getSegAnchorAttrs as bU, DayTableModel as bV, Slicer as bW, applyMutationToEventStore as bX, isPropsValid as bY, isInteractionValid as bZ, isDateSelectionValid as b_, PositionCache as ba, ScrollController as bb, ElementScrollController as bc, WindowScrollController as bd, DateComponent as be, isDateSpansEqual as bf, addMs as bg, addWeeks as bh, diffWeeks as bi, diffWholeWeeks as bj, diffDayAndTime as bk, diffDays as bl, isValidDate as bm, asCleanDays as bn, multiplyDuration as bo, addDurations as bp, asRoughMinutes as bq, asRoughSeconds as br, asRoughMs as bs, wholeDivideDurations as bt, formatIsoTimeString as bu, formatDayString as bv, buildIsoString as bw, formatIsoMonthStr as bx, NamedTimeZoneImpl as by, parse as bz, greatestDurationDenominator as c, hasShrinkWidth as c0, renderMicroColGroup as c1, getScrollGridClassNames as c2, getSectionClassNames as c3, getSectionHasLiquidHeight as c4, getAllowYScrolling as c5, renderChunkContent as c6, computeShrinkWidth as c7, sanitizeShrinkWidth as c8, isColPropsEqual as c9, renderScrollShim as ca, getStickyFooterScrollbar as cb, getStickyHeaderDates as cc, Scroller as cd, getScrollbarWidths as ce, RefMap as cf, getIsRtlScrollbarOnLeft as cg, NowTimer as ch, ScrollResponder as ci, StandardEvent as cj, NowIndicatorContainer as ck, DayCellContainer as cl, hasCustomDayCellContent as cm, EventContainer as cn, renderFill as co, BgEvent as cp, WeekNumberContainer as cq, MoreLinkContainer as cr, computeEarliestSegStart as cs, ViewContainer as ct, triggerDateSelect as cu, getDefaultEventEnd as cv, injectStyles as cw, buildElAttrs as cx, CustomRenderingStore as cy, createDuration as d, BASE_OPTION_DEFAULTS as e, arrayToHash as f, guid as g, filterHash as h, isArraysEqual as i, buildEventSourceRefiners as j, formatWithOrdinals as k, buildRangeApiWithTimeZone as l, mergeProps as m, identity as n, intersectRanges as o, parseEventSource as p, startOfDay as q, requestJson as r, subtractDurations as s, addDays as t, unpromisify as u, hashValuesToArray as v, buildEventApis as w, createFormatter as x, diffWholeDays as y, memoize as z };