summaryrefslogtreecommitdiff
path: root/public/js/fullcalendar/dist/index.global.js
diff options
context:
space:
mode:
Diffstat (limited to 'public/js/fullcalendar/dist/index.global.js')
-rw-r--r--public/js/fullcalendar/dist/index.global.js14775
1 files changed, 14775 insertions, 0 deletions
diff --git a/public/js/fullcalendar/dist/index.global.js b/public/js/fullcalendar/dist/index.global.js
new file mode 100644
index 0000000..9a97af4
--- /dev/null
+++ b/public/js/fullcalendar/dist/index.global.js
@@ -0,0 +1,14775 @@
1/*!
2FullCalendar Standard Bundle v6.1.17
3Docs & License: https://fullcalendar.io/docs/initialize-globals
4(c) 2024 Adam Shaw
5*/
6var FullCalendar = (function (exports) {
7 'use strict';
8
9 var n,l$1,u$1,i$1,t,r$1,o,f$1,e$1,c$1={},s=[],a$1=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function h(n,l){for(var u in l)n[u]=l[u];return n}function v$1(n){var l=n.parentNode;l&&l.removeChild(n);}function y(l,u,i){var t,r,o,f={};for(o in u)"key"==o?t=u[o]:"ref"==o?r=u[o]:f[o]=u[o];if(arguments.length>2&&(f.children=arguments.length>3?n.call(arguments,2):i),"function"==typeof l&&null!=l.defaultProps)for(o in l.defaultProps)void 0===f[o]&&(f[o]=l.defaultProps[o]);return p(l,f,t,r,null)}function p(n,i,t,r,o){var f={type:n,props:i,key:t,ref:r,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==o?++u$1:o};return null==o&&null!=l$1.vnode&&l$1.vnode(f),f}function d(){return {current:null}}function _(n){return n.children}function k$1(n,l,u,i,t){var r;for(r in u)"children"===r||"key"===r||r in l||g$2(n,r,null,u[r],i);for(r in l)t&&"function"!=typeof l[r]||"children"===r||"key"===r||"value"===r||"checked"===r||u[r]===l[r]||g$2(n,r,l[r],u[r],i);}function b$1(n,l,u){"-"===l[0]?n.setProperty(l,null==u?"":u):n[l]=null==u?"":"number"!=typeof u||a$1.test(l)?u:u+"px";}function g$2(n,l,u,i,t){var r;n:if("style"===l)if("string"==typeof u)n.style.cssText=u;else {if("string"==typeof i&&(n.style.cssText=i=""),i)for(l in i)u&&l in u||b$1(n.style,l,"");if(u)for(l in u)i&&u[l]===i[l]||b$1(n.style,l,u[l]);}else if("o"===l[0]&&"n"===l[1])r=l!==(l=l.replace(/Capture$/,"")),l=l.toLowerCase()in n?l.toLowerCase().slice(2):l.slice(2),n.l||(n.l={}),n.l[l+r]=u,u?i||n.addEventListener(l,r?w$2:m$1,r):n.removeEventListener(l,r?w$2:m$1,r);else if("dangerouslySetInnerHTML"!==l){if(t)l=l.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if("width"!==l&&"height"!==l&&"href"!==l&&"list"!==l&&"form"!==l&&"tabIndex"!==l&&"download"!==l&&l in n)try{n[l]=null==u?"":u;break n}catch(n){}"function"==typeof u||(null==u||!1===u&&-1==l.indexOf("-")?n.removeAttribute(l):n.setAttribute(l,u));}}function m$1(n){t=!0;try{return this.l[n.type+!1](l$1.event?l$1.event(n):n)}finally{t=!1;}}function w$2(n){t=!0;try{return this.l[n.type+!0](l$1.event?l$1.event(n):n)}finally{t=!1;}}function x$1(n,l){this.props=n,this.context=l;}function A(n,l){if(null==l)return n.__?A(n.__,n.__.__k.indexOf(n)+1):null;for(var u;l<n.__k.length;l++)if(null!=(u=n.__k[l])&&null!=u.__e)return u.__e;return "function"==typeof n.type?A(n):null}function P$1(n){var l,u;if(null!=(n=n.__)&&null!=n.__c){for(n.__e=n.__c.base=null,l=0;l<n.__k.length;l++)if(null!=(u=n.__k[l])&&null!=u.__e){n.__e=n.__c.base=u.__e;break}return P$1(n)}}function C$1(n){t?setTimeout(n):f$1(n);}function T$1(n){(!n.__d&&(n.__d=!0)&&r$1.push(n)&&!$$1.__r++||o!==l$1.debounceRendering)&&((o=l$1.debounceRendering)||C$1)($$1);}function $$1(){var n,l,u,i,t,o,f,e;for(r$1.sort(function(n,l){return n.__v.__b-l.__v.__b});n=r$1.shift();)n.__d&&(l=r$1.length,i=void 0,t=void 0,f=(o=(u=n).__v).__e,(e=u.__P)&&(i=[],(t=h({},o)).__v=o.__v+1,M(e,o,t,u.__n,void 0!==e.ownerSVGElement,null!=o.__h?[f]:null,i,null==f?A(o):f,o.__h),N(i,o),o.__e!=f&&P$1(o)),r$1.length>l&&r$1.sort(function(n,l){return n.__v.__b-l.__v.__b}));$$1.__r=0;}function H$1(n,l,u,i,t,r,o,f,e,a){var h,v,y,d,k,b,g,m=i&&i.__k||s,w=m.length;for(u.__k=[],h=0;h<l.length;h++)if(null!=(d=u.__k[h]=null==(d=l[h])||"boolean"==typeof d?null:"string"==typeof d||"number"==typeof d||"bigint"==typeof d?p(null,d,null,null,d):Array.isArray(d)?p(_,{children:d},null,null,null):d.__b>0?p(d.type,d.props,d.key,d.ref?d.ref:null,d.__v):d)){if(d.__=u,d.__b=u.__b+1,null===(y=m[h])||y&&d.key==y.key&&d.type===y.type)m[h]=void 0;else for(v=0;v<w;v++){if((y=m[v])&&d.key==y.key&&d.type===y.type){m[v]=void 0;break}y=null;}M(n,d,y=y||c$1,t,r,o,f,e,a),k=d.__e,(v=d.ref)&&y.ref!=v&&(g||(g=[]),y.ref&&g.push(y.ref,null,d),g.push(v,d.__c||k,d)),null!=k?(null==b&&(b=k),"function"==typeof d.type&&d.__k===y.__k?d.__d=e=I$1(d,e,n):e=z$1(n,d,y,m,k,e),"function"==typeof u.type&&(u.__d=e)):e&&y.__e==e&&e.parentNode!=n&&(e=A(y));}for(u.__e=b,h=w;h--;)null!=m[h]&&("function"==typeof u.type&&null!=m[h].__e&&m[h].__e==u.__d&&(u.__d=L$1(i).nextSibling),q(m[h],m[h]));if(g)for(h=0;h<g.length;h++)S(g[h],g[++h],g[++h]);}function I$1(n,l,u){for(var i,t=n.__k,r=0;t&&r<t.length;r++)(i=t[r])&&(i.__=n,l="function"==typeof i.type?I$1(i,l,u):z$1(u,i,i,t,i.__e,l));return l}function j$2(n,l){return l=l||[],null==n||"boolean"==typeof n||(Array.isArray(n)?n.some(function(n){j$2(n,l);}):l.push(n)),l}function z$1(n,l,u,i,t,r){var o,f,e;if(void 0!==l.__d)o=l.__d,l.__d=void 0;else if(null==u||t!=r||null==t.parentNode)n:if(null==r||r.parentNode!==n)n.appendChild(t),o=null;else {for(f=r,e=0;(f=f.nextSibling)&&e<i.length;e+=1)if(f==t)break n;n.insertBefore(t,r),o=r;}return void 0!==o?o:t.nextSibling}function L$1(n){var l,u,i;if(null==n.type||"string"==typeof n.type)return n.__e;if(n.__k)for(l=n.__k.length-1;l>=0;l--)if((u=n.__k[l])&&(i=L$1(u)))return i;return null}function M(n,u,i,t,r,o,f,e,c){var s,a,v,y,p,d,k,b,g,m,w,A,P,C,T,$=u.type;if(void 0!==u.constructor)return null;null!=i.__h&&(c=i.__h,e=u.__e=i.__e,u.__h=null,o=[e]),(s=l$1.__b)&&s(u);try{n:if("function"==typeof $){if(b=u.props,g=(s=$.contextType)&&t[s.__c],m=s?g?g.props.value:s.__:t,i.__c?k=(a=u.__c=i.__c).__=a.__E:("prototype"in $&&$.prototype.render?u.__c=a=new $(b,m):(u.__c=a=new x$1(b,m),a.constructor=$,a.render=B$1),g&&g.sub(a),a.props=b,a.state||(a.state={}),a.context=m,a.__n=t,v=a.__d=!0,a.__h=[],a._sb=[]),null==a.__s&&(a.__s=a.state),null!=$.getDerivedStateFromProps&&(a.__s==a.state&&(a.__s=h({},a.__s)),h(a.__s,$.getDerivedStateFromProps(b,a.__s))),y=a.props,p=a.state,a.__v=u,v)null==$.getDerivedStateFromProps&&null!=a.componentWillMount&&a.componentWillMount(),null!=a.componentDidMount&&a.__h.push(a.componentDidMount);else {if(null==$.getDerivedStateFromProps&&b!==y&&null!=a.componentWillReceiveProps&&a.componentWillReceiveProps(b,m),!a.__e&&null!=a.shouldComponentUpdate&&!1===a.shouldComponentUpdate(b,a.__s,m)||u.__v===i.__v){for(u.__v!==i.__v&&(a.props=b,a.state=a.__s,a.__d=!1),u.__e=i.__e,u.__k=i.__k,u.__k.forEach(function(n){n&&(n.__=u);}),w=0;w<a._sb.length;w++)a.__h.push(a._sb[w]);a._sb=[],a.__h.length&&f.push(a);break n}null!=a.componentWillUpdate&&a.componentWillUpdate(b,a.__s,m),null!=a.componentDidUpdate&&a.__h.push(function(){a.componentDidUpdate(y,p,d);});}if(a.context=m,a.props=b,a.__P=n,A=l$1.__r,P=0,"prototype"in $&&$.prototype.render){for(a.state=a.__s,a.__d=!1,A&&A(u),s=a.render(a.props,a.state,a.context),C=0;C<a._sb.length;C++)a.__h.push(a._sb[C]);a._sb=[];}else do{a.__d=!1,A&&A(u),s=a.render(a.props,a.state,a.context),a.state=a.__s;}while(a.__d&&++P<25);a.state=a.__s,null!=a.getChildContext&&(t=h(h({},t),a.getChildContext())),v||null==a.getSnapshotBeforeUpdate||(d=a.getSnapshotBeforeUpdate(y,p)),T=null!=s&&s.type===_&&null==s.key?s.props.children:s,H$1(n,Array.isArray(T)?T:[T],u,i,t,r,o,f,e,c),a.base=u.__e,u.__h=null,a.__h.length&&f.push(a),k&&(a.__E=a.__=null),a.__e=!1;}else null==o&&u.__v===i.__v?(u.__k=i.__k,u.__e=i.__e):u.__e=O(i.__e,u,i,t,r,o,f,c);(s=l$1.diffed)&&s(u);}catch(n){u.__v=null,(c||null!=o)&&(u.__e=e,u.__h=!!c,o[o.indexOf(e)]=null),l$1.__e(n,u,i);}}function N(n,u){l$1.__c&&l$1.__c(u,n),n.some(function(u){try{n=u.__h,u.__h=[],n.some(function(n){n.call(u);});}catch(n){l$1.__e(n,u.__v);}});}function O(l,u,i,t,r,o,f,e){var s,a,h,y=i.props,p=u.props,d=u.type,_=0;if("svg"===d&&(r=!0),null!=o)for(;_<o.length;_++)if((s=o[_])&&"setAttribute"in s==!!d&&(d?s.localName===d:3===s.nodeType)){l=s,o[_]=null;break}if(null==l){if(null===d)return document.createTextNode(p);l=r?document.createElementNS("http://www.w3.org/2000/svg",d):document.createElement(d,p.is&&p),o=null,e=!1;}if(null===d)y===p||e&&l.data===p||(l.data=p);else {if(o=o&&n.call(l.childNodes),a=(y=i.props||c$1).dangerouslySetInnerHTML,h=p.dangerouslySetInnerHTML,!e){if(null!=o)for(y={},_=0;_<l.attributes.length;_++)y[l.attributes[_].name]=l.attributes[_].value;(h||a)&&(h&&(a&&h.__html==a.__html||h.__html===l.innerHTML)||(l.innerHTML=h&&h.__html||""));}if(k$1(l,p,y,r,e),h)u.__k=[];else if(_=u.props.children,H$1(l,Array.isArray(_)?_:[_],u,i,t,r&&"foreignObject"!==d,o,f,o?o[0]:i.__k&&A(i,0),e),null!=o)for(_=o.length;_--;)null!=o[_]&&v$1(o[_]);e||("value"in p&&void 0!==(_=p.value)&&(_!==l.value||"progress"===d&&!_||"option"===d&&_!==y.value)&&g$2(l,"value",_,y.value,!1),"checked"in p&&void 0!==(_=p.checked)&&_!==l.checked&&g$2(l,"checked",_,y.checked,!1));}return l}function S(n,u,i){try{"function"==typeof n?n(u):n.current=u;}catch(n){l$1.__e(n,i);}}function q(n,u,i){var t,r;if(l$1.unmount&&l$1.unmount(n),(t=n.ref)&&(t.current&&t.current!==n.__e||S(t,null,u)),null!=(t=n.__c)){if(t.componentWillUnmount)try{t.componentWillUnmount();}catch(n){l$1.__e(n,u);}t.base=t.__P=null,n.__c=void 0;}if(t=n.__k)for(r=0;r<t.length;r++)t[r]&&q(t[r],u,i||"function"!=typeof n.type);i||null==n.__e||v$1(n.__e),n.__=n.__e=n.__d=void 0;}function B$1(n,l,u){return this.constructor(n,u)}function D$1(u,i,t){var r,o,f;l$1.__&&l$1.__(u,i),o=(r="function"==typeof t)?null:t&&t.__k||i.__k,f=[],M(i,u=(!r&&t||i).__k=y(_,null,[u]),o||c$1,c$1,void 0!==i.ownerSVGElement,!r&&t?[t]:o?null:i.firstChild?n.call(i.childNodes):null,f,!r&&t?t:o?o.__e:i.firstChild,r),N(f,u);}function E(n,l){D$1(n,l,E);}function F$1(l,u,i){var t,r,o,f=h({},l.props);for(o in u)"key"==o?t=u[o]:"ref"==o?r=u[o]:f[o]=u[o];return arguments.length>2&&(f.children=arguments.length>3?n.call(arguments,2):i),p(l.type,f,t||l.key,r||l.ref,null)}function G$1(n,l){var u={__c:l="__cC"+e$1++,__:n,Consumer:function(n,l){return n.children(l)},Provider:function(n){var u,i;return this.getChildContext||(u=[],(i={})[l]=this,this.getChildContext=function(){return i},this.shouldComponentUpdate=function(n){this.props.value!==n.value&&u.some(function(n){n.__e=!0,T$1(n);});},this.sub=function(n){u.push(n);var l=n.componentWillUnmount;n.componentWillUnmount=function(){u.splice(u.indexOf(n),1),l&&l.call(n);};}),n.children}};return u.Provider.__=u.Consumer.contextType=u}n=s.slice,l$1={__e:function(n,l,u,i){for(var t,r,o;l=l.__;)if((t=l.__c)&&!t.__)try{if((r=t.constructor)&&null!=r.getDerivedStateFromError&&(t.setState(r.getDerivedStateFromError(n)),o=t.__d),null!=t.componentDidCatch&&(t.componentDidCatch(n,i||{}),o=t.__d),o)return t.__E=t}catch(l){n=l;}throw n}},u$1=0,i$1=function(n){return null!=n&&void 0===n.constructor},t=!1,x$1.prototype.setState=function(n,l){var u;u=null!=this.__s&&this.__s!==this.state?this.__s:this.__s=h({},this.state),"function"==typeof n&&(n=n(h({},u),this.props)),n&&h(u,n),null!=n&&this.__v&&(l&&this._sb.push(l),T$1(this));},x$1.prototype.forceUpdate=function(n){this.__v&&(this.__e=!0,n&&this.__h.push(n),T$1(this));},x$1.prototype.render=_,r$1=[],f$1="function"==typeof Promise?Promise.prototype.then.bind(Promise.resolve()):setTimeout,$$1.__r=0,e$1=0;
10
11 var r,u,i,f=[],c=[],e=l$1.__b,a=l$1.__r,v=l$1.diffed,l=l$1.__c,m=l$1.unmount;function b(){for(var t;t=f.shift();)if(t.__P&&t.__H)try{t.__H.__h.forEach(k),t.__H.__h.forEach(w$1),t.__H.__h=[];}catch(r){t.__H.__h=[],l$1.__e(r,t.__v);}}l$1.__b=function(n){r=null,e&&e(n);},l$1.__r=function(n){a&&a(n);var i=(r=n.__c).__H;i&&(u===r?(i.__h=[],r.__h=[],i.__.forEach(function(n){n.__N&&(n.__=n.__N),n.__V=c,n.__N=n.i=void 0;})):(i.__h.forEach(k),i.__h.forEach(w$1),i.__h=[])),u=r;},l$1.diffed=function(t){v&&v(t);var o=t.__c;o&&o.__H&&(o.__H.__h.length&&(1!==f.push(o)&&i===l$1.requestAnimationFrame||((i=l$1.requestAnimationFrame)||j$1)(b)),o.__H.__.forEach(function(n){n.i&&(n.__H=n.i),n.__V!==c&&(n.__=n.__V),n.i=void 0,n.__V=c;})),u=r=null;},l$1.__c=function(t,r){r.some(function(t){try{t.__h.forEach(k),t.__h=t.__h.filter(function(n){return !n.__||w$1(n)});}catch(u){r.some(function(n){n.__h&&(n.__h=[]);}),r=[],l$1.__e(u,t.__v);}}),l&&l(t,r);},l$1.unmount=function(t){m&&m(t);var r,u=t.__c;u&&u.__H&&(u.__H.__.forEach(function(n){try{k(n);}catch(n){r=n;}}),u.__H=void 0,r&&l$1.__e(r,u.__v));};var g$1="function"==typeof requestAnimationFrame;function j$1(n){var t,r=function(){clearTimeout(u),g$1&&cancelAnimationFrame(t),setTimeout(n);},u=setTimeout(r,100);g$1&&(t=requestAnimationFrame(r));}function k(n){var t=r,u=n.__c;"function"==typeof u&&(n.__c=void 0,u()),r=t;}function w$1(n){var t=r;n.__c=n.__(),r=t;}
12
13 function g(n,t){for(var e in t)n[e]=t[e];return n}function C(n,t){for(var e in n)if("__source"!==e&&!(e in t))return !0;for(var r in t)if("__source"!==r&&n[r]!==t[r])return !0;return !1}function w(n){this.props=n;}(w.prototype=new x$1).isPureReactComponent=!0,w.prototype.shouldComponentUpdate=function(n,t){return C(this.props,n)||C(this.state,t)};var x=l$1.__b;l$1.__b=function(n){n.type&&n.type.__f&&n.ref&&(n.props.ref=n.ref,n.ref=null),x&&x(n);};var T=l$1.__e;l$1.__e=function(n,t,e,r){if(n.then)for(var u,o=t;o=o.__;)if((u=o.__c)&&u.__c)return null==t.__e&&(t.__e=e.__e,t.__k=e.__k),u.__c(n,t);T(n,t,e,r);};var I=l$1.unmount;function L(n,t,e){return n&&(n.__c&&n.__c.__H&&(n.__c.__H.__.forEach(function(n){"function"==typeof n.__c&&n.__c();}),n.__c.__H=null),null!=(n=g({},n)).__c&&(n.__c.__P===e&&(n.__c.__P=t),n.__c=null),n.__k=n.__k&&n.__k.map(function(n){return L(n,t,e)})),n}function U(n,t,e){return n&&(n.__v=null,n.__k=n.__k&&n.__k.map(function(n){return U(n,t,e)}),n.__c&&n.__c.__P===t&&(n.__e&&e.insertBefore(n.__e,n.__d),n.__c.__e=!0,n.__c.__P=e)),n}function D(){this.__u=0,this.t=null,this.__b=null;}function F(n){var t=n.__.__c;return t&&t.__a&&t.__a(n)}function V(){this.u=null,this.o=null;}l$1.unmount=function(n){var t=n.__c;t&&t.__R&&t.__R(),t&&!0===n.__h&&(n.type=null),I&&I(n);},(D.prototype=new x$1).__c=function(n,t){var e=t.__c,r=this;null==r.t&&(r.t=[]),r.t.push(e);var u=F(r.__v),o=!1,i=function(){o||(o=!0,e.__R=null,u?u(l):l());};e.__R=i;var l=function(){if(!--r.__u){if(r.state.__a){var n=r.state.__a;r.__v.__k[0]=U(n,n.__c.__P,n.__c.__O);}var t;for(r.setState({__a:r.__b=null});t=r.t.pop();)t.forceUpdate();}},c=!0===t.__h;r.__u++||c||r.setState({__a:r.__b=r.__v.__k[0]}),n.then(i,i);},D.prototype.componentWillUnmount=function(){this.t=[];},D.prototype.render=function(n,e){if(this.__b){if(this.__v.__k){var r=document.createElement("div"),o=this.__v.__k[0].__c;this.__v.__k[0]=L(this.__b,r,o.__O=o.__P);}this.__b=null;}var i=e.__a&&y(_,null,n.fallback);return i&&(i.__h=null),[y(_,null,e.__a?null:n.children),i]};var W=function(n,t,e){if(++e[1]===e[0]&&n.o.delete(t),n.props.revealOrder&&("t"!==n.props.revealOrder[0]||!n.o.size))for(e=n.u;e;){for(;e.length>3;)e.pop()();if(e[1]<e[0])break;n.u=e=e[2];}};function P(n){return this.getChildContext=function(){return n.context},n.children}function $(n){var e=this,r=n.i;e.componentWillUnmount=function(){D$1(null,e.l),e.l=null,e.i=null;},e.i&&e.i!==r&&e.componentWillUnmount(),n.__v?(e.l||(e.i=r,e.l={nodeType:1,parentNode:r,childNodes:[],appendChild:function(n){this.childNodes.push(n),e.i.appendChild(n);},insertBefore:function(n,t){this.childNodes.push(n),e.i.appendChild(n);},removeChild:function(n){this.childNodes.splice(this.childNodes.indexOf(n)>>>1,1),e.i.removeChild(n);}}),D$1(y(P,{context:e.context},n.__v),e.l)):e.l&&e.componentWillUnmount();}function j(n,e){var r=y($,{__v:n,i:e});return r.containerInfo=e,r}(V.prototype=new x$1).__a=function(n){var t=this,e=F(t.__v),r=t.o.get(n);return r[0]++,function(u){var o=function(){t.props.revealOrder?(r.push(u),W(t,n,r)):u();};e?e(o):o();}},V.prototype.render=function(n){this.u=null,this.o=new Map;var t=j$2(n.children);n.revealOrder&&"b"===n.revealOrder[0]&&t.reverse();for(var e=t.length;e--;)this.o.set(t[e],this.u=[1,0,this.u]);return n.children},V.prototype.componentDidUpdate=V.prototype.componentDidMount=function(){var n=this;this.o.forEach(function(t,e){W(n,e,t);});};var z="undefined"!=typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103,B=/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/,H="undefined"!=typeof document,Z=function(n){return ("undefined"!=typeof Symbol&&"symbol"==typeof Symbol()?/fil|che|rad/i:/fil|che|ra/i).test(n)};x$1.prototype.isReactComponent={},["componentWillMount","componentWillReceiveProps","componentWillUpdate"].forEach(function(t){Object.defineProperty(x$1.prototype,t,{configurable:!0,get:function(){return this["UNSAFE_"+t]},set:function(n){Object.defineProperty(this,t,{configurable:!0,writable:!0,value:n});}});});var G=l$1.event;function J(){}function K(){return this.cancelBubble}function Q(){return this.defaultPrevented}l$1.event=function(n){return G&&(n=G(n)),n.persist=J,n.isPropagationStopped=K,n.isDefaultPrevented=Q,n.nativeEvent=n};var nn={configurable:!0,get:function(){return this.class}},tn=l$1.vnode;l$1.vnode=function(n){var t=n.type,e=n.props,u=e;if("string"==typeof t){var o=-1===t.indexOf("-");for(var i in u={},e){var l=e[i];H&&"children"===i&&"noscript"===t||"value"===i&&"defaultValue"in e&&null==l||("defaultValue"===i&&"value"in e&&null==e.value?i="value":"download"===i&&!0===l?l="":/ondoubleclick/i.test(i)?i="ondblclick":/^onchange(textarea|input)/i.test(i+t)&&!Z(e.type)?i="oninput":/^onfocus$/i.test(i)?i="onfocusin":/^onblur$/i.test(i)?i="onfocusout":/^on(Ani|Tra|Tou|BeforeInp|Compo)/.test(i)?i=i.toLowerCase():o&&B.test(i)?i=i.replace(/[A-Z0-9]/g,"-$&").toLowerCase():null===l&&(l=void 0),/^oninput$/i.test(i)&&(i=i.toLowerCase(),u[i]&&(i="oninputCapture")),u[i]=l);}"select"==t&&u.multiple&&Array.isArray(u.value)&&(u.value=j$2(e.children).forEach(function(n){n.props.selected=-1!=u.value.indexOf(n.props.value);})),"select"==t&&null!=u.defaultValue&&(u.value=j$2(e.children).forEach(function(n){n.props.selected=u.multiple?-1!=u.defaultValue.indexOf(n.props.value):u.defaultValue==n.props.value;})),n.props=u,e.class!=e.className&&(nn.enumerable="className"in e,null!=e.className&&(u.class=e.className),Object.defineProperty(u,"className",nn));}n.$$typeof=z,tn&&tn(n);};var en=l$1.__r;l$1.__r=function(n){en&&en(n),n.__c;};
14
15 const styleTexts = [];
16 const styleEls = new Map();
17 function injectStyles(styleText) {
18 styleTexts.push(styleText);
19 styleEls.forEach((styleEl) => {
20 appendStylesTo(styleEl, styleText);
21 });
22 }
23 function ensureElHasStyles(el) {
24 if (el.isConnected && // sometimes true if SSR system simulates DOM
25 el.getRootNode // sometimes undefined if SSR system simulates DOM
26 ) {
27 registerStylesRoot(el.getRootNode());
28 }
29 }
30 function registerStylesRoot(rootNode) {
31 let styleEl = styleEls.get(rootNode);
32 if (!styleEl || !styleEl.isConnected) {
33 styleEl = rootNode.querySelector('style[data-fullcalendar]');
34 if (!styleEl) {
35 styleEl = document.createElement('style');
36 styleEl.setAttribute('data-fullcalendar', '');
37 const nonce = getNonceValue();
38 if (nonce) {
39 styleEl.nonce = nonce;
40 }
41 const parentEl = rootNode === document ? document.head : rootNode;
42 const insertBefore = rootNode === document
43 ? parentEl.querySelector('script,link[rel=stylesheet],link[as=style],style')
44 : parentEl.firstChild;
45 parentEl.insertBefore(styleEl, insertBefore);
46 }
47 styleEls.set(rootNode, styleEl);
48 hydrateStylesRoot(styleEl);
49 }
50 }
51 function hydrateStylesRoot(styleEl) {
52 for (const styleText of styleTexts) {
53 appendStylesTo(styleEl, styleText);
54 }
55 }
56 function appendStylesTo(styleEl, styleText) {
57 const { sheet } = styleEl;
58 const ruleCnt = sheet.cssRules.length;
59 styleText.split('}').forEach((styleStr, i) => {
60 styleStr = styleStr.trim();
61 if (styleStr) {
62 sheet.insertRule(styleStr + '}', ruleCnt + i);
63 }
64 });
65 }
66 // nonce
67 // -------------------------------------------------------------------------------------------------
68 let queriedNonceValue;
69 function getNonceValue() {
70 if (queriedNonceValue === undefined) {
71 queriedNonceValue = queryNonceValue();
72 }
73 return queriedNonceValue;
74 }
75 /*
76 TODO: discourage meta tag and instead put nonce attribute on placeholder <style> tag
77 */
78 function queryNonceValue() {
79 const metaWithNonce = document.querySelector('meta[name="csp-nonce"]');
80 if (metaWithNonce && metaWithNonce.hasAttribute('content')) {
81 return metaWithNonce.getAttribute('content');
82 }
83 const elWithNonce = document.querySelector('script[nonce]');
84 if (elWithNonce) {
85 return elWithNonce.nonce || '';
86 }
87 return '';
88 }
89 // main
90 // -------------------------------------------------------------------------------------------------
91 if (typeof document !== 'undefined') {
92 registerStylesRoot(document);
93 }
94
95 var css_248z$4 = ":root{--fc-small-font-size:.85em;--fc-page-bg-color:#fff;--fc-neutral-bg-color:hsla(0,0%,82%,.3);--fc-neutral-text-color:grey;--fc-border-color:#ddd;--fc-button-text-color:#fff;--fc-button-bg-color:#2c3e50;--fc-button-border-color:#2c3e50;--fc-button-hover-bg-color:#1e2b37;--fc-button-hover-border-color:#1a252f;--fc-button-active-bg-color:#1a252f;--fc-button-active-border-color:#151e27;--fc-event-bg-color:#3788d8;--fc-event-border-color:#3788d8;--fc-event-text-color:#fff;--fc-event-selected-overlay-color:rgba(0,0,0,.25);--fc-more-link-bg-color:#d0d0d0;--fc-more-link-text-color:inherit;--fc-event-resizer-thickness:8px;--fc-event-resizer-dot-total-width:8px;--fc-event-resizer-dot-border-width:1px;--fc-non-business-color:hsla(0,0%,84%,.3);--fc-bg-event-color:#8fdf82;--fc-bg-event-opacity:0.3;--fc-highlight-color:rgba(188,232,241,.3);--fc-today-bg-color:rgba(255,220,40,.15);--fc-now-indicator-color:red}.fc-not-allowed,.fc-not-allowed .fc-event{cursor:not-allowed}.fc{display:flex;flex-direction:column;font-size:1em}.fc,.fc *,.fc :after,.fc :before{box-sizing:border-box}.fc table{border-collapse:collapse;border-spacing:0;font-size:1em}.fc th{text-align:center}.fc td,.fc th{padding:0;vertical-align:top}.fc a[data-navlink]{cursor:pointer}.fc a[data-navlink]:hover{text-decoration:underline}.fc-direction-ltr{direction:ltr;text-align:left}.fc-direction-rtl{direction:rtl;text-align:right}.fc-theme-standard td,.fc-theme-standard th{border:1px solid var(--fc-border-color)}.fc-liquid-hack td,.fc-liquid-hack th{position:relative}@font-face{font-family:fcicons;font-style:normal;font-weight:400;src:url(\"data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg8SBfAAAAC8AAAAYGNtYXAXVtKNAAABHAAAAFRnYXNwAAAAEAAAAXAAAAAIZ2x5ZgYydxIAAAF4AAAFNGhlYWQUJ7cIAAAGrAAAADZoaGVhB20DzAAABuQAAAAkaG10eCIABhQAAAcIAAAALGxvY2ED4AU6AAAHNAAAABhtYXhwAA8AjAAAB0wAAAAgbmFtZXsr690AAAdsAAABhnBvc3QAAwAAAAAI9AAAACAAAwPAAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADpBgPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQAOAAAAAoACAACAAIAAQAg6Qb//f//AAAAAAAg6QD//f//AAH/4xcEAAMAAQAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAWIAjQKeAskAEwAAJSc3NjQnJiIHAQYUFwEWMjc2NCcCnuLiDQ0MJAz/AA0NAQAMJAwNDcni4gwjDQwM/wANIwz/AA0NDCMNAAAAAQFiAI0CngLJABMAACUBNjQnASYiBwYUHwEHBhQXFjI3AZ4BAA0N/wAMJAwNDeLiDQ0MJAyNAQAMIw0BAAwMDSMM4uINIwwNDQAAAAIA4gC3Ax4CngATACcAACUnNzY0JyYiDwEGFB8BFjI3NjQnISc3NjQnJiIPAQYUHwEWMjc2NCcB87e3DQ0MIw3VDQ3VDSMMDQ0BK7e3DQ0MJAzVDQ3VDCQMDQ3zuLcMJAwNDdUNIwzWDAwNIwy4twwkDA0N1Q0jDNYMDA0jDAAAAgDiALcDHgKeABMAJwAAJTc2NC8BJiIHBhQfAQcGFBcWMjchNzY0LwEmIgcGFB8BBwYUFxYyNwJJ1Q0N1Q0jDA0Nt7cNDQwjDf7V1Q0N1QwkDA0Nt7cNDQwkDLfWDCMN1Q0NDCQMt7gMIw0MDNYMIw3VDQ0MJAy3uAwjDQwMAAADAFUAAAOrA1UAMwBoAHcAABMiBgcOAQcOAQcOARURFBYXHgEXHgEXHgEzITI2Nz4BNz4BNz4BNRE0JicuAScuAScuASMFITIWFx4BFx4BFx4BFREUBgcOAQcOAQcOASMhIiYnLgEnLgEnLgE1ETQ2Nz4BNz4BNz4BMxMhMjY1NCYjISIGFRQWM9UNGAwLFQkJDgUFBQUFBQ4JCRULDBgNAlYNGAwLFQkJDgUFBQUFBQ4JCRULDBgN/aoCVgQIBAQHAwMFAQIBAQIBBQMDBwQECAT9qgQIBAQHAwMFAQIBAQIBBQMDBwQECASAAVYRGRkR/qoRGRkRA1UFBAUOCQkVDAsZDf2rDRkLDBUJCA4FBQUFBQUOCQgVDAsZDQJVDRkLDBUJCQ4FBAVVAgECBQMCBwQECAX9qwQJAwQHAwMFAQICAgIBBQMDBwQDCQQCVQUIBAQHAgMFAgEC/oAZEhEZGRESGQAAAAADAFUAAAOrA1UAMwBoAIkAABMiBgcOAQcOAQcOARURFBYXHgEXHgEXHgEzITI2Nz4BNz4BNz4BNRE0JicuAScuAScuASMFITIWFx4BFx4BFx4BFREUBgcOAQcOAQcOASMhIiYnLgEnLgEnLgE1ETQ2Nz4BNz4BNz4BMxMzFRQWMzI2PQEzMjY1NCYrATU0JiMiBh0BIyIGFRQWM9UNGAwLFQkJDgUFBQUFBQ4JCRULDBgNAlYNGAwLFQkJDgUFBQUFBQ4JCRULDBgN/aoCVgQIBAQHAwMFAQIBAQIBBQMDBwQECAT9qgQIBAQHAwMFAQIBAQIBBQMDBwQECASAgBkSEhmAERkZEYAZEhIZgBEZGREDVQUEBQ4JCRUMCxkN/asNGQsMFQkIDgUFBQUFBQ4JCBUMCxkNAlUNGQsMFQkJDgUEBVUCAQIFAwIHBAQIBf2rBAkDBAcDAwUBAgICAgEFAwMHBAMJBAJVBQgEBAcCAwUCAQL+gIASGRkSgBkSERmAEhkZEoAZERIZAAABAOIAjQMeAskAIAAAExcHBhQXFjI/ARcWMjc2NC8BNzY0JyYiDwEnJiIHBhQX4uLiDQ0MJAzi4gwkDA0N4uINDQwkDOLiDCQMDQ0CjeLiDSMMDQ3h4Q0NDCMN4uIMIw0MDOLiDAwNIwwAAAABAAAAAQAAa5n0y18PPPUACwQAAAAAANivOVsAAAAA2K85WwAAAAADqwNVAAAACAACAAAAAAAAAAEAAAPA/8AAAAQAAAAAAAOrAAEAAAAAAAAAAAAAAAAAAAALBAAAAAAAAAAAAAAAAgAAAAQAAWIEAAFiBAAA4gQAAOIEAABVBAAAVQQAAOIAAAAAAAoAFAAeAEQAagCqAOoBngJkApoAAQAAAAsAigADAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA4ArgABAAAAAAABAAcAAAABAAAAAAACAAcAYAABAAAAAAADAAcANgABAAAAAAAEAAcAdQABAAAAAAAFAAsAFQABAAAAAAAGAAcASwABAAAAAAAKABoAigADAAEECQABAA4ABwADAAEECQACAA4AZwADAAEECQADAA4APQADAAEECQAEAA4AfAADAAEECQAFABYAIAADAAEECQAGAA4AUgADAAEECQAKADQApGZjaWNvbnMAZgBjAGkAYwBvAG4Ac1ZlcnNpb24gMS4wAFYAZQByAHMAaQBvAG4AIAAxAC4AMGZjaWNvbnMAZgBjAGkAYwBvAG4Ac2ZjaWNvbnMAZgBjAGkAYwBvAG4Ac1JlZ3VsYXIAUgBlAGcAdQBsAGEAcmZjaWNvbnMAZgBjAGkAYwBvAG4Ac0ZvbnQgZ2VuZXJhdGVkIGJ5IEljb01vb24uAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\") format(\"truetype\")}.fc-icon{speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:inline-block;font-family:fcicons!important;font-style:normal;font-variant:normal;font-weight:400;height:1em;line-height:1;text-align:center;text-transform:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:1em}.fc-icon-chevron-left:before{content:\"\\e900\"}.fc-icon-chevron-right:before{content:\"\\e901\"}.fc-icon-chevrons-left:before{content:\"\\e902\"}.fc-icon-chevrons-right:before{content:\"\\e903\"}.fc-icon-minus-square:before{content:\"\\e904\"}.fc-icon-plus-square:before{content:\"\\e905\"}.fc-icon-x:before{content:\"\\e906\"}.fc .fc-button{border-radius:0;font-family:inherit;font-size:inherit;line-height:inherit;margin:0;overflow:visible;text-transform:none}.fc .fc-button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}.fc .fc-button{-webkit-appearance:button}.fc .fc-button:not(:disabled){cursor:pointer}.fc .fc-button{background-color:transparent;border:1px solid transparent;border-radius:.25em;display:inline-block;font-size:1em;font-weight:400;line-height:1.5;padding:.4em .65em;text-align:center;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle}.fc .fc-button:hover{text-decoration:none}.fc .fc-button:focus{box-shadow:0 0 0 .2rem rgba(44,62,80,.25);outline:0}.fc .fc-button:disabled{opacity:.65}.fc .fc-button-primary{background-color:var(--fc-button-bg-color);border-color:var(--fc-button-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:hover{background-color:var(--fc-button-hover-bg-color);border-color:var(--fc-button-hover-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:disabled{background-color:var(--fc-button-bg-color);border-color:var(--fc-button-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:focus{box-shadow:0 0 0 .2rem rgba(76,91,106,.5)}.fc .fc-button-primary:not(:disabled).fc-button-active,.fc .fc-button-primary:not(:disabled):active{background-color:var(--fc-button-active-bg-color);border-color:var(--fc-button-active-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:not(:disabled).fc-button-active:focus,.fc .fc-button-primary:not(:disabled):active:focus{box-shadow:0 0 0 .2rem rgba(76,91,106,.5)}.fc .fc-button .fc-icon{font-size:1.5em;vertical-align:middle}.fc .fc-button-group{display:inline-flex;position:relative;vertical-align:middle}.fc .fc-button-group>.fc-button{flex:1 1 auto;position:relative}.fc .fc-button-group>.fc-button.fc-button-active,.fc .fc-button-group>.fc-button:active,.fc .fc-button-group>.fc-button:focus,.fc .fc-button-group>.fc-button:hover{z-index:1}.fc-direction-ltr .fc-button-group>.fc-button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-1px}.fc-direction-ltr .fc-button-group>.fc-button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.fc-direction-rtl .fc-button-group>.fc-button:not(:first-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.fc-direction-rtl .fc-button-group>.fc-button:not(:last-child){border-bottom-left-radius:0;border-top-left-radius:0}.fc .fc-toolbar{align-items:center;display:flex;justify-content:space-between}.fc .fc-toolbar.fc-header-toolbar{margin-bottom:1.5em}.fc .fc-toolbar.fc-footer-toolbar{margin-top:1.5em}.fc .fc-toolbar-title{font-size:1.75em;margin:0}.fc-direction-ltr .fc-toolbar>*>:not(:first-child){margin-left:.75em}.fc-direction-rtl .fc-toolbar>*>:not(:first-child){margin-right:.75em}.fc-direction-rtl .fc-toolbar-ltr{flex-direction:row-reverse}.fc .fc-scroller{-webkit-overflow-scrolling:touch;position:relative}.fc .fc-scroller-liquid{height:100%}.fc .fc-scroller-liquid-absolute{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-scroller-harness{direction:ltr;overflow:hidden;position:relative}.fc .fc-scroller-harness-liquid{height:100%}.fc-direction-rtl .fc-scroller-harness>.fc-scroller{direction:rtl}.fc-theme-standard .fc-scrollgrid{border:1px solid var(--fc-border-color)}.fc .fc-scrollgrid,.fc .fc-scrollgrid table{table-layout:fixed;width:100%}.fc .fc-scrollgrid table{border-left-style:hidden;border-right-style:hidden;border-top-style:hidden}.fc .fc-scrollgrid{border-bottom-width:0;border-collapse:separate;border-right-width:0}.fc .fc-scrollgrid-liquid{height:100%}.fc .fc-scrollgrid-section,.fc .fc-scrollgrid-section table,.fc .fc-scrollgrid-section>td{height:1px}.fc .fc-scrollgrid-section-liquid>td{height:100%}.fc .fc-scrollgrid-section>*{border-left-width:0;border-top-width:0}.fc .fc-scrollgrid-section-footer>*,.fc .fc-scrollgrid-section-header>*{border-bottom-width:0}.fc .fc-scrollgrid-section-body table,.fc .fc-scrollgrid-section-footer table{border-bottom-style:hidden}.fc .fc-scrollgrid-section-sticky>*{background:var(--fc-page-bg-color);position:sticky;z-index:3}.fc .fc-scrollgrid-section-header.fc-scrollgrid-section-sticky>*{top:0}.fc .fc-scrollgrid-section-footer.fc-scrollgrid-section-sticky>*{bottom:0}.fc .fc-scrollgrid-sticky-shim{height:1px;margin-bottom:-1px}.fc-sticky{position:sticky}.fc .fc-view-harness{flex-grow:1;position:relative}.fc .fc-view-harness-active>.fc-view{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-col-header-cell-cushion{display:inline-block;padding:2px 4px}.fc .fc-bg-event,.fc .fc-highlight,.fc .fc-non-business{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-non-business{background:var(--fc-non-business-color)}.fc .fc-bg-event{background:var(--fc-bg-event-color);opacity:var(--fc-bg-event-opacity)}.fc .fc-bg-event .fc-event-title{font-size:var(--fc-small-font-size);font-style:italic;margin:.5em}.fc .fc-highlight{background:var(--fc-highlight-color)}.fc .fc-cell-shaded,.fc .fc-day-disabled{background:var(--fc-neutral-bg-color)}a.fc-event,a.fc-event:hover{text-decoration:none}.fc-event.fc-event-draggable,.fc-event[href]{cursor:pointer}.fc-event .fc-event-main{position:relative;z-index:2}.fc-event-dragging:not(.fc-event-selected){opacity:.75}.fc-event-dragging.fc-event-selected{box-shadow:0 2px 7px rgba(0,0,0,.3)}.fc-event .fc-event-resizer{display:none;position:absolute;z-index:4}.fc-event-selected .fc-event-resizer,.fc-event:hover .fc-event-resizer{display:block}.fc-event-selected .fc-event-resizer{background:var(--fc-page-bg-color);border-color:inherit;border-radius:calc(var(--fc-event-resizer-dot-total-width)/2);border-style:solid;border-width:var(--fc-event-resizer-dot-border-width);height:var(--fc-event-resizer-dot-total-width);width:var(--fc-event-resizer-dot-total-width)}.fc-event-selected .fc-event-resizer:before{bottom:-20px;content:\"\";left:-20px;position:absolute;right:-20px;top:-20px}.fc-event-selected,.fc-event:focus{box-shadow:0 2px 5px rgba(0,0,0,.2)}.fc-event-selected:before,.fc-event:focus:before{bottom:0;content:\"\";left:0;position:absolute;right:0;top:0;z-index:3}.fc-event-selected:after,.fc-event:focus:after{background:var(--fc-event-selected-overlay-color);bottom:-1px;content:\"\";left:-1px;position:absolute;right:-1px;top:-1px;z-index:1}.fc-h-event{background-color:var(--fc-event-bg-color);border:1px solid var(--fc-event-border-color);display:block}.fc-h-event .fc-event-main{color:var(--fc-event-text-color)}.fc-h-event .fc-event-main-frame{display:flex}.fc-h-event .fc-event-time{max-width:100%;overflow:hidden}.fc-h-event .fc-event-title-container{flex-grow:1;flex-shrink:1;min-width:0}.fc-h-event .fc-event-title{display:inline-block;left:0;max-width:100%;overflow:hidden;right:0;vertical-align:top}.fc-h-event.fc-event-selected:before{bottom:-10px;top:-10px}.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-start),.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-end){border-bottom-left-radius:0;border-left-width:0;border-top-left-radius:0}.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-end),.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-start){border-bottom-right-radius:0;border-right-width:0;border-top-right-radius:0}.fc-h-event:not(.fc-event-selected) .fc-event-resizer{bottom:0;top:0;width:var(--fc-event-resizer-thickness)}.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start,.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end{cursor:w-resize;left:calc(var(--fc-event-resizer-thickness)*-.5)}.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end,.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start{cursor:e-resize;right:calc(var(--fc-event-resizer-thickness)*-.5)}.fc-h-event.fc-event-selected .fc-event-resizer{margin-top:calc(var(--fc-event-resizer-dot-total-width)*-.5);top:50%}.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-start,.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-end{left:calc(var(--fc-event-resizer-dot-total-width)*-.5)}.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-end,.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-start{right:calc(var(--fc-event-resizer-dot-total-width)*-.5)}.fc .fc-popover{box-shadow:0 2px 6px rgba(0,0,0,.15);position:absolute;z-index:9999}.fc .fc-popover-header{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding:3px 4px}.fc .fc-popover-title{margin:0 2px}.fc .fc-popover-close{cursor:pointer;font-size:1.1em;opacity:.65}.fc-theme-standard .fc-popover{background:var(--fc-page-bg-color);border:1px solid var(--fc-border-color)}.fc-theme-standard .fc-popover-header{background:var(--fc-neutral-bg-color)}";
96 injectStyles(css_248z$4);
97
98 class DelayedRunner {
99 constructor(drainedOption) {
100 this.drainedOption = drainedOption;
101 this.isRunning = false;
102 this.isDirty = false;
103 this.pauseDepths = {};
104 this.timeoutId = 0;
105 }
106 request(delay) {
107 this.isDirty = true;
108 if (!this.isPaused()) {
109 this.clearTimeout();
110 if (delay == null) {
111 this.tryDrain();
112 }
113 else {
114 this.timeoutId = setTimeout(// NOT OPTIMAL! TODO: look at debounce
115 this.tryDrain.bind(this), delay);
116 }
117 }
118 }
119 pause(scope = '') {
120 let { pauseDepths } = this;
121 pauseDepths[scope] = (pauseDepths[scope] || 0) + 1;
122 this.clearTimeout();
123 }
124 resume(scope = '', force) {
125 let { pauseDepths } = this;
126 if (scope in pauseDepths) {
127 if (force) {
128 delete pauseDepths[scope];
129 }
130 else {
131 pauseDepths[scope] -= 1;
132 let depth = pauseDepths[scope];
133 if (depth <= 0) {
134 delete pauseDepths[scope];
135 }
136 }
137 this.tryDrain();
138 }
139 }
140 isPaused() {
141 return Object.keys(this.pauseDepths).length;
142 }
143 tryDrain() {
144 if (!this.isRunning && !this.isPaused()) {
145 this.isRunning = true;
146 while (this.isDirty) {
147 this.isDirty = false;
148 this.drained(); // might set isDirty to true again
149 }
150 this.isRunning = false;
151 }
152 }
153 clear() {
154 this.clearTimeout();
155 this.isDirty = false;
156 this.pauseDepths = {};
157 }
158 clearTimeout() {
159 if (this.timeoutId) {
160 clearTimeout(this.timeoutId);
161 this.timeoutId = 0;
162 }
163 }
164 drained() {
165 if (this.drainedOption) {
166 this.drainedOption();
167 }
168 }
169 }
170
171 function removeElement(el) {
172 if (el.parentNode) {
173 el.parentNode.removeChild(el);
174 }
175 }
176 // Querying
177 // ----------------------------------------------------------------------------------------------------------------
178 function elementClosest(el, selector) {
179 if (el.closest) {
180 return el.closest(selector);
181 // really bad fallback for IE
182 // from https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
183 }
184 if (!document.documentElement.contains(el)) {
185 return null;
186 }
187 do {
188 if (elementMatches(el, selector)) {
189 return el;
190 }
191 el = (el.parentElement || el.parentNode);
192 } while (el !== null && el.nodeType === 1);
193 return null;
194 }
195 function elementMatches(el, selector) {
196 let method = el.matches || el.matchesSelector || el.msMatchesSelector;
197 return method.call(el, selector);
198 }
199 // accepts multiple subject els
200 // returns a real array. good for methods like forEach
201 // TODO: accept the document
202 function findElements(container, selector) {
203 let containers = container instanceof HTMLElement ? [container] : container;
204 let allMatches = [];
205 for (let i = 0; i < containers.length; i += 1) {
206 let matches = containers[i].querySelectorAll(selector);
207 for (let j = 0; j < matches.length; j += 1) {
208 allMatches.push(matches[j]);
209 }
210 }
211 return allMatches;
212 }
213 // accepts multiple subject els
214 // only queries direct child elements // TODO: rename to findDirectChildren!
215 function findDirectChildren(parent, selector) {
216 let parents = parent instanceof HTMLElement ? [parent] : parent;
217 let allMatches = [];
218 for (let i = 0; i < parents.length; i += 1) {
219 let childNodes = parents[i].children; // only ever elements
220 for (let j = 0; j < childNodes.length; j += 1) {
221 let childNode = childNodes[j];
222 if (!selector || elementMatches(childNode, selector)) {
223 allMatches.push(childNode);
224 }
225 }
226 }
227 return allMatches;
228 }
229 // Style
230 // ----------------------------------------------------------------------------------------------------------------
231 const PIXEL_PROP_RE = /(top|left|right|bottom|width|height)$/i;
232 function applyStyle(el, props) {
233 for (let propName in props) {
234 applyStyleProp(el, propName, props[propName]);
235 }
236 }
237 function applyStyleProp(el, name, val) {
238 if (val == null) {
239 el.style[name] = '';
240 }
241 else if (typeof val === 'number' && PIXEL_PROP_RE.test(name)) {
242 el.style[name] = `${val}px`;
243 }
244 else {
245 el.style[name] = val;
246 }
247 }
248 // Event Handling
249 // ----------------------------------------------------------------------------------------------------------------
250 // if intercepting bubbled events at the document/window/body level,
251 // and want to see originating element (the 'target'), use this util instead
252 // of `ev.target` because it goes within web-component boundaries.
253 function getEventTargetViaRoot(ev) {
254 var _a, _b;
255 return (_b = (_a = ev.composedPath) === null || _a === void 0 ? void 0 : _a.call(ev)[0]) !== null && _b !== void 0 ? _b : ev.target;
256 }
257 // Unique ID for DOM attribute
258 let guid$1 = 0;
259 function getUniqueDomId() {
260 guid$1 += 1;
261 return 'fc-dom-' + guid$1;
262 }
263
264 // Stops a mouse/touch event from doing it's native browser action
265 function preventDefault(ev) {
266 ev.preventDefault();
267 }
268 // Event Delegation
269 // ----------------------------------------------------------------------------------------------------------------
270 function buildDelegationHandler(selector, handler) {
271 return (ev) => {
272 let matchedChild = elementClosest(ev.target, selector);
273 if (matchedChild) {
274 handler.call(matchedChild, ev, matchedChild);
275 }
276 };
277 }
278 function listenBySelector(container, eventType, selector, handler) {
279 let attachedHandler = buildDelegationHandler(selector, handler);
280 container.addEventListener(eventType, attachedHandler);
281 return () => {
282 container.removeEventListener(eventType, attachedHandler);
283 };
284 }
285 function listenToHoverBySelector(container, selector, onMouseEnter, onMouseLeave) {
286 let currentMatchedChild;
287 return listenBySelector(container, 'mouseover', selector, (mouseOverEv, matchedChild) => {
288 if (matchedChild !== currentMatchedChild) {
289 currentMatchedChild = matchedChild;
290 onMouseEnter(mouseOverEv, matchedChild);
291 let realOnMouseLeave = (mouseLeaveEv) => {
292 currentMatchedChild = null;
293 onMouseLeave(mouseLeaveEv, matchedChild);
294 matchedChild.removeEventListener('mouseleave', realOnMouseLeave);
295 };
296 // listen to the next mouseleave, and then unattach
297 matchedChild.addEventListener('mouseleave', realOnMouseLeave);
298 }
299 });
300 }
301 // Animation
302 // ----------------------------------------------------------------------------------------------------------------
303 const transitionEventNames = [
304 'webkitTransitionEnd',
305 'otransitionend',
306 'oTransitionEnd',
307 'msTransitionEnd',
308 'transitionend',
309 ];
310 // triggered only when the next single subsequent transition finishes
311 function whenTransitionDone(el, callback) {
312 let realCallback = (ev) => {
313 callback(ev);
314 transitionEventNames.forEach((eventName) => {
315 el.removeEventListener(eventName, realCallback);
316 });
317 };
318 transitionEventNames.forEach((eventName) => {
319 el.addEventListener(eventName, realCallback); // cross-browser way to determine when the transition finishes
320 });
321 }
322 // ARIA workarounds
323 // ----------------------------------------------------------------------------------------------------------------
324 function createAriaClickAttrs(handler) {
325 return Object.assign({ onClick: handler }, createAriaKeyboardAttrs(handler));
326 }
327 function createAriaKeyboardAttrs(handler) {
328 return {
329 tabIndex: 0,
330 onKeyDown(ev) {
331 if (ev.key === 'Enter' || ev.key === ' ') {
332 handler(ev);
333 ev.preventDefault(); // if space, don't scroll down page
334 }
335 },
336 };
337 }
338
339 let guidNumber = 0;
340 function guid() {
341 guidNumber += 1;
342 return String(guidNumber);
343 }
344 /* FullCalendar-specific DOM Utilities
345 ----------------------------------------------------------------------------------------------------------------------*/
346 // Make the mouse cursor express that an event is not allowed in the current area
347 function disableCursor() {
348 document.body.classList.add('fc-not-allowed');
349 }
350 // Returns the mouse cursor to its original look
351 function enableCursor() {
352 document.body.classList.remove('fc-not-allowed');
353 }
354 /* Selection
355 ----------------------------------------------------------------------------------------------------------------------*/
356 function preventSelection(el) {
357 el.style.userSelect = 'none';
358 el.style.webkitUserSelect = 'none';
359 el.addEventListener('selectstart', preventDefault);
360 }
361 function allowSelection(el) {
362 el.style.userSelect = '';
363 el.style.webkitUserSelect = '';
364 el.removeEventListener('selectstart', preventDefault);
365 }
366 /* Context Menu
367 ----------------------------------------------------------------------------------------------------------------------*/
368 function preventContextMenu(el) {
369 el.addEventListener('contextmenu', preventDefault);
370 }
371 function allowContextMenu(el) {
372 el.removeEventListener('contextmenu', preventDefault);
373 }
374 function parseFieldSpecs(input) {
375 let specs = [];
376 let tokens = [];
377 let i;
378 let token;
379 if (typeof input === 'string') {
380 tokens = input.split(/\s*,\s*/);
381 }
382 else if (typeof input === 'function') {
383 tokens = [input];
384 }
385 else if (Array.isArray(input)) {
386 tokens = input;
387 }
388 for (i = 0; i < tokens.length; i += 1) {
389 token = tokens[i];
390 if (typeof token === 'string') {
391 specs.push(token.charAt(0) === '-' ?
392 { field: token.substring(1), order: -1 } :
393 { field: token, order: 1 });
394 }
395 else if (typeof token === 'function') {
396 specs.push({ func: token });
397 }
398 }
399 return specs;
400 }
401 function compareByFieldSpecs(obj0, obj1, fieldSpecs) {
402 let i;
403 let cmp;
404 for (i = 0; i < fieldSpecs.length; i += 1) {
405 cmp = compareByFieldSpec(obj0, obj1, fieldSpecs[i]);
406 if (cmp) {
407 return cmp;
408 }
409 }
410 return 0;
411 }
412 function compareByFieldSpec(obj0, obj1, fieldSpec) {
413 if (fieldSpec.func) {
414 return fieldSpec.func(obj0, obj1);
415 }
416 return flexibleCompare(obj0[fieldSpec.field], obj1[fieldSpec.field])
417 * (fieldSpec.order || 1);
418 }
419 function flexibleCompare(a, b) {
420 if (!a && !b) {
421 return 0;
422 }
423 if (b == null) {
424 return -1;
425 }
426 if (a == null) {
427 return 1;
428 }
429 if (typeof a === 'string' || typeof b === 'string') {
430 return String(a).localeCompare(String(b));
431 }
432 return a - b;
433 }
434 /* String Utilities
435 ----------------------------------------------------------------------------------------------------------------------*/
436 function padStart(val, len) {
437 let s = String(val);
438 return '000'.substr(0, len - s.length) + s;
439 }
440 function formatWithOrdinals(formatter, args, fallbackText) {
441 if (typeof formatter === 'function') {
442 return formatter(...args);
443 }
444 if (typeof formatter === 'string') { // non-blank string
445 return args.reduce((str, arg, index) => (str.replace('$' + index, arg || '')), formatter);
446 }
447 return fallbackText;
448 }
449 /* Number Utilities
450 ----------------------------------------------------------------------------------------------------------------------*/
451 function compareNumbers(a, b) {
452 return a - b;
453 }
454 function isInt(n) {
455 return n % 1 === 0;
456 }
457 /* FC-specific DOM dimension stuff
458 ----------------------------------------------------------------------------------------------------------------------*/
459 function computeSmallestCellWidth(cellEl) {
460 let allWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-frame');
461 let contentWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-cushion');
462 if (!allWidthEl) {
463 throw new Error('needs fc-scrollgrid-shrink-frame className'); // TODO: use const
464 }
465 if (!contentWidthEl) {
466 throw new Error('needs fc-scrollgrid-shrink-cushion className');
467 }
468 return cellEl.getBoundingClientRect().width - allWidthEl.getBoundingClientRect().width + // the cell padding+border
469 contentWidthEl.getBoundingClientRect().width;
470 }
471
472 const INTERNAL_UNITS = ['years', 'months', 'days', 'milliseconds'];
473 const PARSE_RE = /^(-?)(?:(\d+)\.)?(\d+):(\d\d)(?::(\d\d)(?:\.(\d\d\d))?)?/;
474 // Parsing and Creation
475 function createDuration(input, unit) {
476 if (typeof input === 'string') {
477 return parseString(input);
478 }
479 if (typeof input === 'object' && input) { // non-null object
480 return parseObject(input);
481 }
482 if (typeof input === 'number') {
483 return parseObject({ [unit || 'milliseconds']: input });
484 }
485 return null;
486 }
487 function parseString(s) {
488 let m = PARSE_RE.exec(s);
489 if (m) {
490 let sign = m[1] ? -1 : 1;
491 return {
492 years: 0,
493 months: 0,
494 days: sign * (m[2] ? parseInt(m[2], 10) : 0),
495 milliseconds: sign * ((m[3] ? parseInt(m[3], 10) : 0) * 60 * 60 * 1000 + // hours
496 (m[4] ? parseInt(m[4], 10) : 0) * 60 * 1000 + // minutes
497 (m[5] ? parseInt(m[5], 10) : 0) * 1000 + // seconds
498 (m[6] ? parseInt(m[6], 10) : 0) // ms
499 ),
500 };
501 }
502 return null;
503 }
504 function parseObject(obj) {
505 let duration = {
506 years: obj.years || obj.year || 0,
507 months: obj.months || obj.month || 0,
508 days: obj.days || obj.day || 0,
509 milliseconds: (obj.hours || obj.hour || 0) * 60 * 60 * 1000 + // hours
510 (obj.minutes || obj.minute || 0) * 60 * 1000 + // minutes
511 (obj.seconds || obj.second || 0) * 1000 + // seconds
512 (obj.milliseconds || obj.millisecond || obj.ms || 0), // ms
513 };
514 let weeks = obj.weeks || obj.week;
515 if (weeks) {
516 duration.days += weeks * 7;
517 duration.specifiedWeeks = true;
518 }
519 return duration;
520 }
521 // Equality
522 function durationsEqual(d0, d1) {
523 return d0.years === d1.years &&
524 d0.months === d1.months &&
525 d0.days === d1.days &&
526 d0.milliseconds === d1.milliseconds;
527 }
528 function asCleanDays(dur) {
529 if (!dur.years && !dur.months && !dur.milliseconds) {
530 return dur.days;
531 }
532 return 0;
533 }
534 // Simple Math
535 function addDurations(d0, d1) {
536 return {
537 years: d0.years + d1.years,
538 months: d0.months + d1.months,
539 days: d0.days + d1.days,
540 milliseconds: d0.milliseconds + d1.milliseconds,
541 };
542 }
543 function subtractDurations(d1, d0) {
544 return {
545 years: d1.years - d0.years,
546 months: d1.months - d0.months,
547 days: d1.days - d0.days,
548 milliseconds: d1.milliseconds - d0.milliseconds,
549 };
550 }
551 function multiplyDuration(d, n) {
552 return {
553 years: d.years * n,
554 months: d.months * n,
555 days: d.days * n,
556 milliseconds: d.milliseconds * n,
557 };
558 }
559 // Conversions
560 // "Rough" because they are based on average-case Gregorian months/years
561 function asRoughYears(dur) {
562 return asRoughDays(dur) / 365;
563 }
564 function asRoughMonths(dur) {
565 return asRoughDays(dur) / 30;
566 }
567 function asRoughDays(dur) {
568 return asRoughMs(dur) / 864e5;
569 }
570 function asRoughMinutes(dur) {
571 return asRoughMs(dur) / (1000 * 60);
572 }
573 function asRoughSeconds(dur) {
574 return asRoughMs(dur) / 1000;
575 }
576 function asRoughMs(dur) {
577 return dur.years * (365 * 864e5) +
578 dur.months * (30 * 864e5) +
579 dur.days * 864e5 +
580 dur.milliseconds;
581 }
582 // Advanced Math
583 function wholeDivideDurations(numerator, denominator) {
584 let res = null;
585 for (let i = 0; i < INTERNAL_UNITS.length; i += 1) {
586 let unit = INTERNAL_UNITS[i];
587 if (denominator[unit]) {
588 let localRes = numerator[unit] / denominator[unit];
589 if (!isInt(localRes) || (res !== null && res !== localRes)) {
590 return null;
591 }
592 res = localRes;
593 }
594 else if (numerator[unit]) {
595 // needs to divide by something but can't!
596 return null;
597 }
598 }
599 return res;
600 }
601 function greatestDurationDenominator(dur) {
602 let ms = dur.milliseconds;
603 if (ms) {
604 if (ms % 1000 !== 0) {
605 return { unit: 'millisecond', value: ms };
606 }
607 if (ms % (1000 * 60) !== 0) {
608 return { unit: 'second', value: ms / 1000 };
609 }
610 if (ms % (1000 * 60 * 60) !== 0) {
611 return { unit: 'minute', value: ms / (1000 * 60) };
612 }
613 if (ms) {
614 return { unit: 'hour', value: ms / (1000 * 60 * 60) };
615 }
616 }
617 if (dur.days) {
618 if (dur.specifiedWeeks && dur.days % 7 === 0) {
619 return { unit: 'week', value: dur.days / 7 };
620 }
621 return { unit: 'day', value: dur.days };
622 }
623 if (dur.months) {
624 return { unit: 'month', value: dur.months };
625 }
626 if (dur.years) {
627 return { unit: 'year', value: dur.years };
628 }
629 return { unit: 'millisecond', value: 0 };
630 }
631
632 // TODO: new util arrayify?
633 function removeExact(array, exactVal) {
634 let removeCnt = 0;
635 let i = 0;
636 while (i < array.length) {
637 if (array[i] === exactVal) {
638 array.splice(i, 1);
639 removeCnt += 1;
640 }
641 else {
642 i += 1;
643 }
644 }
645 return removeCnt;
646 }
647 function isArraysEqual(a0, a1, equalityFunc) {
648 if (a0 === a1) {
649 return true;
650 }
651 let len = a0.length;
652 let i;
653 if (len !== a1.length) { // not array? or not same length?
654 return false;
655 }
656 for (i = 0; i < len; i += 1) {
657 if (!(equalityFunc ? equalityFunc(a0[i], a1[i]) : a0[i] === a1[i])) {
658 return false;
659 }
660 }
661 return true;
662 }
663
664 const DAY_IDS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
665 // Adding
666 function addWeeks(m, n) {
667 let a = dateToUtcArray(m);
668 a[2] += n * 7;
669 return arrayToUtcDate(a);
670 }
671 function addDays(m, n) {
672 let a = dateToUtcArray(m);
673 a[2] += n;
674 return arrayToUtcDate(a);
675 }
676 function addMs(m, n) {
677 let a = dateToUtcArray(m);
678 a[6] += n;
679 return arrayToUtcDate(a);
680 }
681 // Diffing (all return floats)
682 // TODO: why not use ranges?
683 function diffWeeks(m0, m1) {
684 return diffDays(m0, m1) / 7;
685 }
686 function diffDays(m0, m1) {
687 return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60 * 24);
688 }
689 function diffHours(m0, m1) {
690 return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60);
691 }
692 function diffMinutes(m0, m1) {
693 return (m1.valueOf() - m0.valueOf()) / (1000 * 60);
694 }
695 function diffSeconds(m0, m1) {
696 return (m1.valueOf() - m0.valueOf()) / 1000;
697 }
698 function diffDayAndTime(m0, m1) {
699 let m0day = startOfDay(m0);
700 let m1day = startOfDay(m1);
701 return {
702 years: 0,
703 months: 0,
704 days: Math.round(diffDays(m0day, m1day)),
705 milliseconds: (m1.valueOf() - m1day.valueOf()) - (m0.valueOf() - m0day.valueOf()),
706 };
707 }
708 // Diffing Whole Units
709 function diffWholeWeeks(m0, m1) {
710 let d = diffWholeDays(m0, m1);
711 if (d !== null && d % 7 === 0) {
712 return d / 7;
713 }
714 return null;
715 }
716 function diffWholeDays(m0, m1) {
717 if (timeAsMs(m0) === timeAsMs(m1)) {
718 return Math.round(diffDays(m0, m1));
719 }
720 return null;
721 }
722 // Start-Of
723 function startOfDay(m) {
724 return arrayToUtcDate([
725 m.getUTCFullYear(),
726 m.getUTCMonth(),
727 m.getUTCDate(),
728 ]);
729 }
730 function startOfHour(m) {
731 return arrayToUtcDate([
732 m.getUTCFullYear(),
733 m.getUTCMonth(),
734 m.getUTCDate(),
735 m.getUTCHours(),
736 ]);
737 }
738 function startOfMinute(m) {
739 return arrayToUtcDate([
740 m.getUTCFullYear(),
741 m.getUTCMonth(),
742 m.getUTCDate(),
743 m.getUTCHours(),
744 m.getUTCMinutes(),
745 ]);
746 }
747 function startOfSecond(m) {
748 return arrayToUtcDate([
749 m.getUTCFullYear(),
750 m.getUTCMonth(),
751 m.getUTCDate(),
752 m.getUTCHours(),
753 m.getUTCMinutes(),
754 m.getUTCSeconds(),
755 ]);
756 }
757 // Week Computation
758 function weekOfYear(marker, dow, doy) {
759 let y = marker.getUTCFullYear();
760 let w = weekOfGivenYear(marker, y, dow, doy);
761 if (w < 1) {
762 return weekOfGivenYear(marker, y - 1, dow, doy);
763 }
764 let nextW = weekOfGivenYear(marker, y + 1, dow, doy);
765 if (nextW >= 1) {
766 return Math.min(w, nextW);
767 }
768 return w;
769 }
770 function weekOfGivenYear(marker, year, dow, doy) {
771 let firstWeekStart = arrayToUtcDate([year, 0, 1 + firstWeekOffset(year, dow, doy)]);
772 let dayStart = startOfDay(marker);
773 let days = Math.round(diffDays(firstWeekStart, dayStart));
774 return Math.floor(days / 7) + 1; // zero-indexed
775 }
776 // start-of-first-week - start-of-year
777 function firstWeekOffset(year, dow, doy) {
778 // first-week day -- which january is always in the first week (4 for iso, 1 for other)
779 let fwd = 7 + dow - doy;
780 // first-week day local weekday -- which local weekday is fwd
781 let fwdlw = (7 + arrayToUtcDate([year, 0, fwd]).getUTCDay() - dow) % 7;
782 return -fwdlw + fwd - 1;
783 }
784 // Array Conversion
785 function dateToLocalArray(date) {
786 return [
787 date.getFullYear(),
788 date.getMonth(),
789 date.getDate(),
790 date.getHours(),
791 date.getMinutes(),
792 date.getSeconds(),
793 date.getMilliseconds(),
794 ];
795 }
796 function arrayToLocalDate(a) {
797 return new Date(a[0], a[1] || 0, a[2] == null ? 1 : a[2], // day of month
798 a[3] || 0, a[4] || 0, a[5] || 0);
799 }
800 function dateToUtcArray(date) {
801 return [
802 date.getUTCFullYear(),
803 date.getUTCMonth(),
804 date.getUTCDate(),
805 date.getUTCHours(),
806 date.getUTCMinutes(),
807 date.getUTCSeconds(),
808 date.getUTCMilliseconds(),
809 ];
810 }
811 function arrayToUtcDate(a) {
812 // according to web standards (and Safari), a month index is required.
813 // massage if only given a year.
814 if (a.length === 1) {
815 a = a.concat([0]);
816 }
817 return new Date(Date.UTC(...a));
818 }
819 // Other Utils
820 function isValidDate(m) {
821 return !isNaN(m.valueOf());
822 }
823 function timeAsMs(m) {
824 return m.getUTCHours() * 1000 * 60 * 60 +
825 m.getUTCMinutes() * 1000 * 60 +
826 m.getUTCSeconds() * 1000 +
827 m.getUTCMilliseconds();
828 }
829
830 // timeZoneOffset is in minutes
831 function buildIsoString(marker, timeZoneOffset, stripZeroTime = false) {
832 let s = marker.toISOString();
833 s = s.replace('.000', '');
834 if (stripZeroTime) {
835 s = s.replace('T00:00:00Z', '');
836 }
837 if (s.length > 10) { // time part wasn't stripped, can add timezone info
838 if (timeZoneOffset == null) {
839 s = s.replace('Z', '');
840 }
841 else if (timeZoneOffset !== 0) {
842 s = s.replace('Z', formatTimeZoneOffset(timeZoneOffset, true));
843 }
844 // otherwise, its UTC-0 and we want to keep the Z
845 }
846 return s;
847 }
848 // formats the date, but with no time part
849 // TODO: somehow merge with buildIsoString and stripZeroTime
850 // TODO: rename. omit "string"
851 function formatDayString(marker) {
852 return marker.toISOString().replace(/T.*$/, '');
853 }
854 function formatIsoMonthStr(marker) {
855 return marker.toISOString().match(/^\d{4}-\d{2}/)[0];
856 }
857 // TODO: use Date::toISOString and use everything after the T?
858 function formatIsoTimeString(marker) {
859 return padStart(marker.getUTCHours(), 2) + ':' +
860 padStart(marker.getUTCMinutes(), 2) + ':' +
861 padStart(marker.getUTCSeconds(), 2);
862 }
863 function formatTimeZoneOffset(minutes, doIso = false) {
864 let sign = minutes < 0 ? '-' : '+';
865 let abs = Math.abs(minutes);
866 let hours = Math.floor(abs / 60);
867 let mins = Math.round(abs % 60);
868 if (doIso) {
869 return `${sign + padStart(hours, 2)}:${padStart(mins, 2)}`;
870 }
871 return `GMT${sign}${hours}${mins ? `:${padStart(mins, 2)}` : ''}`;
872 }
873
874 function memoize(workerFunc, resEquality, teardownFunc) {
875 let currentArgs;
876 let currentRes;
877 return function (...newArgs) {
878 if (!currentArgs) {
879 currentRes = workerFunc.apply(this, newArgs);
880 }
881 else if (!isArraysEqual(currentArgs, newArgs)) {
882 if (teardownFunc) {
883 teardownFunc(currentRes);
884 }
885 let res = workerFunc.apply(this, newArgs);
886 if (!resEquality || !resEquality(res, currentRes)) {
887 currentRes = res;
888 }
889 }
890 currentArgs = newArgs;
891 return currentRes;
892 };
893 }
894 function memoizeObjArg(workerFunc, resEquality, teardownFunc) {
895 let currentArg;
896 let currentRes;
897 return (newArg) => {
898 if (!currentArg) {
899 currentRes = workerFunc.call(this, newArg);
900 }
901 else if (!isPropsEqual(currentArg, newArg)) {
902 if (teardownFunc) {
903 teardownFunc(currentRes);
904 }
905 let res = workerFunc.call(this, newArg);
906 if (!resEquality || !resEquality(res, currentRes)) {
907 currentRes = res;
908 }
909 }
910 currentArg = newArg;
911 return currentRes;
912 };
913 }
914 function memoizeArraylike(// used at all?
915 workerFunc, resEquality, teardownFunc) {
916 let currentArgSets = [];
917 let currentResults = [];
918 return (newArgSets) => {
919 let currentLen = currentArgSets.length;
920 let newLen = newArgSets.length;
921 let i = 0;
922 for (; i < currentLen; i += 1) {
923 if (!newArgSets[i]) { // one of the old sets no longer exists
924 if (teardownFunc) {
925 teardownFunc(currentResults[i]);
926 }
927 }
928 else if (!isArraysEqual(currentArgSets[i], newArgSets[i])) {
929 if (teardownFunc) {
930 teardownFunc(currentResults[i]);
931 }
932 let res = workerFunc.apply(this, newArgSets[i]);
933 if (!resEquality || !resEquality(res, currentResults[i])) {
934 currentResults[i] = res;
935 }
936 }
937 }
938 for (; i < newLen; i += 1) {
939 currentResults[i] = workerFunc.apply(this, newArgSets[i]);
940 }
941 currentArgSets = newArgSets;
942 currentResults.splice(newLen); // remove excess
943 return currentResults;
944 };
945 }
946 function memoizeHashlike(workerFunc, resEquality, teardownFunc) {
947 let currentArgHash = {};
948 let currentResHash = {};
949 return (newArgHash) => {
950 let newResHash = {};
951 for (let key in newArgHash) {
952 if (!currentResHash[key]) {
953 newResHash[key] = workerFunc.apply(this, newArgHash[key]);
954 }
955 else if (!isArraysEqual(currentArgHash[key], newArgHash[key])) {
956 if (teardownFunc) {
957 teardownFunc(currentResHash[key]);
958 }
959 let res = workerFunc.apply(this, newArgHash[key]);
960 newResHash[key] = (resEquality && resEquality(res, currentResHash[key]))
961 ? currentResHash[key]
962 : res;
963 }
964 else {
965 newResHash[key] = currentResHash[key];
966 }
967 }
968 currentArgHash = newArgHash;
969 currentResHash = newResHash;
970 return newResHash;
971 };
972 }
973
974 const EXTENDED_SETTINGS_AND_SEVERITIES = {
975 week: 3,
976 separator: 9,
977 omitZeroMinute: 9,
978 meridiem: 9,
979 omitCommas: 9,
980 };
981 const STANDARD_DATE_PROP_SEVERITIES = {
982 timeZoneName: 7,
983 era: 6,
984 year: 5,
985 month: 4,
986 day: 2,
987 weekday: 2,
988 hour: 1,
989 minute: 1,
990 second: 1,
991 };
992 const MERIDIEM_RE = /\s*([ap])\.?m\.?/i; // eats up leading spaces too
993 const COMMA_RE = /,/g; // we need re for globalness
994 const MULTI_SPACE_RE = /\s+/g;
995 const LTR_RE = /\u200e/g; // control character
996 const UTC_RE = /UTC|GMT/;
997 class NativeFormatter {
998 constructor(formatSettings) {
999 let standardDateProps = {};
1000 let extendedSettings = {};
1001 let smallestUnitNum = 9; // the smallest unit in the formatter (9 is a sentinel, beyond max)
1002 for (let name in formatSettings) {
1003 if (name in EXTENDED_SETTINGS_AND_SEVERITIES) {
1004 extendedSettings[name] = formatSettings[name];
1005 const severity = EXTENDED_SETTINGS_AND_SEVERITIES[name];
1006 if (severity < 9) {
1007 smallestUnitNum = Math.min(EXTENDED_SETTINGS_AND_SEVERITIES[name], smallestUnitNum);
1008 }
1009 }
1010 else {
1011 standardDateProps[name] = formatSettings[name];
1012 if (name in STANDARD_DATE_PROP_SEVERITIES) { // TODO: what about hour12? no severity
1013 smallestUnitNum = Math.min(STANDARD_DATE_PROP_SEVERITIES[name], smallestUnitNum);
1014 }
1015 }
1016 }
1017 this.standardDateProps = standardDateProps;
1018 this.extendedSettings = extendedSettings;
1019 this.smallestUnitNum = smallestUnitNum;
1020 this.buildFormattingFunc = memoize(buildFormattingFunc);
1021 }
1022 format(date, context) {
1023 return this.buildFormattingFunc(this.standardDateProps, this.extendedSettings, context)(date);
1024 }
1025 formatRange(start, end, context, betterDefaultSeparator) {
1026 let { standardDateProps, extendedSettings } = this;
1027 let diffSeverity = computeMarkerDiffSeverity(start.marker, end.marker, context.calendarSystem);
1028 if (!diffSeverity) {
1029 return this.format(start, context);
1030 }
1031 let biggestUnitForPartial = diffSeverity;
1032 if (biggestUnitForPartial > 1 && // the two dates are different in a way that's larger scale than time
1033 (standardDateProps.year === 'numeric' || standardDateProps.year === '2-digit') &&
1034 (standardDateProps.month === 'numeric' || standardDateProps.month === '2-digit') &&
1035 (standardDateProps.day === 'numeric' || standardDateProps.day === '2-digit')) {
1036 biggestUnitForPartial = 1; // make it look like the dates are only different in terms of time
1037 }
1038 let full0 = this.format(start, context);
1039 let full1 = this.format(end, context);
1040 if (full0 === full1) {
1041 return full0;
1042 }
1043 let partialDateProps = computePartialFormattingOptions(standardDateProps, biggestUnitForPartial);
1044 let partialFormattingFunc = buildFormattingFunc(partialDateProps, extendedSettings, context);
1045 let partial0 = partialFormattingFunc(start);
1046 let partial1 = partialFormattingFunc(end);
1047 let insertion = findCommonInsertion(full0, partial0, full1, partial1);
1048 let separator = extendedSettings.separator || betterDefaultSeparator || context.defaultSeparator || '';
1049 if (insertion) {
1050 return insertion.before + partial0 + separator + partial1 + insertion.after;
1051 }
1052 return full0 + separator + full1;
1053 }
1054 getSmallestUnit() {
1055 switch (this.smallestUnitNum) {
1056 case 7:
1057 case 6:
1058 case 5:
1059 return 'year';
1060 case 4:
1061 return 'month';
1062 case 3:
1063 return 'week';
1064 case 2:
1065 return 'day';
1066 default:
1067 return 'time'; // really?
1068 }
1069 }
1070 }
1071 function buildFormattingFunc(standardDateProps, extendedSettings, context) {
1072 let standardDatePropCnt = Object.keys(standardDateProps).length;
1073 if (standardDatePropCnt === 1 && standardDateProps.timeZoneName === 'short') {
1074 return (date) => (formatTimeZoneOffset(date.timeZoneOffset));
1075 }
1076 if (standardDatePropCnt === 0 && extendedSettings.week) {
1077 return (date) => (formatWeekNumber(context.computeWeekNumber(date.marker), context.weekText, context.weekTextLong, context.locale, extendedSettings.week));
1078 }
1079 return buildNativeFormattingFunc(standardDateProps, extendedSettings, context);
1080 }
1081 function buildNativeFormattingFunc(standardDateProps, extendedSettings, context) {
1082 standardDateProps = Object.assign({}, standardDateProps); // copy
1083 extendedSettings = Object.assign({}, extendedSettings); // copy
1084 sanitizeSettings(standardDateProps, extendedSettings);
1085 standardDateProps.timeZone = 'UTC'; // we leverage the only guaranteed timeZone for our UTC markers
1086 let normalFormat = new Intl.DateTimeFormat(context.locale.codes, standardDateProps);
1087 let zeroFormat; // needed?
1088 if (extendedSettings.omitZeroMinute) {
1089 let zeroProps = Object.assign({}, standardDateProps);
1090 delete zeroProps.minute; // seconds and ms were already considered in sanitizeSettings
1091 zeroFormat = new Intl.DateTimeFormat(context.locale.codes, zeroProps);
1092 }
1093 return (date) => {
1094 let { marker } = date;
1095 let format;
1096 if (zeroFormat && !marker.getUTCMinutes()) {
1097 format = zeroFormat;
1098 }
1099 else {
1100 format = normalFormat;
1101 }
1102 let s = format.format(marker);
1103 return postProcess(s, date, standardDateProps, extendedSettings, context);
1104 };
1105 }
1106 function sanitizeSettings(standardDateProps, extendedSettings) {
1107 // deal with a browser inconsistency where formatting the timezone
1108 // requires that the hour/minute be present.
1109 if (standardDateProps.timeZoneName) {
1110 if (!standardDateProps.hour) {
1111 standardDateProps.hour = '2-digit';
1112 }
1113 if (!standardDateProps.minute) {
1114 standardDateProps.minute = '2-digit';
1115 }
1116 }
1117 // only support short timezone names
1118 if (standardDateProps.timeZoneName === 'long') {
1119 standardDateProps.timeZoneName = 'short';
1120 }
1121 // if requesting to display seconds, MUST display minutes
1122 if (extendedSettings.omitZeroMinute && (standardDateProps.second || standardDateProps.millisecond)) {
1123 delete extendedSettings.omitZeroMinute;
1124 }
1125 }
1126 function postProcess(s, date, standardDateProps, extendedSettings, context) {
1127 s = s.replace(LTR_RE, ''); // remove left-to-right control chars. do first. good for other regexes
1128 if (standardDateProps.timeZoneName === 'short') {
1129 s = injectTzoStr(s, (context.timeZone === 'UTC' || date.timeZoneOffset == null) ?
1130 'UTC' : // important to normalize for IE, which does "GMT"
1131 formatTimeZoneOffset(date.timeZoneOffset));
1132 }
1133 if (extendedSettings.omitCommas) {
1134 s = s.replace(COMMA_RE, '').trim();
1135 }
1136 if (extendedSettings.omitZeroMinute) {
1137 s = s.replace(':00', ''); // zeroFormat doesn't always achieve this
1138 }
1139 // ^ do anything that might create adjacent spaces before this point,
1140 // because MERIDIEM_RE likes to eat up loading spaces
1141 if (extendedSettings.meridiem === false) {
1142 s = s.replace(MERIDIEM_RE, '').trim();
1143 }
1144 else if (extendedSettings.meridiem === 'narrow') { // a/p
1145 s = s.replace(MERIDIEM_RE, (m0, m1) => m1.toLocaleLowerCase());
1146 }
1147 else if (extendedSettings.meridiem === 'short') { // am/pm
1148 s = s.replace(MERIDIEM_RE, (m0, m1) => `${m1.toLocaleLowerCase()}m`);
1149 }
1150 else if (extendedSettings.meridiem === 'lowercase') { // other meridiem transformers already converted to lowercase
1151 s = s.replace(MERIDIEM_RE, (m0) => m0.toLocaleLowerCase());
1152 }
1153 s = s.replace(MULTI_SPACE_RE, ' ');
1154 s = s.trim();
1155 return s;
1156 }
1157 function injectTzoStr(s, tzoStr) {
1158 let replaced = false;
1159 s = s.replace(UTC_RE, () => {
1160 replaced = true;
1161 return tzoStr;
1162 });
1163 // IE11 doesn't include UTC/GMT in the original string, so append to end
1164 if (!replaced) {
1165 s += ` ${tzoStr}`;
1166 }
1167 return s;
1168 }
1169 function formatWeekNumber(num, weekText, weekTextLong, locale, display) {
1170 let parts = [];
1171 if (display === 'long') {
1172 parts.push(weekTextLong);
1173 }
1174 else if (display === 'short' || display === 'narrow') {
1175 parts.push(weekText);
1176 }
1177 if (display === 'long' || display === 'short') {
1178 parts.push(' ');
1179 }
1180 parts.push(locale.simpleNumberFormat.format(num));
1181 if (locale.options.direction === 'rtl') { // TODO: use control characters instead?
1182 parts.reverse();
1183 }
1184 return parts.join('');
1185 }
1186 // Range Formatting Utils
1187 // 0 = exactly the same
1188 // 1 = different by time
1189 // and bigger
1190 function computeMarkerDiffSeverity(d0, d1, ca) {
1191 if (ca.getMarkerYear(d0) !== ca.getMarkerYear(d1)) {
1192 return 5;
1193 }
1194 if (ca.getMarkerMonth(d0) !== ca.getMarkerMonth(d1)) {
1195 return 4;
1196 }
1197 if (ca.getMarkerDay(d0) !== ca.getMarkerDay(d1)) {
1198 return 2;
1199 }
1200 if (timeAsMs(d0) !== timeAsMs(d1)) {
1201 return 1;
1202 }
1203 return 0;
1204 }
1205 function computePartialFormattingOptions(options, biggestUnit) {
1206 let partialOptions = {};
1207 for (let name in options) {
1208 if (!(name in STANDARD_DATE_PROP_SEVERITIES) || // not a date part prop (like timeZone)
1209 STANDARD_DATE_PROP_SEVERITIES[name] <= biggestUnit) {
1210 partialOptions[name] = options[name];
1211 }
1212 }
1213 return partialOptions;
1214 }
1215 function findCommonInsertion(full0, partial0, full1, partial1) {
1216 let i0 = 0;
1217 while (i0 < full0.length) {
1218 let found0 = full0.indexOf(partial0, i0);
1219 if (found0 === -1) {
1220 break;
1221 }
1222 let before0 = full0.substr(0, found0);
1223 i0 = found0 + partial0.length;
1224 let after0 = full0.substr(i0);
1225 let i1 = 0;
1226 while (i1 < full1.length) {
1227 let found1 = full1.indexOf(partial1, i1);
1228 if (found1 === -1) {
1229 break;
1230 }
1231 let before1 = full1.substr(0, found1);
1232 i1 = found1 + partial1.length;
1233 let after1 = full1.substr(i1);
1234 if (before0 === before1 && after0 === after1) {
1235 return {
1236 before: before0,
1237 after: after0,
1238 };
1239 }
1240 }
1241 }
1242 return null;
1243 }
1244
1245 function expandZonedMarker(dateInfo, calendarSystem) {
1246 let a = calendarSystem.markerToArray(dateInfo.marker);
1247 return {
1248 marker: dateInfo.marker,
1249 timeZoneOffset: dateInfo.timeZoneOffset,
1250 array: a,
1251 year: a[0],
1252 month: a[1],
1253 day: a[2],
1254 hour: a[3],
1255 minute: a[4],
1256 second: a[5],
1257 millisecond: a[6],
1258 };
1259 }
1260
1261 function createVerboseFormattingArg(start, end, context, betterDefaultSeparator) {
1262 let startInfo = expandZonedMarker(start, context.calendarSystem);
1263 let endInfo = end ? expandZonedMarker(end, context.calendarSystem) : null;
1264 return {
1265 date: startInfo,
1266 start: startInfo,
1267 end: endInfo,
1268 timeZone: context.timeZone,
1269 localeCodes: context.locale.codes,
1270 defaultSeparator: betterDefaultSeparator || context.defaultSeparator,
1271 };
1272 }
1273
1274 /*
1275 TODO: fix the terminology of "formatter" vs "formatting func"
1276 */
1277 /*
1278 At the time of instantiation, this object does not know which cmd-formatting system it will use.
1279 It receives this at the time of formatting, as a setting.
1280 */
1281 class CmdFormatter {
1282 constructor(cmdStr) {
1283 this.cmdStr = cmdStr;
1284 }
1285 format(date, context, betterDefaultSeparator) {
1286 return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(date, null, context, betterDefaultSeparator));
1287 }
1288 formatRange(start, end, context, betterDefaultSeparator) {
1289 return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(start, end, context, betterDefaultSeparator));
1290 }
1291 }
1292
1293 class FuncFormatter {
1294 constructor(func) {
1295 this.func = func;
1296 }
1297 format(date, context, betterDefaultSeparator) {
1298 return this.func(createVerboseFormattingArg(date, null, context, betterDefaultSeparator));
1299 }
1300 formatRange(start, end, context, betterDefaultSeparator) {
1301 return this.func(createVerboseFormattingArg(start, end, context, betterDefaultSeparator));
1302 }
1303 }
1304
1305 function createFormatter(input) {
1306 if (typeof input === 'object' && input) { // non-null object
1307 return new NativeFormatter(input);
1308 }
1309 if (typeof input === 'string') {
1310 return new CmdFormatter(input);
1311 }
1312 if (typeof input === 'function') {
1313 return new FuncFormatter(input);
1314 }
1315 return null;
1316 }
1317
1318 // base options
1319 // ------------
1320 const BASE_OPTION_REFINERS = {
1321 navLinkDayClick: identity,
1322 navLinkWeekClick: identity,
1323 duration: createDuration,
1324 bootstrapFontAwesome: identity,
1325 buttonIcons: identity,
1326 customButtons: identity,
1327 defaultAllDayEventDuration: createDuration,
1328 defaultTimedEventDuration: createDuration,
1329 nextDayThreshold: createDuration,
1330 scrollTime: createDuration,
1331 scrollTimeReset: Boolean,
1332 slotMinTime: createDuration,
1333 slotMaxTime: createDuration,
1334 dayPopoverFormat: createFormatter,
1335 slotDuration: createDuration,
1336 snapDuration: createDuration,
1337 headerToolbar: identity,
1338 footerToolbar: identity,
1339 defaultRangeSeparator: String,
1340 titleRangeSeparator: String,
1341 forceEventDuration: Boolean,
1342 dayHeaders: Boolean,
1343 dayHeaderFormat: createFormatter,
1344 dayHeaderClassNames: identity,
1345 dayHeaderContent: identity,
1346 dayHeaderDidMount: identity,
1347 dayHeaderWillUnmount: identity,
1348 dayCellClassNames: identity,
1349 dayCellContent: identity,
1350 dayCellDidMount: identity,
1351 dayCellWillUnmount: identity,
1352 initialView: String,
1353 aspectRatio: Number,
1354 weekends: Boolean,
1355 weekNumberCalculation: identity,
1356 weekNumbers: Boolean,
1357 weekNumberClassNames: identity,
1358 weekNumberContent: identity,
1359 weekNumberDidMount: identity,
1360 weekNumberWillUnmount: identity,
1361 editable: Boolean,
1362 viewClassNames: identity,
1363 viewDidMount: identity,
1364 viewWillUnmount: identity,
1365 nowIndicator: Boolean,
1366 nowIndicatorClassNames: identity,
1367 nowIndicatorContent: identity,
1368 nowIndicatorDidMount: identity,
1369 nowIndicatorWillUnmount: identity,
1370 showNonCurrentDates: Boolean,
1371 lazyFetching: Boolean,
1372 startParam: String,
1373 endParam: String,
1374 timeZoneParam: String,
1375 timeZone: String,
1376 locales: identity,
1377 locale: identity,
1378 themeSystem: String,
1379 dragRevertDuration: Number,
1380 dragScroll: Boolean,
1381 allDayMaintainDuration: Boolean,
1382 unselectAuto: Boolean,
1383 dropAccept: identity,
1384 eventOrder: parseFieldSpecs,
1385 eventOrderStrict: Boolean,
1386 handleWindowResize: Boolean,
1387 windowResizeDelay: Number,
1388 longPressDelay: Number,
1389 eventDragMinDistance: Number,
1390 expandRows: Boolean,
1391 height: identity,
1392 contentHeight: identity,
1393 direction: String,
1394 weekNumberFormat: createFormatter,
1395 eventResizableFromStart: Boolean,
1396 displayEventTime: Boolean,
1397 displayEventEnd: Boolean,
1398 weekText: String,
1399 weekTextLong: String,
1400 progressiveEventRendering: Boolean,
1401 businessHours: identity,
1402 initialDate: identity,
1403 now: identity,
1404 eventDataTransform: identity,
1405 stickyHeaderDates: identity,
1406 stickyFooterScrollbar: identity,
1407 viewHeight: identity,
1408 defaultAllDay: Boolean,
1409 eventSourceFailure: identity,
1410 eventSourceSuccess: identity,
1411 eventDisplay: String,
1412 eventStartEditable: Boolean,
1413 eventDurationEditable: Boolean,
1414 eventOverlap: identity,
1415 eventConstraint: identity,
1416 eventAllow: identity,
1417 eventBackgroundColor: String,
1418 eventBorderColor: String,
1419 eventTextColor: String,
1420 eventColor: String,
1421 eventClassNames: identity,
1422 eventContent: identity,
1423 eventDidMount: identity,
1424 eventWillUnmount: identity,
1425 selectConstraint: identity,
1426 selectOverlap: identity,
1427 selectAllow: identity,
1428 droppable: Boolean,
1429 unselectCancel: String,
1430 slotLabelFormat: identity,
1431 slotLaneClassNames: identity,
1432 slotLaneContent: identity,
1433 slotLaneDidMount: identity,
1434 slotLaneWillUnmount: identity,
1435 slotLabelClassNames: identity,
1436 slotLabelContent: identity,
1437 slotLabelDidMount: identity,
1438 slotLabelWillUnmount: identity,
1439 dayMaxEvents: identity,
1440 dayMaxEventRows: identity,
1441 dayMinWidth: Number,
1442 slotLabelInterval: createDuration,
1443 allDayText: String,
1444 allDayClassNames: identity,
1445 allDayContent: identity,
1446 allDayDidMount: identity,
1447 allDayWillUnmount: identity,
1448 slotMinWidth: Number,
1449 navLinks: Boolean,
1450 eventTimeFormat: createFormatter,
1451 rerenderDelay: Number,
1452 moreLinkText: identity,
1453 moreLinkHint: identity,
1454 selectMinDistance: Number,
1455 selectable: Boolean,
1456 selectLongPressDelay: Number,
1457 eventLongPressDelay: Number,
1458 selectMirror: Boolean,
1459 eventMaxStack: Number,
1460 eventMinHeight: Number,
1461 eventMinWidth: Number,
1462 eventShortHeight: Number,
1463 slotEventOverlap: Boolean,
1464 plugins: identity,
1465 firstDay: Number,
1466 dayCount: Number,
1467 dateAlignment: String,
1468 dateIncrement: createDuration,
1469 hiddenDays: identity,
1470 fixedWeekCount: Boolean,
1471 validRange: identity,
1472 visibleRange: identity,
1473 titleFormat: identity,
1474 eventInteractive: Boolean,
1475 // only used by list-view, but languages define the value, so we need it in base options
1476 noEventsText: String,
1477 viewHint: identity,
1478 navLinkHint: identity,
1479 closeHint: String,
1480 timeHint: String,
1481 eventHint: String,
1482 moreLinkClick: identity,
1483 moreLinkClassNames: identity,
1484 moreLinkContent: identity,
1485 moreLinkDidMount: identity,
1486 moreLinkWillUnmount: identity,
1487 monthStartFormat: createFormatter,
1488 // for connectors
1489 // (can't be part of plugin system b/c must be provided at runtime)
1490 handleCustomRendering: identity,
1491 customRenderingMetaMap: identity,
1492 customRenderingReplaces: Boolean,
1493 };
1494 // do NOT give a type here. need `typeof BASE_OPTION_DEFAULTS` to give real results.
1495 // raw values.
1496 const BASE_OPTION_DEFAULTS = {
1497 eventDisplay: 'auto',
1498 defaultRangeSeparator: ' - ',
1499 titleRangeSeparator: ' \u2013 ',
1500 defaultTimedEventDuration: '01:00:00',
1501 defaultAllDayEventDuration: { day: 1 },
1502 forceEventDuration: false,
1503 nextDayThreshold: '00:00:00',
1504 dayHeaders: true,
1505 initialView: '',
1506 aspectRatio: 1.35,
1507 headerToolbar: {
1508 start: 'title',
1509 center: '',
1510 end: 'today prev,next',
1511 },
1512 weekends: true,
1513 weekNumbers: false,
1514 weekNumberCalculation: 'local',
1515 editable: false,
1516 nowIndicator: false,
1517 scrollTime: '06:00:00',
1518 scrollTimeReset: true,
1519 slotMinTime: '00:00:00',
1520 slotMaxTime: '24:00:00',
1521 showNonCurrentDates: true,
1522 lazyFetching: true,
1523 startParam: 'start',
1524 endParam: 'end',
1525 timeZoneParam: 'timeZone',
1526 timeZone: 'local',
1527 locales: [],
1528 locale: '',
1529 themeSystem: 'standard',
1530 dragRevertDuration: 500,
1531 dragScroll: true,
1532 allDayMaintainDuration: false,
1533 unselectAuto: true,
1534 dropAccept: '*',
1535 eventOrder: 'start,-duration,allDay,title',
1536 dayPopoverFormat: { month: 'long', day: 'numeric', year: 'numeric' },
1537 handleWindowResize: true,
1538 windowResizeDelay: 100,
1539 longPressDelay: 1000,
1540 eventDragMinDistance: 5,
1541 expandRows: false,
1542 navLinks: false,
1543 selectable: false,
1544 eventMinHeight: 15,
1545 eventMinWidth: 30,
1546 eventShortHeight: 30,
1547 monthStartFormat: { month: 'long', day: 'numeric' },
1548 };
1549 // calendar listeners
1550 // ------------------
1551 const CALENDAR_LISTENER_REFINERS = {
1552 datesSet: identity,
1553 eventsSet: identity,
1554 eventAdd: identity,
1555 eventChange: identity,
1556 eventRemove: identity,
1557 windowResize: identity,
1558 eventClick: identity,
1559 eventMouseEnter: identity,
1560 eventMouseLeave: identity,
1561 select: identity,
1562 unselect: identity,
1563 loading: identity,
1564 // internal
1565 _unmount: identity,
1566 _beforeprint: identity,
1567 _afterprint: identity,
1568 _noEventDrop: identity,
1569 _noEventResize: identity,
1570 _resize: identity,
1571 _scrollRequest: identity,
1572 };
1573 // calendar-specific options
1574 // -------------------------
1575 const CALENDAR_OPTION_REFINERS = {
1576 buttonText: identity,
1577 buttonHints: identity,
1578 views: identity,
1579 plugins: identity,
1580 initialEvents: identity,
1581 events: identity,
1582 eventSources: identity,
1583 };
1584 const COMPLEX_OPTION_COMPARATORS = {
1585 headerToolbar: isMaybeObjectsEqual,
1586 footerToolbar: isMaybeObjectsEqual,
1587 buttonText: isMaybeObjectsEqual,
1588 buttonHints: isMaybeObjectsEqual,
1589 buttonIcons: isMaybeObjectsEqual,
1590 dateIncrement: isMaybeObjectsEqual,
1591 plugins: isMaybeArraysEqual,
1592 events: isMaybeArraysEqual,
1593 eventSources: isMaybeArraysEqual,
1594 ['resources']: isMaybeArraysEqual,
1595 };
1596 function isMaybeObjectsEqual(a, b) {
1597 if (typeof a === 'object' && typeof b === 'object' && a && b) { // both non-null objects
1598 return isPropsEqual(a, b);
1599 }
1600 return a === b;
1601 }
1602 function isMaybeArraysEqual(a, b) {
1603 if (Array.isArray(a) && Array.isArray(b)) {
1604 return isArraysEqual(a, b);
1605 }
1606 return a === b;
1607 }
1608 // view-specific options
1609 // ---------------------
1610 const VIEW_OPTION_REFINERS = {
1611 type: String,
1612 component: identity,
1613 buttonText: String,
1614 buttonTextKey: String,
1615 dateProfileGeneratorClass: identity,
1616 usesMinMaxTime: Boolean,
1617 classNames: identity,
1618 content: identity,
1619 didMount: identity,
1620 willUnmount: identity,
1621 };
1622 // util funcs
1623 // ----------------------------------------------------------------------------------------------------
1624 function mergeRawOptions(optionSets) {
1625 return mergeProps(optionSets, COMPLEX_OPTION_COMPARATORS);
1626 }
1627 function refineProps(input, refiners) {
1628 let refined = {};
1629 let extra = {};
1630 for (let propName in refiners) {
1631 if (propName in input) {
1632 refined[propName] = refiners[propName](input[propName]);
1633 }
1634 }
1635 for (let propName in input) {
1636 if (!(propName in refiners)) {
1637 extra[propName] = input[propName];
1638 }
1639 }
1640 return { refined, extra };
1641 }
1642 function identity(raw) {
1643 return raw;
1644 }
1645
1646 const { hasOwnProperty } = Object.prototype;
1647 // Merges an array of objects into a single object.
1648 // The second argument allows for an array of property names who's object values will be merged together.
1649 function mergeProps(propObjs, complexPropsMap) {
1650 let dest = {};
1651 if (complexPropsMap) {
1652 for (let name in complexPropsMap) {
1653 if (complexPropsMap[name] === isMaybeObjectsEqual) { // implies that it's object-mergeable
1654 let complexObjs = [];
1655 // collect the trailing object values, stopping when a non-object is discovered
1656 for (let i = propObjs.length - 1; i >= 0; i -= 1) {
1657 let val = propObjs[i][name];
1658 if (typeof val === 'object' && val) { // non-null object
1659 complexObjs.unshift(val);
1660 }
1661 else if (val !== undefined) {
1662 dest[name] = val; // if there were no objects, this value will be used
1663 break;
1664 }
1665 }
1666 // if the trailing values were objects, use the merged value
1667 if (complexObjs.length) {
1668 dest[name] = mergeProps(complexObjs);
1669 }
1670 }
1671 }
1672 }
1673 // copy values into the destination, going from last to first
1674 for (let i = propObjs.length - 1; i >= 0; i -= 1) {
1675 let props = propObjs[i];
1676 for (let name in props) {
1677 if (!(name in dest)) { // if already assigned by previous props or complex props, don't reassign
1678 dest[name] = props[name];
1679 }
1680 }
1681 }
1682 return dest;
1683 }
1684 function filterHash(hash, func) {
1685 let filtered = {};
1686 for (let key in hash) {
1687 if (func(hash[key], key)) {
1688 filtered[key] = hash[key];
1689 }
1690 }
1691 return filtered;
1692 }
1693 function mapHash(hash, func) {
1694 let newHash = {};
1695 for (let key in hash) {
1696 newHash[key] = func(hash[key], key);
1697 }
1698 return newHash;
1699 }
1700 function arrayToHash(a) {
1701 let hash = {};
1702 for (let item of a) {
1703 hash[item] = true;
1704 }
1705 return hash;
1706 }
1707 // TODO: reassess browser support
1708 // https://caniuse.com/?search=object.values
1709 function hashValuesToArray(obj) {
1710 let a = [];
1711 for (let key in obj) {
1712 a.push(obj[key]);
1713 }
1714 return a;
1715 }
1716 function isPropsEqual(obj0, obj1) {
1717 if (obj0 === obj1) {
1718 return true;
1719 }
1720 for (let key in obj0) {
1721 if (hasOwnProperty.call(obj0, key)) {
1722 if (!(key in obj1)) {
1723 return false;
1724 }
1725 }
1726 }
1727 for (let key in obj1) {
1728 if (hasOwnProperty.call(obj1, key)) {
1729 if (obj0[key] !== obj1[key]) {
1730 return false;
1731 }
1732 }
1733 }
1734 return true;
1735 }
1736 const HANDLER_RE = /^on[A-Z]/;
1737 function isNonHandlerPropsEqual(obj0, obj1) {
1738 const keys = getUnequalProps(obj0, obj1);
1739 for (let key of keys) {
1740 if (!HANDLER_RE.test(key)) {
1741 return false;
1742 }
1743 }
1744 return true;
1745 }
1746 function getUnequalProps(obj0, obj1) {
1747 let keys = [];
1748 for (let key in obj0) {
1749 if (hasOwnProperty.call(obj0, key)) {
1750 if (!(key in obj1)) {
1751 keys.push(key);
1752 }
1753 }
1754 }
1755 for (let key in obj1) {
1756 if (hasOwnProperty.call(obj1, key)) {
1757 if (obj0[key] !== obj1[key]) {
1758 keys.push(key);
1759 }
1760 }
1761 }
1762 return keys;
1763 }
1764 function compareObjs(oldProps, newProps, equalityFuncs = {}) {
1765 if (oldProps === newProps) {
1766 return true;
1767 }
1768 for (let key in newProps) {
1769 if (key in oldProps && isObjValsEqual(oldProps[key], newProps[key], equalityFuncs[key])) ;
1770 else {
1771 return false;
1772 }
1773 }
1774 // check for props that were omitted in the new
1775 for (let key in oldProps) {
1776 if (!(key in newProps)) {
1777 return false;
1778 }
1779 }
1780 return true;
1781 }
1782 /*
1783 assumed "true" equality for handler names like "onReceiveSomething"
1784 */
1785 function isObjValsEqual(val0, val1, comparator) {
1786 if (val0 === val1 || comparator === true) {
1787 return true;
1788 }
1789 if (comparator) {
1790 return comparator(val0, val1);
1791 }
1792 return false;
1793 }
1794 function collectFromHash(hash, startIndex = 0, endIndex, step = 1) {
1795 let res = [];
1796 if (endIndex == null) {
1797 endIndex = Object.keys(hash).length;
1798 }
1799 for (let i = startIndex; i < endIndex; i += step) {
1800 let val = hash[i];
1801 if (val !== undefined) { // will disregard undefined for sparse arrays
1802 res.push(val);
1803 }
1804 }
1805 return res;
1806 }
1807
1808 let calendarSystemClassMap = {};
1809 function registerCalendarSystem(name, theClass) {
1810 calendarSystemClassMap[name] = theClass;
1811 }
1812 function createCalendarSystem(name) {
1813 return new calendarSystemClassMap[name]();
1814 }
1815 class GregorianCalendarSystem {
1816 getMarkerYear(d) {
1817 return d.getUTCFullYear();
1818 }
1819 getMarkerMonth(d) {
1820 return d.getUTCMonth();
1821 }
1822 getMarkerDay(d) {
1823 return d.getUTCDate();
1824 }
1825 arrayToMarker(arr) {
1826 return arrayToUtcDate(arr);
1827 }
1828 markerToArray(marker) {
1829 return dateToUtcArray(marker);
1830 }
1831 }
1832 registerCalendarSystem('gregory', GregorianCalendarSystem);
1833
1834 const ISO_RE = /^\s*(\d{4})(-?(\d{2})(-?(\d{2})([T ](\d{2}):?(\d{2})(:?(\d{2})(\.(\d+))?)?(Z|(([-+])(\d{2})(:?(\d{2}))?))?)?)?)?$/;
1835 function parse(str) {
1836 let m = ISO_RE.exec(str);
1837 if (m) {
1838 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));
1839 if (isValidDate(marker)) {
1840 let timeZoneOffset = null;
1841 if (m[13]) {
1842 timeZoneOffset = (m[15] === '-' ? -1 : 1) * (Number(m[16] || 0) * 60 +
1843 Number(m[18] || 0));
1844 }
1845 return {
1846 marker,
1847 isTimeUnspecified: !m[6],
1848 timeZoneOffset,
1849 };
1850 }
1851 }
1852 return null;
1853 }
1854
1855 class DateEnv {
1856 constructor(settings) {
1857 let timeZone = this.timeZone = settings.timeZone;
1858 let isNamedTimeZone = timeZone !== 'local' && timeZone !== 'UTC';
1859 if (settings.namedTimeZoneImpl && isNamedTimeZone) {
1860 this.namedTimeZoneImpl = new settings.namedTimeZoneImpl(timeZone);
1861 }
1862 this.canComputeOffset = Boolean(!isNamedTimeZone || this.namedTimeZoneImpl);
1863 this.calendarSystem = createCalendarSystem(settings.calendarSystem);
1864 this.locale = settings.locale;
1865 this.weekDow = settings.locale.week.dow;
1866 this.weekDoy = settings.locale.week.doy;
1867 if (settings.weekNumberCalculation === 'ISO') {
1868 this.weekDow = 1;
1869 this.weekDoy = 4;
1870 }
1871 if (typeof settings.firstDay === 'number') {
1872 this.weekDow = settings.firstDay;
1873 }
1874 if (typeof settings.weekNumberCalculation === 'function') {
1875 this.weekNumberFunc = settings.weekNumberCalculation;
1876 }
1877 this.weekText = settings.weekText != null ? settings.weekText : settings.locale.options.weekText;
1878 this.weekTextLong = (settings.weekTextLong != null ? settings.weekTextLong : settings.locale.options.weekTextLong) || this.weekText;
1879 this.cmdFormatter = settings.cmdFormatter;
1880 this.defaultSeparator = settings.defaultSeparator;
1881 }
1882 // Creating / Parsing
1883 createMarker(input) {
1884 let meta = this.createMarkerMeta(input);
1885 if (meta === null) {
1886 return null;
1887 }
1888 return meta.marker;
1889 }
1890 createNowMarker() {
1891 if (this.canComputeOffset) {
1892 return this.timestampToMarker(new Date().valueOf());
1893 }
1894 // if we can't compute the current date val for a timezone,
1895 // better to give the current local date vals than UTC
1896 return arrayToUtcDate(dateToLocalArray(new Date()));
1897 }
1898 createMarkerMeta(input) {
1899 if (typeof input === 'string') {
1900 return this.parse(input);
1901 }
1902 let marker = null;
1903 if (typeof input === 'number') {
1904 marker = this.timestampToMarker(input);
1905 }
1906 else if (input instanceof Date) {
1907 input = input.valueOf();
1908 if (!isNaN(input)) {
1909 marker = this.timestampToMarker(input);
1910 }
1911 }
1912 else if (Array.isArray(input)) {
1913 marker = arrayToUtcDate(input);
1914 }
1915 if (marker === null || !isValidDate(marker)) {
1916 return null;
1917 }
1918 return { marker, isTimeUnspecified: false, forcedTzo: null };
1919 }
1920 parse(s) {
1921 let parts = parse(s);
1922 if (parts === null) {
1923 return null;
1924 }
1925 let { marker } = parts;
1926 let forcedTzo = null;
1927 if (parts.timeZoneOffset !== null) {
1928 if (this.canComputeOffset) {
1929 marker = this.timestampToMarker(marker.valueOf() - parts.timeZoneOffset * 60 * 1000);
1930 }
1931 else {
1932 forcedTzo = parts.timeZoneOffset;
1933 }
1934 }
1935 return { marker, isTimeUnspecified: parts.isTimeUnspecified, forcedTzo };
1936 }
1937 // Accessors
1938 getYear(marker) {
1939 return this.calendarSystem.getMarkerYear(marker);
1940 }
1941 getMonth(marker) {
1942 return this.calendarSystem.getMarkerMonth(marker);
1943 }
1944 getDay(marker) {
1945 return this.calendarSystem.getMarkerDay(marker);
1946 }
1947 // Adding / Subtracting
1948 add(marker, dur) {
1949 let a = this.calendarSystem.markerToArray(marker);
1950 a[0] += dur.years;
1951 a[1] += dur.months;
1952 a[2] += dur.days;
1953 a[6] += dur.milliseconds;
1954 return this.calendarSystem.arrayToMarker(a);
1955 }
1956 subtract(marker, dur) {
1957 let a = this.calendarSystem.markerToArray(marker);
1958 a[0] -= dur.years;
1959 a[1] -= dur.months;
1960 a[2] -= dur.days;
1961 a[6] -= dur.milliseconds;
1962 return this.calendarSystem.arrayToMarker(a);
1963 }
1964 addYears(marker, n) {
1965 let a = this.calendarSystem.markerToArray(marker);
1966 a[0] += n;
1967 return this.calendarSystem.arrayToMarker(a);
1968 }
1969 addMonths(marker, n) {
1970 let a = this.calendarSystem.markerToArray(marker);
1971 a[1] += n;
1972 return this.calendarSystem.arrayToMarker(a);
1973 }
1974 // Diffing Whole Units
1975 diffWholeYears(m0, m1) {
1976 let { calendarSystem } = this;
1977 if (timeAsMs(m0) === timeAsMs(m1) &&
1978 calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1) &&
1979 calendarSystem.getMarkerMonth(m0) === calendarSystem.getMarkerMonth(m1)) {
1980 return calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0);
1981 }
1982 return null;
1983 }
1984 diffWholeMonths(m0, m1) {
1985 let { calendarSystem } = this;
1986 if (timeAsMs(m0) === timeAsMs(m1) &&
1987 calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1)) {
1988 return (calendarSystem.getMarkerMonth(m1) - calendarSystem.getMarkerMonth(m0)) +
1989 (calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0)) * 12;
1990 }
1991 return null;
1992 }
1993 // Range / Duration
1994 greatestWholeUnit(m0, m1) {
1995 let n = this.diffWholeYears(m0, m1);
1996 if (n !== null) {
1997 return { unit: 'year', value: n };
1998 }
1999 n = this.diffWholeMonths(m0, m1);
2000 if (n !== null) {
2001 return { unit: 'month', value: n };
2002 }
2003 n = diffWholeWeeks(m0, m1);
2004 if (n !== null) {
2005 return { unit: 'week', value: n };
2006 }
2007 n = diffWholeDays(m0, m1);
2008 if (n !== null) {
2009 return { unit: 'day', value: n };
2010 }
2011 n = diffHours(m0, m1);
2012 if (isInt(n)) {
2013 return { unit: 'hour', value: n };
2014 }
2015 n = diffMinutes(m0, m1);
2016 if (isInt(n)) {
2017 return { unit: 'minute', value: n };
2018 }
2019 n = diffSeconds(m0, m1);
2020 if (isInt(n)) {
2021 return { unit: 'second', value: n };
2022 }
2023 return { unit: 'millisecond', value: m1.valueOf() - m0.valueOf() };
2024 }
2025 countDurationsBetween(m0, m1, d) {
2026 // TODO: can use greatestWholeUnit
2027 let diff;
2028 if (d.years) {
2029 diff = this.diffWholeYears(m0, m1);
2030 if (diff !== null) {
2031 return diff / asRoughYears(d);
2032 }
2033 }
2034 if (d.months) {
2035 diff = this.diffWholeMonths(m0, m1);
2036 if (diff !== null) {
2037 return diff / asRoughMonths(d);
2038 }
2039 }
2040 if (d.days) {
2041 diff = diffWholeDays(m0, m1);
2042 if (diff !== null) {
2043 return diff / asRoughDays(d);
2044 }
2045 }
2046 return (m1.valueOf() - m0.valueOf()) / asRoughMs(d);
2047 }
2048 // Start-Of
2049 // these DON'T return zoned-dates. only UTC start-of dates
2050 startOf(m, unit) {
2051 if (unit === 'year') {
2052 return this.startOfYear(m);
2053 }
2054 if (unit === 'month') {
2055 return this.startOfMonth(m);
2056 }
2057 if (unit === 'week') {
2058 return this.startOfWeek(m);
2059 }
2060 if (unit === 'day') {
2061 return startOfDay(m);
2062 }
2063 if (unit === 'hour') {
2064 return startOfHour(m);
2065 }
2066 if (unit === 'minute') {
2067 return startOfMinute(m);
2068 }
2069 if (unit === 'second') {
2070 return startOfSecond(m);
2071 }
2072 return null;
2073 }
2074 startOfYear(m) {
2075 return this.calendarSystem.arrayToMarker([
2076 this.calendarSystem.getMarkerYear(m),
2077 ]);
2078 }
2079 startOfMonth(m) {
2080 return this.calendarSystem.arrayToMarker([
2081 this.calendarSystem.getMarkerYear(m),
2082 this.calendarSystem.getMarkerMonth(m),
2083 ]);
2084 }
2085 startOfWeek(m) {
2086 return this.calendarSystem.arrayToMarker([
2087 this.calendarSystem.getMarkerYear(m),
2088 this.calendarSystem.getMarkerMonth(m),
2089 m.getUTCDate() - ((m.getUTCDay() - this.weekDow + 7) % 7),
2090 ]);
2091 }
2092 // Week Number
2093 computeWeekNumber(marker) {
2094 if (this.weekNumberFunc) {
2095 return this.weekNumberFunc(this.toDate(marker));
2096 }
2097 return weekOfYear(marker, this.weekDow, this.weekDoy);
2098 }
2099 // TODO: choke on timeZoneName: long
2100 format(marker, formatter, dateOptions = {}) {
2101 return formatter.format({
2102 marker,
2103 timeZoneOffset: dateOptions.forcedTzo != null ?
2104 dateOptions.forcedTzo :
2105 this.offsetForMarker(marker),
2106 }, this);
2107 }
2108 formatRange(start, end, formatter, dateOptions = {}) {
2109 if (dateOptions.isEndExclusive) {
2110 end = addMs(end, -1);
2111 }
2112 return formatter.formatRange({
2113 marker: start,
2114 timeZoneOffset: dateOptions.forcedStartTzo != null ?
2115 dateOptions.forcedStartTzo :
2116 this.offsetForMarker(start),
2117 }, {
2118 marker: end,
2119 timeZoneOffset: dateOptions.forcedEndTzo != null ?
2120 dateOptions.forcedEndTzo :
2121 this.offsetForMarker(end),
2122 }, this, dateOptions.defaultSeparator);
2123 }
2124 /*
2125 DUMB: the omitTime arg is dumb. if we omit the time, we want to omit the timezone offset. and if we do that,
2126 might as well use buildIsoString or some other util directly
2127 */
2128 formatIso(marker, extraOptions = {}) {
2129 let timeZoneOffset = null;
2130 if (!extraOptions.omitTimeZoneOffset) {
2131 if (extraOptions.forcedTzo != null) {
2132 timeZoneOffset = extraOptions.forcedTzo;
2133 }
2134 else {
2135 timeZoneOffset = this.offsetForMarker(marker);
2136 }
2137 }
2138 return buildIsoString(marker, timeZoneOffset, extraOptions.omitTime);
2139 }
2140 // TimeZone
2141 timestampToMarker(ms) {
2142 if (this.timeZone === 'local') {
2143 return arrayToUtcDate(dateToLocalArray(new Date(ms)));
2144 }
2145 if (this.timeZone === 'UTC' || !this.namedTimeZoneImpl) {
2146 return new Date(ms);
2147 }
2148 return arrayToUtcDate(this.namedTimeZoneImpl.timestampToArray(ms));
2149 }
2150 offsetForMarker(m) {
2151 if (this.timeZone === 'local') {
2152 return -arrayToLocalDate(dateToUtcArray(m)).getTimezoneOffset(); // convert "inverse" offset to "normal" offset
2153 }
2154 if (this.timeZone === 'UTC') {
2155 return 0;
2156 }
2157 if (this.namedTimeZoneImpl) {
2158 return this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m));
2159 }
2160 return null;
2161 }
2162 // Conversion
2163 toDate(m, forcedTzo) {
2164 if (this.timeZone === 'local') {
2165 return arrayToLocalDate(dateToUtcArray(m));
2166 }
2167 if (this.timeZone === 'UTC') {
2168 return new Date(m.valueOf()); // make sure it's a copy
2169 }
2170 if (!this.namedTimeZoneImpl) {
2171 return new Date(m.valueOf() - (forcedTzo || 0));
2172 }
2173 return new Date(m.valueOf() -
2174 this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m)) * 1000 * 60);
2175 }
2176 }
2177
2178 class Theme {
2179 constructor(calendarOptions) {
2180 if (this.iconOverrideOption) {
2181 this.setIconOverride(calendarOptions[this.iconOverrideOption]);
2182 }
2183 }
2184 setIconOverride(iconOverrideHash) {
2185 let iconClassesCopy;
2186 let buttonName;
2187 if (typeof iconOverrideHash === 'object' && iconOverrideHash) { // non-null object
2188 iconClassesCopy = Object.assign({}, this.iconClasses);
2189 for (buttonName in iconOverrideHash) {
2190 iconClassesCopy[buttonName] = this.applyIconOverridePrefix(iconOverrideHash[buttonName]);
2191 }
2192 this.iconClasses = iconClassesCopy;
2193 }
2194 else if (iconOverrideHash === false) {
2195 this.iconClasses = {};
2196 }
2197 }
2198 applyIconOverridePrefix(className) {
2199 let prefix = this.iconOverridePrefix;
2200 if (prefix && className.indexOf(prefix) !== 0) { // if not already present
2201 className = prefix + className;
2202 }
2203 return className;
2204 }
2205 getClass(key) {
2206 return this.classes[key] || '';
2207 }
2208 getIconClass(buttonName, isRtl) {
2209 let className;
2210 if (isRtl && this.rtlIconClasses) {
2211 className = this.rtlIconClasses[buttonName] || this.iconClasses[buttonName];
2212 }
2213 else {
2214 className = this.iconClasses[buttonName];
2215 }
2216 if (className) {
2217 return `${this.baseIconClass} ${className}`;
2218 }
2219 return '';
2220 }
2221 getCustomButtonIconClass(customButtonProps) {
2222 let className;
2223 if (this.iconOverrideCustomButtonOption) {
2224 className = customButtonProps[this.iconOverrideCustomButtonOption];
2225 if (className) {
2226 return `${this.baseIconClass} ${this.applyIconOverridePrefix(className)}`;
2227 }
2228 }
2229 return '';
2230 }
2231 }
2232 Theme.prototype.classes = {};
2233 Theme.prototype.iconClasses = {};
2234 Theme.prototype.baseIconClass = '';
2235 Theme.prototype.iconOverridePrefix = '';
2236
2237 /*
2238 NOTE: this can be a public API, especially createElement for hooks.
2239 See examples/typescript-scheduler/src/index.ts
2240 */
2241 function flushSync(runBeforeFlush) {
2242 runBeforeFlush();
2243 let oldDebounceRendering = l$1.debounceRendering; // orig
2244 let callbackQ = [];
2245 function execCallbackSync(callback) {
2246 callbackQ.push(callback);
2247 }
2248 l$1.debounceRendering = execCallbackSync;
2249 D$1(y(FakeComponent, {}), document.createElement('div'));
2250 while (callbackQ.length) {
2251 callbackQ.shift()();
2252 }
2253 l$1.debounceRendering = oldDebounceRendering;
2254 }
2255 class FakeComponent extends x$1 {
2256 render() { return y('div', {}); }
2257 componentDidMount() { this.setState({}); }
2258 }
2259 // TODO: use preact/compat instead?
2260 function createContext(defaultValue) {
2261 let ContextType = G$1(defaultValue);
2262 let origProvider = ContextType.Provider;
2263 ContextType.Provider = function () {
2264 let isNew = !this.getChildContext;
2265 let children = origProvider.apply(this, arguments); // eslint-disable-line prefer-rest-params
2266 if (isNew) {
2267 let subs = [];
2268 this.shouldComponentUpdate = (_props) => {
2269 if (this.props.value !== _props.value) {
2270 subs.forEach((c) => {
2271 c.context = _props.value;
2272 c.forceUpdate();
2273 });
2274 }
2275 };
2276 this.sub = (c) => {
2277 subs.push(c);
2278 let old = c.componentWillUnmount;
2279 c.componentWillUnmount = () => {
2280 subs.splice(subs.indexOf(c), 1);
2281 old && old.call(c);
2282 };
2283 };
2284 }
2285 return children;
2286 };
2287 return ContextType;
2288 }
2289
2290 class ScrollResponder {
2291 constructor(execFunc, emitter, scrollTime, scrollTimeReset) {
2292 this.execFunc = execFunc;
2293 this.emitter = emitter;
2294 this.scrollTime = scrollTime;
2295 this.scrollTimeReset = scrollTimeReset;
2296 this.handleScrollRequest = (request) => {
2297 this.queuedRequest = Object.assign({}, this.queuedRequest || {}, request);
2298 this.drain();
2299 };
2300 emitter.on('_scrollRequest', this.handleScrollRequest);
2301 this.fireInitialScroll();
2302 }
2303 detach() {
2304 this.emitter.off('_scrollRequest', this.handleScrollRequest);
2305 }
2306 update(isDatesNew) {
2307 if (isDatesNew && this.scrollTimeReset) {
2308 this.fireInitialScroll(); // will drain
2309 }
2310 else {
2311 this.drain();
2312 }
2313 }
2314 fireInitialScroll() {
2315 this.handleScrollRequest({
2316 time: this.scrollTime,
2317 });
2318 }
2319 drain() {
2320 if (this.queuedRequest && this.execFunc(this.queuedRequest)) {
2321 this.queuedRequest = null;
2322 }
2323 }
2324 }
2325
2326 const ViewContextType = createContext({}); // for Components
2327 function buildViewContext(viewSpec, viewApi, viewOptions, dateProfileGenerator, dateEnv, nowManager, theme, pluginHooks, dispatch, getCurrentData, emitter, calendarApi, registerInteractiveComponent, unregisterInteractiveComponent) {
2328 return {
2329 dateEnv,
2330 nowManager,
2331 options: viewOptions,
2332 pluginHooks,
2333 emitter,
2334 dispatch,
2335 getCurrentData,
2336 calendarApi,
2337 viewSpec,
2338 viewApi,
2339 dateProfileGenerator,
2340 theme,
2341 isRtl: viewOptions.direction === 'rtl',
2342 addResizeHandler(handler) {
2343 emitter.on('_resize', handler);
2344 },
2345 removeResizeHandler(handler) {
2346 emitter.off('_resize', handler);
2347 },
2348 createScrollResponder(execFunc) {
2349 return new ScrollResponder(execFunc, emitter, createDuration(viewOptions.scrollTime), viewOptions.scrollTimeReset);
2350 },
2351 registerInteractiveComponent,
2352 unregisterInteractiveComponent,
2353 };
2354 }
2355
2356 /* eslint max-classes-per-file: off */
2357 class PureComponent extends x$1 {
2358 shouldComponentUpdate(nextProps, nextState) {
2359 if (this.debug) {
2360 // eslint-disable-next-line no-console
2361 console.log(getUnequalProps(nextProps, this.props), getUnequalProps(nextState, this.state));
2362 }
2363 return !compareObjs(this.props, nextProps, this.propEquality) ||
2364 !compareObjs(this.state, nextState, this.stateEquality);
2365 }
2366 // HACK for freakin' React StrictMode
2367 safeSetState(newState) {
2368 if (!compareObjs(this.state, Object.assign(Object.assign({}, this.state), newState), this.stateEquality)) {
2369 this.setState(newState);
2370 }
2371 }
2372 }
2373 PureComponent.addPropsEquality = addPropsEquality;
2374 PureComponent.addStateEquality = addStateEquality;
2375 PureComponent.contextType = ViewContextType;
2376 PureComponent.prototype.propEquality = {};
2377 PureComponent.prototype.stateEquality = {};
2378 class BaseComponent extends PureComponent {
2379 }
2380 BaseComponent.contextType = ViewContextType;
2381 function addPropsEquality(propEquality) {
2382 let hash = Object.create(this.prototype.propEquality);
2383 Object.assign(hash, propEquality);
2384 this.prototype.propEquality = hash;
2385 }
2386 function addStateEquality(stateEquality) {
2387 let hash = Object.create(this.prototype.stateEquality);
2388 Object.assign(hash, stateEquality);
2389 this.prototype.stateEquality = hash;
2390 }
2391 // use other one
2392 function setRef(ref, current) {
2393 if (typeof ref === 'function') {
2394 ref(current);
2395 }
2396 else if (ref) {
2397 // see https://github.com/facebook/react/issues/13029
2398 ref.current = current;
2399 }
2400 }
2401
2402 class ContentInjector extends BaseComponent {
2403 constructor() {
2404 super(...arguments);
2405 this.id = guid();
2406 this.queuedDomNodes = [];
2407 this.currentDomNodes = [];
2408 this.handleEl = (el) => {
2409 const { options } = this.context;
2410 const { generatorName } = this.props;
2411 if (!options.customRenderingReplaces || !hasCustomRenderingHandler(generatorName, options)) {
2412 this.updateElRef(el);
2413 }
2414 };
2415 this.updateElRef = (el) => {
2416 if (this.props.elRef) {
2417 setRef(this.props.elRef, el);
2418 }
2419 };
2420 }
2421 render() {
2422 const { props, context } = this;
2423 const { options } = context;
2424 const { customGenerator, defaultGenerator, renderProps } = props;
2425 const attrs = buildElAttrs(props, [], this.handleEl);
2426 let useDefault = false;
2427 let innerContent;
2428 let queuedDomNodes = [];
2429 let currentGeneratorMeta;
2430 if (customGenerator != null) {
2431 const customGeneratorRes = typeof customGenerator === 'function' ?
2432 customGenerator(renderProps, y) :
2433 customGenerator;
2434 if (customGeneratorRes === true) {
2435 useDefault = true;
2436 }
2437 else {
2438 const isObject = customGeneratorRes && typeof customGeneratorRes === 'object'; // non-null
2439 if (isObject && ('html' in customGeneratorRes)) {
2440 attrs.dangerouslySetInnerHTML = { __html: customGeneratorRes.html };
2441 }
2442 else if (isObject && ('domNodes' in customGeneratorRes)) {
2443 queuedDomNodes = Array.prototype.slice.call(customGeneratorRes.domNodes);
2444 }
2445 else if (isObject
2446 ? i$1(customGeneratorRes) // vdom node
2447 : typeof customGeneratorRes !== 'function' // primitive value (like string or number)
2448 ) {
2449 // use in vdom
2450 innerContent = customGeneratorRes;
2451 }
2452 else {
2453 // an exotic object for handleCustomRendering
2454 currentGeneratorMeta = customGeneratorRes;
2455 }
2456 }
2457 }
2458 else {
2459 useDefault = !hasCustomRenderingHandler(props.generatorName, options);
2460 }
2461 if (useDefault && defaultGenerator) {
2462 innerContent = defaultGenerator(renderProps);
2463 }
2464 this.queuedDomNodes = queuedDomNodes;
2465 this.currentGeneratorMeta = currentGeneratorMeta;
2466 return y(props.elTag, attrs, innerContent);
2467 }
2468 componentDidMount() {
2469 this.applyQueueudDomNodes();
2470 this.triggerCustomRendering(true);
2471 }
2472 componentDidUpdate() {
2473 this.applyQueueudDomNodes();
2474 this.triggerCustomRendering(true);
2475 }
2476 componentWillUnmount() {
2477 this.triggerCustomRendering(false); // TODO: different API for removal?
2478 }
2479 triggerCustomRendering(isActive) {
2480 var _a;
2481 const { props, context } = this;
2482 const { handleCustomRendering, customRenderingMetaMap } = context.options;
2483 if (handleCustomRendering) {
2484 const generatorMeta = (_a = this.currentGeneratorMeta) !== null && _a !== void 0 ? _a : customRenderingMetaMap === null || customRenderingMetaMap === void 0 ? void 0 : customRenderingMetaMap[props.generatorName];
2485 if (generatorMeta) {
2486 handleCustomRendering(Object.assign(Object.assign({ id: this.id, isActive, containerEl: this.base, reportNewContainerEl: this.updateElRef, // front-end framework tells us about new container els
2487 generatorMeta }, props), { elClasses: (props.elClasses || []).filter(isTruthy) }));
2488 }
2489 }
2490 }
2491 applyQueueudDomNodes() {
2492 const { queuedDomNodes, currentDomNodes } = this;
2493 const el = this.base;
2494 if (!isArraysEqual(queuedDomNodes, currentDomNodes)) {
2495 currentDomNodes.forEach(removeElement);
2496 for (let newNode of queuedDomNodes) {
2497 el.appendChild(newNode);
2498 }
2499 this.currentDomNodes = queuedDomNodes;
2500 }
2501 }
2502 }
2503 ContentInjector.addPropsEquality({
2504 elClasses: isArraysEqual,
2505 elStyle: isPropsEqual,
2506 elAttrs: isNonHandlerPropsEqual,
2507 renderProps: isPropsEqual,
2508 });
2509 // Util
2510 /*
2511 Does UI-framework provide custom way of rendering that does not use Preact VDOM
2512 AND does the calendar's options define custom rendering?
2513 AKA. Should we NOT render the default content?
2514 */
2515 function hasCustomRenderingHandler(generatorName, options) {
2516 var _a;
2517 return Boolean(options.handleCustomRendering &&
2518 generatorName &&
2519 ((_a = options.customRenderingMetaMap) === null || _a === void 0 ? void 0 : _a[generatorName]));
2520 }
2521 function buildElAttrs(props, extraClassNames, elRef) {
2522 const attrs = Object.assign(Object.assign({}, props.elAttrs), { ref: elRef });
2523 if (props.elClasses || extraClassNames) {
2524 attrs.className = (props.elClasses || [])
2525 .concat(extraClassNames || [])
2526 .concat(attrs.className || [])
2527 .filter(Boolean)
2528 .join(' ');
2529 }
2530 if (props.elStyle) {
2531 attrs.style = props.elStyle;
2532 }
2533 return attrs;
2534 }
2535 function isTruthy(val) {
2536 return Boolean(val);
2537 }
2538
2539 const RenderId = createContext(0);
2540
2541 class ContentContainer extends x$1 {
2542 constructor() {
2543 super(...arguments);
2544 this.InnerContent = InnerContentInjector.bind(undefined, this);
2545 this.handleEl = (el) => {
2546 this.el = el;
2547 if (this.props.elRef) {
2548 setRef(this.props.elRef, el);
2549 if (el && this.didMountMisfire) {
2550 this.componentDidMount();
2551 }
2552 }
2553 };
2554 }
2555 render() {
2556 const { props } = this;
2557 const generatedClassNames = generateClassNames(props.classNameGenerator, props.renderProps);
2558 if (props.children) {
2559 const elAttrs = buildElAttrs(props, generatedClassNames, this.handleEl);
2560 const children = props.children(this.InnerContent, props.renderProps, elAttrs);
2561 if (props.elTag) {
2562 return y(props.elTag, elAttrs, children);
2563 }
2564 else {
2565 return children;
2566 }
2567 }
2568 else {
2569 return y((ContentInjector), Object.assign(Object.assign({}, props), { elRef: this.handleEl, elTag: props.elTag || 'div', elClasses: (props.elClasses || []).concat(generatedClassNames), renderId: this.context }));
2570 }
2571 }
2572 componentDidMount() {
2573 var _a, _b;
2574 if (this.el) {
2575 (_b = (_a = this.props).didMount) === null || _b === void 0 ? void 0 : _b.call(_a, Object.assign(Object.assign({}, this.props.renderProps), { el: this.el }));
2576 }
2577 else {
2578 this.didMountMisfire = true;
2579 }
2580 }
2581 componentWillUnmount() {
2582 var _a, _b;
2583 (_b = (_a = this.props).willUnmount) === null || _b === void 0 ? void 0 : _b.call(_a, Object.assign(Object.assign({}, this.props.renderProps), { el: this.el }));
2584 }
2585 }
2586 ContentContainer.contextType = RenderId;
2587 function InnerContentInjector(containerComponent, props) {
2588 const parentProps = containerComponent.props;
2589 return y((ContentInjector), Object.assign({ renderProps: parentProps.renderProps, generatorName: parentProps.generatorName, customGenerator: parentProps.customGenerator, defaultGenerator: parentProps.defaultGenerator, renderId: containerComponent.context }, props));
2590 }
2591 // Utils
2592 function generateClassNames(classNameGenerator, renderProps) {
2593 const classNames = typeof classNameGenerator === 'function' ?
2594 classNameGenerator(renderProps) :
2595 classNameGenerator || [];
2596 return typeof classNames === 'string' ? [classNames] : classNames;
2597 }
2598
2599 class ViewContainer extends BaseComponent {
2600 render() {
2601 let { props, context } = this;
2602 let { options } = context;
2603 let renderProps = { view: context.viewApi };
2604 return (y(ContentContainer, Object.assign({}, props, { elTag: props.elTag || 'div', elClasses: [
2605 ...buildViewClassNames(props.viewSpec),
2606 ...(props.elClasses || []),
2607 ], renderProps: renderProps, classNameGenerator: options.viewClassNames, generatorName: undefined, didMount: options.viewDidMount, willUnmount: options.viewWillUnmount }), () => props.children));
2608 }
2609 }
2610 function buildViewClassNames(viewSpec) {
2611 return [
2612 `fc-${viewSpec.type}-view`,
2613 'fc-view',
2614 ];
2615 }
2616
2617 function parseRange(input, dateEnv) {
2618 let start = null;
2619 let end = null;
2620 if (input.start) {
2621 start = dateEnv.createMarker(input.start);
2622 }
2623 if (input.end) {
2624 end = dateEnv.createMarker(input.end);
2625 }
2626 if (!start && !end) {
2627 return null;
2628 }
2629 if (start && end && end < start) {
2630 return null;
2631 }
2632 return { start, end };
2633 }
2634 // SIDE-EFFECT: will mutate ranges.
2635 // Will return a new array result.
2636 function invertRanges(ranges, constraintRange) {
2637 let invertedRanges = [];
2638 let { start } = constraintRange; // the end of the previous range. the start of the new range
2639 let i;
2640 let dateRange;
2641 // ranges need to be in order. required for our date-walking algorithm
2642 ranges.sort(compareRanges);
2643 for (i = 0; i < ranges.length; i += 1) {
2644 dateRange = ranges[i];
2645 // add the span of time before the event (if there is any)
2646 if (dateRange.start > start) { // compare millisecond time (skip any ambig logic)
2647 invertedRanges.push({ start, end: dateRange.start });
2648 }
2649 if (dateRange.end > start) {
2650 start = dateRange.end;
2651 }
2652 }
2653 // add the span of time after the last event (if there is any)
2654 if (start < constraintRange.end) { // compare millisecond time (skip any ambig logic)
2655 invertedRanges.push({ start, end: constraintRange.end });
2656 }
2657 return invertedRanges;
2658 }
2659 function compareRanges(range0, range1) {
2660 return range0.start.valueOf() - range1.start.valueOf(); // earlier ranges go first
2661 }
2662 function intersectRanges(range0, range1) {
2663 let { start, end } = range0;
2664 let newRange = null;
2665 if (range1.start !== null) {
2666 if (start === null) {
2667 start = range1.start;
2668 }
2669 else {
2670 start = new Date(Math.max(start.valueOf(), range1.start.valueOf()));
2671 }
2672 }
2673 if (range1.end != null) {
2674 if (end === null) {
2675 end = range1.end;
2676 }
2677 else {
2678 end = new Date(Math.min(end.valueOf(), range1.end.valueOf()));
2679 }
2680 }
2681 if (start === null || end === null || start < end) {
2682 newRange = { start, end };
2683 }
2684 return newRange;
2685 }
2686 function rangesEqual(range0, range1) {
2687 return (range0.start === null ? null : range0.start.valueOf()) === (range1.start === null ? null : range1.start.valueOf()) &&
2688 (range0.end === null ? null : range0.end.valueOf()) === (range1.end === null ? null : range1.end.valueOf());
2689 }
2690 function rangesIntersect(range0, range1) {
2691 return (range0.end === null || range1.start === null || range0.end > range1.start) &&
2692 (range0.start === null || range1.end === null || range0.start < range1.end);
2693 }
2694 function rangeContainsRange(outerRange, innerRange) {
2695 return (outerRange.start === null || (innerRange.start !== null && innerRange.start >= outerRange.start)) &&
2696 (outerRange.end === null || (innerRange.end !== null && innerRange.end <= outerRange.end));
2697 }
2698 function rangeContainsMarker(range, date) {
2699 return (range.start === null || date >= range.start) &&
2700 (range.end === null || date < range.end);
2701 }
2702 // If the given date is not within the given range, move it inside.
2703 // (If it's past the end, make it one millisecond before the end).
2704 function constrainMarkerToRange(date, range) {
2705 if (range.start != null && date < range.start) {
2706 return range.start;
2707 }
2708 if (range.end != null && date >= range.end) {
2709 return new Date(range.end.valueOf() - 1);
2710 }
2711 return date;
2712 }
2713
2714 /* Date stuff that doesn't belong in datelib core
2715 ----------------------------------------------------------------------------------------------------------------------*/
2716 // given a timed range, computes an all-day range that has the same exact duration,
2717 // but whose start time is aligned with the start of the day.
2718 function computeAlignedDayRange(timedRange) {
2719 let dayCnt = Math.floor(diffDays(timedRange.start, timedRange.end)) || 1;
2720 let start = startOfDay(timedRange.start);
2721 let end = addDays(start, dayCnt);
2722 return { start, end };
2723 }
2724 // given a timed range, computes an all-day range based on how for the end date bleeds into the next day
2725 // TODO: give nextDayThreshold a default arg
2726 function computeVisibleDayRange(timedRange, nextDayThreshold = createDuration(0)) {
2727 let startDay = null;
2728 let endDay = null;
2729 if (timedRange.end) {
2730 endDay = startOfDay(timedRange.end);
2731 let endTimeMS = timedRange.end.valueOf() - endDay.valueOf(); // # of milliseconds into `endDay`
2732 // If the end time is actually inclusively part of the next day and is equal to or
2733 // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`.
2734 // Otherwise, leaving it as inclusive will cause it to exclude `endDay`.
2735 if (endTimeMS && endTimeMS >= asRoughMs(nextDayThreshold)) {
2736 endDay = addDays(endDay, 1);
2737 }
2738 }
2739 if (timedRange.start) {
2740 startDay = startOfDay(timedRange.start); // the beginning of the day the range starts
2741 // If end is within `startDay` but not past nextDayThreshold, assign the default duration of one day.
2742 if (endDay && endDay <= startDay) {
2743 endDay = addDays(startDay, 1);
2744 }
2745 }
2746 return { start: startDay, end: endDay };
2747 }
2748 // spans from one day into another?
2749 function isMultiDayRange(range) {
2750 let visibleRange = computeVisibleDayRange(range);
2751 return diffDays(visibleRange.start, visibleRange.end) > 1;
2752 }
2753 function diffDates(date0, date1, dateEnv, largeUnit) {
2754 if (largeUnit === 'year') {
2755 return createDuration(dateEnv.diffWholeYears(date0, date1), 'year');
2756 }
2757 if (largeUnit === 'month') {
2758 return createDuration(dateEnv.diffWholeMonths(date0, date1), 'month');
2759 }
2760 return diffDayAndTime(date0, date1); // returns a duration
2761 }
2762
2763 class DateProfileGenerator {
2764 constructor(props) {
2765 this.props = props;
2766 this.initHiddenDays();
2767 }
2768 /* Date Range Computation
2769 ------------------------------------------------------------------------------------------------------------------*/
2770 // Builds a structure with info about what the dates/ranges will be for the "prev" view.
2771 buildPrev(currentDateProfile, currentDate, forceToValid) {
2772 let { dateEnv } = this.props;
2773 let prevDate = dateEnv.subtract(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month
2774 currentDateProfile.dateIncrement);
2775 return this.build(prevDate, -1, forceToValid);
2776 }
2777 // Builds a structure with info about what the dates/ranges will be for the "next" view.
2778 buildNext(currentDateProfile, currentDate, forceToValid) {
2779 let { dateEnv } = this.props;
2780 let nextDate = dateEnv.add(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month
2781 currentDateProfile.dateIncrement);
2782 return this.build(nextDate, 1, forceToValid);
2783 }
2784 // Builds a structure holding dates/ranges for rendering around the given date.
2785 // Optional direction param indicates whether the date is being incremented/decremented
2786 // from its previous value. decremented = -1, incremented = 1 (default).
2787 build(currentDate, direction, forceToValid = true) {
2788 let { props } = this;
2789 let validRange;
2790 let currentInfo;
2791 let isRangeAllDay;
2792 let renderRange;
2793 let activeRange;
2794 let isValid;
2795 validRange = this.buildValidRange();
2796 validRange = this.trimHiddenDays(validRange);
2797 if (forceToValid) {
2798 currentDate = constrainMarkerToRange(currentDate, validRange);
2799 }
2800 currentInfo = this.buildCurrentRangeInfo(currentDate, direction);
2801 isRangeAllDay = /^(year|month|week|day)$/.test(currentInfo.unit);
2802 renderRange = this.buildRenderRange(this.trimHiddenDays(currentInfo.range), currentInfo.unit, isRangeAllDay);
2803 renderRange = this.trimHiddenDays(renderRange);
2804 activeRange = renderRange;
2805 if (!props.showNonCurrentDates) {
2806 activeRange = intersectRanges(activeRange, currentInfo.range);
2807 }
2808 activeRange = this.adjustActiveRange(activeRange);
2809 activeRange = intersectRanges(activeRange, validRange); // might return null
2810 // it's invalid if the originally requested date is not contained,
2811 // or if the range is completely outside of the valid range.
2812 isValid = rangesIntersect(currentInfo.range, validRange);
2813 // HACK: constrain to render-range so `currentDate` is more useful to view rendering
2814 if (!rangeContainsMarker(renderRange, currentDate)) {
2815 currentDate = renderRange.start;
2816 }
2817 return {
2818 currentDate,
2819 // constraint for where prev/next operations can go and where events can be dragged/resized to.
2820 // an object with optional start and end properties.
2821 validRange,
2822 // range the view is formally responsible for.
2823 // for example, a month view might have 1st-31st, excluding padded dates
2824 currentRange: currentInfo.range,
2825 // name of largest unit being displayed, like "month" or "week"
2826 currentRangeUnit: currentInfo.unit,
2827 isRangeAllDay,
2828 // dates that display events and accept drag-n-drop
2829 // will be `null` if no dates accept events
2830 activeRange,
2831 // date range with a rendered skeleton
2832 // includes not-active days that need some sort of DOM
2833 renderRange,
2834 // Duration object that denotes the first visible time of any given day
2835 slotMinTime: props.slotMinTime,
2836 // Duration object that denotes the exclusive visible end time of any given day
2837 slotMaxTime: props.slotMaxTime,
2838 isValid,
2839 // how far the current date will move for a prev/next operation
2840 dateIncrement: this.buildDateIncrement(currentInfo.duration),
2841 // pass a fallback (might be null) ^
2842 };
2843 }
2844 // Builds an object with optional start/end properties.
2845 // Indicates the minimum/maximum dates to display.
2846 // not responsible for trimming hidden days.
2847 buildValidRange() {
2848 let input = this.props.validRangeInput;
2849 let simpleInput = typeof input === 'function'
2850 ? input.call(this.props.calendarApi, this.props.dateEnv.toDate(this.props.nowManager.getDateMarker()))
2851 : input;
2852 return this.refineRange(simpleInput) ||
2853 { start: null, end: null }; // completely open-ended
2854 }
2855 // Builds a structure with info about the "current" range, the range that is
2856 // highlighted as being the current month for example.
2857 // See build() for a description of `direction`.
2858 // Guaranteed to have `range` and `unit` properties. `duration` is optional.
2859 buildCurrentRangeInfo(date, direction) {
2860 let { props } = this;
2861 let duration = null;
2862 let unit = null;
2863 let range = null;
2864 let dayCount;
2865 if (props.duration) {
2866 duration = props.duration;
2867 unit = props.durationUnit;
2868 range = this.buildRangeFromDuration(date, direction, duration, unit);
2869 }
2870 else if ((dayCount = this.props.dayCount)) {
2871 unit = 'day';
2872 range = this.buildRangeFromDayCount(date, direction, dayCount);
2873 }
2874 else if ((range = this.buildCustomVisibleRange(date))) {
2875 unit = props.dateEnv.greatestWholeUnit(range.start, range.end).unit;
2876 }
2877 else {
2878 duration = this.getFallbackDuration();
2879 unit = greatestDurationDenominator(duration).unit;
2880 range = this.buildRangeFromDuration(date, direction, duration, unit);
2881 }
2882 return { duration, unit, range };
2883 }
2884 getFallbackDuration() {
2885 return createDuration({ day: 1 });
2886 }
2887 // Returns a new activeRange to have time values (un-ambiguate)
2888 // slotMinTime or slotMaxTime causes the range to expand.
2889 adjustActiveRange(range) {
2890 let { dateEnv, usesMinMaxTime, slotMinTime, slotMaxTime } = this.props;
2891 let { start, end } = range;
2892 if (usesMinMaxTime) {
2893 // expand active range if slotMinTime is negative (why not when positive?)
2894 if (asRoughDays(slotMinTime) < 0) {
2895 start = startOfDay(start); // necessary?
2896 start = dateEnv.add(start, slotMinTime);
2897 }
2898 // expand active range if slotMaxTime is beyond one day (why not when negative?)
2899 if (asRoughDays(slotMaxTime) > 1) {
2900 end = startOfDay(end); // necessary?
2901 end = addDays(end, -1);
2902 end = dateEnv.add(end, slotMaxTime);
2903 }
2904 }
2905 return { start, end };
2906 }
2907 // Builds the "current" range when it is specified as an explicit duration.
2908 // `unit` is the already-computed greatestDurationDenominator unit of duration.
2909 buildRangeFromDuration(date, direction, duration, unit) {
2910 let { dateEnv, dateAlignment } = this.props;
2911 let start;
2912 let end;
2913 let res;
2914 // compute what the alignment should be
2915 if (!dateAlignment) {
2916 let { dateIncrement } = this.props;
2917 if (dateIncrement) {
2918 // use the smaller of the two units
2919 if (asRoughMs(dateIncrement) < asRoughMs(duration)) {
2920 dateAlignment = greatestDurationDenominator(dateIncrement).unit;
2921 }
2922 else {
2923 dateAlignment = unit;
2924 }
2925 }
2926 else {
2927 dateAlignment = unit;
2928 }
2929 }
2930 // if the view displays a single day or smaller
2931 if (asRoughDays(duration) <= 1) {
2932 if (this.isHiddenDay(start)) {
2933 start = this.skipHiddenDays(start, direction);
2934 start = startOfDay(start);
2935 }
2936 }
2937 function computeRes() {
2938 start = dateEnv.startOf(date, dateAlignment);
2939 end = dateEnv.add(start, duration);
2940 res = { start, end };
2941 }
2942 computeRes();
2943 // if range is completely enveloped by hidden days, go past the hidden days
2944 if (!this.trimHiddenDays(res)) {
2945 date = this.skipHiddenDays(date, direction);
2946 computeRes();
2947 }
2948 return res;
2949 }
2950 // Builds the "current" range when a dayCount is specified.
2951 buildRangeFromDayCount(date, direction, dayCount) {
2952 let { dateEnv, dateAlignment } = this.props;
2953 let runningCount = 0;
2954 let start = date;
2955 let end;
2956 if (dateAlignment) {
2957 start = dateEnv.startOf(start, dateAlignment);
2958 }
2959 start = startOfDay(start);
2960 start = this.skipHiddenDays(start, direction);
2961 end = start;
2962 do {
2963 end = addDays(end, 1);
2964 if (!this.isHiddenDay(end)) {
2965 runningCount += 1;
2966 }
2967 } while (runningCount < dayCount);
2968 return { start, end };
2969 }
2970 // Builds a normalized range object for the "visible" range,
2971 // which is a way to define the currentRange and activeRange at the same time.
2972 buildCustomVisibleRange(date) {
2973 let { props } = this;
2974 let input = props.visibleRangeInput;
2975 let simpleInput = typeof input === 'function'
2976 ? input.call(props.calendarApi, props.dateEnv.toDate(date))
2977 : input;
2978 let range = this.refineRange(simpleInput);
2979 if (range && (range.start == null || range.end == null)) {
2980 return null;
2981 }
2982 return range;
2983 }
2984 // Computes the range that will represent the element/cells for *rendering*,
2985 // but which may have voided days/times.
2986 // not responsible for trimming hidden days.
2987 buildRenderRange(currentRange, currentRangeUnit, isRangeAllDay) {
2988 return currentRange;
2989 }
2990 // Compute the duration value that should be added/substracted to the current date
2991 // when a prev/next operation happens.
2992 buildDateIncrement(fallback) {
2993 let { dateIncrement } = this.props;
2994 let customAlignment;
2995 if (dateIncrement) {
2996 return dateIncrement;
2997 }
2998 if ((customAlignment = this.props.dateAlignment)) {
2999 return createDuration(1, customAlignment);
3000 }
3001 if (fallback) {
3002 return fallback;
3003 }
3004 return createDuration({ days: 1 });
3005 }
3006 refineRange(rangeInput) {
3007 if (rangeInput) {
3008 let range = parseRange(rangeInput, this.props.dateEnv);
3009 if (range) {
3010 range = computeVisibleDayRange(range);
3011 }
3012 return range;
3013 }
3014 return null;
3015 }
3016 /* Hidden Days
3017 ------------------------------------------------------------------------------------------------------------------*/
3018 // Initializes internal variables related to calculating hidden days-of-week
3019 initHiddenDays() {
3020 let hiddenDays = this.props.hiddenDays || []; // array of day-of-week indices that are hidden
3021 let isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool)
3022 let dayCnt = 0;
3023 let i;
3024 if (this.props.weekends === false) {
3025 hiddenDays.push(0, 6); // 0=sunday, 6=saturday
3026 }
3027 for (i = 0; i < 7; i += 1) {
3028 if (!(isHiddenDayHash[i] = hiddenDays.indexOf(i) !== -1)) {
3029 dayCnt += 1;
3030 }
3031 }
3032 if (!dayCnt) {
3033 throw new Error('invalid hiddenDays'); // all days were hidden? bad.
3034 }
3035 this.isHiddenDayHash = isHiddenDayHash;
3036 }
3037 // Remove days from the beginning and end of the range that are computed as hidden.
3038 // If the whole range is trimmed off, returns null
3039 trimHiddenDays(range) {
3040 let { start, end } = range;
3041 if (start) {
3042 start = this.skipHiddenDays(start);
3043 }
3044 if (end) {
3045 end = this.skipHiddenDays(end, -1, true);
3046 }
3047 if (start == null || end == null || start < end) {
3048 return { start, end };
3049 }
3050 return null;
3051 }
3052 // Is the current day hidden?
3053 // `day` is a day-of-week index (0-6), or a Date (used for UTC)
3054 isHiddenDay(day) {
3055 if (day instanceof Date) {
3056 day = day.getUTCDay();
3057 }
3058 return this.isHiddenDayHash[day];
3059 }
3060 // Incrementing the current day until it is no longer a hidden day, returning a copy.
3061 // DOES NOT CONSIDER validRange!
3062 // If the initial value of `date` is not a hidden day, don't do anything.
3063 // Pass `isExclusive` as `true` if you are dealing with an end date.
3064 // `inc` defaults to `1` (increment one day forward each time)
3065 skipHiddenDays(date, inc = 1, isExclusive = false) {
3066 while (this.isHiddenDayHash[(date.getUTCDay() + (isExclusive ? inc : 0) + 7) % 7]) {
3067 date = addDays(date, inc);
3068 }
3069 return date;
3070 }
3071 }
3072
3073 function createEventInstance(defId, range, forcedStartTzo, forcedEndTzo) {
3074 return {
3075 instanceId: guid(),
3076 defId,
3077 range,
3078 forcedStartTzo: forcedStartTzo == null ? null : forcedStartTzo,
3079 forcedEndTzo: forcedEndTzo == null ? null : forcedEndTzo,
3080 };
3081 }
3082
3083 function parseRecurring(refined, defaultAllDay, dateEnv, recurringTypes) {
3084 for (let i = 0; i < recurringTypes.length; i += 1) {
3085 let parsed = recurringTypes[i].parse(refined, dateEnv);
3086 if (parsed) {
3087 let { allDay } = refined;
3088 if (allDay == null) {
3089 allDay = defaultAllDay;
3090 if (allDay == null) {
3091 allDay = parsed.allDayGuess;
3092 if (allDay == null) {
3093 allDay = false;
3094 }
3095 }
3096 }
3097 return {
3098 allDay,
3099 duration: parsed.duration,
3100 typeData: parsed.typeData,
3101 typeId: i,
3102 };
3103 }
3104 }
3105 return null;
3106 }
3107 function expandRecurring(eventStore, framingRange, context) {
3108 let { dateEnv, pluginHooks, options } = context;
3109 let { defs, instances } = eventStore;
3110 // remove existing recurring instances
3111 // TODO: bad. always expand events as a second step
3112 instances = filterHash(instances, (instance) => !defs[instance.defId].recurringDef);
3113 for (let defId in defs) {
3114 let def = defs[defId];
3115 if (def.recurringDef) {
3116 let { duration } = def.recurringDef;
3117 if (!duration) {
3118 duration = def.allDay ?
3119 options.defaultAllDayEventDuration :
3120 options.defaultTimedEventDuration;
3121 }
3122 let starts = expandRecurringRanges(def, duration, framingRange, dateEnv, pluginHooks.recurringTypes);
3123 for (let start of starts) {
3124 let instance = createEventInstance(defId, {
3125 start,
3126 end: dateEnv.add(start, duration),
3127 });
3128 instances[instance.instanceId] = instance;
3129 }
3130 }
3131 }
3132 return { defs, instances };
3133 }
3134 /*
3135 Event MUST have a recurringDef
3136 */
3137 function expandRecurringRanges(eventDef, duration, framingRange, dateEnv, recurringTypes) {
3138 let typeDef = recurringTypes[eventDef.recurringDef.typeId];
3139 let markers = typeDef.expand(eventDef.recurringDef.typeData, {
3140 start: dateEnv.subtract(framingRange.start, duration),
3141 end: framingRange.end,
3142 }, dateEnv);
3143 // the recurrence plugins don't guarantee that all-day events are start-of-day, so we have to
3144 if (eventDef.allDay) {
3145 markers = markers.map(startOfDay);
3146 }
3147 return markers;
3148 }
3149
3150 const EVENT_NON_DATE_REFINERS = {
3151 id: String,
3152 groupId: String,
3153 title: String,
3154 url: String,
3155 interactive: Boolean,
3156 };
3157 const EVENT_DATE_REFINERS = {
3158 start: identity,
3159 end: identity,
3160 date: identity,
3161 allDay: Boolean,
3162 };
3163 const EVENT_REFINERS = Object.assign(Object.assign(Object.assign({}, EVENT_NON_DATE_REFINERS), EVENT_DATE_REFINERS), { extendedProps: identity });
3164 function parseEvent(raw, eventSource, context, allowOpenRange, refiners = buildEventRefiners(context), defIdMap, instanceIdMap) {
3165 let { refined, extra } = refineEventDef(raw, context, refiners);
3166 let defaultAllDay = computeIsDefaultAllDay(eventSource, context);
3167 let recurringRes = parseRecurring(refined, defaultAllDay, context.dateEnv, context.pluginHooks.recurringTypes);
3168 if (recurringRes) {
3169 let def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', recurringRes.allDay, Boolean(recurringRes.duration), context, defIdMap);
3170 def.recurringDef = {
3171 typeId: recurringRes.typeId,
3172 typeData: recurringRes.typeData,
3173 duration: recurringRes.duration,
3174 };
3175 return { def, instance: null };
3176 }
3177 let singleRes = parseSingle(refined, defaultAllDay, context, allowOpenRange);
3178 if (singleRes) {
3179 let def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', singleRes.allDay, singleRes.hasEnd, context, defIdMap);
3180 let instance = createEventInstance(def.defId, singleRes.range, singleRes.forcedStartTzo, singleRes.forcedEndTzo);
3181 if (instanceIdMap && def.publicId && instanceIdMap[def.publicId]) {
3182 instance.instanceId = instanceIdMap[def.publicId];
3183 }
3184 return { def, instance };
3185 }
3186 return null;
3187 }
3188 function refineEventDef(raw, context, refiners = buildEventRefiners(context)) {
3189 return refineProps(raw, refiners);
3190 }
3191 function buildEventRefiners(context) {
3192 return Object.assign(Object.assign(Object.assign({}, EVENT_UI_REFINERS), EVENT_REFINERS), context.pluginHooks.eventRefiners);
3193 }
3194 /*
3195 Will NOT populate extendedProps with the leftover properties.
3196 Will NOT populate date-related props.
3197 */
3198 function parseEventDef(refined, extra, sourceId, allDay, hasEnd, context, defIdMap) {
3199 let def = {
3200 title: refined.title || '',
3201 groupId: refined.groupId || '',
3202 publicId: refined.id || '',
3203 url: refined.url || '',
3204 recurringDef: null,
3205 defId: ((defIdMap && refined.id) ? defIdMap[refined.id] : '') || guid(),
3206 sourceId,
3207 allDay,
3208 hasEnd,
3209 interactive: refined.interactive,
3210 ui: createEventUi(refined, context),
3211 extendedProps: Object.assign(Object.assign({}, (refined.extendedProps || {})), extra),
3212 };
3213 for (let memberAdder of context.pluginHooks.eventDefMemberAdders) {
3214 Object.assign(def, memberAdder(refined));
3215 }
3216 // help out EventImpl from having user modify props
3217 Object.freeze(def.ui.classNames);
3218 Object.freeze(def.extendedProps);
3219 return def;
3220 }
3221 function parseSingle(refined, defaultAllDay, context, allowOpenRange) {
3222 let { allDay } = refined;
3223 let startMeta;
3224 let startMarker = null;
3225 let hasEnd = false;
3226 let endMeta;
3227 let endMarker = null;
3228 let startInput = refined.start != null ? refined.start : refined.date;
3229 startMeta = context.dateEnv.createMarkerMeta(startInput);
3230 if (startMeta) {
3231 startMarker = startMeta.marker;
3232 }
3233 else if (!allowOpenRange) {
3234 return null;
3235 }
3236 if (refined.end != null) {
3237 endMeta = context.dateEnv.createMarkerMeta(refined.end);
3238 }
3239 if (allDay == null) {
3240 if (defaultAllDay != null) {
3241 allDay = defaultAllDay;
3242 }
3243 else {
3244 // fall back to the date props LAST
3245 allDay = (!startMeta || startMeta.isTimeUnspecified) &&
3246 (!endMeta || endMeta.isTimeUnspecified);
3247 }
3248 }
3249 if (allDay && startMarker) {
3250 startMarker = startOfDay(startMarker);
3251 }
3252 if (endMeta) {
3253 endMarker = endMeta.marker;
3254 if (allDay) {
3255 endMarker = startOfDay(endMarker);
3256 }
3257 if (startMarker && endMarker <= startMarker) {
3258 endMarker = null;
3259 }
3260 }
3261 if (endMarker) {
3262 hasEnd = true;
3263 }
3264 else if (!allowOpenRange) {
3265 hasEnd = context.options.forceEventDuration || false;
3266 endMarker = context.dateEnv.add(startMarker, allDay ?
3267 context.options.defaultAllDayEventDuration :
3268 context.options.defaultTimedEventDuration);
3269 }
3270 return {
3271 allDay,
3272 hasEnd,
3273 range: { start: startMarker, end: endMarker },
3274 forcedStartTzo: startMeta ? startMeta.forcedTzo : null,
3275 forcedEndTzo: endMeta ? endMeta.forcedTzo : null,
3276 };
3277 }
3278 function computeIsDefaultAllDay(eventSource, context) {
3279 let res = null;
3280 if (eventSource) {
3281 res = eventSource.defaultAllDay;
3282 }
3283 if (res == null) {
3284 res = context.options.defaultAllDay;
3285 }
3286 return res;
3287 }
3288
3289 function parseEvents(rawEvents, eventSource, context, allowOpenRange, defIdMap, instanceIdMap) {
3290 let eventStore = createEmptyEventStore();
3291 let eventRefiners = buildEventRefiners(context);
3292 for (let rawEvent of rawEvents) {
3293 let tuple = parseEvent(rawEvent, eventSource, context, allowOpenRange, eventRefiners, defIdMap, instanceIdMap);
3294 if (tuple) {
3295 eventTupleToStore(tuple, eventStore);
3296 }
3297 }
3298 return eventStore;
3299 }
3300 function eventTupleToStore(tuple, eventStore = createEmptyEventStore()) {
3301 eventStore.defs[tuple.def.defId] = tuple.def;
3302 if (tuple.instance) {
3303 eventStore.instances[tuple.instance.instanceId] = tuple.instance;
3304 }
3305 return eventStore;
3306 }
3307 // retrieves events that have the same groupId as the instance specified by `instanceId`
3308 // or they are the same as the instance.
3309 // why might instanceId not be in the store? an event from another calendar?
3310 function getRelevantEvents(eventStore, instanceId) {
3311 let instance = eventStore.instances[instanceId];
3312 if (instance) {
3313 let def = eventStore.defs[instance.defId];
3314 // get events/instances with same group
3315 let newStore = filterEventStoreDefs(eventStore, (lookDef) => isEventDefsGrouped(def, lookDef));
3316 // add the original
3317 // TODO: wish we could use eventTupleToStore or something like it
3318 newStore.defs[def.defId] = def;
3319 newStore.instances[instance.instanceId] = instance;
3320 return newStore;
3321 }
3322 return createEmptyEventStore();
3323 }
3324 function isEventDefsGrouped(def0, def1) {
3325 return Boolean(def0.groupId && def0.groupId === def1.groupId);
3326 }
3327 function createEmptyEventStore() {
3328 return { defs: {}, instances: {} };
3329 }
3330 function mergeEventStores(store0, store1) {
3331 return {
3332 defs: Object.assign(Object.assign({}, store0.defs), store1.defs),
3333 instances: Object.assign(Object.assign({}, store0.instances), store1.instances),
3334 };
3335 }
3336 function filterEventStoreDefs(eventStore, filterFunc) {
3337 let defs = filterHash(eventStore.defs, filterFunc);
3338 let instances = filterHash(eventStore.instances, (instance) => (defs[instance.defId] // still exists?
3339 ));
3340 return { defs, instances };
3341 }
3342 function excludeSubEventStore(master, sub) {
3343 let { defs, instances } = master;
3344 let filteredDefs = {};
3345 let filteredInstances = {};
3346 for (let defId in defs) {
3347 if (!sub.defs[defId]) { // not explicitly excluded
3348 filteredDefs[defId] = defs[defId];
3349 }
3350 }
3351 for (let instanceId in instances) {
3352 if (!sub.instances[instanceId] && // not explicitly excluded
3353 filteredDefs[instances[instanceId].defId] // def wasn't filtered away
3354 ) {
3355 filteredInstances[instanceId] = instances[instanceId];
3356 }
3357 }
3358 return {
3359 defs: filteredDefs,
3360 instances: filteredInstances,
3361 };
3362 }
3363
3364 function normalizeConstraint(input, context) {
3365 if (Array.isArray(input)) {
3366 return parseEvents(input, null, context, true); // allowOpenRange=true
3367 }
3368 if (typeof input === 'object' && input) { // non-null object
3369 return parseEvents([input], null, context, true); // allowOpenRange=true
3370 }
3371 if (input != null) {
3372 return String(input);
3373 }
3374 return null;
3375 }
3376
3377 function parseClassNames(raw) {
3378 if (Array.isArray(raw)) {
3379 return raw;
3380 }
3381 if (typeof raw === 'string') {
3382 return raw.split(/\s+/);
3383 }
3384 return [];
3385 }
3386
3387 // TODO: better called "EventSettings" or "EventConfig"
3388 // TODO: move this file into structs
3389 // TODO: separate constraint/overlap/allow, because selection uses only that, not other props
3390 const EVENT_UI_REFINERS = {
3391 display: String,
3392 editable: Boolean,
3393 startEditable: Boolean,
3394 durationEditable: Boolean,
3395 constraint: identity,
3396 overlap: identity,
3397 allow: identity,
3398 className: parseClassNames,
3399 classNames: parseClassNames,
3400 color: String,
3401 backgroundColor: String,
3402 borderColor: String,
3403 textColor: String,
3404 };
3405 const EMPTY_EVENT_UI = {
3406 display: null,
3407 startEditable: null,
3408 durationEditable: null,
3409 constraints: [],
3410 overlap: null,
3411 allows: [],
3412 backgroundColor: '',
3413 borderColor: '',
3414 textColor: '',
3415 classNames: [],
3416 };
3417 function createEventUi(refined, context) {
3418 let constraint = normalizeConstraint(refined.constraint, context);
3419 return {
3420 display: refined.display || null,
3421 startEditable: refined.startEditable != null ? refined.startEditable : refined.editable,
3422 durationEditable: refined.durationEditable != null ? refined.durationEditable : refined.editable,
3423 constraints: constraint != null ? [constraint] : [],
3424 overlap: refined.overlap != null ? refined.overlap : null,
3425 allows: refined.allow != null ? [refined.allow] : [],
3426 backgroundColor: refined.backgroundColor || refined.color || '',
3427 borderColor: refined.borderColor || refined.color || '',
3428 textColor: refined.textColor || '',
3429 classNames: (refined.className || []).concat(refined.classNames || []), // join singular and plural
3430 };
3431 }
3432 // TODO: prevent against problems with <2 args!
3433 function combineEventUis(uis) {
3434 return uis.reduce(combineTwoEventUis, EMPTY_EVENT_UI);
3435 }
3436 function combineTwoEventUis(item0, item1) {
3437 return {
3438 display: item1.display != null ? item1.display : item0.display,
3439 startEditable: item1.startEditable != null ? item1.startEditable : item0.startEditable,
3440 durationEditable: item1.durationEditable != null ? item1.durationEditable : item0.durationEditable,
3441 constraints: item0.constraints.concat(item1.constraints),
3442 overlap: typeof item1.overlap === 'boolean' ? item1.overlap : item0.overlap,
3443 allows: item0.allows.concat(item1.allows),
3444 backgroundColor: item1.backgroundColor || item0.backgroundColor,
3445 borderColor: item1.borderColor || item0.borderColor,
3446 textColor: item1.textColor || item0.textColor,
3447 classNames: item0.classNames.concat(item1.classNames),
3448 };
3449 }
3450
3451 const EVENT_SOURCE_REFINERS = {
3452 id: String,
3453 defaultAllDay: Boolean,
3454 url: String,
3455 format: String,
3456 events: identity,
3457 eventDataTransform: identity,
3458 // for any network-related sources
3459 success: identity,
3460 failure: identity,
3461 };
3462 function parseEventSource(raw, context, refiners = buildEventSourceRefiners(context)) {
3463 let rawObj;
3464 if (typeof raw === 'string') {
3465 rawObj = { url: raw };
3466 }
3467 else if (typeof raw === 'function' || Array.isArray(raw)) {
3468 rawObj = { events: raw };
3469 }
3470 else if (typeof raw === 'object' && raw) { // not null
3471 rawObj = raw;
3472 }
3473 if (rawObj) {
3474 let { refined, extra } = refineProps(rawObj, refiners);
3475 let metaRes = buildEventSourceMeta(refined, context);
3476 if (metaRes) {
3477 return {
3478 _raw: raw,
3479 isFetching: false,
3480 latestFetchId: '',
3481 fetchRange: null,
3482 defaultAllDay: refined.defaultAllDay,
3483 eventDataTransform: refined.eventDataTransform,
3484 success: refined.success,
3485 failure: refined.failure,
3486 publicId: refined.id || '',
3487 sourceId: guid(),
3488 sourceDefId: metaRes.sourceDefId,
3489 meta: metaRes.meta,
3490 ui: createEventUi(refined, context),
3491 extendedProps: extra,
3492 };
3493 }
3494 }
3495 return null;
3496 }
3497 function buildEventSourceRefiners(context) {
3498 return Object.assign(Object.assign(Object.assign({}, EVENT_UI_REFINERS), EVENT_SOURCE_REFINERS), context.pluginHooks.eventSourceRefiners);
3499 }
3500 function buildEventSourceMeta(raw, context) {
3501 let defs = context.pluginHooks.eventSourceDefs;
3502 for (let i = defs.length - 1; i >= 0; i -= 1) { // later-added plugins take precedence
3503 let def = defs[i];
3504 let meta = def.parseMeta(raw);
3505 if (meta) {
3506 return { sourceDefId: i, meta };
3507 }
3508 }
3509 return null;
3510 }
3511
3512 function reduceEventStore(eventStore, action, eventSources, dateProfile, context) {
3513 switch (action.type) {
3514 case 'RECEIVE_EVENTS': // raw
3515 return receiveRawEvents(eventStore, eventSources[action.sourceId], action.fetchId, action.fetchRange, action.rawEvents, context);
3516 case 'RESET_RAW_EVENTS':
3517 return resetRawEvents(eventStore, eventSources[action.sourceId], action.rawEvents, dateProfile.activeRange, context);
3518 case 'ADD_EVENTS': // already parsed, but not expanded
3519 return addEvent(eventStore, action.eventStore, // new ones
3520 dateProfile ? dateProfile.activeRange : null, context);
3521 case 'RESET_EVENTS':
3522 return action.eventStore;
3523 case 'MERGE_EVENTS': // already parsed and expanded
3524 return mergeEventStores(eventStore, action.eventStore);
3525 case 'PREV': // TODO: how do we track all actions that affect dateProfile :(
3526 case 'NEXT':
3527 case 'CHANGE_DATE':
3528 case 'CHANGE_VIEW_TYPE':
3529 if (dateProfile) {
3530 return expandRecurring(eventStore, dateProfile.activeRange, context);
3531 }
3532 return eventStore;
3533 case 'REMOVE_EVENTS':
3534 return excludeSubEventStore(eventStore, action.eventStore);
3535 case 'REMOVE_EVENT_SOURCE':
3536 return excludeEventsBySourceId(eventStore, action.sourceId);
3537 case 'REMOVE_ALL_EVENT_SOURCES':
3538 return filterEventStoreDefs(eventStore, (eventDef) => (!eventDef.sourceId // only keep events with no source id
3539 ));
3540 case 'REMOVE_ALL_EVENTS':
3541 return createEmptyEventStore();
3542 default:
3543 return eventStore;
3544 }
3545 }
3546 function receiveRawEvents(eventStore, eventSource, fetchId, fetchRange, rawEvents, context) {
3547 if (eventSource && // not already removed
3548 fetchId === eventSource.latestFetchId // TODO: wish this logic was always in event-sources
3549 ) {
3550 let subset = parseEvents(transformRawEvents(rawEvents, eventSource, context), eventSource, context);
3551 if (fetchRange) {
3552 subset = expandRecurring(subset, fetchRange, context);
3553 }
3554 return mergeEventStores(excludeEventsBySourceId(eventStore, eventSource.sourceId), subset);
3555 }
3556 return eventStore;
3557 }
3558 function resetRawEvents(existingEventStore, eventSource, rawEvents, activeRange, context) {
3559 const { defIdMap, instanceIdMap } = buildPublicIdMaps(existingEventStore);
3560 let newEventStore = parseEvents(transformRawEvents(rawEvents, eventSource, context), eventSource, context, false, defIdMap, instanceIdMap);
3561 return expandRecurring(newEventStore, activeRange, context);
3562 }
3563 function transformRawEvents(rawEvents, eventSource, context) {
3564 let calEachTransform = context.options.eventDataTransform;
3565 let sourceEachTransform = eventSource ? eventSource.eventDataTransform : null;
3566 if (sourceEachTransform) {
3567 rawEvents = transformEachRawEvent(rawEvents, sourceEachTransform);
3568 }
3569 if (calEachTransform) {
3570 rawEvents = transformEachRawEvent(rawEvents, calEachTransform);
3571 }
3572 return rawEvents;
3573 }
3574 function transformEachRawEvent(rawEvents, func) {
3575 let refinedEvents;
3576 if (!func) {
3577 refinedEvents = rawEvents;
3578 }
3579 else {
3580 refinedEvents = [];
3581 for (let rawEvent of rawEvents) {
3582 let refinedEvent = func(rawEvent);
3583 if (refinedEvent) {
3584 refinedEvents.push(refinedEvent);
3585 }
3586 else if (refinedEvent == null) {
3587 refinedEvents.push(rawEvent);
3588 } // if a different falsy value, do nothing
3589 }
3590 }
3591 return refinedEvents;
3592 }
3593 function addEvent(eventStore, subset, expandRange, context) {
3594 if (expandRange) {
3595 subset = expandRecurring(subset, expandRange, context);
3596 }
3597 return mergeEventStores(eventStore, subset);
3598 }
3599 function rezoneEventStoreDates(eventStore, oldDateEnv, newDateEnv) {
3600 let { defs } = eventStore;
3601 let instances = mapHash(eventStore.instances, (instance) => {
3602 let def = defs[instance.defId];
3603 if (def.allDay) {
3604 return instance; // isn't dependent on timezone
3605 }
3606 return Object.assign(Object.assign({}, instance), { range: {
3607 start: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.start, instance.forcedStartTzo)),
3608 end: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.end, instance.forcedEndTzo)),
3609 }, forcedStartTzo: newDateEnv.canComputeOffset ? null : instance.forcedStartTzo, forcedEndTzo: newDateEnv.canComputeOffset ? null : instance.forcedEndTzo });
3610 });
3611 return { defs, instances };
3612 }
3613 function excludeEventsBySourceId(eventStore, sourceId) {
3614 return filterEventStoreDefs(eventStore, (eventDef) => eventDef.sourceId !== sourceId);
3615 }
3616 // QUESTION: why not just return instances? do a general object-property-exclusion util
3617 function excludeInstances(eventStore, removals) {
3618 return {
3619 defs: eventStore.defs,
3620 instances: filterHash(eventStore.instances, (instance) => !removals[instance.instanceId]),
3621 };
3622 }
3623 function buildPublicIdMaps(eventStore) {
3624 const { defs, instances } = eventStore;
3625 const defIdMap = {};
3626 const instanceIdMap = {};
3627 for (let defId in defs) {
3628 const def = defs[defId];
3629 const { publicId } = def;
3630 if (publicId) {
3631 defIdMap[publicId] = defId;
3632 }
3633 }
3634 for (let instanceId in instances) {
3635 const instance = instances[instanceId];
3636 const def = defs[instance.defId];
3637 const { publicId } = def;
3638 if (publicId) {
3639 instanceIdMap[publicId] = instanceId;
3640 }
3641 }
3642 return { defIdMap, instanceIdMap };
3643 }
3644
3645 class Emitter {
3646 constructor() {
3647 this.handlers = {};
3648 this.thisContext = null;
3649 }
3650 setThisContext(thisContext) {
3651 this.thisContext = thisContext;
3652 }
3653 setOptions(options) {
3654 this.options = options;
3655 }
3656 on(type, handler) {
3657 addToHash(this.handlers, type, handler);
3658 }
3659 off(type, handler) {
3660 removeFromHash(this.handlers, type, handler);
3661 }
3662 trigger(type, ...args) {
3663 let attachedHandlers = this.handlers[type] || [];
3664 let optionHandler = this.options && this.options[type];
3665 let handlers = [].concat(optionHandler || [], attachedHandlers);
3666 for (let handler of handlers) {
3667 handler.apply(this.thisContext, args);
3668 }
3669 }
3670 hasHandlers(type) {
3671 return Boolean((this.handlers[type] && this.handlers[type].length) ||
3672 (this.options && this.options[type]));
3673 }
3674 }
3675 function addToHash(hash, type, handler) {
3676 (hash[type] || (hash[type] = []))
3677 .push(handler);
3678 }
3679 function removeFromHash(hash, type, handler) {
3680 if (handler) {
3681 if (hash[type]) {
3682 hash[type] = hash[type].filter((func) => func !== handler);
3683 }
3684 }
3685 else {
3686 delete hash[type]; // remove all handler funcs for this type
3687 }
3688 }
3689
3690 const DEF_DEFAULTS = {
3691 startTime: '09:00',
3692 endTime: '17:00',
3693 daysOfWeek: [1, 2, 3, 4, 5],
3694 display: 'inverse-background',
3695 classNames: 'fc-non-business',
3696 groupId: '_businessHours', // so multiple defs get grouped
3697 };
3698 /*
3699 TODO: pass around as EventDefHash!!!
3700 */
3701 function parseBusinessHours(input, context) {
3702 return parseEvents(refineInputs(input), null, context);
3703 }
3704 function refineInputs(input) {
3705 let rawDefs;
3706 if (input === true) {
3707 rawDefs = [{}]; // will get DEF_DEFAULTS verbatim
3708 }
3709 else if (Array.isArray(input)) {
3710 // if specifying an array, every sub-definition NEEDS a day-of-week
3711 rawDefs = input.filter((rawDef) => rawDef.daysOfWeek);
3712 }
3713 else if (typeof input === 'object' && input) { // non-null object
3714 rawDefs = [input];
3715 }
3716 else { // is probably false
3717 rawDefs = [];
3718 }
3719 rawDefs = rawDefs.map((rawDef) => (Object.assign(Object.assign({}, DEF_DEFAULTS), rawDef)));
3720 return rawDefs;
3721 }
3722
3723 function triggerDateSelect(selection, pev, context) {
3724 context.emitter.trigger('select', Object.assign(Object.assign({}, buildDateSpanApiWithContext(selection, context)), { jsEvent: pev ? pev.origEvent : null, view: context.viewApi || context.calendarApi.view }));
3725 }
3726 function triggerDateUnselect(pev, context) {
3727 context.emitter.trigger('unselect', {
3728 jsEvent: pev ? pev.origEvent : null,
3729 view: context.viewApi || context.calendarApi.view,
3730 });
3731 }
3732 function buildDateSpanApiWithContext(dateSpan, context) {
3733 let props = {};
3734 for (let transform of context.pluginHooks.dateSpanTransforms) {
3735 Object.assign(props, transform(dateSpan, context));
3736 }
3737 Object.assign(props, buildDateSpanApi(dateSpan, context.dateEnv));
3738 return props;
3739 }
3740 // Given an event's allDay status and start date, return what its fallback end date should be.
3741 // TODO: rename to computeDefaultEventEnd
3742 function getDefaultEventEnd(allDay, marker, context) {
3743 let { dateEnv, options } = context;
3744 let end = marker;
3745 if (allDay) {
3746 end = startOfDay(end);
3747 end = dateEnv.add(end, options.defaultAllDayEventDuration);
3748 }
3749 else {
3750 end = dateEnv.add(end, options.defaultTimedEventDuration);
3751 }
3752 return end;
3753 }
3754
3755 // applies the mutation to ALL defs/instances within the event store
3756 function applyMutationToEventStore(eventStore, eventConfigBase, mutation, context) {
3757 let eventConfigs = compileEventUis(eventStore.defs, eventConfigBase);
3758 let dest = createEmptyEventStore();
3759 for (let defId in eventStore.defs) {
3760 let def = eventStore.defs[defId];
3761 dest.defs[defId] = applyMutationToEventDef(def, eventConfigs[defId], mutation, context);
3762 }
3763 for (let instanceId in eventStore.instances) {
3764 let instance = eventStore.instances[instanceId];
3765 let def = dest.defs[instance.defId]; // important to grab the newly modified def
3766 dest.instances[instanceId] = applyMutationToEventInstance(instance, def, eventConfigs[instance.defId], mutation, context);
3767 }
3768 return dest;
3769 }
3770 function applyMutationToEventDef(eventDef, eventConfig, mutation, context) {
3771 let standardProps = mutation.standardProps || {};
3772 // if hasEnd has not been specified, guess a good value based on deltas.
3773 // if duration will change, there's no way the default duration will persist,
3774 // and thus, we need to mark the event as having a real end
3775 if (standardProps.hasEnd == null &&
3776 eventConfig.durationEditable &&
3777 (mutation.startDelta || mutation.endDelta)) {
3778 standardProps.hasEnd = true; // TODO: is this mutation okay?
3779 }
3780 let copy = Object.assign(Object.assign(Object.assign({}, eventDef), standardProps), { ui: Object.assign(Object.assign({}, eventDef.ui), standardProps.ui) });
3781 if (mutation.extendedProps) {
3782 copy.extendedProps = Object.assign(Object.assign({}, copy.extendedProps), mutation.extendedProps);
3783 }
3784 for (let applier of context.pluginHooks.eventDefMutationAppliers) {
3785 applier(copy, mutation, context);
3786 }
3787 if (!copy.hasEnd && context.options.forceEventDuration) {
3788 copy.hasEnd = true;
3789 }
3790 return copy;
3791 }
3792 function applyMutationToEventInstance(eventInstance, eventDef, // must first be modified by applyMutationToEventDef
3793 eventConfig, mutation, context) {
3794 let { dateEnv } = context;
3795 let forceAllDay = mutation.standardProps && mutation.standardProps.allDay === true;
3796 let clearEnd = mutation.standardProps && mutation.standardProps.hasEnd === false;
3797 let copy = Object.assign({}, eventInstance);
3798 if (forceAllDay) {
3799 copy.range = computeAlignedDayRange(copy.range);
3800 }
3801 if (mutation.datesDelta && eventConfig.startEditable) {
3802 copy.range = {
3803 start: dateEnv.add(copy.range.start, mutation.datesDelta),
3804 end: dateEnv.add(copy.range.end, mutation.datesDelta),
3805 };
3806 }
3807 if (mutation.startDelta && eventConfig.durationEditable) {
3808 copy.range = {
3809 start: dateEnv.add(copy.range.start, mutation.startDelta),
3810 end: copy.range.end,
3811 };
3812 }
3813 if (mutation.endDelta && eventConfig.durationEditable) {
3814 copy.range = {
3815 start: copy.range.start,
3816 end: dateEnv.add(copy.range.end, mutation.endDelta),
3817 };
3818 }
3819 if (clearEnd) {
3820 copy.range = {
3821 start: copy.range.start,
3822 end: getDefaultEventEnd(eventDef.allDay, copy.range.start, context),
3823 };
3824 }
3825 // in case event was all-day but the supplied deltas were not
3826 // better util for this?
3827 if (eventDef.allDay) {
3828 copy.range = {
3829 start: startOfDay(copy.range.start),
3830 end: startOfDay(copy.range.end),
3831 };
3832 }
3833 // handle invalid durations
3834 if (copy.range.end < copy.range.start) {
3835 copy.range.end = getDefaultEventEnd(eventDef.allDay, copy.range.start, context);
3836 }
3837 return copy;
3838 }
3839
3840 class EventSourceImpl {
3841 constructor(context, internalEventSource) {
3842 this.context = context;
3843 this.internalEventSource = internalEventSource;
3844 }
3845 remove() {
3846 this.context.dispatch({
3847 type: 'REMOVE_EVENT_SOURCE',
3848 sourceId: this.internalEventSource.sourceId,
3849 });
3850 }
3851 refetch() {
3852 this.context.dispatch({
3853 type: 'FETCH_EVENT_SOURCES',
3854 sourceIds: [this.internalEventSource.sourceId],
3855 isRefetch: true,
3856 });
3857 }
3858 get id() {
3859 return this.internalEventSource.publicId;
3860 }
3861 get url() {
3862 return this.internalEventSource.meta.url;
3863 }
3864 get format() {
3865 return this.internalEventSource.meta.format; // TODO: bad. not guaranteed
3866 }
3867 }
3868
3869 class EventImpl {
3870 // instance will be null if expressing a recurring event that has no current instances,
3871 // OR if trying to validate an incoming external event that has no dates assigned
3872 constructor(context, def, instance) {
3873 this._context = context;
3874 this._def = def;
3875 this._instance = instance || null;
3876 }
3877 /*
3878 TODO: make event struct more responsible for this
3879 */
3880 setProp(name, val) {
3881 if (name in EVENT_DATE_REFINERS) {
3882 console.warn('Could not set date-related prop \'name\'. Use one of the date-related methods instead.');
3883 // TODO: make proper aliasing system?
3884 }
3885 else if (name === 'id') {
3886 val = EVENT_NON_DATE_REFINERS[name](val);
3887 this.mutate({
3888 standardProps: { publicId: val }, // hardcoded internal name
3889 });
3890 }
3891 else if (name in EVENT_NON_DATE_REFINERS) {
3892 val = EVENT_NON_DATE_REFINERS[name](val);
3893 this.mutate({
3894 standardProps: { [name]: val },
3895 });
3896 }
3897 else if (name in EVENT_UI_REFINERS) {
3898 let ui = EVENT_UI_REFINERS[name](val);
3899 if (name === 'color') {
3900 ui = { backgroundColor: val, borderColor: val };
3901 }
3902 else if (name === 'editable') {
3903 ui = { startEditable: val, durationEditable: val };
3904 }
3905 else {
3906 ui = { [name]: val };
3907 }
3908 this.mutate({
3909 standardProps: { ui },
3910 });
3911 }
3912 else {
3913 console.warn(`Could not set prop '${name}'. Use setExtendedProp instead.`);
3914 }
3915 }
3916 setExtendedProp(name, val) {
3917 this.mutate({
3918 extendedProps: { [name]: val },
3919 });
3920 }
3921 setStart(startInput, options = {}) {
3922 let { dateEnv } = this._context;
3923 let start = dateEnv.createMarker(startInput);
3924 if (start && this._instance) { // TODO: warning if parsed bad
3925 let instanceRange = this._instance.range;
3926 let startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity); // what if parsed bad!?
3927 if (options.maintainDuration) {
3928 this.mutate({ datesDelta: startDelta });
3929 }
3930 else {
3931 this.mutate({ startDelta });
3932 }
3933 }
3934 }
3935 setEnd(endInput, options = {}) {
3936 let { dateEnv } = this._context;
3937 let end;
3938 if (endInput != null) {
3939 end = dateEnv.createMarker(endInput);
3940 if (!end) {
3941 return; // TODO: warning if parsed bad
3942 }
3943 }
3944 if (this._instance) {
3945 if (end) {
3946 let endDelta = diffDates(this._instance.range.end, end, dateEnv, options.granularity);
3947 this.mutate({ endDelta });
3948 }
3949 else {
3950 this.mutate({ standardProps: { hasEnd: false } });
3951 }
3952 }
3953 }
3954 setDates(startInput, endInput, options = {}) {
3955 let { dateEnv } = this._context;
3956 let standardProps = { allDay: options.allDay };
3957 let start = dateEnv.createMarker(startInput);
3958 let end;
3959 if (!start) {
3960 return; // TODO: warning if parsed bad
3961 }
3962 if (endInput != null) {
3963 end = dateEnv.createMarker(endInput);
3964 if (!end) { // TODO: warning if parsed bad
3965 return;
3966 }
3967 }
3968 if (this._instance) {
3969 let instanceRange = this._instance.range;
3970 // when computing the diff for an event being converted to all-day,
3971 // compute diff off of the all-day values the way event-mutation does.
3972 if (options.allDay === true) {
3973 instanceRange = computeAlignedDayRange(instanceRange);
3974 }
3975 let startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity);
3976 if (end) {
3977 let endDelta = diffDates(instanceRange.end, end, dateEnv, options.granularity);
3978 if (durationsEqual(startDelta, endDelta)) {
3979 this.mutate({ datesDelta: startDelta, standardProps });
3980 }
3981 else {
3982 this.mutate({ startDelta, endDelta, standardProps });
3983 }
3984 }
3985 else { // means "clear the end"
3986 standardProps.hasEnd = false;
3987 this.mutate({ datesDelta: startDelta, standardProps });
3988 }
3989 }
3990 }
3991 moveStart(deltaInput) {
3992 let delta = createDuration(deltaInput);
3993 if (delta) { // TODO: warning if parsed bad
3994 this.mutate({ startDelta: delta });
3995 }
3996 }
3997 moveEnd(deltaInput) {
3998 let delta = createDuration(deltaInput);
3999 if (delta) { // TODO: warning if parsed bad
4000 this.mutate({ endDelta: delta });
4001 }
4002 }
4003 moveDates(deltaInput) {
4004 let delta = createDuration(deltaInput);
4005 if (delta) { // TODO: warning if parsed bad
4006 this.mutate({ datesDelta: delta });
4007 }
4008 }
4009 setAllDay(allDay, options = {}) {
4010 let standardProps = { allDay };
4011 let { maintainDuration } = options;
4012 if (maintainDuration == null) {
4013 maintainDuration = this._context.options.allDayMaintainDuration;
4014 }
4015 if (this._def.allDay !== allDay) {
4016 standardProps.hasEnd = maintainDuration;
4017 }
4018 this.mutate({ standardProps });
4019 }
4020 formatRange(formatInput) {
4021 let { dateEnv } = this._context;
4022 let instance = this._instance;
4023 let formatter = createFormatter(formatInput);
4024 if (this._def.hasEnd) {
4025 return dateEnv.formatRange(instance.range.start, instance.range.end, formatter, {
4026 forcedStartTzo: instance.forcedStartTzo,
4027 forcedEndTzo: instance.forcedEndTzo,
4028 });
4029 }
4030 return dateEnv.format(instance.range.start, formatter, {
4031 forcedTzo: instance.forcedStartTzo,
4032 });
4033 }
4034 mutate(mutation) {
4035 let instance = this._instance;
4036 if (instance) {
4037 let def = this._def;
4038 let context = this._context;
4039 let { eventStore } = context.getCurrentData();
4040 let relevantEvents = getRelevantEvents(eventStore, instance.instanceId);
4041 let eventConfigBase = {
4042 '': {
4043 display: '',
4044 startEditable: true,
4045 durationEditable: true,
4046 constraints: [],
4047 overlap: null,
4048 allows: [],
4049 backgroundColor: '',
4050 borderColor: '',
4051 textColor: '',
4052 classNames: [],
4053 },
4054 };
4055 relevantEvents = applyMutationToEventStore(relevantEvents, eventConfigBase, mutation, context);
4056 let oldEvent = new EventImpl(context, def, instance); // snapshot
4057 this._def = relevantEvents.defs[def.defId];
4058 this._instance = relevantEvents.instances[instance.instanceId];
4059 context.dispatch({
4060 type: 'MERGE_EVENTS',
4061 eventStore: relevantEvents,
4062 });
4063 context.emitter.trigger('eventChange', {
4064 oldEvent,
4065 event: this,
4066 relatedEvents: buildEventApis(relevantEvents, context, instance),
4067 revert() {
4068 context.dispatch({
4069 type: 'RESET_EVENTS',
4070 eventStore, // the ORIGINAL store
4071 });
4072 },
4073 });
4074 }
4075 }
4076 remove() {
4077 let context = this._context;
4078 let asStore = eventApiToStore(this);
4079 context.dispatch({
4080 type: 'REMOVE_EVENTS',
4081 eventStore: asStore,
4082 });
4083 context.emitter.trigger('eventRemove', {
4084 event: this,
4085 relatedEvents: [],
4086 revert() {
4087 context.dispatch({
4088 type: 'MERGE_EVENTS',
4089 eventStore: asStore,
4090 });
4091 },
4092 });
4093 }
4094 get source() {
4095 let { sourceId } = this._def;
4096 if (sourceId) {
4097 return new EventSourceImpl(this._context, this._context.getCurrentData().eventSources[sourceId]);
4098 }
4099 return null;
4100 }
4101 get start() {
4102 return this._instance ?
4103 this._context.dateEnv.toDate(this._instance.range.start) :
4104 null;
4105 }
4106 get end() {
4107 return (this._instance && this._def.hasEnd) ?
4108 this._context.dateEnv.toDate(this._instance.range.end) :
4109 null;
4110 }
4111 get startStr() {
4112 let instance = this._instance;
4113 if (instance) {
4114 return this._context.dateEnv.formatIso(instance.range.start, {
4115 omitTime: this._def.allDay,
4116 forcedTzo: instance.forcedStartTzo,
4117 });
4118 }
4119 return '';
4120 }
4121 get endStr() {
4122 let instance = this._instance;
4123 if (instance && this._def.hasEnd) {
4124 return this._context.dateEnv.formatIso(instance.range.end, {
4125 omitTime: this._def.allDay,
4126 forcedTzo: instance.forcedEndTzo,
4127 });
4128 }
4129 return '';
4130 }
4131 // computable props that all access the def
4132 // TODO: find a TypeScript-compatible way to do this at scale
4133 get id() { return this._def.publicId; }
4134 get groupId() { return this._def.groupId; }
4135 get allDay() { return this._def.allDay; }
4136 get title() { return this._def.title; }
4137 get url() { return this._def.url; }
4138 get display() { return this._def.ui.display || 'auto'; } // bad. just normalize the type earlier
4139 get startEditable() { return this._def.ui.startEditable; }
4140 get durationEditable() { return this._def.ui.durationEditable; }
4141 get constraint() { return this._def.ui.constraints[0] || null; }
4142 get overlap() { return this._def.ui.overlap; }
4143 get allow() { return this._def.ui.allows[0] || null; }
4144 get backgroundColor() { return this._def.ui.backgroundColor; }
4145 get borderColor() { return this._def.ui.borderColor; }
4146 get textColor() { return this._def.ui.textColor; }
4147 // NOTE: user can't modify these because Object.freeze was called in event-def parsing
4148 get classNames() { return this._def.ui.classNames; }
4149 get extendedProps() { return this._def.extendedProps; }
4150 toPlainObject(settings = {}) {
4151 let def = this._def;
4152 let { ui } = def;
4153 let { startStr, endStr } = this;
4154 let res = {
4155 allDay: def.allDay,
4156 };
4157 if (def.title) {
4158 res.title = def.title;
4159 }
4160 if (startStr) {
4161 res.start = startStr;
4162 }
4163 if (endStr) {
4164 res.end = endStr;
4165 }
4166 if (def.publicId) {
4167 res.id = def.publicId;
4168 }
4169 if (def.groupId) {
4170 res.groupId = def.groupId;
4171 }
4172 if (def.url) {
4173 res.url = def.url;
4174 }
4175 if (ui.display && ui.display !== 'auto') {
4176 res.display = ui.display;
4177 }
4178 // TODO: what about recurring-event properties???
4179 // TODO: include startEditable/durationEditable/constraint/overlap/allow
4180 if (settings.collapseColor && ui.backgroundColor && ui.backgroundColor === ui.borderColor) {
4181 res.color = ui.backgroundColor;
4182 }
4183 else {
4184 if (ui.backgroundColor) {
4185 res.backgroundColor = ui.backgroundColor;
4186 }
4187 if (ui.borderColor) {
4188 res.borderColor = ui.borderColor;
4189 }
4190 }
4191 if (ui.textColor) {
4192 res.textColor = ui.textColor;
4193 }
4194 if (ui.classNames.length) {
4195 res.classNames = ui.classNames;
4196 }
4197 if (Object.keys(def.extendedProps).length) {
4198 if (settings.collapseExtendedProps) {
4199 Object.assign(res, def.extendedProps);
4200 }
4201 else {
4202 res.extendedProps = def.extendedProps;
4203 }
4204 }
4205 return res;
4206 }
4207 toJSON() {
4208 return this.toPlainObject();
4209 }
4210 }
4211 function eventApiToStore(eventApi) {
4212 let def = eventApi._def;
4213 let instance = eventApi._instance;
4214 return {
4215 defs: { [def.defId]: def },
4216 instances: instance
4217 ? { [instance.instanceId]: instance }
4218 : {},
4219 };
4220 }
4221 function buildEventApis(eventStore, context, excludeInstance) {
4222 let { defs, instances } = eventStore;
4223 let eventApis = [];
4224 let excludeInstanceId = excludeInstance ? excludeInstance.instanceId : '';
4225 for (let id in instances) {
4226 let instance = instances[id];
4227 let def = defs[instance.defId];
4228 if (instance.instanceId !== excludeInstanceId) {
4229 eventApis.push(new EventImpl(context, def, instance));
4230 }
4231 }
4232 return eventApis;
4233 }
4234
4235 /*
4236 Specifying nextDayThreshold signals that all-day ranges should be sliced.
4237 */
4238 function sliceEventStore(eventStore, eventUiBases, framingRange, nextDayThreshold) {
4239 let inverseBgByGroupId = {};
4240 let inverseBgByDefId = {};
4241 let defByGroupId = {};
4242 let bgRanges = [];
4243 let fgRanges = [];
4244 let eventUis = compileEventUis(eventStore.defs, eventUiBases);
4245 for (let defId in eventStore.defs) {
4246 let def = eventStore.defs[defId];
4247 let ui = eventUis[def.defId];
4248 if (ui.display === 'inverse-background') {
4249 if (def.groupId) {
4250 inverseBgByGroupId[def.groupId] = [];
4251 if (!defByGroupId[def.groupId]) {
4252 defByGroupId[def.groupId] = def;
4253 }
4254 }
4255 else {
4256 inverseBgByDefId[defId] = [];
4257 }
4258 }
4259 }
4260 for (let instanceId in eventStore.instances) {
4261 let instance = eventStore.instances[instanceId];
4262 let def = eventStore.defs[instance.defId];
4263 let ui = eventUis[def.defId];
4264 let origRange = instance.range;
4265 let normalRange = (!def.allDay && nextDayThreshold) ?
4266 computeVisibleDayRange(origRange, nextDayThreshold) :
4267 origRange;
4268 let slicedRange = intersectRanges(normalRange, framingRange);
4269 if (slicedRange) {
4270 if (ui.display === 'inverse-background') {
4271 if (def.groupId) {
4272 inverseBgByGroupId[def.groupId].push(slicedRange);
4273 }
4274 else {
4275 inverseBgByDefId[instance.defId].push(slicedRange);
4276 }
4277 }
4278 else if (ui.display !== 'none') {
4279 (ui.display === 'background' ? bgRanges : fgRanges).push({
4280 def,
4281 ui,
4282 instance,
4283 range: slicedRange,
4284 isStart: normalRange.start && normalRange.start.valueOf() === slicedRange.start.valueOf(),
4285 isEnd: normalRange.end && normalRange.end.valueOf() === slicedRange.end.valueOf(),
4286 });
4287 }
4288 }
4289 }
4290 for (let groupId in inverseBgByGroupId) { // BY GROUP
4291 let ranges = inverseBgByGroupId[groupId];
4292 let invertedRanges = invertRanges(ranges, framingRange);
4293 for (let invertedRange of invertedRanges) {
4294 let def = defByGroupId[groupId];
4295 let ui = eventUis[def.defId];
4296 bgRanges.push({
4297 def,
4298 ui,
4299 instance: null,
4300 range: invertedRange,
4301 isStart: false,
4302 isEnd: false,
4303 });
4304 }
4305 }
4306 for (let defId in inverseBgByDefId) {
4307 let ranges = inverseBgByDefId[defId];
4308 let invertedRanges = invertRanges(ranges, framingRange);
4309 for (let invertedRange of invertedRanges) {
4310 bgRanges.push({
4311 def: eventStore.defs[defId],
4312 ui: eventUis[defId],
4313 instance: null,
4314 range: invertedRange,
4315 isStart: false,
4316 isEnd: false,
4317 });
4318 }
4319 }
4320 return { bg: bgRanges, fg: fgRanges };
4321 }
4322 function hasBgRendering(def) {
4323 return def.ui.display === 'background' || def.ui.display === 'inverse-background';
4324 }
4325 function setElSeg(el, seg) {
4326 el.fcSeg = seg;
4327 }
4328 function getElSeg(el) {
4329 return el.fcSeg ||
4330 el.parentNode.fcSeg || // for the harness
4331 null;
4332 }
4333 // event ui computation
4334 function compileEventUis(eventDefs, eventUiBases) {
4335 return mapHash(eventDefs, (eventDef) => compileEventUi(eventDef, eventUiBases));
4336 }
4337 function compileEventUi(eventDef, eventUiBases) {
4338 let uis = [];
4339 if (eventUiBases['']) {
4340 uis.push(eventUiBases['']);
4341 }
4342 if (eventUiBases[eventDef.defId]) {
4343 uis.push(eventUiBases[eventDef.defId]);
4344 }
4345 uis.push(eventDef.ui);
4346 return combineEventUis(uis);
4347 }
4348 function sortEventSegs(segs, eventOrderSpecs) {
4349 let objs = segs.map(buildSegCompareObj);
4350 objs.sort((obj0, obj1) => compareByFieldSpecs(obj0, obj1, eventOrderSpecs));
4351 return objs.map((c) => c._seg);
4352 }
4353 // returns a object with all primitive props that can be compared
4354 function buildSegCompareObj(seg) {
4355 let { eventRange } = seg;
4356 let eventDef = eventRange.def;
4357 let range = eventRange.instance ? eventRange.instance.range : eventRange.range;
4358 let start = range.start ? range.start.valueOf() : 0; // TODO: better support for open-range events
4359 let end = range.end ? range.end.valueOf() : 0; // "
4360 return Object.assign(Object.assign(Object.assign({}, eventDef.extendedProps), eventDef), { id: eventDef.publicId, start,
4361 end, duration: end - start, allDay: Number(eventDef.allDay), _seg: seg });
4362 }
4363 function computeSegDraggable(seg, context) {
4364 let { pluginHooks } = context;
4365 let transformers = pluginHooks.isDraggableTransformers;
4366 let { def, ui } = seg.eventRange;
4367 let val = ui.startEditable;
4368 for (let transformer of transformers) {
4369 val = transformer(val, def, ui, context);
4370 }
4371 return val;
4372 }
4373 function computeSegStartResizable(seg, context) {
4374 return seg.isStart && seg.eventRange.ui.durationEditable && context.options.eventResizableFromStart;
4375 }
4376 function computeSegEndResizable(seg, context) {
4377 return seg.isEnd && seg.eventRange.ui.durationEditable;
4378 }
4379 function buildSegTimeText(seg, timeFormat, context, defaultDisplayEventTime, // defaults to true
4380 defaultDisplayEventEnd, // defaults to true
4381 startOverride, endOverride) {
4382 let { dateEnv, options } = context;
4383 let { displayEventTime, displayEventEnd } = options;
4384 let eventDef = seg.eventRange.def;
4385 let eventInstance = seg.eventRange.instance;
4386 if (displayEventTime == null) {
4387 displayEventTime = defaultDisplayEventTime !== false;
4388 }
4389 if (displayEventEnd == null) {
4390 displayEventEnd = defaultDisplayEventEnd !== false;
4391 }
4392 let wholeEventStart = eventInstance.range.start;
4393 let wholeEventEnd = eventInstance.range.end;
4394 let segStart = startOverride || seg.start || seg.eventRange.range.start;
4395 let segEnd = endOverride || seg.end || seg.eventRange.range.end;
4396 let isStartDay = startOfDay(wholeEventStart).valueOf() === startOfDay(segStart).valueOf();
4397 let isEndDay = startOfDay(addMs(wholeEventEnd, -1)).valueOf() === startOfDay(addMs(segEnd, -1)).valueOf();
4398 if (displayEventTime && !eventDef.allDay && (isStartDay || isEndDay)) {
4399 segStart = isStartDay ? wholeEventStart : segStart;
4400 segEnd = isEndDay ? wholeEventEnd : segEnd;
4401 if (displayEventEnd && eventDef.hasEnd) {
4402 return dateEnv.formatRange(segStart, segEnd, timeFormat, {
4403 forcedStartTzo: startOverride ? null : eventInstance.forcedStartTzo,
4404 forcedEndTzo: endOverride ? null : eventInstance.forcedEndTzo,
4405 });
4406 }
4407 return dateEnv.format(segStart, timeFormat, {
4408 forcedTzo: startOverride ? null : eventInstance.forcedStartTzo, // nooooo, same
4409 });
4410 }
4411 return '';
4412 }
4413 function getSegMeta(seg, todayRange, nowDate) {
4414 let segRange = seg.eventRange.range;
4415 return {
4416 isPast: segRange.end <= (nowDate || todayRange.start),
4417 isFuture: segRange.start >= (nowDate || todayRange.end),
4418 isToday: todayRange && rangeContainsMarker(todayRange, segRange.start),
4419 };
4420 }
4421 function getEventClassNames(props) {
4422 let classNames = ['fc-event'];
4423 if (props.isMirror) {
4424 classNames.push('fc-event-mirror');
4425 }
4426 if (props.isDraggable) {
4427 classNames.push('fc-event-draggable');
4428 }
4429 if (props.isStartResizable || props.isEndResizable) {
4430 classNames.push('fc-event-resizable');
4431 }
4432 if (props.isDragging) {
4433 classNames.push('fc-event-dragging');
4434 }
4435 if (props.isResizing) {
4436 classNames.push('fc-event-resizing');
4437 }
4438 if (props.isSelected) {
4439 classNames.push('fc-event-selected');
4440 }
4441 if (props.isStart) {
4442 classNames.push('fc-event-start');
4443 }
4444 if (props.isEnd) {
4445 classNames.push('fc-event-end');
4446 }
4447 if (props.isPast) {
4448 classNames.push('fc-event-past');
4449 }
4450 if (props.isToday) {
4451 classNames.push('fc-event-today');
4452 }
4453 if (props.isFuture) {
4454 classNames.push('fc-event-future');
4455 }
4456 return classNames;
4457 }
4458 function buildEventRangeKey(eventRange) {
4459 return eventRange.instance
4460 ? eventRange.instance.instanceId
4461 : `${eventRange.def.defId}:${eventRange.range.start.toISOString()}`;
4462 // inverse-background events don't have specific instances. TODO: better solution
4463 }
4464 function getSegAnchorAttrs(seg, context) {
4465 let { def, instance } = seg.eventRange;
4466 let { url } = def;
4467 if (url) {
4468 return { href: url };
4469 }
4470 let { emitter, options } = context;
4471 let { eventInteractive } = options;
4472 if (eventInteractive == null) {
4473 eventInteractive = def.interactive;
4474 if (eventInteractive == null) {
4475 eventInteractive = Boolean(emitter.hasHandlers('eventClick'));
4476 }
4477 }
4478 // mock what happens in EventClicking
4479 if (eventInteractive) {
4480 // only attach keyboard-related handlers because click handler is already done in EventClicking
4481 return createAriaKeyboardAttrs((ev) => {
4482 emitter.trigger('eventClick', {
4483 el: ev.target,
4484 event: new EventImpl(context, def, instance),
4485 jsEvent: ev,
4486 view: context.viewApi,
4487 });
4488 });
4489 }
4490 return {};
4491 }
4492
4493 const STANDARD_PROPS = {
4494 start: identity,
4495 end: identity,
4496 allDay: Boolean,
4497 };
4498 function parseDateSpan(raw, dateEnv, defaultDuration) {
4499 let span = parseOpenDateSpan(raw, dateEnv);
4500 let { range } = span;
4501 if (!range.start) {
4502 return null;
4503 }
4504 if (!range.end) {
4505 if (defaultDuration == null) {
4506 return null;
4507 }
4508 range.end = dateEnv.add(range.start, defaultDuration);
4509 }
4510 return span;
4511 }
4512 /*
4513 TODO: somehow combine with parseRange?
4514 Will return null if the start/end props were present but parsed invalidly.
4515 */
4516 function parseOpenDateSpan(raw, dateEnv) {
4517 let { refined: standardProps, extra } = refineProps(raw, STANDARD_PROPS);
4518 let startMeta = standardProps.start ? dateEnv.createMarkerMeta(standardProps.start) : null;
4519 let endMeta = standardProps.end ? dateEnv.createMarkerMeta(standardProps.end) : null;
4520 let { allDay } = standardProps;
4521 if (allDay == null) {
4522 allDay = (startMeta && startMeta.isTimeUnspecified) &&
4523 (!endMeta || endMeta.isTimeUnspecified);
4524 }
4525 return Object.assign({ range: {
4526 start: startMeta ? startMeta.marker : null,
4527 end: endMeta ? endMeta.marker : null,
4528 }, allDay }, extra);
4529 }
4530 function isDateSpansEqual(span0, span1) {
4531 return rangesEqual(span0.range, span1.range) &&
4532 span0.allDay === span1.allDay &&
4533 isSpanPropsEqual(span0, span1);
4534 }
4535 // the NON-DATE-RELATED props
4536 function isSpanPropsEqual(span0, span1) {
4537 for (let propName in span1) {
4538 if (propName !== 'range' && propName !== 'allDay') {
4539 if (span0[propName] !== span1[propName]) {
4540 return false;
4541 }
4542 }
4543 }
4544 // are there any props that span0 has that span1 DOESN'T have?
4545 // both have range/allDay, so no need to special-case.
4546 for (let propName in span0) {
4547 if (!(propName in span1)) {
4548 return false;
4549 }
4550 }
4551 return true;
4552 }
4553 function buildDateSpanApi(span, dateEnv) {
4554 return Object.assign(Object.assign({}, buildRangeApi(span.range, dateEnv, span.allDay)), { allDay: span.allDay });
4555 }
4556 function buildRangeApiWithTimeZone(range, dateEnv, omitTime) {
4557 return Object.assign(Object.assign({}, buildRangeApi(range, dateEnv, omitTime)), { timeZone: dateEnv.timeZone });
4558 }
4559 function buildRangeApi(range, dateEnv, omitTime) {
4560 return {
4561 start: dateEnv.toDate(range.start),
4562 end: dateEnv.toDate(range.end),
4563 startStr: dateEnv.formatIso(range.start, { omitTime }),
4564 endStr: dateEnv.formatIso(range.end, { omitTime }),
4565 };
4566 }
4567 function fabricateEventRange(dateSpan, eventUiBases, context) {
4568 let res = refineEventDef({ editable: false }, context);
4569 let def = parseEventDef(res.refined, res.extra, '', // sourceId
4570 dateSpan.allDay, true, // hasEnd
4571 context);
4572 return {
4573 def,
4574 ui: compileEventUi(def, eventUiBases),
4575 instance: createEventInstance(def.defId, dateSpan.range),
4576 range: dateSpan.range,
4577 isStart: true,
4578 isEnd: true,
4579 };
4580 }
4581
4582 /*
4583 given a function that resolves a result asynchronously.
4584 the function can either call passed-in success and failure callbacks,
4585 or it can return a promise.
4586 if you need to pass additional params to func, bind them first.
4587 */
4588 function unpromisify(func, normalizedSuccessCallback, normalizedFailureCallback) {
4589 // guard against success/failure callbacks being called more than once
4590 // and guard against a promise AND callback being used together.
4591 let isResolved = false;
4592 let wrappedSuccess = function (res) {
4593 if (!isResolved) {
4594 isResolved = true;
4595 normalizedSuccessCallback(res);
4596 }
4597 };
4598 let wrappedFailure = function (error) {
4599 if (!isResolved) {
4600 isResolved = true;
4601 normalizedFailureCallback(error);
4602 }
4603 };
4604 let res = func(wrappedSuccess, wrappedFailure);
4605 if (res && typeof res.then === 'function') {
4606 res.then(wrappedSuccess, wrappedFailure);
4607 }
4608 }
4609
4610 class JsonRequestError extends Error {
4611 constructor(message, response) {
4612 super(message);
4613 this.response = response;
4614 }
4615 }
4616 function requestJson(method, url, params) {
4617 method = method.toUpperCase();
4618 const fetchOptions = {
4619 method,
4620 };
4621 if (method === 'GET') {
4622 url += (url.indexOf('?') === -1 ? '?' : '&') +
4623 new URLSearchParams(params);
4624 }
4625 else {
4626 fetchOptions.body = new URLSearchParams(params);
4627 fetchOptions.headers = {
4628 'Content-Type': 'application/x-www-form-urlencoded',
4629 };
4630 }
4631 return fetch(url, fetchOptions).then((fetchRes) => {
4632 if (fetchRes.ok) {
4633 return fetchRes.json().then((parsedResponse) => {
4634 return [parsedResponse, fetchRes];
4635 }, () => {
4636 throw new JsonRequestError('Failure parsing JSON', fetchRes);
4637 });
4638 }
4639 else {
4640 throw new JsonRequestError('Request failed', fetchRes);
4641 }
4642 });
4643 }
4644
4645 let canVGrowWithinCell;
4646 function getCanVGrowWithinCell() {
4647 if (canVGrowWithinCell == null) {
4648 canVGrowWithinCell = computeCanVGrowWithinCell();
4649 }
4650 return canVGrowWithinCell;
4651 }
4652 function computeCanVGrowWithinCell() {
4653 // for SSR, because this function is call immediately at top-level
4654 // TODO: just make this logic execute top-level, immediately, instead of doing lazily
4655 if (typeof document === 'undefined') {
4656 return true;
4657 }
4658 let el = document.createElement('div');
4659 el.style.position = 'absolute';
4660 el.style.top = '0px';
4661 el.style.left = '0px';
4662 el.innerHTML = '<table><tr><td><div></div></td></tr></table>';
4663 el.querySelector('table').style.height = '100px';
4664 el.querySelector('div').style.height = '100%';
4665 document.body.appendChild(el);
4666 let div = el.querySelector('div');
4667 let possible = div.offsetHeight > 0;
4668 document.body.removeChild(el);
4669 return possible;
4670 }
4671
4672 class CalendarRoot extends BaseComponent {
4673 constructor() {
4674 super(...arguments);
4675 this.state = {
4676 forPrint: false,
4677 };
4678 this.handleBeforePrint = () => {
4679 flushSync(() => {
4680 this.setState({ forPrint: true });
4681 });
4682 };
4683 this.handleAfterPrint = () => {
4684 flushSync(() => {
4685 this.setState({ forPrint: false });
4686 });
4687 };
4688 }
4689 render() {
4690 let { props } = this;
4691 let { options } = props;
4692 let { forPrint } = this.state;
4693 let isHeightAuto = forPrint || options.height === 'auto' || options.contentHeight === 'auto';
4694 let height = (!isHeightAuto && options.height != null) ? options.height : '';
4695 let classNames = [
4696 'fc',
4697 forPrint ? 'fc-media-print' : 'fc-media-screen',
4698 `fc-direction-${options.direction}`,
4699 props.theme.getClass('root'),
4700 ];
4701 if (!getCanVGrowWithinCell()) {
4702 classNames.push('fc-liquid-hack');
4703 }
4704 return props.children(classNames, height, isHeightAuto, forPrint);
4705 }
4706 componentDidMount() {
4707 let { emitter } = this.props;
4708 emitter.on('_beforeprint', this.handleBeforePrint);
4709 emitter.on('_afterprint', this.handleAfterPrint);
4710 }
4711 componentWillUnmount() {
4712 let { emitter } = this.props;
4713 emitter.off('_beforeprint', this.handleBeforePrint);
4714 emitter.off('_afterprint', this.handleAfterPrint);
4715 }
4716 }
4717
4718 class Interaction {
4719 constructor(settings) {
4720 this.component = settings.component;
4721 this.isHitComboAllowed = settings.isHitComboAllowed || null;
4722 }
4723 destroy() {
4724 }
4725 }
4726 function parseInteractionSettings(component, input) {
4727 return {
4728 component,
4729 el: input.el,
4730 useEventCenter: input.useEventCenter != null ? input.useEventCenter : true,
4731 isHitComboAllowed: input.isHitComboAllowed || null,
4732 };
4733 }
4734 function interactionSettingsToStore(settings) {
4735 return {
4736 [settings.component.uid]: settings,
4737 };
4738 }
4739 // global state
4740 const interactionSettingsStore = {};
4741
4742 class NowTimer extends x$1 {
4743 constructor(props, context) {
4744 super(props, context);
4745 this.handleRefresh = () => {
4746 let timing = this.computeTiming();
4747 if (timing.state.nowDate.valueOf() !== this.state.nowDate.valueOf()) {
4748 this.setState(timing.state);
4749 }
4750 this.clearTimeout();
4751 this.setTimeout(timing.waitMs);
4752 };
4753 this.handleVisibilityChange = () => {
4754 if (!document.hidden) {
4755 this.handleRefresh();
4756 }
4757 };
4758 this.state = this.computeTiming().state;
4759 }
4760 render() {
4761 let { props, state } = this;
4762 return props.children(state.nowDate, state.todayRange);
4763 }
4764 componentDidMount() {
4765 this.setTimeout();
4766 this.context.nowManager.addResetListener(this.handleRefresh);
4767 // fired tab becomes visible after being hidden
4768 document.addEventListener('visibilitychange', this.handleVisibilityChange);
4769 }
4770 componentDidUpdate(prevProps) {
4771 if (prevProps.unit !== this.props.unit) {
4772 this.clearTimeout();
4773 this.setTimeout();
4774 }
4775 }
4776 componentWillUnmount() {
4777 this.clearTimeout();
4778 this.context.nowManager.removeResetListener(this.handleRefresh);
4779 document.removeEventListener('visibilitychange', this.handleVisibilityChange);
4780 }
4781 computeTiming() {
4782 let { props, context } = this;
4783 let unroundedNow = context.nowManager.getDateMarker();
4784 let currentUnitStart = context.dateEnv.startOf(unroundedNow, props.unit);
4785 let nextUnitStart = context.dateEnv.add(currentUnitStart, createDuration(1, props.unit));
4786 let waitMs = nextUnitStart.valueOf() - unroundedNow.valueOf();
4787 // there is a max setTimeout ms value (https://stackoverflow.com/a/3468650/96342)
4788 // ensure no longer than a day
4789 waitMs = Math.min(1000 * 60 * 60 * 24, waitMs);
4790 return {
4791 state: { nowDate: currentUnitStart, todayRange: buildDayRange(currentUnitStart) },
4792 waitMs,
4793 };
4794 }
4795 setTimeout(waitMs = this.computeTiming().waitMs) {
4796 // NOTE: timeout could take longer than expected if tab sleeps,
4797 // which is why we listen to 'visibilitychange'
4798 this.timeoutId = setTimeout(() => {
4799 // NOTE: timeout could also return *earlier* than expected, and we need to wait 2 ms more
4800 // This is why use use same waitMs from computeTiming, so we don't skip an interval while
4801 // .setState() is executing
4802 const timing = this.computeTiming();
4803 this.setState(timing.state, () => {
4804 this.setTimeout(timing.waitMs);
4805 });
4806 }, waitMs);
4807 }
4808 clearTimeout() {
4809 if (this.timeoutId) {
4810 clearTimeout(this.timeoutId);
4811 }
4812 }
4813 }
4814 NowTimer.contextType = ViewContextType;
4815 function buildDayRange(date) {
4816 let start = startOfDay(date);
4817 let end = addDays(start, 1);
4818 return { start, end };
4819 }
4820
4821 class CalendarImpl {
4822 getCurrentData() {
4823 return this.currentDataManager.getCurrentData();
4824 }
4825 dispatch(action) {
4826 this.currentDataManager.dispatch(action);
4827 }
4828 get view() { return this.getCurrentData().viewApi; }
4829 batchRendering(callback) {
4830 callback();
4831 }
4832 updateSize() {
4833 this.trigger('_resize', true);
4834 }
4835 // Options
4836 // -----------------------------------------------------------------------------------------------------------------
4837 setOption(name, val) {
4838 this.dispatch({
4839 type: 'SET_OPTION',
4840 optionName: name,
4841 rawOptionValue: val,
4842 });
4843 }
4844 getOption(name) {
4845 return this.currentDataManager.currentCalendarOptionsInput[name];
4846 }
4847 getAvailableLocaleCodes() {
4848 return Object.keys(this.getCurrentData().availableRawLocales);
4849 }
4850 // Trigger
4851 // -----------------------------------------------------------------------------------------------------------------
4852 on(handlerName, handler) {
4853 let { currentDataManager } = this;
4854 if (currentDataManager.currentCalendarOptionsRefiners[handlerName]) {
4855 currentDataManager.emitter.on(handlerName, handler);
4856 }
4857 else {
4858 console.warn(`Unknown listener name '${handlerName}'`);
4859 }
4860 }
4861 off(handlerName, handler) {
4862 this.currentDataManager.emitter.off(handlerName, handler);
4863 }
4864 // not meant for public use
4865 trigger(handlerName, ...args) {
4866 this.currentDataManager.emitter.trigger(handlerName, ...args);
4867 }
4868 // View
4869 // -----------------------------------------------------------------------------------------------------------------
4870 changeView(viewType, dateOrRange) {
4871 this.batchRendering(() => {
4872 this.unselect();
4873 if (dateOrRange) {
4874 if (dateOrRange.start && dateOrRange.end) { // a range
4875 this.dispatch({
4876 type: 'CHANGE_VIEW_TYPE',
4877 viewType,
4878 });
4879 this.dispatch({
4880 type: 'SET_OPTION',
4881 optionName: 'visibleRange',
4882 rawOptionValue: dateOrRange,
4883 });
4884 }
4885 else {
4886 let { dateEnv } = this.getCurrentData();
4887 this.dispatch({
4888 type: 'CHANGE_VIEW_TYPE',
4889 viewType,
4890 dateMarker: dateEnv.createMarker(dateOrRange),
4891 });
4892 }
4893 }
4894 else {
4895 this.dispatch({
4896 type: 'CHANGE_VIEW_TYPE',
4897 viewType,
4898 });
4899 }
4900 });
4901 }
4902 // Forces navigation to a view for the given date.
4903 // `viewType` can be a specific view name or a generic one like "week" or "day".
4904 // needs to change
4905 zoomTo(dateMarker, viewType) {
4906 let state = this.getCurrentData();
4907 let spec;
4908 viewType = viewType || 'day'; // day is default zoom
4909 spec = state.viewSpecs[viewType] || this.getUnitViewSpec(viewType);
4910 this.unselect();
4911 if (spec) {
4912 this.dispatch({
4913 type: 'CHANGE_VIEW_TYPE',
4914 viewType: spec.type,
4915 dateMarker,
4916 });
4917 }
4918 else {
4919 this.dispatch({
4920 type: 'CHANGE_DATE',
4921 dateMarker,
4922 });
4923 }
4924 }
4925 // Given a duration singular unit, like "week" or "day", finds a matching view spec.
4926 // Preference is given to views that have corresponding buttons.
4927 getUnitViewSpec(unit) {
4928 let { viewSpecs, toolbarConfig } = this.getCurrentData();
4929 let viewTypes = [].concat(toolbarConfig.header ? toolbarConfig.header.viewsWithButtons : [], toolbarConfig.footer ? toolbarConfig.footer.viewsWithButtons : []);
4930 let i;
4931 let spec;
4932 for (let viewType in viewSpecs) {
4933 viewTypes.push(viewType);
4934 }
4935 for (i = 0; i < viewTypes.length; i += 1) {
4936 spec = viewSpecs[viewTypes[i]];
4937 if (spec) {
4938 if (spec.singleUnit === unit) {
4939 return spec;
4940 }
4941 }
4942 }
4943 return null;
4944 }
4945 // Current Date
4946 // -----------------------------------------------------------------------------------------------------------------
4947 prev() {
4948 this.unselect();
4949 this.dispatch({ type: 'PREV' });
4950 }
4951 next() {
4952 this.unselect();
4953 this.dispatch({ type: 'NEXT' });
4954 }
4955 prevYear() {
4956 let state = this.getCurrentData();
4957 this.unselect();
4958 this.dispatch({
4959 type: 'CHANGE_DATE',
4960 dateMarker: state.dateEnv.addYears(state.currentDate, -1),
4961 });
4962 }
4963 nextYear() {
4964 let state = this.getCurrentData();
4965 this.unselect();
4966 this.dispatch({
4967 type: 'CHANGE_DATE',
4968 dateMarker: state.dateEnv.addYears(state.currentDate, 1),
4969 });
4970 }
4971 today() {
4972 let state = this.getCurrentData();
4973 this.unselect();
4974 this.dispatch({
4975 type: 'CHANGE_DATE',
4976 dateMarker: state.nowManager.getDateMarker(),
4977 });
4978 }
4979 gotoDate(zonedDateInput) {
4980 let state = this.getCurrentData();
4981 this.unselect();
4982 this.dispatch({
4983 type: 'CHANGE_DATE',
4984 dateMarker: state.dateEnv.createMarker(zonedDateInput),
4985 });
4986 }
4987 incrementDate(deltaInput) {
4988 let state = this.getCurrentData();
4989 let delta = createDuration(deltaInput);
4990 if (delta) { // else, warn about invalid input?
4991 this.unselect();
4992 this.dispatch({
4993 type: 'CHANGE_DATE',
4994 dateMarker: state.dateEnv.add(state.currentDate, delta),
4995 });
4996 }
4997 }
4998 getDate() {
4999 let state = this.getCurrentData();
5000 return state.dateEnv.toDate(state.currentDate);
5001 }
5002 // Date Formatting Utils
5003 // -----------------------------------------------------------------------------------------------------------------
5004 formatDate(d, formatter) {
5005 let { dateEnv } = this.getCurrentData();
5006 return dateEnv.format(dateEnv.createMarker(d), createFormatter(formatter));
5007 }
5008 // `settings` is for formatter AND isEndExclusive
5009 formatRange(d0, d1, settings) {
5010 let { dateEnv } = this.getCurrentData();
5011 return dateEnv.formatRange(dateEnv.createMarker(d0), dateEnv.createMarker(d1), createFormatter(settings), settings);
5012 }
5013 formatIso(d, omitTime) {
5014 let { dateEnv } = this.getCurrentData();
5015 return dateEnv.formatIso(dateEnv.createMarker(d), { omitTime });
5016 }
5017 // Date Selection / Event Selection / DayClick
5018 // -----------------------------------------------------------------------------------------------------------------
5019 select(dateOrObj, endDate) {
5020 let selectionInput;
5021 if (endDate == null) {
5022 if (dateOrObj.start != null) {
5023 selectionInput = dateOrObj;
5024 }
5025 else {
5026 selectionInput = {
5027 start: dateOrObj,
5028 end: null,
5029 };
5030 }
5031 }
5032 else {
5033 selectionInput = {
5034 start: dateOrObj,
5035 end: endDate,
5036 };
5037 }
5038 let state = this.getCurrentData();
5039 let selection = parseDateSpan(selectionInput, state.dateEnv, createDuration({ days: 1 }));
5040 if (selection) { // throw parse error otherwise?
5041 this.dispatch({ type: 'SELECT_DATES', selection });
5042 triggerDateSelect(selection, null, state);
5043 }
5044 }
5045 unselect(pev) {
5046 let state = this.getCurrentData();
5047 if (state.dateSelection) {
5048 this.dispatch({ type: 'UNSELECT_DATES' });
5049 triggerDateUnselect(pev, state);
5050 }
5051 }
5052 // Public Events API
5053 // -----------------------------------------------------------------------------------------------------------------
5054 addEvent(eventInput, sourceInput) {
5055 if (eventInput instanceof EventImpl) {
5056 let def = eventInput._def;
5057 let instance = eventInput._instance;
5058 let currentData = this.getCurrentData();
5059 // not already present? don't want to add an old snapshot
5060 if (!currentData.eventStore.defs[def.defId]) {
5061 this.dispatch({
5062 type: 'ADD_EVENTS',
5063 eventStore: eventTupleToStore({ def, instance }), // TODO: better util for two args?
5064 });
5065 this.triggerEventAdd(eventInput);
5066 }
5067 return eventInput;
5068 }
5069 let state = this.getCurrentData();
5070 let eventSource;
5071 if (sourceInput instanceof EventSourceImpl) {
5072 eventSource = sourceInput.internalEventSource;
5073 }
5074 else if (typeof sourceInput === 'boolean') {
5075 if (sourceInput) { // true. part of the first event source
5076 [eventSource] = hashValuesToArray(state.eventSources);
5077 }
5078 }
5079 else if (sourceInput != null) { // an ID. accepts a number too
5080 let sourceApi = this.getEventSourceById(sourceInput); // TODO: use an internal function
5081 if (!sourceApi) {
5082 console.warn(`Could not find an event source with ID "${sourceInput}"`); // TODO: test
5083 return null;
5084 }
5085 eventSource = sourceApi.internalEventSource;
5086 }
5087 let tuple = parseEvent(eventInput, eventSource, state, false);
5088 if (tuple) {
5089 let newEventApi = new EventImpl(state, tuple.def, tuple.def.recurringDef ? null : tuple.instance);
5090 this.dispatch({
5091 type: 'ADD_EVENTS',
5092 eventStore: eventTupleToStore(tuple),
5093 });
5094 this.triggerEventAdd(newEventApi);
5095 return newEventApi;
5096 }
5097 return null;
5098 }
5099 triggerEventAdd(eventApi) {
5100 let { emitter } = this.getCurrentData();
5101 emitter.trigger('eventAdd', {
5102 event: eventApi,
5103 relatedEvents: [],
5104 revert: () => {
5105 this.dispatch({
5106 type: 'REMOVE_EVENTS',
5107 eventStore: eventApiToStore(eventApi),
5108 });
5109 },
5110 });
5111 }
5112 // TODO: optimize
5113 getEventById(id) {
5114 let state = this.getCurrentData();
5115 let { defs, instances } = state.eventStore;
5116 id = String(id);
5117 for (let defId in defs) {
5118 let def = defs[defId];
5119 if (def.publicId === id) {
5120 if (def.recurringDef) {
5121 return new EventImpl(state, def, null);
5122 }
5123 for (let instanceId in instances) {
5124 let instance = instances[instanceId];
5125 if (instance.defId === def.defId) {
5126 return new EventImpl(state, def, instance);
5127 }
5128 }
5129 }
5130 }
5131 return null;
5132 }
5133 getEvents() {
5134 let currentData = this.getCurrentData();
5135 return buildEventApis(currentData.eventStore, currentData);
5136 }
5137 removeAllEvents() {
5138 this.dispatch({ type: 'REMOVE_ALL_EVENTS' });
5139 }
5140 // Public Event Sources API
5141 // -----------------------------------------------------------------------------------------------------------------
5142 getEventSources() {
5143 let state = this.getCurrentData();
5144 let sourceHash = state.eventSources;
5145 let sourceApis = [];
5146 for (let internalId in sourceHash) {
5147 sourceApis.push(new EventSourceImpl(state, sourceHash[internalId]));
5148 }
5149 return sourceApis;
5150 }
5151 getEventSourceById(id) {
5152 let state = this.getCurrentData();
5153 let sourceHash = state.eventSources;
5154 id = String(id);
5155 for (let sourceId in sourceHash) {
5156 if (sourceHash[sourceId].publicId === id) {
5157 return new EventSourceImpl(state, sourceHash[sourceId]);
5158 }
5159 }
5160 return null;
5161 }
5162 addEventSource(sourceInput) {
5163 let state = this.getCurrentData();
5164 if (sourceInput instanceof EventSourceImpl) {
5165 // not already present? don't want to add an old snapshot
5166 if (!state.eventSources[sourceInput.internalEventSource.sourceId]) {
5167 this.dispatch({
5168 type: 'ADD_EVENT_SOURCES',
5169 sources: [sourceInput.internalEventSource],
5170 });
5171 }
5172 return sourceInput;
5173 }
5174 let eventSource = parseEventSource(sourceInput, state);
5175 if (eventSource) { // TODO: error otherwise?
5176 this.dispatch({ type: 'ADD_EVENT_SOURCES', sources: [eventSource] });
5177 return new EventSourceImpl(state, eventSource);
5178 }
5179 return null;
5180 }
5181 removeAllEventSources() {
5182 this.dispatch({ type: 'REMOVE_ALL_EVENT_SOURCES' });
5183 }
5184 refetchEvents() {
5185 this.dispatch({ type: 'FETCH_EVENT_SOURCES', isRefetch: true });
5186 }
5187 // Scroll
5188 // -----------------------------------------------------------------------------------------------------------------
5189 scrollToTime(timeInput) {
5190 let time = createDuration(timeInput);
5191 if (time) {
5192 this.trigger('_scrollRequest', { time });
5193 }
5194 }
5195 }
5196
5197 function pointInsideRect(point, rect) {
5198 return point.left >= rect.left &&
5199 point.left < rect.right &&
5200 point.top >= rect.top &&
5201 point.top < rect.bottom;
5202 }
5203 // Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false
5204 function intersectRects(rect1, rect2) {
5205 let res = {
5206 left: Math.max(rect1.left, rect2.left),
5207 right: Math.min(rect1.right, rect2.right),
5208 top: Math.max(rect1.top, rect2.top),
5209 bottom: Math.min(rect1.bottom, rect2.bottom),
5210 };
5211 if (res.left < res.right && res.top < res.bottom) {
5212 return res;
5213 }
5214 return false;
5215 }
5216 function translateRect(rect, deltaX, deltaY) {
5217 return {
5218 left: rect.left + deltaX,
5219 right: rect.right + deltaX,
5220 top: rect.top + deltaY,
5221 bottom: rect.bottom + deltaY,
5222 };
5223 }
5224 // Returns a new point that will have been moved to reside within the given rectangle
5225 function constrainPoint(point, rect) {
5226 return {
5227 left: Math.min(Math.max(point.left, rect.left), rect.right),
5228 top: Math.min(Math.max(point.top, rect.top), rect.bottom),
5229 };
5230 }
5231 // Returns a point that is the center of the given rectangle
5232 function getRectCenter(rect) {
5233 return {
5234 left: (rect.left + rect.right) / 2,
5235 top: (rect.top + rect.bottom) / 2,
5236 };
5237 }
5238 // Subtracts point2's coordinates from point1's coordinates, returning a delta
5239 function diffPoints(point1, point2) {
5240 return {
5241 left: point1.left - point2.left,
5242 top: point1.top - point2.top,
5243 };
5244 }
5245
5246 const EMPTY_EVENT_STORE = createEmptyEventStore(); // for purecomponents. TODO: keep elsewhere
5247 class Splitter {
5248 constructor() {
5249 this.getKeysForEventDefs = memoize(this._getKeysForEventDefs);
5250 this.splitDateSelection = memoize(this._splitDateSpan);
5251 this.splitEventStore = memoize(this._splitEventStore);
5252 this.splitIndividualUi = memoize(this._splitIndividualUi);
5253 this.splitEventDrag = memoize(this._splitInteraction);
5254 this.splitEventResize = memoize(this._splitInteraction);
5255 this.eventUiBuilders = {}; // TODO: typescript protection
5256 }
5257 splitProps(props) {
5258 let keyInfos = this.getKeyInfo(props);
5259 let defKeys = this.getKeysForEventDefs(props.eventStore);
5260 let dateSelections = this.splitDateSelection(props.dateSelection);
5261 let individualUi = this.splitIndividualUi(props.eventUiBases, defKeys); // the individual *bases*
5262 let eventStores = this.splitEventStore(props.eventStore, defKeys);
5263 let eventDrags = this.splitEventDrag(props.eventDrag);
5264 let eventResizes = this.splitEventResize(props.eventResize);
5265 let splitProps = {};
5266 this.eventUiBuilders = mapHash(keyInfos, (info, key) => this.eventUiBuilders[key] || memoize(buildEventUiForKey));
5267 for (let key in keyInfos) {
5268 let keyInfo = keyInfos[key];
5269 let eventStore = eventStores[key] || EMPTY_EVENT_STORE;
5270 let buildEventUi = this.eventUiBuilders[key];
5271 splitProps[key] = {
5272 businessHours: keyInfo.businessHours || props.businessHours,
5273 dateSelection: dateSelections[key] || null,
5274 eventStore,
5275 eventUiBases: buildEventUi(props.eventUiBases[''], keyInfo.ui, individualUi[key]),
5276 eventSelection: eventStore.instances[props.eventSelection] ? props.eventSelection : '',
5277 eventDrag: eventDrags[key] || null,
5278 eventResize: eventResizes[key] || null,
5279 };
5280 }
5281 return splitProps;
5282 }
5283 _splitDateSpan(dateSpan) {
5284 let dateSpans = {};
5285 if (dateSpan) {
5286 let keys = this.getKeysForDateSpan(dateSpan);
5287 for (let key of keys) {
5288 dateSpans[key] = dateSpan;
5289 }
5290 }
5291 return dateSpans;
5292 }
5293 _getKeysForEventDefs(eventStore) {
5294 return mapHash(eventStore.defs, (eventDef) => this.getKeysForEventDef(eventDef));
5295 }
5296 _splitEventStore(eventStore, defKeys) {
5297 let { defs, instances } = eventStore;
5298 let splitStores = {};
5299 for (let defId in defs) {
5300 for (let key of defKeys[defId]) {
5301 if (!splitStores[key]) {
5302 splitStores[key] = createEmptyEventStore();
5303 }
5304 splitStores[key].defs[defId] = defs[defId];
5305 }
5306 }
5307 for (let instanceId in instances) {
5308 let instance = instances[instanceId];
5309 for (let key of defKeys[instance.defId]) {
5310 if (splitStores[key]) { // must have already been created
5311 splitStores[key].instances[instanceId] = instance;
5312 }
5313 }
5314 }
5315 return splitStores;
5316 }
5317 _splitIndividualUi(eventUiBases, defKeys) {
5318 let splitHashes = {};
5319 for (let defId in eventUiBases) {
5320 if (defId) { // not the '' key
5321 for (let key of defKeys[defId]) {
5322 if (!splitHashes[key]) {
5323 splitHashes[key] = {};
5324 }
5325 splitHashes[key][defId] = eventUiBases[defId];
5326 }
5327 }
5328 }
5329 return splitHashes;
5330 }
5331 _splitInteraction(interaction) {
5332 let splitStates = {};
5333 if (interaction) {
5334 let affectedStores = this._splitEventStore(interaction.affectedEvents, this._getKeysForEventDefs(interaction.affectedEvents));
5335 // can't rely on defKeys because event data is mutated
5336 let mutatedKeysByDefId = this._getKeysForEventDefs(interaction.mutatedEvents);
5337 let mutatedStores = this._splitEventStore(interaction.mutatedEvents, mutatedKeysByDefId);
5338 let populate = (key) => {
5339 if (!splitStates[key]) {
5340 splitStates[key] = {
5341 affectedEvents: affectedStores[key] || EMPTY_EVENT_STORE,
5342 mutatedEvents: mutatedStores[key] || EMPTY_EVENT_STORE,
5343 isEvent: interaction.isEvent,
5344 };
5345 }
5346 };
5347 for (let key in affectedStores) {
5348 populate(key);
5349 }
5350 for (let key in mutatedStores) {
5351 populate(key);
5352 }
5353 }
5354 return splitStates;
5355 }
5356 }
5357 function buildEventUiForKey(allUi, eventUiForKey, individualUi) {
5358 let baseParts = [];
5359 if (allUi) {
5360 baseParts.push(allUi);
5361 }
5362 if (eventUiForKey) {
5363 baseParts.push(eventUiForKey);
5364 }
5365 let stuff = {
5366 '': combineEventUis(baseParts),
5367 };
5368 if (individualUi) {
5369 Object.assign(stuff, individualUi);
5370 }
5371 return stuff;
5372 }
5373
5374 function getDateMeta(date, todayRange, nowDate, dateProfile) {
5375 return {
5376 dow: date.getUTCDay(),
5377 isDisabled: Boolean(dateProfile && (!dateProfile.activeRange || !rangeContainsMarker(dateProfile.activeRange, date))),
5378 isOther: Boolean(dateProfile && !rangeContainsMarker(dateProfile.currentRange, date)),
5379 isToday: Boolean(todayRange && rangeContainsMarker(todayRange, date)),
5380 isPast: Boolean(nowDate ? (date < nowDate) : todayRange ? (date < todayRange.start) : false),
5381 isFuture: Boolean(nowDate ? (date > nowDate) : todayRange ? (date >= todayRange.end) : false),
5382 };
5383 }
5384 function getDayClassNames(meta, theme) {
5385 let classNames = [
5386 'fc-day',
5387 `fc-day-${DAY_IDS[meta.dow]}`,
5388 ];
5389 if (meta.isDisabled) {
5390 classNames.push('fc-day-disabled');
5391 }
5392 else {
5393 if (meta.isToday) {
5394 classNames.push('fc-day-today');
5395 classNames.push(theme.getClass('today'));
5396 }
5397 if (meta.isPast) {
5398 classNames.push('fc-day-past');
5399 }
5400 if (meta.isFuture) {
5401 classNames.push('fc-day-future');
5402 }
5403 if (meta.isOther) {
5404 classNames.push('fc-day-other');
5405 }
5406 }
5407 return classNames;
5408 }
5409 function getSlotClassNames(meta, theme) {
5410 let classNames = [
5411 'fc-slot',
5412 `fc-slot-${DAY_IDS[meta.dow]}`,
5413 ];
5414 if (meta.isDisabled) {
5415 classNames.push('fc-slot-disabled');
5416 }
5417 else {
5418 if (meta.isToday) {
5419 classNames.push('fc-slot-today');
5420 classNames.push(theme.getClass('today'));
5421 }
5422 if (meta.isPast) {
5423 classNames.push('fc-slot-past');
5424 }
5425 if (meta.isFuture) {
5426 classNames.push('fc-slot-future');
5427 }
5428 }
5429 return classNames;
5430 }
5431
5432 const DAY_FORMAT = createFormatter({ year: 'numeric', month: 'long', day: 'numeric' });
5433 const WEEK_FORMAT = createFormatter({ week: 'long' });
5434 function buildNavLinkAttrs(context, dateMarker, viewType = 'day', isTabbable = true) {
5435 const { dateEnv, options, calendarApi } = context;
5436 let dateStr = dateEnv.format(dateMarker, viewType === 'week' ? WEEK_FORMAT : DAY_FORMAT);
5437 if (options.navLinks) {
5438 let zonedDate = dateEnv.toDate(dateMarker);
5439 const handleInteraction = (ev) => {
5440 let customAction = viewType === 'day' ? options.navLinkDayClick :
5441 viewType === 'week' ? options.navLinkWeekClick : null;
5442 if (typeof customAction === 'function') {
5443 customAction.call(calendarApi, dateEnv.toDate(dateMarker), ev);
5444 }
5445 else {
5446 if (typeof customAction === 'string') {
5447 viewType = customAction;
5448 }
5449 calendarApi.zoomTo(dateMarker, viewType);
5450 }
5451 };
5452 return Object.assign({ title: formatWithOrdinals(options.navLinkHint, [dateStr, zonedDate], dateStr), 'data-navlink': '' }, (isTabbable
5453 ? createAriaClickAttrs(handleInteraction)
5454 : { onClick: handleInteraction }));
5455 }
5456 return { 'aria-label': dateStr };
5457 }
5458
5459 let _isRtlScrollbarOnLeft = null;
5460 function getIsRtlScrollbarOnLeft() {
5461 if (_isRtlScrollbarOnLeft === null) {
5462 _isRtlScrollbarOnLeft = computeIsRtlScrollbarOnLeft();
5463 }
5464 return _isRtlScrollbarOnLeft;
5465 }
5466 function computeIsRtlScrollbarOnLeft() {
5467 let outerEl = document.createElement('div');
5468 applyStyle(outerEl, {
5469 position: 'absolute',
5470 top: -1000,
5471 left: 0,
5472 border: 0,
5473 padding: 0,
5474 overflow: 'scroll',
5475 direction: 'rtl',
5476 });
5477 outerEl.innerHTML = '<div></div>';
5478 document.body.appendChild(outerEl);
5479 let innerEl = outerEl.firstChild;
5480 let res = innerEl.getBoundingClientRect().left > outerEl.getBoundingClientRect().left;
5481 removeElement(outerEl);
5482 return res;
5483 }
5484
5485 let _scrollbarWidths;
5486 function getScrollbarWidths() {
5487 if (!_scrollbarWidths) {
5488 _scrollbarWidths = computeScrollbarWidths();
5489 }
5490 return _scrollbarWidths;
5491 }
5492 function computeScrollbarWidths() {
5493 let el = document.createElement('div');
5494 el.style.overflow = 'scroll';
5495 el.style.position = 'absolute';
5496 el.style.top = '-9999px';
5497 el.style.left = '-9999px';
5498 document.body.appendChild(el);
5499 let res = computeScrollbarWidthsForEl(el);
5500 document.body.removeChild(el);
5501 return res;
5502 }
5503 // WARNING: will include border
5504 function computeScrollbarWidthsForEl(el) {
5505 return {
5506 x: el.offsetHeight - el.clientHeight,
5507 y: el.offsetWidth - el.clientWidth,
5508 };
5509 }
5510
5511 function computeEdges(el, getPadding = false) {
5512 let computedStyle = window.getComputedStyle(el);
5513 let borderLeft = parseInt(computedStyle.borderLeftWidth, 10) || 0;
5514 let borderRight = parseInt(computedStyle.borderRightWidth, 10) || 0;
5515 let borderTop = parseInt(computedStyle.borderTopWidth, 10) || 0;
5516 let borderBottom = parseInt(computedStyle.borderBottomWidth, 10) || 0;
5517 let badScrollbarWidths = computeScrollbarWidthsForEl(el); // includes border!
5518 let scrollbarLeftRight = badScrollbarWidths.y - borderLeft - borderRight;
5519 let scrollbarBottom = badScrollbarWidths.x - borderTop - borderBottom;
5520 let res = {
5521 borderLeft,
5522 borderRight,
5523 borderTop,
5524 borderBottom,
5525 scrollbarBottom,
5526 scrollbarLeft: 0,
5527 scrollbarRight: 0,
5528 };
5529 if (getIsRtlScrollbarOnLeft() && computedStyle.direction === 'rtl') { // is the scrollbar on the left side?
5530 res.scrollbarLeft = scrollbarLeftRight;
5531 }
5532 else {
5533 res.scrollbarRight = scrollbarLeftRight;
5534 }
5535 if (getPadding) {
5536 res.paddingLeft = parseInt(computedStyle.paddingLeft, 10) || 0;
5537 res.paddingRight = parseInt(computedStyle.paddingRight, 10) || 0;
5538 res.paddingTop = parseInt(computedStyle.paddingTop, 10) || 0;
5539 res.paddingBottom = parseInt(computedStyle.paddingBottom, 10) || 0;
5540 }
5541 return res;
5542 }
5543 function computeInnerRect(el, goWithinPadding = false, doFromWindowViewport) {
5544 let outerRect = doFromWindowViewport ? el.getBoundingClientRect() : computeRect(el);
5545 let edges = computeEdges(el, goWithinPadding);
5546 let res = {
5547 left: outerRect.left + edges.borderLeft + edges.scrollbarLeft,
5548 right: outerRect.right - edges.borderRight - edges.scrollbarRight,
5549 top: outerRect.top + edges.borderTop,
5550 bottom: outerRect.bottom - edges.borderBottom - edges.scrollbarBottom,
5551 };
5552 if (goWithinPadding) {
5553 res.left += edges.paddingLeft;
5554 res.right -= edges.paddingRight;
5555 res.top += edges.paddingTop;
5556 res.bottom -= edges.paddingBottom;
5557 }
5558 return res;
5559 }
5560 function computeRect(el) {
5561 let rect = el.getBoundingClientRect();
5562 return {
5563 left: rect.left + window.scrollX,
5564 top: rect.top + window.scrollY,
5565 right: rect.right + window.scrollX,
5566 bottom: rect.bottom + window.scrollY,
5567 };
5568 }
5569 function computeClippedClientRect(el) {
5570 let clippingParents = getClippingParents(el);
5571 let rect = el.getBoundingClientRect();
5572 for (let clippingParent of clippingParents) {
5573 let intersection = intersectRects(rect, clippingParent.getBoundingClientRect());
5574 if (intersection) {
5575 rect = intersection;
5576 }
5577 else {
5578 return null;
5579 }
5580 }
5581 return rect;
5582 }
5583 // does not return window
5584 function getClippingParents(el) {
5585 let parents = [];
5586 while (el instanceof HTMLElement) { // will stop when gets to document or null
5587 let computedStyle = window.getComputedStyle(el);
5588 if (computedStyle.position === 'fixed') {
5589 break;
5590 }
5591 if ((/(auto|scroll)/).test(computedStyle.overflow + computedStyle.overflowY + computedStyle.overflowX)) {
5592 parents.push(el);
5593 }
5594 el = el.parentNode;
5595 }
5596 return parents;
5597 }
5598
5599 /*
5600 Records offset information for a set of elements, relative to an origin element.
5601 Can record the left/right OR the top/bottom OR both.
5602 Provides methods for querying the cache by position.
5603 */
5604 class PositionCache {
5605 constructor(originEl, els, isHorizontal, isVertical) {
5606 this.els = els;
5607 let originClientRect = this.originClientRect = originEl.getBoundingClientRect(); // relative to viewport top-left
5608 if (isHorizontal) {
5609 this.buildElHorizontals(originClientRect.left);
5610 }
5611 if (isVertical) {
5612 this.buildElVerticals(originClientRect.top);
5613 }
5614 }
5615 // Populates the left/right internal coordinate arrays
5616 buildElHorizontals(originClientLeft) {
5617 let lefts = [];
5618 let rights = [];
5619 for (let el of this.els) {
5620 let rect = el.getBoundingClientRect();
5621 lefts.push(rect.left - originClientLeft);
5622 rights.push(rect.right - originClientLeft);
5623 }
5624 this.lefts = lefts;
5625 this.rights = rights;
5626 }
5627 // Populates the top/bottom internal coordinate arrays
5628 buildElVerticals(originClientTop) {
5629 let tops = [];
5630 let bottoms = [];
5631 for (let el of this.els) {
5632 let rect = el.getBoundingClientRect();
5633 tops.push(rect.top - originClientTop);
5634 bottoms.push(rect.bottom - originClientTop);
5635 }
5636 this.tops = tops;
5637 this.bottoms = bottoms;
5638 }
5639 // Given a left offset (from document left), returns the index of the el that it horizontally intersects.
5640 // If no intersection is made, returns undefined.
5641 leftToIndex(leftPosition) {
5642 let { lefts, rights } = this;
5643 let len = lefts.length;
5644 let i;
5645 for (i = 0; i < len; i += 1) {
5646 if (leftPosition >= lefts[i] && leftPosition < rights[i]) {
5647 return i;
5648 }
5649 }
5650 return undefined; // TODO: better
5651 }
5652 // Given a top offset (from document top), returns the index of the el that it vertically intersects.
5653 // If no intersection is made, returns undefined.
5654 topToIndex(topPosition) {
5655 let { tops, bottoms } = this;
5656 let len = tops.length;
5657 let i;
5658 for (i = 0; i < len; i += 1) {
5659 if (topPosition >= tops[i] && topPosition < bottoms[i]) {
5660 return i;
5661 }
5662 }
5663 return undefined; // TODO: better
5664 }
5665 // Gets the width of the element at the given index
5666 getWidth(leftIndex) {
5667 return this.rights[leftIndex] - this.lefts[leftIndex];
5668 }
5669 // Gets the height of the element at the given index
5670 getHeight(topIndex) {
5671 return this.bottoms[topIndex] - this.tops[topIndex];
5672 }
5673 similarTo(otherCache) {
5674 return similarNumArrays(this.tops || [], otherCache.tops || []) &&
5675 similarNumArrays(this.bottoms || [], otherCache.bottoms || []) &&
5676 similarNumArrays(this.lefts || [], otherCache.lefts || []) &&
5677 similarNumArrays(this.rights || [], otherCache.rights || []);
5678 }
5679 }
5680 function similarNumArrays(a, b) {
5681 const len = a.length;
5682 if (len !== b.length) {
5683 return false;
5684 }
5685 for (let i = 0; i < len; i++) {
5686 if (Math.round(a[i]) !== Math.round(b[i])) {
5687 return false;
5688 }
5689 }
5690 return true;
5691 }
5692
5693 /* eslint max-classes-per-file: "off" */
5694 /*
5695 An object for getting/setting scroll-related information for an element.
5696 Internally, this is done very differently for window versus DOM element,
5697 so this object serves as a common interface.
5698 */
5699 class ScrollController {
5700 getMaxScrollTop() {
5701 return this.getScrollHeight() - this.getClientHeight();
5702 }
5703 getMaxScrollLeft() {
5704 return this.getScrollWidth() - this.getClientWidth();
5705 }
5706 canScrollVertically() {
5707 return this.getMaxScrollTop() > 0;
5708 }
5709 canScrollHorizontally() {
5710 return this.getMaxScrollLeft() > 0;
5711 }
5712 canScrollUp() {
5713 return this.getScrollTop() > 0;
5714 }
5715 canScrollDown() {
5716 return this.getScrollTop() < this.getMaxScrollTop();
5717 }
5718 canScrollLeft() {
5719 return this.getScrollLeft() > 0;
5720 }
5721 canScrollRight() {
5722 return this.getScrollLeft() < this.getMaxScrollLeft();
5723 }
5724 }
5725 class ElementScrollController extends ScrollController {
5726 constructor(el) {
5727 super();
5728 this.el = el;
5729 }
5730 getScrollTop() {
5731 return this.el.scrollTop;
5732 }
5733 getScrollLeft() {
5734 return this.el.scrollLeft;
5735 }
5736 setScrollTop(top) {
5737 this.el.scrollTop = top;
5738 }
5739 setScrollLeft(left) {
5740 this.el.scrollLeft = left;
5741 }
5742 getScrollWidth() {
5743 return this.el.scrollWidth;
5744 }
5745 getScrollHeight() {
5746 return this.el.scrollHeight;
5747 }
5748 getClientHeight() {
5749 return this.el.clientHeight;
5750 }
5751 getClientWidth() {
5752 return this.el.clientWidth;
5753 }
5754 }
5755 class WindowScrollController extends ScrollController {
5756 getScrollTop() {
5757 return window.scrollY;
5758 }
5759 getScrollLeft() {
5760 return window.scrollX;
5761 }
5762 setScrollTop(n) {
5763 window.scroll(window.scrollX, n);
5764 }
5765 setScrollLeft(n) {
5766 window.scroll(n, window.scrollY);
5767 }
5768 getScrollWidth() {
5769 return document.documentElement.scrollWidth;
5770 }
5771 getScrollHeight() {
5772 return document.documentElement.scrollHeight;
5773 }
5774 getClientHeight() {
5775 return document.documentElement.clientHeight;
5776 }
5777 getClientWidth() {
5778 return document.documentElement.clientWidth;
5779 }
5780 }
5781
5782 /*
5783 an INTERACTABLE date component
5784
5785 PURPOSES:
5786 - hook up to fg, fill, and mirror renderers
5787 - interface for dragging and hits
5788 */
5789 class DateComponent extends BaseComponent {
5790 constructor() {
5791 super(...arguments);
5792 this.uid = guid();
5793 }
5794 // Hit System
5795 // -----------------------------------------------------------------------------------------------------------------
5796 prepareHits() {
5797 }
5798 queryHit(positionLeft, positionTop, elWidth, elHeight) {
5799 return null; // this should be abstract
5800 }
5801 // Pointer Interaction Utils
5802 // -----------------------------------------------------------------------------------------------------------------
5803 isValidSegDownEl(el) {
5804 return !this.props.eventDrag && // HACK
5805 !this.props.eventResize && // HACK
5806 !elementClosest(el, '.fc-event-mirror');
5807 }
5808 isValidDateDownEl(el) {
5809 return !elementClosest(el, '.fc-event:not(.fc-bg-event)') &&
5810 !elementClosest(el, '.fc-more-link') && // a "more.." link
5811 !elementClosest(el, 'a[data-navlink]') && // a clickable nav link
5812 !elementClosest(el, '.fc-popover'); // hack
5813 }
5814 }
5815
5816 class NamedTimeZoneImpl {
5817 constructor(timeZoneName) {
5818 this.timeZoneName = timeZoneName;
5819 }
5820 }
5821
5822 class SegHierarchy {
5823 constructor(getEntryThickness = (entry) => {
5824 // if no thickness known, assume 1 (if 0, so small it always fits)
5825 return entry.thickness || 1;
5826 }) {
5827 this.getEntryThickness = getEntryThickness;
5828 // settings
5829 this.strictOrder = false;
5830 this.allowReslicing = false;
5831 this.maxCoord = -1; // -1 means no max
5832 this.maxStackCnt = -1; // -1 means no max
5833 this.levelCoords = []; // ordered
5834 this.entriesByLevel = []; // parallel with levelCoords
5835 this.stackCnts = {}; // TODO: use better technique!?
5836 }
5837 addSegs(inputs) {
5838 let hiddenEntries = [];
5839 for (let input of inputs) {
5840 this.insertEntry(input, hiddenEntries);
5841 }
5842 return hiddenEntries;
5843 }
5844 insertEntry(entry, hiddenEntries) {
5845 let insertion = this.findInsertion(entry);
5846 if (this.isInsertionValid(insertion, entry)) {
5847 this.insertEntryAt(entry, insertion);
5848 }
5849 else {
5850 this.handleInvalidInsertion(insertion, entry, hiddenEntries);
5851 }
5852 }
5853 isInsertionValid(insertion, entry) {
5854 return (this.maxCoord === -1 || insertion.levelCoord + this.getEntryThickness(entry) <= this.maxCoord) &&
5855 (this.maxStackCnt === -1 || insertion.stackCnt < this.maxStackCnt);
5856 }
5857 handleInvalidInsertion(insertion, entry, hiddenEntries) {
5858 if (this.allowReslicing && insertion.touchingEntry) {
5859 const hiddenEntry = Object.assign(Object.assign({}, entry), { span: intersectSpans(entry.span, insertion.touchingEntry.span) });
5860 hiddenEntries.push(hiddenEntry);
5861 this.splitEntry(entry, insertion.touchingEntry, hiddenEntries);
5862 }
5863 else {
5864 hiddenEntries.push(entry);
5865 }
5866 }
5867 /*
5868 Does NOT add what hit the `barrier` into hiddenEntries. Should already be done.
5869 */
5870 splitEntry(entry, barrier, hiddenEntries) {
5871 let entrySpan = entry.span;
5872 let barrierSpan = barrier.span;
5873 if (entrySpan.start < barrierSpan.start) {
5874 this.insertEntry({
5875 index: entry.index,
5876 thickness: entry.thickness,
5877 span: { start: entrySpan.start, end: barrierSpan.start },
5878 }, hiddenEntries);
5879 }
5880 if (entrySpan.end > barrierSpan.end) {
5881 this.insertEntry({
5882 index: entry.index,
5883 thickness: entry.thickness,
5884 span: { start: barrierSpan.end, end: entrySpan.end },
5885 }, hiddenEntries);
5886 }
5887 }
5888 insertEntryAt(entry, insertion) {
5889 let { entriesByLevel, levelCoords } = this;
5890 if (insertion.lateral === -1) {
5891 // create a new level
5892 insertAt(levelCoords, insertion.level, insertion.levelCoord);
5893 insertAt(entriesByLevel, insertion.level, [entry]);
5894 }
5895 else {
5896 // insert into existing level
5897 insertAt(entriesByLevel[insertion.level], insertion.lateral, entry);
5898 }
5899 this.stackCnts[buildEntryKey(entry)] = insertion.stackCnt;
5900 }
5901 /*
5902 does not care about limits
5903 */
5904 findInsertion(newEntry) {
5905 let { levelCoords, entriesByLevel, strictOrder, stackCnts } = this;
5906 let levelCnt = levelCoords.length;
5907 let candidateCoord = 0;
5908 let touchingLevel = -1;
5909 let touchingLateral = -1;
5910 let touchingEntry = null;
5911 let stackCnt = 0;
5912 for (let trackingLevel = 0; trackingLevel < levelCnt; trackingLevel += 1) {
5913 const trackingCoord = levelCoords[trackingLevel];
5914 // if the current level is past the placed entry, we have found a good empty space and can stop.
5915 // if strictOrder, keep finding more lateral intersections.
5916 if (!strictOrder && trackingCoord >= candidateCoord + this.getEntryThickness(newEntry)) {
5917 break;
5918 }
5919 let trackingEntries = entriesByLevel[trackingLevel];
5920 let trackingEntry;
5921 let searchRes = binarySearch(trackingEntries, newEntry.span.start, getEntrySpanEnd); // find first entry after newEntry's end
5922 let lateralIndex = searchRes[0] + searchRes[1]; // if exact match (which doesn't collide), go to next one
5923 while ( // loop through entries that horizontally intersect
5924 (trackingEntry = trackingEntries[lateralIndex]) && // but not past the whole entry list
5925 trackingEntry.span.start < newEntry.span.end // and not entirely past newEntry
5926 ) {
5927 let trackingEntryBottom = trackingCoord + this.getEntryThickness(trackingEntry);
5928 // intersects into the top of the candidate?
5929 if (trackingEntryBottom > candidateCoord) {
5930 candidateCoord = trackingEntryBottom;
5931 touchingEntry = trackingEntry;
5932 touchingLevel = trackingLevel;
5933 touchingLateral = lateralIndex;
5934 }
5935 // butts up against top of candidate? (will happen if just intersected as well)
5936 if (trackingEntryBottom === candidateCoord) {
5937 // accumulate the highest possible stackCnt of the trackingEntries that butt up
5938 stackCnt = Math.max(stackCnt, stackCnts[buildEntryKey(trackingEntry)] + 1);
5939 }
5940 lateralIndex += 1;
5941 }
5942 }
5943 // the destination level will be after touchingEntry's level. find it
5944 let destLevel = 0;
5945 if (touchingEntry) {
5946 destLevel = touchingLevel + 1;
5947 while (destLevel < levelCnt && levelCoords[destLevel] < candidateCoord) {
5948 destLevel += 1;
5949 }
5950 }
5951 // if adding to an existing level, find where to insert
5952 let destLateral = -1;
5953 if (destLevel < levelCnt && levelCoords[destLevel] === candidateCoord) {
5954 destLateral = binarySearch(entriesByLevel[destLevel], newEntry.span.end, getEntrySpanEnd)[0];
5955 }
5956 return {
5957 touchingLevel,
5958 touchingLateral,
5959 touchingEntry,
5960 stackCnt,
5961 levelCoord: candidateCoord,
5962 level: destLevel,
5963 lateral: destLateral,
5964 };
5965 }
5966 // sorted by levelCoord (lowest to highest)
5967 toRects() {
5968 let { entriesByLevel, levelCoords } = this;
5969 let levelCnt = entriesByLevel.length;
5970 let rects = [];
5971 for (let level = 0; level < levelCnt; level += 1) {
5972 let entries = entriesByLevel[level];
5973 let levelCoord = levelCoords[level];
5974 for (let entry of entries) {
5975 rects.push(Object.assign(Object.assign({}, entry), { thickness: this.getEntryThickness(entry), levelCoord }));
5976 }
5977 }
5978 return rects;
5979 }
5980 }
5981 function getEntrySpanEnd(entry) {
5982 return entry.span.end;
5983 }
5984 function buildEntryKey(entry) {
5985 return entry.index + ':' + entry.span.start;
5986 }
5987 // returns groups with entries sorted by input order
5988 function groupIntersectingEntries(entries) {
5989 let merges = [];
5990 for (let entry of entries) {
5991 let filteredMerges = [];
5992 let hungryMerge = {
5993 span: entry.span,
5994 entries: [entry],
5995 };
5996 for (let merge of merges) {
5997 if (intersectSpans(merge.span, hungryMerge.span)) {
5998 hungryMerge = {
5999 entries: merge.entries.concat(hungryMerge.entries),
6000 span: joinSpans(merge.span, hungryMerge.span),
6001 };
6002 }
6003 else {
6004 filteredMerges.push(merge);
6005 }
6006 }
6007 filteredMerges.push(hungryMerge);
6008 merges = filteredMerges;
6009 }
6010 return merges;
6011 }
6012 function joinSpans(span0, span1) {
6013 return {
6014 start: Math.min(span0.start, span1.start),
6015 end: Math.max(span0.end, span1.end),
6016 };
6017 }
6018 function intersectSpans(span0, span1) {
6019 let start = Math.max(span0.start, span1.start);
6020 let end = Math.min(span0.end, span1.end);
6021 if (start < end) {
6022 return { start, end };
6023 }
6024 return null;
6025 }
6026 // general util
6027 // ---------------------------------------------------------------------------------------------------------------------
6028 function insertAt(arr, index, item) {
6029 arr.splice(index, 0, item);
6030 }
6031 function binarySearch(a, searchVal, getItemVal) {
6032 let startIndex = 0;
6033 let endIndex = a.length; // exclusive
6034 if (!endIndex || searchVal < getItemVal(a[startIndex])) { // no items OR before first item
6035 return [0, 0];
6036 }
6037 if (searchVal > getItemVal(a[endIndex - 1])) { // after last item
6038 return [endIndex, 0];
6039 }
6040 while (startIndex < endIndex) {
6041 let middleIndex = Math.floor(startIndex + (endIndex - startIndex) / 2);
6042 let middleVal = getItemVal(a[middleIndex]);
6043 if (searchVal < middleVal) {
6044 endIndex = middleIndex;
6045 }
6046 else if (searchVal > middleVal) {
6047 startIndex = middleIndex + 1;
6048 }
6049 else { // equal!
6050 return [middleIndex, 1];
6051 }
6052 }
6053 return [startIndex, 0];
6054 }
6055
6056 /*
6057 An abstraction for a dragging interaction originating on an event.
6058 Does higher-level things than PointerDragger, such as possibly:
6059 - a "mirror" that moves with the pointer
6060 - a minimum number of pixels or other criteria for a true drag to begin
6061
6062 subclasses must emit:
6063 - pointerdown
6064 - dragstart
6065 - dragmove
6066 - pointerup
6067 - dragend
6068 */
6069 class ElementDragging {
6070 constructor(el, selector) {
6071 this.emitter = new Emitter();
6072 }
6073 destroy() {
6074 }
6075 setMirrorIsVisible(bool) {
6076 // optional if subclass doesn't want to support a mirror
6077 }
6078 setMirrorNeedsRevert(bool) {
6079 // optional if subclass doesn't want to support a mirror
6080 }
6081 setAutoScrollEnabled(bool) {
6082 // optional
6083 }
6084 }
6085
6086 // TODO: get rid of this in favor of options system,
6087 // tho it's really easy to access this globally rather than pass thru options.
6088 const config = {};
6089
6090 /*
6091 Information about what will happen when an external element is dragged-and-dropped
6092 onto a calendar. Contains information for creating an event.
6093 */
6094 const DRAG_META_REFINERS = {
6095 startTime: createDuration,
6096 duration: createDuration,
6097 create: Boolean,
6098 sourceId: String,
6099 };
6100 function parseDragMeta(raw) {
6101 let { refined, extra } = refineProps(raw, DRAG_META_REFINERS);
6102 return {
6103 startTime: refined.startTime || null,
6104 duration: refined.duration || null,
6105 create: refined.create != null ? refined.create : true,
6106 sourceId: refined.sourceId,
6107 leftoverProps: extra,
6108 };
6109 }
6110
6111 // Computes a default column header formatting string if `colFormat` is not explicitly defined
6112 function computeFallbackHeaderFormat(datesRepDistinctDays, dayCnt) {
6113 // if more than one week row, or if there are a lot of columns with not much space,
6114 // put just the day numbers will be in each cell
6115 if (!datesRepDistinctDays || dayCnt > 10) {
6116 return createFormatter({ weekday: 'short' }); // "Sat"
6117 }
6118 if (dayCnt > 1) {
6119 return createFormatter({ weekday: 'short', month: 'numeric', day: 'numeric', omitCommas: true }); // "Sat 11/12"
6120 }
6121 return createFormatter({ weekday: 'long' }); // "Saturday"
6122 }
6123
6124 const CLASS_NAME = 'fc-col-header-cell'; // do the cushion too? no
6125 function renderInner$1(renderProps) {
6126 return renderProps.text;
6127 }
6128
6129 // BAD name for this class now. used in the Header
6130 class TableDateCell extends BaseComponent {
6131 render() {
6132 let { dateEnv, options, theme, viewApi } = this.context;
6133 let { props } = this;
6134 let { date, dateProfile } = props;
6135 let dayMeta = getDateMeta(date, props.todayRange, null, dateProfile);
6136 let classNames = [CLASS_NAME].concat(getDayClassNames(dayMeta, theme));
6137 let text = dateEnv.format(date, props.dayHeaderFormat);
6138 // if colCnt is 1, we are already in a day-view and don't need a navlink
6139 let navLinkAttrs = (!dayMeta.isDisabled && props.colCnt > 1)
6140 ? buildNavLinkAttrs(this.context, date)
6141 : {};
6142 let publicDate = dateEnv.toDate(date);
6143 // workaround for Luxon (and maybe moment) returning prior-days when start-of-day
6144 // in DST gap: https://github.com/fullcalendar/fullcalendar/issues/7633
6145 if (dateEnv.namedTimeZoneImpl) {
6146 publicDate = addMs(publicDate, 3600000); // add an hour
6147 }
6148 let renderProps = Object.assign(Object.assign(Object.assign({ date: publicDate, view: viewApi }, props.extraRenderProps), { text }), dayMeta);
6149 return (y(ContentContainer, { elTag: "th", elClasses: classNames, elAttrs: Object.assign({ role: 'columnheader', colSpan: props.colSpan, 'data-date': !dayMeta.isDisabled ? formatDayString(date) : undefined }, props.extraDataAttrs), renderProps: renderProps, generatorName: "dayHeaderContent", customGenerator: options.dayHeaderContent, defaultGenerator: renderInner$1, classNameGenerator: options.dayHeaderClassNames, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, (InnerContainer) => (y("div", { className: "fc-scrollgrid-sync-inner" }, !dayMeta.isDisabled && (y(InnerContainer, { elTag: "a", elAttrs: navLinkAttrs, elClasses: [
6150 'fc-col-header-cell-cushion',
6151 props.isSticky && 'fc-sticky',
6152 ] }))))));
6153 }
6154 }
6155
6156 const WEEKDAY_FORMAT = createFormatter({ weekday: 'long' });
6157 class TableDowCell extends BaseComponent {
6158 render() {
6159 let { props } = this;
6160 let { dateEnv, theme, viewApi, options } = this.context;
6161 let date = addDays(new Date(259200000), props.dow); // start with Sun, 04 Jan 1970 00:00:00 GMT
6162 let dateMeta = {
6163 dow: props.dow,
6164 isDisabled: false,
6165 isFuture: false,
6166 isPast: false,
6167 isToday: false,
6168 isOther: false,
6169 };
6170 let text = dateEnv.format(date, props.dayHeaderFormat);
6171 let renderProps = Object.assign(Object.assign(Object.assign(Object.assign({ // TODO: make this public?
6172 date }, dateMeta), { view: viewApi }), props.extraRenderProps), { text });
6173 return (y(ContentContainer, { elTag: "th", elClasses: [
6174 CLASS_NAME,
6175 ...getDayClassNames(dateMeta, theme),
6176 ...(props.extraClassNames || []),
6177 ], elAttrs: Object.assign({ role: 'columnheader', colSpan: props.colSpan }, props.extraDataAttrs), renderProps: renderProps, generatorName: "dayHeaderContent", customGenerator: options.dayHeaderContent, defaultGenerator: renderInner$1, classNameGenerator: options.dayHeaderClassNames, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, (InnerContent) => (y("div", { className: "fc-scrollgrid-sync-inner" },
6178 y(InnerContent, { elTag: "a", elClasses: [
6179 'fc-col-header-cell-cushion',
6180 props.isSticky && 'fc-sticky',
6181 ], elAttrs: {
6182 'aria-label': dateEnv.format(date, WEEKDAY_FORMAT),
6183 } })))));
6184 }
6185 }
6186
6187 class DayHeader extends BaseComponent {
6188 constructor() {
6189 super(...arguments);
6190 this.createDayHeaderFormatter = memoize(createDayHeaderFormatter);
6191 }
6192 render() {
6193 let { context } = this;
6194 let { dates, dateProfile, datesRepDistinctDays, renderIntro } = this.props;
6195 let dayHeaderFormat = this.createDayHeaderFormatter(context.options.dayHeaderFormat, datesRepDistinctDays, dates.length);
6196 return (y(NowTimer, { unit: "day" }, (nowDate, todayRange) => (y("tr", { role: "row" },
6197 renderIntro && renderIntro('day'),
6198 dates.map((date) => (datesRepDistinctDays ? (y(TableDateCell, { key: date.toISOString(), date: date, dateProfile: dateProfile, todayRange: todayRange, colCnt: dates.length, dayHeaderFormat: dayHeaderFormat })) : (y(TableDowCell, { key: date.getUTCDay(), dow: date.getUTCDay(), dayHeaderFormat: dayHeaderFormat }))))))));
6199 }
6200 }
6201 function createDayHeaderFormatter(explicitFormat, datesRepDistinctDays, dateCnt) {
6202 return explicitFormat || computeFallbackHeaderFormat(datesRepDistinctDays, dateCnt);
6203 }
6204
6205 class DaySeriesModel {
6206 constructor(range, dateProfileGenerator) {
6207 let date = range.start;
6208 let { end } = range;
6209 let indices = [];
6210 let dates = [];
6211 let dayIndex = -1;
6212 while (date < end) { // loop each day from start to end
6213 if (dateProfileGenerator.isHiddenDay(date)) {
6214 indices.push(dayIndex + 0.5); // mark that it's between indices
6215 }
6216 else {
6217 dayIndex += 1;
6218 indices.push(dayIndex);
6219 dates.push(date);
6220 }
6221 date = addDays(date, 1);
6222 }
6223 this.dates = dates;
6224 this.indices = indices;
6225 this.cnt = dates.length;
6226 }
6227 sliceRange(range) {
6228 let firstIndex = this.getDateDayIndex(range.start); // inclusive first index
6229 let lastIndex = this.getDateDayIndex(addDays(range.end, -1)); // inclusive last index
6230 let clippedFirstIndex = Math.max(0, firstIndex);
6231 let clippedLastIndex = Math.min(this.cnt - 1, lastIndex);
6232 // deal with in-between indices
6233 clippedFirstIndex = Math.ceil(clippedFirstIndex); // in-between starts round to next cell
6234 clippedLastIndex = Math.floor(clippedLastIndex); // in-between ends round to prev cell
6235 if (clippedFirstIndex <= clippedLastIndex) {
6236 return {
6237 firstIndex: clippedFirstIndex,
6238 lastIndex: clippedLastIndex,
6239 isStart: firstIndex === clippedFirstIndex,
6240 isEnd: lastIndex === clippedLastIndex,
6241 };
6242 }
6243 return null;
6244 }
6245 // Given a date, returns its chronolocial cell-index from the first cell of the grid.
6246 // If the date lies between cells (because of hiddenDays), returns a floating-point value between offsets.
6247 // If before the first offset, returns a negative number.
6248 // If after the last offset, returns an offset past the last cell offset.
6249 // Only works for *start* dates of cells. Will not work for exclusive end dates for cells.
6250 getDateDayIndex(date) {
6251 let { indices } = this;
6252 let dayOffset = Math.floor(diffDays(this.dates[0], date));
6253 if (dayOffset < 0) {
6254 return indices[0] - 1;
6255 }
6256 if (dayOffset >= indices.length) {
6257 return indices[indices.length - 1] + 1;
6258 }
6259 return indices[dayOffset];
6260 }
6261 }
6262
6263 class DayTableModel {
6264 constructor(daySeries, breakOnWeeks) {
6265 let { dates } = daySeries;
6266 let daysPerRow;
6267 let firstDay;
6268 let rowCnt;
6269 if (breakOnWeeks) {
6270 // count columns until the day-of-week repeats
6271 firstDay = dates[0].getUTCDay();
6272 for (daysPerRow = 1; daysPerRow < dates.length; daysPerRow += 1) {
6273 if (dates[daysPerRow].getUTCDay() === firstDay) {
6274 break;
6275 }
6276 }
6277 rowCnt = Math.ceil(dates.length / daysPerRow);
6278 }
6279 else {
6280 rowCnt = 1;
6281 daysPerRow = dates.length;
6282 }
6283 this.rowCnt = rowCnt;
6284 this.colCnt = daysPerRow;
6285 this.daySeries = daySeries;
6286 this.cells = this.buildCells();
6287 this.headerDates = this.buildHeaderDates();
6288 }
6289 buildCells() {
6290 let rows = [];
6291 for (let row = 0; row < this.rowCnt; row += 1) {
6292 let cells = [];
6293 for (let col = 0; col < this.colCnt; col += 1) {
6294 cells.push(this.buildCell(row, col));
6295 }
6296 rows.push(cells);
6297 }
6298 return rows;
6299 }
6300 buildCell(row, col) {
6301 let date = this.daySeries.dates[row * this.colCnt + col];
6302 return {
6303 key: date.toISOString(),
6304 date,
6305 };
6306 }
6307 buildHeaderDates() {
6308 let dates = [];
6309 for (let col = 0; col < this.colCnt; col += 1) {
6310 dates.push(this.cells[0][col].date);
6311 }
6312 return dates;
6313 }
6314 sliceRange(range) {
6315 let { colCnt } = this;
6316 let seriesSeg = this.daySeries.sliceRange(range);
6317 let segs = [];
6318 if (seriesSeg) {
6319 let { firstIndex, lastIndex } = seriesSeg;
6320 let index = firstIndex;
6321 while (index <= lastIndex) {
6322 let row = Math.floor(index / colCnt);
6323 let nextIndex = Math.min((row + 1) * colCnt, lastIndex + 1);
6324 segs.push({
6325 row,
6326 firstCol: index % colCnt,
6327 lastCol: (nextIndex - 1) % colCnt,
6328 isStart: seriesSeg.isStart && index === firstIndex,
6329 isEnd: seriesSeg.isEnd && (nextIndex - 1) === lastIndex,
6330 });
6331 index = nextIndex;
6332 }
6333 }
6334 return segs;
6335 }
6336 }
6337
6338 class Slicer {
6339 constructor() {
6340 this.sliceBusinessHours = memoize(this._sliceBusinessHours);
6341 this.sliceDateSelection = memoize(this._sliceDateSpan);
6342 this.sliceEventStore = memoize(this._sliceEventStore);
6343 this.sliceEventDrag = memoize(this._sliceInteraction);
6344 this.sliceEventResize = memoize(this._sliceInteraction);
6345 this.forceDayIfListItem = false; // hack
6346 }
6347 sliceProps(props, dateProfile, nextDayThreshold, context, ...extraArgs) {
6348 let { eventUiBases } = props;
6349 let eventSegs = this.sliceEventStore(props.eventStore, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs);
6350 return {
6351 dateSelectionSegs: this.sliceDateSelection(props.dateSelection, dateProfile, nextDayThreshold, eventUiBases, context, ...extraArgs),
6352 businessHourSegs: this.sliceBusinessHours(props.businessHours, dateProfile, nextDayThreshold, context, ...extraArgs),
6353 fgEventSegs: eventSegs.fg,
6354 bgEventSegs: eventSegs.bg,
6355 eventDrag: this.sliceEventDrag(props.eventDrag, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs),
6356 eventResize: this.sliceEventResize(props.eventResize, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs),
6357 eventSelection: props.eventSelection,
6358 }; // TODO: give interactionSegs?
6359 }
6360 sliceNowDate(// does not memoize
6361 date, dateProfile, nextDayThreshold, context, ...extraArgs) {
6362 return this._sliceDateSpan({ range: { start: date, end: addMs(date, 1) }, allDay: false }, // add 1 ms, protect against null range
6363 dateProfile, nextDayThreshold, {}, context, ...extraArgs);
6364 }
6365 _sliceBusinessHours(businessHours, dateProfile, nextDayThreshold, context, ...extraArgs) {
6366 if (!businessHours) {
6367 return [];
6368 }
6369 return this._sliceEventStore(expandRecurring(businessHours, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), context), {}, dateProfile, nextDayThreshold, ...extraArgs).bg;
6370 }
6371 _sliceEventStore(eventStore, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs) {
6372 if (eventStore) {
6373 let rangeRes = sliceEventStore(eventStore, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold);
6374 return {
6375 bg: this.sliceEventRanges(rangeRes.bg, extraArgs),
6376 fg: this.sliceEventRanges(rangeRes.fg, extraArgs),
6377 };
6378 }
6379 return { bg: [], fg: [] };
6380 }
6381 _sliceInteraction(interaction, eventUiBases, dateProfile, nextDayThreshold, ...extraArgs) {
6382 if (!interaction) {
6383 return null;
6384 }
6385 let rangeRes = sliceEventStore(interaction.mutatedEvents, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold);
6386 return {
6387 segs: this.sliceEventRanges(rangeRes.fg, extraArgs),
6388 affectedInstances: interaction.affectedEvents.instances,
6389 isEvent: interaction.isEvent,
6390 };
6391 }
6392 _sliceDateSpan(dateSpan, dateProfile, nextDayThreshold, eventUiBases, context, ...extraArgs) {
6393 if (!dateSpan) {
6394 return [];
6395 }
6396 let activeRange = computeActiveRange(dateProfile, Boolean(nextDayThreshold));
6397 let activeDateSpanRange = intersectRanges(dateSpan.range, activeRange);
6398 if (activeDateSpanRange) {
6399 dateSpan = Object.assign(Object.assign({}, dateSpan), { range: activeDateSpanRange });
6400 let eventRange = fabricateEventRange(dateSpan, eventUiBases, context);
6401 let segs = this.sliceRange(dateSpan.range, ...extraArgs);
6402 for (let seg of segs) {
6403 seg.eventRange = eventRange;
6404 }
6405 return segs;
6406 }
6407 return [];
6408 }
6409 /*
6410 "complete" seg means it has component and eventRange
6411 */
6412 sliceEventRanges(eventRanges, extraArgs) {
6413 let segs = [];
6414 for (let eventRange of eventRanges) {
6415 segs.push(...this.sliceEventRange(eventRange, extraArgs));
6416 }
6417 return segs;
6418 }
6419 /*
6420 "complete" seg means it has component and eventRange
6421 */
6422 sliceEventRange(eventRange, extraArgs) {
6423 let dateRange = eventRange.range;
6424 // hack to make multi-day events that are being force-displayed as list-items to take up only one day
6425 if (this.forceDayIfListItem && eventRange.ui.display === 'list-item') {
6426 dateRange = {
6427 start: dateRange.start,
6428 end: addDays(dateRange.start, 1),
6429 };
6430 }
6431 let segs = this.sliceRange(dateRange, ...extraArgs);
6432 for (let seg of segs) {
6433 seg.eventRange = eventRange;
6434 seg.isStart = eventRange.isStart && seg.isStart;
6435 seg.isEnd = eventRange.isEnd && seg.isEnd;
6436 }
6437 return segs;
6438 }
6439 }
6440 /*
6441 for incorporating slotMinTime/slotMaxTime if appropriate
6442 TODO: should be part of DateProfile!
6443 TimelineDateProfile already does this btw
6444 */
6445 function computeActiveRange(dateProfile, isComponentAllDay) {
6446 let range = dateProfile.activeRange;
6447 if (isComponentAllDay) {
6448 return range;
6449 }
6450 return {
6451 start: addMs(range.start, dateProfile.slotMinTime.milliseconds),
6452 end: addMs(range.end, dateProfile.slotMaxTime.milliseconds - 864e5), // 864e5 = ms in a day
6453 };
6454 }
6455
6456 // high-level segmenting-aware tester functions
6457 // ------------------------------------------------------------------------------------------------------------------------
6458 function isInteractionValid(interaction, dateProfile, context) {
6459 let { instances } = interaction.mutatedEvents;
6460 for (let instanceId in instances) {
6461 if (!rangeContainsRange(dateProfile.validRange, instances[instanceId].range)) {
6462 return false;
6463 }
6464 }
6465 return isNewPropsValid({ eventDrag: interaction }, context); // HACK: the eventDrag props is used for ALL interactions
6466 }
6467 function isDateSelectionValid(dateSelection, dateProfile, context) {
6468 if (!rangeContainsRange(dateProfile.validRange, dateSelection.range)) {
6469 return false;
6470 }
6471 return isNewPropsValid({ dateSelection }, context);
6472 }
6473 function isNewPropsValid(newProps, context) {
6474 let calendarState = context.getCurrentData();
6475 let props = Object.assign({ businessHours: calendarState.businessHours, dateSelection: '', eventStore: calendarState.eventStore, eventUiBases: calendarState.eventUiBases, eventSelection: '', eventDrag: null, eventResize: null }, newProps);
6476 return (context.pluginHooks.isPropsValid || isPropsValid)(props, context);
6477 }
6478 function isPropsValid(state, context, dateSpanMeta = {}, filterConfig) {
6479 if (state.eventDrag && !isInteractionPropsValid(state, context, dateSpanMeta, filterConfig)) {
6480 return false;
6481 }
6482 if (state.dateSelection && !isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig)) {
6483 return false;
6484 }
6485 return true;
6486 }
6487 // Moving Event Validation
6488 // ------------------------------------------------------------------------------------------------------------------------
6489 function isInteractionPropsValid(state, context, dateSpanMeta, filterConfig) {
6490 let currentState = context.getCurrentData();
6491 let interaction = state.eventDrag; // HACK: the eventDrag props is used for ALL interactions
6492 let subjectEventStore = interaction.mutatedEvents;
6493 let subjectDefs = subjectEventStore.defs;
6494 let subjectInstances = subjectEventStore.instances;
6495 let subjectConfigs = compileEventUis(subjectDefs, interaction.isEvent ?
6496 state.eventUiBases :
6497 { '': currentState.selectionConfig });
6498 if (filterConfig) {
6499 subjectConfigs = mapHash(subjectConfigs, filterConfig);
6500 }
6501 // exclude the subject events. TODO: exclude defs too?
6502 let otherEventStore = excludeInstances(state.eventStore, interaction.affectedEvents.instances);
6503 let otherDefs = otherEventStore.defs;
6504 let otherInstances = otherEventStore.instances;
6505 let otherConfigs = compileEventUis(otherDefs, state.eventUiBases);
6506 for (let subjectInstanceId in subjectInstances) {
6507 let subjectInstance = subjectInstances[subjectInstanceId];
6508 let subjectRange = subjectInstance.range;
6509 let subjectConfig = subjectConfigs[subjectInstance.defId];
6510 let subjectDef = subjectDefs[subjectInstance.defId];
6511 // constraint
6512 if (!allConstraintsPass(subjectConfig.constraints, subjectRange, otherEventStore, state.businessHours, context)) {
6513 return false;
6514 }
6515 // overlap
6516 let { eventOverlap } = context.options;
6517 let eventOverlapFunc = typeof eventOverlap === 'function' ? eventOverlap : null;
6518 for (let otherInstanceId in otherInstances) {
6519 let otherInstance = otherInstances[otherInstanceId];
6520 // intersect! evaluate
6521 if (rangesIntersect(subjectRange, otherInstance.range)) {
6522 let otherOverlap = otherConfigs[otherInstance.defId].overlap;
6523 // consider the other event's overlap. only do this if the subject event is a "real" event
6524 if (otherOverlap === false && interaction.isEvent) {
6525 return false;
6526 }
6527 if (subjectConfig.overlap === false) {
6528 return false;
6529 }
6530 if (eventOverlapFunc && !eventOverlapFunc(new EventImpl(context, otherDefs[otherInstance.defId], otherInstance), // still event
6531 new EventImpl(context, subjectDef, subjectInstance))) {
6532 return false;
6533 }
6534 }
6535 }
6536 // allow (a function)
6537 let calendarEventStore = currentState.eventStore; // need global-to-calendar, not local to component (splittable)state
6538 for (let subjectAllow of subjectConfig.allows) {
6539 let subjectDateSpan = Object.assign(Object.assign({}, dateSpanMeta), { range: subjectInstance.range, allDay: subjectDef.allDay });
6540 let origDef = calendarEventStore.defs[subjectDef.defId];
6541 let origInstance = calendarEventStore.instances[subjectInstanceId];
6542 let eventApi;
6543 if (origDef) { // was previously in the calendar
6544 eventApi = new EventImpl(context, origDef, origInstance);
6545 }
6546 else { // was an external event
6547 eventApi = new EventImpl(context, subjectDef); // no instance, because had no dates
6548 }
6549 if (!subjectAllow(buildDateSpanApiWithContext(subjectDateSpan, context), eventApi)) {
6550 return false;
6551 }
6552 }
6553 }
6554 return true;
6555 }
6556 // Date Selection Validation
6557 // ------------------------------------------------------------------------------------------------------------------------
6558 function isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig) {
6559 let relevantEventStore = state.eventStore;
6560 let relevantDefs = relevantEventStore.defs;
6561 let relevantInstances = relevantEventStore.instances;
6562 let selection = state.dateSelection;
6563 let selectionRange = selection.range;
6564 let { selectionConfig } = context.getCurrentData();
6565 if (filterConfig) {
6566 selectionConfig = filterConfig(selectionConfig);
6567 }
6568 // constraint
6569 if (!allConstraintsPass(selectionConfig.constraints, selectionRange, relevantEventStore, state.businessHours, context)) {
6570 return false;
6571 }
6572 // overlap
6573 let { selectOverlap } = context.options;
6574 let selectOverlapFunc = typeof selectOverlap === 'function' ? selectOverlap : null;
6575 for (let relevantInstanceId in relevantInstances) {
6576 let relevantInstance = relevantInstances[relevantInstanceId];
6577 // intersect! evaluate
6578 if (rangesIntersect(selectionRange, relevantInstance.range)) {
6579 if (selectionConfig.overlap === false) {
6580 return false;
6581 }
6582 if (selectOverlapFunc && !selectOverlapFunc(new EventImpl(context, relevantDefs[relevantInstance.defId], relevantInstance), null)) {
6583 return false;
6584 }
6585 }
6586 }
6587 // allow (a function)
6588 for (let selectionAllow of selectionConfig.allows) {
6589 let fullDateSpan = Object.assign(Object.assign({}, dateSpanMeta), selection);
6590 if (!selectionAllow(buildDateSpanApiWithContext(fullDateSpan, context), null)) {
6591 return false;
6592 }
6593 }
6594 return true;
6595 }
6596 // Constraint Utils
6597 // ------------------------------------------------------------------------------------------------------------------------
6598 function allConstraintsPass(constraints, subjectRange, otherEventStore, businessHoursUnexpanded, context) {
6599 for (let constraint of constraints) {
6600 if (!anyRangesContainRange(constraintToRanges(constraint, subjectRange, otherEventStore, businessHoursUnexpanded, context), subjectRange)) {
6601 return false;
6602 }
6603 }
6604 return true;
6605 }
6606 function constraintToRanges(constraint, subjectRange, // for expanding a recurring constraint, or expanding business hours
6607 otherEventStore, // for if constraint is an even group ID
6608 businessHoursUnexpanded, // for if constraint is 'businessHours'
6609 context) {
6610 if (constraint === 'businessHours') {
6611 return eventStoreToRanges(expandRecurring(businessHoursUnexpanded, subjectRange, context));
6612 }
6613 if (typeof constraint === 'string') { // an group ID
6614 return eventStoreToRanges(filterEventStoreDefs(otherEventStore, (eventDef) => eventDef.groupId === constraint));
6615 }
6616 if (typeof constraint === 'object' && constraint) { // non-null object
6617 return eventStoreToRanges(expandRecurring(constraint, subjectRange, context));
6618 }
6619 return []; // if it's false
6620 }
6621 // TODO: move to event-store file?
6622 function eventStoreToRanges(eventStore) {
6623 let { instances } = eventStore;
6624 let ranges = [];
6625 for (let instanceId in instances) {
6626 ranges.push(instances[instanceId].range);
6627 }
6628 return ranges;
6629 }
6630 // TODO: move to geom file?
6631 function anyRangesContainRange(outerRanges, innerRange) {
6632 for (let outerRange of outerRanges) {
6633 if (rangeContainsRange(outerRange, innerRange)) {
6634 return true;
6635 }
6636 }
6637 return false;
6638 }
6639
6640 const VISIBLE_HIDDEN_RE = /^(visible|hidden)$/;
6641 class Scroller extends BaseComponent {
6642 constructor() {
6643 super(...arguments);
6644 this.handleEl = (el) => {
6645 this.el = el;
6646 setRef(this.props.elRef, el);
6647 };
6648 }
6649 render() {
6650 let { props } = this;
6651 let { liquid, liquidIsAbsolute } = props;
6652 let isAbsolute = liquid && liquidIsAbsolute;
6653 let className = ['fc-scroller'];
6654 if (liquid) {
6655 if (liquidIsAbsolute) {
6656 className.push('fc-scroller-liquid-absolute');
6657 }
6658 else {
6659 className.push('fc-scroller-liquid');
6660 }
6661 }
6662 return (y("div", { ref: this.handleEl, className: className.join(' '), style: {
6663 overflowX: props.overflowX,
6664 overflowY: props.overflowY,
6665 left: (isAbsolute && -(props.overcomeLeft || 0)) || '',
6666 right: (isAbsolute && -(props.overcomeRight || 0)) || '',
6667 bottom: (isAbsolute && -(props.overcomeBottom || 0)) || '',
6668 marginLeft: (!isAbsolute && -(props.overcomeLeft || 0)) || '',
6669 marginRight: (!isAbsolute && -(props.overcomeRight || 0)) || '',
6670 marginBottom: (!isAbsolute && -(props.overcomeBottom || 0)) || '',
6671 maxHeight: props.maxHeight || '',
6672 } }, props.children));
6673 }
6674 needsXScrolling() {
6675 if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) {
6676 return false;
6677 }
6678 // testing scrollWidth>clientWidth is unreliable cross-browser when pixel heights aren't integers.
6679 // much more reliable to see if children are taller than the scroller, even tho doesn't account for
6680 // inner-child margins and absolute positioning
6681 let { el } = this;
6682 let realClientWidth = this.el.getBoundingClientRect().width - this.getYScrollbarWidth();
6683 let { children } = el;
6684 for (let i = 0; i < children.length; i += 1) {
6685 let childEl = children[i];
6686 if (childEl.getBoundingClientRect().width > realClientWidth) {
6687 return true;
6688 }
6689 }
6690 return false;
6691 }
6692 needsYScrolling() {
6693 if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) {
6694 return false;
6695 }
6696 // testing scrollHeight>clientHeight is unreliable cross-browser when pixel heights aren't integers.
6697 // much more reliable to see if children are taller than the scroller, even tho doesn't account for
6698 // inner-child margins and absolute positioning
6699 let { el } = this;
6700 let realClientHeight = this.el.getBoundingClientRect().height - this.getXScrollbarWidth();
6701 let { children } = el;
6702 for (let i = 0; i < children.length; i += 1) {
6703 let childEl = children[i];
6704 if (childEl.getBoundingClientRect().height > realClientHeight) {
6705 return true;
6706 }
6707 }
6708 return false;
6709 }
6710 getXScrollbarWidth() {
6711 if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) {
6712 return 0;
6713 }
6714 return this.el.offsetHeight - this.el.clientHeight; // only works because we guarantee no borders. TODO: add to CSS with important?
6715 }
6716 getYScrollbarWidth() {
6717 if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) {
6718 return 0;
6719 }
6720 return this.el.offsetWidth - this.el.clientWidth; // only works because we guarantee no borders. TODO: add to CSS with important?
6721 }
6722 }
6723
6724 /*
6725 TODO: somehow infer OtherArgs from masterCallback?
6726 TODO: infer RefType from masterCallback if provided
6727 */
6728 class RefMap {
6729 constructor(masterCallback) {
6730 this.masterCallback = masterCallback;
6731 this.currentMap = {};
6732 this.depths = {};
6733 this.callbackMap = {};
6734 this.handleValue = (val, key) => {
6735 let { depths, currentMap } = this;
6736 let removed = false;
6737 let added = false;
6738 if (val !== null) {
6739 // for bug... ACTUALLY: can probably do away with this now that callers don't share numeric indices anymore
6740 removed = (key in currentMap);
6741 currentMap[key] = val;
6742 depths[key] = (depths[key] || 0) + 1;
6743 added = true;
6744 }
6745 else {
6746 depths[key] -= 1;
6747 if (!depths[key]) {
6748 delete currentMap[key];
6749 delete this.callbackMap[key];
6750 removed = true;
6751 }
6752 }
6753 if (this.masterCallback) {
6754 if (removed) {
6755 this.masterCallback(null, String(key));
6756 }
6757 if (added) {
6758 this.masterCallback(val, String(key));
6759 }
6760 }
6761 };
6762 }
6763 createRef(key) {
6764 let refCallback = this.callbackMap[key];
6765 if (!refCallback) {
6766 refCallback = this.callbackMap[key] = (val) => {
6767 this.handleValue(val, String(key));
6768 };
6769 }
6770 return refCallback;
6771 }
6772 // TODO: check callers that don't care about order. should use getAll instead
6773 // NOTE: this method has become less valuable now that we are encouraged to map order by some other index
6774 // TODO: provide ONE array-export function, buildArray, which fails on non-numeric indexes. caller can manipulate and "collect"
6775 collect(startIndex, endIndex, step) {
6776 return collectFromHash(this.currentMap, startIndex, endIndex, step);
6777 }
6778 getAll() {
6779 return hashValuesToArray(this.currentMap);
6780 }
6781 }
6782
6783 function computeShrinkWidth(chunkEls) {
6784 let shrinkCells = findElements(chunkEls, '.fc-scrollgrid-shrink');
6785 let largestWidth = 0;
6786 for (let shrinkCell of shrinkCells) {
6787 largestWidth = Math.max(largestWidth, computeSmallestCellWidth(shrinkCell));
6788 }
6789 return Math.ceil(largestWidth); // <table> elements work best with integers. round up to ensure contents fits
6790 }
6791 function getSectionHasLiquidHeight(props, sectionConfig) {
6792 return props.liquid && sectionConfig.liquid; // does the section do liquid-height? (need to have whole scrollgrid liquid-height as well)
6793 }
6794 function getAllowYScrolling(props, sectionConfig) {
6795 return sectionConfig.maxHeight != null || // if its possible for the height to max out, we might need scrollbars
6796 getSectionHasLiquidHeight(props, sectionConfig); // if the section is liquid height, it might condense enough to require scrollbars
6797 }
6798 // TODO: ONLY use `arg`. force out internal function to use same API
6799 function renderChunkContent(sectionConfig, chunkConfig, arg, isHeader) {
6800 let { expandRows } = arg;
6801 let content = typeof chunkConfig.content === 'function' ?
6802 chunkConfig.content(arg) :
6803 y('table', {
6804 role: 'presentation',
6805 className: [
6806 chunkConfig.tableClassName,
6807 sectionConfig.syncRowHeights ? 'fc-scrollgrid-sync-table' : '',
6808 ].join(' '),
6809 style: {
6810 minWidth: arg.tableMinWidth,
6811 width: arg.clientWidth,
6812 height: expandRows ? arg.clientHeight : '', // css `height` on a <table> serves as a min-height
6813 },
6814 }, arg.tableColGroupNode, y(isHeader ? 'thead' : 'tbody', {
6815 role: 'presentation',
6816 }, typeof chunkConfig.rowContent === 'function'
6817 ? chunkConfig.rowContent(arg)
6818 : chunkConfig.rowContent));
6819 return content;
6820 }
6821 function isColPropsEqual(cols0, cols1) {
6822 return isArraysEqual(cols0, cols1, isPropsEqual);
6823 }
6824 function renderMicroColGroup(cols, shrinkWidth) {
6825 let colNodes = [];
6826 /*
6827 for ColProps with spans, it would have been great to make a single <col span="">
6828 HOWEVER, Chrome was getting messing up distributing the width to <td>/<th> elements with colspans.
6829 SOLUTION: making individual <col> elements makes Chrome behave.
6830 */
6831 for (let colProps of cols) {
6832 let span = colProps.span || 1;
6833 for (let i = 0; i < span; i += 1) {
6834 colNodes.push(y("col", { style: {
6835 width: colProps.width === 'shrink' ? sanitizeShrinkWidth(shrinkWidth) : (colProps.width || ''),
6836 minWidth: colProps.minWidth || '',
6837 } }));
6838 }
6839 }
6840 return y('colgroup', {}, ...colNodes);
6841 }
6842 function sanitizeShrinkWidth(shrinkWidth) {
6843 /* why 4? if we do 0, it will kill any border, which are needed for computeSmallestCellWidth
6844 4 accounts for 2 2-pixel borders. TODO: better solution? */
6845 return shrinkWidth == null ? 4 : shrinkWidth;
6846 }
6847 function hasShrinkWidth(cols) {
6848 for (let col of cols) {
6849 if (col.width === 'shrink') {
6850 return true;
6851 }
6852 }
6853 return false;
6854 }
6855 function getScrollGridClassNames(liquid, context) {
6856 let classNames = [
6857 'fc-scrollgrid',
6858 context.theme.getClass('table'),
6859 ];
6860 if (liquid) {
6861 classNames.push('fc-scrollgrid-liquid');
6862 }
6863 return classNames;
6864 }
6865 function getSectionClassNames(sectionConfig, wholeTableVGrow) {
6866 let classNames = [
6867 'fc-scrollgrid-section',
6868 `fc-scrollgrid-section-${sectionConfig.type}`,
6869 sectionConfig.className, // used?
6870 ];
6871 if (wholeTableVGrow && sectionConfig.liquid && sectionConfig.maxHeight == null) {
6872 classNames.push('fc-scrollgrid-section-liquid');
6873 }
6874 if (sectionConfig.isSticky) {
6875 classNames.push('fc-scrollgrid-section-sticky');
6876 }
6877 return classNames;
6878 }
6879 function renderScrollShim(arg) {
6880 return (y("div", { className: "fc-scrollgrid-sticky-shim", style: {
6881 width: arg.clientWidth,
6882 minWidth: arg.tableMinWidth,
6883 } }));
6884 }
6885 function getStickyHeaderDates(options) {
6886 let { stickyHeaderDates } = options;
6887 if (stickyHeaderDates == null || stickyHeaderDates === 'auto') {
6888 stickyHeaderDates = options.height === 'auto' || options.viewHeight === 'auto';
6889 }
6890 return stickyHeaderDates;
6891 }
6892 function getStickyFooterScrollbar(options) {
6893 let { stickyFooterScrollbar } = options;
6894 if (stickyFooterScrollbar == null || stickyFooterScrollbar === 'auto') {
6895 stickyFooterScrollbar = options.height === 'auto' || options.viewHeight === 'auto';
6896 }
6897 return stickyFooterScrollbar;
6898 }
6899
6900 class SimpleScrollGrid extends BaseComponent {
6901 constructor() {
6902 super(...arguments);
6903 this.processCols = memoize((a) => a, isColPropsEqual); // so we get same `cols` props every time
6904 // yucky to memoize VNodes, but much more efficient for consumers
6905 this.renderMicroColGroup = memoize(renderMicroColGroup);
6906 this.scrollerRefs = new RefMap();
6907 this.scrollerElRefs = new RefMap(this._handleScrollerEl.bind(this));
6908 this.state = {
6909 shrinkWidth: null,
6910 forceYScrollbars: false,
6911 scrollerClientWidths: {},
6912 scrollerClientHeights: {},
6913 };
6914 // TODO: can do a really simple print-view. dont need to join rows
6915 this.handleSizing = () => {
6916 this.safeSetState(Object.assign({ shrinkWidth: this.computeShrinkWidth() }, this.computeScrollerDims()));
6917 };
6918 }
6919 render() {
6920 let { props, state, context } = this;
6921 let sectionConfigs = props.sections || [];
6922 let cols = this.processCols(props.cols);
6923 let microColGroupNode = this.renderMicroColGroup(cols, state.shrinkWidth);
6924 let classNames = getScrollGridClassNames(props.liquid, context);
6925 if (props.collapsibleWidth) {
6926 classNames.push('fc-scrollgrid-collapsible');
6927 }
6928 // TODO: make DRY
6929 let configCnt = sectionConfigs.length;
6930 let configI = 0;
6931 let currentConfig;
6932 let headSectionNodes = [];
6933 let bodySectionNodes = [];
6934 let footSectionNodes = [];
6935 while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'header') {
6936 headSectionNodes.push(this.renderSection(currentConfig, microColGroupNode, true));
6937 configI += 1;
6938 }
6939 while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'body') {
6940 bodySectionNodes.push(this.renderSection(currentConfig, microColGroupNode, false));
6941 configI += 1;
6942 }
6943 while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'footer') {
6944 footSectionNodes.push(this.renderSection(currentConfig, microColGroupNode, true));
6945 configI += 1;
6946 }
6947 // firefox bug: when setting height on table and there is a thead or tfoot,
6948 // the necessary height:100% on the liquid-height body section forces the *whole* table to be taller. (bug #5524)
6949 // use getCanVGrowWithinCell as a way to detect table-stupid firefox.
6950 // if so, use a simpler dom structure, jam everything into a lone tbody.
6951 let isBuggy = !getCanVGrowWithinCell();
6952 const roleAttrs = { role: 'rowgroup' };
6953 return y('table', {
6954 role: 'grid',
6955 className: classNames.join(' '),
6956 style: { height: props.height },
6957 }, Boolean(!isBuggy && headSectionNodes.length) && y('thead', roleAttrs, ...headSectionNodes), Boolean(!isBuggy && bodySectionNodes.length) && y('tbody', roleAttrs, ...bodySectionNodes), Boolean(!isBuggy && footSectionNodes.length) && y('tfoot', roleAttrs, ...footSectionNodes), isBuggy && y('tbody', roleAttrs, ...headSectionNodes, ...bodySectionNodes, ...footSectionNodes));
6958 }
6959 renderSection(sectionConfig, microColGroupNode, isHeader) {
6960 if ('outerContent' in sectionConfig) {
6961 return (y(_, { key: sectionConfig.key }, sectionConfig.outerContent));
6962 }
6963 return (y("tr", { key: sectionConfig.key, role: "presentation", className: getSectionClassNames(sectionConfig, this.props.liquid).join(' ') }, this.renderChunkTd(sectionConfig, microColGroupNode, sectionConfig.chunk, isHeader)));
6964 }
6965 renderChunkTd(sectionConfig, microColGroupNode, chunkConfig, isHeader) {
6966 if ('outerContent' in chunkConfig) {
6967 return chunkConfig.outerContent;
6968 }
6969 let { props } = this;
6970 let { forceYScrollbars, scrollerClientWidths, scrollerClientHeights } = this.state;
6971 let needsYScrolling = getAllowYScrolling(props, sectionConfig); // TODO: do lazily. do in section config?
6972 let isLiquid = getSectionHasLiquidHeight(props, sectionConfig);
6973 // for `!props.liquid` - is WHOLE scrollgrid natural height?
6974 // TODO: do same thing in advanced scrollgrid? prolly not b/c always has horizontal scrollbars
6975 let overflowY = !props.liquid ? 'visible' :
6976 forceYScrollbars ? 'scroll' :
6977 !needsYScrolling ? 'hidden' :
6978 'auto';
6979 let sectionKey = sectionConfig.key;
6980 let content = renderChunkContent(sectionConfig, chunkConfig, {
6981 tableColGroupNode: microColGroupNode,
6982 tableMinWidth: '',
6983 clientWidth: (!props.collapsibleWidth && scrollerClientWidths[sectionKey] !== undefined) ? scrollerClientWidths[sectionKey] : null,
6984 clientHeight: scrollerClientHeights[sectionKey] !== undefined ? scrollerClientHeights[sectionKey] : null,
6985 expandRows: sectionConfig.expandRows,
6986 syncRowHeights: false,
6987 rowSyncHeights: [],
6988 reportRowHeightChange: () => { },
6989 }, isHeader);
6990 return y(isHeader ? 'th' : 'td', {
6991 ref: chunkConfig.elRef,
6992 role: 'presentation',
6993 }, y("div", { className: `fc-scroller-harness${isLiquid ? ' fc-scroller-harness-liquid' : ''}` },
6994 y(Scroller, { ref: this.scrollerRefs.createRef(sectionKey), elRef: this.scrollerElRefs.createRef(sectionKey), overflowY: overflowY, overflowX: !props.liquid ? 'visible' : 'hidden' /* natural height? */, maxHeight: sectionConfig.maxHeight, liquid: isLiquid, liquidIsAbsolute // because its within a harness
6995 : true }, content)));
6996 }
6997 _handleScrollerEl(scrollerEl, key) {
6998 let section = getSectionByKey(this.props.sections, key);
6999 if (section) {
7000 setRef(section.chunk.scrollerElRef, scrollerEl);
7001 }
7002 }
7003 componentDidMount() {
7004 this.handleSizing();
7005 this.context.addResizeHandler(this.handleSizing);
7006 }
7007 componentDidUpdate() {
7008 // TODO: need better solution when state contains non-sizing things
7009 this.handleSizing();
7010 }
7011 componentWillUnmount() {
7012 this.context.removeResizeHandler(this.handleSizing);
7013 }
7014 computeShrinkWidth() {
7015 return hasShrinkWidth(this.props.cols)
7016 ? computeShrinkWidth(this.scrollerElRefs.getAll())
7017 : 0;
7018 }
7019 computeScrollerDims() {
7020 let scrollbarWidth = getScrollbarWidths();
7021 let { scrollerRefs, scrollerElRefs } = this;
7022 let forceYScrollbars = false;
7023 let scrollerClientWidths = {};
7024 let scrollerClientHeights = {};
7025 for (let sectionKey in scrollerRefs.currentMap) {
7026 let scroller = scrollerRefs.currentMap[sectionKey];
7027 if (scroller && scroller.needsYScrolling()) {
7028 forceYScrollbars = true;
7029 break;
7030 }
7031 }
7032 for (let section of this.props.sections) {
7033 let sectionKey = section.key;
7034 let scrollerEl = scrollerElRefs.currentMap[sectionKey];
7035 if (scrollerEl) {
7036 let harnessEl = scrollerEl.parentNode; // TODO: weird way to get this. need harness b/c doesn't include table borders
7037 scrollerClientWidths[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().width - (forceYScrollbars
7038 ? scrollbarWidth.y // use global because scroller might not have scrollbars yet but will need them in future
7039 : 0));
7040 scrollerClientHeights[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().height);
7041 }
7042 }
7043 return { forceYScrollbars, scrollerClientWidths, scrollerClientHeights };
7044 }
7045 }
7046 SimpleScrollGrid.addStateEquality({
7047 scrollerClientWidths: isPropsEqual,
7048 scrollerClientHeights: isPropsEqual,
7049 });
7050 function getSectionByKey(sections, key) {
7051 for (let section of sections) {
7052 if (section.key === key) {
7053 return section;
7054 }
7055 }
7056 return null;
7057 }
7058
7059 class EventContainer extends BaseComponent {
7060 constructor() {
7061 super(...arguments);
7062 this.handleEl = (el) => {
7063 this.el = el;
7064 if (el) {
7065 setElSeg(el, this.props.seg);
7066 }
7067 };
7068 }
7069 render() {
7070 const { props, context } = this;
7071 const { options } = context;
7072 const { seg } = props;
7073 const { eventRange } = seg;
7074 const { ui } = eventRange;
7075 const renderProps = {
7076 event: new EventImpl(context, eventRange.def, eventRange.instance),
7077 view: context.viewApi,
7078 timeText: props.timeText,
7079 textColor: ui.textColor,
7080 backgroundColor: ui.backgroundColor,
7081 borderColor: ui.borderColor,
7082 isDraggable: !props.disableDragging && computeSegDraggable(seg, context),
7083 isStartResizable: !props.disableResizing && computeSegStartResizable(seg, context),
7084 isEndResizable: !props.disableResizing && computeSegEndResizable(seg),
7085 isMirror: Boolean(props.isDragging || props.isResizing || props.isDateSelecting),
7086 isStart: Boolean(seg.isStart),
7087 isEnd: Boolean(seg.isEnd),
7088 isPast: Boolean(props.isPast),
7089 isFuture: Boolean(props.isFuture),
7090 isToday: Boolean(props.isToday),
7091 isSelected: Boolean(props.isSelected),
7092 isDragging: Boolean(props.isDragging),
7093 isResizing: Boolean(props.isResizing),
7094 };
7095 return (y(ContentContainer, Object.assign({}, props /* contains children */, { elRef: this.handleEl, elClasses: [
7096 ...getEventClassNames(renderProps),
7097 ...seg.eventRange.ui.classNames,
7098 ...(props.elClasses || []),
7099 ], renderProps: renderProps, generatorName: "eventContent", customGenerator: options.eventContent, defaultGenerator: props.defaultGenerator, classNameGenerator: options.eventClassNames, didMount: options.eventDidMount, willUnmount: options.eventWillUnmount })));
7100 }
7101 componentDidUpdate(prevProps) {
7102 if (this.el && this.props.seg !== prevProps.seg) {
7103 setElSeg(this.el, this.props.seg);
7104 }
7105 }
7106 }
7107
7108 // should not be a purecomponent
7109 class StandardEvent extends BaseComponent {
7110 render() {
7111 let { props, context } = this;
7112 let { options } = context;
7113 let { seg } = props;
7114 let { ui } = seg.eventRange;
7115 let timeFormat = options.eventTimeFormat || props.defaultTimeFormat;
7116 let timeText = buildSegTimeText(seg, timeFormat, context, props.defaultDisplayEventTime, props.defaultDisplayEventEnd);
7117 return (y(EventContainer, Object.assign({}, props /* includes elRef */, { elTag: "a", elStyle: {
7118 borderColor: ui.borderColor,
7119 backgroundColor: ui.backgroundColor,
7120 }, elAttrs: getSegAnchorAttrs(seg, context), defaultGenerator: renderInnerContent$1$1, timeText: timeText }), (InnerContent, eventContentArg) => (y(_, null,
7121 y(InnerContent, { elTag: "div", elClasses: ['fc-event-main'], elStyle: { color: eventContentArg.textColor } }),
7122 Boolean(eventContentArg.isStartResizable) && (y("div", { className: "fc-event-resizer fc-event-resizer-start" })),
7123 Boolean(eventContentArg.isEndResizable) && (y("div", { className: "fc-event-resizer fc-event-resizer-end" }))))));
7124 }
7125 }
7126 function renderInnerContent$1$1(innerProps) {
7127 return (y("div", { className: "fc-event-main-frame" },
7128 innerProps.timeText && (y("div", { className: "fc-event-time" }, innerProps.timeText)),
7129 y("div", { className: "fc-event-title-container" },
7130 y("div", { className: "fc-event-title fc-sticky" }, innerProps.event.title || y(_, null, "\u00A0")))));
7131 }
7132
7133 const NowIndicatorContainer = (props) => (y(ViewContextType.Consumer, null, (context) => {
7134 let { options } = context;
7135 let renderProps = {
7136 isAxis: props.isAxis,
7137 date: context.dateEnv.toDate(props.date),
7138 view: context.viewApi,
7139 };
7140 return (y(ContentContainer, Object.assign({}, props /* includes children */, { elTag: props.elTag || 'div', renderProps: renderProps, generatorName: "nowIndicatorContent", customGenerator: options.nowIndicatorContent, classNameGenerator: options.nowIndicatorClassNames, didMount: options.nowIndicatorDidMount, willUnmount: options.nowIndicatorWillUnmount })));
7141 }));
7142
7143 const DAY_NUM_FORMAT = createFormatter({ day: 'numeric' });
7144 class DayCellContainer extends BaseComponent {
7145 constructor() {
7146 super(...arguments);
7147 this.refineRenderProps = memoizeObjArg(refineRenderProps);
7148 }
7149 render() {
7150 let { props, context } = this;
7151 let { options } = context;
7152 let renderProps = this.refineRenderProps({
7153 date: props.date,
7154 dateProfile: props.dateProfile,
7155 todayRange: props.todayRange,
7156 isMonthStart: props.isMonthStart || false,
7157 showDayNumber: props.showDayNumber,
7158 extraRenderProps: props.extraRenderProps,
7159 viewApi: context.viewApi,
7160 dateEnv: context.dateEnv,
7161 monthStartFormat: options.monthStartFormat,
7162 });
7163 return (y(ContentContainer, Object.assign({}, props /* includes children */, { elClasses: [
7164 ...getDayClassNames(renderProps, context.theme),
7165 ...(props.elClasses || []),
7166 ], 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:
7167 // don't use custom classNames if disabled
7168 renderProps.isDisabled ? undefined : options.dayCellClassNames, didMount: options.dayCellDidMount, willUnmount: options.dayCellWillUnmount })));
7169 }
7170 }
7171 function hasCustomDayCellContent(options) {
7172 return Boolean(options.dayCellContent || hasCustomRenderingHandler('dayCellContent', options));
7173 }
7174 function refineRenderProps(raw) {
7175 let { date, dateEnv, dateProfile, isMonthStart } = raw;
7176 let dayMeta = getDateMeta(date, raw.todayRange, null, dateProfile);
7177 let dayNumberText = raw.showDayNumber ? (dateEnv.format(date, isMonthStart ? raw.monthStartFormat : DAY_NUM_FORMAT)) : '';
7178 return Object.assign(Object.assign(Object.assign({ date: dateEnv.toDate(date), view: raw.viewApi }, dayMeta), { isMonthStart,
7179 dayNumberText }), raw.extraRenderProps);
7180 }
7181
7182 class BgEvent extends BaseComponent {
7183 render() {
7184 let { props } = this;
7185 let { seg } = props;
7186 return (y(EventContainer, { elTag: "div", elClasses: ['fc-bg-event'], elStyle: { backgroundColor: seg.eventRange.ui.backgroundColor }, defaultGenerator: renderInnerContent$3, seg: seg, timeText: "", isDragging: false, isResizing: false, isDateSelecting: false, isSelected: false, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday, disableDragging: true, disableResizing: true }));
7187 }
7188 }
7189 function renderInnerContent$3(props) {
7190 let { title } = props.event;
7191 return title && (y("div", { className: "fc-event-title" }, props.event.title));
7192 }
7193 function renderFill(fillType) {
7194 return (y("div", { className: `fc-${fillType}` }));
7195 }
7196
7197 const WeekNumberContainer = (props) => (y(ViewContextType.Consumer, null, (context) => {
7198 let { dateEnv, options } = context;
7199 let { date } = props;
7200 let format = options.weekNumberFormat || props.defaultFormat;
7201 let num = dateEnv.computeWeekNumber(date); // TODO: somehow use for formatting as well?
7202 let text = dateEnv.format(date, format);
7203 let renderProps = { num, text, date };
7204 return (y(ContentContainer // why isn't WeekNumberContentArg being auto-detected?
7205 , Object.assign({}, props /* includes children */, { renderProps: renderProps, generatorName: "weekNumberContent", customGenerator: options.weekNumberContent, defaultGenerator: renderInner, classNameGenerator: options.weekNumberClassNames, didMount: options.weekNumberDidMount, willUnmount: options.weekNumberWillUnmount })));
7206 }));
7207 function renderInner(innerProps) {
7208 return innerProps.text;
7209 }
7210
7211 const PADDING_FROM_VIEWPORT = 10;
7212 class Popover extends BaseComponent {
7213 constructor() {
7214 super(...arguments);
7215 this.state = {
7216 titleId: getUniqueDomId(),
7217 };
7218 this.handleRootEl = (el) => {
7219 this.rootEl = el;
7220 if (this.props.elRef) {
7221 setRef(this.props.elRef, el);
7222 }
7223 };
7224 // Triggered when the user clicks *anywhere* in the document, for the autoHide feature
7225 this.handleDocumentMouseDown = (ev) => {
7226 // only hide the popover if the click happened outside the popover
7227 const target = getEventTargetViaRoot(ev);
7228 if (!this.rootEl.contains(target)) {
7229 this.handleCloseClick();
7230 }
7231 };
7232 this.handleDocumentKeyDown = (ev) => {
7233 if (ev.key === 'Escape') {
7234 this.handleCloseClick();
7235 }
7236 };
7237 this.handleCloseClick = () => {
7238 let { onClose } = this.props;
7239 if (onClose) {
7240 onClose();
7241 }
7242 };
7243 }
7244 render() {
7245 let { theme, options } = this.context;
7246 let { props, state } = this;
7247 let classNames = [
7248 'fc-popover',
7249 theme.getClass('popover'),
7250 ].concat(props.extraClassNames || []);
7251 return j(y("div", Object.assign({}, props.extraAttrs, { id: props.id, className: classNames.join(' '), "aria-labelledby": state.titleId, ref: this.handleRootEl }),
7252 y("div", { className: 'fc-popover-header ' + theme.getClass('popoverHeader') },
7253 y("span", { className: "fc-popover-title", id: state.titleId }, props.title),
7254 y("span", { className: 'fc-popover-close ' + theme.getIconClass('close'), title: options.closeHint, onClick: this.handleCloseClick })),
7255 y("div", { className: 'fc-popover-body ' + theme.getClass('popoverContent') }, props.children)), props.parentEl);
7256 }
7257 componentDidMount() {
7258 document.addEventListener('mousedown', this.handleDocumentMouseDown);
7259 document.addEventListener('keydown', this.handleDocumentKeyDown);
7260 this.updateSize();
7261 }
7262 componentWillUnmount() {
7263 document.removeEventListener('mousedown', this.handleDocumentMouseDown);
7264 document.removeEventListener('keydown', this.handleDocumentKeyDown);
7265 }
7266 updateSize() {
7267 let { isRtl } = this.context;
7268 let { alignmentEl, alignGridTop } = this.props;
7269 let { rootEl } = this;
7270 let alignmentRect = computeClippedClientRect(alignmentEl);
7271 if (alignmentRect) {
7272 let popoverDims = rootEl.getBoundingClientRect();
7273 // position relative to viewport
7274 let popoverTop = alignGridTop
7275 ? elementClosest(alignmentEl, '.fc-scrollgrid').getBoundingClientRect().top
7276 : alignmentRect.top;
7277 let popoverLeft = isRtl ? alignmentRect.right - popoverDims.width : alignmentRect.left;
7278 // constrain
7279 popoverTop = Math.max(popoverTop, PADDING_FROM_VIEWPORT);
7280 popoverLeft = Math.min(popoverLeft, document.documentElement.clientWidth - PADDING_FROM_VIEWPORT - popoverDims.width);
7281 popoverLeft = Math.max(popoverLeft, PADDING_FROM_VIEWPORT);
7282 let origin = rootEl.offsetParent.getBoundingClientRect();
7283 applyStyle(rootEl, {
7284 top: popoverTop - origin.top,
7285 left: popoverLeft - origin.left,
7286 });
7287 }
7288 }
7289 }
7290
7291 class MorePopover extends DateComponent {
7292 constructor() {
7293 super(...arguments);
7294 this.handleRootEl = (rootEl) => {
7295 this.rootEl = rootEl;
7296 if (rootEl) {
7297 this.context.registerInteractiveComponent(this, {
7298 el: rootEl,
7299 useEventCenter: false,
7300 });
7301 }
7302 else {
7303 this.context.unregisterInteractiveComponent(this);
7304 }
7305 };
7306 }
7307 render() {
7308 let { options, dateEnv } = this.context;
7309 let { props } = this;
7310 let { startDate, todayRange, dateProfile } = props;
7311 let title = dateEnv.format(startDate, options.dayPopoverFormat);
7312 return (y(DayCellContainer, { elRef: this.handleRootEl, date: startDate, dateProfile: dateProfile, todayRange: todayRange }, (InnerContent, renderProps, elAttrs) => (y(Popover, { elRef: elAttrs.ref, id: props.id, title: title, extraClassNames: ['fc-more-popover'].concat(elAttrs.className || []), extraAttrs: elAttrs /* TODO: make these time-based when not whole-day? */, parentEl: props.parentEl, alignmentEl: props.alignmentEl, alignGridTop: props.alignGridTop, onClose: props.onClose },
7313 hasCustomDayCellContent(options) && (y(InnerContent, { elTag: "div", elClasses: ['fc-more-popover-misc'] })),
7314 props.children))));
7315 }
7316 queryHit(positionLeft, positionTop, elWidth, elHeight) {
7317 let { rootEl, props } = this;
7318 if (positionLeft >= 0 && positionLeft < elWidth &&
7319 positionTop >= 0 && positionTop < elHeight) {
7320 return {
7321 dateProfile: props.dateProfile,
7322 dateSpan: Object.assign({ allDay: !props.forceTimed, range: {
7323 start: props.startDate,
7324 end: props.endDate,
7325 } }, props.extraDateSpan),
7326 dayEl: rootEl,
7327 rect: {
7328 left: 0,
7329 top: 0,
7330 right: elWidth,
7331 bottom: elHeight,
7332 },
7333 layer: 1, // important when comparing with hits from other components
7334 };
7335 }
7336 return null;
7337 }
7338 }
7339
7340 class MoreLinkContainer extends BaseComponent {
7341 constructor() {
7342 super(...arguments);
7343 this.state = {
7344 isPopoverOpen: false,
7345 popoverId: getUniqueDomId(),
7346 };
7347 this.handleLinkEl = (linkEl) => {
7348 this.linkEl = linkEl;
7349 if (this.props.elRef) {
7350 setRef(this.props.elRef, linkEl);
7351 }
7352 };
7353 this.handleClick = (ev) => {
7354 let { props, context } = this;
7355 let { moreLinkClick } = context.options;
7356 let date = computeRange(props).start;
7357 function buildPublicSeg(seg) {
7358 let { def, instance, range } = seg.eventRange;
7359 return {
7360 event: new EventImpl(context, def, instance),
7361 start: context.dateEnv.toDate(range.start),
7362 end: context.dateEnv.toDate(range.end),
7363 isStart: seg.isStart,
7364 isEnd: seg.isEnd,
7365 };
7366 }
7367 if (typeof moreLinkClick === 'function') {
7368 moreLinkClick = moreLinkClick({
7369 date,
7370 allDay: Boolean(props.allDayDate),
7371 allSegs: props.allSegs.map(buildPublicSeg),
7372 hiddenSegs: props.hiddenSegs.map(buildPublicSeg),
7373 jsEvent: ev,
7374 view: context.viewApi,
7375 });
7376 }
7377 if (!moreLinkClick || moreLinkClick === 'popover') {
7378 this.setState({ isPopoverOpen: true });
7379 }
7380 else if (typeof moreLinkClick === 'string') { // a view name
7381 context.calendarApi.zoomTo(date, moreLinkClick);
7382 }
7383 };
7384 this.handlePopoverClose = () => {
7385 this.setState({ isPopoverOpen: false });
7386 };
7387 }
7388 render() {
7389 let { props, state } = this;
7390 return (y(ViewContextType.Consumer, null, (context) => {
7391 let { viewApi, options, calendarApi } = context;
7392 let { moreLinkText } = options;
7393 let { moreCnt } = props;
7394 let range = computeRange(props);
7395 let text = typeof moreLinkText === 'function' // TODO: eventually use formatWithOrdinals
7396 ? moreLinkText.call(calendarApi, moreCnt)
7397 : `+${moreCnt} ${moreLinkText}`;
7398 let hint = formatWithOrdinals(options.moreLinkHint, [moreCnt], text);
7399 let renderProps = {
7400 num: moreCnt,
7401 shortText: `+${moreCnt}`,
7402 text,
7403 view: viewApi,
7404 };
7405 return (y(_, null,
7406 Boolean(props.moreCnt) && (y(ContentContainer, { elTag: props.elTag || 'a', elRef: this.handleLinkEl, elClasses: [
7407 ...(props.elClasses || []),
7408 'fc-more-link',
7409 ], 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$1, classNameGenerator: options.moreLinkClassNames, didMount: options.moreLinkDidMount, willUnmount: options.moreLinkWillUnmount }, props.children)),
7410 state.isPopoverOpen && (y(MorePopover, { id: state.popoverId, startDate: range.start, endDate: range.end, dateProfile: props.dateProfile, todayRange: props.todayRange, extraDateSpan: props.extraDateSpan, parentEl: this.parentEl, alignmentEl: props.alignmentElRef ?
7411 props.alignmentElRef.current :
7412 this.linkEl, alignGridTop: props.alignGridTop, forceTimed: props.forceTimed, onClose: this.handlePopoverClose }, props.popoverContent()))));
7413 }));
7414 }
7415 componentDidMount() {
7416 this.updateParentEl();
7417 }
7418 componentDidUpdate() {
7419 this.updateParentEl();
7420 }
7421 updateParentEl() {
7422 if (this.linkEl) {
7423 this.parentEl = elementClosest(this.linkEl, '.fc-view-harness');
7424 }
7425 }
7426 }
7427 function renderMoreLinkInner$1(props) {
7428 return props.text;
7429 }
7430 function computeRange(props) {
7431 if (props.allDayDate) {
7432 return {
7433 start: props.allDayDate,
7434 end: addDays(props.allDayDate, 1),
7435 };
7436 }
7437 let { hiddenSegs } = props;
7438 return {
7439 start: computeEarliestSegStart(hiddenSegs),
7440 end: computeLatestSegEnd(hiddenSegs),
7441 };
7442 }
7443 function computeEarliestSegStart(segs) {
7444 return segs.reduce(pickEarliestStart).eventRange.range.start;
7445 }
7446 function pickEarliestStart(seg0, seg1) {
7447 return seg0.eventRange.range.start < seg1.eventRange.range.start ? seg0 : seg1;
7448 }
7449 function computeLatestSegEnd(segs) {
7450 return segs.reduce(pickLatestEnd).eventRange.range.end;
7451 }
7452 function pickLatestEnd(seg0, seg1) {
7453 return seg0.eventRange.range.end > seg1.eventRange.range.end ? seg0 : seg1;
7454 }
7455
7456 class Store {
7457 constructor() {
7458 this.handlers = [];
7459 }
7460 set(value) {
7461 this.currentValue = value;
7462 for (let handler of this.handlers) {
7463 handler(value);
7464 }
7465 }
7466 subscribe(handler) {
7467 this.handlers.push(handler);
7468 if (this.currentValue !== undefined) {
7469 handler(this.currentValue);
7470 }
7471 }
7472 }
7473
7474 /*
7475 Subscribers will get a LIST of CustomRenderings
7476 */
7477 class CustomRenderingStore extends Store {
7478 constructor() {
7479 super(...arguments);
7480 this.map = new Map();
7481 }
7482 // for consistent order
7483 handle(customRendering) {
7484 const { map } = this;
7485 let updated = false;
7486 if (customRendering.isActive) {
7487 map.set(customRendering.id, customRendering);
7488 updated = true;
7489 }
7490 else if (map.has(customRendering.id)) {
7491 map.delete(customRendering.id);
7492 updated = true;
7493 }
7494 if (updated) {
7495 this.set(map);
7496 }
7497 }
7498 }
7499
7500 var internal = {
7501 __proto__: null,
7502 BASE_OPTION_DEFAULTS: BASE_OPTION_DEFAULTS,
7503 BaseComponent: BaseComponent,
7504 BgEvent: BgEvent,
7505 CalendarImpl: CalendarImpl,
7506 CalendarRoot: CalendarRoot,
7507 ContentContainer: ContentContainer,
7508 CustomRenderingStore: CustomRenderingStore,
7509 DateComponent: DateComponent,
7510 DateEnv: DateEnv,
7511 DateProfileGenerator: DateProfileGenerator,
7512 DayCellContainer: DayCellContainer,
7513 DayHeader: DayHeader,
7514 DaySeriesModel: DaySeriesModel,
7515 DayTableModel: DayTableModel,
7516 DelayedRunner: DelayedRunner,
7517 ElementDragging: ElementDragging,
7518 ElementScrollController: ElementScrollController,
7519 Emitter: Emitter,
7520 EventContainer: EventContainer,
7521 EventImpl: EventImpl,
7522 Interaction: Interaction,
7523 MoreLinkContainer: MoreLinkContainer,
7524 NamedTimeZoneImpl: NamedTimeZoneImpl,
7525 NowIndicatorContainer: NowIndicatorContainer,
7526 NowTimer: NowTimer,
7527 PositionCache: PositionCache,
7528 RefMap: RefMap,
7529 ScrollController: ScrollController,
7530 ScrollResponder: ScrollResponder,
7531 Scroller: Scroller,
7532 SegHierarchy: SegHierarchy,
7533 SimpleScrollGrid: SimpleScrollGrid,
7534 Slicer: Slicer,
7535 Splitter: Splitter,
7536 StandardEvent: StandardEvent,
7537 TableDateCell: TableDateCell,
7538 TableDowCell: TableDowCell,
7539 Theme: Theme,
7540 ViewContainer: ViewContainer,
7541 ViewContextType: ViewContextType,
7542 WeekNumberContainer: WeekNumberContainer,
7543 WindowScrollController: WindowScrollController,
7544 addDays: addDays,
7545 addDurations: addDurations,
7546 addMs: addMs,
7547 addWeeks: addWeeks,
7548 allowContextMenu: allowContextMenu,
7549 allowSelection: allowSelection,
7550 applyMutationToEventStore: applyMutationToEventStore,
7551 applyStyle: applyStyle,
7552 asCleanDays: asCleanDays,
7553 asRoughMinutes: asRoughMinutes,
7554 asRoughMs: asRoughMs,
7555 asRoughSeconds: asRoughSeconds,
7556 binarySearch: binarySearch,
7557 buildElAttrs: buildElAttrs,
7558 buildEntryKey: buildEntryKey,
7559 buildEventApis: buildEventApis,
7560 buildEventRangeKey: buildEventRangeKey,
7561 buildIsoString: buildIsoString,
7562 buildNavLinkAttrs: buildNavLinkAttrs,
7563 buildSegTimeText: buildSegTimeText,
7564 collectFromHash: collectFromHash,
7565 combineEventUis: combineEventUis,
7566 compareByFieldSpecs: compareByFieldSpecs,
7567 compareNumbers: compareNumbers,
7568 compareObjs: compareObjs,
7569 computeEarliestSegStart: computeEarliestSegStart,
7570 computeEdges: computeEdges,
7571 computeFallbackHeaderFormat: computeFallbackHeaderFormat,
7572 computeInnerRect: computeInnerRect,
7573 computeRect: computeRect,
7574 computeShrinkWidth: computeShrinkWidth,
7575 computeVisibleDayRange: computeVisibleDayRange,
7576 config: config,
7577 constrainPoint: constrainPoint,
7578 createDuration: createDuration,
7579 createEmptyEventStore: createEmptyEventStore,
7580 createEventInstance: createEventInstance,
7581 createEventUi: createEventUi,
7582 createFormatter: createFormatter,
7583 diffDates: diffDates,
7584 diffDayAndTime: diffDayAndTime,
7585 diffDays: diffDays,
7586 diffPoints: diffPoints,
7587 diffWeeks: diffWeeks,
7588 diffWholeDays: diffWholeDays,
7589 diffWholeWeeks: diffWholeWeeks,
7590 disableCursor: disableCursor,
7591 elementClosest: elementClosest,
7592 elementMatches: elementMatches,
7593 enableCursor: enableCursor,
7594 eventTupleToStore: eventTupleToStore,
7595 filterHash: filterHash,
7596 findDirectChildren: findDirectChildren,
7597 findElements: findElements,
7598 flexibleCompare: flexibleCompare,
7599 formatDayString: formatDayString,
7600 formatIsoMonthStr: formatIsoMonthStr,
7601 formatIsoTimeString: formatIsoTimeString,
7602 getAllowYScrolling: getAllowYScrolling,
7603 getCanVGrowWithinCell: getCanVGrowWithinCell,
7604 getClippingParents: getClippingParents,
7605 getDateMeta: getDateMeta,
7606 getDayClassNames: getDayClassNames,
7607 getDefaultEventEnd: getDefaultEventEnd,
7608 getElSeg: getElSeg,
7609 getEntrySpanEnd: getEntrySpanEnd,
7610 getEventTargetViaRoot: getEventTargetViaRoot,
7611 getIsRtlScrollbarOnLeft: getIsRtlScrollbarOnLeft,
7612 getRectCenter: getRectCenter,
7613 getRelevantEvents: getRelevantEvents,
7614 getScrollGridClassNames: getScrollGridClassNames,
7615 getScrollbarWidths: getScrollbarWidths,
7616 getSectionClassNames: getSectionClassNames,
7617 getSectionHasLiquidHeight: getSectionHasLiquidHeight,
7618 getSegAnchorAttrs: getSegAnchorAttrs,
7619 getSegMeta: getSegMeta,
7620 getSlotClassNames: getSlotClassNames,
7621 getStickyFooterScrollbar: getStickyFooterScrollbar,
7622 getStickyHeaderDates: getStickyHeaderDates,
7623 getUniqueDomId: getUniqueDomId,
7624 greatestDurationDenominator: greatestDurationDenominator,
7625 groupIntersectingEntries: groupIntersectingEntries,
7626 guid: guid,
7627 hasBgRendering: hasBgRendering,
7628 hasCustomDayCellContent: hasCustomDayCellContent,
7629 hasShrinkWidth: hasShrinkWidth,
7630 identity: identity,
7631 injectStyles: injectStyles,
7632 interactionSettingsStore: interactionSettingsStore,
7633 interactionSettingsToStore: interactionSettingsToStore,
7634 intersectRanges: intersectRanges,
7635 intersectRects: intersectRects,
7636 intersectSpans: intersectSpans,
7637 isArraysEqual: isArraysEqual,
7638 isColPropsEqual: isColPropsEqual,
7639 isDateSelectionValid: isDateSelectionValid,
7640 isDateSpansEqual: isDateSpansEqual,
7641 isInt: isInt,
7642 isInteractionValid: isInteractionValid,
7643 isMultiDayRange: isMultiDayRange,
7644 isPropsEqual: isPropsEqual,
7645 isPropsValid: isPropsValid,
7646 isValidDate: isValidDate,
7647 mapHash: mapHash,
7648 memoize: memoize,
7649 memoizeArraylike: memoizeArraylike,
7650 memoizeHashlike: memoizeHashlike,
7651 memoizeObjArg: memoizeObjArg,
7652 mergeEventStores: mergeEventStores,
7653 multiplyDuration: multiplyDuration,
7654 padStart: padStart,
7655 parseBusinessHours: parseBusinessHours,
7656 parseClassNames: parseClassNames,
7657 parseDragMeta: parseDragMeta,
7658 parseEventDef: parseEventDef,
7659 parseFieldSpecs: parseFieldSpecs,
7660 parseMarker: parse,
7661 pointInsideRect: pointInsideRect,
7662 preventContextMenu: preventContextMenu,
7663 preventDefault: preventDefault,
7664 preventSelection: preventSelection,
7665 rangeContainsMarker: rangeContainsMarker,
7666 rangeContainsRange: rangeContainsRange,
7667 rangesEqual: rangesEqual,
7668 rangesIntersect: rangesIntersect,
7669 refineEventDef: refineEventDef,
7670 refineProps: refineProps,
7671 removeElement: removeElement,
7672 removeExact: removeExact,
7673 renderChunkContent: renderChunkContent,
7674 renderFill: renderFill,
7675 renderMicroColGroup: renderMicroColGroup,
7676 renderScrollShim: renderScrollShim,
7677 requestJson: requestJson,
7678 sanitizeShrinkWidth: sanitizeShrinkWidth,
7679 setRef: setRef,
7680 sliceEventStore: sliceEventStore,
7681 sortEventSegs: sortEventSegs,
7682 startOfDay: startOfDay,
7683 translateRect: translateRect,
7684 triggerDateSelect: triggerDateSelect,
7685 unpromisify: unpromisify,
7686 whenTransitionDone: whenTransitionDone,
7687 wholeDivideDurations: wholeDivideDurations
7688 };
7689
7690 var preact = {
7691 __proto__: null,
7692 createPortal: j,
7693 createContext: createContext,
7694 flushSync: flushSync,
7695 Component: x$1,
7696 Fragment: _,
7697 cloneElement: F$1,
7698 createElement: y,
7699 createRef: d,
7700 h: y,
7701 hydrate: E,
7702 get isValidElement () { return i$1; },
7703 get options () { return l$1; },
7704 render: D$1,
7705 toChildArray: j$2
7706 };
7707
7708 const globalLocales = [];
7709
7710 const MINIMAL_RAW_EN_LOCALE = {
7711 code: 'en',
7712 week: {
7713 dow: 0,
7714 doy: 4, // 4 days need to be within the year to be considered the first week
7715 },
7716 direction: 'ltr',
7717 buttonText: {
7718 prev: 'prev',
7719 next: 'next',
7720 prevYear: 'prev year',
7721 nextYear: 'next year',
7722 year: 'year',
7723 today: 'today',
7724 month: 'month',
7725 week: 'week',
7726 day: 'day',
7727 list: 'list',
7728 },
7729 weekText: 'W',
7730 weekTextLong: 'Week',
7731 closeHint: 'Close',
7732 timeHint: 'Time',
7733 eventHint: 'Event',
7734 allDayText: 'all-day',
7735 moreLinkText: 'more',
7736 noEventsText: 'No events to display',
7737 };
7738 const RAW_EN_LOCALE = Object.assign(Object.assign({}, MINIMAL_RAW_EN_LOCALE), {
7739 // Includes things we don't want other locales to inherit,
7740 // things that derive from other translatable strings.
7741 buttonHints: {
7742 prev: 'Previous $0',
7743 next: 'Next $0',
7744 today(buttonText, unit) {
7745 return (unit === 'day')
7746 ? 'Today'
7747 : `This ${buttonText}`;
7748 },
7749 }, viewHint: '$0 view', navLinkHint: 'Go to $0', moreLinkHint(eventCnt) {
7750 return `Show ${eventCnt} more event${eventCnt === 1 ? '' : 's'}`;
7751 } });
7752 function organizeRawLocales(explicitRawLocales) {
7753 let defaultCode = explicitRawLocales.length > 0 ? explicitRawLocales[0].code : 'en';
7754 let allRawLocales = globalLocales.concat(explicitRawLocales);
7755 let rawLocaleMap = {
7756 en: RAW_EN_LOCALE,
7757 };
7758 for (let rawLocale of allRawLocales) {
7759 rawLocaleMap[rawLocale.code] = rawLocale;
7760 }
7761 return {
7762 map: rawLocaleMap,
7763 defaultCode,
7764 };
7765 }
7766 function buildLocale(inputSingular, available) {
7767 if (typeof inputSingular === 'object' && !Array.isArray(inputSingular)) {
7768 return parseLocale(inputSingular.code, [inputSingular.code], inputSingular);
7769 }
7770 return queryLocale(inputSingular, available);
7771 }
7772 function queryLocale(codeArg, available) {
7773 let codes = [].concat(codeArg || []); // will convert to array
7774 let raw = queryRawLocale(codes, available) || RAW_EN_LOCALE;
7775 return parseLocale(codeArg, codes, raw);
7776 }
7777 function queryRawLocale(codes, available) {
7778 for (let i = 0; i < codes.length; i += 1) {
7779 let parts = codes[i].toLocaleLowerCase().split('-');
7780 for (let j = parts.length; j > 0; j -= 1) {
7781 let simpleId = parts.slice(0, j).join('-');
7782 if (available[simpleId]) {
7783 return available[simpleId];
7784 }
7785 }
7786 }
7787 return null;
7788 }
7789 function parseLocale(codeArg, codes, raw) {
7790 let merged = mergeProps([MINIMAL_RAW_EN_LOCALE, raw], ['buttonText']);
7791 delete merged.code; // don't want this part of the options
7792 let { week } = merged;
7793 delete merged.week;
7794 return {
7795 codeArg,
7796 codes,
7797 week,
7798 simpleNumberFormat: new Intl.NumberFormat(codeArg),
7799 options: merged,
7800 };
7801 }
7802
7803 // TODO: easier way to add new hooks? need to update a million things
7804 function createPlugin(input) {
7805 return {
7806 id: guid(),
7807 name: input.name,
7808 premiumReleaseDate: input.premiumReleaseDate ? new Date(input.premiumReleaseDate) : undefined,
7809 deps: input.deps || [],
7810 reducers: input.reducers || [],
7811 isLoadingFuncs: input.isLoadingFuncs || [],
7812 contextInit: [].concat(input.contextInit || []),
7813 eventRefiners: input.eventRefiners || {},
7814 eventDefMemberAdders: input.eventDefMemberAdders || [],
7815 eventSourceRefiners: input.eventSourceRefiners || {},
7816 isDraggableTransformers: input.isDraggableTransformers || [],
7817 eventDragMutationMassagers: input.eventDragMutationMassagers || [],
7818 eventDefMutationAppliers: input.eventDefMutationAppliers || [],
7819 dateSelectionTransformers: input.dateSelectionTransformers || [],
7820 datePointTransforms: input.datePointTransforms || [],
7821 dateSpanTransforms: input.dateSpanTransforms || [],
7822 views: input.views || {},
7823 viewPropsTransformers: input.viewPropsTransformers || [],
7824 isPropsValid: input.isPropsValid || null,
7825 externalDefTransforms: input.externalDefTransforms || [],
7826 viewContainerAppends: input.viewContainerAppends || [],
7827 eventDropTransformers: input.eventDropTransformers || [],
7828 componentInteractions: input.componentInteractions || [],
7829 calendarInteractions: input.calendarInteractions || [],
7830 themeClasses: input.themeClasses || {},
7831 eventSourceDefs: input.eventSourceDefs || [],
7832 cmdFormatter: input.cmdFormatter,
7833 recurringTypes: input.recurringTypes || [],
7834 namedTimeZonedImpl: input.namedTimeZonedImpl,
7835 initialView: input.initialView || '',
7836 elementDraggingImpl: input.elementDraggingImpl,
7837 optionChangeHandlers: input.optionChangeHandlers || {},
7838 scrollGridImpl: input.scrollGridImpl || null,
7839 listenerRefiners: input.listenerRefiners || {},
7840 optionRefiners: input.optionRefiners || {},
7841 propSetHandlers: input.propSetHandlers || {},
7842 };
7843 }
7844 function buildPluginHooks(pluginDefs, globalDefs) {
7845 let currentPluginIds = {};
7846 let hooks = {
7847 premiumReleaseDate: undefined,
7848 reducers: [],
7849 isLoadingFuncs: [],
7850 contextInit: [],
7851 eventRefiners: {},
7852 eventDefMemberAdders: [],
7853 eventSourceRefiners: {},
7854 isDraggableTransformers: [],
7855 eventDragMutationMassagers: [],
7856 eventDefMutationAppliers: [],
7857 dateSelectionTransformers: [],
7858 datePointTransforms: [],
7859 dateSpanTransforms: [],
7860 views: {},
7861 viewPropsTransformers: [],
7862 isPropsValid: null,
7863 externalDefTransforms: [],
7864 viewContainerAppends: [],
7865 eventDropTransformers: [],
7866 componentInteractions: [],
7867 calendarInteractions: [],
7868 themeClasses: {},
7869 eventSourceDefs: [],
7870 cmdFormatter: null,
7871 recurringTypes: [],
7872 namedTimeZonedImpl: null,
7873 initialView: '',
7874 elementDraggingImpl: null,
7875 optionChangeHandlers: {},
7876 scrollGridImpl: null,
7877 listenerRefiners: {},
7878 optionRefiners: {},
7879 propSetHandlers: {},
7880 };
7881 function addDefs(defs) {
7882 for (let def of defs) {
7883 const pluginName = def.name;
7884 const currentId = currentPluginIds[pluginName];
7885 if (currentId === undefined) {
7886 currentPluginIds[pluginName] = def.id;
7887 addDefs(def.deps);
7888 hooks = combineHooks(hooks, def);
7889 }
7890 else if (currentId !== def.id) {
7891 // different ID than the one already added
7892 console.warn(`Duplicate plugin '${pluginName}'`);
7893 }
7894 }
7895 }
7896 if (pluginDefs) {
7897 addDefs(pluginDefs);
7898 }
7899 addDefs(globalDefs);
7900 return hooks;
7901 }
7902 function buildBuildPluginHooks() {
7903 let currentOverrideDefs = [];
7904 let currentGlobalDefs = [];
7905 let currentHooks;
7906 return (overrideDefs, globalDefs) => {
7907 if (!currentHooks || !isArraysEqual(overrideDefs, currentOverrideDefs) || !isArraysEqual(globalDefs, currentGlobalDefs)) {
7908 currentHooks = buildPluginHooks(overrideDefs, globalDefs);
7909 }
7910 currentOverrideDefs = overrideDefs;
7911 currentGlobalDefs = globalDefs;
7912 return currentHooks;
7913 };
7914 }
7915 function combineHooks(hooks0, hooks1) {
7916 return {
7917 premiumReleaseDate: compareOptionalDates(hooks0.premiumReleaseDate, hooks1.premiumReleaseDate),
7918 reducers: hooks0.reducers.concat(hooks1.reducers),
7919 isLoadingFuncs: hooks0.isLoadingFuncs.concat(hooks1.isLoadingFuncs),
7920 contextInit: hooks0.contextInit.concat(hooks1.contextInit),
7921 eventRefiners: Object.assign(Object.assign({}, hooks0.eventRefiners), hooks1.eventRefiners),
7922 eventDefMemberAdders: hooks0.eventDefMemberAdders.concat(hooks1.eventDefMemberAdders),
7923 eventSourceRefiners: Object.assign(Object.assign({}, hooks0.eventSourceRefiners), hooks1.eventSourceRefiners),
7924 isDraggableTransformers: hooks0.isDraggableTransformers.concat(hooks1.isDraggableTransformers),
7925 eventDragMutationMassagers: hooks0.eventDragMutationMassagers.concat(hooks1.eventDragMutationMassagers),
7926 eventDefMutationAppliers: hooks0.eventDefMutationAppliers.concat(hooks1.eventDefMutationAppliers),
7927 dateSelectionTransformers: hooks0.dateSelectionTransformers.concat(hooks1.dateSelectionTransformers),
7928 datePointTransforms: hooks0.datePointTransforms.concat(hooks1.datePointTransforms),
7929 dateSpanTransforms: hooks0.dateSpanTransforms.concat(hooks1.dateSpanTransforms),
7930 views: Object.assign(Object.assign({}, hooks0.views), hooks1.views),
7931 viewPropsTransformers: hooks0.viewPropsTransformers.concat(hooks1.viewPropsTransformers),
7932 isPropsValid: hooks1.isPropsValid || hooks0.isPropsValid,
7933 externalDefTransforms: hooks0.externalDefTransforms.concat(hooks1.externalDefTransforms),
7934 viewContainerAppends: hooks0.viewContainerAppends.concat(hooks1.viewContainerAppends),
7935 eventDropTransformers: hooks0.eventDropTransformers.concat(hooks1.eventDropTransformers),
7936 calendarInteractions: hooks0.calendarInteractions.concat(hooks1.calendarInteractions),
7937 componentInteractions: hooks0.componentInteractions.concat(hooks1.componentInteractions),
7938 themeClasses: Object.assign(Object.assign({}, hooks0.themeClasses), hooks1.themeClasses),
7939 eventSourceDefs: hooks0.eventSourceDefs.concat(hooks1.eventSourceDefs),
7940 cmdFormatter: hooks1.cmdFormatter || hooks0.cmdFormatter,
7941 recurringTypes: hooks0.recurringTypes.concat(hooks1.recurringTypes),
7942 namedTimeZonedImpl: hooks1.namedTimeZonedImpl || hooks0.namedTimeZonedImpl,
7943 initialView: hooks0.initialView || hooks1.initialView,
7944 elementDraggingImpl: hooks0.elementDraggingImpl || hooks1.elementDraggingImpl,
7945 optionChangeHandlers: Object.assign(Object.assign({}, hooks0.optionChangeHandlers), hooks1.optionChangeHandlers),
7946 scrollGridImpl: hooks1.scrollGridImpl || hooks0.scrollGridImpl,
7947 listenerRefiners: Object.assign(Object.assign({}, hooks0.listenerRefiners), hooks1.listenerRefiners),
7948 optionRefiners: Object.assign(Object.assign({}, hooks0.optionRefiners), hooks1.optionRefiners),
7949 propSetHandlers: Object.assign(Object.assign({}, hooks0.propSetHandlers), hooks1.propSetHandlers),
7950 };
7951 }
7952 function compareOptionalDates(date0, date1) {
7953 if (date0 === undefined) {
7954 return date1;
7955 }
7956 if (date1 === undefined) {
7957 return date0;
7958 }
7959 return new Date(Math.max(date0.valueOf(), date1.valueOf()));
7960 }
7961
7962 class StandardTheme extends Theme {
7963 }
7964 StandardTheme.prototype.classes = {
7965 root: 'fc-theme-standard',
7966 tableCellShaded: 'fc-cell-shaded',
7967 buttonGroup: 'fc-button-group',
7968 button: 'fc-button fc-button-primary',
7969 buttonActive: 'fc-button-active',
7970 };
7971 StandardTheme.prototype.baseIconClass = 'fc-icon';
7972 StandardTheme.prototype.iconClasses = {
7973 close: 'fc-icon-x',
7974 prev: 'fc-icon-chevron-left',
7975 next: 'fc-icon-chevron-right',
7976 prevYear: 'fc-icon-chevrons-left',
7977 nextYear: 'fc-icon-chevrons-right',
7978 };
7979 StandardTheme.prototype.rtlIconClasses = {
7980 prev: 'fc-icon-chevron-right',
7981 next: 'fc-icon-chevron-left',
7982 prevYear: 'fc-icon-chevrons-right',
7983 nextYear: 'fc-icon-chevrons-left',
7984 };
7985 StandardTheme.prototype.iconOverrideOption = 'buttonIcons'; // TODO: make TS-friendly
7986 StandardTheme.prototype.iconOverrideCustomButtonOption = 'icon';
7987 StandardTheme.prototype.iconOverridePrefix = 'fc-icon-';
7988
7989 function compileViewDefs(defaultConfigs, overrideConfigs) {
7990 let hash = {};
7991 let viewType;
7992 for (viewType in defaultConfigs) {
7993 ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs);
7994 }
7995 for (viewType in overrideConfigs) {
7996 ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs);
7997 }
7998 return hash;
7999 }
8000 function ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs) {
8001 if (hash[viewType]) {
8002 return hash[viewType];
8003 }
8004 let viewDef = buildViewDef(viewType, hash, defaultConfigs, overrideConfigs);
8005 if (viewDef) {
8006 hash[viewType] = viewDef;
8007 }
8008 return viewDef;
8009 }
8010 function buildViewDef(viewType, hash, defaultConfigs, overrideConfigs) {
8011 let defaultConfig = defaultConfigs[viewType];
8012 let overrideConfig = overrideConfigs[viewType];
8013 let queryProp = (name) => ((defaultConfig && defaultConfig[name] !== null) ? defaultConfig[name] :
8014 ((overrideConfig && overrideConfig[name] !== null) ? overrideConfig[name] : null));
8015 let theComponent = queryProp('component');
8016 let superType = queryProp('superType');
8017 let superDef = null;
8018 if (superType) {
8019 if (superType === viewType) {
8020 throw new Error('Can\'t have a custom view type that references itself');
8021 }
8022 superDef = ensureViewDef(superType, hash, defaultConfigs, overrideConfigs);
8023 }
8024 if (!theComponent && superDef) {
8025 theComponent = superDef.component;
8026 }
8027 if (!theComponent) {
8028 return null; // don't throw a warning, might be settings for a single-unit view
8029 }
8030 return {
8031 type: viewType,
8032 component: theComponent,
8033 defaults: Object.assign(Object.assign({}, (superDef ? superDef.defaults : {})), (defaultConfig ? defaultConfig.rawOptions : {})),
8034 overrides: Object.assign(Object.assign({}, (superDef ? superDef.overrides : {})), (overrideConfig ? overrideConfig.rawOptions : {})),
8035 };
8036 }
8037
8038 function parseViewConfigs(inputs) {
8039 return mapHash(inputs, parseViewConfig);
8040 }
8041 function parseViewConfig(input) {
8042 let rawOptions = typeof input === 'function' ?
8043 { component: input } :
8044 input;
8045 let { component } = rawOptions;
8046 if (rawOptions.content) {
8047 // TODO: remove content/classNames/didMount/etc from options?
8048 component = createViewHookComponent(rawOptions);
8049 }
8050 else if (component && !(component.prototype instanceof BaseComponent)) {
8051 // WHY?: people were using `component` property for `content`
8052 // TODO: converge on one setting name
8053 component = createViewHookComponent(Object.assign(Object.assign({}, rawOptions), { content: component }));
8054 }
8055 return {
8056 superType: rawOptions.type,
8057 component: component,
8058 rawOptions, // includes type and component too :(
8059 };
8060 }
8061 function createViewHookComponent(options) {
8062 return (viewProps) => (y(ViewContextType.Consumer, null, (context) => (y(ContentContainer, { elTag: "div", elClasses: buildViewClassNames(context.viewSpec), renderProps: Object.assign(Object.assign({}, viewProps), { nextDayThreshold: context.options.nextDayThreshold }), generatorName: undefined, customGenerator: options.content, classNameGenerator: options.classNames, didMount: options.didMount, willUnmount: options.willUnmount }))));
8063 }
8064
8065 function buildViewSpecs(defaultInputs, optionOverrides, dynamicOptionOverrides, localeDefaults) {
8066 let defaultConfigs = parseViewConfigs(defaultInputs);
8067 let overrideConfigs = parseViewConfigs(optionOverrides.views);
8068 let viewDefs = compileViewDefs(defaultConfigs, overrideConfigs);
8069 return mapHash(viewDefs, (viewDef) => buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults));
8070 }
8071 function buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults) {
8072 let durationInput = viewDef.overrides.duration ||
8073 viewDef.defaults.duration ||
8074 dynamicOptionOverrides.duration ||
8075 optionOverrides.duration;
8076 let duration = null;
8077 let durationUnit = '';
8078 let singleUnit = '';
8079 let singleUnitOverrides = {};
8080 if (durationInput) {
8081 duration = createDurationCached(durationInput);
8082 if (duration) { // valid?
8083 let denom = greatestDurationDenominator(duration);
8084 durationUnit = denom.unit;
8085 if (denom.value === 1) {
8086 singleUnit = durationUnit;
8087 singleUnitOverrides = overrideConfigs[durationUnit] ? overrideConfigs[durationUnit].rawOptions : {};
8088 }
8089 }
8090 }
8091 let queryButtonText = (optionsSubset) => {
8092 let buttonTextMap = optionsSubset.buttonText || {};
8093 let buttonTextKey = viewDef.defaults.buttonTextKey;
8094 if (buttonTextKey != null && buttonTextMap[buttonTextKey] != null) {
8095 return buttonTextMap[buttonTextKey];
8096 }
8097 if (buttonTextMap[viewDef.type] != null) {
8098 return buttonTextMap[viewDef.type];
8099 }
8100 if (buttonTextMap[singleUnit] != null) {
8101 return buttonTextMap[singleUnit];
8102 }
8103 return null;
8104 };
8105 let queryButtonTitle = (optionsSubset) => {
8106 let buttonHints = optionsSubset.buttonHints || {};
8107 let buttonKey = viewDef.defaults.buttonTextKey; // use same key as text
8108 if (buttonKey != null && buttonHints[buttonKey] != null) {
8109 return buttonHints[buttonKey];
8110 }
8111 if (buttonHints[viewDef.type] != null) {
8112 return buttonHints[viewDef.type];
8113 }
8114 if (buttonHints[singleUnit] != null) {
8115 return buttonHints[singleUnit];
8116 }
8117 return null;
8118 };
8119 return {
8120 type: viewDef.type,
8121 component: viewDef.component,
8122 duration,
8123 durationUnit,
8124 singleUnit,
8125 optionDefaults: viewDef.defaults,
8126 optionOverrides: Object.assign(Object.assign({}, singleUnitOverrides), viewDef.overrides),
8127 buttonTextOverride: queryButtonText(dynamicOptionOverrides) ||
8128 queryButtonText(optionOverrides) || // constructor-specified buttonText lookup hash takes precedence
8129 viewDef.overrides.buttonText,
8130 buttonTextDefault: queryButtonText(localeDefaults) ||
8131 viewDef.defaults.buttonText ||
8132 queryButtonText(BASE_OPTION_DEFAULTS) ||
8133 viewDef.type,
8134 // not DRY
8135 buttonTitleOverride: queryButtonTitle(dynamicOptionOverrides) ||
8136 queryButtonTitle(optionOverrides) ||
8137 viewDef.overrides.buttonHint,
8138 buttonTitleDefault: queryButtonTitle(localeDefaults) ||
8139 viewDef.defaults.buttonHint ||
8140 queryButtonTitle(BASE_OPTION_DEFAULTS),
8141 // will eventually fall back to buttonText
8142 };
8143 }
8144 // hack to get memoization working
8145 let durationInputMap = {};
8146 function createDurationCached(durationInput) {
8147 let json = JSON.stringify(durationInput);
8148 let res = durationInputMap[json];
8149 if (res === undefined) {
8150 res = createDuration(durationInput);
8151 durationInputMap[json] = res;
8152 }
8153 return res;
8154 }
8155
8156 function reduceViewType(viewType, action) {
8157 switch (action.type) {
8158 case 'CHANGE_VIEW_TYPE':
8159 viewType = action.viewType;
8160 }
8161 return viewType;
8162 }
8163
8164 function reduceCurrentDate(currentDate, action) {
8165 switch (action.type) {
8166 case 'CHANGE_DATE':
8167 return action.dateMarker;
8168 default:
8169 return currentDate;
8170 }
8171 }
8172 // should be initialized once and stay constant
8173 // this will change too
8174 function getInitialDate(options, dateEnv, nowManager) {
8175 let initialDateInput = options.initialDate;
8176 // compute the initial ambig-timezone date
8177 if (initialDateInput != null) {
8178 return dateEnv.createMarker(initialDateInput);
8179 }
8180 return nowManager.getDateMarker();
8181 }
8182
8183 function reduceDynamicOptionOverrides(dynamicOptionOverrides, action) {
8184 switch (action.type) {
8185 case 'SET_OPTION':
8186 return Object.assign(Object.assign({}, dynamicOptionOverrides), { [action.optionName]: action.rawOptionValue });
8187 default:
8188 return dynamicOptionOverrides;
8189 }
8190 }
8191
8192 function reduceDateProfile(currentDateProfile, action, currentDate, dateProfileGenerator) {
8193 let dp;
8194 switch (action.type) {
8195 case 'CHANGE_VIEW_TYPE':
8196 return dateProfileGenerator.build(action.dateMarker || currentDate);
8197 case 'CHANGE_DATE':
8198 return dateProfileGenerator.build(action.dateMarker);
8199 case 'PREV':
8200 dp = dateProfileGenerator.buildPrev(currentDateProfile, currentDate);
8201 if (dp.isValid) {
8202 return dp;
8203 }
8204 break;
8205 case 'NEXT':
8206 dp = dateProfileGenerator.buildNext(currentDateProfile, currentDate);
8207 if (dp.isValid) {
8208 return dp;
8209 }
8210 break;
8211 }
8212 return currentDateProfile;
8213 }
8214
8215 function initEventSources(calendarOptions, dateProfile, context) {
8216 let activeRange = dateProfile ? dateProfile.activeRange : null;
8217 return addSources({}, parseInitialSources(calendarOptions, context), activeRange, context);
8218 }
8219 function reduceEventSources(eventSources, action, dateProfile, context) {
8220 let activeRange = dateProfile ? dateProfile.activeRange : null; // need this check?
8221 switch (action.type) {
8222 case 'ADD_EVENT_SOURCES': // already parsed
8223 return addSources(eventSources, action.sources, activeRange, context);
8224 case 'REMOVE_EVENT_SOURCE':
8225 return removeSource(eventSources, action.sourceId);
8226 case 'PREV': // TODO: how do we track all actions that affect dateProfile :(
8227 case 'NEXT':
8228 case 'CHANGE_DATE':
8229 case 'CHANGE_VIEW_TYPE':
8230 if (dateProfile) {
8231 return fetchDirtySources(eventSources, activeRange, context);
8232 }
8233 return eventSources;
8234 case 'FETCH_EVENT_SOURCES':
8235 return fetchSourcesByIds(eventSources, action.sourceIds ? // why no type?
8236 arrayToHash(action.sourceIds) :
8237 excludeStaticSources(eventSources, context), activeRange, action.isRefetch || false, context);
8238 case 'RECEIVE_EVENTS':
8239 case 'RECEIVE_EVENT_ERROR':
8240 return receiveResponse(eventSources, action.sourceId, action.fetchId, action.fetchRange);
8241 case 'REMOVE_ALL_EVENT_SOURCES':
8242 return {};
8243 default:
8244 return eventSources;
8245 }
8246 }
8247 function reduceEventSourcesNewTimeZone(eventSources, dateProfile, context) {
8248 let activeRange = dateProfile ? dateProfile.activeRange : null; // need this check?
8249 return fetchSourcesByIds(eventSources, excludeStaticSources(eventSources, context), activeRange, true, context);
8250 }
8251 function computeEventSourcesLoading(eventSources) {
8252 for (let sourceId in eventSources) {
8253 if (eventSources[sourceId].isFetching) {
8254 return true;
8255 }
8256 }
8257 return false;
8258 }
8259 function addSources(eventSourceHash, sources, fetchRange, context) {
8260 let hash = {};
8261 for (let source of sources) {
8262 hash[source.sourceId] = source;
8263 }
8264 if (fetchRange) {
8265 hash = fetchDirtySources(hash, fetchRange, context);
8266 }
8267 return Object.assign(Object.assign({}, eventSourceHash), hash);
8268 }
8269 function removeSource(eventSourceHash, sourceId) {
8270 return filterHash(eventSourceHash, (eventSource) => eventSource.sourceId !== sourceId);
8271 }
8272 function fetchDirtySources(sourceHash, fetchRange, context) {
8273 return fetchSourcesByIds(sourceHash, filterHash(sourceHash, (eventSource) => isSourceDirty(eventSource, fetchRange, context)), fetchRange, false, context);
8274 }
8275 function isSourceDirty(eventSource, fetchRange, context) {
8276 if (!doesSourceNeedRange(eventSource, context)) {
8277 return !eventSource.latestFetchId;
8278 }
8279 return !context.options.lazyFetching ||
8280 !eventSource.fetchRange ||
8281 eventSource.isFetching || // always cancel outdated in-progress fetches
8282 fetchRange.start < eventSource.fetchRange.start ||
8283 fetchRange.end > eventSource.fetchRange.end;
8284 }
8285 function fetchSourcesByIds(prevSources, sourceIdHash, fetchRange, isRefetch, context) {
8286 let nextSources = {};
8287 for (let sourceId in prevSources) {
8288 let source = prevSources[sourceId];
8289 if (sourceIdHash[sourceId]) {
8290 nextSources[sourceId] = fetchSource(source, fetchRange, isRefetch, context);
8291 }
8292 else {
8293 nextSources[sourceId] = source;
8294 }
8295 }
8296 return nextSources;
8297 }
8298 function fetchSource(eventSource, fetchRange, isRefetch, context) {
8299 let { options, calendarApi } = context;
8300 let sourceDef = context.pluginHooks.eventSourceDefs[eventSource.sourceDefId];
8301 let fetchId = guid();
8302 sourceDef.fetch({
8303 eventSource,
8304 range: fetchRange,
8305 isRefetch,
8306 context,
8307 }, (res) => {
8308 let { rawEvents } = res;
8309 if (options.eventSourceSuccess) {
8310 rawEvents = options.eventSourceSuccess.call(calendarApi, rawEvents, res.response) || rawEvents;
8311 }
8312 if (eventSource.success) {
8313 rawEvents = eventSource.success.call(calendarApi, rawEvents, res.response) || rawEvents;
8314 }
8315 context.dispatch({
8316 type: 'RECEIVE_EVENTS',
8317 sourceId: eventSource.sourceId,
8318 fetchId,
8319 fetchRange,
8320 rawEvents,
8321 });
8322 }, (error) => {
8323 let errorHandled = false;
8324 if (options.eventSourceFailure) {
8325 options.eventSourceFailure.call(calendarApi, error);
8326 errorHandled = true;
8327 }
8328 if (eventSource.failure) {
8329 eventSource.failure(error);
8330 errorHandled = true;
8331 }
8332 if (!errorHandled) {
8333 console.warn(error.message, error);
8334 }
8335 context.dispatch({
8336 type: 'RECEIVE_EVENT_ERROR',
8337 sourceId: eventSource.sourceId,
8338 fetchId,
8339 fetchRange,
8340 error,
8341 });
8342 });
8343 return Object.assign(Object.assign({}, eventSource), { isFetching: true, latestFetchId: fetchId });
8344 }
8345 function receiveResponse(sourceHash, sourceId, fetchId, fetchRange) {
8346 let eventSource = sourceHash[sourceId];
8347 if (eventSource && // not already removed
8348 fetchId === eventSource.latestFetchId) {
8349 return Object.assign(Object.assign({}, sourceHash), { [sourceId]: Object.assign(Object.assign({}, eventSource), { isFetching: false, fetchRange }) });
8350 }
8351 return sourceHash;
8352 }
8353 function excludeStaticSources(eventSources, context) {
8354 return filterHash(eventSources, (eventSource) => doesSourceNeedRange(eventSource, context));
8355 }
8356 function parseInitialSources(rawOptions, context) {
8357 let refiners = buildEventSourceRefiners(context);
8358 let rawSources = [].concat(rawOptions.eventSources || []);
8359 let sources = []; // parsed
8360 if (rawOptions.initialEvents) {
8361 rawSources.unshift(rawOptions.initialEvents);
8362 }
8363 if (rawOptions.events) {
8364 rawSources.unshift(rawOptions.events);
8365 }
8366 for (let rawSource of rawSources) {
8367 let source = parseEventSource(rawSource, context, refiners);
8368 if (source) {
8369 sources.push(source);
8370 }
8371 }
8372 return sources;
8373 }
8374 function doesSourceNeedRange(eventSource, context) {
8375 let defs = context.pluginHooks.eventSourceDefs;
8376 return !defs[eventSource.sourceDefId].ignoreRange;
8377 }
8378
8379 function reduceDateSelection(currentSelection, action) {
8380 switch (action.type) {
8381 case 'UNSELECT_DATES':
8382 return null;
8383 case 'SELECT_DATES':
8384 return action.selection;
8385 default:
8386 return currentSelection;
8387 }
8388 }
8389
8390 function reduceSelectedEvent(currentInstanceId, action) {
8391 switch (action.type) {
8392 case 'UNSELECT_EVENT':
8393 return '';
8394 case 'SELECT_EVENT':
8395 return action.eventInstanceId;
8396 default:
8397 return currentInstanceId;
8398 }
8399 }
8400
8401 function reduceEventDrag(currentDrag, action) {
8402 let newDrag;
8403 switch (action.type) {
8404 case 'UNSET_EVENT_DRAG':
8405 return null;
8406 case 'SET_EVENT_DRAG':
8407 newDrag = action.state;
8408 return {
8409 affectedEvents: newDrag.affectedEvents,
8410 mutatedEvents: newDrag.mutatedEvents,
8411 isEvent: newDrag.isEvent,
8412 };
8413 default:
8414 return currentDrag;
8415 }
8416 }
8417
8418 function reduceEventResize(currentResize, action) {
8419 let newResize;
8420 switch (action.type) {
8421 case 'UNSET_EVENT_RESIZE':
8422 return null;
8423 case 'SET_EVENT_RESIZE':
8424 newResize = action.state;
8425 return {
8426 affectedEvents: newResize.affectedEvents,
8427 mutatedEvents: newResize.mutatedEvents,
8428 isEvent: newResize.isEvent,
8429 };
8430 default:
8431 return currentResize;
8432 }
8433 }
8434
8435 function parseToolbars(calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) {
8436 let header = calendarOptions.headerToolbar ? parseToolbar(calendarOptions.headerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) : null;
8437 let footer = calendarOptions.footerToolbar ? parseToolbar(calendarOptions.footerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) : null;
8438 return { header, footer };
8439 }
8440 function parseToolbar(sectionStrHash, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) {
8441 let sectionWidgets = {};
8442 let viewsWithButtons = [];
8443 let hasTitle = false;
8444 for (let sectionName in sectionStrHash) {
8445 let sectionStr = sectionStrHash[sectionName];
8446 let sectionRes = parseSection(sectionStr, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi);
8447 sectionWidgets[sectionName] = sectionRes.widgets;
8448 viewsWithButtons.push(...sectionRes.viewsWithButtons);
8449 hasTitle = hasTitle || sectionRes.hasTitle;
8450 }
8451 return { sectionWidgets, viewsWithButtons, hasTitle };
8452 }
8453 /*
8454 BAD: querying icons and text here. should be done at render time
8455 */
8456 function parseSection(sectionStr, calendarOptions, // defaults+overrides, then refined
8457 calendarOptionOverrides, // overrides only!, unrefined :(
8458 theme, viewSpecs, calendarApi) {
8459 let isRtl = calendarOptions.direction === 'rtl';
8460 let calendarCustomButtons = calendarOptions.customButtons || {};
8461 let calendarButtonTextOverrides = calendarOptionOverrides.buttonText || {};
8462 let calendarButtonText = calendarOptions.buttonText || {};
8463 let calendarButtonHintOverrides = calendarOptionOverrides.buttonHints || {};
8464 let calendarButtonHints = calendarOptions.buttonHints || {};
8465 let sectionSubstrs = sectionStr ? sectionStr.split(' ') : [];
8466 let viewsWithButtons = [];
8467 let hasTitle = false;
8468 let widgets = sectionSubstrs.map((buttonGroupStr) => (buttonGroupStr.split(',').map((buttonName) => {
8469 if (buttonName === 'title') {
8470 hasTitle = true;
8471 return { buttonName };
8472 }
8473 let customButtonProps;
8474 let viewSpec;
8475 let buttonClick;
8476 let buttonIcon; // only one of these will be set
8477 let buttonText; // "
8478 let buttonHint;
8479 // ^ for the title="" attribute, for accessibility
8480 if ((customButtonProps = calendarCustomButtons[buttonName])) {
8481 buttonClick = (ev) => {
8482 if (customButtonProps.click) {
8483 customButtonProps.click.call(ev.target, ev, ev.target); // TODO: use Calendar this context?
8484 }
8485 };
8486 (buttonIcon = theme.getCustomButtonIconClass(customButtonProps)) ||
8487 (buttonIcon = theme.getIconClass(buttonName, isRtl)) ||
8488 (buttonText = customButtonProps.text);
8489 buttonHint = customButtonProps.hint || customButtonProps.text;
8490 }
8491 else if ((viewSpec = viewSpecs[buttonName])) {
8492 viewsWithButtons.push(buttonName);
8493 buttonClick = () => {
8494 calendarApi.changeView(buttonName);
8495 };
8496 (buttonText = viewSpec.buttonTextOverride) ||
8497 (buttonIcon = theme.getIconClass(buttonName, isRtl)) ||
8498 (buttonText = viewSpec.buttonTextDefault);
8499 let textFallback = viewSpec.buttonTextOverride ||
8500 viewSpec.buttonTextDefault;
8501 buttonHint = formatWithOrdinals(viewSpec.buttonTitleOverride ||
8502 viewSpec.buttonTitleDefault ||
8503 calendarOptions.viewHint, [textFallback, buttonName], // view-name = buttonName
8504 textFallback);
8505 }
8506 else if (calendarApi[buttonName]) { // a calendarApi method
8507 buttonClick = () => {
8508 calendarApi[buttonName]();
8509 };
8510 (buttonText = calendarButtonTextOverrides[buttonName]) ||
8511 (buttonIcon = theme.getIconClass(buttonName, isRtl)) ||
8512 (buttonText = calendarButtonText[buttonName]); // everything else is considered default
8513 if (buttonName === 'prevYear' || buttonName === 'nextYear') {
8514 let prevOrNext = buttonName === 'prevYear' ? 'prev' : 'next';
8515 buttonHint = formatWithOrdinals(calendarButtonHintOverrides[prevOrNext] ||
8516 calendarButtonHints[prevOrNext], [
8517 calendarButtonText.year || 'year',
8518 'year',
8519 ], calendarButtonText[buttonName]);
8520 }
8521 else {
8522 buttonHint = (navUnit) => formatWithOrdinals(calendarButtonHintOverrides[buttonName] ||
8523 calendarButtonHints[buttonName], [
8524 calendarButtonText[navUnit] || navUnit,
8525 navUnit,
8526 ], calendarButtonText[buttonName]);
8527 }
8528 }
8529 return { buttonName, buttonClick, buttonIcon, buttonText, buttonHint };
8530 })));
8531 return { widgets, viewsWithButtons, hasTitle };
8532 }
8533
8534 // always represents the current view. otherwise, it'd need to change value every time date changes
8535 class ViewImpl {
8536 constructor(type, getCurrentData, dateEnv) {
8537 this.type = type;
8538 this.getCurrentData = getCurrentData;
8539 this.dateEnv = dateEnv;
8540 }
8541 get calendar() {
8542 return this.getCurrentData().calendarApi;
8543 }
8544 get title() {
8545 return this.getCurrentData().viewTitle;
8546 }
8547 get activeStart() {
8548 return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.start);
8549 }
8550 get activeEnd() {
8551 return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.end);
8552 }
8553 get currentStart() {
8554 return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.start);
8555 }
8556 get currentEnd() {
8557 return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.end);
8558 }
8559 getOption(name) {
8560 return this.getCurrentData().options[name]; // are the view-specific options
8561 }
8562 }
8563
8564 let eventSourceDef$2 = {
8565 ignoreRange: true,
8566 parseMeta(refined) {
8567 if (Array.isArray(refined.events)) {
8568 return refined.events;
8569 }
8570 return null;
8571 },
8572 fetch(arg, successCallback) {
8573 successCallback({
8574 rawEvents: arg.eventSource.meta,
8575 });
8576 },
8577 };
8578 const arrayEventSourcePlugin = createPlugin({
8579 name: 'array-event-source',
8580 eventSourceDefs: [eventSourceDef$2],
8581 });
8582
8583 let eventSourceDef$1 = {
8584 parseMeta(refined) {
8585 if (typeof refined.events === 'function') {
8586 return refined.events;
8587 }
8588 return null;
8589 },
8590 fetch(arg, successCallback, errorCallback) {
8591 const { dateEnv } = arg.context;
8592 const func = arg.eventSource.meta;
8593 unpromisify(func.bind(null, buildRangeApiWithTimeZone(arg.range, dateEnv)), (rawEvents) => successCallback({ rawEvents }), errorCallback);
8594 },
8595 };
8596 const funcEventSourcePlugin = createPlugin({
8597 name: 'func-event-source',
8598 eventSourceDefs: [eventSourceDef$1],
8599 });
8600
8601 const JSON_FEED_EVENT_SOURCE_REFINERS = {
8602 method: String,
8603 extraParams: identity,
8604 startParam: String,
8605 endParam: String,
8606 timeZoneParam: String,
8607 };
8608
8609 let eventSourceDef = {
8610 parseMeta(refined) {
8611 if (refined.url && (refined.format === 'json' || !refined.format)) {
8612 return {
8613 url: refined.url,
8614 format: 'json',
8615 method: (refined.method || 'GET').toUpperCase(),
8616 extraParams: refined.extraParams,
8617 startParam: refined.startParam,
8618 endParam: refined.endParam,
8619 timeZoneParam: refined.timeZoneParam,
8620 };
8621 }
8622 return null;
8623 },
8624 fetch(arg, successCallback, errorCallback) {
8625 const { meta } = arg.eventSource;
8626 const requestParams = buildRequestParams(meta, arg.range, arg.context);
8627 requestJson(meta.method, meta.url, requestParams).then(([rawEvents, response]) => {
8628 successCallback({ rawEvents, response });
8629 }, errorCallback);
8630 },
8631 };
8632 const jsonFeedEventSourcePlugin = createPlugin({
8633 name: 'json-event-source',
8634 eventSourceRefiners: JSON_FEED_EVENT_SOURCE_REFINERS,
8635 eventSourceDefs: [eventSourceDef],
8636 });
8637 function buildRequestParams(meta, range, context) {
8638 let { dateEnv, options } = context;
8639 let startParam;
8640 let endParam;
8641 let timeZoneParam;
8642 let customRequestParams;
8643 let params = {};
8644 startParam = meta.startParam;
8645 if (startParam == null) {
8646 startParam = options.startParam;
8647 }
8648 endParam = meta.endParam;
8649 if (endParam == null) {
8650 endParam = options.endParam;
8651 }
8652 timeZoneParam = meta.timeZoneParam;
8653 if (timeZoneParam == null) {
8654 timeZoneParam = options.timeZoneParam;
8655 }
8656 // retrieve any outbound GET/POST data from the options
8657 if (typeof meta.extraParams === 'function') {
8658 // supplied as a function that returns a key/value object
8659 customRequestParams = meta.extraParams();
8660 }
8661 else {
8662 // probably supplied as a straight key/value object
8663 customRequestParams = meta.extraParams || {};
8664 }
8665 Object.assign(params, customRequestParams);
8666 params[startParam] = dateEnv.formatIso(range.start);
8667 params[endParam] = dateEnv.formatIso(range.end);
8668 if (dateEnv.timeZone !== 'local') {
8669 params[timeZoneParam] = dateEnv.timeZone;
8670 }
8671 return params;
8672 }
8673
8674 const SIMPLE_RECURRING_REFINERS = {
8675 daysOfWeek: identity,
8676 startTime: createDuration,
8677 endTime: createDuration,
8678 duration: createDuration,
8679 startRecur: identity,
8680 endRecur: identity,
8681 };
8682
8683 let recurring = {
8684 parse(refined, dateEnv) {
8685 if (refined.daysOfWeek || refined.startTime || refined.endTime || refined.startRecur || refined.endRecur) {
8686 let recurringData = {
8687 daysOfWeek: refined.daysOfWeek || null,
8688 startTime: refined.startTime || null,
8689 endTime: refined.endTime || null,
8690 startRecur: refined.startRecur ? dateEnv.createMarker(refined.startRecur) : null,
8691 endRecur: refined.endRecur ? dateEnv.createMarker(refined.endRecur) : null,
8692 dateEnv,
8693 };
8694 let duration;
8695 if (refined.duration) {
8696 duration = refined.duration;
8697 }
8698 if (!duration && refined.startTime && refined.endTime) {
8699 duration = subtractDurations(refined.endTime, refined.startTime);
8700 }
8701 return {
8702 allDayGuess: Boolean(!refined.startTime && !refined.endTime),
8703 duration,
8704 typeData: recurringData, // doesn't need endTime anymore but oh well
8705 };
8706 }
8707 return null;
8708 },
8709 expand(typeData, framingRange, dateEnv) {
8710 let clippedFramingRange = intersectRanges(framingRange, { start: typeData.startRecur, end: typeData.endRecur });
8711 if (clippedFramingRange) {
8712 return expandRanges(typeData.daysOfWeek, typeData.startTime, typeData.dateEnv, dateEnv, clippedFramingRange);
8713 }
8714 return [];
8715 },
8716 };
8717 const simpleRecurringEventsPlugin = createPlugin({
8718 name: 'simple-recurring-event',
8719 recurringTypes: [recurring],
8720 eventRefiners: SIMPLE_RECURRING_REFINERS,
8721 });
8722 function expandRanges(daysOfWeek, startTime, eventDateEnv, calendarDateEnv, framingRange) {
8723 let dowHash = daysOfWeek ? arrayToHash(daysOfWeek) : null;
8724 let dayMarker = startOfDay(framingRange.start);
8725 let endMarker = framingRange.end;
8726 let instanceStarts = [];
8727 while (dayMarker < endMarker) {
8728 let instanceStart;
8729 // if everyday, or this particular day-of-week
8730 if (!dowHash || dowHash[dayMarker.getUTCDay()]) {
8731 if (startTime) {
8732 instanceStart = calendarDateEnv.add(dayMarker, startTime);
8733 }
8734 else {
8735 instanceStart = dayMarker;
8736 }
8737 instanceStarts.push(calendarDateEnv.createMarker(eventDateEnv.toDate(instanceStart)));
8738 }
8739 dayMarker = addDays(dayMarker, 1);
8740 }
8741 return instanceStarts;
8742 }
8743
8744 const changeHandlerPlugin = createPlugin({
8745 name: 'change-handler',
8746 optionChangeHandlers: {
8747 events(events, context) {
8748 handleEventSources([events], context);
8749 },
8750 eventSources: handleEventSources,
8751 },
8752 });
8753 /*
8754 BUG: if `event` was supplied, all previously-given `eventSources` will be wiped out
8755 */
8756 function handleEventSources(inputs, context) {
8757 let unfoundSources = hashValuesToArray(context.getCurrentData().eventSources);
8758 if (unfoundSources.length === 1 &&
8759 inputs.length === 1 &&
8760 Array.isArray(unfoundSources[0]._raw) &&
8761 Array.isArray(inputs[0])) {
8762 context.dispatch({
8763 type: 'RESET_RAW_EVENTS',
8764 sourceId: unfoundSources[0].sourceId,
8765 rawEvents: inputs[0],
8766 });
8767 return;
8768 }
8769 let newInputs = [];
8770 for (let input of inputs) {
8771 let inputFound = false;
8772 for (let i = 0; i < unfoundSources.length; i += 1) {
8773 if (unfoundSources[i]._raw === input) {
8774 unfoundSources.splice(i, 1); // delete
8775 inputFound = true;
8776 break;
8777 }
8778 }
8779 if (!inputFound) {
8780 newInputs.push(input);
8781 }
8782 }
8783 for (let unfoundSource of unfoundSources) {
8784 context.dispatch({
8785 type: 'REMOVE_EVENT_SOURCE',
8786 sourceId: unfoundSource.sourceId,
8787 });
8788 }
8789 for (let newInput of newInputs) {
8790 context.calendarApi.addEventSource(newInput);
8791 }
8792 }
8793
8794 function handleDateProfile(dateProfile, context) {
8795 context.emitter.trigger('datesSet', Object.assign(Object.assign({}, buildRangeApiWithTimeZone(dateProfile.activeRange, context.dateEnv)), { view: context.viewApi }));
8796 }
8797
8798 function handleEventStore(eventStore, context) {
8799 let { emitter } = context;
8800 if (emitter.hasHandlers('eventsSet')) {
8801 emitter.trigger('eventsSet', buildEventApis(eventStore, context));
8802 }
8803 }
8804
8805 /*
8806 this array is exposed on the root namespace so that UMD plugins can add to it.
8807 see the rollup-bundles script.
8808 */
8809 const globalPlugins = [
8810 arrayEventSourcePlugin,
8811 funcEventSourcePlugin,
8812 jsonFeedEventSourcePlugin,
8813 simpleRecurringEventsPlugin,
8814 changeHandlerPlugin,
8815 createPlugin({
8816 name: 'misc',
8817 isLoadingFuncs: [
8818 (state) => computeEventSourcesLoading(state.eventSources),
8819 ],
8820 propSetHandlers: {
8821 dateProfile: handleDateProfile,
8822 eventStore: handleEventStore,
8823 },
8824 }),
8825 ];
8826
8827 class TaskRunner {
8828 constructor(runTaskOption, drainedOption) {
8829 this.runTaskOption = runTaskOption;
8830 this.drainedOption = drainedOption;
8831 this.queue = [];
8832 this.delayedRunner = new DelayedRunner(this.drain.bind(this));
8833 }
8834 request(task, delay) {
8835 this.queue.push(task);
8836 this.delayedRunner.request(delay);
8837 }
8838 pause(scope) {
8839 this.delayedRunner.pause(scope);
8840 }
8841 resume(scope, force) {
8842 this.delayedRunner.resume(scope, force);
8843 }
8844 drain() {
8845 let { queue } = this;
8846 while (queue.length) {
8847 let completedTasks = [];
8848 let task;
8849 while ((task = queue.shift())) {
8850 this.runTask(task);
8851 completedTasks.push(task);
8852 }
8853 this.drained(completedTasks);
8854 } // keep going, in case new tasks were added in the drained handler
8855 }
8856 runTask(task) {
8857 if (this.runTaskOption) {
8858 this.runTaskOption(task);
8859 }
8860 }
8861 drained(completedTasks) {
8862 if (this.drainedOption) {
8863 this.drainedOption(completedTasks);
8864 }
8865 }
8866 }
8867
8868 // Computes what the title at the top of the calendarApi should be for this view
8869 function buildTitle(dateProfile, viewOptions, dateEnv) {
8870 let range;
8871 // for views that span a large unit of time, show the proper interval, ignoring stray days before and after
8872 if (/^(year|month)$/.test(dateProfile.currentRangeUnit)) {
8873 range = dateProfile.currentRange;
8874 }
8875 else { // for day units or smaller, use the actual day range
8876 range = dateProfile.activeRange;
8877 }
8878 return dateEnv.formatRange(range.start, range.end, createFormatter(viewOptions.titleFormat || buildTitleFormat(dateProfile)), {
8879 isEndExclusive: dateProfile.isRangeAllDay,
8880 defaultSeparator: viewOptions.titleRangeSeparator,
8881 });
8882 }
8883 // Generates the format string that should be used to generate the title for the current date range.
8884 // Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`.
8885 function buildTitleFormat(dateProfile) {
8886 let { currentRangeUnit } = dateProfile;
8887 if (currentRangeUnit === 'year') {
8888 return { year: 'numeric' };
8889 }
8890 if (currentRangeUnit === 'month') {
8891 return { year: 'numeric', month: 'long' }; // like "September 2014"
8892 }
8893 let days = diffWholeDays(dateProfile.currentRange.start, dateProfile.currentRange.end);
8894 if (days !== null && days > 1) {
8895 // multi-day range. shorter, like "Sep 9 - 10 2014"
8896 return { year: 'numeric', month: 'short', day: 'numeric' };
8897 }
8898 // one day. longer, like "September 9 2014"
8899 return { year: 'numeric', month: 'long', day: 'numeric' };
8900 }
8901
8902 /*
8903 TODO: test switching timezones when NO timezone plugin
8904 */
8905 class CalendarNowManager {
8906 constructor() {
8907 this.resetListeners = new Set();
8908 }
8909 handleInput(dateEnv, // will change if timezone setup changed
8910 nowInput) {
8911 const oldDateEnv = this.dateEnv;
8912 if (dateEnv !== oldDateEnv) {
8913 if (typeof nowInput === 'function') {
8914 this.nowFn = nowInput;
8915 }
8916 else if (!oldDateEnv) { // first time?
8917 this.nowAnchorDate = dateEnv.toDate(nowInput
8918 ? dateEnv.createMarker(nowInput)
8919 : dateEnv.createNowMarker());
8920 this.nowAnchorQueried = Date.now();
8921 }
8922 this.dateEnv = dateEnv;
8923 // not first time? fire reset handlers
8924 if (oldDateEnv) {
8925 for (const resetListener of this.resetListeners.values()) {
8926 resetListener();
8927 }
8928 }
8929 }
8930 }
8931 getDateMarker() {
8932 return this.nowAnchorDate
8933 ? this.dateEnv.timestampToMarker(this.nowAnchorDate.valueOf() +
8934 (Date.now() - this.nowAnchorQueried))
8935 : this.dateEnv.createMarker(this.nowFn());
8936 }
8937 addResetListener(handler) {
8938 this.resetListeners.add(handler);
8939 }
8940 removeResetListener(handler) {
8941 this.resetListeners.delete(handler);
8942 }
8943 }
8944
8945 // in future refactor, do the redux-style function(state=initial) for initial-state
8946 // also, whatever is happening in constructor, have it happen in action queue too
8947 class CalendarDataManager {
8948 constructor(props) {
8949 this.computeCurrentViewData = memoize(this._computeCurrentViewData);
8950 this.organizeRawLocales = memoize(organizeRawLocales);
8951 this.buildLocale = memoize(buildLocale);
8952 this.buildPluginHooks = buildBuildPluginHooks();
8953 this.buildDateEnv = memoize(buildDateEnv$1);
8954 this.buildTheme = memoize(buildTheme);
8955 this.parseToolbars = memoize(parseToolbars);
8956 this.buildViewSpecs = memoize(buildViewSpecs);
8957 this.buildDateProfileGenerator = memoizeObjArg(buildDateProfileGenerator);
8958 this.buildViewApi = memoize(buildViewApi);
8959 this.buildViewUiProps = memoizeObjArg(buildViewUiProps);
8960 this.buildEventUiBySource = memoize(buildEventUiBySource, isPropsEqual);
8961 this.buildEventUiBases = memoize(buildEventUiBases);
8962 this.parseContextBusinessHours = memoizeObjArg(parseContextBusinessHours);
8963 this.buildTitle = memoize(buildTitle);
8964 this.nowManager = new CalendarNowManager();
8965 this.emitter = new Emitter();
8966 this.actionRunner = new TaskRunner(this._handleAction.bind(this), this.updateData.bind(this));
8967 this.currentCalendarOptionsInput = {};
8968 this.currentCalendarOptionsRefined = {};
8969 this.currentViewOptionsInput = {};
8970 this.currentViewOptionsRefined = {};
8971 this.currentCalendarOptionsRefiners = {};
8972 this.optionsForRefining = [];
8973 this.optionsForHandling = [];
8974 this.getCurrentData = () => this.data;
8975 this.dispatch = (action) => {
8976 this.actionRunner.request(action); // protects against recursive calls to _handleAction
8977 };
8978 this.props = props;
8979 this.actionRunner.pause();
8980 this.nowManager = new CalendarNowManager();
8981 let dynamicOptionOverrides = {};
8982 let optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi);
8983 let currentViewType = optionsData.calendarOptions.initialView || optionsData.pluginHooks.initialView;
8984 let currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides);
8985 // wire things up
8986 // TODO: not DRY
8987 props.calendarApi.currentDataManager = this;
8988 this.emitter.setThisContext(props.calendarApi);
8989 this.emitter.setOptions(currentViewData.options);
8990 let calendarContext = {
8991 nowManager: this.nowManager,
8992 dateEnv: optionsData.dateEnv,
8993 options: optionsData.calendarOptions,
8994 pluginHooks: optionsData.pluginHooks,
8995 calendarApi: props.calendarApi,
8996 dispatch: this.dispatch,
8997 emitter: this.emitter,
8998 getCurrentData: this.getCurrentData,
8999 };
9000 let currentDate = getInitialDate(optionsData.calendarOptions, optionsData.dateEnv, this.nowManager);
9001 let dateProfile = currentViewData.dateProfileGenerator.build(currentDate);
9002 if (!rangeContainsMarker(dateProfile.activeRange, currentDate)) {
9003 currentDate = dateProfile.currentRange.start;
9004 }
9005 // needs to be after setThisContext
9006 for (let callback of optionsData.pluginHooks.contextInit) {
9007 callback(calendarContext);
9008 }
9009 // NOT DRY
9010 let eventSources = initEventSources(optionsData.calendarOptions, dateProfile, calendarContext);
9011 let initialState = {
9012 dynamicOptionOverrides,
9013 currentViewType,
9014 currentDate,
9015 dateProfile,
9016 businessHours: this.parseContextBusinessHours(calendarContext),
9017 eventSources,
9018 eventUiBases: {},
9019 eventStore: createEmptyEventStore(),
9020 renderableEventStore: createEmptyEventStore(),
9021 dateSelection: null,
9022 eventSelection: '',
9023 eventDrag: null,
9024 eventResize: null,
9025 selectionConfig: this.buildViewUiProps(calendarContext).selectionConfig,
9026 };
9027 let contextAndState = Object.assign(Object.assign({}, calendarContext), initialState);
9028 for (let reducer of optionsData.pluginHooks.reducers) {
9029 Object.assign(initialState, reducer(null, null, contextAndState));
9030 }
9031 if (computeIsLoading(initialState, calendarContext)) {
9032 this.emitter.trigger('loading', true); // NOT DRY
9033 }
9034 this.state = initialState;
9035 this.updateData();
9036 this.actionRunner.resume();
9037 }
9038 resetOptions(optionOverrides, changedOptionNames) {
9039 let { props } = this;
9040 if (changedOptionNames === undefined) {
9041 props.optionOverrides = optionOverrides;
9042 }
9043 else {
9044 props.optionOverrides = Object.assign(Object.assign({}, (props.optionOverrides || {})), optionOverrides);
9045 this.optionsForRefining.push(...changedOptionNames);
9046 }
9047 if (changedOptionNames === undefined || changedOptionNames.length) {
9048 this.actionRunner.request({
9049 type: 'NOTHING',
9050 });
9051 }
9052 }
9053 _handleAction(action) {
9054 let { props, state, emitter } = this;
9055 let dynamicOptionOverrides = reduceDynamicOptionOverrides(state.dynamicOptionOverrides, action);
9056 let optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi);
9057 let currentViewType = reduceViewType(state.currentViewType, action);
9058 let currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides);
9059 // wire things up
9060 // TODO: not DRY
9061 props.calendarApi.currentDataManager = this;
9062 emitter.setThisContext(props.calendarApi);
9063 emitter.setOptions(currentViewData.options);
9064 let calendarContext = {
9065 nowManager: this.nowManager,
9066 dateEnv: optionsData.dateEnv,
9067 options: optionsData.calendarOptions,
9068 pluginHooks: optionsData.pluginHooks,
9069 calendarApi: props.calendarApi,
9070 dispatch: this.dispatch,
9071 emitter,
9072 getCurrentData: this.getCurrentData,
9073 };
9074 let { currentDate, dateProfile } = state;
9075 if (this.data && this.data.dateProfileGenerator !== currentViewData.dateProfileGenerator) { // hack
9076 dateProfile = currentViewData.dateProfileGenerator.build(currentDate);
9077 }
9078 currentDate = reduceCurrentDate(currentDate, action);
9079 dateProfile = reduceDateProfile(dateProfile, action, currentDate, currentViewData.dateProfileGenerator);
9080 if (action.type === 'PREV' || // TODO: move this logic into DateProfileGenerator
9081 action.type === 'NEXT' || // "
9082 !rangeContainsMarker(dateProfile.currentRange, currentDate)) {
9083 currentDate = dateProfile.currentRange.start;
9084 }
9085 let eventSources = reduceEventSources(state.eventSources, action, dateProfile, calendarContext);
9086 let eventStore = reduceEventStore(state.eventStore, action, eventSources, dateProfile, calendarContext);
9087 let isEventsLoading = computeEventSourcesLoading(eventSources); // BAD. also called in this func in computeIsLoading
9088 let renderableEventStore = (isEventsLoading && !currentViewData.options.progressiveEventRendering) ?
9089 (state.renderableEventStore || eventStore) : // try from previous state
9090 eventStore;
9091 let { eventUiSingleBase, selectionConfig } = this.buildViewUiProps(calendarContext); // will memoize obj
9092 let eventUiBySource = this.buildEventUiBySource(eventSources);
9093 let eventUiBases = this.buildEventUiBases(renderableEventStore.defs, eventUiSingleBase, eventUiBySource);
9094 let newState = {
9095 dynamicOptionOverrides,
9096 currentViewType,
9097 currentDate,
9098 dateProfile,
9099 eventSources,
9100 eventStore,
9101 renderableEventStore,
9102 selectionConfig,
9103 eventUiBases,
9104 businessHours: this.parseContextBusinessHours(calendarContext),
9105 dateSelection: reduceDateSelection(state.dateSelection, action),
9106 eventSelection: reduceSelectedEvent(state.eventSelection, action),
9107 eventDrag: reduceEventDrag(state.eventDrag, action),
9108 eventResize: reduceEventResize(state.eventResize, action),
9109 };
9110 let contextAndState = Object.assign(Object.assign({}, calendarContext), newState);
9111 for (let reducer of optionsData.pluginHooks.reducers) {
9112 Object.assign(newState, reducer(state, action, contextAndState)); // give the OLD state, for old value
9113 }
9114 let wasLoading = computeIsLoading(state, calendarContext);
9115 let isLoading = computeIsLoading(newState, calendarContext);
9116 // TODO: use propSetHandlers in plugin system
9117 if (!wasLoading && isLoading) {
9118 emitter.trigger('loading', true);
9119 }
9120 else if (wasLoading && !isLoading) {
9121 emitter.trigger('loading', false);
9122 }
9123 this.state = newState;
9124 if (props.onAction) {
9125 props.onAction(action);
9126 }
9127 }
9128 updateData() {
9129 let { props, state } = this;
9130 let oldData = this.data;
9131 let optionsData = this.computeOptionsData(props.optionOverrides, state.dynamicOptionOverrides, props.calendarApi);
9132 let currentViewData = this.computeCurrentViewData(state.currentViewType, optionsData, props.optionOverrides, state.dynamicOptionOverrides);
9133 let data = this.data = Object.assign(Object.assign(Object.assign({ nowManager: this.nowManager, viewTitle: this.buildTitle(state.dateProfile, currentViewData.options, optionsData.dateEnv), calendarApi: props.calendarApi, dispatch: this.dispatch, emitter: this.emitter, getCurrentData: this.getCurrentData }, optionsData), currentViewData), state);
9134 let changeHandlers = optionsData.pluginHooks.optionChangeHandlers;
9135 let oldCalendarOptions = oldData && oldData.calendarOptions;
9136 let newCalendarOptions = optionsData.calendarOptions;
9137 if (oldCalendarOptions && oldCalendarOptions !== newCalendarOptions) {
9138 if (oldCalendarOptions.timeZone !== newCalendarOptions.timeZone) {
9139 // hack
9140 state.eventSources = data.eventSources = reduceEventSourcesNewTimeZone(data.eventSources, state.dateProfile, data);
9141 state.eventStore = data.eventStore = rezoneEventStoreDates(data.eventStore, oldData.dateEnv, data.dateEnv);
9142 state.renderableEventStore = data.renderableEventStore = rezoneEventStoreDates(data.renderableEventStore, oldData.dateEnv, data.dateEnv);
9143 }
9144 for (let optionName in changeHandlers) {
9145 if (this.optionsForHandling.indexOf(optionName) !== -1 ||
9146 oldCalendarOptions[optionName] !== newCalendarOptions[optionName]) {
9147 changeHandlers[optionName](newCalendarOptions[optionName], data);
9148 }
9149 }
9150 }
9151 this.optionsForHandling = [];
9152 if (props.onData) {
9153 props.onData(data);
9154 }
9155 }
9156 computeOptionsData(optionOverrides, dynamicOptionOverrides, calendarApi) {
9157 // TODO: blacklist options that are handled by optionChangeHandlers
9158 if (!this.optionsForRefining.length &&
9159 optionOverrides === this.stableOptionOverrides &&
9160 dynamicOptionOverrides === this.stableDynamicOptionOverrides) {
9161 return this.stableCalendarOptionsData;
9162 }
9163 let { refinedOptions, pluginHooks, localeDefaults, availableLocaleData, extra, } = this.processRawCalendarOptions(optionOverrides, dynamicOptionOverrides);
9164 warnUnknownOptions(extra);
9165 let dateEnv = this.buildDateEnv(refinedOptions.timeZone, refinedOptions.locale, refinedOptions.weekNumberCalculation, refinedOptions.firstDay, refinedOptions.weekText, pluginHooks, availableLocaleData, refinedOptions.defaultRangeSeparator);
9166 let viewSpecs = this.buildViewSpecs(pluginHooks.views, this.stableOptionOverrides, this.stableDynamicOptionOverrides, localeDefaults);
9167 let theme = this.buildTheme(refinedOptions, pluginHooks);
9168 let toolbarConfig = this.parseToolbars(refinedOptions, this.stableOptionOverrides, theme, viewSpecs, calendarApi);
9169 return this.stableCalendarOptionsData = {
9170 calendarOptions: refinedOptions,
9171 pluginHooks,
9172 dateEnv,
9173 viewSpecs,
9174 theme,
9175 toolbarConfig,
9176 localeDefaults,
9177 availableRawLocales: availableLocaleData.map,
9178 };
9179 }
9180 // always called from behind a memoizer
9181 processRawCalendarOptions(optionOverrides, dynamicOptionOverrides) {
9182 let { locales, locale } = mergeRawOptions([
9183 BASE_OPTION_DEFAULTS,
9184 optionOverrides,
9185 dynamicOptionOverrides,
9186 ]);
9187 let availableLocaleData = this.organizeRawLocales(locales);
9188 let availableRawLocales = availableLocaleData.map;
9189 let localeDefaults = this.buildLocale(locale || availableLocaleData.defaultCode, availableRawLocales).options;
9190 let pluginHooks = this.buildPluginHooks(optionOverrides.plugins || [], globalPlugins);
9191 let refiners = this.currentCalendarOptionsRefiners = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, BASE_OPTION_REFINERS), CALENDAR_LISTENER_REFINERS), CALENDAR_OPTION_REFINERS), pluginHooks.listenerRefiners), pluginHooks.optionRefiners);
9192 let extra = {};
9193 let raw = mergeRawOptions([
9194 BASE_OPTION_DEFAULTS,
9195 localeDefaults,
9196 optionOverrides,
9197 dynamicOptionOverrides,
9198 ]);
9199 let refined = {};
9200 let currentRaw = this.currentCalendarOptionsInput;
9201 let currentRefined = this.currentCalendarOptionsRefined;
9202 let anyChanges = false;
9203 for (let optionName in raw) {
9204 if (this.optionsForRefining.indexOf(optionName) === -1 && (raw[optionName] === currentRaw[optionName] || (COMPLEX_OPTION_COMPARATORS[optionName] &&
9205 (optionName in currentRaw) &&
9206 COMPLEX_OPTION_COMPARATORS[optionName](currentRaw[optionName], raw[optionName])))) {
9207 refined[optionName] = currentRefined[optionName];
9208 }
9209 else if (refiners[optionName]) {
9210 refined[optionName] = refiners[optionName](raw[optionName]);
9211 anyChanges = true;
9212 }
9213 else {
9214 extra[optionName] = currentRaw[optionName];
9215 }
9216 }
9217 if (anyChanges) {
9218 this.currentCalendarOptionsInput = raw;
9219 this.currentCalendarOptionsRefined = refined;
9220 this.stableOptionOverrides = optionOverrides;
9221 this.stableDynamicOptionOverrides = dynamicOptionOverrides;
9222 }
9223 this.optionsForHandling.push(...this.optionsForRefining);
9224 this.optionsForRefining = [];
9225 return {
9226 rawOptions: this.currentCalendarOptionsInput,
9227 refinedOptions: this.currentCalendarOptionsRefined,
9228 pluginHooks,
9229 availableLocaleData,
9230 localeDefaults,
9231 extra,
9232 };
9233 }
9234 _computeCurrentViewData(viewType, optionsData, optionOverrides, dynamicOptionOverrides) {
9235 let viewSpec = optionsData.viewSpecs[viewType];
9236 if (!viewSpec) {
9237 throw new Error(`viewType "${viewType}" is not available. Please make sure you've loaded all neccessary plugins`);
9238 }
9239 let { refinedOptions, extra } = this.processRawViewOptions(viewSpec, optionsData.pluginHooks, optionsData.localeDefaults, optionOverrides, dynamicOptionOverrides);
9240 warnUnknownOptions(extra);
9241 this.nowManager.handleInput(optionsData.dateEnv, refinedOptions.now);
9242 let dateProfileGenerator = this.buildDateProfileGenerator({
9243 dateProfileGeneratorClass: viewSpec.optionDefaults.dateProfileGeneratorClass,
9244 nowManager: this.nowManager,
9245 duration: viewSpec.duration,
9246 durationUnit: viewSpec.durationUnit,
9247 usesMinMaxTime: viewSpec.optionDefaults.usesMinMaxTime,
9248 dateEnv: optionsData.dateEnv,
9249 calendarApi: this.props.calendarApi,
9250 slotMinTime: refinedOptions.slotMinTime,
9251 slotMaxTime: refinedOptions.slotMaxTime,
9252 showNonCurrentDates: refinedOptions.showNonCurrentDates,
9253 dayCount: refinedOptions.dayCount,
9254 dateAlignment: refinedOptions.dateAlignment,
9255 dateIncrement: refinedOptions.dateIncrement,
9256 hiddenDays: refinedOptions.hiddenDays,
9257 weekends: refinedOptions.weekends,
9258 validRangeInput: refinedOptions.validRange,
9259 visibleRangeInput: refinedOptions.visibleRange,
9260 fixedWeekCount: refinedOptions.fixedWeekCount,
9261 });
9262 let viewApi = this.buildViewApi(viewType, this.getCurrentData, optionsData.dateEnv);
9263 return { viewSpec, options: refinedOptions, dateProfileGenerator, viewApi };
9264 }
9265 processRawViewOptions(viewSpec, pluginHooks, localeDefaults, optionOverrides, dynamicOptionOverrides) {
9266 let raw = mergeRawOptions([
9267 BASE_OPTION_DEFAULTS,
9268 viewSpec.optionDefaults,
9269 localeDefaults,
9270 optionOverrides,
9271 viewSpec.optionOverrides,
9272 dynamicOptionOverrides,
9273 ]);
9274 let refiners = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, BASE_OPTION_REFINERS), CALENDAR_LISTENER_REFINERS), CALENDAR_OPTION_REFINERS), VIEW_OPTION_REFINERS), pluginHooks.listenerRefiners), pluginHooks.optionRefiners);
9275 let refined = {};
9276 let currentRaw = this.currentViewOptionsInput;
9277 let currentRefined = this.currentViewOptionsRefined;
9278 let anyChanges = false;
9279 let extra = {};
9280 for (let optionName in raw) {
9281 if (raw[optionName] === currentRaw[optionName] ||
9282 (COMPLEX_OPTION_COMPARATORS[optionName] &&
9283 COMPLEX_OPTION_COMPARATORS[optionName](raw[optionName], currentRaw[optionName]))) {
9284 refined[optionName] = currentRefined[optionName];
9285 }
9286 else {
9287 if (raw[optionName] === this.currentCalendarOptionsInput[optionName] ||
9288 (COMPLEX_OPTION_COMPARATORS[optionName] &&
9289 COMPLEX_OPTION_COMPARATORS[optionName](raw[optionName], this.currentCalendarOptionsInput[optionName]))) {
9290 if (optionName in this.currentCalendarOptionsRefined) { // might be an "extra" prop
9291 refined[optionName] = this.currentCalendarOptionsRefined[optionName];
9292 }
9293 }
9294 else if (refiners[optionName]) {
9295 refined[optionName] = refiners[optionName](raw[optionName]);
9296 }
9297 else {
9298 extra[optionName] = raw[optionName];
9299 }
9300 anyChanges = true;
9301 }
9302 }
9303 if (anyChanges) {
9304 this.currentViewOptionsInput = raw;
9305 this.currentViewOptionsRefined = refined;
9306 }
9307 return {
9308 rawOptions: this.currentViewOptionsInput,
9309 refinedOptions: this.currentViewOptionsRefined,
9310 extra,
9311 };
9312 }
9313 }
9314 function buildDateEnv$1(timeZone, explicitLocale, weekNumberCalculation, firstDay, weekText, pluginHooks, availableLocaleData, defaultSeparator) {
9315 let locale = buildLocale(explicitLocale || availableLocaleData.defaultCode, availableLocaleData.map);
9316 return new DateEnv({
9317 calendarSystem: 'gregory',
9318 timeZone,
9319 namedTimeZoneImpl: pluginHooks.namedTimeZonedImpl,
9320 locale,
9321 weekNumberCalculation,
9322 firstDay,
9323 weekText,
9324 cmdFormatter: pluginHooks.cmdFormatter,
9325 defaultSeparator,
9326 });
9327 }
9328 function buildTheme(options, pluginHooks) {
9329 let ThemeClass = pluginHooks.themeClasses[options.themeSystem] || StandardTheme;
9330 return new ThemeClass(options);
9331 }
9332 function buildDateProfileGenerator(props) {
9333 let DateProfileGeneratorClass = props.dateProfileGeneratorClass || DateProfileGenerator;
9334 return new DateProfileGeneratorClass(props);
9335 }
9336 function buildViewApi(type, getCurrentData, dateEnv) {
9337 return new ViewImpl(type, getCurrentData, dateEnv);
9338 }
9339 function buildEventUiBySource(eventSources) {
9340 return mapHash(eventSources, (eventSource) => eventSource.ui);
9341 }
9342 function buildEventUiBases(eventDefs, eventUiSingleBase, eventUiBySource) {
9343 let eventUiBases = { '': eventUiSingleBase };
9344 for (let defId in eventDefs) {
9345 let def = eventDefs[defId];
9346 if (def.sourceId && eventUiBySource[def.sourceId]) {
9347 eventUiBases[defId] = eventUiBySource[def.sourceId];
9348 }
9349 }
9350 return eventUiBases;
9351 }
9352 function buildViewUiProps(calendarContext) {
9353 let { options } = calendarContext;
9354 return {
9355 eventUiSingleBase: createEventUi({
9356 display: options.eventDisplay,
9357 editable: options.editable,
9358 startEditable: options.eventStartEditable,
9359 durationEditable: options.eventDurationEditable,
9360 constraint: options.eventConstraint,
9361 overlap: typeof options.eventOverlap === 'boolean' ? options.eventOverlap : undefined,
9362 allow: options.eventAllow,
9363 backgroundColor: options.eventBackgroundColor,
9364 borderColor: options.eventBorderColor,
9365 textColor: options.eventTextColor,
9366 color: options.eventColor,
9367 // classNames: options.eventClassNames // render hook will handle this
9368 }, calendarContext),
9369 selectionConfig: createEventUi({
9370 constraint: options.selectConstraint,
9371 overlap: typeof options.selectOverlap === 'boolean' ? options.selectOverlap : undefined,
9372 allow: options.selectAllow,
9373 }, calendarContext),
9374 };
9375 }
9376 function computeIsLoading(state, context) {
9377 for (let isLoadingFunc of context.pluginHooks.isLoadingFuncs) {
9378 if (isLoadingFunc(state)) {
9379 return true;
9380 }
9381 }
9382 return false;
9383 }
9384 function parseContextBusinessHours(calendarContext) {
9385 return parseBusinessHours(calendarContext.options.businessHours, calendarContext);
9386 }
9387 function warnUnknownOptions(options, viewName) {
9388 for (let optionName in options) {
9389 console.warn(`Unknown option '${optionName}'` +
9390 (viewName ? ` for view '${viewName}'` : ''));
9391 }
9392 }
9393
9394 class ToolbarSection extends BaseComponent {
9395 render() {
9396 let children = this.props.widgetGroups.map((widgetGroup) => this.renderWidgetGroup(widgetGroup));
9397 return y('div', { className: 'fc-toolbar-chunk' }, ...children);
9398 }
9399 renderWidgetGroup(widgetGroup) {
9400 let { props } = this;
9401 let { theme } = this.context;
9402 let children = [];
9403 let isOnlyButtons = true;
9404 for (let widget of widgetGroup) {
9405 let { buttonName, buttonClick, buttonText, buttonIcon, buttonHint } = widget;
9406 if (buttonName === 'title') {
9407 isOnlyButtons = false;
9408 children.push(y("h2", { className: "fc-toolbar-title", id: props.titleId }, props.title));
9409 }
9410 else {
9411 let isPressed = buttonName === props.activeButton;
9412 let isDisabled = (!props.isTodayEnabled && buttonName === 'today') ||
9413 (!props.isPrevEnabled && buttonName === 'prev') ||
9414 (!props.isNextEnabled && buttonName === 'next');
9415 let buttonClasses = [`fc-${buttonName}-button`, theme.getClass('button')];
9416 if (isPressed) {
9417 buttonClasses.push(theme.getClass('buttonActive'));
9418 }
9419 children.push(y("button", { type: "button", title: typeof buttonHint === 'function' ? buttonHint(props.navUnit) : buttonHint, disabled: isDisabled, "aria-pressed": isPressed, className: buttonClasses.join(' '), onClick: buttonClick }, buttonText || (buttonIcon ? y("span", { className: buttonIcon, role: "img" }) : '')));
9420 }
9421 }
9422 if (children.length > 1) {
9423 let groupClassName = (isOnlyButtons && theme.getClass('buttonGroup')) || '';
9424 return y('div', { className: groupClassName }, ...children);
9425 }
9426 return children[0];
9427 }
9428 }
9429
9430 class Toolbar extends BaseComponent {
9431 render() {
9432 let { model, extraClassName } = this.props;
9433 let forceLtr = false;
9434 let startContent;
9435 let endContent;
9436 let sectionWidgets = model.sectionWidgets;
9437 let centerContent = sectionWidgets.center;
9438 if (sectionWidgets.left) {
9439 forceLtr = true;
9440 startContent = sectionWidgets.left;
9441 }
9442 else {
9443 startContent = sectionWidgets.start;
9444 }
9445 if (sectionWidgets.right) {
9446 forceLtr = true;
9447 endContent = sectionWidgets.right;
9448 }
9449 else {
9450 endContent = sectionWidgets.end;
9451 }
9452 let classNames = [
9453 extraClassName || '',
9454 'fc-toolbar',
9455 forceLtr ? 'fc-toolbar-ltr' : '',
9456 ];
9457 return (y("div", { className: classNames.join(' ') },
9458 this.renderSection('start', startContent || []),
9459 this.renderSection('center', centerContent || []),
9460 this.renderSection('end', endContent || [])));
9461 }
9462 renderSection(key, widgetGroups) {
9463 let { props } = this;
9464 return (y(ToolbarSection, { key: key, widgetGroups: widgetGroups, title: props.title, navUnit: props.navUnit, activeButton: props.activeButton, isTodayEnabled: props.isTodayEnabled, isPrevEnabled: props.isPrevEnabled, isNextEnabled: props.isNextEnabled, titleId: props.titleId }));
9465 }
9466 }
9467
9468 class ViewHarness extends BaseComponent {
9469 constructor() {
9470 super(...arguments);
9471 this.state = {
9472 availableWidth: null,
9473 };
9474 this.handleEl = (el) => {
9475 this.el = el;
9476 setRef(this.props.elRef, el);
9477 this.updateAvailableWidth();
9478 };
9479 this.handleResize = () => {
9480 this.updateAvailableWidth();
9481 };
9482 }
9483 render() {
9484 let { props, state } = this;
9485 let { aspectRatio } = props;
9486 let classNames = [
9487 'fc-view-harness',
9488 (aspectRatio || props.liquid || props.height)
9489 ? 'fc-view-harness-active' // harness controls the height
9490 : 'fc-view-harness-passive', // let the view do the height
9491 ];
9492 let height = '';
9493 let paddingBottom = '';
9494 if (aspectRatio) {
9495 if (state.availableWidth !== null) {
9496 height = state.availableWidth / aspectRatio;
9497 }
9498 else {
9499 // while waiting to know availableWidth, we can't set height to *zero*
9500 // because will cause lots of unnecessary scrollbars within scrollgrid.
9501 // BETTER: don't start rendering ANYTHING yet until we know container width
9502 // NOTE: why not always use paddingBottom? Causes height oscillation (issue 5606)
9503 paddingBottom = `${(1 / aspectRatio) * 100}%`;
9504 }
9505 }
9506 else {
9507 height = props.height || '';
9508 }
9509 return (y("div", { "aria-labelledby": props.labeledById, ref: this.handleEl, className: classNames.join(' '), style: { height, paddingBottom } }, props.children));
9510 }
9511 componentDidMount() {
9512 this.context.addResizeHandler(this.handleResize);
9513 }
9514 componentWillUnmount() {
9515 this.context.removeResizeHandler(this.handleResize);
9516 }
9517 updateAvailableWidth() {
9518 if (this.el && // needed. but why?
9519 this.props.aspectRatio // aspectRatio is the only height setting that needs availableWidth
9520 ) {
9521 this.setState({ availableWidth: this.el.offsetWidth });
9522 }
9523 }
9524 }
9525
9526 /*
9527 Detects when the user clicks on an event within a DateComponent
9528 */
9529 class EventClicking extends Interaction {
9530 constructor(settings) {
9531 super(settings);
9532 this.handleSegClick = (ev, segEl) => {
9533 let { component } = this;
9534 let { context } = component;
9535 let seg = getElSeg(segEl);
9536 if (seg && // might be the <div> surrounding the more link
9537 component.isValidSegDownEl(ev.target)) {
9538 // our way to simulate a link click for elements that can't be <a> tags
9539 // grab before trigger fired in case trigger trashes DOM thru rerendering
9540 let hasUrlContainer = elementClosest(ev.target, '.fc-event-forced-url');
9541 let url = hasUrlContainer ? hasUrlContainer.querySelector('a[href]').href : '';
9542 context.emitter.trigger('eventClick', {
9543 el: segEl,
9544 event: new EventImpl(component.context, seg.eventRange.def, seg.eventRange.instance),
9545 jsEvent: ev,
9546 view: context.viewApi,
9547 });
9548 if (url && !ev.defaultPrevented) {
9549 window.location.href = url;
9550 }
9551 }
9552 };
9553 this.destroy = listenBySelector(settings.el, 'click', '.fc-event', // on both fg and bg events
9554 this.handleSegClick);
9555 }
9556 }
9557
9558 /*
9559 Triggers events and adds/removes core classNames when the user's pointer
9560 enters/leaves event-elements of a component.
9561 */
9562 class EventHovering extends Interaction {
9563 constructor(settings) {
9564 super(settings);
9565 // for simulating an eventMouseLeave when the event el is destroyed while mouse is over it
9566 this.handleEventElRemove = (el) => {
9567 if (el === this.currentSegEl) {
9568 this.handleSegLeave(null, this.currentSegEl);
9569 }
9570 };
9571 this.handleSegEnter = (ev, segEl) => {
9572 if (getElSeg(segEl)) { // TODO: better way to make sure not hovering over more+ link or its wrapper
9573 this.currentSegEl = segEl;
9574 this.triggerEvent('eventMouseEnter', ev, segEl);
9575 }
9576 };
9577 this.handleSegLeave = (ev, segEl) => {
9578 if (this.currentSegEl) {
9579 this.currentSegEl = null;
9580 this.triggerEvent('eventMouseLeave', ev, segEl);
9581 }
9582 };
9583 this.removeHoverListeners = listenToHoverBySelector(settings.el, '.fc-event', // on both fg and bg events
9584 this.handleSegEnter, this.handleSegLeave);
9585 }
9586 destroy() {
9587 this.removeHoverListeners();
9588 }
9589 triggerEvent(publicEvName, ev, segEl) {
9590 let { component } = this;
9591 let { context } = component;
9592 let seg = getElSeg(segEl);
9593 if (!ev || component.isValidSegDownEl(ev.target)) {
9594 context.emitter.trigger(publicEvName, {
9595 el: segEl,
9596 event: new EventImpl(context, seg.eventRange.def, seg.eventRange.instance),
9597 jsEvent: ev,
9598 view: context.viewApi,
9599 });
9600 }
9601 }
9602 }
9603
9604 class CalendarContent extends PureComponent {
9605 constructor() {
9606 super(...arguments);
9607 this.buildViewContext = memoize(buildViewContext);
9608 this.buildViewPropTransformers = memoize(buildViewPropTransformers);
9609 this.buildToolbarProps = memoize(buildToolbarProps);
9610 this.headerRef = d();
9611 this.footerRef = d();
9612 this.interactionsStore = {};
9613 // eslint-disable-next-line
9614 this.state = {
9615 viewLabelId: getUniqueDomId(),
9616 };
9617 // Component Registration
9618 // -----------------------------------------------------------------------------------------------------------------
9619 this.registerInteractiveComponent = (component, settingsInput) => {
9620 let settings = parseInteractionSettings(component, settingsInput);
9621 let DEFAULT_INTERACTIONS = [
9622 EventClicking,
9623 EventHovering,
9624 ];
9625 let interactionClasses = DEFAULT_INTERACTIONS.concat(this.props.pluginHooks.componentInteractions);
9626 let interactions = interactionClasses.map((TheInteractionClass) => new TheInteractionClass(settings));
9627 this.interactionsStore[component.uid] = interactions;
9628 interactionSettingsStore[component.uid] = settings;
9629 };
9630 this.unregisterInteractiveComponent = (component) => {
9631 let listeners = this.interactionsStore[component.uid];
9632 if (listeners) {
9633 for (let listener of listeners) {
9634 listener.destroy();
9635 }
9636 delete this.interactionsStore[component.uid];
9637 }
9638 delete interactionSettingsStore[component.uid];
9639 };
9640 // Resizing
9641 // -----------------------------------------------------------------------------------------------------------------
9642 this.resizeRunner = new DelayedRunner(() => {
9643 this.props.emitter.trigger('_resize', true); // should window resizes be considered "forced" ?
9644 this.props.emitter.trigger('windowResize', { view: this.props.viewApi });
9645 });
9646 this.handleWindowResize = (ev) => {
9647 let { options } = this.props;
9648 if (options.handleWindowResize &&
9649 ev.target === window // avoid jqui events
9650 ) {
9651 this.resizeRunner.request(options.windowResizeDelay);
9652 }
9653 };
9654 }
9655 /*
9656 renders INSIDE of an outer div
9657 */
9658 render() {
9659 let { props } = this;
9660 let { toolbarConfig, options } = props;
9661 let viewVGrow = false;
9662 let viewHeight = '';
9663 let viewAspectRatio;
9664 if (props.isHeightAuto || props.forPrint) {
9665 viewHeight = '';
9666 }
9667 else if (options.height != null) {
9668 viewVGrow = true;
9669 }
9670 else if (options.contentHeight != null) {
9671 viewHeight = options.contentHeight;
9672 }
9673 else {
9674 viewAspectRatio = Math.max(options.aspectRatio, 0.5); // prevent from getting too tall
9675 }
9676 let viewContext = this.buildViewContext(props.viewSpec, props.viewApi, props.options, props.dateProfileGenerator, props.dateEnv, props.nowManager, props.theme, props.pluginHooks, props.dispatch, props.getCurrentData, props.emitter, props.calendarApi, this.registerInteractiveComponent, this.unregisterInteractiveComponent);
9677 let viewLabelId = (toolbarConfig.header && toolbarConfig.header.hasTitle)
9678 ? this.state.viewLabelId
9679 : undefined;
9680 return (y(ViewContextType.Provider, { value: viewContext },
9681 y(NowTimer, { unit: "day" }, (nowDate) => {
9682 let toolbarProps = this.buildToolbarProps(props.viewSpec, props.dateProfile, props.dateProfileGenerator, props.currentDate, nowDate, props.viewTitle);
9683 return (y(_, null,
9684 toolbarConfig.header && (y(Toolbar, Object.assign({ ref: this.headerRef, extraClassName: "fc-header-toolbar", model: toolbarConfig.header, titleId: viewLabelId }, toolbarProps))),
9685 y(ViewHarness, { liquid: viewVGrow, height: viewHeight, aspectRatio: viewAspectRatio, labeledById: viewLabelId },
9686 this.renderView(props),
9687 this.buildAppendContent()),
9688 toolbarConfig.footer && (y(Toolbar, Object.assign({ ref: this.footerRef, extraClassName: "fc-footer-toolbar", model: toolbarConfig.footer, titleId: "" }, toolbarProps)))));
9689 })));
9690 }
9691 componentDidMount() {
9692 let { props } = this;
9693 this.calendarInteractions = props.pluginHooks.calendarInteractions
9694 .map((CalendarInteractionClass) => new CalendarInteractionClass(props));
9695 window.addEventListener('resize', this.handleWindowResize);
9696 let { propSetHandlers } = props.pluginHooks;
9697 for (let propName in propSetHandlers) {
9698 propSetHandlers[propName](props[propName], props);
9699 }
9700 }
9701 componentDidUpdate(prevProps) {
9702 let { props } = this;
9703 let { propSetHandlers } = props.pluginHooks;
9704 for (let propName in propSetHandlers) {
9705 if (props[propName] !== prevProps[propName]) {
9706 propSetHandlers[propName](props[propName], props);
9707 }
9708 }
9709 }
9710 componentWillUnmount() {
9711 window.removeEventListener('resize', this.handleWindowResize);
9712 this.resizeRunner.clear();
9713 for (let interaction of this.calendarInteractions) {
9714 interaction.destroy();
9715 }
9716 this.props.emitter.trigger('_unmount');
9717 }
9718 buildAppendContent() {
9719 let { props } = this;
9720 let children = props.pluginHooks.viewContainerAppends.map((buildAppendContent) => buildAppendContent(props));
9721 return y(_, {}, ...children);
9722 }
9723 renderView(props) {
9724 let { pluginHooks } = props;
9725 let { viewSpec } = props;
9726 let viewProps = {
9727 dateProfile: props.dateProfile,
9728 businessHours: props.businessHours,
9729 eventStore: props.renderableEventStore,
9730 eventUiBases: props.eventUiBases,
9731 dateSelection: props.dateSelection,
9732 eventSelection: props.eventSelection,
9733 eventDrag: props.eventDrag,
9734 eventResize: props.eventResize,
9735 isHeightAuto: props.isHeightAuto,
9736 forPrint: props.forPrint,
9737 };
9738 let transformers = this.buildViewPropTransformers(pluginHooks.viewPropsTransformers);
9739 for (let transformer of transformers) {
9740 Object.assign(viewProps, transformer.transform(viewProps, props));
9741 }
9742 let ViewComponent = viewSpec.component;
9743 return (y(ViewComponent, Object.assign({}, viewProps)));
9744 }
9745 }
9746 function buildToolbarProps(viewSpec, dateProfile, dateProfileGenerator, currentDate, now, title) {
9747 // don't force any date-profiles to valid date profiles (the `false`) so that we can tell if it's invalid
9748 let todayInfo = dateProfileGenerator.build(now, undefined, false); // TODO: need `undefined` or else INFINITE LOOP for some reason
9749 let prevInfo = dateProfileGenerator.buildPrev(dateProfile, currentDate, false);
9750 let nextInfo = dateProfileGenerator.buildNext(dateProfile, currentDate, false);
9751 return {
9752 title,
9753 activeButton: viewSpec.type,
9754 navUnit: viewSpec.singleUnit,
9755 isTodayEnabled: todayInfo.isValid && !rangeContainsMarker(dateProfile.currentRange, now),
9756 isPrevEnabled: prevInfo.isValid,
9757 isNextEnabled: nextInfo.isValid,
9758 };
9759 }
9760 // Plugin
9761 // -----------------------------------------------------------------------------------------------------------------
9762 function buildViewPropTransformers(theClasses) {
9763 return theClasses.map((TheClass) => new TheClass());
9764 }
9765
9766 class Calendar extends CalendarImpl {
9767 constructor(el, optionOverrides = {}) {
9768 super();
9769 this.isRendering = false;
9770 this.isRendered = false;
9771 this.currentClassNames = [];
9772 this.customContentRenderId = 0;
9773 this.handleAction = (action) => {
9774 // actions we know we want to render immediately
9775 switch (action.type) {
9776 case 'SET_EVENT_DRAG':
9777 case 'SET_EVENT_RESIZE':
9778 this.renderRunner.tryDrain();
9779 }
9780 };
9781 this.handleData = (data) => {
9782 this.currentData = data;
9783 this.renderRunner.request(data.calendarOptions.rerenderDelay);
9784 };
9785 this.handleRenderRequest = () => {
9786 if (this.isRendering) {
9787 this.isRendered = true;
9788 let { currentData } = this;
9789 flushSync(() => {
9790 D$1(y(CalendarRoot, { options: currentData.calendarOptions, theme: currentData.theme, emitter: currentData.emitter }, (classNames, height, isHeightAuto, forPrint) => {
9791 this.setClassNames(classNames);
9792 this.setHeight(height);
9793 return (y(RenderId.Provider, { value: this.customContentRenderId },
9794 y(CalendarContent, Object.assign({ isHeightAuto: isHeightAuto, forPrint: forPrint }, currentData))));
9795 }), this.el);
9796 });
9797 }
9798 else if (this.isRendered) {
9799 this.isRendered = false;
9800 D$1(null, this.el);
9801 this.setClassNames([]);
9802 this.setHeight('');
9803 }
9804 };
9805 ensureElHasStyles(el);
9806 this.el = el;
9807 this.renderRunner = new DelayedRunner(this.handleRenderRequest);
9808 new CalendarDataManager({
9809 optionOverrides,
9810 calendarApi: this,
9811 onAction: this.handleAction,
9812 onData: this.handleData,
9813 });
9814 }
9815 render() {
9816 let wasRendering = this.isRendering;
9817 if (!wasRendering) {
9818 this.isRendering = true;
9819 }
9820 else {
9821 this.customContentRenderId += 1;
9822 }
9823 this.renderRunner.request();
9824 if (wasRendering) {
9825 this.updateSize();
9826 }
9827 }
9828 destroy() {
9829 if (this.isRendering) {
9830 this.isRendering = false;
9831 this.renderRunner.request();
9832 }
9833 }
9834 updateSize() {
9835 flushSync(() => {
9836 super.updateSize();
9837 });
9838 }
9839 batchRendering(func) {
9840 this.renderRunner.pause('batchRendering');
9841 func();
9842 this.renderRunner.resume('batchRendering');
9843 }
9844 pauseRendering() {
9845 this.renderRunner.pause('pauseRendering');
9846 }
9847 resumeRendering() {
9848 this.renderRunner.resume('pauseRendering', true);
9849 }
9850 resetOptions(optionOverrides, changedOptionNames) {
9851 this.currentDataManager.resetOptions(optionOverrides, changedOptionNames);
9852 }
9853 setClassNames(classNames) {
9854 if (!isArraysEqual(classNames, this.currentClassNames)) {
9855 let { classList } = this.el;
9856 for (let className of this.currentClassNames) {
9857 classList.remove(className);
9858 }
9859 for (let className of classNames) {
9860 classList.add(className);
9861 }
9862 this.currentClassNames = classNames;
9863 }
9864 }
9865 setHeight(height) {
9866 applyStyleProp(this.el, 'height', height);
9867 }
9868 }
9869
9870 function formatDate(dateInput, options = {}) {
9871 let dateEnv = buildDateEnv(options);
9872 let formatter = createFormatter(options);
9873 let dateMeta = dateEnv.createMarkerMeta(dateInput);
9874 if (!dateMeta) { // TODO: warning?
9875 return '';
9876 }
9877 return dateEnv.format(dateMeta.marker, formatter, {
9878 forcedTzo: dateMeta.forcedTzo,
9879 });
9880 }
9881 function formatRange(startInput, endInput, options) {
9882 let dateEnv = buildDateEnv(typeof options === 'object' && options ? options : {}); // pass in if non-null object
9883 let formatter = createFormatter(options);
9884 let startMeta = dateEnv.createMarkerMeta(startInput);
9885 let endMeta = dateEnv.createMarkerMeta(endInput);
9886 if (!startMeta || !endMeta) { // TODO: warning?
9887 return '';
9888 }
9889 return dateEnv.formatRange(startMeta.marker, endMeta.marker, formatter, {
9890 forcedStartTzo: startMeta.forcedTzo,
9891 forcedEndTzo: endMeta.forcedTzo,
9892 isEndExclusive: options.isEndExclusive,
9893 defaultSeparator: BASE_OPTION_DEFAULTS.defaultRangeSeparator,
9894 });
9895 }
9896 // TODO: more DRY and optimized
9897 function buildDateEnv(settings) {
9898 let locale = buildLocale(settings.locale || 'en', organizeRawLocales([]).map); // TODO: don't hardcode 'en' everywhere
9899 return new DateEnv(Object.assign(Object.assign({ timeZone: BASE_OPTION_DEFAULTS.timeZone, calendarSystem: 'gregory' }, settings), { locale }));
9900 }
9901
9902 // HELPERS
9903 /*
9904 if nextDayThreshold is specified, slicing is done in an all-day fashion.
9905 you can get nextDayThreshold from context.nextDayThreshold
9906 */
9907 function sliceEvents(props, allDay) {
9908 return sliceEventStore(props.eventStore, props.eventUiBases, props.dateProfile.activeRange, allDay ? props.nextDayThreshold : null).fg;
9909 }
9910
9911 const version = '6.1.17';
9912
9913 config.touchMouseIgnoreWait = 500;
9914 let ignoreMouseDepth = 0;
9915 let listenerCnt = 0;
9916 let isWindowTouchMoveCancelled = false;
9917 /*
9918 Uses a "pointer" abstraction, which monitors UI events for both mouse and touch.
9919 Tracks when the pointer "drags" on a certain element, meaning down+move+up.
9920
9921 Also, tracks if there was touch-scrolling.
9922 Also, can prevent touch-scrolling from happening.
9923 Also, can fire pointermove events when scrolling happens underneath, even when no real pointer movement.
9924
9925 emits:
9926 - pointerdown
9927 - pointermove
9928 - pointerup
9929 */
9930 class PointerDragging {
9931 constructor(containerEl) {
9932 this.subjectEl = null;
9933 // options that can be directly assigned by caller
9934 this.selector = ''; // will cause subjectEl in all emitted events to be this element
9935 this.handleSelector = '';
9936 this.shouldIgnoreMove = false;
9937 this.shouldWatchScroll = true; // for simulating pointermove on scroll
9938 // internal states
9939 this.isDragging = false;
9940 this.isTouchDragging = false;
9941 this.wasTouchScroll = false;
9942 // Mouse
9943 // ----------------------------------------------------------------------------------------------------
9944 this.handleMouseDown = (ev) => {
9945 if (!this.shouldIgnoreMouse() &&
9946 isPrimaryMouseButton(ev) &&
9947 this.tryStart(ev)) {
9948 let pev = this.createEventFromMouse(ev, true);
9949 this.emitter.trigger('pointerdown', pev);
9950 this.initScrollWatch(pev);
9951 if (!this.shouldIgnoreMove) {
9952 document.addEventListener('mousemove', this.handleMouseMove);
9953 }
9954 document.addEventListener('mouseup', this.handleMouseUp);
9955 }
9956 };
9957 this.handleMouseMove = (ev) => {
9958 let pev = this.createEventFromMouse(ev);
9959 this.recordCoords(pev);
9960 this.emitter.trigger('pointermove', pev);
9961 };
9962 this.handleMouseUp = (ev) => {
9963 document.removeEventListener('mousemove', this.handleMouseMove);
9964 document.removeEventListener('mouseup', this.handleMouseUp);
9965 this.emitter.trigger('pointerup', this.createEventFromMouse(ev));
9966 this.cleanup(); // call last so that pointerup has access to props
9967 };
9968 // Touch
9969 // ----------------------------------------------------------------------------------------------------
9970 this.handleTouchStart = (ev) => {
9971 if (this.tryStart(ev)) {
9972 this.isTouchDragging = true;
9973 let pev = this.createEventFromTouch(ev, true);
9974 this.emitter.trigger('pointerdown', pev);
9975 this.initScrollWatch(pev);
9976 // unlike mouse, need to attach to target, not document
9977 // https://stackoverflow.com/a/45760014
9978 let targetEl = ev.target;
9979 if (!this.shouldIgnoreMove) {
9980 targetEl.addEventListener('touchmove', this.handleTouchMove);
9981 }
9982 targetEl.addEventListener('touchend', this.handleTouchEnd);
9983 targetEl.addEventListener('touchcancel', this.handleTouchEnd); // treat it as a touch end
9984 // attach a handler to get called when ANY scroll action happens on the page.
9985 // this was impossible to do with normal on/off because 'scroll' doesn't bubble.
9986 // http://stackoverflow.com/a/32954565/96342
9987 window.addEventListener('scroll', this.handleTouchScroll, true);
9988 }
9989 };
9990 this.handleTouchMove = (ev) => {
9991 let pev = this.createEventFromTouch(ev);
9992 this.recordCoords(pev);
9993 this.emitter.trigger('pointermove', pev);
9994 };
9995 this.handleTouchEnd = (ev) => {
9996 if (this.isDragging) { // done to guard against touchend followed by touchcancel
9997 let targetEl = ev.target;
9998 targetEl.removeEventListener('touchmove', this.handleTouchMove);
9999 targetEl.removeEventListener('touchend', this.handleTouchEnd);
10000 targetEl.removeEventListener('touchcancel', this.handleTouchEnd);
10001 window.removeEventListener('scroll', this.handleTouchScroll, true); // useCaptured=true
10002 this.emitter.trigger('pointerup', this.createEventFromTouch(ev));
10003 this.cleanup(); // call last so that pointerup has access to props
10004 this.isTouchDragging = false;
10005 startIgnoringMouse();
10006 }
10007 };
10008 this.handleTouchScroll = () => {
10009 this.wasTouchScroll = true;
10010 };
10011 this.handleScroll = (ev) => {
10012 if (!this.shouldIgnoreMove) {
10013 let pageX = (window.scrollX - this.prevScrollX) + this.prevPageX;
10014 let pageY = (window.scrollY - this.prevScrollY) + this.prevPageY;
10015 this.emitter.trigger('pointermove', {
10016 origEvent: ev,
10017 isTouch: this.isTouchDragging,
10018 subjectEl: this.subjectEl,
10019 pageX,
10020 pageY,
10021 deltaX: pageX - this.origPageX,
10022 deltaY: pageY - this.origPageY,
10023 });
10024 }
10025 };
10026 this.containerEl = containerEl;
10027 this.emitter = new Emitter();
10028 containerEl.addEventListener('mousedown', this.handleMouseDown);
10029 containerEl.addEventListener('touchstart', this.handleTouchStart, { passive: true });
10030 listenerCreated();
10031 }
10032 destroy() {
10033 this.containerEl.removeEventListener('mousedown', this.handleMouseDown);
10034 this.containerEl.removeEventListener('touchstart', this.handleTouchStart, { passive: true });
10035 listenerDestroyed();
10036 }
10037 tryStart(ev) {
10038 let subjectEl = this.querySubjectEl(ev);
10039 let downEl = ev.target;
10040 if (subjectEl &&
10041 (!this.handleSelector || elementClosest(downEl, this.handleSelector))) {
10042 this.subjectEl = subjectEl;
10043 this.isDragging = true; // do this first so cancelTouchScroll will work
10044 this.wasTouchScroll = false;
10045 return true;
10046 }
10047 return false;
10048 }
10049 cleanup() {
10050 isWindowTouchMoveCancelled = false;
10051 this.isDragging = false;
10052 this.subjectEl = null;
10053 // keep wasTouchScroll around for later access
10054 this.destroyScrollWatch();
10055 }
10056 querySubjectEl(ev) {
10057 if (this.selector) {
10058 return elementClosest(ev.target, this.selector);
10059 }
10060 return this.containerEl;
10061 }
10062 shouldIgnoreMouse() {
10063 return ignoreMouseDepth || this.isTouchDragging;
10064 }
10065 // can be called by user of this class, to cancel touch-based scrolling for the current drag
10066 cancelTouchScroll() {
10067 if (this.isDragging) {
10068 isWindowTouchMoveCancelled = true;
10069 }
10070 }
10071 // Scrolling that simulates pointermoves
10072 // ----------------------------------------------------------------------------------------------------
10073 initScrollWatch(ev) {
10074 if (this.shouldWatchScroll) {
10075 this.recordCoords(ev);
10076 window.addEventListener('scroll', this.handleScroll, true); // useCapture=true
10077 }
10078 }
10079 recordCoords(ev) {
10080 if (this.shouldWatchScroll) {
10081 this.prevPageX = ev.pageX;
10082 this.prevPageY = ev.pageY;
10083 this.prevScrollX = window.scrollX;
10084 this.prevScrollY = window.scrollY;
10085 }
10086 }
10087 destroyScrollWatch() {
10088 if (this.shouldWatchScroll) {
10089 window.removeEventListener('scroll', this.handleScroll, true); // useCaptured=true
10090 }
10091 }
10092 // Event Normalization
10093 // ----------------------------------------------------------------------------------------------------
10094 createEventFromMouse(ev, isFirst) {
10095 let deltaX = 0;
10096 let deltaY = 0;
10097 // TODO: repeat code
10098 if (isFirst) {
10099 this.origPageX = ev.pageX;
10100 this.origPageY = ev.pageY;
10101 }
10102 else {
10103 deltaX = ev.pageX - this.origPageX;
10104 deltaY = ev.pageY - this.origPageY;
10105 }
10106 return {
10107 origEvent: ev,
10108 isTouch: false,
10109 subjectEl: this.subjectEl,
10110 pageX: ev.pageX,
10111 pageY: ev.pageY,
10112 deltaX,
10113 deltaY,
10114 };
10115 }
10116 createEventFromTouch(ev, isFirst) {
10117 let touches = ev.touches;
10118 let pageX;
10119 let pageY;
10120 let deltaX = 0;
10121 let deltaY = 0;
10122 // if touch coords available, prefer,
10123 // because FF would give bad ev.pageX ev.pageY
10124 if (touches && touches.length) {
10125 pageX = touches[0].pageX;
10126 pageY = touches[0].pageY;
10127 }
10128 else {
10129 pageX = ev.pageX;
10130 pageY = ev.pageY;
10131 }
10132 // TODO: repeat code
10133 if (isFirst) {
10134 this.origPageX = pageX;
10135 this.origPageY = pageY;
10136 }
10137 else {
10138 deltaX = pageX - this.origPageX;
10139 deltaY = pageY - this.origPageY;
10140 }
10141 return {
10142 origEvent: ev,
10143 isTouch: true,
10144 subjectEl: this.subjectEl,
10145 pageX,
10146 pageY,
10147 deltaX,
10148 deltaY,
10149 };
10150 }
10151 }
10152 // Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac)
10153 function isPrimaryMouseButton(ev) {
10154 return ev.button === 0 && !ev.ctrlKey;
10155 }
10156 // Ignoring fake mouse events generated by touch
10157 // ----------------------------------------------------------------------------------------------------
10158 function startIgnoringMouse() {
10159 ignoreMouseDepth += 1;
10160 setTimeout(() => {
10161 ignoreMouseDepth -= 1;
10162 }, config.touchMouseIgnoreWait);
10163 }
10164 // We want to attach touchmove as early as possible for Safari
10165 // ----------------------------------------------------------------------------------------------------
10166 function listenerCreated() {
10167 listenerCnt += 1;
10168 if (listenerCnt === 1) {
10169 window.addEventListener('touchmove', onWindowTouchMove, { passive: false });
10170 }
10171 }
10172 function listenerDestroyed() {
10173 listenerCnt -= 1;
10174 if (!listenerCnt) {
10175 window.removeEventListener('touchmove', onWindowTouchMove, { passive: false });
10176 }
10177 }
10178 function onWindowTouchMove(ev) {
10179 if (isWindowTouchMoveCancelled) {
10180 ev.preventDefault();
10181 }
10182 }
10183
10184 /*
10185 An effect in which an element follows the movement of a pointer across the screen.
10186 The moving element is a clone of some other element.
10187 Must call start + handleMove + stop.
10188 */
10189 class ElementMirror {
10190 constructor() {
10191 this.isVisible = false; // must be explicitly enabled
10192 this.sourceEl = null;
10193 this.mirrorEl = null;
10194 this.sourceElRect = null; // screen coords relative to viewport
10195 // options that can be set directly by caller
10196 this.parentNode = document.body; // HIGHLY SUGGESTED to set this to sidestep ShadowDOM issues
10197 this.zIndex = 9999;
10198 this.revertDuration = 0;
10199 }
10200 start(sourceEl, pageX, pageY) {
10201 this.sourceEl = sourceEl;
10202 this.sourceElRect = this.sourceEl.getBoundingClientRect();
10203 this.origScreenX = pageX - window.scrollX;
10204 this.origScreenY = pageY - window.scrollY;
10205 this.deltaX = 0;
10206 this.deltaY = 0;
10207 this.updateElPosition();
10208 }
10209 handleMove(pageX, pageY) {
10210 this.deltaX = (pageX - window.scrollX) - this.origScreenX;
10211 this.deltaY = (pageY - window.scrollY) - this.origScreenY;
10212 this.updateElPosition();
10213 }
10214 // can be called before start
10215 setIsVisible(bool) {
10216 if (bool) {
10217 if (!this.isVisible) {
10218 if (this.mirrorEl) {
10219 this.mirrorEl.style.display = '';
10220 }
10221 this.isVisible = bool; // needs to happen before updateElPosition
10222 this.updateElPosition(); // because was not updating the position while invisible
10223 }
10224 }
10225 else if (this.isVisible) {
10226 if (this.mirrorEl) {
10227 this.mirrorEl.style.display = 'none';
10228 }
10229 this.isVisible = bool;
10230 }
10231 }
10232 // always async
10233 stop(needsRevertAnimation, callback) {
10234 let done = () => {
10235 this.cleanup();
10236 callback();
10237 };
10238 if (needsRevertAnimation &&
10239 this.mirrorEl &&
10240 this.isVisible &&
10241 this.revertDuration && // if 0, transition won't work
10242 (this.deltaX || this.deltaY) // if same coords, transition won't work
10243 ) {
10244 this.doRevertAnimation(done, this.revertDuration);
10245 }
10246 else {
10247 setTimeout(done, 0);
10248 }
10249 }
10250 doRevertAnimation(callback, revertDuration) {
10251 let mirrorEl = this.mirrorEl;
10252 let finalSourceElRect = this.sourceEl.getBoundingClientRect(); // because autoscrolling might have happened
10253 mirrorEl.style.transition =
10254 'top ' + revertDuration + 'ms,' +
10255 'left ' + revertDuration + 'ms';
10256 applyStyle(mirrorEl, {
10257 left: finalSourceElRect.left,
10258 top: finalSourceElRect.top,
10259 });
10260 whenTransitionDone(mirrorEl, () => {
10261 mirrorEl.style.transition = '';
10262 callback();
10263 });
10264 }
10265 cleanup() {
10266 if (this.mirrorEl) {
10267 removeElement(this.mirrorEl);
10268 this.mirrorEl = null;
10269 }
10270 this.sourceEl = null;
10271 }
10272 updateElPosition() {
10273 if (this.sourceEl && this.isVisible) {
10274 applyStyle(this.getMirrorEl(), {
10275 left: this.sourceElRect.left + this.deltaX,
10276 top: this.sourceElRect.top + this.deltaY,
10277 });
10278 }
10279 }
10280 getMirrorEl() {
10281 let sourceElRect = this.sourceElRect;
10282 let mirrorEl = this.mirrorEl;
10283 if (!mirrorEl) {
10284 mirrorEl = this.mirrorEl = this.sourceEl.cloneNode(true); // cloneChildren=true
10285 // we don't want long taps or any mouse interaction causing selection/menus.
10286 // would use preventSelection(), but that prevents selectstart, causing problems.
10287 mirrorEl.style.userSelect = 'none';
10288 mirrorEl.style.webkitUserSelect = 'none';
10289 mirrorEl.style.pointerEvents = 'none';
10290 mirrorEl.classList.add('fc-event-dragging');
10291 applyStyle(mirrorEl, {
10292 position: 'fixed',
10293 zIndex: this.zIndex,
10294 visibility: '',
10295 boxSizing: 'border-box',
10296 width: sourceElRect.right - sourceElRect.left,
10297 height: sourceElRect.bottom - sourceElRect.top,
10298 right: 'auto',
10299 bottom: 'auto',
10300 margin: 0,
10301 });
10302 this.parentNode.appendChild(mirrorEl);
10303 }
10304 return mirrorEl;
10305 }
10306 }
10307
10308 /*
10309 Is a cache for a given element's scroll information (all the info that ScrollController stores)
10310 in addition the "client rectangle" of the element.. the area within the scrollbars.
10311
10312 The cache can be in one of two modes:
10313 - doesListening:false - ignores when the container is scrolled by someone else
10314 - doesListening:true - watch for scrolling and update the cache
10315 */
10316 class ScrollGeomCache extends ScrollController {
10317 constructor(scrollController, doesListening) {
10318 super();
10319 this.handleScroll = () => {
10320 this.scrollTop = this.scrollController.getScrollTop();
10321 this.scrollLeft = this.scrollController.getScrollLeft();
10322 this.handleScrollChange();
10323 };
10324 this.scrollController = scrollController;
10325 this.doesListening = doesListening;
10326 this.scrollTop = this.origScrollTop = scrollController.getScrollTop();
10327 this.scrollLeft = this.origScrollLeft = scrollController.getScrollLeft();
10328 this.scrollWidth = scrollController.getScrollWidth();
10329 this.scrollHeight = scrollController.getScrollHeight();
10330 this.clientWidth = scrollController.getClientWidth();
10331 this.clientHeight = scrollController.getClientHeight();
10332 this.clientRect = this.computeClientRect(); // do last in case it needs cached values
10333 if (this.doesListening) {
10334 this.getEventTarget().addEventListener('scroll', this.handleScroll);
10335 }
10336 }
10337 destroy() {
10338 if (this.doesListening) {
10339 this.getEventTarget().removeEventListener('scroll', this.handleScroll);
10340 }
10341 }
10342 getScrollTop() {
10343 return this.scrollTop;
10344 }
10345 getScrollLeft() {
10346 return this.scrollLeft;
10347 }
10348 setScrollTop(top) {
10349 this.scrollController.setScrollTop(top);
10350 if (!this.doesListening) {
10351 // we are not relying on the element to normalize out-of-bounds scroll values
10352 // so we need to sanitize ourselves
10353 this.scrollTop = Math.max(Math.min(top, this.getMaxScrollTop()), 0);
10354 this.handleScrollChange();
10355 }
10356 }
10357 setScrollLeft(top) {
10358 this.scrollController.setScrollLeft(top);
10359 if (!this.doesListening) {
10360 // we are not relying on the element to normalize out-of-bounds scroll values
10361 // so we need to sanitize ourselves
10362 this.scrollLeft = Math.max(Math.min(top, this.getMaxScrollLeft()), 0);
10363 this.handleScrollChange();
10364 }
10365 }
10366 getClientWidth() {
10367 return this.clientWidth;
10368 }
10369 getClientHeight() {
10370 return this.clientHeight;
10371 }
10372 getScrollWidth() {
10373 return this.scrollWidth;
10374 }
10375 getScrollHeight() {
10376 return this.scrollHeight;
10377 }
10378 handleScrollChange() {
10379 }
10380 }
10381
10382 class ElementScrollGeomCache extends ScrollGeomCache {
10383 constructor(el, doesListening) {
10384 super(new ElementScrollController(el), doesListening);
10385 }
10386 getEventTarget() {
10387 return this.scrollController.el;
10388 }
10389 computeClientRect() {
10390 return computeInnerRect(this.scrollController.el);
10391 }
10392 }
10393
10394 class WindowScrollGeomCache extends ScrollGeomCache {
10395 constructor(doesListening) {
10396 super(new WindowScrollController(), doesListening);
10397 }
10398 getEventTarget() {
10399 return window;
10400 }
10401 computeClientRect() {
10402 return {
10403 left: this.scrollLeft,
10404 right: this.scrollLeft + this.clientWidth,
10405 top: this.scrollTop,
10406 bottom: this.scrollTop + this.clientHeight,
10407 };
10408 }
10409 // the window is the only scroll object that changes it's rectangle relative
10410 // to the document's topleft as it scrolls
10411 handleScrollChange() {
10412 this.clientRect = this.computeClientRect();
10413 }
10414 }
10415
10416 // If available we are using native "performance" API instead of "Date"
10417 // Read more about it on MDN:
10418 // https://developer.mozilla.org/en-US/docs/Web/API/Performance
10419 const getTime = typeof performance === 'function' ? performance.now : Date.now;
10420 /*
10421 For a pointer interaction, automatically scrolls certain scroll containers when the pointer
10422 approaches the edge.
10423
10424 The caller must call start + handleMove + stop.
10425 */
10426 class AutoScroller {
10427 constructor() {
10428 // options that can be set by caller
10429 this.isEnabled = true;
10430 this.scrollQuery = [window, '.fc-scroller'];
10431 this.edgeThreshold = 50; // pixels
10432 this.maxVelocity = 300; // pixels per second
10433 // internal state
10434 this.pointerScreenX = null;
10435 this.pointerScreenY = null;
10436 this.isAnimating = false;
10437 this.scrollCaches = null;
10438 // protect against the initial pointerdown being too close to an edge and starting the scroll
10439 this.everMovedUp = false;
10440 this.everMovedDown = false;
10441 this.everMovedLeft = false;
10442 this.everMovedRight = false;
10443 this.animate = () => {
10444 if (this.isAnimating) { // wasn't cancelled between animation calls
10445 let edge = this.computeBestEdge(this.pointerScreenX + window.scrollX, this.pointerScreenY + window.scrollY);
10446 if (edge) {
10447 let now = getTime();
10448 this.handleSide(edge, (now - this.msSinceRequest) / 1000);
10449 this.requestAnimation(now);
10450 }
10451 else {
10452 this.isAnimating = false; // will stop animation
10453 }
10454 }
10455 };
10456 }
10457 start(pageX, pageY, scrollStartEl) {
10458 if (this.isEnabled) {
10459 this.scrollCaches = this.buildCaches(scrollStartEl);
10460 this.pointerScreenX = null;
10461 this.pointerScreenY = null;
10462 this.everMovedUp = false;
10463 this.everMovedDown = false;
10464 this.everMovedLeft = false;
10465 this.everMovedRight = false;
10466 this.handleMove(pageX, pageY);
10467 }
10468 }
10469 handleMove(pageX, pageY) {
10470 if (this.isEnabled) {
10471 let pointerScreenX = pageX - window.scrollX;
10472 let pointerScreenY = pageY - window.scrollY;
10473 let yDelta = this.pointerScreenY === null ? 0 : pointerScreenY - this.pointerScreenY;
10474 let xDelta = this.pointerScreenX === null ? 0 : pointerScreenX - this.pointerScreenX;
10475 if (yDelta < 0) {
10476 this.everMovedUp = true;
10477 }
10478 else if (yDelta > 0) {
10479 this.everMovedDown = true;
10480 }
10481 if (xDelta < 0) {
10482 this.everMovedLeft = true;
10483 }
10484 else if (xDelta > 0) {
10485 this.everMovedRight = true;
10486 }
10487 this.pointerScreenX = pointerScreenX;
10488 this.pointerScreenY = pointerScreenY;
10489 if (!this.isAnimating) {
10490 this.isAnimating = true;
10491 this.requestAnimation(getTime());
10492 }
10493 }
10494 }
10495 stop() {
10496 if (this.isEnabled) {
10497 this.isAnimating = false; // will stop animation
10498 for (let scrollCache of this.scrollCaches) {
10499 scrollCache.destroy();
10500 }
10501 this.scrollCaches = null;
10502 }
10503 }
10504 requestAnimation(now) {
10505 this.msSinceRequest = now;
10506 requestAnimationFrame(this.animate);
10507 }
10508 handleSide(edge, seconds) {
10509 let { scrollCache } = edge;
10510 let { edgeThreshold } = this;
10511 let invDistance = edgeThreshold - edge.distance;
10512 let velocity = // the closer to the edge, the faster we scroll
10513 ((invDistance * invDistance) / (edgeThreshold * edgeThreshold)) * // quadratic
10514 this.maxVelocity * seconds;
10515 let sign = 1;
10516 switch (edge.name) {
10517 case 'left':
10518 sign = -1;
10519 // falls through
10520 case 'right':
10521 scrollCache.setScrollLeft(scrollCache.getScrollLeft() + velocity * sign);
10522 break;
10523 case 'top':
10524 sign = -1;
10525 // falls through
10526 case 'bottom':
10527 scrollCache.setScrollTop(scrollCache.getScrollTop() + velocity * sign);
10528 break;
10529 }
10530 }
10531 // left/top are relative to document topleft
10532 computeBestEdge(left, top) {
10533 let { edgeThreshold } = this;
10534 let bestSide = null;
10535 let scrollCaches = this.scrollCaches || [];
10536 for (let scrollCache of scrollCaches) {
10537 let rect = scrollCache.clientRect;
10538 let leftDist = left - rect.left;
10539 let rightDist = rect.right - left;
10540 let topDist = top - rect.top;
10541 let bottomDist = rect.bottom - top;
10542 // completely within the rect?
10543 if (leftDist >= 0 && rightDist >= 0 && topDist >= 0 && bottomDist >= 0) {
10544 if (topDist <= edgeThreshold && this.everMovedUp && scrollCache.canScrollUp() &&
10545 (!bestSide || bestSide.distance > topDist)) {
10546 bestSide = { scrollCache, name: 'top', distance: topDist };
10547 }
10548 if (bottomDist <= edgeThreshold && this.everMovedDown && scrollCache.canScrollDown() &&
10549 (!bestSide || bestSide.distance > bottomDist)) {
10550 bestSide = { scrollCache, name: 'bottom', distance: bottomDist };
10551 }
10552 /*
10553 TODO: fix broken RTL scrolling. canScrollLeft always returning false
10554 https://github.com/fullcalendar/fullcalendar/issues/4837
10555 */
10556 if (leftDist <= edgeThreshold && this.everMovedLeft && scrollCache.canScrollLeft() &&
10557 (!bestSide || bestSide.distance > leftDist)) {
10558 bestSide = { scrollCache, name: 'left', distance: leftDist };
10559 }
10560 if (rightDist <= edgeThreshold && this.everMovedRight && scrollCache.canScrollRight() &&
10561 (!bestSide || bestSide.distance > rightDist)) {
10562 bestSide = { scrollCache, name: 'right', distance: rightDist };
10563 }
10564 }
10565 }
10566 return bestSide;
10567 }
10568 buildCaches(scrollStartEl) {
10569 return this.queryScrollEls(scrollStartEl).map((el) => {
10570 if (el === window) {
10571 return new WindowScrollGeomCache(false); // false = don't listen to user-generated scrolls
10572 }
10573 return new ElementScrollGeomCache(el, false); // false = don't listen to user-generated scrolls
10574 });
10575 }
10576 queryScrollEls(scrollStartEl) {
10577 let els = [];
10578 for (let query of this.scrollQuery) {
10579 if (typeof query === 'object') {
10580 els.push(query);
10581 }
10582 else {
10583 /*
10584 TODO: in the future, always have auto-scroll happen on element where current Hit came from
10585 Ticket: https://github.com/fullcalendar/fullcalendar/issues/4593
10586 */
10587 els.push(...Array.prototype.slice.call(scrollStartEl.getRootNode().querySelectorAll(query)));
10588 }
10589 }
10590 return els;
10591 }
10592 }
10593
10594 /*
10595 Monitors dragging on an element. Has a number of high-level features:
10596 - minimum distance required before dragging
10597 - minimum wait time ("delay") before dragging
10598 - a mirror element that follows the pointer
10599 */
10600 class FeaturefulElementDragging extends ElementDragging {
10601 constructor(containerEl, selector) {
10602 super(containerEl);
10603 this.containerEl = containerEl;
10604 // options that can be directly set by caller
10605 // the caller can also set the PointerDragging's options as well
10606 this.delay = null;
10607 this.minDistance = 0;
10608 this.touchScrollAllowed = true; // prevents drag from starting and blocks scrolling during drag
10609 this.mirrorNeedsRevert = false;
10610 this.isInteracting = false; // is the user validly moving the pointer? lasts until pointerup
10611 this.isDragging = false; // is it INTENTFULLY dragging? lasts until after revert animation
10612 this.isDelayEnded = false;
10613 this.isDistanceSurpassed = false;
10614 this.delayTimeoutId = null;
10615 this.onPointerDown = (ev) => {
10616 if (!this.isDragging) { // so new drag doesn't happen while revert animation is going
10617 this.isInteracting = true;
10618 this.isDelayEnded = false;
10619 this.isDistanceSurpassed = false;
10620 preventSelection(document.body);
10621 preventContextMenu(document.body);
10622 // prevent links from being visited if there's an eventual drag.
10623 // also prevents selection in older browsers (maybe?).
10624 // not necessary for touch, besides, browser would complain about passiveness.
10625 if (!ev.isTouch) {
10626 ev.origEvent.preventDefault();
10627 }
10628 this.emitter.trigger('pointerdown', ev);
10629 if (this.isInteracting && // not destroyed via pointerdown handler
10630 !this.pointer.shouldIgnoreMove) {
10631 // actions related to initiating dragstart+dragmove+dragend...
10632 this.mirror.setIsVisible(false); // reset. caller must set-visible
10633 this.mirror.start(ev.subjectEl, ev.pageX, ev.pageY); // must happen on first pointer down
10634 this.startDelay(ev);
10635 if (!this.minDistance) {
10636 this.handleDistanceSurpassed(ev);
10637 }
10638 }
10639 }
10640 };
10641 this.onPointerMove = (ev) => {
10642 if (this.isInteracting) {
10643 this.emitter.trigger('pointermove', ev);
10644 if (!this.isDistanceSurpassed) {
10645 let minDistance = this.minDistance;
10646 let distanceSq; // current distance from the origin, squared
10647 let { deltaX, deltaY } = ev;
10648 distanceSq = deltaX * deltaX + deltaY * deltaY;
10649 if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem
10650 this.handleDistanceSurpassed(ev);
10651 }
10652 }
10653 if (this.isDragging) {
10654 // a real pointer move? (not one simulated by scrolling)
10655 if (ev.origEvent.type !== 'scroll') {
10656 this.mirror.handleMove(ev.pageX, ev.pageY);
10657 this.autoScroller.handleMove(ev.pageX, ev.pageY);
10658 }
10659 this.emitter.trigger('dragmove', ev);
10660 }
10661 }
10662 };
10663 this.onPointerUp = (ev) => {
10664 if (this.isInteracting) {
10665 this.isInteracting = false;
10666 allowSelection(document.body);
10667 allowContextMenu(document.body);
10668 this.emitter.trigger('pointerup', ev); // can potentially set mirrorNeedsRevert
10669 if (this.isDragging) {
10670 this.autoScroller.stop();
10671 this.tryStopDrag(ev); // which will stop the mirror
10672 }
10673 if (this.delayTimeoutId) {
10674 clearTimeout(this.delayTimeoutId);
10675 this.delayTimeoutId = null;
10676 }
10677 }
10678 };
10679 let pointer = this.pointer = new PointerDragging(containerEl);
10680 pointer.emitter.on('pointerdown', this.onPointerDown);
10681 pointer.emitter.on('pointermove', this.onPointerMove);
10682 pointer.emitter.on('pointerup', this.onPointerUp);
10683 if (selector) {
10684 pointer.selector = selector;
10685 }
10686 this.mirror = new ElementMirror();
10687 this.autoScroller = new AutoScroller();
10688 }
10689 destroy() {
10690 this.pointer.destroy();
10691 // HACK: simulate a pointer-up to end the current drag
10692 // TODO: fire 'dragend' directly and stop interaction. discourage use of pointerup event (b/c might not fire)
10693 this.onPointerUp({});
10694 }
10695 startDelay(ev) {
10696 if (typeof this.delay === 'number') {
10697 this.delayTimeoutId = setTimeout(() => {
10698 this.delayTimeoutId = null;
10699 this.handleDelayEnd(ev);
10700 }, this.delay); // not assignable to number!
10701 }
10702 else {
10703 this.handleDelayEnd(ev);
10704 }
10705 }
10706 handleDelayEnd(ev) {
10707 this.isDelayEnded = true;
10708 this.tryStartDrag(ev);
10709 }
10710 handleDistanceSurpassed(ev) {
10711 this.isDistanceSurpassed = true;
10712 this.tryStartDrag(ev);
10713 }
10714 tryStartDrag(ev) {
10715 if (this.isDelayEnded && this.isDistanceSurpassed) {
10716 if (!this.pointer.wasTouchScroll || this.touchScrollAllowed) {
10717 this.isDragging = true;
10718 this.mirrorNeedsRevert = false;
10719 this.autoScroller.start(ev.pageX, ev.pageY, this.containerEl);
10720 this.emitter.trigger('dragstart', ev);
10721 if (this.touchScrollAllowed === false) {
10722 this.pointer.cancelTouchScroll();
10723 }
10724 }
10725 }
10726 }
10727 tryStopDrag(ev) {
10728 // .stop() is ALWAYS asynchronous, which we NEED because we want all pointerup events
10729 // that come from the document to fire beforehand. much more convenient this way.
10730 this.mirror.stop(this.mirrorNeedsRevert, this.stopDrag.bind(this, ev));
10731 }
10732 stopDrag(ev) {
10733 this.isDragging = false;
10734 this.emitter.trigger('dragend', ev);
10735 }
10736 // fill in the implementations...
10737 setIgnoreMove(bool) {
10738 this.pointer.shouldIgnoreMove = bool;
10739 }
10740 setMirrorIsVisible(bool) {
10741 this.mirror.setIsVisible(bool);
10742 }
10743 setMirrorNeedsRevert(bool) {
10744 this.mirrorNeedsRevert = bool;
10745 }
10746 setAutoScrollEnabled(bool) {
10747 this.autoScroller.isEnabled = bool;
10748 }
10749 }
10750
10751 /*
10752 When this class is instantiated, it records the offset of an element (relative to the document topleft),
10753 and continues to monitor scrolling, updating the cached coordinates if it needs to.
10754 Does not access the DOM after instantiation, so highly performant.
10755
10756 Also keeps track of all scrolling/overflow:hidden containers that are parents of the given element
10757 and an determine if a given point is inside the combined clipping rectangle.
10758 */
10759 class OffsetTracker {
10760 constructor(el) {
10761 this.el = el;
10762 this.origRect = computeRect(el);
10763 // will work fine for divs that have overflow:hidden
10764 this.scrollCaches = getClippingParents(el).map((scrollEl) => new ElementScrollGeomCache(scrollEl, true));
10765 }
10766 destroy() {
10767 for (let scrollCache of this.scrollCaches) {
10768 scrollCache.destroy();
10769 }
10770 }
10771 computeLeft() {
10772 let left = this.origRect.left;
10773 for (let scrollCache of this.scrollCaches) {
10774 left += scrollCache.origScrollLeft - scrollCache.getScrollLeft();
10775 }
10776 return left;
10777 }
10778 computeTop() {
10779 let top = this.origRect.top;
10780 for (let scrollCache of this.scrollCaches) {
10781 top += scrollCache.origScrollTop - scrollCache.getScrollTop();
10782 }
10783 return top;
10784 }
10785 isWithinClipping(pageX, pageY) {
10786 let point = { left: pageX, top: pageY };
10787 for (let scrollCache of this.scrollCaches) {
10788 if (!isIgnoredClipping(scrollCache.getEventTarget()) &&
10789 !pointInsideRect(point, scrollCache.clientRect)) {
10790 return false;
10791 }
10792 }
10793 return true;
10794 }
10795 }
10796 // certain clipping containers should never constrain interactions, like <html> and <body>
10797 // https://github.com/fullcalendar/fullcalendar/issues/3615
10798 function isIgnoredClipping(node) {
10799 let tagName = node.tagName;
10800 return tagName === 'HTML' || tagName === 'BODY';
10801 }
10802
10803 /*
10804 Tracks movement over multiple droppable areas (aka "hits")
10805 that exist in one or more DateComponents.
10806 Relies on an existing draggable.
10807
10808 emits:
10809 - pointerdown
10810 - dragstart
10811 - hitchange - fires initially, even if not over a hit
10812 - pointerup
10813 - (hitchange - again, to null, if ended over a hit)
10814 - dragend
10815 */
10816 class HitDragging {
10817 constructor(dragging, droppableStore) {
10818 // options that can be set by caller
10819 this.useSubjectCenter = false;
10820 this.requireInitial = true; // if doesn't start out on a hit, won't emit any events
10821 this.disablePointCheck = false;
10822 this.initialHit = null;
10823 this.movingHit = null;
10824 this.finalHit = null; // won't ever be populated if shouldIgnoreMove
10825 this.handlePointerDown = (ev) => {
10826 let { dragging } = this;
10827 this.initialHit = null;
10828 this.movingHit = null;
10829 this.finalHit = null;
10830 this.prepareHits();
10831 this.processFirstCoord(ev);
10832 if (this.initialHit || !this.requireInitial) {
10833 dragging.setIgnoreMove(false);
10834 // TODO: fire this before computing processFirstCoord, so listeners can cancel. this gets fired by almost every handler :(
10835 this.emitter.trigger('pointerdown', ev);
10836 }
10837 else {
10838 dragging.setIgnoreMove(true);
10839 }
10840 };
10841 this.handleDragStart = (ev) => {
10842 this.emitter.trigger('dragstart', ev);
10843 this.handleMove(ev, true); // force = fire even if initially null
10844 };
10845 this.handleDragMove = (ev) => {
10846 this.emitter.trigger('dragmove', ev);
10847 this.handleMove(ev);
10848 };
10849 this.handlePointerUp = (ev) => {
10850 this.releaseHits();
10851 this.emitter.trigger('pointerup', ev);
10852 };
10853 this.handleDragEnd = (ev) => {
10854 if (this.movingHit) {
10855 this.emitter.trigger('hitupdate', null, true, ev);
10856 }
10857 this.finalHit = this.movingHit;
10858 this.movingHit = null;
10859 this.emitter.trigger('dragend', ev);
10860 };
10861 this.droppableStore = droppableStore;
10862 dragging.emitter.on('pointerdown', this.handlePointerDown);
10863 dragging.emitter.on('dragstart', this.handleDragStart);
10864 dragging.emitter.on('dragmove', this.handleDragMove);
10865 dragging.emitter.on('pointerup', this.handlePointerUp);
10866 dragging.emitter.on('dragend', this.handleDragEnd);
10867 this.dragging = dragging;
10868 this.emitter = new Emitter();
10869 }
10870 // sets initialHit
10871 // sets coordAdjust
10872 processFirstCoord(ev) {
10873 let origPoint = { left: ev.pageX, top: ev.pageY };
10874 let adjustedPoint = origPoint;
10875 let subjectEl = ev.subjectEl;
10876 let subjectRect;
10877 if (subjectEl instanceof HTMLElement) { // i.e. not a Document/ShadowRoot
10878 subjectRect = computeRect(subjectEl);
10879 adjustedPoint = constrainPoint(adjustedPoint, subjectRect);
10880 }
10881 let initialHit = this.initialHit = this.queryHitForOffset(adjustedPoint.left, adjustedPoint.top);
10882 if (initialHit) {
10883 if (this.useSubjectCenter && subjectRect) {
10884 let slicedSubjectRect = intersectRects(subjectRect, initialHit.rect);
10885 if (slicedSubjectRect) {
10886 adjustedPoint = getRectCenter(slicedSubjectRect);
10887 }
10888 }
10889 this.coordAdjust = diffPoints(adjustedPoint, origPoint);
10890 }
10891 else {
10892 this.coordAdjust = { left: 0, top: 0 };
10893 }
10894 }
10895 handleMove(ev, forceHandle) {
10896 let hit = this.queryHitForOffset(ev.pageX + this.coordAdjust.left, ev.pageY + this.coordAdjust.top);
10897 if (forceHandle || !isHitsEqual(this.movingHit, hit)) {
10898 this.movingHit = hit;
10899 this.emitter.trigger('hitupdate', hit, false, ev);
10900 }
10901 }
10902 prepareHits() {
10903 this.offsetTrackers = mapHash(this.droppableStore, (interactionSettings) => {
10904 interactionSettings.component.prepareHits();
10905 return new OffsetTracker(interactionSettings.el);
10906 });
10907 }
10908 releaseHits() {
10909 let { offsetTrackers } = this;
10910 for (let id in offsetTrackers) {
10911 offsetTrackers[id].destroy();
10912 }
10913 this.offsetTrackers = {};
10914 }
10915 queryHitForOffset(offsetLeft, offsetTop) {
10916 let { droppableStore, offsetTrackers } = this;
10917 let bestHit = null;
10918 for (let id in droppableStore) {
10919 let component = droppableStore[id].component;
10920 let offsetTracker = offsetTrackers[id];
10921 if (offsetTracker && // wasn't destroyed mid-drag
10922 offsetTracker.isWithinClipping(offsetLeft, offsetTop)) {
10923 let originLeft = offsetTracker.computeLeft();
10924 let originTop = offsetTracker.computeTop();
10925 let positionLeft = offsetLeft - originLeft;
10926 let positionTop = offsetTop - originTop;
10927 let { origRect } = offsetTracker;
10928 let width = origRect.right - origRect.left;
10929 let height = origRect.bottom - origRect.top;
10930 if (
10931 // must be within the element's bounds
10932 positionLeft >= 0 && positionLeft < width &&
10933 positionTop >= 0 && positionTop < height) {
10934 let hit = component.queryHit(positionLeft, positionTop, width, height);
10935 if (hit && (
10936 // make sure the hit is within activeRange, meaning it's not a dead cell
10937 rangeContainsRange(hit.dateProfile.activeRange, hit.dateSpan.range)) &&
10938 // Ensure the component we are querying for the hit is accessibly my the pointer
10939 // Prevents obscured calendars (ex: under a modal dialog) from accepting hit
10940 // https://github.com/fullcalendar/fullcalendar/issues/5026
10941 (this.disablePointCheck ||
10942 offsetTracker.el.contains(offsetTracker.el.getRootNode().elementFromPoint(
10943 // add-back origins to get coordinate relative to top-left of window viewport
10944 positionLeft + originLeft - window.scrollX, positionTop + originTop - window.scrollY))) &&
10945 (!bestHit || hit.layer > bestHit.layer)) {
10946 hit.componentId = id;
10947 hit.context = component.context;
10948 // TODO: better way to re-orient rectangle
10949 hit.rect.left += originLeft;
10950 hit.rect.right += originLeft;
10951 hit.rect.top += originTop;
10952 hit.rect.bottom += originTop;
10953 bestHit = hit;
10954 }
10955 }
10956 }
10957 }
10958 return bestHit;
10959 }
10960 }
10961 function isHitsEqual(hit0, hit1) {
10962 if (!hit0 && !hit1) {
10963 return true;
10964 }
10965 if (Boolean(hit0) !== Boolean(hit1)) {
10966 return false;
10967 }
10968 return isDateSpansEqual(hit0.dateSpan, hit1.dateSpan);
10969 }
10970
10971 function buildDatePointApiWithContext(dateSpan, context) {
10972 let props = {};
10973 for (let transform of context.pluginHooks.datePointTransforms) {
10974 Object.assign(props, transform(dateSpan, context));
10975 }
10976 Object.assign(props, buildDatePointApi(dateSpan, context.dateEnv));
10977 return props;
10978 }
10979 function buildDatePointApi(span, dateEnv) {
10980 return {
10981 date: dateEnv.toDate(span.range.start),
10982 dateStr: dateEnv.formatIso(span.range.start, { omitTime: span.allDay }),
10983 allDay: span.allDay,
10984 };
10985 }
10986
10987 /*
10988 Monitors when the user clicks on a specific date/time of a component.
10989 A pointerdown+pointerup on the same "hit" constitutes a click.
10990 */
10991 class DateClicking extends Interaction {
10992 constructor(settings) {
10993 super(settings);
10994 this.handlePointerDown = (pev) => {
10995 let { dragging } = this;
10996 let downEl = pev.origEvent.target;
10997 // do this in pointerdown (not dragend) because DOM might be mutated by the time dragend is fired
10998 dragging.setIgnoreMove(!this.component.isValidDateDownEl(downEl));
10999 };
11000 // won't even fire if moving was ignored
11001 this.handleDragEnd = (ev) => {
11002 let { component } = this;
11003 let { pointer } = this.dragging;
11004 if (!pointer.wasTouchScroll) {
11005 let { initialHit, finalHit } = this.hitDragging;
11006 if (initialHit && finalHit && isHitsEqual(initialHit, finalHit)) {
11007 let { context } = component;
11008 let arg = Object.assign(Object.assign({}, buildDatePointApiWithContext(initialHit.dateSpan, context)), { dayEl: initialHit.dayEl, jsEvent: ev.origEvent, view: context.viewApi || context.calendarApi.view });
11009 context.emitter.trigger('dateClick', arg);
11010 }
11011 }
11012 };
11013 // we DO want to watch pointer moves because otherwise finalHit won't get populated
11014 this.dragging = new FeaturefulElementDragging(settings.el);
11015 this.dragging.autoScroller.isEnabled = false;
11016 let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings));
11017 hitDragging.emitter.on('pointerdown', this.handlePointerDown);
11018 hitDragging.emitter.on('dragend', this.handleDragEnd);
11019 }
11020 destroy() {
11021 this.dragging.destroy();
11022 }
11023 }
11024
11025 /*
11026 Tracks when the user selects a portion of time of a component,
11027 constituted by a drag over date cells, with a possible delay at the beginning of the drag.
11028 */
11029 class DateSelecting extends Interaction {
11030 constructor(settings) {
11031 super(settings);
11032 this.dragSelection = null;
11033 this.handlePointerDown = (ev) => {
11034 let { component, dragging } = this;
11035 let { options } = component.context;
11036 let canSelect = options.selectable &&
11037 component.isValidDateDownEl(ev.origEvent.target);
11038 // don't bother to watch expensive moves if component won't do selection
11039 dragging.setIgnoreMove(!canSelect);
11040 // if touch, require user to hold down
11041 dragging.delay = ev.isTouch ? getComponentTouchDelay$1(component) : null;
11042 };
11043 this.handleDragStart = (ev) => {
11044 this.component.context.calendarApi.unselect(ev); // unselect previous selections
11045 };
11046 this.handleHitUpdate = (hit, isFinal) => {
11047 let { context } = this.component;
11048 let dragSelection = null;
11049 let isInvalid = false;
11050 if (hit) {
11051 let initialHit = this.hitDragging.initialHit;
11052 let disallowed = hit.componentId === initialHit.componentId
11053 && this.isHitComboAllowed
11054 && !this.isHitComboAllowed(initialHit, hit);
11055 if (!disallowed) {
11056 dragSelection = joinHitsIntoSelection(initialHit, hit, context.pluginHooks.dateSelectionTransformers);
11057 }
11058 if (!dragSelection || !isDateSelectionValid(dragSelection, hit.dateProfile, context)) {
11059 isInvalid = true;
11060 dragSelection = null;
11061 }
11062 }
11063 if (dragSelection) {
11064 context.dispatch({ type: 'SELECT_DATES', selection: dragSelection });
11065 }
11066 else if (!isFinal) { // only unselect if moved away while dragging
11067 context.dispatch({ type: 'UNSELECT_DATES' });
11068 }
11069 if (!isInvalid) {
11070 enableCursor();
11071 }
11072 else {
11073 disableCursor();
11074 }
11075 if (!isFinal) {
11076 this.dragSelection = dragSelection; // only clear if moved away from all hits while dragging
11077 }
11078 };
11079 this.handlePointerUp = (pev) => {
11080 if (this.dragSelection) {
11081 // selection is already rendered, so just need to report selection
11082 triggerDateSelect(this.dragSelection, pev, this.component.context);
11083 this.dragSelection = null;
11084 }
11085 };
11086 let { component } = settings;
11087 let { options } = component.context;
11088 let dragging = this.dragging = new FeaturefulElementDragging(settings.el);
11089 dragging.touchScrollAllowed = false;
11090 dragging.minDistance = options.selectMinDistance || 0;
11091 dragging.autoScroller.isEnabled = options.dragScroll;
11092 let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings));
11093 hitDragging.emitter.on('pointerdown', this.handlePointerDown);
11094 hitDragging.emitter.on('dragstart', this.handleDragStart);
11095 hitDragging.emitter.on('hitupdate', this.handleHitUpdate);
11096 hitDragging.emitter.on('pointerup', this.handlePointerUp);
11097 }
11098 destroy() {
11099 this.dragging.destroy();
11100 }
11101 }
11102 function getComponentTouchDelay$1(component) {
11103 let { options } = component.context;
11104 let delay = options.selectLongPressDelay;
11105 if (delay == null) {
11106 delay = options.longPressDelay;
11107 }
11108 return delay;
11109 }
11110 function joinHitsIntoSelection(hit0, hit1, dateSelectionTransformers) {
11111 let dateSpan0 = hit0.dateSpan;
11112 let dateSpan1 = hit1.dateSpan;
11113 let ms = [
11114 dateSpan0.range.start,
11115 dateSpan0.range.end,
11116 dateSpan1.range.start,
11117 dateSpan1.range.end,
11118 ];
11119 ms.sort(compareNumbers);
11120 let props = {};
11121 for (let transformer of dateSelectionTransformers) {
11122 let res = transformer(hit0, hit1);
11123 if (res === false) {
11124 return null;
11125 }
11126 if (res) {
11127 Object.assign(props, res);
11128 }
11129 }
11130 props.range = { start: ms[0], end: ms[3] };
11131 props.allDay = dateSpan0.allDay;
11132 return props;
11133 }
11134
11135 class EventDragging extends Interaction {
11136 constructor(settings) {
11137 super(settings);
11138 // internal state
11139 this.subjectEl = null;
11140 this.subjectSeg = null; // the seg being selected/dragged
11141 this.isDragging = false;
11142 this.eventRange = null;
11143 this.relevantEvents = null; // the events being dragged
11144 this.receivingContext = null;
11145 this.validMutation = null;
11146 this.mutatedRelevantEvents = null;
11147 this.handlePointerDown = (ev) => {
11148 let origTarget = ev.origEvent.target;
11149 let { component, dragging } = this;
11150 let { mirror } = dragging;
11151 let { options } = component.context;
11152 let initialContext = component.context;
11153 this.subjectEl = ev.subjectEl;
11154 let subjectSeg = this.subjectSeg = getElSeg(ev.subjectEl);
11155 let eventRange = this.eventRange = subjectSeg.eventRange;
11156 let eventInstanceId = eventRange.instance.instanceId;
11157 this.relevantEvents = getRelevantEvents(initialContext.getCurrentData().eventStore, eventInstanceId);
11158 dragging.minDistance = ev.isTouch ? 0 : options.eventDragMinDistance;
11159 dragging.delay =
11160 // only do a touch delay if touch and this event hasn't been selected yet
11161 (ev.isTouch && eventInstanceId !== component.props.eventSelection) ?
11162 getComponentTouchDelay(component) :
11163 null;
11164 if (options.fixedMirrorParent) {
11165 mirror.parentNode = options.fixedMirrorParent;
11166 }
11167 else {
11168 mirror.parentNode = elementClosest(origTarget, '.fc');
11169 }
11170 mirror.revertDuration = options.dragRevertDuration;
11171 let isValid = component.isValidSegDownEl(origTarget) &&
11172 !elementClosest(origTarget, '.fc-event-resizer'); // NOT on a resizer
11173 dragging.setIgnoreMove(!isValid);
11174 // disable dragging for elements that are resizable (ie, selectable)
11175 // but are not draggable
11176 this.isDragging = isValid &&
11177 ev.subjectEl.classList.contains('fc-event-draggable');
11178 };
11179 this.handleDragStart = (ev) => {
11180 let initialContext = this.component.context;
11181 let eventRange = this.eventRange;
11182 let eventInstanceId = eventRange.instance.instanceId;
11183 if (ev.isTouch) {
11184 // need to select a different event?
11185 if (eventInstanceId !== this.component.props.eventSelection) {
11186 initialContext.dispatch({ type: 'SELECT_EVENT', eventInstanceId });
11187 }
11188 }
11189 else {
11190 // if now using mouse, but was previous touch interaction, clear selected event
11191 initialContext.dispatch({ type: 'UNSELECT_EVENT' });
11192 }
11193 if (this.isDragging) {
11194 initialContext.calendarApi.unselect(ev); // unselect *date* selection
11195 initialContext.emitter.trigger('eventDragStart', {
11196 el: this.subjectEl,
11197 event: new EventImpl(initialContext, eventRange.def, eventRange.instance),
11198 jsEvent: ev.origEvent,
11199 view: initialContext.viewApi,
11200 });
11201 }
11202 };
11203 this.handleHitUpdate = (hit, isFinal) => {
11204 if (!this.isDragging) {
11205 return;
11206 }
11207 let relevantEvents = this.relevantEvents;
11208 let initialHit = this.hitDragging.initialHit;
11209 let initialContext = this.component.context;
11210 // states based on new hit
11211 let receivingContext = null;
11212 let mutation = null;
11213 let mutatedRelevantEvents = null;
11214 let isInvalid = false;
11215 let interaction = {
11216 affectedEvents: relevantEvents,
11217 mutatedEvents: createEmptyEventStore(),
11218 isEvent: true,
11219 };
11220 if (hit) {
11221 receivingContext = hit.context;
11222 let receivingOptions = receivingContext.options;
11223 if (initialContext === receivingContext ||
11224 (receivingOptions.editable && receivingOptions.droppable)) {
11225 mutation = computeEventMutation(initialHit, hit, this.eventRange.instance.range.start, receivingContext.getCurrentData().pluginHooks.eventDragMutationMassagers);
11226 if (mutation) {
11227 mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, receivingContext.getCurrentData().eventUiBases, mutation, receivingContext);
11228 interaction.mutatedEvents = mutatedRelevantEvents;
11229 if (!isInteractionValid(interaction, hit.dateProfile, receivingContext)) {
11230 isInvalid = true;
11231 mutation = null;
11232 mutatedRelevantEvents = null;
11233 interaction.mutatedEvents = createEmptyEventStore();
11234 }
11235 }
11236 }
11237 else {
11238 receivingContext = null;
11239 }
11240 }
11241 this.displayDrag(receivingContext, interaction);
11242 if (!isInvalid) {
11243 enableCursor();
11244 }
11245 else {
11246 disableCursor();
11247 }
11248 if (!isFinal) {
11249 if (initialContext === receivingContext && // TODO: write test for this
11250 isHitsEqual(initialHit, hit)) {
11251 mutation = null;
11252 }
11253 this.dragging.setMirrorNeedsRevert(!mutation);
11254 // render the mirror if no already-rendered mirror
11255 // TODO: wish we could somehow wait for dispatch to guarantee render
11256 this.dragging.setMirrorIsVisible(!hit || !this.subjectEl.getRootNode().querySelector('.fc-event-mirror'));
11257 // assign states based on new hit
11258 this.receivingContext = receivingContext;
11259 this.validMutation = mutation;
11260 this.mutatedRelevantEvents = mutatedRelevantEvents;
11261 }
11262 };
11263 this.handlePointerUp = () => {
11264 if (!this.isDragging) {
11265 this.cleanup(); // because handleDragEnd won't fire
11266 }
11267 };
11268 this.handleDragEnd = (ev) => {
11269 if (this.isDragging) {
11270 let initialContext = this.component.context;
11271 let initialView = initialContext.viewApi;
11272 let { receivingContext, validMutation } = this;
11273 let eventDef = this.eventRange.def;
11274 let eventInstance = this.eventRange.instance;
11275 let eventApi = new EventImpl(initialContext, eventDef, eventInstance);
11276 let relevantEvents = this.relevantEvents;
11277 let mutatedRelevantEvents = this.mutatedRelevantEvents;
11278 let { finalHit } = this.hitDragging;
11279 this.clearDrag(); // must happen after revert animation
11280 initialContext.emitter.trigger('eventDragStop', {
11281 el: this.subjectEl,
11282 event: eventApi,
11283 jsEvent: ev.origEvent,
11284 view: initialView,
11285 });
11286 if (validMutation) {
11287 // dropped within same calendar
11288 if (receivingContext === initialContext) {
11289 let updatedEventApi = new EventImpl(initialContext, mutatedRelevantEvents.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null);
11290 initialContext.dispatch({
11291 type: 'MERGE_EVENTS',
11292 eventStore: mutatedRelevantEvents,
11293 });
11294 let eventChangeArg = {
11295 oldEvent: eventApi,
11296 event: updatedEventApi,
11297 relatedEvents: buildEventApis(mutatedRelevantEvents, initialContext, eventInstance),
11298 revert() {
11299 initialContext.dispatch({
11300 type: 'MERGE_EVENTS',
11301 eventStore: relevantEvents, // the pre-change data
11302 });
11303 },
11304 };
11305 let transformed = {};
11306 for (let transformer of initialContext.getCurrentData().pluginHooks.eventDropTransformers) {
11307 Object.assign(transformed, transformer(validMutation, initialContext));
11308 }
11309 initialContext.emitter.trigger('eventDrop', Object.assign(Object.assign(Object.assign({}, eventChangeArg), transformed), { el: ev.subjectEl, delta: validMutation.datesDelta, jsEvent: ev.origEvent, view: initialView }));
11310 initialContext.emitter.trigger('eventChange', eventChangeArg);
11311 // dropped in different calendar
11312 }
11313 else if (receivingContext) {
11314 let eventRemoveArg = {
11315 event: eventApi,
11316 relatedEvents: buildEventApis(relevantEvents, initialContext, eventInstance),
11317 revert() {
11318 initialContext.dispatch({
11319 type: 'MERGE_EVENTS',
11320 eventStore: relevantEvents,
11321 });
11322 },
11323 };
11324 initialContext.emitter.trigger('eventLeave', Object.assign(Object.assign({}, eventRemoveArg), { draggedEl: ev.subjectEl, view: initialView }));
11325 initialContext.dispatch({
11326 type: 'REMOVE_EVENTS',
11327 eventStore: relevantEvents,
11328 });
11329 initialContext.emitter.trigger('eventRemove', eventRemoveArg);
11330 let addedEventDef = mutatedRelevantEvents.defs[eventDef.defId];
11331 let addedEventInstance = mutatedRelevantEvents.instances[eventInstance.instanceId];
11332 let addedEventApi = new EventImpl(receivingContext, addedEventDef, addedEventInstance);
11333 receivingContext.dispatch({
11334 type: 'MERGE_EVENTS',
11335 eventStore: mutatedRelevantEvents,
11336 });
11337 let eventAddArg = {
11338 event: addedEventApi,
11339 relatedEvents: buildEventApis(mutatedRelevantEvents, receivingContext, addedEventInstance),
11340 revert() {
11341 receivingContext.dispatch({
11342 type: 'REMOVE_EVENTS',
11343 eventStore: mutatedRelevantEvents,
11344 });
11345 },
11346 };
11347 receivingContext.emitter.trigger('eventAdd', eventAddArg);
11348 if (ev.isTouch) {
11349 receivingContext.dispatch({
11350 type: 'SELECT_EVENT',
11351 eventInstanceId: eventInstance.instanceId,
11352 });
11353 }
11354 receivingContext.emitter.trigger('drop', Object.assign(Object.assign({}, buildDatePointApiWithContext(finalHit.dateSpan, receivingContext)), { draggedEl: ev.subjectEl, jsEvent: ev.origEvent, view: finalHit.context.viewApi }));
11355 receivingContext.emitter.trigger('eventReceive', Object.assign(Object.assign({}, eventAddArg), { draggedEl: ev.subjectEl, view: finalHit.context.viewApi }));
11356 }
11357 }
11358 else {
11359 initialContext.emitter.trigger('_noEventDrop');
11360 }
11361 }
11362 this.cleanup();
11363 };
11364 let { component } = this;
11365 let { options } = component.context;
11366 let dragging = this.dragging = new FeaturefulElementDragging(settings.el);
11367 dragging.pointer.selector = EventDragging.SELECTOR;
11368 dragging.touchScrollAllowed = false;
11369 dragging.autoScroller.isEnabled = options.dragScroll;
11370 let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsStore);
11371 hitDragging.useSubjectCenter = settings.useEventCenter;
11372 hitDragging.emitter.on('pointerdown', this.handlePointerDown);
11373 hitDragging.emitter.on('dragstart', this.handleDragStart);
11374 hitDragging.emitter.on('hitupdate', this.handleHitUpdate);
11375 hitDragging.emitter.on('pointerup', this.handlePointerUp);
11376 hitDragging.emitter.on('dragend', this.handleDragEnd);
11377 }
11378 destroy() {
11379 this.dragging.destroy();
11380 }
11381 // render a drag state on the next receivingCalendar
11382 displayDrag(nextContext, state) {
11383 let initialContext = this.component.context;
11384 let prevContext = this.receivingContext;
11385 // does the previous calendar need to be cleared?
11386 if (prevContext && prevContext !== nextContext) {
11387 // does the initial calendar need to be cleared?
11388 // if so, don't clear all the way. we still need to to hide the affectedEvents
11389 if (prevContext === initialContext) {
11390 prevContext.dispatch({
11391 type: 'SET_EVENT_DRAG',
11392 state: {
11393 affectedEvents: state.affectedEvents,
11394 mutatedEvents: createEmptyEventStore(),
11395 isEvent: true,
11396 },
11397 });
11398 // completely clear the old calendar if it wasn't the initial
11399 }
11400 else {
11401 prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
11402 }
11403 }
11404 if (nextContext) {
11405 nextContext.dispatch({ type: 'SET_EVENT_DRAG', state });
11406 }
11407 }
11408 clearDrag() {
11409 let initialCalendar = this.component.context;
11410 let { receivingContext } = this;
11411 if (receivingContext) {
11412 receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
11413 }
11414 // the initial calendar might have an dummy drag state from displayDrag
11415 if (initialCalendar !== receivingContext) {
11416 initialCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' });
11417 }
11418 }
11419 cleanup() {
11420 this.subjectSeg = null;
11421 this.isDragging = false;
11422 this.eventRange = null;
11423 this.relevantEvents = null;
11424 this.receivingContext = null;
11425 this.validMutation = null;
11426 this.mutatedRelevantEvents = null;
11427 }
11428 }
11429 // TODO: test this in IE11
11430 // QUESTION: why do we need it on the resizable???
11431 EventDragging.SELECTOR = '.fc-event-draggable, .fc-event-resizable';
11432 function computeEventMutation(hit0, hit1, eventInstanceStart, massagers) {
11433 let dateSpan0 = hit0.dateSpan;
11434 let dateSpan1 = hit1.dateSpan;
11435 let date0 = dateSpan0.range.start;
11436 let date1 = dateSpan1.range.start;
11437 let standardProps = {};
11438 if (dateSpan0.allDay !== dateSpan1.allDay) {
11439 standardProps.allDay = dateSpan1.allDay;
11440 standardProps.hasEnd = hit1.context.options.allDayMaintainDuration;
11441 if (dateSpan1.allDay) {
11442 // means date1 is already start-of-day,
11443 // but date0 needs to be converted
11444 date0 = startOfDay(eventInstanceStart);
11445 }
11446 else {
11447 // Moving from allDate->timed
11448 // Doesn't matter where on the event the drag began, mutate the event's start-date to date1
11449 date0 = eventInstanceStart;
11450 }
11451 }
11452 let delta = diffDates(date0, date1, hit0.context.dateEnv, hit0.componentId === hit1.componentId ?
11453 hit0.largeUnit :
11454 null);
11455 if (delta.milliseconds) { // has hours/minutes/seconds
11456 standardProps.allDay = false;
11457 }
11458 let mutation = {
11459 datesDelta: delta,
11460 standardProps,
11461 };
11462 for (let massager of massagers) {
11463 massager(mutation, hit0, hit1);
11464 }
11465 return mutation;
11466 }
11467 function getComponentTouchDelay(component) {
11468 let { options } = component.context;
11469 let delay = options.eventLongPressDelay;
11470 if (delay == null) {
11471 delay = options.longPressDelay;
11472 }
11473 return delay;
11474 }
11475
11476 class EventResizing extends Interaction {
11477 constructor(settings) {
11478 super(settings);
11479 // internal state
11480 this.draggingSegEl = null;
11481 this.draggingSeg = null; // TODO: rename to resizingSeg? subjectSeg?
11482 this.eventRange = null;
11483 this.relevantEvents = null;
11484 this.validMutation = null;
11485 this.mutatedRelevantEvents = null;
11486 this.handlePointerDown = (ev) => {
11487 let { component } = this;
11488 let segEl = this.querySegEl(ev);
11489 let seg = getElSeg(segEl);
11490 let eventRange = this.eventRange = seg.eventRange;
11491 this.dragging.minDistance = component.context.options.eventDragMinDistance;
11492 // if touch, need to be working with a selected event
11493 this.dragging.setIgnoreMove(!this.component.isValidSegDownEl(ev.origEvent.target) ||
11494 (ev.isTouch && this.component.props.eventSelection !== eventRange.instance.instanceId));
11495 };
11496 this.handleDragStart = (ev) => {
11497 let { context } = this.component;
11498 let eventRange = this.eventRange;
11499 this.relevantEvents = getRelevantEvents(context.getCurrentData().eventStore, this.eventRange.instance.instanceId);
11500 let segEl = this.querySegEl(ev);
11501 this.draggingSegEl = segEl;
11502 this.draggingSeg = getElSeg(segEl);
11503 context.calendarApi.unselect();
11504 context.emitter.trigger('eventResizeStart', {
11505 el: segEl,
11506 event: new EventImpl(context, eventRange.def, eventRange.instance),
11507 jsEvent: ev.origEvent,
11508 view: context.viewApi,
11509 });
11510 };
11511 this.handleHitUpdate = (hit, isFinal, ev) => {
11512 let { context } = this.component;
11513 let relevantEvents = this.relevantEvents;
11514 let initialHit = this.hitDragging.initialHit;
11515 let eventInstance = this.eventRange.instance;
11516 let mutation = null;
11517 let mutatedRelevantEvents = null;
11518 let isInvalid = false;
11519 let interaction = {
11520 affectedEvents: relevantEvents,
11521 mutatedEvents: createEmptyEventStore(),
11522 isEvent: true,
11523 };
11524 if (hit) {
11525 let disallowed = hit.componentId === initialHit.componentId
11526 && this.isHitComboAllowed
11527 && !this.isHitComboAllowed(initialHit, hit);
11528 if (!disallowed) {
11529 mutation = computeMutation(initialHit, hit, ev.subjectEl.classList.contains('fc-event-resizer-start'), eventInstance.range);
11530 }
11531 }
11532 if (mutation) {
11533 mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, context.getCurrentData().eventUiBases, mutation, context);
11534 interaction.mutatedEvents = mutatedRelevantEvents;
11535 if (!isInteractionValid(interaction, hit.dateProfile, context)) {
11536 isInvalid = true;
11537 mutation = null;
11538 mutatedRelevantEvents = null;
11539 interaction.mutatedEvents = null;
11540 }
11541 }
11542 if (mutatedRelevantEvents) {
11543 context.dispatch({
11544 type: 'SET_EVENT_RESIZE',
11545 state: interaction,
11546 });
11547 }
11548 else {
11549 context.dispatch({ type: 'UNSET_EVENT_RESIZE' });
11550 }
11551 if (!isInvalid) {
11552 enableCursor();
11553 }
11554 else {
11555 disableCursor();
11556 }
11557 if (!isFinal) {
11558 if (mutation && isHitsEqual(initialHit, hit)) {
11559 mutation = null;
11560 }
11561 this.validMutation = mutation;
11562 this.mutatedRelevantEvents = mutatedRelevantEvents;
11563 }
11564 };
11565 this.handleDragEnd = (ev) => {
11566 let { context } = this.component;
11567 let eventDef = this.eventRange.def;
11568 let eventInstance = this.eventRange.instance;
11569 let eventApi = new EventImpl(context, eventDef, eventInstance);
11570 let relevantEvents = this.relevantEvents;
11571 let mutatedRelevantEvents = this.mutatedRelevantEvents;
11572 context.emitter.trigger('eventResizeStop', {
11573 el: this.draggingSegEl,
11574 event: eventApi,
11575 jsEvent: ev.origEvent,
11576 view: context.viewApi,
11577 });
11578 if (this.validMutation) {
11579 let updatedEventApi = new EventImpl(context, mutatedRelevantEvents.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null);
11580 context.dispatch({
11581 type: 'MERGE_EVENTS',
11582 eventStore: mutatedRelevantEvents,
11583 });
11584 let eventChangeArg = {
11585 oldEvent: eventApi,
11586 event: updatedEventApi,
11587 relatedEvents: buildEventApis(mutatedRelevantEvents, context, eventInstance),
11588 revert() {
11589 context.dispatch({
11590 type: 'MERGE_EVENTS',
11591 eventStore: relevantEvents, // the pre-change events
11592 });
11593 },
11594 };
11595 context.emitter.trigger('eventResize', Object.assign(Object.assign({}, eventChangeArg), { el: this.draggingSegEl, startDelta: this.validMutation.startDelta || createDuration(0), endDelta: this.validMutation.endDelta || createDuration(0), jsEvent: ev.origEvent, view: context.viewApi }));
11596 context.emitter.trigger('eventChange', eventChangeArg);
11597 }
11598 else {
11599 context.emitter.trigger('_noEventResize');
11600 }
11601 // reset all internal state
11602 this.draggingSeg = null;
11603 this.relevantEvents = null;
11604 this.validMutation = null;
11605 // okay to keep eventInstance around. useful to set it in handlePointerDown
11606 };
11607 let { component } = settings;
11608 let dragging = this.dragging = new FeaturefulElementDragging(settings.el);
11609 dragging.pointer.selector = '.fc-event-resizer';
11610 dragging.touchScrollAllowed = false;
11611 dragging.autoScroller.isEnabled = component.context.options.dragScroll;
11612 let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings));
11613 hitDragging.emitter.on('pointerdown', this.handlePointerDown);
11614 hitDragging.emitter.on('dragstart', this.handleDragStart);
11615 hitDragging.emitter.on('hitupdate', this.handleHitUpdate);
11616 hitDragging.emitter.on('dragend', this.handleDragEnd);
11617 }
11618 destroy() {
11619 this.dragging.destroy();
11620 }
11621 querySegEl(ev) {
11622 return elementClosest(ev.subjectEl, '.fc-event');
11623 }
11624 }
11625 function computeMutation(hit0, hit1, isFromStart, instanceRange) {
11626 let dateEnv = hit0.context.dateEnv;
11627 let date0 = hit0.dateSpan.range.start;
11628 let date1 = hit1.dateSpan.range.start;
11629 let delta = diffDates(date0, date1, dateEnv, hit0.largeUnit);
11630 if (isFromStart) {
11631 if (dateEnv.add(instanceRange.start, delta) < instanceRange.end) {
11632 return { startDelta: delta };
11633 }
11634 }
11635 else if (dateEnv.add(instanceRange.end, delta) > instanceRange.start) {
11636 return { endDelta: delta };
11637 }
11638 return null;
11639 }
11640
11641 class UnselectAuto {
11642 constructor(context) {
11643 this.context = context;
11644 this.isRecentPointerDateSelect = false; // wish we could use a selector to detect date selection, but uses hit system
11645 this.matchesCancel = false;
11646 this.matchesEvent = false;
11647 this.onSelect = (selectInfo) => {
11648 if (selectInfo.jsEvent) {
11649 this.isRecentPointerDateSelect = true;
11650 }
11651 };
11652 this.onDocumentPointerDown = (pev) => {
11653 let unselectCancel = this.context.options.unselectCancel;
11654 let downEl = getEventTargetViaRoot(pev.origEvent);
11655 this.matchesCancel = !!elementClosest(downEl, unselectCancel);
11656 this.matchesEvent = !!elementClosest(downEl, EventDragging.SELECTOR); // interaction started on an event?
11657 };
11658 this.onDocumentPointerUp = (pev) => {
11659 let { context } = this;
11660 let { documentPointer } = this;
11661 let calendarState = context.getCurrentData();
11662 // touch-scrolling should never unfocus any type of selection
11663 if (!documentPointer.wasTouchScroll) {
11664 if (calendarState.dateSelection && // an existing date selection?
11665 !this.isRecentPointerDateSelect // a new pointer-initiated date selection since last onDocumentPointerUp?
11666 ) {
11667 let unselectAuto = context.options.unselectAuto;
11668 if (unselectAuto && (!unselectAuto || !this.matchesCancel)) {
11669 context.calendarApi.unselect(pev);
11670 }
11671 }
11672 if (calendarState.eventSelection && // an existing event selected?
11673 !this.matchesEvent // interaction DIDN'T start on an event
11674 ) {
11675 context.dispatch({ type: 'UNSELECT_EVENT' });
11676 }
11677 }
11678 this.isRecentPointerDateSelect = false;
11679 };
11680 let documentPointer = this.documentPointer = new PointerDragging(document);
11681 documentPointer.shouldIgnoreMove = true;
11682 documentPointer.shouldWatchScroll = false;
11683 documentPointer.emitter.on('pointerdown', this.onDocumentPointerDown);
11684 documentPointer.emitter.on('pointerup', this.onDocumentPointerUp);
11685 /*
11686 TODO: better way to know about whether there was a selection with the pointer
11687 */
11688 context.emitter.on('select', this.onSelect);
11689 }
11690 destroy() {
11691 this.context.emitter.off('select', this.onSelect);
11692 this.documentPointer.destroy();
11693 }
11694 }
11695
11696 const OPTION_REFINERS$3 = {
11697 fixedMirrorParent: identity,
11698 };
11699 const LISTENER_REFINERS = {
11700 dateClick: identity,
11701 eventDragStart: identity,
11702 eventDragStop: identity,
11703 eventDrop: identity,
11704 eventResizeStart: identity,
11705 eventResizeStop: identity,
11706 eventResize: identity,
11707 drop: identity,
11708 eventReceive: identity,
11709 eventLeave: identity,
11710 };
11711
11712 /*
11713 Given an already instantiated draggable object for one-or-more elements,
11714 Interprets any dragging as an attempt to drag an events that lives outside
11715 of a calendar onto a calendar.
11716 */
11717 class ExternalElementDragging {
11718 constructor(dragging, suppliedDragMeta) {
11719 this.receivingContext = null;
11720 this.droppableEvent = null; // will exist for all drags, even if create:false
11721 this.suppliedDragMeta = null;
11722 this.dragMeta = null;
11723 this.handleDragStart = (ev) => {
11724 this.dragMeta = this.buildDragMeta(ev.subjectEl);
11725 };
11726 this.handleHitUpdate = (hit, isFinal, ev) => {
11727 let { dragging } = this.hitDragging;
11728 let receivingContext = null;
11729 let droppableEvent = null;
11730 let isInvalid = false;
11731 let interaction = {
11732 affectedEvents: createEmptyEventStore(),
11733 mutatedEvents: createEmptyEventStore(),
11734 isEvent: this.dragMeta.create,
11735 };
11736 if (hit) {
11737 receivingContext = hit.context;
11738 if (this.canDropElOnCalendar(ev.subjectEl, receivingContext)) {
11739 droppableEvent = computeEventForDateSpan(hit.dateSpan, this.dragMeta, receivingContext);
11740 interaction.mutatedEvents = eventTupleToStore(droppableEvent);
11741 isInvalid = !isInteractionValid(interaction, hit.dateProfile, receivingContext);
11742 if (isInvalid) {
11743 interaction.mutatedEvents = createEmptyEventStore();
11744 droppableEvent = null;
11745 }
11746 }
11747 }
11748 this.displayDrag(receivingContext, interaction);
11749 // show mirror if no already-rendered mirror element OR if we are shutting down the mirror (?)
11750 // TODO: wish we could somehow wait for dispatch to guarantee render
11751 dragging.setMirrorIsVisible(isFinal || !droppableEvent || !document.querySelector('.fc-event-mirror'));
11752 if (!isInvalid) {
11753 enableCursor();
11754 }
11755 else {
11756 disableCursor();
11757 }
11758 if (!isFinal) {
11759 dragging.setMirrorNeedsRevert(!droppableEvent);
11760 this.receivingContext = receivingContext;
11761 this.droppableEvent = droppableEvent;
11762 }
11763 };
11764 this.handleDragEnd = (pev) => {
11765 let { receivingContext, droppableEvent } = this;
11766 this.clearDrag();
11767 if (receivingContext && droppableEvent) {
11768 let finalHit = this.hitDragging.finalHit;
11769 let finalView = finalHit.context.viewApi;
11770 let dragMeta = this.dragMeta;
11771 receivingContext.emitter.trigger('drop', Object.assign(Object.assign({}, buildDatePointApiWithContext(finalHit.dateSpan, receivingContext)), { draggedEl: pev.subjectEl, jsEvent: pev.origEvent, view: finalView }));
11772 if (dragMeta.create) {
11773 let addingEvents = eventTupleToStore(droppableEvent);
11774 receivingContext.dispatch({
11775 type: 'MERGE_EVENTS',
11776 eventStore: addingEvents,
11777 });
11778 if (pev.isTouch) {
11779 receivingContext.dispatch({
11780 type: 'SELECT_EVENT',
11781 eventInstanceId: droppableEvent.instance.instanceId,
11782 });
11783 }
11784 // signal that an external event landed
11785 receivingContext.emitter.trigger('eventReceive', {
11786 event: new EventImpl(receivingContext, droppableEvent.def, droppableEvent.instance),
11787 relatedEvents: [],
11788 revert() {
11789 receivingContext.dispatch({
11790 type: 'REMOVE_EVENTS',
11791 eventStore: addingEvents,
11792 });
11793 },
11794 draggedEl: pev.subjectEl,
11795 view: finalView,
11796 });
11797 }
11798 }
11799 this.receivingContext = null;
11800 this.droppableEvent = null;
11801 };
11802 let hitDragging = this.hitDragging = new HitDragging(dragging, interactionSettingsStore);
11803 hitDragging.requireInitial = false; // will start outside of a component
11804 hitDragging.emitter.on('dragstart', this.handleDragStart);
11805 hitDragging.emitter.on('hitupdate', this.handleHitUpdate);
11806 hitDragging.emitter.on('dragend', this.handleDragEnd);
11807 this.suppliedDragMeta = suppliedDragMeta;
11808 }
11809 buildDragMeta(subjectEl) {
11810 if (typeof this.suppliedDragMeta === 'object') {
11811 return parseDragMeta(this.suppliedDragMeta);
11812 }
11813 if (typeof this.suppliedDragMeta === 'function') {
11814 return parseDragMeta(this.suppliedDragMeta(subjectEl));
11815 }
11816 return getDragMetaFromEl(subjectEl);
11817 }
11818 displayDrag(nextContext, state) {
11819 let prevContext = this.receivingContext;
11820 if (prevContext && prevContext !== nextContext) {
11821 prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
11822 }
11823 if (nextContext) {
11824 nextContext.dispatch({ type: 'SET_EVENT_DRAG', state });
11825 }
11826 }
11827 clearDrag() {
11828 if (this.receivingContext) {
11829 this.receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' });
11830 }
11831 }
11832 canDropElOnCalendar(el, receivingContext) {
11833 let dropAccept = receivingContext.options.dropAccept;
11834 if (typeof dropAccept === 'function') {
11835 return dropAccept.call(receivingContext.calendarApi, el);
11836 }
11837 if (typeof dropAccept === 'string' && dropAccept) {
11838 return Boolean(elementMatches(el, dropAccept));
11839 }
11840 return true;
11841 }
11842 }
11843 // Utils for computing event store from the DragMeta
11844 // ----------------------------------------------------------------------------------------------------
11845 function computeEventForDateSpan(dateSpan, dragMeta, context) {
11846 let defProps = Object.assign({}, dragMeta.leftoverProps);
11847 for (let transform of context.pluginHooks.externalDefTransforms) {
11848 Object.assign(defProps, transform(dateSpan, dragMeta));
11849 }
11850 let { refined, extra } = refineEventDef(defProps, context);
11851 let def = parseEventDef(refined, extra, dragMeta.sourceId, dateSpan.allDay, context.options.forceEventDuration || Boolean(dragMeta.duration), // hasEnd
11852 context);
11853 let start = dateSpan.range.start;
11854 // only rely on time info if drop zone is all-day,
11855 // otherwise, we already know the time
11856 if (dateSpan.allDay && dragMeta.startTime) {
11857 start = context.dateEnv.add(start, dragMeta.startTime);
11858 }
11859 let end = dragMeta.duration ?
11860 context.dateEnv.add(start, dragMeta.duration) :
11861 getDefaultEventEnd(dateSpan.allDay, start, context);
11862 let instance = createEventInstance(def.defId, { start, end });
11863 return { def, instance };
11864 }
11865 // Utils for extracting data from element
11866 // ----------------------------------------------------------------------------------------------------
11867 function getDragMetaFromEl(el) {
11868 let str = getEmbeddedElData(el, 'event');
11869 let obj = str ?
11870 JSON.parse(str) :
11871 { create: false }; // if no embedded data, assume no event creation
11872 return parseDragMeta(obj);
11873 }
11874 config.dataAttrPrefix = '';
11875 function getEmbeddedElData(el, name) {
11876 let prefix = config.dataAttrPrefix;
11877 let prefixedName = (prefix ? prefix + '-' : '') + name;
11878 return el.getAttribute('data-' + prefixedName) || '';
11879 }
11880
11881 /*
11882 Makes an element (that is *external* to any calendar) draggable.
11883 Can pass in data that determines how an event will be created when dropped onto a calendar.
11884 Leverages FullCalendar's internal drag-n-drop functionality WITHOUT a third-party drag system.
11885 */
11886 class ExternalDraggable {
11887 constructor(el, settings = {}) {
11888 this.handlePointerDown = (ev) => {
11889 let { dragging } = this;
11890 let { minDistance, longPressDelay } = this.settings;
11891 dragging.minDistance =
11892 minDistance != null ?
11893 minDistance :
11894 (ev.isTouch ? 0 : BASE_OPTION_DEFAULTS.eventDragMinDistance);
11895 dragging.delay =
11896 ev.isTouch ? // TODO: eventually read eventLongPressDelay instead vvv
11897 (longPressDelay != null ? longPressDelay : BASE_OPTION_DEFAULTS.longPressDelay) :
11898 0;
11899 };
11900 this.handleDragStart = (ev) => {
11901 if (ev.isTouch &&
11902 this.dragging.delay &&
11903 ev.subjectEl.classList.contains('fc-event')) {
11904 this.dragging.mirror.getMirrorEl().classList.add('fc-event-selected');
11905 }
11906 };
11907 this.settings = settings;
11908 let dragging = this.dragging = new FeaturefulElementDragging(el);
11909 dragging.touchScrollAllowed = false;
11910 if (settings.itemSelector != null) {
11911 dragging.pointer.selector = settings.itemSelector;
11912 }
11913 if (settings.appendTo != null) {
11914 dragging.mirror.parentNode = settings.appendTo; // TODO: write tests
11915 }
11916 dragging.emitter.on('pointerdown', this.handlePointerDown);
11917 dragging.emitter.on('dragstart', this.handleDragStart);
11918 new ExternalElementDragging(dragging, settings.eventData); // eslint-disable-line no-new
11919 }
11920 destroy() {
11921 this.dragging.destroy();
11922 }
11923 }
11924
11925 /*
11926 Detects when a *THIRD-PARTY* drag-n-drop system interacts with elements.
11927 The third-party system is responsible for drawing the visuals effects of the drag.
11928 This class simply monitors for pointer movements and fires events.
11929 It also has the ability to hide the moving element (the "mirror") during the drag.
11930 */
11931 class InferredElementDragging extends ElementDragging {
11932 constructor(containerEl) {
11933 super(containerEl);
11934 this.shouldIgnoreMove = false;
11935 this.mirrorSelector = '';
11936 this.currentMirrorEl = null;
11937 this.handlePointerDown = (ev) => {
11938 this.emitter.trigger('pointerdown', ev);
11939 if (!this.shouldIgnoreMove) {
11940 // fire dragstart right away. does not support delay or min-distance
11941 this.emitter.trigger('dragstart', ev);
11942 }
11943 };
11944 this.handlePointerMove = (ev) => {
11945 if (!this.shouldIgnoreMove) {
11946 this.emitter.trigger('dragmove', ev);
11947 }
11948 };
11949 this.handlePointerUp = (ev) => {
11950 this.emitter.trigger('pointerup', ev);
11951 if (!this.shouldIgnoreMove) {
11952 // fire dragend right away. does not support a revert animation
11953 this.emitter.trigger('dragend', ev);
11954 }
11955 };
11956 let pointer = this.pointer = new PointerDragging(containerEl);
11957 pointer.emitter.on('pointerdown', this.handlePointerDown);
11958 pointer.emitter.on('pointermove', this.handlePointerMove);
11959 pointer.emitter.on('pointerup', this.handlePointerUp);
11960 }
11961 destroy() {
11962 this.pointer.destroy();
11963 }
11964 setIgnoreMove(bool) {
11965 this.shouldIgnoreMove = bool;
11966 }
11967 setMirrorIsVisible(bool) {
11968 if (bool) {
11969 // restore a previously hidden element.
11970 // use the reference in case the selector class has already been removed.
11971 if (this.currentMirrorEl) {
11972 this.currentMirrorEl.style.visibility = '';
11973 this.currentMirrorEl = null;
11974 }
11975 }
11976 else {
11977 let mirrorEl = this.mirrorSelector
11978 // TODO: somehow query FullCalendars WITHIN shadow-roots
11979 ? document.querySelector(this.mirrorSelector)
11980 : null;
11981 if (mirrorEl) {
11982 this.currentMirrorEl = mirrorEl;
11983 mirrorEl.style.visibility = 'hidden';
11984 }
11985 }
11986 }
11987 }
11988
11989 /*
11990 Bridges third-party drag-n-drop systems with FullCalendar.
11991 Must be instantiated and destroyed by caller.
11992 */
11993 class ThirdPartyDraggable {
11994 constructor(containerOrSettings, settings) {
11995 let containerEl = document;
11996 if (
11997 // wish we could just test instanceof EventTarget, but doesn't work in IE11
11998 containerOrSettings === document ||
11999 containerOrSettings instanceof Element) {
12000 containerEl = containerOrSettings;
12001 settings = settings || {};
12002 }
12003 else {
12004 settings = (containerOrSettings || {});
12005 }
12006 let dragging = this.dragging = new InferredElementDragging(containerEl);
12007 if (typeof settings.itemSelector === 'string') {
12008 dragging.pointer.selector = settings.itemSelector;
12009 }
12010 else if (containerEl === document) {
12011 dragging.pointer.selector = '[data-event]';
12012 }
12013 if (typeof settings.mirrorSelector === 'string') {
12014 dragging.mirrorSelector = settings.mirrorSelector;
12015 }
12016 let externalDragging = new ExternalElementDragging(dragging, settings.eventData);
12017 // The hit-detection system requires that the dnd-mirror-element be pointer-events:none,
12018 // but this can't be guaranteed for third-party draggables, so disable
12019 externalDragging.hitDragging.disablePointCheck = true;
12020 }
12021 destroy() {
12022 this.dragging.destroy();
12023 }
12024 }
12025
12026 var index$4 = createPlugin({
12027 name: '@fullcalendar/interaction',
12028 componentInteractions: [DateClicking, DateSelecting, EventDragging, EventResizing],
12029 calendarInteractions: [UnselectAuto],
12030 elementDraggingImpl: FeaturefulElementDragging,
12031 optionRefiners: OPTION_REFINERS$3,
12032 listenerRefiners: LISTENER_REFINERS,
12033 });
12034
12035 /* An abstract class for the daygrid views, as well as month view. Renders one or more rows of day cells.
12036 ----------------------------------------------------------------------------------------------------------------------*/
12037 // It is a manager for a Table subcomponent, which does most of the heavy lifting.
12038 // It is responsible for managing width/height.
12039 class TableView extends DateComponent {
12040 constructor() {
12041 super(...arguments);
12042 this.headerElRef = d();
12043 }
12044 renderSimpleLayout(headerRowContent, bodyContent) {
12045 let { props, context } = this;
12046 let sections = [];
12047 let stickyHeaderDates = getStickyHeaderDates(context.options);
12048 if (headerRowContent) {
12049 sections.push({
12050 type: 'header',
12051 key: 'header',
12052 isSticky: stickyHeaderDates,
12053 chunk: {
12054 elRef: this.headerElRef,
12055 tableClassName: 'fc-col-header',
12056 rowContent: headerRowContent,
12057 },
12058 });
12059 }
12060 sections.push({
12061 type: 'body',
12062 key: 'body',
12063 liquid: true,
12064 chunk: { content: bodyContent },
12065 });
12066 return (y(ViewContainer, { elClasses: ['fc-daygrid'], viewSpec: context.viewSpec },
12067 y(SimpleScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, cols: [] /* TODO: make optional? */, sections: sections })));
12068 }
12069 renderHScrollLayout(headerRowContent, bodyContent, colCnt, dayMinWidth) {
12070 let ScrollGrid = this.context.pluginHooks.scrollGridImpl;
12071 if (!ScrollGrid) {
12072 throw new Error('No ScrollGrid implementation');
12073 }
12074 let { props, context } = this;
12075 let stickyHeaderDates = !props.forPrint && getStickyHeaderDates(context.options);
12076 let stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(context.options);
12077 let sections = [];
12078 if (headerRowContent) {
12079 sections.push({
12080 type: 'header',
12081 key: 'header',
12082 isSticky: stickyHeaderDates,
12083 chunks: [{
12084 key: 'main',
12085 elRef: this.headerElRef,
12086 tableClassName: 'fc-col-header',
12087 rowContent: headerRowContent,
12088 }],
12089 });
12090 }
12091 sections.push({
12092 type: 'body',
12093 key: 'body',
12094 liquid: true,
12095 chunks: [{
12096 key: 'main',
12097 content: bodyContent,
12098 }],
12099 });
12100 if (stickyFooterScrollbar) {
12101 sections.push({
12102 type: 'footer',
12103 key: 'footer',
12104 isSticky: true,
12105 chunks: [{
12106 key: 'main',
12107 content: renderScrollShim,
12108 }],
12109 });
12110 }
12111 return (y(ViewContainer, { elClasses: ['fc-daygrid'], viewSpec: context.viewSpec },
12112 y(ScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, forPrint: props.forPrint, collapsibleWidth: props.forPrint, colGroups: [{ cols: [{ span: colCnt, minWidth: dayMinWidth }] }], sections: sections })));
12113 }
12114 }
12115
12116 function splitSegsByRow(segs, rowCnt) {
12117 let byRow = [];
12118 for (let i = 0; i < rowCnt; i += 1) {
12119 byRow[i] = [];
12120 }
12121 for (let seg of segs) {
12122 byRow[seg.row].push(seg);
12123 }
12124 return byRow;
12125 }
12126 function splitSegsByFirstCol(segs, colCnt) {
12127 let byCol = [];
12128 for (let i = 0; i < colCnt; i += 1) {
12129 byCol[i] = [];
12130 }
12131 for (let seg of segs) {
12132 byCol[seg.firstCol].push(seg);
12133 }
12134 return byCol;
12135 }
12136 function splitInteractionByRow(ui, rowCnt) {
12137 let byRow = [];
12138 if (!ui) {
12139 for (let i = 0; i < rowCnt; i += 1) {
12140 byRow[i] = null;
12141 }
12142 }
12143 else {
12144 for (let i = 0; i < rowCnt; i += 1) {
12145 byRow[i] = {
12146 affectedInstances: ui.affectedInstances,
12147 isEvent: ui.isEvent,
12148 segs: [],
12149 };
12150 }
12151 for (let seg of ui.segs) {
12152 byRow[seg.row].segs.push(seg);
12153 }
12154 }
12155 return byRow;
12156 }
12157
12158 const DEFAULT_TABLE_EVENT_TIME_FORMAT = createFormatter({
12159 hour: 'numeric',
12160 minute: '2-digit',
12161 omitZeroMinute: true,
12162 meridiem: 'narrow',
12163 });
12164 function hasListItemDisplay(seg) {
12165 let { display } = seg.eventRange.ui;
12166 return display === 'list-item' || (display === 'auto' &&
12167 !seg.eventRange.def.allDay &&
12168 seg.firstCol === seg.lastCol && // can't be multi-day
12169 seg.isStart && // "
12170 seg.isEnd // "
12171 );
12172 }
12173
12174 class TableBlockEvent extends BaseComponent {
12175 render() {
12176 let { props } = this;
12177 return (y(StandardEvent, Object.assign({}, props, { elClasses: ['fc-daygrid-event', 'fc-daygrid-block-event', 'fc-h-event'], defaultTimeFormat: DEFAULT_TABLE_EVENT_TIME_FORMAT, defaultDisplayEventEnd: props.defaultDisplayEventEnd, disableResizing: !props.seg.eventRange.def.allDay })));
12178 }
12179 }
12180
12181 class TableListItemEvent extends BaseComponent {
12182 render() {
12183 let { props, context } = this;
12184 let { options } = context;
12185 let { seg } = props;
12186 let timeFormat = options.eventTimeFormat || DEFAULT_TABLE_EVENT_TIME_FORMAT;
12187 let timeText = buildSegTimeText(seg, timeFormat, context, true, props.defaultDisplayEventEnd);
12188 return (y(EventContainer, Object.assign({}, props, { elTag: "a", elClasses: ['fc-daygrid-event', 'fc-daygrid-dot-event'], elAttrs: getSegAnchorAttrs(props.seg, context), defaultGenerator: renderInnerContent$2, timeText: timeText, isResizing: false, isDateSelecting: false })));
12189 }
12190 }
12191 function renderInnerContent$2(renderProps) {
12192 return (y(_, null,
12193 y("div", { className: "fc-daygrid-event-dot", style: { borderColor: renderProps.borderColor || renderProps.backgroundColor } }),
12194 renderProps.timeText && (y("div", { className: "fc-event-time" }, renderProps.timeText)),
12195 y("div", { className: "fc-event-title" }, renderProps.event.title || y(_, null, "\u00A0"))));
12196 }
12197
12198 class TableCellMoreLink extends BaseComponent {
12199 constructor() {
12200 super(...arguments);
12201 this.compileSegs = memoize(compileSegs);
12202 }
12203 render() {
12204 let { props } = this;
12205 let { allSegs, invisibleSegs } = this.compileSegs(props.singlePlacements);
12206 return (y(MoreLinkContainer, { elClasses: ['fc-daygrid-more-link'], dateProfile: props.dateProfile, todayRange: props.todayRange, allDayDate: props.allDayDate, moreCnt: props.moreCnt, allSegs: allSegs, hiddenSegs: invisibleSegs, alignmentElRef: props.alignmentElRef, alignGridTop: props.alignGridTop, extraDateSpan: props.extraDateSpan, popoverContent: () => {
12207 let isForcedInvisible = (props.eventDrag ? props.eventDrag.affectedInstances : null) ||
12208 (props.eventResize ? props.eventResize.affectedInstances : null) ||
12209 {};
12210 return (y(_, null, allSegs.map((seg) => {
12211 let instanceId = seg.eventRange.instance.instanceId;
12212 return (y("div", { className: "fc-daygrid-event-harness", key: instanceId, style: {
12213 visibility: isForcedInvisible[instanceId] ? 'hidden' : '',
12214 } }, hasListItemDisplay(seg) ? (y(TableListItemEvent, Object.assign({ seg: seg, isDragging: false, isSelected: instanceId === props.eventSelection, defaultDisplayEventEnd: false }, getSegMeta(seg, props.todayRange)))) : (y(TableBlockEvent, Object.assign({ seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === props.eventSelection, defaultDisplayEventEnd: false }, getSegMeta(seg, props.todayRange))))));
12215 })));
12216 } }));
12217 }
12218 }
12219 function compileSegs(singlePlacements) {
12220 let allSegs = [];
12221 let invisibleSegs = [];
12222 for (let placement of singlePlacements) {
12223 allSegs.push(placement.seg);
12224 if (!placement.isVisible) {
12225 invisibleSegs.push(placement.seg);
12226 }
12227 }
12228 return { allSegs, invisibleSegs };
12229 }
12230
12231 const DEFAULT_WEEK_NUM_FORMAT$1 = createFormatter({ week: 'narrow' });
12232 class TableCell extends DateComponent {
12233 constructor() {
12234 super(...arguments);
12235 this.rootElRef = d();
12236 this.state = {
12237 dayNumberId: getUniqueDomId(),
12238 };
12239 this.handleRootEl = (el) => {
12240 setRef(this.rootElRef, el);
12241 setRef(this.props.elRef, el);
12242 };
12243 }
12244 render() {
12245 let { context, props, state, rootElRef } = this;
12246 let { options, dateEnv } = context;
12247 let { date, dateProfile } = props;
12248 // TODO: memoize this?
12249 const isMonthStart = props.showDayNumber &&
12250 shouldDisplayMonthStart(date, dateProfile.currentRange, dateEnv);
12251 return (y(DayCellContainer, { elTag: "td", elRef: this.handleRootEl, elClasses: [
12252 'fc-daygrid-day',
12253 ...(props.extraClassNames || []),
12254 ], elAttrs: Object.assign(Object.assign(Object.assign({}, props.extraDataAttrs), (props.showDayNumber ? { 'aria-labelledby': state.dayNumberId } : {})), { role: 'gridcell' }), defaultGenerator: renderTopInner, date: date, dateProfile: dateProfile, todayRange: props.todayRange, showDayNumber: props.showDayNumber, isMonthStart: isMonthStart, extraRenderProps: props.extraRenderProps }, (InnerContent, renderProps) => (y("div", { ref: props.innerElRef, className: "fc-daygrid-day-frame fc-scrollgrid-sync-inner", style: { minHeight: props.minHeight } },
12255 props.showWeekNumber && (y(WeekNumberContainer, { elTag: "a", elClasses: ['fc-daygrid-week-number'], elAttrs: buildNavLinkAttrs(context, date, 'week'), date: date, defaultFormat: DEFAULT_WEEK_NUM_FORMAT$1 })),
12256 !renderProps.isDisabled &&
12257 (props.showDayNumber || hasCustomDayCellContent(options) || props.forceDayTop) ? (y("div", { className: "fc-daygrid-day-top" },
12258 y(InnerContent, { elTag: "a", elClasses: [
12259 'fc-daygrid-day-number',
12260 isMonthStart && 'fc-daygrid-month-start',
12261 ], elAttrs: Object.assign(Object.assign({}, buildNavLinkAttrs(context, date)), { id: state.dayNumberId }) }))) : props.showDayNumber ? (
12262 // for creating correct amount of space (see issue #7162)
12263 y("div", { className: "fc-daygrid-day-top", style: { visibility: 'hidden' } },
12264 y("a", { className: "fc-daygrid-day-number" }, "\u00A0"))) : undefined,
12265 y("div", { className: "fc-daygrid-day-events", ref: props.fgContentElRef },
12266 props.fgContent,
12267 y("div", { className: "fc-daygrid-day-bottom", style: { marginTop: props.moreMarginTop } },
12268 y(TableCellMoreLink, { allDayDate: date, singlePlacements: props.singlePlacements, moreCnt: props.moreCnt, alignmentElRef: rootElRef, alignGridTop: !props.showDayNumber, extraDateSpan: props.extraDateSpan, dateProfile: props.dateProfile, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, todayRange: props.todayRange }))),
12269 y("div", { className: "fc-daygrid-day-bg" }, props.bgContent)))));
12270 }
12271 }
12272 function renderTopInner(props) {
12273 return props.dayNumberText || y(_, null, "\u00A0");
12274 }
12275 function shouldDisplayMonthStart(date, currentRange, dateEnv) {
12276 const { start: currentStart, end: currentEnd } = currentRange;
12277 const currentEndIncl = addMs(currentEnd, -1);
12278 const currentFirstYear = dateEnv.getYear(currentStart);
12279 const currentFirstMonth = dateEnv.getMonth(currentStart);
12280 const currentLastYear = dateEnv.getYear(currentEndIncl);
12281 const currentLastMonth = dateEnv.getMonth(currentEndIncl);
12282 // spans more than one month?
12283 return !(currentFirstYear === currentLastYear && currentFirstMonth === currentLastMonth) &&
12284 Boolean(
12285 // first date in current view?
12286 date.valueOf() === currentStart.valueOf() ||
12287 // a month-start that's within the current range?
12288 (dateEnv.getDay(date) === 1 && date.valueOf() < currentEnd.valueOf()));
12289 }
12290
12291 function generateSegKey(seg) {
12292 return seg.eventRange.instance.instanceId + ':' + seg.firstCol;
12293 }
12294 function generateSegUid(seg) {
12295 return generateSegKey(seg) + ':' + seg.lastCol;
12296 }
12297 function computeFgSegPlacement(segs, // assumed already sorted
12298 dayMaxEvents, dayMaxEventRows, strictOrder, segHeights, maxContentHeight, cells) {
12299 let hierarchy = new DayGridSegHierarchy((segEntry) => {
12300 // TODO: more DRY with generateSegUid
12301 let segUid = segs[segEntry.index].eventRange.instance.instanceId +
12302 ':' + segEntry.span.start +
12303 ':' + (segEntry.span.end - 1);
12304 // if no thickness known, assume 1 (if 0, so small it always fits)
12305 return segHeights[segUid] || 1;
12306 });
12307 hierarchy.allowReslicing = true;
12308 hierarchy.strictOrder = strictOrder;
12309 if (dayMaxEvents === true || dayMaxEventRows === true) {
12310 hierarchy.maxCoord = maxContentHeight;
12311 hierarchy.hiddenConsumes = true;
12312 }
12313 else if (typeof dayMaxEvents === 'number') {
12314 hierarchy.maxStackCnt = dayMaxEvents;
12315 }
12316 else if (typeof dayMaxEventRows === 'number') {
12317 hierarchy.maxStackCnt = dayMaxEventRows;
12318 hierarchy.hiddenConsumes = true;
12319 }
12320 // create segInputs only for segs with known heights
12321 let segInputs = [];
12322 let unknownHeightSegs = [];
12323 for (let i = 0; i < segs.length; i += 1) {
12324 let seg = segs[i];
12325 let segUid = generateSegUid(seg);
12326 let eventHeight = segHeights[segUid];
12327 if (eventHeight != null) {
12328 segInputs.push({
12329 index: i,
12330 span: {
12331 start: seg.firstCol,
12332 end: seg.lastCol + 1,
12333 },
12334 });
12335 }
12336 else {
12337 unknownHeightSegs.push(seg);
12338 }
12339 }
12340 let hiddenEntries = hierarchy.addSegs(segInputs);
12341 let segRects = hierarchy.toRects();
12342 let { singleColPlacements, multiColPlacements, leftoverMargins } = placeRects(segRects, segs, cells);
12343 let moreCnts = [];
12344 let moreMarginTops = [];
12345 // add segs with unknown heights
12346 for (let seg of unknownHeightSegs) {
12347 multiColPlacements[seg.firstCol].push({
12348 seg,
12349 isVisible: false,
12350 isAbsolute: true,
12351 absoluteTop: 0,
12352 marginTop: 0,
12353 });
12354 for (let col = seg.firstCol; col <= seg.lastCol; col += 1) {
12355 singleColPlacements[col].push({
12356 seg: resliceSeg(seg, col, col + 1, cells),
12357 isVisible: false,
12358 isAbsolute: false,
12359 absoluteTop: 0,
12360 marginTop: 0,
12361 });
12362 }
12363 }
12364 // add the hidden entries
12365 for (let col = 0; col < cells.length; col += 1) {
12366 moreCnts.push(0);
12367 }
12368 for (let hiddenEntry of hiddenEntries) {
12369 let seg = segs[hiddenEntry.index];
12370 let hiddenSpan = hiddenEntry.span;
12371 multiColPlacements[hiddenSpan.start].push({
12372 seg: resliceSeg(seg, hiddenSpan.start, hiddenSpan.end, cells),
12373 isVisible: false,
12374 isAbsolute: true,
12375 absoluteTop: 0,
12376 marginTop: 0,
12377 });
12378 for (let col = hiddenSpan.start; col < hiddenSpan.end; col += 1) {
12379 moreCnts[col] += 1;
12380 singleColPlacements[col].push({
12381 seg: resliceSeg(seg, col, col + 1, cells),
12382 isVisible: false,
12383 isAbsolute: false,
12384 absoluteTop: 0,
12385 marginTop: 0,
12386 });
12387 }
12388 }
12389 // deal with leftover margins
12390 for (let col = 0; col < cells.length; col += 1) {
12391 moreMarginTops.push(leftoverMargins[col]);
12392 }
12393 return { singleColPlacements, multiColPlacements, moreCnts, moreMarginTops };
12394 }
12395 // rects ordered by top coord, then left
12396 function placeRects(allRects, segs, cells) {
12397 let rectsByEachCol = groupRectsByEachCol(allRects, cells.length);
12398 let singleColPlacements = [];
12399 let multiColPlacements = [];
12400 let leftoverMargins = [];
12401 for (let col = 0; col < cells.length; col += 1) {
12402 let rects = rectsByEachCol[col];
12403 // compute all static segs in singlePlacements
12404 let singlePlacements = [];
12405 let currentHeight = 0;
12406 let currentMarginTop = 0;
12407 for (let rect of rects) {
12408 let seg = segs[rect.index];
12409 singlePlacements.push({
12410 seg: resliceSeg(seg, col, col + 1, cells),
12411 isVisible: true,
12412 isAbsolute: false,
12413 absoluteTop: rect.levelCoord,
12414 marginTop: rect.levelCoord - currentHeight,
12415 });
12416 currentHeight = rect.levelCoord + rect.thickness;
12417 }
12418 // compute mixed static/absolute segs in multiPlacements
12419 let multiPlacements = [];
12420 currentHeight = 0;
12421 currentMarginTop = 0;
12422 for (let rect of rects) {
12423 let seg = segs[rect.index];
12424 let isAbsolute = rect.span.end - rect.span.start > 1; // multi-column?
12425 let isFirstCol = rect.span.start === col;
12426 currentMarginTop += rect.levelCoord - currentHeight; // amount of space since bottom of previous seg
12427 currentHeight = rect.levelCoord + rect.thickness; // height will now be bottom of current seg
12428 if (isAbsolute) {
12429 currentMarginTop += rect.thickness;
12430 if (isFirstCol) {
12431 multiPlacements.push({
12432 seg: resliceSeg(seg, rect.span.start, rect.span.end, cells),
12433 isVisible: true,
12434 isAbsolute: true,
12435 absoluteTop: rect.levelCoord,
12436 marginTop: 0,
12437 });
12438 }
12439 }
12440 else if (isFirstCol) {
12441 multiPlacements.push({
12442 seg: resliceSeg(seg, rect.span.start, rect.span.end, cells),
12443 isVisible: true,
12444 isAbsolute: false,
12445 absoluteTop: rect.levelCoord,
12446 marginTop: currentMarginTop, // claim the margin
12447 });
12448 currentMarginTop = 0;
12449 }
12450 }
12451 singleColPlacements.push(singlePlacements);
12452 multiColPlacements.push(multiPlacements);
12453 leftoverMargins.push(currentMarginTop);
12454 }
12455 return { singleColPlacements, multiColPlacements, leftoverMargins };
12456 }
12457 function groupRectsByEachCol(rects, colCnt) {
12458 let rectsByEachCol = [];
12459 for (let col = 0; col < colCnt; col += 1) {
12460 rectsByEachCol.push([]);
12461 }
12462 for (let rect of rects) {
12463 for (let col = rect.span.start; col < rect.span.end; col += 1) {
12464 rectsByEachCol[col].push(rect);
12465 }
12466 }
12467 return rectsByEachCol;
12468 }
12469 function resliceSeg(seg, spanStart, spanEnd, cells) {
12470 if (seg.firstCol === spanStart && seg.lastCol === spanEnd - 1) {
12471 return seg;
12472 }
12473 let eventRange = seg.eventRange;
12474 let origRange = eventRange.range;
12475 let slicedRange = intersectRanges(origRange, {
12476 start: cells[spanStart].date,
12477 end: addDays(cells[spanEnd - 1].date, 1),
12478 });
12479 return Object.assign(Object.assign({}, seg), { firstCol: spanStart, lastCol: spanEnd - 1, eventRange: {
12480 def: eventRange.def,
12481 ui: Object.assign(Object.assign({}, eventRange.ui), { durationEditable: false }),
12482 instance: eventRange.instance,
12483 range: slicedRange,
12484 }, isStart: seg.isStart && slicedRange.start.valueOf() === origRange.start.valueOf(), isEnd: seg.isEnd && slicedRange.end.valueOf() === origRange.end.valueOf() });
12485 }
12486 class DayGridSegHierarchy extends SegHierarchy {
12487 constructor() {
12488 super(...arguments);
12489 // config
12490 this.hiddenConsumes = false;
12491 // allows us to keep hidden entries in the hierarchy so they take up space
12492 this.forceHidden = {};
12493 }
12494 addSegs(segInputs) {
12495 const hiddenSegs = super.addSegs(segInputs);
12496 const { entriesByLevel } = this;
12497 const excludeHidden = (entry) => !this.forceHidden[buildEntryKey(entry)];
12498 // remove the forced-hidden segs
12499 for (let level = 0; level < entriesByLevel.length; level += 1) {
12500 entriesByLevel[level] = entriesByLevel[level].filter(excludeHidden);
12501 }
12502 return hiddenSegs;
12503 }
12504 handleInvalidInsertion(insertion, entry, hiddenEntries) {
12505 const { entriesByLevel, forceHidden } = this;
12506 const { touchingEntry, touchingLevel, touchingLateral } = insertion;
12507 // the entry that the new insertion is touching must be hidden
12508 if (this.hiddenConsumes && touchingEntry) {
12509 const touchingEntryId = buildEntryKey(touchingEntry);
12510 if (!forceHidden[touchingEntryId]) {
12511 if (this.allowReslicing) {
12512 // split up the touchingEntry, reinsert it
12513 const hiddenEntry = Object.assign(Object.assign({}, touchingEntry), { span: intersectSpans(touchingEntry.span, entry.span) });
12514 // reinsert the area that turned into a "more" link (so no other entries try to
12515 // occupy the space) but mark it forced-hidden
12516 const hiddenEntryId = buildEntryKey(hiddenEntry);
12517 forceHidden[hiddenEntryId] = true;
12518 entriesByLevel[touchingLevel][touchingLateral] = hiddenEntry;
12519 hiddenEntries.push(hiddenEntry);
12520 this.splitEntry(touchingEntry, entry, hiddenEntries);
12521 }
12522 else {
12523 forceHidden[touchingEntryId] = true;
12524 hiddenEntries.push(touchingEntry);
12525 }
12526 }
12527 }
12528 // will try to reslice...
12529 super.handleInvalidInsertion(insertion, entry, hiddenEntries);
12530 }
12531 }
12532
12533 class TableRow extends DateComponent {
12534 constructor() {
12535 super(...arguments);
12536 this.cellElRefs = new RefMap(); // the <td>
12537 this.frameElRefs = new RefMap(); // the fc-daygrid-day-frame
12538 this.fgElRefs = new RefMap(); // the fc-daygrid-day-events
12539 this.segHarnessRefs = new RefMap(); // indexed by "instanceId:firstCol"
12540 this.rootElRef = d();
12541 this.state = {
12542 framePositions: null,
12543 maxContentHeight: null,
12544 segHeights: {},
12545 };
12546 this.handleResize = (isForced) => {
12547 if (isForced) {
12548 this.updateSizing(true); // isExternal=true
12549 }
12550 };
12551 }
12552 render() {
12553 let { props, state, context } = this;
12554 let { options } = context;
12555 let colCnt = props.cells.length;
12556 let businessHoursByCol = splitSegsByFirstCol(props.businessHourSegs, colCnt);
12557 let bgEventSegsByCol = splitSegsByFirstCol(props.bgEventSegs, colCnt);
12558 let highlightSegsByCol = splitSegsByFirstCol(this.getHighlightSegs(), colCnt);
12559 let mirrorSegsByCol = splitSegsByFirstCol(this.getMirrorSegs(), colCnt);
12560 let { singleColPlacements, multiColPlacements, moreCnts, moreMarginTops } = computeFgSegPlacement(sortEventSegs(props.fgEventSegs, options.eventOrder), props.dayMaxEvents, props.dayMaxEventRows, options.eventOrderStrict, state.segHeights, state.maxContentHeight, props.cells);
12561 let isForcedInvisible = // TODO: messy way to compute this
12562 (props.eventDrag && props.eventDrag.affectedInstances) ||
12563 (props.eventResize && props.eventResize.affectedInstances) ||
12564 {};
12565 return (y("tr", { ref: this.rootElRef, role: "row" },
12566 props.renderIntro && props.renderIntro(),
12567 props.cells.map((cell, col) => {
12568 let normalFgNodes = this.renderFgSegs(col, props.forPrint ? singleColPlacements[col] : multiColPlacements[col], props.todayRange, isForcedInvisible);
12569 let mirrorFgNodes = this.renderFgSegs(col, buildMirrorPlacements(mirrorSegsByCol[col], multiColPlacements), props.todayRange, {}, Boolean(props.eventDrag), Boolean(props.eventResize), false);
12570 return (y(TableCell, { key: cell.key, elRef: this.cellElRefs.createRef(cell.key), innerElRef: this.frameElRefs.createRef(cell.key) /* FF <td> problem, but okay to use for left/right. TODO: rename prop */, dateProfile: props.dateProfile, date: cell.date, showDayNumber: props.showDayNumbers, showWeekNumber: props.showWeekNumbers && col === 0, forceDayTop: props.showWeekNumbers /* even displaying weeknum for row, not necessarily day */, todayRange: props.todayRange, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, extraRenderProps: cell.extraRenderProps, extraDataAttrs: cell.extraDataAttrs, extraClassNames: cell.extraClassNames, extraDateSpan: cell.extraDateSpan, moreCnt: moreCnts[col], moreMarginTop: moreMarginTops[col], singlePlacements: singleColPlacements[col], fgContentElRef: this.fgElRefs.createRef(cell.key), fgContent: ( // Fragment scopes the keys
12571 y(_, null,
12572 y(_, null, normalFgNodes),
12573 y(_, null, mirrorFgNodes))), bgContent: ( // Fragment scopes the keys
12574 y(_, null,
12575 this.renderFillSegs(highlightSegsByCol[col], 'highlight'),
12576 this.renderFillSegs(businessHoursByCol[col], 'non-business'),
12577 this.renderFillSegs(bgEventSegsByCol[col], 'bg-event'))), minHeight: props.cellMinHeight }));
12578 })));
12579 }
12580 componentDidMount() {
12581 this.updateSizing(true);
12582 this.context.addResizeHandler(this.handleResize);
12583 }
12584 componentDidUpdate(prevProps, prevState) {
12585 let currentProps = this.props;
12586 this.updateSizing(!isPropsEqual(prevProps, currentProps));
12587 }
12588 componentWillUnmount() {
12589 this.context.removeResizeHandler(this.handleResize);
12590 }
12591 getHighlightSegs() {
12592 let { props } = this;
12593 if (props.eventDrag && props.eventDrag.segs.length) { // messy check
12594 return props.eventDrag.segs;
12595 }
12596 if (props.eventResize && props.eventResize.segs.length) { // messy check
12597 return props.eventResize.segs;
12598 }
12599 return props.dateSelectionSegs;
12600 }
12601 getMirrorSegs() {
12602 let { props } = this;
12603 if (props.eventResize && props.eventResize.segs.length) { // messy check
12604 return props.eventResize.segs;
12605 }
12606 return [];
12607 }
12608 renderFgSegs(col, segPlacements, todayRange, isForcedInvisible, isDragging, isResizing, isDateSelecting) {
12609 let { context } = this;
12610 let { eventSelection } = this.props;
12611 let { framePositions } = this.state;
12612 let defaultDisplayEventEnd = this.props.cells.length === 1; // colCnt === 1
12613 let isMirror = isDragging || isResizing || isDateSelecting;
12614 let nodes = [];
12615 if (framePositions) {
12616 for (let placement of segPlacements) {
12617 let { seg } = placement;
12618 let { instanceId } = seg.eventRange.instance;
12619 let isVisible = placement.isVisible && !isForcedInvisible[instanceId];
12620 let isAbsolute = placement.isAbsolute;
12621 let left = '';
12622 let right = '';
12623 if (isAbsolute) {
12624 if (context.isRtl) {
12625 right = 0;
12626 left = framePositions.lefts[seg.lastCol] - framePositions.lefts[seg.firstCol];
12627 }
12628 else {
12629 left = 0;
12630 right = framePositions.rights[seg.firstCol] - framePositions.rights[seg.lastCol];
12631 }
12632 }
12633 /*
12634 known bug: events that are force to be list-item but span multiple days still take up space in later columns
12635 todo: in print view, for multi-day events, don't display title within non-start/end segs
12636 */
12637 nodes.push(y("div", { className: 'fc-daygrid-event-harness' + (isAbsolute ? ' fc-daygrid-event-harness-abs' : ''), key: generateSegKey(seg), ref: isMirror ? null : this.segHarnessRefs.createRef(generateSegUid(seg)), style: {
12638 visibility: isVisible ? '' : 'hidden',
12639 marginTop: isAbsolute ? '' : placement.marginTop,
12640 top: isAbsolute ? placement.absoluteTop : '',
12641 left,
12642 right,
12643 } }, hasListItemDisplay(seg) ? (y(TableListItemEvent, Object.assign({ seg: seg, isDragging: isDragging, isSelected: instanceId === eventSelection, defaultDisplayEventEnd: defaultDisplayEventEnd }, getSegMeta(seg, todayRange)))) : (y(TableBlockEvent, Object.assign({ seg: seg, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === eventSelection, defaultDisplayEventEnd: defaultDisplayEventEnd }, getSegMeta(seg, todayRange))))));
12644 }
12645 }
12646 return nodes;
12647 }
12648 renderFillSegs(segs, fillType) {
12649 let { isRtl } = this.context;
12650 let { todayRange } = this.props;
12651 let { framePositions } = this.state;
12652 let nodes = [];
12653 if (framePositions) {
12654 for (let seg of segs) {
12655 let leftRightCss = isRtl ? {
12656 right: 0,
12657 left: framePositions.lefts[seg.lastCol] - framePositions.lefts[seg.firstCol],
12658 } : {
12659 left: 0,
12660 right: framePositions.rights[seg.firstCol] - framePositions.rights[seg.lastCol],
12661 };
12662 nodes.push(y("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-daygrid-bg-harness", style: leftRightCss }, fillType === 'bg-event' ?
12663 y(BgEvent, Object.assign({ seg: seg }, getSegMeta(seg, todayRange))) :
12664 renderFill(fillType)));
12665 }
12666 }
12667 return y(_, {}, ...nodes);
12668 }
12669 updateSizing(isExternalSizingChange) {
12670 let { props, state, frameElRefs } = this;
12671 if (!props.forPrint &&
12672 props.clientWidth !== null // positioning ready?
12673 ) {
12674 if (isExternalSizingChange) {
12675 let frameEls = props.cells.map((cell) => frameElRefs.currentMap[cell.key]);
12676 if (frameEls.length) {
12677 let originEl = this.rootElRef.current;
12678 let newPositionCache = new PositionCache(originEl, frameEls, true, // isHorizontal
12679 false);
12680 if (!state.framePositions || !state.framePositions.similarTo(newPositionCache)) {
12681 this.setState({
12682 framePositions: new PositionCache(originEl, frameEls, true, // isHorizontal
12683 false),
12684 });
12685 }
12686 }
12687 }
12688 const oldSegHeights = this.state.segHeights;
12689 const newSegHeights = this.querySegHeights();
12690 const limitByContentHeight = props.dayMaxEvents === true || props.dayMaxEventRows === true;
12691 this.safeSetState({
12692 // HACK to prevent oscillations of events being shown/hidden from max-event-rows
12693 // Essentially, once you compute an element's height, never null-out.
12694 // TODO: always display all events, as visibility:hidden?
12695 segHeights: Object.assign(Object.assign({}, oldSegHeights), newSegHeights),
12696 maxContentHeight: limitByContentHeight ? this.computeMaxContentHeight() : null,
12697 });
12698 }
12699 }
12700 querySegHeights() {
12701 let segElMap = this.segHarnessRefs.currentMap;
12702 let segHeights = {};
12703 // get the max height amongst instance segs
12704 for (let segUid in segElMap) {
12705 let height = Math.round(segElMap[segUid].getBoundingClientRect().height);
12706 segHeights[segUid] = Math.max(segHeights[segUid] || 0, height);
12707 }
12708 return segHeights;
12709 }
12710 computeMaxContentHeight() {
12711 let firstKey = this.props.cells[0].key;
12712 let cellEl = this.cellElRefs.currentMap[firstKey];
12713 let fcContainerEl = this.fgElRefs.currentMap[firstKey];
12714 return cellEl.getBoundingClientRect().bottom - fcContainerEl.getBoundingClientRect().top;
12715 }
12716 getCellEls() {
12717 let elMap = this.cellElRefs.currentMap;
12718 return this.props.cells.map((cell) => elMap[cell.key]);
12719 }
12720 }
12721 TableRow.addStateEquality({
12722 segHeights: isPropsEqual,
12723 });
12724 function buildMirrorPlacements(mirrorSegs, colPlacements) {
12725 if (!mirrorSegs.length) {
12726 return [];
12727 }
12728 let topsByInstanceId = buildAbsoluteTopHash(colPlacements); // TODO: cache this at first render?
12729 return mirrorSegs.map((seg) => ({
12730 seg,
12731 isVisible: true,
12732 isAbsolute: true,
12733 absoluteTop: topsByInstanceId[seg.eventRange.instance.instanceId],
12734 marginTop: 0,
12735 }));
12736 }
12737 function buildAbsoluteTopHash(colPlacements) {
12738 let topsByInstanceId = {};
12739 for (let placements of colPlacements) {
12740 for (let placement of placements) {
12741 topsByInstanceId[placement.seg.eventRange.instance.instanceId] = placement.absoluteTop;
12742 }
12743 }
12744 return topsByInstanceId;
12745 }
12746
12747 class TableRows extends DateComponent {
12748 constructor() {
12749 super(...arguments);
12750 this.splitBusinessHourSegs = memoize(splitSegsByRow);
12751 this.splitBgEventSegs = memoize(splitSegsByRow);
12752 this.splitFgEventSegs = memoize(splitSegsByRow);
12753 this.splitDateSelectionSegs = memoize(splitSegsByRow);
12754 this.splitEventDrag = memoize(splitInteractionByRow);
12755 this.splitEventResize = memoize(splitInteractionByRow);
12756 this.rowRefs = new RefMap();
12757 }
12758 render() {
12759 let { props, context } = this;
12760 let rowCnt = props.cells.length;
12761 let businessHourSegsByRow = this.splitBusinessHourSegs(props.businessHourSegs, rowCnt);
12762 let bgEventSegsByRow = this.splitBgEventSegs(props.bgEventSegs, rowCnt);
12763 let fgEventSegsByRow = this.splitFgEventSegs(props.fgEventSegs, rowCnt);
12764 let dateSelectionSegsByRow = this.splitDateSelectionSegs(props.dateSelectionSegs, rowCnt);
12765 let eventDragByRow = this.splitEventDrag(props.eventDrag, rowCnt);
12766 let eventResizeByRow = this.splitEventResize(props.eventResize, rowCnt);
12767 // for DayGrid view with many rows, force a min-height on cells so doesn't appear squished
12768 // choose 7 because a month view will have max 6 rows
12769 let cellMinHeight = (rowCnt >= 7 && props.clientWidth) ?
12770 props.clientWidth / context.options.aspectRatio / 6 :
12771 null;
12772 return (y(NowTimer, { unit: "day" }, (nowDate, todayRange) => (y(_, null, props.cells.map((cells, row) => (y(TableRow, { ref: this.rowRefs.createRef(row), key: cells.length
12773 ? cells[0].date.toISOString() /* best? or put key on cell? or use diff formatter? */
12774 : row // in case there are no cells (like when resource view is loading)
12775 , showDayNumbers: rowCnt > 1, showWeekNumbers: props.showWeekNumbers, todayRange: todayRange, dateProfile: props.dateProfile, cells: cells, renderIntro: props.renderRowIntro, businessHourSegs: businessHourSegsByRow[row], eventSelection: props.eventSelection, bgEventSegs: bgEventSegsByRow[row].filter(isSegAllDay) /* hack */, fgEventSegs: fgEventSegsByRow[row], dateSelectionSegs: dateSelectionSegsByRow[row], eventDrag: eventDragByRow[row], eventResize: eventResizeByRow[row], dayMaxEvents: props.dayMaxEvents, dayMaxEventRows: props.dayMaxEventRows, clientWidth: props.clientWidth, clientHeight: props.clientHeight, cellMinHeight: cellMinHeight, forPrint: props.forPrint })))))));
12776 }
12777 componentDidMount() {
12778 this.registerInteractiveComponent();
12779 }
12780 componentDidUpdate() {
12781 // for if started with zero cells
12782 this.registerInteractiveComponent();
12783 }
12784 registerInteractiveComponent() {
12785 if (!this.rootEl) {
12786 // HACK: need a daygrid wrapper parent to do positioning
12787 // NOTE: a daygrid resource view w/o resources can have zero cells
12788 const firstCellEl = this.rowRefs.currentMap[0].getCellEls()[0];
12789 const rootEl = firstCellEl ? firstCellEl.closest('.fc-daygrid-body') : null;
12790 if (rootEl) {
12791 this.rootEl = rootEl;
12792 this.context.registerInteractiveComponent(this, {
12793 el: rootEl,
12794 isHitComboAllowed: this.props.isHitComboAllowed,
12795 });
12796 }
12797 }
12798 }
12799 componentWillUnmount() {
12800 if (this.rootEl) {
12801 this.context.unregisterInteractiveComponent(this);
12802 this.rootEl = null;
12803 }
12804 }
12805 // Hit System
12806 // ----------------------------------------------------------------------------------------------------
12807 prepareHits() {
12808 this.rowPositions = new PositionCache(this.rootEl, this.rowRefs.collect().map((rowObj) => rowObj.getCellEls()[0]), // first cell el in each row. TODO: not optimal
12809 false, true);
12810 this.colPositions = new PositionCache(this.rootEl, this.rowRefs.currentMap[0].getCellEls(), // cell els in first row
12811 true, // horizontal
12812 false);
12813 }
12814 queryHit(positionLeft, positionTop) {
12815 let { colPositions, rowPositions } = this;
12816 let col = colPositions.leftToIndex(positionLeft);
12817 let row = rowPositions.topToIndex(positionTop);
12818 if (row != null && col != null) {
12819 let cell = this.props.cells[row][col];
12820 return {
12821 dateProfile: this.props.dateProfile,
12822 dateSpan: Object.assign({ range: this.getCellRange(row, col), allDay: true }, cell.extraDateSpan),
12823 dayEl: this.getCellEl(row, col),
12824 rect: {
12825 left: colPositions.lefts[col],
12826 right: colPositions.rights[col],
12827 top: rowPositions.tops[row],
12828 bottom: rowPositions.bottoms[row],
12829 },
12830 layer: 0,
12831 };
12832 }
12833 return null;
12834 }
12835 getCellEl(row, col) {
12836 return this.rowRefs.currentMap[row].getCellEls()[col]; // TODO: not optimal
12837 }
12838 getCellRange(row, col) {
12839 let start = this.props.cells[row][col].date;
12840 let end = addDays(start, 1);
12841 return { start, end };
12842 }
12843 }
12844 function isSegAllDay(seg) {
12845 return seg.eventRange.def.allDay;
12846 }
12847
12848 class Table extends DateComponent {
12849 constructor() {
12850 super(...arguments);
12851 this.elRef = d();
12852 this.needsScrollReset = false;
12853 }
12854 render() {
12855 let { props } = this;
12856 let { dayMaxEventRows, dayMaxEvents, expandRows } = props;
12857 let limitViaBalanced = dayMaxEvents === true || dayMaxEventRows === true;
12858 // if rows can't expand to fill fixed height, can't do balanced-height event limit
12859 // TODO: best place to normalize these options?
12860 if (limitViaBalanced && !expandRows) {
12861 limitViaBalanced = false;
12862 dayMaxEventRows = null;
12863 dayMaxEvents = null;
12864 }
12865 let classNames = [
12866 'fc-daygrid-body',
12867 limitViaBalanced ? 'fc-daygrid-body-balanced' : 'fc-daygrid-body-unbalanced',
12868 expandRows ? '' : 'fc-daygrid-body-natural', // will height of one row depend on the others?
12869 ];
12870 return (y("div", { ref: this.elRef, className: classNames.join(' '), style: {
12871 // these props are important to give this wrapper correct dimensions for interactions
12872 // TODO: if we set it here, can we avoid giving to inner tables?
12873 width: props.clientWidth,
12874 minWidth: props.tableMinWidth,
12875 } },
12876 y("table", { role: "presentation", className: "fc-scrollgrid-sync-table", style: {
12877 width: props.clientWidth,
12878 minWidth: props.tableMinWidth,
12879 height: expandRows ? props.clientHeight : '',
12880 } },
12881 props.colGroupNode,
12882 y("tbody", { role: "presentation" },
12883 y(TableRows, { dateProfile: props.dateProfile, cells: props.cells, renderRowIntro: props.renderRowIntro, showWeekNumbers: props.showWeekNumbers, clientWidth: props.clientWidth, clientHeight: props.clientHeight, businessHourSegs: props.businessHourSegs, bgEventSegs: props.bgEventSegs, fgEventSegs: props.fgEventSegs, dateSelectionSegs: props.dateSelectionSegs, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, dayMaxEvents: dayMaxEvents, dayMaxEventRows: dayMaxEventRows, forPrint: props.forPrint, isHitComboAllowed: props.isHitComboAllowed })))));
12884 }
12885 componentDidMount() {
12886 this.requestScrollReset();
12887 }
12888 componentDidUpdate(prevProps) {
12889 if (prevProps.dateProfile !== this.props.dateProfile) {
12890 this.requestScrollReset();
12891 }
12892 else {
12893 this.flushScrollReset();
12894 }
12895 }
12896 requestScrollReset() {
12897 this.needsScrollReset = true;
12898 this.flushScrollReset();
12899 }
12900 flushScrollReset() {
12901 if (this.needsScrollReset &&
12902 this.props.clientWidth // sizes computed?
12903 ) {
12904 const subjectEl = getScrollSubjectEl(this.elRef.current, this.props.dateProfile);
12905 if (subjectEl) {
12906 const originEl = subjectEl.closest('.fc-daygrid-body');
12907 const scrollEl = originEl.closest('.fc-scroller');
12908 const scrollTop = subjectEl.getBoundingClientRect().top -
12909 originEl.getBoundingClientRect().top;
12910 scrollEl.scrollTop = scrollTop ? (scrollTop + 1) : 0; // overcome border
12911 }
12912 this.needsScrollReset = false;
12913 }
12914 }
12915 }
12916 function getScrollSubjectEl(containerEl, dateProfile) {
12917 let el;
12918 if (dateProfile.currentRangeUnit.match(/year|month/)) {
12919 el = containerEl.querySelector(`[data-date="${formatIsoMonthStr(dateProfile.currentDate)}-01"]`);
12920 // even if view is month-based, first-of-month might be hidden...
12921 }
12922 if (!el) {
12923 el = containerEl.querySelector(`[data-date="${formatDayString(dateProfile.currentDate)}"]`);
12924 // could still be hidden if an interior-view hidden day
12925 }
12926 return el;
12927 }
12928
12929 class DayTableSlicer extends Slicer {
12930 constructor() {
12931 super(...arguments);
12932 this.forceDayIfListItem = true;
12933 }
12934 sliceRange(dateRange, dayTableModel) {
12935 return dayTableModel.sliceRange(dateRange);
12936 }
12937 }
12938
12939 class DayTable extends DateComponent {
12940 constructor() {
12941 super(...arguments);
12942 this.slicer = new DayTableSlicer();
12943 this.tableRef = d();
12944 }
12945 render() {
12946 let { props, context } = this;
12947 return (y(Table, Object.assign({ ref: this.tableRef }, this.slicer.sliceProps(props, props.dateProfile, props.nextDayThreshold, context, props.dayTableModel), { dateProfile: props.dateProfile, cells: props.dayTableModel.cells, colGroupNode: props.colGroupNode, tableMinWidth: props.tableMinWidth, renderRowIntro: props.renderRowIntro, dayMaxEvents: props.dayMaxEvents, dayMaxEventRows: props.dayMaxEventRows, showWeekNumbers: props.showWeekNumbers, expandRows: props.expandRows, headerAlignElRef: props.headerAlignElRef, clientWidth: props.clientWidth, clientHeight: props.clientHeight, forPrint: props.forPrint })));
12948 }
12949 }
12950
12951 class DayTableView extends TableView {
12952 constructor() {
12953 super(...arguments);
12954 this.buildDayTableModel = memoize(buildDayTableModel);
12955 this.headerRef = d();
12956 this.tableRef = d();
12957 // can't override any lifecycle methods from parent
12958 }
12959 render() {
12960 let { options, dateProfileGenerator } = this.context;
12961 let { props } = this;
12962 let dayTableModel = this.buildDayTableModel(props.dateProfile, dateProfileGenerator);
12963 let headerContent = options.dayHeaders && (y(DayHeader, { ref: this.headerRef, dateProfile: props.dateProfile, dates: dayTableModel.headerDates, datesRepDistinctDays: dayTableModel.rowCnt === 1 }));
12964 let bodyContent = (contentArg) => (y(DayTable, { ref: this.tableRef, dateProfile: props.dateProfile, dayTableModel: dayTableModel, businessHours: props.businessHours, dateSelection: props.dateSelection, eventStore: props.eventStore, eventUiBases: props.eventUiBases, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, nextDayThreshold: options.nextDayThreshold, colGroupNode: contentArg.tableColGroupNode, tableMinWidth: contentArg.tableMinWidth, dayMaxEvents: options.dayMaxEvents, dayMaxEventRows: options.dayMaxEventRows, showWeekNumbers: options.weekNumbers, expandRows: !props.isHeightAuto, headerAlignElRef: this.headerElRef, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, forPrint: props.forPrint }));
12965 return options.dayMinWidth
12966 ? this.renderHScrollLayout(headerContent, bodyContent, dayTableModel.colCnt, options.dayMinWidth)
12967 : this.renderSimpleLayout(headerContent, bodyContent);
12968 }
12969 }
12970 function buildDayTableModel(dateProfile, dateProfileGenerator) {
12971 let daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator);
12972 return new DayTableModel(daySeries, /year|month|week/.test(dateProfile.currentRangeUnit));
12973 }
12974
12975 class TableDateProfileGenerator extends DateProfileGenerator {
12976 // Computes the date range that will be rendered
12977 buildRenderRange(currentRange, currentRangeUnit, isRangeAllDay) {
12978 let renderRange = super.buildRenderRange(currentRange, currentRangeUnit, isRangeAllDay);
12979 let { props } = this;
12980 return buildDayTableRenderRange({
12981 currentRange: renderRange,
12982 snapToWeek: /^(year|month)$/.test(currentRangeUnit),
12983 fixedWeekCount: props.fixedWeekCount,
12984 dateEnv: props.dateEnv,
12985 });
12986 }
12987 }
12988 function buildDayTableRenderRange(props) {
12989 let { dateEnv, currentRange } = props;
12990 let { start, end } = currentRange;
12991 let endOfWeek;
12992 // year and month views should be aligned with weeks. this is already done for week
12993 if (props.snapToWeek) {
12994 start = dateEnv.startOfWeek(start);
12995 // make end-of-week if not already
12996 endOfWeek = dateEnv.startOfWeek(end);
12997 if (endOfWeek.valueOf() !== end.valueOf()) {
12998 end = addWeeks(endOfWeek, 1);
12999 }
13000 }
13001 // ensure 6 weeks
13002 if (props.fixedWeekCount) {
13003 // TODO: instead of these date-math gymnastics (for multimonth view),
13004 // compute dateprofiles of all months, then use start of first and end of last.
13005 let lastMonthRenderStart = dateEnv.startOfWeek(dateEnv.startOfMonth(addDays(currentRange.end, -1)));
13006 let rowCnt = Math.ceil(// could be partial weeks due to hiddenDays
13007 diffWeeks(lastMonthRenderStart, end));
13008 end = addWeeks(end, 6 - rowCnt);
13009 }
13010 return { start, end };
13011 }
13012
13013 var css_248z$3 = ":root{--fc-daygrid-event-dot-width:8px}.fc-daygrid-day-events:after,.fc-daygrid-day-events:before,.fc-daygrid-day-frame:after,.fc-daygrid-day-frame:before,.fc-daygrid-event-harness:after,.fc-daygrid-event-harness:before{clear:both;content:\"\";display:table}.fc .fc-daygrid-body{position:relative;z-index:1}.fc .fc-daygrid-day.fc-day-today{background-color:var(--fc-today-bg-color)}.fc .fc-daygrid-day-frame{min-height:100%;position:relative}.fc .fc-daygrid-day-top{display:flex;flex-direction:row-reverse}.fc .fc-day-other .fc-daygrid-day-top{opacity:.3}.fc .fc-daygrid-day-number{padding:4px;position:relative;z-index:4}.fc .fc-daygrid-month-start{font-size:1.1em;font-weight:700}.fc .fc-daygrid-day-events{margin-top:1px}.fc .fc-daygrid-body-balanced .fc-daygrid-day-events{left:0;position:absolute;right:0}.fc .fc-daygrid-body-unbalanced .fc-daygrid-day-events{min-height:2em;position:relative}.fc .fc-daygrid-body-natural .fc-daygrid-day-events{margin-bottom:1em}.fc .fc-daygrid-event-harness{position:relative}.fc .fc-daygrid-event-harness-abs{left:0;position:absolute;right:0;top:0}.fc .fc-daygrid-bg-harness{bottom:0;position:absolute;top:0}.fc .fc-daygrid-day-bg .fc-non-business{z-index:1}.fc .fc-daygrid-day-bg .fc-bg-event{z-index:2}.fc .fc-daygrid-day-bg .fc-highlight{z-index:3}.fc .fc-daygrid-event{margin-top:1px;z-index:6}.fc .fc-daygrid-event.fc-event-mirror{z-index:7}.fc .fc-daygrid-day-bottom{font-size:.85em;margin:0 2px}.fc .fc-daygrid-day-bottom:after,.fc .fc-daygrid-day-bottom:before{clear:both;content:\"\";display:table}.fc .fc-daygrid-more-link{border-radius:3px;cursor:pointer;line-height:1;margin-top:1px;max-width:100%;overflow:hidden;padding:2px;position:relative;white-space:nowrap;z-index:4}.fc .fc-daygrid-more-link:hover{background-color:rgba(0,0,0,.1)}.fc .fc-daygrid-week-number{background-color:var(--fc-neutral-bg-color);color:var(--fc-neutral-text-color);min-width:1.5em;padding:2px;position:absolute;text-align:center;top:0;z-index:5}.fc .fc-more-popover .fc-popover-body{min-width:220px;padding:10px}.fc-direction-ltr .fc-daygrid-event.fc-event-start,.fc-direction-rtl .fc-daygrid-event.fc-event-end{margin-left:2px}.fc-direction-ltr .fc-daygrid-event.fc-event-end,.fc-direction-rtl .fc-daygrid-event.fc-event-start{margin-right:2px}.fc-direction-ltr .fc-daygrid-more-link{float:left}.fc-direction-ltr .fc-daygrid-week-number{border-radius:0 0 3px 0;left:0}.fc-direction-rtl .fc-daygrid-more-link{float:right}.fc-direction-rtl .fc-daygrid-week-number{border-radius:0 0 0 3px;right:0}.fc-liquid-hack .fc-daygrid-day-frame{position:static}.fc-daygrid-event{border-radius:3px;font-size:var(--fc-small-font-size);position:relative;white-space:nowrap}.fc-daygrid-block-event .fc-event-time{font-weight:700}.fc-daygrid-block-event .fc-event-time,.fc-daygrid-block-event .fc-event-title{padding:1px}.fc-daygrid-dot-event{align-items:center;display:flex;padding:2px 0}.fc-daygrid-dot-event .fc-event-title{flex-grow:1;flex-shrink:1;font-weight:700;min-width:0;overflow:hidden}.fc-daygrid-dot-event.fc-event-mirror,.fc-daygrid-dot-event:hover{background:rgba(0,0,0,.1)}.fc-daygrid-dot-event.fc-event-selected:before{bottom:-10px;top:-10px}.fc-daygrid-event-dot{border:calc(var(--fc-daygrid-event-dot-width)/2) solid var(--fc-event-border-color);border-radius:calc(var(--fc-daygrid-event-dot-width)/2);box-sizing:content-box;height:0;margin:0 4px;width:0}.fc-direction-ltr .fc-daygrid-event .fc-event-time{margin-right:3px}.fc-direction-rtl .fc-daygrid-event .fc-event-time{margin-left:3px}";
13014 injectStyles(css_248z$3);
13015
13016 var index$3 = createPlugin({
13017 name: '@fullcalendar/daygrid',
13018 initialView: 'dayGridMonth',
13019 views: {
13020 dayGrid: {
13021 component: DayTableView,
13022 dateProfileGeneratorClass: TableDateProfileGenerator,
13023 },
13024 dayGridDay: {
13025 type: 'dayGrid',
13026 duration: { days: 1 },
13027 },
13028 dayGridWeek: {
13029 type: 'dayGrid',
13030 duration: { weeks: 1 },
13031 },
13032 dayGridMonth: {
13033 type: 'dayGrid',
13034 duration: { months: 1 },
13035 fixedWeekCount: true,
13036 },
13037 dayGridYear: {
13038 type: 'dayGrid',
13039 duration: { years: 1 },
13040 },
13041 },
13042 });
13043
13044 class AllDaySplitter extends Splitter {
13045 getKeyInfo() {
13046 return {
13047 allDay: {},
13048 timed: {},
13049 };
13050 }
13051 getKeysForDateSpan(dateSpan) {
13052 if (dateSpan.allDay) {
13053 return ['allDay'];
13054 }
13055 return ['timed'];
13056 }
13057 getKeysForEventDef(eventDef) {
13058 if (!eventDef.allDay) {
13059 return ['timed'];
13060 }
13061 if (hasBgRendering(eventDef)) {
13062 return ['timed', 'allDay'];
13063 }
13064 return ['allDay'];
13065 }
13066 }
13067
13068 const DEFAULT_SLAT_LABEL_FORMAT = createFormatter({
13069 hour: 'numeric',
13070 minute: '2-digit',
13071 omitZeroMinute: true,
13072 meridiem: 'short',
13073 });
13074 function TimeColsAxisCell(props) {
13075 let classNames = [
13076 'fc-timegrid-slot',
13077 'fc-timegrid-slot-label',
13078 props.isLabeled ? 'fc-scrollgrid-shrink' : 'fc-timegrid-slot-minor',
13079 ];
13080 return (y(ViewContextType.Consumer, null, (context) => {
13081 if (!props.isLabeled) {
13082 return (y("td", { className: classNames.join(' '), "data-time": props.isoTimeStr }));
13083 }
13084 let { dateEnv, options, viewApi } = context;
13085 let labelFormat = // TODO: fully pre-parse
13086 options.slotLabelFormat == null ? DEFAULT_SLAT_LABEL_FORMAT :
13087 Array.isArray(options.slotLabelFormat) ? createFormatter(options.slotLabelFormat[0]) :
13088 createFormatter(options.slotLabelFormat);
13089 let renderProps = {
13090 level: 0,
13091 time: props.time,
13092 date: dateEnv.toDate(props.date),
13093 view: viewApi,
13094 text: dateEnv.format(props.date, labelFormat),
13095 };
13096 return (y(ContentContainer, { elTag: "td", elClasses: classNames, elAttrs: {
13097 'data-time': props.isoTimeStr,
13098 }, renderProps: renderProps, generatorName: "slotLabelContent", customGenerator: options.slotLabelContent, defaultGenerator: renderInnerContent$1, classNameGenerator: options.slotLabelClassNames, didMount: options.slotLabelDidMount, willUnmount: options.slotLabelWillUnmount }, (InnerContent) => (y("div", { className: "fc-timegrid-slot-label-frame fc-scrollgrid-shrink-frame" },
13099 y(InnerContent, { elTag: "div", elClasses: [
13100 'fc-timegrid-slot-label-cushion',
13101 'fc-scrollgrid-shrink-cushion',
13102 ] })))));
13103 }));
13104 }
13105 function renderInnerContent$1(props) {
13106 return props.text;
13107 }
13108
13109 class TimeBodyAxis extends BaseComponent {
13110 render() {
13111 return this.props.slatMetas.map((slatMeta) => (y("tr", { key: slatMeta.key },
13112 y(TimeColsAxisCell, Object.assign({}, slatMeta)))));
13113 }
13114 }
13115
13116 const DEFAULT_WEEK_NUM_FORMAT = createFormatter({ week: 'short' });
13117 const AUTO_ALL_DAY_MAX_EVENT_ROWS = 5;
13118 class TimeColsView extends DateComponent {
13119 constructor() {
13120 super(...arguments);
13121 this.allDaySplitter = new AllDaySplitter(); // for use by subclasses
13122 this.headerElRef = d();
13123 this.rootElRef = d();
13124 this.scrollerElRef = d();
13125 this.state = {
13126 slatCoords: null,
13127 };
13128 this.handleScrollTopRequest = (scrollTop) => {
13129 let scrollerEl = this.scrollerElRef.current;
13130 if (scrollerEl) { // TODO: not sure how this could ever be null. weirdness with the reducer
13131 scrollerEl.scrollTop = scrollTop;
13132 }
13133 };
13134 /* Header Render Methods
13135 ------------------------------------------------------------------------------------------------------------------*/
13136 this.renderHeadAxis = (rowKey, frameHeight = '') => {
13137 let { options } = this.context;
13138 let { dateProfile } = this.props;
13139 let range = dateProfile.renderRange;
13140 let dayCnt = diffDays(range.start, range.end);
13141 // only do in day views (to avoid doing in week views that dont need it)
13142 let navLinkAttrs = (dayCnt === 1)
13143 ? buildNavLinkAttrs(this.context, range.start, 'week')
13144 : {};
13145 if (options.weekNumbers && rowKey === 'day') {
13146 return (y(WeekNumberContainer, { elTag: "th", elClasses: [
13147 'fc-timegrid-axis',
13148 'fc-scrollgrid-shrink',
13149 ], elAttrs: {
13150 'aria-hidden': true,
13151 }, date: range.start, defaultFormat: DEFAULT_WEEK_NUM_FORMAT }, (InnerContent) => (y("div", { className: [
13152 'fc-timegrid-axis-frame',
13153 'fc-scrollgrid-shrink-frame',
13154 'fc-timegrid-axis-frame-liquid',
13155 ].join(' '), style: { height: frameHeight } },
13156 y(InnerContent, { elTag: "a", elClasses: [
13157 'fc-timegrid-axis-cushion',
13158 'fc-scrollgrid-shrink-cushion',
13159 'fc-scrollgrid-sync-inner',
13160 ], elAttrs: navLinkAttrs })))));
13161 }
13162 return (y("th", { "aria-hidden": true, className: "fc-timegrid-axis" },
13163 y("div", { className: "fc-timegrid-axis-frame", style: { height: frameHeight } })));
13164 };
13165 /* Table Component Render Methods
13166 ------------------------------------------------------------------------------------------------------------------*/
13167 // only a one-way height sync. we don't send the axis inner-content height to the DayGrid,
13168 // but DayGrid still needs to have classNames on inner elements in order to measure.
13169 this.renderTableRowAxis = (rowHeight) => {
13170 let { options, viewApi } = this.context;
13171 let renderProps = {
13172 text: options.allDayText,
13173 view: viewApi,
13174 };
13175 return (
13176 // TODO: make reusable hook. used in list view too
13177 y(ContentContainer, { elTag: "td", elClasses: [
13178 'fc-timegrid-axis',
13179 'fc-scrollgrid-shrink',
13180 ], elAttrs: {
13181 'aria-hidden': true,
13182 }, renderProps: renderProps, generatorName: "allDayContent", customGenerator: options.allDayContent, defaultGenerator: renderAllDayInner$1, classNameGenerator: options.allDayClassNames, didMount: options.allDayDidMount, willUnmount: options.allDayWillUnmount }, (InnerContent) => (y("div", { className: [
13183 'fc-timegrid-axis-frame',
13184 'fc-scrollgrid-shrink-frame',
13185 rowHeight == null ? ' fc-timegrid-axis-frame-liquid' : '',
13186 ].join(' '), style: { height: rowHeight } },
13187 y(InnerContent, { elTag: "span", elClasses: [
13188 'fc-timegrid-axis-cushion',
13189 'fc-scrollgrid-shrink-cushion',
13190 'fc-scrollgrid-sync-inner',
13191 ] })))));
13192 };
13193 this.handleSlatCoords = (slatCoords) => {
13194 this.setState({ slatCoords });
13195 };
13196 }
13197 // rendering
13198 // ----------------------------------------------------------------------------------------------------
13199 renderSimpleLayout(headerRowContent, allDayContent, timeContent) {
13200 let { context, props } = this;
13201 let sections = [];
13202 let stickyHeaderDates = getStickyHeaderDates(context.options);
13203 if (headerRowContent) {
13204 sections.push({
13205 type: 'header',
13206 key: 'header',
13207 isSticky: stickyHeaderDates,
13208 chunk: {
13209 elRef: this.headerElRef,
13210 tableClassName: 'fc-col-header',
13211 rowContent: headerRowContent,
13212 },
13213 });
13214 }
13215 if (allDayContent) {
13216 sections.push({
13217 type: 'body',
13218 key: 'all-day',
13219 chunk: { content: allDayContent },
13220 });
13221 sections.push({
13222 type: 'body',
13223 key: 'all-day-divider',
13224 outerContent: ( // TODO: rename to cellContent so don't need to define <tr>?
13225 y("tr", { role: "presentation", className: "fc-scrollgrid-section" },
13226 y("td", { className: 'fc-timegrid-divider ' + context.theme.getClass('tableCellShaded') }))),
13227 });
13228 }
13229 sections.push({
13230 type: 'body',
13231 key: 'body',
13232 liquid: true,
13233 expandRows: Boolean(context.options.expandRows),
13234 chunk: {
13235 scrollerElRef: this.scrollerElRef,
13236 content: timeContent,
13237 },
13238 });
13239 return (y(ViewContainer, { elRef: this.rootElRef, elClasses: ['fc-timegrid'], viewSpec: context.viewSpec },
13240 y(SimpleScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, cols: [{ width: 'shrink' }], sections: sections })));
13241 }
13242 renderHScrollLayout(headerRowContent, allDayContent, timeContent, colCnt, dayMinWidth, slatMetas, slatCoords) {
13243 let ScrollGrid = this.context.pluginHooks.scrollGridImpl;
13244 if (!ScrollGrid) {
13245 throw new Error('No ScrollGrid implementation');
13246 }
13247 let { context, props } = this;
13248 let stickyHeaderDates = !props.forPrint && getStickyHeaderDates(context.options);
13249 let stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(context.options);
13250 let sections = [];
13251 if (headerRowContent) {
13252 sections.push({
13253 type: 'header',
13254 key: 'header',
13255 isSticky: stickyHeaderDates,
13256 syncRowHeights: true,
13257 chunks: [
13258 {
13259 key: 'axis',
13260 rowContent: (arg) => (y("tr", { role: "presentation" }, this.renderHeadAxis('day', arg.rowSyncHeights[0]))),
13261 },
13262 {
13263 key: 'cols',
13264 elRef: this.headerElRef,
13265 tableClassName: 'fc-col-header',
13266 rowContent: headerRowContent,
13267 },
13268 ],
13269 });
13270 }
13271 if (allDayContent) {
13272 sections.push({
13273 type: 'body',
13274 key: 'all-day',
13275 syncRowHeights: true,
13276 chunks: [
13277 {
13278 key: 'axis',
13279 rowContent: (contentArg) => (y("tr", { role: "presentation" }, this.renderTableRowAxis(contentArg.rowSyncHeights[0]))),
13280 },
13281 {
13282 key: 'cols',
13283 content: allDayContent,
13284 },
13285 ],
13286 });
13287 sections.push({
13288 key: 'all-day-divider',
13289 type: 'body',
13290 outerContent: ( // TODO: rename to cellContent so don't need to define <tr>?
13291 y("tr", { role: "presentation", className: "fc-scrollgrid-section" },
13292 y("td", { colSpan: 2, className: 'fc-timegrid-divider ' + context.theme.getClass('tableCellShaded') }))),
13293 });
13294 }
13295 let isNowIndicator = context.options.nowIndicator;
13296 sections.push({
13297 type: 'body',
13298 key: 'body',
13299 liquid: true,
13300 expandRows: Boolean(context.options.expandRows),
13301 chunks: [
13302 {
13303 key: 'axis',
13304 content: (arg) => (
13305 // TODO: make this now-indicator arrow more DRY with TimeColsContent
13306 y("div", { className: "fc-timegrid-axis-chunk" },
13307 y("table", { "aria-hidden": true, style: { height: arg.expandRows ? arg.clientHeight : '' } },
13308 arg.tableColGroupNode,
13309 y("tbody", null,
13310 y(TimeBodyAxis, { slatMetas: slatMetas }))),
13311 y("div", { className: "fc-timegrid-now-indicator-container" },
13312 y(NowTimer, { unit: isNowIndicator ? 'minute' : 'day' /* hacky */ }, (nowDate) => {
13313 let nowIndicatorTop = isNowIndicator &&
13314 slatCoords &&
13315 slatCoords.safeComputeTop(nowDate); // might return void
13316 if (typeof nowIndicatorTop === 'number') {
13317 return (y(NowIndicatorContainer, { elClasses: ['fc-timegrid-now-indicator-arrow'], elStyle: { top: nowIndicatorTop }, isAxis: true, date: nowDate }));
13318 }
13319 return null;
13320 })))),
13321 },
13322 {
13323 key: 'cols',
13324 scrollerElRef: this.scrollerElRef,
13325 content: timeContent,
13326 },
13327 ],
13328 });
13329 if (stickyFooterScrollbar) {
13330 sections.push({
13331 key: 'footer',
13332 type: 'footer',
13333 isSticky: true,
13334 chunks: [
13335 {
13336 key: 'axis',
13337 content: renderScrollShim,
13338 },
13339 {
13340 key: 'cols',
13341 content: renderScrollShim,
13342 },
13343 ],
13344 });
13345 }
13346 return (y(ViewContainer, { elRef: this.rootElRef, elClasses: ['fc-timegrid'], viewSpec: context.viewSpec },
13347 y(ScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, forPrint: props.forPrint, collapsibleWidth: false, colGroups: [
13348 { width: 'shrink', cols: [{ width: 'shrink' }] },
13349 { cols: [{ span: colCnt, minWidth: dayMinWidth }] },
13350 ], sections: sections })));
13351 }
13352 /* Dimensions
13353 ------------------------------------------------------------------------------------------------------------------*/
13354 getAllDayMaxEventProps() {
13355 let { dayMaxEvents, dayMaxEventRows } = this.context.options;
13356 if (dayMaxEvents === true || dayMaxEventRows === true) { // is auto?
13357 dayMaxEvents = undefined;
13358 dayMaxEventRows = AUTO_ALL_DAY_MAX_EVENT_ROWS; // make sure "auto" goes to a real number
13359 }
13360 return { dayMaxEvents, dayMaxEventRows };
13361 }
13362 }
13363 function renderAllDayInner$1(renderProps) {
13364 return renderProps.text;
13365 }
13366
13367 class TimeColsSlatsCoords {
13368 constructor(positions, dateProfile, slotDuration) {
13369 this.positions = positions;
13370 this.dateProfile = dateProfile;
13371 this.slotDuration = slotDuration;
13372 }
13373 safeComputeTop(date) {
13374 let { dateProfile } = this;
13375 if (rangeContainsMarker(dateProfile.currentRange, date)) {
13376 let startOfDayDate = startOfDay(date);
13377 let timeMs = date.valueOf() - startOfDayDate.valueOf();
13378 if (timeMs >= asRoughMs(dateProfile.slotMinTime) &&
13379 timeMs < asRoughMs(dateProfile.slotMaxTime)) {
13380 return this.computeTimeTop(createDuration(timeMs));
13381 }
13382 }
13383 return null;
13384 }
13385 // Computes the top coordinate, relative to the bounds of the grid, of the given date.
13386 // A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight.
13387 computeDateTop(when, startOfDayDate) {
13388 if (!startOfDayDate) {
13389 startOfDayDate = startOfDay(when);
13390 }
13391 return this.computeTimeTop(createDuration(when.valueOf() - startOfDayDate.valueOf()));
13392 }
13393 // Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration).
13394 // This is a makeshify way to compute the time-top. Assumes all slatMetas dates are uniform.
13395 // Eventually allow computation with arbirary slat dates.
13396 computeTimeTop(duration) {
13397 let { positions, dateProfile } = this;
13398 let len = positions.els.length;
13399 // floating-point value of # of slots covered
13400 let slatCoverage = (duration.milliseconds - asRoughMs(dateProfile.slotMinTime)) / asRoughMs(this.slotDuration);
13401 let slatIndex;
13402 let slatRemainder;
13403 // compute a floating-point number for how many slats should be progressed through.
13404 // from 0 to number of slats (inclusive)
13405 // constrained because slotMinTime/slotMaxTime might be customized.
13406 slatCoverage = Math.max(0, slatCoverage);
13407 slatCoverage = Math.min(len, slatCoverage);
13408 // an integer index of the furthest whole slat
13409 // from 0 to number slats (*exclusive*, so len-1)
13410 slatIndex = Math.floor(slatCoverage);
13411 slatIndex = Math.min(slatIndex, len - 1);
13412 // how much further through the slatIndex slat (from 0.0-1.0) must be covered in addition.
13413 // could be 1.0 if slatCoverage is covering *all* the slots
13414 slatRemainder = slatCoverage - slatIndex;
13415 return positions.tops[slatIndex] +
13416 positions.getHeight(slatIndex) * slatRemainder;
13417 }
13418 }
13419
13420 class TimeColsSlatsBody extends BaseComponent {
13421 render() {
13422 let { props, context } = this;
13423 let { options } = context;
13424 let { slatElRefs } = props;
13425 return (y("tbody", null, props.slatMetas.map((slatMeta, i) => {
13426 let renderProps = {
13427 time: slatMeta.time,
13428 date: context.dateEnv.toDate(slatMeta.date),
13429 view: context.viewApi,
13430 };
13431 return (y("tr", { key: slatMeta.key, ref: slatElRefs.createRef(slatMeta.key) },
13432 props.axis && (y(TimeColsAxisCell, Object.assign({}, slatMeta))),
13433 y(ContentContainer, { elTag: "td", elClasses: [
13434 'fc-timegrid-slot',
13435 'fc-timegrid-slot-lane',
13436 !slatMeta.isLabeled && 'fc-timegrid-slot-minor',
13437 ], elAttrs: {
13438 'data-time': slatMeta.isoTimeStr,
13439 }, renderProps: renderProps, generatorName: "slotLaneContent", customGenerator: options.slotLaneContent, classNameGenerator: options.slotLaneClassNames, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount })));
13440 })));
13441 }
13442 }
13443
13444 /*
13445 for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL.
13446 */
13447 class TimeColsSlats extends BaseComponent {
13448 constructor() {
13449 super(...arguments);
13450 this.rootElRef = d();
13451 this.slatElRefs = new RefMap();
13452 }
13453 render() {
13454 let { props, context } = this;
13455 return (y("div", { ref: this.rootElRef, className: "fc-timegrid-slots" },
13456 y("table", { "aria-hidden": true, className: context.theme.getClass('table'), style: {
13457 minWidth: props.tableMinWidth,
13458 width: props.clientWidth,
13459 height: props.minHeight,
13460 } },
13461 props.tableColGroupNode /* relies on there only being a single <col> for the axis */,
13462 y(TimeColsSlatsBody, { slatElRefs: this.slatElRefs, axis: props.axis, slatMetas: props.slatMetas }))));
13463 }
13464 componentDidMount() {
13465 this.updateSizing();
13466 }
13467 componentDidUpdate() {
13468 this.updateSizing();
13469 }
13470 componentWillUnmount() {
13471 if (this.props.onCoords) {
13472 this.props.onCoords(null);
13473 }
13474 }
13475 updateSizing() {
13476 let { context, props } = this;
13477 if (props.onCoords &&
13478 props.clientWidth !== null // means sizing has stabilized
13479 ) {
13480 let rootEl = this.rootElRef.current;
13481 if (rootEl.offsetHeight) { // not hidden by css
13482 props.onCoords(new TimeColsSlatsCoords(new PositionCache(this.rootElRef.current, collectSlatEls(this.slatElRefs.currentMap, props.slatMetas), false, true), this.props.dateProfile, context.options.slotDuration));
13483 }
13484 }
13485 }
13486 }
13487 function collectSlatEls(elMap, slatMetas) {
13488 return slatMetas.map((slatMeta) => elMap[slatMeta.key]);
13489 }
13490
13491 function splitSegsByCol(segs, colCnt) {
13492 let segsByCol = [];
13493 let i;
13494 for (i = 0; i < colCnt; i += 1) {
13495 segsByCol.push([]);
13496 }
13497 if (segs) {
13498 for (i = 0; i < segs.length; i += 1) {
13499 segsByCol[segs[i].col].push(segs[i]);
13500 }
13501 }
13502 return segsByCol;
13503 }
13504 function splitInteractionByCol(ui, colCnt) {
13505 let byRow = [];
13506 if (!ui) {
13507 for (let i = 0; i < colCnt; i += 1) {
13508 byRow[i] = null;
13509 }
13510 }
13511 else {
13512 for (let i = 0; i < colCnt; i += 1) {
13513 byRow[i] = {
13514 affectedInstances: ui.affectedInstances,
13515 isEvent: ui.isEvent,
13516 segs: [],
13517 };
13518 }
13519 for (let seg of ui.segs) {
13520 byRow[seg.col].segs.push(seg);
13521 }
13522 }
13523 return byRow;
13524 }
13525
13526 class TimeColMoreLink extends BaseComponent {
13527 render() {
13528 let { props } = this;
13529 return (y(MoreLinkContainer, { elClasses: ['fc-timegrid-more-link'], elStyle: {
13530 top: props.top,
13531 bottom: props.bottom,
13532 }, allDayDate: null, moreCnt: props.hiddenSegs.length, allSegs: props.hiddenSegs, hiddenSegs: props.hiddenSegs, extraDateSpan: props.extraDateSpan, dateProfile: props.dateProfile, todayRange: props.todayRange, popoverContent: () => renderPlainFgSegs(props.hiddenSegs, props), defaultGenerator: renderMoreLinkInner, forceTimed: true }, (InnerContent) => (y(InnerContent, { elTag: "div", elClasses: ['fc-timegrid-more-link-inner', 'fc-sticky'] }))));
13533 }
13534 }
13535 function renderMoreLinkInner(props) {
13536 return props.shortText;
13537 }
13538
13539 // segInputs assumed sorted
13540 function buildPositioning(segInputs, strictOrder, maxStackCnt) {
13541 let hierarchy = new SegHierarchy();
13542 if (strictOrder != null) {
13543 hierarchy.strictOrder = strictOrder;
13544 }
13545 if (maxStackCnt != null) {
13546 hierarchy.maxStackCnt = maxStackCnt;
13547 }
13548 let hiddenEntries = hierarchy.addSegs(segInputs);
13549 let hiddenGroups = groupIntersectingEntries(hiddenEntries);
13550 let web = buildWeb(hierarchy);
13551 web = stretchWeb(web, 1); // all levelCoords/thickness will have 0.0-1.0
13552 let segRects = webToRects(web);
13553 return { segRects, hiddenGroups };
13554 }
13555 function buildWeb(hierarchy) {
13556 const { entriesByLevel } = hierarchy;
13557 const buildNode = cacheable((level, lateral) => level + ':' + lateral, (level, lateral) => {
13558 let siblingRange = findNextLevelSegs(hierarchy, level, lateral);
13559 let nextLevelRes = buildNodes(siblingRange, buildNode);
13560 let entry = entriesByLevel[level][lateral];
13561 return [
13562 Object.assign(Object.assign({}, entry), { nextLevelNodes: nextLevelRes[0] }),
13563 entry.thickness + nextLevelRes[1], // the pressure builds
13564 ];
13565 });
13566 return buildNodes(entriesByLevel.length
13567 ? { level: 0, lateralStart: 0, lateralEnd: entriesByLevel[0].length }
13568 : null, buildNode)[0];
13569 }
13570 function buildNodes(siblingRange, buildNode) {
13571 if (!siblingRange) {
13572 return [[], 0];
13573 }
13574 let { level, lateralStart, lateralEnd } = siblingRange;
13575 let lateral = lateralStart;
13576 let pairs = [];
13577 while (lateral < lateralEnd) {
13578 pairs.push(buildNode(level, lateral));
13579 lateral += 1;
13580 }
13581 pairs.sort(cmpDescPressures);
13582 return [
13583 pairs.map(extractNode),
13584 pairs[0][1], // first item's pressure
13585 ];
13586 }
13587 function cmpDescPressures(a, b) {
13588 return b[1] - a[1];
13589 }
13590 function extractNode(a) {
13591 return a[0];
13592 }
13593 function findNextLevelSegs(hierarchy, subjectLevel, subjectLateral) {
13594 let { levelCoords, entriesByLevel } = hierarchy;
13595 let subjectEntry = entriesByLevel[subjectLevel][subjectLateral];
13596 let afterSubject = levelCoords[subjectLevel] + subjectEntry.thickness;
13597 let levelCnt = levelCoords.length;
13598 let level = subjectLevel;
13599 // skip past levels that are too high up
13600 for (; level < levelCnt && levelCoords[level] < afterSubject; level += 1)
13601 ; // do nothing
13602 for (; level < levelCnt; level += 1) {
13603 let entries = entriesByLevel[level];
13604 let entry;
13605 let searchIndex = binarySearch(entries, subjectEntry.span.start, getEntrySpanEnd);
13606 let lateralStart = searchIndex[0] + searchIndex[1]; // if exact match (which doesn't collide), go to next one
13607 let lateralEnd = lateralStart;
13608 while ( // loop through entries that horizontally intersect
13609 (entry = entries[lateralEnd]) && // but not past the whole seg list
13610 entry.span.start < subjectEntry.span.end) {
13611 lateralEnd += 1;
13612 }
13613 if (lateralStart < lateralEnd) {
13614 return { level, lateralStart, lateralEnd };
13615 }
13616 }
13617 return null;
13618 }
13619 function stretchWeb(topLevelNodes, totalThickness) {
13620 const stretchNode = cacheable((node, startCoord, prevThickness) => buildEntryKey(node), (node, startCoord, prevThickness) => {
13621 let { nextLevelNodes, thickness } = node;
13622 let allThickness = thickness + prevThickness;
13623 let thicknessFraction = thickness / allThickness;
13624 let endCoord;
13625 let newChildren = [];
13626 if (!nextLevelNodes.length) {
13627 endCoord = totalThickness;
13628 }
13629 else {
13630 for (let childNode of nextLevelNodes) {
13631 if (endCoord === undefined) {
13632 let res = stretchNode(childNode, startCoord, allThickness);
13633 endCoord = res[0];
13634 newChildren.push(res[1]);
13635 }
13636 else {
13637 let res = stretchNode(childNode, endCoord, 0);
13638 newChildren.push(res[1]);
13639 }
13640 }
13641 }
13642 let newThickness = (endCoord - startCoord) * thicknessFraction;
13643 return [endCoord - newThickness, Object.assign(Object.assign({}, node), { thickness: newThickness, nextLevelNodes: newChildren })];
13644 });
13645 return topLevelNodes.map((node) => stretchNode(node, 0, 0)[1]);
13646 }
13647 // not sorted in any particular order
13648 function webToRects(topLevelNodes) {
13649 let rects = [];
13650 const processNode = cacheable((node, levelCoord, stackDepth) => buildEntryKey(node), (node, levelCoord, stackDepth) => {
13651 let rect = Object.assign(Object.assign({}, node), { levelCoord,
13652 stackDepth, stackForward: 0 });
13653 rects.push(rect);
13654 return (rect.stackForward = processNodes(node.nextLevelNodes, levelCoord + node.thickness, stackDepth + 1) + 1);
13655 });
13656 function processNodes(nodes, levelCoord, stackDepth) {
13657 let stackForward = 0;
13658 for (let node of nodes) {
13659 stackForward = Math.max(processNode(node, levelCoord, stackDepth), stackForward);
13660 }
13661 return stackForward;
13662 }
13663 processNodes(topLevelNodes, 0, 0);
13664 return rects; // TODO: sort rects by levelCoord to be consistent with toRects?
13665 }
13666 // TODO: move to general util
13667 function cacheable(keyFunc, workFunc) {
13668 const cache = {};
13669 return (...args) => {
13670 let key = keyFunc(...args);
13671 return (key in cache)
13672 ? cache[key]
13673 : (cache[key] = workFunc(...args));
13674 };
13675 }
13676
13677 function computeSegVCoords(segs, colDate, slatCoords = null, eventMinHeight = 0) {
13678 let vcoords = [];
13679 if (slatCoords) {
13680 for (let i = 0; i < segs.length; i += 1) {
13681 let seg = segs[i];
13682 let spanStart = slatCoords.computeDateTop(seg.start, colDate);
13683 let spanEnd = Math.max(spanStart + (eventMinHeight || 0), // :(
13684 slatCoords.computeDateTop(seg.end, colDate));
13685 vcoords.push({
13686 start: Math.round(spanStart),
13687 end: Math.round(spanEnd), //
13688 });
13689 }
13690 }
13691 return vcoords;
13692 }
13693 function computeFgSegPlacements(segs, segVCoords, // might not have for every seg
13694 eventOrderStrict, eventMaxStack) {
13695 let segInputs = [];
13696 let dumbSegs = []; // segs without coords
13697 for (let i = 0; i < segs.length; i += 1) {
13698 let vcoords = segVCoords[i];
13699 if (vcoords) {
13700 segInputs.push({
13701 index: i,
13702 thickness: 1,
13703 span: vcoords,
13704 });
13705 }
13706 else {
13707 dumbSegs.push(segs[i]);
13708 }
13709 }
13710 let { segRects, hiddenGroups } = buildPositioning(segInputs, eventOrderStrict, eventMaxStack);
13711 let segPlacements = [];
13712 for (let segRect of segRects) {
13713 segPlacements.push({
13714 seg: segs[segRect.index],
13715 rect: segRect,
13716 });
13717 }
13718 for (let dumbSeg of dumbSegs) {
13719 segPlacements.push({ seg: dumbSeg, rect: null });
13720 }
13721 return { segPlacements, hiddenGroups };
13722 }
13723
13724 const DEFAULT_TIME_FORMAT$1 = createFormatter({
13725 hour: 'numeric',
13726 minute: '2-digit',
13727 meridiem: false,
13728 });
13729 class TimeColEvent extends BaseComponent {
13730 render() {
13731 return (y(StandardEvent, Object.assign({}, this.props, { elClasses: [
13732 'fc-timegrid-event',
13733 'fc-v-event',
13734 this.props.isShort && 'fc-timegrid-event-short',
13735 ], defaultTimeFormat: DEFAULT_TIME_FORMAT$1 })));
13736 }
13737 }
13738
13739 class TimeCol extends BaseComponent {
13740 constructor() {
13741 super(...arguments);
13742 this.sortEventSegs = memoize(sortEventSegs);
13743 }
13744 // TODO: memoize event-placement?
13745 render() {
13746 let { props, context } = this;
13747 let { options } = context;
13748 let isSelectMirror = options.selectMirror;
13749 let mirrorSegs = // yuck
13750 (props.eventDrag && props.eventDrag.segs) ||
13751 (props.eventResize && props.eventResize.segs) ||
13752 (isSelectMirror && props.dateSelectionSegs) ||
13753 [];
13754 let interactionAffectedInstances = // TODO: messy way to compute this
13755 (props.eventDrag && props.eventDrag.affectedInstances) ||
13756 (props.eventResize && props.eventResize.affectedInstances) ||
13757 {};
13758 let sortedFgSegs = this.sortEventSegs(props.fgEventSegs, options.eventOrder);
13759 return (y(DayCellContainer, { elTag: "td", elRef: props.elRef, elClasses: [
13760 'fc-timegrid-col',
13761 ...(props.extraClassNames || []),
13762 ], elAttrs: Object.assign({ role: 'gridcell' }, props.extraDataAttrs), date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, extraRenderProps: props.extraRenderProps }, (InnerContent) => (y("div", { className: "fc-timegrid-col-frame" },
13763 y("div", { className: "fc-timegrid-col-bg" },
13764 this.renderFillSegs(props.businessHourSegs, 'non-business'),
13765 this.renderFillSegs(props.bgEventSegs, 'bg-event'),
13766 this.renderFillSegs(props.dateSelectionSegs, 'highlight')),
13767 y("div", { className: "fc-timegrid-col-events" }, this.renderFgSegs(sortedFgSegs, interactionAffectedInstances, false, false, false)),
13768 y("div", { className: "fc-timegrid-col-events" }, this.renderFgSegs(mirrorSegs, {}, Boolean(props.eventDrag), Boolean(props.eventResize), Boolean(isSelectMirror), 'mirror')),
13769 y("div", { className: "fc-timegrid-now-indicator-container" }, this.renderNowIndicator(props.nowIndicatorSegs)),
13770 hasCustomDayCellContent(options) && (y(InnerContent, { elTag: "div", elClasses: ['fc-timegrid-col-misc'] }))))));
13771 }
13772 renderFgSegs(sortedFgSegs, segIsInvisible, isDragging, isResizing, isDateSelecting, forcedKey) {
13773 let { props } = this;
13774 if (props.forPrint) {
13775 return renderPlainFgSegs(sortedFgSegs, props);
13776 }
13777 return this.renderPositionedFgSegs(sortedFgSegs, segIsInvisible, isDragging, isResizing, isDateSelecting, forcedKey);
13778 }
13779 renderPositionedFgSegs(segs, // if not mirror, needs to be sorted
13780 segIsInvisible, isDragging, isResizing, isDateSelecting, forcedKey) {
13781 let { eventMaxStack, eventShortHeight, eventOrderStrict, eventMinHeight } = this.context.options;
13782 let { date, slatCoords, eventSelection, todayRange, nowDate } = this.props;
13783 let isMirror = isDragging || isResizing || isDateSelecting;
13784 let segVCoords = computeSegVCoords(segs, date, slatCoords, eventMinHeight);
13785 let { segPlacements, hiddenGroups } = computeFgSegPlacements(segs, segVCoords, eventOrderStrict, eventMaxStack);
13786 return (y(_, null,
13787 this.renderHiddenGroups(hiddenGroups, segs),
13788 segPlacements.map((segPlacement) => {
13789 let { seg, rect } = segPlacement;
13790 let instanceId = seg.eventRange.instance.instanceId;
13791 let isVisible = isMirror || Boolean(!segIsInvisible[instanceId] && rect);
13792 let vStyle = computeSegVStyle(rect && rect.span);
13793 let hStyle = (!isMirror && rect) ? this.computeSegHStyle(rect) : { left: 0, right: 0 };
13794 let isInset = Boolean(rect) && rect.stackForward > 0;
13795 let isShort = Boolean(rect) && (rect.span.end - rect.span.start) < eventShortHeight; // look at other places for this problem
13796 return (y("div", { className: 'fc-timegrid-event-harness' +
13797 (isInset ? ' fc-timegrid-event-harness-inset' : ''), key: forcedKey || instanceId, style: Object.assign(Object.assign({ visibility: isVisible ? '' : 'hidden' }, vStyle), hStyle) },
13798 y(TimeColEvent, Object.assign({ seg: seg, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === eventSelection, isShort: isShort }, getSegMeta(seg, todayRange, nowDate)))));
13799 })));
13800 }
13801 // will already have eventMinHeight applied because segInputs already had it
13802 renderHiddenGroups(hiddenGroups, segs) {
13803 let { extraDateSpan, dateProfile, todayRange, nowDate, eventSelection, eventDrag, eventResize } = this.props;
13804 return (y(_, null, hiddenGroups.map((hiddenGroup) => {
13805 let positionCss = computeSegVStyle(hiddenGroup.span);
13806 let hiddenSegs = compileSegsFromEntries(hiddenGroup.entries, segs);
13807 return (y(TimeColMoreLink, { key: buildIsoString(computeEarliestSegStart(hiddenSegs)), hiddenSegs: hiddenSegs, top: positionCss.top, bottom: positionCss.bottom, extraDateSpan: extraDateSpan, dateProfile: dateProfile, todayRange: todayRange, nowDate: nowDate, eventSelection: eventSelection, eventDrag: eventDrag, eventResize: eventResize }));
13808 })));
13809 }
13810 renderFillSegs(segs, fillType) {
13811 let { props, context } = this;
13812 let segVCoords = computeSegVCoords(segs, props.date, props.slatCoords, context.options.eventMinHeight); // don't assume all populated
13813 let children = segVCoords.map((vcoords, i) => {
13814 let seg = segs[i];
13815 return (y("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-timegrid-bg-harness", style: computeSegVStyle(vcoords) }, fillType === 'bg-event' ?
13816 y(BgEvent, Object.assign({ seg: seg }, getSegMeta(seg, props.todayRange, props.nowDate))) :
13817 renderFill(fillType)));
13818 });
13819 return y(_, null, children);
13820 }
13821 renderNowIndicator(segs) {
13822 let { slatCoords, date } = this.props;
13823 if (!slatCoords) {
13824 return null;
13825 }
13826 return segs.map((seg, i) => (y(NowIndicatorContainer
13827 // key doesn't matter. will only ever be one
13828 , {
13829 // key doesn't matter. will only ever be one
13830 key: i, elClasses: ['fc-timegrid-now-indicator-line'], elStyle: {
13831 top: slatCoords.computeDateTop(seg.start, date),
13832 }, isAxis: false, date: date })));
13833 }
13834 computeSegHStyle(segHCoords) {
13835 let { isRtl, options } = this.context;
13836 let shouldOverlap = options.slotEventOverlap;
13837 let nearCoord = segHCoords.levelCoord; // the left side if LTR. the right side if RTL. floating-point
13838 let farCoord = segHCoords.levelCoord + segHCoords.thickness; // the right side if LTR. the left side if RTL. floating-point
13839 let left; // amount of space from left edge, a fraction of the total width
13840 let right; // amount of space from right edge, a fraction of the total width
13841 if (shouldOverlap) {
13842 // double the width, but don't go beyond the maximum forward coordinate (1.0)
13843 farCoord = Math.min(1, nearCoord + (farCoord - nearCoord) * 2);
13844 }
13845 if (isRtl) {
13846 left = 1 - farCoord;
13847 right = nearCoord;
13848 }
13849 else {
13850 left = nearCoord;
13851 right = 1 - farCoord;
13852 }
13853 let props = {
13854 zIndex: segHCoords.stackDepth + 1,
13855 left: left * 100 + '%',
13856 right: right * 100 + '%',
13857 };
13858 if (shouldOverlap && !segHCoords.stackForward) {
13859 // add padding to the edge so that forward stacked events don't cover the resizer's icon
13860 props[isRtl ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width
13861 }
13862 return props;
13863 }
13864 }
13865 function renderPlainFgSegs(sortedFgSegs, { todayRange, nowDate, eventSelection, eventDrag, eventResize }) {
13866 let hiddenInstances = (eventDrag ? eventDrag.affectedInstances : null) ||
13867 (eventResize ? eventResize.affectedInstances : null) ||
13868 {};
13869 return (y(_, null, sortedFgSegs.map((seg) => {
13870 let instanceId = seg.eventRange.instance.instanceId;
13871 return (y("div", { key: instanceId, style: { visibility: hiddenInstances[instanceId] ? 'hidden' : '' } },
13872 y(TimeColEvent, Object.assign({ seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === eventSelection, isShort: false }, getSegMeta(seg, todayRange, nowDate)))));
13873 })));
13874 }
13875 function computeSegVStyle(segVCoords) {
13876 if (!segVCoords) {
13877 return { top: '', bottom: '' };
13878 }
13879 return {
13880 top: segVCoords.start,
13881 bottom: -segVCoords.end,
13882 };
13883 }
13884 function compileSegsFromEntries(segEntries, allSegs) {
13885 return segEntries.map((segEntry) => allSegs[segEntry.index]);
13886 }
13887
13888 class TimeColsContent extends BaseComponent {
13889 constructor() {
13890 super(...arguments);
13891 this.splitFgEventSegs = memoize(splitSegsByCol);
13892 this.splitBgEventSegs = memoize(splitSegsByCol);
13893 this.splitBusinessHourSegs = memoize(splitSegsByCol);
13894 this.splitNowIndicatorSegs = memoize(splitSegsByCol);
13895 this.splitDateSelectionSegs = memoize(splitSegsByCol);
13896 this.splitEventDrag = memoize(splitInteractionByCol);
13897 this.splitEventResize = memoize(splitInteractionByCol);
13898 this.rootElRef = d();
13899 this.cellElRefs = new RefMap();
13900 }
13901 render() {
13902 let { props, context } = this;
13903 let nowIndicatorTop = context.options.nowIndicator &&
13904 props.slatCoords &&
13905 props.slatCoords.safeComputeTop(props.nowDate); // might return void
13906 let colCnt = props.cells.length;
13907 let fgEventSegsByRow = this.splitFgEventSegs(props.fgEventSegs, colCnt);
13908 let bgEventSegsByRow = this.splitBgEventSegs(props.bgEventSegs, colCnt);
13909 let businessHourSegsByRow = this.splitBusinessHourSegs(props.businessHourSegs, colCnt);
13910 let nowIndicatorSegsByRow = this.splitNowIndicatorSegs(props.nowIndicatorSegs, colCnt);
13911 let dateSelectionSegsByRow = this.splitDateSelectionSegs(props.dateSelectionSegs, colCnt);
13912 let eventDragByRow = this.splitEventDrag(props.eventDrag, colCnt);
13913 let eventResizeByRow = this.splitEventResize(props.eventResize, colCnt);
13914 return (y("div", { className: "fc-timegrid-cols", ref: this.rootElRef },
13915 y("table", { role: "presentation", style: {
13916 minWidth: props.tableMinWidth,
13917 width: props.clientWidth,
13918 } },
13919 props.tableColGroupNode,
13920 y("tbody", { role: "presentation" },
13921 y("tr", { role: "row" },
13922 props.axis && (y("td", { "aria-hidden": true, className: "fc-timegrid-col fc-timegrid-axis" },
13923 y("div", { className: "fc-timegrid-col-frame" },
13924 y("div", { className: "fc-timegrid-now-indicator-container" }, typeof nowIndicatorTop === 'number' && (y(NowIndicatorContainer, { elClasses: ['fc-timegrid-now-indicator-arrow'], elStyle: { top: nowIndicatorTop }, isAxis: true, date: props.nowDate })))))),
13925 props.cells.map((cell, i) => (y(TimeCol, { key: cell.key, elRef: this.cellElRefs.createRef(cell.key), dateProfile: props.dateProfile, date: cell.date, nowDate: props.nowDate, todayRange: props.todayRange, extraRenderProps: cell.extraRenderProps, extraDataAttrs: cell.extraDataAttrs, extraClassNames: cell.extraClassNames, extraDateSpan: cell.extraDateSpan, fgEventSegs: fgEventSegsByRow[i], bgEventSegs: bgEventSegsByRow[i], businessHourSegs: businessHourSegsByRow[i], nowIndicatorSegs: nowIndicatorSegsByRow[i], dateSelectionSegs: dateSelectionSegsByRow[i], eventDrag: eventDragByRow[i], eventResize: eventResizeByRow[i], slatCoords: props.slatCoords, eventSelection: props.eventSelection, forPrint: props.forPrint }))))))));
13926 }
13927 componentDidMount() {
13928 this.updateCoords();
13929 }
13930 componentDidUpdate() {
13931 this.updateCoords();
13932 }
13933 updateCoords() {
13934 let { props } = this;
13935 if (props.onColCoords &&
13936 props.clientWidth !== null // means sizing has stabilized
13937 ) {
13938 props.onColCoords(new PositionCache(this.rootElRef.current, collectCellEls(this.cellElRefs.currentMap, props.cells), true, // horizontal
13939 false));
13940 }
13941 }
13942 }
13943 function collectCellEls(elMap, cells) {
13944 return cells.map((cell) => elMap[cell.key]);
13945 }
13946
13947 /* A component that renders one or more columns of vertical time slots
13948 ----------------------------------------------------------------------------------------------------------------------*/
13949 class TimeCols extends DateComponent {
13950 constructor() {
13951 super(...arguments);
13952 this.processSlotOptions = memoize(processSlotOptions);
13953 this.state = {
13954 slatCoords: null,
13955 };
13956 this.handleRootEl = (el) => {
13957 if (el) {
13958 this.context.registerInteractiveComponent(this, {
13959 el,
13960 isHitComboAllowed: this.props.isHitComboAllowed,
13961 });
13962 }
13963 else {
13964 this.context.unregisterInteractiveComponent(this);
13965 }
13966 };
13967 this.handleScrollRequest = (request) => {
13968 let { onScrollTopRequest } = this.props;
13969 let { slatCoords } = this.state;
13970 if (onScrollTopRequest && slatCoords) {
13971 if (request.time) {
13972 let top = slatCoords.computeTimeTop(request.time);
13973 top = Math.ceil(top); // zoom can give weird floating-point values. rather scroll a little bit further
13974 if (top) {
13975 top += 1; // to overcome top border that slots beyond the first have. looks better
13976 }
13977 onScrollTopRequest(top);
13978 }
13979 return true;
13980 }
13981 return false;
13982 };
13983 this.handleColCoords = (colCoords) => {
13984 this.colCoords = colCoords;
13985 };
13986 this.handleSlatCoords = (slatCoords) => {
13987 this.setState({ slatCoords });
13988 if (this.props.onSlatCoords) {
13989 this.props.onSlatCoords(slatCoords);
13990 }
13991 };
13992 }
13993 render() {
13994 let { props, state } = this;
13995 return (y("div", { className: "fc-timegrid-body", ref: this.handleRootEl, style: {
13996 // these props are important to give this wrapper correct dimensions for interactions
13997 // TODO: if we set it here, can we avoid giving to inner tables?
13998 width: props.clientWidth,
13999 minWidth: props.tableMinWidth,
14000 } },
14001 y(TimeColsSlats, { axis: props.axis, dateProfile: props.dateProfile, slatMetas: props.slatMetas, clientWidth: props.clientWidth, minHeight: props.expandRows ? props.clientHeight : '', tableMinWidth: props.tableMinWidth, tableColGroupNode: props.axis ? props.tableColGroupNode : null /* axis depends on the colgroup's shrinking */, onCoords: this.handleSlatCoords }),
14002 y(TimeColsContent, { cells: props.cells, axis: props.axis, dateProfile: props.dateProfile, businessHourSegs: props.businessHourSegs, bgEventSegs: props.bgEventSegs, fgEventSegs: props.fgEventSegs, dateSelectionSegs: props.dateSelectionSegs, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, todayRange: props.todayRange, nowDate: props.nowDate, nowIndicatorSegs: props.nowIndicatorSegs, clientWidth: props.clientWidth, tableMinWidth: props.tableMinWidth, tableColGroupNode: props.tableColGroupNode, slatCoords: state.slatCoords, onColCoords: this.handleColCoords, forPrint: props.forPrint })));
14003 }
14004 componentDidMount() {
14005 this.scrollResponder = this.context.createScrollResponder(this.handleScrollRequest);
14006 }
14007 componentDidUpdate(prevProps) {
14008 this.scrollResponder.update(prevProps.dateProfile !== this.props.dateProfile);
14009 }
14010 componentWillUnmount() {
14011 this.scrollResponder.detach();
14012 }
14013 queryHit(positionLeft, positionTop) {
14014 let { dateEnv, options } = this.context;
14015 let { colCoords } = this;
14016 let { dateProfile } = this.props;
14017 let { slatCoords } = this.state;
14018 let { snapDuration, snapsPerSlot } = this.processSlotOptions(this.props.slotDuration, options.snapDuration);
14019 let colIndex = colCoords.leftToIndex(positionLeft);
14020 let slatIndex = slatCoords.positions.topToIndex(positionTop);
14021 if (colIndex != null && slatIndex != null) {
14022 let cell = this.props.cells[colIndex];
14023 let slatTop = slatCoords.positions.tops[slatIndex];
14024 let slatHeight = slatCoords.positions.getHeight(slatIndex);
14025 let partial = (positionTop - slatTop) / slatHeight; // floating point number between 0 and 1
14026 let localSnapIndex = Math.floor(partial * snapsPerSlot); // the snap # relative to start of slat
14027 let snapIndex = slatIndex * snapsPerSlot + localSnapIndex;
14028 let dayDate = this.props.cells[colIndex].date;
14029 let time = addDurations(dateProfile.slotMinTime, multiplyDuration(snapDuration, snapIndex));
14030 let start = dateEnv.add(dayDate, time);
14031 let end = dateEnv.add(start, snapDuration);
14032 return {
14033 dateProfile,
14034 dateSpan: Object.assign({ range: { start, end }, allDay: false }, cell.extraDateSpan),
14035 dayEl: colCoords.els[colIndex],
14036 rect: {
14037 left: colCoords.lefts[colIndex],
14038 right: colCoords.rights[colIndex],
14039 top: slatTop,
14040 bottom: slatTop + slatHeight,
14041 },
14042 layer: 0,
14043 };
14044 }
14045 return null;
14046 }
14047 }
14048 function processSlotOptions(slotDuration, snapDurationOverride) {
14049 let snapDuration = snapDurationOverride || slotDuration;
14050 let snapsPerSlot = wholeDivideDurations(slotDuration, snapDuration);
14051 if (snapsPerSlot === null) {
14052 snapDuration = slotDuration;
14053 snapsPerSlot = 1;
14054 // TODO: say warning?
14055 }
14056 return { snapDuration, snapsPerSlot };
14057 }
14058
14059 class DayTimeColsSlicer extends Slicer {
14060 sliceRange(range, dayRanges) {
14061 let segs = [];
14062 for (let col = 0; col < dayRanges.length; col += 1) {
14063 let segRange = intersectRanges(range, dayRanges[col]);
14064 if (segRange) {
14065 segs.push({
14066 start: segRange.start,
14067 end: segRange.end,
14068 isStart: segRange.start.valueOf() === range.start.valueOf(),
14069 isEnd: segRange.end.valueOf() === range.end.valueOf(),
14070 col,
14071 });
14072 }
14073 }
14074 return segs;
14075 }
14076 }
14077
14078 class DayTimeCols extends DateComponent {
14079 constructor() {
14080 super(...arguments);
14081 this.buildDayRanges = memoize(buildDayRanges);
14082 this.slicer = new DayTimeColsSlicer();
14083 this.timeColsRef = d();
14084 }
14085 render() {
14086 let { props, context } = this;
14087 let { dateProfile, dayTableModel } = props;
14088 let { nowIndicator, nextDayThreshold } = context.options;
14089 let dayRanges = this.buildDayRanges(dayTableModel, dateProfile, context.dateEnv);
14090 // give it the first row of cells
14091 // TODO: would move this further down hierarchy, but sliceNowDate needs it
14092 return (y(NowTimer, { unit: nowIndicator ? 'minute' : 'day' }, (nowDate, todayRange) => (y(TimeCols, Object.assign({ ref: this.timeColsRef }, this.slicer.sliceProps(props, dateProfile, null, context, dayRanges), { forPrint: props.forPrint, axis: props.axis, dateProfile: dateProfile, slatMetas: props.slatMetas, slotDuration: props.slotDuration, cells: dayTableModel.cells[0], tableColGroupNode: props.tableColGroupNode, tableMinWidth: props.tableMinWidth, clientWidth: props.clientWidth, clientHeight: props.clientHeight, expandRows: props.expandRows, nowDate: nowDate, nowIndicatorSegs: nowIndicator && this.slicer.sliceNowDate(nowDate, dateProfile, nextDayThreshold, context, dayRanges), todayRange: todayRange, onScrollTopRequest: props.onScrollTopRequest, onSlatCoords: props.onSlatCoords })))));
14093 }
14094 }
14095 function buildDayRanges(dayTableModel, dateProfile, dateEnv) {
14096 let ranges = [];
14097 for (let date of dayTableModel.headerDates) {
14098 ranges.push({
14099 start: dateEnv.add(date, dateProfile.slotMinTime),
14100 end: dateEnv.add(date, dateProfile.slotMaxTime),
14101 });
14102 }
14103 return ranges;
14104 }
14105
14106 // potential nice values for the slot-duration and interval-duration
14107 // from largest to smallest
14108 const STOCK_SUB_DURATIONS = [
14109 { hours: 1 },
14110 { minutes: 30 },
14111 { minutes: 15 },
14112 { seconds: 30 },
14113 { seconds: 15 },
14114 ];
14115 function buildSlatMetas(slotMinTime, slotMaxTime, explicitLabelInterval, slotDuration, dateEnv) {
14116 let dayStart = new Date(0);
14117 let slatTime = slotMinTime;
14118 let slatIterator = createDuration(0);
14119 let labelInterval = explicitLabelInterval || computeLabelInterval(slotDuration);
14120 let metas = [];
14121 while (asRoughMs(slatTime) < asRoughMs(slotMaxTime)) {
14122 let date = dateEnv.add(dayStart, slatTime);
14123 let isLabeled = wholeDivideDurations(slatIterator, labelInterval) !== null;
14124 metas.push({
14125 date,
14126 time: slatTime,
14127 key: date.toISOString(),
14128 isoTimeStr: formatIsoTimeString(date),
14129 isLabeled,
14130 });
14131 slatTime = addDurations(slatTime, slotDuration);
14132 slatIterator = addDurations(slatIterator, slotDuration);
14133 }
14134 return metas;
14135 }
14136 // Computes an automatic value for slotLabelInterval
14137 function computeLabelInterval(slotDuration) {
14138 let i;
14139 let labelInterval;
14140 let slotsPerLabel;
14141 // find the smallest stock label interval that results in more than one slots-per-label
14142 for (i = STOCK_SUB_DURATIONS.length - 1; i >= 0; i -= 1) {
14143 labelInterval = createDuration(STOCK_SUB_DURATIONS[i]);
14144 slotsPerLabel = wholeDivideDurations(labelInterval, slotDuration);
14145 if (slotsPerLabel !== null && slotsPerLabel > 1) {
14146 return labelInterval;
14147 }
14148 }
14149 return slotDuration; // fall back
14150 }
14151
14152 class DayTimeColsView extends TimeColsView {
14153 constructor() {
14154 super(...arguments);
14155 this.buildTimeColsModel = memoize(buildTimeColsModel);
14156 this.buildSlatMetas = memoize(buildSlatMetas);
14157 }
14158 render() {
14159 let { options, dateEnv, dateProfileGenerator } = this.context;
14160 let { props } = this;
14161 let { dateProfile } = props;
14162 let dayTableModel = this.buildTimeColsModel(dateProfile, dateProfileGenerator);
14163 let splitProps = this.allDaySplitter.splitProps(props);
14164 let slatMetas = this.buildSlatMetas(dateProfile.slotMinTime, dateProfile.slotMaxTime, options.slotLabelInterval, options.slotDuration, dateEnv);
14165 let { dayMinWidth } = options;
14166 let hasAttachedAxis = !dayMinWidth;
14167 let hasDetachedAxis = dayMinWidth;
14168 let headerContent = options.dayHeaders && (y(DayHeader, { dates: dayTableModel.headerDates, dateProfile: dateProfile, datesRepDistinctDays: true, renderIntro: hasAttachedAxis ? this.renderHeadAxis : null }));
14169 let allDayContent = (options.allDaySlot !== false) && ((contentArg) => (y(DayTable, Object.assign({}, splitProps.allDay, { dateProfile: dateProfile, dayTableModel: dayTableModel, nextDayThreshold: options.nextDayThreshold, tableMinWidth: contentArg.tableMinWidth, colGroupNode: contentArg.tableColGroupNode, renderRowIntro: hasAttachedAxis ? this.renderTableRowAxis : null, showWeekNumbers: false, expandRows: false, headerAlignElRef: this.headerElRef, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, forPrint: props.forPrint }, this.getAllDayMaxEventProps()))));
14170 let timeGridContent = (contentArg) => (y(DayTimeCols, Object.assign({}, splitProps.timed, { dayTableModel: dayTableModel, dateProfile: dateProfile, axis: hasAttachedAxis, slotDuration: options.slotDuration, slatMetas: slatMetas, forPrint: props.forPrint, tableColGroupNode: contentArg.tableColGroupNode, tableMinWidth: contentArg.tableMinWidth, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, onSlatCoords: this.handleSlatCoords, expandRows: contentArg.expandRows, onScrollTopRequest: this.handleScrollTopRequest })));
14171 return hasDetachedAxis
14172 ? this.renderHScrollLayout(headerContent, allDayContent, timeGridContent, dayTableModel.colCnt, dayMinWidth, slatMetas, this.state.slatCoords)
14173 : this.renderSimpleLayout(headerContent, allDayContent, timeGridContent);
14174 }
14175 }
14176 function buildTimeColsModel(dateProfile, dateProfileGenerator) {
14177 let daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator);
14178 return new DayTableModel(daySeries, false);
14179 }
14180
14181 var css_248z$2 = ".fc-v-event{background-color:var(--fc-event-bg-color);border:1px solid var(--fc-event-border-color);display:block}.fc-v-event .fc-event-main{color:var(--fc-event-text-color);height:100%}.fc-v-event .fc-event-main-frame{display:flex;flex-direction:column;height:100%}.fc-v-event .fc-event-time{flex-grow:0;flex-shrink:0;max-height:100%;overflow:hidden}.fc-v-event .fc-event-title-container{flex-grow:1;flex-shrink:1;min-height:0}.fc-v-event .fc-event-title{bottom:0;max-height:100%;overflow:hidden;top:0}.fc-v-event:not(.fc-event-start){border-top-left-radius:0;border-top-right-radius:0;border-top-width:0}.fc-v-event:not(.fc-event-end){border-bottom-left-radius:0;border-bottom-right-radius:0;border-bottom-width:0}.fc-v-event.fc-event-selected:before{left:-10px;right:-10px}.fc-v-event .fc-event-resizer-start{cursor:n-resize}.fc-v-event .fc-event-resizer-end{cursor:s-resize}.fc-v-event:not(.fc-event-selected) .fc-event-resizer{height:var(--fc-event-resizer-thickness);left:0;right:0}.fc-v-event:not(.fc-event-selected) .fc-event-resizer-start{top:calc(var(--fc-event-resizer-thickness)/-2)}.fc-v-event:not(.fc-event-selected) .fc-event-resizer-end{bottom:calc(var(--fc-event-resizer-thickness)/-2)}.fc-v-event.fc-event-selected .fc-event-resizer{left:50%;margin-left:calc(var(--fc-event-resizer-dot-total-width)/-2)}.fc-v-event.fc-event-selected .fc-event-resizer-start{top:calc(var(--fc-event-resizer-dot-total-width)/-2)}.fc-v-event.fc-event-selected .fc-event-resizer-end{bottom:calc(var(--fc-event-resizer-dot-total-width)/-2)}.fc .fc-timegrid .fc-daygrid-body{z-index:2}.fc .fc-timegrid-divider{padding:0 0 2px}.fc .fc-timegrid-body{min-height:100%;position:relative;z-index:1}.fc .fc-timegrid-axis-chunk{position:relative}.fc .fc-timegrid-axis-chunk>table,.fc .fc-timegrid-slots{position:relative;z-index:1}.fc .fc-timegrid-slot{border-bottom:0;height:1.5em}.fc .fc-timegrid-slot:empty:before{content:\"\\00a0\"}.fc .fc-timegrid-slot-minor{border-top-style:dotted}.fc .fc-timegrid-slot-label-cushion{display:inline-block;white-space:nowrap}.fc .fc-timegrid-slot-label{vertical-align:middle}.fc .fc-timegrid-axis-cushion,.fc .fc-timegrid-slot-label-cushion{padding:0 4px}.fc .fc-timegrid-axis-frame-liquid{height:100%}.fc .fc-timegrid-axis-frame{align-items:center;display:flex;justify-content:flex-end;overflow:hidden}.fc .fc-timegrid-axis-cushion{flex-shrink:0;max-width:60px}.fc-direction-ltr .fc-timegrid-slot-label-frame{text-align:right}.fc-direction-rtl .fc-timegrid-slot-label-frame{text-align:left}.fc-liquid-hack .fc-timegrid-axis-frame-liquid{bottom:0;height:auto;left:0;position:absolute;right:0;top:0}.fc .fc-timegrid-col.fc-day-today{background-color:var(--fc-today-bg-color)}.fc .fc-timegrid-col-frame{min-height:100%;position:relative}.fc-media-screen.fc-liquid-hack .fc-timegrid-col-frame{bottom:0;height:auto;left:0;position:absolute;right:0;top:0}.fc-media-screen .fc-timegrid-cols{bottom:0;left:0;position:absolute;right:0;top:0}.fc-media-screen .fc-timegrid-cols>table{height:100%}.fc-media-screen .fc-timegrid-col-bg,.fc-media-screen .fc-timegrid-col-events,.fc-media-screen .fc-timegrid-now-indicator-container{left:0;position:absolute;right:0;top:0}.fc .fc-timegrid-col-bg{z-index:2}.fc .fc-timegrid-col-bg .fc-non-business{z-index:1}.fc .fc-timegrid-col-bg .fc-bg-event{z-index:2}.fc .fc-timegrid-col-bg .fc-highlight{z-index:3}.fc .fc-timegrid-bg-harness{left:0;position:absolute;right:0}.fc .fc-timegrid-col-events{z-index:3}.fc .fc-timegrid-now-indicator-container{bottom:0;overflow:hidden}.fc-direction-ltr .fc-timegrid-col-events{margin:0 2.5% 0 2px}.fc-direction-rtl .fc-timegrid-col-events{margin:0 2px 0 2.5%}.fc-timegrid-event-harness{position:absolute}.fc-timegrid-event-harness>.fc-timegrid-event{bottom:0;left:0;position:absolute;right:0;top:0}.fc-timegrid-event-harness-inset .fc-timegrid-event,.fc-timegrid-event.fc-event-mirror,.fc-timegrid-more-link{box-shadow:0 0 0 1px var(--fc-page-bg-color)}.fc-timegrid-event,.fc-timegrid-more-link{border-radius:3px;font-size:var(--fc-small-font-size)}.fc-timegrid-event{margin-bottom:1px}.fc-timegrid-event .fc-event-main{padding:1px 1px 0}.fc-timegrid-event .fc-event-time{font-size:var(--fc-small-font-size);margin-bottom:1px;white-space:nowrap}.fc-timegrid-event-short .fc-event-main-frame{flex-direction:row;overflow:hidden}.fc-timegrid-event-short .fc-event-time:after{content:\"\\00a0-\\00a0\"}.fc-timegrid-event-short .fc-event-title{font-size:var(--fc-small-font-size)}.fc-timegrid-more-link{background:var(--fc-more-link-bg-color);color:var(--fc-more-link-text-color);cursor:pointer;margin-bottom:1px;position:absolute;z-index:9999}.fc-timegrid-more-link-inner{padding:3px 2px;top:0}.fc-direction-ltr .fc-timegrid-more-link{right:0}.fc-direction-rtl .fc-timegrid-more-link{left:0}.fc .fc-timegrid-now-indicator-arrow,.fc .fc-timegrid-now-indicator-line{pointer-events:none}.fc .fc-timegrid-now-indicator-line{border-color:var(--fc-now-indicator-color);border-style:solid;border-width:1px 0 0;left:0;position:absolute;right:0;z-index:4}.fc .fc-timegrid-now-indicator-arrow{border-color:var(--fc-now-indicator-color);border-style:solid;margin-top:-5px;position:absolute;z-index:4}.fc-direction-ltr .fc-timegrid-now-indicator-arrow{border-bottom-color:transparent;border-top-color:transparent;border-width:5px 0 5px 6px;left:0}.fc-direction-rtl .fc-timegrid-now-indicator-arrow{border-bottom-color:transparent;border-top-color:transparent;border-width:5px 6px 5px 0;right:0}";
14182 injectStyles(css_248z$2);
14183
14184 const OPTION_REFINERS$2 = {
14185 allDaySlot: Boolean,
14186 };
14187
14188 var index$2 = createPlugin({
14189 name: '@fullcalendar/timegrid',
14190 initialView: 'timeGridWeek',
14191 optionRefiners: OPTION_REFINERS$2,
14192 views: {
14193 timeGrid: {
14194 component: DayTimeColsView,
14195 usesMinMaxTime: true,
14196 allDaySlot: true,
14197 slotDuration: '00:30:00',
14198 slotEventOverlap: true, // a bad name. confused with overlap/constraint system
14199 },
14200 timeGridDay: {
14201 type: 'timeGrid',
14202 duration: { days: 1 },
14203 },
14204 timeGridWeek: {
14205 type: 'timeGrid',
14206 duration: { weeks: 1 },
14207 },
14208 },
14209 });
14210
14211 class ListViewHeaderRow extends BaseComponent {
14212 constructor() {
14213 super(...arguments);
14214 this.state = {
14215 textId: getUniqueDomId(),
14216 };
14217 }
14218 render() {
14219 let { theme, dateEnv, options, viewApi } = this.context;
14220 let { cellId, dayDate, todayRange } = this.props;
14221 let { textId } = this.state;
14222 let dayMeta = getDateMeta(dayDate, todayRange);
14223 // will ever be falsy?
14224 let text = options.listDayFormat ? dateEnv.format(dayDate, options.listDayFormat) : '';
14225 // will ever be falsy? also, BAD NAME "alt"
14226 let sideText = options.listDaySideFormat ? dateEnv.format(dayDate, options.listDaySideFormat) : '';
14227 let renderProps = Object.assign({ date: dateEnv.toDate(dayDate), view: viewApi, textId,
14228 text,
14229 sideText, navLinkAttrs: buildNavLinkAttrs(this.context, dayDate), sideNavLinkAttrs: buildNavLinkAttrs(this.context, dayDate, 'day', false) }, dayMeta);
14230 // TODO: make a reusable HOC for dayHeader (used in daygrid/timegrid too)
14231 return (y(ContentContainer, { elTag: "tr", elClasses: [
14232 'fc-list-day',
14233 ...getDayClassNames(dayMeta, theme),
14234 ], elAttrs: {
14235 'data-date': formatDayString(dayDate),
14236 }, renderProps: renderProps, generatorName: "dayHeaderContent", customGenerator: options.dayHeaderContent, defaultGenerator: renderInnerContent, classNameGenerator: options.dayHeaderClassNames, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, (InnerContent) => ( // TODO: force-hide top border based on :first-child
14237 y("th", { scope: "colgroup", colSpan: 3, id: cellId, "aria-labelledby": textId },
14238 y(InnerContent, { elTag: "div", elClasses: [
14239 'fc-list-day-cushion',
14240 theme.getClass('tableCellShaded'),
14241 ] })))));
14242 }
14243 }
14244 function renderInnerContent(props) {
14245 return (y(_, null,
14246 props.text && (y("a", Object.assign({ id: props.textId, className: "fc-list-day-text" }, props.navLinkAttrs), props.text)),
14247 props.sideText && ( /* not keyboard tabbable */y("a", Object.assign({ "aria-hidden": true, className: "fc-list-day-side-text" }, props.sideNavLinkAttrs), props.sideText))));
14248 }
14249
14250 const DEFAULT_TIME_FORMAT = createFormatter({
14251 hour: 'numeric',
14252 minute: '2-digit',
14253 meridiem: 'short',
14254 });
14255 class ListViewEventRow extends BaseComponent {
14256 render() {
14257 let { props, context } = this;
14258 let { options } = context;
14259 let { seg, timeHeaderId, eventHeaderId, dateHeaderId } = props;
14260 let timeFormat = options.eventTimeFormat || DEFAULT_TIME_FORMAT;
14261 return (y(EventContainer, Object.assign({}, props, { elTag: "tr", elClasses: [
14262 'fc-list-event',
14263 seg.eventRange.def.url && 'fc-event-forced-url',
14264 ], defaultGenerator: () => renderEventInnerContent(seg, context) /* weird */, seg: seg, timeText: "", disableDragging: true, disableResizing: true }), (InnerContent, eventContentArg) => (y(_, null,
14265 buildTimeContent(seg, timeFormat, context, timeHeaderId, dateHeaderId),
14266 y("td", { "aria-hidden": true, className: "fc-list-event-graphic" },
14267 y("span", { className: "fc-list-event-dot", style: {
14268 borderColor: eventContentArg.borderColor || eventContentArg.backgroundColor,
14269 } })),
14270 y(InnerContent, { elTag: "td", elClasses: ['fc-list-event-title'], elAttrs: { headers: `${eventHeaderId} ${dateHeaderId}` } })))));
14271 }
14272 }
14273 function renderEventInnerContent(seg, context) {
14274 let interactiveAttrs = getSegAnchorAttrs(seg, context);
14275 return (y("a", Object.assign({}, interactiveAttrs), seg.eventRange.def.title));
14276 }
14277 function buildTimeContent(seg, timeFormat, context, timeHeaderId, dateHeaderId) {
14278 let { options } = context;
14279 if (options.displayEventTime !== false) {
14280 let eventDef = seg.eventRange.def;
14281 let eventInstance = seg.eventRange.instance;
14282 let doAllDay = false;
14283 let timeText;
14284 if (eventDef.allDay) {
14285 doAllDay = true;
14286 }
14287 else if (isMultiDayRange(seg.eventRange.range)) { // TODO: use (!isStart || !isEnd) instead?
14288 if (seg.isStart) {
14289 timeText = buildSegTimeText(seg, timeFormat, context, null, null, eventInstance.range.start, seg.end);
14290 }
14291 else if (seg.isEnd) {
14292 timeText = buildSegTimeText(seg, timeFormat, context, null, null, seg.start, eventInstance.range.end);
14293 }
14294 else {
14295 doAllDay = true;
14296 }
14297 }
14298 else {
14299 timeText = buildSegTimeText(seg, timeFormat, context);
14300 }
14301 if (doAllDay) {
14302 let renderProps = {
14303 text: context.options.allDayText,
14304 view: context.viewApi,
14305 };
14306 return (y(ContentContainer, { elTag: "td", elClasses: ['fc-list-event-time'], elAttrs: {
14307 headers: `${timeHeaderId} ${dateHeaderId}`,
14308 }, renderProps: renderProps, generatorName: "allDayContent", customGenerator: options.allDayContent, defaultGenerator: renderAllDayInner, classNameGenerator: options.allDayClassNames, didMount: options.allDayDidMount, willUnmount: options.allDayWillUnmount }));
14309 }
14310 return (y("td", { className: "fc-list-event-time" }, timeText));
14311 }
14312 return null;
14313 }
14314 function renderAllDayInner(renderProps) {
14315 return renderProps.text;
14316 }
14317
14318 /*
14319 Responsible for the scroller, and forwarding event-related actions into the "grid".
14320 */
14321 class ListView extends DateComponent {
14322 constructor() {
14323 super(...arguments);
14324 this.computeDateVars = memoize(computeDateVars);
14325 this.eventStoreToSegs = memoize(this._eventStoreToSegs);
14326 this.state = {
14327 timeHeaderId: getUniqueDomId(),
14328 eventHeaderId: getUniqueDomId(),
14329 dateHeaderIdRoot: getUniqueDomId(),
14330 };
14331 this.setRootEl = (rootEl) => {
14332 if (rootEl) {
14333 this.context.registerInteractiveComponent(this, {
14334 el: rootEl,
14335 });
14336 }
14337 else {
14338 this.context.unregisterInteractiveComponent(this);
14339 }
14340 };
14341 }
14342 render() {
14343 let { props, context } = this;
14344 let { dayDates, dayRanges } = this.computeDateVars(props.dateProfile);
14345 let eventSegs = this.eventStoreToSegs(props.eventStore, props.eventUiBases, dayRanges);
14346 return (y(ViewContainer, { elRef: this.setRootEl, elClasses: [
14347 'fc-list',
14348 context.theme.getClass('table'),
14349 context.options.stickyHeaderDates !== false ?
14350 'fc-list-sticky' :
14351 '',
14352 ], viewSpec: context.viewSpec },
14353 y(Scroller, { liquid: !props.isHeightAuto, overflowX: props.isHeightAuto ? 'visible' : 'hidden', overflowY: props.isHeightAuto ? 'visible' : 'auto' }, eventSegs.length > 0 ?
14354 this.renderSegList(eventSegs, dayDates) :
14355 this.renderEmptyMessage())));
14356 }
14357 renderEmptyMessage() {
14358 let { options, viewApi } = this.context;
14359 let renderProps = {
14360 text: options.noEventsText,
14361 view: viewApi,
14362 };
14363 return (y(ContentContainer, { elTag: "div", elClasses: ['fc-list-empty'], renderProps: renderProps, generatorName: "noEventsContent", customGenerator: options.noEventsContent, defaultGenerator: renderNoEventsInner, classNameGenerator: options.noEventsClassNames, didMount: options.noEventsDidMount, willUnmount: options.noEventsWillUnmount }, (InnerContent) => (y(InnerContent, { elTag: "div", elClasses: ['fc-list-empty-cushion'] }))));
14364 }
14365 renderSegList(allSegs, dayDates) {
14366 let { theme, options } = this.context;
14367 let { timeHeaderId, eventHeaderId, dateHeaderIdRoot } = this.state;
14368 let segsByDay = groupSegsByDay(allSegs); // sparse array
14369 return (y(NowTimer, { unit: "day" }, (nowDate, todayRange) => {
14370 let innerNodes = [];
14371 for (let dayIndex = 0; dayIndex < segsByDay.length; dayIndex += 1) {
14372 let daySegs = segsByDay[dayIndex];
14373 if (daySegs) { // sparse array, so might be undefined
14374 let dayStr = formatDayString(dayDates[dayIndex]);
14375 let dateHeaderId = dateHeaderIdRoot + '-' + dayStr;
14376 // append a day header
14377 innerNodes.push(y(ListViewHeaderRow, { key: dayStr, cellId: dateHeaderId, dayDate: dayDates[dayIndex], todayRange: todayRange }));
14378 daySegs = sortEventSegs(daySegs, options.eventOrder);
14379 for (let seg of daySegs) {
14380 innerNodes.push(y(ListViewEventRow, Object.assign({ key: dayStr + ':' + seg.eventRange.instance.instanceId /* are multiple segs for an instanceId */, seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: false, timeHeaderId: timeHeaderId, eventHeaderId: eventHeaderId, dateHeaderId: dateHeaderId }, getSegMeta(seg, todayRange, nowDate))));
14381 }
14382 }
14383 }
14384 return (y("table", { className: 'fc-list-table ' + theme.getClass('table') },
14385 y("thead", null,
14386 y("tr", null,
14387 y("th", { scope: "col", id: timeHeaderId }, options.timeHint),
14388 y("th", { scope: "col", "aria-hidden": true }),
14389 y("th", { scope: "col", id: eventHeaderId }, options.eventHint))),
14390 y("tbody", null, innerNodes)));
14391 }));
14392 }
14393 _eventStoreToSegs(eventStore, eventUiBases, dayRanges) {
14394 return this.eventRangesToSegs(sliceEventStore(eventStore, eventUiBases, this.props.dateProfile.activeRange, this.context.options.nextDayThreshold).fg, dayRanges);
14395 }
14396 eventRangesToSegs(eventRanges, dayRanges) {
14397 let segs = [];
14398 for (let eventRange of eventRanges) {
14399 segs.push(...this.eventRangeToSegs(eventRange, dayRanges));
14400 }
14401 return segs;
14402 }
14403 eventRangeToSegs(eventRange, dayRanges) {
14404 let { dateEnv } = this.context;
14405 let { nextDayThreshold } = this.context.options;
14406 let range = eventRange.range;
14407 let allDay = eventRange.def.allDay;
14408 let dayIndex;
14409 let segRange;
14410 let seg;
14411 let segs = [];
14412 for (dayIndex = 0; dayIndex < dayRanges.length; dayIndex += 1) {
14413 segRange = intersectRanges(range, dayRanges[dayIndex]);
14414 if (segRange) {
14415 seg = {
14416 component: this,
14417 eventRange,
14418 start: segRange.start,
14419 end: segRange.end,
14420 isStart: eventRange.isStart && segRange.start.valueOf() === range.start.valueOf(),
14421 isEnd: eventRange.isEnd && segRange.end.valueOf() === range.end.valueOf(),
14422 dayIndex,
14423 };
14424 segs.push(seg);
14425 // detect when range won't go fully into the next day,
14426 // and mutate the latest seg to the be the end.
14427 if (!seg.isEnd && !allDay &&
14428 dayIndex + 1 < dayRanges.length &&
14429 range.end <
14430 dateEnv.add(dayRanges[dayIndex + 1].start, nextDayThreshold)) {
14431 seg.end = range.end;
14432 seg.isEnd = true;
14433 break;
14434 }
14435 }
14436 }
14437 return segs;
14438 }
14439 }
14440 function renderNoEventsInner(renderProps) {
14441 return renderProps.text;
14442 }
14443 function computeDateVars(dateProfile) {
14444 let dayStart = startOfDay(dateProfile.renderRange.start);
14445 let viewEnd = dateProfile.renderRange.end;
14446 let dayDates = [];
14447 let dayRanges = [];
14448 while (dayStart < viewEnd) {
14449 dayDates.push(dayStart);
14450 dayRanges.push({
14451 start: dayStart,
14452 end: addDays(dayStart, 1),
14453 });
14454 dayStart = addDays(dayStart, 1);
14455 }
14456 return { dayDates, dayRanges };
14457 }
14458 // Returns a sparse array of arrays, segs grouped by their dayIndex
14459 function groupSegsByDay(segs) {
14460 let segsByDay = []; // sparse array
14461 let i;
14462 let seg;
14463 for (i = 0; i < segs.length; i += 1) {
14464 seg = segs[i];
14465 (segsByDay[seg.dayIndex] || (segsByDay[seg.dayIndex] = []))
14466 .push(seg);
14467 }
14468 return segsByDay;
14469 }
14470
14471 var css_248z$1 = ":root{--fc-list-event-dot-width:10px;--fc-list-event-hover-bg-color:#f5f5f5}.fc-theme-standard .fc-list{border:1px solid var(--fc-border-color)}.fc .fc-list-empty{align-items:center;background-color:var(--fc-neutral-bg-color);display:flex;height:100%;justify-content:center}.fc .fc-list-empty-cushion{margin:5em 0}.fc .fc-list-table{border-style:hidden;width:100%}.fc .fc-list-table tr>*{border-left:0;border-right:0}.fc .fc-list-sticky .fc-list-day>*{background:var(--fc-page-bg-color);position:sticky;top:0}.fc .fc-list-table thead{left:-10000px;position:absolute}.fc .fc-list-table tbody>tr:first-child th{border-top:0}.fc .fc-list-table th{padding:0}.fc .fc-list-day-cushion,.fc .fc-list-table td{padding:8px 14px}.fc .fc-list-day-cushion:after{clear:both;content:\"\";display:table}.fc-theme-standard .fc-list-day-cushion{background-color:var(--fc-neutral-bg-color)}.fc-direction-ltr .fc-list-day-text,.fc-direction-rtl .fc-list-day-side-text{float:left}.fc-direction-ltr .fc-list-day-side-text,.fc-direction-rtl .fc-list-day-text{float:right}.fc-direction-ltr .fc-list-table .fc-list-event-graphic{padding-right:0}.fc-direction-rtl .fc-list-table .fc-list-event-graphic{padding-left:0}.fc .fc-list-event.fc-event-forced-url{cursor:pointer}.fc .fc-list-event:hover td{background-color:var(--fc-list-event-hover-bg-color)}.fc .fc-list-event-graphic,.fc .fc-list-event-time{white-space:nowrap;width:1px}.fc .fc-list-event-dot{border:calc(var(--fc-list-event-dot-width)/2) solid var(--fc-event-border-color);border-radius:calc(var(--fc-list-event-dot-width)/2);box-sizing:content-box;display:inline-block;height:0;width:0}.fc .fc-list-event-title a{color:inherit;text-decoration:none}.fc .fc-list-event.fc-event-forced-url:hover a{text-decoration:underline}";
14472 injectStyles(css_248z$1);
14473
14474 const OPTION_REFINERS$1 = {
14475 listDayFormat: createFalsableFormatter,
14476 listDaySideFormat: createFalsableFormatter,
14477 noEventsClassNames: identity,
14478 noEventsContent: identity,
14479 noEventsDidMount: identity,
14480 noEventsWillUnmount: identity,
14481 // noEventsText is defined in base options
14482 };
14483 function createFalsableFormatter(input) {
14484 return input === false ? null : createFormatter(input);
14485 }
14486
14487 var index$1 = createPlugin({
14488 name: '@fullcalendar/list',
14489 optionRefiners: OPTION_REFINERS$1,
14490 views: {
14491 list: {
14492 component: ListView,
14493 buttonTextKey: 'list',
14494 listDayFormat: { month: 'long', day: 'numeric', year: 'numeric' }, // like "January 1, 2016"
14495 },
14496 listDay: {
14497 type: 'list',
14498 duration: { days: 1 },
14499 listDayFormat: { weekday: 'long' }, // day-of-week is all we need. full date is probably in headerToolbar
14500 },
14501 listWeek: {
14502 type: 'list',
14503 duration: { weeks: 1 },
14504 listDayFormat: { weekday: 'long' },
14505 listDaySideFormat: { month: 'long', day: 'numeric', year: 'numeric' },
14506 },
14507 listMonth: {
14508 type: 'list',
14509 duration: { month: 1 },
14510 listDaySideFormat: { weekday: 'long' }, // day-of-week is nice-to-have
14511 },
14512 listYear: {
14513 type: 'list',
14514 duration: { year: 1 },
14515 listDaySideFormat: { weekday: 'long' }, // day-of-week is nice-to-have
14516 },
14517 },
14518 });
14519
14520 class SingleMonth extends DateComponent {
14521 constructor() {
14522 super(...arguments);
14523 this.buildDayTableModel = memoize(buildDayTableModel);
14524 this.slicer = new DayTableSlicer();
14525 this.state = {
14526 labelId: getUniqueDomId(),
14527 };
14528 }
14529 render() {
14530 const { props, state, context } = this;
14531 const { dateProfile, forPrint } = props;
14532 const { options } = context;
14533 const dayTableModel = this.buildDayTableModel(dateProfile, context.dateProfileGenerator);
14534 const slicedProps = this.slicer.sliceProps(props, dateProfile, options.nextDayThreshold, context, dayTableModel);
14535 // ensure single-month has aspect ratio
14536 const tableHeight = props.tableWidth != null ? props.tableWidth / options.aspectRatio : null;
14537 const rowCnt = dayTableModel.cells.length;
14538 const rowHeight = tableHeight != null ? tableHeight / rowCnt : null;
14539 return (y("div", { ref: props.elRef, "data-date": props.isoDateStr, className: "fc-multimonth-month", style: { width: props.width }, role: "grid", "aria-labelledby": state.labelId },
14540 y("div", { className: "fc-multimonth-header", style: { marginBottom: rowHeight }, role: "presentation" },
14541 y("div", { className: "fc-multimonth-title", id: state.labelId }, context.dateEnv.format(props.dateProfile.currentRange.start, props.titleFormat)),
14542 y("table", { className: [
14543 'fc-multimonth-header-table',
14544 context.theme.getClass('table'),
14545 ].join(' '), role: "presentation" },
14546 y("thead", { role: "rowgroup" },
14547 y(DayHeader, { dateProfile: props.dateProfile, dates: dayTableModel.headerDates, datesRepDistinctDays: false })))),
14548 y("div", { className: [
14549 'fc-multimonth-daygrid',
14550 'fc-daygrid',
14551 'fc-daygrid-body',
14552 !forPrint && 'fc-daygrid-body-balanced',
14553 forPrint && 'fc-daygrid-body-unbalanced',
14554 forPrint && 'fc-daygrid-body-natural',
14555 ].join(' '), style: { marginTop: -rowHeight } },
14556 y("table", { className: [
14557 'fc-multimonth-daygrid-table',
14558 context.theme.getClass('table'),
14559 ].join(' '), style: { height: forPrint ? '' : tableHeight }, role: "presentation" },
14560 y("tbody", { role: "rowgroup" },
14561 y(TableRows, Object.assign({}, slicedProps, { dateProfile: dateProfile, cells: dayTableModel.cells, eventSelection: props.eventSelection, dayMaxEvents: !forPrint, dayMaxEventRows: !forPrint, showWeekNumbers: options.weekNumbers, clientWidth: props.clientWidth, clientHeight: props.clientHeight, forPrint: forPrint })))))));
14562 }
14563 }
14564
14565 class MultiMonthView extends DateComponent {
14566 constructor() {
14567 super(...arguments);
14568 this.splitDateProfileByMonth = memoize(splitDateProfileByMonth);
14569 this.buildMonthFormat = memoize(buildMonthFormat);
14570 this.scrollElRef = d();
14571 this.firstMonthElRef = d();
14572 this.needsScrollReset = false;
14573 this.handleSizing = (isForced) => {
14574 if (isForced) {
14575 this.updateSize();
14576 }
14577 };
14578 }
14579 render() {
14580 const { context, props, state } = this;
14581 const { options } = context;
14582 const { clientWidth, clientHeight } = state;
14583 const monthHPadding = state.monthHPadding || 0;
14584 const colCount = Math.min(clientWidth != null ?
14585 Math.floor(clientWidth / (options.multiMonthMinWidth + monthHPadding)) :
14586 1, options.multiMonthMaxColumns) || 1;
14587 const monthWidthPct = (100 / colCount) + '%';
14588 const monthTableWidth = clientWidth == null ? null :
14589 (clientWidth / colCount) - monthHPadding;
14590 const isLegitSingleCol = clientWidth != null && colCount === 1;
14591 const monthDateProfiles = this.splitDateProfileByMonth(context.dateProfileGenerator, props.dateProfile, context.dateEnv, isLegitSingleCol ? false : options.fixedWeekCount, options.showNonCurrentDates);
14592 const monthTitleFormat = this.buildMonthFormat(options.multiMonthTitleFormat, monthDateProfiles);
14593 const rootClassNames = [
14594 'fc-multimonth',
14595 isLegitSingleCol ?
14596 'fc-multimonth-singlecol' :
14597 'fc-multimonth-multicol',
14598 (monthTableWidth != null && monthTableWidth < 400) ?
14599 'fc-multimonth-compact' :
14600 '',
14601 props.isHeightAuto ?
14602 '' :
14603 'fc-scroller', // for AutoScroller
14604 ];
14605 return (y(ViewContainer, { elRef: this.scrollElRef, elClasses: rootClassNames, viewSpec: context.viewSpec }, monthDateProfiles.map((monthDateProfile, i) => {
14606 const monthStr = formatIsoMonthStr(monthDateProfile.currentRange.start);
14607 return (y(SingleMonth, Object.assign({}, props, { key: monthStr, isoDateStr: monthStr, elRef: i === 0 ? this.firstMonthElRef : undefined, titleFormat: monthTitleFormat, dateProfile: monthDateProfile, width: monthWidthPct, tableWidth: monthTableWidth, clientWidth: clientWidth, clientHeight: clientHeight })));
14608 })));
14609 }
14610 componentDidMount() {
14611 this.updateSize();
14612 this.context.addResizeHandler(this.handleSizing);
14613 this.requestScrollReset();
14614 }
14615 componentDidUpdate(prevProps) {
14616 if (!isPropsEqual(prevProps, this.props)) { // an external change?
14617 this.handleSizing(false);
14618 }
14619 if (prevProps.dateProfile !== this.props.dateProfile) {
14620 this.requestScrollReset();
14621 }
14622 else {
14623 this.flushScrollReset();
14624 }
14625 }
14626 componentWillUnmount() {
14627 this.context.removeResizeHandler(this.handleSizing);
14628 }
14629 updateSize() {
14630 const scrollEl = this.scrollElRef.current;
14631 const firstMonthEl = this.firstMonthElRef.current;
14632 if (scrollEl) {
14633 this.setState({
14634 clientWidth: scrollEl.clientWidth,
14635 clientHeight: scrollEl.clientHeight,
14636 });
14637 }
14638 if (firstMonthEl && scrollEl) {
14639 if (this.state.monthHPadding == null) { // always remember initial non-zero value
14640 this.setState({
14641 monthHPadding: scrollEl.clientWidth - // go within padding
14642 firstMonthEl.firstChild.offsetWidth,
14643 });
14644 }
14645 }
14646 }
14647 requestScrollReset() {
14648 this.needsScrollReset = true;
14649 this.flushScrollReset();
14650 }
14651 flushScrollReset() {
14652 if (this.needsScrollReset &&
14653 this.state.monthHPadding != null // indicates sizing already happened
14654 ) {
14655 const { currentDate } = this.props.dateProfile;
14656 const scrollEl = this.scrollElRef.current;
14657 const monthEl = scrollEl.querySelector(`[data-date="${formatIsoMonthStr(currentDate)}"]`);
14658 scrollEl.scrollTop = monthEl.getBoundingClientRect().top -
14659 this.firstMonthElRef.current.getBoundingClientRect().top;
14660 this.needsScrollReset = false;
14661 }
14662 }
14663 // workaround for when queued setState render (w/ clientWidth) gets cancelled because
14664 // subsequent update and shouldComponentUpdate says not to render :(
14665 shouldComponentUpdate() {
14666 return true;
14667 }
14668 }
14669 // date profile
14670 // -------------------------------------------------------------------------------------------------
14671 const oneMonthDuration = createDuration(1, 'month');
14672 function splitDateProfileByMonth(dateProfileGenerator, dateProfile, dateEnv, fixedWeekCount, showNonCurrentDates) {
14673 const { start, end } = dateProfile.currentRange;
14674 let monthStart = start;
14675 const monthDateProfiles = [];
14676 while (monthStart.valueOf() < end.valueOf()) {
14677 const monthEnd = dateEnv.add(monthStart, oneMonthDuration);
14678 const currentRange = {
14679 // yuck
14680 start: dateProfileGenerator.skipHiddenDays(monthStart),
14681 end: dateProfileGenerator.skipHiddenDays(monthEnd, -1, true),
14682 };
14683 let renderRange = buildDayTableRenderRange({
14684 currentRange,
14685 snapToWeek: true,
14686 fixedWeekCount,
14687 dateEnv,
14688 });
14689 renderRange = {
14690 // yuck
14691 start: dateProfileGenerator.skipHiddenDays(renderRange.start),
14692 end: dateProfileGenerator.skipHiddenDays(renderRange.end, -1, true),
14693 };
14694 const activeRange = dateProfile.activeRange ?
14695 intersectRanges(dateProfile.activeRange, showNonCurrentDates ? renderRange : currentRange) :
14696 null;
14697 monthDateProfiles.push({
14698 currentDate: dateProfile.currentDate,
14699 isValid: dateProfile.isValid,
14700 validRange: dateProfile.validRange,
14701 renderRange,
14702 activeRange,
14703 currentRange,
14704 currentRangeUnit: 'month',
14705 isRangeAllDay: true,
14706 dateIncrement: dateProfile.dateIncrement,
14707 slotMinTime: dateProfile.slotMaxTime,
14708 slotMaxTime: dateProfile.slotMinTime,
14709 });
14710 monthStart = monthEnd;
14711 }
14712 return monthDateProfiles;
14713 }
14714 // date formatting
14715 // -------------------------------------------------------------------------------------------------
14716 const YEAR_MONTH_FORMATTER = createFormatter({ year: 'numeric', month: 'long' });
14717 const YEAR_FORMATTER = createFormatter({ month: 'long' });
14718 function buildMonthFormat(formatOverride, monthDateProfiles) {
14719 return formatOverride ||
14720 ((monthDateProfiles[0].currentRange.start.getUTCFullYear() !==
14721 monthDateProfiles[monthDateProfiles.length - 1].currentRange.start.getUTCFullYear())
14722 ? YEAR_MONTH_FORMATTER
14723 : YEAR_FORMATTER);
14724 }
14725
14726 const OPTION_REFINERS = {
14727 multiMonthTitleFormat: createFormatter,
14728 multiMonthMaxColumns: Number,
14729 multiMonthMinWidth: Number,
14730 };
14731
14732 var css_248z = ".fc .fc-multimonth{border:1px solid var(--fc-border-color);display:flex;flex-wrap:wrap;overflow-x:hidden;overflow-y:auto}.fc .fc-multimonth-title{font-size:1.2em;font-weight:700;padding:1em 0;text-align:center}.fc .fc-multimonth-daygrid{background:var(--fc-page-bg-color)}.fc .fc-multimonth-daygrid-table,.fc .fc-multimonth-header-table{table-layout:fixed;width:100%}.fc .fc-multimonth-daygrid-table{border-top-style:hidden!important}.fc .fc-multimonth-singlecol .fc-multimonth{position:relative}.fc .fc-multimonth-singlecol .fc-multimonth-header{background:var(--fc-page-bg-color);position:relative;top:0;z-index:2}.fc .fc-multimonth-singlecol .fc-multimonth-daygrid{position:relative;z-index:1}.fc .fc-multimonth-singlecol .fc-multimonth-daygrid-table,.fc .fc-multimonth-singlecol .fc-multimonth-header-table{border-left-style:hidden;border-right-style:hidden}.fc .fc-multimonth-singlecol .fc-multimonth-month:last-child .fc-multimonth-daygrid-table{border-bottom-style:hidden}.fc .fc-multimonth-multicol{line-height:1}.fc .fc-multimonth-multicol .fc-multimonth-month{padding:0 1.2em 1.2em}.fc .fc-multimonth-multicol .fc-daygrid-more-link{border:1px solid var(--fc-event-border-color);display:block;float:none;padding:1px}.fc .fc-multimonth-compact{line-height:1}.fc .fc-multimonth-compact .fc-multimonth-daygrid-table,.fc .fc-multimonth-compact .fc-multimonth-header-table{font-size:.9em}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-header{position:sticky}.fc-media-print .fc-multimonth{overflow:visible}";
14733 injectStyles(css_248z);
14734
14735 var index = createPlugin({
14736 name: '@fullcalendar/multimonth',
14737 initialView: 'multiMonthYear',
14738 optionRefiners: OPTION_REFINERS,
14739 views: {
14740 multiMonth: {
14741 component: MultiMonthView,
14742 dateProfileGeneratorClass: TableDateProfileGenerator,
14743 multiMonthMinWidth: 350,
14744 multiMonthMaxColumns: 3,
14745 },
14746 multiMonthYear: {
14747 type: 'multiMonth',
14748 duration: { years: 1 },
14749 fixedWeekCount: true,
14750 showNonCurrentDates: false,
14751 },
14752 },
14753 });
14754
14755 globalPlugins.push(index$4, index$3, index$2, index$1, index);
14756
14757 exports.Calendar = Calendar;
14758 exports.Draggable = ExternalDraggable;
14759 exports.Internal = internal;
14760 exports.JsonRequestError = JsonRequestError;
14761 exports.Preact = preact;
14762 exports.ThirdPartyDraggable = ThirdPartyDraggable;
14763 exports.createPlugin = createPlugin;
14764 exports.formatDate = formatDate;
14765 exports.formatRange = formatRange;
14766 exports.globalLocales = globalLocales;
14767 exports.globalPlugins = globalPlugins;
14768 exports.sliceEvents = sliceEvents;
14769 exports.version = version;
14770
14771 Object.defineProperty(exports, '__esModule', { value: true });
14772
14773 return exports;
14774
14775})({});